listpage_cli 0.0.298 → 0.0.300
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/package.json +1 -1
- package/templates/backend-template/package.json.tmpl +1 -1
- package/templates/frontend-template/package.json.tmpl +2 -2
- package/templates/skills-template/listpage-http/SKILL.md +69 -0
- package/templates/skills-template/listpage-http/api.md +104 -0
- package/templates/skills-template/listpage-http/examples.md +257 -0
package/package.json
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"dependencies": {
|
|
13
13
|
"react": "^19.2.0",
|
|
14
14
|
"react-dom": "^19.2.0",
|
|
15
|
-
"listpage-next": "~0.0.
|
|
15
|
+
"listpage-next": "~0.0.300",
|
|
16
16
|
"react-router-dom": ">=6.0.0",
|
|
17
17
|
"@ant-design/v5-patch-for-react-19": "~1.0.3",
|
|
18
18
|
"ahooks": "^3.9.5",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"styled-components": "^6.1.19",
|
|
24
24
|
"mobx": "~6.15.0",
|
|
25
25
|
"@ant-design/icons": "~6.0.2",
|
|
26
|
-
"listpage-components": "~0.0.
|
|
26
|
+
"listpage-components": "~0.0.300",
|
|
27
27
|
"lucide-react": "~0.575.0"
|
|
28
28
|
"mobx-react-lite": "~4.1.1"
|
|
29
29
|
},
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: listpage-http
|
|
3
|
+
description: 使用 listpage-http 在前端项目中接管所有 HTTP 请求,统一通过 src/api 目录的 request-config 与 client 调用。Use when user mentions listpage-http, request-config, api client or统一 HTTP 请求管理.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# listpage-http 使用技能
|
|
7
|
+
|
|
8
|
+
详细 API 说明与代码示例见 [api.md](api.md)、[examples.md](examples.md)。
|
|
9
|
+
|
|
10
|
+
## 总体目标
|
|
11
|
+
|
|
12
|
+
在新项目中用 `listpage-http` 接管所有 HTTP 请求,形成统一、可追踪的接口层:
|
|
13
|
+
|
|
14
|
+
- 所有后端接口 **必须在一个 request-config 映射表中集中注册**。
|
|
15
|
+
- 所有业务代码 **只能通过统一的 client 调用接口**。
|
|
16
|
+
- 每个业务模块在 `src/api` 下有对应的「类型文件」,只负责声明请求/响应类型。
|
|
17
|
+
- **接口报错已由此 API 层接管**(通过 `onError`、未授权跳转等统一处理),业务层**不需要也不应再**在调用处捕获接口错误(如对 `api.xxx.yyy()` 包一层 try/catch 专门处理接口失败)。
|
|
18
|
+
|
|
19
|
+
## 目录规范
|
|
20
|
+
|
|
21
|
+
推荐统一目录结构(可按项目实际略调,但语义不变):
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
src/
|
|
25
|
+
api/
|
|
26
|
+
request-config.ts # 用 defineEndpoint 注册所有接口
|
|
27
|
+
index.ts # createApiClient,默认导出 client
|
|
28
|
+
user.ts # user 模块:只放接口的 Req/Res 类型
|
|
29
|
+
order.ts # 其他模块同理……
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
规范要求:
|
|
33
|
+
|
|
34
|
+
1. **`src/api/*.ts`(如 `user.ts`)**
|
|
35
|
+
- 只定义对应模块的 **请求体类型 (ReqXXX)** 和 **响应数据类型 (ResXXX)**。
|
|
36
|
+
- 如果类型过多,可以拆分到子目录,最终由该模块 `index.ts` 统一导出类型。
|
|
37
|
+
- 不在这里直接发请求,不引入 `createApiClient`。
|
|
38
|
+
|
|
39
|
+
2. **`src/api/request-config.ts`**
|
|
40
|
+
- 作为**唯一的接口映射表**。
|
|
41
|
+
- 使用 `defineEndpoint<Req, Res>()` 注册所有后端接口。
|
|
42
|
+
- 新增接口时,必须先在这里补配置,保证接口「一表可查」。
|
|
43
|
+
|
|
44
|
+
3. **`src/api/index.ts`**
|
|
45
|
+
- 使用 `createApiClient(requestConfig, clientOptions)` 创建客户端。
|
|
46
|
+
- **默认导出 client**,供业务代码通过 `import api from '@/api'` 使用。
|
|
47
|
+
- 负责配置 baseURL、tokenKey、默认超时、成功码、未授权处理、**统一错误处理(onError)**等;接口报错由此层统一接管,业务层无需再捕获。
|
|
48
|
+
|
|
49
|
+
## 使用流程(6 步)
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
Task Progress:
|
|
53
|
+
- [ ] 步骤 1:为模块创建类型文件(如 src/api/user.ts),定义 ReqXXX / ResXXX
|
|
54
|
+
- [ ] 步骤 2:在 src/api/request-config.ts 中用 defineEndpoint 注册接口
|
|
55
|
+
- [ ] 步骤 3:在 src/api/index.ts 中用 createApiClient 创建并导出 client
|
|
56
|
+
- [ ] 步骤 4:在 service / hooks 中通过 api.xxx.yyy 调用接口
|
|
57
|
+
- [ ] 步骤 5:禁止业务代码直接使用 fetch / axios 等绕过 api 目录
|
|
58
|
+
- [ ] 步骤 6:新增接口时,始终遵循「类型 → request-config → 业务调用」顺序
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## 命名与模块约定
|
|
62
|
+
|
|
63
|
+
- **文件命名**:统一使用短横线间隔(kebab-case),例如:`request-config.ts`、`user-profile.ts`、`order-detail.ts`。
|
|
64
|
+
- **模块名**:`requestConfig` 中一级 key 建议与模块文件名一致,如 `user`、`order`。
|
|
65
|
+
- **方法名**:使用动词短语小驼峰,如 `login`、`list`、`detail`、`create`、`update`、`remove`。
|
|
66
|
+
- **类型命名**:
|
|
67
|
+
- 请求体:`ReqLogin`、`ReqUserList` 等。
|
|
68
|
+
- 响应数据:`ResLogin`、`ResUserList` 等。
|
|
69
|
+
- **响应 envelope**:后端返回一般为 `{ code, message, data }`,`listpage-http` 用 `ApiEnvelope<T>` 表示,内部会按 `successCodes`、`unauthorizedCodes` 解析。
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# API(listpage-http)
|
|
2
|
+
|
|
3
|
+
基于浏览器原生 `fetch` 的配置驱动 HTTP 客户端,统一处理 JSON 请求与 SSE 流。
|
|
4
|
+
|
|
5
|
+
## 核心类型与方法
|
|
6
|
+
|
|
7
|
+
来自 `listpage-http` 的主要导出(见 `src/index.ts`):
|
|
8
|
+
|
|
9
|
+
- **类型**
|
|
10
|
+
- `ClientOptions`:创建客户端时的全局配置。
|
|
11
|
+
- `RequestOptions`:单次调用时的可选配置(超时、headers、缓存等)。
|
|
12
|
+
- `EndpointConfig<Req, Res>`:单个接口配置泛型。
|
|
13
|
+
- `EndpointMode`:`"json" | "upload" | "download" | "sse"`。
|
|
14
|
+
- `ApiEnvelope<T>`:后端 `{ code, message, data }` 统一模型。
|
|
15
|
+
- **配置工具**
|
|
16
|
+
- `defineEndpoint<Req, Res>(config: EndpointConfig<Req, Res>)`:声明一个接口。
|
|
17
|
+
- **客户端**
|
|
18
|
+
- `createApiClient<Cfg>(config: Cfg, clientOptions: ClientOptions): ApiClient<Cfg>`
|
|
19
|
+
- `ApiClient<Cfg>`:根据 `requestConfig` 推导出的类型安全客户端。
|
|
20
|
+
|
|
21
|
+
### EndpointConfig<Req, Res>
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
export type EndpointConfig<Req, Res> = {
|
|
25
|
+
method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
|
26
|
+
path: string;
|
|
27
|
+
defaultOptions?: RequestOptions;
|
|
28
|
+
type?: "http" | "sse";
|
|
29
|
+
mode?: "json" | "upload" | "download" | "sse";
|
|
30
|
+
authRequired?: boolean; // 默认 true
|
|
31
|
+
};
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### defineEndpoint
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
export function defineEndpoint<Req, Res>(
|
|
38
|
+
config: EndpointConfig<Req, Res>
|
|
39
|
+
): EndpointConfig<Req, Res>;
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
用途:
|
|
43
|
+
|
|
44
|
+
- 声明接口的 HTTP 方法、路径、是否 SSE、是否需要鉴权,以及数据模式(`mode`)。
|
|
45
|
+
- 当 `mode === "upload"` 时,请求体参数会被视为普通对象,并在内部自动转换为 `FormData` 进行 `multipart/form-data` 上传。
|
|
46
|
+
- 绑定请求泛型 `Req` 与响应泛型 `Res`,后续通过 `ApiClient` 自动推导。
|
|
47
|
+
|
|
48
|
+
### ClientOptions
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
export interface ClientOptions {
|
|
52
|
+
baseURL: string;
|
|
53
|
+
tokenKey?: string;
|
|
54
|
+
defaultTimeout?: number;
|
|
55
|
+
defaultCacheTime?: number;
|
|
56
|
+
defaultHeaders?: Record<string, string>;
|
|
57
|
+
successCodes?: number[];
|
|
58
|
+
unauthorizedCodes?: number[];
|
|
59
|
+
unauthorizedRedirectPath?: string;
|
|
60
|
+
server?: {
|
|
61
|
+
protocol?: string;
|
|
62
|
+
host?: string;
|
|
63
|
+
port?: number;
|
|
64
|
+
publicPath?: string;
|
|
65
|
+
};
|
|
66
|
+
onError?: (code: number, message: string) => void;
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
说明:
|
|
71
|
+
|
|
72
|
+
- `baseURL`:通常指 `/api/v1` 这一级;也可以配合 `server` 决定完整 URL。
|
|
73
|
+
- `tokenKey`:约定从何处读取 Token(如 localStorage key),具体读取逻辑视实现而定。
|
|
74
|
+
- `successCodes`:哪些业务 code 视为成功(例如 `[20000, 200]`)。
|
|
75
|
+
- `unauthorizedCodes`:未授权业务码(如 `[401, 403]`),可配合跳登录。
|
|
76
|
+
- `onError`:统一错误处理函数,网络错误、4xx/5xx 业务错误都会透传进来。
|
|
77
|
+
|
|
78
|
+
### RequestOptions
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
export interface RequestOptions {
|
|
82
|
+
timeout?: number; // 单次调用超时(传 0 表示显式关闭超时)
|
|
83
|
+
headers?: Record<string, string>;
|
|
84
|
+
cache?: {
|
|
85
|
+
key?: string;
|
|
86
|
+
time?: number; // 缓存时间(ms)
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
说明:
|
|
92
|
+
|
|
93
|
+
- 若未显式配置 `timeout`,则不启用超时逻辑(不会使用 `ClientOptions.defaultTimeout`)。
|
|
94
|
+
- 若在接口级或调用级将 `timeout` 显式设置为 `0`,则表示**本次请求不使用超时逻辑**,并覆盖掉任何默认超时。
|
|
95
|
+
- 若不配 `cache`,则该次请求不走缓存逻辑。
|
|
96
|
+
|
|
97
|
+
### ApiClient<Cfg>
|
|
98
|
+
|
|
99
|
+
由 `createApiClient` 根据 `requestConfig` 自动推导:
|
|
100
|
+
|
|
101
|
+
- 普通 HTTP 接口:`(params: Req, options?: RequestOptions) => Promise<Res>`
|
|
102
|
+
- SSE 接口:`(params: Req, options?: RequestOptions) => Promise<ReadableStream<any>>`
|
|
103
|
+
|
|
104
|
+
业务调用时通过 `api.{module}.{fn}(params, options?)` 即可,类型由 `Req`/`Res` 自动推断。
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
# listpage-http 示例
|
|
2
|
+
|
|
3
|
+
## 1. 定义 user 模块类型(`src/api/user.ts`)
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
export interface ReqLogin {
|
|
7
|
+
email: string;
|
|
8
|
+
password: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface ResLogin {
|
|
12
|
+
token: string;
|
|
13
|
+
}
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
> 只放类型,不在这里发请求。
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## 2. 集中注册接口(`src/api/request-config.ts`)
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import { defineEndpoint } from "listpage-http";
|
|
24
|
+
import type { ReqLogin, ResLogin } from "./user";
|
|
25
|
+
|
|
26
|
+
export const requestConfig = {
|
|
27
|
+
user: {
|
|
28
|
+
login: defineEndpoint<ReqLogin, ResLogin>({
|
|
29
|
+
method: "POST",
|
|
30
|
+
path: "/auth/login",
|
|
31
|
+
defaultOptions: {
|
|
32
|
+
timeout: 10_000,
|
|
33
|
+
headers: {
|
|
34
|
+
"Content-Type": "application/json",
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
// type: "http", // 默认 http,可省略
|
|
38
|
+
// mode: "json", // 默认 json,可省略
|
|
39
|
+
// authRequired: false, // 如登录接口可显式关闭鉴权
|
|
40
|
+
}),
|
|
41
|
+
},
|
|
42
|
+
} as const;
|
|
43
|
+
|
|
44
|
+
export type RequestConfig = typeof requestConfig;
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## 3. 无参数接口示例(`Req = void`)
|
|
50
|
+
|
|
51
|
+
对于完全没有请求体的接口,推荐将 `Req` 定义为 `void`,并在调用时传 `undefined` 作为第一个参数:
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
// src/api/system.ts
|
|
55
|
+
export type ReqPing = void;
|
|
56
|
+
|
|
57
|
+
export interface ResPing {
|
|
58
|
+
ok: boolean;
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
// src/api/request-config.ts 片段
|
|
64
|
+
import { defineEndpoint } from "listpage-http";
|
|
65
|
+
import type { ReqPing, ResPing } from "./system";
|
|
66
|
+
|
|
67
|
+
export const requestConfig = {
|
|
68
|
+
system: {
|
|
69
|
+
ping: defineEndpoint<ReqPing, ResPing>({
|
|
70
|
+
method: "GET",
|
|
71
|
+
path: "/system/ping",
|
|
72
|
+
}),
|
|
73
|
+
},
|
|
74
|
+
} as const;
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
// service 调用:显式传入 undefined 表示没有请求体
|
|
79
|
+
import api from "@/api";
|
|
80
|
+
import type { ResPing } from "@/api/system";
|
|
81
|
+
|
|
82
|
+
export async function ping(): Promise<ResPing> {
|
|
83
|
+
return api.system.ping(undefined, { timeout: 3_000 });
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## 4. 创建并导出 client(`src/api/index.ts`)
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
import { createApiClient } from "listpage-http";
|
|
93
|
+
import { requestConfig, type RequestConfig } from "./request-config";
|
|
94
|
+
|
|
95
|
+
const isDev = process.env.NODE_ENV === "development";
|
|
96
|
+
|
|
97
|
+
const api = createApiClient<RequestConfig>(requestConfig, {
|
|
98
|
+
baseURL: isDev ? "http://localhost:3000/api/v1" : "/api/v1",
|
|
99
|
+
tokenKey: "AUTH_TOKEN",
|
|
100
|
+
defaultTimeout: 10_000,
|
|
101
|
+
defaultCacheTime: 5_000,
|
|
102
|
+
successCodes: [20000, 200],
|
|
103
|
+
unauthorizedCodes: [401, 403],
|
|
104
|
+
unauthorizedRedirectPath: "/login",
|
|
105
|
+
onError: (code, message) => {
|
|
106
|
+
// 统一错误处理:可根据 code 决定弹 Toast、跳转登录等
|
|
107
|
+
console.error("[API Error]", code, message);
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
export default api;
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## 5. 在 service 中调用(推荐,不直接在组件里写细节)
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
// src/services/auth.ts
|
|
120
|
+
import api from "@/api";
|
|
121
|
+
import type { ReqLogin, ResLogin } from "@/api/user";
|
|
122
|
+
|
|
123
|
+
export async function login(values: ReqLogin): Promise<ResLogin> {
|
|
124
|
+
const data = await api.user.login(values, {
|
|
125
|
+
timeout: 5_000, // 可选:覆盖默认超时
|
|
126
|
+
});
|
|
127
|
+
return data;
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## 6. 在组件中使用
|
|
134
|
+
|
|
135
|
+
接口报错已由 api 层(`onError`)统一处理,业务层**不需要**对 `api` 调用再包 try/catch 来弹错误提示。
|
|
136
|
+
|
|
137
|
+
```tsx
|
|
138
|
+
import { useState } from "react";
|
|
139
|
+
import { message } from "antd";
|
|
140
|
+
import { login } from "@/services/auth";
|
|
141
|
+
|
|
142
|
+
export function LoginForm() {
|
|
143
|
+
const [loading, setLoading] = useState(false);
|
|
144
|
+
|
|
145
|
+
const handleSubmit = async (values: { email: string; password: string }) => {
|
|
146
|
+
setLoading(true);
|
|
147
|
+
await login(values);
|
|
148
|
+
message.success("登录成功");
|
|
149
|
+
// TODO: 写入 token / 跳转
|
|
150
|
+
setLoading(false);
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
// 此处省略表单 JSX,只保留调用方式示例
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## 7. SSE 示例(可选)
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
// 在 request-config 中配置 SSE 接口
|
|
164
|
+
import { defineEndpoint } from "listpage-http";
|
|
165
|
+
|
|
166
|
+
export const requestConfig = {
|
|
167
|
+
agent: {
|
|
168
|
+
generateCode: defineEndpoint<{ id: string }, void>({
|
|
169
|
+
method: "POST",
|
|
170
|
+
path: "/agent/generate-code",
|
|
171
|
+
type: "sse",
|
|
172
|
+
mode: "sse",
|
|
173
|
+
defaultOptions: {
|
|
174
|
+
timeout: 3_000, // 建连超时
|
|
175
|
+
},
|
|
176
|
+
}),
|
|
177
|
+
},
|
|
178
|
+
} as const;
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
```ts
|
|
182
|
+
// 调用方式
|
|
183
|
+
import api from "@/api";
|
|
184
|
+
|
|
185
|
+
async function startSse(id: string) {
|
|
186
|
+
const stream = await api.agent.generateCode({ id });
|
|
187
|
+
|
|
188
|
+
const reader = stream.getReader();
|
|
189
|
+
const decoder = new TextDecoder();
|
|
190
|
+
|
|
191
|
+
while (true) {
|
|
192
|
+
const { done, value } = await reader.read();
|
|
193
|
+
if (done) break;
|
|
194
|
+
const chunk = decoder.decode(value, { stream: true });
|
|
195
|
+
console.log("SSE chunk:", chunk);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## 8. 文件上传示例(`mode: "upload"`,内部自动 FormData)
|
|
203
|
+
|
|
204
|
+
```ts
|
|
205
|
+
// src/api/file.ts
|
|
206
|
+
export interface ReqUploadFile {
|
|
207
|
+
file: File;
|
|
208
|
+
bizType: string;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export interface ResUploadFile {
|
|
212
|
+
url: string;
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
```ts
|
|
217
|
+
// src/api/request-config.ts 片段
|
|
218
|
+
import { defineEndpoint } from "listpage-http";
|
|
219
|
+
import type { ReqUploadFile, ResUploadFile } from "./file";
|
|
220
|
+
|
|
221
|
+
export const requestConfig = {
|
|
222
|
+
file: {
|
|
223
|
+
upload: defineEndpoint<ReqUploadFile, ResUploadFile>({
|
|
224
|
+
method: "POST",
|
|
225
|
+
path: "/file/upload",
|
|
226
|
+
mode: "upload", // 关键:内部会将 ReqUploadFile 转成 FormData
|
|
227
|
+
}),
|
|
228
|
+
},
|
|
229
|
+
} as const;
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
```ts
|
|
233
|
+
// service 调用
|
|
234
|
+
import api from "@/api";
|
|
235
|
+
import type { ReqUploadFile, ResUploadFile } from "@/api/file";
|
|
236
|
+
|
|
237
|
+
export async function uploadFile(params: ReqUploadFile): Promise<ResUploadFile> {
|
|
238
|
+
return api.file.upload(params);
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
说明:
|
|
243
|
+
|
|
244
|
+
- 当某个接口配置 `mode: "upload"` 时,`listpage-http` 会将第二个参数(`params`)视为普通对象:
|
|
245
|
+
- 普通字段:`formData.append(key, value)`。
|
|
246
|
+
- 数组字段:对每一项执行一次 `append`。
|
|
247
|
+
- `null` / `undefined` 字段会被跳过,不会被序列化为 `"null"` 或 `"undefined"`。
|
|
248
|
+
- Content-Type 由浏览器在提交 `FormData` 时自动携带带 boundary 的 `multipart/form-data`,如需覆盖可在 headers 中显式传入。
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
## 9. 新增接口 Checklist
|
|
253
|
+
|
|
254
|
+
1. 在对应模块类型文件(如 `src/api/order.ts`)中添加 `ReqOrderList` / `ResOrderList` 等类型。
|
|
255
|
+
2. 在 `src/api/request-config.ts` 中为该模块新增一个 `defineEndpoint`(例如 `order.list`)。
|
|
256
|
+
3. 在 service/hook 中通过 `api.order.list(params)` 调用。
|
|
257
|
+
4. 禁止在业务代码中直接使用 `fetch`/`axios`,必须通过统一的 `api` 客户端。
|