@utogether/udp-core 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (239) hide show
  1. package/build/plugins.ts +13 -2
  2. package/dist/{403-JWjatlxJ.js → 403-B1rIjAAu.js} +7 -6
  3. package/dist/{404-BcdMJfPb.js → 404-mBqc2y4t.js} +14 -13
  4. package/dist/{500-bsa3F_cc.js → 500-BoI45Zdh.js} +10 -9
  5. package/dist/{AuthorityInfo-CqItgNs5.js → AuthorityInfo-B08NBIIn.js} +1 -1
  6. package/dist/AuthorityInfo.vue_vue_type_style_index_0_lang-BLP1SaiH.js +100 -0
  7. package/dist/{AuthorityPanel-BaLMwMgW.js → AuthorityPanel-BeBNiwqc.js} +1 -1
  8. package/dist/{AuthorityPanel.vue_vue_type_style_index_0_lang-C_bkqLD9.js → AuthorityPanel.vue_vue_type_style_index_0_lang-CIYmnP9-.js} +6 -6
  9. package/dist/{Company-DVff9IA7.js → Company-JGGyWEWH.js} +3 -3
  10. package/dist/{CompanyPanel-CNrdq4XE.js → CompanyPanel-BQ_cCmDx.js} +16 -16
  11. package/dist/{Department-CqcpfL4i.js → Department-z2iO6hwM.js} +10 -10
  12. package/dist/{DepartmentPanel-Bvti4LGu.js → DepartmentPanel-BtQe7zwU.js} +108 -78
  13. package/dist/{DesignPanel-CjykspE1.js → DesignPanel-7mhtVWas.js} +1 -1
  14. package/dist/{DesignPanel.vue_vue_type_style_index_0_lang-BK9EERdd.js → DesignPanel.vue_vue_type_style_index_0_lang-4EsHemj_.js} +24 -26
  15. package/dist/DictView-CjchV2Yk.js +109 -0
  16. package/dist/InvOrganization-Pn1O_XP0.js +74 -0
  17. package/dist/Org-BqytV_vi.js +39 -0
  18. package/dist/{Preview-Cm_7RhYU.js → Preview-CLpUUMay.js} +2 -2
  19. package/dist/{ReportDefine-CJVlQ--7.js → ReportDefine-Cz1KtEUF.js} +1 -1
  20. package/dist/{ReportDesign-B0unlrkt.js → ReportDesign-BaORYud4.js} +46 -46
  21. package/dist/{ReportQuery-BKuMCEvF.js → ReportQuery-CPCPXiXz.js} +5 -5
  22. package/dist/{ReportQueryFrom-DAngMJLU.js → ReportQueryFrom-C_AcrfkJ.js} +1 -1
  23. package/dist/{ReportQueryFrom.vue_vue_type_style_index_0_lang-CPpwmztJ.js → ReportQueryFrom.vue_vue_type_style_index_0_lang-Bm67ejay.js} +6 -5
  24. package/dist/{ReportTemplate-8YH3L8Pv.js → ReportTemplate-CFiNMz79.js} +26 -26
  25. package/dist/{Role-MxI30-0W.js → Role-B-XDoJd5.js} +6 -6
  26. package/dist/{RoleAssign-BzUY_y_y.js → RoleAssign-BolW8YVs.js} +9 -9
  27. package/dist/{RolePanel-DS_TErTn.js → RolePanel-2kfs5tw9.js} +1 -1
  28. package/dist/{RolePanel-CXdcvsR5.js → RolePanel-HilSuYns.js} +1 -1
  29. package/dist/RolePanel.vue_vue_type_script_setup_true_lang-CE4gApUY.js +132 -0
  30. package/dist/{RolePanel.vue_vue_type_script_setup_true_lang-Ddl-A7Zh.js → RolePanel.vue_vue_type_script_setup_true_lang-DPzgfAyV.js} +42 -36
  31. package/dist/{ScrollPanel.vue_vue_type_style_index_0_lang-DnchUgIF.js → ScrollPanel.vue_vue_type_style_index_0_lang-CCTH4RkH.js} +20 -20
  32. package/dist/{Staff-D7tXo_Gn.js → Staff-BTk3whFC.js} +3 -3
  33. package/dist/{StaffInfo-Bk8BY8PO.js → StaffInfo-d3AuSzlA.js} +1 -1
  34. package/dist/{StaffInfo.vue_vue_type_script_setup_true_lang-DqKwZi5f.js → StaffInfo.vue_vue_type_script_setup_true_lang-eOL4VlfE.js} +13 -13
  35. package/dist/{StaffPanel-VtpGCTIq.js → StaffPanel-DV-D4jjz.js} +1 -1
  36. package/dist/StaffPanel.vue_vue_type_script_setup_true_lang-4sFq3CEo.js +135 -0
  37. package/dist/{SysUser-D0Q6OvD7.js → SysUser-BnjYytws.js} +2 -2
  38. package/dist/{SysUserPanel-1_vrsANQ.js → SysUserPanel-CBoyD-Qi.js} +1 -1
  39. package/dist/SysUserPanel.vue_vue_type_script_setup_true_lang-PrTlTZR-.js +341 -0
  40. package/dist/{SystemMenu-9PG3vESE.js → SystemMenu-kYB_ZaUt.js} +49 -47
  41. package/dist/{UserInfo-qgXUEGwi.js → UserInfo-4dx97VBL.js} +1 -1
  42. package/dist/{UserInfo.vue_vue_type_style_index_0_lang-CN0C5rVk.js → UserInfo.vue_vue_type_style_index_0_lang-BpbC_ZDm.js} +35 -33
  43. package/dist/{childView-C7ZSA5fR.js → childView-CHPNfTEb.js} +1 -1
  44. package/dist/{childView-yV2QLwfA.js → childView-CKA_JgVZ.js} +1 -1
  45. package/dist/{childView.vue_vue_type_style_index_0_lang-DdKxPGdh.js → childView.vue_vue_type_style_index_0_lang-Bym2fQRd.js} +7 -7
  46. package/dist/childView.vue_vue_type_style_index_0_lang-W7bCtXeu.js +177 -0
  47. package/dist/{code-rule-95K1jr-u.js → code-rule-CbxuZg0-.js} +42 -41
  48. package/dist/core.es.js +19 -10
  49. package/dist/{cron-task-DGrQwOHs.js → cron-task-nTOpqQYf.js} +5 -5
  50. package/dist/flow-task-B07st2aD.js +10 -0
  51. package/dist/{frameView-7SmME93D.js → frameView-Z1tPUyCh.js} +15 -14
  52. package/dist/img/l_img.svg +1 -1
  53. package/dist/img/minicolors.png +0 -0
  54. package/dist/img/v_img.svg +1 -1
  55. package/dist/index-C3q8HoJM.js +4650 -0
  56. package/dist/{layoutView-C6WIVWGZ.js → layoutView--MGA9zUB.js} +1776 -1764
  57. package/dist/{log-in-e7D5Ss1P.js → log-in-CSYJDA6m.js} +36 -29
  58. package/dist/log-out-DiwGCg7p.js +130 -0
  59. package/dist/login-C6Y0ajDp.js +251 -0
  60. package/dist/{login-log-CvVnyGi3.js → login-log-C0V-_l3F.js} +6 -4
  61. package/dist/{lov-view-DoF5LqFQ.js → lov-view-Cmv7wZZ9.js} +9 -9
  62. package/dist/{menuInfo-CNzGQwOD.js → menuInfo-UeutJpOa.js} +1 -1
  63. package/dist/{menuInfo.vue_vue_type_style_index_0_lang-51SYxVc_.js → menuInfo.vue_vue_type_style_index_0_lang-CWX4Mu67.js} +144 -121
  64. package/dist/pda-app-B6w99SJo.js +710 -0
  65. package/dist/redirect-BqegffKC.js +15 -0
  66. package/dist/{resource-gVFFMO9l.js → resource-BybJvUv0.js} +17 -17
  67. package/dist/{su-welcome-BN_s_RX_.js → su-welcome-C1bmxHoY.js} +124 -126
  68. package/dist/sys-config-BnmIDnCj.js +370 -0
  69. package/dist/udp-core.css +1 -9
  70. package/dist/utogether-MlnyYtNS.js +4 -0
  71. package/index.ts +19 -6
  72. package/package.json +18 -17
  73. package/src/App.vue +2 -7
  74. package/src/api/http.ts +1 -4
  75. package/src/api/index.ts +5 -3
  76. package/src/api/user.ts +2 -2
  77. package/src/components/SuCharts/src/UserInfo.vue +3 -3
  78. package/src/components/SuScrollTree/ScrollPanel.vue +4 -9
  79. package/src/components/udp/content/index.vue +88 -0
  80. package/src/components/udp/form/form.vue +109 -0
  81. package/src/components/udp/grid/index.vue +524 -0
  82. package/src/components/udp/index.ts +5 -4
  83. package/src/components/udp/ut-stamp-badge/index.vue +271 -0
  84. package/src/components/udp/utils.ts +408 -40
  85. package/src/directives/permission/index.ts +1 -1
  86. package/src/layout/components/lay-navbar/index.vue +9 -7
  87. package/src/layout/components/lay-panel/index.vue +3 -3
  88. package/src/layout/components/lay-search/index.vue +1 -1
  89. package/src/layout/components/lay-select-org/index.vue +4 -9
  90. package/src/layout/components/lay-setting/index.vue +503 -510
  91. package/src/layout/components/lay-sidebar/breadCrumb.vue +1 -1
  92. package/src/layout/components/lay-sidebar/horizontal.vue +8 -6
  93. package/src/layout/components/lay-sidebar/mixNav.vue +260 -258
  94. package/src/layout/components/lay-sidebar/sidebar-logo.vue +101 -98
  95. package/src/layout/components/lay-tag/index.vue +598 -625
  96. package/src/layout/hooks/useDataThemeChange.ts +1 -1
  97. package/src/layout/hooks/useNav.ts +176 -173
  98. package/src/layout/hooks/useTag.ts +227 -233
  99. package/src/layout/layoutView.vue +215 -215
  100. package/src/layout/types.ts +93 -92
  101. package/src/main.ts +115 -109
  102. package/src/plugins/i18n/en.ts +26 -13
  103. package/src/plugins/i18n/module/u-workflow.ts +1 -1
  104. package/src/plugins/i18n/zh.ts +363 -337
  105. package/src/plugins/vxe-table/index.ts +74 -4
  106. package/src/plugins/vxe-table/render.tsx +186 -58
  107. package/src/router/index.ts +187 -183
  108. package/src/router/modules/flow.ts +35 -0
  109. package/src/router/modules/home.ts +32 -32
  110. package/src/router/modules/remaining.ts +1 -26
  111. package/src/router/utils.ts +420 -377
  112. package/src/store/modules/app.ts +2 -4
  113. package/src/store/modules/epTheme.ts +48 -49
  114. package/src/store/modules/multiTags.ts +15 -14
  115. package/src/store/modules/permission.ts +25 -15
  116. package/src/store/modules/system.ts +1 -3
  117. package/src/style/button.scss +11 -4
  118. package/src/style/login.css +1 -1
  119. package/src/style/tailwind.css +1 -68
  120. package/src/style/vxetable.scss +103 -11
  121. package/src/utils/authority/index.ts +1 -1
  122. package/src/utils/dataFormat/index.ts +223 -223
  123. package/src/utils/index.ts +3 -1
  124. package/src/utils/lifecycle.ts +39 -20
  125. package/src/utils/propTypes.ts +1 -6
  126. package/src/utils/storage/index.ts +2 -2
  127. package/src/utils/{http → udp/http}/index.ts +27 -30
  128. package/src/utils/{http → udp/http}/types.d.ts +2 -6
  129. package/src/views/login/login-view.vue +6 -20
  130. package/src/views/organization/company/CompanyPanel.vue +2 -2
  131. package/src/views/organization/department/Department.vue +58 -58
  132. package/src/views/organization/department/DepartmentPanel.vue +303 -283
  133. package/src/views/organization/inv-org/InvOrganization.vue +23 -9
  134. package/src/views/organization/org/Org.vue +9 -5
  135. package/src/views/organization/staff/StaffInfo.vue +127 -133
  136. package/src/views/organization/staff/StaffPanel.vue +162 -145
  137. package/src/views/system/cron/cron-task.vue +2 -12
  138. package/src/views/system/menu/AuthorityPanel.vue +2 -2
  139. package/src/views/system/menu/SystemMenu.vue +14 -20
  140. package/src/views/system/menu/menuInfo.vue +39 -23
  141. package/src/views/system/role/AuthorityInfo.vue +19 -15
  142. package/src/views/system/role/Role.vue +1 -5
  143. package/src/views/system/role/RolePanel.vue +11 -2
  144. package/src/views/system/role/UserInfo.vue +11 -9
  145. package/src/views/system/role-assign/RoleAssign.vue +2 -2
  146. package/src/views/system/role-assign/RolePanel.vue +12 -9
  147. package/src/views/system/sys/sys-config.vue +70 -21
  148. package/src/views/system/sysUser/SysUserPanel.vue +347 -278
  149. package/src/views/uapp/pda/pda-app.vue +48 -16
  150. package/src/views/udev/coderule/code-rule.vue +132 -121
  151. package/src/views/udev/dict/DictView.vue +118 -106
  152. package/src/views/udev/dict/childView.vue +183 -222
  153. package/src/views/udev/lov/childView.vue +1 -7
  154. package/src/views/ufile/aggregation/File.vue +5 -5
  155. package/src/views/ufile/file/water-mark.vue +14 -14
  156. package/src/views/uhome/components/menu-favorite.vue +314 -331
  157. package/src/views/uhome/su-welcome.vue +319 -339
  158. package/src/views/ulogin/login.vue +325 -316
  159. package/src/views/upms/interface/log-in.vue +100 -106
  160. package/src/views/upms/interface/log-out.vue +104 -107
  161. package/src/views/upms/user/login-log.vue +54 -60
  162. package/src/views/urpt/design/DesignPanel.vue +16 -35
  163. package/src/views/urpt/design/Preview.vue +1 -0
  164. package/src/views/urpt/design/ReportDesign.vue +17 -23
  165. package/src/views/urpt/static-resource/resource.vue +3 -3
  166. package/src/views/urpt/template/ReportTemplate.vue +7 -7
  167. package/src/views/utask/flow-task.vue +18 -0
  168. package/types/global.d.ts +231 -236
  169. package/vite.config.ts +13 -2
  170. package/dist/AuthorityInfo.vue_vue_type_style_index_0_lang-32L40GY2.js +0 -102
  171. package/dist/DictView-q7kR9K--.js +0 -95
  172. package/dist/InvOrganization-DI45LqZV.js +0 -260
  173. package/dist/Org-CZju_ZiR.js +0 -35
  174. package/dist/RolePanel.vue_vue_type_script_setup_true_lang-DJKu6PXz.js +0 -126
  175. package/dist/StaffPanel.vue_vue_type_script_setup_true_lang-C3Ixs12y.js +0 -111
  176. package/dist/SysUserPanel.vue_vue_type_script_setup_true_lang-C5XEmxsV.js +0 -288
  177. package/dist/childView.vue_vue_type_style_index_0_lang-bkmucBUL.js +0 -187
  178. package/dist/core.umd.js +0 -173
  179. package/dist/index-OswH7SsT.js +0 -9937
  180. package/dist/log-out--RRncZhN.js +0 -120
  181. package/dist/login-BhCMeCLS.js +0 -251
  182. package/dist/pda-app-nn3llDUx.js +0 -2209
  183. package/dist/redirect-CmMplDV4.js +0 -15
  184. package/dist/sys-config-DGutV-VX.js +0 -277
  185. package/dist/utogether-wFDCI28t.js +0 -182
  186. package/src/assets/images/empty.png +0 -0
  187. package/src/assets/images/logo.png +0 -0
  188. package/src/components/ReCountTo/README.md +0 -2
  189. package/src/components/ReCountTo/index.ts +0 -18
  190. package/src/components/ReCountTo/src/normal/index.tsx +0 -165
  191. package/src/components/ReCountTo/src/normal/props.ts +0 -37
  192. package/src/components/ReCountTo/src/rebound/index.tsx +0 -67
  193. package/src/components/ReCountTo/src/rebound/props.ts +0 -14
  194. package/src/components/ReCountTo/src/rebound/rebound.css +0 -77
  195. package/src/components/ReCropper/index.ts +0 -14
  196. package/src/components/ReCropper/src/index.tsx +0 -141
  197. package/src/components/ReFlicker/index.css +0 -39
  198. package/src/components/ReFlicker/index.ts +0 -50
  199. package/src/components/ReFlop/index.ts +0 -14
  200. package/src/components/ReFlop/src/Filpper.tsx +0 -99
  201. package/src/components/ReFlop/src/filpper.css +0 -184
  202. package/src/components/ReFlop/src/index.vue +0 -126
  203. package/src/components/ReFlowChart/index.ts +0 -24
  204. package/src/components/ReFlowChart/src/Control.vue +0 -139
  205. package/src/components/ReFlowChart/src/DataDialog.vue +0 -12
  206. package/src/components/ReFlowChart/src/NodePanel.vue +0 -151
  207. package/src/components/ReFlowChart/src/adpterForTurbo.ts +0 -160
  208. package/src/components/ReFlowChart/src/assets/iconfont/iconfont.css +0 -49
  209. package/src/components/ReFlowChart/src/assets/iconfont/iconfont.eot +0 -0
  210. package/src/components/ReFlowChart/src/assets/iconfont/iconfont.js +0 -61
  211. package/src/components/ReFlowChart/src/assets/iconfont/iconfont.json +0 -58
  212. package/src/components/ReFlowChart/src/assets/iconfont/iconfont.svg +0 -47
  213. package/src/components/ReFlowChart/src/assets/iconfont/iconfont.ttf +0 -0
  214. package/src/components/ReFlowChart/src/assets/iconfont/iconfont.woff +0 -0
  215. package/src/components/ReFlowChart/src/assets/iconfont/iconfont.woff2 +0 -0
  216. package/src/components/ReFlowChart/src/config.ts +0 -62
  217. package/src/components/ReSplitPane/index.css +0 -49
  218. package/src/components/ReSplitPane/index.tsx +0 -119
  219. package/src/components/ReSplitPane/resizer.css +0 -45
  220. package/src/components/ReSplitPane/resizer.tsx +0 -30
  221. package/src/components/SuCommon/card/components/Card.vue +0 -148
  222. package/src/components/SuCommon/card/components/DialogForm.vue +0 -127
  223. package/src/components/SuCommon/card/index.vue +0 -142
  224. package/src/components/SuCommon/icon-select/index.vue +0 -241
  225. package/src/components/SuCommon/table/index.vue +0 -208
  226. package/src/components/SuCommon/utils/index.ts +0 -103
  227. package/src/components/SuCommon/utils/slot.tsx +0 -50
  228. package/src/components/udp/form-upload.vue +0 -132
  229. package/src/components/udp/modal-form.vue +0 -180
  230. package/src/utils/udp/useRender.ts +0 -420
  231. package/src/views/components/contextmenu/basic.vue +0 -74
  232. package/src/views/components/contextmenu/context-menu.vue +0 -40
  233. package/src/views/components/contextmenu/menuDynamic.vue +0 -99
  234. package/src/views/components/contextmenu/menuGroup.vue +0 -71
  235. package/src/views/components/count-to/index.vue +0 -43
  236. package/src/views/components/cropping/index.vue +0 -59
  237. package/src/views/components/cropping/picture.jpeg +0 -0
  238. package/src/views/components/split-pane/index.vue +0 -82
  239. package/src/views/components/video/index.vue +0 -57
