xto-fronted 0.4.110 → 0.4.111
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/api/index.js +1 -1
- package/dist/composables/useApp.d.ts +1 -0
- package/dist/{index-Ch8vmOiD.js → index-BNLsx1WO.js} +1 -1
- package/dist/{index-DTWHgS2f.js → index-Cjel_bu8.js} +579 -575
- package/dist/{index-C3r5dnEs.js → index-DB43988M.js} +1 -1
- package/dist/{index-CLcJb_H7.js → index-DKHtW-p9.js} +1 -1
- package/dist/{index-BMIR-PSJ.js → index-q2D-YeQK.js} +1 -1
- package/dist/index.js +2 -2
- package/dist/stores/user.d.ts +10 -1
- package/dist/style.css +1 -1
- package/dist/{user-CHxvQHYq.js → user-Dr3u-kRk.js} +336 -332
- package/dist/utils/config.d.ts +6 -1
- package/package.json +1 -1
- package/src/components/Layout/Header.vue +1012 -1012
- package/src/directives/permission.ts +36 -36
- package/src/stores/app.ts +179 -179
- package/src/stores/menu.ts +109 -109
- package/src/stores/user.ts +52 -50
- package/src/types/api.d.ts +81 -80
- package/src/utils/config.ts +16 -3
- package/src/utils/permission.ts +39 -39
|
@@ -1,37 +1,37 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 权限指令
|
|
3
|
-
* v-permission="['user:edit']" 或 v-permission="'user:edit'"
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type { Directive, DirectiveBinding } from 'vue'
|
|
7
|
-
import { useUserStore } from '@/stores/user'
|
|
8
|
-
import { useMenuStore } from '@/stores/menu'
|
|
9
|
-
|
|
10
|
-
const permission: Directive = {
|
|
11
|
-
mounted(el: HTMLElement, binding: DirectiveBinding<string | string[]>) {
|
|
12
|
-
const userStore = useUserStore()
|
|
13
|
-
const menuStore = useMenuStore()
|
|
14
|
-
|
|
15
|
-
// 如果没有登录,移除元素
|
|
16
|
-
if (!userStore.isLoggedIn) {
|
|
17
|
-
el.parentNode?.removeChild(el)
|
|
18
|
-
return
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// 获取权限标识
|
|
22
|
-
const value = binding.value
|
|
23
|
-
const permissions = Array.isArray(value) ? value : [value]
|
|
24
|
-
|
|
25
|
-
// 检查是否有权限
|
|
26
|
-
if (!menuStore.hasPermission(permissions)) {
|
|
27
|
-
el.parentNode?.removeChild(el)
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export default permission
|
|
33
|
-
|
|
34
|
-
// 注册指令
|
|
35
|
-
export function setupPermissionDirective(app: any) {
|
|
36
|
-
app.directive('permission', permission)
|
|
1
|
+
/**
|
|
2
|
+
* 权限指令
|
|
3
|
+
* v-permission="['user:edit']" 或 v-permission="'user:edit'"
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { Directive, DirectiveBinding } from 'vue'
|
|
7
|
+
import { useUserStore } from '@/stores/user'
|
|
8
|
+
import { useMenuStore } from '@/stores/menu'
|
|
9
|
+
|
|
10
|
+
const permission: Directive = {
|
|
11
|
+
mounted(el: HTMLElement, binding: DirectiveBinding<string | string[]>) {
|
|
12
|
+
const userStore = useUserStore()
|
|
13
|
+
const menuStore = useMenuStore()
|
|
14
|
+
|
|
15
|
+
// 如果没有登录,移除元素
|
|
16
|
+
if (!userStore.isLoggedIn) {
|
|
17
|
+
el.parentNode?.removeChild(el)
|
|
18
|
+
return
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// 获取权限标识
|
|
22
|
+
const value = binding.value
|
|
23
|
+
const permissions = Array.isArray(value) ? value : [value]
|
|
24
|
+
|
|
25
|
+
// 检查是否有权限
|
|
26
|
+
if (!menuStore.hasPermission(permissions)) {
|
|
27
|
+
el.parentNode?.removeChild(el)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export default permission
|
|
33
|
+
|
|
34
|
+
// 注册指令
|
|
35
|
+
export function setupPermissionDirective(app: any) {
|
|
36
|
+
app.directive('permission', permission)
|
|
37
37
|
}
|
package/src/stores/app.ts
CHANGED
|
@@ -1,180 +1,180 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 应用状态
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { defineStore } from 'pinia'
|
|
6
|
-
import { ref, computed, watch } from 'vue'
|
|
7
|
-
import { local } from '@/utils/storage'
|
|
8
|
-
|
|
9
|
-
export type ThemeMode = 'light' | 'dark'
|
|
10
|
-
export type LayoutMode = 'sidebar' | 'top' | 'mix'
|
|
11
|
-
|
|
12
|
-
export const useAppStore = defineStore('app', () => {
|
|
13
|
-
// 状态
|
|
14
|
-
const appName = ref<string>(local.get<string>('appName') || 'XTO App')
|
|
15
|
-
const indexPath = ref<string>(local.get<string>('indexPath') || '/dashboard')
|
|
16
|
-
const loginPath = ref<string>(local.get<string>('loginPath') || '/login')
|
|
17
|
-
const isDark = ref<boolean>(local.get<boolean>('isDark') || false)
|
|
18
|
-
const theme = ref<ThemeMode>(local.get<ThemeMode>('theme') || 'light')
|
|
19
|
-
const layout = ref<LayoutMode>(local.get<LayoutMode>('layout') || 'sidebar')
|
|
20
|
-
const isCollapsed = ref<boolean>(local.get<boolean>('isCollapsed') || false)
|
|
21
|
-
const showTabs = ref<boolean>(local.get<boolean>('showTabs') ?? true)
|
|
22
|
-
const showFooter = ref<boolean>(local.get<boolean>('showFooter') ?? true)
|
|
23
|
-
const showBreadcrumb = ref<boolean>(local.get<boolean>('showBreadcrumb') ?? true)
|
|
24
|
-
const primaryColor = ref<string>(local.get<string>('primaryColor') || '#409eff')
|
|
25
|
-
const cachedViews = ref<string[]>([])
|
|
26
|
-
const mixSubMenus = ref<any[]>([])
|
|
27
|
-
|
|
28
|
-
// 计算属性
|
|
29
|
-
const themeClass = computed(() => (isDark.value ? 'dark' : 'light'))
|
|
30
|
-
|
|
31
|
-
// 设置应用名称
|
|
32
|
-
const setAppName = (name: string) => {
|
|
33
|
-
appName.value = name
|
|
34
|
-
local.set('appName', name)
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// 设置默认首页路径
|
|
38
|
-
const setIndexPath = (path: string) => {
|
|
39
|
-
indexPath.value = path
|
|
40
|
-
local.set('indexPath', path)
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// 设置登录页路径
|
|
44
|
-
const setLoginPath = (path: string) => {
|
|
45
|
-
loginPath.value = path
|
|
46
|
-
local.set('loginPath', path)
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// 切换主题
|
|
50
|
-
const toggleTheme = () => {
|
|
51
|
-
isDark.value = !isDark.value
|
|
52
|
-
theme.value = isDark.value ? 'dark' : 'light'
|
|
53
|
-
updateTheme()
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// 设置主题
|
|
57
|
-
const setTheme = (mode: ThemeMode) => {
|
|
58
|
-
theme.value = mode
|
|
59
|
-
isDark.value = mode === 'dark'
|
|
60
|
-
updateTheme()
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// 更新主题
|
|
64
|
-
const updateTheme = () => {
|
|
65
|
-
const html = document.documentElement
|
|
66
|
-
if (isDark.value) {
|
|
67
|
-
html.classList.add('dark')
|
|
68
|
-
} else {
|
|
69
|
-
html.classList.remove('dark')
|
|
70
|
-
}
|
|
71
|
-
local.set('isDark', isDark.value)
|
|
72
|
-
local.set('theme', theme.value)
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// 切换菜单折叠
|
|
76
|
-
const toggleCollapse = () => {
|
|
77
|
-
isCollapsed.value = !isCollapsed.value
|
|
78
|
-
local.set('isCollapsed', isCollapsed.value)
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// 设置布局
|
|
82
|
-
const setLayout = (mode: LayoutMode) => {
|
|
83
|
-
layout.value = mode
|
|
84
|
-
local.set('layout', mode)
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// 切换标签页
|
|
88
|
-
const toggleTabs = () => {
|
|
89
|
-
showTabs.value = !showTabs.value
|
|
90
|
-
local.set('showTabs', showTabs.value)
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// 切换底部
|
|
94
|
-
const toggleFooter = () => {
|
|
95
|
-
showFooter.value = !showFooter.value
|
|
96
|
-
local.set('showFooter', showFooter.value)
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// 切换面包屑
|
|
100
|
-
const toggleBreadcrumb = () => {
|
|
101
|
-
showBreadcrumb.value = !showBreadcrumb.value
|
|
102
|
-
local.set('showBreadcrumb', showBreadcrumb.value)
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// 设置主题色
|
|
106
|
-
const setPrimaryColor = (color: string) => {
|
|
107
|
-
primaryColor.value = color
|
|
108
|
-
document.documentElement.style.setProperty('--color-primary', color)
|
|
109
|
-
local.set('primaryColor', color)
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// 添加缓存页面
|
|
113
|
-
const addCachedView = (name: string) => {
|
|
114
|
-
if (!cachedViews.value.includes(name)) {
|
|
115
|
-
cachedViews.value.push(name)
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// 移除缓存页面
|
|
120
|
-
const removeCachedView = (name: string) => {
|
|
121
|
-
const index = cachedViews.value.indexOf(name)
|
|
122
|
-
if (index > -1) {
|
|
123
|
-
cachedViews.value.splice(index, 1)
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// 清除缓存页面
|
|
128
|
-
const clearCachedViews = () => {
|
|
129
|
-
cachedViews.value = []
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// 设置混合模式子菜单
|
|
133
|
-
const setMixSubMenus = (menus: any[]) => {
|
|
134
|
-
mixSubMenus.value = menus
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// 初始化主题
|
|
138
|
-
const initTheme = () => {
|
|
139
|
-
updateTheme()
|
|
140
|
-
if (primaryColor.value !== '#409eff') {
|
|
141
|
-
document.documentElement.style.setProperty('--color-primary', primaryColor.value)
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// 监听主题变化
|
|
146
|
-
watch(isDark, updateTheme)
|
|
147
|
-
|
|
148
|
-
return {
|
|
149
|
-
appName,
|
|
150
|
-
indexPath,
|
|
151
|
-
loginPath,
|
|
152
|
-
isDark,
|
|
153
|
-
theme,
|
|
154
|
-
layout,
|
|
155
|
-
isCollapsed,
|
|
156
|
-
showTabs,
|
|
157
|
-
showFooter,
|
|
158
|
-
showBreadcrumb,
|
|
159
|
-
primaryColor,
|
|
160
|
-
cachedViews,
|
|
161
|
-
mixSubMenus,
|
|
162
|
-
themeClass,
|
|
163
|
-
setAppName,
|
|
164
|
-
setIndexPath,
|
|
165
|
-
setLoginPath,
|
|
166
|
-
toggleTheme,
|
|
167
|
-
toggleCollapse,
|
|
168
|
-
setTheme,
|
|
169
|
-
setLayout,
|
|
170
|
-
toggleTabs,
|
|
171
|
-
toggleFooter,
|
|
172
|
-
toggleBreadcrumb,
|
|
173
|
-
setPrimaryColor,
|
|
174
|
-
addCachedView,
|
|
175
|
-
removeCachedView,
|
|
176
|
-
clearCachedViews,
|
|
177
|
-
setMixSubMenus,
|
|
178
|
-
initTheme
|
|
179
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* 应用状态
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { defineStore } from 'pinia'
|
|
6
|
+
import { ref, computed, watch } from 'vue'
|
|
7
|
+
import { local } from '@/utils/storage'
|
|
8
|
+
|
|
9
|
+
export type ThemeMode = 'light' | 'dark'
|
|
10
|
+
export type LayoutMode = 'sidebar' | 'top' | 'mix'
|
|
11
|
+
|
|
12
|
+
export const useAppStore = defineStore('app', () => {
|
|
13
|
+
// 状态
|
|
14
|
+
const appName = ref<string>(local.get<string>('appName') || 'XTO App')
|
|
15
|
+
const indexPath = ref<string>(local.get<string>('indexPath') || '/dashboard')
|
|
16
|
+
const loginPath = ref<string>(local.get<string>('loginPath') || '/login')
|
|
17
|
+
const isDark = ref<boolean>(local.get<boolean>('isDark') || false)
|
|
18
|
+
const theme = ref<ThemeMode>(local.get<ThemeMode>('theme') || 'light')
|
|
19
|
+
const layout = ref<LayoutMode>(local.get<LayoutMode>('layout') || 'sidebar')
|
|
20
|
+
const isCollapsed = ref<boolean>(local.get<boolean>('isCollapsed') || false)
|
|
21
|
+
const showTabs = ref<boolean>(local.get<boolean>('showTabs') ?? true)
|
|
22
|
+
const showFooter = ref<boolean>(local.get<boolean>('showFooter') ?? true)
|
|
23
|
+
const showBreadcrumb = ref<boolean>(local.get<boolean>('showBreadcrumb') ?? true)
|
|
24
|
+
const primaryColor = ref<string>(local.get<string>('primaryColor') || '#409eff')
|
|
25
|
+
const cachedViews = ref<string[]>([])
|
|
26
|
+
const mixSubMenus = ref<any[]>([])
|
|
27
|
+
|
|
28
|
+
// 计算属性
|
|
29
|
+
const themeClass = computed(() => (isDark.value ? 'dark' : 'light'))
|
|
30
|
+
|
|
31
|
+
// 设置应用名称
|
|
32
|
+
const setAppName = (name: string) => {
|
|
33
|
+
appName.value = name
|
|
34
|
+
local.set('appName', name)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// 设置默认首页路径
|
|
38
|
+
const setIndexPath = (path: string) => {
|
|
39
|
+
indexPath.value = path
|
|
40
|
+
local.set('indexPath', path)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// 设置登录页路径
|
|
44
|
+
const setLoginPath = (path: string) => {
|
|
45
|
+
loginPath.value = path
|
|
46
|
+
local.set('loginPath', path)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// 切换主题
|
|
50
|
+
const toggleTheme = () => {
|
|
51
|
+
isDark.value = !isDark.value
|
|
52
|
+
theme.value = isDark.value ? 'dark' : 'light'
|
|
53
|
+
updateTheme()
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// 设置主题
|
|
57
|
+
const setTheme = (mode: ThemeMode) => {
|
|
58
|
+
theme.value = mode
|
|
59
|
+
isDark.value = mode === 'dark'
|
|
60
|
+
updateTheme()
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// 更新主题
|
|
64
|
+
const updateTheme = () => {
|
|
65
|
+
const html = document.documentElement
|
|
66
|
+
if (isDark.value) {
|
|
67
|
+
html.classList.add('dark')
|
|
68
|
+
} else {
|
|
69
|
+
html.classList.remove('dark')
|
|
70
|
+
}
|
|
71
|
+
local.set('isDark', isDark.value)
|
|
72
|
+
local.set('theme', theme.value)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// 切换菜单折叠
|
|
76
|
+
const toggleCollapse = () => {
|
|
77
|
+
isCollapsed.value = !isCollapsed.value
|
|
78
|
+
local.set('isCollapsed', isCollapsed.value)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// 设置布局
|
|
82
|
+
const setLayout = (mode: LayoutMode) => {
|
|
83
|
+
layout.value = mode
|
|
84
|
+
local.set('layout', mode)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// 切换标签页
|
|
88
|
+
const toggleTabs = () => {
|
|
89
|
+
showTabs.value = !showTabs.value
|
|
90
|
+
local.set('showTabs', showTabs.value)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// 切换底部
|
|
94
|
+
const toggleFooter = () => {
|
|
95
|
+
showFooter.value = !showFooter.value
|
|
96
|
+
local.set('showFooter', showFooter.value)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// 切换面包屑
|
|
100
|
+
const toggleBreadcrumb = () => {
|
|
101
|
+
showBreadcrumb.value = !showBreadcrumb.value
|
|
102
|
+
local.set('showBreadcrumb', showBreadcrumb.value)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// 设置主题色
|
|
106
|
+
const setPrimaryColor = (color: string) => {
|
|
107
|
+
primaryColor.value = color
|
|
108
|
+
document.documentElement.style.setProperty('--color-primary', color)
|
|
109
|
+
local.set('primaryColor', color)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// 添加缓存页面
|
|
113
|
+
const addCachedView = (name: string) => {
|
|
114
|
+
if (!cachedViews.value.includes(name)) {
|
|
115
|
+
cachedViews.value.push(name)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// 移除缓存页面
|
|
120
|
+
const removeCachedView = (name: string) => {
|
|
121
|
+
const index = cachedViews.value.indexOf(name)
|
|
122
|
+
if (index > -1) {
|
|
123
|
+
cachedViews.value.splice(index, 1)
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// 清除缓存页面
|
|
128
|
+
const clearCachedViews = () => {
|
|
129
|
+
cachedViews.value = []
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// 设置混合模式子菜单
|
|
133
|
+
const setMixSubMenus = (menus: any[]) => {
|
|
134
|
+
mixSubMenus.value = menus
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// 初始化主题
|
|
138
|
+
const initTheme = () => {
|
|
139
|
+
updateTheme()
|
|
140
|
+
if (primaryColor.value !== '#409eff') {
|
|
141
|
+
document.documentElement.style.setProperty('--color-primary', primaryColor.value)
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// 监听主题变化
|
|
146
|
+
watch(isDark, updateTheme)
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
appName,
|
|
150
|
+
indexPath,
|
|
151
|
+
loginPath,
|
|
152
|
+
isDark,
|
|
153
|
+
theme,
|
|
154
|
+
layout,
|
|
155
|
+
isCollapsed,
|
|
156
|
+
showTabs,
|
|
157
|
+
showFooter,
|
|
158
|
+
showBreadcrumb,
|
|
159
|
+
primaryColor,
|
|
160
|
+
cachedViews,
|
|
161
|
+
mixSubMenus,
|
|
162
|
+
themeClass,
|
|
163
|
+
setAppName,
|
|
164
|
+
setIndexPath,
|
|
165
|
+
setLoginPath,
|
|
166
|
+
toggleTheme,
|
|
167
|
+
toggleCollapse,
|
|
168
|
+
setTheme,
|
|
169
|
+
setLayout,
|
|
170
|
+
toggleTabs,
|
|
171
|
+
toggleFooter,
|
|
172
|
+
toggleBreadcrumb,
|
|
173
|
+
setPrimaryColor,
|
|
174
|
+
addCachedView,
|
|
175
|
+
removeCachedView,
|
|
176
|
+
clearCachedViews,
|
|
177
|
+
setMixSubMenus,
|
|
178
|
+
initTheme
|
|
179
|
+
}
|
|
180
180
|
})
|
package/src/stores/menu.ts
CHANGED
|
@@ -1,110 +1,110 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 菜单状态
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { defineStore } from 'pinia'
|
|
6
|
-
import { ref, computed } from 'vue'
|
|
7
|
-
import type { MenuItem } from '@/types/api'
|
|
8
|
-
import { local } from '@/utils/storage'
|
|
9
|
-
|
|
10
|
-
const MENU_LIST_KEY = 'menu_list'
|
|
11
|
-
const PERMISSION_LIST_KEY = 'permission_list'
|
|
12
|
-
|
|
13
|
-
// 首页菜单(参考 tineco-ui)
|
|
14
|
-
const indexMenu: MenuItem = {
|
|
15
|
-
menuCode: 'home',
|
|
16
|
-
menuName: '首页',
|
|
17
|
-
menuUrl: '/dashboard',
|
|
18
|
-
icon: 'home',
|
|
19
|
-
closable: false,
|
|
20
|
-
isDefault: false,
|
|
21
|
-
isOut: false
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* 过滤菜单项,只保留菜单(type !== 1),过滤掉功能按钮(type === 1)
|
|
26
|
-
* @param menus 原始菜单列表
|
|
27
|
-
* @returns 过滤后的菜单列表
|
|
28
|
-
*/
|
|
29
|
-
function filterMenus(menus: MenuItem[]): MenuItem[] {
|
|
30
|
-
return menus
|
|
31
|
-
.filter(menu => menu.type !== 1) // 过滤掉功能按钮
|
|
32
|
-
.map(menu => ({
|
|
33
|
-
...menu,
|
|
34
|
-
children: menu.children ? filterMenus(menu.children) : undefined
|
|
35
|
-
}))
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* 提取所有功能权限(type === 1 的项)
|
|
40
|
-
* @param menus 原始菜单列表
|
|
41
|
-
* @returns 权限标识列表
|
|
42
|
-
*/
|
|
43
|
-
function extractPermissions(menus: MenuItem[]): string[] {
|
|
44
|
-
const permissions: string[] = []
|
|
45
|
-
|
|
46
|
-
function traverse(items: MenuItem[]) {
|
|
47
|
-
items.forEach(item => {
|
|
48
|
-
// type === 1 表示功能按钮
|
|
49
|
-
if (item.type === 1 && item.menuCode) {
|
|
50
|
-
permissions.push(item.menuCode)
|
|
51
|
-
}
|
|
52
|
-
// 递归处理子项
|
|
53
|
-
if (item.children) {
|
|
54
|
-
traverse(item.children)
|
|
55
|
-
}
|
|
56
|
-
})
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
traverse(menus)
|
|
60
|
-
return permissions
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export const useMenuStore = defineStore('menu', () => {
|
|
64
|
-
// 状态
|
|
65
|
-
const menuList = ref<MenuItem[]>(local.get<MenuItem[]>(MENU_LIST_KEY) || [])
|
|
66
|
-
// 功能权限列表
|
|
67
|
-
const permissions = ref<string[]>(local.get<string[]>(PERMISSION_LIST_KEY) || [])
|
|
68
|
-
|
|
69
|
-
// 计算属性
|
|
70
|
-
const hasMenu = computed(() => menuList.value.length > 0)
|
|
71
|
-
|
|
72
|
-
// 设置菜单(会自动过滤功能项,并提取权限)
|
|
73
|
-
const setMenuList = (menus: MenuItem[]) => {
|
|
74
|
-
// 提取功能权限
|
|
75
|
-
permissions.value = extractPermissions(menus)
|
|
76
|
-
local.set(PERMISSION_LIST_KEY, permissions.value)
|
|
77
|
-
|
|
78
|
-
// 过滤菜单,只保留菜单项(type !== 1)
|
|
79
|
-
const filteredMenus = filterMenus(menus)
|
|
80
|
-
|
|
81
|
-
// 添加首页菜单到开头
|
|
82
|
-
menuList.value = [indexMenu, ...filteredMenus]
|
|
83
|
-
local.set(MENU_LIST_KEY, menuList.value)
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// 清除菜单
|
|
87
|
-
const clearMenu = () => {
|
|
88
|
-
menuList.value = []
|
|
89
|
-
permissions.value = []
|
|
90
|
-
local.remove(MENU_LIST_KEY)
|
|
91
|
-
local.remove(PERMISSION_LIST_KEY)
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// 检查是否有某个权限
|
|
95
|
-
const hasPermission = (permission: string | string[]): boolean => {
|
|
96
|
-
if (Array.isArray(permission)) {
|
|
97
|
-
return permission.some(p => permissions.value.includes(p))
|
|
98
|
-
}
|
|
99
|
-
return permissions.value.includes(permission)
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
return {
|
|
103
|
-
menuList,
|
|
104
|
-
permissions,
|
|
105
|
-
hasMenu,
|
|
106
|
-
setMenuList,
|
|
107
|
-
clearMenu,
|
|
108
|
-
hasPermission
|
|
109
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* 菜单状态
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { defineStore } from 'pinia'
|
|
6
|
+
import { ref, computed } from 'vue'
|
|
7
|
+
import type { MenuItem } from '@/types/api'
|
|
8
|
+
import { local } from '@/utils/storage'
|
|
9
|
+
|
|
10
|
+
const MENU_LIST_KEY = 'menu_list'
|
|
11
|
+
const PERMISSION_LIST_KEY = 'permission_list'
|
|
12
|
+
|
|
13
|
+
// 首页菜单(参考 tineco-ui)
|
|
14
|
+
const indexMenu: MenuItem = {
|
|
15
|
+
menuCode: 'home',
|
|
16
|
+
menuName: '首页',
|
|
17
|
+
menuUrl: '/dashboard',
|
|
18
|
+
icon: 'home',
|
|
19
|
+
closable: false,
|
|
20
|
+
isDefault: false,
|
|
21
|
+
isOut: false
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 过滤菜单项,只保留菜单(type !== 1),过滤掉功能按钮(type === 1)
|
|
26
|
+
* @param menus 原始菜单列表
|
|
27
|
+
* @returns 过滤后的菜单列表
|
|
28
|
+
*/
|
|
29
|
+
function filterMenus(menus: MenuItem[]): MenuItem[] {
|
|
30
|
+
return menus
|
|
31
|
+
.filter(menu => menu.type !== 1) // 过滤掉功能按钮
|
|
32
|
+
.map(menu => ({
|
|
33
|
+
...menu,
|
|
34
|
+
children: menu.children ? filterMenus(menu.children) : undefined
|
|
35
|
+
}))
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 提取所有功能权限(type === 1 的项)
|
|
40
|
+
* @param menus 原始菜单列表
|
|
41
|
+
* @returns 权限标识列表
|
|
42
|
+
*/
|
|
43
|
+
function extractPermissions(menus: MenuItem[]): string[] {
|
|
44
|
+
const permissions: string[] = []
|
|
45
|
+
|
|
46
|
+
function traverse(items: MenuItem[]) {
|
|
47
|
+
items.forEach(item => {
|
|
48
|
+
// type === 1 表示功能按钮
|
|
49
|
+
if (item.type === 1 && item.menuCode) {
|
|
50
|
+
permissions.push(item.menuCode)
|
|
51
|
+
}
|
|
52
|
+
// 递归处理子项
|
|
53
|
+
if (item.children) {
|
|
54
|
+
traverse(item.children)
|
|
55
|
+
}
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
traverse(menus)
|
|
60
|
+
return permissions
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export const useMenuStore = defineStore('menu', () => {
|
|
64
|
+
// 状态
|
|
65
|
+
const menuList = ref<MenuItem[]>(local.get<MenuItem[]>(MENU_LIST_KEY) || [])
|
|
66
|
+
// 功能权限列表
|
|
67
|
+
const permissions = ref<string[]>(local.get<string[]>(PERMISSION_LIST_KEY) || [])
|
|
68
|
+
|
|
69
|
+
// 计算属性
|
|
70
|
+
const hasMenu = computed(() => menuList.value.length > 0)
|
|
71
|
+
|
|
72
|
+
// 设置菜单(会自动过滤功能项,并提取权限)
|
|
73
|
+
const setMenuList = (menus: MenuItem[]) => {
|
|
74
|
+
// 提取功能权限
|
|
75
|
+
permissions.value = extractPermissions(menus)
|
|
76
|
+
local.set(PERMISSION_LIST_KEY, permissions.value)
|
|
77
|
+
|
|
78
|
+
// 过滤菜单,只保留菜单项(type !== 1)
|
|
79
|
+
const filteredMenus = filterMenus(menus)
|
|
80
|
+
|
|
81
|
+
// 添加首页菜单到开头
|
|
82
|
+
menuList.value = [indexMenu, ...filteredMenus]
|
|
83
|
+
local.set(MENU_LIST_KEY, menuList.value)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// 清除菜单
|
|
87
|
+
const clearMenu = () => {
|
|
88
|
+
menuList.value = []
|
|
89
|
+
permissions.value = []
|
|
90
|
+
local.remove(MENU_LIST_KEY)
|
|
91
|
+
local.remove(PERMISSION_LIST_KEY)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// 检查是否有某个权限
|
|
95
|
+
const hasPermission = (permission: string | string[]): boolean => {
|
|
96
|
+
if (Array.isArray(permission)) {
|
|
97
|
+
return permission.some(p => permissions.value.includes(p))
|
|
98
|
+
}
|
|
99
|
+
return permissions.value.includes(permission)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
menuList,
|
|
104
|
+
permissions,
|
|
105
|
+
hasMenu,
|
|
106
|
+
setMenuList,
|
|
107
|
+
clearMenu,
|
|
108
|
+
hasPermission
|
|
109
|
+
}
|
|
110
110
|
})
|