mta-mcp 1.0.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/README.md +818 -0
- package/agents/_TEMPLATE.md +153 -0
- package/agents/flutter.agent.md +222 -0
- package/agents/i18n.agent.md +78 -0
- package/agents/logicflow.agent.md +97 -0
- package/agents/vue3.agent.md +176 -0
- package/agents/wechat-miniprogram.agent.md +89 -0
- package/bin/mta.cjs +132 -0
- package/common/i18n.md +385 -0
- package/common/typescript-strict.md +186 -0
- package/dist/index.d.ts +64 -0
- package/dist/index.js +6493 -0
- package/dist/index.js.map +1 -0
- package/package.json +81 -0
- package/standards/README.md +194 -0
- package/standards/core/code-generation.md +421 -0
- package/standards/core/code-style.md +308 -0
- package/standards/core/dart-base.md +572 -0
- package/standards/core/mandatory-rules.md +103 -0
- package/standards/core/typescript-base.md +179 -0
- package/standards/frameworks/flutter-ui-system.md +497 -0
- package/standards/frameworks/flutter.md +1268 -0
- package/standards/frameworks/pinia.md +172 -0
- package/standards/frameworks/vue3-composition.md +779 -0
- package/standards/frameworks/wechat-miniprogram.md +2177 -0
- package/standards/libraries/element-plus.md +1128 -0
- package/standards/libraries/i18n.md +360 -0
- package/standards/libraries/logicflow.md +1007 -0
- package/standards/patterns/api-layer.md +187 -0
- package/standards/patterns/component-design.md +200 -0
- package/standards/patterns/design-system-restoration.md +570 -0
- package/standards/patterns/vue-api-mock-layer.md +958 -0
- package/standards/patterns/vue-css-nesting.md +604 -0
- package/standards/troubleshooting-cases/flutter/textfield-vertical-centering.md +107 -0
- package/standards/workflows/design-restoration-guide.md +164 -0
- package/standards/workflows/large-project-split.md +359 -0
- package/standards/workflows/problem-diagnosis.md +280 -0
- package/standards/workflows/textfield-centering-guide.md +157 -0
- 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/config-templates/agents-section.md +9 -0
- package/templates/config-templates/custom-section.md +6 -0
- package/templates/config-templates/header.md +29 -0
- package/templates/config-templates/workflow-minimal.md +44 -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/troubleshooting/README.md +368 -0
- package/troubleshooting/USAGE_GUIDE.md +289 -0
- package/troubleshooting/flutter/clip-/351/230/264/345/275/261/350/243/201/345/211/252.md +244 -0
- package/troubleshooting/flutter/component-/351/200/232/347/224/250/345/214/226/346/217/220/345/217/226.md +269 -0
- package/troubleshooting/flutter/input-/345/255/227/346/256/265/347/274/272/345/244/261.md +240 -0
- package/troubleshooting/flutter/input-/350/276/271/346/241/206/351/227/256/351/242/230.md +236 -0
- package/troubleshooting/flutter/layout-/345/260/272/345/257/270/344/270/215/345/214/271/351/205/215.md +214 -0
- package/troubleshooting/flutter/shadow-/351/200/217/345/207/272/351/227/256/351/242/230.md +172 -0
- package/troubleshooting/flutter/sketch-/345/210/227/350/241/250item/345/214/272/345/237/237.md +212 -0
- package/troubleshooting/flutter/sketch-/345/233/276/346/240/207/345/260/272/345/257/270.md +135 -0
- package/troubleshooting/flutter/sketch-/345/256/214/346/225/264/346/217/220/345/217/226.md +201 -0
- package/troubleshooting/flutter/sketch-/345/261/236/346/200/247/346/234/252/344/275/277/347/224/250.md +139 -0
- package/troubleshooting/flutter/sketch-/350/203/214/346/231/257/345/261/202/351/253/230/345/272/246.md +264 -0
- package/troubleshooting/flutter/svg-/346/234/252/345/261/205/344/270/255.md +120 -0
- package/troubleshooting/flutter/svg-/351/242/234/350/211/262/345/274/202/345/270/270.md +117 -0
- package/troubleshooting/flutter/tabbar-/345/212/250/347/224/273/345/220/214/346/255/245.md +107 -0
- package/troubleshooting/flutter/withopacity-/345/274/203/347/224/250.md +81 -0
- package/troubleshooting/vue3/cascader-/350/257/257/346/233/277/346/215/242.md +130 -0
- package/troubleshooting/vue3/drawer-input-/346/240/267/345/274/217.md +181 -0
- package/troubleshooting/vue3/table-/347/274/226/350/276/221/345/217/226/346/266/210.md +148 -0
- package/troubleshooting/vue3/table-/350/276/271/346/241/206/351/227/256/351/242/230.md +178 -0
|
@@ -0,0 +1,958 @@
|
|
|
1
|
+
# Vue API + Mock 层封装模式
|
|
2
|
+
|
|
3
|
+
> 适用场景:Vue 3 + TypeScript 项目(兼容 Element Plus / Ant Design Vue / Naive UI 等)
|
|
4
|
+
> 核心特性:API 集中管理、Mock 无缝切换、wrap 错误处理、文件上传下载
|
|
5
|
+
> 设计原则:**灵活可扩展**,模块按需添加,不限定具体业务
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 一、目录结构
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
src/api/
|
|
13
|
+
├── index.ts # 统一导出入口
|
|
14
|
+
├── request.ts # Axios 核心封装(必需)
|
|
15
|
+
├── mock/ # Mock 系统(可选)
|
|
16
|
+
│ ├── index.ts # Mock 开关与工具函数
|
|
17
|
+
│ └── [module].ts # 按业务模块创建,如 user.ts, order.ts
|
|
18
|
+
└── modules/ # 业务 API 模块
|
|
19
|
+
├── index.ts # 模块统一导出
|
|
20
|
+
└── [module].ts # 按业务模块创建,如 user.ts, order.ts
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
**模块命名建议**(根据项目实际需求选择):
|
|
24
|
+
|
|
25
|
+
| 通用模块 | 电商类 | 管理系统 | 社交类 |
|
|
26
|
+
|---------|--------|----------|--------|
|
|
27
|
+
| `user.ts` | `product.ts` | `auth.ts` | `post.ts` |
|
|
28
|
+
| `common.ts` | `order.ts` | `permission.ts` | `comment.ts` |
|
|
29
|
+
| `upload.ts` | `cart.ts` | `log.ts` | `message.ts` |
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## 二、核心文件模板
|
|
34
|
+
|
|
35
|
+
### 2.1 request.ts - Axios 核心封装
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
/**
|
|
39
|
+
* Axios 请求封装
|
|
40
|
+
* 集成 Mock 系统、错误处理、Token 管理
|
|
41
|
+
*
|
|
42
|
+
* 💡 自定义点:
|
|
43
|
+
* - API_TIMEOUT: 超时时间
|
|
44
|
+
* - NO_TOKEN_URLS: 无需 Token 的白名单
|
|
45
|
+
* - TOKEN_KEY/USER_KEY: 存储键名
|
|
46
|
+
* - ERROR_CODE_MAP: 错误码映射
|
|
47
|
+
* - 响应拦截器中的业务状态码判断
|
|
48
|
+
*/
|
|
49
|
+
import axios from 'axios'
|
|
50
|
+
import type { AxiosInstance, AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig } from 'axios'
|
|
51
|
+
// import { ElMessage } from 'element-plus' // Element Plus
|
|
52
|
+
// import { message } from 'ant-design-vue' // Ant Design Vue
|
|
53
|
+
// import { useMessage } from 'naive-ui' // Naive UI
|
|
54
|
+
import router from '@/router'
|
|
55
|
+
import type { ApiResponse } from '@/types/api'
|
|
56
|
+
|
|
57
|
+
// ========== 常量配置(按项目调整)==========
|
|
58
|
+
|
|
59
|
+
const API_TIMEOUT = 30000
|
|
60
|
+
|
|
61
|
+
// 无需 Token 的接口白名单(按项目调整)
|
|
62
|
+
const NO_TOKEN_URLS = [
|
|
63
|
+
'/login',
|
|
64
|
+
'/register',
|
|
65
|
+
'/sms-code',
|
|
66
|
+
'/captcha',
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
// ========== Token 管理(按项目调整键名)==========
|
|
70
|
+
|
|
71
|
+
const TOKEN_KEY = 'app_token'
|
|
72
|
+
const USER_KEY = 'app_user'
|
|
73
|
+
|
|
74
|
+
export const getToken = (): string | null => localStorage.getItem(TOKEN_KEY)
|
|
75
|
+
export const setToken = (token: string): void => localStorage.setItem(TOKEN_KEY, token)
|
|
76
|
+
export const clearToken = (): void => {
|
|
77
|
+
localStorage.removeItem(TOKEN_KEY)
|
|
78
|
+
localStorage.removeItem(USER_KEY)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export const getUserInfo = <T = any>(): T | null => {
|
|
82
|
+
const userStr = localStorage.getItem(USER_KEY)
|
|
83
|
+
if (!userStr) return null
|
|
84
|
+
try {
|
|
85
|
+
return JSON.parse(userStr)
|
|
86
|
+
} catch {
|
|
87
|
+
return null
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export const setUserInfo = (user: any): void => {
|
|
92
|
+
localStorage.setItem(USER_KEY, JSON.stringify(user))
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ========== 消息提示(按 UI 框架调整)==========
|
|
96
|
+
|
|
97
|
+
// 封装消息提示,便于切换 UI 框架
|
|
98
|
+
const showMessage = {
|
|
99
|
+
success: (msg: string) => console.log('✅', msg), // 替换为 UI 框架方法
|
|
100
|
+
error: (msg: string) => console.error('❌', msg),
|
|
101
|
+
warning: (msg: string) => console.warn('⚠️', msg),
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Element Plus 示例:
|
|
105
|
+
// import { ElMessage } from 'element-plus'
|
|
106
|
+
// const showMessage = {
|
|
107
|
+
// success: (msg: string) => ElMessage.success(msg),
|
|
108
|
+
// error: (msg: string) => ElMessage.error(msg),
|
|
109
|
+
// warning: (msg: string) => ElMessage.warning(msg),
|
|
110
|
+
// }
|
|
111
|
+
|
|
112
|
+
// ========== 错误处理 ==========
|
|
113
|
+
|
|
114
|
+
const ERROR_CODE_MAP: Record<number, string> = {
|
|
115
|
+
400: '请求参数错误',
|
|
116
|
+
401: '登录已过期,请重新登录',
|
|
117
|
+
403: '没有操作权限',
|
|
118
|
+
404: '请求的资源不存在',
|
|
119
|
+
500: '服务器内部错误',
|
|
120
|
+
502: '网关错误',
|
|
121
|
+
503: '服务暂不可用',
|
|
122
|
+
504: '网关超时',
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const handleRequestError = (error: any): Promise<never> => {
|
|
126
|
+
console.error('请求错误:', error)
|
|
127
|
+
|
|
128
|
+
if (error.code === 'ERR_NETWORK') {
|
|
129
|
+
showMessage.error('网络连接失败,请检查网络设置')
|
|
130
|
+
} else if (error.code === 'ECONNABORTED') {
|
|
131
|
+
showMessage.error('请求超时,请稍后重试')
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return Promise.reject(error)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const handleResponseError = (error: any): Promise<never> => {
|
|
138
|
+
const status = error?.response?.status
|
|
139
|
+
const data = error?.response?.data
|
|
140
|
+
const message = data?.message || ERROR_CODE_MAP[status] || '请求失败,请稍后重试'
|
|
141
|
+
|
|
142
|
+
if (status === 401) {
|
|
143
|
+
clearToken()
|
|
144
|
+
showMessage.warning(message)
|
|
145
|
+
router.push({ name: 'login' }) // 按项目调整登录路由
|
|
146
|
+
return Promise.reject(error)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
showMessage.error(message)
|
|
150
|
+
return Promise.reject(error)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// ========== Axios 实例 ==========
|
|
154
|
+
|
|
155
|
+
const instance: AxiosInstance = axios.create({
|
|
156
|
+
baseURL: import.meta.env.VITE_API_BASE_URL || '/api',
|
|
157
|
+
timeout: API_TIMEOUT,
|
|
158
|
+
headers: { 'Content-Type': 'application/json' },
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
// 请求拦截器
|
|
162
|
+
instance.interceptors.request.use(
|
|
163
|
+
(config: InternalAxiosRequestConfig) => {
|
|
164
|
+
const needToken = !NO_TOKEN_URLS.some(url => config.url?.includes(url))
|
|
165
|
+
|
|
166
|
+
if (needToken) {
|
|
167
|
+
const token = getToken()
|
|
168
|
+
if (token) {
|
|
169
|
+
// 按后端要求调整 Token 传递方式
|
|
170
|
+
config.headers.Authorization = `Bearer ${token}`
|
|
171
|
+
// config.headers.token = token // 备选方式
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return config
|
|
176
|
+
},
|
|
177
|
+
handleRequestError
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
// 响应拦截器(按后端响应结构调整)
|
|
181
|
+
instance.interceptors.response.use(
|
|
182
|
+
(response: AxiosResponse) => {
|
|
183
|
+
const res = response.data
|
|
184
|
+
|
|
185
|
+
// 文件流直接返回
|
|
186
|
+
if (response.config.responseType === 'blob') {
|
|
187
|
+
return res
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// 业务状态码判断(按后端约定调整)
|
|
191
|
+
// 常见格式:{ code: 0, data: {}, message: '' }
|
|
192
|
+
// 或:{ code: 200, data: {}, msg: '' }
|
|
193
|
+
// 或:{ success: true, data: {}, message: '' }
|
|
194
|
+
if (res.code === 0 || res.code === 200 || res.success === true) {
|
|
195
|
+
return res
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// 401 Token 过期
|
|
199
|
+
if (res.code === 401) {
|
|
200
|
+
clearToken()
|
|
201
|
+
showMessage.warning(res.message || '登录已过期')
|
|
202
|
+
router.push({ name: 'login' })
|
|
203
|
+
return Promise.reject(new Error(res.message))
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// 其他业务错误
|
|
207
|
+
showMessage.error(res.message || res.msg || '请求失败')
|
|
208
|
+
return Promise.reject(new Error(res.message || res.msg))
|
|
209
|
+
},
|
|
210
|
+
handleResponseError
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
// ========== 请求方法 ==========
|
|
214
|
+
|
|
215
|
+
export async function get<T = any>(
|
|
216
|
+
url: string,
|
|
217
|
+
params?: Record<string, any>,
|
|
218
|
+
config?: AxiosRequestConfig
|
|
219
|
+
): Promise<ApiResponse<T>> {
|
|
220
|
+
return instance.get(url, { params, ...config })
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export async function post<T = any>(
|
|
224
|
+
url: string,
|
|
225
|
+
data?: any,
|
|
226
|
+
config?: AxiosRequestConfig
|
|
227
|
+
): Promise<ApiResponse<T>> {
|
|
228
|
+
return instance.post(url, data, config)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export async function put<T = any>(
|
|
232
|
+
url: string,
|
|
233
|
+
data?: any,
|
|
234
|
+
config?: AxiosRequestConfig
|
|
235
|
+
): Promise<ApiResponse<T>> {
|
|
236
|
+
return instance.put(url, data, config)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export async function del<T = any>(
|
|
240
|
+
url: string,
|
|
241
|
+
params?: Record<string, any>,
|
|
242
|
+
config?: AxiosRequestConfig
|
|
243
|
+
): Promise<ApiResponse<T>> {
|
|
244
|
+
return instance.delete(url, { params, ...config })
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// ========== 工具函数 ==========
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* 包装异步请求,返回 [data, error] 元组
|
|
251
|
+
* 借鉴 Go 语言错误处理风格,避免 try/catch
|
|
252
|
+
*
|
|
253
|
+
* @example
|
|
254
|
+
* const [res, err] = await wrap(api.getList(params))
|
|
255
|
+
* if (err) {
|
|
256
|
+
* console.error('请求失败:', err)
|
|
257
|
+
* return
|
|
258
|
+
* }
|
|
259
|
+
* // 使用 res.data
|
|
260
|
+
*/
|
|
261
|
+
export async function wrap<T>(
|
|
262
|
+
promise: Promise<T>
|
|
263
|
+
): Promise<[T, null] | [null, Error]> {
|
|
264
|
+
try {
|
|
265
|
+
const data = await promise
|
|
266
|
+
return [data, null]
|
|
267
|
+
} catch (error) {
|
|
268
|
+
return [null, error as Error]
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* 下载文件
|
|
274
|
+
*/
|
|
275
|
+
export async function downloadFile(
|
|
276
|
+
url: string,
|
|
277
|
+
params?: Record<string, any>,
|
|
278
|
+
filename?: string
|
|
279
|
+
): Promise<void> {
|
|
280
|
+
const response = await instance.get(url, {
|
|
281
|
+
params,
|
|
282
|
+
responseType: 'blob',
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
const blob = new Blob([response as any])
|
|
286
|
+
const link = document.createElement('a')
|
|
287
|
+
link.href = URL.createObjectURL(blob)
|
|
288
|
+
link.download = filename || 'download'
|
|
289
|
+
link.click()
|
|
290
|
+
URL.revokeObjectURL(link.href)
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* 上传文件
|
|
295
|
+
*/
|
|
296
|
+
export async function uploadFile<T = any>(
|
|
297
|
+
url: string,
|
|
298
|
+
file: File,
|
|
299
|
+
fieldName = 'file',
|
|
300
|
+
extraData?: Record<string, any>
|
|
301
|
+
): Promise<ApiResponse<T>> {
|
|
302
|
+
const formData = new FormData()
|
|
303
|
+
formData.append(fieldName, file)
|
|
304
|
+
|
|
305
|
+
if (extraData) {
|
|
306
|
+
Object.entries(extraData).forEach(([key, value]) => {
|
|
307
|
+
formData.append(key, value)
|
|
308
|
+
})
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return instance.post(url, formData, {
|
|
312
|
+
headers: { 'Content-Type': 'multipart/form-data' },
|
|
313
|
+
})
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
export default instance
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
### 2.2 mock/index.ts - Mock 系统核心
|
|
320
|
+
|
|
321
|
+
```typescript
|
|
322
|
+
// Mock 系统入口
|
|
323
|
+
import type { ApiResponse, PageData } from '@/types/common'
|
|
324
|
+
|
|
325
|
+
export const MOCK_ENABLED = import.meta.env.VITE_MOCK_ENABLED === 'true'
|
|
326
|
+
export const MOCK_DELAY = 300
|
|
327
|
+
|
|
328
|
+
export const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms))
|
|
329
|
+
export const delay = sleep
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* 生成分页响应数据
|
|
333
|
+
*/
|
|
334
|
+
export function generatePageResponse<T>(
|
|
335
|
+
list: T[],
|
|
336
|
+
page: number,
|
|
337
|
+
pageSize: number,
|
|
338
|
+
total?: number
|
|
339
|
+
): ApiResponse<PageData<T>> {
|
|
340
|
+
const start = (page - 1) * pageSize
|
|
341
|
+
const end = start + pageSize
|
|
342
|
+
const data = list.slice(start, end)
|
|
343
|
+
return {
|
|
344
|
+
code: 0,
|
|
345
|
+
data: { list: data, total: total ?? list.length, page, pageSize },
|
|
346
|
+
message: 'success',
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* 生成成功响应
|
|
352
|
+
*/
|
|
353
|
+
export function successResponse<T>(data: T): ApiResponse<T> {
|
|
354
|
+
return { code: 0, data, message: 'success' }
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* 生成错误响应
|
|
359
|
+
*/
|
|
360
|
+
export function errorResponse(message: string, code = -1) {
|
|
361
|
+
return { code, data: null, message }
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* 生成随机 ID
|
|
366
|
+
*/
|
|
367
|
+
export function randomId(): string {
|
|
368
|
+
return Date.now().toString(36) + Math.random().toString(36).substr(2, 9)
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* 生成随机日期(近 n 天内)
|
|
373
|
+
*/
|
|
374
|
+
export function randomDate(days = 30): string {
|
|
375
|
+
const date = new Date()
|
|
376
|
+
date.setDate(date.getDate() - Math.floor(Math.random() * days))
|
|
377
|
+
return date.toISOString().split('T')[0] + ' ' + date.toTimeString().split(' ')[0]
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* 从数组中随机选择
|
|
382
|
+
*/
|
|
383
|
+
export function randomPick<T>(arr: T[]): T {
|
|
384
|
+
return arr[Math.floor(Math.random() * arr.length)]
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* 生成随机金额
|
|
389
|
+
*/
|
|
390
|
+
export function randomAmount(min = 100, max = 10000): number {
|
|
391
|
+
return Number((Math.random() * (max - min) + min).toFixed(2))
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// 导出各模块 Mock
|
|
395
|
+
export * from './auth'
|
|
396
|
+
// export * from './customer'
|
|
397
|
+
// export * from './finance'
|
|
398
|
+
// export * from './system'
|
|
399
|
+
// export * from './transaction'
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
### 2.3 API 模块示例 - modules/[module].ts
|
|
403
|
+
|
|
404
|
+
```typescript
|
|
405
|
+
/**
|
|
406
|
+
* [模块名] API
|
|
407
|
+
*
|
|
408
|
+
* 💡 创建新模块时复制此模板,替换:
|
|
409
|
+
* - 模块名称和描述
|
|
410
|
+
* - URLs 对象中的接口地址
|
|
411
|
+
* - 类型定义
|
|
412
|
+
* - 具体的 API 函数
|
|
413
|
+
*/
|
|
414
|
+
import { post, get, put, del } from '../request'
|
|
415
|
+
import { MOCK_ENABLED } from '../mock'
|
|
416
|
+
// import * as mockModule from '../mock/[module]' // 对应的 Mock
|
|
417
|
+
import type { ApiResponse, PageResponse, PageParams } from '@/types/api'
|
|
418
|
+
|
|
419
|
+
// ========== 接口地址(按实际后端调整)==========
|
|
420
|
+
|
|
421
|
+
const URLs = {
|
|
422
|
+
list: '/xxx/list',
|
|
423
|
+
detail: '/xxx/detail',
|
|
424
|
+
create: '/xxx/create',
|
|
425
|
+
update: '/xxx/update',
|
|
426
|
+
delete: '/xxx/delete',
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// ========== 类型定义(按实际业务调整)==========
|
|
430
|
+
|
|
431
|
+
export interface ItemData {
|
|
432
|
+
id: string
|
|
433
|
+
name: string
|
|
434
|
+
status: string
|
|
435
|
+
createdAt: string
|
|
436
|
+
// ... 其他字段
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
export interface CreateParams {
|
|
440
|
+
name: string
|
|
441
|
+
// ... 其他参数
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
export interface ListParams extends PageParams {
|
|
445
|
+
keyword?: string
|
|
446
|
+
status?: string
|
|
447
|
+
// ... 其他筛选条件
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// ========== 接口实现 ==========
|
|
451
|
+
|
|
452
|
+
/** 获取列表 */
|
|
453
|
+
export async function getList(params: ListParams): Promise<PageResponse<ItemData>> {
|
|
454
|
+
// if (MOCK_ENABLED) return mockModule.mockGetList(params)
|
|
455
|
+
return get(URLs.list, params)
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/** 获取详情 */
|
|
459
|
+
export async function getDetail(id: string): Promise<ApiResponse<ItemData>> {
|
|
460
|
+
// if (MOCK_ENABLED) return mockModule.mockGetDetail(id)
|
|
461
|
+
return get(URLs.detail, { id })
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/** 创建 */
|
|
465
|
+
export async function create(data: CreateParams): Promise<ApiResponse<ItemData>> {
|
|
466
|
+
// if (MOCK_ENABLED) return mockModule.mockCreate(data)
|
|
467
|
+
return post(URLs.create, data)
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/** 更新 */
|
|
471
|
+
export async function update(id: string, data: Partial<CreateParams>): Promise<ApiResponse<ItemData>> {
|
|
472
|
+
// if (MOCK_ENABLED) return mockModule.mockUpdate(id, data)
|
|
473
|
+
return put(`${URLs.update}/${id}`, data)
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/** 删除 */
|
|
477
|
+
export async function remove(id: string): Promise<ApiResponse<null>> {
|
|
478
|
+
// if (MOCK_ENABLED) return mockModule.mockDelete(id)
|
|
479
|
+
return del(URLs.delete, { id })
|
|
480
|
+
}
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
### 2.4 modules/index.ts - 统一导出
|
|
484
|
+
|
|
485
|
+
```typescript
|
|
486
|
+
/**
|
|
487
|
+
* API 模块统一导出
|
|
488
|
+
*
|
|
489
|
+
* 💡 添加新模块时:
|
|
490
|
+
* 1. 创建 modules/[module].ts
|
|
491
|
+
* 2. 在此处添加导入和导出
|
|
492
|
+
*/
|
|
493
|
+
|
|
494
|
+
// 示例:导出各业务模块
|
|
495
|
+
// export * as userApi from './user'
|
|
496
|
+
// export * as orderApi from './order'
|
|
497
|
+
// export * as productApi from './product'
|
|
498
|
+
|
|
499
|
+
// 按项目实际模块调整...
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
### 2.5 index.ts - API 统一入口
|
|
503
|
+
|
|
504
|
+
```typescript
|
|
505
|
+
/**
|
|
506
|
+
* API 统一入口
|
|
507
|
+
*
|
|
508
|
+
* 使用方式:
|
|
509
|
+
*
|
|
510
|
+
* 1. 直接导入模块使用
|
|
511
|
+
* import { userApi } from '@/api'
|
|
512
|
+
* const res = await userApi.getList(params)
|
|
513
|
+
*
|
|
514
|
+
* 2. 使用 wrap 函数处理错误(推荐)
|
|
515
|
+
* import { wrap, userApi } from '@/api'
|
|
516
|
+
* const [res, err] = await wrap(userApi.getList(params))
|
|
517
|
+
* if (err) return
|
|
518
|
+
*
|
|
519
|
+
* 3. 直接使用请求方法
|
|
520
|
+
* import { get, post } from '@/api'
|
|
521
|
+
* const res = await post('/custom/url', data)
|
|
522
|
+
*/
|
|
523
|
+
|
|
524
|
+
// 导出基础请求方法
|
|
525
|
+
export { get, post, put, del, wrap, downloadFile, uploadFile } from './request'
|
|
526
|
+
export { default as axios } from './request'
|
|
527
|
+
|
|
528
|
+
// 导出 Token 管理方法
|
|
529
|
+
export { getToken, setToken, clearToken, getUserInfo, setUserInfo } from './request'
|
|
530
|
+
|
|
531
|
+
// 导出所有 API 模块
|
|
532
|
+
export * from './modules'
|
|
533
|
+
|
|
534
|
+
// 导出 Mock 配置
|
|
535
|
+
export { MOCK_ENABLED } from './mock'
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
---
|
|
539
|
+
|
|
540
|
+
## 三、类型定义模板
|
|
541
|
+
|
|
542
|
+
### 3.1 types/api.ts
|
|
543
|
+
|
|
544
|
+
```typescript
|
|
545
|
+
/**
|
|
546
|
+
* API 通用类型定义
|
|
547
|
+
*
|
|
548
|
+
* 💡 按后端实际响应结构调整字段名
|
|
549
|
+
*/
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* API 响应基础结构
|
|
553
|
+
* 常见格式:
|
|
554
|
+
* - { code: 0, data: T, message: '' }
|
|
555
|
+
* - { code: 200, data: T, msg: '' }
|
|
556
|
+
* - { success: true, data: T, message: '' }
|
|
557
|
+
*/
|
|
558
|
+
export interface ApiResponse<T = any> {
|
|
559
|
+
code: number
|
|
560
|
+
data: T
|
|
561
|
+
message: string
|
|
562
|
+
// msg?: string // 备选字段名
|
|
563
|
+
// success?: boolean // 备选判断字段
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
/**
|
|
567
|
+
* 分页请求参数
|
|
568
|
+
* 常见字段名:page/pageNum/current, pageSize/size/limit
|
|
569
|
+
*/
|
|
570
|
+
export interface PageParams {
|
|
571
|
+
page: number
|
|
572
|
+
pageSize: number
|
|
573
|
+
// pageNum?: number // 备选字段名
|
|
574
|
+
// current?: number // 备选字段名
|
|
575
|
+
// size?: number // 备选字段名
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
/**
|
|
579
|
+
* 分页响应数据
|
|
580
|
+
* 常见字段名:list/records/items, total/totalCount
|
|
581
|
+
*/
|
|
582
|
+
export interface PageData<T> {
|
|
583
|
+
list: T[]
|
|
584
|
+
total: number
|
|
585
|
+
page: number
|
|
586
|
+
pageSize: number
|
|
587
|
+
// records?: T[] // 备选字段名
|
|
588
|
+
// items?: T[] // 备选字段名
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
/**
|
|
592
|
+
* 分页响应(完整)
|
|
593
|
+
*/
|
|
594
|
+
export type PageResponse<T> = ApiResponse<PageData<T>>
|
|
595
|
+
|
|
596
|
+
/**
|
|
597
|
+
* 通用 ID 类型(按项目调整)
|
|
598
|
+
*/
|
|
599
|
+
export type ID = string | number
|
|
600
|
+
|
|
601
|
+
/**
|
|
602
|
+
* 通用状态枚举示例
|
|
603
|
+
*/
|
|
604
|
+
export type Status = 'active' | 'inactive' | 'pending' | 'deleted'
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
---
|
|
608
|
+
|
|
609
|
+
## 四、环境变量配置
|
|
610
|
+
|
|
611
|
+
### .env.development
|
|
612
|
+
|
|
613
|
+
```bash
|
|
614
|
+
VITE_API_BASE_URL=/api
|
|
615
|
+
VITE_MOCK_ENABLED=true
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
### .env.production
|
|
619
|
+
|
|
620
|
+
```bash
|
|
621
|
+
VITE_API_BASE_URL=https://api.example.com
|
|
622
|
+
VITE_MOCK_ENABLED=false
|
|
623
|
+
```
|
|
624
|
+
|
|
625
|
+
---
|
|
626
|
+
|
|
627
|
+
## 五、使用示例
|
|
628
|
+
|
|
629
|
+
### 5.1 基础调用
|
|
630
|
+
|
|
631
|
+
```typescript
|
|
632
|
+
import { userApi } from '@/api'
|
|
633
|
+
|
|
634
|
+
const handleSubmit = async () => {
|
|
635
|
+
try {
|
|
636
|
+
const res = await userApi.create(formData)
|
|
637
|
+
if (res.code === 0) {
|
|
638
|
+
// 成功处理
|
|
639
|
+
}
|
|
640
|
+
} catch (err) {
|
|
641
|
+
console.error(err)
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
```
|
|
645
|
+
|
|
646
|
+
### 5.2 使用 wrap 函数(推荐)
|
|
647
|
+
|
|
648
|
+
```typescript
|
|
649
|
+
import { wrap, userApi } from '@/api'
|
|
650
|
+
|
|
651
|
+
const handleSubmit = async () => {
|
|
652
|
+
const [res, err] = await wrap(userApi.create(formData))
|
|
653
|
+
|
|
654
|
+
if (err) {
|
|
655
|
+
console.error('操作失败:', err)
|
|
656
|
+
return
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// 成功处理
|
|
660
|
+
router.push('/list')
|
|
661
|
+
}
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
### 5.3 列表页面通用模式
|
|
665
|
+
|
|
666
|
+
```typescript
|
|
667
|
+
import { wrap } from '@/api'
|
|
668
|
+
import { xxxApi } from '@/api' // 替换为实际模块
|
|
669
|
+
|
|
670
|
+
// 状态
|
|
671
|
+
const loading = ref(false)
|
|
672
|
+
const list = ref<ItemData[]>([])
|
|
673
|
+
const total = ref(0)
|
|
674
|
+
const currentPage = ref(1)
|
|
675
|
+
const pageSize = ref(10)
|
|
676
|
+
const searchParams = ref({})
|
|
677
|
+
|
|
678
|
+
// 获取列表
|
|
679
|
+
const fetchList = async () => {
|
|
680
|
+
loading.value = true
|
|
681
|
+
|
|
682
|
+
const [res, err] = await wrap(xxxApi.getList({
|
|
683
|
+
page: currentPage.value,
|
|
684
|
+
pageSize: pageSize.value,
|
|
685
|
+
...searchParams.value
|
|
686
|
+
}))
|
|
687
|
+
|
|
688
|
+
loading.value = false
|
|
689
|
+
|
|
690
|
+
if (err) return
|
|
691
|
+
|
|
692
|
+
list.value = res.data.list
|
|
693
|
+
total.value = res.data.total
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
// 删除
|
|
697
|
+
const handleDelete = async (id: string) => {
|
|
698
|
+
const [, err] = await wrap(xxxApi.remove(id))
|
|
699
|
+
if (err) return
|
|
700
|
+
|
|
701
|
+
// 刷新列表
|
|
702
|
+
fetchList()
|
|
703
|
+
}
|
|
704
|
+
```
|
|
705
|
+
|
|
706
|
+
---
|
|
707
|
+
|
|
708
|
+
## 六、Mock 数据编写规范
|
|
709
|
+
|
|
710
|
+
### 6.1 基本结构
|
|
711
|
+
|
|
712
|
+
```typescript
|
|
713
|
+
// mock/[module].ts - Mock 模块模板
|
|
714
|
+
import { successResponse, errorResponse, generatePageResponse, randomId, delay } from './index'
|
|
715
|
+
import type { PageParams } from '@/types/api'
|
|
716
|
+
|
|
717
|
+
// 模拟数据类型
|
|
718
|
+
interface MockItem {
|
|
719
|
+
id: string
|
|
720
|
+
name: string
|
|
721
|
+
status: string
|
|
722
|
+
createdAt: string
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
// 数据缓存
|
|
726
|
+
let dataCache: MockItem[] | null = null
|
|
727
|
+
|
|
728
|
+
// 生成单条数据
|
|
729
|
+
function generateItem(index: number): MockItem {
|
|
730
|
+
return {
|
|
731
|
+
id: randomId(),
|
|
732
|
+
name: `项目${index}`,
|
|
733
|
+
status: randomPick(['active', 'inactive']),
|
|
734
|
+
createdAt: randomDate(30)
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
// 获取数据(带缓存)
|
|
739
|
+
function getData(): MockItem[] {
|
|
740
|
+
if (!dataCache) {
|
|
741
|
+
dataCache = Array.from({ length: 50 }, (_, i) => generateItem(i + 1))
|
|
742
|
+
}
|
|
743
|
+
return dataCache
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
/** 模拟获取列表 */
|
|
747
|
+
export async function mockGetList(params: PageParams & { keyword?: string }) {
|
|
748
|
+
await delay(300)
|
|
749
|
+
|
|
750
|
+
let data = getData()
|
|
751
|
+
|
|
752
|
+
// 筛选(按实际需求调整)
|
|
753
|
+
if (params.keyword) {
|
|
754
|
+
data = data.filter(item => item.name.includes(params.keyword!))
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
return generatePageResponse(data, params.page || 1, params.pageSize || 10)
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
/** 模拟获取详情 */
|
|
761
|
+
export async function mockGetDetail(id: string) {
|
|
762
|
+
await delay(200)
|
|
763
|
+
|
|
764
|
+
const item = getData().find(d => d.id === id)
|
|
765
|
+
if (!item) return errorResponse('数据不存在')
|
|
766
|
+
|
|
767
|
+
return successResponse(item)
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
/** 模拟创建 */
|
|
771
|
+
export async function mockCreate(data: Partial<MockItem>) {
|
|
772
|
+
await delay(300)
|
|
773
|
+
|
|
774
|
+
const newItem = { ...generateItem(getData().length + 1), ...data }
|
|
775
|
+
getData().unshift(newItem)
|
|
776
|
+
|
|
777
|
+
return successResponse(newItem)
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
/** 模拟更新 */
|
|
781
|
+
export async function mockUpdate(id: string, data: Partial<MockItem>) {
|
|
782
|
+
await delay(300)
|
|
783
|
+
|
|
784
|
+
const list = getData()
|
|
785
|
+
const index = list.findIndex(d => d.id === id)
|
|
786
|
+
if (index === -1) return errorResponse('数据不存在')
|
|
787
|
+
|
|
788
|
+
list[index] = { ...list[index], ...data }
|
|
789
|
+
return successResponse(list[index])
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
/** 模拟删除 */
|
|
793
|
+
export async function mockDelete(id: string) {
|
|
794
|
+
await delay(200)
|
|
795
|
+
|
|
796
|
+
const list = getData()
|
|
797
|
+
const index = list.findIndex(d => d.id === id)
|
|
798
|
+
if (index === -1) return errorResponse('数据不存在')
|
|
799
|
+
|
|
800
|
+
list.splice(index, 1)
|
|
801
|
+
return successResponse(null)
|
|
802
|
+
}
|
|
803
|
+
```
|
|
804
|
+
|
|
805
|
+
### 6.2 登录认证 Mock 示例
|
|
806
|
+
|
|
807
|
+
```typescript
|
|
808
|
+
// mock/auth.ts
|
|
809
|
+
import { successResponse, errorResponse, randomId, delay } from './index'
|
|
810
|
+
|
|
811
|
+
// 模拟用户(按项目调整)
|
|
812
|
+
const mockUsers: Record<string, { password: string; user: any }> = {
|
|
813
|
+
admin: {
|
|
814
|
+
password: '123456',
|
|
815
|
+
user: { id: '1', username: 'admin', name: '管理员', roles: ['admin'] }
|
|
816
|
+
},
|
|
817
|
+
user: {
|
|
818
|
+
password: '123456',
|
|
819
|
+
user: { id: '2', username: 'user', name: '普通用户', roles: ['user'] }
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
const tokenStore = new Map<string, any>()
|
|
824
|
+
|
|
825
|
+
export async function mockLogin(username: string, password: string) {
|
|
826
|
+
await delay(500)
|
|
827
|
+
|
|
828
|
+
const userData = mockUsers[username]
|
|
829
|
+
if (!userData || userData.password !== password) {
|
|
830
|
+
return errorResponse('用户名或密码错误')
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
const token = `mock_${randomId()}`
|
|
834
|
+
tokenStore.set(token, userData.user)
|
|
835
|
+
|
|
836
|
+
return successResponse({ token, user: userData.user })
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
export async function mockGetUserInfo(token: string) {
|
|
840
|
+
await delay(200)
|
|
841
|
+
|
|
842
|
+
const user = tokenStore.get(token)
|
|
843
|
+
if (!user) return errorResponse('Token 无效', 401)
|
|
844
|
+
|
|
845
|
+
return successResponse(user)
|
|
846
|
+
}
|
|
847
|
+
```
|
|
848
|
+
|
|
849
|
+
---
|
|
850
|
+
|
|
851
|
+
## 七、最佳实践
|
|
852
|
+
|
|
853
|
+
### 7.1 API 模块组织
|
|
854
|
+
|
|
855
|
+
- 每个业务模块一个文件,按实际需求命名
|
|
856
|
+
- URLs 集中定义在文件顶部
|
|
857
|
+
- 类型定义紧跟 URLs
|
|
858
|
+
- Mock 判断在函数内部
|
|
859
|
+
|
|
860
|
+
### 7.2 错误处理
|
|
861
|
+
|
|
862
|
+
- 使用 `wrap` 函数统一处理
|
|
863
|
+
- 避免重复的 try/catch
|
|
864
|
+
- 错误信息由拦截器统一展示
|
|
865
|
+
|
|
866
|
+
### 7.3 Mock 开发
|
|
867
|
+
|
|
868
|
+
- Mock 函数与真实 API 返回结构一致
|
|
869
|
+
- 使用缓存避免重复生成数据
|
|
870
|
+
- 支持基本的筛选和分页
|
|
871
|
+
- 登录 Mock 要模拟 Token 存储
|
|
872
|
+
|
|
873
|
+
### 7.4 类型安全
|
|
874
|
+
|
|
875
|
+
- 所有 API 函数都有返回类型
|
|
876
|
+
- 参数使用 interface 定义
|
|
877
|
+
- 避免使用 any
|
|
878
|
+
|
|
879
|
+
### 7.5 适配不同后端
|
|
880
|
+
|
|
881
|
+
**响应结构适配**:
|
|
882
|
+
```typescript
|
|
883
|
+
// 后端 A:{ code: 0, data, message }
|
|
884
|
+
// 后端 B:{ code: 200, data, msg }
|
|
885
|
+
// 后端 C:{ success: true, data, message }
|
|
886
|
+
|
|
887
|
+
// 在响应拦截器中统一处理
|
|
888
|
+
if (res.code === 0 || res.code === 200 || res.success === true) {
|
|
889
|
+
return res
|
|
890
|
+
}
|
|
891
|
+
```
|
|
892
|
+
|
|
893
|
+
**分页参数适配**:
|
|
894
|
+
```typescript
|
|
895
|
+
// 封装转换函数
|
|
896
|
+
function toBackendPageParams(params: PageParams) {
|
|
897
|
+
return {
|
|
898
|
+
pageNum: params.page, // 或 current
|
|
899
|
+
pageSize: params.pageSize // 或 size
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
```
|
|
903
|
+
|
|
904
|
+
---
|
|
905
|
+
|
|
906
|
+
## 八、快速接入清单
|
|
907
|
+
|
|
908
|
+
新项目接入此模式时,按以下步骤操作:
|
|
909
|
+
|
|
910
|
+
### 8.1 使用模板(推荐)
|
|
911
|
+
|
|
912
|
+
直接复制模板目录 `templates/vue/api-layer/` 到项目 `src/api/`:
|
|
913
|
+
|
|
914
|
+
```bash
|
|
915
|
+
# 模板位置
|
|
916
|
+
copilot-prompts/templates/vue/api-layer/
|
|
917
|
+
├── _CONFIG.md # 配置说明(无需复制)
|
|
918
|
+
├── request.ts # ← 复制
|
|
919
|
+
├── types.ts # ← 复制
|
|
920
|
+
├── index.ts # ← 复制
|
|
921
|
+
├── mock/index.ts # ← 复制
|
|
922
|
+
└── modules/
|
|
923
|
+
├── index.ts # ← 复制
|
|
924
|
+
└── _template.ts # ← 复制后重命名为业务模块
|
|
925
|
+
```
|
|
926
|
+
|
|
927
|
+
详细配置说明见:[templates/vue/api-layer/_CONFIG.md](../../../templates/vue/api-layer/_CONFIG.md)
|
|
928
|
+
|
|
929
|
+
### 8.2 手动创建
|
|
930
|
+
|
|
931
|
+
如需从零开始,确保创建以下文件:
|
|
932
|
+
|
|
933
|
+
- [ ] `src/api/request.ts` - 核心封装
|
|
934
|
+
- [ ] `src/api/types.ts` - 类型定义
|
|
935
|
+
- [ ] `src/api/mock/index.ts` - Mock 工具
|
|
936
|
+
- [ ] `src/api/modules/index.ts` - 模块导出
|
|
937
|
+
- [ ] `src/api/index.ts` - 统一入口
|
|
938
|
+
|
|
939
|
+
### 8.3 按项目调整
|
|
940
|
+
|
|
941
|
+
- [ ] `request.ts` 中的消息提示方法(适配 UI 框架)
|
|
942
|
+
- [ ] `request.ts` 中的 Token 传递方式(按后端要求)
|
|
943
|
+
- [ ] `request.ts` 中的业务状态码判断(按后端约定)
|
|
944
|
+
- [ ] `types/api.ts` 中的响应结构(按后端格式)
|
|
945
|
+
- [ ] `.env` 中的环境变量
|
|
946
|
+
|
|
947
|
+
### 8.3 创建业务模块
|
|
948
|
+
|
|
949
|
+
- [ ] 根据业务需求创建 `modules/xxx.ts`
|
|
950
|
+
- [ ] 对应创建 `mock/xxx.ts`(如需要)
|
|
951
|
+
- [ ] 在 `modules/index.ts` 中导出
|
|
952
|
+
|
|
953
|
+
---
|
|
954
|
+
|
|
955
|
+
**适用技术栈**: Vue 3 + TypeScript + 任意 UI 框架 + Vite/Webpack
|
|
956
|
+
**维护者**: MTA工作室
|
|
957
|
+
**创建日期**: 2025-12-25
|
|
958
|
+
**版本**: v1.1(通用化版本)
|