@@ -1,625 +1,598 @@
1
- <script lang="ts">
2
- export default { name: 'LayoutTag' };
3
- </script>
4
- <script setup lang="ts">
5
- import { ref, watch, unref, nextTick, onBeforeUnmount } from 'vue';
6
- import { isEqual, isEmpty } from 'xe-utils';
7
- import { useResizeObserver, useDebounceFn, useFullscreen, onClickOutside } from '@vueuse/core';
8
- import { delay } from '@utogether/utils';
9
- import { emitter } from '../../../utils/mitt';
10
- import { routerArrays } from '../../types';
11
- import { useSettingStoreHook } from '../../../store/modules/settings';
12
- import { handleAliveRoute } from '../../../router/utils';
13
- import { useMultiTagsStoreHook } from '../../../store/modules/multiTags';
14
- import { usePermissionStoreHook } from '../../../store/modules/permission';
15
- import { useTags } from '../../hooks/useTag';
16
- import { RouteConfigs } from '../../types';
17
- import TagChrome from '../lay-chrome/index.vue';
18
-
19
- const {
20
- route,
21
- router,
22
- visible,
23
- showTags,
24
- instance,
25
- multiTags,
26
- tagsViews,
27
- buttonTop,
28
- buttonLeft,
29
- showModel,
30
- translateX,
31
- isFixedTag,
32
- SuSetting,
33
- activeIndex,
34
- getTabStyle,
35
- iconIsActive,
36
- linkIsActive,
37
- currentSelect,
38
- scheduleIsActive,
39
- getContextMenuStyle,
40
- closeMenu,
41
- onMounted,
42
- onMouseenter,
43
- onMouseleave,
44
- transformI18n,
45
- onContentFullScreen
46
- } = useTags();
47
-
48
- const suSetting = useSettingStoreHook();
49
- const tabDom = ref();
50
- const containerDom = ref();
51
- const scrollbarDom = ref();
52
- const contextmenuRef = ref();
53
- const isShowArrow = ref(false);
54
- const { isFullscreen, toggle } = useFullscreen();
55
-
56
- const fixedTags = [
57
- ...routerArrays,
58
- ...usePermissionStoreHook().flatteningRoutes.filter(v => v?.meta?.fixedTag)
59
- ];
60
- const dynamicTagView = async () => {
61
- await nextTick();
62
- const index = multiTags.value.findIndex(item => {
63
- if (!isEmpty(route.query)) {
64
- return isEqual(route.query, item.query);
65
- } else if (!isEmpty(route.params)) {
66
- return isEqual(route.params, item.params);
67
- } else {
68
- return route.path === item.path;
69
- }
70
- });
71
- moveToView(index);
72
- };
73
-
74
- const moveToView = async (index: number): Promise<void> => {
75
- await nextTick();
76
- const tabNavPadding = 10;
77
- if (!instance.refs['dynamic' + index]) return;
78
- const tabItemEl = instance.refs['dynamic' + index][0];
79
- const tabItemElOffsetLeft = (tabItemEl as HTMLElement)?.offsetLeft;
80
- const tabItemOffsetWidth = (tabItemEl as HTMLElement)?.offsetWidth;
81
- // 标签页导航栏可视长度(不包含溢出部分)
82
- const scrollbarDomWidth = scrollbarDom.value ? scrollbarDom.value.offsetWidth : 0;
83
- // 已有标签页总长度(包含溢出部分)
84
- const tabDomWidth = tabDom.value ? tabDom.value?.offsetWidth : 0;
85
- scrollbarDomWidth <= tabDomWidth ? (isShowArrow.value = true) : (isShowArrow.value = false);
86
- if (tabDomWidth < scrollbarDomWidth || tabItemElOffsetLeft === 0) {
87
- translateX.value = 0;
88
- } else if (tabItemElOffsetLeft < -translateX.value) {
89
- // 标签在可视区域左侧
90
- translateX.value = -tabItemElOffsetLeft + tabNavPadding;
91
- } else if (
92
- tabItemElOffsetLeft > -translateX.value &&
93
- tabItemElOffsetLeft + tabItemOffsetWidth < -translateX.value + scrollbarDomWidth
94
- ) {
95
- // 标签在可视区域
96
- translateX.value = Math.min(
97
- 0,
98
- scrollbarDomWidth - tabItemOffsetWidth - tabItemElOffsetLeft - tabNavPadding
99
- );
100
- } else {
101
- // 标签在可视区域右侧
102
- translateX.value = -(tabItemElOffsetLeft - (scrollbarDomWidth - tabNavPadding - tabItemOffsetWidth));
103
- }
104
- };
105
-
106
- const handleScroll = (offset: number): void => {
107
- const scrollbarDomWidth = scrollbarDom.value ? scrollbarDom.value?.offsetWidth : 0;
108
- const tabDomWidth = tabDom.value ? tabDom.value.offsetWidth : 0;
109
- if (offset > 0) {
110
- translateX.value = Math.min(0, translateX.value + offset);
111
- } else {
112
- if (scrollbarDomWidth < tabDomWidth) {
113
- if (translateX.value >= -(tabDomWidth - scrollbarDomWidth)) {
114
- translateX.value = Math.max(translateX.value + offset, scrollbarDomWidth - tabDomWidth);
115
- }
116
- } else {
117
- translateX.value = 0;
118
- }
119
- }
120
- };
121
-
122
- function dynamicRouteTag(value: string): void {
123
- const hasValue = multiTags.value.some(item => {
124
- return item.path === value;
125
- });
126
-
127
- function concatPath(arr: object[], value: string) {
128
- if (!hasValue) {
129
- arr.forEach((arrItem: any) => {
130
- if (arrItem.path === value) {
131
- useMultiTagsStoreHook().handleTags('push', {
132
- path: value,
133
- meta: arrItem.meta,
134
- name: arrItem.name
135
- });
136
- } else {
137
- if (arrItem.children && arrItem.children.length > 0) {
138
- concatPath(arrItem.children, value);
139
- }
140
- }
141
- });
142
- }
143
- }
144
- concatPath(router.options.routes as any, value);
145
- }
146
-
147
- /** 刷新路由 */
148
- function onFresh() {
149
- const { fullPath, query } = unref(route);
150
- router.replace({
151
- path: '/redirect' + fullPath,
152
- query: query
153
- });
154
- handleAliveRoute(route as ToRouteType, 'refresh');
155
- }
156
-
157
- function deleteDynamicTag(obj: any, current: any, tag?: string) {
158
- const valueIndex: number = multiTags.value.findIndex((item: any) => {
159
- if (item.query) {
160
- if (item.path === obj.path) {
161
- return item.query === obj.query;
162
- }
163
- } else if (item.params) {
164
- if (item.path === obj.path) {
165
- return item.params === obj.params;
166
- }
167
- } else {
168
- return item.path === obj.path;
169
- }
170
- });
171
-
172
- const spliceRoute = (startIndex?: number, length?: number, other?: boolean): void => {
173
- if (other) {
174
- useMultiTagsStoreHook().handleTags('equal', [fixedTags, obj]);
175
- } else {
176
- useMultiTagsStoreHook().handleTags('splice', '', {
177
- startIndex,
178
- length
179
- }) as any;
180
- }
181
- dynamicTagView();
182
- };
183
-
184
- if (tag === 'other') {
185
- spliceRoute(1, 1, true);
186
- } else if (tag === 'left') {
187
- spliceRoute(fixedTags.length, valueIndex - 1);
188
- } else if (tag === 'right') {
189
- spliceRoute(valueIndex + 1, multiTags.value.length);
190
- } else {
191
- // 从当前匹配到的路径中删除
192
- spliceRoute(valueIndex, 1);
193
- }
194
- const newRoute = useMultiTagsStoreHook().handleTags('slice');
195
- if (current === route.path) {
196
- // 如果删除当前激活tag就自动切换到最后一个tag
197
- if (tag === 'left') return;
198
- if (newRoute[0]?.query) {
199
- router.push({ name: newRoute[0].name, query: newRoute[0].query });
200
- } else if (newRoute[0]?.params) {
201
- router.push({ name: newRoute[0].name, params: newRoute[0].params });
202
- } else {
203
- router.push({ path: newRoute[0].path });
204
- }
205
- } else {
206
- if (!multiTags.value.length) return;
207
- if (multiTags.value.some(item => item.path === route.path)) return;
208
- if (newRoute[0]?.query) {
209
- router.push({ name: newRoute[0].name, query: newRoute[0].query });
210
- } else if (newRoute[0]?.params) {
211
- router.push({ name: newRoute[0].name, params: newRoute[0].params });
212
- } else {
213
- router.push({ path: newRoute[0].path });
214
- }
215
- }
216
- }
217
-
218
- function deleteMenu(item, tag?: string) {
219
- deleteDynamicTag(item, item.path, tag);
220
- handleAliveRoute(route as ToRouteType);
221
- }
222
-
223
- function onClickDrop(key, item, selectRoute?: RouteConfigs) {
224
- if (item && item.disabled) return;
225
- let selectTagRoute;
226
- if (selectRoute) {
227
- selectTagRoute = {
228
- path: selectRoute.path,
229
- meta: selectRoute.meta,
230
- name: selectRoute.name,
231
- query: selectRoute?.query,
232
- params: selectRoute?.params
233
- };
234
- } else {
235
- selectTagRoute = { path: route.path, meta: route.meta };
236
- }
237
-
238
- // 当前路由信息
239
- switch (key) {
240
- case 0:
241
- // 刷新路由
242
- onFresh();
243
- break;
244
- case 1:
245
- // 关闭当前标签页
246
- deleteMenu(selectTagRoute);
247
- break;
248
- case 2:
249
- // 关闭左侧标签页
250
- deleteMenu(selectTagRoute, 'left');
251
- break;
252
- case 3:
253
- // 关闭右侧标签页
254
- deleteMenu(selectTagRoute, 'right');
255
- break;
256
- case 4:
257
- // 关闭其他标签页
258
- deleteMenu(selectTagRoute, 'other');
259
- break;
260
- case 5:
261
- // 关闭全部标签页
262
- useMultiTagsStoreHook().handleTags('splice', '', {
263
- startIndex: fixedTags.length,
264
- length: multiTags.value.length
265
- });
266
- router.push('/welcome');
267
- handleAliveRoute(route as ToRouteType);
268
- break;
269
- case 6:
270
- // 整体页面全屏
271
- toggle();
272
- setTimeout(() => {
273
- if (isFullscreen.value) {
274
- tagsViews[6].icon = 'ri:fullscreen-exit-fill';
275
- tagsViews[6].text = 'message.btn.wholeExitFullScreen';
276
- } else {
277
- tagsViews[6].icon = 'ri:fullscreen-fill';
278
- tagsViews[6].text = 'message.btn.wholeFullScreen';
279
- }
280
- }, 100);
281
- break;
282
- case 7:
283
- // 内容区全屏
284
- onContentFullScreen();
285
- setTimeout(() => {
286
- if (SuSetting.hiddenSideBar) {
287
- tagsViews[7].icon = 'ri:fullscreen-exit-fill';
288
- tagsViews[7].text = 'message.btn.contentExitFullScreen';
289
- } else {
290
- tagsViews[7].icon = 'ri:fullscreen-fill';
291
- tagsViews[7].text = 'message.btn.contentFullScreen';
292
- }
293
- }, 100);
294
- break;
295
- }
296
- setTimeout(() => {
297
- showMenuModel(route.fullPath, route.query);
298
- });
299
- }
300
-
301
- function handleCommand(command: any) {
302
- const { key, item } = command;
303
- onClickDrop(key, item);
304
- }
305
-
306
- /** 触发右键中菜单的点击事件 */
307
- function selectTag(key, item) {
308
- closeMenu();
309
- onClickDrop(key, item, currentSelect.value);
310
- }
311
-
312
- function showMenus(value: boolean) {
313
- Array.of(1, 2, 3, 4, 5).forEach(v => {
314
- tagsViews[v].show = value;
315
- });
316
- }
317
-
318
- function disabledMenus(value: boolean, fixedTag = false) {
319
- Array.of(1, 2, 3, 4, 5).forEach(v => {
320
- tagsViews[v].disabled = value;
321
- });
322
- if (fixedTag) {
323
- tagsViews[2].show = false;
324
- tagsViews[2].disabled = true;
325
- }
326
- }
327
-
328
- /** 检查当前右键的菜单两边是否存在别的菜单,如果左侧的菜单是顶级菜单,则不显示关闭左侧标签页,如果右侧没有菜单,则不显示关闭右侧标签页 */
329
- function showMenuModel(currentPath: string, query: object = {}, refresh = false) {
330
- const allRoute = multiTags.value;
331
- const routeLength = multiTags.value.length;
332
- let currentIndex = -1;
333
- if (isEmpty(query)) {
334
- currentIndex = allRoute.findIndex(v => v.path === currentPath);
335
- } else {
336
- currentIndex = allRoute.findIndex(v => isEqual(v.query, query));
337
- }
338
-
339
- function fixedTagDisabled() {
340
- if (allRoute[currentIndex]?.meta?.fixedTag) {
341
- Array.of(1, 2, 3, 4, 5).forEach(v => {
342
- tagsViews[v].disabled = true;
343
- });
344
- }
345
- }
346
-
347
- showMenus(true);
348
-
349
- if (refresh) {
350
- tagsViews[0].show = true;
351
- }
352
-
353
- /**
354
- * currentIndex为1时,左侧的菜单顶级菜单,则不显示关闭左侧标签页
355
- * 如果currentIndex等于routeLength-1,右侧没有菜单,则不显示关闭右侧标签页
356
- */
357
- if (currentIndex === 1 && routeLength !== 2) {
358
- // 左侧的菜单是顶级菜单,右侧存在别的菜单
359
- tagsViews[2].show = false;
360
- Array.of(1, 3, 4, 5).forEach(v => {
361
- tagsViews[v].disabled = false;
362
- });
363
- tagsViews[2].disabled = true;
364
- fixedTagDisabled();
365
- } else if (currentIndex === 1 && routeLength === 2) {
366
- disabledMenus(false);
367
- // 左侧的菜单是顶级菜单,右侧不存在别的菜单
368
- Array.of(2, 3, 4).forEach(v => {
369
- tagsViews[v].show = false;
370
- tagsViews[v].disabled = true;
371
- });
372
- fixedTagDisabled();
373
- } else if (routeLength - 1 === currentIndex && currentIndex !== 0) {
374
- // 当前路由是所有路由中的最后一个
375
- tagsViews[3].show = false;
376
- Array.of(1, 2, 4, 5).forEach(v => {
377
- tagsViews[v].disabled = false;
378
- });
379
- tagsViews[3].disabled = true;
380
- if (allRoute[currentIndex - 1]?.meta?.fixedTag) {
381
- tagsViews[2].show = false;
382
- tagsViews[2].disabled = true;
383
- }
384
- fixedTagDisabled();
385
- } else if (currentIndex === 0 || currentPath === '/redirect/welcome') {
386
- // 当前路由为首页
387
- disabledMenus(true);
388
- } else {
389
- disabledMenus(false, allRoute[currentIndex - 1]?.meta?.fixedTag);
390
- fixedTagDisabled();
391
- }
392
- }
393
-
394
- function openMenu(tag, e) {
395
- closeMenu();
396
- if (tag.path === '/welcome' || tag?.meta?.fixedTag) {
397
- // 右键菜单为首页或拥有 fixedTag 属性,只显示刷新
398
- showMenus(false);
399
- tagsViews[0].show = true;
400
- } else if (route.path !== tag.path && route.name !== tag.name) {
401
- // 右键菜单不匹配当前路由,隐藏刷新
402
- tagsViews[0].show = false;
403
- showMenuModel(tag.path, tag.query);
404
- } else if (multiTags.value.length === 2 && route.path !== tag.path) {
405
- showMenus(true);
406
- // 只有两个标签时不显示关闭其他标签页
407
- tagsViews[4].show = false;
408
- } else if (route.path === tag.path) {
409
- // 右键当前激活的菜单
410
- showMenuModel(tag.path, tag.query, true);
411
- }
412
-
413
- currentSelect.value = tag;
414
- const menuMinWidth = 140;
415
- const offsetLeft = unref(containerDom).getBoundingClientRect().left;
416
- const offsetWidth = unref(containerDom).offsetWidth;
417
- const maxLeft = offsetWidth - menuMinWidth;
418
- const left = e.clientX - offsetLeft + 5;
419
- if (left > maxLeft) {
420
- buttonLeft.value = maxLeft;
421
- } else {
422
- buttonLeft.value = left;
423
- }
424
- suSetting.hiddenSideBar ? (buttonTop.value = e.clientY) : (buttonTop.value = e.clientY - 40);
425
- nextTick(() => {
426
- visible.value = true;
427
- });
428
- }
429
-
430
- /** 触发tags标签切换 */
431
- function tagOnClick(item) {
432
- const { name, path } = item;
433
- if (name) {
434
- if (item.query) {
435
- router.push({
436
- name,
437
- query: item.query
438
- });
439
- } else if (item.params) {
440
- router.push({
441
- name,
442
- params: item.params
443
- });
444
- } else {
445
- router.push({ name });
446
- }
447
- } else {
448
- router.push({ path });
449
- }
450
- // showMenuModel(item?.path, item?.query);
451
- emitter.emit('tagOnClick', item);
452
- }
453
-
454
- onClickOutside(contextmenuRef, closeMenu, {
455
- detectIframe: true
456
- });
457
-
458
- watch(route, () => {
459
- activeIndex.value = -1;
460
- dynamicTagView();
461
- });
462
-
463
- watch(isFullscreen, () => {
464
- tagsViews[6].icon = 'ri:fullscreen-fill';
465
- tagsViews[6].text = 'message.wholeFullScreen';
466
- });
467
-
468
- onMounted(() => {
469
- if (!instance) return;
470
-
471
- // 根据当前路由初始化操作标签页的禁用状态
472
- showMenuModel(route.fullPath);
473
-
474
- // 触发隐藏标签页
475
- emitter.on('tagViewsChange', (key: any) => {
476
- if (unref(showTags as any) === key) return;
477
- (showTags as any).value = key;
478
- });
479
-
480
- // 改变标签风格
481
- emitter.on('tagViewsShowModel', key => {
482
- showModel.value = key;
483
- });
484
-
485
- // 接收侧边栏切换传递过来的参数
486
- emitter.on('changLayoutRoute', indexPath => {
487
- dynamicRouteTag(indexPath);
488
- setTimeout(() => {
489
- showMenuModel(indexPath);
490
- });
491
- });
492
-
493
- useResizeObserver(
494
- scrollbarDom,
495
- useDebounceFn(() => {
496
- dynamicTagView();
497
- }, 200)
498
- );
499
- delay().then(() => dynamicTagView());
500
- });
501
-
502
- onBeforeUnmount(() => {
503
- // 解绑`tagViewsChange`、`tagViewsShowModel`、`changLayoutRoute`公共事件,防止多次触发
504
- emitter.off('tagViewsChange');
505
- emitter.off('tagViewsShowModel');
506
- emitter.off('changLayoutRoute');
507
- });
508
- </script>
509
-
510
- <template>
511
- <div v-if="!showTags" ref="containerDom" class="tags-view">
512
- <span v-show="isShowArrow" class="arrow-left">
513
- <IconifyIconOffline icon="ri:arrow-left-s-line" @click="handleScroll(200)" />
514
- </span>
515
- <div
516
- ref="scrollbarDom"
517
- class="scroll-container"
518
- :class="showModel === 'chrome' && 'chrome-scroll-container'"
519
- >
520
- <div ref="tabDom" class="select-none tab" :style="getTabStyle">
521
- <div
522
- v-for="(item, index) in multiTags"
523
- :key="index"
524
- :ref="'dynamic' + index"
525
- :class="[
526
- 'scroll-item is-closable',
527
- linkIsActive(item),
528
- showModel === 'chrome' && 'chrome-item',
529
- isFixedTag(item) && 'fixed-tag'
530
- ]"
531
- @contextmenu.prevent="openMenu(item, $event)"
532
- @mouseenter.prevent="onMouseenter(index)"
533
- @mouseleave.prevent="onMouseleave(index)"
534
- @click="tagOnClick(item)"
535
- >
536
- <template v-if="showModel !== 'chrome'">
537
- <span class="tag-title dark:text-text_color_primary! dark:hover:text-primary!">
538
- {{ transformI18n(item.meta.title) }}
539
- </span>
540
- <span
541
- v-if="
542
- isFixedTag(item)
543
- ? false
544
- : iconIsActive(item, index) || (index === activeIndex && index !== 0)
545
- "
546
- class="el-icon-close"
547
- @click.stop="deleteMenu(item)"
548
- >
549
- <IconifyIconOffline icon="ri:close-fill" />
550
- </span>
551
- <span
552
- v-if="showModel !== 'card'"
553
- :ref="'schedule' + index"
554
- :class="[scheduleIsActive(item)]"
555
- />
556
- </template>
557
- <div v-else class="chrome-tab">
558
- <div class="chrome-tab__bg">
559
- <TagChrome />
560
- </div>
561
- <span class="tag-title">
562
- {{ transformI18n(item.meta.title) }}
563
- </span>
564
- <span
565
- v-if="isFixedTag(item) ? false : index !== 0"
566
- class="chrome-close-btn"
567
- @click.stop="deleteMenu(item)"
568
- >
569
- <IconifyIconOffline icon="ri:close-fill" />
570
- </span>
571
- <span class="chrome-tab-divider" />
572
- </div>
573
- </div>
574
- </div>
575
- </div>
576
- <span v-show="isShowArrow" class="arrow-right">
577
- <IconifyIconOffline icon="ri:arrow-right-s-line" @click="handleScroll(-200)" />
578
- </span>
579
- <!-- 右键菜单按钮 -->
580
- <transition name="el-zoom-in-top">
581
- <ul
582
- v-show="visible"
583
- ref="contextmenuRef"
584
- :key="Math.random()"
585
- :style="getContextMenuStyle"
586
- class="contextmenu"
587
- >
588
- <div
589
- v-for="(item, key) in tagsViews.slice(0, 6)"
590
- :key="key"
591
- style="display: flex; align-items: center"
592
- >
593
- <li v-if="item.show" @click="selectTag(key, item)">
594
- <IconifyIconOffline :icon="item.icon" />
595
- {{ $t(item.text) }}
596
- </li>
597
- </div>
598
- </ul>
599
- </transition>
600
- <!-- 右侧功能按钮 -->
601
- <el-dropdown trigger="click" placement="bottom-end" @command="handleCommand">
602
- <span class="arrow-down">
603
- <IconifyIconOffline icon="ri:arrow-down-s-line" class="dark:text-white" />
604
- </span>
605
- <template #dropdown>
606
- <el-dropdown-menu>
607
- <el-dropdown-item
608
- v-for="(item, key) in tagsViews"
609
- :key="key"
610
- :command="{ key, item }"
611
- :divided="item.divided"
612
- :disabled="item.disabled"
613
- >
614
- <IconifyIconOffline :icon="item.icon" />
615
- {{ $t(item.text) }}
616
- </el-dropdown-item>
617
- </el-dropdown-menu>
618
- </template>
619
- </el-dropdown>
620
- </div>
621
- </template>
622
-
623
- <style lang="scss" scoped>
624
- @import url('./index.scss');
625
- </style>
1
+ <script lang="ts">
2
+ export default { name: 'LayoutTag' };
3
+ </script>
4
+ <script setup lang="ts">
5
+ import { ref, watch, unref, nextTick, onBeforeUnmount } from 'vue';
6
+ import { isEqual, isEmpty } from 'xe-utils';
7
+ import { useResizeObserver, useDebounceFn, useFullscreen, onClickOutside } from '@vueuse/core';
8
+ import { delay } from '@utogether/utils';
9
+ import { emitter } from '../../../utils/mitt';
10
+ import { routerArrays } from '../../types';
11
+ import { useSettingStoreHook } from '../../../store/modules/settings';
12
+ import { handleAliveRoute, getTopMenu } from '../../../router/utils';
13
+ import { useMultiTagsStoreHook } from '../../../store/modules/multiTags';
14
+ import { usePermissionStoreHook } from '../../../store/modules/permission';
15
+ import { useTags } from '../../hooks/useTag';
16
+ import { RouteConfigs } from '../../types';
17
+ import TagChrome from '../lay-chrome/index.vue';
18
+
19
+ const {
20
+ route,
21
+ router,
22
+ visible,
23
+ showTags,
24
+ instance,
25
+ multiTags,
26
+ tagsViews,
27
+ buttonTop,
28
+ buttonLeft,
29
+ showModel,
30
+ translateX,
31
+ isFixedTag,
32
+ SuSetting,
33
+ activeIndex,
34
+ getTabStyle,
35
+ iconIsActive,
36
+ linkIsActive,
37
+ currentSelect,
38
+ scheduleIsActive,
39
+ getContextMenuStyle,
40
+ closeMenu,
41
+ onMounted,
42
+ onMouseenter,
43
+ onMouseleave,
44
+ transformI18n,
45
+ onContentFullScreen
46
+ } = useTags();
47
+
48
+ const suSetting = useSettingStoreHook();
49
+ const tabDom = ref();
50
+ const containerDom = ref();
51
+ const scrollbarDom = ref();
52
+ const contextmenuRef = ref();
53
+ const isShowArrow = ref(false);
54
+ const topPath = getTopMenu()?.path;
55
+ const { isFullscreen, toggle } = useFullscreen();
56
+
57
+ const fixedTags = [...routerArrays, ...usePermissionStoreHook().flatteningRoutes.filter(v => v?.meta?.fixedTag)];
58
+ const dynamicTagView = async () => {
59
+ await nextTick();
60
+ const index = multiTags.value.findIndex(item => {
61
+ if (!isEmpty(route.query)) {
62
+ return isEqual(route.query, item.query);
63
+ } else if (!isEmpty(route.params)) {
64
+ return isEqual(route.params, item.params);
65
+ } else {
66
+ return route.path === item.path;
67
+ }
68
+ });
69
+ moveToView(index);
70
+ };
71
+
72
+ const moveToView = async (index: number): Promise<void> => {
73
+ await nextTick();
74
+ const tabNavPadding = 10;
75
+ if (!instance.refs['dynamic' + index]) return;
76
+ const tabItemEl = instance.refs['dynamic' + index][0];
77
+ const tabItemElOffsetLeft = (tabItemEl as HTMLElement)?.offsetLeft;
78
+ const tabItemOffsetWidth = (tabItemEl as HTMLElement)?.offsetWidth;
79
+ // 标签页导航栏可视长度(不包含溢出部分)
80
+ const scrollbarDomWidth = scrollbarDom.value ? scrollbarDom.value.offsetWidth : 0;
81
+ // 已有标签页总长度(包含溢出部分)
82
+ const tabDomWidth = tabDom.value ? tabDom.value?.offsetWidth : 0;
83
+ scrollbarDomWidth <= tabDomWidth ? (isShowArrow.value = true) : (isShowArrow.value = false);
84
+ if (tabDomWidth < scrollbarDomWidth || tabItemElOffsetLeft === 0) {
85
+ translateX.value = 0;
86
+ } else if (tabItemElOffsetLeft < -translateX.value) {
87
+ // 标签在可视区域左侧
88
+ translateX.value = -tabItemElOffsetLeft + tabNavPadding;
89
+ } else if (
90
+ tabItemElOffsetLeft > -translateX.value &&
91
+ tabItemElOffsetLeft + tabItemOffsetWidth < -translateX.value + scrollbarDomWidth
92
+ ) {
93
+ // 标签在可视区域
94
+ translateX.value = Math.min(0, scrollbarDomWidth - tabItemOffsetWidth - tabItemElOffsetLeft - tabNavPadding);
95
+ } else {
96
+ // 标签在可视区域右侧
97
+ translateX.value = -(tabItemElOffsetLeft - (scrollbarDomWidth - tabNavPadding - tabItemOffsetWidth));
98
+ }
99
+ };
100
+
101
+ const handleScroll = (offset: number): void => {
102
+ const scrollbarDomWidth = scrollbarDom.value ? scrollbarDom.value?.offsetWidth : 0;
103
+ const tabDomWidth = tabDom.value ? tabDom.value.offsetWidth : 0;
104
+ if (offset > 0) {
105
+ translateX.value = Math.min(0, translateX.value + offset);
106
+ } else {
107
+ if (scrollbarDomWidth < tabDomWidth) {
108
+ if (translateX.value >= -(tabDomWidth - scrollbarDomWidth)) {
109
+ translateX.value = Math.max(translateX.value + offset, scrollbarDomWidth - tabDomWidth);
110
+ }
111
+ } else {
112
+ translateX.value = 0;
113
+ }
114
+ }
115
+ };
116
+
117
+ function dynamicRouteTag(value: string): void {
118
+ const hasValue = multiTags.value.some(item => {
119
+ return item.path === value;
120
+ });
121
+
122
+ function concatPath(arr: object[], value: string) {
123
+ if (!hasValue) {
124
+ arr.forEach((arrItem: any) => {
125
+ if (arrItem.path === value) {
126
+ useMultiTagsStoreHook().handleTags('push', {
127
+ path: value,
128
+ meta: arrItem.meta,
129
+ name: arrItem.name
130
+ });
131
+ } else {
132
+ if (arrItem.children && arrItem.children.length > 0) {
133
+ concatPath(arrItem.children, value);
134
+ }
135
+ }
136
+ });
137
+ }
138
+ }
139
+ concatPath(router.options.routes as any, value);
140
+ }
141
+
142
+ /** 刷新路由 */
143
+ function onFresh() {
144
+ const { fullPath, query } = unref(route);
145
+ router.replace({
146
+ path: '/redirect' + fullPath,
147
+ query: query
148
+ });
149
+ handleAliveRoute(route as ToRouteType, 'refresh');
150
+ }
151
+
152
+ function deleteDynamicTag(obj: any, current: any, tag?: string) {
153
+ const valueIndex: number = multiTags.value.findIndex((item: any) => {
154
+ if (item.query) {
155
+ if (item.path === obj.path) {
156
+ return item.query === obj.query;
157
+ }
158
+ } else if (item.params) {
159
+ if (item.path === obj.path) {
160
+ return item.params === obj.params;
161
+ }
162
+ } else {
163
+ return item.path === obj.path;
164
+ }
165
+ });
166
+
167
+ const spliceRoute = (startIndex?: number, length?: number, other?: boolean): void => {
168
+ if (other) {
169
+ useMultiTagsStoreHook().handleTags('equal', [fixedTags, obj].flat());
170
+ } else {
171
+ useMultiTagsStoreHook().handleTags('splice', '', {
172
+ startIndex,
173
+ length
174
+ }) as any;
175
+ }
176
+ dynamicTagView();
177
+ };
178
+
179
+ if (tag === 'other') {
180
+ spliceRoute(1, 1, true);
181
+ } else if (tag === 'left') {
182
+ spliceRoute(fixedTags.length, valueIndex - fixedTags.length);
183
+ } else if (tag === 'right') {
184
+ spliceRoute(valueIndex + 1, multiTags.value.length);
185
+ } else {
186
+ // 从当前匹配到的路径中删除
187
+ spliceRoute(valueIndex, 1);
188
+ }
189
+ const newRoute = useMultiTagsStoreHook().handleTags('slice');
190
+ if (current === route.path) {
191
+ // 如果删除当前激活tag就自动切换到最后一个tag
192
+ if (tag === 'left') return;
193
+ if (newRoute[0]?.query) {
194
+ router.push({ name: newRoute[0].name, query: newRoute[0].query });
195
+ } else if (newRoute[0]?.params) {
196
+ router.push({ name: newRoute[0].name, params: newRoute[0].params });
197
+ } else {
198
+ router.push({ path: newRoute[0].path });
199
+ }
200
+ } else {
201
+ if (!multiTags.value.length) return;
202
+ if (multiTags.value.some(item => item.path === route.path)) return;
203
+ if (newRoute[0]?.query) {
204
+ router.push({ name: newRoute[0].name, query: newRoute[0].query });
205
+ } else if (newRoute[0]?.params) {
206
+ router.push({ name: newRoute[0].name, params: newRoute[0].params });
207
+ } else {
208
+ router.push({ path: newRoute[0].path });
209
+ }
210
+ }
211
+ }
212
+
213
+ function deleteMenu(item, tag?: string) {
214
+ deleteDynamicTag(item, item.path, tag);
215
+ handleAliveRoute(route as ToRouteType);
216
+ }
217
+
218
+ function onClickDrop(key, item, selectRoute?: RouteConfigs) {
219
+ if (item && item.disabled) return;
220
+ let selectTagRoute;
221
+ if (selectRoute) {
222
+ selectTagRoute = {
223
+ path: selectRoute.path,
224
+ meta: selectRoute.meta,
225
+ name: selectRoute.name,
226
+ query: selectRoute?.query,
227
+ params: selectRoute?.params
228
+ };
229
+ } else {
230
+ selectTagRoute = { path: route.path, meta: route.meta };
231
+ }
232
+
233
+ // 当前路由信息
234
+ switch (key) {
235
+ case 0:
236
+ // 刷新路由
237
+ onFresh();
238
+ break;
239
+ case 1:
240
+ // 关闭当前标签页
241
+ deleteMenu(selectTagRoute);
242
+ break;
243
+ case 2:
244
+ // 关闭左侧标签页
245
+ deleteMenu(selectTagRoute, 'left');
246
+ break;
247
+ case 3:
248
+ // 关闭右侧标签页
249
+ deleteMenu(selectTagRoute, 'right');
250
+ break;
251
+ case 4:
252
+ // 关闭其他标签页
253
+ deleteMenu(selectTagRoute, 'other');
254
+ break;
255
+ case 5:
256
+ // 关闭全部标签页
257
+ useMultiTagsStoreHook().handleTags('splice', '', {
258
+ startIndex: fixedTags.length,
259
+ length: multiTags.value.length
260
+ });
261
+ router.push(topPath);
262
+
263
+ handleAliveRoute(route as ToRouteType);
264
+ break;
265
+ case 6:
266
+ // 整体页面全屏
267
+ toggle();
268
+ setTimeout(() => {
269
+ if (isFullscreen.value) {
270
+ tagsViews[6].icon = 'ri:fullscreen-exit-fill';
271
+ tagsViews[6].text = 'message.btn.wholeExitFullScreen';
272
+ } else {
273
+ tagsViews[6].icon = 'ri:fullscreen-fill';
274
+ tagsViews[6].text = 'message.btn.wholeFullScreen';
275
+ }
276
+ }, 100);
277
+ break;
278
+ case 7:
279
+ // 内容区全屏
280
+ onContentFullScreen();
281
+ setTimeout(() => {
282
+ if (SuSetting.hiddenSideBar) {
283
+ tagsViews[7].icon = 'ri:fullscreen-exit-fill';
284
+ tagsViews[7].text = 'message.btn.contentExitFullScreen';
285
+ } else {
286
+ tagsViews[7].icon = 'ri:fullscreen-fill';
287
+ tagsViews[7].text = 'message.btn.contentFullScreen';
288
+ }
289
+ }, 100);
290
+ break;
291
+ }
292
+ setTimeout(() => {
293
+ showMenuModel(route.fullPath, route.query, route.params);
294
+ });
295
+ }
296
+
297
+ function handleCommand(command: any) {
298
+ const { key, item } = command;
299
+ onClickDrop(key, item);
300
+ }
301
+
302
+ /** 触发右键中菜单的点击事件 */
303
+ function selectTag(key, item) {
304
+ closeMenu();
305
+ onClickDrop(key, item, currentSelect.value);
306
+ }
307
+
308
+ function showMenus(value: boolean) {
309
+ Array.of(1, 2, 3, 4, 5).forEach(v => {
310
+ tagsViews[v].show = value;
311
+ });
312
+ }
313
+
314
+ function disabledMenus(value: boolean, fixedTag = false) {
315
+ Array.of(1, 2, 3, 4, 5).forEach(v => {
316
+ tagsViews[v].disabled = value;
317
+ });
318
+ if (fixedTag) {
319
+ tagsViews[2].show = false;
320
+ tagsViews[2].disabled = true;
321
+ }
322
+ }
323
+
324
+ /** 检查当前右键的菜单两边是否存在别的菜单,如果左侧的菜单是顶级菜单,则不显示关闭左侧标签页,如果右侧没有菜单,则不显示关闭右侧标签页 */
325
+ function showMenuModel(currentPath: string, query: object = {}, params: object = {}, refresh = false) {
326
+ const allRoute = multiTags.value;
327
+ const routeLength = multiTags.value.length;
328
+ let currentIndex = -1;
329
+ if (!isEmpty(params)) {
330
+ currentIndex = allRoute.findIndex(v => isEqual(v.params, params));
331
+ } else if (isEmpty(query)) {
332
+ currentIndex = allRoute.findIndex(v => v.path === currentPath);
333
+ } else {
334
+ currentIndex = allRoute.findIndex(v => isEqual(v.query, query));
335
+ }
336
+
337
+ function fixedTagDisabled() {
338
+ if (allRoute[currentIndex]?.meta?.fixedTag) {
339
+ Array.of(1, 2, 3, 4, 5).forEach(v => {
340
+ tagsViews[v].disabled = true;
341
+ });
342
+ }
343
+ }
344
+
345
+ showMenus(true);
346
+
347
+ if (refresh) {
348
+ tagsViews[0].show = true;
349
+ }
350
+
351
+ /**
352
+ * currentIndex为1时,左侧的菜单顶级菜单,则不显示关闭左侧标签页
353
+ * 如果currentIndex等于routeLength-1,右侧没有菜单,则不显示关闭右侧标签页
354
+ */
355
+ if (currentIndex === 1 && routeLength !== 2) {
356
+ // 左侧的菜单是顶级菜单,右侧存在别的菜单
357
+ tagsViews[2].show = false;
358
+ Array.of(1, 3, 4, 5).forEach(v => {
359
+ tagsViews[v].disabled = false;
360
+ });
361
+ tagsViews[2].disabled = true;
362
+ fixedTagDisabled();
363
+ } else if (currentIndex === 1 && routeLength === 2) {
364
+ disabledMenus(false);
365
+ // 左侧的菜单是顶级菜单,右侧不存在别的菜单
366
+ Array.of(2, 3, 4).forEach(v => {
367
+ tagsViews[v].show = false;
368
+ tagsViews[v].disabled = true;
369
+ });
370
+ fixedTagDisabled();
371
+ } else if (routeLength - 1 === currentIndex && currentIndex !== 0) {
372
+ // 当前路由是所有路由中的最后一个
373
+ tagsViews[3].show = false;
374
+ Array.of(1, 2, 4, 5).forEach(v => {
375
+ tagsViews[v].disabled = false;
376
+ });
377
+ tagsViews[3].disabled = true;
378
+ if (allRoute[currentIndex - 1]?.meta?.fixedTag) {
379
+ tagsViews[2].show = false;
380
+ tagsViews[2].disabled = true;
381
+ }
382
+ fixedTagDisabled();
383
+ } else if (currentIndex === 0 || currentPath === `/redirect${topPath}`) {
384
+ // 当前路由为首页
385
+ disabledMenus(true);
386
+ } else {
387
+ disabledMenus(false, allRoute[currentIndex - 1]?.meta?.fixedTag);
388
+ fixedTagDisabled();
389
+ }
390
+ }
391
+
392
+ function openMenu(tag, e) {
393
+ closeMenu();
394
+ if (tag.path === '/welcome' || tag?.meta?.fixedTag) {
395
+ // 右键菜单为首页或拥有 fixedTag 属性,只显示刷新
396
+ showMenus(false);
397
+ tagsViews[0].show = true;
398
+ } else if (route.path !== tag.path && route.name !== tag.name) {
399
+ // 右键菜单不匹配当前路由,隐藏刷新
400
+ tagsViews[0].show = false;
401
+ showMenuModel(tag.path, tag.query, tag.params);
402
+ } else if (multiTags.value.length === 2 && route.path !== tag.path) {
403
+ showMenus(true);
404
+ // 只有两个标签时不显示关闭其他标签页
405
+ tagsViews[4].show = false;
406
+ showMenuModel(tag.path, tag.query, tag.params);
407
+ } else if (route.path === tag.path) {
408
+ // 右键当前激活的菜单
409
+ showMenuModel(tag.path, tag.query, tag.params, true);
410
+ }
411
+
412
+ currentSelect.value = tag;
413
+ const menuMinWidth = 140;
414
+ const offsetLeft = unref(containerDom).getBoundingClientRect().left;
415
+ const offsetWidth = unref(containerDom).offsetWidth;
416
+ const maxLeft = offsetWidth - menuMinWidth;
417
+ const left = e.clientX - offsetLeft + 5;
418
+ if (left > maxLeft) {
419
+ buttonLeft.value = maxLeft;
420
+ } else {
421
+ buttonLeft.value = left;
422
+ }
423
+ suSetting.hiddenSideBar ? (buttonTop.value = e.clientY) : (buttonTop.value = e.clientY - 40);
424
+ nextTick(() => {
425
+ visible.value = true;
426
+ });
427
+ }
428
+
429
+ /** 触发tags标签切换 */
430
+ function tagOnClick(item) {
431
+ const { name, path } = item;
432
+ if (name) {
433
+ if (item.query) {
434
+ router.push({
435
+ name,
436
+ query: item.query
437
+ });
438
+ } else if (item.params) {
439
+ router.push({
440
+ name,
441
+ params: item.params
442
+ });
443
+ } else {
444
+ router.push({ name });
445
+ }
446
+ } else {
447
+ router.push({ path });
448
+ }
449
+ // showMenuModel(item?.path, item?.query);
450
+ emitter.emit('tagOnClick', item);
451
+ }
452
+
453
+ onClickOutside(contextmenuRef, closeMenu, {
454
+ detectIframe: true
455
+ });
456
+
457
+ watch(route, () => {
458
+ activeIndex.value = -1;
459
+ dynamicTagView();
460
+ });
461
+
462
+ watch(isFullscreen, () => {
463
+ tagsViews[6].icon = 'ri:fullscreen-fill';
464
+ tagsViews[6].text = 'message.wholeFullScreen';
465
+ });
466
+
467
+ onMounted(() => {
468
+ if (!instance) return;
469
+
470
+ // 根据当前路由初始化操作标签页的禁用状态
471
+ showMenuModel(route.fullPath);
472
+
473
+ // 触发隐藏标签页
474
+ emitter.on('tagViewsChange', (key: any) => {
475
+ if (unref(showTags as any) === key) return;
476
+ (showTags as any).value = key;
477
+ });
478
+
479
+ // 改变标签风格
480
+ emitter.on('tagViewsShowModel', key => {
481
+ showModel.value = key;
482
+ });
483
+
484
+ // 接收侧边栏切换传递过来的参数
485
+ emitter.on('changLayoutRoute', indexPath => {
486
+ dynamicRouteTag(indexPath);
487
+ setTimeout(() => {
488
+ showMenuModel(indexPath);
489
+ });
490
+ });
491
+
492
+ useResizeObserver(
493
+ scrollbarDom,
494
+ useDebounceFn(() => {
495
+ dynamicTagView();
496
+ }, 200)
497
+ );
498
+ delay().then(() => dynamicTagView());
499
+ });
500
+
501
+ onBeforeUnmount(() => {
502
+ // 解绑`tagViewsChange`、`tagViewsShowModel`、`changLayoutRoute`公共事件,防止多次触发
503
+ emitter.off('tagViewsChange');
504
+ emitter.off('tagViewsShowModel');
505
+ emitter.off('changLayoutRoute');
506
+ });
507
+ </script>
508
+
509
+ <template>
510
+ <div v-if="!showTags" ref="containerDom" class="tags-view">
511
+ <span v-show="isShowArrow" class="arrow-left">
512
+ <IconifyIconOffline icon="ri:arrow-left-s-line" @click="handleScroll(200)" />
513
+ </span>
514
+ <div ref="scrollbarDom" class="scroll-container" :class="showModel === 'chrome' && 'chrome-scroll-container'">
515
+ <div ref="tabDom" class="select-none tab" :style="getTabStyle">
516
+ <div
517
+ v-for="(item, index) in multiTags"
518
+ :key="index"
519
+ :ref="'dynamic' + index"
520
+ :class="[
521
+ 'scroll-item is-closable',
522
+ linkIsActive(item),
523
+ showModel === 'chrome' && 'chrome-item',
524
+ isFixedTag(item) && 'fixed-tag'
525
+ ]"
526
+ @contextmenu.prevent="openMenu(item, $event)"
527
+ @mouseenter.prevent="onMouseenter(index)"
528
+ @mouseleave.prevent="onMouseleave(index)"
529
+ @click="tagOnClick(item)"
530
+ >
531
+ <template v-if="showModel !== 'chrome'">
532
+ <span class="tag-title dark:text-text_color_primary! dark:hover:text-primary!">
533
+ {{ transformI18n(item?.meta?.title) }}
534
+ </span>
535
+ <span
536
+ v-if="isFixedTag(item) ? false : iconIsActive(item, index) || (index === activeIndex && index !== 0)"
537
+ class="el-icon-close"
538
+ @click.stop="deleteMenu(item)"
539
+ >
540
+ <IconifyIconOffline icon="ri:close-fill" />
541
+ </span>
542
+ <span v-if="showModel !== 'card'" :ref="'schedule' + index" :class="[scheduleIsActive(item)]" />
543
+ </template>
544
+ <div v-else class="chrome-tab">
545
+ <div class="chrome-tab__bg">
546
+ <TagChrome />
547
+ </div>
548
+ <span class="tag-title">
549
+ {{ transformI18n(item?.meta?.title) }}
550
+ </span>
551
+ <span v-if="isFixedTag(item) ? false : index !== 0" class="chrome-close-btn" @click.stop="deleteMenu(item)">
552
+ <IconifyIconOffline icon="ri:close-fill" />
553
+ </span>
554
+ <span class="chrome-tab-divider" />
555
+ </div>
556
+ </div>
557
+ </div>
558
+ </div>
559
+ <span v-show="isShowArrow" class="arrow-right">
560
+ <IconifyIconOffline icon="ri:arrow-right-s-line" @click="handleScroll(-200)" />
561
+ </span>
562
+ <!-- 右键菜单按钮 -->
563
+ <transition name="el-zoom-in-top">
564
+ <ul v-show="visible" ref="contextmenuRef" :key="Math.random()" :style="getContextMenuStyle" class="contextmenu">
565
+ <div v-for="(item, key) in tagsViews.slice(0, 6)" :key="key" style="display: flex; align-items: center">
566
+ <li v-if="item.show" @click="selectTag(key, item)">
567
+ <IconifyIconOffline :icon="item.icon" />
568
+ {{ $t(item.text) }}
569
+ </li>
570
+ </div>
571
+ </ul>
572
+ </transition>
573
+ <!-- 右侧功能按钮 -->
574
+ <el-dropdown trigger="click" placement="bottom-end" @command="handleCommand">
575
+ <span class="arrow-down">
576
+ <IconifyIconOffline icon="ri:arrow-down-s-line" class="dark:text-white" />
577
+ </span>
578
+ <template #dropdown>
579
+ <el-dropdown-menu>
580
+ <el-dropdown-item
581
+ v-for="(item, key) in tagsViews"
582
+ :key="key"
583
+ :command="{ key, item }"
584
+ :divided="item.divided"
585
+ :disabled="item.disabled"
586
+ >
587
+ <IconifyIconOffline :icon="item.icon" />
588
+ {{ $t(item.text) }}
589
+ </el-dropdown-item>
590
+ </el-dropdown-menu>
591
+ </template>
592
+ </el-dropdown>
593
+ </div>
594
+ </template>
595
+
596
+ <style lang="scss" scoped>
597
+ @import url('./index.scss');
598
+ </style>