zant-admin 1.0.0

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 (82) hide show
  1. package/README.en.md +36 -0
  2. package/README.md +248 -0
  3. package/SCAFFOLD_README.md +215 -0
  4. package/bin/cli.js +99 -0
  5. package/bin/generator.js +503 -0
  6. package/bin/prompts.js +159 -0
  7. package/bin/utils.js +134 -0
  8. package/package.json +74 -0
  9. package/public/logo.png +0 -0
  10. package/src/App.vue +16 -0
  11. package/src/api/methods/logError.js +8 -0
  12. package/src/api/methods/logOperation.js +8 -0
  13. package/src/api/methods/login.js +6 -0
  14. package/src/api/methods/quartz.js +36 -0
  15. package/src/api/methods/region.js +16 -0
  16. package/src/api/methods/sysAccount.js +30 -0
  17. package/src/api/methods/sysDict.js +29 -0
  18. package/src/api/methods/sysDictItem.js +26 -0
  19. package/src/api/methods/sysMenu.js +42 -0
  20. package/src/api/methods/sysRole.js +35 -0
  21. package/src/api/methods/sysUser.js +25 -0
  22. package/src/api/methods/system.js +16 -0
  23. package/src/api/request.js +225 -0
  24. package/src/assets/css/style.css +70 -0
  25. package/src/assets/css/zcui.css +340 -0
  26. package/src/assets/imgs/loginbackground.svg +69 -0
  27. package/src/assets/imgs/logo.png +0 -0
  28. package/src/assets/imgs/md/1.png +0 -0
  29. package/src/assets/imgs/md/10.png +0 -0
  30. package/src/assets/imgs/md/11.png +0 -0
  31. package/src/assets/imgs/md/2.png +0 -0
  32. package/src/assets/imgs/md/3.png +0 -0
  33. package/src/assets/imgs/md/4.png +0 -0
  34. package/src/assets/imgs/md/5.png +0 -0
  35. package/src/assets/imgs/md/6.png +0 -0
  36. package/src/assets/imgs/md/7.png +0 -0
  37. package/src/assets/imgs/md/8.png +0 -0
  38. package/src/assets/imgs/md/9.png +0 -0
  39. package/src/components/FormTable.vue +875 -0
  40. package/src/components/IconPicker.vue +344 -0
  41. package/src/components/MainPage.vue +957 -0
  42. package/src/components/details/logErrorDetails.vue +58 -0
  43. package/src/components/details/logOperationDetails.vue +76 -0
  44. package/src/components/edit/QuartzEdit.vue +221 -0
  45. package/src/components/edit/SysAccountEdit.vue +178 -0
  46. package/src/components/edit/SysDictEdit.vue +114 -0
  47. package/src/components/edit/SysDictItemEdit.vue +134 -0
  48. package/src/components/edit/SysRoleEdit.vue +109 -0
  49. package/src/components/edit/sysMenuEdit.vue +305 -0
  50. package/src/config/index.js +74 -0
  51. package/src/directives/permission.js +45 -0
  52. package/src/main.js +38 -0
  53. package/src/router/index.js +270 -0
  54. package/src/stores/config.js +37 -0
  55. package/src/stores/dict.js +33 -0
  56. package/src/stores/menu.js +57 -0
  57. package/src/stores/user.js +21 -0
  58. package/src/utils/baseEcharts.js +661 -0
  59. package/src/utils/dictTemplate.js +26 -0
  60. package/src/utils/regionUtils.js +169 -0
  61. package/src/utils/useFormCRUD.js +60 -0
  62. package/src/views/baiscstatis/center.vue +463 -0
  63. package/src/views/baiscstatis/iframePage.vue +31 -0
  64. package/src/views/baiscstatis/notFound.vue +192 -0
  65. package/src/views/console.vue +771 -0
  66. package/src/views/demo/importexport.vue +123 -0
  67. package/src/views/demo/region.vue +240 -0
  68. package/src/views/demo/statistics.vue +195 -0
  69. package/src/views/home.vue +7 -0
  70. package/src/views/login.vue +272 -0
  71. package/src/views/operations/log/logError.vue +78 -0
  72. package/src/views/operations/log/logLogin.vue +66 -0
  73. package/src/views/operations/log/logOperation.vue +103 -0
  74. package/src/views/operations/log/logQuartz.vue +57 -0
  75. package/src/views/operations/quartz.vue +181 -0
  76. package/src/views/operations/serviceMonitoring.vue +134 -0
  77. package/src/views/system/sysAccount.vue +123 -0
  78. package/src/views/system/sysDict.vue +156 -0
  79. package/src/views/system/sysDictItem.vue +118 -0
  80. package/src/views/system/sysMenu.vue +223 -0
  81. package/src/views/system/sysRole.vue +184 -0
  82. package/templates/env.production +2 -0
