@utogether/udp-core 1.0.1-beta.27 → 1.0.1-beta.30

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 (95) hide show
  1. package/build/plugins.ts +37 -37
  2. package/dist/{403-A9aD_bZN.js → 403-DdjDWbkf.js} +1 -1
  3. package/dist/{404-DREd70aQ.js → 404-D9vF2gFz.js} +1 -1
  4. package/dist/{500-C5qVXkGt.js → 500-mVaDmUjL.js} +1 -1
  5. package/dist/{AuthorityInfo-CYO2IVau.js → AuthorityInfo-DOUJ_Zrj.js} +1 -1
  6. package/dist/{AuthorityInfo.vue_vue_type_style_index_0_lang-V7e_KwWp.js → AuthorityInfo.vue_vue_type_style_index_0_lang-B12sAzqt.js} +1 -1
  7. package/dist/{Company-CaottHQO.js → Company-DY__kM9F.js} +3 -3
  8. package/dist/{CompanyPanel-B3hOiXqj.js → CompanyPanel-C5N8QcMA.js} +1 -1
  9. package/dist/{Department-CHkuRvJN.js → Department-DsuDZdy5.js} +3 -3
  10. package/dist/{DepartmentPanel-DieWfIxW.js → DepartmentPanel-CdwI7Rzu.js} +2 -2
  11. package/dist/{DesignPanel-REWXp3x4.js → DesignPanel-B50-g0TL.js} +1 -1
  12. package/dist/{DesignPanel.vue_vue_type_style_index_0_lang-Df0xc5XP.js → DesignPanel.vue_vue_type_style_index_0_lang-KV8JOZ6v.js} +2 -2
  13. package/dist/{DictView-DnpDHskG.js → DictView-Be2Z5Obk.js} +1 -1
  14. package/dist/{InvOrganization-DoIsDrAx.js → InvOrganization-Lui1q3Ou.js} +1 -1
  15. package/dist/{Org-C4P5Cwia.js → Org-qO5ExsPN.js} +1 -1
  16. package/dist/{Preview-d90pQdFJ.js → Preview-Dq4dfV2D.js} +1 -1
  17. package/dist/{ReportDefine-DNrp4lFP.js → ReportDefine-DH3FvbS1.js} +1 -1
  18. package/dist/{ReportDesign-Ch8x6_mW.js → ReportDesign-Bou01Jbr.js} +2 -2
  19. package/dist/{ReportQuery-C1yR0ULS.js → ReportQuery-BBkmPPSw.js} +1 -1
  20. package/dist/{ReportQueryFrom-BKwf61CL.js → ReportQueryFrom-BCY8lTJ7.js} +1 -1
  21. package/dist/{ReportQueryFrom.vue_vue_type_style_index_0_lang-D2ddteNz.js → ReportQueryFrom.vue_vue_type_style_index_0_lang-CpktcM8J.js} +1 -1
  22. package/dist/{ReportTemplate-HLHli4Cc.js → ReportTemplate-C_yJXxQ-.js} +1 -1
  23. package/dist/{Role-4GbuIhk6.js → Role-CTatYsrC.js} +3 -3
  24. package/dist/{RoleAssign-FGrSCg5R.js → RoleAssign-Cqt3CelK.js} +3 -3
  25. package/dist/{RolePanel-Ce1eKsM8.js → RolePanel-C3JMpNu8.js} +1 -1
  26. package/dist/{RolePanel-BcCozGfv.js → RolePanel-brKRJIhD.js} +1 -1
  27. package/dist/{RolePanel.vue_vue_type_script_setup_true_lang-BiOcU5GE.js → RolePanel.vue_vue_type_script_setup_true_lang-DD8u9pq8.js} +1 -1
  28. package/dist/{RolePanel.vue_vue_type_script_setup_true_lang-CqER1M9Y.js → RolePanel.vue_vue_type_script_setup_true_lang-i6Gq4r5B.js} +3 -3
  29. package/dist/{ScrollPanel.vue_vue_type_style_index_0_lang-BbgS5f79.js → ScrollPanel.vue_vue_type_style_index_0_lang-CiWKyjm3.js} +1 -1
  30. package/dist/{Staff-CBt_0rqg.js → Staff-Cd3twQ6Y.js} +3 -3
  31. package/dist/{StaffInfo-RCdWZr1r.js → StaffInfo-DJp0a0qd.js} +1 -1
  32. package/dist/{StaffInfo.vue_vue_type_script_setup_true_lang-Dm8Yin3r.js → StaffInfo.vue_vue_type_script_setup_true_lang-Dp2AhJ4I.js} +1 -1
  33. package/dist/{StaffPanel-2ka-H3IV.js → StaffPanel-B93hClzs.js} +1 -1
  34. package/dist/{StaffPanel.vue_vue_type_script_setup_true_lang-B0u-PqTe.js → StaffPanel.vue_vue_type_script_setup_true_lang-CXjUzq7n.js} +2 -2
  35. package/dist/{SysUser-Cs7uN3HU.js → SysUser-BvI6vaqI.js} +2 -2
  36. package/dist/{SysUserPanel-Df3qGMep.js → SysUserPanel-C9V8X-Ek.js} +1 -1
  37. package/dist/{SysUserPanel.vue_vue_type_script_setup_true_lang-ClwozbZE.js → SysUserPanel.vue_vue_type_script_setup_true_lang-bMqTui07.js} +1 -1
  38. package/dist/{SystemMenu-BMYVQd-E.js → SystemMenu-D4WME5d0.js} +2 -2
  39. package/dist/{UserInfo-DDk2iOqY.js → UserInfo-D-AfdfJs.js} +1 -1
  40. package/dist/{UserInfo.vue_vue_type_style_index_0_lang-CEccCnDi.js → UserInfo.vue_vue_type_style_index_0_lang-C-JQYIxe.js} +1 -1
  41. package/dist/{childView-C7u4Budn.js → childView--WMSo-wH.js} +1 -1
  42. package/dist/{childView-DFPGu0W-.js → childView-1MTl6Kaj.js} +1 -1
  43. package/dist/childView.vue_vue_type_style_index_0_lang-B8Losc4Y.js +170 -0
  44. package/dist/{childView.vue_vue_type_style_index_0_lang-B3fNaibG.js → childView.vue_vue_type_style_index_0_lang-BAVKNIXs.js} +1 -1
  45. package/dist/{code-rule-BcaRQ1zV.js → code-rule-D-RqrQB6.js} +40 -37
  46. package/dist/core.es.js +1 -1
  47. package/dist/{cron-task-DJQLZCsO.js → cron-task-D-X-KFZm.js} +1 -1
  48. package/dist/{frameView-uPEaJRC7.js → frameView-MAW_-GHJ.js} +1 -1
  49. package/dist/{index-D7BXpJPu.js → index-Bc9vCX7z.js} +45 -45
  50. package/dist/{layoutView-zw1-fplL.js → layoutView-De2QIEr2.js} +3 -3
  51. package/dist/{login-M-rLGYIn.js → login-rdZ0GPYc.js} +1 -1
  52. package/dist/{lov-view-BVfIna0M.js → lov-view-Ao7LMspL.js} +2 -2
  53. package/dist/{menuInfo-BTqoNOuZ.js → menuInfo-DmOcK6An.js} +1 -1
  54. package/dist/{menuInfo.vue_vue_type_style_index_0_lang-CHco0XcS.js → menuInfo.vue_vue_type_style_index_0_lang-Ln-zFWV7.js} +1 -1
  55. package/dist/{pda-app-BzzynCVk.js → pda-app-DY63mQ2T.js} +1 -1
  56. package/dist/{resource-CJQV7_75.js → resource-B21uV58T.js} +1 -1
  57. package/dist/{su-welcome-BxQYF4Ou.js → su-welcome-DgjzJeDF.js} +22 -19
  58. package/dist/{sys-config-DNFqTmvL.js → sys-config-DOtK2I32.js} +1 -1
  59. package/dist/udp-core.css +1 -1
  60. package/index.ts +49 -49
  61. package/package.json +3 -3
  62. package/src/components/SuScrollTree/ScrollPanel.vue +170 -170
  63. package/src/components/udp/content/index.vue +89 -0
  64. package/src/components/udp/form/form.vue +109 -109
  65. package/src/components/udp/grid/index.vue +524 -524
  66. package/src/components/udp/index.ts +2 -1
  67. package/src/components/udp/ut-stamp-badge/index.vue +272 -0
  68. package/src/components/udp/utils.ts +408 -408
  69. package/src/layout/components/lay-sidebar/sidebar-logo.vue +94 -94
  70. package/src/layout/components/lay-tag/index.vue +596 -596
  71. package/src/layout/hooks/useTag.ts +227 -227
  72. package/src/main.ts +1 -1
  73. package/src/plugins/i18n/module/u-workflow.ts +71 -71
  74. package/src/plugins/vxe-table/index.ts +116 -116
  75. package/src/plugins/vxe-table/render.tsx +945 -945
  76. package/src/router/index.ts +187 -187
  77. package/src/router/modules/flow.ts +35 -35
  78. package/src/router/utils.ts +420 -420
  79. package/src/store/modules/multiTags.ts +110 -110
  80. package/src/store/modules/permission.ts +113 -113
  81. package/src/style/button.scss +85 -85
  82. package/src/style/vxetable.scss +348 -348
  83. package/src/views/organization/department/DepartmentPanel.vue +269 -269
  84. package/src/views/system/menu/SystemMenu.vue +185 -185
  85. package/src/views/system/menu/menuInfo.vue +384 -384
  86. package/src/views/system/sysUser/SysUserPanel.vue +348 -348
  87. package/src/views/udev/coderule/code-rule.vue +17 -5
  88. package/src/views/udev/dict/DictView.vue +118 -118
  89. package/src/views/udev/dict/childView.vue +69 -109
  90. package/src/views/udev/lov/childView.vue +174 -174
  91. package/src/views/uhome/components/menu-favorite.vue +1 -1
  92. package/src/views/upms/interface/log-in.vue +100 -100
  93. package/src/views/upms/user/login-log.vue +54 -54
  94. package/src/views/utask/flow-task.vue +18 -18
  95. package/dist/childView.vue_vue_type_style_index_0_lang-BUMcbqzM.js +0 -187
