xto-fronted 0.4.89 → 0.4.91

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 (116) hide show
  1. package/dist/assets/404-Bq0LY5Cd.js +1 -0
  2. package/dist/assets/404-Cw_4ZCL6.css +1 -0
  3. package/dist/assets/{index-BDgOY6Rp.js → index-7ZZxpSfk.js} +1 -1
  4. package/dist/assets/index-BJUe8VUp.js +1 -0
  5. package/dist/assets/{index-Bz0BgZQ1.js → index-BlOR_ICg.js} +1 -1
  6. package/dist/assets/index-BlRslYYI.css +1 -0
  7. package/dist/assets/index-BudArKxR.css +1 -0
  8. package/dist/assets/{index-CwRA10ac.js → index-BzbOWBCV.js} +1 -1
  9. package/dist/assets/index-CFhWBbxk.css +1 -0
  10. package/dist/assets/{index-CfpZmcpk.css → index-CH6aTfYg.css} +1 -1
  11. package/dist/assets/{index-BIoRANs0.js → index-CT5f37nN.js} +1 -1
  12. package/dist/assets/index-Ce-kjtEM.js +2 -0
  13. package/dist/assets/{index-t-2Y0KhA.css → index-Cpew6d-v.css} +1 -1
  14. package/dist/assets/index-DkkuYBgT.css +1 -0
  15. package/dist/assets/index-vfvEFrCH.css +1 -0
  16. package/dist/assets/{index-CwJSA85U.js → index-wVLLAoVp.js} +1 -1
  17. package/dist/assets/vendor-DZmPBJ9d.js +16 -0
  18. package/dist/assets/vue-vendor-DjmFuEnG.js +29 -0
  19. package/dist/assets/{xto-base-PwLGsxxb.js → xto-base-B5HYOo6i.js} +1 -1
  20. package/dist/assets/{xto-core-CtL4zKiV.js → xto-core-DZYp_YAR.js} +1 -1
  21. package/dist/assets/{xto-data-bCXQa7fT.js → xto-data-ogck6x_i.js} +1 -1
  22. package/dist/assets/{xto-feedback-CPydp0kn.js → xto-feedback-C0-6cAL6.js} +1 -1
  23. package/dist/assets/{xto-form-bywohdAf.js → xto-form-IDg_78Vf.js} +1 -1
  24. package/dist/assets/{xto-navigation-Bbdpine9.js → xto-navigation-CPYLzfu7.js} +1 -1
  25. package/dist/index.html +9 -9
  26. package/package.json +91 -91
  27. package/src/App.vue +48 -48
  28. package/src/assets/styles/_dark.scss +639 -572
  29. package/src/assets/styles/_root.scss +183 -183
  30. package/src/assets/styles/_variables.scss +69 -69
  31. package/src/assets/styles/index.scss +460 -460
  32. package/src/components/Layout/Sidebar.vue +198 -198
  33. package/src/components/Layout/TopMenu.vue +1170 -1170
  34. package/src/components/Layout/index.vue +192 -192
  35. package/src/directives/permission.ts +12 -3
  36. package/src/index.ts +100 -100
  37. package/src/router/layoutRoute.ts +59 -59
  38. package/src/stores/menu.ts +64 -3
  39. package/src/types/json-bigint.d.ts +18 -18
  40. package/src/utils/permission.ts +12 -5
  41. package/src/utils/request.ts +184 -164
  42. package/src/views/dashboard/index.vue +545 -545
  43. package/src/views/error/403.vue +251 -251
  44. package/src/views/error/404.vue +253 -253
  45. package/src/views/login/index.vue +586 -586
  46. package/src/views/system/menu/index.vue +690 -690
  47. package/src/views/system/role/index.vue +583 -583
  48. package/src/views/system/user/index.vue +655 -655
  49. package/dist/App.vue.d.ts +0 -2
  50. package/dist/api/auth.d.ts +0 -8
  51. package/dist/api/system.d.ts +0 -16
  52. package/dist/api/user.d.ts +0 -13
  53. package/dist/assets/404-C9Uh6Uu-.css +0 -1
  54. package/dist/assets/404-zjGLLssH.js +0 -1
  55. package/dist/assets/index-B5xc4gQB.css +0 -1
  56. package/dist/assets/index-CAdztNsv.css +0 -1
  57. package/dist/assets/index-CCXrcISf.css +0 -1
  58. package/dist/assets/index-D8NDxq9d.js +0 -1
  59. package/dist/assets/index-DEB6-Iv_.js +0 -2
  60. package/dist/assets/index-DM4Ezclc.css +0 -1
  61. package/dist/assets/index-DYv7nImj.css +0 -1
  62. package/dist/assets/vendor-CUVPinTg.js +0 -13
  63. package/dist/assets/vue-vendor-DeJXJVbN.js +0 -29
  64. package/dist/components/Layout/Footer.vue.d.ts +0 -2
  65. package/dist/components/Layout/Header.vue.d.ts +0 -5
  66. package/dist/components/Layout/MixTopMenu.vue.d.ts +0 -5
  67. package/dist/components/Layout/Sidebar.vue.d.ts +0 -11
  68. package/dist/components/Layout/SidebarMenuItem.vue.d.ts +0 -5
  69. package/dist/components/Layout/Tabs.vue.d.ts +0 -2
  70. package/dist/components/Layout/TopMenu.vue.d.ts +0 -5
  71. package/dist/components/Layout/index.vue.d.ts +0 -2
  72. package/dist/composables/useApp.d.ts +0 -29
  73. package/dist/composables/useAuth.d.ts +0 -6
  74. package/dist/composables/useForm.d.ts +0 -20
  75. package/dist/composables/useI18n.d.ts +0 -30
  76. package/dist/composables/useTable.d.ts +0 -29
  77. package/dist/directives/permission.d.ts +0 -4
  78. package/dist/enums/index.d.ts +0 -32
  79. package/dist/index-BRvi9qW-.js +0 -515
  80. package/dist/index-BVGW4DDQ.js +0 -189
  81. package/dist/index-Bmf0YbVq.js +0 -189
  82. package/dist/index-C2-a5KSQ.js +0 -4233
  83. package/dist/index-CeZ0CSSs.js +0 -641
  84. package/dist/index-D25KzR0I.js +0 -479
  85. package/dist/index-DEYOivza.js +0 -641
  86. package/dist/index-DReodgBw.js +0 -4233
  87. package/dist/index-DjERNRXX.js +0 -515
  88. package/dist/index-gBlRG4kk.js +0 -479
  89. package/dist/index.d.ts +0 -59
  90. package/dist/index.es.js +0 -94
  91. package/dist/index.umd.js +0 -8
  92. package/dist/main.d.ts +0 -0
  93. package/dist/router/dynamicRoutes.d.ts +0 -30
  94. package/dist/router/guards.d.ts +0 -17
  95. package/dist/router/index.d.ts +0 -6
  96. package/dist/router/layoutRoute.d.ts +0 -22
  97. package/dist/router/staticRoutes.d.ts +0 -2
  98. package/dist/stores/app.d.ts +0 -93
  99. package/dist/stores/auth.d.ts +0 -41
  100. package/dist/stores/index.d.ts +0 -10
  101. package/dist/stores/locale.d.ts +0 -42
  102. package/dist/stores/menu.d.ts +0 -77
  103. package/dist/stores/user.d.ts +0 -92
  104. package/dist/style.css +0 -1
  105. package/dist/utils/auth.d.ts +0 -27
  106. package/dist/utils/config.d.ts +0 -30
  107. package/dist/utils/permission.d.ts +0 -18
  108. package/dist/utils/request.d.ts +0 -24
  109. package/dist/utils/storage.d.ts +0 -24
  110. package/dist/views/dashboard/index.vue.d.ts +0 -2
  111. package/dist/views/error/403.vue.d.ts +0 -2
  112. package/dist/views/error/404.vue.d.ts +0 -2
  113. package/dist/views/login/index.vue.d.ts +0 -4
  114. package/dist/views/system/menu/index.vue.d.ts +0 -4
  115. package/dist/views/system/role/index.vue.d.ts +0 -4
  116. package/dist/views/system/user/index.vue.d.ts +0 -4
