docs: 更新三份 README 文档以反映实际项目情况
This commit is contained in:
55
README.md
55
README.md
@@ -13,7 +13,7 @@ nex/
|
|||||||
│ │ ├── service/ # 业务逻辑层
|
│ │ ├── service/ # 业务逻辑层
|
||||||
│ │ ├── repository/ # 数据访问层
|
│ │ ├── repository/ # 数据访问层
|
||||||
│ │ ├── domain/ # 领域模型
|
│ │ ├── domain/ # 领域模型
|
||||||
│ │ ├── protocol/ # 协议适配器(OpenAI/Anthropic)
|
│ │ ├── conversion/ # 协议转换引擎(OpenAI/Anthropic 适配器)
|
||||||
│ │ ├── provider/ # 供应商客户端
|
│ │ ├── provider/ # 供应商客户端
|
||||||
│ │ └── config/ # 配置管理
|
│ │ └── config/ # 配置管理
|
||||||
│ ├── pkg/ # 公共包(logger/errors/validator)
|
│ ├── pkg/ # 公共包(logger/errors/validator)
|
||||||
@@ -38,10 +38,13 @@ nex/
|
|||||||
## 功能特性
|
## 功能特性
|
||||||
|
|
||||||
- **双协议支持**:同时支持 OpenAI 和 Anthropic 协议
|
- **双协议支持**:同时支持 OpenAI 和 Anthropic 协议
|
||||||
|
- **跨协议转换**:Hub-and-Spoke 架构实现 OpenAI ↔ Anthropic 双向转换
|
||||||
- **统一模型 ID**:`provider_id/model_name` 格式全局唯一标识模型(如 `openai/gpt-4`)
|
- **统一模型 ID**:`provider_id/model_name` 格式全局唯一标识模型(如 `openai/gpt-4`)
|
||||||
- **透明代理**:对 OpenAI 兼容供应商 Smart Passthrough,最小化改写保持参数保真
|
- **Smart Passthrough**:同协议请求零序列化开销,仅改写 model 字段
|
||||||
- **流式响应**:完整支持 SSE 流式传输
|
- **流式响应**:完整支持 SSE 流式传输,包括跨协议流式转换
|
||||||
- **Function Calling**:支持工具调用(Tools)
|
- **Function Calling**:支持工具调用(Tools)
|
||||||
|
- **Thinking / Reasoning**:支持 OpenAI `reasoning_effort` 和 Anthropic `thinking` 配置
|
||||||
|
- **扩展接口**:支持 Embeddings 和 Rerank 接口
|
||||||
- **多供应商管理**:配置和管理多个供应商(供应商 ID 仅限字母、数字、下划线)
|
- **多供应商管理**:配置和管理多个供应商(供应商 ID 仅限字母、数字、下划线)
|
||||||
- **用量统计**:按供应商、模型、日期统计请求数量
|
- **用量统计**:按供应商、模型、日期统计请求数量
|
||||||
- **Web 配置界面**:提供供应商和模型配置管理
|
- **Web 配置界面**:提供供应商和模型配置管理
|
||||||
@@ -54,7 +57,7 @@ nex/
|
|||||||
- **ORM**: GORM
|
- **ORM**: GORM
|
||||||
- **数据库**: SQLite
|
- **数据库**: SQLite
|
||||||
- **日志**: zap + lumberjack(结构化日志 + 日志轮转)
|
- **日志**: zap + lumberjack(结构化日志 + 日志轮转)
|
||||||
- **配置**: gopkg.in/yaml.v3
|
- **配置**: Viper + pflag(多层配置:CLI > 环境变量 > 配置文件 > 默认值)
|
||||||
- **验证**: go-playground/validator/v10
|
- **验证**: go-playground/validator/v10
|
||||||
- **迁移**: goose
|
- **迁移**: goose
|
||||||
|
|
||||||
@@ -100,12 +103,19 @@ bun dev
|
|||||||
|
|
||||||
### 代理接口(对外部应用)
|
### 代理接口(对外部应用)
|
||||||
|
|
||||||
代理接口统一使用 `provider_id/model_name` 格式的模型 ID(如 `openai/gpt-4`)。同协议请求走 Smart Passthrough,最小化 JSON 改写保持参数保真;跨协议请求走完整 decode/encode 转换。
|
代理接口统一使用 `/{protocol}/*path` 路由格式,模型 ID 使用 `provider_id/model_name` 格式(如 `openai/gpt-4`)。同协议请求走 Smart Passthrough,最小化 JSON 改写保持参数保真;跨协议请求走完整 decode/encode 转换。
|
||||||
|
|
||||||
- `POST /v1/chat/completions` - OpenAI Chat Completions API
|
**OpenAI 协议**(`protocol=openai`):
|
||||||
- `POST /v1/messages` - Anthropic Messages API
|
- `POST /openai/chat/completions` - 对话补全
|
||||||
- `GET /v1/models` - 模型列表(本地数据库聚合,不请求上游)
|
- `GET /openai/models` - 模型列表(本地数据库聚合)
|
||||||
- `GET /v1/models/{provider_id}/{model_name}` - 模型详情(本地数据库查询)
|
- `GET /openai/models/{provider_id}/{model_name}` - 模型详情(本地数据库查询)
|
||||||
|
- `POST /openai/embeddings` - 嵌入
|
||||||
|
- `POST /openai/rerank` - 重排序
|
||||||
|
|
||||||
|
**Anthropic 协议**(`protocol=anthropic`):
|
||||||
|
- `POST /anthropic/v1/messages` - 消息对话
|
||||||
|
- `GET /anthropic/v1/models` - 模型列表(本地数据库聚合)
|
||||||
|
- `GET /anthropic/v1/models/{provider_id}/{model_name}` - 模型详情(本地数据库查询)
|
||||||
|
|
||||||
### 管理接口(对前端)
|
### 管理接口(对前端)
|
||||||
|
|
||||||
@@ -131,6 +141,10 @@ bun dev
|
|||||||
|
|
||||||
## 配置
|
## 配置
|
||||||
|
|
||||||
|
配置支持多种方式,优先级为:**CLI 参数 > 环境变量 > 配置文件 > 默认值**
|
||||||
|
|
||||||
|
### 配置文件
|
||||||
|
|
||||||
配置文件位于 `~/.nex/config.yaml`,首次启动自动生成:
|
配置文件位于 `~/.nex/config.yaml`,首次启动自动生成:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
@@ -154,7 +168,28 @@ log:
|
|||||||
compress: true
|
compress: true
|
||||||
```
|
```
|
||||||
|
|
||||||
数据文件:
|
### 环境变量
|
||||||
|
|
||||||
|
所有配置项支持环境变量,使用 `NEX_` 前缀:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export NEX_SERVER_PORT=9000
|
||||||
|
export NEX_DATABASE_PATH=/data/nex.db
|
||||||
|
export NEX_LOG_LEVEL=debug
|
||||||
|
```
|
||||||
|
|
||||||
|
命名规则:配置路径转大写 + 下划线(如 `server.port` → `NEX_SERVER_PORT`)。
|
||||||
|
|
||||||
|
### CLI 参数
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./server --server-port 9000 --log-level debug --database-path /tmp/test.db
|
||||||
|
```
|
||||||
|
|
||||||
|
命名规则:配置路径转 kebab-case(如 `server.port` → `--server-port`)。
|
||||||
|
|
||||||
|
### 数据文件
|
||||||
|
|
||||||
- `~/.nex/config.yaml` - 配置文件
|
- `~/.nex/config.yaml` - 配置文件
|
||||||
- `~/.nex/config.db` - SQLite 数据库
|
- `~/.nex/config.db` - SQLite 数据库
|
||||||
- `~/.nex/log/` - 日志目录
|
- `~/.nex/log/` - 日志目录
|
||||||
|
|||||||
@@ -116,8 +116,11 @@ backend/
|
|||||||
├── migrations/ # 数据库迁移
|
├── migrations/ # 数据库迁移
|
||||||
│ └── 20260421000001_initial_schema.sql
|
│ └── 20260421000001_initial_schema.sql
|
||||||
├── tests/ # 集成测试
|
├── tests/ # 集成测试
|
||||||
│ ├── helpers.go
|
│ ├── helpers.go # 测试辅助函数
|
||||||
│ └── integration/
|
│ ├── config/ # 测试配置
|
||||||
|
│ ├── integration/ # 集成测试
|
||||||
|
│ │ └── e2e_conversion_test.go # E2E 协议转换测试
|
||||||
|
│ └── mocks/ # Mock 实现
|
||||||
├── Makefile
|
├── Makefile
|
||||||
├── go.mod
|
├── go.mod
|
||||||
└── README.md
|
└── README.md
|
||||||
@@ -146,6 +149,120 @@ Client Request (clientProtocol)
|
|||||||
|
|
||||||
同协议时自动透传,跳过序列化开销。
|
同协议时自动透传,跳过序列化开销。
|
||||||
|
|
||||||
|
## 协议转换架构
|
||||||
|
|
||||||
|
### Canonical Model 中间表示
|
||||||
|
|
||||||
|
所有协议转换都经过 Canonical Model 中间表示层,实现 Hub-and-Spoke 架构:
|
||||||
|
|
||||||
|
```
|
||||||
|
OpenAI Request → Canonical Request → Anthropic Request
|
||||||
|
(中间表示)
|
||||||
|
OpenAI Response ← Canonical Response ← Anthropic Response
|
||||||
|
```
|
||||||
|
|
||||||
|
**CanonicalRequest 核心字段**:
|
||||||
|
- `Model` - 统一模型 ID
|
||||||
|
- `Messages` - 消息列表(支持 text、tool_use、tool_result、thinking 类型)
|
||||||
|
- `Tools` - 工具定义
|
||||||
|
- `Thinking` - 推理配置(`budget_tokens`、`effort`)
|
||||||
|
- `Parameters` - 通用参数(`max_tokens`、`temperature`、`top_p` 等)
|
||||||
|
|
||||||
|
### Smart Passthrough 机制
|
||||||
|
|
||||||
|
同协议请求走 Smart Passthrough 路径,**零序列化开销**:
|
||||||
|
|
||||||
|
```
|
||||||
|
1. 检测 clientProtocol == providerProtocol
|
||||||
|
2. 仅改写请求体中的 model 字段:unified_id → upstream_model_name
|
||||||
|
3. 直接转发请求到上游
|
||||||
|
4. 响应中仅改写 model 字段:upstream_model_name → unified_id
|
||||||
|
```
|
||||||
|
|
||||||
|
### 流式转换器层次
|
||||||
|
|
||||||
|
```
|
||||||
|
StreamConverter (接口)
|
||||||
|
├── PassthroughStreamConverter # 直接透传,无任何处理
|
||||||
|
├── SmartPassthroughStreamConverter # 同协议 + 逐 chunk 改写 model
|
||||||
|
└── CanonicalStreamConverter # 跨协议完整转换(decode → encode)
|
||||||
|
```
|
||||||
|
|
||||||
|
### InterfaceType 枚举
|
||||||
|
|
||||||
|
| 类型 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| `CHAT` | 对话补全(chat/completions、messages) |
|
||||||
|
| `MODELS` | 模型列表 |
|
||||||
|
| `MODEL_INFO` | 模型详情 |
|
||||||
|
| `EMBEDDINGS` | 嵌入接口 |
|
||||||
|
| `RERANK` | 重排序接口 |
|
||||||
|
| `PASSTHROUGH` | 未知接口,直接透传 |
|
||||||
|
|
||||||
|
## 协议适配器特性
|
||||||
|
|
||||||
|
### OpenAI 适配器
|
||||||
|
|
||||||
|
**特有字段支持**:
|
||||||
|
- `reasoning_effort` - 映射到 Canonical Thinking 配置(`none` → 禁用,其他 → `effort`)
|
||||||
|
- `reasoning_content` - 非标准字段,映射到 Canonical thinking 块
|
||||||
|
- `max_completion_tokens` - 新字段,优先于 `max_tokens`
|
||||||
|
- `refusal` - 非标准字段,作为 text 块处理
|
||||||
|
|
||||||
|
**废弃字段兼容**:
|
||||||
|
- `functions` / `function_call` - 自动转换为 `tools` / `tool_choice`
|
||||||
|
|
||||||
|
**消息处理**:
|
||||||
|
- 合并连续同角色消息(Anthropic 不支持连续同角色)
|
||||||
|
- 工具选择映射:`any` → `required`
|
||||||
|
|
||||||
|
### Anthropic 适配器
|
||||||
|
|
||||||
|
**特有字段支持**:
|
||||||
|
- `thinking` - 推理配置(`type: enabled`、`budget_tokens`、`effort`)
|
||||||
|
- `output_config` - 结构化输出配置
|
||||||
|
- `disable_parallel_tool_use` - 禁用并行工具调用
|
||||||
|
- `container` - 工具容器字段
|
||||||
|
|
||||||
|
**不支持的功能**:
|
||||||
|
- Embeddings 接口(返回 `INTERFACE_NOT_SUPPORTED` 错误)
|
||||||
|
|
||||||
|
### 跨协议转换注意事项
|
||||||
|
|
||||||
|
| 源协议 | 目标协议 | 转换说明 |
|
||||||
|
|--------|----------|----------|
|
||||||
|
| OpenAI | Anthropic | `reasoning_effort` → `thinking`,消息角色合并 |
|
||||||
|
| Anthropic | OpenAI | `thinking` 块 → `reasoning_content`,工具选择转换 |
|
||||||
|
|
||||||
|
## 错误码
|
||||||
|
|
||||||
|
### ConversionError 错误码
|
||||||
|
|
||||||
|
| 错误码 | 说明 |
|
||||||
|
|--------|------|
|
||||||
|
| `INVALID_INPUT` | 输入数据无效 |
|
||||||
|
| `MISSING_REQUIRED_FIELD` | 缺少必填字段 |
|
||||||
|
| `INCOMPATIBLE_FEATURE` | 功能不兼容(如跨协议不支持某特性) |
|
||||||
|
| `FIELD_MAPPING_FAILURE` | 字段映射失败 |
|
||||||
|
| `TOOL_CALL_PARSE_ERROR` | 工具调用解析错误 |
|
||||||
|
| `JSON_PARSE_ERROR` | JSON 解析错误 |
|
||||||
|
| `STREAM_STATE_ERROR` | 流式状态错误 |
|
||||||
|
| `UTF8_DECODE_ERROR` | UTF-8 解码错误(流式 chunk 截断) |
|
||||||
|
| `PROTOCOL_CONSTRAINT_VIOLATION` | 协议约束违反 |
|
||||||
|
| `ENCODING_FAILURE` | 编码失败 |
|
||||||
|
| `INTERFACE_NOT_SUPPORTED` | 接口不支持(如 Anthropic Embeddings) |
|
||||||
|
|
||||||
|
### AppError 预定义错误
|
||||||
|
|
||||||
|
| 错误 | HTTP 状态码 | 说明 |
|
||||||
|
|------|-------------|------|
|
||||||
|
| `ErrModelNotFound` | 404 | 模型未找到 |
|
||||||
|
| `ErrModelDisabled` | 404 | 模型已禁用 |
|
||||||
|
| `ErrProviderNotFound` | 404 | 供应商未找到 |
|
||||||
|
| `ErrInvalidProviderID` | 400 | 供应商 ID 格式无效 |
|
||||||
|
| `ErrDuplicateModel` | 409 | 同一供应商下模型名称重复 |
|
||||||
|
| `ErrImmutableField` | 400 | 不可修改字段(如供应商 ID) |
|
||||||
|
|
||||||
## 运行方式
|
## 运行方式
|
||||||
|
|
||||||
### 安装依赖
|
### 安装依赖
|
||||||
@@ -278,10 +395,10 @@ goose -dir migrations sqlite3 ~/.nex/config.db up
|
|||||||
#### OpenAI 协议
|
#### OpenAI 协议
|
||||||
|
|
||||||
```
|
```
|
||||||
POST /openai/v1/chat/completions
|
POST /openai/chat/completions
|
||||||
GET /openai/v1/models
|
GET /openai/models
|
||||||
POST /openai/v1/embeddings
|
POST /openai/embeddings
|
||||||
POST /openai/v1/rerank
|
POST /openai/rerank
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Anthropic 协议
|
#### Anthropic 协议
|
||||||
@@ -322,7 +439,7 @@ GET /anthropic/v1/models
|
|||||||
- Anthropic 协议:配置到域名,不包含版本路径,如 `https://api.anthropic.com`
|
- Anthropic 协议:配置到域名,不包含版本路径,如 `https://api.anthropic.com`
|
||||||
|
|
||||||
**对外 URL 格式**:
|
**对外 URL 格式**:
|
||||||
- OpenAI 协议:`/{protocol}/{endpoint}`,如 `/openai/chat/completions`、`/openai/models`
|
- OpenAI 协议:`/{protocol}/{endpoint}`,如 `/openai/chat/completions`、`/openai/models`、`/openai/embeddings`
|
||||||
- Anthropic 协议:`/{protocol}/v1/{endpoint}`,如 `/anthropic/v1/messages`、`/anthropic/v1/models`
|
- Anthropic 协议:`/{protocol}/v1/{endpoint}`,如 `/anthropic/v1/messages`、`/anthropic/v1/models`
|
||||||
|
|
||||||
#### 模型管理
|
#### 模型管理
|
||||||
|
|||||||
@@ -14,6 +14,68 @@ AI 网关管理前端,提供供应商配置和用量统计界面。
|
|||||||
- **样式**: SCSS Modules(禁止使用纯 CSS)
|
- **样式**: SCSS Modules(禁止使用纯 CSS)
|
||||||
- **测试**: Vitest + React Testing Library + Playwright
|
- **测试**: Vitest + React Testing Library + Playwright
|
||||||
|
|
||||||
|
## API 层
|
||||||
|
|
||||||
|
### 字段转换机制
|
||||||
|
|
||||||
|
后端使用 `snake_case`,前端使用 `camelCase`,API 层自动转换:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 发送请求时:camelCase → snake_case
|
||||||
|
toApi({ providerId: "openai" }) // → { provider_id: "openai" }
|
||||||
|
|
||||||
|
// 接收响应时:snake_case → camelCase
|
||||||
|
fromApi({ provider_id: "openai" }) // → { providerId: "openai" }
|
||||||
|
```
|
||||||
|
|
||||||
|
### 统一请求函数
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export async function request<T>(method: string, path: string, body?: unknown): Promise<T>
|
||||||
|
```
|
||||||
|
|
||||||
|
- 自动处理字段转换
|
||||||
|
- 自动处理 204 响应(无 body)
|
||||||
|
- 抛出 `ApiError` 包含 `status`、`code`、`message`
|
||||||
|
|
||||||
|
### 错误处理
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class ApiError extends Error {
|
||||||
|
status: number; // HTTP 状态码
|
||||||
|
code?: string; // 业务错误码
|
||||||
|
message: string; // 错误消息
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## TanStack Query 模式
|
||||||
|
|
||||||
|
### Query Keys 定义
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/hooks/useProviders.ts
|
||||||
|
export const providerKeys = {
|
||||||
|
all: ['providers'] as const,
|
||||||
|
};
|
||||||
|
|
||||||
|
// src/hooks/useModels.ts
|
||||||
|
export const modelKeys = {
|
||||||
|
all: ['models'] as const,
|
||||||
|
byProvider: (providerId: string) => [...modelKeys.all, { providerId }] as const,
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Mutation 使用
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const mutation = useMutation({
|
||||||
|
mutationFn: createProvider,
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: providerKeys.all });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
## 项目结构
|
## 项目结构
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -25,7 +87,7 @@ frontend/
|
|||||||
│ │ ├── models.ts # Model CRUD
|
│ │ ├── models.ts # Model CRUD
|
||||||
│ │ └── stats.ts # Stats 查询
|
│ │ └── stats.ts # Stats 查询
|
||||||
│ ├── components/
|
│ ├── components/
|
||||||
│ │ └── AppLayout/ # 顶部导航布局
|
│ │ └── AppLayout/ # 侧边栏导航布局
|
||||||
│ ├── hooks/ # TanStack Query hooks
|
│ ├── hooks/ # TanStack Query hooks
|
||||||
│ │ ├── useProviders.ts
|
│ │ ├── useProviders.ts
|
||||||
│ │ ├── useModels.ts
|
│ │ ├── useModels.ts
|
||||||
@@ -33,6 +95,7 @@ frontend/
|
|||||||
│ ├── pages/
|
│ ├── pages/
|
||||||
│ │ ├── Providers/ # 供应商管理(含内嵌模型管理)
|
│ │ ├── Providers/ # 供应商管理(含内嵌模型管理)
|
||||||
│ │ ├── Stats/ # 用量统计
|
│ │ ├── Stats/ # 用量统计
|
||||||
|
│ │ ├── Settings/ # 设置(开发中)
|
||||||
│ │ └── NotFound.tsx
|
│ │ └── NotFound.tsx
|
||||||
│ ├── routes/
|
│ ├── routes/
|
||||||
│ │ └── index.tsx # 路由配置
|
│ │ └── index.tsx # 路由配置
|
||||||
@@ -125,11 +188,50 @@ bun run test:e2e
|
|||||||
- 按模型筛选
|
- 按模型筛选
|
||||||
- 按日期范围筛选(DatePicker.RangePicker)
|
- 按日期范围筛选(DatePicker.RangePicker)
|
||||||
|
|
||||||
|
## 测试策略
|
||||||
|
|
||||||
|
### 目录结构
|
||||||
|
|
||||||
|
```
|
||||||
|
__tests__/
|
||||||
|
├── setup.ts # 测试配置(happy-dom)
|
||||||
|
├── api/ # API 层测试
|
||||||
|
│ └── client.test.ts
|
||||||
|
├── hooks/ # TanStack Query Hook 测试
|
||||||
|
│ ├── useProviders.test.ts
|
||||||
|
│ └── useModels.test.ts
|
||||||
|
└── components/ # 组件测试
|
||||||
|
└── AppLayout.test.tsx
|
||||||
|
```
|
||||||
|
|
||||||
|
### E2E 测试
|
||||||
|
|
||||||
|
- 位于 `e2e/` 目录
|
||||||
|
- 使用 Playwright
|
||||||
|
- 自动启动后端服务(临时端口 19026)
|
||||||
|
- 配置文件:`playwright.config.ts`
|
||||||
|
|
||||||
|
### Mock 策略
|
||||||
|
|
||||||
|
- API 层测试使用 MSW(Mock Service Worker)
|
||||||
|
- Hook 测试使用 `@testing-library/react-hooks`
|
||||||
|
- 组件测试使用 `@testing-library/react`
|
||||||
|
|
||||||
|
## 环境变量
|
||||||
|
|
||||||
|
| 变量 | 开发环境 | 生产环境 | 说明 |
|
||||||
|
|------|----------|----------|------|
|
||||||
|
| `VITE_API_BASE` | (空) | `/api` | API 基础路径,空则走 Vite proxy |
|
||||||
|
|
||||||
|
**E2E 测试特有**:
|
||||||
|
- `NEX_BACKEND_PORT` - E2E 后端端口(默认 19026)
|
||||||
|
- `NEX_E2E_TEMP_DIR` - E2E 临时目录
|
||||||
|
|
||||||
## 开发规范
|
## 开发规范
|
||||||
|
|
||||||
- 所有样式使用 SCSS,禁止使用纯 CSS 文件
|
- 所有样式使用 SCSS,禁止使用纯 CSS 文件
|
||||||
- 组件级样式使用 SCSS Modules(*.module.scss)
|
- 组件级样式使用 SCSS Modules(*.module.scss)
|
||||||
- 图标优先使用 @ant-design/icons
|
- 图标优先使用 TDesign 图标(tdesign-icons-react)
|
||||||
- TypeScript strict 模式,禁止 any 类型
|
- TypeScript strict 模式,禁止 any 类型
|
||||||
- API 层自动处理 snake_case ↔ camelCase 字段转换
|
- API 层自动处理 snake_case ↔ camelCase 字段转换
|
||||||
- 使用路径别名 `@/` 引用 src 目录
|
- 使用路径别名 `@/` 引用 src 目录
|
||||||
@@ -143,4 +245,4 @@ bun run test:e2e
|
|||||||
1. 在 `src/pages/` 创建页面目录和组件
|
1. 在 `src/pages/` 创建页面目录和组件
|
||||||
2. 在 `src/hooks/` 创建对应的 TanStack Query hook
|
2. 在 `src/hooks/` 创建对应的 TanStack Query hook
|
||||||
3. 在 `src/routes/index.tsx` 添加路由
|
3. 在 `src/routes/index.tsx` 添加路由
|
||||||
4. 在 `src/components/AppLayout/index.tsx` 的 menuItems 添加导航项
|
4. 在 `src/components/AppLayout/index.tsx` 的 Menu 中添加 MenuItem
|
||||||
|
|||||||
Reference in New Issue
Block a user