xto-fronted 0.1.1 → 0.1.3

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 (67) hide show
  1. package/.env.development +3 -4
  2. package/.env.production +3 -4
  3. package/bin/cli.js +104 -0
  4. package/dist/{403-MQkNUulz.js → 403-DM5wfQkM.js} +6 -6
  5. package/dist/{404-BOFYLq4X.js → 404-BurAu5LC.js} +7 -7
  6. package/dist/api/auth.d.ts +9 -8
  7. package/dist/api/menu.d.ts +3 -0
  8. package/dist/api/user.d.ts +2 -12
  9. package/dist/composables/index.d.ts +8 -0
  10. package/dist/composables/useApp.d.ts +64 -0
  11. package/dist/composables/useAuth.d.ts +19 -4
  12. package/dist/composables/useMenu.d.ts +34 -0
  13. package/dist/config/index.d.ts +11 -0
  14. package/dist/index-BNiEld34.js +15 -0
  15. package/dist/index-Be9RiEfo.js +98 -0
  16. package/dist/index-BqRv1bdN.js +1185 -0
  17. package/dist/index-CQLVXvNJ.js +15 -0
  18. package/dist/index-CyiE8n2V.js +15 -0
  19. package/dist/index-xauR1bOL.js +15 -0
  20. package/dist/index.d.ts +7 -4
  21. package/dist/index.es.js +50 -66
  22. package/dist/index.umd.js +1 -1
  23. package/dist/stores/auth.d.ts +60 -23
  24. package/dist/stores/menu.d.ts +40 -29
  25. package/dist/stores/user.d.ts +63 -84
  26. package/dist/style.css +1 -1
  27. package/dist/utils/auth.d.ts +15 -7
  28. package/dist/utils/permission.d.ts +1 -6
  29. package/dist/views/system/menu/index.vue.d.ts +1 -3
  30. package/dist/views/system/role/index.vue.d.ts +1 -3
  31. package/dist/views/system/user/index.vue.d.ts +1 -3
  32. package/package.json +27 -19
  33. package/src/api/auth.ts +34 -25
  34. package/src/api/menu.ts +13 -0
  35. package/src/api/user.ts +11 -45
  36. package/src/components/Layout/Header.vue +334 -389
  37. package/src/components/Layout/Sidebar.vue +212 -296
  38. package/src/components/Layout/Tabs.vue +19 -133
  39. package/src/composables/index.ts +9 -0
  40. package/src/composables/useApp.ts +170 -0
  41. package/src/composables/useAuth.ts +69 -44
  42. package/src/composables/useMenu.ts +141 -0
  43. package/src/config/index.ts +19 -0
  44. package/src/directives/permission.ts +40 -37
  45. package/src/index.ts +9 -4
  46. package/src/router/index.ts +70 -80
  47. package/src/stores/auth.ts +44 -31
  48. package/src/stores/menu.ts +157 -79
  49. package/src/stores/user.ts +40 -72
  50. package/src/types/api.d.ts +102 -83
  51. package/src/types/xto.d.ts +148 -148
  52. package/src/utils/auth.ts +85 -61
  53. package/src/utils/permission.ts +29 -41
  54. package/src/utils/request.ts +125 -125
  55. package/src/utils/storage.ts +10 -1
  56. package/src/views/dashboard/index.vue +31 -283
  57. package/src/views/login/index.vue +140 -247
  58. package/src/views/system/menu/index.vue +31 -380
  59. package/src/views/system/role/index.vue +31 -303
  60. package/src/views/system/user/index.vue +31 -326
  61. package/vite.config.ts +3 -3
  62. package/dist/index-BJxYdNPy.js +0 -475
  63. package/dist/index-BvnIIBR1.js +0 -142
  64. package/dist/index-CEvAq6KE.js +0 -372
  65. package/dist/index-DPkqej__.js +0 -345
  66. package/dist/index-pq9Z5K62.js +0 -184
  67. package/dist/index-vVfjShJR.js +0 -1183