@@ -1,596 +1,596 @@
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 = [...routerArrays, ...usePermissionStoreHook().flatteningRoutes.filter(v => v?.meta?.fixedTag)];
57
- const dynamicTagView = async () => {
58
- await nextTick();
59
- const index = multiTags.value.findIndex(item => {
60
- if (!isEmpty(route.query)) {
61
- return isEqual(route.query, item.query);
62
- } else if (!isEmpty(route.params)) {
63
- return isEqual(route.params, item.params);
64
- } else {
65
- return route.path === item.path;
66
- }
67
- });
68
- moveToView(index);
69
- };
70
-
71
- const moveToView = async (index: number): Promise<void> => {
72
- await nextTick();
73
- const tabNavPadding = 10;
74
- if (!instance.refs['dynamic' + index]) return;
75
- const tabItemEl = instance.refs['dynamic' + index][0];
76
- const tabItemElOffsetLeft = (tabItemEl as HTMLElement)?.offsetLeft;
77
- const tabItemOffsetWidth = (tabItemEl as HTMLElement)?.offsetWidth;
78
- // 标签页导航栏可视长度(不包含溢出部分)
79
- const scrollbarDomWidth = scrollbarDom.value ? scrollbarDom.value.offsetWidth : 0;
80
- // 已有标签页总长度(包含溢出部分)
81
- const tabDomWidth = tabDom.value ? tabDom.value?.offsetWidth : 0;
82
- scrollbarDomWidth <= tabDomWidth ? (isShowArrow.value = true) : (isShowArrow.value = false);
83
- if (tabDomWidth < scrollbarDomWidth || tabItemElOffsetLeft === 0) {
84
- translateX.value = 0;
85
- } else if (tabItemElOffsetLeft < -translateX.value) {
86
- // 标签在可视区域左侧
87
- translateX.value = -tabItemElOffsetLeft + tabNavPadding;
88
- } else if (
89
- tabItemElOffsetLeft > -translateX.value &&
90
- tabItemElOffsetLeft + tabItemOffsetWidth < -translateX.value + scrollbarDomWidth
91
- ) {
92
- // 标签在可视区域
93
- translateX.value = Math.min(0, scrollbarDomWidth - tabItemOffsetWidth - tabItemElOffsetLeft - tabNavPadding);
94
- } else {
95
- // 标签在可视区域右侧
96
- translateX.value = -(tabItemElOffsetLeft - (scrollbarDomWidth - tabNavPadding - tabItemOffsetWidth));
97
- }
98
- };
99
-
100
- const handleScroll = (offset: number): void => {
101
- const scrollbarDomWidth = scrollbarDom.value ? scrollbarDom.value?.offsetWidth : 0;
102
- const tabDomWidth = tabDom.value ? tabDom.value.offsetWidth : 0;
103
- if (offset > 0) {
104
- translateX.value = Math.min(0, translateX.value + offset);
105
- } else {
106
- if (scrollbarDomWidth < tabDomWidth) {
107
- if (translateX.value >= -(tabDomWidth - scrollbarDomWidth)) {
108
- translateX.value = Math.max(translateX.value + offset, scrollbarDomWidth - tabDomWidth);
109
- }
110
- } else {
111
- translateX.value = 0;
112
- }
113
- }
114
- };
115
-
116
- function dynamicRouteTag(value: string): void {
117
- const hasValue = multiTags.value.some(item => {
118
- return item.path === value;
119
- });
120
-
121
- function concatPath(arr: object[], value: string) {
122
- if (!hasValue) {
123
- arr.forEach((arrItem: any) => {
124
- if (arrItem.path === value) {
125
- useMultiTagsStoreHook().handleTags('push', {
126
- path: value,
127
- meta: arrItem.meta,
128
- name: arrItem.name
129
- });
130
- } else {
131
- if (arrItem.children && arrItem.children.length > 0) {
132
- concatPath(arrItem.children, value);
133
- }
134
- }
135
- });
136
- }
137
- }
138
- concatPath(router.options.routes as any, value);
139
- }
140
-
141
- /** 刷新路由 */
142
- function onFresh() {
143
- const { fullPath, query } = unref(route);
144
- router.replace({
145
- path: '/redirect' + fullPath,
146
- query: query
147
- });
148
- handleAliveRoute(route as ToRouteType, 'refresh');
149
- }
150
-
151
- function deleteDynamicTag(obj: any, current: any, tag?: string) {
152
- const valueIndex: number = multiTags.value.findIndex((item: any) => {
153
- if (item.query) {
154
- if (item.path === obj.path) {
155
- return item.query === obj.query;
156
- }
157
- } else if (item.params) {
158
- if (item.path === obj.path) {
159
- return item.params === obj.params;
160
- }
161
- } else {
162
- return item.path === obj.path;
163
- }
164
- });
165
-
166
- const spliceRoute = (startIndex?: number, length?: number, other?: boolean): void => {
167
- if (other) {
168
- useMultiTagsStoreHook().handleTags('equal', [fixedTags, obj].flat());
169
- } else {
170
- useMultiTagsStoreHook().handleTags('splice', '', {
171
- startIndex,
172
- length
173
- }) as any;
174
- }
175
- dynamicTagView();
176
- };
177
-
178
- if (tag === 'other') {
179
- spliceRoute(1, 1, true);
180
- } else if (tag === 'left') {
181
- spliceRoute(fixedTags.length, valueIndex - fixedTags.length);
182
- } else if (tag === 'right') {
183
- spliceRoute(valueIndex + 1, multiTags.value.length);
184
- } else {
185
- // 从当前匹配到的路径中删除
186
- spliceRoute(valueIndex, 1);
187
- }
188
- const newRoute = useMultiTagsStoreHook().handleTags('slice');
189
- if (current === route.path) {
190
- // 如果删除当前激活tag就自动切换到最后一个tag
191
- if (tag === 'left') return;
192
- if (newRoute[0]?.query) {
193
- router.push({ name: newRoute[0].name, query: newRoute[0].query });
194
- } else if (newRoute[0]?.params) {
195
- router.push({ name: newRoute[0].name, params: newRoute[0].params });
196
- } else {
197
- router.push({ path: newRoute[0].path });
198
- }
199
- } else {
200
- if (!multiTags.value.length) return;
201
- if (multiTags.value.some(item => item.path === route.path)) return;
202
- if (newRoute[0]?.query) {
203
- router.push({ name: newRoute[0].name, query: newRoute[0].query });
204
- } else if (newRoute[0]?.params) {
205
- router.push({ name: newRoute[0].name, params: newRoute[0].params });
206
- } else {
207
- router.push({ path: newRoute[0].path });
208
- }
209
- }
210
- }
211
-
212
- function deleteMenu(item, tag?: string) {
213
- deleteDynamicTag(item, item.path, tag);
214
- handleAliveRoute(route as ToRouteType);
215
- }
216
-
217
- function onClickDrop(key, item, selectRoute?: RouteConfigs) {
218
- if (item && item.disabled) return;
219
- let selectTagRoute;
220
- if (selectRoute) {
221
- selectTagRoute = {
222
- path: selectRoute.path,
223
- meta: selectRoute.meta,
224
- name: selectRoute.name,
225
- query: selectRoute?.query,
226
- params: selectRoute?.params
227
- };
228
- } else {
229
- selectTagRoute = { path: route.path, meta: route.meta };
230
- }
231
-
232
- // 当前路由信息
233
- switch (key) {
234
- case 0:
235
- // 刷新路由
236
- onFresh();
237
- break;
238
- case 1:
239
- // 关闭当前标签页
240
- deleteMenu(selectTagRoute);
241
- break;
242
- case 2:
243
- // 关闭左侧标签页
244
- deleteMenu(selectTagRoute, 'left');
245
- break;
246
- case 3:
247
- // 关闭右侧标签页
248
- deleteMenu(selectTagRoute, 'right');
249
- break;
250
- case 4:
251
- // 关闭其他标签页
252
- deleteMenu(selectTagRoute, 'other');
253
- break;
254
- case 5:
255
- // 关闭全部标签页
256
- useMultiTagsStoreHook().handleTags('splice', '', {
257
- startIndex: fixedTags.length,
258
- length: multiTags.value.length
259
- });
260
- router.push('/welcome');
261
- handleAliveRoute(route as ToRouteType);
262
- break;
263
- case 6:
264
- // 整体页面全屏
265
- toggle();
266
- setTimeout(() => {
267
- if (isFullscreen.value) {
268
- tagsViews[6].icon = 'ri:fullscreen-exit-fill';
269
- tagsViews[6].text = 'message.btn.wholeExitFullScreen';
270
- } else {
271
- tagsViews[6].icon = 'ri:fullscreen-fill';
272
- tagsViews[6].text = 'message.btn.wholeFullScreen';
273
- }
274
- }, 100);
275
- break;
276
- case 7:
277
- // 内容区全屏
278
- onContentFullScreen();
279
- setTimeout(() => {
280
- if (SuSetting.hiddenSideBar) {
281
- tagsViews[7].icon = 'ri:fullscreen-exit-fill';
282
- tagsViews[7].text = 'message.btn.contentExitFullScreen';
283
- } else {
284
- tagsViews[7].icon = 'ri:fullscreen-fill';
285
- tagsViews[7].text = 'message.btn.contentFullScreen';
286
- }
287
- }, 100);
288
- break;
289
- }
290
- setTimeout(() => {
291
- showMenuModel(route.fullPath, route.query, route.params);
292
- });
293
- }
294
-
295
- function handleCommand(command: any) {
296
- const { key, item } = command;
297
- onClickDrop(key, item);
298
- }
299
-
300
- /** 触发右键中菜单的点击事件 */
301
- function selectTag(key, item) {
302
- closeMenu();
303
- onClickDrop(key, item, currentSelect.value);
304
- }
305
-
306
- function showMenus(value: boolean) {
307
- Array.of(1, 2, 3, 4, 5).forEach(v => {
308
- tagsViews[v].show = value;
309
- });
310
- }
311
-
312
- function disabledMenus(value: boolean, fixedTag = false) {
313
- Array.of(1, 2, 3, 4, 5).forEach(v => {
314
- tagsViews[v].disabled = value;
315
- });
316
- if (fixedTag) {
317
- tagsViews[2].show = false;
318
- tagsViews[2].disabled = true;
319
- }
320
- }
321
-
322
- /** 检查当前右键的菜单两边是否存在别的菜单,如果左侧的菜单是顶级菜单,则不显示关闭左侧标签页,如果右侧没有菜单,则不显示关闭右侧标签页 */
323
- function showMenuModel(currentPath: string, query: object = {}, params: object = {}, refresh = false) {
324
- const allRoute = multiTags.value;
325
- const routeLength = multiTags.value.length;
326
- let currentIndex = -1;
327
- if (!isEmpty(params)) {
328
- currentIndex = allRoute.findIndex(v => isEqual(v.params, params));
329
- } else if (isEmpty(query)) {
330
- currentIndex = allRoute.findIndex(v => v.path === currentPath);
331
- } else {
332
- currentIndex = allRoute.findIndex(v => isEqual(v.query, query));
333
- }
334
-
335
- function fixedTagDisabled() {
336
- if (allRoute[currentIndex]?.meta?.fixedTag) {
337
- Array.of(1, 2, 3, 4, 5).forEach(v => {
338
- tagsViews[v].disabled = true;
339
- });
340
- }
341
- }
342
-
343
- showMenus(true);
344
-
345
- if (refresh) {
346
- tagsViews[0].show = true;
347
- }
348
-
349
- /**
350
- * currentIndex为1时,左侧的菜单顶级菜单,则不显示关闭左侧标签页
351
- * 如果currentIndex等于routeLength-1,右侧没有菜单,则不显示关闭右侧标签页
352
- */
353
- if (currentIndex === 1 && routeLength !== 2) {
354
- // 左侧的菜单是顶级菜单,右侧存在别的菜单
355
- tagsViews[2].show = false;
356
- Array.of(1, 3, 4, 5).forEach(v => {
357
- tagsViews[v].disabled = false;
358
- });
359
- tagsViews[2].disabled = true;
360
- fixedTagDisabled();
361
- } else if (currentIndex === 1 && routeLength === 2) {
362
- disabledMenus(false);
363
- // 左侧的菜单是顶级菜单,右侧不存在别的菜单
364
- Array.of(2, 3, 4).forEach(v => {
365
- tagsViews[v].show = false;
366
- tagsViews[v].disabled = true;
367
- });
368
- fixedTagDisabled();
369
- } else if (routeLength - 1 === currentIndex && currentIndex !== 0) {
370
- // 当前路由是所有路由中的最后一个
371
- tagsViews[3].show = false;
372
- Array.of(1, 2, 4, 5).forEach(v => {
373
- tagsViews[v].disabled = false;
374
- });
375
- tagsViews[3].disabled = true;
376
- if (allRoute[currentIndex - 1]?.meta?.fixedTag) {
377
- tagsViews[2].show = false;
378
- tagsViews[2].disabled = true;
379
- }
380
- fixedTagDisabled();
381
- } else if (currentIndex === 0 || currentPath === '/redirect/welcome') {
382
- // 当前路由为首页
383
- disabledMenus(true);
384
- } else {
385
- disabledMenus(false, allRoute[currentIndex - 1]?.meta?.fixedTag);
386
- fixedTagDisabled();
387
- }
388
- }
389
-
390
- function openMenu(tag, e) {
391
- closeMenu();
392
- if (tag.path === '/welcome' || tag?.meta?.fixedTag) {
393
- // 右键菜单为首页或拥有 fixedTag 属性,只显示刷新
394
- showMenus(false);
395
- tagsViews[0].show = true;
396
- } else if (route.path !== tag.path && route.name !== tag.name) {
397
- // 右键菜单不匹配当前路由,隐藏刷新
398
- tagsViews[0].show = false;
399
- showMenuModel(tag.path, tag.query, tag.params);
400
- } else if (multiTags.value.length === 2 && route.path !== tag.path) {
401
- showMenus(true);
402
- // 只有两个标签时不显示关闭其他标签页
403
- tagsViews[4].show = false;
404
- showMenuModel(tag.path, tag.query, tag.params);
405
- } else if (route.path === tag.path) {
406
- // 右键当前激活的菜单
407
- showMenuModel(tag.path, tag.query, tag.params, true);
408
- }
409
-
410
- currentSelect.value = tag;
411
- const menuMinWidth = 140;
412
- const offsetLeft = unref(containerDom).getBoundingClientRect().left;
413
- const offsetWidth = unref(containerDom).offsetWidth;
414
- const maxLeft = offsetWidth - menuMinWidth;
415
- const left = e.clientX - offsetLeft + 5;
416
- if (left > maxLeft) {
417
- buttonLeft.value = maxLeft;
418
- } else {
419
- buttonLeft.value = left;
420
- }
421
- suSetting.hiddenSideBar ? (buttonTop.value = e.clientY) : (buttonTop.value = e.clientY - 40);
422
- nextTick(() => {
423
- visible.value = true;
424
- });
425
- }
426
-
427
- /** 触发tags标签切换 */
428
- function tagOnClick(item) {
429
- const { name, path } = item;
430
- if (name) {
431
- if (item.query) {
432
- router.push({
433
- name,
434
- query: item.query
435
- });
436
- } else if (item.params) {
437
- router.push({
438
- name,
439
- params: item.params
440
- });
441
- } else {
442
- router.push({ name });
443
- }
444
- } else {
445
- router.push({ path });
446
- }
447
- // showMenuModel(item?.path, item?.query);
448
- emitter.emit('tagOnClick', item);
449
- }
450
-
451
- onClickOutside(contextmenuRef, closeMenu, {
452
- detectIframe: true
453
- });
454
-
455
- watch(route, () => {
456
- activeIndex.value = -1;
457
- dynamicTagView();
458
- });
459
-
460
- watch(isFullscreen, () => {
461
- tagsViews[6].icon = 'ri:fullscreen-fill';
462
- tagsViews[6].text = 'message.wholeFullScreen';
463
- });
464
-
465
- onMounted(() => {
466
- if (!instance) return;
467
-
468
- // 根据当前路由初始化操作标签页的禁用状态
469
- showMenuModel(route.fullPath);
470
-
471
- // 触发隐藏标签页
472
- emitter.on('tagViewsChange', (key: any) => {
473
- if (unref(showTags as any) === key) return;
474
- (showTags as any).value = key;
475
- });
476
-
477
- // 改变标签风格
478
- emitter.on('tagViewsShowModel', key => {
479
- showModel.value = key;
480
- });
481
-
482
- // 接收侧边栏切换传递过来的参数
483
- emitter.on('changLayoutRoute', indexPath => {
484
- dynamicRouteTag(indexPath);
485
- setTimeout(() => {
486
- showMenuModel(indexPath);
487
- });
488
- });
489
-
490
- useResizeObserver(
491
- scrollbarDom,
492
- useDebounceFn(() => {
493
- dynamicTagView();
494
- }, 200)
495
- );
496
- delay().then(() => dynamicTagView());
497
- });
498
-
499
- onBeforeUnmount(() => {
500
- // 解绑`tagViewsChange`、`tagViewsShowModel`、`changLayoutRoute`公共事件,防止多次触发
501
- emitter.off('tagViewsChange');
502
- emitter.off('tagViewsShowModel');
503
- emitter.off('changLayoutRoute');
504
- });
505
- </script>
506
-
507
- <template>
508
- <div v-if="!showTags" ref="containerDom" class="tags-view">
509
- <span v-show="isShowArrow" class="arrow-left">
510
- <IconifyIconOffline icon="ri:arrow-left-s-line" @click="handleScroll(200)" />
511
- </span>
512
- <div ref="scrollbarDom" class="scroll-container" :class="showModel === 'chrome' && 'chrome-scroll-container'">
513
- <div ref="tabDom" class="select-none tab" :style="getTabStyle">
514
- <div
515
- v-for="(item, index) in multiTags"
516
- :key="index"
517
- :ref="'dynamic' + index"
518
- :class="[
519
- 'scroll-item is-closable',
520
- linkIsActive(item),
521
- showModel === 'chrome' && 'chrome-item',
522
- isFixedTag(item) && 'fixed-tag'
523
- ]"
524
- @contextmenu.prevent="openMenu(item, $event)"
525
- @mouseenter.prevent="onMouseenter(index)"
526
- @mouseleave.prevent="onMouseleave(index)"
527
- @click="tagOnClick(item)"
528
- >
529
- <template v-if="showModel !== 'chrome'">
530
- <span class="tag-title dark:text-text_color_primary! dark:hover:text-primary!">
531
- {{ transformI18n(item?.meta?.title) }}
532
- </span>
533
- <span
534
- v-if="isFixedTag(item) ? false : iconIsActive(item, index) || (index === activeIndex && index !== 0)"
535
- class="el-icon-close"
536
- @click.stop="deleteMenu(item)"
537
- >
538
- <IconifyIconOffline icon="ri:close-fill" />
539
- </span>
540
- <span v-if="showModel !== 'card'" :ref="'schedule' + index" :class="[scheduleIsActive(item)]" />
541
- </template>
542
- <div v-else class="chrome-tab">
543
- <div class="chrome-tab__bg">
544
- <TagChrome />
545
- </div>
546
- <span class="tag-title">
547
- {{ transformI18n(item?.meta?.title) }}
548
- </span>
549
- <span v-if="isFixedTag(item) ? false : index !== 0" class="chrome-close-btn" @click.stop="deleteMenu(item)">
550
- <IconifyIconOffline icon="ri:close-fill" />
551
- </span>
552
- <span class="chrome-tab-divider" />
553
- </div>
554
- </div>
555
- </div>
556
- </div>
557
- <span v-show="isShowArrow" class="arrow-right">
558
- <IconifyIconOffline icon="ri:arrow-right-s-line" @click="handleScroll(-200)" />
559
- </span>
560
- <!-- 右键菜单按钮 -->
561
- <transition name="el-zoom-in-top">
562
- <ul v-show="visible" ref="contextmenuRef" :key="Math.random()" :style="getContextMenuStyle" class="contextmenu">
563
- <div v-for="(item, key) in tagsViews.slice(0, 6)" :key="key" style="display: flex; align-items: center">
564
- <li v-if="item.show" @click="selectTag(key, item)">
565
- <IconifyIconOffline :icon="item.icon" />
566
- {{ $t(item.text) }}
567
- </li>
568
- </div>
569
- </ul>
570
- </transition>
571
- <!-- 右侧功能按钮 -->
572
- <el-dropdown trigger="click" placement="bottom-end" @command="handleCommand">
573
- <span class="arrow-down">
574
- <IconifyIconOffline icon="ri:arrow-down-s-line" class="dark:text-white" />
575
- </span>
576
- <template #dropdown>
577
- <el-dropdown-menu>
578
- <el-dropdown-item
579
- v-for="(item, key) in tagsViews"
580
- :key="key"
581
- :command="{ key, item }"
582
- :divided="item.divided"
583
- :disabled="item.disabled"
584
- >
585
- <IconifyIconOffline :icon="item.icon" />
586
- {{ $t(item.text) }}
587
- </el-dropdown-item>
588
- </el-dropdown-menu>
589
- </template>
590
- </el-dropdown>
591
- </div>
592
- </template>
593
-
594
- <style lang="scss" scoped>
595
- @import url('./index.scss');
596
- </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 = [...routerArrays, ...usePermissionStoreHook().flatteningRoutes.filter(v => v?.meta?.fixedTag)];
57
+ const dynamicTagView = async () => {
58
+ await nextTick();
59
+ const index = multiTags.value.findIndex(item => {
60
+ if (!isEmpty(route.query)) {
61
+ return isEqual(route.query, item.query);
62
+ } else if (!isEmpty(route.params)) {
63
+ return isEqual(route.params, item.params);
64
+ } else {
65
+ return route.path === item.path;
66
+ }
67
+ });
68
+ moveToView(index);
69
+ };
70
+
71
+ const moveToView = async (index: number): Promise<void> => {
72
+ await nextTick();
73
+ const tabNavPadding = 10;
74
+ if (!instance.refs['dynamic' + index]) return;
75
+ const tabItemEl = instance.refs['dynamic' + index][0];
76
+ const tabItemElOffsetLeft = (tabItemEl as HTMLElement)?.offsetLeft;
77
+ const tabItemOffsetWidth = (tabItemEl as HTMLElement)?.offsetWidth;
78
+ // 标签页导航栏可视长度(不包含溢出部分)
79
+ const scrollbarDomWidth = scrollbarDom.value ? scrollbarDom.value.offsetWidth : 0;
80
+ // 已有标签页总长度(包含溢出部分)
81
+ const tabDomWidth = tabDom.value ? tabDom.value?.offsetWidth : 0;
82
+ scrollbarDomWidth <= tabDomWidth ? (isShowArrow.value = true) : (isShowArrow.value = false);
83
+ if (tabDomWidth < scrollbarDomWidth || tabItemElOffsetLeft === 0) {
84
+ translateX.value = 0;
85
+ } else if (tabItemElOffsetLeft < -translateX.value) {
86
+ // 标签在可视区域左侧
87
+ translateX.value = -tabItemElOffsetLeft + tabNavPadding;
88
+ } else if (
89
+ tabItemElOffsetLeft > -translateX.value &&
90
+ tabItemElOffsetLeft + tabItemOffsetWidth < -translateX.value + scrollbarDomWidth
91
+ ) {
92
+ // 标签在可视区域
93
+ translateX.value = Math.min(0, scrollbarDomWidth - tabItemOffsetWidth - tabItemElOffsetLeft - tabNavPadding);
94
+ } else {
95
+ // 标签在可视区域右侧
96
+ translateX.value = -(tabItemElOffsetLeft - (scrollbarDomWidth - tabNavPadding - tabItemOffsetWidth));
97
+ }
98
+ };
99
+
100
+ const handleScroll = (offset: number): void => {
101
+ const scrollbarDomWidth = scrollbarDom.value ? scrollbarDom.value?.offsetWidth : 0;
102
+ const tabDomWidth = tabDom.value ? tabDom.value.offsetWidth : 0;
103
+ if (offset > 0) {
104
+ translateX.value = Math.min(0, translateX.value + offset);
105
+ } else {
106
+ if (scrollbarDomWidth < tabDomWidth) {
107
+ if (translateX.value >= -(tabDomWidth - scrollbarDomWidth)) {
108
+ translateX.value = Math.max(translateX.value + offset, scrollbarDomWidth - tabDomWidth);
109
+ }
110
+ } else {
111
+ translateX.value = 0;
112
+ }
113
+ }
114
+ };
115
+
116
+ function dynamicRouteTag(value: string): void {
117
+ const hasValue = multiTags.value.some(item => {
118
+ return item.path === value;
119
+ });
120
+
121
+ function concatPath(arr: object[], value: string) {
122
+ if (!hasValue) {
123
+ arr.forEach((arrItem: any) => {
124
+ if (arrItem.path === value) {
125
+ useMultiTagsStoreHook().handleTags('push', {
126
+ path: value,
127
+ meta: arrItem.meta,
128
+ name: arrItem.name
129
+ });
130
+ } else {
131
+ if (arrItem.children && arrItem.children.length > 0) {
132
+ concatPath(arrItem.children, value);
133
+ }
134
+ }
135
+ });
136
+ }
137
+ }
138
+ concatPath(router.options.routes as any, value);
139
+ }
140
+
141
+ /** 刷新路由 */
142
+ function onFresh() {
143
+ const { fullPath, query } = unref(route);
144
+ router.replace({
145
+ path: '/redirect' + fullPath,
146
+ query: query
147
+ });
148
+ handleAliveRoute(route as ToRouteType, 'refresh');
149
+ }
150
+
151
+ function deleteDynamicTag(obj: any, current: any, tag?: string) {
152
+ const valueIndex: number = multiTags.value.findIndex((item: any) => {
153
+ if (item.query) {
154
+ if (item.path === obj.path) {
155
+ return item.query === obj.query;
156
+ }
157
+ } else if (item.params) {
158
+ if (item.path === obj.path) {
159
+ return item.params === obj.params;
160
+ }
161
+ } else {
162
+ return item.path === obj.path;
163
+ }
164
+ });
165
+
166
+ const spliceRoute = (startIndex?: number, length?: number, other?: boolean): void => {
167
+ if (other) {
168
+ useMultiTagsStoreHook().handleTags('equal', [fixedTags, obj].flat());
169
+ } else {
170
+ useMultiTagsStoreHook().handleTags('splice', '', {
171
+ startIndex,
172
+ length
173
+ }) as any;
174
+ }
175
+ dynamicTagView();
176
+ };
177
+
178
+ if (tag === 'other') {
179
+ spliceRoute(1, 1, true);
180
+ } else if (tag === 'left') {
181
+ spliceRoute(fixedTags.length, valueIndex - fixedTags.length);
182
+ } else if (tag === 'right') {
183
+ spliceRoute(valueIndex + 1, multiTags.value.length);
184
+ } else {
185
+ // 从当前匹配到的路径中删除
186
+ spliceRoute(valueIndex, 1);
187
+ }
188
+ const newRoute = useMultiTagsStoreHook().handleTags('slice');
189
+ if (current === route.path) {
190
+ // 如果删除当前激活tag就自动切换到最后一个tag
191
+ if (tag === 'left') return;
192
+ if (newRoute[0]?.query) {
193
+ router.push({ name: newRoute[0].name, query: newRoute[0].query });
194
+ } else if (newRoute[0]?.params) {
195
+ router.push({ name: newRoute[0].name, params: newRoute[0].params });
196
+ } else {
197
+ router.push({ path: newRoute[0].path });
198
+ }
199
+ } else {
200
+ if (!multiTags.value.length) return;
201
+ if (multiTags.value.some(item => item.path === route.path)) return;
202
+ if (newRoute[0]?.query) {
203
+ router.push({ name: newRoute[0].name, query: newRoute[0].query });
204
+ } else if (newRoute[0]?.params) {
205
+ router.push({ name: newRoute[0].name, params: newRoute[0].params });
206
+ } else {
207
+ router.push({ path: newRoute[0].path });
208
+ }
209
+ }
210
+ }
211
+
212
+ function deleteMenu(item, tag?: string) {
213
+ deleteDynamicTag(item, item.path, tag);
214
+ handleAliveRoute(route as ToRouteType);
215
+ }
216
+
217
+ function onClickDrop(key, item, selectRoute?: RouteConfigs) {
218
+ if (item && item.disabled) return;
219
+ let selectTagRoute;
220
+ if (selectRoute) {
221
+ selectTagRoute = {
222
+ path: selectRoute.path,
223
+ meta: selectRoute.meta,
224
+ name: selectRoute.name,
225
+ query: selectRoute?.query,
226
+ params: selectRoute?.params
227
+ };
228
+ } else {
229
+ selectTagRoute = { path: route.path, meta: route.meta };
230
+ }
231
+
232
+ // 当前路由信息
233
+ switch (key) {
234
+ case 0:
235
+ // 刷新路由
236
+ onFresh();
237
+ break;
238
+ case 1:
239
+ // 关闭当前标签页
240
+ deleteMenu(selectTagRoute);
241
+ break;
242
+ case 2:
243
+ // 关闭左侧标签页
244
+ deleteMenu(selectTagRoute, 'left');
245
+ break;
246
+ case 3:
247
+ // 关闭右侧标签页
248
+ deleteMenu(selectTagRoute, 'right');
249
+ break;
250
+ case 4:
251
+ // 关闭其他标签页
252
+ deleteMenu(selectTagRoute, 'other');
253
+ break;
254
+ case 5:
255
+ // 关闭全部标签页
256
+ useMultiTagsStoreHook().handleTags('splice', '', {
257
+ startIndex: fixedTags.length,
258
+ length: multiTags.value.length
259
+ });
260
+ router.push('/welcome');
261
+ handleAliveRoute(route as ToRouteType);
262
+ break;
263
+ case 6:
264
+ // 整体页面全屏
265
+ toggle();
266
+ setTimeout(() => {
267
+ if (isFullscreen.value) {
268
+ tagsViews[6].icon = 'ri:fullscreen-exit-fill';
269
+ tagsViews[6].text = 'message.btn.wholeExitFullScreen';
270
+ } else {
271
+ tagsViews[6].icon = 'ri:fullscreen-fill';
272
+ tagsViews[6].text = 'message.btn.wholeFullScreen';
273
+ }
274
+ }, 100);
275
+ break;
276
+ case 7:
277
+ // 内容区全屏
278
+ onContentFullScreen();
279
+ setTimeout(() => {
280
+ if (SuSetting.hiddenSideBar) {
281
+ tagsViews[7].icon = 'ri:fullscreen-exit-fill';
282
+ tagsViews[7].text = 'message.btn.contentExitFullScreen';
283
+ } else {
284
+ tagsViews[7].icon = 'ri:fullscreen-fill';
285
+ tagsViews[7].text = 'message.btn.contentFullScreen';
286
+ }
287
+ }, 100);
288
+ break;
289
+ }
290
+ setTimeout(() => {
291
+ showMenuModel(route.fullPath, route.query, route.params);
292
+ });
293
+ }
294
+
295
+ function handleCommand(command: any) {
296
+ const { key, item } = command;
297
+ onClickDrop(key, item);
298
+ }
299
+
300
+ /** 触发右键中菜单的点击事件 */
301
+ function selectTag(key, item) {
302
+ closeMenu();
303
+ onClickDrop(key, item, currentSelect.value);
304
+ }
305
+
306
+ function showMenus(value: boolean) {
307
+ Array.of(1, 2, 3, 4, 5).forEach(v => {
308
+ tagsViews[v].show = value;
309
+ });
310
+ }
311
+
312
+ function disabledMenus(value: boolean, fixedTag = false) {
313
+ Array.of(1, 2, 3, 4, 5).forEach(v => {
314
+ tagsViews[v].disabled = value;
315
+ });
316
+ if (fixedTag) {
317
+ tagsViews[2].show = false;
318
+ tagsViews[2].disabled = true;
319
+ }
320
+ }
321
+
322
+ /** 检查当前右键的菜单两边是否存在别的菜单,如果左侧的菜单是顶级菜单,则不显示关闭左侧标签页,如果右侧没有菜单,则不显示关闭右侧标签页 */
323
+ function showMenuModel(currentPath: string, query: object = {}, params: object = {}, refresh = false) {
324
+ const allRoute = multiTags.value;
325
+ const routeLength = multiTags.value.length;
326
+ let currentIndex = -1;
327
+ if (!isEmpty(params)) {
328
+ currentIndex = allRoute.findIndex(v => isEqual(v.params, params));
329
+ } else if (isEmpty(query)) {
330
+ currentIndex = allRoute.findIndex(v => v.path === currentPath);
331
+ } else {
332
+ currentIndex = allRoute.findIndex(v => isEqual(v.query, query));
333
+ }
334
+
335
+ function fixedTagDisabled() {
336
+ if (allRoute[currentIndex]?.meta?.fixedTag) {
337
+ Array.of(1, 2, 3, 4, 5).forEach(v => {
338
+ tagsViews[v].disabled = true;
339
+ });
340
+ }
341
+ }
342
+
343
+ showMenus(true);
344
+
345
+ if (refresh) {
346
+ tagsViews[0].show = true;
347
+ }
348
+
349
+ /**
350
+ * currentIndex为1时,左侧的菜单顶级菜单,则不显示关闭左侧标签页
351
+ * 如果currentIndex等于routeLength-1,右侧没有菜单,则不显示关闭右侧标签页
352
+ */
353
+ if (currentIndex === 1 && routeLength !== 2) {
354
+ // 左侧的菜单是顶级菜单,右侧存在别的菜单
355
+ tagsViews[2].show = false;
356
+ Array.of(1, 3, 4, 5).forEach(v => {
357
+ tagsViews[v].disabled = false;
358
+ });
359
+ tagsViews[2].disabled = true;
360
+ fixedTagDisabled();
361
+ } else if (currentIndex === 1 && routeLength === 2) {
362
+ disabledMenus(false);
363
+ // 左侧的菜单是顶级菜单,右侧不存在别的菜单
364
+ Array.of(2, 3, 4).forEach(v => {
365
+ tagsViews[v].show = false;
366
+ tagsViews[v].disabled = true;
367
+ });
368
+ fixedTagDisabled();
369
+ } else if (routeLength - 1 === currentIndex && currentIndex !== 0) {
370
+ // 当前路由是所有路由中的最后一个
371
+ tagsViews[3].show = false;
372
+ Array.of(1, 2, 4, 5).forEach(v => {
373
+ tagsViews[v].disabled = false;
374
+ });
375
+ tagsViews[3].disabled = true;
376
+ if (allRoute[currentIndex - 1]?.meta?.fixedTag) {
377
+ tagsViews[2].show = false;
378
+ tagsViews[2].disabled = true;
379
+ }
380
+ fixedTagDisabled();
381
+ } else if (currentIndex === 0 || currentPath === '/redirect/welcome') {
382
+ // 当前路由为首页
383
+ disabledMenus(true);
384
+ } else {
385
+ disabledMenus(false, allRoute[currentIndex - 1]?.meta?.fixedTag);
386
+ fixedTagDisabled();
387
+ }
388
+ }
389
+
390
+ function openMenu(tag, e) {
391
+ closeMenu();
392
+ if (tag.path === '/welcome' || tag?.meta?.fixedTag) {
393
+ // 右键菜单为首页或拥有 fixedTag 属性,只显示刷新
394
+ showMenus(false);
395
+ tagsViews[0].show = true;
396
+ } else if (route.path !== tag.path && route.name !== tag.name) {
397
+ // 右键菜单不匹配当前路由,隐藏刷新
398
+ tagsViews[0].show = false;
399
+ showMenuModel(tag.path, tag.query, tag.params);
400
+ } else if (multiTags.value.length === 2 && route.path !== tag.path) {
401
+ showMenus(true);
402
+ // 只有两个标签时不显示关闭其他标签页
403
+ tagsViews[4].show = false;
404
+ showMenuModel(tag.path, tag.query, tag.params);
405
+ } else if (route.path === tag.path) {
406
+ // 右键当前激活的菜单
407
+ showMenuModel(tag.path, tag.query, tag.params, true);
408
+ }
409
+
410
+ currentSelect.value = tag;
411
+ const menuMinWidth = 140;
412
+ const offsetLeft = unref(containerDom).getBoundingClientRect().left;
413
+ const offsetWidth = unref(containerDom).offsetWidth;
414
+ const maxLeft = offsetWidth - menuMinWidth;
415
+ const left = e.clientX - offsetLeft + 5;
416
+ if (left > maxLeft) {
417
+ buttonLeft.value = maxLeft;
418
+ } else {
419
+ buttonLeft.value = left;
420
+ }
421
+ suSetting.hiddenSideBar ? (buttonTop.value = e.clientY) : (buttonTop.value = e.clientY - 40);
422
+ nextTick(() => {
423
+ visible.value = true;
424
+ });
425
+ }
426
+
427
+ /** 触发tags标签切换 */
428
+ function tagOnClick(item) {
429
+ const { name, path } = item;
430
+ if (name) {
431
+ if (item.query) {
432
+ router.push({
433
+ name,
434
+ query: item.query
435
+ });
436
+ } else if (item.params) {
437
+ router.push({
438
+ name,
439
+ params: item.params
440
+ });
441
+ } else {
442
+ router.push({ name });
443
+ }
444
+ } else {
445
+ router.push({ path });
446
+ }
447
+ // showMenuModel(item?.path, item?.query);
448
+ emitter.emit('tagOnClick', item);
449
+ }
450
+
451
+ onClickOutside(contextmenuRef, closeMenu, {
452
+ detectIframe: true
453
+ });
454
+
455
+ watch(route, () => {
456
+ activeIndex.value = -1;
457
+ dynamicTagView();
458
+ });
459
+
460
+ watch(isFullscreen, () => {
461
+ tagsViews[6].icon = 'ri:fullscreen-fill';
462
+ tagsViews[6].text = 'message.wholeFullScreen';
463
+ });
464
+
465
+ onMounted(() => {
466
+ if (!instance) return;
467
+
468
+ // 根据当前路由初始化操作标签页的禁用状态
469
+ showMenuModel(route.fullPath);
470
+
471
+ // 触发隐藏标签页
472
+ emitter.on('tagViewsChange', (key: any) => {
473
+ if (unref(showTags as any) === key) return;
474
+ (showTags as any).value = key;
475
+ });
476
+
477
+ // 改变标签风格
478
+ emitter.on('tagViewsShowModel', key => {
479
+ showModel.value = key;
480
+ });
481
+
482
+ // 接收侧边栏切换传递过来的参数
483
+ emitter.on('changLayoutRoute', indexPath => {
484
+ dynamicRouteTag(indexPath);
485
+ setTimeout(() => {
486
+ showMenuModel(indexPath);
487
+ });
488
+ });
489
+
490
+ useResizeObserver(
491
+ scrollbarDom,
492
+ useDebounceFn(() => {
493
+ dynamicTagView();
494
+ }, 200)
495
+ );
496
+ delay().then(() => dynamicTagView());
497
+ });
498
+
499
+ onBeforeUnmount(() => {
500
+ // 解绑`tagViewsChange`、`tagViewsShowModel`、`changLayoutRoute`公共事件,防止多次触发
501
+ emitter.off('tagViewsChange');
502
+ emitter.off('tagViewsShowModel');
503
+ emitter.off('changLayoutRoute');
504
+ });
505
+ </script>
506
+
507
+ <template>
508
+ <div v-if="!showTags" ref="containerDom" class="tags-view">
509
+ <span v-show="isShowArrow" class="arrow-left">
510
+ <IconifyIconOffline icon="ri:arrow-left-s-line" @click="handleScroll(200)" />
511
+ </span>
512
+ <div ref="scrollbarDom" class="scroll-container" :class="showModel === 'chrome' && 'chrome-scroll-container'">
513
+ <div ref="tabDom" class="select-none tab" :style="getTabStyle">
514
+ <div
515
+ v-for="(item, index) in multiTags"
516
+ :key="index"
517
+ :ref="'dynamic' + index"
518
+ :class="[
519
+ 'scroll-item is-closable',
520
+ linkIsActive(item),
521
+ showModel === 'chrome' && 'chrome-item',
522
+ isFixedTag(item) && 'fixed-tag'
523
+ ]"
524
+ @contextmenu.prevent="openMenu(item, $event)"
525
+ @mouseenter.prevent="onMouseenter(index)"
526
+ @mouseleave.prevent="onMouseleave(index)"
527
+ @click="tagOnClick(item)"
528
+ >
529
+ <template v-if="showModel !== 'chrome'">
530
+ <span class="tag-title dark:text-text_color_primary! dark:hover:text-primary!">
531
+ {{ transformI18n(item?.meta?.title) }}
532
+ </span>
533
+ <span
534
+ v-if="isFixedTag(item) ? false : iconIsActive(item, index) || (index === activeIndex && index !== 0)"
535
+ class="el-icon-close"
536
+ @click.stop="deleteMenu(item)"
537
+ >
538
+ <IconifyIconOffline icon="ri:close-fill" />
539
+ </span>
540
+ <span v-if="showModel !== 'card'" :ref="'schedule' + index" :class="[scheduleIsActive(item)]" />
541
+ </template>
542
+ <div v-else class="chrome-tab">
543
+ <div class="chrome-tab__bg">
544
+ <TagChrome />
545
+ </div>
546
+ <span class="tag-title">
547
+ {{ transformI18n(item?.meta?.title) }}
548
+ </span>
549
+ <span v-if="isFixedTag(item) ? false : index !== 0" class="chrome-close-btn" @click.stop="deleteMenu(item)">
550
+ <IconifyIconOffline icon="ri:close-fill" />
551
+ </span>
552
+ <span class="chrome-tab-divider" />
553
+ </div>
554
+ </div>
555
+ </div>
556
+ </div>
557
+ <span v-show="isShowArrow" class="arrow-right">
558
+ <IconifyIconOffline icon="ri:arrow-right-s-line" @click="handleScroll(-200)" />
559
+ </span>
560
+ <!-- 右键菜单按钮 -->
561
+ <transition name="el-zoom-in-top">
562
+ <ul v-show="visible" ref="contextmenuRef" :key="Math.random()" :style="getContextMenuStyle" class="contextmenu">
563
+ <div v-for="(item, key) in tagsViews.slice(0, 6)" :key="key" style="display: flex; align-items: center">
564
+ <li v-if="item.show" @click="selectTag(key, item)">
565
+ <IconifyIconOffline :icon="item.icon" />
566
+ {{ $t(item.text) }}
567
+ </li>
568
+ </div>
569
+ </ul>
570
+ </transition>
571
+ <!-- 右侧功能按钮 -->
572
+ <el-dropdown trigger="click" placement="bottom-end" @command="handleCommand">
573
+ <span class="arrow-down">
574
+ <IconifyIconOffline icon="ri:arrow-down-s-line" class="dark:text-white" />
575
+ </span>
576
+ <template #dropdown>
577
+ <el-dropdown-menu>
578
+ <el-dropdown-item
579
+ v-for="(item, key) in tagsViews"
580
+ :key="key"
581
+ :command="{ key, item }"
582
+ :divided="item.divided"
583
+ :disabled="item.disabled"
584
+ >
585
+ <IconifyIconOffline :icon="item.icon" />
586
+ {{ $t(item.text) }}
587
+ </el-dropdown-item>
588
+ </el-dropdown-menu>
589
+ </template>
590
+ </el-dropdown>
591
+ </div>
592
+ </template>
593
+
594
+ <style lang="scss" scoped>
595
+ @import url('./index.scss');
596
+ </style>