Commit 62aeb80fde57075d32ef58a97fc03781ad6c2b91

Authored by zichun
0 parents

chore: plan phase done

.githooks/pre-push 0 → 100755
  1 +++ a/.githooks/pre-push
  1 +#!/usr/bin/env bash
  2 +# .githooks/pre-push — run scripts/test.sh before every push.
  3 +# No --no-verify: the claude-code hook deny-no-verify.sh also blocks it.
  4 +
  5 +set -euo pipefail
  6 +
  7 +cd "$(git rev-parse --show-toplevel)"
  8 +
  9 +./scripts/test.sh
.gitignore 0 → 100644
  1 +++ a/.gitignore
  1 +# ==== ERP 插件推荐忽略项(skeleton-gen 追加) ====
  2 +# 本地运行时配置(含真实凭据,严禁入库)
  3 +.env.local
  4 +.env.*.local
  5 +
  6 +# Java / Maven
  7 +target/
  8 +*.class
  9 +
  10 +# Node / 前端构建产物
  11 +node_modules/
  12 +dist/
  13 +build/
  14 +coverage/
  15 +
  16 +# IDE
  17 +.idea/
  18 +.vscode/
  19 +*.iml
  20 +
  21 +# OS
  22 +.DS_Store
  23 +Thumbs.db
  24 +
  25 +# 日志
  26 +*.log
  27 +logs/
  28 +
  29 +# 插件运行时临时文件
  30 +.tmp/
  31 +*.raw
  32 +# ==== 结束 ====
CLAUDE.md 0 → 100644
  1 +++ a/CLAUDE.md
  1 +# CLAUDE.md — ERP项目 Claude Code 主指令文件
  2 +
  3 +> 本文件是 Claude Code 的"操作手册"。Claude Code 启动时会自动读取此文件。
  4 +
  5 +---
  6 +
  7 +## 🎯 项目概述
  8 +
  9 +- **项目名称**: 小羚羊
  10 +- **项目简述**: 测试ERP
  11 +- **目标用户**: 企业内部管理人员
  12 +- **部署方式**: 私有化部署
  13 +
  14 +---
  15 +
  16 +## 🔄 B 阶段开发流程(模块循环 + 功能循环)
  17 +
  18 +两层嵌套循环**全部固化到 skills**。入口:`/erp-workflow:coding-start`。
  19 +
  20 +- **模块循环(外)**:`module-start` → `test-gate` → `module-report` → `mr-create` → 人工 Approve MR → 下一模块
  21 +- **功能循环(内,每 REQ-XXX-NNN 一遍)**:`feature-brainstorm` → `feature-plan` → `feature-tdd` → `feature-verify` → `feature-review`
  22 +
  23 +MR 前测试闸门:
  24 + - `test-gate`(子会话跑 `scripts/test.sh` 全量——本模块所有 REQ + 已完成模块回归);
  25 + - `.githooks/pre-push` 兜底
  26 + - `git push --no-verify` 被 `deny-no-verify.sh` 硬拦。
  27 +
  28 +---
  29 +
  30 +## ✅ 模块完成判定规则
  31 +
  32 +`docs/08-模块任务管理.md § 二` 是**模块元数据表**——每个模块记录依赖 / 路径 / MR iid / 功能(REQ)子项清单。**模块完成由 `MR:` 字段 + `GitLab API state=merged` 判定**;功能子项勾选只作可视化进度,不参与模块完成判定。
  33 +
  34 +### 规则定义
  35 +
  36 +每个模块在 docs/08 § 二 中长这样:
  37 +
  38 +```markdown
  39 +- module_0 系统管理
  40 + - 依赖: —
  41 + - 路径: backend/module/sys/, frontend/pages/sys/
  42 + - MR: —
  43 + - 功能:
  44 + - [ ] REQ-SYS-001 用户登录
  45 + - [ ] REQ-SYS-002 用户注册
  46 +```
  47 +
  48 +- `MR:` 字段由 `mr-create` 在创建 MR 时从 `—` 改为 `!<iid>`。
  49 +- 每个 `REQ-*` 子项由 `feature-review` 在 `verdict=approve` 时自动勾选为 `[x]`
  50 +- 子项全部勾选不等于模块完成,模块完成判定仍以 `MR:` + GitLab API state 为准。
  51 +
  52 +### 模块状态语义
  53 +
  54 +| `MR:` 字段 | `GitLab API state` | 含义 | 你(Claude Code)的行为 |
  55 +|---|---|---|---|
  56 +| `—` | — | 模块未开始(未创建 MR) | ✅ 开始本模块开发 |
  57 +| `!<iid>` | `opened` / `closed` | 模块开发中 / 打回 | ✅ 继续推进该模块 |
  58 +| `!<iid>` | `merged` | 模块**已完成** | 🟢 进入下一未完成模块 |
  59 +
  60 +### 模块完成报告
  61 +
  62 +由 `module-report` skill 产出,模板位于 由 module-report skill 持有(12 节标准化,含跨模块改动等 CLAUDE.md 软规则映射节)。CC 不手写模块报告,仅填模板。
  63 +
  64 +---
  65 +
  66 +## 🏷️ 占位符统一约定
  67 +
  68 +项目文档里有 **2 种填写占位** + **1 种提示占位**:
  69 +
  70 +| 格式 | 谁填 | 使用阶段 | 说明 |
  71 +|------|-----|---------|------|
  72 +| `【人工填写:<简短说明>】` | 人 | 仅 A 阶段文档 | 密钥 / 账密 / 包名 / 命名约定 / 小版本号等人工才能决定的值;B 阶段 plan/spec 禁止出现,查不到真值时用 `AskUserQuestion` 问用户 |
  73 +| `TBD(<责任人>)` | CC 自动 | A 或 B | 后缀附带责任方(如 `TBD(A3 自动补)` / `TBD(A5 自动补)`);由对应 skill 就地补填,`module-report` § ⑦ 检查 `TBD(CC 补)` 残留 |
  74 +
  75 +**HTML 注释 `<!-- ... -->`**:提示占位,是**插件内部大纲模板**里给 LLM 的**填空提示 / 章节引导**,指引 LLM 按结构填实际内容。skill 生成时会**剥除**这些注释,最终产物里注释不会保留。
  76 +
  77 +---
  78 +
  79 +## 📐 编码行为约束
  80 +
  81 +### 你必须做的 ✅
  82 +
  83 +1. **严格遵循** `docs/04-技术规范.md`——命名 / 编码 / 统一响应 / 异常处理 / 数据访问 / 配置与安全 等项目专属技术规约全部在此
  84 +2. **严格遵循** `docs/09-项目目录结构.md`——文件放对位置
  85 +3. **每个后端接口** 必须先在 `docs/05-API接口契约.md` 定义,再编码实现
  86 +4. **每个功能可追溯到 `REQ-XXX-NNN`**——commit tag + 代码注释(如 `// REQ-SYS-001: 用户登录`)+ plan/spec 文件名均用此 tag
  87 +5. **遇到跨模块改动**(动到非当前模块的代码)——按 § 🟡 软规则 **S2** 执行(允许改,但必须留痕)
  88 +6. **遇到技术栈外组件引入**(`docs/04 § 零` 技术栈表外的框架 / 中间件 / 关键库),按 § 🟡 软规则 **S1** 执行(允许引入,但必须先 AskUserQuestion)
  89 +
  90 +### 你禁止做的 🚫
  91 +
  92 +1. **主会话直接 `mysql -e` 跑业务 DDL**(只读查询 / 临时本地调试除外)——业务 schema 必须走 `sql/migrations/V_n__*.sql`,详见下方 Schema 演化规约
  93 +2. **手动 Edit `docs/08 § 二` 的 `MR:` 字段**,必须要由 `mr-create` 自动回写
  94 +
  95 +### Schema 演化规约(Flyway migration)
  96 +
  97 +1. **文件命名**:`sql/migrations/V<n>__<snake_case_desc>.sql`,例:`V5__add_user_email_unique_index.sql`
  98 +2. **版本号分配**:建文件前 `ls sql/migrations/V*.sql` 查当前最大 n,新文件 `n_max + 1`
  99 +3. **Apply 方式**:Spring Boot 启动 / 测试启动时 Flyway 自动 apply(项目必须在 `pom.xml` 声明 `flyway-core` + `flyway-mysql` 依赖)。`scripts/setup-test-db.sh` 只负责清空库,不做 apply
  100 +4. **已合并的 migration 永不修改**:发现错了写一个补救 migration(如 `V7__fix_V5_index_name.sql`),旧 `V_n.sql`
  101 +5. **临时调试 DDL**:临时在本地试字段/索引可手动 `mysql -e`,但不写 migration;下次 `setup-test-db.sh` 会 drop+create 清掉
  102 +6. **A4 生成的 V1**:`V1__initial_schema.sql` 是 A 阶段由 `db-init` 从 `docs/03-数据库设计文档.md`(A3 正向设计的 schema SSoT)翻译生成的初始版本;后续 V2/V3/... 由 B 阶段每个 REQ 按需写入,**同时**反向同步更新 docs/03 对应表小节以保持 SSoT 一致
  103 +
  104 +---
  105 +
  106 +## 🗂️ Git 提交规范
  107 +
  108 +每次提交必须遵循以下格式:
  109 +
  110 +```
  111 +<type>(<scope>): <subject>
  112 +```
  113 +
  114 +- `scope`: 模块名,如 `user` / `inventory` / `order`
  115 +- `subject`: 简短描述;业务类(feat / fix / test)必须带 `REQ-XXX-NNN` 后缀
  116 +
  117 +`type` 含义:
  118 +
  119 +| type | 看到它意味着 |
  120 +|-----|-------------|
  121 +| `feat` | **新能力上线**——用户多了一个功能、接口、页面或业务规则 |
  122 +| `fix` | **修 bug**——原来行为错了,这次改对 |
  123 +| `refactor` | **重构**——外部行为不变,只改代码结构 / 命名 / 抽象 |
  124 +| `docs` | **文档改动**——只动 Markdown / 代码注释,不动实现 |
  125 +| `style` | **格式调整**——空白 / 缩进 / import 顺序,逻辑 0 变化 |
  126 +| `test` | **只动测试代码**——补用例 / 修 fixture,不碰实现 |
  127 +| `chore` | **流程维护**——构建 / 依赖 / 工具 / 证据档案 / MR 元数据等非业务动作 |
  128 +
  129 +---
  130 +
  131 +## 🚩 中断机制
  132 +
  133 +功能循环(每个功能 REQ-XXX 的 Brainstorm → Plan → TDD → Verify → AI 自审)默认 **静默编程**,但触发以下任何一条必须**立刻停下、记录原因、等人决策**,不得自行绕过:
  134 +
  135 +| # | 中断 | 例子 |
  136 +| - | --- | --- |
  137 +| 1 | **测试反复失败** | 同一测试同一功能内连续 **10 次**修复失败 |
  138 +| 2 | **要改密钥 / 账密 / 包名** | `docs/07-环境配置.md` 里由人工标注必须填的字段 |
  139 +| 3 | **外部接口不可达** | 第三方 API 无法连接、证书失效等环境问题,并无法自行解决 |
  140 +
  141 +> 其余需要人类判断的场景一律走普通 `AskUserQuestion` Q&A,不中断、不写 Blocker 文件。
  142 +
  143 +**触发中断时的固定动作:**
  144 +
  145 +1. 在当前功能的 plan 文件里追加一节 `## 🚩 Blocker`(报告格式由 `interrupt-check` 的 `interrupt-block-template.md` 持有)
  146 +2. 停止后续所有功能的静默执行
  147 +3. 在主会话输出一句话摘要 + 指向 blocker 文件的路径,等人回复
  148 +
  149 +---
  150 +
  151 +## 🟡 软规则(允许继续,但有强制后续动作)
  152 +
  153 +以下情况 **不触发中断**,CC 可自行继续推进,但必须在约定位置留痕,模块完成时统一审计。
  154 +
  155 +| # | 软规则 | 允许动作 | 强制后续 |
  156 +| - | ----- | ------- | ------- |
  157 +| S1 | **技术栈外组件引入** | 用 `AskUserQuestion` 给用户三选一:接受引入 / 换方案 / 拒绝 | ① **接受** → 同会话直接在 `docs/04 § 零` 追加一行 → 继续流程 ② **换方案 / 拒绝** → 视为常规歧义澄清,继续 Q&A 收敛 ③ 不写 Blocker、不中断流程 |
  158 +| S2 | **跨模块改动** | **默认不改**,仅为当前模块实现所必需时允许修改 | ① hook `log-cross-module.sh` 自动落存根 ② `module-report` 一次性调用 `cross-module-log` skill 批量补齐「原因 / 影响评估」+ 「跨模块改动」节完整贴入《模块完成报告》 |
  159 +
  160 +---
  161 +
  162 +## 🧭 通用工作准则(General Principles)
  163 +
  164 +### 1. Think Before Coding
  165 +
  166 +**Don't assume. Don't hide confusion. Surface tradeoffs.**
  167 +
  168 +Before implementing:
  169 +- State your assumptions explicitly. If uncertain, ask.
  170 +- If multiple interpretations exist, present them - don't pick silently.
  171 +- If a simpler approach exists, say so. Push back when warranted.
  172 +- If something is unclear, stop. Name what's confusing. Ask.
  173 +
  174 +### 2. Simplicity First
  175 +
  176 +**Minimum code that solves the problem. Nothing speculative.**
  177 +
  178 +- No features beyond what was asked.
  179 +- No abstractions for single-use code.
  180 +- No "flexibility" or "configurability" that wasn't requested.
  181 +- No error handling for impossible scenarios.
  182 +- If you write 200 lines and it could be 50, rewrite it.
  183 +
  184 +Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify.
  185 +
  186 +### 3. Surgical Changes
  187 +
  188 +**Touch only what you must. Clean up only your own mess.**
  189 +
  190 +When editing existing code:
  191 +- Don't "improve" adjacent code, comments, or formatting.
  192 +- Don't refactor things that aren't broken.
  193 +- Match existing style, even if you'd do it differently.
  194 +- If you notice unrelated dead code, mention it - don't delete it.
  195 +
  196 +When your changes create orphans:
  197 +- Remove imports/variables/functions that YOUR changes made unused.
  198 +- Don't remove pre-existing dead code unless asked.
  199 +
  200 +The test: Every changed line should trace directly to the user's request.
  201 +
  202 +### 4. Goal-Driven Execution
  203 +
  204 +**Define success criteria. Loop until verified.**
  205 +
  206 +Transform tasks into verifiable goals:
  207 +- "Add validation" → "Write tests for invalid inputs, then make them pass"
  208 +- "Fix the bug" → "Write a test that reproduces it, then make it pass"
  209 +- "Refactor X" → "Ensure tests pass before and after"
  210 +
  211 +For multi-step tasks, state a brief plan:
  212 +```
  213 +1. [Step] → verify: [check]
  214 +2. [Step] → verify: [check]
  215 +3. [Step] → verify: [check]
  216 +```
  217 +
  218 +Strong success criteria let you loop independently. Weak criteria ("make it work") require constant clarification.
