@utogether/udp-core 1.0.1-beta.1 → 1.0.1-beta.11

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 (147) hide show
  1. package/build/plugins.ts +39 -32
  2. package/dist/{403-Dp617CWX.js → 403-Dsgsr2M-.js} +1 -1
  3. package/dist/{404-Cz_Axb6Y.js → 404-DmPHEfqM.js} +1 -1
  4. package/dist/{500-BGCtRNse.js → 500-OYEgHR2a.js} +1 -1
  5. package/dist/{AuthorityInfo-DGGfm7IS.js → AuthorityInfo-B1H1txG-.js} +1 -1
  6. package/dist/AuthorityInfo.vue_vue_type_style_index_0_lang-BUlmQtjQ.js +100 -0
  7. package/dist/{Company-IV3GTnzY.js → Company-C8ojgx93.js} +3 -3
  8. package/dist/{CompanyPanel-qV-_VtoL.js → CompanyPanel-Dageer1t.js} +16 -16
  9. package/dist/{Department-B3W-OxW8.js → Department-Dr94_85I.js} +3 -3
  10. package/dist/{DepartmentPanel-Cw3OWxE7.js → DepartmentPanel-Bc5r1HpT.js} +1 -1
  11. package/dist/{DesignPanel-BFxR2fHJ.js → DesignPanel-CcWt1Myp.js} +1 -1
  12. package/dist/{DesignPanel.vue_vue_type_style_index_0_lang-DljbeFba.js → DesignPanel.vue_vue_type_style_index_0_lang-CKA6nx1l.js} +24 -26
  13. package/dist/DictView-BHj6wexC.js +110 -0
  14. package/dist/InvOrganization-DP3Le9g-.js +72 -0
  15. package/dist/Org-BRGHTwsw.js +39 -0
  16. package/dist/{Preview-BlDMmpdR.js → Preview-CqMaUD_3.js} +1 -1
  17. package/dist/{ReportDefine-Cub_85LA.js → ReportDefine-PEQdTCOY.js} +1 -1
  18. package/dist/{ReportDesign-hFhq5UVE.js → ReportDesign-CB4bv8ba.js} +43 -43
  19. package/dist/{ReportQuery-ChkWEyxT.js → ReportQuery-B7t4lMK0.js} +1 -1
  20. package/dist/{ReportQueryFrom-KVyD_8Dj.js → ReportQueryFrom-VBxUIMRJ.js} +1 -1
  21. package/dist/{ReportQueryFrom.vue_vue_type_style_index_0_lang-CLNODquq.js → ReportQueryFrom.vue_vue_type_style_index_0_lang-HeLA4Qs5.js} +1 -1
  22. package/dist/{ReportTemplate-ag9NDvh2.js → ReportTemplate-DNkv7F5p.js} +28 -28
  23. package/dist/{Role-_q3lQ8CZ.js → Role-BGfTpZK4.js} +6 -6
  24. package/dist/{RoleAssign-DZb9IRsm.js → RoleAssign-DrKFWZzA.js} +8 -8
  25. package/dist/{RolePanel-CsLsz-Ds.js → RolePanel-3lvRh7aS.js} +1 -1
  26. package/dist/{RolePanel-BQb1LlhD.js → RolePanel-BRI5QYe4.js} +1 -1
  27. package/dist/RolePanel.vue_vue_type_script_setup_true_lang-BXKZb7yt.js +132 -0
  28. package/dist/{RolePanel.vue_vue_type_script_setup_true_lang-BrnRgHEk.js → RolePanel.vue_vue_type_script_setup_true_lang-Cci_Ysty.js} +44 -38
  29. package/dist/{ScrollPanel.vue_vue_type_style_index_0_lang-CaFKRwXu.js → ScrollPanel.vue_vue_type_style_index_0_lang-BmId6zzA.js} +21 -21
  30. package/dist/{Staff-BSf9Ypbk.js → Staff-Dr0dq3fW.js} +3 -3
  31. package/dist/{StaffInfo-BNKasyMF.js → StaffInfo-_N9g2hPa.js} +1 -1
  32. package/dist/{StaffInfo.vue_vue_type_script_setup_true_lang-DDZ7ukd0.js → StaffInfo.vue_vue_type_script_setup_true_lang-DiTJY2ug.js} +11 -11
  33. package/dist/{StaffPanel-Bpq0WVlH.js → StaffPanel-mxJQiW3G.js} +1 -1
  34. package/dist/{StaffPanel.vue_vue_type_script_setup_true_lang-BhiJ0Q-Q.js → StaffPanel.vue_vue_type_script_setup_true_lang-GRGn-5t6.js} +2 -2
  35. package/dist/{SysUser-FAABuNti.js → SysUser-DgA19hZE.js} +2 -2
  36. package/dist/{SysUserPanel-PxJeOgHm.js → SysUserPanel-Bc85z5_K.js} +1 -1
  37. package/dist/SysUserPanel.vue_vue_type_script_setup_true_lang-BHDrP1Pk.js +294 -0
  38. package/dist/{SystemMenu-C-7NAGon.js → SystemMenu-Be-gFIie.js} +26 -26
  39. package/dist/{UserInfo-ClXKtyGo.js → UserInfo-BCYuwFmr.js} +1 -1
  40. package/dist/{UserInfo.vue_vue_type_style_index_0_lang-8N7P4Hl7.js → UserInfo.vue_vue_type_style_index_0_lang-B1lgokO6.js} +39 -37
  41. package/dist/{childView-C_HmDQNd.js → childView-DhcBr1UA.js} +1 -1
  42. package/dist/{childView-uUlBcTza.js → childView-Dk4zZoXd.js} +1 -1
  43. package/dist/{childView.vue_vue_type_style_index_0_lang-Ckjmw6wJ.js → childView.vue_vue_type_style_index_0_lang-CVnLnl03.js} +40 -40
  44. package/dist/{childView.vue_vue_type_style_index_0_lang-y0sDvYx5.js → childView.vue_vue_type_style_index_0_lang-D4GYVhOc.js} +1 -1
  45. package/dist/{code-rule-AgCVDKFy.js → code-rule-BSwPwuAL.js} +9 -10
  46. package/dist/core.es.js +14 -11
  47. package/dist/{cron-task-C6FgQxTi.js → cron-task-T1ztyra7.js} +1 -1
  48. package/dist/{frameView-BDgISK7N.js → frameView-BDgngiBq.js} +1 -1
  49. package/dist/img/l_img.svg +1 -1
  50. package/dist/img/minicolors.png +0 -0
  51. package/dist/img/v_img.svg +1 -1
  52. package/dist/index-BSj2AtVL.js +4468 -0
  53. package/dist/{layoutView-yb3DV2DQ.js → layoutView-i7dLLBzJ.js} +1181 -1176
  54. package/dist/{login-RRpljbkm.js → login-K5Ludp8x.js} +105 -113
  55. package/dist/{login-log-CvVnyGi3.js → login-log-kqKzKTto.js} +1 -1
  56. package/dist/{lov-view-C9-rjzZR.js → lov-view-lAexiucu.js} +2 -2
  57. package/dist/{menuInfo-CzPQyFhp.js → menuInfo-BaN5YZPp.js} +1 -1
  58. package/dist/{menuInfo.vue_vue_type_style_index_0_lang-BumXunCg.js → menuInfo.vue_vue_type_style_index_0_lang-yI4QOYxG.js} +100 -97
  59. package/dist/{pda-app-Dvy3U-b6.js → pda-app-DEtDXFvM.js} +211 -197
  60. package/dist/{resource-Fy0lFkSV.js → resource-Bztmz7pn.js} +15 -15
  61. package/dist/{su-welcome-DYvSCUST.js → su-welcome-Bx8nBPi9.js} +109 -110
  62. package/dist/sys-config-nvrtYGgi.js +290 -0
  63. package/dist/udp-core.css +2 -2
  64. package/dist/{utogether-CjmJiHoE.js → utogether-yPnwDAIH.js} +1 -1
  65. package/index.ts +40 -36
  66. package/package.json +18 -18
  67. package/src/App.vue +65 -70
  68. package/src/api/index.ts +31 -31
  69. package/src/components/SuCharts/src/UserInfo.vue +78 -78
  70. package/src/components/SuScrollTree/ScrollPanel.vue +3 -3
  71. package/src/components/udp/count-down.vue +536 -0
  72. package/src/components/udp/flip-down/FlipCard/flip-card.vue +251 -0
  73. package/src/components/udp/flip-down/FlipCard/interfaces.ts +4 -0
  74. package/src/components/udp/flip-down/FlipClock/flip-clock.vue +113 -0
  75. package/src/components/udp/form-upload.vue +414 -64
  76. package/src/components/udp/form.vue +112 -0
  77. package/src/components/udp/grid.vue +495 -0
  78. package/src/components/udp/index.ts +10 -4
  79. package/src/components/udp/lov.vue +388 -0
  80. package/src/components/udp/modal-form.vue +13 -3
  81. package/src/components/udp/modal-grid.vue +298 -0
  82. package/src/components/udp/upload.vue +423 -0
  83. package/src/components/udp/utils.ts +447 -40
  84. package/src/directives/permission/index.ts +1 -1
  85. package/src/layout/components/lay-navbar/index.vue +239 -237
  86. package/src/layout/components/lay-panel/index.vue +150 -150
  87. package/src/layout/components/lay-search/index.vue +25 -25
  88. package/src/layout/components/lay-select-org/index.vue +64 -69
  89. package/src/layout/components/lay-sidebar/breadCrumb.vue +1 -1
  90. package/src/layout/components/lay-sidebar/horizontal.vue +190 -191
  91. package/src/layout/components/lay-sidebar/mixNav.vue +260 -258
  92. package/src/layout/components/lay-sidebar/sidebar-logo.vue +92 -98
  93. package/src/layout/components/lay-tag/index.vue +625 -625
  94. package/src/layout/hooks/useNav.ts +173 -173
  95. package/src/layout/layoutView.vue +215 -215
  96. package/src/main.ts +119 -109
  97. package/src/plugins/i18n/en.ts +302 -289
  98. package/src/plugins/i18n/zh.ts +349 -337
  99. package/src/plugins/vxe-table/index.ts +53 -46
  100. package/src/plugins/vxe-table/render.tsx +956 -817
  101. package/src/router/index.ts +177 -183
  102. package/src/router/modules/remaining.ts +58 -83
  103. package/src/store/modules/permission.ts +1 -4
  104. package/src/style/button.scss +85 -78
  105. package/src/style/tailwind.css +1 -68
  106. package/src/style/vxetable.scss +44 -11
  107. package/src/utils/authority/index.ts +1 -1
  108. package/src/utils/{http → udp/http}/index.ts +283 -297
  109. package/src/utils/{http → udp/http}/types.d.ts +8 -5
  110. package/src/utils/udp/useRender.ts +17 -6
  111. package/src/views/login/login-view.vue +300 -314
  112. package/src/views/organization/company/CompanyPanel.vue +259 -259
  113. package/src/views/organization/inv-org/InvOrganization.vue +68 -54
  114. package/src/views/organization/org/Org.vue +9 -5
  115. package/src/views/system/menu/SystemMenu.vue +183 -197
  116. package/src/views/system/menu/menuInfo.vue +363 -371
  117. package/src/views/system/role/AuthorityInfo.vue +19 -15
  118. package/src/views/system/role/Role.vue +1 -5
  119. package/src/views/system/role/RolePanel.vue +11 -2
  120. package/src/views/system/role/UserInfo.vue +195 -193
  121. package/src/views/system/role-assign/RoleAssign.vue +57 -57
  122. package/src/views/system/role-assign/RolePanel.vue +139 -136
  123. package/src/views/system/sys/sys-config.vue +287 -291
  124. package/src/views/system/sysUser/SysUserPanel.vue +278 -278
  125. package/src/views/uapp/pda/pda-app.vue +208 -176
  126. package/src/views/udev/dict/DictView.vue +118 -106
  127. package/src/views/udev/dict/childView.vue +7 -7
  128. package/src/views/udev/lov/lov-view.vue +91 -91
  129. package/src/views/ufile/aggregation/File.vue +5 -5
  130. package/src/views/ufile/file/water-mark.vue +14 -14
  131. package/src/views/uhome/su-welcome.vue +3 -3
  132. package/src/views/ulogin/login.vue +12 -8
  133. package/src/views/upms/user/login-log.vue +1 -1
  134. package/src/views/urpt/design/DesignPanel.vue +507 -526
  135. package/src/views/urpt/design/ReportDesign.vue +15 -19
  136. package/src/views/urpt/static-resource/resource.vue +3 -3
  137. package/src/views/urpt/template/ReportTemplate.vue +7 -7
  138. package/types/global.d.ts +2 -1
  139. package/vite.config.ts +6 -2
  140. package/dist/AuthorityInfo.vue_vue_type_style_index_0_lang-BqccGW7v.js +0 -102
  141. package/dist/DictView-C-i7e4hZ.js +0 -95
  142. package/dist/InvOrganization-cfT6riGU.js +0 -260
  143. package/dist/Org-CA7vTDIF.js +0 -35
  144. package/dist/RolePanel.vue_vue_type_script_setup_true_lang-cmW7zBLu.js +0 -126
  145. package/dist/SysUserPanel.vue_vue_type_script_setup_true_lang-njefUln5.js +0 -288
  146. package/dist/index-DzOzUkf6.js +0 -3388
  147. package/dist/sys-config-DJ1vNQTy.js +0 -277
@@ -1,625 +1,625 @@
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 } 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 - fixedTags.length);
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>