xto-fronted 0.4.2 → 0.4.4

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.
@@ -1,32 +1,22 @@
1
1
  /**
2
2
  * 权限指令
3
3
  * v-permission="['user:edit']" 或 v-permission="'user:edit'"
4
+ * 注意:tineco-ui 不支持 permissions 字段,此指令暂时只检查登录状态
4
5
  */
5
6
 
6
7
  import type { Directive, DirectiveBinding } from 'vue'
7
8
  import { useUserStore } from '@/stores/user'
8
9
 
9
10
  const permission: Directive = {
10
- mounted(el: HTMLElement, binding: DirectiveBinding<string | string[]>) {
11
+ mounted(el: HTMLElement, _binding: DirectiveBinding<string | string[]>) {
11
12
  const userStore = useUserStore()
12
- const { value } = binding
13
13
 
14
- if (!value) return
15
-
16
- const permissions = userStore.permissions
17
-
18
- // 判断是否有权限
19
- let hasPermission = false
20
- if (Array.isArray(value)) {
21
- hasPermission = value.some(p => permissions.includes(p) || permissions.includes('*'))
22
- } else {
23
- hasPermission = permissions.includes(value) || permissions.includes('*')
24
- }
25
-
26
- // 没有权限则移除元素
27
- if (!hasPermission) {
14
+ // 如果没有登录,移除元素
15
+ if (!userStore.isLoggedIn) {
28
16
  el.parentNode?.removeChild(el)
29
17
  }
18
+
19
+ // 注意:tineco-ui 不支持 permissions 字段,此指令暂时不做权限判断
30
20
  }
31
21
  }
32
22
 
@@ -111,52 +111,52 @@ export const defaultDynamicRoutes: RouteRecordRaw[] = [
111
111
  }
112
112
  ]
113
113
 
114
- // Mock 菜单数据
114
+ // Mock 菜单数据(参考 tineco-ui 格式)
115
115
  export const mockMenuData = [
116
116
  {
117
- id: 1,
118
- name: 'Dashboard',
119
- path: '/dashboard',
120
- component: 'dashboard/index',
117
+ menuCode: 'dashboard',
118
+ menuName: '仪表盘',
119
+ menuUrl: '/dashboard',
121
120
  icon: 'dashboard',
122
- title: '仪表盘',
123
- keepAlive: true,
124
- affix: true
121
+ closable: true,
122
+ isDefault: true,
123
+ isOut: false
125
124
  },
126
125
  {
127
- id: 2,
128
- name: 'System',
129
- path: '/system',
130
- redirect: '/system/user',
126
+ menuCode: 'system',
127
+ menuName: '系统管理',
128
+ menuUrl: '/system',
131
129
  icon: 'setting',
132
- title: '系统管理',
130
+ closable: false,
131
+ isDefault: false,
132
+ isOut: false,
133
133
  children: [
134
134
  {
135
- id: 21,
136
- name: 'SystemUser',
137
- path: '/system/user',
138
- component: 'system/user/index',
135
+ menuCode: 'system_user',
136
+ menuName: '用户管理',
137
+ menuUrl: '/system/user',
139
138
  icon: 'user',
140
- title: '用户管理',
141
- keepAlive: true
139
+ closable: true,
140
+ isDefault: false,
141
+ isOut: false
142
142
  },
143
143
  {
144
- id: 22,
145
- name: 'SystemRole',
146
- path: '/system/role',
147
- component: 'system/role/index',
144
+ menuCode: 'system_role',
145
+ menuName: '角色管理',
146
+ menuUrl: '/system/role',
148
147
  icon: 'role',
149
- title: '角色管理',
150
- keepAlive: true
148
+ closable: true,
149
+ isDefault: false,
150
+ isOut: false
151
151
  },
152
152
  {
153
- id: 23,
154
- name: 'SystemMenu',
155
- path: '/system/menu',
156
- component: 'system/menu/index',
153
+ menuCode: 'system_menu',
154
+ menuName: '菜单管理',
155
+ menuUrl: '/system/menu',
157
156
  icon: 'menu',
158
- title: '菜单管理',
159
- keepAlive: true
157
+ closable: true,
158
+ isDefault: false,
159
+ isOut: false
160
160
  }
161
161
  ]
162
162
  }
@@ -20,6 +20,8 @@ interface RouterGuardOptions {
20
20
  loginPath?: string
21
21
  // 首页路径
22
22
  homePath?: string
23
+ // 应用ID
24
+ appId?: string
23
25
  // 获取用户信息的回调(可选,默认调用 API)
24
26
  fetchUserInfo?: () => Promise<any>
25
27
  // 获取菜单的回调(可选,默认调用 API)
@@ -39,9 +41,6 @@ export function setupRouterGuards(router: Router, options: RouterGuardOptions =
39
41
  const homePath = options.homePath || '/'
40
42
 
41
43
  router.beforeEach(async (to, _from, next) => {
42
- // 启动进度条(可选)
43
- // NProgress.start()
44
-
45
44
  const appStore = useAppStore()
46
45
  const userStore = useUserStore()
47
46
  const menuStore = useMenuStore()
@@ -57,7 +56,6 @@ export function setupRouterGuards(router: Router, options: RouterGuardOptions =
57
56
  if (to.path === loginPath) {
58
57
  // 已登录访问登录页,跳转到首页
59
58
  next({ path: homePath })
60
- // NProgress.done()
61
59
  } else {
62
60
  // 检查是否已获取用户信息
63
61
  if (userStore.isLoggedIn) {
@@ -84,7 +82,7 @@ export function setupRouterGuards(router: Router, options: RouterGuardOptions =
84
82
  const menuList = await options.fetchMenu()
85
83
  menuStore.setMenuList(menuList)
86
84
  } else {
87
- const menuList = await getMenuTree()
85
+ const menuList = await getMenuTree(options.appId)
88
86
  menuStore.setMenuList(menuList)
89
87
  }
90
88
 
@@ -107,9 +105,9 @@ export function setupRouterGuards(router: Router, options: RouterGuardOptions =
107
105
  menuStore.clearMenu()
108
106
  // 清除 token
109
107
  localStorage.removeItem('token')
110
- localStorage.removeItem('refreshToken')
108
+ localStorage.removeItem('token_type')
109
+ localStorage.removeItem('refresh_token')
111
110
  next({ path: loginPath, query: { redirect: to.fullPath } })
112
- // NProgress.done()
113
111
  }
114
112
  }
115
113
  }
@@ -121,13 +119,11 @@ export function setupRouterGuards(router: Router, options: RouterGuardOptions =
121
119
  } else {
122
120
  // 不在白名单中,跳转到登录页
123
121
  next({ path: loginPath, query: { redirect: to.fullPath } })
124
- // NProgress.done()
125
122
  }
126
123
  }
127
124
  })
128
125
 
129
126
  router.afterEach(() => {
130
- // 结束进度条
131
- // NProgress.done()
127
+ // 可以在这里添加进度条结束等
132
128
  })
133
129
  }
@@ -37,16 +37,14 @@ router.beforeEach(async (to, _from, next) => {
37
37
  const userStore = useUserStore()
38
38
  if (!userStore.isLoggedIn) {
39
39
  userStore.setUserInfo({
40
- id: 1,
41
- username: 'admin',
42
- nickname: '管理员',
43
- avatar: '',
40
+ appId: import.meta.env.VITE_APP_ID,
41
+ userId: '1',
42
+ userName: '管理员',
43
+ departmentName: '技术部',
44
44
  email: 'admin@example.com',
45
- phone: '13800138000',
46
- status: 1,
47
- roles: ['admin'],
48
- permissions: ['*'],
49
- createTime: new Date().toISOString()
45
+ mobilePhone: '13800138000',
46
+ positionName: '管理员',
47
+ avatar: ''
50
48
  })
51
49
 
52
50
  // 设置菜单
@@ -4,7 +4,7 @@
4
4
 
5
5
  import { defineStore } from 'pinia'
6
6
  import { ref, computed } from 'vue'
7
- import { getToken, clearToken, setTokenInfo, hasToken } from '@/utils/auth'
7
+ import { getToken, clearToken, setTokenInfo, hasToken, type TokenInfo } from '@/utils/auth'
8
8
 
9
9
  export const useAuthStore = defineStore('auth', () => {
10
10
  // 状态
@@ -17,9 +17,9 @@ export const useAuthStore = defineStore('auth', () => {
17
17
  const clientId = ref<string>('')
18
18
  const loginPath = ref<string>('/login')
19
19
 
20
- // 设置 token
21
- const login = (tokenInfo: { token: string; refreshToken: string; expireTime: number }) => {
22
- token.value = tokenInfo.token
20
+ // 设置 token(参考 tineco-ui)
21
+ const login = (tokenInfo: TokenInfo) => {
22
+ token.value = tokenInfo.access_token
23
23
  setTokenInfo(tokenInfo)
24
24
  }
25
25
 
@@ -4,77 +4,46 @@
4
4
 
5
5
  import { defineStore } from 'pinia'
6
6
  import { ref, computed } from 'vue'
7
- import type { AppRoute } from '@/types/router'
8
7
  import type { MenuItem } from '@/types/api'
9
8
  import { local } from '@/utils/storage'
10
- import router from '@/router'
9
+
10
+ const MENU_LIST_KEY = 'menu_list'
11
+
12
+ // 首页菜单(参考 tineco-ui)
13
+ const indexMenu: MenuItem = {
14
+ menuCode: 'home',
15
+ menuName: '首页',
16
+ menuUrl: '/dashboard',
17
+ icon: 'home',
18
+ closable: false,
19
+ isDefault: false,
20
+ isOut: false
21
+ }
11
22
 
12
23
  export const useMenuStore = defineStore('menu', () => {
13
24
  // 状态
14
- const menuList = ref<MenuItem[]>(local.get<MenuItem[]>('menuList') || [])
25
+ const menuList = ref<MenuItem[]>(local.get<MenuItem[]>(MENU_LIST_KEY) || [])
15
26
 
16
27
  // 计算属性
17
28
  const hasMenu = computed(() => menuList.value.length > 0)
18
29
 
19
30
  // 设置菜单
20
31
  const setMenuList = (menus: MenuItem[]) => {
21
- menuList.value = menus
22
- local.set('menuList', menus)
32
+ // 添加首页菜单到开头
33
+ menuList.value = [indexMenu, ...menus]
34
+ local.set(MENU_LIST_KEY, menuList.value)
23
35
  }
24
36
 
25
37
  // 清除菜单
26
38
  const clearMenu = () => {
27
39
  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
- })
40
+ local.remove(MENU_LIST_KEY)
70
41
  }
71
42
 
72
43
  return {
73
44
  menuList,
74
45
  hasMenu,
75
46
  setMenuList,
76
- clearMenu,
77
- generateRoutes,
78
- addRoutes
47
+ clearMenu
79
48
  }
80
49
  })
@@ -7,67 +7,45 @@ import { ref, computed } from 'vue'
7
7
  import type { UserInfo } from '@/types/api'
8
8
  import { local } from '@/utils/storage'
9
9
 
10
+ const USER_INFO_KEY = 'user_info'
11
+
10
12
  export const useUserStore = defineStore('user', () => {
11
13
  // 状态
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') || [])
14
+ const userInfo = ref<UserInfo | null>(local.get<UserInfo>(USER_INFO_KEY))
15
15
 
16
16
  // 计算属性
17
17
  const isLoggedIn = computed(() => !!userInfo.value)
18
- const username = computed(() => userInfo.value?.username || '')
19
- const nickname = computed(() => userInfo.value?.nickname || '')
18
+ const userId = computed(() => userInfo.value?.userId || '')
19
+ const userName = computed(() => userInfo.value?.userName || '')
20
+ const departmentName = computed(() => userInfo.value?.departmentName || '')
21
+ const email = computed(() => userInfo.value?.email || '')
22
+ const mobilePhone = computed(() => userInfo.value?.mobilePhone || '')
23
+ const positionName = computed(() => userInfo.value?.positionName || '')
20
24
  const avatar = computed(() => userInfo.value?.avatar || '')
21
- const userId = computed(() => userInfo.value?.id)
22
25
 
23
26
  // 设置用户信息
24
27
  const setUserInfo = (info: UserInfo) => {
25
28
  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 || [])
29
+ local.set(USER_INFO_KEY, info)
31
30
  }
32
31
 
33
32
  // 清除用户信息
34
33
  const clearUserInfo = () => {
35
34
  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)
35
+ local.remove(USER_INFO_KEY)
57
36
  }
58
37
 
59
38
  return {
60
39
  userInfo,
61
- roles,
62
- permissions,
63
40
  isLoggedIn,
64
- username,
65
- nickname,
66
- avatar,
67
41
  userId,
42
+ userName,
43
+ departmentName,
44
+ email,
45
+ mobilePhone,
46
+ positionName,
47
+ avatar,
68
48
  setUserInfo,
69
- clearUserInfo,
70
- hasPermission,
71
- hasRole
49
+ clearUserInfo
72
50
  }
73
51
  })
@@ -24,22 +24,19 @@ export interface PageResponse<T> {
24
24
  pageSize: number
25
25
  }
26
26
 
27
- // 用户信息
27
+ // 用户信息(参考 tineco-ui)
28
28
  export interface UserInfo {
29
- id: number | string
30
- username: string
31
- nickname: string
32
- avatar?: string
29
+ appId: string
30
+ userId: string
31
+ userName: string
32
+ departmentName?: string
33
33
  email?: string
34
- phone?: string
35
- status: number
36
- roles: string[]
37
- permissions: string[]
38
- createTime?: string
39
- updateTime?: string
34
+ mobilePhone?: string
35
+ positionName?: string
36
+ avatar?: string
40
37
  }
41
38
 
42
- // 登录请求
39
+ // 登录请求参数(参考 tineco-ui)
43
40
  export interface LoginParams {
44
41
  appId: string
45
42
  clientId: string
@@ -48,28 +45,26 @@ export interface LoginParams {
48
45
  code?: boolean
49
46
  }
50
47
 
51
- // 登录响应
48
+ // 登录响应(参考 tineco-ui)
52
49
  export interface LoginResult {
53
- token: string
54
- refreshToken: string
55
- expireTime: number
56
- user: UserInfo
50
+ access_token: string
51
+ token_type: string
52
+ refresh_token: string
53
+ expires_time: number
54
+ refresh_time: number
55
+ code?: string
57
56
  }
58
57
 
59
- // 菜单项
58
+ // 菜单项(参考 tineco-ui)
60
59
  export interface MenuItem {
61
- id: number | string
62
- name: string
63
- path: string
64
- component?: string
65
- redirect?: string
60
+ menuCode: string
61
+ menuName: string
62
+ menuUrl: string
66
63
  icon?: string
67
- title: string
68
- hidden?: boolean
69
- keepAlive?: boolean
70
- affix?: boolean
71
- order?: number
72
- parentId?: number | string | null
64
+ closable?: boolean
65
+ isDefault?: boolean
66
+ isOut?: boolean
67
+ type?: number // 1 为按钮,其他为菜单
73
68
  children?: MenuItem[]
74
69
  }
75
70
 
package/src/utils/auth.ts CHANGED
@@ -5,13 +5,19 @@
5
5
  import { local } from './storage'
6
6
 
7
7
  const TOKEN_KEY = 'token'
8
+ const TOKEN_TYPE_KEY = 'token_type'
8
9
  const REFRESH_TOKEN_KEY = 'refresh_token'
9
- const TOKEN_EXPIRE_KEY = 'token_expire'
10
+ const TOKEN_EXPIRE_KEY = 'expires_time'
11
+ const REFRESH_TIME_KEY = 'refresh_time'
12
+ const CODE_KEY = 'code'
10
13
 
11
14
  export interface TokenInfo {
12
- token: string
13
- refreshToken: string
14
- expireTime: number
15
+ access_token: string
16
+ token_type: string
17
+ refresh_token: string
18
+ expires_time: number
19
+ refresh_time: number
20
+ code?: string
15
21
  }
16
22
 
17
23
  export const getToken = (): string | null => {
@@ -22,6 +28,14 @@ export const setToken = (token: string): void => {
22
28
  local.set(TOKEN_KEY, token)
23
29
  }
24
30
 
31
+ export const getTokenType = (): string | null => {
32
+ return local.get<string>(TOKEN_TYPE_KEY)
33
+ }
34
+
35
+ export const setTokenType = (tokenType: string): void => {
36
+ local.set(TOKEN_TYPE_KEY, tokenType)
37
+ }
38
+
25
39
  export const getRefreshToken = (): string | null => {
26
40
  return local.get<string>(REFRESH_TOKEN_KEY)
27
41
  }
@@ -38,16 +52,40 @@ export const setTokenExpire = (expireTime: number): void => {
38
52
  local.set(TOKEN_EXPIRE_KEY, expireTime)
39
53
  }
40
54
 
55
+ export const getRefreshTime = (): number | null => {
56
+ return local.get<number>(REFRESH_TIME_KEY)
57
+ }
58
+
59
+ export const setRefreshTime = (refreshTime: number): void => {
60
+ local.set(REFRESH_TIME_KEY, refreshTime)
61
+ }
62
+
63
+ export const getCode = (): string | null => {
64
+ return local.get<string>(CODE_KEY)
65
+ }
66
+
67
+ export const setCode = (code: string): void => {
68
+ local.set(CODE_KEY, code)
69
+ }
70
+
41
71
  export const setTokenInfo = (info: TokenInfo): void => {
42
- setToken(info.token)
43
- setRefreshToken(info.refreshToken)
44
- setTokenExpire(info.expireTime)
72
+ setToken(info.access_token)
73
+ setTokenType(info.token_type || 'Bearer')
74
+ setRefreshToken(info.refresh_token)
75
+ setTokenExpire(info.expires_time)
76
+ setRefreshTime(info.refresh_time)
77
+ if (info.code) {
78
+ setCode(info.code)
79
+ }
45
80
  }
46
81
 
47
82
  export const clearToken = (): void => {
48
83
  local.remove(TOKEN_KEY)
84
+ local.remove(TOKEN_TYPE_KEY)
49
85
  local.remove(REFRESH_TOKEN_KEY)
50
86
  local.remove(TOKEN_EXPIRE_KEY)
87
+ local.remove(REFRESH_TIME_KEY)
88
+ local.remove(CODE_KEY)
51
89
  }
52
90
 
53
91
  export const isTokenExpired = (): boolean => {
@@ -1,42 +1,33 @@
1
1
  /**
2
2
  * 权限工具函数
3
+ * 注意:tineco-ui 不支持 permissions 和 roles 字段,这些函数暂时返回登录状态
3
4
  */
4
5
 
5
6
  import { useUserStore } from '@/stores/user'
6
7
 
7
8
  /**
8
9
  * 检查是否有权限
9
- * @param permission 权限标识
10
+ * @param _permission 权限标识(暂不使用)
10
11
  */
11
- export function hasPermission(permission: string | string[]): boolean {
12
+ export function hasPermission(_permission: string | string[]): boolean {
12
13
  const userStore = useUserStore()
13
- const permissions = userStore.permissions
14
-
15
- if (Array.isArray(permission)) {
16
- return permission.some(p => permissions.includes(p))
17
- }
18
-
19
- return permissions.includes(permission)
14
+ // tineco-ui 不支持 permissions 字段,暂时返回已登录状态
15
+ return userStore.isLoggedIn
20
16
  }
21
17
 
22
18
  /**
23
19
  * 检查是否有角色
24
- * @param role 角色标识
20
+ * @param _role 角色标识(暂不使用)
25
21
  */
26
- export function hasRole(role: string | string[]): boolean {
22
+ export function hasRole(_role: string | string[]): boolean {
27
23
  const userStore = useUserStore()
28
- const roles = userStore.roles
29
-
30
- if (Array.isArray(role)) {
31
- return role.some(r => roles.includes(r))
32
- }
33
-
34
- return roles.includes(role)
24
+ // tineco-ui 不支持 roles 字段,暂时返回已登录状态
25
+ return userStore.isLoggedIn
35
26
  }
36
27
 
37
28
  /**
38
29
  * 检查是否是管理员
39
30
  */
40
31
  export function isAdmin(): boolean {
41
- return hasRole('admin')
32
+ return true // tineco-ui 不支持 roles 字段,暂时返回 true
42
33
  }
@@ -3,7 +3,7 @@
3
3
  */
4
4
 
5
5
  import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig } from 'axios'
6
- import { getToken, clearToken } from './auth'
6
+ import { getToken, getTokenType, clearToken } from './auth'
7
7
  import { Message } from '@xto/feedback'
8
8
 
9
9
  // 响应数据接口(Euler 框架 Result 格式)
@@ -39,8 +39,9 @@ const createRequest = (): AxiosInstance => {
39
39
  instance.interceptors.request.use(
40
40
  (config: InternalAxiosRequestConfig) => {
41
41
  const token = getToken()
42
+ const tokenType = getTokenType() || 'Bearer'
42
43
  if (token) {
43
- config.headers.Authorization = `Bearer ${token}`
44
+ config.headers.Authorization = `${tokenType} ${token}`
44
45
  }
45
46
  return config
46
47
  },
@@ -54,7 +55,7 @@ const createRequest = (): AxiosInstance => {
54
55
  (response: AxiosResponse<ApiResponse>) => {
55
56
  const { data } = response
56
57
 
57
- // Euler 框架返回 Result 包装格式,成功时直接返回 data 中的实际数据
58
+ // Euler 框架返回 Result 包装格式,成功时直接返回 data.data 中的实际数据
58
59
  if (data.code === 200 || data.code === 0) {
59
60
  return data.data as any
60
61
  }