@@ -0,0 +1,957 @@
1
+ <template>
2
+ <div class="common-layout">
3
+ <a-layout v-if="formConfig.navigationMode == 'side'">
4
+ <!-- 侧边栏部分 -->
5
+ <a-layout-sider
6
+ v-model:collapsed="collapsed"
7
+ :trigger="null"
8
+ collapsible
9
+ :style="{
10
+ overflow: 'auto',
11
+ height: '100vh',
12
+ position: 'fixed',
13
+ left: 0,
14
+ top: 0,
15
+ bottom: 0,
16
+ background: formConfig.themeClass == 'light' ? '#fff' : '',
17
+ }"
18
+ >
19
+ <div class="logo">
20
+ <span
21
+ v-if="!collapsed"
22
+ :class="
23
+ formConfig.themeClass == 'light'
24
+ ? 'text-color-primary'
25
+ : 'text-color-white '
26
+ "
27
+ >
28
+ <div class="flex-row">
29
+ <img
30
+ src="@/assets/imgs/logo.png"
31
+ alt=""
32
+ width="30px"
33
+ class="margin-right-10"
34
+ />{{ config.projectName }}
35
+ </div>
36
+ </span>
37
+ <span
38
+ v-else
39
+ :class="
40
+ formConfig.themeClass == 'light'
41
+ ? 'text-color-primary'
42
+ : 'text-color-white '
43
+ "
44
+ ><img src="@/assets/imgs/logo.png" alt="" width="30px"
45
+ /></span>
46
+ </div>
47
+ <a-menu
48
+ v-model:openKeys="openmenuKey"
49
+ v-model:selectedKeys="selectmenuKey"
50
+ mode="inline"
51
+ :theme="formConfig.themeClass"
52
+ :inline-collapsed="collapsed"
53
+ :items="menuItems"
54
+ @click="menuclick"
55
+ ></a-menu>
56
+ </a-layout-sider>
57
+ <!-- 主体部分 -->
58
+ <a-layout :style="{ marginLeft: collapsed ? '80px' : '200px' }">
59
+ <a-layout-header
60
+ class="zc-layout-header"
61
+ :style="{
62
+ position: 'fixed',
63
+ zIndex: 1,
64
+ width: collapsed ? 'calc(100% - 80px)' : 'calc(100% - 200px)',
65
+ }"
66
+ >
67
+ <div class="flex-row">
68
+ <div class="flex-item text-align-left">
69
+ <menu-unfold-outlined
70
+ v-if="collapsed"
71
+ class="trigger"
72
+ @click="() => (collapsed = !collapsed)"
73
+ />
74
+ <menu-fold-outlined
75
+ v-else
76
+ class="trigger"
77
+ @click="() => (collapsed = !collapsed)"
78
+ />
79
+ <a class="text-color-black" @click="reload">
80
+ <UndoOutlined :style="{ fontSize: '16px' }" />
81
+ </a>
82
+ <!-- 面包屑导航 -->
83
+ <a-breadcrumb class="breadcrumb" v-if="breadcrumbItems.length > 0">
84
+
85
+ <a-breadcrumb-item v-for="item in breadcrumbItems" :key="item.key">
86
+ {{ item.title }}
87
+ </a-breadcrumb-item>
88
+ </a-breadcrumb>
89
+ </div>
90
+ <div class="flex-item text-align-right padding-right-20">
91
+ <a-space :size="18">
92
+ <a-dropdown>
93
+ <a class="text-color-black" @click.prevent>
94
+ <a-avatar
95
+ :size="35"
96
+ :style="{
97
+ backgroundColor: '#1677ff',
98
+ verticalAlign: 'middle',
99
+ margin: '0 0 5px 0',
100
+ }"
101
+ >
102
+ {{ username }}
103
+ </a-avatar>
104
+ </a>
105
+ <template #overlay>
106
+ <a-menu @click="avatardropdownonClick">
107
+ <a-menu-item key="1"> 个人中心 </a-menu-item>
108
+ <a-menu-item key="2"> 退出 </a-menu-item>
109
+ </a-menu>
110
+ </template>
111
+ </a-dropdown>
112
+ <a
113
+ class="text-color-black"
114
+ @click.prevent
115
+ @click="toggleFullScreen"
116
+ >
117
+ <a-tooltip :title="isFullScreen ? '退出全屏' : '进入全屏'">
118
+ <FullscreenOutlined v-if="isFullScreen" />
119
+ <FullscreenExitOutlined v-else />
120
+ </a-tooltip>
121
+ </a>
122
+ <a-dropdown>
123
+ <a class="text-color-black" @click.prevent>
124
+ <GlobalOutlined />
125
+ </a>
126
+ <template #overlay>
127
+ <a-menu @click="dropdownonLanguageClick">
128
+ <a-menu-item key="zhCN">
129
+ <span
130
+ :class="
131
+ config.dayjsLocale == 'zh-cn'
132
+ ? 'text-color-primary'
133
+ : ''
134
+ "
135
+ >简体中文</span
136
+ >
137
+ </a-menu-item>
138
+ <a-menu-item key="enUS">
139
+ <span
140
+ :class="
141
+ config.dayjsLocale == 'en'
142
+ ? 'text-color-primary'
143
+ : ''
144
+ "
145
+ >English</span
146
+ >
147
+ </a-menu-item>
148
+ </a-menu>
149
+ </template>
150
+ </a-dropdown>
151
+ <a class="text-color-black" @click.prevent @click="showDrawer">
152
+ <MoreOutlined />
153
+ </a>
154
+ </a-space>
155
+ </div>
156
+ </div>
157
+ </a-layout-header>
158
+ <!-- 选项卡组件 -->
159
+ <div class="zc-layout-content-tabs">
160
+ <div
161
+ class="zc-layout-content-tabs-left"
162
+ @wheel.prevent="onWheelScroll"
163
+ ref="scrollContainer"
164
+ >
165
+ <a-tag
166
+ v-for="pane in panes"
167
+ :key="pane.key"
168
+ :class="selectmenuKey[0] == pane.key ? 'tag-select' : 'tag'"
169
+ :closable="pane.closable"
170
+ :bordered="false"
171
+ @close="tagdelete(pane)"
172
+ @click="tagchange(pane)"
173
+ >
174
+ {{ pane.title }}
175
+ </a-tag>
176
+ </div>
177
+ <div class="zc-layout-content-tabs-right">
178
+ <a-dropdown class="zc-layout-content-tabs-right-dropdown">
179
+ <a class="ant-dropdown-link" @click.prevent>
180
+ <DownOutlined />
181
+ </a>
182
+ <template #overlay>
183
+ <a-menu @click="dropdownonClick">
184
+ <a-menu-item key="1">
185
+ <ArrowLeftOutlined /> 关闭左侧
186
+ </a-menu-item>
187
+ <a-menu-item key="2">
188
+ <ArrowRightOutlined /> 关闭右侧
189
+ </a-menu-item>
190
+ <a-menu-item key="3">
191
+ <CloseOutlined /> 关闭其它
192
+ </a-menu-item>
193
+ <a-menu-item key="4">
194
+ <CloseCircleOutlined /> 全部关闭
195
+ </a-menu-item>
196
+ </a-menu>
197
+ </template>
198
+ </a-dropdown>
199
+ </div>
200
+ </div>
201
+ <!-- 路由视图部分 -->
202
+ <a-layout-content :style="{ margin: '8px' }">
203
+ <router-view v-slot="{ Component, route }">
204
+ <keep-alive v-if="route.meta.cache">
205
+ <component :is="Component" />
206
+ </keep-alive>
207
+ <component v-else :is="Component" />
208
+ </router-view>
209
+ </a-layout-content>
210
+ </a-layout>
211
+ </a-layout>
212
+ <a-layout v-else>
213
+ <a-layout-header
214
+ class="zc-layout-header-top"
215
+ :style="{
216
+ position: 'fixed',
217
+ zIndex: 1,
218
+ width: '100%',
219
+ background: formConfig.themeClass == 'light' ? '#fff' : '#001529',
220
+ }"
221
+ >
222
+ <div class="flex-row">
223
+ <div class="flex-item-9">
224
+ <div class="flex-row">
225
+ <div
226
+ class="flex-item-1"
227
+ style="line-height: 32px; height: 32px; margin: 16px"
228
+ >
229
+ <a-flex>
230
+ <img
231
+ src="@/assets/imgs/logo.png"
232
+ alt=""
233
+ width="30px"
234
+ class="margin-right-10"
235
+ />
236
+ <span
237
+ :class="
238
+ formConfig.themeClass == 'light'
239
+ ? 'text-color-primary'
240
+ : 'text-color-white '
241
+ "
242
+ style="font-size: 16px"
243
+ >{{ config.projectName }}</span
244
+ >
245
+ </a-flex>
246
+ </div>
247
+ <div class="flex-item-9">
248
+ <a-menu
249
+ v-model:selectedKeys="selectmenuKey"
250
+ mode="horizontal"
251
+ :theme="formConfig.themeClass"
252
+ :items="menuItems"
253
+ @click="menuclick"
254
+ ></a-menu>
255
+ </div>
256
+ </div>
257
+
258
+ </div>
259
+ <div class="flex-item-1 text-align-right padding-right-20">
260
+ <a-space :size="18">
261
+ <a-dropdown>
262
+ <a
263
+ :class="
264
+ formConfig.themeClass == 'light'
265
+ ? 'text-color-black'
266
+ : 'text-color-white'
267
+ "
268
+ @click.prevent
269
+ >
270
+ <a-avatar
271
+ :size="35"
272
+ :style="{
273
+ backgroundColor: '#1677ff',
274
+ verticalAlign: 'middle',
275
+ margin: '0 0 5px 0',
276
+ }"
277
+ >
278
+ {{ username }}
279
+ </a-avatar>
280
+ </a>
281
+ <template #overlay>
282
+ <a-menu @click="avatardropdownonClick">
283
+ <a-menu-item key="1"> 个人中心 </a-menu-item>
284
+ <a-menu-item key="2"> 退出 </a-menu-item>
285
+ </a-menu>
286
+ </template>
287
+ </a-dropdown>
288
+ <a
289
+ :class="
290
+ formConfig.themeClass == 'light'
291
+ ? 'text-color-black'
292
+ : 'text-color-white'
293
+ "
294
+ @click.prevent
295
+ @click="toggleFullScreen"
296
+ >
297
+ <a-tooltip :title="isFullScreen ? '退出全屏' : '进入全屏'">
298
+ <FullscreenOutlined v-if="isFullScreen" />
299
+ <FullscreenExitOutlined v-else />
300
+ </a-tooltip>
301
+ </a>
302
+ <a-dropdown>
303
+ <a
304
+ :class="
305
+ formConfig.themeClass == 'light'
306
+ ? 'text-color-black'
307
+ : 'text-color-white'
308
+ "
309
+ @click.prevent
310
+ >
311
+ <GlobalOutlined />
312
+ </a>
313
+ <template #overlay>
314
+ <a-menu @click="dropdownonLanguageClick">
315
+ <a-menu-item key="zhCN">
316
+ <span
317
+ :class="
318
+ config.locale.locale == 'zh-cn'
319
+ ? 'text-color-primary'
320
+ : ''
321
+ "
322
+ >简体中文</span
323
+ >
324
+ </a-menu-item>
325
+ <a-menu-item key="enUS">
326
+ <span
327
+ :class="
328
+ config.locale.locale == 'en'
329
+ ? 'text-color-primary'
330
+ : ''
331
+ "
332
+ >English</span
333
+ >
334
+ </a-menu-item>
335
+ </a-menu>
336
+ </template>
337
+ </a-dropdown>
338
+ <a
339
+ :class="
340
+ formConfig.themeClass == 'light'
341
+ ? 'text-color-black'
342
+ : 'text-color-white'
343
+ "
344
+ @click.prevent
345
+ @click="showDrawer"
346
+ >
347
+ <MoreOutlined />
348
+ </a>
349
+ </a-space>
350
+ </div>
351
+ </div>
352
+ </a-layout-header>
353
+ <!-- 选项卡组件 -->
354
+ <div class="zc-layout-content-tabs">
355
+ <div
356
+ class="zc-layout-content-tabs-left"
357
+ @wheel.prevent="onWheelScroll"
358
+ ref="scrollContainer"
359
+ >
360
+ <a-tag
361
+ v-for="pane in panes"
362
+ :key="pane.key"
363
+ :class="selectmenuKey[0] == pane.key ? 'tag-select' : 'tag'"
364
+ :closable="pane.closable"
365
+ :bordered="false"
366
+ @close="tagdelete(pane)"
367
+ @click="tagchange(pane)"
368
+ >
369
+ {{ pane.title }}
370
+ </a-tag>
371
+ </div>
372
+ <div class="zc-layout-content-tabs-right">
373
+ <a-dropdown class="zc-layout-content-tabs-right-dropdown">
374
+ <a class="ant-dropdown-link" @click.prevent>
375
+ <DownOutlined />
376
+ </a>
377
+ <template #overlay>
378
+ <a-menu @click="dropdownonClick">
379
+ <a-menu-item key="1">
380
+ <ArrowLeftOutlined /> 关闭左侧
381
+ </a-menu-item>
382
+ <a-menu-item key="2">
383
+ <ArrowRightOutlined /> 关闭右侧
384
+ </a-menu-item>
385
+ <a-menu-item key="3"> <CloseOutlined /> 关闭其它 </a-menu-item>
386
+ <a-menu-item key="4">
387
+ <CloseCircleOutlined /> 全部关闭
388
+ </a-menu-item>
389
+ </a-menu>
390
+ </template>
391
+ </a-dropdown>
392
+ </div>
393
+ </div>
394
+ <!-- 面包屑导航 -->
395
+ <a-breadcrumb class="breadcrumb-top" v-if="breadcrumbItems.length > 0">
396
+ <a-breadcrumb-item v-for="item in breadcrumbItems" :key="item.key">
397
+ {{ item.title }}
398
+ </a-breadcrumb-item>
399
+ </a-breadcrumb>
400
+ <!-- 路由视图部分 -->
401
+ <a-layout-content :style="{ margin: '8px' }">
402
+ <router-view v-slot="{ Component, route }">
403
+ <keep-alive v-if="route.meta.cache">
404
+ <component :is="Component" />
405
+ </keep-alive>
406
+ <component v-else :is="Component" />
407
+ </router-view>
408
+ </a-layout-content>
409
+ </a-layout>
410
+ <!-- 抽屉组件 -->
411
+ <a-drawer title="配置" :closable="false" :open="open" @close="onClose">
412
+ <a-form name="config_form" :model="formConfig">
413
+ <div class="blockquote">主题</div>
414
+ <div class="padding-10">
415
+ <a-radio-group
416
+ v-model:value="formConfig.themeClass"
417
+ @change="themeClasschangeTheme"
418
+ >
419
+ <a-radio value="dark">黑</a-radio>
420
+ <a-radio value="light">白</a-radio>
421
+ </a-radio-group>
422
+ </div>
423
+ <div class="blockquote">导航模式</div>
424
+ <div class="padding-10">
425
+ <a-radio-group
426
+ v-model:value="formConfig.navigationMode"
427
+ @change="navigationModechangeTheme"
428
+ >
429
+ <a-radio value="side">侧边菜单布局</a-radio>
430
+ <a-radio value="top">顶部菜单布局</a-radio>
431
+ </a-radio-group>
432
+ </div>
433
+ <div class="blockquote">表格设置</div>
434
+ <div class="padding-10">
435
+ <a-row>
436
+ <a-col :span="12">表格边框</a-col>
437
+ <a-col :span="12">
438
+ <a-switch
439
+ v-model:checked="formConfig.tableBordered"
440
+ checked-children="边框开"
441
+ un-checked-children="边框关"
442
+ @change="tableBorderedchangeTheme"
443
+ />
444
+ </a-col>
445
+ </a-row>
446
+ </div>
447
+ </a-form>
448
+ </a-drawer>
449
+ </div>
450
+ </template>
451
+ <script setup>
452
+ import { ref, reactive, h, provide, getCurrentInstance, watch, onMounted } from 'vue'
453
+ import { menuStore } from '@/stores/menu'
454
+ import { configStore } from '@/stores/config'
455
+ import { useUserStore } from '@/stores/user'
456
+ import router from '@/router'
457
+ import zhCN from 'ant-design-vue/es/locale/zh_CN'
458
+ import enUS from 'ant-design-vue/es/locale/en_US'
459
+ const user = useUserStore()
460
+ const username = user.userInfo.name
461
+ // 菜单状态
462
+ const menu = menuStore()
463
+ const config = configStore()
464
+ const collapsed = ref(false)
465
+ const openmenuKey = ref(menu.openmenuKey)
466
+ const selectmenuKey = ref(menu.selectmenuKey)
467
+ const panes = ref(menu.tagmenus)
468
+ // 定义一个布尔状态来跟踪是否处于全屏
469
+ const isFullScreen = ref(false)
470
+ const formConfig = reactive({
471
+ themeClass: config.themeClass,
472
+ tableBordered: config.tableBordered,
473
+ navigationMode: config.navigationMode,
474
+ })
475
+ const menuItems = ref([])
476
+ const { proxy } = getCurrentInstance() // 获取当前实例
477
+ // 菜单项生成
478
+ const menuinit = () => {
479
+ const icons = proxy.$icons // 访问全局图标
480
+ const createMenuItem = menu => {
481
+ return menu.map(item => {
482
+ // 动态解析图标
483
+ const iconComponent = item.icon ? icons[item.icon] : null
484
+ const menuItem = {
485
+ key: item.id,
486
+ parentId: item.parentId,
487
+ icon: iconComponent ? () => h(iconComponent) : undefined, // 动态渲染图标
488
+ label: item.title,
489
+ title: item.title,
490
+ path: item.path,
491
+ type: item.type,
492
+ url: item.url,
493
+ }
494
+ if (item.children && item.children.length > 0) {
495
+ menuItem.children = createMenuItem(item.children)
496
+ }
497
+ return menuItem
498
+ })
499
+ }
500
+ menuItems.value = createMenuItem(menu.menus)
501
+ }
502
+ menuinit()
503
+ // 菜单点击事件
504
+ const menuclick = ({ item, key }) => {
505
+ const type = item.originItemValue.type
506
+ const url = item.url
507
+
508
+ if (type === 4 && url) {
509
+ // 外链:新窗口打开,不生成 tab
510
+ window.open(url, '_blank')
511
+ return
512
+ }
513
+ let current = panes.value.find(x => x.key === key)
514
+ if (!current) {
515
+ current = {
516
+ key,
517
+ parentId: item.parentId,
518
+ title: item.title,
519
+ path: item.path,
520
+ closable: true,
521
+ type,
522
+ url,
523
+ }
524
+ panes.value.push(current)
525
+ }
526
+ updateMenuPanes(current)
527
+ }
528
+ // 点击 Tag 切换选项卡
529
+ const tagchange = pane => {
530
+ const current = panes.value.find(x => x.key === pane.key)
531
+ if (current) {
532
+ updateMenuPanes(current)
533
+ }
534
+ }
535
+ // 删除 Tag
536
+ const tagdelete = pane => {
537
+ if (pane.key == selectmenuKey.value[0]) {
538
+ console.log(JSON.stringify(panes.value))
539
+ const index = panes.value.findIndex(item => item.key === pane.key)
540
+ let nextPane = null
541
+ //如果有下一条
542
+ if (index + 1 < panes.value.length) {
543
+ // 有下一个
544
+ nextPane = panes.value[index + 1]
545
+ } else if (index - 1 >= 0) {
546
+ // 有上一个
547
+ nextPane = panes.value[index - 1]
548
+ }
549
+ if (nextPane) {
550
+ updateMenuPanes(nextPane)
551
+ }
552
+ }
553
+ panes.value = panes.value.filter(x => x.key !== pane.key)
554
+ menu.tagmenus = panes.value
555
+ }
556
+ // 下拉菜单点击事件
557
+ const dropdownonClick = ({ key }) => {
558
+ const index = panes.value.findIndex(
559
+ item => item.key === selectmenuKey.value[0],
560
+ )
561
+ switch (key) {
562
+ case '1':
563
+ panes.value = panes.value.filter(
564
+ (item, i) => i >= index || item.key === 0,
565
+ )
566
+ menu.tagmenus = panes.value
567
+ break
568
+ case '2':
569
+ panes.value = panes.value.filter((item, i) => i <= index)
570
+ menu.tagmenus = panes.value
571
+ break
572
+ case '3':
573
+ panes.value = panes.value.filter(
574
+ x => x.key === selectmenuKey.value[0] || x.key === 0,
575
+ )
576
+ menu.tagmenus = panes.value
577
+ break
578
+ case '4':
579
+ panes.value = panes.value.filter(x => x.key === 0)
580
+ menu.tagmenus = panes.value
581
+ // 切换到控制台
582
+ const consolePane = {
583
+ key: 'console',
584
+ parentId: 0,
585
+ title: '控制台',
586
+ path: '/console',
587
+ closable: false,
588
+ type: 1, // 普通路由
589
+ url: '',
590
+ }
591
+ updateMenuPanes(consolePane)
592
+ break
593
+ default:
594
+ break
595
+ }
596
+ }
597
+ const findAllByHref = (items, targetHref) => {
598
+ let result = []
599
+ items.forEach(item => {
600
+ if (item.path === targetHref) {
601
+ result.push(item)
602
+ }
603
+ if (item.children && item.children.length > 0) {
604
+ result = result.concat(findAllByHref(item.children, targetHref))
605
+ }
606
+ })
607
+ return result
608
+ }
609
+ //个人中心和退出
610
+ const avatardropdownonClick = ({ key }) => {
611
+ switch (key) {
612
+ case '1':
613
+ const result = findAllByHref(menuItems.value, '/center')
614
+ let pane
615
+ if (result.length === 0) {
616
+ // 如果菜单项中没有个人中心,创建一个默认的个人中心菜单项
617
+ pane = {
618
+ key: 'center',
619
+ parentId: '0',
620
+ title: '个人中心',
621
+ path: '/center',
622
+ closable: true,
623
+ type: 2, // 普通菜单
624
+ url: '',
625
+ }
626
+ } else {
627
+ pane = {
628
+ key: result[0].key,
629
+ parentId: result[0].parentId,
630
+ title: result[0].title,
631
+ path: result[0].path,
632
+ closable: true,
633
+ type: result[0].type,
634
+ url: result[0].url,
635
+ }
636
+ }
637
+ const existingTab = panes.value.find(x => x.key === pane.key)
638
+ if (!existingTab) {
639
+ panes.value.push(pane)
640
+ }
641
+ updateMenuPanes(pane)
642
+ break
643
+ case '2':
644
+ user.$reset()
645
+ menu.$reset()
646
+ router.push('/login')
647
+ break
648
+ default:
649
+ break
650
+ }
651
+ }
652
+
653
+ function findParentKeys(menuArray, targetKey, parentKeys = []) {
654
+ for (const item of menuArray) {
655
+ // 如果找到目标项,返回当前收集的父级key
656
+ if (item.key === targetKey) {
657
+ return parentKeys
658
+ }
659
+
660
+ // 如果有子菜单,递归查找
661
+ if (item.children && item.children.length > 0) {
662
+ const result = findParentKeys(item.children, targetKey, [
663
+ ...parentKeys,
664
+ item.key,
665
+ ])
666
+ if (result) {
667
+ return result
668
+ }
669
+ }
670
+ }
671
+ return null
672
+ }
673
+ // 更新选中的选项卡和路由
674
+ const updateMenuPanes = pane => {
675
+ selectmenuKey.value = [pane.key]
676
+ if (pane.key != 0) {
677
+ const parentKeys = findParentKeys(menuItems.value, pane.key)
678
+ // 设置展开的菜单key,包括所有上级key
679
+ openmenuKey.value = parentKeys ? [...parentKeys] : []
680
+ } else {
681
+ openmenuKey.value = [pane.parentId]
682
+ }
683
+ menu.setSelectmenuKey(selectmenuKey.value)
684
+ menu.setOpenmenuKey(openmenuKey.value)
685
+
686
+ if (pane.type === 3 && pane.url) {
687
+ // 内链:用 iframe 打开
688
+ router.push({
689
+ path: '/iframePage',
690
+ query: { url: encodeURIComponent(pane.url) },
691
+ })
692
+ } else {
693
+ // 普通菜单:走路由
694
+ router.push(pane.path)
695
+ }
696
+ }
697
+ //国际化
698
+ const dropdownonLanguageClick = ({ key }) => {
699
+ if (key === 'zhCN') {
700
+ config.setLocale(zhCN)
701
+ } else {
702
+ config.setLocale(enUS)
703
+ }
704
+ }
705
+ // 抽屉状态
706
+ const open = ref(false)
707
+ const showDrawer = () => {
708
+ open.value = true
709
+ }
710
+ const onClose = () => {
711
+ open.value = false
712
+ }
713
+ // 主题切换
714
+ const themeClasschangeTheme = e => {
715
+ config.themeClass = e.target.value
716
+ }
717
+ //导航模式
718
+ const navigationModechangeTheme = e => {
719
+ config.navigationMode = e.target.value
720
+ }
721
+
722
+ // 定义全屏切换方法
723
+ const toggleFullScreen = () => {
724
+ const element = document.documentElement
725
+ if (!isFullScreen.value) {
726
+ // 进入全屏模式
727
+ if (element.requestFullscreen) {
728
+ element.requestFullscreen()
729
+ } else if (element.mozRequestFullScreen) {
730
+ // Firefox
731
+ element.mozRequestFullScreen()
732
+ } else if (element.webkitRequestFullscreen) {
733
+ // Chrome, Safari and Opera
734
+ element.webkitRequestFullscreen()
735
+ } else if (element.msRequestFullscreen) {
736
+ // IE/Edge
737
+ element.msRequestFullscreen()
738
+ }
739
+ } else {
740
+ // 退出全屏模式
741
+ if (document.exitFullscreen) {
742
+ document.exitFullscreen()
743
+ } else if (document.mozCancelFullScreen) {
744
+ // Firefox
745
+ document.mozCancelFullScreen()
746
+ } else if (document.webkitExitFullscreen) {
747
+ // Chrome, Safari and Opera
748
+ document.webkitExitFullscreen()
749
+ } else if (document.msExitFullscreen) {
750
+ // IE/Edge
751
+ document.msExitFullscreen()
752
+ }
753
+ }
754
+ isFullScreen.value = !isFullScreen.value // 切换全屏状态
755
+ }
756
+ //表格边框切换
757
+ const tableBorderedchangeTheme = checked => {
758
+ config.tableBordered = checked
759
+ }
760
+ const reload = () => {
761
+ window.location.reload()
762
+ }
763
+ const scrollContainer = ref(null)
764
+ const onWheelScroll = e => {
765
+ if (scrollContainer.value) {
766
+ scrollContainer.value.scrollLeft += e.deltaY // 将垂直滚动转为水平滚动
767
+ }
768
+ }
769
+ // 生成面包屑导航项
770
+ const breadcrumbItems = ref([])
771
+
772
+ /**
773
+ * 根据当前选中的菜单项生成面包屑导航
774
+ */
775
+ const generateBreadcrumbItems = () => {
776
+ breadcrumbItems.value = []
777
+
778
+ if (selectmenuKey.value.length === 0) return
779
+
780
+ const currentKey = selectmenuKey.value[0]
781
+ const findItemPath = (items, targetKey, path = []) => {
782
+ for (const item of items) {
783
+ if (item.key === targetKey) {
784
+ return [...path, item]
785
+ }
786
+ if (item.children && item.children.length > 0) {
787
+ const result = findItemPath(item.children, targetKey, [...path, item])
788
+ if (result) return result
789
+ }
790
+ }
791
+ return null
792
+ }
793
+
794
+ const itemPath = findItemPath(menuItems.value, currentKey)
795
+ if (itemPath) {
796
+ breadcrumbItems.value = itemPath.map(item => ({
797
+ key: item.key,
798
+ title: item.title,
799
+ path: item.path,
800
+ type: item.type,
801
+ url: item.url
802
+ }))
803
+ }
804
+ }
805
+
806
+ // 监听选中菜单项变化,更新面包屑
807
+ watch(selectmenuKey, () => {
808
+ generateBreadcrumbItems()
809
+ }, { immediate: true })
810
+
811
+ // 组件挂载时恢复路由状态
812
+ onMounted(() => {
813
+ // 检查是否有持久化的选中菜单项
814
+ if (selectmenuKey.value && selectmenuKey.value.length > 0 && selectmenuKey.value[0] !== 0) {
815
+ const currentKey = selectmenuKey.value[0]
816
+
817
+ // 在tagmenus中查找对应的pane
818
+ const savedPane = panes.value.find(pane => pane.key === currentKey)
819
+
820
+ if (savedPane) {
821
+ // 如果找到了对应的pane,恢复路由状态
822
+ updateMenuPanes(savedPane)
823
+ } else {
824
+ // 如果没有找到对应的pane,检查当前路由是否与持久化状态匹配
825
+ const currentRoute = router.currentRoute.value
826
+ if (currentRoute.path === '/' || currentRoute.path === '/home') {
827
+ // 如果当前在首页,但持久化状态不是首页,尝试恢复
828
+ const menuItem = findMenuItemByKey(menuItems.value, currentKey)
829
+ if (menuItem) {
830
+ // 创建新的pane并跳转
831
+ const newPane = {
832
+ key: menuItem.key,
833
+ parentId: menuItem.parentId,
834
+ title: menuItem.title,
835
+ path: menuItem.path,
836
+ closable: true,
837
+ type: menuItem.type,
838
+ url: menuItem.url,
839
+ }
840
+ panes.value.push(newPane)
841
+ updateMenuPanes(newPane)
842
+ }
843
+ }
844
+ }
845
+ }
846
+ })
847
+
848
+ // 根据key在菜单项中查找对应项
849
+ function findMenuItemByKey(items, targetKey) {
850
+ for (const item of items) {
851
+ if (item.key === targetKey) {
852
+ return item
853
+ }
854
+ if (item.children && item.children.length > 0) {
855
+ const result = findMenuItemByKey(item.children, targetKey)
856
+ if (result) return result
857
+ }
858
+ }
859
+ return null
860
+ }
861
+
862
+ // 提供方法
863
+ provide('menuinit', menuinit)
864
+ provide('menuclick', menuclick)
865
+ </script>
866
+ <style scoped>
867
+ .trigger {
868
+ font-size: 18px;
869
+ line-height: 64px;
870
+ padding: 0 24px;
871
+ cursor: pointer;
872
+ transition: color 0.3s;
873
+ }
874
+ .trigger:hover {
875
+ color: #1890ff;
876
+ }
877
+ .logo {
878
+ text-align: center;
879
+ line-height: 32px;
880
+ height: 32px;
881
+ margin: 16px;
882
+ font-size: 16px;
883
+ }
884
+ .site-layout .site-layout-background {
885
+ background: #fff;
886
+ }
887
+ .zc-layout-header {
888
+ background: #fff !important;
889
+ padding: 0 !important;
890
+ border-bottom: 1px solid rgba(5, 5, 5, 0.06);
891
+ }
892
+ .zc-layout-header-top {
893
+ padding: 0 !important;
894
+ }
895
+ .zc-layout-content-tabs {
896
+ display: flex;
897
+ flex-direction: row;
898
+ justify-content: center;
899
+ align-items: center;
900
+ margin-top: 69px;
901
+ }
902
+ .tag {
903
+ background-color: #fff;
904
+ color: #999999;
905
+ height: 30px;
906
+ line-height: 30px;
907
+ cursor: pointer;
908
+ font-size: 14px;
909
+ padding: 0 15px;
910
+ }
911
+ .tag-select {
912
+ background-color: #fff;
913
+ color: #1677ff;
914
+ height: 30px;
915
+ line-height: 30px;
916
+ cursor: pointer;
917
+ font-size: 14px;
918
+ padding: 0 15px;
919
+ }
920
+ .zc-layout-content-tabs-left {
921
+ flex: 0.95;
922
+ padding-left: 10px;
923
+ white-space: nowrap;
924
+ overflow-x: auto;
925
+ overflow-y: hidden;
926
+ /* 确保只有横向滑动 */
927
+ width: 100%;
928
+ /* 父容器宽度 */
929
+ -ms-overflow-style: none;
930
+ /* 适用于 IE 和 Edge */
931
+ scrollbar-width: none;
932
+ /* 适用于 Firefox */
933
+ }
934
+ .zc-layout-content-tabs-left::-webkit-scrollbar {
935
+ display: none;
936
+ /* 隐藏 Webkit 浏览器中的滚动条 */
937
+ }
938
+ .zc-layout-content-tabs-right {
939
+ flex: 0.05;
940
+ text-align: right;
941
+ padding-right: 10px;
942
+ }
943
+ .zc-layout-content-tabs-right-dropdown {
944
+ background-color: white;
945
+ padding: 8px 10px;
946
+ border-radius: 4px;
947
+ }
948
+ .breadcrumb {
949
+ display: inline-block;
950
+ margin-left: 16px;
951
+ vertical-align: middle;
952
+ }
953
+ .breadcrumb-top {
954
+ margin-top: 8px;
955
+ padding-left: 16px;
956
+ }
957
+ </style>