@@ -1,60 +1,60 @@
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
- interface CreateRouterOptions {
14
- base?: string
15
- }
16
-
17
- /**
18
- * 创建布局路由
19
- * @param children 子路由配置
20
- * @param options 配置选项
21
- * @returns 布局路由配置
22
- */
23
- export function createLayoutRoute(
24
- children: RouteRecordRaw[],
25
- options: LayoutRouteOptions = {}
26
- ): RouteRecordRaw {
27
- const indexPath = options.indexPath || '/dashboard'
28
-
29
- // 添加 catch-all 路由,让404在Layout内部渲染,保持左侧菜单显示
30
- const catchAllRoute: RouteRecordRaw = {
31
- path: '/:pathMatch(.*)*',
32
- name: 'CatchAll',
33
- component: () => import('@/views/error/404.vue'),
34
- meta: {
35
- title: '404'
36
- }
37
- }
38
-
39
- return {
40
- path: '/',
41
- name: 'Layout',
42
- component: Layout,
43
- redirect: indexPath,
44
- children: [...children, catchAllRoute]
45
- }
46
- }
47
-
48
- /**
49
- * 创建路由实例
50
- * @param routes 路由配置数组
51
- * @param options 配置选项
52
- * @returns 路由实例
53
- */
54
- export function createRouter(routes: RouteRecordRaw[], options: CreateRouterOptions = {}): Router {
55
- return vueCreateRouter({
56
- history: createWebHistory(options.base),
57
- routes,
58
- scrollBehavior: () => ({ left: 0, top: 0 })
59
- })
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
+ interface CreateRouterOptions {
14
+ base?: string
15
+ }
16
+
17
+ /**
18
+ * 创建布局路由
19
+ * @param children 子路由配置
20
+ * @param options 配置选项
21
+ * @returns 布局路由配置
22
+ */
23
+ export function createLayoutRoute(
24
+ children: RouteRecordRaw[],
25
+ options: LayoutRouteOptions = {}
26
+ ): RouteRecordRaw {
27
+ const indexPath = options.indexPath || '/dashboard'
28
+
29
+ // 添加 catch-all 路由,让404在Layout内部渲染,保持左侧菜单显示
30
+ const catchAllRoute: RouteRecordRaw = {
31
+ path: '/:pathMatch(.*)*',
32
+ name: 'CatchAll',
33
+ component: () => import('@/views/error/404.vue'),
34
+ meta: {
35
+ title: '404'
36
+ }
37
+ }
38
+
39
+ return {
40
+ path: '/',
41
+ name: 'Layout',
42
+ component: Layout,
43
+ redirect: indexPath,
44
+ children: [...children, catchAllRoute]
45
+ }
46
+ }
47
+
48
+ /**
49
+ * 创建路由实例
50
+ * @param routes 路由配置数组
51
+ * @param options 配置选项
52
+ * @returns 路由实例
53
+ */
54
+ export function createRouter(routes: RouteRecordRaw[], options: CreateRouterOptions = {}): Router {
55
+ return vueCreateRouter({
56
+ history: createWebHistory(options.base),
57
+ routes,
58
+ scrollBehavior: () => ({ left: 0, top: 0 })
59
+ })
60
60
  }
@@ -8,6 +8,7 @@ import type { MenuItem } from '@/types/api'
8
8
  import { local } from '@/utils/storage'
9
9
 
10
10
  const MENU_LIST_KEY = 'menu_list'
11
+ const PERMISSION_LIST_KEY = 'permission_list'
11
12
 
12
13
  // 首页菜单(参考 tineco-ui)
13
14
  const indexMenu: MenuItem = {
@@ -20,30 +21,90 @@ const indexMenu: MenuItem = {
20
21
  isOut: false
21
22
  }
22
23
 
24
+ /**
25
+ * 过滤菜单项,只保留菜单(type !== 1),过滤掉功能按钮(type === 1)
26
+ * @param menus 原始菜单列表
27
+ * @returns 过滤后的菜单列表
28
+ */
29
+ function filterMenus(menus: MenuItem[]): MenuItem[] {
30
+ return menus
31
+ .filter(menu => menu.type !== 1) // 过滤掉功能按钮
32
+ .map(menu => ({
33
+ ...menu,
34
+ children: menu.children ? filterMenus(menu.children) : undefined
35
+ }))
36
+ }
37
+
38
+ /**
39
+ * 提取所有功能权限(type === 1 的项)
40
+ * @param menus 原始菜单列表
41
+ * @returns 权限标识列表
42
+ */
43
+ function extractPermissions(menus: MenuItem[]): string[] {
44
+ const permissions: string[] = []
45
+
46
+ function traverse(items: MenuItem[]) {
47
+ items.forEach(item => {
48
+ // type === 1 表示功能按钮
49
+ if (item.type === 1 && item.menuCode) {
50
+ permissions.push(item.menuCode)
51
+ }
52
+ // 递归处理子项
53
+ if (item.children) {
54
+ traverse(item.children)
55
+ }
56
+ })
57
+ }
58
+
59
+ traverse(menus)
60
+ return permissions
61
+ }
62
+
23
63
  export const useMenuStore = defineStore('menu', () => {
24
64
  // 状态
25
65
  const menuList = ref<MenuItem[]>(local.get<MenuItem[]>(MENU_LIST_KEY) || [])
66
+ // 功能权限列表
67
+ const permissions = ref<string[]>(local.get<string[]>(PERMISSION_LIST_KEY) || [])
26
68
 
27
69
  // 计算属性
28
70
  const hasMenu = computed(() => menuList.value.length > 0)
29
71
 
30
- // 设置菜单
72
+ // 设置菜单(会自动过滤功能项,并提取权限)
31
73
  const setMenuList = (menus: MenuItem[]) => {
74
+ // 提取功能权限
75
+ permissions.value = extractPermissions(menus)
76
+ local.set(PERMISSION_LIST_KEY, permissions.value)
77
+
78
+ // 过滤菜单,只保留菜单项(type !== 1)
79
+ const filteredMenus = filterMenus(menus)
80
+
32
81
  // 添加首页菜单到开头
33
- menuList.value = [indexMenu, ...menus]
82
+ menuList.value = [indexMenu, ...filteredMenus]
34
83
  local.set(MENU_LIST_KEY, menuList.value)
35
84
  }
36
85
 
37
86
  // 清除菜单
38
87
  const clearMenu = () => {
39
88
  menuList.value = []
89
+ permissions.value = []
40
90
  local.remove(MENU_LIST_KEY)
91
+ local.remove(PERMISSION_LIST_KEY)
92
+ }
93
+
94
+ // 检查是否有某个权限
95
+ const hasPermission = (permission: string | string[]): boolean => {
96
+ if (Array.isArray(permission)) {
97
+ return permission.some(p => permissions.value.includes(p))
98
+ }
99
+ return permissions.value.includes(permission)
41
100
  }
42
101
 
43
102
  return {
44
103
  menuList,
104
+ permissions,
45
105
  hasMenu,
46
106
  setMenuList,
47
- clearMenu
107
+ clearMenu,
108
+ hasPermission
48
109
  }
49
110
  })
@@ -1,18 +1,18 @@
1
- declare module 'json-bigint' {
2
- interface JSONBigintOptions {
3
- storeAsString?: boolean
4
- alwaysParseAsBig?: boolean
5
- useNativeBigInt?: boolean
6
- protoAction?: 'error' | 'ignore' | 'preserve'
7
- constructorAction?: 'error' | 'ignore' | 'preserve'
8
- }
9
-
10
- interface JSONBigint {
11
- parse(text: string): any
12
- stringify(value: any, replacer?: any, space?: string | number): string
13
- }
14
-
15
- function JSONBig(options?: JSONBigintOptions): JSONBigint
16
-
17
- export = JSONBig
18
- }
1
+ declare module 'json-bigint' {
2
+ interface JSONBigintOptions {
3
+ storeAsString?: boolean
4
+ alwaysParseAsBig?: boolean
5
+ useNativeBigInt?: boolean
6
+ protoAction?: 'error' | 'ignore' | 'preserve'
7
+ constructorAction?: 'error' | 'ignore' | 'preserve'
8
+ }
9
+
10
+ interface JSONBigint {
11
+ parse(text: string): any
12
+ stringify(value: any, replacer?: any, space?: string | number): string
13
+ }
14
+
15
+ function JSONBig(options?: JSONBigintOptions): JSONBigint
16
+
17
+ export = JSONBig
18
+ }
@@ -1,18 +1,25 @@
1
1
  /**
2
2
  * 权限工具函数
3
- * 注意:tineco-ui 不支持 permissions 和 roles 字段,这些函数暂时返回登录状态
4
3
  */
5
4
 
5
+ import { useMenuStore } from '@/stores/menu'
6
6
  import { useUserStore } from '@/stores/user'
7
7
 
8
8
  /**
9
9
  * 检查是否有权限
10
- * @param _permission 权限标识(暂不使用)
10
+ * @param permission 权限标识(菜单 code)
11
11
  */
12
- export function hasPermission(_permission: string | string[]): boolean {
12
+ export function hasPermission(permission: string | string[]): boolean {
13
13
  const userStore = useUserStore()
14
- // tineco-ui 不支持 permissions 字段,暂时返回已登录状态
15
- return userStore.isLoggedIn
14
+ const menuStore = useMenuStore()
15
+
16
+ // 未登录则无权限
17
+ if (!userStore.isLoggedIn) {
18
+ return false
19
+ }
20
+
21
+ // 使用菜单 store 中的权限判断
22
+ return menuStore.hasPermission(permission)
16
23
  }
17
24
 
18
25
  /**
@@ -1,164 +1,184 @@
1
- /**
2
- * Axios 请求封装
3
- */
4
-
5
- import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig } from 'axios'
6
- import { getToken, getTokenType, clearToken } from './auth'
7
- import { Message } from '@xto/feedback'
8
- import JSONBig from 'json-bigint'
9
-
10
- // 配置 json-bigint 将大整数转为字符串,避免 JavaScript Number 精度丢失
11
- const JSONBigString = JSONBig({ storeAsString: true })
12
-
13
- // 响应数据接口(Euler 框架 Result 格式)
14
- export interface ApiResponse<T = unknown> {
15
- code: number
16
- data: T
17
- message: string
18
- }
19
-
20
- // 分页参数(Euler 框架格式)
21
- export interface PageParams {
22
- pageNo: number
23
- pageSize: number
24
- }
25
-
26
- // 分页响应(Euler 框架格式)
27
- export interface PageResponse<T> {
28
- records: T[]
29
- total: number
30
- }
31
-
32
- // 创建 axios 实例
33
- const createRequest = (): AxiosInstance => {
34
- const instance = axios.create({
35
- baseURL: import.meta.env.VITE_API_BASE_URL,
36
- timeout: 30000,
37
- headers: {
38
- 'Content-Type': 'application/json'
39
- },
40
- // 使用 json-bigint 解析响应数据,避免大整数精度丢失
41
- transformResponse: [
42
- (data) => {
43
- if (typeof data === 'string') {
44
- try {
45
- return JSONBigString.parse(data)
46
- } catch (e) {
47
- return data
48
- }
49
- }
50
- return data
51
- }
52
- ]
53
- })
54
-
55
- // 请求拦截器
56
- instance.interceptors.request.use(
57
- (config: InternalAxiosRequestConfig) => {
58
- const token = getToken()
59
- const tokenType = getTokenType() || 'Bearer'
60
- if (token) {
61
- config.headers.Authorization = `${tokenType} ${token}`
62
- }
63
- return config
64
- },
65
- (error) => {
66
- return Promise.reject(error)
67
- }
68
- )
69
-
70
- // 响应拦截器
71
- instance.interceptors.response.use(
72
- (response: AxiosResponse<ApiResponse>) => {
73
- const { data } = response
74
-
75
- // Euler 框架返回 Result 包装格式,成功时直接返回 data.data 中的实际数据
76
- if (data.code === 200 || data.code === 0) {
77
- return data.data as any
78
- }
79
-
80
- // Token 过期或无效(code: 9121)
81
- if (data.code === 9121) {
82
- Message.error('登录已过期,请重新登录')
83
- clearToken()
84
- window.location.href = '/login'
85
- return Promise.reject(new Error(data.message || 'EXPIRED OR INVALID TOKEN'))
86
- }
87
-
88
- // 业务错误
89
- Message.error(data.message || '请求失败')
90
- return Promise.reject(new Error(data.message || '请求失败'))
91
- },
92
- (error) => {
93
- const { response } = error
94
-
95
- if (response) {
96
- // 检查响应体中的 code
97
- const { data } = response
98
- if (data?.code === 9121) {
99
- Message.error('登录已过期,请重新登录')
100
- clearToken()
101
- window.location.href = '/login'
102
- return Promise.reject(error)
103
- }
104
-
105
- switch (response.status) {
106
- case 401:
107
- Message.error('登录已过期,请重新登录')
108
- clearToken()
109
- window.location.href = '/login'
110
- break
111
- case 403:
112
- Message.error('没有权限访问')
113
- break
114
- case 404:
115
- Message.error('请求资源不存在')
116
- break
117
- case 500:
118
- Message.error('服务器错误')
119
- break
120
- default:
121
- Message.error(response.data?.message || '请求失败')
122
- }
123
- } else {
124
- Message.error('网络连接失败')
125
- }
126
-
127
- return Promise.reject(error)
128
- }
129
- )
130
-
131
- return instance
132
- }
133
-
134
- const request = createRequest()
135
-
136
- // 请求方法 - 直接返回实际数据,无需手动解包
137
- export const http = {
138
- get<T = unknown>(url: string, config?: AxiosRequestConfig): Promise<T> {
139
- return request.get(url, config)
140
- },
141
-
142
- post<T = unknown>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<T> {
143
- return request.post(url, data, config)
144
- },
145
-
146
- put<T = unknown>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<T> {
147
- return request.put(url, data, config)
148
- },
149
-
150
- patch<T = unknown>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<T> {
151
- return request.patch(url, data, config)
152
- },
153
-
154
- delete<T = unknown>(url: string, config?: AxiosRequestConfig): Promise<T> {
155
- return request.delete(url, config)
156
- },
157
-
158
- // 文件下载 - 返回 Blob 对象
159
- download(url: string, config?: AxiosRequestConfig): Promise<Blob> {
160
- return request.get(url, { ...config, responseType: 'blob' })
161
- }
162
- }
163
-
164
- export default request
1
+ /**
2
+ * Axios 请求封装
3
+ */
4
+
5
+ import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig } from 'axios'
6
+ import { getToken, getTokenType, clearToken } from './auth'
7
+ import { Message } from '@xto/feedback'
8
+ import JSONBig from 'json-bigint'
9
+
10
+ // 配置 json-bigint 将大整数转为字符串,避免 JavaScript Number 精度丢失
11
+ const JSONBigString = JSONBig({ storeAsString: true })
12
+
13
+ // 响应数据接口(Euler 框架 Result 格式)
14
+ export interface ApiResponse<T = unknown> {
15
+ code: number
16
+ data: T
17
+ message: string
18
+ }
19
+
20
+ // 分页参数(Euler 框架格式)
21
+ export interface PageParams {
22
+ pageNo: number
23
+ pageSize: number
24
+ }
25
+
26
+ // 分页响应(Euler 框架格式)
27
+ export interface PageResponse<T> {
28
+ records: T[]
29
+ total: number
30
+ }
31
+
32
+ // 运行时 API 基础 URL
33
+ let runtimeApiBaseUrl: string | undefined
34
+
35
+ /**
36
+ * 设置运行时 API 基础 URL
37
+ * 用于 Nacos 动态配置场景
38
+ */
39
+ export function setApiBaseUrl(url: string) {
40
+ runtimeApiBaseUrl = url
41
+ // 更新已有实例的 baseURL
42
+ if (request) {
43
+ request.defaults.baseURL = url
44
+ }
45
+ }
46
+
47
+ // 创建 axios 实例
48
+ const createRequest = (): AxiosInstance => {
49
+ const instance = axios.create({
50
+ timeout: 30000,
51
+ headers: {
52
+ 'Content-Type': 'application/json'
53
+ },
54
+ // 使用 json-bigint 解析响应数据,避免大整数精度丢失
55
+ transformResponse: [
56
+ (data) => {
57
+ if (typeof data === 'string') {
58
+ try {
59
+ return JSONBigString.parse(data)
60
+ } catch (e) {
61
+ return data
62
+ }
63
+ }
64
+ return data
65
+ }
66
+ ]
67
+ })
68
+
69
+ // 请求拦截器 - 动态设置 baseURL
70
+ instance.interceptors.request.use(
71
+ (config: InternalAxiosRequestConfig) => {
72
+ // 优先使用运行时配置的 baseURL
73
+ const baseUrl = runtimeApiBaseUrl || import.meta.env.VITE_API_BASE_URL
74
+ if (baseUrl && !config.baseURL) {
75
+ config.baseURL = baseUrl
76
+ }
77
+
78
+ const token = getToken()
79
+ const tokenType = getTokenType() || 'Bearer'
80
+ if (token) {
81
+ config.headers.Authorization = `${tokenType} ${token}`
82
+ }
83
+ return config
84
+ },
85
+ (error) => {
86
+ return Promise.reject(error)
87
+ }
88
+ )
89
+
90
+ // 响应拦截器
91
+ instance.interceptors.response.use(
92
+ (response: AxiosResponse<ApiResponse>) => {
93
+ const { data } = response
94
+
95
+ // Euler 框架返回 Result 包装格式,成功时直接返回 data.data 中的实际数据
96
+ if (data.code === 200 || data.code === 0) {
97
+ return data.data as any
98
+ }
99
+
100
+ // Token 过期或无效(code: 9121)
101
+ if (data.code === 9121) {
102
+ Message.error('登录已过期,请重新登录')
103
+ clearToken()
104
+ window.location.href = '/login'
105
+ return Promise.reject(new Error(data.message || 'EXPIRED OR INVALID TOKEN'))
106
+ }
107
+
108
+ // 业务错误
109
+ Message.error(data.message || '请求失败')
110
+ return Promise.reject(new Error(data.message || '请求失败'))
111
+ },
112
+ (error) => {
113
+ const { response } = error
114
+
115
+ if (response) {
116
+ // 检查响应体中的 code
117
+ const { data } = response
118
+ if (data?.code === 9121) {
119
+ Message.error('登录已过期,请重新登录')
120
+ clearToken()
121
+ window.location.href = '/login'
122
+ return Promise.reject(error)
123
+ }
124
+
125
+ switch (response.status) {
126
+ case 401:
127
+ Message.error('登录已过期,请重新登录')
128
+ clearToken()
129
+ window.location.href = '/login'
130
+ break
131
+ case 403:
132
+ Message.error('没有权限访问')
133
+ break
134
+ case 404:
135
+ Message.error('请求资源不存在')
136
+ break
137
+ case 500:
138
+ Message.error('服务器错误')
139
+ break
140
+ default:
141
+ Message.error(response.data?.message || '请求失败')
142
+ }
143
+ } else {
144
+ Message.error('网络连接失败')
145
+ }
146
+
147
+ return Promise.reject(error)
148
+ }
149
+ )
150
+
151
+ return instance
152
+ }
153
+
154
+ const request = createRequest()
155
+
156
+ // 请求方法 - 直接返回实际数据,无需手动解包
157
+ export const http = {
158
+ get<T = unknown>(url: string, config?: AxiosRequestConfig): Promise<T> {
159
+ return request.get(url, config)
160
+ },
161
+
162
+ post<T = unknown>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<T> {
163
+ return request.post(url, data, config)
164
+ },
165
+
166
+ put<T = unknown>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<T> {
167
+ return request.put(url, data, config)
168
+ },
169
+
170
+ patch<T = unknown>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<T> {
171
+ return request.patch(url, data, config)
172
+ },
173
+
174
+ delete<T = unknown>(url: string, config?: AxiosRequestConfig): Promise<T> {
175
+ return request.delete(url, config)
176
+ },
177
+
178
+ // 文件下载 - 返回 Blob 对象
179
+ download(url: string, config?: AxiosRequestConfig): Promise<Blob> {
180
+ return request.get(url, { ...config, responseType: 'blob' })
181
+ }
182
+ }
183
+
184
+ export default request