mta-mcp 2.6.0 → 2.7.0
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/agents/flutter.agent.md +6 -7
- package/agents/i18n.agent.md +1 -0
- package/agents/logicflow.agent.md +1 -0
- package/agents/vue3.agent.md +1 -0
- package/dist/index.js +941 -764
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
- package/standards/frameworks/flutter-ui-system.md +1 -2
- package/standards/workflows/large-project-split.md +1 -1
- package/templates/README.md +144 -0
- package/templates/common/types/_CONFIG.md +12 -0
- package/templates/common/types/api.ts +39 -0
- package/templates/common/types/common.ts +70 -0
- package/templates/copilot-instructions-mcp-optimized.md +158 -0
- package/templates/vue/api-layer/_CONFIG.md +145 -0
- package/templates/vue/api-layer/index.ts +58 -0
- package/templates/vue/api-layer/mock/index.ts +122 -0
- package/templates/vue/api-layer/modules/_template.ts +109 -0
- package/templates/vue/api-layer/modules/index.ts +16 -0
- package/templates/vue/api-layer/request.ts +279 -0
- package/templates/vue/api-layer/types.ts +80 -0
- package/agents/vue3.agent.md.bak +0 -132
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mock 系统入口
|
|
3
|
+
*
|
|
4
|
+
* 提供 Mock 开关和常用工具函数
|
|
5
|
+
*/
|
|
6
|
+
import type { ApiResponse, PageData } from '../types'
|
|
7
|
+
|
|
8
|
+
// Mock 开关(由环境变量控制)
|
|
9
|
+
export const MOCK_ENABLED = import.meta.env.VITE_MOCK_ENABLED === 'true'
|
|
10
|
+
|
|
11
|
+
// Mock 延迟时间(模拟网络请求)
|
|
12
|
+
export const MOCK_DELAY = 300
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 延迟函数
|
|
16
|
+
*/
|
|
17
|
+
export const delay = (ms: number = MOCK_DELAY) =>
|
|
18
|
+
new Promise(resolve => setTimeout(resolve, ms))
|
|
19
|
+
|
|
20
|
+
// 别名
|
|
21
|
+
export const sleep = delay
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 生成成功响应
|
|
25
|
+
*/
|
|
26
|
+
export function successResponse<T>(data: T): ApiResponse<T> {
|
|
27
|
+
return {
|
|
28
|
+
code: 0,
|
|
29
|
+
data,
|
|
30
|
+
message: 'success',
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 生成错误响应
|
|
36
|
+
*/
|
|
37
|
+
export function errorResponse(message: string, code = -1): ApiResponse<null> {
|
|
38
|
+
return {
|
|
39
|
+
code,
|
|
40
|
+
data: null,
|
|
41
|
+
message,
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* 生成分页响应
|
|
47
|
+
*/
|
|
48
|
+
export function generatePageResponse<T>(
|
|
49
|
+
list: T[],
|
|
50
|
+
page: number,
|
|
51
|
+
pageSize: number,
|
|
52
|
+
total?: number
|
|
53
|
+
): ApiResponse<PageData<T>> {
|
|
54
|
+
const start = (page - 1) * pageSize
|
|
55
|
+
const end = start + pageSize
|
|
56
|
+
const data = list.slice(start, end)
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
code: 0,
|
|
60
|
+
data: {
|
|
61
|
+
list: data,
|
|
62
|
+
total: total ?? list.length,
|
|
63
|
+
page,
|
|
64
|
+
pageSize,
|
|
65
|
+
},
|
|
66
|
+
message: 'success',
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* 生成随机 ID
|
|
72
|
+
*/
|
|
73
|
+
export function randomId(): string {
|
|
74
|
+
return Date.now().toString(36) + Math.random().toString(36).substr(2, 9)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* 生成随机日期(近 n 天内)
|
|
79
|
+
*/
|
|
80
|
+
export function randomDate(days = 30): string {
|
|
81
|
+
const date = new Date()
|
|
82
|
+
date.setDate(date.getDate() - Math.floor(Math.random() * days))
|
|
83
|
+
return date.toISOString().split('T')[0] + ' ' + date.toTimeString().split(' ')[0]
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* 从数组中随机选择
|
|
88
|
+
*/
|
|
89
|
+
export function randomPick<T>(arr: T[]): T {
|
|
90
|
+
return arr[Math.floor(Math.random() * arr.length)]
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* 生成随机整数
|
|
95
|
+
*/
|
|
96
|
+
export function randomInt(min: number, max: number): number {
|
|
97
|
+
return Math.floor(Math.random() * (max - min + 1)) + min
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* 生成随机金额(保留两位小数)
|
|
102
|
+
*/
|
|
103
|
+
export function randomAmount(min = 100, max = 10000): number {
|
|
104
|
+
return Number((Math.random() * (max - min) + min).toFixed(2))
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* 生成随机手机号
|
|
109
|
+
*/
|
|
110
|
+
export function randomPhone(): string {
|
|
111
|
+
const prefixes = ['138', '139', '150', '151', '152', '158', '159', '186', '187', '188']
|
|
112
|
+
return randomPick(prefixes) + String(Math.random()).slice(2, 10)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* 生成随机邮箱
|
|
117
|
+
*/
|
|
118
|
+
export function randomEmail(name?: string): string {
|
|
119
|
+
const domains = ['qq.com', '163.com', 'gmail.com', 'outlook.com']
|
|
120
|
+
const prefix = name || `user${randomInt(1000, 9999)}`
|
|
121
|
+
return `${prefix}@${randomPick(domains)}`
|
|
122
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* [模块名] API
|
|
3
|
+
*
|
|
4
|
+
* 💡 使用说明:
|
|
5
|
+
* 1. 复制此文件,重命名为业务模块名(如 user.ts, order.ts)
|
|
6
|
+
* 2. 修改 URLs 对象中的接口地址
|
|
7
|
+
* 3. 定义业务相关的类型
|
|
8
|
+
* 4. 实现具体的 API 函数
|
|
9
|
+
* 5. 在 modules/index.ts 中导出
|
|
10
|
+
*/
|
|
11
|
+
import { post, get, put, del } from '../request'
|
|
12
|
+
import { MOCK_ENABLED } from '../mock'
|
|
13
|
+
// import * as mockModule from '../mock/[module]'
|
|
14
|
+
import type { ApiResponse, PageResponse, PageParams } from '../types'
|
|
15
|
+
|
|
16
|
+
// ========== 接口地址 ==========
|
|
17
|
+
|
|
18
|
+
const URLs = {
|
|
19
|
+
list: '/xxx/list',
|
|
20
|
+
detail: '/xxx/detail',
|
|
21
|
+
create: '/xxx/create',
|
|
22
|
+
update: '/xxx/update',
|
|
23
|
+
delete: '/xxx/delete',
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// ========== 类型定义 ==========
|
|
27
|
+
|
|
28
|
+
/** 数据项类型 */
|
|
29
|
+
export interface ItemData {
|
|
30
|
+
id: string
|
|
31
|
+
name: string
|
|
32
|
+
status: string
|
|
33
|
+
createdAt: string
|
|
34
|
+
updatedAt: string
|
|
35
|
+
// ... 添加其他字段
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** 创建/更新参数 */
|
|
39
|
+
export interface CreateParams {
|
|
40
|
+
name: string
|
|
41
|
+
// ... 添加其他参数
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** 列表查询参数 */
|
|
45
|
+
export interface ListParams extends PageParams {
|
|
46
|
+
keyword?: string
|
|
47
|
+
status?: string
|
|
48
|
+
startDate?: string
|
|
49
|
+
endDate?: string
|
|
50
|
+
// ... 添加其他筛选条件
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ========== 接口实现 ==========
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* 获取列表
|
|
57
|
+
*/
|
|
58
|
+
export async function getList(params: ListParams): Promise<PageResponse<ItemData>> {
|
|
59
|
+
// if (MOCK_ENABLED) return mockModule.mockGetList(params)
|
|
60
|
+
return get(URLs.list, params)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* 获取详情
|
|
65
|
+
*/
|
|
66
|
+
export async function getDetail(id: string): Promise<ApiResponse<ItemData>> {
|
|
67
|
+
// if (MOCK_ENABLED) return mockModule.mockGetDetail(id)
|
|
68
|
+
return get(URLs.detail, { id })
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* 创建
|
|
73
|
+
*/
|
|
74
|
+
export async function create(data: CreateParams): Promise<ApiResponse<ItemData>> {
|
|
75
|
+
// if (MOCK_ENABLED) return mockModule.mockCreate(data)
|
|
76
|
+
return post(URLs.create, data)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* 更新
|
|
81
|
+
*/
|
|
82
|
+
export async function update(id: string, data: Partial<CreateParams>): Promise<ApiResponse<ItemData>> {
|
|
83
|
+
// if (MOCK_ENABLED) return mockModule.mockUpdate(id, data)
|
|
84
|
+
return put(`${URLs.update}/${id}`, data)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* 删除
|
|
89
|
+
*/
|
|
90
|
+
export async function remove(id: string): Promise<ApiResponse<null>> {
|
|
91
|
+
// if (MOCK_ENABLED) return mockModule.mockDelete(id)
|
|
92
|
+
return del(URLs.delete, { id })
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* 批量删除
|
|
97
|
+
*/
|
|
98
|
+
export async function batchRemove(ids: string[]): Promise<ApiResponse<null>> {
|
|
99
|
+
// if (MOCK_ENABLED) return mockModule.mockBatchDelete(ids)
|
|
100
|
+
return post(`${URLs.delete}/batch`, { ids })
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* 更新状态
|
|
105
|
+
*/
|
|
106
|
+
export async function updateStatus(id: string, status: string): Promise<ApiResponse<null>> {
|
|
107
|
+
// if (MOCK_ENABLED) return mockModule.mockUpdateStatus(id, status)
|
|
108
|
+
return put(`${URLs.update}/${id}/status`, { status })
|
|
109
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API 模块统一导出
|
|
3
|
+
*
|
|
4
|
+
* 💡 添加新模块时:
|
|
5
|
+
* 1. 复制 _template.ts,重命名为业务模块名
|
|
6
|
+
* 2. 在此处添加 export
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* export * as userApi from './user'
|
|
10
|
+
* export * as orderApi from './order'
|
|
11
|
+
* export * as productApi from './product'
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
// 按项目实际模块添加导出...
|
|
15
|
+
// export * as userApi from './user'
|
|
16
|
+
// export * as authApi from './auth'
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Axios 请求封装
|
|
3
|
+
*
|
|
4
|
+
* 💡 自定义点(按项目调整):
|
|
5
|
+
* - API_TIMEOUT: 请求超时时间
|
|
6
|
+
* - NO_TOKEN_URLS: 无需 Token 的接口白名单
|
|
7
|
+
* - TOKEN_KEY/USER_KEY: 本地存储键名
|
|
8
|
+
* - showMessage: UI 框架消息提示方法
|
|
9
|
+
* - 响应拦截器中的业务状态码判断
|
|
10
|
+
*/
|
|
11
|
+
import axios from 'axios'
|
|
12
|
+
import type { AxiosInstance, AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig } from 'axios'
|
|
13
|
+
import router from '@/router'
|
|
14
|
+
import type { ApiResponse } from './types'
|
|
15
|
+
|
|
16
|
+
// ========== 常量配置 ==========
|
|
17
|
+
|
|
18
|
+
const API_TIMEOUT = 30000
|
|
19
|
+
|
|
20
|
+
// 无需 Token 的接口白名单(按项目调整)
|
|
21
|
+
const NO_TOKEN_URLS = [
|
|
22
|
+
'/login',
|
|
23
|
+
'/register',
|
|
24
|
+
'/captcha',
|
|
25
|
+
'/sms-code',
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
// ========== Token 管理 ==========
|
|
29
|
+
|
|
30
|
+
const TOKEN_KEY = 'app_token'
|
|
31
|
+
const USER_KEY = 'app_user'
|
|
32
|
+
|
|
33
|
+
export const getToken = (): string | null => localStorage.getItem(TOKEN_KEY)
|
|
34
|
+
export const setToken = (token: string): void => localStorage.setItem(TOKEN_KEY, token)
|
|
35
|
+
export const clearToken = (): void => {
|
|
36
|
+
localStorage.removeItem(TOKEN_KEY)
|
|
37
|
+
localStorage.removeItem(USER_KEY)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export const getUserInfo = <T = any>(): T | null => {
|
|
41
|
+
const userStr = localStorage.getItem(USER_KEY)
|
|
42
|
+
if (!userStr) return null
|
|
43
|
+
try {
|
|
44
|
+
return JSON.parse(userStr)
|
|
45
|
+
} catch {
|
|
46
|
+
return null
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export const setUserInfo = (user: any): void => {
|
|
51
|
+
localStorage.setItem(USER_KEY, JSON.stringify(user))
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ========== 消息提示 ==========
|
|
55
|
+
|
|
56
|
+
// 💡 按 UI 框架替换此对象
|
|
57
|
+
// Element Plus: import { ElMessage } from 'element-plus'
|
|
58
|
+
// Ant Design Vue: import { message } from 'ant-design-vue'
|
|
59
|
+
|
|
60
|
+
const showMessage = {
|
|
61
|
+
success: (msg: string) => console.log('✅', msg),
|
|
62
|
+
error: (msg: string) => console.error('❌', msg),
|
|
63
|
+
warning: (msg: string) => console.warn('⚠️', msg),
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Element Plus 示例:
|
|
67
|
+
// const showMessage = {
|
|
68
|
+
// success: (msg: string) => ElMessage.success(msg),
|
|
69
|
+
// error: (msg: string) => ElMessage.error(msg),
|
|
70
|
+
// warning: (msg: string) => ElMessage.warning(msg),
|
|
71
|
+
// }
|
|
72
|
+
|
|
73
|
+
// ========== 错误码映射 ==========
|
|
74
|
+
|
|
75
|
+
const ERROR_CODE_MAP: Record<number, string> = {
|
|
76
|
+
400: '请求参数错误',
|
|
77
|
+
401: '登录已过期,请重新登录',
|
|
78
|
+
403: '没有操作权限',
|
|
79
|
+
404: '请求的资源不存在',
|
|
80
|
+
500: '服务器内部错误',
|
|
81
|
+
502: '网关错误',
|
|
82
|
+
503: '服务暂不可用',
|
|
83
|
+
504: '网关超时',
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ========== 错误处理 ==========
|
|
87
|
+
|
|
88
|
+
const handleRequestError = (error: any): Promise<never> => {
|
|
89
|
+
console.error('请求错误:', error)
|
|
90
|
+
|
|
91
|
+
if (error.code === 'ERR_NETWORK') {
|
|
92
|
+
showMessage.error('网络连接失败,请检查网络设置')
|
|
93
|
+
} else if (error.code === 'ECONNABORTED') {
|
|
94
|
+
showMessage.error('请求超时,请稍后重试')
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return Promise.reject(error)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const handleResponseError = (error: any): Promise<never> => {
|
|
101
|
+
const status = error?.response?.status
|
|
102
|
+
const data = error?.response?.data
|
|
103
|
+
const message = data?.message || ERROR_CODE_MAP[status] || '请求失败,请稍后重试'
|
|
104
|
+
|
|
105
|
+
if (status === 401) {
|
|
106
|
+
clearToken()
|
|
107
|
+
showMessage.warning(message)
|
|
108
|
+
router.push({ name: 'login' }) // 💡 按项目调整登录路由
|
|
109
|
+
return Promise.reject(error)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
showMessage.error(message)
|
|
113
|
+
return Promise.reject(error)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ========== Axios 实例 ==========
|
|
117
|
+
|
|
118
|
+
const instance: AxiosInstance = axios.create({
|
|
119
|
+
baseURL: import.meta.env.VITE_API_BASE_URL || '/api',
|
|
120
|
+
timeout: API_TIMEOUT,
|
|
121
|
+
headers: { 'Content-Type': 'application/json' },
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
// 请求拦截器
|
|
125
|
+
instance.interceptors.request.use(
|
|
126
|
+
(config: InternalAxiosRequestConfig) => {
|
|
127
|
+
const needToken = !NO_TOKEN_URLS.some(url => config.url?.includes(url))
|
|
128
|
+
|
|
129
|
+
if (needToken) {
|
|
130
|
+
const token = getToken()
|
|
131
|
+
if (token) {
|
|
132
|
+
// 💡 按后端要求调整 Token 传递方式
|
|
133
|
+
config.headers.Authorization = `Bearer ${token}`
|
|
134
|
+
// config.headers.token = token
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return config
|
|
139
|
+
},
|
|
140
|
+
handleRequestError
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
// 响应拦截器
|
|
144
|
+
instance.interceptors.response.use(
|
|
145
|
+
(response: AxiosResponse) => {
|
|
146
|
+
const res = response.data
|
|
147
|
+
|
|
148
|
+
// 文件流直接返回
|
|
149
|
+
if (response.config.responseType === 'blob') {
|
|
150
|
+
return res
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// 💡 业务状态码判断(按后端约定调整)
|
|
154
|
+
// 格式 A: { code: 0, data, message }
|
|
155
|
+
// 格式 B: { code: 200, data, msg }
|
|
156
|
+
// 格式 C: { success: true, data, message }
|
|
157
|
+
if (res.code === 0 || res.code === 200 || res.success === true) {
|
|
158
|
+
return res
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// 401 Token 过期
|
|
162
|
+
if (res.code === 401) {
|
|
163
|
+
clearToken()
|
|
164
|
+
showMessage.warning(res.message || '登录已过期')
|
|
165
|
+
router.push({ name: 'login' })
|
|
166
|
+
return Promise.reject(new Error(res.message))
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// 其他业务错误
|
|
170
|
+
showMessage.error(res.message || res.msg || '请求失败')
|
|
171
|
+
return Promise.reject(new Error(res.message || res.msg))
|
|
172
|
+
},
|
|
173
|
+
handleResponseError
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
// ========== 请求方法 ==========
|
|
177
|
+
|
|
178
|
+
export async function get<T = any>(
|
|
179
|
+
url: string,
|
|
180
|
+
params?: Record<string, any>,
|
|
181
|
+
config?: AxiosRequestConfig
|
|
182
|
+
): Promise<ApiResponse<T>> {
|
|
183
|
+
return instance.get(url, { params, ...config })
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export async function post<T = any>(
|
|
187
|
+
url: string,
|
|
188
|
+
data?: any,
|
|
189
|
+
config?: AxiosRequestConfig
|
|
190
|
+
): Promise<ApiResponse<T>> {
|
|
191
|
+
return instance.post(url, data, config)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export async function put<T = any>(
|
|
195
|
+
url: string,
|
|
196
|
+
data?: any,
|
|
197
|
+
config?: AxiosRequestConfig
|
|
198
|
+
): Promise<ApiResponse<T>> {
|
|
199
|
+
return instance.put(url, data, config)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export async function del<T = any>(
|
|
203
|
+
url: string,
|
|
204
|
+
params?: Record<string, any>,
|
|
205
|
+
config?: AxiosRequestConfig
|
|
206
|
+
): Promise<ApiResponse<T>> {
|
|
207
|
+
return instance.delete(url, { params, ...config })
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// ========== 工具函数 ==========
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* 包装异步请求,返回 [data, error] 元组
|
|
214
|
+
* 借鉴 Go 语言错误处理风格,避免 try/catch
|
|
215
|
+
*
|
|
216
|
+
* @example
|
|
217
|
+
* const [res, err] = await wrap(userApi.getList(params))
|
|
218
|
+
* if (err) {
|
|
219
|
+
* console.error('请求失败:', err)
|
|
220
|
+
* return
|
|
221
|
+
* }
|
|
222
|
+
* // 使用 res.data
|
|
223
|
+
*/
|
|
224
|
+
export async function wrap<T>(
|
|
225
|
+
promise: Promise<T>
|
|
226
|
+
): Promise<[T, null] | [null, Error]> {
|
|
227
|
+
try {
|
|
228
|
+
const data = await promise
|
|
229
|
+
return [data, null]
|
|
230
|
+
} catch (error) {
|
|
231
|
+
return [null, error as Error]
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* 下载文件
|
|
237
|
+
*/
|
|
238
|
+
export async function downloadFile(
|
|
239
|
+
url: string,
|
|
240
|
+
params?: Record<string, any>,
|
|
241
|
+
filename?: string
|
|
242
|
+
): Promise<void> {
|
|
243
|
+
const response = await instance.get(url, {
|
|
244
|
+
params,
|
|
245
|
+
responseType: 'blob',
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
const blob = new Blob([response as any])
|
|
249
|
+
const link = document.createElement('a')
|
|
250
|
+
link.href = URL.createObjectURL(blob)
|
|
251
|
+
link.download = filename || 'download'
|
|
252
|
+
link.click()
|
|
253
|
+
URL.revokeObjectURL(link.href)
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* 上传文件
|
|
258
|
+
*/
|
|
259
|
+
export async function uploadFile<T = any>(
|
|
260
|
+
url: string,
|
|
261
|
+
file: File,
|
|
262
|
+
fieldName = 'file',
|
|
263
|
+
extraData?: Record<string, any>
|
|
264
|
+
): Promise<ApiResponse<T>> {
|
|
265
|
+
const formData = new FormData()
|
|
266
|
+
formData.append(fieldName, file)
|
|
267
|
+
|
|
268
|
+
if (extraData) {
|
|
269
|
+
Object.entries(extraData).forEach(([key, value]) => {
|
|
270
|
+
formData.append(key, value)
|
|
271
|
+
})
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return instance.post(url, formData, {
|
|
275
|
+
headers: { 'Content-Type': 'multipart/form-data' },
|
|
276
|
+
})
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export default instance
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API 通用类型定义
|
|
3
|
+
*
|
|
4
|
+
* 💡 按后端实际响应结构调整字段名
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* API 响应基础结构
|
|
9
|
+
*
|
|
10
|
+
* 常见格式:
|
|
11
|
+
* - { code: 0, data: T, message: '' }
|
|
12
|
+
* - { code: 200, data: T, msg: '' }
|
|
13
|
+
* - { success: true, data: T, message: '' }
|
|
14
|
+
*/
|
|
15
|
+
export interface ApiResponse<T = any> {
|
|
16
|
+
code: number
|
|
17
|
+
data: T
|
|
18
|
+
message: string
|
|
19
|
+
// msg?: string // 备选字段名
|
|
20
|
+
// success?: boolean // 备选判断字段
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 分页请求参数
|
|
25
|
+
*
|
|
26
|
+
* 常见字段名:
|
|
27
|
+
* - page / pageNum / current
|
|
28
|
+
* - pageSize / size / limit
|
|
29
|
+
*/
|
|
30
|
+
export interface PageParams {
|
|
31
|
+
page: number
|
|
32
|
+
pageSize: number
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* 分页响应数据
|
|
37
|
+
*
|
|
38
|
+
* 常见字段名:
|
|
39
|
+
* - list / records / items / rows
|
|
40
|
+
* - total / totalCount / count
|
|
41
|
+
*/
|
|
42
|
+
export interface PageData<T> {
|
|
43
|
+
list: T[]
|
|
44
|
+
total: number
|
|
45
|
+
page: number
|
|
46
|
+
pageSize: number
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* 分页响应(完整)
|
|
51
|
+
*/
|
|
52
|
+
export type PageResponse<T> = ApiResponse<PageData<T>>
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 通用 ID 类型
|
|
56
|
+
*/
|
|
57
|
+
export type ID = string | number
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* 通用状态枚举
|
|
61
|
+
*/
|
|
62
|
+
export type CommonStatus = 'active' | 'inactive' | 'pending' | 'deleted'
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* 排序参数
|
|
66
|
+
*/
|
|
67
|
+
export interface SortParams {
|
|
68
|
+
sortField?: string
|
|
69
|
+
sortOrder?: 'asc' | 'desc' | 'ascend' | 'descend'
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* 时间范围参数
|
|
74
|
+
*/
|
|
75
|
+
export interface DateRangeParams {
|
|
76
|
+
startDate?: string
|
|
77
|
+
endDate?: string
|
|
78
|
+
startTime?: string
|
|
79
|
+
endTime?: string
|
|
80
|
+
}
|