@@ -1,81 +1,71 @@
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
-
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 { useAppStore } from '@/stores/app'
10
+ import { useApp } from '@/composables'
11
+
12
+ const router = createRouter({
13
+ history: createWebHistory(),
14
+ routes: [...staticRoutes, layoutRoute, errorRoute],
15
+ scrollBehavior: () => ({ left: 0, top: 0 })
16
+ })
17
+
18
+ // 白名单路由
19
+ const whiteList = ['/login', '/404', '/403']
20
+
21
+ // 路由守卫
22
+ router.beforeEach(async (to, _from, next) => {
23
+ const appStore = useAppStore()
24
+
25
+ // 初始化主题
26
+ appStore.initTheme()
27
+
28
+ // 已登录
29
+ if (hasToken()) {
30
+ if (to.path === '/login') {
31
+ // 已登录,跳转到首页
32
+ next({ path: '/' })
33
+ } else {
34
+ // 初始化应用(加载用户信息和菜单)
35
+ const { initApp, isLoggedIn } = useApp()
36
+
37
+ if (!isLoggedIn.value) {
38
+ const success = await initApp()
39
+ if (!success) {
40
+ // 初始化失败,跳转登录页
41
+ next('/login')
42
+ return
43
+ }
44
+ }
45
+
46
+ // 添加缓存页面
47
+ if (to.name && to.meta.keepAlive) {
48
+ appStore.addCachedView(to.name as string)
49
+ }
50
+ next()
51
+ }
52
+ } else {
53
+ // 未登录
54
+ if (whiteList.includes(to.path)) {
55
+ next()
56
+ } else {
57
+ // 记录当前路径,登录后跳转
58
+ next({ path: '/login', query: { redirectUrl: encodeURIComponent(to.fullPath) } })
59
+ }
60
+ }
61
+ })
62
+
63
+ export function resetRouter() {
64
+ const newRouter = createRouter({
65
+ history: createWebHistory(),
66
+ routes: [...staticRoutes, layoutRoute, errorRoute]
67
+ })
68
+ ;(router as any).matcher = (newRouter as any).matcher
69
+ }
70
+
81
71
  export default router
@@ -1,32 +1,45 @@
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
- // 设置 token
15
- const login = (tokenInfo: { token: string; refreshToken: string; expireTime: number }) => {
16
- token.value = tokenInfo.token
17
- setTokenInfo(tokenInfo)
18
- }
19
-
20
- // 登出
21
- const logout = () => {
22
- token.value = null
23
- clearToken()
24
- }
25
-
26
- return {
27
- token,
28
- isLoggedIn,
29
- login,
30
- logout
31
- }
1
+ /**
2
+ * 认证状态
3
+ */
4
+
5
+ import { defineStore } from 'pinia'
6
+ import { ref, computed } from 'vue'
7
+ import { getLoginInfo, setLoginInfo, clearToken, hasToken } from '@/utils/auth'
8
+ import type { LoginInfo } from '@/utils/auth'
9
+
10
+ export const useAuthStore = defineStore('auth', () => {
11
+ // 状态
12
+ const loginInfo = ref<LoginInfo | null>(getLoginInfo())
13
+ const isLoggedIn = computed(() => hasToken())
14
+
15
+ // 设置登录信息
16
+ const login = (data: Record<string, unknown>) => {
17
+ setLoginInfo(data)
18
+ loginInfo.value = getLoginInfo()
19
+ }
20
+
21
+ // 登出
22
+ const logout = () => {
23
+ loginInfo.value = null
24
+ clearToken()
25
+ }
26
+
27
+ // 获取 access token
28
+ const accessToken = computed(() => loginInfo.value?.accessToken || null)
29
+
30
+ // 获取 token type
31
+ const tokenType = computed(() => loginInfo.value?.tokenType || 'Bearer')
32
+
33
+ // 获取 code
34
+ const code = computed(() => loginInfo.value?.code || null)
35
+
36
+ return {
37
+ loginInfo,
38
+ isLoggedIn,
39
+ accessToken,
40
+ tokenType,
41
+ code,
42
+ login,
43
+ logout
44
+ }
32
45
  })