docs/01-需求清单/USR-用户管理/REQ-USR-001.md 0 → 100644
  1 +++ a/docs/01-需求清单/USR-用户管理/REQ-USR-001.md
  1 +### REQ-USR-001 增加用户
  2 +
  3 +**目标**: 管理员在后台新建用户账号,指定用户名、密码及角色,账号立即生效可用
  4 +
  5 +- **输入**:
  6 +
  7 + - **表1**:
  8 +
  9 + | 字段 | 类型 | 必填 | 输入方式 | 显示来源 | 默认值 | 业务规则 |
  10 + | -------- | ---- | --- | ---- | ----------------- | --------- | ------------------- |
  11 + | 创建时间 | 日期时间 | — | 系统生成 | — | 当前日期 | 保存后自动生成;只读 |
  12 + | 制单人 | 文本 | — | 系统生成 | — | 当前登录用户 | 保存后自动生成;只读 |
  13 + | 员工名 | 文本 | 否 | 下拉单选 | `职员表` | — | 关联职员(可选) |
  14 + | 用户号 | 文本 | 是 | 手工输入 |— | — | 关联职员选择后自动输入员工姓名 |
  15 + | 用户名 | 文本 | 是 | 手工输入 |— | — | 关联职员选择后自动输入员工姓名 |
  16 + | 类型 | 文本 | 是 | 下拉单选 | 普通用户/超级管理员 | 普通用户 | — |
  17 + | 语言 | 文本 | 是 | 下拉单选 | 中文/英文/繁体 | — | — |
  18 + | 单据修改权限 | 布尔 | 否 | 复选框 | — | 否 | — |
  19 + | 密码 | 文本 | - | 系统生成 | 不显示 | 666666 | 保存后自动设为初始化 |
  20 +
  21 + - **表2** - 权限组:
  22 +
  23 + | 字段 | 类型 | 必填 | 输入方式 | 显示来源 | 默认值 | 业务规则 |
  24 + | -------- | ---- | --- | ---- | ----------------- | --------- | ------------------- |
  25 + | 复选框 | 布尔 | 否 | 复选框 | — | 否 | 是否选择当前行权限 |
  26 + | 权限分类 | 文本 | — | — | — | — | — |
  27 +
  28 +
  29 +- **输出**:
  30 +
  31 + - **表1**:
  32 +
  33 + | 字段 | 类型 | 显示来源 |
  34 + | --- | --- | --- |
  35 + | 用户号 | 文本 | - |
  36 +
  37 +- **跨字段规则**: 用户名在系统内全局唯一;角色取值受系统配置约束
  38 +- **边界**: 密码以哈希形式存储
  39 +- **验收**: 提交合法数据后用户记录出现在列表;重复用户名返回错误提示;普通账号无权访问此功能
  40 +- **依赖表**: `usr_user`(写入用户主记录), `tStaff`(读取员工名下拉列表), `usr_permission_group`(读取可选权限列表), `usr_user_permission`(写入用户权限关联)
  41 +- **依赖接口**: `POST /api/auth/login`(REQ-USR-004,需鉴权);`POST /api/usr/users`(本 REQ 自身接口)
docs/01-需求清单/USR-用户管理/REQ-USR-002.md 0 → 100644
  1 +++ a/docs/01-需求清单/USR-用户管理/REQ-USR-002.md
  1 +### REQ-USR-002 修改用户
  2 +
  3 +**目标**: 管理员可更新已有用户的基本信息(姓名、角色、状态等),修改实时生效
  4 +
  5 +- **输入**: 选中目标
  6 +
  7 + - **表1**:
  8 +
  9 + | 字段 | 类型 | 必填 | 输入方式 | 显示来源 | 默认值 | 业务规则 |
  10 + | -------- | ---- | --- | ---- | ----------------- | --------- | ------------------- |
  11 + | 创建时间 | 日期时间 | — | 系统生成 | — | 原值 | 保存后自动生成;只读 |
  12 + | 制单人 | 文本 | — | 系统生成 | — | 原值 | 保存后自动生成;只读 |
  13 + | 员工名 | 文本 | 否 | 下拉单选 | `职员表` | 原值 | 关联职员(可选) |
  14 + | 用户号 | 文本 | 是 | 手工输入 |— | 原值 | 关联职员选择后自动输入员工姓名 |
  15 + | 用户名 | 文本 | 是 | 手工输入 |— | 原值 | 关联职员选择后自动输入员工姓名 |
  16 + | 类型 | 文本 | 是 | 下拉单选 | 普通用户/超级管理员 | 原值 | — |
  17 + | 语言 | 文本 | 是 | 下拉单选 | 中文/英文/繁体 | 原值 | — |
  18 + | 单据修改权限 | 布尔 | 否 | 复选框 | — | 原值 | — |
  19 + | 密码 | 文本 | - | 系统生成 | 不显示 | 原值 | 保存后自动设为初始化 |
  20 +
  21 + - **表2** - 权限组:
  22 +
  23 + | 字段 | 类型 | 必填 | 输入方式 | 显示来源 | 默认值 | 业务规则 |
  24 + | -------- | ---- | --- | ---- | ----------------- | --------- | ------------------- |
  25 + | 复选框 | 布尔 | 否 | 复选框 | — | 原值 | 是否选择当前行的权限 |
  26 + | 权限分类 | 文本 | — | — | — | — | — |
  27 +
  28 +- **输出**:
  29 +
  30 + - **表1**:
  31 +
  32 + | 字段 | 类型 | 显示来源 |
  33 + | --- | --- | --- |
  34 + | 用户 id | 文本 | `职员表` |
  35 +
  36 +- **跨字段规则**: 密码不在该接口修改;角色变更需具备相应权限
  37 +- **边界**: 必须传入有效用户 id;字段格式与新增一致
  38 +- **验收**: 修改角色或状态后立即反映在用户列表;被禁用账号无法登录并收到明确提示
  39 +- **依赖表**: `usr_user`(更新用户基本信息), `tStaff`(读取员工名下拉列表), `usr_permission_group`(读取可选权限列表), `usr_user_permission`(先删后插,更新权限关联)
  40 +- **依赖接口**: `POST /api/auth/login`(REQ-USR-004,需鉴权);`GET /api/usr/users`(REQ-USR-003,查询定位目标用户);`PUT /api/usr/users/{userId}`(本 REQ 自身接口)
docs/01-需求清单/USR-用户管理/REQ-USR-003.md 0 → 100644
  1 +++ a/docs/01-需求清单/USR-用户管理/REQ-USR-003.md
  1 +### REQ-USR-003 查询用户
  2 +
  3 +**目标**: 管理员可按用户名、角色或状态筛选并分页浏览用户列表
  4 +
  5 +- **输入**:
  6 +
  7 + - **表1**:
  8 +
  9 + | 字段 | 类型 | 必填 | 输入方式 | 显示来源 | 默认值 | 业务规则 |
  10 + | ---- | ---- | --- | ---- | ----------------------------------------------- | ------- | --------------- |
  11 + | 查询字段 | 文本 | 否 | 下拉单选 | 用户名/员工名/用户号/部门/用户类型/作废/登录日期/制单人 | 用户名 | — |
  12 + | 匹配方式 | 文本 | 否 | 下拉单选 | 包含/不包含/等于 | 包含 | — |
  13 + | 查询值 | 文本 | 否 | 手工输入 | — | — | 与「查询字段」配合使用,空为选择全部 |
  14 +
  15 +- **输出**:
  16 +
  17 + - **表1**:
  18 +
  19 + | 字段 | 类型 | 显示来源 |
  20 + | ---- | ---- | ----- |
  21 + | 序号 | 数字 | 系统生成 |
  22 + | 用户名 | 文本 | `用户表` |
  23 + | 员工名 | 文本 | `职员表` |
  24 + | 用户号 | 文本 | `用户表` |
  25 + | 部门 | 文本 | `职员表` |
  26 + | 用户类型 | 文本 | `用户表` |
  27 + | 语言 | 文本 | `用户表` |
  28 + | 作废 | 布尔 | `用户表` |
  29 + | 登录日期 | 日期时间 | `用户表` |
  30 + | 制单人 | 文本 | `用户表` |
  31 + | 制单日期 | 日期时间 | `用户表` |
  32 +
  33 +- **跨字段规则**: -
  34 +- **边界**: 单页最大条数受限(默认 100);密码与敏感字段不返回;查询为只读,不产生写副作用
  35 +- **验收**: 按条件筛选返回正确结果集;无匹配时返回空列表而非报错;分页参数越界时返回最后一页
  36 +- **依赖表**: `usr_user`(主查询表), `tStaff`(关联查询员工名 / 部门,对应输出列), `usr_user_permission`(可关联查询用户权限)
  37 +- **依赖接口**: `POST /api/auth/login`(REQ-USR-004,需鉴权);`GET /api/usr/users`(本 REQ 自身接口)
docs/01-需求清单/USR-用户管理/REQ-USR-004.md 0 → 100644
  1 +++ a/docs/01-需求清单/USR-用户管理/REQ-USR-004.md
  1 +### REQ-USR-004 用户登录
  2 +
  3 +**目标**: 用户通过用户名+密码完成身份认证,获取 JWT Token 用于后续接口鉴权
  4 +
  5 +- **输入**:
  6 +
  7 + - **表1**:
  8 +
  9 + | 字段 | 类型 | 必填 | 输入方式 | 显示来源 | 默认值 | 业务规则 |
  10 + | --- | ---- | --- | ---- | ------- | --- | ------------------- |
  11 + | 用户名 | 文本 | 是 | 手工输入 | — | — | - |
  12 + | 密码 | 文本 | 是 | 手工输入 | — | — | 输入显示星号 |
  13 + | 版本 | 文本 | 是 | 下拉单选 | `公司表` | 标准版 | |
  14 +
  15 +- **输出**: 成功/失败
  16 +
  17 +- **跨字段规则**: 校验用户名 + 密码哈希;连续失败达到阈值临时锁定账号;登录成功签发限时 token 并返回基本用户信息
  18 +- **边界**: token 设置合理过期时间;接口需具备防暴力破解保护
  19 +- **验收**: 正确凭据返回 Token 且可通过鉴权接口验证;错误密码返回通用错误消息(不区分用户名或密码错误);锁定账号返回锁定提示
  20 +- **依赖表**: `usr_user`(校验用户名+密码哈希、读写登录失败计数与锁定时间、更新最后登录时间), `brand`(读取公司/版本下拉列表,并作为登录账号 `sNo` 前缀来源)
  21 +- **依赖接口**: 自身即认证入口,无依赖接口;`POST /api/auth/login`
docs/01-需求清单/USR-用户管理/_module.md 0 → 100644
  1 +++ a/docs/01-需求清单/USR-用户管理/_module.md
  1 +# USR-用户管理
  2 +
  3 +- **模块简述**: 用户账户的全生命周期管理,包括新建、编辑、查询及身份认证
  4 +- **依赖模块**: —(无模块间依赖)
  5 +- **涉及表**: `usr_user`, `usr_permission_group`, `usr_user_permission`
