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.
- package/.env.development +4 -0
- package/.env.production +4 -0
- package/README.md +94 -196
- package/dist/{components/Layout/TopMenu.vue.d.ts → App.vue.d.ts} +1 -1
- package/dist/api/auth.d.ts +8 -10
- package/dist/api/system.d.ts +11 -12
- package/dist/api/user.d.ts +12 -3
- package/dist/components/Layout/Footer.vue.d.ts +1 -1
- package/dist/components/Layout/Header.vue.d.ts +3 -14
- package/dist/components/Layout/Sidebar.vue.d.ts +1 -1
- package/dist/components/Layout/Tabs.vue.d.ts +1 -1
- package/dist/components/Layout/index.vue.d.ts +1 -1
- package/dist/composables/useAuth.d.ts +4 -19
- package/dist/directives/permission.d.ts +0 -1
- package/dist/index-CWRs4WMN.js +372 -0
- package/dist/index-CpxpXTQX.js +1462 -0
- package/dist/index-Cu3Z2-PY.js +345 -0
- package/dist/index-DPEVEyik.js +475 -0
- package/dist/index-DYnXaqYf.js +142 -0
- package/dist/index.d.ts +12 -25
- package/dist/index.es.js +76 -1521
- package/dist/index.umd.js +1 -20
- package/dist/main.d.ts +0 -1
- package/dist/router/dynamicRoutes.d.ts +33 -17
- package/dist/router/index.d.ts +4 -26
- package/dist/router/layoutRoute.d.ts +18 -0
- package/dist/router/staticRoutes.d.ts +2 -18
- package/dist/setup.d.ts +17 -0
- package/dist/stores/app.d.ts +15 -9
- package/dist/stores/auth.d.ts +48 -62
- package/dist/stores/index.d.ts +3 -1
- package/dist/stores/menu.d.ts +29 -47
- package/dist/stores/user.d.ts +84 -64
- package/dist/style.css +1 -1
- package/dist/utils/auth.d.ts +10 -10
- package/dist/utils/permission.d.ts +10 -1
- package/dist/utils/request.d.ts +7 -23
- package/dist/{components/Layout/Breadcrumb.vue.d.ts → views/dashboard/index.vue.d.ts} +1 -1
- package/dist/{components/Error → views/error}/403.vue.d.ts +1 -1
- package/dist/{components/Error → views/error}/404.vue.d.ts +1 -1
- package/dist/views/login/index.vue.d.ts +4 -0
- package/dist/views/system/menu/index.vue.d.ts +4 -0
- package/dist/views/system/role/index.vue.d.ts +4 -0
- package/dist/views/system/user/index.vue.d.ts +4 -0
- package/dist/vite.svg +9 -9
- package/index.html +13 -0
- package/package.json +27 -31
- package/public/vite.svg +10 -0
- package/src/App.vue +20 -0
- package/src/api/auth.ts +26 -0
- package/src/api/system.ts +65 -0
- package/src/api/user.ts +46 -0
- package/src/assets/styles/_dark.scss +407 -0
- package/src/assets/styles/_reset.scss +126 -0
- package/src/assets/styles/_root.scss +140 -0
- package/src/assets/styles/_transition.scss +119 -0
- package/src/assets/styles/_variables.scss +45 -0
- package/src/assets/styles/index.scss +187 -0
- package/src/components/Layout/Footer.vue +17 -0
- package/src/components/Layout/Header.vue +390 -0
- package/src/components/Layout/Sidebar.vue +297 -0
- package/src/components/Layout/Tabs.vue +134 -0
- package/src/components/Layout/index.vue +62 -0
- package/src/composables/useAuth.ts +45 -0
- package/src/composables/useForm.ts +79 -0
- package/src/composables/useTable.ts +97 -0
- package/src/directives/permission.ts +38 -0
- package/src/enums/index.ts +63 -0
- package/src/env.d.ts +17 -0
- package/src/index.ts +48 -0
- package/src/main.ts +34 -0
- package/src/router/dynamicRoutes.ts +163 -0
- package/src/router/index.ts +81 -0
- package/src/router/layoutRoute.ts +45 -0
- package/src/router/staticRoutes.ts +43 -0
- package/src/setup.ts +54 -0
- package/src/stores/app.ts +163 -0
- package/src/stores/auth.ts +66 -0
- package/src/stores/index.ts +15 -0
- package/src/stores/menu.ts +80 -0
- package/src/stores/user.ts +73 -0
- package/src/style.css +11 -0
- package/src/types/api.d.ts +84 -0
- package/src/types/global.d.ts +45 -0
- package/src/types/router.d.ts +48 -0
- package/src/types/xto.d.ts +149 -0
- package/src/utils/auth.ts +62 -0
- package/src/utils/permission.ts +42 -0
- package/src/utils/request.ts +124 -0
- package/src/utils/storage.ts +63 -0
- package/src/views/dashboard/index.vue +284 -0
- package/src/views/error/403.vue +57 -0
- package/src/views/error/404.vue +57 -0
- package/src/views/login/index.vue +248 -0
- package/src/views/system/menu/index.vue +381 -0
- package/src/views/system/role/index.vue +304 -0
- package/src/views/system/user/index.vue +327 -0
- package/tsconfig.json +26 -0
- package/tsconfig.node.json +11 -0
- package/vite.config.ts +140 -0
- package/dist/api/menu.d.ts +0 -4
- package/dist/components/Login/index.vue.d.ts +0 -25
- package/dist/components/SettingDrawer/index.vue.d.ts +0 -19
- package/dist/composables/index.d.ts +0 -8
- package/dist/composables/useApp.d.ts +0 -65
- package/dist/composables/useMenu.d.ts +0 -34
- 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,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
|
+
})
|