xto-fronted 0.4.24 → 0.4.25
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/dist/index-BEWrRNps.js +475 -0
- package/dist/index-C_MzXmh2.js +2830 -0
- package/dist/index-DGAqp0QX.js +372 -0
- package/dist/index-DeLbU1t-.js +345 -0
- package/dist/index-g9bRMdAX.js +142 -0
- package/dist/index.es.js +1 -1
- package/dist/index.umd.js +1 -1
- package/dist/stores/app.d.ts +8 -2
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/assets/styles/_dark.scss +67 -0
- package/src/components/Layout/MixTopMenu.vue +870 -0
- package/src/components/Layout/index.vue +220 -124
- package/src/stores/app.ts +15 -0
|
@@ -1,125 +1,221 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
import { computed } from 'vue'
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
//
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
//
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
//
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, watch } from 'vue'
|
|
3
|
+
import { useRoute } from 'vue-router'
|
|
4
|
+
import { useAppStore } from '@/stores/app'
|
|
5
|
+
import { useMenuStore } from '@/stores/menu'
|
|
6
|
+
import Sidebar from './Sidebar.vue'
|
|
7
|
+
import Header from './Header.vue'
|
|
8
|
+
import TopMenu from './TopMenu.vue'
|
|
9
|
+
import MixTopMenu from './MixTopMenu.vue'
|
|
10
|
+
|
|
11
|
+
const route = useRoute()
|
|
12
|
+
const appStore = useAppStore()
|
|
13
|
+
const menuStore = useMenuStore()
|
|
14
|
+
|
|
15
|
+
const sidebarWidth = computed(() =>
|
|
16
|
+
appStore.isCollapsed ? '64px' : '210px'
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
// 布局模式
|
|
20
|
+
const layoutMode = computed(() => appStore.layout)
|
|
21
|
+
|
|
22
|
+
// 混合模式下当前选中一级菜单的子菜单(二级及以下)
|
|
23
|
+
const mixSubMenus = computed(() => {
|
|
24
|
+
if (layoutMode.value !== 'mix') return []
|
|
25
|
+
|
|
26
|
+
const topMenuPath = appStore.activeTopMenuPath
|
|
27
|
+
if (!topMenuPath) return []
|
|
28
|
+
|
|
29
|
+
// 找到当前选中的一级菜单
|
|
30
|
+
const topMenu = menuStore.menuList.find(m => m.menuUrl === topMenuPath)
|
|
31
|
+
// 返回其children(二级菜单),如果没有children则返回空数组
|
|
32
|
+
return topMenu?.children || []
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
// 混合模式下是否显示左侧菜单(有二级菜单才显示)
|
|
36
|
+
const showMixSidebar = computed(() => {
|
|
37
|
+
return layoutMode.value === 'mix' && mixSubMenus.value.length > 0
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
// 根据当前路由自动匹配一级菜单
|
|
41
|
+
const syncTopMenuByRoute = (path: string) => {
|
|
42
|
+
if (layoutMode.value === 'mix' && menuStore.menuList.length > 0) {
|
|
43
|
+
// 遍历一级菜单
|
|
44
|
+
for (const menu of menuStore.menuList) {
|
|
45
|
+
// 检查是否直接是一级菜单路径
|
|
46
|
+
if (menu.menuUrl === path) {
|
|
47
|
+
appStore.setActiveTopMenuPath(menu.menuUrl)
|
|
48
|
+
return
|
|
49
|
+
}
|
|
50
|
+
// 检查是否是该一级菜单的子菜单(包括多级子菜单)
|
|
51
|
+
if (menu.children && menu.children.length > 0) {
|
|
52
|
+
const isInChildren = checkPathInChildren(menu.children, path)
|
|
53
|
+
if (isInChildren) {
|
|
54
|
+
appStore.setActiveTopMenuPath(menu.menuUrl)
|
|
55
|
+
return
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// 如果没匹配到,默认选中第一个有子菜单的一级菜单,或第一个菜单
|
|
60
|
+
const firstMenuWithChildren = menuStore.menuList.find(m => m.children && m.children.length > 0)
|
|
61
|
+
const defaultMenu = firstMenuWithChildren || menuStore.menuList[0]
|
|
62
|
+
if (defaultMenu) {
|
|
63
|
+
appStore.setActiveTopMenuPath(defaultMenu.menuUrl)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// 递归检查路径是否在子菜单中
|
|
69
|
+
const checkPathInChildren = (children: any[], path: string): boolean => {
|
|
70
|
+
for (const child of children) {
|
|
71
|
+
if (child.menuUrl === path) {
|
|
72
|
+
return true
|
|
73
|
+
}
|
|
74
|
+
if (child.children && child.children.length > 0) {
|
|
75
|
+
if (checkPathInChildren(child.children, path)) {
|
|
76
|
+
return true
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return false
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// 监听路由变化,同步一级菜单选中状态
|
|
84
|
+
watch(
|
|
85
|
+
() => route.path,
|
|
86
|
+
(path) => {
|
|
87
|
+
syncTopMenuByRoute(path)
|
|
88
|
+
},
|
|
89
|
+
{ immediate: true }
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
// 监听菜单列表变化,重新同步
|
|
93
|
+
watch(
|
|
94
|
+
() => menuStore.menuList,
|
|
95
|
+
() => {
|
|
96
|
+
if (layoutMode.value === 'mix') {
|
|
97
|
+
syncTopMenuByRoute(route.path)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
)
|
|
101
|
+
</script>
|
|
102
|
+
|
|
103
|
+
<template>
|
|
104
|
+
<div class="layout" :class="`layout--${layoutMode}`">
|
|
105
|
+
<!-- sidebar模式:左侧菜单 + Header + 内容 -->
|
|
106
|
+
<template v-if="layoutMode === 'sidebar'">
|
|
107
|
+
<aside class="layout__aside" :style="{ width: sidebarWidth }">
|
|
108
|
+
<Sidebar :menu-list="menuStore.menuList" />
|
|
109
|
+
</aside>
|
|
110
|
+
<div class="layout__main">
|
|
111
|
+
<header class="layout__header">
|
|
112
|
+
<Header />
|
|
113
|
+
</header>
|
|
114
|
+
<main class="layout__content">
|
|
115
|
+
<router-view />
|
|
116
|
+
</main>
|
|
117
|
+
</div>
|
|
118
|
+
</template>
|
|
119
|
+
|
|
120
|
+
<!-- top模式:顶部菜单 + 内容 -->
|
|
121
|
+
<template v-if="layoutMode === 'top'">
|
|
122
|
+
<div class="layout__top-menu">
|
|
123
|
+
<TopMenu />
|
|
124
|
+
</div>
|
|
125
|
+
<div class="layout__main">
|
|
126
|
+
<main class="layout__content">
|
|
127
|
+
<router-view />
|
|
128
|
+
</main>
|
|
129
|
+
</div>
|
|
130
|
+
</template>
|
|
131
|
+
|
|
132
|
+
<!-- mix模式:顶部一级菜单 + 左侧二级菜单 + 内容 -->
|
|
133
|
+
<template v-if="layoutMode === 'mix'">
|
|
134
|
+
<div class="layout__mix-top">
|
|
135
|
+
<MixTopMenu />
|
|
136
|
+
</div>
|
|
137
|
+
<div class="layout__mix-body">
|
|
138
|
+
<!-- 只有当一级菜单有子菜单时才显示左侧菜单 -->
|
|
139
|
+
<aside v-if="showMixSidebar" class="layout__aside" :style="{ width: sidebarWidth }">
|
|
140
|
+
<Sidebar :menu-list="mixSubMenus" />
|
|
141
|
+
</aside>
|
|
142
|
+
<div class="layout__main">
|
|
143
|
+
<main class="layout__content">
|
|
144
|
+
<router-view />
|
|
145
|
+
</main>
|
|
146
|
+
</div>
|
|
147
|
+
</div>
|
|
148
|
+
</template>
|
|
149
|
+
</div>
|
|
150
|
+
</template>
|
|
151
|
+
|
|
152
|
+
<style lang="scss" scoped>
|
|
153
|
+
.layout {
|
|
154
|
+
display: flex;
|
|
155
|
+
width: 100%;
|
|
156
|
+
height: 100%;
|
|
157
|
+
|
|
158
|
+
// 左侧菜单模式
|
|
159
|
+
&--sidebar {
|
|
160
|
+
flex-direction: row;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// 顶部菜单模式
|
|
164
|
+
&--top {
|
|
165
|
+
flex-direction: column;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// 混合菜单模式
|
|
169
|
+
&--mix {
|
|
170
|
+
flex-direction: column;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
&__aside {
|
|
174
|
+
transition: width 0.3s;
|
|
175
|
+
overflow: hidden;
|
|
176
|
+
flex-shrink: 0;
|
|
177
|
+
height: 100%;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
&__top-menu {
|
|
181
|
+
width: 100%;
|
|
182
|
+
height: 50px;
|
|
183
|
+
background-color: var(--bg-color);
|
|
184
|
+
border-bottom: 1px solid var(--color-border-lighter);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
&__mix-top {
|
|
188
|
+
width: 100%;
|
|
189
|
+
height: 50px;
|
|
190
|
+
background-color: var(--bg-color);
|
|
191
|
+
border-bottom: 1px solid var(--color-border-lighter);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
&__mix-body {
|
|
195
|
+
display: flex;
|
|
196
|
+
flex: 1;
|
|
197
|
+
overflow: hidden;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
&__main {
|
|
201
|
+
flex: 1;
|
|
202
|
+
display: flex;
|
|
203
|
+
flex-direction: column;
|
|
204
|
+
overflow: hidden;
|
|
205
|
+
height: 100%;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
&__header {
|
|
209
|
+
height: 50px;
|
|
210
|
+
background-color: var(--bg-color);
|
|
211
|
+
border-bottom: 1px solid var(--color-border-lighter);
|
|
212
|
+
flex-shrink: 0;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
&__content {
|
|
216
|
+
flex: 1;
|
|
217
|
+
overflow: auto;
|
|
218
|
+
background-color: var(--bg-color-page);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
125
221
|
</style>
|
package/src/stores/app.ts
CHANGED
|
@@ -22,6 +22,8 @@ export const useAppStore = defineStore('app', () => {
|
|
|
22
22
|
const showBreadcrumb = ref<boolean>(local.get<boolean>('showBreadcrumb') ?? true)
|
|
23
23
|
const primaryColor = ref<string>(local.get<string>('primaryColor') || '#409eff')
|
|
24
24
|
const cachedViews = ref<string[]>([])
|
|
25
|
+
// 混合模式下选中的一级菜单路径
|
|
26
|
+
const activeTopMenuPath = ref<string>(local.get<string>('activeTopMenuPath') || '')
|
|
25
27
|
|
|
26
28
|
// 计算属性
|
|
27
29
|
const themeClass = computed(() => (isDark.value ? 'dark' : 'light'))
|
|
@@ -74,6 +76,17 @@ export const useAppStore = defineStore('app', () => {
|
|
|
74
76
|
const setLayout = (mode: LayoutMode) => {
|
|
75
77
|
layout.value = mode
|
|
76
78
|
local.set('layout', mode)
|
|
79
|
+
// 切换布局时清除一级菜单选中状态
|
|
80
|
+
if (mode !== 'mix') {
|
|
81
|
+
activeTopMenuPath.value = ''
|
|
82
|
+
local.remove('activeTopMenuPath')
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// 设置混合模式选中的一级菜单
|
|
87
|
+
const setActiveTopMenuPath = (path: string) => {
|
|
88
|
+
activeTopMenuPath.value = path
|
|
89
|
+
local.set('activeTopMenuPath', path)
|
|
77
90
|
}
|
|
78
91
|
|
|
79
92
|
// 切换标签页
|
|
@@ -144,6 +157,7 @@ export const useAppStore = defineStore('app', () => {
|
|
|
144
157
|
showBreadcrumb,
|
|
145
158
|
primaryColor,
|
|
146
159
|
cachedViews,
|
|
160
|
+
activeTopMenuPath,
|
|
147
161
|
themeClass,
|
|
148
162
|
setAppName,
|
|
149
163
|
setIndexPath,
|
|
@@ -151,6 +165,7 @@ export const useAppStore = defineStore('app', () => {
|
|
|
151
165
|
toggleCollapse,
|
|
152
166
|
setTheme,
|
|
153
167
|
setLayout,
|
|
168
|
+
setActiveTopMenuPath,
|
|
154
169
|
toggleTabs,
|
|
155
170
|
toggleFooter,
|
|
156
171
|
toggleBreadcrumb,
|