@utogether/udp-core 1.0.1-beta.2 → 1.0.1-beta.4

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 (98) hide show
  1. package/build/plugins.ts +6 -1
  2. package/dist/{403-BBQoJwTM.js → 403-CT4J-t-o.js} +1 -1
  3. package/dist/{404-BbJPSIWM.js → 404-BAudfnaK.js} +1 -1
  4. package/dist/{500-BtFL9R4M.js → 500-BlOGtpEE.js} +1 -1
  5. package/dist/{AuthorityInfo-DhiwCeLN.js → AuthorityInfo-Bl03tEhF.js} +1 -1
  6. package/dist/{AuthorityInfo.vue_vue_type_style_index_0_lang-Duyweh89.js → AuthorityInfo.vue_vue_type_style_index_0_lang-CUwS2F3R.js} +1 -1
  7. package/dist/{Company-CGqmslx-.js → Company-CIj9XgQn.js} +3 -3
  8. package/dist/{CompanyPanel-hlDsxD-6.js → CompanyPanel-CublK5NN.js} +16 -16
  9. package/dist/{Department-BZyJtacc.js → Department-BolPW-W3.js} +3 -3
  10. package/dist/{DepartmentPanel-D-a_EBFt.js → DepartmentPanel-BttxSHfM.js} +1 -1
  11. package/dist/{DesignPanel-Bl4luWDV.js → DesignPanel-BwCbn92O.js} +1 -1
  12. package/dist/{DesignPanel.vue_vue_type_style_index_0_lang-I8C3iGvz.js → DesignPanel.vue_vue_type_style_index_0_lang-y2AnCTg6.js} +2 -2
  13. package/dist/{DictView-B4a7Hs1X.js → DictView-D85ziNV2.js} +13 -12
  14. package/dist/InvOrganization-CaAk_f7F.js +66 -0
  15. package/dist/{Org-BTGTrAVz.js → Org-CreIihFE.js} +1 -1
  16. package/dist/{Preview-pHD84xqI.js → Preview-DMGbhV6q.js} +1 -1
  17. package/dist/{ReportDefine-DnnCNQWS.js → ReportDefine-DogZwa8T.js} +1 -1
  18. package/dist/{ReportDesign-BnI_Q4pg.js → ReportDesign-CdSsJJeA.js} +2 -2
  19. package/dist/{ReportQuery-Dby2MmtM.js → ReportQuery-DPvKqeUm.js} +1 -1
  20. package/dist/{ReportQueryFrom-Blm1N8P1.js → ReportQueryFrom-UokkfBx_.js} +1 -1
  21. package/dist/{ReportQueryFrom.vue_vue_type_style_index_0_lang-CnG_Ybnt.js → ReportQueryFrom.vue_vue_type_style_index_0_lang-BlOoThY7.js} +1 -1
  22. package/dist/{ReportTemplate-D65RXRY_.js → ReportTemplate-DCnmllXW.js} +1 -1
  23. package/dist/{Role-eFZoTpXc.js → Role-sfmwLiNM.js} +3 -3
  24. package/dist/{RoleAssign-D9-Y3UNz.js → RoleAssign-WRk90VO2.js} +3 -3
  25. package/dist/{RolePanel-DlFw6HSf.js → RolePanel-DhP0_5ur.js} +1 -1
  26. package/dist/{RolePanel-CoUOc3sX.js → RolePanel-iWysS02n.js} +1 -1
  27. package/dist/{RolePanel.vue_vue_type_script_setup_true_lang-DKHgNWQP.js → RolePanel.vue_vue_type_script_setup_true_lang-CYXqW-d4.js} +1 -1
  28. package/dist/{RolePanel.vue_vue_type_script_setup_true_lang-Clwk-MHw.js → RolePanel.vue_vue_type_script_setup_true_lang-_hVY31HC.js} +3 -3
  29. package/dist/{ScrollPanel.vue_vue_type_style_index_0_lang-M2FQJwPU.js → ScrollPanel.vue_vue_type_style_index_0_lang-BozDFES-.js} +1 -1
  30. package/dist/{Staff-DFBLCsAd.js → Staff-Bha-Ijax.js} +3 -3
  31. package/dist/{StaffInfo-d2CK0oBA.js → StaffInfo-CbfYI8UW.js} +1 -1
  32. package/dist/{StaffInfo.vue_vue_type_script_setup_true_lang-4m8wK9tq.js → StaffInfo.vue_vue_type_script_setup_true_lang-Ce-MUVYH.js} +1 -1
  33. package/dist/{StaffPanel-D6aWLKN3.js → StaffPanel-B8094e97.js} +1 -1
  34. package/dist/{StaffPanel.vue_vue_type_script_setup_true_lang-C7YT2CVb.js → StaffPanel.vue_vue_type_script_setup_true_lang-_kJIBCtx.js} +2 -2
  35. package/dist/{SysUser-BdZvYxQH.js → SysUser-D1J9vPnb.js} +2 -2
  36. package/dist/{SysUserPanel-BIVa6LLr.js → SysUserPanel-DGDPKKoY.js} +1 -1
  37. package/dist/{SysUserPanel.vue_vue_type_script_setup_true_lang-BsBKpYR7.js → SysUserPanel.vue_vue_type_script_setup_true_lang-D-5-zKn6.js} +1 -1
  38. package/dist/{SystemMenu-C-5VKlHK.js → SystemMenu-Dz8Pllh5.js} +9 -9
  39. package/dist/{UserInfo-DqXCRZts.js → UserInfo-ecAJA2Yg.js} +1 -1
  40. package/dist/{UserInfo.vue_vue_type_style_index_0_lang-N3TwfpPx.js → UserInfo.vue_vue_type_style_index_0_lang-4_-I_sQN.js} +1 -1
  41. package/dist/{childView-B2lSsqS3.js → childView-4uI_RT2m.js} +1 -1
  42. package/dist/{childView-DRUNqgjI.js → childView-OXXoMFg3.js} +1 -1
  43. package/dist/{childView.vue_vue_type_style_index_0_lang-oGriyFTv.js → childView.vue_vue_type_style_index_0_lang-DC-yx4Kv.js} +1 -1
  44. package/dist/{childView.vue_vue_type_style_index_0_lang-B9XBDDU_.js → childView.vue_vue_type_style_index_0_lang-e43PUsUO.js} +1 -1
  45. package/dist/{code-rule-DZC9T6cl.js → code-rule-B667R66s.js} +1 -1
  46. package/dist/core.es.js +14 -11
  47. package/dist/{cron-task-BJwPeA5F.js → cron-task-DcmGSr2D.js} +1 -1
  48. package/dist/{frameView-C6wkvok-.js → frameView-BzgaLYjQ.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-DZc3qRTx.js → index-CLliRySq.js} +516 -560
  53. package/dist/{layoutView-CeJBpZb_.js → layoutView-BBQB8pDc.js} +109 -108
  54. package/dist/{login-BOxwzwdB.js → login-DIG7pRU7.js} +2 -2
  55. package/dist/{lov-view-DRF-99U4.js → lov-view-CSIPKZSY.js} +2 -2
  56. package/dist/{menuInfo-3Sjvs9nM.js → menuInfo-CPHjl7id.js} +1 -1
  57. package/dist/{menuInfo.vue_vue_type_style_index_0_lang-DrQ560nm.js → menuInfo.vue_vue_type_style_index_0_lang-BXNiinTg.js} +31 -29
  58. package/dist/{pda-app-B9tn7jdv.js → pda-app-BLjbMcdt.js} +1 -1
  59. package/dist/{resource-BqnxbQNe.js → resource-MxqaSJqB.js} +1 -1
  60. package/dist/{su-welcome-VwifUK_O.js → su-welcome-C0flTYCx.js} +1 -1
  61. package/dist/{sys-config-C0-gSBFO.js → sys-config-CXzGs3Nl.js} +1 -1
  62. package/dist/udp-core.css +2 -2
  63. package/dist/{utogether-Di1byIum.js → utogether-CFdIDuAT.js} +1 -1
  64. package/index.ts +40 -36
  65. package/package.json +1 -1
  66. package/src/App.vue +70 -70
  67. package/src/components/udp/grid.vue +505 -0
  68. package/src/components/udp/index.ts +7 -4
  69. package/src/components/udp/lov.vue +410 -0
  70. package/src/components/udp/modal-form.vue +2 -2
  71. package/src/components/udp/modal-grid.vue +297 -0
  72. package/src/components/udp/utils.ts +379 -40
  73. package/src/directives/permission/index.ts +1 -1
  74. package/src/layout/components/lay-navbar/index.vue +1 -1
  75. package/src/layout/components/lay-panel/index.vue +150 -150
  76. package/src/layout/components/lay-sidebar/breadCrumb.vue +1 -1
  77. package/src/layout/components/lay-tag/index.vue +625 -625
  78. package/src/layout/layoutView.vue +215 -215
  79. package/src/main.ts +7 -6
  80. package/src/plugins/i18n/en.ts +291 -289
  81. package/src/plugins/i18n/zh.ts +338 -337
  82. package/src/plugins/vxe-table/index.ts +1 -1
  83. package/src/plugins/vxe-table/render.tsx +4 -2
  84. package/src/router/index.ts +187 -183
  85. package/src/router/modules/remaining.ts +58 -83
  86. package/src/style/button.scss +85 -78
  87. package/src/style/tailwind.css +1 -68
  88. package/src/style/vxetable.scss +265 -256
  89. package/src/utils/authority/index.ts +1 -1
  90. package/src/views/organization/company/CompanyPanel.vue +259 -259
  91. package/src/views/organization/inv-org/InvOrganization.vue +2 -3
  92. package/src/views/system/menu/SystemMenu.vue +1 -1
  93. package/src/views/system/menu/menuInfo.vue +376 -373
  94. package/src/views/udev/dict/DictView.vue +106 -106
  95. package/src/views/udev/lov/lov-view.vue +91 -91
  96. package/types/global.d.ts +2 -1
  97. package/vite.config.ts +4 -1
  98. package/dist/InvOrganization-q4T3y8dQ.js +0 -260
@@ -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>