docs/01-需求清单/index.md 0 → 100644
  1 +++ a/docs/01-需求清单/index.md
  1 +# 需求清单
  2 +
  3 +> 本目录按模块组织所有功能需求。每个模块一个子目录,含 `_module.md`(模块头)和 `REQ-XXX-NNN.md`(每张 REQ 卡片一个文件)。下方核心功能点供 CC 拆分出 REQ 编号 + 标题 + 草拟规则;卡片内输入 / 输出的简述句和 N 张字段表由人工编辑。
  4 +
  5 +## 模块索引
  6 +
  7 +| 模块代码 | 模块名称 | 核心功能点(简要) |
  8 +| ---- | ---- | ------------------- |
  9 +| USR | 用户管理 | 增加用户,修改用户,查询用户,登录用户 |
  10 +
  11 +## 填写说明
  12 +
  13 +1. 每个模块占一行,`模块代码` 用大写英文缩写(如 SYS / PUR / INV / SAL / FIN / HR)
  14 +2. `核心功能点` 只需列关键词,CC 会基于此拆分出 N 张 REQ 卡片骨架(卡片内输入 / 输出的简述句和字段表仍由人工编辑)
  15 +3. 填完后运行 `/erp-workflow:plan-start`,CC 会自动检测并进入需求生成阶段
docs/02-开发计划.md 0 → 100644
  1 +++ a/docs/02-开发计划.md
  1 +# 02-开发计划
  2 +
  3 +## 一、模块依赖表
  4 +
  5 +| 模块 ID | 模块名 | 依赖模块 | 依赖表 |
  6 +|---|---|---|---|
  7 +| module_usr | 用户管理 | — | `usr_user`, `tStaff`, `usr_permission_group`, `usr_user_permission`, `brand` |
  8 +
  9 +## 二、开发顺序清单(CC 分发权威)
  10 +
  11 +> 本清单由 A5 `downstream-gen` 一次性生成。**每行是一个 REQ**,不是模块。CC 按表格行序从上到下扫描,对每个 REQ 所属模块查 `docs/08 § 二` 的 `MR:` 字段 + GitLab API `state`:`merged` 跳过,其他(`—` / opened / closed / 查不到)选为当前模块;`module-start` 会把该模块的所有 REQ 一次做完。
  12 +>
  13 +> **约束**:同一模块的所有 REQ 必须**连续排列**。允许打破依赖拓扑(如环依赖、业务必须先做),但必须在「备注」列写明原因。
  14 +
  15 +| # | REQ | 所属模块 | 选中理由 | 备注 |
  16 +|---|-----|---------|---------|------|
  17 +| 1 | **REQ-USR-004** | module_usr | 所属模块无依赖,基础模块;登录是所有操作的认证基础 | — |
  18 +| 2 | **REQ-USR-001** | module_usr | 依赖 REQ-USR-004 已在前;先创建用户才能操作 | — |
  19 +| 3 | **REQ-USR-003** | module_usr | 依赖 REQ-USR-001 已在前;查询依赖有数据可查 | — |
  20 +| 4 | **REQ-USR-002** | module_usr | 依赖 REQ-USR-001/003 已在前;修改依赖先能创建和查询 | — |
  21 +
  22 +## 三、关键说明
  23 +
  24 +- 当前项目仅含 **1 个模块**(module_usr 用户管理),无模块间依赖,拓扑排序退化为单链。
  25 +- 模块内 REQ 顺序优先级:认证(004)> 写操作创建(001)> 只读查询(003)> 写操作修改(002)。
  26 +- `tStaff` 和 `brand` 为跨模块基础数据表,USR 模块只读引用,不需要先实现对应写接口。
docs/03-数据库设计文档.md 0 → 100644
  1 +++ a/docs/03-数据库设计文档.md
  1 +# 03-数据库设计文档
  2 +
  3 +- **Schema**: `xlyweberp_vibe_erp_test`
  4 +- **Migration 清单**: `sql/migrations/V*.sql`(由 Flyway 顺序 apply)
  5 +- **生成方式**: 由 A3 `db-design-gen` 基于 `docs/01-需求清单/<module>/REQ-*.md` REQ 卡片正向设计生成(schema SSoT)。
  6 +
  7 +## 项目标准列约定
  8 +
  9 +下文每张业务表的字段清单都自动包含以下 5 个标准列(匈牙利前缀 `i` int / `s` varchar / `t` datetime)。渲染时由 `docs-03-table-template.md` 模板内置原样输出。
  10 +
  11 +| 列名 | 类型 | 可空 | 主键 | 说明 |
  12 +|---|---|---|---|---|
  13 +| `iIncrement` | int | 否 | 是 | 整数主键 ID(自增方式由实现决定:DB `AUTO_INCREMENT` 或应用 / 触发器分配) |
  14 +| `sId` | varchar(100) | 是 | — | 业务 ID(对外暴露的字符串标识,如 UUID / 人类可读编号) |
  15 +| `sBrandsId` | varchar(100) | 是 | — | 品牌 ID(多租户隔离) |
  16 +| `sSubsidiaryId` | varchar(100) | 是 | — | 子公司 ID(组织层级隔离) |
  17 +| `tCreateDate` | datetime | 否 | — | 记录创建时间 |
  18 +
  19 +字典 / 辅助表如有豁免,在该表业务注记里注明豁免原因。
  20 +
  21 +## ER 关系概览
  22 +
  23 +```
  24 +usr_user(用户主表)
  25 + └─< usr_user_permission(用户权限关联,多对多中间表)>─ usr_permission_group(权限分类表)
  26 +
  27 +跨模块引用(当前阶段记录,对应表由后续模块设计):
  28 + usr_user.sEmployeeId → 职员表.sId(跨模块,可空,ON DELETE SET NULL)
  29 +```
  30 +
  31 +## 表清单
  32 +
  33 +- `usr_user` — 用户账户主表,存储登录信息、类型、语言偏好及安全控制字段
  34 +- `tStaff` — 职员维度(员工名 / 部门 / 编号)
  35 +- `usr_permission_group` — 权限分类/权限组定义表,每行对应一个可分配给用户的权限项
  36 +- `usr_user_permission` — 用户与权限组的多对多关联表
  37 +- `brand` — 公司/品牌主表,登录账号前缀来源
  38 +
  39 +---
  40 +
  41 +## `usr_user` — 用户账户主表,存储登录信息、类型、语言偏好及安全控制字段
  42 +
  43 +### 字段
  44 +
  45 +| 字段 | 类型 | Nullable | 默认 | 业务含义 |
  46 +|---|---|---|---|---|
  47 +| `iIncrement` | int | 否 | — | 整数主键 ID(标准列) |
  48 +| `sId` | varchar(100) | 是 | — | 业务 ID(标准列) |
  49 +| `sBrandsId` | varchar(100) | 是 | — | 品牌 ID(多租户隔离,标准列) |
  50 +| `sSubsidiaryId` | varchar(100) | 是 | — | 子公司 ID(组织层级隔离,标准列) |
  51 +| `tCreateDate` | datetime | 否 | — | 创建时间(标准列) |
  52 +| `sUserCode` | varchar(50) | 否 | — | 用户号(业务编号,人类可读唯一标识) |
  53 +| `sUsername` | varchar(100) | 否 | — | 用户名(登录标识,全局唯一,不可修改) |
  54 +| `sPasswordHash` | varchar(255) | 否 | — | BCrypt 哈希密码,禁止存储明文 |
  55 +| `sUserType` | varchar(20) | 否 | `普通用户` | 用户类型:`普通用户` / `超级管理员` |
  56 +| `sLanguage` | varchar(20) | 否 | `中文` | 界面语言:`中文` / `英文` / `繁体` |
  57 +| `bCanEditDoc` | tinyint(1) | 否 | `0` | 单据修改权限:0=否,1=是 |
  58 +| `bIsDisabled` | tinyint(1) | 否 | `0` | 是否作废/禁用:0=正常,1=禁用 |
  59 +| `sEmployeeId` | varchar(100) | 是 | NULL | 关联职员 ID(跨模块引用,职员未关联时为 NULL) |
  60 +| `sCreatorUsername` | varchar(100) | 是 | NULL | 制单人用户名(冗余字段,便于列表展示) |
  61 +| `tLastLoginDate` | datetime | 是 | NULL | 最后登录时间 |
  62 +| `iLoginFailCount` | int | 否 | `0` | 连续登录失败次数,用于防暴力破解 |
  63 +| `tLockUntil` | datetime | 是 | NULL | 账号锁定截止时间,NULL 表示未锁定 |
  64 +
  65 +### 索引
  66 +
  67 +- `uk_usr_user_username` (UNIQUE): `sUsername` — 全局唯一约束
  68 +- `uk_usr_user_usercode` (UNIQUE): `sUserCode` — 用户号唯一约束
  69 +- `idx_usr_user_tenant` (INDEX): `sBrandsId, sSubsidiaryId` — 多租户隔离查询
  70 +- `idx_usr_user_type` (INDEX): `sUserType` — 按用户类型过滤
  71 +- `idx_usr_user_disabled` (INDEX): `bIsDisabled` — 按状态过滤
  72 +
  73 +### 外键
  74 +
  75 +- 无(`sEmployeeId` 为跨模块引用,职员表由其他模块管理,当前阶段不建强约束)
  76 +
  77 +### 业务注记
  78 +
  79 +用户登录标识为 `sUsername`(全局唯一),`sUserCode` 为业务编号(可为工号等人类可读标识)。密码以 BCrypt 哈希存储于 `sPasswordHash`,禁止回显。`iLoginFailCount` 达到阈值(默认 5 次)时写入 `tLockUntil`(当前时间 +30 分钟),解锁后计数归零。`sEmployeeId` 软关联职员表,职员表由后续模块设计;`sCreatorUsername` 为冗余字段,记录新建时的操作人用户名。
  80 +
  81 +---
  82 +
  83 +## `tStaff` — 职员维度(员工名 / 部门 / 编号)
  84 +
  85 +### 字段
  86 +
  87 +| 字段 | 类型 | Nullable | 默认 | 业务含义 |
  88 +|---|---|---|---|---|
  89 +| `iIncrement` | int | 否 | — | 整数主键 ID(标准列) |
  90 +| `sId` | varchar(100) | 是 | — | 业务 ID(标准列) |
  91 +| `sBrandsId` | varchar(100) | 是 | — | 品牌 ID(多租户隔离,标准列) |
  92 +| `sSubsidiaryId` | varchar(100) | 是 | — | 子公司 ID(组织层级隔离,标准列) |
  93 +| `tCreateDate` | datetime | 否 | — | 创建时间(标准列) |
  94 +| `sStaffNo` | varchar(50) | 是 | — | 职员编号;系统内唯一 |
  95 +| `sStaffName` | varchar(50) | 否 | — | 职员姓名 |
  96 +| `sDepartment` | varchar(100) | 是 | NULL | 所属部门(本期暂用字符串,未来如需独立 `tDepartment` 字典表再另行重构) |
  97 +| `sCreatedBy` | varchar(50) | 是 | — | 制单人 |
  98 +| `bDeleted` | bit(1) | 否 | 0 | 软删除标记 |
  99 +| `tDeletedDate` | datetime | 是 | NULL | 软删除时间 |
  100 +| `sDeletedBy` | varchar(50) | 是 | NULL | 软删除操作人 |
  101 +
  102 +### 索引
  103 +
  104 +- `uk_staff_no` (UNIQUE): (`sStaffNo`)
  105 +- `idx_staff_name` (NORMAL): (`sStaffName`)
  106 +- `idx_department` (NORMAL): (`sDepartment`)
  107 +
  108 +### 外键
  109 +
  110 +(无)
  111 +
  112 +### 业务注记
  113 +
  114 +- USR 模块通过 `tUser.iStaffId` 引用本表;REQ-USR-003 列表的「员工名 / 部门」均来自本表。
  115 +- 部门当前以字符串保存,便于本期快速落地;后续若需独立部门字典表(如审批流引用部门),改为 `iDepartmentId` 外键。
  116 +
  117 +---
  118 +
  119 +## `usr_permission_group` — 权限分类/权限组定义表,每行对应一个可分配给用户的权限项
  120 +
  121 +### 字段
  122 +
  123 +| 字段 | 类型 | Nullable | 默认 | 业务含义 |
  124 +|---|---|---|---|---|
  125 +| `iIncrement` | int | 否 | — | 整数主键 ID(标准列) |
  126 +| `sId` | varchar(100) | 是 | — | 业务 ID(标准列) |
  127 +| `sBrandsId` | varchar(100) | 是 | — | 品牌 ID(多租户隔离,标准列) |
  128 +| `sSubsidiaryId` | varchar(100) | 是 | — | 子公司 ID(组织层级隔离,标准列) |
  129 +| `tCreateDate` | datetime | 否 | — | 创建时间(标准列) |
  130 +| `sGroupCode` | varchar(100) | 否 | — | 权限代码(如 `usr:create`、`usr:edit`),全局唯一 |
  131 +| `sGroupName` | varchar(200) | 否 | — | 权限显示名称(如"新增用户"、"修改用户") |
  132 +| `sCategory` | varchar(100) | 是 | NULL | 权限分类标签,用于前端权限分组展示 |
  133 +
  134 +### 索引
  135 +
  136 +- `uk_usr_perm_group_code` (UNIQUE): `sGroupCode` — 权限代码唯一约束
  137 +- `idx_usr_perm_group_tenant` (INDEX): `sBrandsId, sSubsidiaryId` — 多租户隔离查询
  138 +
  139 +### 外键
  140 +
  141 +- 无
  142 +
  143 +### 业务注记
  144 +
  145 +权限分类表存储系统内所有可分配权限项的定义,`sGroupCode` 即前端 `PermButton` 校验的权限码字符串。`sCategory` 用于将权限归组(如"用户管理"分类下含 `usr:create`、`usr:edit`、`usr:query` 等),对应前端 REQ-USR-001/002 权限组表格的"权限分类"列。
  146 +
  147 +---
  148 +
  149 +## `usr_user_permission` — 用户与权限组的多对多关联表
  150 +
  151 +### 字段
  152 +
  153 +| 字段 | 类型 | Nullable | 默认 | 业务含义 |
  154 +|---|---|---|---|---|
  155 +| `iIncrement` | int | 否 | — | 整数主键 ID(标准列) |
  156 +| `sId` | varchar(100) | 是 | — | 业务 ID(标准列) |
  157 +| `sBrandsId` | varchar(100) | 是 | — | 品牌 ID(多租户隔离,标准列) |
  158 +| `sSubsidiaryId` | varchar(100) | 是 | — | 子公司 ID(组织层级隔离,标准列) |
  159 +| `tCreateDate` | datetime | 否 | — | 创建时间(标准列) |
  160 +| `sUserId` | varchar(100) | 否 | — | 关联 `usr_user.sId` |
  161 +| `sPermGroupId` | varchar(100) | 否 | — | 关联 `usr_permission_group.sId` |
  162 +
  163 +### 索引
  164 +
  165 +- `uk_usr_user_perm` (UNIQUE): `sUserId, sPermGroupId` — 防止重复授权
  166 +- `idx_usr_user_perm_user` (INDEX): `sUserId` — 按用户查询其所有权限
  167 +- `idx_usr_user_perm_group` (INDEX): `sPermGroupId` — 按权限组查询持有该权限的用户
  168 +
  169 +### 外键
  170 +
  171 +- `fk_usr_user_perm_user`: `sUserId` → `usr_user.sId` (ON DELETE CASCADE ON UPDATE CASCADE)
  172 +- `fk_usr_user_perm_group`: `sPermGroupId` → `usr_permission_group.sId` (ON DELETE CASCADE ON UPDATE CASCADE)
  173 +
  174 +### 业务注记
  175 +
  176 +多对多中间表,记录每个用户持有的权限组。新增用户时(REQ-USR-001)保存权限组复选框选中项,修改用户时(REQ-USR-002)先删除该用户旧记录再批量插入新记录(replace 策略)。用户被删除或权限组被删除时,对应关联记录级联删除。
  177 +
  178 +---
  179 +
  180 +## `brand` — 公司表
  181 +
  182 +### 字段
  183 +
  184 +| 字段 | 类型 | Nullable | 默认 | 业务含义 |
  185 +|---|---|---|---|---|
  186 +| `iIncrement` | int | 否 | — | 整数主键 ID(标准列) |
  187 +| `sId` | varchar(100) | 是 | — | 业务 ID(标准列) |
  188 +| `sBrandsId` | varchar(100) | 是 | — | 品牌 ID(多租户隔离,标准列) |
  189 +| `sSubsidiaryId` | varchar(100) | 是 | — | 子公司 ID(组织层级隔离,标准列) |
  190 +| `tCreateDate` | datetime | 否 | — | 创建时间(标准列) |
  191 +| `sName` | varchar(100) | 是 | — | 公司名称 |
  192 +| `sShortName` | varchar(100) | 是 | — | 公司简称 |
  193 +| `sNo` | varchar(100) | 是 | — | 单位编号(登录账号根据单位编号作为前缀) |
  194 +
  195 +### 索引
  196 +
  197 +- `uk_brand_no` (UNIQUE): `sNo` — 单位编号唯一约束(登录账号前缀来源,必须唯一)
  198 +- `idx_brand_name` (INDEX): `sName` — 按公司名称查询
  199 +
  200 +### 外键
  201 +
  202 +- 无
  203 +
  204 +### 业务注记
  205 +
  206 +公司/品牌主表,记录系统内每个租户的基础信息。`sNo` 为单位编号,登录用户的账号以该编号为前缀生成(参见 `usr_user.sUsername` 的命名规则);因此 `sNo` 必须全局唯一。`sShortName` 为公司简称,用于前端列表 / 抬头等空间受限位置展示。本表为多租户体系的根表,其他业务表的 `sBrandsId` 软关联到本表 `sId`,当前阶段不建强外键约束。
