xto-fronted 0.2.5 → 0.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/.env.development +4 -0
  2. package/.env.production +4 -0
  3. package/README.md +94 -196
  4. package/dist/{components/Layout/TopMenu.vue.d.ts → App.vue.d.ts} +1 -1
  5. package/dist/api/auth.d.ts +8 -10
  6. package/dist/api/system.d.ts +11 -12
  7. package/dist/api/user.d.ts +12 -3
  8. package/dist/components/Layout/Footer.vue.d.ts +1 -1
  9. package/dist/components/Layout/Header.vue.d.ts +3 -14
  10. package/dist/components/Layout/Sidebar.vue.d.ts +1 -1
  11. package/dist/components/Layout/Tabs.vue.d.ts +1 -1
  12. package/dist/components/Layout/index.vue.d.ts +1 -1
  13. package/dist/composables/useAuth.d.ts +4 -19
  14. package/dist/directives/permission.d.ts +0 -1
  15. package/dist/index-CWRs4WMN.js +372 -0
  16. package/dist/index-CpxpXTQX.js +1462 -0
  17. package/dist/index-Cu3Z2-PY.js +345 -0
  18. package/dist/index-DPEVEyik.js +475 -0
  19. package/dist/index-DYnXaqYf.js +142 -0
  20. package/dist/index.d.ts +12 -25
  21. package/dist/index.es.js +76 -1521
  22. package/dist/index.umd.js +1 -20
  23. package/dist/main.d.ts +0 -1
  24. package/dist/router/dynamicRoutes.d.ts +33 -17
  25. package/dist/router/index.d.ts +4 -26
  26. package/dist/router/layoutRoute.d.ts +18 -0
  27. package/dist/router/staticRoutes.d.ts +2 -18
  28. package/dist/setup.d.ts +17 -0
  29. package/dist/stores/app.d.ts +15 -9
  30. package/dist/stores/auth.d.ts +48 -62
  31. package/dist/stores/index.d.ts +3 -1
  32. package/dist/stores/menu.d.ts +29 -47
  33. package/dist/stores/user.d.ts +84 -64
  34. package/dist/style.css +1 -1
  35. package/dist/utils/auth.d.ts +10 -10
  36. package/dist/utils/permission.d.ts +10 -1
  37. package/dist/utils/request.d.ts +7 -23
  38. package/dist/{components/Layout/Breadcrumb.vue.d.ts → views/dashboard/index.vue.d.ts} +1 -1
  39. package/dist/{components/Error → views/error}/403.vue.d.ts +1 -1
  40. package/dist/{components/Error → views/error}/404.vue.d.ts +1 -1
  41. package/dist/views/login/index.vue.d.ts +4 -0
  42. package/dist/views/system/menu/index.vue.d.ts +4 -0
  43. package/dist/views/system/role/index.vue.d.ts +4 -0
  44. package/dist/views/system/user/index.vue.d.ts +4 -0
  45. package/dist/vite.svg +9 -9
  46. package/index.html +13 -0
  47. package/package.json +27 -31
  48. package/public/vite.svg +10 -0
  49. package/src/App.vue +20 -0
  50. package/src/api/auth.ts +26 -0
  51. package/src/api/system.ts +65 -0
  52. package/src/api/user.ts +46 -0
  53. package/src/assets/styles/_dark.scss +407 -0
  54. package/src/assets/styles/_reset.scss +126 -0
  55. package/src/assets/styles/_root.scss +140 -0
  56. package/src/assets/styles/_transition.scss +119 -0
  57. package/src/assets/styles/_variables.scss +45 -0
  58. package/src/assets/styles/index.scss +187 -0
  59. package/src/components/Layout/Footer.vue +17 -0
  60. package/src/components/Layout/Header.vue +390 -0
  61. package/src/components/Layout/Sidebar.vue +297 -0
  62. package/src/components/Layout/Tabs.vue +134 -0
  63. package/src/components/Layout/index.vue +62 -0
  64. package/src/composables/useAuth.ts +45 -0
  65. package/src/composables/useForm.ts +79 -0
  66. package/src/composables/useTable.ts +97 -0
  67. package/src/directives/permission.ts +38 -0
  68. package/src/enums/index.ts +63 -0
  69. package/src/env.d.ts +17 -0
  70. package/src/index.ts +48 -0
  71. package/src/main.ts +34 -0
  72. package/src/router/dynamicRoutes.ts +163 -0
  73. package/src/router/index.ts +81 -0
  74. package/src/router/layoutRoute.ts +45 -0
  75. package/src/router/staticRoutes.ts +43 -0
  76. package/src/setup.ts +54 -0
  77. package/src/stores/app.ts +163 -0
  78. package/src/stores/auth.ts +66 -0
  79. package/src/stores/index.ts +15 -0
  80. package/src/stores/menu.ts +80 -0
  81. package/src/stores/user.ts +73 -0
  82. package/src/style.css +11 -0
  83. package/src/types/api.d.ts +84 -0
  84. package/src/types/global.d.ts +45 -0
  85. package/src/types/router.d.ts +48 -0
  86. package/src/types/xto.d.ts +149 -0
  87. package/src/utils/auth.ts +62 -0
  88. package/src/utils/permission.ts +42 -0
  89. package/src/utils/request.ts +124 -0
  90. package/src/utils/storage.ts +63 -0
  91. package/src/views/dashboard/index.vue +284 -0
  92. package/src/views/error/403.vue +57 -0
  93. package/src/views/error/404.vue +57 -0
  94. package/src/views/login/index.vue +248 -0
  95. package/src/views/system/menu/index.vue +381 -0
  96. package/src/views/system/role/index.vue +304 -0
  97. package/src/views/system/user/index.vue +327 -0
  98. package/tsconfig.json +26 -0
  99. package/tsconfig.node.json +11 -0
  100. package/vite.config.ts +140 -0
  101. package/dist/api/menu.d.ts +0 -4
  102. package/dist/components/Login/index.vue.d.ts +0 -25
  103. package/dist/components/SettingDrawer/index.vue.d.ts +0 -19
  104. package/dist/composables/index.d.ts +0 -8
  105. package/dist/composables/useApp.d.ts +0 -65
  106. package/dist/composables/useMenu.d.ts +0 -34
  107. package/dist/config/index.d.ts +0 -31
