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