docs/04-技术规范.md 0 → 100644
  1 +++ a/docs/04-技术规范.md
  1 +# 04-技术规范
  2 +
  3 +## 零、技术栈总览
  4 +
  5 +| 分层模块 | 技术 | 版本要求 | 说明 |
  6 +|---|---|---|---|
  7 +| 前端基础框架 | React | 18.x | 构建前端应用 |
  8 +| 前端 UI 组件 | Ant Design | 5.x | 页面组件与交互控件 |
  9 +| 前端状态管理 | Redux Toolkit | 最新稳定版 | 管理全局状态 |
  10 +| 前端路由管理 | React Router | v6 | 页面路由与导航 |
  11 +| 前端工程化构建 | Vite | 最新稳定版 | 前端开发与打包构建 |
  12 +| 前端接口通信 | Axios | 最新稳定版 | 调用后端 API |
  13 +| 后端基础框架 | Spring Boot | 3.x | 构建后端服务 |
  14 +| 后端数据访问 | MyBatis-Plus | 最新稳定版 | 数据库访问与 ORM 增强 |
  15 +| 工作流引擎 | Activiti | 6.x | 审批流、流程流转 |
  16 +| 缓存服务 | Redis | 最新稳定版 | 缓存、会话、分布式能力 |
  17 +| 报表打印 | JXLS | 2.8.1 | 基于 Excel 模板生成报表 |
  18 +| Excel 导入导出 | EasyExcel | 4.0.3 | Excel 数据导入导出 |
  19 +| 关系型数据库 | MySQL | 8.x | 核心业务数据存储 |
  20 +| 数据库 schema 迁移 | Flyway (`flyway-core` + `flyway-mysql`) | 10.x / 最新稳定版 | `sql/migrations/V_n__*.sql` 顺序 apply;Spring Boot 启动时自动应用 |
  21 +| 接口风格 | RESTful API | 统一规范 | 前后端接口设计规范 |
  22 +| 权限认证 | Spring Security / JWT | 最新稳定版 | 登录认证、权限控制 |
  23 +| API 文档 | OpenAPI / Swagger | 最新稳定版 | 接口文档与调试 |
  24 +| 项目构建管理 | Maven | 3.9.x | Java 项目依赖与构建 |
  25 +| JDK 运行环境 | Java | 17 / 21 | Spring Boot 3 推荐版本 |
  26 +| 部署容器 | Docker | 最新稳定版 | 容器化部署 |
  27 +| Web 服务器 / 反向代理 | Nginx | 最新稳定版 | 前端托管、反向代理、负载分发 |
  28 +| 日志管理 | Logback | 默认集成 / 最新稳定版 | 应用日志输出 |
  29 +| 对象映射工具 | MapStruct | 最新稳定版 | DTO / VO / Entity 转换 |
  30 +| 工具类库 | Hutool / Apache Commons | 最新稳定版 | 常用工具方法支持 |
  31 +
  32 +> 本表由 scope-lock 锁定。后续所有规范基于此表推导。
  33 +
  34 +## 一、后端规范
  35 +
  36 +### 1.1 分层结构
  37 +
  38 +基于 Spring Boot 3.x + MyBatis-Plus 的标准三层架构:
  39 +
  40 +| 层 | 包路径 | 职责 |
  41 +|---|---|---|
  42 +| Controller | `module/<mod>/controller/` | 接收 HTTP 请求,校验入参,调 Service,返回统一响应体 |
  43 +| Service | `module/<mod>/service/` | 核心业务逻辑,事务边界,编排 Mapper 和跨域调用 |
  44 +| Mapper | `module/<mod>/mapper/` | MyBatis-Plus 数据访问,简单 CRUD 继承 BaseMapper,复杂 SQL 写 XML |
  45 +| Entity | `module/<mod>/entity/` | 与数据库表一一对应,不含业务方法 |
  46 +| DTO / VO | `module/<mod>/dto/` `module/<mod>/vo/` | 解耦接口层与实体层;DTO 为入参,VO 为出参 |
  47 +| Config | `config/` | Spring Bean 配置、Security 规则、Swagger、MyBatis 拦截器等全局配置 |
  48 +| Common | `common/` | 统一响应体、全局异常处理器、枚举、常量、工具类 |
  49 +
  50 +### 1.2 命名约定
  51 +
  52 +- **根包名**:`com.example.erp`
  53 +- **类名**:`UpperCamelCase`;示例:`UserController`、`UserServiceImpl`
  54 +- **方法名**:`lowerCamelCase`;示例:`createUser()`、`getUserById()`
  55 +- **常量**:`UPPER_SNAKE_CASE`,统一放 `common/constants/`;示例:`MAX_LOGIN_RETRY`
  56 +- **数据库表**:`lower_snake_case`;示例:`sys_user`、`usr_role`
  57 +
  58 +### 1.3 统一响应格式
  59 +
  60 +```json
  61 +// 成功
  62 +{ "code": 0, "message": "success", "data": { ... } }
  63 +
  64 +// 失败
  65 +{ "code": 10001, "message": "用户名已存在", "data": null }
  66 +```
  67 +
  68 +错误码段位:
  69 +- `0`:成功
  70 +- `10xxx`:通用业务错误(参数校验、权限等)
  71 +- `20xxx`:用户管理模块(USR)
  72 +- `99xxx`:系统内部错误
  73 +
  74 +### 1.4 异常处理
  75 +
  76 +- 使用 `@RestControllerAdvice` 全局捕获异常,统一返回规范响应体
  77 +- 业务异常继承 `BizException(code, message)`,在 Service 层抛出
  78 +- `RuntimeException` / 未知异常统一映射为 `code=99000`,返回用户友好文案
  79 +- **接口响应禁止回显后端异常堆栈**;堆栈信息只写 Logback 日志,不出现在 HTTP 响应体
  80 +
  81 +### 1.5 事务
  82 +
  83 +- 事务边界在 Service 层,使用 `@Transactional`
  84 +- 只读查询加 `@Transactional(readOnly = true)`
  85 +- 禁止在 Controller 层加事务注解
  86 +- 跨模块数据操作通过 Service 接口调用,不直接跨 Mapper 调用其他模块
  87 +
  88 +### 1.6 认证
  89 +
  90 +基于 Spring Security + JWT:
  91 +- Token 生命周期:Access Token 有效期 24 小时,Refresh Token 有效期 7 天
  92 +- 刷新机制:前端检测到 401 时,用 Refresh Token 换新 Access Token;Refresh Token 过期则强制重新登录
  93 +- 密钥管理:JWT 签名密钥从 `.env.local` 注入,生产环境通过 Docker 环境变量覆盖,**禁止硬编码**
  94 +
  95 +## 二、前端规范
  96 +
  97 +### 2.1 目录约定
  98 +
  99 +| 目录 | 职责 |
  100 +|---|---|
  101 +| `api/` | Axios 实例封装 + 各模块接口定义;**前端禁止直接写 SQL 或操作 DB**,所有数据访问经此层 |
  102 +| `components/` | 通用业务组件(`PermButton`、`PageContainer` 等)|
  103 +| `pages/` | 页面组件,按模块子目录组织(`pages/usr/`)|
  104 +| `store/slices/` | Redux Toolkit Slice,每模块一个文件 |
  105 +| `hooks/` | 自定义 React Hooks(`useTable`、`usePermission` 等)|
  106 +| `utils/` | 纯函数工具(日期格式化、金额格式化等)|
  107 +
  108 +### 2.2 状态管理
  109 +
  110 +- **全局状态**(认证信息、用户权限):Redux Toolkit Store
  111 +- **模块级 UI 状态**(列表筛选条件、表单开关):组件本地 `useState` / `useReducer`
  112 +- **服务端数据**:通过 RTK Query 或自定义 Hook 管理远程数据缓存与加载状态,不建议直接放 Redux
  113 +
  114 +### 2.3 请求封装
  115 +
  116 +`api/request.ts` 封装 Axios 实例:
  117 +- **认证注入**:请求拦截器从内存/Cookie 读取 Access Token,注入 `Authorization: Bearer <token>`
  118 +- **超时**:默认 10s
  119 +- **错误重试**:网络超时自动重试 1 次,业务错误不重试
  120 +- **401 处理**:响应拦截器捕获 401,触发 Refresh Token 流程;再次失败则清除登录态并跳转登录页
  121 +
  122 +### 2.4 错误处理
  123 +
  124 +| 层级 | 处理方式 |
  125 +|---|---|
  126 +| 网络错误 / 超时 | `message.error("网络异常,请检查连接")` |
  127 +| 业务错误(HTTP 200 + code ≠ 0) | 读取 `message` 字段,`message.error(接口文案)` |
  128 +| 页面级错误(路由 / 权限) | React Router ErrorBoundary,展示 `Result` 组件 |
  129 +
  130 +### 2.5 样式与主题
  131 +
  132 +- CSS 变量命名格式:`--color-<scope>-<role>-<state>`(如 `--color-form-bg-edit`、`--color-table-row-bg-hover`)
  133 +- 文件位置:`src/styles/tokens.css`,由 skeleton-gen 生成骨架,色值在 docs/06 § 四锁定后填入
  134 +- 组件样式中**只用 `var(--color-xxx)`**,禁止硬编码 hex / rgba
  135 +- 与 Ant Design 对接:在 `App.tsx` 的 `ConfigProvider.theme.token` 中将 Ant Design 主题 token 映射到 CSS 变量(如 `colorPrimary: 'var(--color-primary)'`)
  136 +- 具体 token 表见 docs/06 § 四
  137 +
  138 +## 三、共同约定
  139 +
  140 +### 3.1 Git 提交
  141 +
  142 +`<type>(<scope>): <subject> REQ-XXX-NNN`
  143 +
  144 +示例:`feat(usr): 增加用户创建接口 REQ-USR-001`
  145 +
  146 +type 取值:`feat` / `fix` / `refactor` / `test` / `docs` / `chore`
  147 +
  148 +### 3.2 分页查询
  149 +
  150 +- **入参**:`pageNum`(从 1 起)、`pageSize`(默认 20,最大 100)
  151 +- **出参**:`{ list: [], total: N, pageNum: N, pageSize: N }`
  152 +- 后端使用 MyBatis-Plus `Page<T>` 对象;前端使用 Ant Design `Table` 的 `pagination` 属性
  153 +
  154 +### 3.3 日期与金额
  155 +
  156 +- **后端日期类型**:`LocalDateTime`,JSON 序列化为 `yyyy-MM-dd HH:mm:ss`
  157 +- **后端金额类型**:`BigDecimal`,精度 2 位小数,不在接口层做四舍五入
  158 +- **前端日期展示**:使用 `dayjs` 格式化
  159 +- **前端金额展示**:保留 2 位小数并加千分位,使用封装的 `formatAmount(val)` 工具函数
  160 +
  161 +### 3.4 数据访问规约
  162 +
  163 +- SELECT 字段**显式列举**,**禁止 `SELECT *`**
  164 +- 循环中**禁止执行 DB 查询**(N+1 反模式),改用批量查 / `IN` 子句 / `JOIN`
  165 +- Mapper XML 里字段与表名使用 MyBatis-Plus 的常量引用,避免拼字符串
  166 +
  167 +### 3.5 配置与安全
  168 +
  169 +- DB 连接 / 端口 / 密钥 / 第三方 URL 一律放 `application.yml`(占位符)+ `.env.local`(真实值),**代码里禁止硬编码**
  170 +- **前端**:`localStorage` 不存储敏感信息(token / 身份 / 个人数据),推荐 HttpOnly Cookie 或内存 + Refresh Token 模式
  171 +- 接口响应禁止回显后端异常堆栈(与 § 1.4 一致)
