ss-index-components.js 44 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156
  1. import { isNum, toStyleStr } from "./tools.js";
  2. import { eventBus, EVEN_VAR } from "./EventBus.js";
  3. // import { debounce } from "../lib/tools.js";
  4. // 首页组件的名字
  5. const winName = {
  6. launch: "launch",
  7. Notice: "Notice",
  8. Statistics: "Statistics",
  9. TodoList: "TodoList",
  10. UrgingList: "UrgingList",
  11. UserInfo: "UserInfo",
  12. };
  13. // 尺寸窗口名字
  14. const size2Win = (name) => `${name}-${document.body.clientWidth}`;
  15. // 加载尺寸
  16. const loadWinSize = function (name) {
  17. const val = localStorage.getItem(size2Win(name));
  18. if (val) {
  19. try {
  20. return JSON.parse(val) || {};
  21. } catch (err) {
  22. return {};
  23. }
  24. } else {
  25. return {};
  26. }
  27. };
  28. const saveWinSize = function (name, obj) {
  29. localStorage.setItem(size2Win(name), JSON.stringify(obj));
  30. };
  31. // 全局头部
  32. export const GlobalHeader = {
  33. name: 'GlobalHeader',
  34. props: {
  35. menuData: {
  36. type: Array,
  37. required: true
  38. },
  39. iconItems: {
  40. type: Array,
  41. required: true
  42. }
  43. },
  44. setup(props) {
  45. //原值为'/newUI/skin/easy/images/logo/full-logo.png' Ben(20251205)
  46. const fullLogo = Vue.ref('/skin/easy/images/logo/full-logo.png'); // 需要指定完整路径
  47. const convertMenuData = (menuData) => {
  48. return menuData.map(item => ({
  49. id: item.id, //菜单id
  50. pid: item.pid, //父菜单id
  51. label: item.desc, //菜单名称
  52. component: item.url, //菜单链接
  53. js: item.js, //菜单要执行的js
  54. class: item.icon, // 如果icon为空则使用默认class
  55. itemType: item.type, //菜单 1:菜单项 2:菜单组
  56. }));
  57. };
  58. const menuItemsNew = Vue.ref(convertMenuData(props.menuData));
  59. Vue.watchEffect(() => {
  60. if (props.menuData && props.menuData.length) {
  61. menuItemsNew.value = convertMenuData(props.menuData);
  62. console.log('Header menu items updated:', menuItemsNew.value);
  63. }
  64. });
  65. const menuItems = Vue.ref([]);
  66. const breadCrumbs = Vue.ref([
  67. { label: '首页', component: '/index.html' },
  68. ]);
  69. const iconItems = Vue.ref(props.iconItems);
  70. const onClickMenuItemsNew = (item) => {
  71. console.log(item.component, item.js)
  72. if (item.component && item.js) {
  73. eval(item.js)
  74. } else if(item.component){
  75. eventBus.publish(EVEN_VAR.currentPage, item.component);
  76. } else {
  77. eval(item.js)
  78. }
  79. };
  80. const onClickMenuItem = (item) => {
  81. if (item.type == 'page') {
  82. eventBus.publish(EVEN_VAR.currentPage, item.component);
  83. breadCrumbs.value[0] = item
  84. } else if (item.type == 'dialog') {
  85. SS.openDialog({
  86. headerTitle: item.label,
  87. src: '.'+item.component,
  88. height: item.height,
  89. width:item.width
  90. });
  91. }
  92. };
  93. const breadCrumbClick = (item) => {
  94. eventBus.publish(EVEN_VAR.currentPage, item.component);
  95. breadCrumbs.value[0] = item
  96. }
  97. const gotoSearch = () => {
  98. eventBus.publish(EVEN_VAR.showGlobalSearchDialog);
  99. };
  100. const SsIcon = Vue.resolveComponent('ss-icon');
  101. const SsHeaderIcon = Vue.resolveComponent('ss-header-icon');
  102. const SsGolbalMenuIcon = Vue.resolveComponent('ss-golbal-menu-icon');
  103. // 引入搜索框
  104. const SsSearch = Vue.resolveComponent('ss-search');
  105. // 处理面包屑导航
  106. const renderBreadcrumbs = () => {
  107. const breadcrumbElements = [Vue.h(SsIcon, { class: "home-icon", name: "home", type: "common", size: "22px" })];
  108. // 遍历面包屑数组,添加面包屑和分隔符
  109. breadCrumbs.value.forEach((crumb, index) => {
  110. // 添加面包屑链接
  111. breadcrumbElements.push(
  112. Vue.h('span', { onClick: () => breadCrumbClick(crumb) }, crumb.label)
  113. );
  114. // 除了最后一个元素,每个面包屑后添加分隔符图标
  115. if (index < breadCrumbs.value.length - 1) {
  116. breadcrumbElements.push(
  117. Vue.h(SsIcon, { class: "split-icon", size: "12px", name: "double-arrow-right", type: "common" })
  118. );
  119. }
  120. });
  121. return breadcrumbElements;
  122. };
  123. return () => Vue.h('div', { class: 'block-self flex-between-center global-header-container' }, [
  124. Vue.h('div', { class: 'icon-area flex-start-center' }, [
  125. Vue.h('div', { class: 'logo', onClick: () => console.log('Home clicked') }, [
  126. Vue.h('div', { class: 'img' }, [
  127. Vue.h('img', { src: fullLogo.value })
  128. ]),
  129. Vue.h('div', { class: 'menu', onClick: Vue.withModifiers(() => { }, ['stop']) },
  130. // menuItems.value.map(item =>
  131. // Vue.h('div', { onClick: () => onClickMenuItem(item) },[
  132. // Vue.h(SsGolbalMenuIcon, { class: item.class || '' }),
  133. // item.label
  134. // ])
  135. // )
  136. menuItemsNew.value.map(item =>
  137. Vue.h('div', { onClick: () => onClickMenuItemsNew(item) },[
  138. Vue.h(SsGolbalMenuIcon, { class: item.class || '' }),
  139. item.label
  140. ])
  141. )
  142. )
  143. ]),
  144. // Vue.h('div', { class: 'bread-crumb', style: { height: "100% !important" } }, [
  145. // Vue.h('div', { class: 'content' }, renderBreadcrumbs())
  146. // ])
  147. ]),
  148. Vue.h('div', { class: 'menu-area' },
  149. Vue.h('div', { class: 'search-area' }, [
  150. Vue.h(SsSearch, { theme: 'dark', placeholder: '跨对象搜索', onClick: gotoSearch })
  151. ]),
  152. iconItems.value.map(icon =>
  153. Vue.h('div', { class: 'icon-item', onClick: icon.action, style: { display: icon.condition ? (icon.condition() ? 'block' : 'none') : 'block' } }, [
  154. Vue.h(SsHeaderIcon, { class: icon.class })
  155. ])
  156. )
  157. )
  158. ]);
  159. }
  160. };
  161. // 全局菜单
  162. export const GlobalMenu = {
  163. name: 'GlobalMenu',
  164. props: {
  165. menuItems: {
  166. type: Array,
  167. required: true
  168. },
  169. initialSize: {
  170. type: String,
  171. default: 'min'
  172. },
  173. // onMenuClick: {
  174. // type: Function,
  175. // required: true,
  176. // default: () => {}
  177. // }
  178. },
  179. setup(props) {
  180. const menuItemsNew = Vue.ref([]);
  181. const activeItem = Vue.ref(''); // 默认选中第一个菜单项
  182. // 将后台返回的菜单数据转换为树结构
  183. const convertMenuDataToTree = (menuData) => {
  184. // 先转换格式
  185. const formattedData = menuData.map(item => ({
  186. id: item.id,
  187. pid: item.pid,
  188. name: item.desc,
  189. component: item.url,
  190. js: item.js,
  191. class: item.type == 1 ? 'nav-icon-folder-close' : item.icon || 'header-home',
  192. itemType: item.type,
  193. children: []
  194. }));
  195. // 创建一个映射表,方便查找
  196. const map = {};
  197. formattedData.forEach(item => {
  198. map[item.id] = item;
  199. });
  200. // 构建树结构
  201. const treeData = [];
  202. formattedData.forEach(item => {
  203. if (item.pid && map[item.pid]) {
  204. // 如果有父节点,就放到父节点的children中
  205. if (!map[item.pid].children) {
  206. map[item.pid].children = [];
  207. }
  208. map[item.pid].children.push(item);
  209. } else {
  210. // 没有父节点就是顶层节点
  211. treeData.push(item);
  212. }
  213. });
  214. return treeData;
  215. };
  216. // 添加 watchEffect 来监听 props.menuItems 的变化
  217. Vue.watchEffect(() => {
  218. if (props.menuItems && props.menuItems.length) {
  219. menuItemsNew.value = convertMenuDataToTree(props.menuItems);
  220. activeItem.value = menuItemsNew.value[0]?.name || '';
  221. console.log('Menu items updated:', menuItemsNew.value);
  222. if(menuItemsNew.value[0]?.component){
  223. eventBus.publish(EVEN_VAR.currentPage, menuItemsNew.value[0]?.component);
  224. }else{
  225. eventBus.publish(EVEN_VAR.currentPage, menuItemsNew.value[0]?.children[0]?.component);
  226. }
  227. }
  228. });
  229. const SsIcon = Vue.resolveComponent('ss-icon');
  230. const SsNavIcon = Vue.resolveComponent('ss-nav-icon');
  231. // 移动状态控制逻辑到组件内
  232. const leftSideTypeDict = {
  233. min: { key: "min", icon: "arrow-double-right" },
  234. max: { key: "max", icon: "arrow-double-left" },
  235. };
  236. const leftSideType = Vue.ref(leftSideTypeDict[props.initialSize]);
  237. let waitLeftSideChangeTimer = null;
  238. // 控制子菜单展开状态
  239. const expandedMenus = Vue.ref(new Set());
  240. const onMenuClick = (item) => {
  241. console.log(item)
  242. console.log(activeItem.value)
  243. if(item.component && item.js){
  244. eval(item.js)
  245. } else if(item.component){
  246. eventBus.publish(EVEN_VAR.currentPage, item.component);
  247. }else{
  248. eval(item.js)
  249. }
  250. }
  251. const toggleLeftSideType = () => {
  252. leftSideType.value =
  253. leftSideType.value.key === 'min' ? leftSideTypeDict.max : leftSideTypeDict.min;
  254. };
  255. const doChangeLeftSideType2Max = () => {
  256. if (leftSideType.value.key === 'min') {
  257. clearTimeout(waitLeftSideChangeTimer);
  258. waitLeftSideChangeTimer = setTimeout(() => {
  259. leftSideType.value = leftSideTypeDict.max;
  260. }, 1000);
  261. }
  262. };
  263. const onLeaveLeftMenuArea = () => {
  264. if (waitLeftSideChangeTimer) {
  265. clearTimeout(waitLeftSideChangeTimer);
  266. waitLeftSideChangeTimer = null;
  267. leftSideType.value = leftSideTypeDict.min;
  268. }
  269. };
  270. return () => Vue.h('div', { class: 'left-side' }, [
  271. Vue.h('div', {
  272. class: 'left-side-container',
  273. size: leftSideType.value.key
  274. }, [
  275. // 收缩按钮
  276. Vue.h('div', {
  277. class: 'btn-size',
  278. onClick: toggleLeftSideType
  279. }, [
  280. Vue.h(SsIcon, {
  281. name: leftSideType.value.icon,
  282. type: 'common',
  283. size: '12px'
  284. })
  285. ]),
  286. // 左侧内容
  287. Vue.h('div', {
  288. class: 'left-side-content',
  289. onMouseenter: doChangeLeftSideType2Max,
  290. onMousemove: doChangeLeftSideType2Max,
  291. onMouseleave: onLeaveLeftMenuArea
  292. }, [
  293. // 菜单项列表
  294. ...menuItemsNew.value.map(icon => [
  295. // 父菜单项
  296. Vue.h('div', {
  297. class:['menu-item',{active:activeItem.value === icon.name}],
  298. onClick: () => {
  299. activeItem.value = icon.name;
  300. if (icon.children.length > 0) {
  301. if (expandedMenus.value.has(icon.name)) {
  302. expandedMenus.value.delete(icon.name);
  303. } else {
  304. expandedMenus.value.add(icon.name);
  305. }
  306. } else {
  307. onMenuClick(icon);
  308. }
  309. }
  310. }, [
  311. Vue.h('div', { class: 'menu-item-content' }, [
  312. Vue.h(SsNavIcon, {
  313. // 如果是文件夹类型(type == 1),根据展开状态设置不同的class
  314. class: icon.itemType == 1 ?
  315. (expandedMenus.value.has(icon.name) ? 'nav-icon-folder-open' : 'nav-icon-folder-close')
  316. : (icon.class || '')
  317. }),
  318. Vue.h('div', { class: 'menu-item-label' }, icon.name || ''),
  319. Vue.h('div', { class: 'edit-mark' }, [
  320. Vue.h(SsIcon, { name: 'close-mark-fill', size: '36px' })
  321. ])
  322. ])
  323. ]),
  324. // 子菜单项(与父菜单项同级)
  325. ...(icon.children && expandedMenus.value.has(icon.name) ?
  326. icon.children.map(child =>
  327. Vue.h('div', {
  328. class:['menu-item',{active:activeItem.value === child.name}],
  329. onClick: (e) => {
  330. activeItem.value = child.name;
  331. e.stopPropagation();
  332. onMenuClick(child);
  333. }
  334. }, [
  335. Vue.h('div', { class: 'menu-item-content' }, [
  336. Vue.h('div', { class: 'icon-container' }, [ // 添加图标容器
  337. Vue.h(SsNavIcon, {
  338. class: child.class || ''
  339. }),
  340. Vue.h('div', { class: 'menu-item-dot' }) // 小圆点放在图标容器内
  341. ]),
  342. Vue.h('div', { class: 'menu-item-label' }, child.name),
  343. Vue.h('div', { class: 'edit-mark' }, [
  344. Vue.h(SsIcon, { name: 'close-mark-fill', size: '36px' })
  345. ])
  346. ])
  347. ])
  348. ) : []
  349. )
  350. ]).flat(),
  351. // 添加按钮
  352. Vue.h('div', { class: 'menu-item add-menu-btn' }, [
  353. Vue.h('div', { class: 'add' }, [
  354. Vue.h(SsNavIcon, { class: 'nav-icon-add' })
  355. ])
  356. ])
  357. ])
  358. ])
  359. ]);
  360. }
  361. };
  362. // 基础组件 首页组件头部
  363. export const HeaderContainer = {
  364. name: 'HeaderContainer',
  365. props: {
  366. title: String,
  367. icon: String,
  368. },
  369. emits: ['setting', 'refresh'],
  370. setup(props, { emit }) {
  371. const onSetting = () => {
  372. emit('setting');
  373. };
  374. const onRefresh = () => {
  375. emit('refresh');
  376. };
  377. return {
  378. props,
  379. onSetting,
  380. onRefresh
  381. };
  382. },
  383. render() {
  384. const SsIcon = Vue.resolveComponent('ss-icon');
  385. return Vue.h('div', { class: 'edit-box-header-container' }, [
  386. Vue.h('div', { class: ['title', { visibility: !!(this.props.icon || this.props.title) }] }, [
  387. this.props.icon ? Vue.h('div', { class: 'icon', onClick: this.onRefresh }, [
  388. Vue.h(SsIcon, { class: 'normal', name: this.props.icon, size: '22px' }),
  389. Vue.h(SsIcon, { class: 'hover', name: 'refresh', size: '22px' })
  390. ]) : null,
  391. Vue.h('div', this.props.title)
  392. ]),
  393. Vue.h('div', { class: 'handle-bar' }, [
  394. Vue.h('div', { class: 'left-bar' }), // Assuming left-bar is empty
  395. Vue.h('div', { class: 'setting', onClick: this.onSetting }, [
  396. Vue.h(SsIcon, { name: 'setting', size: '22px' })
  397. ])
  398. ])
  399. ]);
  400. }
  401. };
  402. // 基础组件 首页组件边框
  403. export const EditBox = {
  404. name: 'EditBox',
  405. setup(props, { emit, slots }) {
  406. const mousePos = Vue.reactive({
  407. rightCenter: "right-center",
  408. rightBottom: "right-bottom",
  409. bottomCenter: "bottom-center",
  410. });
  411. const curActionMousePos = Vue.ref("");
  412. const editBoxContainer = Vue.ref(null);
  413. const onMousemove = (e) => {
  414. if (e.buttons === 1 && curActionMousePos.value) {
  415. const dom = editBoxContainer.value;
  416. if (dom) {
  417. const zoom = Number(document.body.style.zoom || 1);
  418. const winRect = dom.getBoundingClientRect();
  419. const { x: xSource, y: ySource } = e;
  420. const x = xSource / zoom;
  421. const y = ySource / zoom;
  422. const val = {
  423. left: winRect.left,
  424. top: winRect.top,
  425. width: winRect.width,
  426. height: winRect.height,
  427. };
  428. const toHObj = (source) => ({
  429. ...source,
  430. height: Math.abs(y - source.top),
  431. top: y >= 0 ? source.top : source.top + (y - source.top),
  432. });
  433. const toWObj = (source) => ({
  434. ...source,
  435. width: Math.abs(x - source.left),
  436. left: x >= 0 ? source.left : source.left + (x - source.left),
  437. });
  438. if (curActionMousePos.value === mousePos.bottomCenter) {
  439. emit("size", toHObj(val));
  440. } else if (curActionMousePos.value === mousePos.rightCenter) {
  441. emit("size", toWObj(val));
  442. } else if (curActionMousePos.value === mousePos.rightBottom) {
  443. emit("size", toWObj(toHObj(val)));
  444. }
  445. }
  446. }
  447. };
  448. const onMouseup = () => {
  449. curActionMousePos.value = "";
  450. };
  451. const onMouseDown = (pos) => {
  452. console.log(pos)
  453. curActionMousePos.value = pos;
  454. };
  455. Vue.onMounted(() => {
  456. document.body.addEventListener("mousemove", onMousemove);
  457. document.body.addEventListener("mouseup", onMouseup);
  458. });
  459. Vue.onUnmounted(() => {
  460. document.body.removeEventListener("mousemove", onMousemove);
  461. document.body.removeEventListener("mouseup", onMouseup);
  462. });
  463. const SsIcon = Vue.resolveComponent('ss-icon');
  464. return () => Vue.h('div', { class: 'edit-box-container', ref: editBoxContainer }, [
  465. Vue.h('div', { class: ['edit-tools', { active: !!curActionMousePos.value }] }, [
  466. Vue.h('div', { class: 'close' }, [
  467. Vue.h(SsIcon, { name: "close-mark-fill", size: "36px" })
  468. ]),
  469. Vue.h('div', { class: 'right-center', onMousedown: () => onMouseDown(mousePos.rightCenter) }, [
  470. Vue.h('div', { class: 'icon' }, [
  471. Vue.h(SsIcon, { name: "resize", size: "30px" })
  472. ])
  473. ]),
  474. Vue.h('div', { class: 'right-bottom', onMousedown: () => onMouseDown(mousePos.rightBottom) }, [
  475. Vue.h('div', { class: 'icon' }, [
  476. Vue.h(SsIcon, { name: "resize", size: "30px" })
  477. ])
  478. ]),
  479. Vue.h('div', { class: 'bottom-center', onMousedown: () => onMouseDown(mousePos.bottomCenter) }, [
  480. Vue.h('div', { class: 'icon' }, [
  481. Vue.h(SsIcon, { name: "resize", size: "30px" })
  482. ])
  483. ])
  484. ]),
  485. Vue.h('div', { class: 'content-area' }, slots.default ? slots.default() : [])
  486. ]);
  487. }
  488. };
  489. // 基础组件 头像
  490. export const Avatar = {
  491. name: 'Avatar',
  492. props: {
  493. url: {
  494. type: String,
  495. required: true
  496. },
  497. size: {
  498. type: [String, Number],
  499. default: 50
  500. },
  501. unit: {
  502. type: String,
  503. default: 'px'
  504. },
  505. shape: {
  506. validator: function (value) {
  507. return ['circle', 'round'].includes(value);
  508. },
  509. default: 'circle'
  510. }
  511. },
  512. setup(props) {
  513. // 计算属性,转换尺寸并添加单位
  514. const style = Vue.computed(() => {
  515. const addUnit = (n) => isNum(n) ? `${n}${props.unit}` : (n || '');
  516. const styleObj = {
  517. width: addUnit(props.size),
  518. height: addUnit(props.size),
  519. };
  520. return toStyleStr(styleObj);
  521. });
  522. // 返回渲染函数所需要的数据
  523. return {
  524. props,
  525. style
  526. };
  527. },
  528. render() {
  529. return Vue.h('img', {
  530. src: this.props.url,
  531. style: this.style.value,
  532. class: ['avatar-container', this.props.shape]
  533. });
  534. }
  535. };
  536. // 基础组件 文件夹或者文件的组件
  537. export const FolderContainer = {
  538. name: 'FolderContainer',
  539. props: {
  540. list: {
  541. type: Array,
  542. required: true,
  543. },
  544. draggable: {
  545. type: Boolean,
  546. default: false,
  547. },
  548. },
  549. setup(props, { emit }) {
  550. const toggleFolder = (index, isFolder) => {
  551. const item = props.list[index];
  552. if (isFolder) {
  553. item.open = !item.open;
  554. }
  555. };
  556. const onClickItem = (item) => {
  557. emit('click', item);
  558. };
  559. const onItemStartDrag = (e, item) => {
  560. e.dataTransfer.setData("text/plain", JSON.stringify(item));
  561. console.log("开始拖拽的对象是=>", { item });
  562. };
  563. const onItemDrop = (e, item, pre) => {
  564. var data = e.dataTransfer.getData("text/plain");
  565. try {
  566. const obj = JSON.parse(data);
  567. item.children.push(obj);
  568. console.log("目标对象=>", { e, item, pre, obj });
  569. } catch (err) {
  570. console.log("拖拽的节点不正确", { item, err });
  571. }
  572. };
  573. return {
  574. toggleFolder,
  575. onClickItem,
  576. onItemStartDrag,
  577. onItemDrop,
  578. };
  579. },
  580. render() {
  581. const SsIcon = Vue.resolveComponent('ss-icon');
  582. return Vue.h('div', { class: 'folder-container' }, this.list.map((groupItem, i) => {
  583. return Vue.h('div', { class: 'group-item', key: i }, [
  584. Vue.h('div', {
  585. class: ['group-title', { active: !!groupItem.curActionMousePos }],
  586. onDrop: $event => this.onItemDrop($event, groupItem),
  587. onDragover: $event => $event.preventDefault(),
  588. }, [
  589. groupItem.type === 'folder' ? Vue.h('div', {
  590. class: 'folder',
  591. onClick: $event => this.toggleFolder(i, groupItem.type === 'folder'),
  592. 'data-num': groupItem.children.length,
  593. }, [
  594. groupItem.open ? Vue.h(SsIcon, { name: 'folder-expand-fill' }) : Vue.h(SsIcon, { name: 'folder-collapse-fill' })
  595. ]) : groupItem.type === 'file' ? Vue.h(SsIcon, { name: 'file' }) : null,
  596. Vue.h('div', groupItem.title)
  597. ]),
  598. groupItem.children.length > 0 && groupItem.open ? Vue.h('ul', {
  599. class: 'group-childs',
  600. onDrop: $event => this.onItemDrop($event, groupItem),
  601. onDragover: $event => $event.preventDefault(),
  602. }, groupItem.children.map((item, j) => {
  603. return Vue.h('li', {
  604. key: j,
  605. draggable: this.draggable,
  606. onDragstart: $event => this.onItemStartDrag($event, item),
  607. onDrop: $event => this.onItemDrop($event, groupItem, item),
  608. onDragover: $event => $event.preventDefault(),
  609. onClick: () => this.onClickItem(item),
  610. }, [
  611. Vue.h('div', item.title),
  612. Vue.h('div', item.time)
  613. ]);
  614. })) : null
  615. ]);
  616. }));
  617. }
  618. };
  619. // 个人卡片
  620. export const UserInfo = {
  621. name: 'UserInfo',
  622. props: {
  623. obj: {
  624. type: Object,
  625. default: () => ({})
  626. }
  627. },
  628. setup(props, { emit }) {
  629. const winInfo = Vue.reactive(loadWinSize(winName.UserInfo));
  630. const onSetting = () => {
  631. console.log("点击了设置按钮");
  632. };
  633. const onRefresh = () => {
  634. console.log("点击了刷新按钮");
  635. };
  636. const onSizeChange = (rect) => {
  637. const style = {};
  638. for (const key in rect) {
  639. style[key] = rect[key] + 'px';
  640. }
  641. saveWinSize(winName.UserInfo, style);
  642. Object.assign(winInfo, style);
  643. };
  644. return {
  645. winInfo,
  646. onSetting,
  647. onRefresh,
  648. onSizeChange,
  649. props
  650. };
  651. },
  652. render() {
  653. const SsIcon = Vue.resolveComponent('ss-icon');
  654. return Vue.h('div', { class: 'user-info-container can-resize-box', style: this.winInfo },
  655. Vue.h(EditBox, {
  656. onSize: this.onSizeChange
  657. }, {
  658. default: () => [
  659. Vue.h('div', { class: 'header' },
  660. Vue.h(HeaderContainer, {
  661. onSetting: this.onSetting,
  662. onRefresh: this.onRefresh
  663. })
  664. ),
  665. Vue.h('div', { class: 'body' }, [
  666. Vue.h('div', { class: 'user-info', style: "margin-bottom: 18px" }, [
  667. Vue.h('div', { class: 'avatar' },
  668. Vue.h(Avatar, {
  669. url: '../images/example/user-avatar.png',
  670. size: '90px'
  671. })
  672. ),
  673. Vue.h('div', { class: 'info' }, [
  674. Vue.h('p', this.props.obj.name + ',上午好!'),
  675. Vue.h('p', '最近登录时间:2024年/08/08 13:03'),
  676. Vue.h('p', '明天有暴雨,记得出门带伞噢!')
  677. ])
  678. ]),
  679. Vue.h('div', { class: 'progress-bar' }, [
  680. Vue.h('div', { class: 'progress' }, [
  681. Vue.h('div', { class: 'line', style: 'width: 60%' }, [
  682. Vue.h('div', '60%(课时)')
  683. ])
  684. ])
  685. ]),
  686. Vue.h('div', { class: 'other-info' }, [
  687. Vue.h('div', {}, [
  688. Vue.h(SsIcon, { name: "userGroup", size: "22px" }),
  689. Vue.h('div', {}, "《关于公司全面预算规划会议》"),
  690. ]),
  691. Vue.h('div', {}, [
  692. Vue.h(SsIcon, { name: "card", type: "common", size: "22px" }),
  693. Vue.h('div', {}, "一卡通余额:**************"),
  694. ]),
  695. Vue.h('div', {}, [
  696. Vue.h(SsIcon, { name: "site", type: "common", size: "22px" }),
  697. Vue.h('div', {}, "个人网站:xxx.xxx.xxx"),
  698. ]),
  699. ])
  700. ])
  701. ]
  702. })
  703. );
  704. }
  705. };
  706. // 待办
  707. export const TodoList = {
  708. name: 'TodoList',
  709. setup() {
  710. const todoGroupList = Vue.reactive([
  711. {
  712. title: "草稿",
  713. type: "folder",
  714. open: false,
  715. children: [
  716. { title: "被退回", time: "08/10 18:12" },
  717. { title: "项目立项申请", time: "08/10 18:12" },
  718. { title: "报销申请", time: "08/10 18:12" },
  719. { title: "资产领用申请", time: "08/10 18:12" },
  720. ],
  721. },
  722. {
  723. title: "被退回",
  724. type: "folder",
  725. open: true,
  726. children: [
  727. { title: "被退回", time: "08/10 18:12" },
  728. { title: "项目立项申请", time: "08/10 18:12" },
  729. { title: "报销申请", time: "08/10 18:12" },
  730. { title: "资产领用申请", time: "08/10 18:12" },
  731. ],
  732. },
  733. {
  734. title: "2020-高薪技术业项目申报",
  735. type: "file",
  736. children: [],
  737. },
  738. ]);
  739. const winInfo = Vue.ref(loadWinSize(winName.TodoList));
  740. const onSetting = () => {
  741. console.log("Clicked on settings button");
  742. };
  743. const onRefresh = () => {
  744. console.log("Clicked on refresh button");
  745. };
  746. const onSizeChange = (rect) => {
  747. console.log(rect)
  748. const style = {};
  749. for (const key in rect) {
  750. style[key] = rect[key] + "px";
  751. }
  752. saveWinSize(winName.TodoList, style);
  753. winInfo.value = style;
  754. };
  755. const onItemClick = (e) => {
  756. console.log("===>>>", e);
  757. };
  758. Vue.onMounted(() => {
  759. // console.log("TodoList component is mounted");
  760. });
  761. return () => Vue.h('div', { class: 'todo-list-container can-resize-box', style: winInfo.value },
  762. Vue.h(EditBox, { onSize: onSizeChange }, {
  763. default: () => [
  764. Vue.h('div', { class: 'header' }, [
  765. Vue.h(HeaderContainer, {
  766. title: "待办",
  767. icon: "todo",
  768. onSetting,
  769. onRefresh
  770. })
  771. ]),
  772. Vue.h('div', { class: 'body' }, [
  773. Vue.h(FolderContainer, { list: todoGroupList, onClick: onItemClick })
  774. ])
  775. ]
  776. })
  777. )
  778. }
  779. };
  780. // 催办
  781. export const UrgingList = {
  782. name: 'UrgingList',
  783. setup() {
  784. const todoGroupList = Vue.reactive([
  785. {
  786. title: "项目",
  787. type: "folder",
  788. open: true,
  789. children: [
  790. { title: "《高新技术企业认定》项目报销", time: "08/10 18:12" },
  791. { title: "日常办公报销", time: "08/10 18:12" },
  792. { title: "固定资产维修费用报销", time: "08/10 18:12" },
  793. ],
  794. },
  795. {
  796. title: "报销",
  797. type: "folder",
  798. open: true,
  799. children: [
  800. {
  801. title: "2020-企业-技术改造专项资金项目-立项申请",
  802. time: "08/10 18:12",
  803. },
  804. ],
  805. },
  806. {
  807. title: "2020-高薪技术企业项目申报",
  808. type: "file",
  809. children: [],
  810. },
  811. {
  812. title: "2020-高薪技术企业项目申报-技术改造专项资金项目-立项申请",
  813. type: "file",
  814. children: [],
  815. },
  816. ]);
  817. const winInfo = Vue.ref(loadWinSize(winName.UrgingList));
  818. const onSetting = () => {
  819. console.log("Clicked on settings button");
  820. };
  821. const onRefresh = () => {
  822. console.log("Clicked on refresh button");
  823. };
  824. const onSizeChange = (rect) => {
  825. const style = {};
  826. for (const key in rect) {
  827. style[key] = rect[key] + "px";
  828. }
  829. saveWinSize(winName.UrgingList, style);
  830. winInfo.value = style;
  831. };
  832. return () =>
  833. Vue.h('div', { class: 'todo-list-container can-resize-box', style: winInfo.value }, Vue.h(EditBox, { onSize: onSizeChange }, {
  834. default: () => [
  835. Vue.h('div', { class: 'header' }, [
  836. Vue.h(HeaderContainer, {
  837. title: "催办",
  838. icon: "alarm-clock",
  839. onSetting,
  840. onRefresh
  841. })
  842. ]),
  843. Vue.h('div', { class: 'body' }, [
  844. Vue.h(FolderContainer, { list: todoGroupList })
  845. ])
  846. ]
  847. })
  848. )
  849. }
  850. };
  851. // 公示公告
  852. export const Notice = {
  853. name: 'Notice',
  854. setup() {
  855. const list = Vue.reactive([
  856. { title: "关于2022年度中层干部的任免公告", time: "10/08 08:30" },
  857. { title: "2022年重组团队从心出发", time: "10/08 08:30" },
  858. { title: "关于单位进入粤港澳创新创业大赛决赛", time: "10/08 08:30" },
  859. { title: "关于2022年度中层干部的任免公告", time: "10/08 08:30" },
  860. ]);
  861. const winInfo = Vue.ref(loadWinSize(winName.Notice));
  862. const onSetting = () => {
  863. console.log("Clicked on settings button");
  864. };
  865. const onRefresh = () => {
  866. console.log("Clicked on refresh button");
  867. };
  868. const onSizeChange = (rect) => {
  869. const style = {};
  870. for (const key in rect) {
  871. style[key] = rect[key] + "px";
  872. }
  873. saveWinSize(winName.Notice, style);
  874. winInfo.value = style;
  875. };
  876. const SsIcon = Vue.resolveComponent('ss-icon');
  877. return () => Vue.h('div', { class: 'notice-list-container can-resize-box', style: winInfo.value }, [
  878. Vue.h(EditBox, { onSize: onSizeChange }, {
  879. default: () => [
  880. Vue.h('div', { class: 'header' }, [
  881. Vue.h(HeaderContainer, {
  882. title: "公示公告",
  883. icon: "notice",
  884. onSetting: onSetting,
  885. onRefresh: onRefresh
  886. })
  887. ]),
  888. Vue.h('div', { class: 'body' }, list.map((item, i) =>
  889. Vue.h('div', { key: i }, [
  890. Vue.h('div', [
  891. Vue.h(SsIcon, { name: "file", size: "22px" }),
  892. Vue.h('span', item.title)
  893. ]),
  894. Vue.h('div', item.time)
  895. ])
  896. ))
  897. ]
  898. })
  899. ]);
  900. }
  901. };
  902. // 快捷发起
  903. export const Launch = {
  904. name: 'Launch',
  905. setup() {
  906. const winInfo = Vue.ref(loadWinSize(winName.launch));
  907. const popupInfo = Vue.reactive({
  908. status: false,
  909. height: 100,
  910. width: 100,
  911. left: 0,
  912. top: 0,
  913. });
  914. const popupStyle = Vue.computed(() => {
  915. const { width, height, left, top } = popupInfo;
  916. return {
  917. left: `${left - (130 - width) / 2}px`,
  918. top: `${top + height}px`,
  919. };
  920. });
  921. const items = Vue.reactive([
  922. { name: "qingjia", text: "请假" },
  923. {
  924. name: "shoufukuan",
  925. text: "收付款",
  926. hasPopup: true,
  927. popupContent: [
  928. { text: "收款" },
  929. { text: "付款" }
  930. ]
  931. },
  932. {
  933. name: "kaoqin",
  934. text: "考勤",
  935. hasPopup: true,
  936. popupContent: [
  937. { text: "2" },
  938. { text: "3" }
  939. ]
  940. }
  941. ]);
  942. const onSetting = () => console.log("Clicked on settings button");
  943. const onRefresh = () => console.log("Clicked on refresh button");
  944. const onMouseMove = (e, item) => {
  945. console.log("鼠标进入了",item.text)
  946. showPopupDialog(e, item)
  947. };
  948. const onMouseleave = (e,item) => {
  949. console.log("鼠标离开了",item.text)
  950. popupInfo.status = false;
  951. };
  952. const showPopupDialog = (e, item) => {
  953. const dom = e.target.closest('.item');
  954. if (dom) {
  955. const rect = dom.getBoundingClientRect();
  956. popupInfo.height = rect.height;
  957. popupInfo.left = rect.left;
  958. popupInfo.top = rect.top;
  959. popupInfo.width = rect.width;
  960. setTimeout(() => {
  961. popupInfo.status = true;
  962. }, 600)
  963. }
  964. };
  965. const onSizeChange = (rect) => {
  966. const style = {};
  967. for (const key in rect) style[key] = rect[key] + "px";
  968. saveWinSize(winName.launch, style);
  969. winInfo.value = style;
  970. };
  971. // Vue.onMounted(() => console.log("LaunchContainer component is mounted"));
  972. const SsIcon = Vue.resolveComponent('ss-icon');
  973. return () => Vue.h('div', { class: 'launch-container can-resize-box', style: winInfo.value }, [
  974. Vue.h(EditBox, { onSize: onSizeChange }, {
  975. default: () => [
  976. Vue.h('div', { class: 'header' }, [
  977. Vue.h(HeaderContainer, { title: "快捷发起", icon: "lightning", onSetting, onRefresh })
  978. ]),
  979. Vue.h('div', { class: 'body' }, items.map(item =>
  980. Vue.h('div', { class: 'item', onMousemove: e => onMouseMove(e, item), onMouseleave: e => onMouseleave(e, item) }, [
  981. Vue.h(SsIcon, {
  982. class: item.hasPopup && item.popupContent.length > 0 ? "mark-down" : "",
  983. name: item.name,
  984. size: "36px"
  985. }),
  986. Vue.h('div', { class: 'text' }, item.text),
  987. item.hasPopup && popupInfo.status ? Vue.h('div', { class: 'popup', style: popupStyle.value }, item.popupContent.map(content =>
  988. Vue.h('div', content.text)
  989. )) : null
  990. ])
  991. ))
  992. ]
  993. })
  994. ])
  995. }
  996. };
  997. // 项目实时统计图
  998. export const Statistics = {
  999. name: 'Statistics',
  1000. setup() {
  1001. const winInfo = Vue.ref(loadWinSize(winName.Statistics));
  1002. const chartInstance = Vue.ref(null);
  1003. const option = {
  1004. tooltip: {
  1005. trigger: "axis",
  1006. axisPointer: {
  1007. type: "cross",
  1008. crossStyle: {
  1009. color: "#999",
  1010. },
  1011. },
  1012. },
  1013. legend: {
  1014. data: ["项目金额", "成本"]
  1015. },
  1016. xAxis: [{
  1017. type: "category",
  1018. data: ["2月", "3月", "4���", "5月", "6月", "7月", "8月", "9月", "10月", "11月"],
  1019. axisPointer: {
  1020. type: "shadow"
  1021. },
  1022. }],
  1023. yAxis: [{
  1024. type: "value",
  1025. name: "单位:万(RMB)",
  1026. min: 0,
  1027. max: 1000,
  1028. interval: 200,
  1029. axisLabel: {
  1030. formatter: "{value} "
  1031. },
  1032. }, {
  1033. type: "value",
  1034. name: "成本",
  1035. min: 0,
  1036. max: 1000,
  1037. interval: 200,
  1038. axisLabel: {
  1039. formatter: "{value}"
  1040. },
  1041. }],
  1042. series: [{
  1043. name: "项目金额",
  1044. type: "bar",
  1045. tooltip: {
  1046. valueFormatter: function (value) {
  1047. return value + " ml";
  1048. }
  1049. },
  1050. data: [250, 850, 500, 650, 450, 850, 780, 450, 700, 850]
  1051. }, {
  1052. name: "成本",
  1053. type: "line",
  1054. yAxisIndex: 1,
  1055. tooltip: {
  1056. valueFormatter: function (value) {
  1057. return value;
  1058. }
  1059. },
  1060. data: [400, 390, 580, 590, 430, 420, 430, 550, 230, 250]
  1061. }]
  1062. }
  1063. // 初始化echants
  1064. const initChart = () => {
  1065. const chartDom = document.getElementById("statistics-chart");
  1066. if (!chartDom) return;
  1067. chartInstance.value = echarts.init(chartDom);
  1068. chartInstance.value.setOption(option);
  1069. };
  1070. const onSetting = () => console.log("Clicked on settings button");
  1071. const onRefresh = () => {
  1072. console.log("Clicked on refresh button");
  1073. if (chartInstance.value) {
  1074. chartInstance.value.clear();
  1075. initChart(); // Reinitialize the chart to reflect any new data or settings
  1076. }
  1077. };
  1078. const onSizeChange = (rect) => {
  1079. const style = {};
  1080. for (const key in rect) style[key] = rect[key] + "px";
  1081. saveWinSize(winName.Statistics, style);
  1082. winInfo.value = style;
  1083. if (chartInstance.value) {
  1084. chartInstance.value.resize();
  1085. }
  1086. };
  1087. Vue.onMounted(() => {
  1088. // console.log("StatisticsContainer component is mounted");
  1089. initChart(); // Initialize the chart when component is mounted
  1090. });
  1091. // return { winInfo, onSetting, onRefresh, onSizeChange };
  1092. return () => Vue.h('div', { class: 'statistics-container can-resize-box', style: winInfo.value }, [
  1093. Vue.h(EditBox, { onSize: onSizeChange }, {
  1094. default: () => [
  1095. Vue.h('div', { class: 'header' }, [
  1096. Vue.h(HeaderContainer, { title: "项目实时统计图", icon: "layer", onSetting, onRefresh })
  1097. ]),
  1098. Vue.h('div', { class: 'body' }, [
  1099. Vue.h('div', { id: 'statistics-chart', class: 'chart-container' })
  1100. ])
  1101. ]
  1102. })
  1103. ]);
  1104. },
  1105. };