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.
- package/dist/assets/404-Bq0LY5Cd.js +1 -0
- package/dist/assets/404-Cw_4ZCL6.css +1 -0
- package/dist/assets/{index-BDgOY6Rp.js → index-7ZZxpSfk.js} +1 -1
- package/dist/assets/index-BJUe8VUp.js +1 -0
- package/dist/assets/{index-Bz0BgZQ1.js → index-BlOR_ICg.js} +1 -1
- package/dist/assets/index-BlRslYYI.css +1 -0
- package/dist/assets/index-BudArKxR.css +1 -0
- package/dist/assets/{index-CwRA10ac.js → index-BzbOWBCV.js} +1 -1
- package/dist/assets/index-CFhWBbxk.css +1 -0
- package/dist/assets/{index-CfpZmcpk.css → index-CH6aTfYg.css} +1 -1
- package/dist/assets/{index-BIoRANs0.js → index-CT5f37nN.js} +1 -1
- package/dist/assets/index-Ce-kjtEM.js +2 -0
- package/dist/assets/{index-t-2Y0KhA.css → index-Cpew6d-v.css} +1 -1
- package/dist/assets/index-DkkuYBgT.css +1 -0
- package/dist/assets/index-vfvEFrCH.css +1 -0
- package/dist/assets/{index-CwJSA85U.js → index-wVLLAoVp.js} +1 -1
- package/dist/assets/vendor-DZmPBJ9d.js +16 -0
- package/dist/assets/vue-vendor-DjmFuEnG.js +29 -0
- package/dist/assets/{xto-base-PwLGsxxb.js → xto-base-B5HYOo6i.js} +1 -1
- package/dist/assets/{xto-core-CtL4zKiV.js → xto-core-DZYp_YAR.js} +1 -1
- package/dist/assets/{xto-data-bCXQa7fT.js → xto-data-ogck6x_i.js} +1 -1
- package/dist/assets/{xto-feedback-CPydp0kn.js → xto-feedback-C0-6cAL6.js} +1 -1
- package/dist/assets/{xto-form-bywohdAf.js → xto-form-IDg_78Vf.js} +1 -1
- package/dist/assets/{xto-navigation-Bbdpine9.js → xto-navigation-CPYLzfu7.js} +1 -1
- package/dist/index.html +9 -9
- package/package.json +91 -91
- package/src/App.vue +48 -48
- package/src/assets/styles/_dark.scss +639 -572
- package/src/assets/styles/_root.scss +183 -183
- package/src/assets/styles/_variables.scss +69 -69
- package/src/assets/styles/index.scss +460 -460
- package/src/components/Layout/Sidebar.vue +198 -198
- package/src/components/Layout/TopMenu.vue +1170 -1170
- package/src/components/Layout/index.vue +192 -192
- package/src/directives/permission.ts +12 -3
- package/src/index.ts +100 -100
- package/src/router/layoutRoute.ts +59 -59
- package/src/stores/menu.ts +64 -3
- package/src/types/json-bigint.d.ts +18 -18
- package/src/utils/permission.ts +12 -5
- package/src/utils/request.ts +184 -164
- package/src/views/dashboard/index.vue +545 -545
- package/src/views/error/403.vue +251 -251
- package/src/views/error/404.vue +253 -253
- package/src/views/login/index.vue +586 -586
- package/src/views/system/menu/index.vue +690 -690
- package/src/views/system/role/index.vue +583 -583
- package/src/views/system/user/index.vue +655 -655
- package/dist/App.vue.d.ts +0 -2
- package/dist/api/auth.d.ts +0 -8
- package/dist/api/system.d.ts +0 -16
- package/dist/api/user.d.ts +0 -13
- package/dist/assets/404-C9Uh6Uu-.css +0 -1
- package/dist/assets/404-zjGLLssH.js +0 -1
- package/dist/assets/index-B5xc4gQB.css +0 -1
- package/dist/assets/index-CAdztNsv.css +0 -1
- package/dist/assets/index-CCXrcISf.css +0 -1
- package/dist/assets/index-D8NDxq9d.js +0 -1
- package/dist/assets/index-DEB6-Iv_.js +0 -2
- package/dist/assets/index-DM4Ezclc.css +0 -1
- package/dist/assets/index-DYv7nImj.css +0 -1
- package/dist/assets/vendor-CUVPinTg.js +0 -13
- package/dist/assets/vue-vendor-DeJXJVbN.js +0 -29
- package/dist/components/Layout/Footer.vue.d.ts +0 -2
- package/dist/components/Layout/Header.vue.d.ts +0 -5
- package/dist/components/Layout/MixTopMenu.vue.d.ts +0 -5
- package/dist/components/Layout/Sidebar.vue.d.ts +0 -11
- package/dist/components/Layout/SidebarMenuItem.vue.d.ts +0 -5
- package/dist/components/Layout/Tabs.vue.d.ts +0 -2
- package/dist/components/Layout/TopMenu.vue.d.ts +0 -5
- package/dist/components/Layout/index.vue.d.ts +0 -2
- package/dist/composables/useApp.d.ts +0 -29
- package/dist/composables/useAuth.d.ts +0 -6
- package/dist/composables/useForm.d.ts +0 -20
- package/dist/composables/useI18n.d.ts +0 -30
- package/dist/composables/useTable.d.ts +0 -29
- package/dist/directives/permission.d.ts +0 -4
- package/dist/enums/index.d.ts +0 -32
- package/dist/index-BRvi9qW-.js +0 -515
- package/dist/index-BVGW4DDQ.js +0 -189
- package/dist/index-Bmf0YbVq.js +0 -189
- package/dist/index-C2-a5KSQ.js +0 -4233
- package/dist/index-CeZ0CSSs.js +0 -641
- package/dist/index-D25KzR0I.js +0 -479
- package/dist/index-DEYOivza.js +0 -641
- package/dist/index-DReodgBw.js +0 -4233
- package/dist/index-DjERNRXX.js +0 -515
- package/dist/index-gBlRG4kk.js +0 -479
- package/dist/index.d.ts +0 -59
- package/dist/index.es.js +0 -94
- package/dist/index.umd.js +0 -8
- package/dist/main.d.ts +0 -0
- package/dist/router/dynamicRoutes.d.ts +0 -30
- package/dist/router/guards.d.ts +0 -17
- package/dist/router/index.d.ts +0 -6
- package/dist/router/layoutRoute.d.ts +0 -22
- package/dist/router/staticRoutes.d.ts +0 -2
- package/dist/stores/app.d.ts +0 -93
- package/dist/stores/auth.d.ts +0 -41
- package/dist/stores/index.d.ts +0 -10
- package/dist/stores/locale.d.ts +0 -42
- package/dist/stores/menu.d.ts +0 -77
- package/dist/stores/user.d.ts +0 -92
- package/dist/style.css +0 -1
- package/dist/utils/auth.d.ts +0 -27
- package/dist/utils/config.d.ts +0 -30
- package/dist/utils/permission.d.ts +0 -18
- package/dist/utils/request.d.ts +0 -24
- package/dist/utils/storage.d.ts +0 -24
- package/dist/views/dashboard/index.vue.d.ts +0 -2
- package/dist/views/error/403.vue.d.ts +0 -2
- package/dist/views/error/404.vue.d.ts +0 -2
- package/dist/views/login/index.vue.d.ts +0 -4
- package/dist/views/system/menu/index.vue.d.ts +0 -4
- package/dist/views/system/role/index.vue.d.ts +0 -4
- 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
|
}
|
package/src/stores/menu.ts
CHANGED
|
@@ -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, ...
|
|
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
|
+
}
|
package/src/utils/permission.ts
CHANGED
|
@@ -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
|
|
10
|
+
* @param permission 权限标识(菜单 code)
|
|
11
11
|
*/
|
|
12
|
-
export function hasPermission(
|
|
12
|
+
export function hasPermission(permission: string | string[]): boolean {
|
|
13
13
|
const userStore = useUserStore()
|
|
14
|
-
|
|
15
|
-
|
|
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
|
/**
|
package/src/utils/request.ts
CHANGED
|
@@ -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
|
-
//
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
(
|
|
93
|
-
const {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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
|