docs/05-API接口契约.md 0 → 100644
  1 +++ a/docs/05-API接口契约.md
  1 +# 05-API接口契约
  2 +
  3 +BasePath: `/api`
  4 +端口: `8080`(见 docs/07 § 二,可在 application.yml 覆盖)
  5 +
  6 +## 全局约定
  7 +
  8 +### 响应格式
  9 +```json
  10 +{"code": 200, "message": "操作成功", "data": {}, "timestamp": 1700000000000}
  11 +```
  12 +
  13 +### 错误码
  14 +| 范围 | 含义 |
  15 +|---|---|
  16 +| 200 | 成功 |
  17 +| 400xx | 客户端参数错误 |
  18 +| 401xx | 认证/授权错误 |
  19 +| 403xx | 权限不足 |
  20 +| 404xx | 资源不存在 |
  21 +| 500xx | 服务端内部错误 |
  22 +
  23 +### 鉴权
  24 +除登录接口外,所有接口均需在请求头携带 `Authorization: Bearer <access_token>`。Token 由 REQ-USR-004 登录接口签发,有效期 24 小时。
  25 +
  26 +### 分页参数
  27 +列表查询接口统一使用以下分页入参:`pageNum`(页码,从 1 起)、`pageSize`(每页条数,默认 20,最大 100)。响应体 `data` 字段格式:`{ list: [], total: N, pageNum: N, pageSize: N }`。
  28 +
  29 +## 接口清单
  30 +(各模块接口段落见下方,由 `downstream-gen` 按 REQ 填入)
  31 +
  32 +---
  33 +
  34 +## module_usr 用户管理
  35 +
  36 +### REQ-USR-004 用户登录
  37 +
  38 +- **Method**: POST
  39 +- **Path**: `/api/auth/login`
  40 +- **Auth**: 无(公开接口)
  41 +- **Permission**: —
  42 +- **请求**: `{ "username": "string", "password": "string", "brandNo": "string" }` — 用户名、密码(明文,HTTPS 传输)、公司编号(对应 brand.sNo)
  43 +- **响应**: `{ "accessToken": "string", "refreshToken": "string", "expiresIn": 86400, "userInfo": { "userId": "string", "username": "string", "userType": "string", "language": "string" } }`
  44 +
  45 +#### 错误码
  46 +- `40100` — 用户名或密码错误(不区分哪个错,防信息泄露)
  47 +- `40101` — 账号已被禁用
  48 +- `40102` — 账号已被锁定,请 N 分钟后重试
  49 +- `40400` — 公司编号不存在
  50 +
  51 +---
  52 +
  53 +### REQ-USR-001 增加用户
  54 +
  55 +- **Method**: POST
  56 +- **Path**: `/api/usr/users`
  57 +- **Auth**: Bearer Token
  58 +- **Permission**: `usr:create`
  59 +- **请求**: `{ "userCode": "string", "username": "string", "userType": "普通用户|超级管理员", "language": "中文|英文|繁体", "canEditDoc": false, "employeeId": "string|null", "permGroupIds": ["string"] }` — 初始密码由后端自动设为系统默认值(666666 哈希)
  60 +- **响应**: `{ "userId": "string", "userCode": "string", "username": "string" }`
  61 +
  62 +#### 错误码
  63 +- `40001` — 参数校验失败(缺必填 / 格式错)
  64 +- `40300` — 权限不足
  65 +- `40901` — 用户名已存在
  66 +- `40902` — 用户号已存在
  67 +
  68 +---
  69 +
  70 +### REQ-USR-003 查询用户
  71 +
  72 +- **Method**: GET
  73 +- **Path**: `/api/usr/users`
  74 +- **Auth**: Bearer Token
  75 +- **Permission**: `usr:query`
  76 +- **请求**: Query 参数:`field=用户名|员工名|用户号|部门|用户类型|作废|登录日期|制单人`、`matchMode=包含|不包含|等于`、`value=string`、`pageNum=int`、`pageSize=int`
  77 +- **响应**: `{ list: [{ "userId", "username", "staffName", "userCode", "department", "userType", "language", "isDisabled", "lastLoginDate", "creatorUsername", "createDate" }], total, pageNum, pageSize }` — 密码字段不返回
  78 +
  79 +#### 错误码
  80 +- `40001` — 参数校验失败
  81 +- `40300` — 权限不足
  82 +
  83 +---
  84 +
  85 +### REQ-USR-002 修改用户
  86 +
  87 +- **Method**: PUT
  88 +- **Path**: `/api/usr/users/{userId}`
  89 +- **Auth**: Bearer Token
  90 +- **Permission**: `usr:edit`
  91 +- **请求**: Path 参数 `userId`(usr_user.sId);Body:`{ "userType": "普通用户|超级管理员", "language": "中文|英文|繁体", "canEditDoc": false, "isDisabled": false, "employeeId": "string|null", "permGroupIds": ["string"] }` — 用户名不可修改;密码由单独接口重置
  92 +- **响应**: `{ "userId": "string", "username": "string", "updatedAt": "datetime" }`
  93 +
  94 +#### 错误码
  95 +- `40001` — 参数校验失败
  96 +- `40300` — 权限不足
  97 +- `40301` — 禁止修改自己的管理员角色
  98 +- `40400` — 用户不存在
docs/06-UI交互规范.md 0 → 100644
  1 +++ a/docs/06-UI交互规范.md
  1 +# 06-UI交互规范
  2 +
  3 +## 一、整体布局
  4 +
  5 +### 1.1 页面框架
  6 +
  7 +使用 Ant Design `Layout` 组件搭建整体骨架:
  8 +- **Header**:顶部导航栏,显示系统名称、用户信息、退出按钮
  9 +- **Sider**:左侧菜单栏,使用 `Menu` 组件,支持折叠
  10 +- **Content**:右侧主内容区,承载各模块页面
  11 +
  12 +整体结构为 `Header + (Sider + Content)` 的经典 Admin 布局。
  13 +
  14 +### 1.2 布局参数
  15 +
  16 +| 参数 | 默认值 | 说明 |
  17 +|---|---|---|
  18 +| Header 高度 | 64px | 固定顶部 |
  19 +| Sidebar 宽度(展开) | 220px | 左侧菜单展开宽度 |
  20 +| Sidebar 宽度(折叠) | 64px | 左侧菜单折叠宽度 |
  21 +| Content 内边距 | 24px | 内容区四周间距 |
  22 +| 最小支持分辨率 | 1280×720 | 企业内部应用最低屏幕要求 |
  23 +
  24 +## 二、标准页面类型
  25 +
  26 +### 2.1 列表页
  27 +
  28 +- **顶部操作区**:新增、导入、导出按钮靠右对齐
  29 +- **搜索栏**:使用 `Form` + `Input` / `Select` / `DatePicker` 组合,搜索按钮在最右侧
  30 +- **表格**:使用 `Table` 组件,支持分页、行选择、列排序
  31 +- **分页**:位于表格底部右侧,显示总条数,每页默认 20 条
  32 +
  33 +### 2.2 表单页
  34 +
  35 +- 新增/编辑统一使用 `Drawer`(宽度 520px),避免完整页面跳转
  36 +- 校验时机:失焦时触发单字段校验,提交时触发全量校验
  37 +- 提交按钮位于 Drawer 底部右侧(「确定」+「取消」),固定不随内容滚动
  38 +
  39 +### 2.3 详情页
  40 +
  41 +- 使用 `Descriptions` 展示基本信息(labelWidth 100px,border 模式)
  42 +- 多维度信息使用 `Tabs` 切换(如基本信息 / 操作日志 / 关联记录)
  43 +
  44 +### 2.4 树形管理页
  45 +
  46 +用于用户管理中的角色/权限树,使用 Ant Design `Tree` + 右侧 `Drawer` 的左右布局:
  47 +- 左侧 Tree 支持搜索、展开/折叠
  48 +- 右侧 Drawer 展示选中节点的详细信息或编辑表单
  49 +
  50 +## 三、通用交互规则
  51 +
  52 +### 3.1 操作反馈
  53 +
  54 +- **成功**:`message.success()`,右上角弹出,持续 2s
  55 +- **失败**:`message.error()`,持续 3s,错误文案来自接口返回 `message` 字段
  56 +- **危险操作**(删除、批量操作):`Modal.confirm()` 二次确认,标注受影响数量
  57 +- **长耗时操作**(导入/导出):按钮进入 `loading` 态,禁止重复点击
  58 +
  59 +### 3.2 数据展示
  60 +
  61 +| 状态 | 组件 | 文案 |
  62 +|---|---|---|
  63 +| 空数据 | `Empty` | "暂无数据" |
  64 +| 加载中 | `Spin` + `Skeleton` | — |
  65 +| 接口异常 | `Result` (status=500) | "数据加载失败,请刷新重试" |
  66 +
  67 +### 3.3 权限控制(前端)
  68 +
  69 +- **菜单级**:根据后端返回的权限码动态渲染菜单项,无权限则不显示
  70 +- **按钮级**:封装 `<PermButton permission="xxx:yyy">` 组件,无权限时按钮隐藏
  71 +- **路由级**:在 React Router 的路由守卫中校验权限,无权限跳转 403 页面
  72 +- 权限码格式:`模块代码:操作`(如 `usr:create`、`usr:edit`),与后端 RBAC 保持一致
  73 +
  74 +## 四、Design Tokens
  75 +
  76 +所有色值统一以 CSS 变量定义于 `src/styles/tokens.css`;命名规范见 docs/04 § 2.5。
  77 +
  78 +### 4.1 全局调色板
  79 +
  80 +| 语义 | 变量名 | 默认值 | 用途 |
  81 +|---|---|---|---|
  82 +| 主色 | `--color-primary` | `#1677ff` | 按钮/链接/高亮 |
  83 +| 成功 | `--color-success` | `#52c41a` | 成功状态/标签 |
  84 +| 警告 | `--color-warning` | `#faad14` | 警告提示 |
  85 +| 错误 | `--color-error` | `#ff4d4f` | 错误/危险操作 |
  86 +| 主文字 | `--color-text-primary` | `#000000e0` | 主要文本 |
  87 +| 次文字 | `--color-text-secondary` | `#00000073` | 辅助文本/提示 |
  88 +| 边框 | `--color-border` | `#d9d9d9` | 输入框/分割线边框 |
  89 +| 背景 | `--color-bg-base` | `#f5f5f5` | 页面底色 |
  90 +
  91 +### 4.2 组件级状态色
  92 +
  93 +| 序号 | 组件 | 编辑bg | 只读bg | 悬浮bg | 编辑fg | 只读fg | 悬浮fg | 备注 |
  94 +|---|---|---|---|---|---|---|---|---|
  95 +| 1 | 表单输入框 | `var(--color-bg-input-edit)` | `var(--color-bg-input-readonly)` | `var(--color-bg-input-hover)` | `var(--color-text-primary)` | `var(--color-text-secondary)` | `var(--color-text-primary)` | — |
  96 +| 2 | 表格行 | — | `var(--color-bg-table-row)` | `var(--color-bg-table-row-hover)` | — | `var(--color-text-primary)` | `var(--color-text-primary)` | 选中行用主色淡色 |
  97 +| 3 | 侧边菜单项 | — | `var(--color-bg-menu-item)` | `var(--color-bg-menu-item-hover)` | — | `var(--color-text-primary)` | `var(--color-primary)` | 活动项用主色 |
  98 +
  99 +**Token 默认值**
  100 +
  101 +| 变量名 | 默认值 |
  102 +|---|---|
  103 +| `--color-bg-input-edit` | `#ffffff` |
  104 +| `--color-bg-input-readonly` | `#f5f5f5` |
  105 +| `--color-bg-input-hover` | `#ffffff` |
  106 +| `--color-bg-table-row` | `#ffffff` |
  107 +| `--color-bg-table-row-hover` | `#fafafa` |
  108 +| `--color-bg-menu-item` | `transparent` |
  109 +| `--color-bg-menu-item-hover` | `rgba(0,0,0,0.06)` |
  110 +
  111 +### 4.3 引用约定
  112 +
  113 +- 组件样式只用 `var(--color-xxx)`,禁止硬编码 hex / rgba
  114 +- 新增 token 须先登记到 § 4.1/4.2 再补 `tokens.css`
  115 +- 修改色值只改 `tokens.css` 一处,不允许组件级覆盖
  116 +
  117 +## 五、页面清单
  118 +(由 `downstream-gen` 按模块追加段落)
  119 +
  120 +### module_usr 用户管理
  121 +
  122 +- **登录页** (`/login`)
  123 + - 类型: 表单页
  124 + - 对应 REQ: REQ-USR-004
  125 + - 入口菜单: 不在菜单内,直接路由跳转
  126 + - 主要交互: 填写公司编号/用户名/密码 → 提交登录 → 成功跳转主页;失败显示错误提示;连续失败锁定提示
  127 +
  128 +- **用户列表页** (`/usr/users`)
  129 + - 类型: 列表页
  130 + - 对应 REQ: REQ-USR-001, REQ-USR-002, REQ-USR-003
  131 + - 入口菜单: 系统管理 → 用户管理
  132 + - 主要交互: 顶部搜索栏(字段/匹配方式/值三联筛选)+ 表格分页展示;行内「编辑」按钮弹出 Drawer;顶部「新增」按钮弹出新增 Drawer;Drawer 含权限组复选列表
