1
+ <template >
2
+ <div class =" flex h-full" >
3
+ <!-- 一级菜单常驻侧边栏 -->
4
+ <div
5
+ class =" relative h-full bg-white text-slate-700 dark:text-slate-300 dark:bg-slate-900 border-r shadow dark:shadow-gray-700"
6
+ :style =" {
7
+ width: config.layout_side_collapsed_width + 'px'
8
+ }"
9
+ >
10
+ <el-scrollbar >
11
+ <el-menu
12
+ :collapse =" true"
13
+ :collapse-transition =" false"
14
+ :default-active =" topActive"
15
+ class =" border-r-0 w-full"
16
+ unique-opened
17
+ @select =" selectTopMenuItem"
18
+ >
19
+ <template v-for =" item in routerStore .asyncRouters [0 ]?.children || []" >
20
+ <el-menu-item
21
+ v-if =" !item.hidden && (!item.children || item.children.length === 0)"
22
+ :key =" item.name"
23
+ :index =" item.name"
24
+ class =" dark:text-slate-300 overflow-hidden"
25
+ :style =" {
26
+ height: config.layout_side_item_height + 'px'
27
+ }"
28
+ >
29
+ <el-icon v-if =" item.meta.icon" >
30
+ <component :is =" item.meta.icon" />
31
+ </el-icon >
32
+ <template v-else >
33
+ {{ item.meta.title[0] }}
34
+ </template >
35
+ <template #title >
36
+ {{ item.meta.title }}
37
+ </template >
38
+ </el-menu-item >
39
+ <template v-else-if =" ! item .hidden " >
40
+ <el-menu-item
41
+ :key =" item.name"
42
+ :index =" item.name"
43
+ :class =" {'is-active': topActive === item.name}"
44
+ class =" dark:text-slate-300 overflow-hidden"
45
+ :style =" {
46
+ height: config.layout_side_item_height + 'px'
47
+ }"
48
+ >
49
+ <el-icon v-if =" item.meta.icon" >
50
+ <component :is =" item.meta.icon" />
51
+ </el-icon >
52
+ <template v-else >
53
+ {{ item.meta.title[0] }}
54
+ </template >
55
+ <template #title >
56
+ {{ item.meta.title }}
57
+ </template >
58
+ </el-menu-item >
59
+ </template >
60
+
61
+ </template >
62
+ </el-menu >
63
+ </el-scrollbar >
64
+ </div >
65
+
66
+ <!-- 二级菜单并列显示 -->
67
+ <div
68
+ class =" relative h-full bg-white text-slate-700 dark:text-slate-300 dark:bg-slate-900 border-r shadow dark:shadow-gray-700 px-2"
69
+ :style =" {
70
+ width: layoutSideWidth + 'px'
71
+ }"
72
+ >
73
+ <el-scrollbar >
74
+ <el-menu
75
+ :collapse =" isCollapse"
76
+ :collapse-transition =" false"
77
+ :default-active =" active"
78
+ class =" border-r-0 w-full"
79
+ unique-opened
80
+ @select =" selectMenuItem"
81
+ >
82
+ <template v-for =" item in secondLevelMenus " >
83
+ <aside-component
84
+ v-if =" !item.hidden"
85
+ :key =" item.name"
86
+ :router-info =" item"
87
+ />
88
+ </template >
89
+ </el-menu >
90
+ </el-scrollbar >
91
+ <div
92
+ class =" absolute bottom-8 right-2 w-8 h-8 bg-gray-50 dark:bg-slate-800 flex items-center justify-center rounded cursor-pointer"
93
+ :class =" isCollapse ? 'right-0 left-0 mx-auto' : 'right-2'"
94
+ @click =" toggleCollapse"
95
+ >
96
+ <el-icon v-if =" !isCollapse" >
97
+ <DArrowLeft />
98
+ </el-icon >
99
+ <el-icon v-else >
100
+ <DArrowRight />
101
+ </el-icon >
102
+ </div >
103
+ </div >
104
+ </div >
105
+ </template >
106
+
107
+ <script setup>
108
+ import AsideComponent from ' @/view/layout/aside/asideComponent/index.vue'
109
+ import { ref , provide , watchEffect , computed } from ' vue'
110
+ import { useRoute , useRouter } from ' vue-router'
111
+ import { useRouterStore } from ' @/pinia/modules/router'
112
+ import { useAppStore } from ' @/pinia'
113
+ import { storeToRefs } from ' pinia'
114
+
115
+ const appStore = useAppStore ()
116
+ const { device , config } = storeToRefs (appStore)
117
+
118
+ defineOptions ({
119
+ name: ' SidebarMode'
120
+ })
121
+
122
+ const route = useRoute ()
123
+ const router = useRouter ()
124
+ const routerStore = useRouterStore ()
125
+ const isCollapse = ref (false )
126
+ const active = ref (' ' )
127
+ const topActive = ref (' ' )
128
+ const secondLevelMenus = ref ([])
129
+
130
+ const layoutSideWidth = computed (() => {
131
+ if (! isCollapse .value ) {
132
+ return config .value .layout_side_width
133
+ } else {
134
+ return config .value .layout_side_collapsed_width
135
+ }
136
+ })
137
+
138
+
139
+ provide (' isCollapse' , isCollapse)
140
+
141
+ // 更新二级菜单
142
+ const updateSecondLevelMenus = (menuName ) => {
143
+ const menu = routerStore .asyncRouters [0 ]? .children .find (item => item .name === menuName)
144
+ if (menu && menu .children && menu .children .length > 0 ) {
145
+ secondLevelMenus .value = menu .children
146
+ } else {
147
+ secondLevelMenus .value = []
148
+ }
149
+ }
150
+
151
+ // 选择一级菜单
152
+ const selectTopMenuItem = (index ) => {
153
+ topActive .value = index
154
+ updateSecondLevelMenus (index)
155
+
156
+ // 如果选中的一级菜单有子菜单,则导航到第一个子菜单
157
+ const menu = routerStore .asyncRouters [0 ]? .children .find (item => item .name === index)
158
+ if (menu && menu .children && menu .children .length > 0 ) {
159
+ const firstVisibleChild = menu .children .find (child => ! child .hidden )
160
+ if (firstVisibleChild) {
161
+ navigateToMenuItem (firstVisibleChild .name )
162
+ }
163
+ } else {
164
+ // 如果没有子菜单,直接导航到该菜单
165
+ navigateToMenuItem (index)
166
+ }
167
+ }
168
+
169
+ // 选择二级或更深层级的菜单
170
+ const selectMenuItem = (index ) => {
171
+ navigateToMenuItem (index)
172
+ }
173
+
174
+ // 导航到指定菜单
175
+ const navigateToMenuItem = (index ) => {
176
+ const query = {}
177
+ const params = {}
178
+ routerStore .routeMap [index]? .parameters &&
179
+ routerStore .routeMap [index]? .parameters .forEach ((item ) => {
180
+ if (item .type === ' query' ) {
181
+ query[item .key ] = item .value
182
+ } else {
183
+ params[item .key ] = item .value
184
+ }
185
+ })
186
+ if (index === route .name ) return
187
+ if (index .indexOf (' http://' ) > - 1 || index .indexOf (' https://' ) > - 1 ) {
188
+ if (index === ' Iframe' ) {
189
+ query .url = decodeURIComponent (index)
190
+ router .push ({
191
+ name: ' Iframe' ,
192
+ query,
193
+ params
194
+ })
195
+ return
196
+ } else {
197
+ window .open (index, ' _blank' )
198
+ return
199
+ }
200
+ } else {
201
+ router .push ({ name: index, query, params })
202
+ }
203
+ }
204
+
205
+ const toggleCollapse = () => {
206
+ isCollapse .value = ! isCollapse .value
207
+ }
208
+
209
+
210
+
211
+ watchEffect (() => {
212
+ if (route .name === ' Iframe' ) {
213
+ active .value = decodeURIComponent (route .query .url )
214
+ return
215
+ }
216
+ active .value = route .meta .activeName || route .name
217
+
218
+ // 找到当前路由所属的一级菜单
219
+ const findParentMenu = () => {
220
+ for (const topMenu of routerStore .asyncRouters [0 ]? .children || []) {
221
+ if (topMenu .hidden ) continue
222
+
223
+ // 检查当前路由是否是这个一级菜单的子菜单
224
+ if (topMenu .children && topMenu .children .some (child => child .name === route .name )) {
225
+ return topMenu .name
226
+ }
227
+
228
+ // 递归检查更深层级
229
+ const checkChildren = (items ) => {
230
+ for (const item of items || []) {
231
+ if (item .name === route .name ) {
232
+ return true
233
+ }
234
+ if (item .children && checkChildren (item .children )) {
235
+ return true
236
+ }
237
+ }
238
+ return false
239
+ }
240
+
241
+ if (topMenu .children && checkChildren (topMenu .children )) {
242
+ return topMenu .name
243
+ }
244
+ }
245
+ return null
246
+ }
247
+
248
+ const parentMenu = findParentMenu ()
249
+ if (parentMenu) {
250
+ topActive .value = parentMenu
251
+ updateSecondLevelMenus (parentMenu)
252
+ } else if (routerStore .asyncRouters [0 ]? .children ? .length > 0 ) {
253
+ // 如果没有找到父菜单,默认选择第一个有子菜单的一级菜单
254
+ const firstMenuWithChildren = routerStore .asyncRouters [0 ].children .find (
255
+ item => ! item .hidden && item .children && item .children .length > 0
256
+ )
257
+ if (firstMenuWithChildren) {
258
+ topActive .value = firstMenuWithChildren .name
259
+ updateSecondLevelMenus (firstMenuWithChildren .name )
260
+ }
261
+ }
262
+ })
263
+
264
+ watchEffect (() => {
265
+ if (device .value === ' mobile' ) {
266
+ isCollapse .value = true
267
+ } else {
268
+ isCollapse .value = false
269
+ }
270
+ })
271
+ < / script>
272
+
273
+ < style lang= " scss" scoped>
274
+ : deep (.el - menu -- vertical ) {
275
+ .is - active {
276
+ background- color: var (-- el- color- primary- light- 8 ) ! important;
277
+ }
278
+ }
279
+ < / style>
0 commit comments