import { isNum, toStyleStr } from "./tools.js"; import { eventBus, EVEN_VAR } from "./EventBus.js"; // import { debounce } from "../lib/tools.js"; // 首页组件的名字 const winName = { launch: "launch", Notice: "Notice", Statistics: "Statistics", TodoList: "TodoList", UrgingList: "UrgingList", UserInfo: "UserInfo", }; // 尺寸窗口名字 const size2Win = (name) => `${name}-${document.body.clientWidth}`; // 加载尺寸 const loadWinSize = function (name) { const val = localStorage.getItem(size2Win(name)); if (val) { try { return JSON.parse(val) || {}; } catch (err) { return {}; } } else { return {}; } }; const saveWinSize = function (name, obj) { localStorage.setItem(size2Win(name), JSON.stringify(obj)); }; // 全局头部 export const GlobalHeader = { name: 'GlobalHeader', props: { menuData: { type: Array, required: true }, iconItems: { type: Array, required: true } }, setup(props) { //原值为'/newUI/skin/easy/images/logo/full-logo.png' Ben(20251205) const fullLogo = Vue.ref('/skin/easy/images/logo/full-logo.png'); // 需要指定完整路径 const convertMenuData = (menuData) => { return menuData.map(item => ({ id: item.id, //菜单id pid: item.pid, //父菜单id label: item.desc, //菜单名称 component: item.url, //菜单链接 js: item.js, //菜单要执行的js class: item.icon, // 如果icon为空则使用默认class itemType: item.type, //菜单 1:菜单项 2:菜单组 })); }; const menuItemsNew = Vue.ref(convertMenuData(props.menuData)); Vue.watchEffect(() => { if (props.menuData && props.menuData.length) { menuItemsNew.value = convertMenuData(props.menuData); console.log('Header menu items updated:', menuItemsNew.value); } }); const menuItems = Vue.ref([]); const breadCrumbs = Vue.ref([ { label: '首页', component: '/index.html' }, ]); const iconItems = Vue.ref(props.iconItems); const onClickMenuItemsNew = (item) => { console.log(item.component, item.js) if (item.component && item.js) { eval(item.js) } else if(item.component){ eventBus.publish(EVEN_VAR.currentPage, item.component); } else { eval(item.js) } }; const onClickMenuItem = (item) => { if (item.type == 'page') { eventBus.publish(EVEN_VAR.currentPage, item.component); breadCrumbs.value[0] = item } else if (item.type == 'dialog') { SS.openDialog({ headerTitle: item.label, src: '.'+item.component, height: item.height, width:item.width }); } }; const breadCrumbClick = (item) => { eventBus.publish(EVEN_VAR.currentPage, item.component); breadCrumbs.value[0] = item } const gotoSearch = () => { eventBus.publish(EVEN_VAR.showGlobalSearchDialog); }; const SsIcon = Vue.resolveComponent('ss-icon'); const SsHeaderIcon = Vue.resolveComponent('ss-header-icon'); const SsGolbalMenuIcon = Vue.resolveComponent('ss-golbal-menu-icon'); // 引入搜索框 const SsSearch = Vue.resolveComponent('ss-search'); // 处理面包屑导航 const renderBreadcrumbs = () => { const breadcrumbElements = [Vue.h(SsIcon, { class: "home-icon", name: "home", type: "common", size: "22px" })]; // 遍历面包屑数组,添加面包屑和分隔符 breadCrumbs.value.forEach((crumb, index) => { // 添加面包屑链接 breadcrumbElements.push( Vue.h('span', { onClick: () => breadCrumbClick(crumb) }, crumb.label) ); // 除了最后一个元素,每个面包屑后添加分隔符图标 if (index < breadCrumbs.value.length - 1) { breadcrumbElements.push( Vue.h(SsIcon, { class: "split-icon", size: "12px", name: "double-arrow-right", type: "common" }) ); } }); return breadcrumbElements; }; return () => Vue.h('div', { class: 'block-self flex-between-center global-header-container' }, [ Vue.h('div', { class: 'icon-area flex-start-center' }, [ Vue.h('div', { class: 'logo', onClick: () => console.log('Home clicked') }, [ Vue.h('div', { class: 'img' }, [ Vue.h('img', { src: fullLogo.value }) ]), Vue.h('div', { class: 'menu', onClick: Vue.withModifiers(() => { }, ['stop']) }, // menuItems.value.map(item => // Vue.h('div', { onClick: () => onClickMenuItem(item) },[ // Vue.h(SsGolbalMenuIcon, { class: item.class || '' }), // item.label // ]) // ) menuItemsNew.value.map(item => Vue.h('div', { onClick: () => onClickMenuItemsNew(item) },[ Vue.h(SsGolbalMenuIcon, { class: item.class || '' }), item.label ]) ) ) ]), // Vue.h('div', { class: 'bread-crumb', style: { height: "100% !important" } }, [ // Vue.h('div', { class: 'content' }, renderBreadcrumbs()) // ]) ]), Vue.h('div', { class: 'menu-area' }, Vue.h('div', { class: 'search-area' }, [ Vue.h(SsSearch, { theme: 'dark', placeholder: '跨对象搜索', onClick: gotoSearch }) ]), iconItems.value.map(icon => Vue.h('div', { class: 'icon-item', onClick: icon.action, style: { display: icon.condition ? (icon.condition() ? 'block' : 'none') : 'block' } }, [ Vue.h(SsHeaderIcon, { class: icon.class }) ]) ) ) ]); } }; // 全局菜单 export const GlobalMenu = { name: 'GlobalMenu', props: { menuItems: { type: Array, required: true }, initialSize: { type: String, default: 'min' }, // onMenuClick: { // type: Function, // required: true, // default: () => {} // } }, setup(props) { const menuItemsNew = Vue.ref([]); const activeItem = Vue.ref(''); // 默认选中第一个菜单项 // 将后台返回的菜单数据转换为树结构 const convertMenuDataToTree = (menuData) => { // 先转换格式 const formattedData = menuData.map(item => ({ id: item.id, pid: item.pid, name: item.desc, component: item.url, js: item.js, // v3.0 优先使用 item.icon,没有才用 menu-base-icon + 默认图标 by xu 20251215 class: item.icon || (item.type == 1 ? 'menu-base-icon icon-folder-close' : 'menu-base-icon icon-shouye'), itemType: item.type, children: [] })); // 创建一个映射表,方便查找 const map = {}; formattedData.forEach(item => { map[item.id] = item; }); // 构建树结构 const treeData = []; formattedData.forEach(item => { if (item.pid && map[item.pid]) { // 如果有父节点,就放到父节点的children中 if (!map[item.pid].children) { map[item.pid].children = []; } map[item.pid].children.push(item); } else { // 没有父节点就是顶层节点 treeData.push(item); } }); return treeData; }; // 添加 watchEffect 来监听 props.menuItems 的变化 Vue.watchEffect(() => { if (props.menuItems && props.menuItems.length) { menuItemsNew.value = convertMenuDataToTree(props.menuItems); // 初始化 activeItem const firstItem = menuItemsNew.value[0]; if(firstItem?.component){ // 首页有URL,直接设置为首页 activeItem.value = firstItem.name; eventBus.publish(EVEN_VAR.currentPage, firstItem.component); } else if(firstItem?.children && firstItem.children.length > 0){ // 首页没有URL,但有子菜单,设置为第一个子菜单 activeItem.value = firstItem.children[0].name; eventBus.publish(EVEN_VAR.currentPage, firstItem.children[0].component); } else { activeItem.value = ''; } console.log('Menu items updated:', menuItemsNew.value); } }); // v3.0 使用 ss-icon 组件替代 SsNavIcon by xu 20251215 const SsIcon = Vue.resolveComponent('ss-icon'); // 移动状态控制逻辑到组件内 const leftSideTypeDict = { min: { key: "min", icon: "arrow-double-right" }, max: { key: "max", icon: "arrow-double-left" }, }; const leftSideType = Vue.ref(leftSideTypeDict[props.initialSize]); let waitLeftSideChangeTimer = null; // 控制子菜单展开状态 const expandedMenus = Vue.ref(new Set()); // 点击子菜单不高亮父菜单 by xu 20251211 const shouldHighlightMenu = (item) => { if (!item) { return false; } const hasChildren = Array.isArray(item.children) && item.children.length > 0; return !hasChildren && activeItem.value === item.name; }; // ===== 三种菜单模式管理 ===== const menuModeDict = { collapse: { key: "collapse", label: "收起", width: "60px" }, fixed: { key: "fixed", label: "固定", width: "60px" }, expand: { key: "expand", label: "展开", width: "230px" } }; // 当前菜单模式(默认收起) const currentMenuMode = Vue.ref(menuModeDict.collapse); // 切换菜单模式(循环:收起 → 固定 → 展开 → 收起) const toggleMenuMode = () => { const modeOrder = ['collapse', 'fixed', 'expand']; const currentIndex = modeOrder.indexOf(currentMenuMode.value.key); const nextIndex = (currentIndex + 1) % modeOrder.length; const nextMode = menuModeDict[modeOrder[nextIndex]]; currentMenuMode.value = nextMode; // 更新 CSS 变量 updateLayoutWidth(nextMode.width); }; // 更新布局宽度 const updateLayoutWidth = (width) => { const layoutContainer = document.querySelector('.layout-container'); if (layoutContainer) { layoutContainer.style.setProperty('--left-side-width', width); } }; const onMenuClick = (item) => { console.log(item) console.log(activeItem.value) if(item.component && item.js){ eval(item.js) } else if(item.component){ eventBus.publish(EVEN_VAR.currentPage, item.component); }else{ eval(item.js) } } const toggleLeftSideType = () => { leftSideType.value = leftSideType.value.key === 'min' ? leftSideTypeDict.max : leftSideTypeDict.min; }; const doChangeLeftSideType2Max = () => { // 只有在收起模式下才响应鼠标悬停 if (currentMenuMode.value.key === 'collapse' && leftSideType.value.key === 'min') { // 即刻展开,不需要延迟 clearTimeout(waitLeftSideChangeTimer); leftSideType.value = leftSideTypeDict.max; } }; const onLeaveLeftMenuArea = () => { // 只有在收起模式下才响应鼠标离开 if (currentMenuMode.value.key === 'collapse') { if (waitLeftSideChangeTimer) { clearTimeout(waitLeftSideChangeTimer); waitLeftSideChangeTimer = null; } leftSideType.value = leftSideTypeDict.min; } }; return () => Vue.h('div', { class: 'left-side' }, [ Vue.h('div', { class: 'left-side-container', 'data-mode': currentMenuMode.value.key, // 添加模式标识 size: leftSideType.value.key }, [ // ===== 固定区域:首页 ===== Vue.h('div', { class: 'fixed-top' }, [ menuItemsNew.value.length > 0 ? Vue.h('div', { class: ['menu-item', 'level-1', { active: shouldHighlightMenu(menuItemsNew.value[0]) }], onClick: () => { const firstItem = menuItemsNew.value[0]; activeItem.value = firstItem.name; onMenuClick(firstItem); } }, [ Vue.h('div', { class: 'menu-item-content' }, [ // v3.0 使用 ss-icon + 双 class (图标类 + menu-icon 组类) by xu 20251215 Vue.h(SsIcon, { class: 'menu-base-icon icon-shouye'}), Vue.h('div', { class: 'menu-item-label' }, menuItemsNew.value[0]?.name || '首页') ]) ]) : null ]), // ===== 可滚动区域:其他菜单项 ===== Vue.h('div', { class: 'scrollable-content', onMouseenter: doChangeLeftSideType2Max, onMousemove: doChangeLeftSideType2Max, onMouseleave: onLeaveLeftMenuArea }, [ // 菜单项列表(跳过首页) ...menuItemsNew.value.slice(1).map(icon => [ // 父菜单项 Vue.h('div', { class: ['menu-item', 'level-1', { active: shouldHighlightMenu(icon) }], onClick: () => { if (icon.children && icon.children.length > 0) { // 有子菜单:仅切换展开/收起,不设置 activeItem(不高亮) if (expandedMenus.value.has(icon.name)) { expandedMenus.value.delete(icon.name); } else { expandedMenus.value.add(icon.name); } } else { // 无子菜单:设置 activeItem 并导航 activeItem.value = icon.name; onMenuClick(icon); } } }, [ Vue.h('div', { class: 'menu-item-content' }, [ // v3.0 收起时显示自定义图标,展开时统一显示 folder-open by xu 20251215 Vue.h(SsIcon, { class: (expandedMenus.value.has(icon.name) ? 'icon-folder-open' : (icon.class || 'menu-base-icon icon-folder-close')) + ' menu-icon' }), Vue.h('div', { class: 'menu-item-label' }, icon.name || ''), // 有子菜单时显示小圆点(在一级菜单右上角) icon.children && icon.children.length > 0 ? Vue.h('div', { class: 'has-children-dot' }) : null ]) ]), // 子菜单项(与父菜单项同级) ...(icon.children && expandedMenus.value.has(icon.name) ? icon.children.map(child => Vue.h('div', { class: ['menu-item', 'level-2', { active: activeItem.value === child.name }], onClick: (e) => { activeItem.value = child.name; e.stopPropagation(); onMenuClick(child); } }, [ Vue.h('div', { class: 'menu-item-content' }, [ // v3.0 使用 ss-icon + 双 class by xu 20251215 Vue.h(SsIcon, { class: (child.class || '') + ' menu-icon' }), Vue.h('div', { class: 'menu-item-label' }, child.name) ]) ]) ) : [] ) ]).flat() ]), // ===== 固定区域:菜单模式按钮 ===== Vue.h('div', { class: 'fixed-bottom' }, [ Vue.h('div', { class: 'menu-mode-btn', onClick: toggleMenuMode }, [ Vue.h(SsIcon, { class: 'menu-base-icon icon-qiehuan' }) ]) ]) ]) ]); } }; // 基础组件 首页组件头部 export const HeaderContainer = { name: 'HeaderContainer', props: { title: String, icon: String, }, emits: ['setting', 'refresh'], setup(props, { emit }) { const onSetting = () => { emit('setting'); }; const onRefresh = () => { emit('refresh'); }; return { props, onSetting, onRefresh }; }, render() { const SsIcon = Vue.resolveComponent('ss-icon'); return Vue.h('div', { class: 'edit-box-header-container' }, [ Vue.h('div', { class: ['title', { visibility: !!(this.props.icon || this.props.title) }] }, [ this.props.icon ? Vue.h('div', { class: 'icon', onClick: this.onRefresh }, [ Vue.h(SsIcon, { class: 'normal', name: this.props.icon, size: '22px' }), Vue.h(SsIcon, { class: 'hover', name: 'refresh', size: '22px' }) ]) : null, Vue.h('div', this.props.title) ]), Vue.h('div', { class: 'handle-bar' }, [ Vue.h('div', { class: 'left-bar' }), // Assuming left-bar is empty Vue.h('div', { class: 'setting', onClick: this.onSetting }, [ Vue.h(SsIcon, { name: 'setting', size: '22px' }) ]) ]) ]); } }; // 基础组件 首页组件边框 export const EditBox = { name: 'EditBox', setup(props, { emit, slots }) { const mousePos = Vue.reactive({ rightCenter: "right-center", rightBottom: "right-bottom", bottomCenter: "bottom-center", }); const curActionMousePos = Vue.ref(""); const editBoxContainer = Vue.ref(null); const onMousemove = (e) => { if (e.buttons === 1 && curActionMousePos.value) { const dom = editBoxContainer.value; if (dom) { const zoom = Number(document.body.style.zoom || 1); const winRect = dom.getBoundingClientRect(); const { x: xSource, y: ySource } = e; const x = xSource / zoom; const y = ySource / zoom; const val = { left: winRect.left, top: winRect.top, width: winRect.width, height: winRect.height, }; const toHObj = (source) => ({ ...source, height: Math.abs(y - source.top), top: y >= 0 ? source.top : source.top + (y - source.top), }); const toWObj = (source) => ({ ...source, width: Math.abs(x - source.left), left: x >= 0 ? source.left : source.left + (x - source.left), }); if (curActionMousePos.value === mousePos.bottomCenter) { emit("size", toHObj(val)); } else if (curActionMousePos.value === mousePos.rightCenter) { emit("size", toWObj(val)); } else if (curActionMousePos.value === mousePos.rightBottom) { emit("size", toWObj(toHObj(val))); } } } }; const onMouseup = () => { curActionMousePos.value = ""; }; const onMouseDown = (pos) => { console.log(pos) curActionMousePos.value = pos; }; Vue.onMounted(() => { document.body.addEventListener("mousemove", onMousemove); document.body.addEventListener("mouseup", onMouseup); }); Vue.onUnmounted(() => { document.body.removeEventListener("mousemove", onMousemove); document.body.removeEventListener("mouseup", onMouseup); }); const SsIcon = Vue.resolveComponent('ss-icon'); return () => Vue.h('div', { class: 'edit-box-container', ref: editBoxContainer }, [ Vue.h('div', { class: ['edit-tools', { active: !!curActionMousePos.value }] }, [ Vue.h('div', { class: 'close' }, [ Vue.h(SsIcon, { name: "close-mark-fill", size: "36px" }) ]), Vue.h('div', { class: 'right-center', onMousedown: () => onMouseDown(mousePos.rightCenter) }, [ Vue.h('div', { class: 'icon' }, [ Vue.h(SsIcon, { name: "resize", size: "30px" }) ]) ]), Vue.h('div', { class: 'right-bottom', onMousedown: () => onMouseDown(mousePos.rightBottom) }, [ Vue.h('div', { class: 'icon' }, [ Vue.h(SsIcon, { name: "resize", size: "30px" }) ]) ]), Vue.h('div', { class: 'bottom-center', onMousedown: () => onMouseDown(mousePos.bottomCenter) }, [ Vue.h('div', { class: 'icon' }, [ Vue.h(SsIcon, { name: "resize", size: "30px" }) ]) ]) ]), Vue.h('div', { class: 'content-area' }, slots.default ? slots.default() : []) ]); } }; // 基础组件 头像 export const Avatar = { name: 'Avatar', props: { url: { type: String, required: true }, size: { type: [String, Number], default: 50 }, unit: { type: String, default: 'px' }, shape: { validator: function (value) { return ['circle', 'round'].includes(value); }, default: 'circle' } }, setup(props) { // 计算属性,转换尺寸并添加单位 const style = Vue.computed(() => { const addUnit = (n) => isNum(n) ? `${n}${props.unit}` : (n || ''); const styleObj = { width: addUnit(props.size), height: addUnit(props.size), }; return toStyleStr(styleObj); }); // 返回渲染函数所需要的数据 return { props, style }; }, render() { return Vue.h('img', { src: this.props.url, style: this.style.value, class: ['avatar-container', this.props.shape] }); } }; // 基础组件 文件夹或者文件的组件 export const FolderContainer = { name: 'FolderContainer', props: { list: { type: Array, required: true, }, draggable: { type: Boolean, default: false, }, }, setup(props, { emit }) { const toggleFolder = (index, isFolder) => { const item = props.list[index]; if (isFolder) { item.open = !item.open; } }; const onClickItem = (item) => { emit('click', item); }; const onItemStartDrag = (e, item) => { e.dataTransfer.setData("text/plain", JSON.stringify(item)); console.log("开始拖拽的对象是=>", { item }); }; const onItemDrop = (e, item, pre) => { var data = e.dataTransfer.getData("text/plain"); try { const obj = JSON.parse(data); item.children.push(obj); console.log("目标对象=>", { e, item, pre, obj }); } catch (err) { console.log("拖拽的节点不正确", { item, err }); } }; return { toggleFolder, onClickItem, onItemStartDrag, onItemDrop, }; }, render() { const SsIcon = Vue.resolveComponent('ss-icon'); return Vue.h('div', { class: 'folder-container' }, this.list.map((groupItem, i) => { return Vue.h('div', { class: 'group-item', key: i }, [ Vue.h('div', { class: ['group-title', { active: !!groupItem.curActionMousePos }], onDrop: $event => this.onItemDrop($event, groupItem), onDragover: $event => $event.preventDefault(), }, [ groupItem.type === 'folder' ? Vue.h('div', { class: 'folder', onClick: $event => this.toggleFolder(i, groupItem.type === 'folder'), 'data-num': groupItem.children.length, }, [ groupItem.open ? Vue.h(SsIcon, { name: 'folder-expand-fill' }) : Vue.h(SsIcon, { name: 'folder-collapse-fill' }) ]) : groupItem.type === 'file' ? Vue.h(SsIcon, { name: 'file' }) : null, Vue.h('div', groupItem.title) ]), groupItem.children.length > 0 && groupItem.open ? Vue.h('ul', { class: 'group-childs', onDrop: $event => this.onItemDrop($event, groupItem), onDragover: $event => $event.preventDefault(), }, groupItem.children.map((item, j) => { return Vue.h('li', { key: j, draggable: this.draggable, onDragstart: $event => this.onItemStartDrag($event, item), onDrop: $event => this.onItemDrop($event, groupItem, item), onDragover: $event => $event.preventDefault(), onClick: () => this.onClickItem(item), }, [ Vue.h('div', item.title), Vue.h('div', item.time) ]); })) : null ]); })); } }; // 个人卡片 export const UserInfo = { name: 'UserInfo', props: { obj: { type: Object, default: () => ({}) } }, setup(props, { emit }) { const winInfo = Vue.reactive(loadWinSize(winName.UserInfo)); const onSetting = () => { console.log("点击了设置按钮"); }; const onRefresh = () => { console.log("点击了刷新按钮"); }; const onSizeChange = (rect) => { const style = {}; for (const key in rect) { style[key] = rect[key] + 'px'; } saveWinSize(winName.UserInfo, style); Object.assign(winInfo, style); }; return { winInfo, onSetting, onRefresh, onSizeChange, props }; }, render() { const SsIcon = Vue.resolveComponent('ss-icon'); return Vue.h('div', { class: 'user-info-container can-resize-box', style: this.winInfo }, Vue.h(EditBox, { onSize: this.onSizeChange }, { default: () => [ Vue.h('div', { class: 'header' }, Vue.h(HeaderContainer, { onSetting: this.onSetting, onRefresh: this.onRefresh }) ), Vue.h('div', { class: 'body' }, [ Vue.h('div', { class: 'user-info', style: "margin-bottom: 18px" }, [ Vue.h('div', { class: 'avatar' }, Vue.h(Avatar, { url: '../images/example/user-avatar.png', size: '90px' }) ), Vue.h('div', { class: 'info' }, [ Vue.h('p', this.props.obj.name + ',上午好!'), Vue.h('p', '最近登录时间:2024年/08/08 13:03'), Vue.h('p', '明天有暴雨,记得出门带伞噢!') ]) ]), Vue.h('div', { class: 'progress-bar' }, [ Vue.h('div', { class: 'progress' }, [ Vue.h('div', { class: 'line', style: 'width: 60%' }, [ Vue.h('div', '60%(课时)') ]) ]) ]), Vue.h('div', { class: 'other-info' }, [ Vue.h('div', {}, [ Vue.h(SsIcon, { name: "userGroup", size: "22px" }), Vue.h('div', {}, "《关于公司全面预算规划会议》"), ]), Vue.h('div', {}, [ Vue.h(SsIcon, { name: "card", type: "common", size: "22px" }), Vue.h('div', {}, "一卡通余额:**************"), ]), Vue.h('div', {}, [ Vue.h(SsIcon, { name: "site", type: "common", size: "22px" }), Vue.h('div', {}, "个人网站:xxx.xxx.xxx"), ]), ]) ]) ] }) ); } }; // 待办 export const TodoList = { name: 'TodoList', setup() { const todoGroupList = Vue.reactive([ { title: "草稿", type: "folder", open: false, children: [ { title: "被退回", time: "08/10 18:12" }, { title: "项目立项申请", time: "08/10 18:12" }, { title: "报销申请", time: "08/10 18:12" }, { title: "资产领用申请", time: "08/10 18:12" }, ], }, { title: "被退回", type: "folder", open: true, children: [ { title: "被退回", time: "08/10 18:12" }, { title: "项目立项申请", time: "08/10 18:12" }, { title: "报销申请", time: "08/10 18:12" }, { title: "资产领用申请", time: "08/10 18:12" }, ], }, { title: "2020-高薪技术业项目申报", type: "file", children: [], }, ]); const winInfo = Vue.ref(loadWinSize(winName.TodoList)); const onSetting = () => { console.log("Clicked on settings button"); }; const onRefresh = () => { console.log("Clicked on refresh button"); }; const onSizeChange = (rect) => { console.log(rect) const style = {}; for (const key in rect) { style[key] = rect[key] + "px"; } saveWinSize(winName.TodoList, style); winInfo.value = style; }; const onItemClick = (e) => { console.log("===>>>", e); }; Vue.onMounted(() => { // console.log("TodoList component is mounted"); }); return () => Vue.h('div', { class: 'todo-list-container can-resize-box', style: winInfo.value }, Vue.h(EditBox, { onSize: onSizeChange }, { default: () => [ Vue.h('div', { class: 'header' }, [ Vue.h(HeaderContainer, { title: "待办", icon: "todo", onSetting, onRefresh }) ]), Vue.h('div', { class: 'body' }, [ Vue.h(FolderContainer, { list: todoGroupList, onClick: onItemClick }) ]) ] }) ) } }; // 催办 export const UrgingList = { name: 'UrgingList', setup() { const todoGroupList = Vue.reactive([ { title: "项目", type: "folder", open: true, children: [ { title: "《高新技术企业认定》项目报销", time: "08/10 18:12" }, { title: "日常办公报销", time: "08/10 18:12" }, { title: "固定资产维修费用报销", time: "08/10 18:12" }, ], }, { title: "报销", type: "folder", open: true, children: [ { title: "2020-企业-技术改造专项资金项目-立项申请", time: "08/10 18:12", }, ], }, { title: "2020-高薪技术企业项目申报", type: "file", children: [], }, { title: "2020-高薪技术企业项目申报-技术改造专项资金项目-立项申请", type: "file", children: [], }, ]); const winInfo = Vue.ref(loadWinSize(winName.UrgingList)); const onSetting = () => { console.log("Clicked on settings button"); }; const onRefresh = () => { console.log("Clicked on refresh button"); }; const onSizeChange = (rect) => { const style = {}; for (const key in rect) { style[key] = rect[key] + "px"; } saveWinSize(winName.UrgingList, style); winInfo.value = style; }; return () => Vue.h('div', { class: 'todo-list-container can-resize-box', style: winInfo.value }, Vue.h(EditBox, { onSize: onSizeChange }, { default: () => [ Vue.h('div', { class: 'header' }, [ Vue.h(HeaderContainer, { title: "催办", icon: "alarm-clock", onSetting, onRefresh }) ]), Vue.h('div', { class: 'body' }, [ Vue.h(FolderContainer, { list: todoGroupList }) ]) ] }) ) } }; // 公示公告 export const Notice = { name: 'Notice', setup() { const list = Vue.reactive([ { title: "关于2022年度中层干部的任免公告", time: "10/08 08:30" }, { title: "2022年重组团队从心出发", time: "10/08 08:30" }, { title: "关于单位进入粤港澳创新创业大赛决赛", time: "10/08 08:30" }, { title: "关于2022年度中层干部的任免公告", time: "10/08 08:30" }, ]); const winInfo = Vue.ref(loadWinSize(winName.Notice)); const onSetting = () => { console.log("Clicked on settings button"); }; const onRefresh = () => { console.log("Clicked on refresh button"); }; const onSizeChange = (rect) => { const style = {}; for (const key in rect) { style[key] = rect[key] + "px"; } saveWinSize(winName.Notice, style); winInfo.value = style; }; const SsIcon = Vue.resolveComponent('ss-icon'); return () => Vue.h('div', { class: 'notice-list-container can-resize-box', style: winInfo.value }, [ Vue.h(EditBox, { onSize: onSizeChange }, { default: () => [ Vue.h('div', { class: 'header' }, [ Vue.h(HeaderContainer, { title: "公示公告", icon: "notice", onSetting: onSetting, onRefresh: onRefresh }) ]), Vue.h('div', { class: 'body' }, list.map((item, i) => Vue.h('div', { key: i }, [ Vue.h('div', [ Vue.h(SsIcon, { name: "file", size: "22px" }), Vue.h('span', item.title) ]), Vue.h('div', item.time) ]) )) ] }) ]); } }; // 快捷发起 export const Launch = { name: 'Launch', setup() { const winInfo = Vue.ref(loadWinSize(winName.launch)); const popupInfo = Vue.reactive({ status: false, height: 100, width: 100, left: 0, top: 0, }); const popupStyle = Vue.computed(() => { const { width, height, left, top } = popupInfo; return { left: `${left - (130 - width) / 2}px`, top: `${top + height}px`, }; }); const items = Vue.reactive([ { name: "qingjia", text: "请假" }, { name: "shoufukuan", text: "收付款", hasPopup: true, popupContent: [ { text: "收款" }, { text: "付款" } ] }, { name: "kaoqin", text: "考勤", hasPopup: true, popupContent: [ { text: "2" }, { text: "3" } ] } ]); const onSetting = () => console.log("Clicked on settings button"); const onRefresh = () => console.log("Clicked on refresh button"); const onMouseMove = (e, item) => { console.log("鼠标进入了",item.text) showPopupDialog(e, item) }; const onMouseleave = (e,item) => { console.log("鼠标离开了",item.text) popupInfo.status = false; }; const showPopupDialog = (e, item) => { const dom = e.target.closest('.item'); if (dom) { const rect = dom.getBoundingClientRect(); popupInfo.height = rect.height; popupInfo.left = rect.left; popupInfo.top = rect.top; popupInfo.width = rect.width; setTimeout(() => { popupInfo.status = true; }, 600) } }; const onSizeChange = (rect) => { const style = {}; for (const key in rect) style[key] = rect[key] + "px"; saveWinSize(winName.launch, style); winInfo.value = style; }; // Vue.onMounted(() => console.log("LaunchContainer component is mounted")); const SsIcon = Vue.resolveComponent('ss-icon'); return () => Vue.h('div', { class: 'launch-container can-resize-box', style: winInfo.value }, [ Vue.h(EditBox, { onSize: onSizeChange }, { default: () => [ Vue.h('div', { class: 'header' }, [ Vue.h(HeaderContainer, { title: "快捷发起", icon: "lightning", onSetting, onRefresh }) ]), Vue.h('div', { class: 'body' }, items.map(item => Vue.h('div', { class: 'item', onMousemove: e => onMouseMove(e, item), onMouseleave: e => onMouseleave(e, item) }, [ Vue.h(SsIcon, { class: item.hasPopup && item.popupContent.length > 0 ? "mark-down" : "", name: item.name, size: "36px" }), Vue.h('div', { class: 'text' }, item.text), item.hasPopup && popupInfo.status ? Vue.h('div', { class: 'popup', style: popupStyle.value }, item.popupContent.map(content => Vue.h('div', content.text) )) : null ]) )) ] }) ]) } }; // 项目实时统计图 export const Statistics = { name: 'Statistics', setup() { const winInfo = Vue.ref(loadWinSize(winName.Statistics)); const chartInstance = Vue.ref(null); const option = { tooltip: { trigger: "axis", axisPointer: { type: "cross", crossStyle: { color: "#999", }, }, }, legend: { data: ["项目金额", "成本"] }, xAxis: [{ type: "category", data: ["2月", "3月", "4���", "5月", "6月", "7月", "8月", "9月", "10月", "11月"], axisPointer: { type: "shadow" }, }], yAxis: [{ type: "value", name: "单位:万(RMB)", min: 0, max: 1000, interval: 200, axisLabel: { formatter: "{value} " }, }, { type: "value", name: "成本", min: 0, max: 1000, interval: 200, axisLabel: { formatter: "{value}" }, }], series: [{ name: "项目金额", type: "bar", tooltip: { valueFormatter: function (value) { return value + " ml"; } }, data: [250, 850, 500, 650, 450, 850, 780, 450, 700, 850] }, { name: "成本", type: "line", yAxisIndex: 1, tooltip: { valueFormatter: function (value) { return value; } }, data: [400, 390, 580, 590, 430, 420, 430, 550, 230, 250] }] } // 初始化echants const initChart = () => { const chartDom = document.getElementById("statistics-chart"); if (!chartDom) return; chartInstance.value = echarts.init(chartDom); chartInstance.value.setOption(option); }; const onSetting = () => console.log("Clicked on settings button"); const onRefresh = () => { console.log("Clicked on refresh button"); if (chartInstance.value) { chartInstance.value.clear(); initChart(); // Reinitialize the chart to reflect any new data or settings } }; const onSizeChange = (rect) => { const style = {}; for (const key in rect) style[key] = rect[key] + "px"; saveWinSize(winName.Statistics, style); winInfo.value = style; if (chartInstance.value) { chartInstance.value.resize(); } }; Vue.onMounted(() => { // console.log("StatisticsContainer component is mounted"); initChart(); // Initialize the chart when component is mounted }); // return { winInfo, onSetting, onRefresh, onSizeChange }; return () => Vue.h('div', { class: 'statistics-container can-resize-box', style: winInfo.value }, [ Vue.h(EditBox, { onSize: onSizeChange }, { default: () => [ Vue.h('div', { class: 'header' }, [ Vue.h(HeaderContainer, { title: "项目实时统计图", icon: "layer", onSetting, onRefresh }) ]), Vue.h('div', { class: 'body' }, [ Vue.h('div', { id: 'statistics-chart', class: 'chart-container' }) ]) ] }) ]); }, };