Commit 2bc3429d277b84733e3674c89607d305744362da
0 parents
chore: plan phase done
Showing
23 changed files
with
1762 additions
and
0 deletions
.githooks/pre-push
0 → 100755
.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 | +B 阶段分两段,**全部固化到 skills**。入口:`/erp-workflow:coding-start`。 | |
| 19 | + | |
| 20 | +### 阶段路由(coding-start 内,只做分发) | |
| 21 | + | |
| 22 | +`coding-start` 每次入口做两段完成性检查后真值表派发: | |
| 23 | + | |
| 24 | +- 后端完成性检查 → `backend_done`(扫 docs/08 § 二 + GitLab state) | |
| 25 | +- 前端完成性检查 → `frontend_done`(扫 docs/08 § 三 整体 MR + state) | |
| 26 | + | |
| 27 | +| `backend_done` | `frontend_done` | 派发 | | |
| 28 | +|---|---|---| | |
| 29 | +| `false` | 任意 | `module-start`(写后端) | | |
| 30 | +| `true` | `false` | `frontend-start`(写前端) | | |
| 31 | +| `true` | `true` | "所有阶段已完成" | | |
| 32 | + | |
| 33 | +前端阶段前置(prototype/ 门禁)由 `frontend-start` 自带,不在 coding-start。`module-start` 与 `frontend-start` **互不感知对方**。 | |
| 34 | + | |
| 35 | +### 后端阶段(每模块一个 MR) | |
| 36 | + | |
| 37 | +- **模块循环(外)**:`module-start` → `test-gate(phase=backend)` → `module-report` → `mr-create` → 人工 Approve MR → 用户重跑 coding-start → coding-start 再次路由 | |
| 38 | +- **功能循环(内,每 REQ-XXX-NNN 一遍)**:`feature-brainstorm` → `feature-plan` → `feature-tdd` → `feature-verify` → `feature-review` | |
| 39 | +- 后端阶段任务严格落在 `backend/` 路径下;docs/01 REQ 卡片的 UI 描述在此阶段忽略,UI 推迟到前端阶段。 | |
| 40 | + | |
| 41 | +### 前端阶段(整体一个 MR,所有后端模块 merged 后启动) | |
| 42 | + | |
| 43 | +- **FE 清单(AI 自主推导,无审阅断点)**:`frontend-start` 进入时扫 prototype + docs/01 + docs/05 → AI 自主推导 FE 业务功能清单写入 `docs/08 § 三`(已有则加载)。**FE 是业务功能粒度,与 prototype HTML 文件数无关**——一个 HTML 可拆多个 FE,多个 HTML 也可合成一个 FE。FE 清单的合理性由 fe-feature-review / mr-create 在整体 MR 提交时一并校核(人工只在 MR Approve+Merge 处介入)。 | |
| 44 | +- **FE 循环(外)**:`frontend-start` → fe-feature 循环 → `test-gate(phase=frontend)` → `module-report(phase=frontend)` → `mr-create`(分支 `frontend-phase`,docs/08 § 三 整体 MR)。 | |
| 45 | +- **FE 功能循环(内,每个 FE-NN 一遍)**:`fe-feature-brainstorm` → `fe-feature-plan` → `fe-feature-tdd` → `fe-feature-verify` → `fe-feature-review`(专用 `fe-code-reviewer` agent,硬编码 7 维 review checklist) | |
| 46 | +- 前端阶段任务严格落在 `frontend/` 路径下;布局以 `prototype/` 为权威。 | |
| 47 | + | |
| 48 | +### MR 前测试闸门 | |
| 49 | + | |
| 50 | +- `test-gate`:后端阶段子会话跑 `scripts/test.sh`(含本模块新增 + 已合并模块回归);前端阶段子会话跑 vitest + playwright。 | |
| 51 | +- `.githooks/pre-push` 兜底;`git push --no-verify` 被 `deny-no-verify.sh` 硬拦。 | |
| 52 | + | |
| 53 | +--- | |
| 54 | + | |
| 55 | +## ✅ 阶段完成判定规则 | |
| 56 | + | |
| 57 | +`docs/08-模块任务管理.md` 分两段: | |
| 58 | +- `§ 二`:后端模块元数据表(每个模块一行 bullet,记录依赖 / 路径 / MR iid / 功能子项) | |
| 59 | +- `§ 三`:前端阶段元数据(整体 MR + FE 子项清单,由 `frontend-start` 在所有后端模块 merged 后填入) | |
| 60 | + | |
| 61 | +**阶段完成判定**统一以 `MR:` 字段(§ 二 各模块) / `整体 MR:` 字段(§ 三)+ `GitLab API state=merged` 判定;子项勾选只作可视化进度,不参与完成判定。 | |
| 62 | + | |
| 63 | +### 后端模块格式 | |
| 64 | + | |
| 65 | +每个后端模块在 docs/08 § 二 中长这样: | |
| 66 | + | |
| 67 | +```markdown | |
| 68 | +- module_0 系统管理 | |
| 69 | + - 依赖: — | |
| 70 | + - 路径: backend/module/sys/ | |
| 71 | + - MR: — | |
| 72 | + - 功能: | |
| 73 | + - [ ] REQ-SYS-001 用户登录 | |
| 74 | + - [ ] REQ-SYS-002 用户注册 | |
| 75 | +``` | |
| 76 | + | |
| 77 | +- `MR:` 字段由 `mr-create` 在创建 MR 时从 `—` 改为 `!<iid>`。 | |
| 78 | +- 每个 `REQ-*` 子项由 `feature-review` 在 `verdict=approve` 时自动勾选为 `[x]`。 | |
| 79 | +- 路径限定为后端目录(如 `backend/module/sys/`);前端代码不在此阶段产生。 | |
| 80 | + | |
| 81 | +### 前端阶段格式(§ 三) | |
| 82 | + | |
| 83 | +```markdown | |
| 84 | +- 整体 MR: — | |
| 85 | +- 功能: | |
| 86 | + - [ ] FE-01 用户登录与注册 | 关联 REQ:REQ-SYS-001, REQ-SYS-002 | 关联原型:prototype/auth.html | |
| 87 | + - [ ] FE-02 仪表盘总览 | 关联 REQ:REQ-DASH-001 | 关联原型:prototype/dashboard.html | |
| 88 | +``` | |
| 89 | + | |
| 90 | +- `整体 MR:` 字段由 `mr-create` 在创建前端 MR 时从 `—` 改为 `!<iid>`。 | |
| 91 | +- "功能:" 列表由 `frontend-start` 进入时由 AI 自主推导写入(无人工审阅断点)。FE 是业务功能粒度,与 prototype HTML 文件数无关;合理性由 fe-feature-review / 整体 MR 时统一校核。 | |
| 92 | +- 每个 `FE-NN` 子项由 `fe-feature-review` 在 `verdict=approve` 时自动勾选为 `[x]`。 | |
| 93 | +- 进入前端阶段前 `frontend-start` 步骤 1 自带 prototype/ 门禁,强制检查项目根 `prototype/` 至少含 1 个 `*.html` mockup。 | |
| 94 | + | |
| 95 | +### 状态语义(后端模块 + 前端阶段共用) | |
| 96 | + | |
| 97 | +| `MR:` 字段 | `GitLab API state` | 含义 | 你(Claude Code)的行为 | | |
| 98 | +|---|---|---|---| | |
| 99 | +| `—` | — | 该阶段未开始(未创建 MR) | ✅ 开始该阶段开发 | | |
| 100 | +| `!<iid>` | `opened` / `closed` | 阶段开发中 / 打回 | ✅ 继续推进该阶段 | | |
| 101 | +| `!<iid>` | `merged` | 阶段**已完成** | 🟢 后端:进入下一未完成模块;后端全完 → 前端阶段;前端 merged → 全部完成 | | |
| 102 | + | |
| 103 | +### 模块完成报告 | |
| 104 | + | |
| 105 | +由 `module-report` skill 产出,模板位于 由 module-report skill 持有(12 节标准化,含跨模块改动等 CLAUDE.md 软规则映射节)。CC 不手写模块报告,仅填模板。 | |
| 106 | + | |
| 107 | +--- | |
| 108 | + | |
| 109 | +## 🏷️ 占位符统一约定 | |
| 110 | + | |
| 111 | +项目文档里有 **2 种填写占位** + **1 种提示占位**: | |
| 112 | + | |
| 113 | +| 格式 | 谁填 | 使用阶段 | 说明 | | |
| 114 | +|------|-----|---------|------| | |
| 115 | +| `【人工填写:<简短说明>】` | 人 | 仅 A 阶段文档 | 密钥 / 账密 / 包名 / 命名约定 / 小版本号等人工才能决定的值;B 阶段 plan/spec 禁止出现,查不到真值时用 `AskUserQuestion` 问用户 | | |
| 116 | +| `TBD(<责任人>)` | CC 自动 | A 或 B | 后缀附带责任方(如 `TBD(A3 自动补)` / `TBD(A5 自动补)`);由对应 skill 就地补填,`module-report` § ⑦ 检查 `TBD(CC 补)` 残留 | | |
| 117 | + | |
| 118 | +**HTML 注释 `<!-- ... -->`**:提示占位,是**插件内部大纲模板**里给 LLM 的**填空提示 / 章节引导**,指引 LLM 按结构填实际内容。skill 生成时会**剥除**这些注释,最终产物里注释不会保留。 | |
| 119 | + | |
| 120 | +--- | |
| 121 | + | |
| 122 | +## 📐 编码行为约束 | |
| 123 | + | |
| 124 | +### 你必须做的 ✅ | |
| 125 | + | |
| 126 | +1. **严格遵循** `docs/04-技术规范.md`——命名 / 编码 / 统一响应 / 异常处理 / 数据访问 / 配置与安全 等项目专属技术规约全部在此 | |
| 127 | +2. **严格遵循** `docs/09-项目目录结构.md`——文件放对位置 | |
| 128 | +3. **每个后端接口** 必须先在 `docs/05-API接口契约.md` 定义,再编码实现 | |
| 129 | +4. **每个功能可追溯到 `REQ-XXX-NNN`**——commit tag + 代码注释(如 `// REQ-SYS-001: 用户登录`)+ plan/spec 文件名均用此 tag | |
| 130 | +5. **遇到跨模块改动**(动到非当前模块的代码)——按 § 🟡 软规则 **S2** 执行(允许改,但必须留痕) | |
| 131 | +6. **遇到技术栈外组件引入**(`docs/04 § 零` 技术栈表外的框架 / 中间件 / 关键库),按 § 🟡 软规则 **S1** 执行(允许引入,但必须先 AskUserQuestion) | |
| 132 | + | |
| 133 | +### 你禁止做的 🚫 | |
| 134 | + | |
| 135 | +1. **主会话直接 `mysql -e` 跑业务 DDL**(只读查询 / 临时本地调试除外)——业务 schema 必须走 `sql/migrations/V_n__*.sql`,详见下方 Schema 演化规约 | |
| 136 | +2. **手动 Edit `docs/08 § 二/§ 三` 的 `MR:` / `整体 MR:` 字段**,必须要由 `mr-create` 自动回写 | |
| 137 | + | |
| 138 | +### Schema 演化规约(Flyway migration) | |
| 139 | + | |
| 140 | +1. **文件命名**:`sql/migrations/V<n>__<snake_case_desc>.sql`,例:`V5__add_user_email_unique_index.sql` | |
| 141 | +2. **版本号分配**:建文件前 `ls sql/migrations/V*.sql` 查当前最大 n,新文件 `n_max + 1` | |
| 142 | +3. **Apply 方式**:Spring Boot 启动 / 测试启动时 Flyway 自动 apply(项目必须在 `pom.xml` 声明 `flyway-core` + `flyway-mysql` 依赖)。`scripts/setup-test-db.sh` 只负责清空库,不做 apply | |
| 143 | +4. **已合并的 migration 永不修改**:发现错了写一个补救 migration(如 `V7__fix_V5_index_name.sql`),旧 `V_n.sql` | |
| 144 | +5. **临时调试 DDL**:临时在本地试字段/索引可手动 `mysql -e`,但不写 migration;下次 `setup-test-db.sh` 会 drop+create 清掉 | |
| 145 | +6. **A4 生成的 V1**:`V1__initial_schema.sql` 是 A 阶段由 `db-init` 从 `docs/03-数据库设计文档.md`(A3 正向设计的 schema SSoT)翻译生成的初始版本;后续 V2/V3/... 由 B 阶段每个 REQ 按需写入,**同时**反向同步更新 docs/03 对应表小节以保持 SSoT 一致 | |
| 146 | + | |
| 147 | +--- | |
| 148 | + | |
| 149 | +## 🗂️ Git 提交规范 | |
| 150 | + | |
| 151 | +每次提交必须遵循以下格式: | |
| 152 | + | |
| 153 | +``` | |
| 154 | +<type>(<scope>): <subject> | |
| 155 | +``` | |
| 156 | + | |
| 157 | +- `scope`: 模块名,如 `user` / `inventory` / `order` | |
| 158 | +- `subject`: 简短描述;业务类(feat / fix / test)必须带 `REQ-XXX-NNN` 后缀 | |
| 159 | + | |
| 160 | +`type` 含义: | |
| 161 | + | |
| 162 | +| type | 看到它意味着 | | |
| 163 | +|-----|-------------| | |
| 164 | +| `feat` | **新能力上线**——用户多了一个功能、接口、页面或业务规则 | | |
| 165 | +| `fix` | **修 bug**——原来行为错了,这次改对 | | |
| 166 | +| `refactor` | **重构**——外部行为不变,只改代码结构 / 命名 / 抽象 | | |
| 167 | +| `docs` | **文档改动**——只动 Markdown / 代码注释,不动实现 | | |
| 168 | +| `style` | **格式调整**——空白 / 缩进 / import 顺序,逻辑 0 变化 | | |
| 169 | +| `test` | **只动测试代码**——补用例 / 修 fixture,不碰实现 | | |
| 170 | +| `chore` | **流程维护**——构建 / 依赖 / 工具 / 证据档案 / MR 元数据等非业务动作 | | |
| 171 | + | |
| 172 | +--- | |
| 173 | + | |
| 174 | +## 🚩 中断机制 | |
| 175 | + | |
| 176 | +功能循环(每个功能 REQ-XXX 的 Brainstorm → Plan → TDD → Verify → AI 自审)默认 **静默编程**,但触发以下任何一条必须**立刻停下、记录原因、等人决策**,不得自行绕过: | |
| 177 | + | |
| 178 | +| # | 中断 | 例子 | | |
| 179 | +| - | --- | --- | | |
| 180 | +| 1 | **测试反复失败** | 同一测试同一功能内连续 **10 次**修复失败 | | |
| 181 | +| 2 | **要改密钥 / 账密 / 包名** | `docs/07-环境配置.md` 里由人工标注必须填的字段 | | |
| 182 | +| 3 | **外部接口不可达** | 第三方 API 无法连接、证书失效等环境问题,并无法自行解决 | | |
| 183 | + | |
| 184 | +> 其余需要人类判断的场景一律走普通 `AskUserQuestion` Q&A,不中断、不写 Blocker 文件。 | |
| 185 | + | |
| 186 | +**触发中断时的固定动作:** | |
| 187 | + | |
| 188 | +1. 在当前功能的 plan 文件里追加一节 `## 🚩 Blocker`(报告格式由 `interrupt-check` 的 `interrupt-block-template.md` 持有) | |
| 189 | +2. 停止后续所有功能的静默执行 | |
| 190 | +3. 在主会话输出一句话摘要 + 指向 blocker 文件的路径,等人回复 | |
| 191 | + | |
| 192 | +--- | |
| 193 | + | |
| 194 | +## 🟡 软规则(允许继续,但有强制后续动作) | |
| 195 | + | |
| 196 | +以下情况 **不触发中断**,CC 可自行继续推进,但必须在约定位置留痕,模块完成时统一审计。 | |
| 197 | + | |
| 198 | +| # | 软规则 | 允许动作 | 强制后续 | | |
| 199 | +| - | ----- | ------- | ------- | | |
| 200 | +| S1 | **技术栈外组件引入** | 用 `AskUserQuestion` 给用户三选一:接受引入 / 换方案 / 拒绝 | ① **接受** → 同会话直接在 `docs/04 § 零` 追加一行 → 继续流程 ② **换方案 / 拒绝** → 视为常规歧义澄清,继续 Q&A 收敛 ③ 不写 Blocker、不中断流程 | | |
| 201 | +| S2 | **跨模块改动** | **默认不改**,仅为当前模块实现所必需时允许修改 | ① hook `log-cross-module.sh` 自动落存根 ② `module-report` 一次性调用 `cross-module-log` skill 批量补齐「原因 / 影响评估」+ 「跨模块改动」节完整贴入《模块完成报告》 | | |
| 202 | + | |
| 203 | +--- | |
| 204 | + | |
| 205 | +## 🧭 通用工作准则(General Principles) | |
| 206 | + | |
| 207 | +### 1. Think Before Coding | |
| 208 | + | |
| 209 | +**Don't assume. Don't hide confusion. Surface tradeoffs.** | |
| 210 | + | |
| 211 | +Before implementing: | |
| 212 | +- State your assumptions explicitly. If uncertain, ask. | |
| 213 | +- If multiple interpretations exist, present them - don't pick silently. | |
| 214 | +- If a simpler approach exists, say so. Push back when warranted. | |
| 215 | +- If something is unclear, stop. Name what's confusing. Ask. | |
| 216 | + | |
| 217 | +### 2. Simplicity First | |
| 218 | + | |
| 219 | +**Minimum code that solves the problem. Nothing speculative.** | |
| 220 | + | |
| 221 | +- No features beyond what was asked. | |
| 222 | +- No abstractions for single-use code. | |
| 223 | +- No "flexibility" or "configurability" that wasn't requested. | |
| 224 | +- No error handling for impossible scenarios. | |
| 225 | +- If you write 200 lines and it could be 50, rewrite it. | |
| 226 | + | |
| 227 | +Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify. | |
| 228 | + | |
| 229 | +### 3. Surgical Changes | |
| 230 | + | |
| 231 | +**Touch only what you must. Clean up only your own mess.** | |
| 232 | + | |
| 233 | +When editing existing code: | |
| 234 | +- Don't "improve" adjacent code, comments, or formatting. | |
| 235 | +- Don't refactor things that aren't broken. | |
| 236 | +- Match existing style, even if you'd do it differently. | |
| 237 | +- If you notice unrelated dead code, mention it - don't delete it. | |
| 238 | + | |
| 239 | +When your changes create orphans: | |
| 240 | +- Remove imports/variables/functions that YOUR changes made unused. | |
| 241 | +- Don't remove pre-existing dead code unless asked. | |
| 242 | + | |
| 243 | +The test: Every changed line should trace directly to the user's request. | |
| 244 | + | |
| 245 | +### 4. Goal-Driven Execution | |
| 246 | + | |
| 247 | +**Define success criteria. Loop until verified.** | |
| 248 | + | |
| 249 | +Transform tasks into verifiable goals: | |
| 250 | +- "Add validation" → "Write tests for invalid inputs, then make them pass" | |
| 251 | +- "Fix the bug" → "Write a test that reproduces it, then make it pass" | |
| 252 | +- "Refactor X" → "Ensure tests pass before and after" | |
| 253 | + | |
| 254 | +For multi-step tasks, state a brief plan: | |
| 255 | +``` | |
| 256 | +1. [Step] → verify: [check] | |
| 257 | +2. [Step] → verify: [check] | |
| 258 | +3. [Step] → verify: [check] | |
| 259 | +``` | |
| 260 | + | |
| 261 | +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 | +**目标**: 用户通过用户名+密码完成身份认证,获取 JWT Token 用于后续接口鉴权 | |
| 5 | + | |
| 6 | +- **输入**: | |
| 7 | + | |
| 8 | + - **表1**: | |
| 9 | + | |
| 10 | + | 字段 | 类型 | 必填 | 输入方式 | 显示来源 | 预加载 | 默认值 | 业务规则 | | |
| 11 | + | --- | ---- | --- | ---- | ------- | ----- | --- | ----------- | | |
| 12 | + | 用户名 | 文本 | 是 | 手工输入 | — | — | — | — | | |
| 13 | + | 密码 | 文本 | 是 | 手工输入 | — | — | — | 输入显示星号 | | |
| 14 | + | 版本 | 文本 | 是 | 下拉单选 | `公司表` | 页面加载时 | 标准版 | | | |
| 15 | + | |
| 16 | +- **输出**: 成功/失败 | |
| 17 | + | |
| 18 | +- **跨字段规则**: 校验用户名 + 密码哈希;连续失败达到阈值临时锁定账号;登录成功签发限时 token 并返回基本用户信息 | |
| 19 | +- **边界**: token 设置合理过期时间;接口需具备防暴力破解保护 | |
| 20 | +- **验收**: 正确凭据返回 Token 且可通过鉴权接口验证;错误密码返回通用错误消息(不区分用户名或密码错误);锁定账号返回锁定提示 | |
| 21 | +- **依赖表**: `t_user`(认证主体 + 失败计数 + 锁定时间 + 登录时间)/ `t_company`(「版本」下拉数据源) | |
| 22 | +- **依赖接口**: `POST /api/usr/auth/login`(本 REQ 提供);后续配套 `POST /api/usr/auth/refresh`、`POST /api/usr/auth/logout` 由同模块衍生接口(不另立 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 | + | 密码 | 文本 | — | 系统生成 | 不显示 | — | 666666 | 保存后自动设为初始化 | | |
| 20 | + | |
| 21 | + - **表2** - 权限组: | |
| 22 | + | |
| 23 | + | 字段 | 类型 | 必填 | 输入方式 | 显示来源 | 预加载 | 默认值 | 业务规则 | | |
| 24 | + | -------- | ---- | --- | ---- | ----------------- | --------- | --------- | ------------------- | | |
| 25 | + | 复选框 | 布尔 | 否 | 复选框 | — | — | 否 | 是否选择当前行权限 | | |
| 26 | + | 权限分类 | 文本 | — | — | — | 页面加载时 | — | — | | |
| 27 | + | |
| 28 | + | |
| 29 | +- **输出**: | |
| 30 | + | |
| 31 | + - **表1**: | |
| 32 | + | |
| 33 | + | 字段 | 类型 | 显示来源 | | |
| 34 | + | --- | --- | --- | | |
| 35 | + | 用户号 | 文本 | — | | |
| 36 | + | |
| 37 | +- **跨字段规则**: 用户名在系统内全局唯一;角色取值受系统配置约束 | |
| 38 | +- **边界**: 密码以哈希形式存储 | |
| 39 | +- **验收**: 提交合法数据后用户记录出现在列表;重复用户名返回错误提示;普通账号无权访问此功能 | |
| 40 | +- **依赖表**: `t_user`(新建主记录)/ `t_employee`(员工名下拉 + 关联)/ `t_permission`(权限分类下拉)/ `t_user_permission`(写入权限组关联) | |
| 41 | +- **依赖接口**: `POST /api/usr/users`(本 REQ 提供);前置 `POST /api/usr/auth/login`(REQ-USR-001)获取 JWT | ... | ... |
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 | + | 语言 | 文本 | 是 | 下拉单选 | 中文/英文/繁体 | 页面加载时 | 原值 | — | | |
| 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 | +- **依赖表**: `t_user`(更新主记录)/ `t_employee`(员工名下拉 + 关联)/ `t_permission`(权限分类下拉)/ `t_user_permission`(删旧 + 写新权限组关联) | |
| 40 | +- **依赖接口**: `PUT /api/usr/users/{iIncrement}`(本 REQ 提供);前置 `POST /api/usr/auth/login`(REQ-USR-001)+ 数据来源 `POST /api/usr/users`(REQ-USR-002) | ... | ... |
docs/01-需求清单/USR-用户管理/REQ-USR-004.md
0 → 100644
| 1 | +++ a/docs/01-需求清单/USR-用户管理/REQ-USR-004.md | |
| 1 | +### REQ-USR-004 查询用户 | |
| 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 | +- **依赖表**: `t_user`(列表主表 + 类型 / 作废 / 登录日期 / 制单人)/ `t_employee`(员工名)/ `t_department`(部门) | |
| 37 | +- **依赖接口**: `GET /api/usr/users`(本 REQ 提供);前置 `POST /api/usr/auth/login`(REQ-USR-001) | ... | ... |
docs/01-需求清单/USR-用户管理/_module.md
0 → 100644
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-用户管理 | — | `t_user` / `t_employee` / `t_department` / `t_permission` / `t_user_permission` / `t_company` | | |
| 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-001** | module_usr | 所属模块无依赖,认证接口为基础设施(其余 REQ 接口需 JWT 鉴权) | — | | |
| 18 | +| 2 | **REQ-USR-002** | module_usr | 依赖 REQ-USR-001 已在前;新增用户为后续修改 / 查询提供数据 | — | | |
| 19 | +| 3 | **REQ-USR-003** | module_usr | 依赖 REQ-USR-002 已在前(先有用户才能修改) | — | | |
| 20 | +| 4 | **REQ-USR-004** | module_usr | 依赖 REQ-USR-002 已在前(先有用户才能查询) | — | | |
| 21 | + | |
| 22 | +> **后端模块全部 merged 后**:用户重跑 `/erp-workflow:coding-start` → coding-start 检测到 `backend_done=true && frontend_done=false` → 派发 `frontend-start`。`frontend-start` 步骤 1 自带 prototype/ 门禁(≥ 1 个 `*.html` mockup,缺失则 AskUserQuestion 提示用户补齐)。前端阶段以业务功能(不是 HTML 文件数)为粒度拆分 FE,每个 FE 跑一次 feature 循环(fe-feature-*),最后整个阶段合 1 个 MR(分支 `frontend-phase`,记录在 `docs/08 § 三 整体 MR`)。 | |
| 23 | + | |
| 24 | +## 三、关键说明 | |
| 25 | + | |
| 26 | +- 本期需求只覆盖 USR 用户管理一个模块;后续若新增 PUR / SAL / FIN 等模块,需重新跑 `/erp-workflow:plan-start` 让 A5 重算依赖与顺序。 | |
| 27 | +- REQ-USR-001 排第一是「基础设施先行」策略:JWT 鉴权机制建好后,其余 REQ 才能复用 `@PreAuthorize` 与统一异常处理;初始管理员账号由 V1 后续的种子 migration 注入(或人工 SQL 直插)。 | |
| 28 | +- `t_employee` / `t_department` / `t_permission` / `t_company` 四张维表在 module_usr 内部按需访问,不另起模块;后续如演化出独立的 HR / 组织架构模块,再单独拆分。 | ... | ... |
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 | +USR 用户管理模块共 6 张表,围绕「用户」为核心: | |
| 24 | + | |
| 25 | +- `t_user`(用户主表)通过 `iEmployeeId` 关联 `t_employee`(职员),实现「用户号 / 员工名」一对一联动。 | |
| 26 | +- `t_employee`(职员表)通过 `iDepartmentId` 关联 `t_department`(部门表),支撑 REQ-USR-004 按部门筛选。 | |
| 27 | +- `t_user` 与 `t_permission`(权限分类字典)通过 `t_user_permission`(用户-权限关联表)建立多对多关系,实现 REQ-USR-002 / 003 的「权限组」分配。 | |
| 28 | +- `t_company`(公司版本字典)独立于 `t_user`,仅为 REQ-USR-001 登录入参「版本」下拉提供可选项,不与用户产生强引用。 | |
| 29 | + | |
| 30 | +依赖方向:`t_user → t_employee → t_department`、`t_user ↔ t_permission`(多对多)、`t_company` 独立。 | |
| 31 | + | |
| 32 | +## 表清单 | |
| 33 | +- `t_user` — 系统用户主表,承载登录认证、用户类型、语言偏好、单据权限等基础属性 | |
| 34 | +- `t_employee` — 公司职员主档,与用户表通过 `iEmployeeId` 关联,提供姓名 / 工号 / 部门归属 | |
| 35 | +- `t_department` — 部门组织树,支撑职员归属与按部门筛选 | |
| 36 | +- `t_permission` — 权限分类字典,定义系统可分配的权限项 | |
| 37 | +- `t_user_permission` — 用户-权限分类多对多关联表 | |
| 38 | +- `t_company` — 公司 / 版本字典(标准版 / 专业版 / 旗舰版),登录时「版本」下拉数据源 | |
| 39 | + | |
| 40 | +## `t_user` — 系统用户主表,承载登录认证与基础属性 | |
| 41 | + | |
| 42 | +### 字段 | |
| 43 | + | |
| 44 | +| 字段 | 类型 | Nullable | 默认 | 业务含义 | | |
| 45 | +|---|---|---|---|---| | |
| 46 | +| `iIncrement` | int | 否 | 自增函数 | 整数主键 ID(标准列) | | |
| 47 | +| `sId` | varchar(100) | 是 | uuid 函数 | 业务 ID(标准列) | | |
| 48 | +| `sBrandsId` | varchar(100) | 是 | `1111111111` | 品牌 ID(多租户隔离,标准列) | | |
| 49 | +| `sSubsidiaryId` | varchar(100) | 是 | `1111111111` | 子公司 ID(组织层级隔离,标准列) | | |
| 50 | +| `tCreateDate` | datetime | 否 | 当前时间 | 创建时间(标准列) | | |
| 51 | +| `sUserNo` | varchar(50) | 否 | — | 用户号;关联职员后自动同步员工号;系统内唯一 | | |
| 52 | +| `sUserName` | varchar(50) | 否 | — | 登录用户名;系统内唯一;3-50 位 | | |
| 53 | +| `iEmployeeId` | int | 是 | NULL | 关联职员 `t_employee.iIncrement`;可空(非员工账号如系统管理员) | | |
| 54 | +| `sPasswordHash` | varchar(255) | 否 | — | 密码哈希(BCrypt / Argon2);禁止明文;初始密码 `666666` 哈希后存入 | | |
| 55 | +| `sUserType` | varchar(20) | 否 | `NORMAL` | 用户类型枚举:`NORMAL`(普通用户)/ `SUPER_ADMIN`(超级管理员) | | |
| 56 | +| `sLanguage` | varchar(10) | 否 | `zh-CN` | 语言枚举:`zh-CN`(中文)/ `en-US`(英文)/ `zh-TW`(繁体) | | |
| 57 | +| `bModifyDoc` | tinyint(1) | 否 | 0 | 单据修改权限:0 否 / 1 是 | | |
| 58 | +| `bVoid` | tinyint(1) | 否 | 0 | 作废标记(软删除):0 启用 / 1 已作废 | | |
| 59 | +| `iLoginFailCount` | int | 否 | 0 | 连续登录失败次数;达到阈值触发临时锁定;登录成功后清零 | | |
| 60 | +| `tLockUntil` | datetime | 是 | NULL | 锁定截止时间;NULL 表示未锁定 | | |
| 61 | +| `tLastLoginDate` | datetime | 是 | NULL | 最近一次登录时间 | | |
| 62 | +| `sCreator` | varchar(100) | 是 | NULL | 制单人(创建该账号的操作员用户名) | | |
| 63 | + | |
| 64 | +### 索引 | |
| 65 | + | |
| 66 | +- `uk_user_username` (UNIQUE): `sUserName` | |
| 67 | +- `uk_user_userno` (UNIQUE): `sUserNo` | |
| 68 | +- `idx_user_employee` (BTREE): `iEmployeeId` | |
| 69 | +- `idx_user_tenant` (BTREE): `sBrandsId`, `sSubsidiaryId` | |
| 70 | +- `idx_user_void` (BTREE): `bVoid` | |
| 71 | + | |
| 72 | +### 外键 | |
| 73 | + | |
| 74 | +- `fk_user_employee`: `iEmployeeId` → `t_employee.iIncrement` (ON DELETE SET NULL / ON UPDATE RESTRICT) | |
| 75 | + | |
| 76 | +### 业务注记 | |
| 77 | + | |
| 78 | +- 登录认证主体表;密码以哈希形式存储,登录失败计数与锁定时间均落库以保证服务重启不丢;亦可由 Redis 镜像加速,但 DB 字段为权威。 | |
| 79 | +- `bVoid = 1` 视为已作废账号,不可登录、不出现在默认查询结果中(REQ-USR-004 列表默认隐藏作废)。 | |
| 80 | +- 用户名 / 用户号唯一性由 `uk_user_username` / `uk_user_userno` 保证;新增 / 修改时若冲突由 DB 抛唯一约束错误,Service 转译为 `40001 用户名已存在`。 | |
| 81 | + | |
| 82 | +## `t_employee` — 公司职员主档 | |
| 83 | + | |
| 84 | +### 字段 | |
| 85 | + | |
| 86 | +| 字段 | 类型 | Nullable | 默认 | 业务含义 | | |
| 87 | +|---|---|---|---|---| | |
| 88 | +| `iIncrement` | int | 否 | 自增函数 | 整数主键 ID(标准列) | | |
| 89 | +| `sId` | varchar(100) | 是 | uuid 函数 | 业务 ID(标准列) | | |
| 90 | +| `sBrandsId` | varchar(100) | 是 | `1111111111` | 品牌 ID(多租户隔离,标准列) | | |
| 91 | +| `sSubsidiaryId` | varchar(100) | 是 | `1111111111` | 子公司 ID(组织层级隔离,标准列) | | |
| 92 | +| `tCreateDate` | datetime | 否 | 当前时间 | 创建时间(标准列) | | |
| 93 | +| `sEmployeeNo` | varchar(50) | 否 | — | 员工号;系统内唯一 | | |
| 94 | +| `sName` | varchar(100) | 否 | — | 姓名 | | |
| 95 | +| `iDepartmentId` | int | 是 | NULL | 部门 ID,关联 `t_department.iIncrement` | | |
| 96 | +| `sPhone` | varchar(20) | 是 | NULL | 手机号 | | |
| 97 | +| `sEmail` | varchar(100) | 是 | NULL | 邮箱 | | |
| 98 | +| `bDisabled` | tinyint(1) | 否 | 0 | 是否离职:0 在职 / 1 离职 | | |
| 99 | + | |
| 100 | +### 索引 | |
| 101 | + | |
| 102 | +- `uk_employee_no` (UNIQUE): `sEmployeeNo` | |
| 103 | +- `idx_employee_dept` (BTREE): `iDepartmentId` | |
| 104 | +- `idx_employee_name` (BTREE): `sName` | |
| 105 | +- `idx_employee_tenant` (BTREE): `sBrandsId`, `sSubsidiaryId` | |
| 106 | + | |
| 107 | +### 外键 | |
| 108 | + | |
| 109 | +- `fk_employee_department`: `iDepartmentId` → `t_department.iIncrement` (ON DELETE SET NULL / ON UPDATE RESTRICT) | |
| 110 | + | |
| 111 | +### 业务注记 | |
| 112 | + | |
| 113 | +- 用户表通过 `iEmployeeId` 关联本表;REQ-USR-002 / 003 选择员工时下拉数据源;REQ-USR-004 查询字段「员工名 / 部门」均联表本表。 | |
| 114 | +- 离职员工保留记录(`bDisabled = 1`),其关联用户应被自动作废(应用层逻辑,未做 DB 触发器)。 | |
| 115 | + | |
| 116 | +## `t_department` — 部门组织树 | |
| 117 | + | |
| 118 | +### 字段 | |
| 119 | + | |
| 120 | +| 字段 | 类型 | Nullable | 默认 | 业务含义 | | |
| 121 | +|---|---|---|---|---| | |
| 122 | +| `iIncrement` | int | 否 | 自增函数 | 整数主键 ID(标准列) | | |
| 123 | +| `sId` | varchar(100) | 是 | uuid 函数 | 业务 ID(标准列) | | |
| 124 | +| `sBrandsId` | varchar(100) | 是 | `1111111111` | 品牌 ID(多租户隔离,标准列) | | |
| 125 | +| `sSubsidiaryId` | varchar(100) | 是 | `1111111111` | 子公司 ID(组织层级隔离,标准列) | | |
| 126 | +| `tCreateDate` | datetime | 否 | 当前时间 | 创建时间(标准列) | | |
| 127 | +| `sName` | varchar(100) | 否 | — | 部门名称 | | |
| 128 | +| `sCode` | varchar(50) | 否 | — | 部门编码;系统内唯一 | | |
| 129 | +| `iParentId` | int | 是 | NULL | 上级部门 ID,NULL 表示根部门 | | |
| 130 | +| `iSortOrder` | int | 否 | 0 | 排序值,小者靠前 | | |
| 131 | + | |
| 132 | +### 索引 | |
| 133 | + | |
| 134 | +- `uk_department_code` (UNIQUE): `sCode` | |
| 135 | +- `idx_department_parent` (BTREE): `iParentId` | |
| 136 | +- `idx_department_tenant` (BTREE): `sBrandsId`, `sSubsidiaryId` | |
| 137 | + | |
| 138 | +### 外键 | |
| 139 | + | |
| 140 | +- `fk_department_parent`: `iParentId` → `t_department.iIncrement` (ON DELETE RESTRICT / ON UPDATE RESTRICT) | |
| 141 | + | |
| 142 | +### 业务注记 | |
| 143 | + | |
| 144 | +- 自引用形成部门树,根部门 `iParentId IS NULL`。 | |
| 145 | +- 部门删除采用「拒绝删除有下级 / 有员工的部门」策略(应用层校验);DB 外键 RESTRICT 兜底。 | |
| 146 | + | |
| 147 | +## `t_permission` — 权限分类字典 | |
| 148 | + | |
| 149 | +### 字段 | |
| 150 | + | |
| 151 | +| 字段 | 类型 | Nullable | 默认 | 业务含义 | | |
| 152 | +|---|---|---|---|---| | |
| 153 | +| `iIncrement` | int | 否 | 自增函数 | 整数主键 ID(标准列) | | |
| 154 | +| `sId` | varchar(100) | 是 | uuid 函数 | 业务 ID(标准列) | | |
| 155 | +| `sBrandsId` | varchar(100) | 是 | `1111111111` | 品牌 ID(多租户隔离,标准列) | | |
| 156 | +| `sSubsidiaryId` | varchar(100) | 是 | `1111111111` | 子公司 ID(组织层级隔离,标准列) | | |
| 157 | +| `tCreateDate` | datetime | 否 | 当前时间 | 创建时间(标准列) | | |
| 158 | +| `sCode` | varchar(50) | 否 | — | 权限码,例如 `USR:ADD` / `USR:EDIT`;系统内唯一 | | |
| 159 | +| `sName` | varchar(100) | 否 | — | 权限分类名称(展示用) | | |
| 160 | +| `iSortOrder` | int | 否 | 0 | 同分类内排序 | | |
| 161 | + | |
| 162 | +### 索引 | |
| 163 | + | |
| 164 | +- `uk_permission_code` (UNIQUE): `sCode` | |
| 165 | + | |
| 166 | +### 外键 | |
| 167 | + | |
| 168 | +- 无 | |
| 169 | + | |
| 170 | +### 业务注记 | |
| 171 | + | |
| 172 | +- 字典表,启动时由初始化脚本灌入;不允许业务删除,禁用通过软标记(暂未引入 `bVoid`,由 docs/01 补充时再加)。 | |
| 173 | +- 权限码格式 `<模块代码>:<动作>`,与后端 `@PreAuthorize('hasAuthority(...)')` 一一对应(docs/06 § 1.3)。 | |
| 174 | + | |
| 175 | +## `t_user_permission` — 用户-权限分类关联表 | |
| 176 | + | |
| 177 | +### 字段 | |
| 178 | + | |
| 179 | +| 字段 | 类型 | Nullable | 默认 | 业务含义 | | |
| 180 | +|---|---|---|---|---| | |
| 181 | +| `iIncrement` | int | 否 | 自增函数 | 整数主键 ID(标准列) | | |
| 182 | +| `sId` | varchar(100) | 是 | uuid 函数 | 业务 ID(标准列) | | |
| 183 | +| `sBrandsId` | varchar(100) | 是 | `1111111111` | 品牌 ID(多租户隔离,标准列) | | |
| 184 | +| `sSubsidiaryId` | varchar(100) | 是 | `1111111111` | 子公司 ID(组织层级隔离,标准列) | | |
| 185 | +| `tCreateDate` | datetime | 否 | 当前时间 | 创建时间(标准列) | | |
| 186 | +| `iUserId` | int | 否 | — | 用户 ID,关联 `t_user.iIncrement` | | |
| 187 | +| `iPermissionId` | int | 否 | — | 权限分类 ID,关联 `t_permission.iIncrement` | | |
| 188 | + | |
| 189 | +### 索引 | |
| 190 | + | |
| 191 | +- `uk_user_perm` (UNIQUE): `iUserId`, `iPermissionId` | |
| 192 | +- `idx_user_perm_perm` (BTREE): `iPermissionId` | |
| 193 | + | |
| 194 | +### 外键 | |
| 195 | + | |
| 196 | +- `fk_userperm_user`: `iUserId` → `t_user.iIncrement` (ON DELETE CASCADE / ON UPDATE RESTRICT) | |
| 197 | +- `fk_userperm_perm`: `iPermissionId` → `t_permission.iIncrement` (ON DELETE RESTRICT / ON UPDATE RESTRICT) | |
| 198 | + | |
| 199 | +### 业务注记 | |
| 200 | + | |
| 201 | +- 关联表豁免 `sBrandsId` / `sSubsidiaryId` 的业务语义但保留列以遵守项目标准;写入时复用 `t_user` 的对应租户标识。 | |
| 202 | +- 删除用户时级联清理本表行(`ON DELETE CASCADE`);删除字典项保护已分配数据(`ON DELETE RESTRICT`)。 | |
| 203 | +- 唯一约束 `(iUserId, iPermissionId)` 确保同一用户对同一权限分类不重复授权。 | |
| 204 | + | |
| 205 | +## `t_company` — 公司 / 版本字典 | |
| 206 | + | |
| 207 | +### 字段 | |
| 208 | + | |
| 209 | +| 字段 | 类型 | Nullable | 默认 | 业务含义 | | |
| 210 | +|---|---|---|---|---| | |
| 211 | +| `iIncrement` | int | 否 | 自增函数 | 整数主键 ID(标准列) | | |
| 212 | +| `sId` | varchar(100) | 是 | uuid 函数 | 业务 ID(标准列) | | |
| 213 | +| `sBrandsId` | varchar(100) | 是 | `1111111111` | 品牌 ID(多租户隔离,标准列) | | |
| 214 | +| `sSubsidiaryId` | varchar(100) | 是 | `1111111111` | 子公司 ID(组织层级隔离,标准列) | | |
| 215 | +| `tCreateDate` | datetime | 否 | 当前时间 | 创建时间(标准列) | | |
| 216 | +| `sCode` | varchar(50) | 否 | — | 公司 / 版本编码;系统内唯一 | | |
| 217 | +| `sName` | varchar(100) | 否 | — | 显示名称 | | |
| 218 | + | |
| 219 | +### 索引 | |
| 220 | + | |
| 221 | +- `uk_company_code` (UNIQUE): `sCode` | |
| 222 | + | |
| 223 | +### 外键 | |
| 224 | + | |
| 225 | +- 无 | |
| 226 | + | |
| 227 | +### 业务注记 | |
| 228 | + | |
| 229 | +- REQ-USR-001 登录入参「版本」下拉数据源;启动时由初始化脚本插入至少一行默认公司数据;具体「版本」字段语义待后续业务确认后再补列。 | |
| 230 | +- 当前 REQ 描述不足以确认与 `t_user` 是否需要强关联,故先做独立字典;后续如需「用户绑定公司 / 版本」再于 `t_user` 增列。 | ... | ... |
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 | +| 层 | 职责 | | |
| 39 | +|---|---| | |
| 40 | +| `controller/` | 接收 HTTP 请求、参数校验(`@Valid`)、调用 Service、组装响应;不写业务逻辑 | | |
| 41 | +| `service/` | 业务编排、事务边界、跨 Mapper 调用;接口 + `impl/` 实现 | | |
| 42 | +| `mapper/` | MyBatis-Plus 数据访问(Java 接口 + XML),仅做单表 CRUD 与简单关联查询 | | |
| 43 | +| `entity/` | 与数据库表 1:1 的实体类,字段名/类型严格对齐 docs/03 | | |
| 44 | +| `dto/` | 入参对象(前端 → 后端),含 `@NotNull` / `@Pattern` 等校验注解 | | |
| 45 | +| `vo/` | 出参对象(后端 → 前端),由 MapStruct 从 Entity 转换 | | |
| 46 | +| `config/` | Spring 配置类(Security / Redis / MyBatis-Plus / Swagger / Activiti) | | |
| 47 | +| `common/` | 全局响应包装、统一异常处理器、拦截器、工具类 | | |
| 48 | +| `security/` | JWT 生成 / 验证、`UserDetailsService` 实现、权限注解 | | |
| 49 | + | |
| 50 | +### 1.2 命名约定 | |
| 51 | + | |
| 52 | +- **包名**:全小写,单数。根包 `com.example.erp`。业务模块包 `<ROOT>.module.<模块代码小写>`(示例:`com.example.erp.module.usr`)。 | |
| 53 | +- **类名**:大驼峰;按层级加后缀(`UserController` / `UserServiceImpl` / `UserMapper` / `UserCreateDTO` / `UserListVO`)。 | |
| 54 | +- **方法名**:小驼峰;动词开头(`createUser` / `listUsers` / `disableUser`),与 REST 动作语义对齐。 | |
| 55 | +- **常量**:全大写下划线(`MAX_LOGIN_FAILS = 5`),放对应业务模块的 `constant/` 子包或公共 `common/constant/`。 | |
| 56 | +- **示例 1**:`com.example.erp.module.usr.controller.UserController#listUsers(UserQueryDTO)` 返回 `PageVO<UserListVO>`。 | |
| 57 | +- **示例 2**:`com.example.erp.module.usr.service.impl.UserServiceImpl#disableUser(Long id)` 写入 `t_user_audit_log` 审计。 | |
| 58 | + | |
| 59 | +### 1.3 统一响应格式 | |
| 60 | + | |
| 61 | +后端所有 HTTP 接口返回 `Result<T>` 包装: | |
| 62 | + | |
| 63 | +```json | |
| 64 | +// 成功 | |
| 65 | +{ "code": 0, "message": "ok", "data": { ... } } | |
| 66 | +// 失败 | |
| 67 | +{ "code": 40001, "message": "用户名已存在", "data": null } | |
| 68 | +``` | |
| 69 | + | |
| 70 | +错误码段位: | |
| 71 | + | |
| 72 | +| 段位 | 含义 | | |
| 73 | +|---|---| | |
| 74 | +| `0` | 成功 | | |
| 75 | +| `1xxxx` | 系统级(参数校验、未授权、未登录) | | |
| 76 | +| `2xxxx` | 通用业务错误(资源不存在、状态非法) | | |
| 77 | +| `4xxxx` | 模块业务错误(按模块再分子段:USR=`40xxx`,PUR=`41xxx`...) | | |
| 78 | +| `5xxxx` | 第三方 / 基础设施错误(DB / Redis / 外部服务) | | |
| 79 | + | |
| 80 | +### 1.4 异常处理 | |
| 81 | + | |
| 82 | +- **全局异常处理器**:`@RestControllerAdvice` 统一捕获,转换为 `Result` 失败结构。 | |
| 83 | +- **必须 catch**:`MethodArgumentNotValidException`(参数校验)/ `BindException` / 业务自定义 `BizException` / `AccessDeniedException`。 | |
| 84 | +- **禁止 catch**:泛 `Exception` 在业务代码中吞掉;必须让全局处理器接管。 | |
| 85 | +- **接口响应禁止回显后端异常堆栈**:返回用户友好错误码 + 文案;堆栈仅写入 logback。 | |
| 86 | + | |
| 87 | +### 1.5 事务 | |
| 88 | + | |
| 89 | +- **事务边界**:放在 Service 层方法上(`@Transactional`),Controller / Mapper 禁止开事务。 | |
| 90 | +- **传播策略**:默认 `REQUIRED`;只读查询用 `@Transactional(readOnly = true)`。 | |
| 91 | +- **跨服务调用禁止开新事务嵌套**:跨进程 / 跨服务的一致性走「最终一致 + 补偿」或 Activiti 工作流,不要用分布式事务。 | |
| 92 | + | |
| 93 | +### 1.6 认证 | |
| 94 | + | |
| 95 | +- **协议**:JWT(HS256),头 `Authorization: Bearer <token>`。 | |
| 96 | +- **生命周期**:access token 8 小时,refresh token 7 天;登录返回两枚 token;access 过期前端凭 refresh 刷新一次。 | |
| 97 | +- **刷新机制**:refresh token 只能用于换 access,不能直接调业务接口;服务端可吊销(Redis 存吊销名单)。 | |
| 98 | +- **密钥管理**:JWT 签名密钥放 `.env.local` 的 `JWT_SECRET`,至少 32 字节;生产环境必须更换默认值。 | |
| 99 | +- **登录失败锁定**:连续 5 次失败锁账户 30 分钟,Redis 计数器 key `login:fail:<username>`。 | |
| 100 | + | |
| 101 | +## 二、前端规范 | |
| 102 | + | |
| 103 | +### 2.1 目录约定 | |
| 104 | + | |
| 105 | +| 目录 | 职责 | | |
| 106 | +|---|---| | |
| 107 | +| `src/api/` | Axios 实例 + 每模块一个 API 文件;**所有 HTTP 调用唯一入口** | | |
| 108 | +| `src/components/` | 跨页面通用组件(`AuthButton` / `AppTable` / `PageHeader`) | | |
| 109 | +| `src/pages/` | 业务页面,按模块组织子目录 | | |
| 110 | +| `src/store/` | Redux Toolkit slice,仅放真正的全局状态 | | |
| 111 | +| `src/hooks/` | 自定义 hook(`useAuth` / `usePagination` / `useDebounce`) | | |
| 112 | +| `src/utils/` | 纯函数工具(格式化、校验、正则) | | |
| 113 | +| `src/styles/` | `tokens.css` + 全局样式 | | |
| 114 | +| `src/router/` | 路由表 + 路由守卫 | | |
| 115 | + | |
| 116 | +> **前端禁止直接写 SQL / 操作 DB**,所有数据访问走 `api/` 层统一封装。 | |
| 117 | + | |
| 118 | +### 2.2 状态管理 | |
| 119 | + | |
| 120 | +- **Redux 存什么**:全局共享 + 跨页面持久(当前用户信息、权限码列表、菜单树、字典)。 | |
| 121 | +- **Redux 不存什么**:单个页面的表单值、Modal 开关、表格分页参数 → 放组件 `useState` / `useReducer`。 | |
| 122 | +- **服务端数据**:业务数据(列表、明细)不要塞 Redux,每次进入页面走 `api/` 取最新;只有字典 / 全局枚举做内存缓存(`store/slices/dictSlice`)。 | |
| 123 | + | |
| 124 | +### 2.3 请求封装 | |
| 125 | + | |
| 126 | +- **Axios 实例**:`src/api/http.ts` 统一创建,`baseURL` 取 `import.meta.env.VITE_API_BASE`。 | |
| 127 | +- **请求拦截器**:自动注入 `Authorization: Bearer <accessToken>`;token 临期则先刷新再发起原请求。 | |
| 128 | +- **响应拦截器**:剥 `Result.data` 给业务;`code !== 0` 时 `message.error(message)` 并 reject。 | |
| 129 | +- **超时**:默认 15s;上传 / 导出接口单独 60s。 | |
| 130 | +- **错误重试**:仅对幂等 GET 重试 1 次,POST / PUT / DELETE 禁止重试。 | |
| 131 | + | |
| 132 | +### 2.4 错误处理 | |
| 133 | + | |
| 134 | +- **网络错误**:Axios `error.code === 'ERR_NETWORK'` → 全局 `message.error('网络异常,请检查连接')`。 | |
| 135 | +- **业务错误**:`code !== 0` 在响应拦截器统一弹 message;页面只需处理 `try/catch` 中的 reject。 | |
| 136 | +- **页面级错误**:路由级 `ErrorBoundary` 包顶层,组件 throw 时显示统一错误页(docs/06 § 1.2)。 | |
| 137 | + | |
| 138 | +### 2.5 样式与主题 | |
| 139 | + | |
| 140 | +- **CSS 变量命名**:`--color-<scope>-<role>-<state>` | |
| 141 | + - `scope` = `form` / `table-row` / `btn` / `link` / ... | |
| 142 | + - `role` = `bg` / `fg` / `border` | |
| 143 | + - `state` = `edit` / `readonly` / `hover` / `selected` | |
| 144 | +- **文件位置**:`src/styles/tokens.css`,由 skeleton-gen 生成空骨架,色值由 docs/06 § 二锁定后填入。 | |
| 145 | +- **组件样式**:只用 `var(--color-xxx)`,禁止硬编码 hex / rgba。 | |
| 146 | +- **Ant Design 主题对接**:`<ConfigProvider theme={{ token: { colorPrimary: 'var(--color-primary)', ... } }}>` 把 tokens 注入 antd 主题。 | |
| 147 | + | |
| 148 | +具体 token 表见 docs/06 § 二。 | |
| 149 | + | |
| 150 | +## 三、共同约定 | |
| 151 | + | |
| 152 | +### 3.1 Git 提交 | |
| 153 | + | |
| 154 | +`<type>(<scope>): <subject> REQ-XXX-NNN` | |
| 155 | + | |
| 156 | +- `type` ∈ `feat` / `fix` / `refactor` / `test` / `docs` / `chore` / `style` | |
| 157 | +- `scope` 为模块代码小写(`usr` / `pur` / `sal`)或 `infra` | |
| 158 | +- `subject` 50 字以内动词开头 | |
| 159 | +- 末尾必须挂 `REQ-XXX-NNN`(如 `REQ-USR-001`),CI 用此挂关联 | |
| 160 | + | |
| 161 | +### 3.2 分页查询 | |
| 162 | + | |
| 163 | +- **后端入参**:`PageQuery { pageNum: int = 1, pageSize: int = 20 }`(`pageSize` 上限 100)+ 业务过滤字段;继承基类避免重复声明。 | |
| 164 | +- **后端出参**:`PageVO<T> { records: List<T>, total: long, pageNum: int, pageSize: int }`。 | |
| 165 | +- **前端组件**:统一封装 `<AppTable />` 内置 antd `Table` + `Pagination`,受控 `current` / `pageSize` / `total`。 | |
| 166 | +- **默认排序**:按创建时间倒序;可按列点击切换。 | |
| 167 | + | |
| 168 | +### 3.3 日期与金额 | |
| 169 | + | |
| 170 | +- **后端类型**:日期 `LocalDate`,时间 `LocalDateTime`,金额 `BigDecimal`(scale=2,HALF_UP)。 | |
| 171 | +- **序列化**:Jackson 全局配置 `LocalDateTime` ↔ `yyyy-MM-dd HH:mm:ss`;`BigDecimal` 序列化为字符串避免精度丢失。 | |
| 172 | +- **前端展示**:`utils/format.ts` 提供 `formatDate(d)` / `formatMoney(n)`;金额展示固定 2 位小数 + 千分位(`¥1,234.56`)。 | |
| 173 | + | |
| 174 | +### 3.4 数据访问规约 | |
| 175 | + | |
| 176 | +- **SELECT 字段显式列举**,禁止 `SELECT *`;Mapper XML 用 `<sql id="Base_Column_List">` 集中维护。 | |
| 177 | +- **禁止 N+1**:循环中不得执行 DB 查询;改用批量查(`IN (?,?,?)`)/ JOIN / `<foreach>`。 | |
| 178 | +- **表名 / 字段名**:Mapper XML 中通过 `<sql>` 片段或常量引用,禁止字符串拼接(防 SQL 注入 + 利于改名)。 | |
| 179 | +- **分页**:统一用 MyBatis-Plus `Page<T>` + `selectPage(...)`,禁止 LIMIT 字符串拼接。 | |
| 180 | + | |
| 181 | +### 3.5 配置与安全 | |
| 182 | + | |
| 183 | +- **配置**:DB 连接 / 端口 / 密钥 / 第三方 URL 等一律放 `application.yml` + `.env.local`,代码里**禁止硬编码**;`application.yml` 用 `${VAR_NAME:default}` 引用环境变量。 | |
| 184 | +- **前端安全**: | |
| 185 | + - `localStorage` **不存敏感信息**(token / 身份 / 个人数据);推荐 HttpOnly Cookie 或 「内存 access token + HttpOnly refresh cookie」组合。 | |
| 186 | + - 接口响应禁止回显后端异常堆栈(与 § 1.4 一致)。 | |
| 187 | + - XSS:所有用户输入展示走 React JSX 自动转义;`dangerouslySetInnerHTML` 禁用,除非内容来源已白名单化。 | ... | ... |
docs/05-API接口契约.md
0 → 100644
| 1 | +++ a/docs/05-API接口契约.md | |
| 1 | +# 05-API接口契约 | |
| 2 | + | |
| 3 | +BasePath: `/api` | |
| 4 | +端口: `8080` | |
| 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 | + | |
| 25 | +所有业务接口走 JWT Bearer。客户端在 `Authorization: Bearer <accessToken>` 头中携带访问令牌。 | |
| 26 | + | |
| 27 | +- 登录 `POST /api/usr/auth/login` 返回 `accessToken`(8 小时)+ `refreshToken`(7 天) | |
| 28 | +- 临期前端用 refresh token 换新 access token:`POST /api/usr/auth/refresh` | |
| 29 | +- 注销 `POST /api/usr/auth/logout` 服务端把 token 加入 Redis 吊销名单 | |
| 30 | +- 未携带 / 过期 / 已吊销 → 401,对应错误码 `40101 未登录` / `40102 Token 已过期` / `40103 Token 已吊销` | |
| 31 | + | |
| 32 | +### 分页参数 | |
| 33 | + | |
| 34 | +统一查询入参: | |
| 35 | + | |
| 36 | +| 字段 | 类型 | 必填 | 默认 | 说明 | | |
| 37 | +|---|---|---|---|---| | |
| 38 | +| `pageNum` | int | 否 | 1 | 当前页码,从 1 开始 | | |
| 39 | +| `pageSize` | int | 否 | 20 | 每页条数;上限 100 | | |
| 40 | +| `orderBy` | string | 否 | `tCreateDate DESC` | 排序表达式,列名 + 方向 | | |
| 41 | + | |
| 42 | +响应包装: | |
| 43 | + | |
| 44 | +```json | |
| 45 | +{ | |
| 46 | + "code": 200, "message": "ok", | |
| 47 | + "data": { | |
| 48 | + "records": [ ... ], | |
| 49 | + "total": 1234, | |
| 50 | + "pageNum": 1, | |
| 51 | + "pageSize": 20 | |
| 52 | + }, | |
| 53 | + "timestamp": 1700000000000 | |
| 54 | +} | |
| 55 | +``` | |
| 56 | + | |
| 57 | +## 接口清单 | |
| 58 | +(各模块接口段落见下方,由 `downstream-gen` 按 REQ 填入) | |
| 59 | + | |
| 60 | +## module_usr — USR 用户管理 | |
| 61 | + | |
| 62 | +### REQ-USR-001 用户登录 | |
| 63 | + | |
| 64 | +- **Method**: POST | |
| 65 | +- **Path**: `/api/usr/auth/login` | |
| 66 | +- **Auth**: 公开(无需 JWT) | |
| 67 | +- **请求**: | |
| 68 | + ```json | |
| 69 | + { | |
| 70 | + "userName": "admin", | |
| 71 | + "password": "P@ssw0rd", | |
| 72 | + "companyVersion": "STANDARD" | |
| 73 | + } | |
| 74 | + ``` | |
| 75 | + - `userName` (string, 必填, 3-50 位) | |
| 76 | + - `password` (string, 必填, 明文传输,由 TLS 保证机密性;后端用 BCrypt/Argon2 比对哈希) | |
| 77 | + - `companyVersion` (string, 必填, 枚举 `STANDARD` / `PRO` / `FLAGSHIP`) | |
| 78 | +- **响应**: | |
| 79 | + ```json | |
| 80 | + { | |
| 81 | + "code": 200, "message": "ok", | |
| 82 | + "data": { | |
| 83 | + "accessToken": "eyJ...", | |
| 84 | + "refreshToken": "eyJ...", | |
| 85 | + "expiresIn": 28800, | |
| 86 | + "user": { | |
| 87 | + "iIncrement": 1, | |
| 88 | + "sUserName": "admin", | |
| 89 | + "sUserType": "SUPER_ADMIN", | |
| 90 | + "sLanguage": "zh-CN", | |
| 91 | + "permissionCodes": ["USR:ADD", "USR:EDIT", "USR:DELETE", "USR:VIEW"] | |
| 92 | + } | |
| 93 | + }, | |
| 94 | + "timestamp": 1700000000000 | |
| 95 | + } | |
| 96 | + ``` | |
| 97 | + | |
| 98 | +#### 错误码 | |
| 99 | +- `40001` — 用户名或密码错误(不区分两者,防爆破) | |
| 100 | +- `40301` — 账号已锁定,请稍后再试 | |
| 101 | +- `40302` — 账号已作废 | |
| 102 | +- `40010` — 参数校验失败(用户名 / 密码 / 版本字段为空或格式不合法) | |
| 103 | + | |
| 104 | +--- | |
| 105 | + | |
| 106 | +### REQ-USR-002 新增用户 | |
| 107 | + | |
| 108 | +- **Method**: POST | |
| 109 | +- **Path**: `/api/usr/users` | |
| 110 | +- **Auth**: JWT;要求权限码 `USR:ADD` | |
| 111 | +- **请求**: | |
| 112 | + ```json | |
| 113 | + { | |
| 114 | + "userNo": "U10001", | |
| 115 | + "userName": "zhangsan", | |
| 116 | + "employeeId": 12, | |
| 117 | + "userType": "NORMAL", | |
| 118 | + "language": "zh-CN", | |
| 119 | + "modifyDoc": false, | |
| 120 | + "permissionIds": [3, 5, 8] | |
| 121 | + } | |
| 122 | + ``` | |
| 123 | + - `userNo` / `userName` 必填且系统内唯一 | |
| 124 | + - `employeeId` 可空(非员工账号) | |
| 125 | + - `userType` 枚举 `NORMAL` / `SUPER_ADMIN`(仅超级管理员可创建超级管理员) | |
| 126 | + - `language` 枚举 `zh-CN` / `en-US` / `zh-TW` | |
| 127 | + - `permissionIds` 权限分类 ID 数组,可为空 | |
| 128 | + - 初始密码后端固定生成(`666666`),不在请求体中 | |
| 129 | +- **响应**: | |
| 130 | + ```json | |
| 131 | + { "code": 200, "message": "ok", "data": { "userNo": "U10001", "iIncrement": 42 }, "timestamp": 1700000000000 } | |
| 132 | + ``` | |
| 133 | + | |
| 134 | +#### 错误码 | |
| 135 | +- `40001` — 用户名 / 用户号已存在 | |
| 136 | +- `40010` — 参数校验失败 | |
| 137 | +- `40310` — 普通用户无权创建超级管理员 | |
| 138 | +- `40411` — 关联职员不存在 | |
| 139 | +- `40412` — 关联权限分类不存在 | |
| 140 | + | |
| 141 | +--- | |
| 142 | + | |
| 143 | +### REQ-USR-003 修改用户 | |
| 144 | + | |
| 145 | +- **Method**: PUT | |
| 146 | +- **Path**: `/api/usr/users/{iIncrement}` | |
| 147 | +- **Auth**: JWT;要求权限码 `USR:EDIT` | |
| 148 | +- **请求**: | |
| 149 | + ```json | |
| 150 | + { | |
| 151 | + "employeeId": 12, | |
| 152 | + "userType": "NORMAL", | |
| 153 | + "language": "en-US", | |
| 154 | + "modifyDoc": true, | |
| 155 | + "void": false, | |
| 156 | + "permissionIds": [3, 5] | |
| 157 | + } | |
| 158 | + ``` | |
| 159 | + - `userName` 不可修改,故请求体不包含 | |
| 160 | + - `void = true` 等价于禁用(软删除) | |
| 161 | + - `permissionIds` 全量覆盖(先 DELETE 后 INSERT,事务内) | |
| 162 | +- **响应**: | |
| 163 | + ```json | |
| 164 | + { "code": 200, "message": "ok", "data": { "iIncrement": 42 }, "timestamp": 1700000000000 } | |
| 165 | + ``` | |
| 166 | + | |
| 167 | +#### 错误码 | |
| 168 | +- `40404` — 目标用户不存在 | |
| 169 | +- `40310` — 不可修改超级管理员(非超级管理员调用) | |
| 170 | +- `40311` — 不可禁用 / 修改自己 | |
| 171 | +- `40411` — 关联职员不存在 | |
| 172 | +- `40412` — 关联权限分类不存在 | |
| 173 | +- `40010` — 参数校验失败 | |
| 174 | + | |
| 175 | +--- | |
| 176 | + | |
| 177 | +### REQ-USR-004 查询用户 | |
| 178 | + | |
| 179 | +- **Method**: GET | |
| 180 | +- **Path**: `/api/usr/users` | |
| 181 | +- **Auth**: JWT;要求权限码 `USR:VIEW` | |
| 182 | +- **请求**: query string | |
| 183 | + - `searchField` 枚举 `userName` / `employeeName` / `userNo` / `departmentName` / `userType` / `void` / `lastLoginDate` / `creator` | |
| 184 | + - `matchMode` 枚举 `CONTAINS` / `NOT_CONTAINS` / `EQUALS` | |
| 185 | + - `searchValue` 字符串,空则不过滤 | |
| 186 | + - `includeVoid` 布尔,默认 false(不返回已作废用户) | |
| 187 | + - `pageNum` / `pageSize` / `orderBy` 通用分页参数 | |
| 188 | +- **响应**: 分页结构,`records[]` 元素: | |
| 189 | + ```json | |
| 190 | + { | |
| 191 | + "iIncrement": 42, | |
| 192 | + "sUserName": "zhangsan", | |
| 193 | + "employeeName": "张三", | |
| 194 | + "sUserNo": "U10001", | |
| 195 | + "departmentName": "技术部", | |
| 196 | + "sUserType": "NORMAL", | |
| 197 | + "sLanguage": "zh-CN", | |
| 198 | + "bVoid": false, | |
| 199 | + "tLastLoginDate": "2026-05-13 09:12:33", | |
| 200 | + "sCreator": "admin", | |
| 201 | + "tCreateDate": "2025-12-01 10:00:00" | |
| 202 | + } | |
| 203 | + ``` | |
| 204 | + - 不返回 `sPasswordHash` / `iLoginFailCount` / `tLockUntil` | |
| 205 | + | |
| 206 | +#### 错误码 | |
| 207 | +- `40010` — 参数校验失败(`pageSize > 100` / `matchMode` 不合法 / `searchField` 不合法) | ... | ... |
docs/06-UI交互规范.md
0 → 100644
| 1 | +++ a/docs/06-UI交互规范.md | |
| 1 | +# 06-UI交互规范 | |
| 2 | + | |
| 3 | +> 本项目所有页面布局以项目根 `prototype/` 目录下的静态 HTML mockup 为权威。前端阶段(fe-feature-*)实现时直接以 prototype/ HTML 推导组件树与样式。本文件仅承载跨页面通用规则与 Design Tokens。 | |
| 4 | + | |
| 5 | +## 一、通用交互规则 | |
| 6 | + | |
| 7 | +### 1.1 操作反馈 | |
| 8 | + | |
| 9 | +- **成功提示**:使用 Ant Design `message.success()`,默认 3s 自动消失;新增 / 修改 / 删除等写操作必须给反馈。 | |
| 10 | +- **失败提示**:使用 `message.error()` 显示后端返回的 `message` 字段;表单字段级错误用 Form 的 `validateStatus="error"` + `help` 同步显示。 | |
| 11 | +- **危险操作二次确认**:删除 / 禁用 / 重置密码等不可逆操作必须用 `Modal.confirm({ okType: 'danger' })` 二次确认,按钮文案使用业务动词(如「确认禁用」),不用「确定」。 | |
| 12 | +- **长耗时按钮 loading**:提交类按钮在请求未返回前必须置为 `loading=true` 并禁用,避免重复提交;查询类操作在表格组件上启用 `loading` 属性即可。 | |
| 13 | + | |
| 14 | +### 1.2 数据展示 | |
| 15 | + | |
| 16 | +- **空状态**:列表 / 表格无数据时使用 `<Empty description="暂无数据" />`,明细页无数据时附加「返回列表」按钮。 | |
| 17 | +- **加载状态**:页面级加载用 `<Spin spinning>` 包裹主区域;表格 / 卡片局部加载用组件自带 `loading` 属性。 | |
| 18 | +- **异常状态**:接口 5xx 或网络异常时显示 `<Result status="error" title="加载失败" extra={<Button>重试</Button>} />`;403 时显示「无权限」并提供「返回首页」按钮。 | |
| 19 | + | |
| 20 | +### 1.3 权限控制(前端) | |
| 21 | + | |
| 22 | +- **菜单级**:登录后由后端返回菜单权限码列表,前端 Layout 按权限码过滤后渲染左侧菜单。 | |
| 23 | +- **按钮级**:包装 `<AuthButton code="USR:ADD">新增</AuthButton>`,权限码不在用户列表时直接不渲染(避免显示再禁用)。 | |
| 24 | +- **路由级**:在 React Router 外层包 `<AuthRoute />`,路由元数据声明 `meta.code`,无权限重定向 403 页面。 | |
| 25 | +- **关联后端 RBAC**:权限码格式 `<模块代码>:<动作>`(如 `USR:ADD` / `USR:EDIT` / `USR:DELETE`),与后端 Spring Security `@PreAuthorize("hasAuthority('USR:ADD')")` 一一对应。 | |
| 26 | + | |
| 27 | +## 二、Design Tokens | |
| 28 | + | |
| 29 | +> 所有色值统一以 CSS 变量定义于 `src/styles/tokens.css`;命名规范见 docs/04 § 2.5。 | |
| 30 | + | |
| 31 | +### 2.1 全局调色板 | |
| 32 | + | |
| 33 | +与 Ant Design 5 主题对齐,统一通过 `<ConfigProvider theme={{ token: {...} }}>` 注入。 | |
| 34 | + | |
| 35 | +| 语义 | 变量名 | 默认值 | 用途 | | |
| 36 | +|---|---|---|---| | |
| 37 | +| 主色 | `--color-primary` | `#1677ff` | 主操作按钮 / 链接 / 选中态 | | |
| 38 | +| 成功 | `--color-success` | `#52c41a` | 成功消息 / 启用状态标签 | | |
| 39 | +| 警告 | `--color-warning` | `#faad14` | 警告消息 / 待处理状态 | | |
| 40 | +| 错误 | `--color-error` | `#ff4d4f` | 错误消息 / 危险按钮 / 禁用状态 | | |
| 41 | +| 主文字 | `--color-text-primary` | `rgba(0, 0, 0, 0.88)` | 正文 / 表格内容 | | |
| 42 | +| 次文字 | `--color-text-secondary` | `rgba(0, 0, 0, 0.65)` | 辅助说明 / 占位 | | |
| 43 | +| 边框 | `--color-border` | `#d9d9d9` | 表单输入 / 卡片边界 | | |
| 44 | +| 背景 | `--color-bg-base` | `#ffffff` | 页面主背景 | | |
| 45 | +| 弱背景 | `--color-bg-layout` | `#f5f5f5` | 页面 layout 间隙 / 灰底 | | |
| 46 | + | |
| 47 | +### 2.2 组件级状态色 | |
| 48 | + | |
| 49 | +| 序号 | 组件 | 编辑bg | 只读bg | 悬浮bg | 编辑fg | 只读fg | 悬浮fg | 备注 | | |
| 50 | +|---|---|---|---|---|---|---|---|---| | |
| 51 | +| 1 | 表单输入框 | `var(--color-bg-base)` | `var(--color-bg-layout)` | `var(--color-bg-base)` | `var(--color-text-primary)` | `var(--color-text-secondary)` | `var(--color-text-primary)` | 只读态边框使用 `transparent` | | |
| 52 | +| 2 | 下拉单选 | `var(--color-bg-base)` | `var(--color-bg-layout)` | `var(--color-bg-base)` | `var(--color-text-primary)` | `var(--color-text-secondary)` | `var(--color-text-primary)` | — | | |
| 53 | +| 3 | 表格行 | — | `var(--color-bg-base)` | `var(--color-bg-row-hover)` | — | `var(--color-text-primary)` | `var(--color-text-primary)` | 编辑态走 Modal,行内不编辑 | | |
| 54 | +| 4 | 主按钮 | `var(--color-primary)` | `var(--color-bg-layout)` | `var(--color-primary-hover)` | `#ffffff` | `var(--color-text-secondary)` | `#ffffff` | 危险按钮替换 `--color-error` | | |
| 55 | +| 5 | 链接 | — | — | `var(--color-primary-hover)` | `var(--color-primary)` | — | `var(--color-primary-hover)` | — | | |
| 56 | + | |
| 57 | +**Token 默认值**: | |
| 58 | + | |
| 59 | +| Token | 默认值 | | |
| 60 | +|---|---| | |
| 61 | +| `--color-primary-hover` | `#4096ff` | | |
| 62 | +| `--color-bg-row-hover` | `#fafafa` | | |
| 63 | + | |
| 64 | +### 2.3 引用约定 | |
| 65 | + | |
| 66 | +- 组件样式只用 `var(--color-xxx)`,禁止硬编码 hex / rgba。 | |
| 67 | +- 新增 token 须先登记到 § 2.1 / 2.2 再补 `tokens.css`。 | |
| 68 | +- 修改色值只改 `tokens.css` 一处,不允许组件覆盖。 | |
| 69 | + | |
| 70 | +## 三、页面清单 | |
| 71 | + | |
| 72 | +### module_usr USR-用户管理 | |
| 73 | + | |
| 74 | +- **登录页** (`/login`) | |
| 75 | + - 类型: 表单页 | |
| 76 | + - 对应 REQ: REQ-USR-001 | |
| 77 | + - 入口菜单: 无(未登录态唯一可达页面) | |
| 78 | + - 主要交互: 表单输入 用户名 / 密码 / 版本 → 提交 → 成功跳首页 / 失败留页并显示通用错误消息;账号锁定时显示锁定提示 | |
| 79 | +- **用户列表页** (`/usr/users`) | |
| 80 | + - 类型: 列表页 | |
| 81 | + - 对应 REQ: REQ-USR-004 | |
| 82 | + - 入口菜单: 系统管理 → 用户管理 → 用户列表 | |
| 83 | + - 主要交互: 查询字段 + 匹配方式 + 查询值 三段筛选 → 表格分页展示 → 行内「编辑」「禁用 / 启用」按钮(权限码 `USR:EDIT`);顶部「新增用户」按钮(权限码 `USR:ADD`)跳新增表单 | |
| 84 | +- **用户新增页** (`/usr/users/new`) | |
| 85 | + - 类型: 表单页 | |
| 86 | + - 对应 REQ: REQ-USR-002 | |
| 87 | + - 入口菜单: 系统管理 → 用户管理 → 用户列表 → 新增按钮 | |
| 88 | + - 主要交互: 员工名(下拉单选)选择后自动回填用户号 / 用户名;类型 / 语言下拉;权限组表格(多选);提交校验失败显示字段级 `validateStatus="error"`;成功 message.success 后回列表页 | |
| 89 | +- **用户编辑页** (`/usr/users/:id/edit`) | |
| 90 | + - 类型: 表单页 | |
| 91 | + - 对应 REQ: REQ-USR-003 | |
| 92 | + - 入口菜单: 用户列表 → 行内「编辑」按钮 | |
| 93 | + - 主要交互: 复用新增表单组件;用户名只读;提供「重置密码」按钮(二次确认);提供「禁用 / 启用」开关;禁用自己时按钮置灰 | ... | ... |
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 推荐版本 | | |
| 8 | +| 运行时 | MySQL | 8.x | 关系数据库 | | |
| 9 | +| 运行时 | Redis | 最新稳定版 | 缓存 / 会话 | | |
| 10 | +| 运行时 | Node.js | 20.x LTS | 前端构建 + 本地 dev server | | |
| 11 | +| 构建(后端) | Maven | 3.9.x | Java 依赖与构建工具 | | |
| 12 | +| 构建(前端) | pnpm | 8.x(或 npm 10.x) | 前端依赖管理 | | |
| 13 | +| 构建(前端) | Vite | 最新稳定版 | 前端开发与打包 | | |
| 14 | +| 容器 | Docker | 最新稳定版 | 容器化部署 | | |
| 15 | +| 容器 | Docker Compose | 最新稳定版 | 本地一键启依赖(MySQL + Redis) | | |
| 16 | +| 反向代理 | Nginx | 最新稳定版 | 前端静态托管 / 反向代理 | | |
| 17 | +| CLI 工具 | git | 2.30+ | 代码版本控制 | | |
| 18 | +| CLI 工具 | mysql client | 8.x | 本地执行 SQL / 验证 migration | | |
| 19 | +| CLI 工具 | glab(可选) | 最新稳定版 | GitLab MR 创建(亦可直接走 Web) | | |
| 20 | + | |
| 21 | +## 二、端口约定 | |
| 22 | + | |
| 23 | +| 服务 | 端口 | 说明 | | |
| 24 | +|---|---|---| | |
| 25 | +| 后端 HTTP | 8080 | Spring Boot 默认端口(`server.port`) | | |
| 26 | +| 前端 dev server | 5173 | Vite 默认端口(`vite --port`) | | |
| 27 | +| MySQL | 3306 | 默认端口,本地开发可用 Docker Compose 暴露 | | |
| 28 | +| Redis | 6379 | 默认端口 | | |
| 29 | +| Nginx | 80 / 443 | 生产部署反向代理入口 | | |
| 30 | + | |
| 31 | +## 三、环境变量 | |
| 32 | + | |
| 33 | +运行时凭据(数据库连接、JWT 密钥等)全部放在仓库根的 `.env.local`,不入 git。 | |
| 34 | +字段清单与占位符见该文件,真实值由开发者本地填写。 | |
| 35 | + | |
| 36 | +## 四、常用命令 | |
| 37 | + | |
| 38 | +| 命令 | 说明 | | |
| 39 | +|---|---| | |
| 40 | +| `./mvnw spring-boot:run` | 本地启动后端(含 Flyway 自动 apply migration) | | |
| 41 | +| `pnpm dev` | 本地启动前端 dev server(默认 5173) | | |
| 42 | +| `./mvnw clean package -DskipTests` | 后端打包生成 jar | | |
| 43 | +| `pnpm build` | 前端打包到 `dist/` | | |
| 44 | +| `bash scripts/test.sh` | 执行后端 + 前端测试组合(lint / unit / e2e) | | |
| 45 | +| `bash scripts/setup-test-db.sh` | 重置本地测试数据库(DROP + CREATE + apply V1) | | |
| 46 | +| `glab mr create` | 推送当前分支并创建 GitLab MR(亦可走 Web 端) | | ... | ... |
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 决定派发。后端模块全部 merged 后自动进入 § 三 前端阶段。) | |
| 47 | + | |
| 48 | +<!-- 模块格式示例(由 A5 downstream-gen 追加;功能子项由 feature-review 在 approve 时勾选): | |
| 49 | +- module_0 系统管理 | |
| 50 | + - 依赖: — | |
| 51 | + - 路径: backend/module/sys/ | |
| 52 | + - MR: — | |
| 53 | + - 功能: | |
| 54 | + - [ ] REQ-SYS-001 用户登录 | |
| 55 | + - [ ] REQ-SYS-002 用户注册 | |
| 56 | +--> | |
| 57 | + | |
| 58 | +- module_usr USR-用户管理 | |
| 59 | + - 依赖: — | |
| 60 | + - 路径: backend/src/main/java/com/example/erp/module/usr/ | |
| 61 | + - MR: — | |
| 62 | + - 功能: | |
| 63 | + - [ ] REQ-USR-001 用户登录 | |
| 64 | + - [ ] REQ-USR-002 新增用户 | |
| 65 | + - [ ] REQ-USR-003 修改用户 | |
| 66 | + - [ ] REQ-USR-004 查询用户 | |
| 67 | + | |
| 68 | +## 三、Coding 阶段(前端整体) | |
| 69 | + | |
| 70 | +(`frontend-start` 进入时扫 prototype/ + docs/01 + docs/05 → AI 自主推导 FE 业务功能清单写到下方"功能:"项(无人工审阅断点;合理性由整体 MR 时统一校核)。已有清单则直接加载。整个前端阶段 1 个 MR,分支 `frontend-phase`。) | |
| 71 | + | |
| 72 | +- 整体 MR: — | |
| 73 | +- 功能: | |
| 74 | + <!-- AI 进入时按以下行格式写入(每行 1 个 FE,可关联多个 REQ / 多份原型): | |
| 75 | + - [ ] FE-NN 功能名 | 关联 REQ:REQ-A, REQ-B | 关联原型:prototype/<file>.html, prototype/<other>.html | |
| 76 | + | |
| 77 | + 示例: | |
| 78 | + - [ ] FE-01 用户登录与注册 | 关联 REQ:REQ-SYS-001, REQ-SYS-002 | 关联原型:prototype/auth.html | |
| 79 | + - [ ] FE-02 仪表盘总览 | 关联 REQ:REQ-DASH-001 | 关联原型:prototype/dashboard.html | |
| 80 | + --> | ... | ... |
docs/09-项目目录结构.md
0 → 100644
| 1 | +++ a/docs/09-项目目录结构.md | |
| 1 | +# 09-项目目录结构 | |
| 2 | + | |
| 3 | +## 一、仓库顶层 | |
| 4 | + | |
| 5 | +``` | |
| 6 | +. | |
| 7 | +├── CLAUDE.md # 项目级规范与流程指令 | |
| 8 | +├── README.md # 项目说明(可选) | |
| 9 | +├── .env.local # 本地凭据(不入 git) | |
| 10 | +├── .gitignore | |
| 11 | +├── .githooks/ | |
| 12 | +│ └── pre-push # 推送前自动跑 scripts/test.sh | |
| 13 | +├── scripts/ | |
| 14 | +│ ├── test.sh # 后端 + 前端测试组合入口 | |
| 15 | +│ └── setup-test-db.sh # 重置本地测试数据库 | |
| 16 | +├── sql/ | |
| 17 | +│ └── migrations/ # Flyway V*__*.sql 文件 | |
| 18 | +├── docs/ # 全量项目文档(见 § 四) | |
| 19 | +├── prototype/ # 静态 HTML mockup(前端实现权威) | |
| 20 | +├── backend/ # 后端工程(Spring Boot + Maven) | |
| 21 | +└── frontend/ # 前端工程(Vite + React) | |
| 22 | +``` | |
| 23 | + | |
| 24 | +## 二、后端目录 | |
| 25 | + | |
| 26 | +``` | |
| 27 | +backend/ | |
| 28 | +├── pom.xml | |
| 29 | +└── src/ | |
| 30 | + ├── main/ | |
| 31 | + │ ├── java/ | |
| 32 | + │ │ └── com.example.erp/ | |
| 33 | + │ │ ├── ErpApplication.java | |
| 34 | + │ │ ├── common/ # 全局响应 / 异常 / 拦截器 / 工具 | |
| 35 | + │ │ │ ├── result/ | |
| 36 | + │ │ │ ├── exception/ | |
| 37 | + │ │ │ ├── interceptor/ | |
| 38 | + │ │ │ └── util/ | |
| 39 | + │ │ ├── config/ # Spring 配置类(Security / Redis / MyBatis-Plus / Swagger / Activiti) | |
| 40 | + │ │ ├── module/ # 业务模块(按 docs/01 索引拆分) | |
| 41 | + │ │ │ └── usr/ # USR 用户管理 | |
| 42 | + │ │ │ ├── controller/ | |
| 43 | + │ │ │ ├── service/ | |
| 44 | + │ │ │ │ └── impl/ | |
| 45 | + │ │ │ ├── mapper/ | |
| 46 | + │ │ │ ├── entity/ | |
| 47 | + │ │ │ ├── dto/ | |
| 48 | + │ │ │ └── vo/ | |
| 49 | + │ │ └── security/ # 认证 / 鉴权 / JWT | |
| 50 | + │ └── resources/ | |
| 51 | + │ ├── application.yml | |
| 52 | + │ ├── application-dev.yml | |
| 53 | + │ ├── application-prod.yml | |
| 54 | + │ ├── mapper/ # MyBatis XML(按模块再分子目录) | |
| 55 | + │ └── logback-spring.xml | |
| 56 | + └── test/ | |
| 57 | + └── java/ | |
| 58 | + └── com.example.erp/ | |
| 59 | + └── module/ | |
| 60 | + └── usr/ # 与 main/ 镜像 | |
| 61 | +``` | |
| 62 | + | |
| 63 | +## 三、前端目录 | |
| 64 | + | |
| 65 | +``` | |
| 66 | +frontend/ | |
| 67 | +├── package.json | |
| 68 | +├── vite.config.ts | |
| 69 | +├── tsconfig.json | |
| 70 | +├── index.html | |
| 71 | +└── src/ | |
| 72 | + ├── main.tsx # 入口(挂载 React + Router + Store + ConfigProvider) | |
| 73 | + ├── App.tsx # 顶层路由 + 全局 Layout | |
| 74 | + ├── api/ # Axios 实例 + 各模块 API(数据访问统一入口) | |
| 75 | + │ └── usr.ts # USR 用户管理 API | |
| 76 | + ├── components/ # 跨页面通用组件(AuthButton / AppTable / PageHeader 等) | |
| 77 | + ├── pages/ # 按业务模块组织页面 | |
| 78 | + │ └── usr/ # USR 用户管理 | |
| 79 | + │ ├── UserList.tsx | |
| 80 | + │ ├── UserEdit.tsx | |
| 81 | + │ └── Login.tsx | |
| 82 | + ├── store/ # Redux Toolkit slices(全局状态) | |
| 83 | + │ └── slices/ | |
| 84 | + ├── hooks/ # 自定义 hook(useAuth / usePagination 等) | |
| 85 | + ├── utils/ # 工具函数(formatDate / formatMoney / regex 等) | |
| 86 | + ├── styles/ | |
| 87 | + │ ├── tokens.css # Design Tokens(docs/06 § 二) | |
| 88 | + │ └── global.css # 全局样式 reset / typography | |
| 89 | + ├── router/ # 路由表(含 meta.code 权限码) | |
| 90 | + └── assets/ # 静态资源(图片 / 字体) | |
| 91 | +``` | |
| 92 | + | |
| 93 | +> 注:仓库根 `src/styles/tokens.css` 由 skeleton-gen 创建,作为 Design Tokens 的「上游」源;前端工程化前可先保留在根 `src/styles/` 下,前端工程初始化时迁入 `frontend/src/styles/`(同名同内容)。 | |
| 94 | + | |
| 95 | +## 四、docs/ 结构 | |
| 96 | + | |
| 97 | +``` | |
| 98 | +docs/ | |
| 99 | +├── 01-需求清单/ # 每模块一子目录(_module.md 模块头 + REQ-*.md 卡片) | |
| 100 | +├── 02-开发计划.md | |
| 101 | +├── 03-数据库设计文档.md | |
| 102 | +├── 04-技术规范.md | |
| 103 | +├── 05-API接口契约.md | |
| 104 | +├── 06-UI交互规范.md | |
| 105 | +├── 07-环境配置.md | |
| 106 | +├── 08-模块任务管理.md | |
| 107 | +├── 09-项目目录结构.md | |
| 108 | +├── 10-验收检查清单.md | |
| 109 | +└── superpowers/ # CC 运行时产物 | |
| 110 | +``` | |
| 111 | + | |
| 112 | +## 五、命名与放置约定 | |
| 113 | + | |
| 114 | +- **后端根包**:`com.example.erp`,下文统称 `<ROOT>`;新增业务模块时落到 `<ROOT>.module.<模块代码小写>`(如 `<ROOT>.module.usr`)。 | |
| 115 | +- **Controller**:`<ROOT>.module.<m>.controller.<Module>Controller`,文件名首字母大写驼峰,URI 前缀 `/api/<模块代码小写>`。 | |
| 116 | +- **Service**:接口 `<Module>Service` + 实现 `<Module>ServiceImpl`,实现类放 `service/impl/`。 | |
| 117 | +- **Mapper**:`<Module>Mapper`(Java 接口)+ `resources/mapper/<m>/<Module>Mapper.xml`(XML 同名)。 | |
| 118 | +- **Entity / DTO / VO**: | |
| 119 | + - `entity/` 数据库实体(与表 1:1,字段类型同 docs/03) | |
| 120 | + - `dto/` 入参(前端 → 后端的请求体) | |
| 121 | + - `vo/` 出参(后端 → 前端的响应体) | |
| 122 | +- **前端组件**:通用组件放 `frontend/src/components/`,文件名首字母大写驼峰(`AuthButton.tsx`);样式同名 `.module.css`(启用 CSS Modules)。 | |
| 123 | +- **前端页面**:放 `frontend/src/pages/<模块代码小写>/`,文件名按业务功能命名(`UserList.tsx` / `UserEdit.tsx`),路由 path 前缀 `/<模块代码小写>`。 | |
| 124 | +- **API 客户端**:每个模块一个文件 `frontend/src/api/<模块代码小写>.ts`,导出该模块所有接口函数;禁止在组件里直接 `axios.xxx`。 | ... | ... |
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 && ./mvnw -B -DskipTests package); else echo "[test.sh] skip backend build"; fi | |
| 24 | +if [ $HAS_FRONTEND -eq 1 ]; then (cd frontend && pnpm install --frozen-lockfile && pnpm 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 && ./mvnw -B -q -P lint verify -DskipTests || ./mvnw -B -q checkstyle:check spotbugs:check || :); else echo "[test.sh] skip backend lint"; fi | |
| 28 | +if [ $HAS_FRONTEND -eq 1 ]; then (cd frontend && pnpm 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 && ./mvnw -B test); else echo "[test.sh] skip backend test"; fi | |
| 32 | +if [ $HAS_FRONTEND -eq 1 ]; then (cd frontend && pnpm test -- --run); else echo "[test.sh] skip frontend test"; fi | |
| 33 | + | |
| 34 | +echo "[test.sh] 5/6 E2E" | |
| 35 | +if [ $HAS_FRONTEND -eq 1 ] && [ -f frontend/playwright.config.ts ]; then (cd frontend && pnpm exec playwright test); else echo "[test.sh] e2e 略"; fi | |
| 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-14T01:37:50Z | |
| 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 | +-- =========================================================================== | |
| 9 | +-- t_user — 系统用户主表,承载登录认证与基础属性 | |
| 10 | +-- =========================================================================== | |
| 11 | +CREATE TABLE `t_user` ( | |
| 12 | + `iIncrement` INT NOT NULL AUTO_INCREMENT COMMENT '整数主键 ID(标准列)', | |
| 13 | + `sId` VARCHAR(100) NULL DEFAULT (UUID()) COMMENT '业务 ID(标准列)', | |
| 14 | + `sBrandsId` VARCHAR(100) NULL DEFAULT '1111111111' COMMENT '品牌 ID(多租户隔离,标准列)', | |
| 15 | + `sSubsidiaryId` VARCHAR(100) NULL DEFAULT '1111111111' COMMENT '子公司 ID(组织层级隔离,标准列)', | |
| 16 | + `tCreateDate` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间(标准列)', | |
| 17 | + `sUserNo` VARCHAR(50) NOT NULL COMMENT '用户号;关联职员后自动同步员工号;系统内唯一', | |
| 18 | + `sUserName` VARCHAR(50) NOT NULL COMMENT '登录用户名;系统内唯一;3-50 位', | |
| 19 | + `iEmployeeId` INT NULL DEFAULT NULL COMMENT '关联职员 t_employee.iIncrement;可空(非员工账号如系统管理员)', | |
| 20 | + `sPasswordHash` VARCHAR(255) NOT NULL COMMENT '密码哈希(BCrypt / Argon2);禁止明文;初始密码 666666 哈希后存入', | |
| 21 | + `sUserType` VARCHAR(20) NOT NULL DEFAULT 'NORMAL' COMMENT '用户类型枚举:NORMAL(普通用户)/ SUPER_ADMIN(超级管理员)', | |
| 22 | + `sLanguage` VARCHAR(10) NOT NULL DEFAULT 'zh-CN' COMMENT '语言枚举:zh-CN(中文)/ en-US(英文)/ zh-TW(繁体)', | |
| 23 | + `bModifyDoc` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '单据修改权限:0 否 / 1 是', | |
| 24 | + `bVoid` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '作废标记(软删除):0 启用 / 1 已作废', | |
| 25 | + `iLoginFailCount` INT NOT NULL DEFAULT 0 COMMENT '连续登录失败次数;达到阈值触发临时锁定;登录成功后清零', | |
| 26 | + `tLockUntil` DATETIME NULL DEFAULT NULL COMMENT '锁定截止时间;NULL 表示未锁定', | |
| 27 | + `tLastLoginDate` DATETIME NULL DEFAULT NULL COMMENT '最近一次登录时间', | |
| 28 | + `sCreator` VARCHAR(100) NULL DEFAULT NULL COMMENT '制单人(创建该账号的操作员用户名)', | |
| 29 | + PRIMARY KEY (`iIncrement`), | |
| 30 | + UNIQUE KEY `uk_user_username` (`sUserName`), | |
| 31 | + UNIQUE KEY `uk_user_userno` (`sUserNo`), | |
| 32 | + KEY `idx_user_employee` (`iEmployeeId`), | |
| 33 | + KEY `idx_user_tenant` (`sBrandsId`, `sSubsidiaryId`), | |
| 34 | + KEY `idx_user_void` (`bVoid`) | |
| 35 | +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='系统用户主表,承载登录认证与基础属性'; | |
| 36 | + | |
| 37 | +-- =========================================================================== | |
| 38 | +-- t_employee — 公司职员主档 | |
| 39 | +-- =========================================================================== | |
| 40 | +CREATE TABLE `t_employee` ( | |
| 41 | + `iIncrement` INT NOT NULL AUTO_INCREMENT COMMENT '整数主键 ID(标准列)', | |
| 42 | + `sId` VARCHAR(100) NULL DEFAULT (UUID()) COMMENT '业务 ID(标准列)', | |
| 43 | + `sBrandsId` VARCHAR(100) NULL DEFAULT '1111111111' COMMENT '品牌 ID(多租户隔离,标准列)', | |
| 44 | + `sSubsidiaryId` VARCHAR(100) NULL DEFAULT '1111111111' COMMENT '子公司 ID(组织层级隔离,标准列)', | |
| 45 | + `tCreateDate` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间(标准列)', | |
| 46 | + `sEmployeeNo` VARCHAR(50) NOT NULL COMMENT '员工号;系统内唯一', | |
| 47 | + `sName` VARCHAR(100) NOT NULL COMMENT '姓名', | |
| 48 | + `iDepartmentId` INT NULL DEFAULT NULL COMMENT '部门 ID,关联 t_department.iIncrement', | |
| 49 | + `sPhone` VARCHAR(20) NULL DEFAULT NULL COMMENT '手机号', | |
| 50 | + `sEmail` VARCHAR(100) NULL DEFAULT NULL COMMENT '邮箱', | |
| 51 | + `bDisabled` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否离职:0 在职 / 1 离职', | |
| 52 | + PRIMARY KEY (`iIncrement`), | |
| 53 | + UNIQUE KEY `uk_employee_no` (`sEmployeeNo`), | |
| 54 | + KEY `idx_employee_dept` (`iDepartmentId`), | |
| 55 | + KEY `idx_employee_name` (`sName`), | |
| 56 | + KEY `idx_employee_tenant` (`sBrandsId`, `sSubsidiaryId`) | |
| 57 | +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='公司职员主档'; | |
| 58 | + | |
| 59 | +-- =========================================================================== | |
| 60 | +-- t_department — 部门组织树 | |
| 61 | +-- =========================================================================== | |
| 62 | +CREATE TABLE `t_department` ( | |
| 63 | + `iIncrement` INT NOT NULL AUTO_INCREMENT COMMENT '整数主键 ID(标准列)', | |
| 64 | + `sId` VARCHAR(100) NULL DEFAULT (UUID()) COMMENT '业务 ID(标准列)', | |
| 65 | + `sBrandsId` VARCHAR(100) NULL DEFAULT '1111111111' COMMENT '品牌 ID(多租户隔离,标准列)', | |
| 66 | + `sSubsidiaryId` VARCHAR(100) NULL DEFAULT '1111111111' COMMENT '子公司 ID(组织层级隔离,标准列)', | |
| 67 | + `tCreateDate` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间(标准列)', | |
| 68 | + `sName` VARCHAR(100) NOT NULL COMMENT '部门名称', | |
| 69 | + `sCode` VARCHAR(50) NOT NULL COMMENT '部门编码;系统内唯一', | |
| 70 | + `iParentId` INT NULL DEFAULT NULL COMMENT '上级部门 ID,NULL 表示根部门', | |
| 71 | + `iSortOrder` INT NOT NULL DEFAULT 0 COMMENT '排序值,小者靠前', | |
| 72 | + PRIMARY KEY (`iIncrement`), | |
| 73 | + UNIQUE KEY `uk_department_code` (`sCode`), | |
| 74 | + KEY `idx_department_parent` (`iParentId`), | |
| 75 | + KEY `idx_department_tenant` (`sBrandsId`, `sSubsidiaryId`) | |
| 76 | +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='部门组织树'; | |
| 77 | + | |
| 78 | +-- =========================================================================== | |
| 79 | +-- t_permission — 权限分类字典 | |
| 80 | +-- =========================================================================== | |
| 81 | +CREATE TABLE `t_permission` ( | |
| 82 | + `iIncrement` INT NOT NULL AUTO_INCREMENT COMMENT '整数主键 ID(标准列)', | |
| 83 | + `sId` VARCHAR(100) NULL DEFAULT (UUID()) COMMENT '业务 ID(标准列)', | |
| 84 | + `sBrandsId` VARCHAR(100) NULL DEFAULT '1111111111' COMMENT '品牌 ID(多租户隔离,标准列)', | |
| 85 | + `sSubsidiaryId` VARCHAR(100) NULL DEFAULT '1111111111' COMMENT '子公司 ID(组织层级隔离,标准列)', | |
| 86 | + `tCreateDate` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间(标准列)', | |
| 87 | + `sCode` VARCHAR(50) NOT NULL COMMENT '权限码,例如 USR:ADD / USR:EDIT;系统内唯一', | |
| 88 | + `sName` VARCHAR(100) NOT NULL COMMENT '权限分类名称(展示用)', | |
| 89 | + `iSortOrder` INT NOT NULL DEFAULT 0 COMMENT '同分类内排序', | |
| 90 | + PRIMARY KEY (`iIncrement`), | |
| 91 | + UNIQUE KEY `uk_permission_code` (`sCode`) | |
| 92 | +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='权限分类字典'; | |
| 93 | + | |
| 94 | +-- =========================================================================== | |
| 95 | +-- t_user_permission — 用户-权限分类关联表 | |
| 96 | +-- =========================================================================== | |
| 97 | +CREATE TABLE `t_user_permission` ( | |
| 98 | + `iIncrement` INT NOT NULL AUTO_INCREMENT COMMENT '整数主键 ID(标准列)', | |
| 99 | + `sId` VARCHAR(100) NULL DEFAULT (UUID()) COMMENT '业务 ID(标准列)', | |
| 100 | + `sBrandsId` VARCHAR(100) NULL DEFAULT '1111111111' COMMENT '品牌 ID(多租户隔离,标准列)', | |
| 101 | + `sSubsidiaryId` VARCHAR(100) NULL DEFAULT '1111111111' COMMENT '子公司 ID(组织层级隔离,标准列)', | |
| 102 | + `tCreateDate` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间(标准列)', | |
| 103 | + `iUserId` INT NOT NULL COMMENT '用户 ID,关联 t_user.iIncrement', | |
| 104 | + `iPermissionId` INT NOT NULL COMMENT '权限分类 ID,关联 t_permission.iIncrement', | |
| 105 | + PRIMARY KEY (`iIncrement`), | |
| 106 | + UNIQUE KEY `uk_user_perm` (`iUserId`, `iPermissionId`), | |
| 107 | + KEY `idx_user_perm_perm` (`iPermissionId`) | |
| 108 | +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户-权限分类关联表'; | |
| 109 | + | |
| 110 | +-- =========================================================================== | |
| 111 | +-- t_company — 公司 / 版本字典 | |
| 112 | +-- =========================================================================== | |
| 113 | +CREATE TABLE `t_company` ( | |
| 114 | + `iIncrement` INT NOT NULL AUTO_INCREMENT COMMENT '整数主键 ID(标准列)', | |
| 115 | + `sId` VARCHAR(100) NULL DEFAULT (UUID()) COMMENT '业务 ID(标准列)', | |
| 116 | + `sBrandsId` VARCHAR(100) NULL DEFAULT '1111111111' COMMENT '品牌 ID(多租户隔离,标准列)', | |
| 117 | + `sSubsidiaryId` VARCHAR(100) NULL DEFAULT '1111111111' COMMENT '子公司 ID(组织层级隔离,标准列)', | |
| 118 | + `tCreateDate` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间(标准列)', | |
| 119 | + `sCode` VARCHAR(50) NOT NULL COMMENT '公司 / 版本编码;系统内唯一', | |
| 120 | + `sName` VARCHAR(100) NOT NULL COMMENT '显示名称', | |
| 121 | + PRIMARY KEY (`iIncrement`), | |
| 122 | + UNIQUE KEY `uk_company_code` (`sCode`) | |
| 123 | +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='公司 / 版本字典'; | |
| 124 | + | |
| 125 | +-- =========================================================================== | |
| 126 | +-- 外键约束(统一在最后追加,避免建表顺序依赖) | |
| 127 | +-- =========================================================================== | |
| 128 | +ALTER TABLE `t_user` | |
| 129 | + ADD CONSTRAINT `fk_user_employee` FOREIGN KEY (`iEmployeeId`) REFERENCES `t_employee` (`iIncrement`) ON DELETE SET NULL ON UPDATE RESTRICT; | |
| 130 | + | |
| 131 | +ALTER TABLE `t_employee` | |
| 132 | + ADD CONSTRAINT `fk_employee_department` FOREIGN KEY (`iDepartmentId`) REFERENCES `t_department` (`iIncrement`) ON DELETE SET NULL ON UPDATE RESTRICT; | |
| 133 | + | |
| 134 | +ALTER TABLE `t_department` | |
| 135 | + ADD CONSTRAINT `fk_department_parent` FOREIGN KEY (`iParentId`) REFERENCES `t_department` (`iIncrement`) ON DELETE RESTRICT ON UPDATE RESTRICT; | |
| 136 | + | |
| 137 | +ALTER TABLE `t_user_permission` | |
| 138 | + ADD CONSTRAINT `fk_userperm_user` FOREIGN KEY (`iUserId`) REFERENCES `t_user` (`iIncrement`) ON DELETE CASCADE ON UPDATE RESTRICT; | |
| 139 | + | |
| 140 | +ALTER TABLE `t_user_permission` | |
| 141 | + ADD CONSTRAINT `fk_userperm_perm` FOREIGN KEY (`iPermissionId`) REFERENCES `t_permission` (`iIncrement`) ON DELETE RESTRICT ON UPDATE RESTRICT; | ... | ... |
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 | +} | ... | ... |