@@ -1,80 +1,158 @@
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
- }
1
+ /**
2
+ * 菜单状态
3
+ */
4
+
5
+ import { defineStore } from 'pinia'
6
+ import { ref, computed } from 'vue'
7
+ import type { MenuItem, RemoteMenuItem } from '@/types/api'
8
+ import { local } from '@/utils/storage'
9
+
10
+ export const useMenuStore = defineStore('menu', () => {
11
+ // 存储 key
12
+ const MENU_LIST_KEY = 'menu_list'
13
+ const MENU_BTN_LIST_KEY = 'menu_btn_list'
14
+
15
+ // 状态
16
+ const menuList = ref<MenuItem[]>(local.get<MenuItem[]>(MENU_LIST_KEY) || [])
17
+ const menuBtnListMap = ref<Record<string, MenuItem[]>>(local.get<Record<string, MenuItem[]>>(MENU_BTN_LIST_KEY) || {})
18
+
19
+ // 首页菜单信息
20
+ const indexMenu: MenuItem = {
21
+ code: 'home',
22
+ name: '首页',
23
+ icon: 'home',
24
+ closable: false,
25
+ default: false,
26
+ out: false,
27
+ path: '/dashboard',
28
+ title: '首页'
29
+ }
30
+
31
+ // 计算属性
32
+ const hasMenu = computed(() => menuList.value.length > 0)
33
+
34
+ // 获取首页地址
35
+ const index = computed(() => {
36
+ function recursion(menuList: MenuItem[]): string | null {
37
+ if (menuList && menuList.length > 0) {
38
+ for (let i = 0; i < menuList.length; i++) {
39
+ const menu = menuList[i]
40
+ if (menu.default) {
41
+ return menu.path
42
+ } else {
43
+ const url = recursion(menu.children || [])
44
+ if (url) {
45
+ return url
46
+ }
47
+ }
48
+ }
49
+ }
50
+ return null
51
+ }
52
+ const indexUrl = recursion(menuList.value)
53
+ return indexUrl || indexMenu.path
54
+ })
55
+
56
+ // 解析后端菜单数据
57
+ const parseMenuData = (remoteMenuList: RemoteMenuItem[], parentMenuUrl?: string) => {
58
+ const localMenuList: MenuItem[] = []
59
+
60
+ if (!remoteMenuList || remoteMenuList.length <= 0) {
61
+ return localMenuList
62
+ }
63
+
64
+ remoteMenuList.forEach(remoteMenu => {
65
+ if (remoteMenu.type === 1) {
66
+ // 按钮权限
67
+ const btn: MenuItem = {
68
+ code: remoteMenu.menuCode,
69
+ name: remoteMenu.menuName,
70
+ path: '',
71
+ title: remoteMenu.menuName
72
+ }
73
+ const parentUrl = parentMenuUrl || ''
74
+ let btnList = menuBtnListMap.value[parentUrl] || []
75
+ btnList.push(btn)
76
+ menuBtnListMap.value[parentUrl] = btnList
77
+ } else {
78
+ // 菜单权限
79
+ const children: MenuItem[] = []
80
+ let menuPath = remoteMenu.menuUrl
81
+
82
+ // 处理外链
83
+ let isOut = remoteMenu.isOut || false
84
+ if (!isOut && remoteMenu.menuUrl) {
85
+ if (remoteMenu.menuUrl.startsWith('http')) {
86
+ menuPath = '/iframe/' + encodeURIComponent(menuPath)
87
+ } else if (remoteMenu.menuUrl.startsWith('keep-alive:')) {
88
+ menuPath = '/iframe/keep-alive/' + encodeURIComponent(menuPath.split('keep-alive:')[1])
89
+ }
90
+ }
91
+
92
+ const menu: MenuItem = {
93
+ code: remoteMenu.menuCode,
94
+ name: remoteMenu.menuName,
95
+ path: menuPath,
96
+ icon: remoteMenu.icon,
97
+ closable: remoteMenu.closable,
98
+ default: remoteMenu.isDefault,
99
+ out: isOut,
100
+ children: children,
101
+ title: remoteMenu.menuName
102
+ }
103
+
104
+ localMenuList.push(menu)
105
+
106
+ // 递归处理子菜单
107
+ if (remoteMenu.children && remoteMenu.children.length > 0) {
108
+ const childMenus = parseMenuData(remoteMenu.children, remoteMenu.menuUrl)
109
+ menu.children = childMenus
110
+ }
111
+ }
112
+ })
113
+
114
+ return localMenuList
115
+ }
116
+
117
+ // 设置菜单
118
+ const setMenuList = (menus: MenuItem[]) => {
119
+ menuList.value = menus
120
+ local.set(MENU_LIST_KEY, menus)
121
+ }
122
+
123
+ // 设置菜单(从后端数据)
124
+ const setMenuFromRemote = (remoteMenus: RemoteMenuItem[]) => {
125
+ // 清除旧数据
126
+ clearMenu()
127
+
128
+ // 添加首页
129
+ menuList.value.push(indexMenu)
130
+
131
+ // 解析后端菜单
132
+ const parsedMenus = parseMenuData(remoteMenus)
133
+ menuList.value.push(...parsedMenus)
134
+
135
+ // 保存到本地存储
136
+ local.set(MENU_LIST_KEY, menuList.value)
137
+ local.set(MENU_BTN_LIST_KEY, menuBtnListMap.value)
138
+ }
139
+
140
+ // 清除菜单
141
+ const clearMenu = () => {
142
+ menuList.value = []
143
+ menuBtnListMap.value = {}
144
+ local.remove(MENU_LIST_KEY)
145
+ local.remove(MENU_BTN_LIST_KEY)
146
+ }
147
+
148
+ return {
149
+ menuList,
150
+ menuBtnListMap,
151
+ hasMenu,
152
+ index,
153
+ indexMenu,
154
+ setMenuList,
155
+ setMenuFromRemote,
156
+ clearMenu
157
+ }
80
158
  })