docs/07-环境配置.md 0 → 100644
  1 +++ a/docs/07-环境配置.md
  1 +# 07-环境配置
  2 +
  3 +## 一、依赖清单
  4 +
  5 +| 层 | 依赖 | 版本 | 说明 |
  6 +|---|---|---|---|
  7 +| 运行时 | Java (JDK) | 17 / 21 | Spring Boot 3.x 推荐版本 |
  8 +| 运行时 | MySQL | 8.x | 核心业务数据库 |
  9 +| 运行时 | Redis | 最新稳定版 | 缓存/会话/分布式锁 |
  10 +| 运行时 | Node.js | 18.x+ | 前端开发环境 |
  11 +| 构建 | Maven | 3.9.x | Java 后端构建工具 |
  12 +| 构建 | npm | 随 Node.js | 前端包管理器 |
  13 +| 构建 | Vite | 最新稳定版 | 前端打包构建 |
  14 +| 容器 | Docker | 最新稳定版 | 容器化部署 |
  15 +| 容器 | Docker Compose | v2+ | 本地多服务编排 |
  16 +| CLI 工具 | Flyway CLI(可选) | 10.x | 手动执行数据库迁移 |
  17 +| Web 服务器 | Nginx | 最新稳定版 | 前端托管 + 反向代理 |
  18 +
  19 +## 二、端口约定
  20 +
  21 +| 服务 | 端口 | 说明 |
  22 +|---|---|---|
  23 +| 后端 HTTP | 8080 | Spring Boot 默认端口 |
  24 +| 前端 dev server | 5173 | Vite 开发服务器默认端口 |
  25 +| MySQL | 3306 | 数据库默认端口 |
  26 +| Redis | 6379 | 缓存服务默认端口 |
  27 +| Nginx(生产) | 80 / 443 | HTTP / HTTPS 反向代理 |
  28 +
  29 +## 三、环境变量
  30 +
  31 +运行时凭据(数据库连接、JWT 密钥等)全部放在仓库根的 `.env.local`,不入 git。
  32 +字段清单与占位符见该文件,真实值由开发者本地填写。
  33 +
  34 +## 四、常用命令
  35 +
  36 +| 命令 | 说明 |
  37 +|---|---|
  38 +| `cd backend && mvn spring-boot:run` | 启动后端开发服务 |
  39 +| `cd frontend && npm run dev` | 启动前端开发服务器 |
  40 +| `cd backend && mvn clean package -DskipTests` | 打包后端(跳过测试) |
  41 +| `cd frontend && npm run build` | 打包前端静态资源 |
  42 +| `bash scripts/test.sh` | 运行全量测试(build + lint + unit + e2e) |
  43 +| `bash scripts/setup-test-db.sh` | 重置测试数据库 |
  44 +| `git push origin <branch>` | 推送分支(触发 pre-push 钩子) |
