## ADDED Requirements ### Requirement: 最小必要改动 同一功能或缺陷修复的代码变更 SHALL 仅包含完成任务所必需的修改;不得在同一提交中进行无关重构、大范围格式化或与需求无关的文件挪动。 #### Scenario: 修复单接口校验 - **WHEN** 修复某一接口字段校验 - **THEN** diff SHALL 主要限于该接口、对应 schema 与必要测试,而不包含同模块无关重命名 ### Requirement: Web 管理端工程约定 `web-admin` SHALL 遵循 `AGENTS.md` 中的栈与工具约定:包管理使用 pnpm;格式化以 Prettier(单引号、无分号、2 空格等)为准;ESLint/Stylelint 规则与仓库配置一致;Vue 与 Element Plus 依赖自动导入策略时,新增全局组件 SHALL 遵循既有 `unplugin` 配置而非随意手写重复导入(除非文件类型要求)。 #### Scenario: 提交前检查 - **WHEN** 提交 `web-admin` 源码 - **THEN** 贡献者 SHALL 在本地或通过 CI 运行与仓库一致的 lint/format 门禁 ### Requirement: 后端工程约定 `backend` SHALL 使用 Python 3.9 与既有 FastAPI、SQLAlchemy、Pydantic v2 模式;新增领域逻辑 SHALL 优先落在 `app/services/` 与 `app/crud/`,路由层保持薄;配置 SHALL 通过 `pydantic-settings` 与 `.env` 系列文件管理。 #### Scenario: 新增业务接口 - **WHEN** 新增一组管理端 API - **THEN** 路由 SHALL 放入 `app/api/` 合适模块,schema 放入 `app/schemas/`,数据库访问放入 `app/crud/` 或复用基类 ### Requirement: 数据库脚本与迁移 涉及表结构变更时,维护者 SHALL 在 `docs/sql/` 中维护或更新与需求一致的 SQL 说明(全量基线导出为 `zkxw.sql`;增量变更可补充同目录下其他 `.sql`),并评估 Alembic/脚本与运行环境的同步(与 `AGENTS.md` 数据库变更约定一致)。 #### Scenario: 新增业务表 - **WHEN** 增加新表或字段 - **THEN** 仓库 SHALL 包含可复现的 schema 片段或迁移,且与 OpenSpec `database-conventions` 中的公共字段规则一致 ### Requirement: 单文件行数上限 前端(`web-admin`)与后端(`backend`)的单个源码文件 SHALL 不超过 500 行。当文件因功能增长接近或超过此限制时,SHALL 将其拆分为多个职责单一的小文件(如:前端提取子组件、composable 或工具函数;后端按领域拆分 service、crud 模块或辅助函数)。配置文件、类型声明文件、SQL 脚本与自动生成文件不在此限制范围内。 #### Scenario: 前端页面文件增长 - **WHEN** 一个 Vue 组件文件超过 500 行 - **THEN** SHALL 将弹窗、复杂表格、可复用面板等拆分为独立子组件或 composable 函数,确保主入口文件在 500 行以内 #### Scenario: 后端模块文件增长 - **WHEN** 一个 Python 模块文件(api route、service、crud 等)超过 500 行 - **THEN** SHALL 按领域或功能边界拆分为多个子模块(如 `crud/production_task.py` → `crud/production_task.py` + `crud/production_task_step.py`),确保单个文件在 500 行以内 ### Requirement: 提交说明可读性 提交信息 SHALL 使用完整句子说明「改了什么、为何」,并符合仓库 Conventional Commits / commitlint 类型约束。 #### Scenario: merge request - **WHEN** 创建合并请求 - **THEN** 描述 SHALL 使评审者无需阅读聊天历史即可理解动机与风险 ### Requirement: 前端 API 返回值消费约定 `web-admin` 的 axios 拦截器(`src/utils/http/index.ts`)已对响应做两层解包:`res.data.data` → 返回值即为后端 `data` 字段的内容。因此页面组件中消费 API 函数返回值时,SHALL NOT 再做 `{ data }` 解构或 `.data` 取值,SHALL 直接从返回值中读取业务字段。 #### Scenario: 调用列表 API - **GIVEN** 后端返回 `{"code": 200, "data": {"list": [...], "total": 12}}` - **WHEN** 页面调用 `const result = await fetchXxxList(params)` - **THEN** `result` 已经是 `{"list": [...], "total": 12}`,SHALL 使用 `result.list`、`result.total`,SHALL NOT 写 `const { data } = await fetchXxxList()` 再从 `data.list` 取值 #### Scenario: 调用单条 API - **GIVEN** 后端返回 `{"code": 200, "data": {"smsContent": "..."}}` - **WHEN** 页面调用 `const result = await fetchGenerateSms(id)` - **THEN** `result` 已经是 `{"smsContent": "..."}`,SHALL 使用 `result.smsContent`,SHALL NOT 写 `const { data } = await fetchGenerateSms()` 再从 `data.smsContent` 取值 #### Scenario: 新增 API 调用前确认返回值层级 - **WHEN** 编写新的页面消费 API 函数 - **THEN** 开发者 SHALL 先查看 `src/utils/http/index.ts` 第 420 行的 `return res.data.data as T`,确认拦截器已剥离 `{code, data}` 外层包装,消费端直接拿返回值即可 ### Requirement: 前后端 HTTP 方法一致性 前端 API 函数的 HTTP 方法(GET / POST / PUT / DELETE)SHALL 与后端路由装饰器(`@router.get` / `@router.post` / `@router.put` / `@router.delete`)严格一致。有副作用的操作(创建、更新、状态变更、内容生成)SHALL 使用 POST 或 PUT。 #### Scenario: 新增 API 接口 - **WHEN** 后端定义 `@router.post("/generate-sms")` - **THEN** 前端对应的 `fetchGenerateSms` SHALL 使用 `request.post()`,SHALL NOT 使用 `request.get()` #### Scenario: 代码评审检查项 - **WHEN** 评审涉及前后端联调的变更 - **THEN** 评审者 SHALL 校验前端 `request.get/post/put/delete` 与后端 `@router.get/post/put/delete` 的方法一一匹配