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
@@ -0,0 +1,170 @@
1
+ /**
2
+ * 应用核心 Hook
3
+ * 封装登录、退出、初始化等核心逻辑
4
+ */
5
+
6
+ import { computed, ref } from 'vue'
7
+ import { useRouter, useRoute } from 'vue-router'
8
+ import { Message } from '@xto/feedback'
9
+ import { useAuthStore } from '@/stores/auth'
10
+ import { useUserStore } from '@/stores/user'
11
+ import { useMenuStore } from '@/stores/menu'
12
+ import { login as loginApi, logout as logoutApi } from '@/api/auth'
13
+ import { getCurrentUser } from '@/api/user'
14
+ import { getUserMenu } from '@/api/menu'
15
+ import type { UserInfo, RemoteMenuItem, LoginResult } from '@/types/api'
16
+
17
+ export function useApp() {
18
+ const router = useRouter()
19
+ const route = useRoute()
20
+ const authStore = useAuthStore()
21
+ const userStore = useUserStore()
22
+ const menuStore = useMenuStore()
23
+
24
+ const loading = ref(false)
25
+
26
+ // ========== 计算属性 ==========
27
+
28
+ // 是否已登录
29
+ const isLoggedIn = computed(() => authStore.isLoggedIn)
30
+
31
+ // 用户信息
32
+ const userInfo = computed(() => userStore.userInfo)
33
+
34
+ // 用户名
35
+ const userName = computed(() => userStore.userName || '')
36
+
37
+ // 菜单列表
38
+ const menuList = computed(() => menuStore.menuList)
39
+
40
+ // 首页路径
41
+ const indexPath = computed(() => menuStore.index)
42
+
43
+ // ========== 登录 ==========
44
+
45
+ const login = async (uid: string, password: string) => {
46
+ loading.value = true
47
+ try {
48
+ // 1. 清除旧状态
49
+ clearAllState()
50
+
51
+ // 2. 调用登录接口
52
+ const res = await loginApi({ uid, password })
53
+ const loginData = res.data as LoginResult
54
+
55
+ // 3. 保存登录信息
56
+ authStore.login(loginData as unknown as Record<string, unknown>)
57
+
58
+ // 4. 加载用户信息
59
+ await loadUserInfo()
60
+
61
+ // 5. 加载菜单
62
+ await loadMenu()
63
+
64
+ // 6. 处理跳转
65
+ const redirectUrl = route.query['redirectUrl'] as string
66
+ if (redirectUrl) {
67
+ const url = decodeURIComponent(redirectUrl)
68
+ if (url.startsWith('http')) {
69
+ // 外网地址,带上 code 跳转
70
+ const code = loginData.code
71
+ location.href = url.includes('?') ? `${url}&code=${code}` : `${url}?code=${code}`
72
+ } else {
73
+ await router.replace(url)
74
+ }
75
+ } else {
76
+ await router.replace(indexPath.value || '/')
77
+ }
78
+
79
+ Message.success('登录成功')
80
+ return { success: true, data: loginData }
81
+ } catch (error: any) {
82
+ Message.error(error?.message || '登录失败')
83
+ return { success: false, error }
84
+ } finally {
85
+ loading.value = false
86
+ }
87
+ }
88
+
89
+ // ========== 退出登录 ==========
90
+
91
+ const logout = async (showMessage = true) => {
92
+ try {
93
+ await logoutApi()
94
+ } catch (error) {
95
+ console.error('退出登录接口失败', error)
96
+ } finally {
97
+ clearAllState()
98
+ router.push('/login')
99
+ if (showMessage) {
100
+ Message.success('退出登录成功')
101
+ }
102
+ }
103
+ }
104
+
105
+ // ========== 加载用户信息 ==========
106
+
107
+ const loadUserInfo = async () => {
108
+ const res = await getCurrentUser()
109
+ userStore.setUserInfo(res.data as UserInfo)
110
+ return res.data
111
+ }
112
+
113
+ // ========== 加载菜单 ==========
114
+
115
+ const loadMenu = async () => {
116
+ const res = await getUserMenu()
117
+ menuStore.setMenuFromRemote(res.data as RemoteMenuItem[])
118
+ return menuStore.menuList
119
+ }
120
+
121
+ // ========== 初始化应用(路由守卫中使用) ==========
122
+
123
+ const initApp = async () => {
124
+ if (!authStore.isLoggedIn) {
125
+ return false
126
+ }
127
+
128
+ try {
129
+ // 如果有 token 但没有用户信息,则加载
130
+ if (!userStore.userInfo) {
131
+ await loadUserInfo()
132
+ }
133
+ // 如果没有菜单,则加载
134
+ if (!menuStore.hasMenu) {
135
+ await loadMenu()
136
+ }
137
+ return true
138
+ } catch (error) {
139
+ console.error('初始化应用失败', error)
140
+ clearAllState()
141
+ return false
142
+ }
143
+ }
144
+
145
+ // ========== 清除所有状态 ==========
146
+
147
+ const clearAllState = () => {
148
+ authStore.logout()
149
+ userStore.clearUserInfo()
150
+ menuStore.clearMenu()
151
+ }
152
+
153
+ return {
154
+ // 状态
155
+ loading,
156
+ isLoggedIn,
157
+ userInfo,
158
+ userName,
159
+ menuList,
160
+ indexPath,
161
+
162
+ // 方法
163
+ login,
164
+ logout,
165
+ loadUserInfo,
166
+ loadMenu,
167
+ initApp,
168
+ clearAllState
169
+ }
170
+ }
@@ -1,45 +1,70 @@
1
- /**
2
- * 权限组合函数
3
- */
4
-
5
- import { computed } from 'vue'
6
- import { useUserStore } from '@/stores/user'
7
-
8
- export function useAuth() {
9
- const userStore = useUserStore()
10
-
11
- // 检查权限
12
- const hasPermission = (permission: string | string[]): boolean => {
13
- const permissions = userStore.permissions
14
- if (permissions.includes('*')) return true
15
-
16
- if (Array.isArray(permission)) {
17
- return permission.some(p => permissions.includes(p))
18
- }
19
- return permissions.includes(permission)
20
- }
21
-
22
- // 检查角色
23
- const hasRole = (role: string | string[]): boolean => {
24
- const roles = userStore.roles
25
- if (roles.includes('admin')) return true
26
-
27
- if (Array.isArray(role)) {
28
- return role.some(r => roles.includes(r))
29
- }
30
- return roles.includes(role)
31
- }
32
-
33
- // 是否是管理员
34
- const isAdmin = computed(() => userStore.roles.includes('admin'))
35
-
36
- // 是否已登录
37
- const isLoggedIn = computed(() => userStore.isLoggedIn)
38
-
39
- return {
40
- hasPermission,
41
- hasRole,
42
- isAdmin,
43
- isLoggedIn
44
- }
1
+ /**
2
+ * 权限 Hook
3
+ * 封装权限检查、按钮权限等逻辑
4
+ */
5
+
6
+ import { computed } from 'vue'
7
+ import { useMenuStore } from '@/stores/menu'
8
+ import { useAuthStore } from '@/stores/auth'
9
+ import { useUserStore } from '@/stores/user'
10
+
11
+ export function useAuth() {
12
+ const menuStore = useMenuStore()
13
+ const authStore = useAuthStore()
14
+ const userStore = useUserStore()
15
+
16
+ // 是否已登录
17
+ const isLoggedIn = computed(() => authStore.isLoggedIn)
18
+
19
+ // 用户名
20
+ const userName = computed(() => userStore.userName)
21
+
22
+ // 用户信息
23
+ const userInfo = computed(() => userStore.userInfo)
24
+
25
+ // ========== 检查按钮权限 ==========
26
+
27
+ const hasPermission = (permission: string | string[]): boolean => {
28
+ const currentPath = window.location.pathname
29
+ const btnList = menuStore.menuBtnListMap[currentPath] || []
30
+ const btnCodes = btnList.map(item => item.code)
31
+
32
+ if (Array.isArray(permission)) {
33
+ return permission.some(p => btnCodes.includes(p))
34
+ }
35
+ return btnCodes.includes(permission)
36
+ }
37
+
38
+ // ========== 检查是否有任一权限 ==========
39
+
40
+ const hasAnyPermission = (permissions: string[]): boolean => {
41
+ return permissions.some(p => hasPermission(p))
42
+ }
43
+
44
+ // ========== 检查是否有全部权限 ==========
45
+
46
+ const hasAllPermissions = (permissions: string[]): boolean => {
47
+ return permissions.every(p => hasPermission(p))
48
+ }
49
+
50
+ // ========== 获取当前页面按钮权限列表 ==========
51
+
52
+ const getCurrentPagePermissions = (): string[] => {
53
+ const currentPath = window.location.pathname
54
+ const btnList = menuStore.menuBtnListMap[currentPath] || []
55
+ return btnList.map(item => item.code)
56
+ }
57
+
58
+ return {
59
+ // 状态
60
+ isLoggedIn,
61
+ userName,
62
+ userInfo,
63
+
64
+ // 方法
65
+ hasPermission,
66
+ hasAnyPermission,
67
+ hasAllPermissions,
68
+ getCurrentPagePermissions
69
+ }
45
70
  }
@@ -0,0 +1,141 @@
1
+ /**
2
+ * 菜单 Hook
3
+ * 封装菜单渲染、搜索、图标映射等逻辑
4
+ */
5
+
6
+ import { ref, computed } from 'vue'
7
+ import { useRoute, useRouter } from 'vue-router'
8
+ import { useMenuStore } from '@/stores/menu'
9
+ import { useAppStore } from '@/stores/app'
10
+ import type { MenuItem } from '@/types/api'
11
+
12
+ // 默认图标映射
13
+ const defaultIconMap: Record<string, string> = {
14
+ home: '🏠',
15
+ dashboard: '📊',
16
+ system: '⚙️',
17
+ user: '👤',
18
+ role: '👥',
19
+ menu: '📋',
20
+ setting: '🔧'
21
+ }
22
+
23
+ export function useMenu(iconMap?: Record<string, string>) {
24
+ const route = useRoute()
25
+ const router = useRouter()
26
+ const menuStore = useMenuStore()
27
+ const appStore = useAppStore()
28
+
29
+ // 合并图标映射
30
+ const mergedIconMap = { ...defaultIconMap, ...iconMap }
31
+
32
+ // 搜索关键词
33
+ const searchKeyword = ref('')
34
+
35
+ // 当前激活菜单
36
+ const activeMenu = computed(() => route.path)
37
+
38
+ // 侧边栏是否折叠
39
+ const isCollapsed = computed(() => appStore.isCollapsed)
40
+
41
+ // 菜单列表
42
+ const menuList = computed(() => menuStore.menuList)
43
+
44
+ // 是否有菜单
45
+ const hasMenu = computed(() => menuStore.hasMenu)
46
+
47
+ // ========== 扁平化菜单(用于搜索) ==========
48
+
49
+ const flattenMenus = (
50
+ menus: MenuItem[],
51
+ parentTitle = ''
52
+ ): (MenuItem & { parentTitle: string })[] => {
53
+ const result: (MenuItem & { parentTitle: string })[] = []
54
+ menus.forEach(menu => {
55
+ if (menu.children && menu.children.length > 0) {
56
+ result.push(...flattenMenus(menu.children, menu.title))
57
+ } else {
58
+ result.push({ ...menu, parentTitle })
59
+ }
60
+ })
61
+ return result
62
+ }
63
+
64
+ // ========== 搜索结果 ==========
65
+
66
+ const searchResults = computed(() => {
67
+ if (!searchKeyword.value.trim()) return []
68
+ const flatMenus = flattenMenus(menuList.value)
69
+ return flatMenus.filter(menu =>
70
+ menu.title.toLowerCase().includes(searchKeyword.value.toLowerCase())
71
+ )
72
+ })
73
+
74
+ // ========== 过滤后的菜单列表 ==========
75
+
76
+ const filteredMenuList = computed(() => {
77
+ if (!searchKeyword.value.trim()) return menuList.value
78
+
79
+ return menuList.value.map(menu => {
80
+ if (menu.children && menu.children.length > 0) {
81
+ const filteredChildren = menu.children.filter(child =>
82
+ child.title.toLowerCase().includes(searchKeyword.value.toLowerCase())
83
+ )
84
+ if (filteredChildren.length > 0) {
85
+ return { ...menu, children: filteredChildren }
86
+ }
87
+ return null
88
+ }
89
+ if (menu.title.toLowerCase().includes(searchKeyword.value.toLowerCase())) {
90
+ return menu
91
+ }
92
+ return null
93
+ }).filter(Boolean) as MenuItem[]
94
+ })
95
+
96
+ // ========== 获取菜单图标 ==========
97
+
98
+ const getMenuIcon = (icon?: string): string => {
99
+ return mergedIconMap[icon || ''] || '📄'
100
+ }
101
+
102
+ // ========== 菜单选择 ==========
103
+
104
+ const handleMenuSelect = (index: string) => {
105
+ if (index && index !== route.path) {
106
+ router.push(index)
107
+ searchKeyword.value = ''
108
+ }
109
+ }
110
+
111
+ // ========== 搜索结果点击 ==========
112
+
113
+ const handleSearchItemClick = (path: string) => {
114
+ router.push(path)
115
+ searchKeyword.value = ''
116
+ }
117
+
118
+ // ========== 清除搜索 ==========
119
+
120
+ const clearSearch = () => {
121
+ searchKeyword.value = ''
122
+ }
123
+
124
+ return {
125
+ // 状态
126
+ searchKeyword,
127
+ activeMenu,
128
+ isCollapsed,
129
+ menuList,
130
+ hasMenu,
131
+ searchResults,
132
+ filteredMenuList,
133
+
134
+ // 方法
135
+ flattenMenus,
136
+ getMenuIcon,
137
+ handleMenuSelect,
138
+ handleSearchItemClick,
139
+ clearSearch
140
+ }
141
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * 应用配置
3
+ */
4
+
5
+ export interface AppConfig {
6
+ webTitle: string
7
+ baseUrl: string
8
+ appId: string
9
+ clientId: string
10
+ }
11
+
12
+ const config: AppConfig = {
13
+ webTitle: import.meta.env.VITE_APP_NAME || 'analysis-web',
14
+ baseUrl: 'https://cloud-api-test.tineco.com',
15
+ appId: import.meta.env.VITE_APP_ID || 'DBU-CONTENT-ANALYSIS',
16
+ clientId: import.meta.env.VITE_APP_CLIENT_ID || 'Tineco'
17
+ }
18
+
19
+ export default config
@@ -1,38 +1,41 @@
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
-
9
- const permission: Directive = {
10
- mounted(el: HTMLElement, binding: DirectiveBinding<string | string[]>) {
11
- const userStore = useUserStore()
12
- const { value } = binding
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) {
28
- el.parentNode?.removeChild(el)
29
- }
30
- }
31
- }
32
-
33
- export default permission
34
-
35
- // 注册指令
36
- export function setupPermissionDirective(app: any) {
37
- 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 { useMenuStore } from '@/stores/menu'
8
+
9
+ const permission: Directive = {
10
+ mounted(el: HTMLElement, binding: DirectiveBinding<string | string[]>) {
11
+ const menuStore = useMenuStore()
12
+ const { value } = binding
13
+
14
+ if (!value) return
15
+
16
+ // 获取当前页面的按钮权限
17
+ const currentPath = window.location.pathname
18
+ const btnList = menuStore.menuBtnListMap[currentPath] || []
19
+ const btnCodes = btnList.map(item => item.code)
20
+
21
+ // 判断是否有权限
22
+ let hasPermission = false
23
+ if (Array.isArray(value)) {
24
+ hasPermission = value.some(p => btnCodes.includes(p))
25
+ } else {
26
+ hasPermission = btnCodes.includes(value)
27
+ }
28
+
29
+ // 没有权限则移除元素
30
+ if (!hasPermission) {
31
+ el.parentNode?.removeChild(el)
32
+ }
33
+ }
34
+ }
35
+
36
+ export default permission
37
+
38
+ // 注册指令
39
+ export function setupPermissionDirective(app: any) {
40
+ app.directive('permission', permission)
38
41
  }
package/src/index.ts CHANGED
@@ -6,9 +6,11 @@ export { default as Tabs } from './components/Layout/Tabs.vue'
6
6
  export { default as Footer } from './components/Layout/Footer.vue'
7
7
 
8
8
  // 组合式函数
9
- export * from './composables/useAuth'
10
- export * from './composables/useForm'
11
- export * from './composables/useTable'
9
+ export { useApp } from './composables/useApp'
10
+ export { useAuth } from './composables/useAuth'
11
+ export { useMenu } from './composables/useMenu'
12
+ export { useForm } from './composables/useForm'
13
+ export { useTable } from './composables/useTable'
12
14
 
13
15
  // 工具函数
14
16
  export * from './utils/auth'
@@ -29,9 +31,12 @@ export * from './router/dynamicRoutes'
29
31
 
30
32
  // API
31
33
  export * from './api/auth'
32
- export * from './api/system'
34
+ export * from './api/menu'
33
35
  export * from './api/user'
34
36
 
37
+ // 配置
38
+ export { default as appConfig } from './config'
39
+
35
40
  // 枚举
36
41
  export * from './enums'
37
42