@@ -0,0 +1,163 @@
1
+ /**
2
+ * 动态路由模板
3
+ */
4
+
5
+ import type { RouteRecordRaw } from 'vue-router'
6
+
7
+ // 布局路由(包含默认子路由)
8
+ export const layoutRoute: RouteRecordRaw = {
9
+ path: '/',
10
+ name: 'Layout',
11
+ component: () => import('@/components/Layout/index.vue'),
12
+ redirect: '/dashboard',
13
+ children: [
14
+ {
15
+ path: '/dashboard',
16
+ name: 'Dashboard',
17
+ component: () => import('@/views/dashboard/index.vue'),
18
+ meta: {
19
+ title: '仪表盘',
20
+ icon: 'dashboard',
21
+ keepAlive: true,
22
+ affix: true
23
+ }
24
+ },
25
+ {
26
+ path: '/system/user',
27
+ name: 'SystemUser',
28
+ component: () => import('@/views/system/user/index.vue'),
29
+ meta: {
30
+ title: '用户管理',
31
+ icon: 'user',
32
+ keepAlive: true
33
+ }
34
+ },
35
+ {
36
+ path: '/system/role',
37
+ name: 'SystemRole',
38
+ component: () => import('@/views/system/role/index.vue'),
39
+ meta: {
40
+ title: '角色管理',
41
+ icon: 'role',
42
+ keepAlive: true
43
+ }
44
+ },
45
+ {
46
+ path: '/system/menu',
47
+ name: 'SystemMenu',
48
+ component: () => import('@/views/system/menu/index.vue'),
49
+ meta: {
50
+ title: '菜单管理',
51
+ icon: 'menu',
52
+ keepAlive: true
53
+ }
54
+ }
55
+ ]
56
+ }
57
+
58
+ // 默认路由(用于开发,后续由后端返回)
59
+ export const defaultDynamicRoutes: RouteRecordRaw[] = [
60
+ {
61
+ path: '/dashboard',
62
+ name: 'Dashboard',
63
+ component: () => import('@/views/dashboard/index.vue'),
64
+ meta: {
65
+ title: '仪表盘',
66
+ icon: 'dashboard',
67
+ keepAlive: true,
68
+ affix: true
69
+ }
70
+ },
71
+ {
72
+ path: '/system',
73
+ name: 'System',
74
+ redirect: '/system/user',
75
+ meta: {
76
+ title: '系统管理',
77
+ icon: 'setting'
78
+ },
79
+ children: [
80
+ {
81
+ path: 'user',
82
+ name: 'SystemUser',
83
+ component: () => import('@/views/system/user/index.vue'),
84
+ meta: {
85
+ title: '用户管理',
86
+ icon: 'user',
87
+ keepAlive: true
88
+ }
89
+ },
90
+ {
91
+ path: 'role',
92
+ name: 'SystemRole',
93
+ component: () => import('@/views/system/role/index.vue'),
94
+ meta: {
95
+ title: '角色管理',
96
+ icon: 'role',
97
+ keepAlive: true
98
+ }
99
+ },
100
+ {
101
+ path: 'menu',
102
+ name: 'SystemMenu',
103
+ component: () => import('@/views/system/menu/index.vue'),
104
+ meta: {
105
+ title: '菜单管理',
106
+ icon: 'menu',
107
+ keepAlive: true
108
+ }
109
+ }
110
+ ]
111
+ }
112
+ ]
113
+
114
+ // Mock 菜单数据
115
+ export const mockMenuData = [
116
+ {
117
+ id: 1,
118
+ name: 'Dashboard',
119
+ path: '/dashboard',
120
+ component: 'dashboard/index',
121
+ icon: 'dashboard',
122
+ title: '仪表盘',
123
+ keepAlive: true,
124
+ affix: true
125
+ },
126
+ {
127
+ id: 2,
128
+ name: 'System',
129
+ path: '/system',
130
+ redirect: '/system/user',
131
+ icon: 'setting',
132
+ title: '系统管理',
133
+ children: [
134
+ {
135
+ id: 21,
136
+ name: 'SystemUser',
137
+ path: '/system/user',
138
+ component: 'system/user/index',
139
+ icon: 'user',
140
+ title: '用户管理',
141
+ keepAlive: true
142
+ },
143
+ {
144
+ id: 22,
145
+ name: 'SystemRole',
146
+ path: '/system/role',
147
+ component: 'system/role/index',
148
+ icon: 'role',
149
+ title: '角色管理',
150
+ keepAlive: true
151
+ },
152
+ {
153
+ id: 23,
154
+ name: 'SystemMenu',
155
+ path: '/system/menu',
156
+ component: 'system/menu/index',
157
+ icon: 'menu',
158
+ title: '菜单管理',
159
+ keepAlive: true
160
+ }
161
+ ]
162
+ }
163
+ ]
@@ -0,0 +1,81 @@
1
+ /**
2
+ * 路由配置
3
+ */
4
+
5
+ import { createRouter, createWebHistory } from 'vue-router'
6
+ import { staticRoutes, errorRoute } from './staticRoutes'
7
+ import { layoutRoute } from './dynamicRoutes'
8
+ import { hasToken } from '@/utils/auth'
9
+ import { useUserStore } from '@/stores/user'
10
+ import { useMenuStore } from '@/stores/menu'
11
+ import { useAppStore } from '@/stores/app'
12
+ import { mockMenuData } from './dynamicRoutes'
13
+
14
+ const router = createRouter({
15
+ history: createWebHistory(),
16
+ routes: [...staticRoutes, layoutRoute, errorRoute],
17
+ scrollBehavior: () => ({ left: 0, top: 0 })
18
+ })
19
+
20
+ // 白名单路由
21
+ const whiteList = ['/login', '/404', '/403']
22
+
23
+ // 路由守卫
24
+ router.beforeEach(async (to, _from, next) => {
25
+ const appStore = useAppStore()
26
+
27
+ // 初始化主题
28
+ appStore.initTheme()
29
+
30
+ // 已登录
31
+ if (hasToken()) {
32
+ if (to.path === '/login') {
33
+ // 已登录,跳转到首页
34
+ next({ path: '/' })
35
+ } else {
36
+ // 设置用户信息(如果未设置)
37
+ const userStore = useUserStore()
38
+ if (!userStore.isLoggedIn) {
39
+ userStore.setUserInfo({
40
+ id: 1,
41
+ username: 'admin',
42
+ nickname: '管理员',
43
+ avatar: '',
44
+ email: 'admin@example.com',
45
+ phone: '13800138000',
46
+ status: 1,
47
+ roles: ['admin'],
48
+ permissions: ['*'],
49
+ createTime: new Date().toISOString()
50
+ })
51
+
52
+ // 设置菜单
53
+ const menuStore = useMenuStore()
54
+ menuStore.setMenuList(mockMenuData)
55
+ }
56
+
57
+ // 添加缓存页面
58
+ if (to.name && to.meta.keepAlive) {
59
+ appStore.addCachedView(to.name as string)
60
+ }
61
+ next()
62
+ }
63
+ } else {
64
+ // 未登录
65
+ if (whiteList.includes(to.path)) {
66
+ next()
67
+ } else {
68
+ next('/login')
69
+ }
70
+ }
71
+ })
72
+
73
+ export function resetRouter() {
74
+ const newRouter = createRouter({
75
+ history: createWebHistory(),
76
+ routes: [...staticRoutes, layoutRoute, errorRoute]
77
+ })
78
+ ;(router as any).matcher = (newRouter as any).matcher
79
+ }
80
+
81
+ export default router
@@ -0,0 +1,45 @@
1
+ /**
2
+ * 布局路由工厂函数
3
+ */
4
+
5
+ import { createRouter as vueCreateRouter, createWebHistory } from 'vue-router'
6
+ import type { RouteRecordRaw, Router } from 'vue-router'
7
+ import Layout from '@/components/Layout/index.vue'
8
+
9
+ interface LayoutRouteOptions {
10
+ indexPath?: string
11
+ }
12
+
13
+ /**
14
+ * 创建布局路由
15
+ * @param children 子路由配置
16
+ * @param options 配置选项
17
+ * @returns 布局路由配置
18
+ */
19
+ export function createLayoutRoute(
20
+ children: RouteRecordRaw[],
21
+ options: LayoutRouteOptions = {}
22
+ ): RouteRecordRaw {
23
+ const indexPath = options.indexPath || '/dashboard'
24
+
25
+ return {
26
+ path: '/',
27
+ name: 'Layout',
28
+ component: Layout,
29
+ redirect: indexPath,
30
+ children
31
+ }
32
+ }
33
+
34
+ /**
35
+ * 创建路由实例
36
+ * @param routes 路由配置数组
37
+ * @returns 路由实例
38
+ */
39
+ export function createRouter(routes: RouteRecordRaw[]): Router {
40
+ return vueCreateRouter({
41
+ history: createWebHistory(),
42
+ routes,
43
+ scrollBehavior: () => ({ left: 0, top: 0 })
44
+ })
45
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * 静态路由
3
+ */
4
+
5
+ import type { RouteRecordRaw } from 'vue-router'
6
+
7
+ export const staticRoutes: RouteRecordRaw[] = [
8
+ {
9
+ path: '/login',
10
+ name: 'Login',
11
+ component: () => import('@/views/login/index.vue'),
12
+ meta: {
13
+ title: '登录',
14
+ hidden: true
15
+ }
16
+ },
17
+ {
18
+ path: '/404',
19
+ name: 'NotFound',
20
+ component: () => import('@/views/error/404.vue'),
21
+ meta: {
22
+ title: '404',
23
+ hidden: true
24
+ }
25
+ },
26
+ {
27
+ path: '/403',
28
+ name: 'Forbidden',
29
+ component: () => import('@/views/error/403.vue'),
30
+ meta: {
31
+ title: '403',
32
+ hidden: true
33
+ }
34
+ }
35
+ ]
36
+
37
+ export const errorRoute: RouteRecordRaw = {
38
+ path: '/:pathMatch(.*)*',
39
+ redirect: '/404',
40
+ meta: {
41
+ hidden: true
42
+ }
43
+ }
package/src/setup.ts ADDED
@@ -0,0 +1,54 @@
1
+ /**
2
+ * 应用初始化配置
3
+ */
4
+
5
+ import { useAppStore } from './stores/app'
6
+ import { useAuthStore } from './stores/auth'
7
+
8
+ interface XtoAppConfig {
9
+ appName?: string
10
+ baseUrl?: string
11
+ appId?: string
12
+ clientId?: string
13
+ indexPath?: string
14
+ loginPath?: string
15
+ }
16
+
17
+ /**
18
+ * 创建 XTO 应用配置
19
+ * @param config 应用配置
20
+ */
21
+ export function createXtoApp(config: XtoAppConfig = {}) {
22
+ const appStore = useAppStore()
23
+ const authStore = useAuthStore()
24
+
25
+ // 设置应用名称
26
+ if (config.appName) {
27
+ appStore.setAppName(config.appName)
28
+ }
29
+
30
+ // 设置 API 基础路径
31
+ if (config.baseUrl) {
32
+ authStore.setBaseUrl(config.baseUrl)
33
+ }
34
+
35
+ // 设置应用 ID
36
+ if (config.appId) {
37
+ authStore.setAppId(config.appId)
38
+ }
39
+
40
+ // 设置客户端 ID
41
+ if (config.clientId) {
42
+ authStore.setClientId(config.clientId)
43
+ }
44
+
45
+ // 设置默认首页路径
46
+ if (config.indexPath) {
47
+ appStore.setIndexPath(config.indexPath)
48
+ }
49
+
50
+ // 设置登录页路径
51
+ if (config.loginPath) {
52
+ authStore.setLoginPath(config.loginPath)
53
+ }
54
+ }
@@ -0,0 +1,163 @@
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 isDark = ref<boolean>(local.get<boolean>('isDark') || false)
17
+ const theme = ref<ThemeMode>(local.get<ThemeMode>('theme') || 'light')
18
+ const layout = ref<LayoutMode>(local.get<LayoutMode>('layout') || 'sidebar')
19
+ const isCollapsed = ref<boolean>(local.get<boolean>('isCollapsed') || false)
20
+ const showTabs = ref<boolean>(local.get<boolean>('showTabs') ?? true)
21
+ const showFooter = ref<boolean>(local.get<boolean>('showFooter') ?? true)
22
+ const showBreadcrumb = ref<boolean>(local.get<boolean>('showBreadcrumb') ?? true)
23
+ const primaryColor = ref<string>(local.get<string>('primaryColor') || '#409eff')
24
+ const cachedViews = ref<string[]>([])
25
+
26
+ // 计算属性
27
+ const themeClass = computed(() => (isDark.value ? 'dark' : 'light'))
28
+
29
+ // 设置应用名称
30
+ const setAppName = (name: string) => {
31
+ appName.value = name
32
+ local.set('appName', name)
33
+ }
34
+
35
+ // 设置默认首页路径
36
+ const setIndexPath = (path: string) => {
37
+ indexPath.value = path
38
+ local.set('indexPath', path)
39
+ }
40
+
41
+ // 切换主题
42
+ const toggleTheme = () => {
43
+ isDark.value = !isDark.value
44
+ theme.value = isDark.value ? 'dark' : 'light'
45
+ updateTheme()
46
+ }
47
+
48
+ // 设置主题
49
+ const setTheme = (mode: ThemeMode) => {
50
+ theme.value = mode
51
+ isDark.value = mode === 'dark'
52
+ updateTheme()
53
+ }
54
+
55
+ // 更新主题
56
+ const updateTheme = () => {
57
+ const html = document.documentElement
58
+ if (isDark.value) {
59
+ html.classList.add('dark')
60
+ } else {
61
+ html.classList.remove('dark')
62
+ }
63
+ local.set('isDark', isDark.value)
64
+ local.set('theme', theme.value)
65
+ }
66
+
67
+ // 切换菜单折叠
68
+ const toggleCollapse = () => {
69
+ isCollapsed.value = !isCollapsed.value
70
+ local.set('isCollapsed', isCollapsed.value)
71
+ }
72
+
73
+ // 设置布局
74
+ const setLayout = (mode: LayoutMode) => {
75
+ layout.value = mode
76
+ local.set('layout', mode)
77
+ }
78
+
79
+ // 切换标签页
80
+ const toggleTabs = () => {
81
+ showTabs.value = !showTabs.value
82
+ local.set('showTabs', showTabs.value)
83
+ }
84
+
85
+ // 切换底部
86
+ const toggleFooter = () => {
87
+ showFooter.value = !showFooter.value
88
+ local.set('showFooter', showFooter.value)
89
+ }
90
+
91
+ // 切换面包屑
92
+ const toggleBreadcrumb = () => {
93
+ showBreadcrumb.value = !showBreadcrumb.value
94
+ local.set('showBreadcrumb', showBreadcrumb.value)
95
+ }
96
+
97
+ // 设置主题色
98
+ const setPrimaryColor = (color: string) => {
99
+ primaryColor.value = color
100
+ document.documentElement.style.setProperty('--color-primary', color)
101
+ local.set('primaryColor', color)
102
+ }
103
+
104
+ // 添加缓存页面
105
+ const addCachedView = (name: string) => {
106
+ if (!cachedViews.value.includes(name)) {
107
+ cachedViews.value.push(name)
108
+ }
109
+ }
110
+
111
+ // 移除缓存页面
112
+ const removeCachedView = (name: string) => {
113
+ const index = cachedViews.value.indexOf(name)
114
+ if (index > -1) {
115
+ cachedViews.value.splice(index, 1)
116
+ }
117
+ }
118
+
119
+ // 清除缓存页面
120
+ const clearCachedViews = () => {
121
+ cachedViews.value = []
122
+ }
123
+
124
+ // 初始化主题
125
+ const initTheme = () => {
126
+ updateTheme()
127
+ if (primaryColor.value !== '#409eff') {
128
+ document.documentElement.style.setProperty('--color-primary', primaryColor.value)
129
+ }
130
+ }
131
+
132
+ // 监听主题变化
133
+ watch(isDark, updateTheme)
134
+
135
+ return {
136
+ appName,
137
+ indexPath,
138
+ isDark,
139
+ theme,
140
+ layout,
141
+ isCollapsed,
142
+ showTabs,
143
+ showFooter,
144
+ showBreadcrumb,
145
+ primaryColor,
146
+ cachedViews,
147
+ themeClass,
148
+ setAppName,
149
+ setIndexPath,
150
+ toggleTheme,
151
+ toggleCollapse,
152
+ setTheme,
153
+ setLayout,
154
+ toggleTabs,
155
+ toggleFooter,
156
+ toggleBreadcrumb,
157
+ setPrimaryColor,
158
+ addCachedView,
159
+ removeCachedView,
160
+ clearCachedViews,
161
+ initTheme
162
+ }
163
+ })
@@ -0,0 +1,66 @@
1
+ /**
2
+ * 认证状态
3
+ */
4
+
5
+ import { defineStore } from 'pinia'
6
+ import { ref, computed } from 'vue'
7
+ import { getToken, clearToken, setTokenInfo, hasToken } from '@/utils/auth'
8
+
9
+ export const useAuthStore = defineStore('auth', () => {
10
+ // 状态
11
+ const token = ref<string | null>(getToken())
12
+ const isLoggedIn = computed(() => hasToken())
13
+
14
+ // 应用配置
15
+ const baseUrl = ref<string>('')
16
+ const appId = ref<string>('')
17
+ const clientId = ref<string>('')
18
+ const loginPath = ref<string>('/login')
19
+
20
+ // 设置 token
21
+ const login = (tokenInfo: { token: string; refreshToken: string; expireTime: number }) => {
22
+ token.value = tokenInfo.token
23
+ setTokenInfo(tokenInfo)
24
+ }
25
+
26
+ // 登出
27
+ const logout = () => {
28
+ token.value = null
29
+ clearToken()
30
+ }
31
+
32
+ // 设置 API 基础路径
33
+ const setBaseUrl = (url: string) => {
34
+ baseUrl.value = url
35
+ }
36
+
37
+ // 设置应用 ID
38
+ const setAppId = (id: string) => {
39
+ appId.value = id
40
+ }
41
+
42
+ // 设置客户端 ID
43
+ const setClientId = (id: string) => {
44
+ clientId.value = id
45
+ }
46
+
47
+ // 设置登录页路径
48
+ const setLoginPath = (path: string) => {
49
+ loginPath.value = path
50
+ }
51
+
52
+ return {
53
+ token,
54
+ isLoggedIn,
55
+ baseUrl,
56
+ appId,
57
+ clientId,
58
+ loginPath,
59
+ login,
60
+ logout,
61
+ setBaseUrl,
62
+ setAppId,
63
+ setClientId,
64
+ setLoginPath
65
+ }
66
+ })
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Pinia Store 入口
3
+ */
4
+
5
+ import { createPinia } from 'pinia'
6
+
7
+ const pinia = createPinia()
8
+
9
+ export default pinia
10
+
11
+ // 导出所有 stores
12
+ export * from './user'
13
+ export * from './auth'
14
+ export * from './menu'
15
+ export * from './app'
@@ -0,0 +1,80 @@
1
+ /**
2
+ * 菜单状态
3
+ */
4
+
5
+ import { defineStore } from 'pinia'
6
+ import { ref, computed } from 'vue'
7
+ import type { AppRoute } from '@/types/router'
8
+ import type { MenuItem } from '@/types/api'
9
+ import { local } from '@/utils/storage'
10
+ import router from '@/router'
11
+
12
+ export const useMenuStore = defineStore('menu', () => {
13
+ // 状态
14
+ const menuList = ref<MenuItem[]>(local.get<MenuItem[]>('menuList') || [])
15
+
16
+ // 计算属性
17
+ const hasMenu = computed(() => menuList.value.length > 0)
18
+
19
+ // 设置菜单
20
+ const setMenuList = (menus: MenuItem[]) => {
21
+ menuList.value = menus
22
+ local.set('menuList', menus)
23
+ }
24
+
25
+ // 清除菜单
26
+ const clearMenu = () => {
27
+ menuList.value = []
28
+ local.remove('menuList')
29
+ }
30
+
31
+ // 生成路由
32
+ const generateRoutes = (menus: MenuItem[]): AppRoute[] => {
33
+ return menus
34
+ .filter(menu => !menu.hidden)
35
+ .map(menu => {
36
+ const route: AppRoute = {
37
+ path: menu.path,
38
+ name: menu.name,
39
+ meta: {
40
+ title: menu.title,
41
+ icon: menu.icon,
42
+ keepAlive: menu.keepAlive,
43
+ hidden: menu.hidden
44
+ }
45
+ }
46
+
47
+ if (menu.redirect) {
48
+ route.redirect = menu.redirect
49
+ }
50
+
51
+ if (menu.component) {
52
+ // 动态导入组件
53
+ route.component = () => import(`@/views/${menu.component}.vue`)
54
+ }
55
+
56
+ if (menu.children && menu.children.length > 0) {
57
+ route.children = generateRoutes(menu.children)
58
+ }
59
+
60
+ return route
61
+ })
62
+ }
63
+
64
+ // 添加路由
65
+ const addRoutes = (menus: MenuItem[]) => {
66
+ const routes = generateRoutes(menus)
67
+ routes.forEach(route => {
68
+ router.addRoute('Layout', route as any)
69
+ })
70
+ }
71
+
72
+ return {
73
+ menuList,
74
+ hasMenu,
75
+ setMenuList,
76
+ clearMenu,
77
+ generateRoutes,
78
+ addRoutes
79
+ }
80
+ })