@@ -1,73 +1,41 @@
1
- /**
2
- * 用户状态
3
- */
4
-
5
- import { defineStore } from 'pinia'
6
- import { ref, computed } from 'vue'
7
- import type { UserInfo } from '@/types/api'
8
- import { local } from '@/utils/storage'
9
-
10
- export const useUserStore = defineStore('user', () => {
11
- // 状态
12
- const userInfo = ref<UserInfo | null>(local.get<UserInfo>('userInfo'))
13
- const roles = ref<string[]>(local.get<string[]>('roles') || [])
14
- const permissions = ref<string[]>(local.get<string[]>('permissions') || [])
15
-
16
- // 计算属性
17
- const isLoggedIn = computed(() => !!userInfo.value)
18
- const username = computed(() => userInfo.value?.username || '')
19
- const nickname = computed(() => userInfo.value?.nickname || '')
20
- const avatar = computed(() => userInfo.value?.avatar || '')
21
- const userId = computed(() => userInfo.value?.id)
22
-
23
- // 设置用户信息
24
- const setUserInfo = (info: UserInfo) => {
25
- userInfo.value = info
26
- roles.value = info.roles || []
27
- permissions.value = info.permissions || []
28
- local.set('userInfo', info)
29
- local.set('roles', info.roles || [])
30
- local.set('permissions', info.permissions || [])
31
- }
32
-
33
- // 清除用户信息
34
- const clearUserInfo = () => {
35
- userInfo.value = null
36
- roles.value = []
37
- permissions.value = []
38
- local.remove('userInfo')
39
- local.remove('roles')
40
- local.remove('permissions')
41
- }
42
-
43
- // 检查权限
44
- const hasPermission = (permission: string | string[]): boolean => {
45
- if (Array.isArray(permission)) {
46
- return permission.some(p => permissions.value.includes(p))
47
- }
48
- return permissions.value.includes(permission)
49
- }
50
-
51
- // 检查角色
52
- const hasRole = (role: string | string[]): boolean => {
53
- if (Array.isArray(role)) {
54
- return role.some(r => roles.value.includes(r))
55
- }
56
- return roles.value.includes(role)
57
- }
58
-
59
- return {
60
- userInfo,
61
- roles,
62
- permissions,
63
- isLoggedIn,
64
- username,
65
- nickname,
66
- avatar,
67
- userId,
68
- setUserInfo,
69
- clearUserInfo,
70
- hasPermission,
71
- hasRole
72
- }
1
+ /**
2
+ * 用户状态
3
+ */
4
+
5
+ import { defineStore } from 'pinia'
6
+ import { ref, computed } from 'vue'
7
+ import type { UserInfo } from '@/types/api'
8
+ import { local } from '@/utils/storage'
9
+
10
+ export const useUserStore = defineStore('user', () => {
11
+ // 状态
12
+ const userInfo = ref<UserInfo | null>(local.get<UserInfo>('user_info'))
13
+
14
+ // 计算属性
15
+ const isLoggedIn = computed(() => !!userInfo.value)
16
+ const userName = computed(() => userInfo.value?.userName || '')
17
+ const avatar = computed(() => userInfo.value?.avatar || '')
18
+ const userId = computed(() => userInfo.value?.userId)
19
+
20
+ // 设置用户信息
21
+ const setUserInfo = (info: UserInfo) => {
22
+ userInfo.value = info
23
+ local.set('user_info', info)
24
+ }
25
+
26
+ // 清除用户信息
27
+ const clearUserInfo = () => {
28
+ userInfo.value = null
29
+ local.remove('user_info')
30
+ }
31
+
32
+ return {
33
+ userInfo,
34
+ isLoggedIn,
35
+ userName,
36
+ avatar,
37
+ userId,
38
+ setUserInfo,
39
+ clearUserInfo
40
+ }
73
41
  })