# 前端配置界面

## Purpose

TBD - 提供供应商、模型配置和用量统计的前端管理界面

## Requirements

### Requirement: 样式体系

前端样式 SHALL 优先使用 TDesign 样式体系，SCSS 作为补充工具。

#### Scenario: TDesign 组件 Props 优先

- **WHEN** 实现组件视觉效果
- **THEN** 前端 SHALL 优先使用 TDesign 组件的视觉增强 Props（如 color、trend、hoverShadow、stripe、variant、shape 等）
- **THEN** 前端 SHALL NOT 通过 CSS 类名覆盖组件内部样式

#### Scenario: CSS Variables 主题微调

- **WHEN** 需要调整全局视觉风格
- **THEN** 前端 SHALL 通过 \`:root\` 中声明 TDesign CSS Variables（\`--td-*\`）进行覆盖
- **THEN** 前端 SHALL NOT 使用 \`!important\` 或高优先级选择器覆盖组件样式

#### Scenario: 布局样式 Token 化

- **WHEN** 编写布局级 inline style
- **THEN** 前端 SHALL 使用 TDesign CSS Token 引用（\`var(--td-*)\`）替代硬编码颜色值
- **THEN** 前端 SHALL NOT 在布局样式中硬编码 \`#fff\`、\`#e7e7e7\`、\`#999\` 等颜色值

#### Scenario: SCSS 补充使用

- **WHEN** TDesign 样式体系无法满足需求
- **THEN** 前端 MAY 使用 SCSS 作为补充
- **THEN** SCSS 文件 SHALL 仅用于 \`:root\` 级别的 CSS Variables 声明和全局 reset
- **THEN** 前端 SHALL NOT 使用纯 CSS 文件（*.css）


### Requirement: 提供统计查看页面

前端 SHALL 使用 TDesign 组件提供统计仪表盘页面。

#### Scenario: 显示统计概览

- **WHEN** 加载统计页面
- **THEN** 前端 SHALL 在顶部显示统计摘要卡片（总请求量、活跃模型数、活跃供应商数、今日请求量）
- **THEN** 统计摘要数据 SHALL 从 stats API 返回数据中前端聚合
- **THEN** 每个 Statistic 组件 SHALL 使用 \`color\` prop 设置预设颜色风格
- **THEN** 每个 Statistic 组件 SHALL 使用 \`prefix\` prop 显示图标前缀
- **THEN** 每个 Statistic 组件 SHALL 使用 \`suffix\` prop 显示单位文字
- **THEN** Statistic 组件首次加载 SHALL 使用 \`animation\` prop 展示数字滚动动画
- **THEN** Card 组件 SHALL 设置 \`bordered={false}\` 和 \`hoverShadow\`
- **THEN** 前端 SHALL 显示请求趋势折线图
- **THEN** 趋势图表卡片 SHALL 设置 \`headerBordered\`
- **THEN** 前端 SHALL 使用 TDesign Table 显示统计数据
- **THEN** 统计数据 SHALL 按供应商和模型显示请求计数

#### Scenario: 统计表格列宽约束

- **WHEN** 渲染统计表格
- **THEN** 供应商列 SHALL 固定宽度 180px 并启用 ellipsis + Tooltip
- **THEN** 模型列 SHALL 固定宽度 250px 并启用 ellipsis + Tooltip
- **THEN** 日期列 SHALL 固定宽度 120px
- **THEN** 请求数列 SHALL 固定宽度 100px 并右对齐

#### Scenario: 统计表格空状态

- **WHEN** 统计数据为空
- **THEN** 表格 SHALL 显示自定义空状态文案 "暂无统计数据"

#### Scenario: 按供应商过滤统计

- **WHEN** 用户从 TDesign Select 选择供应商
- **THEN** 前端 SHALL 自动查询并过滤统计
- **THEN** 统计摘要卡片和趋势图表 SHALL 同步更新

#### Scenario: 按日期范围过滤统计

- **WHEN** 用户使用 TDesign DateRangePicker 选择日期范围
- **THEN** 前端 SHALL 自动查询并过滤统计
- **THEN** 统计摘要卡片和趋势图表 SHALL 同步更新

#### Scenario: 仪表盘区域排列

- **WHEN** 渲染统计页面
- **THEN** 页面 SHALL 按以下顺序从上到下排列：统计摘要卡片、趋势图表、筛选栏和数据表格
- **THEN** 各区域之间 SHALL 有合理的垂直间距
- **THEN** 筛选栏和数据表格 SHALL 保持在同一个卡片中

#### Scenario: 数据联动

- **WHEN** 用户通过筛选栏修改筛选条件
- **THEN** 统计摘要卡片和趋势图表 SHALL 随筛选条件变化更新
- **THEN** 数据表格 SHALL 同步更新
- **THEN** 所有区域 SHALL 共享同一份筛选后的数据


### Requirement: 提供供应商管理页面

前端 SHALL 使用 TDesign 组件提供供应商管理页面。

#### Scenario: 显示供应商列表

- **WHEN** 加载供应商管理页面
- **THEN** 前端 SHALL 使用 TDesign Table 显示所有已配置供应商
- **THEN** Table SHALL 启用 \`stripe\`（斑马纹）和 \`hover\`（行悬浮高亮）
- **THEN** 每个供应商 SHALL 显示 name、base_url 和 enabled 状态（使用 Tag 组件）
- **THEN** 状态 Tag SHALL 使用 \`variant="light"\` 和 \`shape="round"\`
- **THEN** 协议 Tag SHALL 使用 \`variant="light"\` 和 \`shape="round"\`
- **THEN** API Key SHALL 显示完整值（不进行掩码处理）
- **THEN** 表格 SHALL 支持展开行以显示关联模型
- **THEN** Card 组件 SHALL 设置 \`hoverShadow\` 和 \`headerBordered\`

#### Scenario: 表格列宽约束

- **WHEN** 渲染供应商表格
- **THEN** 名称列 SHALL 固定宽度 180px 并启用 ellipsis（超长文本显示省略号，hover 显示 Tooltip）
- **THEN** Base URL 列 SHALL 不设固定宽度（浮动填充剩余空间）并启用 ellipsis + Tooltip
- **THEN** API Key 列 SHALL 不设固定宽度（浮动填充剩余空间）并启用 ellipsis + Tooltip
- **THEN** 状态列 SHALL 固定宽度 80px
- **THEN** 操作列 SHALL 固定宽度 160px

#### Scenario: 表格空状态

- **WHEN** 供应商列表为空
- **THEN** 表格 SHALL 显示自定义空状态文案 "暂无供应商，点击上方按钮添加"

#### Scenario: 添加新供应商

- **WHEN** 用户点击"添加供应商"按钮
- **THEN** 前端 SHALL 使用 TDesign Dialog + Form 显示输入表单
- **THEN** Dialog SHALL 使用 \`placement="center"\` 居中显示
- **THEN** Dialog SHALL 设置 \`width="520px"\`
- **THEN** 表单 SHALL 包含 id、name、api_key、base_url 字段，带校验规则
- **THEN** Dialog SHALL 禁用蒙版点击关闭（closeOnOverlayClick={false}）
- **THEN** Dialog SHALL 禁用 ESC 键关闭（closeOnEscKeydown={false}）
- **THEN** Dialog SHALL 设置 lazy={false} 禁用懒加载
- **WHEN** 用户提交包含有效数据的表单
- **THEN** 前端 SHALL 通过 mutateAsync 调用创建 API
- **THEN** 成功后 SHALL 关闭 Dialog 并刷新供应商列表
- **THEN** 失败 SHALL 保持 Dialog 打开并显示错误提示（MessagePlugin.error）

#### Scenario: 编辑现有供应商

- **WHEN** 用户点击供应商的"编辑"按钮
- **THEN** 前端 SHALL 使用 TDesign Dialog + Form 显示预填充数据的表单
- **THEN** Dialog SHALL 使用 \`placement="center"\` 居中显示
- **THEN** Dialog SHALL 设置 \`width="520px"\`
- **THEN** API Key SHALL 回显当前值（完整值）
- **THEN** API Key 输入框 SHALL 为普通文本输入（不使用 password 类型）
- **THEN** API Key 字段 SHALL 始终为必填
- **THEN** Dialog SHALL 禁用蒙版点击关闭（closeOnOverlayClick={false}）
- **THEN** Dialog SHALL 禁用 ESC 键关闭（closeOnEscKeydown={false}）
- **THEN** Dialog SHALL 设置 lazy={false} 禁用懒加载
- **WHEN** 用户提交包含更新数据的表单
- **THEN** 前端 SHALL 通过 mutateAsync 调用更新 API
- **THEN** 成功后 SHALL 关闭 Dialog 并刷新供应商列表
- **THEN** 失败 SHALL 保持 Dialog 打开并显示错误提示（MessagePlugin.error）

#### Scenario: 删除供应商

- **WHEN** 用户点击供应商的"删除"按钮
- **THEN** 前端 SHALL 使用 TDesign Popconfirm 弹出确认
- **WHEN** 用户确认删除
- **THEN** 前端 SHALL 通过 useMutation 调用删除 API
- **THEN** 成功后 SHALL 刷新供应商列表


### Requirement: 提供模型管理界面

前端 SHALL 在供应商页面展开行中提供模型管理。

#### Scenario: 显示供应商的模型

- **WHEN** 展开供应商行
- **THEN** 前端 SHALL 显示该供应商的模型列表
- **THEN** Table SHALL 启用 \`stripe\`（斑马纹）和 \`hover\`（行悬浮高亮）
- **THEN** 每个模型 SHALL 显示 model_name 和 enabled 状态
- **THEN** 状态 Tag SHALL 使用 \`variant="light"\` 和 \`shape="round"\`
- **THEN** 模型名称列 SHALL 启用 ellipsis（超长文本显示省略号，hover 显示 Tooltip）

#### Scenario: 模型表格空状态

- **WHEN** 模型列表为空
- **THEN** 表格 SHALL 显示自定义空状态文案 "暂无模型，点击上方按钮添加"

#### Scenario: 为供应商添加模型

- **WHEN** 用户在展开行中点击"添加模型"
- **THEN** 前端 SHALL 显示 TDesign Dialog + Form
- **THEN** Dialog SHALL 使用 \`placement="center"\` 居中显示
- **THEN** Dialog SHALL 设置 \`width="520px"\`
- **THEN** provider_id SHALL 自动关联当前供应商
- **THEN** 供应商选择 SHALL 使用 \`options\` 属性
- **THEN** 创建表单 SHALL NOT 包含 ID 输入框（后端自动生成 UUID）
- **THEN** Dialog SHALL 禁用蒙版点击关闭（closeOnOverlayClick={false}）
- **THEN** Dialog SHALL 禁用 ESC 键关闭（closeOnEscKeydown={false}）
- **WHEN** 用户提交表单
- **THEN** 前端 SHALL 通过 mutateAsync 调用创建 API
- **THEN** 成功后 SHALL 关闭 Dialog 并刷新模型列表
- **THEN** 失败 SHALL 保持 Dialog 打开并显示错误提示（MessagePlugin.error）

#### Scenario: 编辑模型

- **WHEN** 用户点击模型的"编辑"
- **THEN** 前端 SHALL 显示编辑表单
- **THEN** Dialog SHALL 使用 \`placement="center"\` 居中显示
- **THEN** Dialog SHALL 设置 \`width="520px"\`
- **THEN** 编辑表单 SHALL NOT 包含统一模型 ID 字段
- **THEN** Dialog SHALL 禁用蒙版点击关闭（closeOnOverlayClick={false}）
- **THEN** Dialog SHALL 禁用 ESC 键关闭（closeOnEscKeydown={false}）
- **THEN** Dialog SHALL 设置 lazy={false} 禁用懒加载
- **WHEN** 用户提交表单
- **THEN** 前端 SHALL 通过 mutateAsync 调用更新 API
- **THEN** 成功后 SHALL 关闭 Dialog 并刷新模型列表
- **THEN** 失败 SHALL 保持 Dialog 打开并显示错误提示（MessagePlugin.error）

#### Scenario: 删除模型

- **WHEN** 用户点击模型的"删除"
- **THEN** 前端 SHALL 使用 Popconfirm 弹出确认
- **WHEN** 用户确认删除
- **THEN** 前端 SHALL 通过 useMutation 调用删除 API
- **THEN** 成功后 SHALL 刷新模型列表


### Requirement: 提供响应式布局

前端 SHALL 使用 TDesign Layout 提供侧边栏导航布局。

#### Scenario: 桌面布局

- **WHEN** 在桌面屏幕上查看前端
- **THEN** 布局 SHALL 使用 TDesign \`Layout.Aside\` + \`Menu\`
- **THEN** 侧边栏 SHALL 显示导航菜单，包含图标和文字标签
- **THEN** 侧边栏 SHALL 使用固定宽度 232px
- **THEN** Menu 组件 SHALL 使用 \`logo\` prop 显示品牌标识
- **THEN** Menu 组件 SHALL 使用 \`operations\` prop 在底部显示操作区域
- **THEN** Menu 组件 SHALL 支持 \`collapsed\` 折叠功能

#### Scenario: 页面内容区域

- **WHEN** 显示页面内容
- **THEN** 内容区域 SHALL 在 \`Layout.Content\` 中渲染
- **THEN** 页面之间 SHALL 通过 React Router Outlet 渲染

#### Scenario: Header 区域

- **WHEN** 渲染页面 Header
- **THEN** Header SHALL 仅显示当前页面标题
- **THEN** Header SHALL 不包含导航菜单
- **THEN** Header 背景色 SHALL 使用 \`var(--td-bg-color-container)\` Token
- **THEN** Header 底部分割线 SHALL 使用 \`var(--td-component-stroke)\` Token

### Requirement: 显示协议字段

前端 SHALL 在供应商管理界面显示协议字段。

#### Scenario: 供应商表格显示协议列

- **WHEN** 渲染供应商表格
- **THEN** 表格 SHALL 包含协议列
- **THEN** 协议列 SHALL 显示 "OpenAI" 或 "Anthropic" 标签
- **THEN** 标签 SHALL 使用 \`variant="light"\` 和 \`shape="round"\`
- **THEN** OpenAI 协议 SHALL 使用主题色标签
- **THEN** Anthropic 协议 SHALL 使用成功色标签

#### Scenario: 供应商表单选择协议

- **WHEN** 创建或编辑供应商
- **THEN** 表单 SHALL 包含协议选择下拉框
- **THEN** 下拉框 SHALL 提供 "OpenAI" 和 "Anthropic" 选项
- **THEN** 协议字段 SHALL 为必填项

### Requirement: 使用 TDesign UI 组件库

前端 SHALL 使用 TDesign 作为 UI 组件库。

#### Scenario: 全局配置

- **WHEN** 应用启动
- **THEN** ConfigProvider SHALL 注入全局配置
- **THEN** 全局配置 SHALL 包含 \`animation\` 配置（ripple、expand、fade 动画开启）
- **THEN** 全局配置 SHALL 包含 \`table.size\` 默认尺寸设置

### Requirement: 主题定制

前端 SHALL 通过 TDesign CSS Variables 进行主题微调。

#### Scenario: 页面背景色

- **WHEN** 渲染页面
- **THEN** \`:root\` SHALL 设置 \`--td-bg-color-page\` 为 \`#f5f7fa\`

#### Scenario: 圆角调整

- **WHEN** 渲染组件
- **THEN** \`:root\` SHALL 设置 \`--td-radius-default\` 为 \`6px\`
- **THEN** \`:root\` SHALL 设置 \`--td-radius-medium\` 为 \`9px\`
- **THEN** \`:root\` SHALL 设置 \`--td-radius-large\` 为 \`12px\`
- **THEN** \`:root\` SHALL 设置 \`--td-radius-extraLarge\` 为 \`16px\`

#### Scenario: 字体栈声明

- **WHEN** 渲染页面
- **THEN** \`:root\` SHALL 设置 \`--td-font-family\` 为系统字体栈（-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto 等）

### Requirement: 提供请求趋势图表

前端 SHALL 使用 Recharts 提供请求趋势可视化。

#### Scenario: 显示面积图表

- **WHEN** 渲染请求趋势图表
- **THEN** 前端 SHALL 使用 Recharts AreaChart + Area 组件（替代 LineChart + Line）
- **THEN** 面积填充 SHALL 使用线性渐变（从品牌色到透明）
- **THEN** 图表 SHALL 仅使用 Recharts 组件 Props API 定制样式
- **THEN** 前端 SHALL NOT 通过 CSS 覆盖 Recharts 内部样式类名

#### Scenario: 图表加载态

- **WHEN** 统计数据加载中
- **THEN** UsageChart 的 Card 组件 SHALL 显示骨架屏（loading prop）
- **THEN** StatsPage SHALL 向 UsageChart 传递 isLoading 状态

### Requirement: 提供 404 页面

前端 SHALL 提供页面未找到的 404 页面。

#### Scenario: 显示 404 页面

- **WHEN** 用户访问不存在的路由
- **THEN** 前端 SHALL 显示 404 页面
- **THEN** 404 标题颜色 SHALL 使用 \`var(--td-text-color-placeholder)\` Token
- **THEN** 描述文字颜色 SHALL 使用 \`var(--td-text-color-secondary)\` Token

### Requirement: 提供设置页面

前端 SHALL 提供设置页面。

#### Scenario: 显示设置页面

- **WHEN** 用户访问设置页面
- **THEN** 前端 SHALL 显示设置页面
- **THEN** 开发中提示文字颜色 SHALL 使用 \`var(--td-text-color-placeholder)\` Token


### Requirement: 显示统一模型 ID

前端 SHALL 在所有显示模型的地方使用统一模型 ID。

#### Scenario: 模型表格显示统一 ID 列

- **WHEN** 渲染模型表格
- **THEN** 表格 SHALL 包含统一模型 ID 列
- **THEN** 统一模型 ID 列 SHALL 显示 \`provider_id/model_name\` 格式
- **THEN** 统一模型 ID 列 SHALL 启用 ellipsis（超长文本显示省略号，hover 显示 Tooltip）
- **THEN** 统一模型 ID 列 SHALL 固定宽度 250px

#### Scenario: 统一模型 ID 降级显示

- **WHEN** 后端未返回 unified_id 字段
- **THEN** 前端 SHALL 拼接 providerId 和 modelName 显示
- **THEN** 拼接格式 SHALL 为 \`{providerId}/{modelName}\`

### Requirement: 提取并映射错误码

前端 SHALL 提取后端结构化错误响应中的错误码并映射为友好消息。

#### Scenario: API 客户端提取错误码

- **WHEN** 后端返回结构化错误响应 \`{error: string, code: string}\`
- **THEN** API 客户端 SHALL 提取 code 字段
- **THEN** ApiError 对象 SHALL 包含 code 字段
- **THEN** code 字段 SHALL 为可选（兼容旧错误格式）

#### Scenario: Hooks 映射错误码为中文消息

- **WHEN** 处理 API 错误
- **THEN** Hooks SHALL 使用错误码映射表
- **THEN** 映射表 SHALL 包含以下错误码：
  - \`duplicate_model\` → "同一供应商下模型名称已存在"
  - \`invalid_provider_id\` → "供应商 ID 仅允许字母、数字、下划线，长度 1-64"
  - \`immutable_field\` → "供应商 ID 不允许修改"
- **THEN** 未定义的错误码 SHALL 降级使用原始错误消息

### Requirement: 优雅处理 API 错误

前端 SHALL 处理 API 错误并显示用户友好的消息。

#### Scenario: API 请求失败

- **WHEN** API 请求失败（网络错误、4xx、5xx）
- **THEN** 前端 SHALL 显示全局错误提示
- **THEN** 错误消息 SHALL 具有描述性

#### Scenario: 验证错误

- **WHEN** 用户提交包含无效数据的表单
- **THEN** 前端 SHALL 在相关字段旁显示验证错误
- **THEN** 前端 SHALL 阻止表单提交

### Requirement: 提供侧边栏导航

前端 SHALL 使用 TDesign \`Layout.Aside\` 提供侧边栏导航。

#### Scenario: 侧边栏内容

- **WHEN** 渲染侧边栏
- **THEN** 侧边栏顶部 SHALL 显示应用名称/Logo
- **THEN** 侧边栏 SHALL 包含导航菜单
- **THEN** 导航菜单项 SHALL 包含：供应商管理（ServerIcon 图标）、用量统计（ChartLineIcon 图标）、设置（SettingIcon 图标）、关于（InfoCircleIcon 图标）

#### Scenario: 导航菜单交互

- **WHEN** 用户点击导航中的"供应商管理"
- **THEN** 前端 SHALL 导航到 \`/providers\` 并高亮当前菜单项
- **WHEN** 用户点击导航中的"用量统计"
- **THEN** 前端 SHALL 导航到 \`/stats\` 并高亮当前菜单项
- **WHEN** 用户点击导航中的"设置"
- **THEN** 前端 SHALL 导航到 `/settings` 并高亮当前菜单项
- **WHEN** 用户点击导航中的"关于"
- **THEN** 前端 SHALL 导航到 `/about` 并高亮当前菜单项

### Requirement: 提供导航

前端 SHALL 使用 React Router v7 提供导航，并支持路由级懒加载。

#### Scenario: 路由配置

- **WHEN** 应用启动
- **THEN** 前端 SHALL 使用 React Router v7 Library 模式（BrowserRouter）
- **THEN** `/providers` 路径 SHALL 显示供应商管理页面
- **THEN** `/stats` 路径 SHALL 显示用量统计页面
- **THEN** `/settings` 路径 SHALL 显示设置页面
- **THEN** `/about` 路径 SHALL 显示关于页面
- **THEN** `/` 路径 SHALL 重定向到 `/providers`
- **THEN** 不存在的路径 SHALL 显示 404 页面

#### Scenario: 路由级懒加载

- **WHEN** 用户访问某个路由
- **THEN** 前端 SHALL 使用 `React.lazy()` 按需加载对应页面组件
- **THEN** 页面组件加载期间 SHALL 显示 TDesign `Loading` 组件作为 fallback
- **THEN** 所有页面组件 SHALL 通过动态 `import()` 导入

#### Scenario: 导航菜单

- **WHEN** 用户点击导航中的"供应商管理"
- **THEN** 前端 SHALL 导航到 `/providers` 并高亮当前菜单项
- **WHEN** 用户点击导航中的"用量统计"
- **THEN** 前端 SHALL 导航到 `/stats` 并高亮当前菜单项

#### Scenario: URL 同步

- **WHEN** 用户在供应商页面刷新浏览器
- **THEN** 前端 SHALL 保持在供应商页面（URL 持久化）
- **WHEN** 用户使用浏览器后退按钮
- **THEN** 前端 SHALL 正确导航到上一个页面

### Requirement: React 19 适配器

前端 SHALL 导入 TDesign react-19-adapter 以支持 React 19。

#### Scenario: 导入适配器

- **WHEN** 应用启动
- **THEN** main.tsx SHALL 导入 'tdesign-react/es/_util/react-19-adapter'
- **THEN** MessagePlugin、DialogPlugin 等插件式调用 SHALL 正常工作

#### Scenario: 错误提示显示

- **WHEN** API 请求失败
- **THEN** MessagePlugin.error SHALL 正确渲染错误提示
- **THEN** 错误提示 SHALL 显示在页面顶部（placement: top）
- **THEN** 错误提示 SHALL 在 3 秒后自动消失

### Requirement: 使用 React 和 TypeScript

前端 SHALL 使用 React 和 TypeScript 实现，遵循 strict 模式。

#### Scenario: TypeScript strict 模式

- **WHEN** 编写前端代码
- **THEN** TypeScript 配置 SHALL 开启 strict: true
- **THEN** TypeScript 配置 SHALL 开启 noUncheckedIndexedAccess
- **THEN** 所有代码 SHALL NOT 使用 any 类型
- **THEN** tsconfig SHALL 合并为单文件（不使用 project references）
- **THEN** type import SHALL 使用内联风格 `import { type Foo }`
- **THEN** 代码 SHALL NOT 使用非空断言 `foo!`
- **THEN** 代码 SHALL NOT 使用 `console.log`、`console.info`、`console.debug`（仅允许 `console.warn` 和 `console.error`）

#### Scenario: React 函数组件

- **WHEN** 实现 UI
- **THEN** 它 SHALL 使用 React 函数组件
- **THEN** 它 SHALL 使用自定义 Hooks 封装业务逻辑

### Requirement: 使用 Vite 构建

前端 SHALL 使用 Vite 作为构建工具。

#### Scenario: 开发服务器

- **WHEN** 在开发模式下启动前端
- **THEN** Vite SHALL 使用热模块替换服务应用

#### Scenario: 生产构建

- **WHEN** 为生产构建前端
- **THEN** Vite SHALL 生成优化的静态文件
- **THEN** Vite SHALL 对业务代码执行混淆处理
- **THEN** 混淆 SHALL 仅应用于 src 目录下的业务代码
- **THEN** 混淆 SHALL NOT 应用于 node_modules 中的第三方库
- **THEN** 构建流程 SHALL 在 vite build 之前执行 ESLint 检查和 Prettier 格式检查
- **THEN** ESLint 检查失败 SHALL 中断构建
- **THEN** Prettier 格式检查失败 SHALL 中断构建

### Requirement: 开发环境格式化工具

前端 SHALL 配置开发环境格式化工具，确保开发者保存时自动格式化代码。

#### Scenario: VS Code 保存时自动格式化

- **WHEN** 开发者在 VS Code 中保存文件
- **THEN** 文件 SHALL 自动使用 Prettier 格式化
- **THEN** ESLint 可修复的问题 SHALL 自动修复

#### Scenario: 编辑器统一配置

- **WHEN** 开发者在编辑器中打开项目
- **THEN** 编辑器 SHALL 自动应用 `.editorconfig` 配置
- **THEN** 编辑器 SHALL 使用 2 空格缩进、UTF-8 编码、Unix 换行符

#### Scenario: VS Code 推荐安装扩展

- **WHEN** 开发者使用 VS Code 打开项目
- **THEN** VS Code SHALL 提示安装 Prettier 和 ESLint 扩展

### Requirement: 与后端 API 通信

前端 SHALL 使用 TanStack Query v5 和统一 API 客户端与后端通信。

#### Scenario: API 基础 URL 配置

- **WHEN** 前端发起 API 请求
- **THEN** 开发环境 SHALL 通过 Vite proxy 转发 /api 请求到后端
- **THEN** 生产环境 SHALL 使用环境变量 VITE_API_BASE 配置基础 URL
- **THEN** 前端 SHALL NOT 硬编码 API 基础 URL

#### Scenario: 统一 API 客户端

- **WHEN** 进行 API 调用
- **THEN** 所有调用 SHALL 通过 api/client.ts 的 request<T>() 方法
- **THEN** 错误处理 SHALL 统一抛出 ApiError（包含 status 和 message）
- **THEN** 开发环境 SHALL 使用 Vite proxy 转发 API 请求

#### Scenario: 字段名转换

- **WHEN** 接收后端 API 响应
- **THEN** API 层 SHALL 将 snake_case 字段转换为 camelCase
- **WHEN** 发送请求到后端 API
- **THEN** API 层 SHALL 将 camelCase 字段转换为 snake_case
- **THEN** hooks 和组件 SHALL 仅使用 camelCase 字段

#### Scenario: TanStack Query 数据管理

- **WHEN** 页面加载数据
- **THEN** 前端 SHALL 使用 TanStack Query 的 useQuery hook
- **THEN** 前端 SHALL 自动缓存请求结果
- **THEN** 前端 SHALL 自动处理加载和错误状态

#### Scenario: TanStack Query 写操作

- **WHEN** 用户执行创建、更新或删除操作
- **THEN** 前端 SHALL 使用 TanStack Query 的 useMutation hook
- **THEN** 操作成功后 SHALL 自动失效相关查询缓存
- **THEN** 操作失败 SHALL 显示错误提示

#### Scenario: 错误提示

- **WHEN** API 请求失败（网络错误、4xx、5xx）
- **THEN** 前端 SHALL 显示全局错误提示
- **THEN** 错误消息 SHALL 具有描述性