docs/08-模块任务管理.md 0 → 100644
  1 +++ a/docs/08-模块任务管理.md
  1 +# 08-工作流进度
  2 +
  3 +> 全流程进度跟踪。CC 每完成一项产出就勾选一项。
  4 +> - **§ 一 Plan(A0~A5)**:`plan-start` 找第一个未勾 A 子项分发到对应 skill
  5 +> - **§ 二 Coding(模块)**:分发以 `docs/02-开发计划.md § 二 开发顺序清单` 为准;`coding-start` 按 docs/02 顺序扫描,对每个 REQ 所属模块查询本 § 二的 `MR:` 字段 + GitLab API `state`,找第一个非 merged 模块分发。本 § 二 行序无语义,仅作模块元数据表
  6 +
  7 +## 一、Plan 阶段(一次性)
  8 +
  9 +- [x] A0 项目初始化 — project-init
  10 + - [x] 依赖检查通过
  11 + - [x] 项目文件骨架已创建(CLAUDE.md + docs/01-需求清单/index.md + docs/04-技术规范.md)
  12 + - [x] Git 已初始化
  13 +
  14 +- [x] A1 范围锁定 — scope-lock
  15 + - [x] 项目概述已填写(CLAUDE.md § 🎯 项目概述)
  16 + - [x] 技术栈已确认(docs/04 § 零)
  17 + - [x] 需求清单索引已填写(docs/01-需求清单/index.md)
  18 + - [x] REQ 卡片骨架已生成(docs/01-需求清单/<module>/REQ-*.md,业务内容留待人工填写)
  19 +
  20 +- [x] A2 骨架生成 — skeleton-gen
  21 + - [x] 架构文档已生成(docs/04 § 一+、docs/06、docs/07、docs/09)
  22 + - [x] 工具脚本已生成(scripts/*.sh、.githooks/pre-push、.env.local)
  23 + - [x] .gitignore 已配置
  24 +
  25 +- [x] A3 DB 设计 + REQ 回填 — db-design-gen
  26 + - [x] docs/03-数据库设计文档.md 已生成
  27 + - [x] docs/01 各 REQ 卡片"依赖表" + 模块头"涉及表" 已回填
  28 +
  29 +- [x] A4 DB 初始化 — db-init
  30 + - [x] sql/migrations/V1__initial_schema.sql 已生成
  31 + - [x] DDL 与 docs/03 全量一致
  32 + - [x] .env.local 凭据已验证(mysql -e "SELECT 1" OK)
  33 + - [x] setup-test-db.sh 防护通过 + DROP+CREATE + apply V1 已执行
  34 + - [x] SHOW TABLES 行数 == docs/03 表数量
  35 +
  36 +- [x] A5 下游文档生成 — downstream-gen
  37 + - [x] docs/02 开发计划已生成
  38 + - [x] docs/05 API 契约已生成
  39 + - [x] docs/06 § 五 页面清单已填入
  40 + - [x] docs/10 验收清单已生成
  41 + - [x] 下方模块列表已填入
  42 + - [x] REQ 卡片依赖接口已回填
  43 +
  44 +## 二、Coding 阶段(按模块循环)
  45 +
  46 +(A5 填入后,每行一个模块。每个模块的 `MR:` 字段在 `—` 和 `!<iid>` 之间变化,完成由 GitLab API `state=merged` 判定。`coding-start` 每次按 docs/02 REQ 序扫每模块的 MR state 决定派发。)
  47 +
  48 +<!-- 模块格式示例(由 A5 downstream-gen 追加;功能子项由 feature-review 在 approve 时勾选):
  49 +- module_0 系统管理
  50 + - 依赖: —
  51 + - 路径: backend/module/sys/, frontend/pages/sys/
  52 + - MR: —
  53 + - 功能:
  54 + - [ ] REQ-SYS-001 用户登录
  55 + - [ ] REQ-SYS-002 用户注册
  56 +-->
  57 +
  58 +- module_usr 用户管理
  59 + - 依赖: —
  60 + - 路径: backend/module/usr/, frontend/pages/usr/
  61 + - MR: —
  62 + - 功能:
  63 + - [ ] REQ-USR-004 用户登录
  64 + - [ ] REQ-USR-001 增加用户
  65 + - [ ] REQ-USR-003 查询用户
  66 + - [ ] REQ-USR-002 修改用户
docs/09-项目目录结构.md 0 → 100644
  1 +++ a/docs/09-项目目录结构.md
  1 +# 09-项目目录结构
  2 +
  3 +## 一、仓库顶层
  4 +
  5 +```
  6 +<project-root>/
  7 +├── CLAUDE.md # CC 项目指令
  8 +├── README.md # 项目说明
  9 +├── .env.local # 本地环境变量(不入 git)
  10 +├── .gitignore
  11 +├── .githooks/
  12 +│ └── pre-push # push 前测试闸门
  13 +├── scripts/
  14 +│ ├── setup-test-db.sh # 重置测试数据库
  15 +│ └── test.sh # 全量测试入口
  16 +├── sql/
  17 +│ └── migrations/ # Flyway V_n__*.sql
  18 +├── docs/ # 规划文档(见 § 四)
  19 +├── backend/ # Spring Boot 后端
  20 +└── frontend/ # React 前端
  21 +```
  22 +
  23 +## 二、后端目录
  24 +
  25 +```
  26 +backend/
  27 +├── pom.xml
  28 +└── src/
  29 + ├── main/
  30 + │ ├── java/
  31 + │ │ └── com/example/erp/
  32 + │ │ ├── config/ # Spring 配置类(Security、Swagger、MyBatis 等)
  33 + │ │ ├── common/ # 通用工具(统一响应体、全局异常、枚举、常量)
  34 + │ │ ├── module/
  35 + │ │ │ └── usr/ # USR 用户管理模块
  36 + │ │ │ ├── controller/ # REST 接口层
  37 + │ │ │ ├── service/ # 业务逻辑层
  38 + │ │ │ ├── mapper/ # MyBatis-Plus Mapper
  39 + │ │ │ ├── entity/ # 数据库实体
  40 + │ │ │ ├── dto/ # 请求参数 DTO
  41 + │ │ │ └── vo/ # 响应视图 VO
  42 + │ │ └── Application.java # 启动类
  43 + │ └── resources/
  44 + │ ├── application.yml # 主配置
  45 + │ ├── application-dev.yml # 开发环境配置
  46 + │ ├── application-prod.yml # 生产环境配置
  47 + │ └── mapper/ # MyBatis XML(可选)
  48 + └── test/
  49 + └── java/
  50 + └── com/example/erp/
  51 + └── module/
  52 + └── usr/ # USR 模块单元/集成测试
  53 +```
  54 +
  55 +## 三、前端目录
  56 +
  57 +```
  58 +frontend/
  59 +├── package.json
  60 +├── vite.config.ts
  61 +├── index.html
  62 +└── src/
  63 + ├── main.tsx # 应用入口
  64 + ├── App.tsx # 根组件 + 路由配置
  65 + ├── styles/
  66 + │ └── tokens.css # Design Token CSS 变量
  67 + ├── api/ # Axios 封装 + 各模块接口
  68 + │ └── usr.ts # USR 用户管理接口
  69 + ├── store/ # Redux Toolkit Store
  70 + │ └── slices/
  71 + │ └── authSlice.ts # 认证状态
  72 + ├── hooks/ # 通用 React Hooks
  73 + ├── components/ # 通用业务组件(PermButton 等)
  74 + ├── pages/ # 页面组件(按模块子目录)
  75 + │ ├── usr/ # USR 用户管理页面
  76 + │ │ ├── UserListPage.tsx
  77 + │ │ ├── UserFormDrawer.tsx
  78 + │ │ └── LoginPage.tsx
  79 + │ └── 403.tsx # 无权限页
  80 + └── utils/ # 工具函数
  81 +```
  82 +
  83 +## 四、docs/ 结构
  84 +
  85 +```
  86 +docs/
  87 +├── 01-需求清单/ # 每模块一子目录(_module.md 模块头 + REQ-*.md 卡片)
  88 +├── 02-开发计划.md
  89 +├── 03-数据库设计文档.md
  90 +├── 04-技术规范.md
  91 +├── 05-API接口契约.md
  92 +├── 06-UI交互规范.md
  93 +├── 07-环境配置.md
  94 +├── 08-模块任务管理.md
  95 +├── 09-项目目录结构.md
  96 +├── 10-验收检查清单.md
  97 +└── superpowers/ # CC 运行时产物
  98 +```
  99 +
  100 +## 五、命名与放置约定
  101 +
  102 +- **根包名**:`com.example.erp`
  103 +- **Controller**:`XxxController.java`,放 `module/<mod>/controller/`,只做入参校验 + 调 Service
  104 +- **Service**:`XxxService.java` 接口 + `XxxServiceImpl.java` 实现,放 `module/<mod>/service/`
  105 +- **Mapper**:`XxxMapper.java`,放 `module/<mod>/mapper/`,继承 `BaseMapper<T>`
  106 +- **DTO**:请求参数用 `XxxReqDTO`,放 `module/<mod>/dto/`
  107 +- **VO**:响应视图用 `XxxVO`,放 `module/<mod>/vo/`
  108 +- **前端组件**:PascalCase,如 `UserFormDrawer.tsx`,放 `components/` 或页面同级
  109 +- **前端页面**:PascalCase + `Page` 后缀,如 `UserListPage.tsx`,放 `pages/<mod>/`
  110 +- **前端 API**:camelCase 模块名,如 `usr.ts`,放 `api/`
docs/10-验收检查清单.md 0 → 100644
  1 +++ a/docs/10-验收检查清单.md
  1 +# 10-验收检查清单
  2 +
  3 +通用验收项(全项目适用):
  4 +
  5 +- [ ] `scripts/test.sh` 本地全绿
  6 +- [ ] 所有 schema 改动都有对应 `sql/migrations/V_n__<desc>.sql`
  7 +- [ ] 所有新接口在 `docs/05` 中有契约定义
  8 +- [ ] 所有新功能代码注释含 REQ-XXX-NNN
  9 +- [ ] 统一响应格式 `{code, message, data, timestamp}`
  10 +- [ ] 异常走全局处理器,不暴露堆栈到前端
  11 +- [ ] 前端不存敏感信息到 localStorage
  12 +
  13 +> 本文档仅维护项目级验收 SOP。粒度更细的验收信息分散在:
  14 +> - **每 REQ 的业务验收点**:`docs/01-需求清单/<module>/<req_id>.md § 验收`
  15 +> - **每模块的实测验收(数据 / UI / 自动化用例位置)**:由 B 阶段 `module-report` 在该模块完成时填入模块完成报告
  16 +> - **REQ 开发进度(feature-review approve 状态)**:`docs/08 § 二`
scripts/setup-test-db.sh 0 → 100755
  1 +++ a/scripts/setup-test-db.sh
  1 +#!/usr/bin/env bash
  2 +# scripts/setup-test-db.sh — 数据库重置脚本:drop + create 空库。
  3 +# schema apply 由 Flyway 在 Spring Boot 启动时自动处理(见 docs/04 技术栈 + sql/migrations/V*.sql)。
  4 +# seed 数据由测试框架负责(Spring @Sql / Flyway R__seed.sql / data.sql)。
  5 +#
  6 +# 使用场景:
  7 +# - scripts/test.sh 开头:清空库,让 Spring 启动时 Flyway 从 V1 开始重放所有 migration
  8 +# - scripts/test.sh 结尾:清空库,避免测试遗留污染下次运行
  9 +# - 手动调试时:reset 到零状态
  10 +#
  11 +# 防护:本脚本只允许在本地 host + 测试库名上执行;非预期目标会被拒绝,
  12 +# 避免 .env.local 误指向 staging/prod 时触发不可逆 DROP。
  13 +
  14 +set -euo pipefail
  15 +
  16 +ENV_FILE="$(dirname "$0")/../.env.local"
  17 +[ -f "$ENV_FILE" ] || { echo "[setup-test-db] ⚠️ .env.local 不存在($ENV_FILE)" >&2; exit 1; }
  18 +
  19 +# 用 set -a 加载,让 KEY=VALUE 导出为环境变量;密码中含特殊字符时 .env.local 请用单引号包裹
  20 +set -a; . "$ENV_FILE"; set +a
  21 +
  22 +# 防护 1:默认只允许本地 host(localhost / 127.0.0.1 / ::1)。
  23 +# 若要为本项目额外允许某些远程 host(如公司测试 MySQL),在 .env.local 里设:
  24 +# TEST_DB_ALLOWED_HOSTS="118.178.19.35 test-mysql.internal" # 空格或逗号分隔
  25 +# 被列入者可直接 DROP CREATE,不再需要 TEST_DB_ALLOW_REMOTE=1。
  26 +ALLOWED_HOSTS="localhost 127.0.0.1 ::1 ${TEST_DB_ALLOWED_HOSTS//,/ }"
  27 +host_allowed=0
  28 +for h in $ALLOWED_HOSTS; do
  29 + [ "${DB_HOST:-}" = "$h" ] && { host_allowed=1; break; }
  30 +done
  31 +if [ "$host_allowed" -ne 1 ]; then
  32 + echo "[setup-test-db] ⚠️ 拒绝在非白名单 host (${DB_HOST}) 上执行 DROP DATABASE" >&2
  33 + echo " 当前白名单:${ALLOWED_HOSTS}" >&2
  34 + echo " 加入 host:在 .env.local 追加 TEST_DB_ALLOWED_HOSTS=\"<host1> <host2>\"" >&2
  35 + echo " 一次性绕过:TEST_DB_ALLOW_REMOTE=1 $0" >&2
  36 + [ "${TEST_DB_ALLOW_REMOTE:-0}" = "1" ] || exit 1
  37 +fi
  38 +
  39 +# 防护 2:schema 名需像测试/开发库(含 test / _dev / _local),否则要求显式确认
  40 +case "${DB_SCHEMA:-}" in
  41 + *test*|*_dev|*_local|*_ci)
  42 + ;;
  43 + *)
  44 + echo "[setup-test-db] ⚠️ schema '${DB_SCHEMA}' 不像测试库(期望命名含 test / _dev / _local / _ci)" >&2
  45 + echo " 如确为期望行为,请显式声明:TEST_DB_ALLOW_PROD_NAME=1 $0" >&2
  46 + [ "${TEST_DB_ALLOW_PROD_NAME:-0}" = "1" ] || exit 1
  47 + ;;
  48 +esac
  49 +
  50 +# 防护 3:显式 banner,让人看见自己在 drop 什么;远程 host 额外提示白名单内容
  51 +echo "[setup-test-db] 即将 DROP + CREATE \`${DB_SCHEMA}\` on ${DB_HOST}:${DB_PORT}"
  52 +case "${DB_HOST:-}" in
  53 + localhost|127.0.0.1|::1) ;;
  54 + *)
  55 + echo "[setup-test-db] ⚠️ 目标是 **远程** host(已在 TEST_DB_ALLOWED_HOSTS 白名单中,每次 test.sh 都会 DROP)"
  56 + echo "[setup-test-db] 当前白名单: ${ALLOWED_HOSTS}"
  57 + echo "[setup-test-db] 若不希望每次自动 DROP,从 .env.local 的 TEST_DB_ALLOWED_HOSTS 删掉此 host"
  58 + ;;
  59 +esac
  60 +
  61 +MYSQL_CMD="mysql -h${DB_HOST} -P${DB_PORT} -u${DB_USER} -p${DB_PASSWORD}"
  62 +
  63 +$MYSQL_CMD -e "DROP DATABASE IF EXISTS \`${DB_SCHEMA}\`; CREATE DATABASE \`${DB_SCHEMA}\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
  64 +
  65 +echo "[setup-test-db] done — schema will be applied by Flyway when Spring Boot starts"
scripts/test.sh 0 → 100755
  1 +++ a/scripts/test.sh
  1 +#!/usr/bin/env bash
  2 +# scripts/test.sh —— 合并到默认分支(main / master)前的测试闸门。
  3 +# 顺序:detect → setup-db → build → lint → unit+integration → e2e → reset-db
  4 +# 由 .githooks/pre-push 和 test-gate skill(通过子会话)调用。
  5 +
  6 +set -euo pipefail
  7 +
  8 +PROJECT_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
  9 +cd "$PROJECT_ROOT"
  10 +
  11 +# Stack detection (runtime, mode-agnostic)
  12 +HAS_BACKEND=0; [ -d backend ] && HAS_BACKEND=1
  13 +HAS_FRONTEND=0; [ -d frontend ] && HAS_FRONTEND=1
  14 +if [ $HAS_BACKEND -eq 0 ] && [ $HAS_FRONTEND -eq 0 ]; then
  15 + echo "[test.sh] FATAL: neither backend/ nor frontend/ exists" >&2
  16 + exit 1
  17 +fi
  18 +
  19 +echo "[test.sh] 1/6 setup test db"
  20 +./scripts/setup-test-db.sh
  21 +
  22 +echo "[test.sh] 2/6 build"
  23 +if [ $HAS_BACKEND -eq 1 ]; then (cd backend && mvn clean package -DskipTests); else echo "[test.sh] skip backend build"; fi
  24 +if [ $HAS_FRONTEND -eq 1 ]; then (cd frontend && npm run build); else echo "[test.sh] skip frontend build"; fi
  25 +
  26 +echo "[test.sh] 3/6 lint"
  27 +if [ $HAS_BACKEND -eq 1 ]; then (cd backend && mvn checkstyle:check); else echo "[test.sh] skip backend lint"; fi
  28 +if [ $HAS_FRONTEND -eq 1 ]; then (cd frontend && npm run lint); else echo "[test.sh] skip frontend lint"; fi
  29 +
  30 +echo "[test.sh] 4/6 unit + integration"
  31 +if [ $HAS_BACKEND -eq 1 ]; then (cd backend && mvn verify); else echo "[test.sh] skip backend test"; fi
  32 +if [ $HAS_FRONTEND -eq 1 ]; then (cd frontend && npm run test -- --run); else echo "[test.sh] skip frontend test"; fi
  33 +
  34 +echo "[test.sh] 5/6 E2E"
  35 +echo "[test.sh] e2e 略"
  36 +
  37 +echo "[test.sh] 6/6 reset test db"
  38 +./scripts/setup-test-db.sh
  39 +
  40 +echo "[test.sh] GREEN"
sql/migrations/.gitkeep 0 → 100644
  1 +++ a/sql/migrations/.gitkeep
sql/migrations/V1__initial_schema.sql 0 → 100644
  1 +++ a/sql/migrations/V1__initial_schema.sql
  1 +-- Flyway migration V1 — initial schema for 小羚羊
  2 +-- Generated: 2026-05-08T01:01:55Z
  3 +-- Source: 由 A4 db-init 从 docs/03-数据库设计文档.md 翻译生成(schema SSoT 是 docs/03)
  4 +-- This is the FIRST migration; subsequent schema changes must be written as new files sql/migrations/V2__<desc>.sql, V3__... etc.
  5 +-- Apply: Flyway runs this automatically at Spring Boot startup.
  6 +-- Do not hand-edit this file after it is committed; write a new migration instead.
  7 +
  8 +SET NAMES utf8mb4;
  9 +SET CHARACTER_SET_CLIENT = utf8mb4;
  10 +
  11 +-- ============================================================
  12 +-- Table: usr_user
  13 +-- ============================================================
  14 +CREATE TABLE `usr_user` (
  15 + `iIncrement` INT NOT NULL AUTO_INCREMENT COMMENT '整数主键 ID(标准列)',
  16 + `sId` VARCHAR(100) NULL COMMENT '业务 ID(标准列)',
  17 + `sBrandsId` VARCHAR(100) NULL COMMENT '品牌 ID(多租户隔离,标准列)',
  18 + `sSubsidiaryId` VARCHAR(100) NULL COMMENT '子公司 ID(组织层级隔离,标准列)',
  19 + `tCreateDate` DATETIME NOT NULL COMMENT '创建时间(标准列)',
  20 + `sUserCode` VARCHAR(50) NOT NULL COMMENT '用户号(业务编号,人类可读唯一标识)',
  21 + `sUsername` VARCHAR(100) NOT NULL COMMENT '用户名(登录标识,全局唯一,不可修改)',
  22 + `sPasswordHash` VARCHAR(255) NOT NULL COMMENT 'BCrypt 哈希密码,禁止存储明文',
  23 + `sUserType` VARCHAR(20) NOT NULL DEFAULT '普通用户' COMMENT '用户类型:普通用户 / 超级管理员',
  24 + `sLanguage` VARCHAR(20) NOT NULL DEFAULT '中文' COMMENT '界面语言:中文 / 英文 / 繁体',
  25 + `bCanEditDoc` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '单据修改权限:0=否,1=是',
  26 + `bIsDisabled` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否作废/禁用:0=正常,1=禁用',
  27 + `sEmployeeId` VARCHAR(100) NULL COMMENT '关联职员 ID(跨模块引用,职员未关联时为 NULL)',
  28 + `sCreatorUsername` VARCHAR(100) NULL COMMENT '制单人用户名(冗余字段,便于列表展示)',
  29 + `tLastLoginDate` DATETIME NULL COMMENT '最后登录时间',
  30 + `iLoginFailCount` INT NOT NULL DEFAULT 0 COMMENT '连续登录失败次数,用于防暴力破解',
  31 + `tLockUntil` DATETIME NULL COMMENT '账号锁定截止时间,NULL 表示未锁定',
  32 + PRIMARY KEY (`iIncrement`)
  33 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
  34 + COMMENT='用户账户主表,存储登录信息、类型、语言偏好及安全控制字段';
  35 +
  36 +-- ============================================================
  37 +-- Table: usr_permission_group
  38 +-- ============================================================
  39 +CREATE TABLE `usr_permission_group` (
  40 + `iIncrement` INT NOT NULL AUTO_INCREMENT COMMENT '整数主键 ID(标准列)',
  41 + `sId` VARCHAR(100) NULL COMMENT '业务 ID(标准列)',
  42 + `sBrandsId` VARCHAR(100) NULL COMMENT '品牌 ID(多租户隔离,标准列)',
  43 + `sSubsidiaryId` VARCHAR(100) NULL COMMENT '子公司 ID(组织层级隔离,标准列)',
  44 + `tCreateDate` DATETIME NOT NULL COMMENT '创建时间(标准列)',
  45 + `sGroupCode` VARCHAR(100) NOT NULL COMMENT '权限代码(如 usr:create、usr:edit),全局唯一',
  46 + `sGroupName` VARCHAR(200) NOT NULL COMMENT '权限显示名称(如"新增用户"、"修改用户")',
  47 + `sCategory` VARCHAR(100) NULL COMMENT '权限分类标签,用于前端权限分组展示',
  48 + PRIMARY KEY (`iIncrement`)
  49 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
  50 + COMMENT='权限分类/权限组定义表,每行对应一个可分配给用户的权限项';
  51 +
  52 +-- ============================================================
  53 +-- Table: usr_user_permission
  54 +-- ============================================================
  55 +CREATE TABLE `usr_user_permission` (
  56 + `iIncrement` INT NOT NULL AUTO_INCREMENT COMMENT '整数主键 ID(标准列)',
  57 + `sId` VARCHAR(100) NULL COMMENT '业务 ID(标准列)',
  58 + `sBrandsId` VARCHAR(100) NULL COMMENT '品牌 ID(多租户隔离,标准列)',
  59 + `sSubsidiaryId` VARCHAR(100) NULL COMMENT '子公司 ID(组织层级隔离,标准列)',
  60 + `tCreateDate` DATETIME NOT NULL COMMENT '创建时间(标准列)',
  61 + `sUserId` VARCHAR(100) NOT NULL COMMENT '关联 usr_user.sId',
  62 + `sPermGroupId` VARCHAR(100) NOT NULL COMMENT '关联 usr_permission_group.sId',
  63 + PRIMARY KEY (`iIncrement`)
  64 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
  65 + COMMENT='用户与权限组的多对多关联表';
  66 +
  67 +-- ============================================================
  68 +-- Table: tStaff
  69 +-- ============================================================
  70 +CREATE TABLE `tStaff` (
  71 + `iIncrement` INT NOT NULL AUTO_INCREMENT COMMENT '整数主键 ID(标准列)',
  72 + `sId` VARCHAR(100) NULL COMMENT '业务 ID(标准列)',
  73 + `sBrandsId` VARCHAR(100) NULL COMMENT '品牌 ID(多租户隔离,标准列)',
  74 + `sSubsidiaryId` VARCHAR(100) NULL COMMENT '子公司 ID(组织层级隔离,标准列)',
  75 + `tCreateDate` DATETIME NOT NULL COMMENT '创建时间(标准列)',
  76 + `sStaffNo` VARCHAR(50) NULL COMMENT '职员编号;系统内唯一',
  77 + `sStaffName` VARCHAR(50) NOT NULL COMMENT '职员姓名',
  78 + `sDepartment` VARCHAR(100) NULL DEFAULT NULL COMMENT '所属部门(本期暂用字符串,未来如需独立 tDepartment 字典表再另行重构)',
  79 + `sCreatedBy` VARCHAR(50) NULL COMMENT '制单人',
  80 + `bDeleted` BIT(1) NOT NULL DEFAULT 0 COMMENT '软删除标记',
  81 + `tDeletedDate` DATETIME NULL DEFAULT NULL COMMENT '软删除时间',
  82 + `sDeletedBy` VARCHAR(50) NULL DEFAULT NULL COMMENT '软删除操作人',
  83 + PRIMARY KEY (`iIncrement`)
  84 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
  85 + COMMENT='职员维度(员工名 / 部门 / 编号)';
  86 +
  87 +-- ============================================================
  88 +-- Table: brand
  89 +-- ============================================================
  90 +CREATE TABLE `brand` (
  91 + `iIncrement` INT NOT NULL AUTO_INCREMENT COMMENT '整数主键 ID(标准列)',
  92 + `sId` VARCHAR(100) NULL COMMENT '业务 ID(标准列)',
  93 + `sBrandsId` VARCHAR(100) NULL COMMENT '品牌 ID(多租户隔离,标准列)',
  94 + `sSubsidiaryId` VARCHAR(100) NULL COMMENT '子公司 ID(组织层级隔离,标准列)',
  95 + `tCreateDate` DATETIME NOT NULL COMMENT '创建时间(标准列)',
  96 + `sName` VARCHAR(100) NULL COMMENT '公司名称',
  97 + `sShortName` VARCHAR(100) NULL COMMENT '公司简称',
  98 + `sNo` VARCHAR(100) NULL COMMENT '单位编号(登录账号根据单位编号作为前缀)',
  99 + PRIMARY KEY (`iIncrement`)
  100 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
  101 + COMMENT='公司表';
  102 +
  103 +-- ============================================================
  104 +-- Indexes: usr_user
  105 +-- ============================================================
  106 +CREATE UNIQUE INDEX `uk_usr_user_sid` ON `usr_user` (`sId`);
  107 +CREATE UNIQUE INDEX `uk_usr_user_username` ON `usr_user` (`sUsername`);
  108 +CREATE UNIQUE INDEX `uk_usr_user_usercode` ON `usr_user` (`sUserCode`);
  109 +CREATE INDEX `idx_usr_user_tenant` ON `usr_user` (`sBrandsId`, `sSubsidiaryId`);
  110 +CREATE INDEX `idx_usr_user_type` ON `usr_user` (`sUserType`);
  111 +CREATE INDEX `idx_usr_user_disabled` ON `usr_user` (`bIsDisabled`);
  112 +
  113 +-- ============================================================
  114 +-- Indexes: usr_permission_group
  115 +-- ============================================================
  116 +CREATE UNIQUE INDEX `uk_usr_perm_group_sid` ON `usr_permission_group` (`sId`);
  117 +CREATE UNIQUE INDEX `uk_usr_perm_group_code` ON `usr_permission_group` (`sGroupCode`);
  118 +CREATE INDEX `idx_usr_perm_group_tenant` ON `usr_permission_group` (`sBrandsId`, `sSubsidiaryId`);
  119 +
  120 +-- ============================================================
  121 +-- Indexes: usr_user_permission
  122 +-- ============================================================
  123 +CREATE UNIQUE INDEX `uk_usr_user_perm` ON `usr_user_permission` (`sUserId`, `sPermGroupId`);
  124 +CREATE INDEX `idx_usr_user_perm_user` ON `usr_user_permission` (`sUserId`);
  125 +CREATE INDEX `idx_usr_user_perm_group` ON `usr_user_permission` (`sPermGroupId`);
  126 +
  127 +-- ============================================================
  128 +-- Indexes: tStaff
  129 +-- ============================================================
  130 +CREATE UNIQUE INDEX `uk_staff_no` ON `tStaff` (`sStaffNo`);
  131 +CREATE INDEX `idx_staff_name` ON `tStaff` (`sStaffName`);
  132 +CREATE INDEX `idx_department` ON `tStaff` (`sDepartment`);
  133 +
  134 +-- ============================================================
  135 +-- Indexes: brand
  136 +-- ============================================================
  137 +CREATE UNIQUE INDEX `uk_brand_no` ON `brand` (`sNo`);
  138 +CREATE INDEX `idx_brand_name` ON `brand` (`sName`);
  139 +
  140 +-- ============================================================
  141 +-- Foreign Keys
  142 +-- ============================================================
  143 +ALTER TABLE `usr_user_permission`
  144 + ADD CONSTRAINT `fk_usr_user_perm_user`
  145 + FOREIGN KEY (`sUserId`) REFERENCES `usr_user` (`sId`)
  146 + ON DELETE CASCADE ON UPDATE CASCADE;
  147 +
  148 +ALTER TABLE `usr_user_permission`
  149 + ADD CONSTRAINT `fk_usr_user_perm_group`
  150 + FOREIGN KEY (`sPermGroupId`) REFERENCES `usr_permission_group` (`sId`)
  151 + ON DELETE CASCADE ON UPDATE CASCADE;
src/styles/tokens.css 0 → 100644
  1 +++ a/src/styles/tokens.css
  1 +/*
  2 + * src/styles/tokens.css — Design Tokens
  3 + * 命名规范见 docs/04-技术规范.md § 2.5
  4 + * 色值锁定见 docs/06-UI交互规范.md § 四
  5 + *
  6 + * 命名格式:--color-<scope>-<role>-<state>
  7 + * <scope> 组件域:form / table-row / table-header / ...
  8 + * <role> 作用:bg(背景)/ fg(前景/字体)/ border
  9 + * <state> 状态:edit / readonly / hover / selected(无状态时省略)
  10 + *
  11 + * 约束:
  12 + * - 组件样式中只用 var(--color-xxx),禁止硬编码 hex / rgba
  13 + * - 修改色值只改本文件,不允许在组件级覆盖
  14 + * - 新增 token 须先登记到 docs/06 § 4.1 / 4.2,再补到此处
  15 + */
  16 +
  17 +:root {
  18 + /* === 1. 全局调色板(与 Ant Design 主题对齐) === */
  19 + --color-primary: #1890ff;
  20 + --color-success: #52c41a;
  21 + --color-warning: #faad14;
  22 + --color-error: #ff4d4f;
  23 + --color-text: rgba(0, 0, 0, 0.85);
  24 + --color-text-secondary: rgba(0, 0, 0, 0.45);
  25 + --color-border: #d9d9d9;
  26 + --color-bg-base: #f0f2f5;
  27 +
  28 + /* === 2. 组件级状态色(与 docs/06 § 4.2 一一对应) === */
  29 +
  30 + /* form:输入框 / 备注框 / 时间框 / 下拉框共用 */
  31 + --color-form-bg-edit: #ffffff;
  32 + --color-form-bg-readonly: #f1f2f8;
  33 + --color-form-bg-hover: #f5f5f5; /* 仅下拉框使用 */
  34 + --color-form-fg: #000000;
  35 +
  36 + /* table */
  37 + --color-table-row-bg-selected: #86d5fb;
  38 + --color-table-row-bg-hover: #fff7e6;
  39 + --color-table-row-bg-readonly: #f1f2f8; /* = rgb(241, 242, 248) */
  40 + --color-table-row-fg: #000000;
  41 + --color-table-header-bg: #f5f5f5;
  42 + --color-table-header-fg: rgba(0, 0, 0, 0.85); /* = #000000D9 */
  43 +}