diff --git b/.githooks/pre-push a/.githooks/pre-push new file mode 100755 index 0000000..8289050 --- /dev/null +++ a/.githooks/pre-push @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +# .githooks/pre-push — run scripts/test.sh before every push. +# No --no-verify: the claude-code hook deny-no-verify.sh also blocks it. + +set -euo pipefail + +cd "$(git rev-parse --show-toplevel)" + +./scripts/test.sh diff --git b/.gitignore a/.gitignore new file mode 100644 index 0000000..ac39aa6 --- /dev/null +++ a/.gitignore @@ -0,0 +1,32 @@ +# ==== ERP 插件推荐忽略项(skeleton-gen 追加) ==== +# 本地运行时配置(含真实凭据,严禁入库) +.env.local +.env.*.local + +# Java / Maven +target/ +*.class + +# Node / 前端构建产物 +node_modules/ +dist/ +build/ +coverage/ + +# IDE +.idea/ +.vscode/ +*.iml + +# OS +.DS_Store +Thumbs.db + +# 日志 +*.log +logs/ + +# 插件运行时临时文件 +.tmp/ +*.raw +# ==== 结束 ==== diff --git b/CLAUDE.md a/CLAUDE.md new file mode 100644 index 0000000..cbd9440 --- /dev/null +++ a/CLAUDE.md @@ -0,0 +1,261 @@ +# CLAUDE.md — ERP项目 Claude Code 主指令文件 + +> 本文件是 Claude Code 的"操作手册"。Claude Code 启动时会自动读取此文件。 + +--- + +## 🎯 项目概述 + +- **项目名称**: 小羚羊 +- **项目简述**: 测试ERP +- **目标用户**: 企业内部管理人员 +- **部署方式**: 私有化部署 + +--- + +## 🔄 B 阶段开发流程(后端模块循环 → 前端整体阶段) + +B 阶段分两段,**全部固化到 skills**。入口:`/erp-workflow:coding-start`。 + +### 阶段路由(coding-start 内,只做分发) + +`coding-start` 每次入口做两段完成性检查后真值表派发: + +- 后端完成性检查 → `backend_done`(扫 docs/08 § 二 + GitLab state) +- 前端完成性检查 → `frontend_done`(扫 docs/08 § 三 整体 MR + state) + +| `backend_done` | `frontend_done` | 派发 | +|---|---|---| +| `false` | 任意 | `module-start`(写后端) | +| `true` | `false` | `frontend-start`(写前端) | +| `true` | `true` | "所有阶段已完成" | + +前端阶段前置(prototype/ 门禁)由 `frontend-start` 自带,不在 coding-start。`module-start` 与 `frontend-start` **互不感知对方**。 + +### 后端阶段(每模块一个 MR) + +- **模块循环(外)**:`module-start` → `test-gate(phase=backend)` → `module-report` → `mr-create` → 人工 Approve MR → 用户重跑 coding-start → coding-start 再次路由 +- **功能循环(内,每 REQ-XXX-NNN 一遍)**:`feature-brainstorm` → `feature-plan` → `feature-tdd` → `feature-verify` → `feature-review` +- 后端阶段任务严格落在 `backend/` 路径下;docs/01 REQ 卡片的 UI 描述在此阶段忽略,UI 推迟到前端阶段。 + +### 前端阶段(整体一个 MR,所有后端模块 merged 后启动) + +- **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 处介入)。 +- **FE 循环(外)**:`frontend-start` → fe-feature 循环 → `test-gate(phase=frontend)` → `module-report(phase=frontend)` → `mr-create`(分支 `frontend-phase`,docs/08 § 三 整体 MR)。 +- **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) +- 前端阶段任务严格落在 `frontend/` 路径下;布局以 `prototype/` 为权威。 + +### MR 前测试闸门 + +- `test-gate`:后端阶段子会话跑 `scripts/test.sh`(含本模块新增 + 已合并模块回归);前端阶段子会话跑 vitest + playwright。 +- `.githooks/pre-push` 兜底;`git push --no-verify` 被 `deny-no-verify.sh` 硬拦。 + +--- + +## ✅ 阶段完成判定规则 + +`docs/08-模块任务管理.md` 分两段: +- `§ 二`:后端模块元数据表(每个模块一行 bullet,记录依赖 / 路径 / MR iid / 功能子项) +- `§ 三`:前端阶段元数据(整体 MR + FE 子项清单,由 `frontend-start` 在所有后端模块 merged 后填入) + +**阶段完成判定**统一以 `MR:` 字段(§ 二 各模块) / `整体 MR:` 字段(§ 三)+ `GitLab API state=merged` 判定;子项勾选只作可视化进度,不参与完成判定。 + +### 后端模块格式 + +每个后端模块在 docs/08 § 二 中长这样: + +```markdown +- module_0 系统管理 + - 依赖: — + - 路径: backend/module/sys/ + - MR: — + - 功能: + - [ ] REQ-SYS-001 用户登录 + - [ ] REQ-SYS-002 用户注册 +``` + +- `MR:` 字段由 `mr-create` 在创建 MR 时从 `—` 改为 `!`。 +- 每个 `REQ-*` 子项由 `feature-review` 在 `verdict=approve` 时自动勾选为 `[x]`。 +- 路径限定为后端目录(如 `backend/module/sys/`);前端代码不在此阶段产生。 + +### 前端阶段格式(§ 三) + +```markdown +- 整体 MR: — +- 功能: + - [ ] FE-01 用户登录与注册 | 关联 REQ:REQ-SYS-001, REQ-SYS-002 | 关联原型:prototype/auth.html + - [ ] FE-02 仪表盘总览 | 关联 REQ:REQ-DASH-001 | 关联原型:prototype/dashboard.html +``` + +- `整体 MR:` 字段由 `mr-create` 在创建前端 MR 时从 `—` 改为 `!`。 +- "功能:" 列表由 `frontend-start` 进入时由 AI 自主推导写入(无人工审阅断点)。FE 是业务功能粒度,与 prototype HTML 文件数无关;合理性由 fe-feature-review / 整体 MR 时统一校核。 +- 每个 `FE-NN` 子项由 `fe-feature-review` 在 `verdict=approve` 时自动勾选为 `[x]`。 +- 进入前端阶段前 `frontend-start` 步骤 1 自带 prototype/ 门禁,强制检查项目根 `prototype/` 至少含 1 个 `*.html` mockup。 + +### 状态语义(后端模块 + 前端阶段共用) + +| `MR:` 字段 | `GitLab API state` | 含义 | 你(Claude Code)的行为 | +|---|---|---|---| +| `—` | — | 该阶段未开始(未创建 MR) | ✅ 开始该阶段开发 | +| `!` | `opened` / `closed` | 阶段开发中 / 打回 | ✅ 继续推进该阶段 | +| `!` | `merged` | 阶段**已完成** | 🟢 后端:进入下一未完成模块;后端全完 → 前端阶段;前端 merged → 全部完成 | + +### 模块完成报告 + +由 `module-report` skill 产出,模板位于 由 module-report skill 持有(12 节标准化,含跨模块改动等 CLAUDE.md 软规则映射节)。CC 不手写模块报告,仅填模板。 + +--- + +## 🏷️ 占位符统一约定 + +项目文档里有 **2 种填写占位** + **1 种提示占位**: + +| 格式 | 谁填 | 使用阶段 | 说明 | +|------|-----|---------|------| +| `【人工填写:<简短说明>】` | 人 | 仅 A 阶段文档 | 密钥 / 账密 / 包名 / 命名约定 / 小版本号等人工才能决定的值;B 阶段 plan/spec 禁止出现,查不到真值时用 `AskUserQuestion` 问用户 | +| `TBD(<责任人>)` | CC 自动 | A 或 B | 后缀附带责任方(如 `TBD(A3 自动补)` / `TBD(A5 自动补)`);由对应 skill 就地补填,`module-report` § ⑦ 检查 `TBD(CC 补)` 残留 | + +**HTML 注释 ``**:提示占位,是**插件内部大纲模板**里给 LLM 的**填空提示 / 章节引导**,指引 LLM 按结构填实际内容。skill 生成时会**剥除**这些注释,最终产物里注释不会保留。 + +--- + +## 📐 编码行为约束 + +### 你必须做的 ✅ + +1. **严格遵循** `docs/04-技术规范.md`——命名 / 编码 / 统一响应 / 异常处理 / 数据访问 / 配置与安全 等项目专属技术规约全部在此 +2. **严格遵循** `docs/09-项目目录结构.md`——文件放对位置 +3. **每个后端接口** 必须先在 `docs/05-API接口契约.md` 定义,再编码实现 +4. **每个功能可追溯到 `REQ-XXX-NNN`**——commit tag + 代码注释(如 `// REQ-SYS-001: 用户登录`)+ plan/spec 文件名均用此 tag +5. **遇到跨模块改动**(动到非当前模块的代码)——按 § 🟡 软规则 **S2** 执行(允许改,但必须留痕) +6. **遇到技术栈外组件引入**(`docs/04 § 零` 技术栈表外的框架 / 中间件 / 关键库),按 § 🟡 软规则 **S1** 执行(允许引入,但必须先 AskUserQuestion) + +### 你禁止做的 🚫 + +1. **主会话直接 `mysql -e` 跑业务 DDL**(只读查询 / 临时本地调试除外)——业务 schema 必须走 `sql/migrations/V_n__*.sql`,详见下方 Schema 演化规约 +2. **手动 Edit `docs/08 § 二/§ 三` 的 `MR:` / `整体 MR:` 字段**,必须要由 `mr-create` 自动回写 + +### Schema 演化规约(Flyway migration) + +1. **文件命名**:`sql/migrations/V__.sql`,例:`V5__add_user_email_unique_index.sql` +2. **版本号分配**:建文件前 `ls sql/migrations/V*.sql` 查当前最大 n,新文件 `n_max + 1` +3. **Apply 方式**:Spring Boot 启动 / 测试启动时 Flyway 自动 apply(项目必须在 `pom.xml` 声明 `flyway-core` + `flyway-mysql` 依赖)。`scripts/setup-test-db.sh` 只负责清空库,不做 apply +4. **已合并的 migration 永不修改**:发现错了写一个补救 migration(如 `V7__fix_V5_index_name.sql`),旧 `V_n.sql` +5. **临时调试 DDL**:临时在本地试字段/索引可手动 `mysql -e`,但不写 migration;下次 `setup-test-db.sh` 会 drop+create 清掉 +6. **A4 生成的 V1**:`V1__initial_schema.sql` 是 A 阶段由 `db-init` 从 `docs/03-数据库设计文档.md`(A3 正向设计的 schema SSoT)翻译生成的初始版本;后续 V2/V3/... 由 B 阶段每个 REQ 按需写入,**同时**反向同步更新 docs/03 对应表小节以保持 SSoT 一致 + +--- + +## 🗂️ Git 提交规范 + +每次提交必须遵循以下格式: + +``` +(): +``` + +- `scope`: 模块名,如 `user` / `inventory` / `order` +- `subject`: 简短描述;业务类(feat / fix / test)必须带 `REQ-XXX-NNN` 后缀 + +`type` 含义: + +| type | 看到它意味着 | +|-----|-------------| +| `feat` | **新能力上线**——用户多了一个功能、接口、页面或业务规则 | +| `fix` | **修 bug**——原来行为错了,这次改对 | +| `refactor` | **重构**——外部行为不变,只改代码结构 / 命名 / 抽象 | +| `docs` | **文档改动**——只动 Markdown / 代码注释,不动实现 | +| `style` | **格式调整**——空白 / 缩进 / import 顺序,逻辑 0 变化 | +| `test` | **只动测试代码**——补用例 / 修 fixture,不碰实现 | +| `chore` | **流程维护**——构建 / 依赖 / 工具 / 证据档案 / MR 元数据等非业务动作 | + +--- + +## 🚩 中断机制 + +功能循环(每个功能 REQ-XXX 的 Brainstorm → Plan → TDD → Verify → AI 自审)默认 **静默编程**,但触发以下任何一条必须**立刻停下、记录原因、等人决策**,不得自行绕过: + +| # | 中断 | 例子 | +| - | --- | --- | +| 1 | **测试反复失败** | 同一测试同一功能内连续 **10 次**修复失败 | +| 2 | **要改密钥 / 账密 / 包名** | `docs/07-环境配置.md` 里由人工标注必须填的字段 | +| 3 | **外部接口不可达** | 第三方 API 无法连接、证书失效等环境问题,并无法自行解决 | + +> 其余需要人类判断的场景一律走普通 `AskUserQuestion` Q&A,不中断、不写 Blocker 文件。 + +**触发中断时的固定动作:** + +1. 在当前功能的 plan 文件里追加一节 `## 🚩 Blocker`(报告格式由 `interrupt-check` 的 `interrupt-block-template.md` 持有) +2. 停止后续所有功能的静默执行 +3. 在主会话输出一句话摘要 + 指向 blocker 文件的路径,等人回复 + +--- + +## 🟡 软规则(允许继续,但有强制后续动作) + +以下情况 **不触发中断**,CC 可自行继续推进,但必须在约定位置留痕,模块完成时统一审计。 + +| # | 软规则 | 允许动作 | 强制后续 | +| - | ----- | ------- | ------- | +| S1 | **技术栈外组件引入** | 用 `AskUserQuestion` 给用户三选一:接受引入 / 换方案 / 拒绝 | ① **接受** → 同会话直接在 `docs/04 § 零` 追加一行 → 继续流程 ② **换方案 / 拒绝** → 视为常规歧义澄清,继续 Q&A 收敛 ③ 不写 Blocker、不中断流程 | +| S2 | **跨模块改动** | **默认不改**,仅为当前模块实现所必需时允许修改 | ① hook `log-cross-module.sh` 自动落存根 ② `module-report` 一次性调用 `cross-module-log` skill 批量补齐「原因 / 影响评估」+ 「跨模块改动」节完整贴入《模块完成报告》 | + +--- + +## 🧭 通用工作准则(General Principles) + +### 1. Think Before Coding + +**Don't assume. Don't hide confusion. Surface tradeoffs.** + +Before implementing: +- State your assumptions explicitly. If uncertain, ask. +- If multiple interpretations exist, present them - don't pick silently. +- If a simpler approach exists, say so. Push back when warranted. +- If something is unclear, stop. Name what's confusing. Ask. + +### 2. Simplicity First + +**Minimum code that solves the problem. Nothing speculative.** + +- No features beyond what was asked. +- No abstractions for single-use code. +- No "flexibility" or "configurability" that wasn't requested. +- No error handling for impossible scenarios. +- If you write 200 lines and it could be 50, rewrite it. + +Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify. + +### 3. Surgical Changes + +**Touch only what you must. Clean up only your own mess.** + +When editing existing code: +- Don't "improve" adjacent code, comments, or formatting. +- Don't refactor things that aren't broken. +- Match existing style, even if you'd do it differently. +- If you notice unrelated dead code, mention it - don't delete it. + +When your changes create orphans: +- Remove imports/variables/functions that YOUR changes made unused. +- Don't remove pre-existing dead code unless asked. + +The test: Every changed line should trace directly to the user's request. + +### 4. Goal-Driven Execution + +**Define success criteria. Loop until verified.** + +Transform tasks into verifiable goals: +- "Add validation" → "Write tests for invalid inputs, then make them pass" +- "Fix the bug" → "Write a test that reproduces it, then make it pass" +- "Refactor X" → "Ensure tests pass before and after" + +For multi-step tasks, state a brief plan: +``` +1. [Step] → verify: [check] +2. [Step] → verify: [check] +3. [Step] → verify: [check] +``` + +Strong success criteria let you loop independently. Weak criteria ("make it work") require constant clarification. diff --git b/docs/01-需求清单/USR-用户管理/REQ-USR-001.md a/docs/01-需求清单/USR-用户管理/REQ-USR-001.md new file mode 100644 index 0000000..efd0fec --- /dev/null +++ a/docs/01-需求清单/USR-用户管理/REQ-USR-001.md @@ -0,0 +1,22 @@ +### REQ-USR-001 用户登录 + + +**目标**: 用户通过用户名+密码完成身份认证,获取 JWT Token 用于后续接口鉴权 + +- **输入**: + + - **表1**: + + | 字段 | 类型 | 必填 | 输入方式 | 显示来源 | 预加载 | 默认值 | 业务规则 | + | --- | ---- | --- | ---- | ------- | ----- | --- | ----------- | + | 用户名 | 文本 | 是 | 手工输入 | — | — | — | — | + | 密码 | 文本 | 是 | 手工输入 | — | — | — | 输入显示星号 | + | 版本 | 文本 | 是 | 下拉单选 | `公司表` | 页面加载时 | 标准版 | | + +- **输出**: 成功/失败 + +- **跨字段规则**: 校验用户名 + 密码哈希;连续失败达到阈值临时锁定账号;登录成功签发限时 token 并返回基本用户信息 +- **边界**: token 设置合理过期时间;接口需具备防暴力破解保护 +- **验收**: 正确凭据返回 Token 且可通过鉴权接口验证;错误密码返回通用错误消息(不区分用户名或密码错误);锁定账号返回锁定提示 +- **依赖表**: `t_user`(认证主体 + 失败计数 + 锁定时间 + 登录时间)/ `t_company`(「版本」下拉数据源) +- **依赖接口**: `POST /api/usr/auth/login`(本 REQ 提供);后续配套 `POST /api/usr/auth/refresh`、`POST /api/usr/auth/logout` 由同模块衍生接口(不另立 REQ) diff --git b/docs/01-需求清单/USR-用户管理/REQ-USR-002.md a/docs/01-需求清单/USR-用户管理/REQ-USR-002.md new file mode 100644 index 0000000..41fed2e --- /dev/null +++ a/docs/01-需求清单/USR-用户管理/REQ-USR-002.md @@ -0,0 +1,41 @@ +### REQ-USR-002 新增用户 + +**目标**: 用户在后台新建用户账号,指定用户名、密码及角色,账号立即生效可用 + +- **输入**: + + - **表1**: + + | 字段 | 类型 | 必填 | 输入方式 | 显示来源 | 预加载 | 默认值 | 业务规则 | + | -------- | ---- | --- | ---- | ----------------- | --------- | --------- | ------------------- | + | 创建时间 | 日期时间 | — | 系统生成 | — | 页面加载时 | 当前日期 | 保存后自动生成;只读 | + | 制单人 | 文本 | — | 系统生成 | — | 页面加载时 | 当前登录用户 | 保存后自动生成;只读 | + | 员工名 | 文本 | 否 | 下拉单选 | `职员表` | 用户操作时 | — | 关联职员(可选) | + | 用户号 | 文本 | 是 | 手工输入 | — | 用户操作时 | — | 关联职员选择后自动输入员工姓名 | + | 用户名 | 文本 | 是 | 手工输入 | — | 用户操作时 | — | 关联职员选择后自动输入员工姓名 | + | 类型 | 文本 | 是 | 下拉单选 | 普通用户/超级管理员 | 页面加载时 | 普通用户 | — | + | 语言 | 文本 | 是 | 下拉单选 | 中文/英文/繁体 | 页面加载时 | — | — | + | 单据修改权限 | 布尔 | 否 | 复选框 | — | — | 否 | — | + | 密码 | 文本 | — | 系统生成 | 不显示 | — | 666666 | 保存后自动设为初始化 | + + - **表2** - 权限组: + + | 字段 | 类型 | 必填 | 输入方式 | 显示来源 | 预加载 | 默认值 | 业务规则 | + | -------- | ---- | --- | ---- | ----------------- | --------- | --------- | ------------------- | + | 复选框 | 布尔 | 否 | 复选框 | — | — | 否 | 是否选择当前行权限 | + | 权限分类 | 文本 | — | — | — | 页面加载时 | — | — | + + +- **输出**: + + - **表1**: + + | 字段 | 类型 | 显示来源 | + | --- | --- | --- | + | 用户号 | 文本 | — | + +- **跨字段规则**: 用户名在系统内全局唯一;角色取值受系统配置约束 +- **边界**: 密码以哈希形式存储 +- **验收**: 提交合法数据后用户记录出现在列表;重复用户名返回错误提示;普通账号无权访问此功能 +- **依赖表**: `t_user`(新建主记录)/ `t_employee`(员工名下拉 + 关联)/ `t_permission`(权限分类下拉)/ `t_user_permission`(写入权限组关联) +- **依赖接口**: `POST /api/usr/users`(本 REQ 提供);前置 `POST /api/usr/auth/login`(REQ-USR-001)获取 JWT diff --git b/docs/01-需求清单/USR-用户管理/REQ-USR-003.md a/docs/01-需求清单/USR-用户管理/REQ-USR-003.md new file mode 100644 index 0000000..0ad6f89 --- /dev/null +++ a/docs/01-需求清单/USR-用户管理/REQ-USR-003.md @@ -0,0 +1,40 @@ +### REQ-USR-003 修改用户 + +**目标**: 用户可更新已有用户的基本信息(姓名、角色、状态等),修改实时生效 + +- **输入**: 选中目标 + + - **表1**: + + | 字段 | 类型 | 必填 | 输入方式 | 显示来源 | 预加载 | 默认值 | 业务规则 | + | -------- | ---- | --- | ---- | ----------------- | --------- | --------- | ------------------- | + | 创建时间 | 日期时间 | — | 系统生成 | — | 页面加载时 | 原值 | 保存后自动生成;只读 | + | 制单人 | 文本 | — | 系统生成 | — | 页面加载时 | 原值 | 保存后自动生成;只读 | + | 员工名 | 文本 | 否 | 下拉单选 | `职员表` | 页面加载时 | 原值 | 关联职员(可选) | + | 用户号 | 文本 | 是 | 手工输入 | — | 页面加载时 | 原值 | 关联职员选择后自动输入员工姓名 | + | 用户名 | 文本 | 是 | 手工输入 | — | 页面加载时 | 原值 | 关联职员选择后自动输入员工姓名 | + | 类型 | 文本 | 是 | 下拉单选 | 普通用户/超级管理员 | 页面加载时 | 原值 | — | + | 语言 | 文本 | 是 | 下拉单选 | 中文/英文/繁体 | 页面加载时 | 原值 | — | + | 单据修改权限 | 布尔 | 否 | 复选框 | — | 页面加载时 | 原值 | — | + | 密码 | 文本 | — | 系统生成 | 不显示 | 页面加载时 | 原值 | 保存后自动设为初始化 | + + - **表2** - 权限组: + + | 字段 | 类型 | 必填 | 输入方式 | 显示来源 | 预加载 | 默认值 | 业务规则 | + | -------- | ---- | --- | ---- | ----------------- | --------- | --------- | ------------------- | + | 复选框 | 布尔 | 否 | 复选框 | — | 页面加载时 | 原值 | 是否选择当前行的权限 | + | 权限分类 | 文本 | — | — | — | 页面加载时 | — | — | + +- **输出**: + + - **表1**: + + | 字段 | 类型 | 显示来源 | + | --- | --- | --- | + | 用户 id | 文本 | `职员表` | + +- **跨字段规则**: 密码不在该接口修改;角色变更需具备相应权限 +- **边界**: 必须传入有效用户 id;字段格式与新增一致 +- **验收**: 修改角色或状态后立即反映在用户列表;被禁用账号无法登录并收到明确提示 +- **依赖表**: `t_user`(更新主记录)/ `t_employee`(员工名下拉 + 关联)/ `t_permission`(权限分类下拉)/ `t_user_permission`(删旧 + 写新权限组关联) +- **依赖接口**: `PUT /api/usr/users/{iIncrement}`(本 REQ 提供);前置 `POST /api/usr/auth/login`(REQ-USR-001)+ 数据来源 `POST /api/usr/users`(REQ-USR-002) diff --git b/docs/01-需求清单/USR-用户管理/REQ-USR-004.md a/docs/01-需求清单/USR-用户管理/REQ-USR-004.md new file mode 100644 index 0000000..5bd10cb --- /dev/null +++ a/docs/01-需求清单/USR-用户管理/REQ-USR-004.md @@ -0,0 +1,37 @@ +### REQ-USR-004 查询用户 + +**目标**: 用户可按用户名、角色或状态筛选并分页浏览用户列表 + +- **输入**: + + - **表1**: + + | 字段 | 类型 | 必填 | 输入方式 | 显示来源 | 预加载 | 默认值 | 业务规则 | + | ---- | ---- | --- | ---- | ----------------------------------------------- | ----- | ------- | --------------- | + | 查询字段 | 文本 | 否 | 下拉单选 | 用户名/员工名/用户号/部门/用户类型/作废/登录日期/制单人 | 页面加载时 | 用户名 | — | + | 匹配方式 | 文本 | 否 | 下拉单选 | 包含/不包含/等于 | 页面加载时 | 包含 | — | + | 查询值 | 文本 | 否 | 手工输入 | — | — | — | 与「查询字段」配合使用,空为选择全部 | + +- **输出**: + + - **表1**: + + | 字段 | 类型 | 显示来源 | + | ---- | ---- | ----- | + | 序号 | 数字 | 系统生成 | + | 用户名 | 文本 | `用户表` | + | 员工名 | 文本 | `职员表` | + | 用户号 | 文本 | `用户表` | + | 部门 | 文本 | `职员表` | + | 用户类型 | 文本 | `用户表` | + | 语言 | 文本 | `用户表` | + | 作废 | 布尔 | `用户表` | + | 登录日期 | 日期时间 | `用户表` | + | 制单人 | 文本 | `用户表` | + | 制单日期 | 日期时间 | `用户表` | + +- **跨字段规则**: - +- **边界**: 单页最大条数受限(默认 100);密码与敏感字段不返回;查询为只读,不产生写副作用 +- **验收**: 按条件筛选返回正确结果集;无匹配时返回空列表而非报错;分页参数越界时返回最后一页 +- **依赖表**: `t_user`(列表主表 + 类型 / 作废 / 登录日期 / 制单人)/ `t_employee`(员工名)/ `t_department`(部门) +- **依赖接口**: `GET /api/usr/users`(本 REQ 提供);前置 `POST /api/usr/auth/login`(REQ-USR-001) diff --git b/docs/01-需求清单/USR-用户管理/_module.md a/docs/01-需求清单/USR-用户管理/_module.md new file mode 100644 index 0000000..a1e1c19 --- /dev/null +++ a/docs/01-需求清单/USR-用户管理/_module.md @@ -0,0 +1,5 @@ +# USR-用户管理 + +- **模块简述**: 提供用户全生命周期管理,含登录认证、新增、修改、查询等业务操作 +- **依赖模块**: —(基础模块) +- **涉及表**: `t_user` / `t_employee` / `t_department` / `t_permission` / `t_user_permission` / `t_company` diff --git b/docs/01-需求清单/index.md a/docs/01-需求清单/index.md new file mode 100644 index 0000000..3ef4cdb --- /dev/null +++ a/docs/01-需求清单/index.md @@ -0,0 +1,15 @@ +# 需求清单 + +> 本目录按模块组织所有功能需求。每个模块一个子目录,含 `_module.md`(模块头)和 `REQ-XXX-NNN.md`(每张 REQ 卡片一个文件)。下方核心功能点供 CC 拆分出 REQ 编号 + 标题 + 草拟规则;卡片内输入 / 输出的简述句和 N 张字段表由人工编辑。 + +## 模块索引 + +| 模块代码 | 模块名称 | 核心功能点(简要) | +| ---- | ---- | ------------------- | +| USR | 用户管理 | 增加用户,修改用户,查询用户,登录用户 | + +## 填写说明 + +1. 每个模块占一行,`模块代码` 用大写英文缩写(如 SYS / PUR / INV / SAL / FIN / HR) +2. `核心功能点` 只需列关键词,CC 会基于此拆分出 N 张 REQ 卡片骨架(卡片内输入 / 输出的简述句和字段表仍由人工编辑) +3. 填完后运行 `/erp-workflow:plan-start`,CC 会自动检测并进入需求生成阶段 diff --git b/docs/02-开发计划.md a/docs/02-开发计划.md new file mode 100644 index 0000000..b01308a --- /dev/null +++ a/docs/02-开发计划.md @@ -0,0 +1,28 @@ +# 02-开发计划 + +## 一、模块依赖表 + +| 模块 ID | 模块名 | 依赖模块 | 依赖表 | +|---|---|---|---| +| module_usr | USR-用户管理 | — | `t_user` / `t_employee` / `t_department` / `t_permission` / `t_user_permission` / `t_company` | + +## 二、开发顺序清单(CC 分发权威) + +> 本清单由 A5 `downstream-gen` 一次性生成。**每行是一个 REQ**,不是模块。CC 按表格行序从上到下扫描,对每个 REQ 所属模块查 `docs/08 § 二` 的 `MR:` 字段 + GitLab API `state`:`merged` 跳过,其他(`—` / opened / closed / 查不到)选为当前模块;`module-start` 会把该模块的所有 REQ 一次做完。 +> +> **约束**:同一模块的所有 REQ 必须**连续排列**。允许打破依赖拓扑(如环依赖、业务必须先做),但必须在「备注」列写明原因。 + +| # | REQ | 所属模块 | 选中理由 | 备注 | +|---|-----|---------|---------|------| +| 1 | **REQ-USR-001** | module_usr | 所属模块无依赖,认证接口为基础设施(其余 REQ 接口需 JWT 鉴权) | — | +| 2 | **REQ-USR-002** | module_usr | 依赖 REQ-USR-001 已在前;新增用户为后续修改 / 查询提供数据 | — | +| 3 | **REQ-USR-003** | module_usr | 依赖 REQ-USR-002 已在前(先有用户才能修改) | — | +| 4 | **REQ-USR-004** | module_usr | 依赖 REQ-USR-002 已在前(先有用户才能查询) | — | + +> **后端模块全部 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`)。 + +## 三、关键说明 + +- 本期需求只覆盖 USR 用户管理一个模块;后续若新增 PUR / SAL / FIN 等模块,需重新跑 `/erp-workflow:plan-start` 让 A5 重算依赖与顺序。 +- REQ-USR-001 排第一是「基础设施先行」策略:JWT 鉴权机制建好后,其余 REQ 才能复用 `@PreAuthorize` 与统一异常处理;初始管理员账号由 V1 后续的种子 migration 注入(或人工 SQL 直插)。 +- `t_employee` / `t_department` / `t_permission` / `t_company` 四张维表在 module_usr 内部按需访问,不另起模块;后续如演化出独立的 HR / 组织架构模块,再单独拆分。 diff --git b/docs/03-数据库设计文档.md a/docs/03-数据库设计文档.md new file mode 100644 index 0000000..f838262 --- /dev/null +++ a/docs/03-数据库设计文档.md @@ -0,0 +1,230 @@ +# 03-数据库设计文档 + +- **Schema**: `xlyweberp_vibe_erp_test` +- **Migration 清单**: `sql/migrations/V*.sql`(由 Flyway 顺序 apply) +- **生成方式**: 由 A3 `db-design-gen` 基于 `docs/01-需求清单//REQ-*.md` REQ 卡片正向设计生成(schema SSoT)。 + +## 项目标准列约定 + +下文每张业务表的字段清单都自动包含以下 5 个标准列(匈牙利前缀 `i` int / `s` varchar / `t` datetime)。渲染时由 `docs-03-table-template.md` 模板内置原样输出。 + +| 列名 | 类型 | 可空 | 主键 | 说明 | +|---|---|---|---|---| +| `iIncrement` | int | 否 | 是 | 整数主键 ID(自增方式由实现决定:DB `AUTO_INCREMENT` 或应用 / 触发器分配) | +| `sId` | varchar(100) | 是 | — | 业务 ID(对外暴露的字符串标识,如 UUID / 人类可读编号) | +| `sBrandsId` | varchar(100) | 是 | — | 品牌 ID(多租户隔离) | +| `sSubsidiaryId` | varchar(100) | 是 | — | 子公司 ID(组织层级隔离) | +| `tCreateDate` | datetime | 否 | — | 记录创建时间 | + +字典 / 辅助表如有豁免,在该表业务注记里注明豁免原因。 + +## ER 关系概览 + +USR 用户管理模块共 6 张表,围绕「用户」为核心: + +- `t_user`(用户主表)通过 `iEmployeeId` 关联 `t_employee`(职员),实现「用户号 / 员工名」一对一联动。 +- `t_employee`(职员表)通过 `iDepartmentId` 关联 `t_department`(部门表),支撑 REQ-USR-004 按部门筛选。 +- `t_user` 与 `t_permission`(权限分类字典)通过 `t_user_permission`(用户-权限关联表)建立多对多关系,实现 REQ-USR-002 / 003 的「权限组」分配。 +- `t_company`(公司版本字典)独立于 `t_user`,仅为 REQ-USR-001 登录入参「版本」下拉提供可选项,不与用户产生强引用。 + +依赖方向:`t_user → t_employee → t_department`、`t_user ↔ t_permission`(多对多)、`t_company` 独立。 + +## 表清单 +- `t_user` — 系统用户主表,承载登录认证、用户类型、语言偏好、单据权限等基础属性 +- `t_employee` — 公司职员主档,与用户表通过 `iEmployeeId` 关联,提供姓名 / 工号 / 部门归属 +- `t_department` — 部门组织树,支撑职员归属与按部门筛选 +- `t_permission` — 权限分类字典,定义系统可分配的权限项 +- `t_user_permission` — 用户-权限分类多对多关联表 +- `t_company` — 公司 / 版本字典(标准版 / 专业版 / 旗舰版),登录时「版本」下拉数据源 + +## `t_user` — 系统用户主表,承载登录认证与基础属性 + +### 字段 + +| 字段 | 类型 | Nullable | 默认 | 业务含义 | +|---|---|---|---|---| +| `iIncrement` | int | 否 | 自增函数 | 整数主键 ID(标准列) | +| `sId` | varchar(100) | 是 | uuid 函数 | 业务 ID(标准列) | +| `sBrandsId` | varchar(100) | 是 | `1111111111` | 品牌 ID(多租户隔离,标准列) | +| `sSubsidiaryId` | varchar(100) | 是 | `1111111111` | 子公司 ID(组织层级隔离,标准列) | +| `tCreateDate` | datetime | 否 | 当前时间 | 创建时间(标准列) | +| `sUserNo` | varchar(50) | 否 | — | 用户号;关联职员后自动同步员工号;系统内唯一 | +| `sUserName` | varchar(50) | 否 | — | 登录用户名;系统内唯一;3-50 位 | +| `iEmployeeId` | int | 是 | NULL | 关联职员 `t_employee.iIncrement`;可空(非员工账号如系统管理员) | +| `sPasswordHash` | varchar(255) | 否 | — | 密码哈希(BCrypt / Argon2);禁止明文;初始密码 `666666` 哈希后存入 | +| `sUserType` | varchar(20) | 否 | `NORMAL` | 用户类型枚举:`NORMAL`(普通用户)/ `SUPER_ADMIN`(超级管理员) | +| `sLanguage` | varchar(10) | 否 | `zh-CN` | 语言枚举:`zh-CN`(中文)/ `en-US`(英文)/ `zh-TW`(繁体) | +| `bModifyDoc` | tinyint(1) | 否 | 0 | 单据修改权限:0 否 / 1 是 | +| `bVoid` | tinyint(1) | 否 | 0 | 作废标记(软删除):0 启用 / 1 已作废 | +| `iLoginFailCount` | int | 否 | 0 | 连续登录失败次数;达到阈值触发临时锁定;登录成功后清零 | +| `tLockUntil` | datetime | 是 | NULL | 锁定截止时间;NULL 表示未锁定 | +| `tLastLoginDate` | datetime | 是 | NULL | 最近一次登录时间 | +| `sCreator` | varchar(100) | 是 | NULL | 制单人(创建该账号的操作员用户名) | + +### 索引 + +- `uk_user_username` (UNIQUE): `sUserName` +- `uk_user_userno` (UNIQUE): `sUserNo` +- `idx_user_employee` (BTREE): `iEmployeeId` +- `idx_user_tenant` (BTREE): `sBrandsId`, `sSubsidiaryId` +- `idx_user_void` (BTREE): `bVoid` + +### 外键 + +- `fk_user_employee`: `iEmployeeId` → `t_employee.iIncrement` (ON DELETE SET NULL / ON UPDATE RESTRICT) + +### 业务注记 + +- 登录认证主体表;密码以哈希形式存储,登录失败计数与锁定时间均落库以保证服务重启不丢;亦可由 Redis 镜像加速,但 DB 字段为权威。 +- `bVoid = 1` 视为已作废账号,不可登录、不出现在默认查询结果中(REQ-USR-004 列表默认隐藏作废)。 +- 用户名 / 用户号唯一性由 `uk_user_username` / `uk_user_userno` 保证;新增 / 修改时若冲突由 DB 抛唯一约束错误,Service 转译为 `40001 用户名已存在`。 + +## `t_employee` — 公司职员主档 + +### 字段 + +| 字段 | 类型 | Nullable | 默认 | 业务含义 | +|---|---|---|---|---| +| `iIncrement` | int | 否 | 自增函数 | 整数主键 ID(标准列) | +| `sId` | varchar(100) | 是 | uuid 函数 | 业务 ID(标准列) | +| `sBrandsId` | varchar(100) | 是 | `1111111111` | 品牌 ID(多租户隔离,标准列) | +| `sSubsidiaryId` | varchar(100) | 是 | `1111111111` | 子公司 ID(组织层级隔离,标准列) | +| `tCreateDate` | datetime | 否 | 当前时间 | 创建时间(标准列) | +| `sEmployeeNo` | varchar(50) | 否 | — | 员工号;系统内唯一 | +| `sName` | varchar(100) | 否 | — | 姓名 | +| `iDepartmentId` | int | 是 | NULL | 部门 ID,关联 `t_department.iIncrement` | +| `sPhone` | varchar(20) | 是 | NULL | 手机号 | +| `sEmail` | varchar(100) | 是 | NULL | 邮箱 | +| `bDisabled` | tinyint(1) | 否 | 0 | 是否离职:0 在职 / 1 离职 | + +### 索引 + +- `uk_employee_no` (UNIQUE): `sEmployeeNo` +- `idx_employee_dept` (BTREE): `iDepartmentId` +- `idx_employee_name` (BTREE): `sName` +- `idx_employee_tenant` (BTREE): `sBrandsId`, `sSubsidiaryId` + +### 外键 + +- `fk_employee_department`: `iDepartmentId` → `t_department.iIncrement` (ON DELETE SET NULL / ON UPDATE RESTRICT) + +### 业务注记 + +- 用户表通过 `iEmployeeId` 关联本表;REQ-USR-002 / 003 选择员工时下拉数据源;REQ-USR-004 查询字段「员工名 / 部门」均联表本表。 +- 离职员工保留记录(`bDisabled = 1`),其关联用户应被自动作废(应用层逻辑,未做 DB 触发器)。 + +## `t_department` — 部门组织树 + +### 字段 + +| 字段 | 类型 | Nullable | 默认 | 业务含义 | +|---|---|---|---|---| +| `iIncrement` | int | 否 | 自增函数 | 整数主键 ID(标准列) | +| `sId` | varchar(100) | 是 | uuid 函数 | 业务 ID(标准列) | +| `sBrandsId` | varchar(100) | 是 | `1111111111` | 品牌 ID(多租户隔离,标准列) | +| `sSubsidiaryId` | varchar(100) | 是 | `1111111111` | 子公司 ID(组织层级隔离,标准列) | +| `tCreateDate` | datetime | 否 | 当前时间 | 创建时间(标准列) | +| `sName` | varchar(100) | 否 | — | 部门名称 | +| `sCode` | varchar(50) | 否 | — | 部门编码;系统内唯一 | +| `iParentId` | int | 是 | NULL | 上级部门 ID,NULL 表示根部门 | +| `iSortOrder` | int | 否 | 0 | 排序值,小者靠前 | + +### 索引 + +- `uk_department_code` (UNIQUE): `sCode` +- `idx_department_parent` (BTREE): `iParentId` +- `idx_department_tenant` (BTREE): `sBrandsId`, `sSubsidiaryId` + +### 外键 + +- `fk_department_parent`: `iParentId` → `t_department.iIncrement` (ON DELETE RESTRICT / ON UPDATE RESTRICT) + +### 业务注记 + +- 自引用形成部门树,根部门 `iParentId IS NULL`。 +- 部门删除采用「拒绝删除有下级 / 有员工的部门」策略(应用层校验);DB 外键 RESTRICT 兜底。 + +## `t_permission` — 权限分类字典 + +### 字段 + +| 字段 | 类型 | Nullable | 默认 | 业务含义 | +|---|---|---|---|---| +| `iIncrement` | int | 否 | 自增函数 | 整数主键 ID(标准列) | +| `sId` | varchar(100) | 是 | uuid 函数 | 业务 ID(标准列) | +| `sBrandsId` | varchar(100) | 是 | `1111111111` | 品牌 ID(多租户隔离,标准列) | +| `sSubsidiaryId` | varchar(100) | 是 | `1111111111` | 子公司 ID(组织层级隔离,标准列) | +| `tCreateDate` | datetime | 否 | 当前时间 | 创建时间(标准列) | +| `sCode` | varchar(50) | 否 | — | 权限码,例如 `USR:ADD` / `USR:EDIT`;系统内唯一 | +| `sName` | varchar(100) | 否 | — | 权限分类名称(展示用) | +| `iSortOrder` | int | 否 | 0 | 同分类内排序 | + +### 索引 + +- `uk_permission_code` (UNIQUE): `sCode` + +### 外键 + +- 无 + +### 业务注记 + +- 字典表,启动时由初始化脚本灌入;不允许业务删除,禁用通过软标记(暂未引入 `bVoid`,由 docs/01 补充时再加)。 +- 权限码格式 `<模块代码>:<动作>`,与后端 `@PreAuthorize('hasAuthority(...)')` 一一对应(docs/06 § 1.3)。 + +## `t_user_permission` — 用户-权限分类关联表 + +### 字段 + +| 字段 | 类型 | Nullable | 默认 | 业务含义 | +|---|---|---|---|---| +| `iIncrement` | int | 否 | 自增函数 | 整数主键 ID(标准列) | +| `sId` | varchar(100) | 是 | uuid 函数 | 业务 ID(标准列) | +| `sBrandsId` | varchar(100) | 是 | `1111111111` | 品牌 ID(多租户隔离,标准列) | +| `sSubsidiaryId` | varchar(100) | 是 | `1111111111` | 子公司 ID(组织层级隔离,标准列) | +| `tCreateDate` | datetime | 否 | 当前时间 | 创建时间(标准列) | +| `iUserId` | int | 否 | — | 用户 ID,关联 `t_user.iIncrement` | +| `iPermissionId` | int | 否 | — | 权限分类 ID,关联 `t_permission.iIncrement` | + +### 索引 + +- `uk_user_perm` (UNIQUE): `iUserId`, `iPermissionId` +- `idx_user_perm_perm` (BTREE): `iPermissionId` + +### 外键 + +- `fk_userperm_user`: `iUserId` → `t_user.iIncrement` (ON DELETE CASCADE / ON UPDATE RESTRICT) +- `fk_userperm_perm`: `iPermissionId` → `t_permission.iIncrement` (ON DELETE RESTRICT / ON UPDATE RESTRICT) + +### 业务注记 + +- 关联表豁免 `sBrandsId` / `sSubsidiaryId` 的业务语义但保留列以遵守项目标准;写入时复用 `t_user` 的对应租户标识。 +- 删除用户时级联清理本表行(`ON DELETE CASCADE`);删除字典项保护已分配数据(`ON DELETE RESTRICT`)。 +- 唯一约束 `(iUserId, iPermissionId)` 确保同一用户对同一权限分类不重复授权。 + +## `t_company` — 公司 / 版本字典 + +### 字段 + +| 字段 | 类型 | Nullable | 默认 | 业务含义 | +|---|---|---|---|---| +| `iIncrement` | int | 否 | 自增函数 | 整数主键 ID(标准列) | +| `sId` | varchar(100) | 是 | uuid 函数 | 业务 ID(标准列) | +| `sBrandsId` | varchar(100) | 是 | `1111111111` | 品牌 ID(多租户隔离,标准列) | +| `sSubsidiaryId` | varchar(100) | 是 | `1111111111` | 子公司 ID(组织层级隔离,标准列) | +| `tCreateDate` | datetime | 否 | 当前时间 | 创建时间(标准列) | +| `sCode` | varchar(50) | 否 | — | 公司 / 版本编码;系统内唯一 | +| `sName` | varchar(100) | 否 | — | 显示名称 | + +### 索引 + +- `uk_company_code` (UNIQUE): `sCode` + +### 外键 + +- 无 + +### 业务注记 + +- REQ-USR-001 登录入参「版本」下拉数据源;启动时由初始化脚本插入至少一行默认公司数据;具体「版本」字段语义待后续业务确认后再补列。 +- 当前 REQ 描述不足以确认与 `t_user` 是否需要强关联,故先做独立字典;后续如需「用户绑定公司 / 版本」再于 `t_user` 增列。 diff --git b/docs/04-技术规范.md a/docs/04-技术规范.md new file mode 100644 index 0000000..ad1239b --- /dev/null +++ a/docs/04-技术规范.md @@ -0,0 +1,187 @@ +# 04-技术规范 + +## 零、技术栈总览 + +| 分层模块 | 技术 | 版本要求 | 说明 | +|---|---|---|---| +| 前端基础框架 | React | 18.x | 构建前端应用 | +| 前端 UI 组件 | Ant Design | 5.x | 页面组件与交互控件 | +| 前端状态管理 | Redux Toolkit | 最新稳定版 | 管理全局状态 | +| 前端路由管理 | React Router | v6 | 页面路由与导航 | +| 前端工程化构建 | Vite | 最新稳定版 | 前端开发与打包构建 | +| 前端接口通信 | Axios | 最新稳定版 | 调用后端 API | +| 后端基础框架 | Spring Boot | 3.x | 构建后端服务 | +| 后端数据访问 | MyBatis-Plus | 最新稳定版 | 数据库访问与 ORM 增强 | +| 工作流引擎 | Activiti | 6.x | 审批流、流程流转 | +| 缓存服务 | Redis | 最新稳定版 | 缓存、会话、分布式能力 | +| 报表打印 | JXLS | 2.8.1 | 基于 Excel 模板生成报表 | +| Excel 导入导出 | EasyExcel | 4.0.3 | Excel 数据导入导出 | +| 关系型数据库 | MySQL | 8.x | 核心业务数据存储 | +| 数据库 schema 迁移 | Flyway (`flyway-core` + `flyway-mysql`) | 10.x / 最新稳定版 | `sql/migrations/V_n__*.sql` 顺序 apply;Spring Boot 启动时自动应用 | +| 接口风格 | RESTful API | 统一规范 | 前后端接口设计规范 | +| 权限认证 | Spring Security / JWT | 最新稳定版 | 登录认证、权限控制 | +| API 文档 | OpenAPI / Swagger | 最新稳定版 | 接口文档与调试 | +| 项目构建管理 | Maven | 3.9.x | Java 项目依赖与构建 | +| JDK 运行环境 | Java | 17 / 21 | Spring Boot 3 推荐版本 | +| 部署容器 | Docker | 最新稳定版 | 容器化部署 | +| Web 服务器 / 反向代理 | Nginx | 最新稳定版 | 前端托管、反向代理、负载分发 | +| 日志管理 | Logback | 默认集成 / 最新稳定版 | 应用日志输出 | +| 对象映射工具 | MapStruct | 最新稳定版 | DTO / VO / Entity 转换 | +| 工具类库 | Hutool / Apache Commons | 最新稳定版 | 常用工具方法支持 | + +> 本表由 scope-lock 锁定。后续所有规范基于此表推导。 + +## 一、后端规范 + +### 1.1 分层结构 + +| 层 | 职责 | +|---|---| +| `controller/` | 接收 HTTP 请求、参数校验(`@Valid`)、调用 Service、组装响应;不写业务逻辑 | +| `service/` | 业务编排、事务边界、跨 Mapper 调用;接口 + `impl/` 实现 | +| `mapper/` | MyBatis-Plus 数据访问(Java 接口 + XML),仅做单表 CRUD 与简单关联查询 | +| `entity/` | 与数据库表 1:1 的实体类,字段名/类型严格对齐 docs/03 | +| `dto/` | 入参对象(前端 → 后端),含 `@NotNull` / `@Pattern` 等校验注解 | +| `vo/` | 出参对象(后端 → 前端),由 MapStruct 从 Entity 转换 | +| `config/` | Spring 配置类(Security / Redis / MyBatis-Plus / Swagger / Activiti) | +| `common/` | 全局响应包装、统一异常处理器、拦截器、工具类 | +| `security/` | JWT 生成 / 验证、`UserDetailsService` 实现、权限注解 | + +### 1.2 命名约定 + +- **包名**:全小写,单数。根包 `com.example.erp`。业务模块包 `.module.<模块代码小写>`(示例:`com.example.erp.module.usr`)。 +- **类名**:大驼峰;按层级加后缀(`UserController` / `UserServiceImpl` / `UserMapper` / `UserCreateDTO` / `UserListVO`)。 +- **方法名**:小驼峰;动词开头(`createUser` / `listUsers` / `disableUser`),与 REST 动作语义对齐。 +- **常量**:全大写下划线(`MAX_LOGIN_FAILS = 5`),放对应业务模块的 `constant/` 子包或公共 `common/constant/`。 +- **示例 1**:`com.example.erp.module.usr.controller.UserController#listUsers(UserQueryDTO)` 返回 `PageVO`。 +- **示例 2**:`com.example.erp.module.usr.service.impl.UserServiceImpl#disableUser(Long id)` 写入 `t_user_audit_log` 审计。 + +### 1.3 统一响应格式 + +后端所有 HTTP 接口返回 `Result` 包装: + +```json +// 成功 +{ "code": 0, "message": "ok", "data": { ... } } +// 失败 +{ "code": 40001, "message": "用户名已存在", "data": null } +``` + +错误码段位: + +| 段位 | 含义 | +|---|---| +| `0` | 成功 | +| `1xxxx` | 系统级(参数校验、未授权、未登录) | +| `2xxxx` | 通用业务错误(资源不存在、状态非法) | +| `4xxxx` | 模块业务错误(按模块再分子段:USR=`40xxx`,PUR=`41xxx`...) | +| `5xxxx` | 第三方 / 基础设施错误(DB / Redis / 外部服务) | + +### 1.4 异常处理 + +- **全局异常处理器**:`@RestControllerAdvice` 统一捕获,转换为 `Result` 失败结构。 +- **必须 catch**:`MethodArgumentNotValidException`(参数校验)/ `BindException` / 业务自定义 `BizException` / `AccessDeniedException`。 +- **禁止 catch**:泛 `Exception` 在业务代码中吞掉;必须让全局处理器接管。 +- **接口响应禁止回显后端异常堆栈**:返回用户友好错误码 + 文案;堆栈仅写入 logback。 + +### 1.5 事务 + +- **事务边界**:放在 Service 层方法上(`@Transactional`),Controller / Mapper 禁止开事务。 +- **传播策略**:默认 `REQUIRED`;只读查询用 `@Transactional(readOnly = true)`。 +- **跨服务调用禁止开新事务嵌套**:跨进程 / 跨服务的一致性走「最终一致 + 补偿」或 Activiti 工作流,不要用分布式事务。 + +### 1.6 认证 + +- **协议**:JWT(HS256),头 `Authorization: Bearer `。 +- **生命周期**:access token 8 小时,refresh token 7 天;登录返回两枚 token;access 过期前端凭 refresh 刷新一次。 +- **刷新机制**:refresh token 只能用于换 access,不能直接调业务接口;服务端可吊销(Redis 存吊销名单)。 +- **密钥管理**:JWT 签名密钥放 `.env.local` 的 `JWT_SECRET`,至少 32 字节;生产环境必须更换默认值。 +- **登录失败锁定**:连续 5 次失败锁账户 30 分钟,Redis 计数器 key `login:fail:`。 + +## 二、前端规范 + +### 2.1 目录约定 + +| 目录 | 职责 | +|---|---| +| `src/api/` | Axios 实例 + 每模块一个 API 文件;**所有 HTTP 调用唯一入口** | +| `src/components/` | 跨页面通用组件(`AuthButton` / `AppTable` / `PageHeader`) | +| `src/pages/` | 业务页面,按模块组织子目录 | +| `src/store/` | Redux Toolkit slice,仅放真正的全局状态 | +| `src/hooks/` | 自定义 hook(`useAuth` / `usePagination` / `useDebounce`) | +| `src/utils/` | 纯函数工具(格式化、校验、正则) | +| `src/styles/` | `tokens.css` + 全局样式 | +| `src/router/` | 路由表 + 路由守卫 | + +> **前端禁止直接写 SQL / 操作 DB**,所有数据访问走 `api/` 层统一封装。 + +### 2.2 状态管理 + +- **Redux 存什么**:全局共享 + 跨页面持久(当前用户信息、权限码列表、菜单树、字典)。 +- **Redux 不存什么**:单个页面的表单值、Modal 开关、表格分页参数 → 放组件 `useState` / `useReducer`。 +- **服务端数据**:业务数据(列表、明细)不要塞 Redux,每次进入页面走 `api/` 取最新;只有字典 / 全局枚举做内存缓存(`store/slices/dictSlice`)。 + +### 2.3 请求封装 + +- **Axios 实例**:`src/api/http.ts` 统一创建,`baseURL` 取 `import.meta.env.VITE_API_BASE`。 +- **请求拦截器**:自动注入 `Authorization: Bearer `;token 临期则先刷新再发起原请求。 +- **响应拦截器**:剥 `Result.data` 给业务;`code !== 0` 时 `message.error(message)` 并 reject。 +- **超时**:默认 15s;上传 / 导出接口单独 60s。 +- **错误重试**:仅对幂等 GET 重试 1 次,POST / PUT / DELETE 禁止重试。 + +### 2.4 错误处理 + +- **网络错误**:Axios `error.code === 'ERR_NETWORK'` → 全局 `message.error('网络异常,请检查连接')`。 +- **业务错误**:`code !== 0` 在响应拦截器统一弹 message;页面只需处理 `try/catch` 中的 reject。 +- **页面级错误**:路由级 `ErrorBoundary` 包顶层,组件 throw 时显示统一错误页(docs/06 § 1.2)。 + +### 2.5 样式与主题 + +- **CSS 变量命名**:`--color---` + - `scope` = `form` / `table-row` / `btn` / `link` / ... + - `role` = `bg` / `fg` / `border` + - `state` = `edit` / `readonly` / `hover` / `selected` +- **文件位置**:`src/styles/tokens.css`,由 skeleton-gen 生成空骨架,色值由 docs/06 § 二锁定后填入。 +- **组件样式**:只用 `var(--color-xxx)`,禁止硬编码 hex / rgba。 +- **Ant Design 主题对接**:`` 把 tokens 注入 antd 主题。 + +具体 token 表见 docs/06 § 二。 + +## 三、共同约定 + +### 3.1 Git 提交 + +`(): REQ-XXX-NNN` + +- `type` ∈ `feat` / `fix` / `refactor` / `test` / `docs` / `chore` / `style` +- `scope` 为模块代码小写(`usr` / `pur` / `sal`)或 `infra` +- `subject` 50 字以内动词开头 +- 末尾必须挂 `REQ-XXX-NNN`(如 `REQ-USR-001`),CI 用此挂关联 + +### 3.2 分页查询 + +- **后端入参**:`PageQuery { pageNum: int = 1, pageSize: int = 20 }`(`pageSize` 上限 100)+ 业务过滤字段;继承基类避免重复声明。 +- **后端出参**:`PageVO { records: List, total: long, pageNum: int, pageSize: int }`。 +- **前端组件**:统一封装 `` 内置 antd `Table` + `Pagination`,受控 `current` / `pageSize` / `total`。 +- **默认排序**:按创建时间倒序;可按列点击切换。 + +### 3.3 日期与金额 + +- **后端类型**:日期 `LocalDate`,时间 `LocalDateTime`,金额 `BigDecimal`(scale=2,HALF_UP)。 +- **序列化**:Jackson 全局配置 `LocalDateTime` ↔ `yyyy-MM-dd HH:mm:ss`;`BigDecimal` 序列化为字符串避免精度丢失。 +- **前端展示**:`utils/format.ts` 提供 `formatDate(d)` / `formatMoney(n)`;金额展示固定 2 位小数 + 千分位(`¥1,234.56`)。 + +### 3.4 数据访问规约 + +- **SELECT 字段显式列举**,禁止 `SELECT *`;Mapper XML 用 `` 集中维护。 +- **禁止 N+1**:循环中不得执行 DB 查询;改用批量查(`IN (?,?,?)`)/ JOIN / ``。 +- **表名 / 字段名**:Mapper XML 中通过 `` 片段或常量引用,禁止字符串拼接(防 SQL 注入 + 利于改名)。 +- **分页**:统一用 MyBatis-Plus `Page` + `selectPage(...)`,禁止 LIMIT 字符串拼接。 + +### 3.5 配置与安全 + +- **配置**:DB 连接 / 端口 / 密钥 / 第三方 URL 等一律放 `application.yml` + `.env.local`,代码里**禁止硬编码**;`application.yml` 用 `${VAR_NAME:default}` 引用环境变量。 +- **前端安全**: + - `localStorage` **不存敏感信息**(token / 身份 / 个人数据);推荐 HttpOnly Cookie 或 「内存 access token + HttpOnly refresh cookie」组合。 + - 接口响应禁止回显后端异常堆栈(与 § 1.4 一致)。 + - XSS:所有用户输入展示走 React JSX 自动转义;`dangerouslySetInnerHTML` 禁用,除非内容来源已白名单化。 diff --git b/docs/05-API接口契约.md a/docs/05-API接口契约.md new file mode 100644 index 0000000..17659fe --- /dev/null +++ a/docs/05-API接口契约.md @@ -0,0 +1,207 @@ +# 05-API接口契约 + +BasePath: `/api` +端口: `8080` + +## 全局约定 + +### 响应格式 +```json +{"code": 200, "message": "操作成功", "data": {}, "timestamp": 1700000000000} +``` + +### 错误码 +| 范围 | 含义 | +|---|---| +| 200 | 成功 | +| 400xx | 客户端参数错误 | +| 401xx | 认证/授权错误 | +| 403xx | 权限不足 | +| 404xx | 资源不存在 | +| 500xx | 服务端内部错误 | + +### 鉴权 + +所有业务接口走 JWT Bearer。客户端在 `Authorization: Bearer ` 头中携带访问令牌。 + +- 登录 `POST /api/usr/auth/login` 返回 `accessToken`(8 小时)+ `refreshToken`(7 天) +- 临期前端用 refresh token 换新 access token:`POST /api/usr/auth/refresh` +- 注销 `POST /api/usr/auth/logout` 服务端把 token 加入 Redis 吊销名单 +- 未携带 / 过期 / 已吊销 → 401,对应错误码 `40101 未登录` / `40102 Token 已过期` / `40103 Token 已吊销` + +### 分页参数 + +统一查询入参: + +| 字段 | 类型 | 必填 | 默认 | 说明 | +|---|---|---|---|---| +| `pageNum` | int | 否 | 1 | 当前页码,从 1 开始 | +| `pageSize` | int | 否 | 20 | 每页条数;上限 100 | +| `orderBy` | string | 否 | `tCreateDate DESC` | 排序表达式,列名 + 方向 | + +响应包装: + +```json +{ + "code": 200, "message": "ok", + "data": { + "records": [ ... ], + "total": 1234, + "pageNum": 1, + "pageSize": 20 + }, + "timestamp": 1700000000000 +} +``` + +## 接口清单 +(各模块接口段落见下方,由 `downstream-gen` 按 REQ 填入) + +## module_usr — USR 用户管理 + +### REQ-USR-001 用户登录 + +- **Method**: POST +- **Path**: `/api/usr/auth/login` +- **Auth**: 公开(无需 JWT) +- **请求**: + ```json + { + "userName": "admin", + "password": "P@ssw0rd", + "companyVersion": "STANDARD" + } + ``` + - `userName` (string, 必填, 3-50 位) + - `password` (string, 必填, 明文传输,由 TLS 保证机密性;后端用 BCrypt/Argon2 比对哈希) + - `companyVersion` (string, 必填, 枚举 `STANDARD` / `PRO` / `FLAGSHIP`) +- **响应**: + ```json + { + "code": 200, "message": "ok", + "data": { + "accessToken": "eyJ...", + "refreshToken": "eyJ...", + "expiresIn": 28800, + "user": { + "iIncrement": 1, + "sUserName": "admin", + "sUserType": "SUPER_ADMIN", + "sLanguage": "zh-CN", + "permissionCodes": ["USR:ADD", "USR:EDIT", "USR:DELETE", "USR:VIEW"] + } + }, + "timestamp": 1700000000000 + } + ``` + +#### 错误码 +- `40001` — 用户名或密码错误(不区分两者,防爆破) +- `40301` — 账号已锁定,请稍后再试 +- `40302` — 账号已作废 +- `40010` — 参数校验失败(用户名 / 密码 / 版本字段为空或格式不合法) + +--- + +### REQ-USR-002 新增用户 + +- **Method**: POST +- **Path**: `/api/usr/users` +- **Auth**: JWT;要求权限码 `USR:ADD` +- **请求**: + ```json + { + "userNo": "U10001", + "userName": "zhangsan", + "employeeId": 12, + "userType": "NORMAL", + "language": "zh-CN", + "modifyDoc": false, + "permissionIds": [3, 5, 8] + } + ``` + - `userNo` / `userName` 必填且系统内唯一 + - `employeeId` 可空(非员工账号) + - `userType` 枚举 `NORMAL` / `SUPER_ADMIN`(仅超级管理员可创建超级管理员) + - `language` 枚举 `zh-CN` / `en-US` / `zh-TW` + - `permissionIds` 权限分类 ID 数组,可为空 + - 初始密码后端固定生成(`666666`),不在请求体中 +- **响应**: + ```json + { "code": 200, "message": "ok", "data": { "userNo": "U10001", "iIncrement": 42 }, "timestamp": 1700000000000 } + ``` + +#### 错误码 +- `40001` — 用户名 / 用户号已存在 +- `40010` — 参数校验失败 +- `40310` — 普通用户无权创建超级管理员 +- `40411` — 关联职员不存在 +- `40412` — 关联权限分类不存在 + +--- + +### REQ-USR-003 修改用户 + +- **Method**: PUT +- **Path**: `/api/usr/users/{iIncrement}` +- **Auth**: JWT;要求权限码 `USR:EDIT` +- **请求**: + ```json + { + "employeeId": 12, + "userType": "NORMAL", + "language": "en-US", + "modifyDoc": true, + "void": false, + "permissionIds": [3, 5] + } + ``` + - `userName` 不可修改,故请求体不包含 + - `void = true` 等价于禁用(软删除) + - `permissionIds` 全量覆盖(先 DELETE 后 INSERT,事务内) +- **响应**: + ```json + { "code": 200, "message": "ok", "data": { "iIncrement": 42 }, "timestamp": 1700000000000 } + ``` + +#### 错误码 +- `40404` — 目标用户不存在 +- `40310` — 不可修改超级管理员(非超级管理员调用) +- `40311` — 不可禁用 / 修改自己 +- `40411` — 关联职员不存在 +- `40412` — 关联权限分类不存在 +- `40010` — 参数校验失败 + +--- + +### REQ-USR-004 查询用户 + +- **Method**: GET +- **Path**: `/api/usr/users` +- **Auth**: JWT;要求权限码 `USR:VIEW` +- **请求**: query string + - `searchField` 枚举 `userName` / `employeeName` / `userNo` / `departmentName` / `userType` / `void` / `lastLoginDate` / `creator` + - `matchMode` 枚举 `CONTAINS` / `NOT_CONTAINS` / `EQUALS` + - `searchValue` 字符串,空则不过滤 + - `includeVoid` 布尔,默认 false(不返回已作废用户) + - `pageNum` / `pageSize` / `orderBy` 通用分页参数 +- **响应**: 分页结构,`records[]` 元素: + ```json + { + "iIncrement": 42, + "sUserName": "zhangsan", + "employeeName": "张三", + "sUserNo": "U10001", + "departmentName": "技术部", + "sUserType": "NORMAL", + "sLanguage": "zh-CN", + "bVoid": false, + "tLastLoginDate": "2026-05-13 09:12:33", + "sCreator": "admin", + "tCreateDate": "2025-12-01 10:00:00" + } + ``` + - 不返回 `sPasswordHash` / `iLoginFailCount` / `tLockUntil` + +#### 错误码 +- `40010` — 参数校验失败(`pageSize > 100` / `matchMode` 不合法 / `searchField` 不合法) diff --git b/docs/06-UI交互规范.md a/docs/06-UI交互规范.md new file mode 100644 index 0000000..2c16448 --- /dev/null +++ a/docs/06-UI交互规范.md @@ -0,0 +1,93 @@ +# 06-UI交互规范 + +> 本项目所有页面布局以项目根 `prototype/` 目录下的静态 HTML mockup 为权威。前端阶段(fe-feature-*)实现时直接以 prototype/ HTML 推导组件树与样式。本文件仅承载跨页面通用规则与 Design Tokens。 + +## 一、通用交互规则 + +### 1.1 操作反馈 + +- **成功提示**:使用 Ant Design `message.success()`,默认 3s 自动消失;新增 / 修改 / 删除等写操作必须给反馈。 +- **失败提示**:使用 `message.error()` 显示后端返回的 `message` 字段;表单字段级错误用 Form 的 `validateStatus="error"` + `help` 同步显示。 +- **危险操作二次确认**:删除 / 禁用 / 重置密码等不可逆操作必须用 `Modal.confirm({ okType: 'danger' })` 二次确认,按钮文案使用业务动词(如「确认禁用」),不用「确定」。 +- **长耗时按钮 loading**:提交类按钮在请求未返回前必须置为 `loading=true` 并禁用,避免重复提交;查询类操作在表格组件上启用 `loading` 属性即可。 + +### 1.2 数据展示 + +- **空状态**:列表 / 表格无数据时使用 ``,明细页无数据时附加「返回列表」按钮。 +- **加载状态**:页面级加载用 `` 包裹主区域;表格 / 卡片局部加载用组件自带 `loading` 属性。 +- **异常状态**:接口 5xx 或网络异常时显示 `重试} />`;403 时显示「无权限」并提供「返回首页」按钮。 + +### 1.3 权限控制(前端) + +- **菜单级**:登录后由后端返回菜单权限码列表,前端 Layout 按权限码过滤后渲染左侧菜单。 +- **按钮级**:包装 `新增`,权限码不在用户列表时直接不渲染(避免显示再禁用)。 +- **路由级**:在 React Router 外层包 ``,路由元数据声明 `meta.code`,无权限重定向 403 页面。 +- **关联后端 RBAC**:权限码格式 `<模块代码>:<动作>`(如 `USR:ADD` / `USR:EDIT` / `USR:DELETE`),与后端 Spring Security `@PreAuthorize("hasAuthority('USR:ADD')")` 一一对应。 + +## 二、Design Tokens + +> 所有色值统一以 CSS 变量定义于 `src/styles/tokens.css`;命名规范见 docs/04 § 2.5。 + +### 2.1 全局调色板 + +与 Ant Design 5 主题对齐,统一通过 `` 注入。 + +| 语义 | 变量名 | 默认值 | 用途 | +|---|---|---|---| +| 主色 | `--color-primary` | `#1677ff` | 主操作按钮 / 链接 / 选中态 | +| 成功 | `--color-success` | `#52c41a` | 成功消息 / 启用状态标签 | +| 警告 | `--color-warning` | `#faad14` | 警告消息 / 待处理状态 | +| 错误 | `--color-error` | `#ff4d4f` | 错误消息 / 危险按钮 / 禁用状态 | +| 主文字 | `--color-text-primary` | `rgba(0, 0, 0, 0.88)` | 正文 / 表格内容 | +| 次文字 | `--color-text-secondary` | `rgba(0, 0, 0, 0.65)` | 辅助说明 / 占位 | +| 边框 | `--color-border` | `#d9d9d9` | 表单输入 / 卡片边界 | +| 背景 | `--color-bg-base` | `#ffffff` | 页面主背景 | +| 弱背景 | `--color-bg-layout` | `#f5f5f5` | 页面 layout 间隙 / 灰底 | + +### 2.2 组件级状态色 + +| 序号 | 组件 | 编辑bg | 只读bg | 悬浮bg | 编辑fg | 只读fg | 悬浮fg | 备注 | +|---|---|---|---|---|---|---|---|---| +| 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` | +| 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)` | — | +| 3 | 表格行 | — | `var(--color-bg-base)` | `var(--color-bg-row-hover)` | — | `var(--color-text-primary)` | `var(--color-text-primary)` | 编辑态走 Modal,行内不编辑 | +| 4 | 主按钮 | `var(--color-primary)` | `var(--color-bg-layout)` | `var(--color-primary-hover)` | `#ffffff` | `var(--color-text-secondary)` | `#ffffff` | 危险按钮替换 `--color-error` | +| 5 | 链接 | — | — | `var(--color-primary-hover)` | `var(--color-primary)` | — | `var(--color-primary-hover)` | — | + +**Token 默认值**: + +| Token | 默认值 | +|---|---| +| `--color-primary-hover` | `#4096ff` | +| `--color-bg-row-hover` | `#fafafa` | + +### 2.3 引用约定 + +- 组件样式只用 `var(--color-xxx)`,禁止硬编码 hex / rgba。 +- 新增 token 须先登记到 § 2.1 / 2.2 再补 `tokens.css`。 +- 修改色值只改 `tokens.css` 一处,不允许组件覆盖。 + +## 三、页面清单 + +### module_usr USR-用户管理 + +- **登录页** (`/login`) + - 类型: 表单页 + - 对应 REQ: REQ-USR-001 + - 入口菜单: 无(未登录态唯一可达页面) + - 主要交互: 表单输入 用户名 / 密码 / 版本 → 提交 → 成功跳首页 / 失败留页并显示通用错误消息;账号锁定时显示锁定提示 +- **用户列表页** (`/usr/users`) + - 类型: 列表页 + - 对应 REQ: REQ-USR-004 + - 入口菜单: 系统管理 → 用户管理 → 用户列表 + - 主要交互: 查询字段 + 匹配方式 + 查询值 三段筛选 → 表格分页展示 → 行内「编辑」「禁用 / 启用」按钮(权限码 `USR:EDIT`);顶部「新增用户」按钮(权限码 `USR:ADD`)跳新增表单 +- **用户新增页** (`/usr/users/new`) + - 类型: 表单页 + - 对应 REQ: REQ-USR-002 + - 入口菜单: 系统管理 → 用户管理 → 用户列表 → 新增按钮 + - 主要交互: 员工名(下拉单选)选择后自动回填用户号 / 用户名;类型 / 语言下拉;权限组表格(多选);提交校验失败显示字段级 `validateStatus="error"`;成功 message.success 后回列表页 +- **用户编辑页** (`/usr/users/:id/edit`) + - 类型: 表单页 + - 对应 REQ: REQ-USR-003 + - 入口菜单: 用户列表 → 行内「编辑」按钮 + - 主要交互: 复用新增表单组件;用户名只读;提供「重置密码」按钮(二次确认);提供「禁用 / 启用」开关;禁用自己时按钮置灰 diff --git b/docs/07-环境配置.md a/docs/07-环境配置.md new file mode 100644 index 0000000..fbcc21b --- /dev/null +++ a/docs/07-环境配置.md @@ -0,0 +1,46 @@ +# 07-环境配置 + +## 一、依赖清单 + +| 层 | 依赖 | 版本 | 说明 | +|---|---|---|---| +| 运行时 | Java (JDK) | 17 / 21 | Spring Boot 3 推荐版本 | +| 运行时 | MySQL | 8.x | 关系数据库 | +| 运行时 | Redis | 最新稳定版 | 缓存 / 会话 | +| 运行时 | Node.js | 20.x LTS | 前端构建 + 本地 dev server | +| 构建(后端) | Maven | 3.9.x | Java 依赖与构建工具 | +| 构建(前端) | pnpm | 8.x(或 npm 10.x) | 前端依赖管理 | +| 构建(前端) | Vite | 最新稳定版 | 前端开发与打包 | +| 容器 | Docker | 最新稳定版 | 容器化部署 | +| 容器 | Docker Compose | 最新稳定版 | 本地一键启依赖(MySQL + Redis) | +| 反向代理 | Nginx | 最新稳定版 | 前端静态托管 / 反向代理 | +| CLI 工具 | git | 2.30+ | 代码版本控制 | +| CLI 工具 | mysql client | 8.x | 本地执行 SQL / 验证 migration | +| CLI 工具 | glab(可选) | 最新稳定版 | GitLab MR 创建(亦可直接走 Web) | + +## 二、端口约定 + +| 服务 | 端口 | 说明 | +|---|---|---| +| 后端 HTTP | 8080 | Spring Boot 默认端口(`server.port`) | +| 前端 dev server | 5173 | Vite 默认端口(`vite --port`) | +| MySQL | 3306 | 默认端口,本地开发可用 Docker Compose 暴露 | +| Redis | 6379 | 默认端口 | +| Nginx | 80 / 443 | 生产部署反向代理入口 | + +## 三、环境变量 + +运行时凭据(数据库连接、JWT 密钥等)全部放在仓库根的 `.env.local`,不入 git。 +字段清单与占位符见该文件,真实值由开发者本地填写。 + +## 四、常用命令 + +| 命令 | 说明 | +|---|---| +| `./mvnw spring-boot:run` | 本地启动后端(含 Flyway 自动 apply migration) | +| `pnpm dev` | 本地启动前端 dev server(默认 5173) | +| `./mvnw clean package -DskipTests` | 后端打包生成 jar | +| `pnpm build` | 前端打包到 `dist/` | +| `bash scripts/test.sh` | 执行后端 + 前端测试组合(lint / unit / e2e) | +| `bash scripts/setup-test-db.sh` | 重置本地测试数据库(DROP + CREATE + apply V1) | +| `glab mr create` | 推送当前分支并创建 GitLab MR(亦可走 Web 端) | diff --git b/docs/08-模块任务管理.md a/docs/08-模块任务管理.md new file mode 100644 index 0000000..6f0416a --- /dev/null +++ a/docs/08-模块任务管理.md @@ -0,0 +1,80 @@ +# 08-工作流进度 + +> 全流程进度跟踪。CC 每完成一项产出就勾选一项。 +> - **§ 一 Plan(A0~A5)**:`plan-start` 找第一个未勾 A 子项分发到对应 skill +> - **§ 二 Coding(模块)**:分发以 `docs/02-开发计划.md § 二 开发顺序清单` 为准;`coding-start` 按 docs/02 顺序扫描,对每个 REQ 所属模块查询本 § 二的 `MR:` 字段 + GitLab API `state`,找第一个非 merged 模块分发。本 § 二 行序无语义,仅作模块元数据表 + +## 一、Plan 阶段(一次性) + +- [x] A0 项目初始化 — project-init + - [x] 依赖检查通过 + - [x] 项目文件骨架已创建(CLAUDE.md + docs/01-需求清单/index.md + docs/04-技术规范.md) + - [x] Git 已初始化 + +- [x] A1 范围锁定 — scope-lock + - [x] 项目概述已填写(CLAUDE.md § 🎯 项目概述) + - [x] 技术栈已确认(docs/04 § 零) + - [x] 需求清单索引已填写(docs/01-需求清单/index.md) + - [x] REQ 卡片骨架已生成(docs/01-需求清单//REQ-*.md,业务内容留待人工填写) + +- [x] A2 骨架生成 — skeleton-gen + - [x] 架构文档已生成(docs/04 § 一+、docs/06、docs/07、docs/09) + - [x] 工具脚本已生成(scripts/*.sh、.githooks/pre-push、.env.local) + - [x] .gitignore 已配置 + +- [x] A3 DB 设计 + REQ 回填 — db-design-gen + - [x] docs/03-数据库设计文档.md 已生成 + - [x] docs/01 各 REQ 卡片"依赖表" + 模块头"涉及表" 已回填 + +- [x] A4 DB 初始化 — db-init + - [x] sql/migrations/V1__initial_schema.sql 已生成 + - [x] DDL 与 docs/03 全量一致 + - [x] .env.local 凭据已验证(mysql -e "SELECT 1" OK) + - [x] setup-test-db.sh 防护通过 + DROP+CREATE + apply V1 已执行 + - [x] SHOW TABLES 行数 == docs/03 表数量 + +- [x] A5 下游文档生成 — downstream-gen + - [x] docs/02 开发计划已生成 + - [x] docs/05 API 契约已生成 + - [x] docs/06 § 三 页面清单已填入 + - [x] docs/10 验收清单已生成 + - [x] 下方模块列表已填入 + - [x] REQ 卡片依赖接口已回填 + +## 二、Coding 阶段(后端模块循环) + +(A5 填入后,每行一个后端模块。每个模块的 `MR:` 字段在 `—` 和 `!` 之间变化,完成由 GitLab API `state=merged` 判定。`coding-start` 每次按 docs/02 REQ 序扫每模块的 MR state 决定派发。后端模块全部 merged 后自动进入 § 三 前端阶段。) + + + +- module_usr USR-用户管理 + - 依赖: — + - 路径: backend/src/main/java/com/example/erp/module/usr/ + - MR: — + - 功能: + - [ ] REQ-USR-001 用户登录 + - [ ] REQ-USR-002 新增用户 + - [ ] REQ-USR-003 修改用户 + - [ ] REQ-USR-004 查询用户 + +## 三、Coding 阶段(前端整体) + +(`frontend-start` 进入时扫 prototype/ + docs/01 + docs/05 → AI 自主推导 FE 业务功能清单写到下方"功能:"项(无人工审阅断点;合理性由整体 MR 时统一校核)。已有清单则直接加载。整个前端阶段 1 个 MR,分支 `frontend-phase`。) + +- 整体 MR: — +- 功能: + diff --git b/docs/09-项目目录结构.md a/docs/09-项目目录结构.md new file mode 100644 index 0000000..4845ea9 --- /dev/null +++ a/docs/09-项目目录结构.md @@ -0,0 +1,124 @@ +# 09-项目目录结构 + +## 一、仓库顶层 + +``` +. +├── CLAUDE.md # 项目级规范与流程指令 +├── README.md # 项目说明(可选) +├── .env.local # 本地凭据(不入 git) +├── .gitignore +├── .githooks/ +│ └── pre-push # 推送前自动跑 scripts/test.sh +├── scripts/ +│ ├── test.sh # 后端 + 前端测试组合入口 +│ └── setup-test-db.sh # 重置本地测试数据库 +├── sql/ +│ └── migrations/ # Flyway V*__*.sql 文件 +├── docs/ # 全量项目文档(见 § 四) +├── prototype/ # 静态 HTML mockup(前端实现权威) +├── backend/ # 后端工程(Spring Boot + Maven) +└── frontend/ # 前端工程(Vite + React) +``` + +## 二、后端目录 + +``` +backend/ +├── pom.xml +└── src/ + ├── main/ + │ ├── java/ + │ │ └── com.example.erp/ + │ │ ├── ErpApplication.java + │ │ ├── common/ # 全局响应 / 异常 / 拦截器 / 工具 + │ │ │ ├── result/ + │ │ │ ├── exception/ + │ │ │ ├── interceptor/ + │ │ │ └── util/ + │ │ ├── config/ # Spring 配置类(Security / Redis / MyBatis-Plus / Swagger / Activiti) + │ │ ├── module/ # 业务模块(按 docs/01 索引拆分) + │ │ │ └── usr/ # USR 用户管理 + │ │ │ ├── controller/ + │ │ │ ├── service/ + │ │ │ │ └── impl/ + │ │ │ ├── mapper/ + │ │ │ ├── entity/ + │ │ │ ├── dto/ + │ │ │ └── vo/ + │ │ └── security/ # 认证 / 鉴权 / JWT + │ └── resources/ + │ ├── application.yml + │ ├── application-dev.yml + │ ├── application-prod.yml + │ ├── mapper/ # MyBatis XML(按模块再分子目录) + │ └── logback-spring.xml + └── test/ + └── java/ + └── com.example.erp/ + └── module/ + └── usr/ # 与 main/ 镜像 +``` + +## 三、前端目录 + +``` +frontend/ +├── package.json +├── vite.config.ts +├── tsconfig.json +├── index.html +└── src/ + ├── main.tsx # 入口(挂载 React + Router + Store + ConfigProvider) + ├── App.tsx # 顶层路由 + 全局 Layout + ├── api/ # Axios 实例 + 各模块 API(数据访问统一入口) + │ └── usr.ts # USR 用户管理 API + ├── components/ # 跨页面通用组件(AuthButton / AppTable / PageHeader 等) + ├── pages/ # 按业务模块组织页面 + │ └── usr/ # USR 用户管理 + │ ├── UserList.tsx + │ ├── UserEdit.tsx + │ └── Login.tsx + ├── store/ # Redux Toolkit slices(全局状态) + │ └── slices/ + ├── hooks/ # 自定义 hook(useAuth / usePagination 等) + ├── utils/ # 工具函数(formatDate / formatMoney / regex 等) + ├── styles/ + │ ├── tokens.css # Design Tokens(docs/06 § 二) + │ └── global.css # 全局样式 reset / typography + ├── router/ # 路由表(含 meta.code 权限码) + └── assets/ # 静态资源(图片 / 字体) +``` + +> 注:仓库根 `src/styles/tokens.css` 由 skeleton-gen 创建,作为 Design Tokens 的「上游」源;前端工程化前可先保留在根 `src/styles/` 下,前端工程初始化时迁入 `frontend/src/styles/`(同名同内容)。 + +## 四、docs/ 结构 + +``` +docs/ +├── 01-需求清单/ # 每模块一子目录(_module.md 模块头 + REQ-*.md 卡片) +├── 02-开发计划.md +├── 03-数据库设计文档.md +├── 04-技术规范.md +├── 05-API接口契约.md +├── 06-UI交互规范.md +├── 07-环境配置.md +├── 08-模块任务管理.md +├── 09-项目目录结构.md +├── 10-验收检查清单.md +└── superpowers/ # CC 运行时产物 +``` + +## 五、命名与放置约定 + +- **后端根包**:`com.example.erp`,下文统称 ``;新增业务模块时落到 `.module.<模块代码小写>`(如 `.module.usr`)。 +- **Controller**:`.module..controller.Controller`,文件名首字母大写驼峰,URI 前缀 `/api/<模块代码小写>`。 +- **Service**:接口 `Service` + 实现 `ServiceImpl`,实现类放 `service/impl/`。 +- **Mapper**:`Mapper`(Java 接口)+ `resources/mapper//Mapper.xml`(XML 同名)。 +- **Entity / DTO / VO**: + - `entity/` 数据库实体(与表 1:1,字段类型同 docs/03) + - `dto/` 入参(前端 → 后端的请求体) + - `vo/` 出参(后端 → 前端的响应体) +- **前端组件**:通用组件放 `frontend/src/components/`,文件名首字母大写驼峰(`AuthButton.tsx`);样式同名 `.module.css`(启用 CSS Modules)。 +- **前端页面**:放 `frontend/src/pages/<模块代码小写>/`,文件名按业务功能命名(`UserList.tsx` / `UserEdit.tsx`),路由 path 前缀 `/<模块代码小写>`。 +- **API 客户端**:每个模块一个文件 `frontend/src/api/<模块代码小写>.ts`,导出该模块所有接口函数;禁止在组件里直接 `axios.xxx`。 diff --git b/docs/10-验收检查清单.md a/docs/10-验收检查清单.md new file mode 100644 index 0000000..3a9c860 --- /dev/null +++ a/docs/10-验收检查清单.md @@ -0,0 +1,16 @@ +# 10-验收检查清单 + +通用验收项(全项目适用): + +- [ ] `scripts/test.sh` 本地全绿 +- [ ] 所有 schema 改动都有对应 `sql/migrations/V_n__.sql` +- [ ] 所有新接口在 `docs/05` 中有契约定义 +- [ ] 所有新功能代码注释含 REQ-XXX-NNN +- [ ] 统一响应格式 `{code, message, data, timestamp}` +- [ ] 异常走全局处理器,不暴露堆栈到前端 +- [ ] 前端不存敏感信息到 localStorage + +> 本文档仅维护项目级验收 SOP。粒度更细的验收信息分散在: +> - **每 REQ 的业务验收点**:`docs/01-需求清单//.md § 验收` +> - **每模块的实测验收(数据 / UI / 自动化用例位置)**:由 B 阶段 `module-report` 在该模块完成时填入模块完成报告 +> - **REQ 开发进度(feature-review approve 状态)**:`docs/08 § 二` diff --git b/scripts/setup-test-db.sh a/scripts/setup-test-db.sh new file mode 100755 index 0000000..41a3970 --- /dev/null +++ a/scripts/setup-test-db.sh @@ -0,0 +1,65 @@ +#!/usr/bin/env bash +# scripts/setup-test-db.sh — 数据库重置脚本:drop + create 空库。 +# schema apply 由 Flyway 在 Spring Boot 启动时自动处理(见 docs/04 技术栈 + sql/migrations/V*.sql)。 +# seed 数据由测试框架负责(Spring @Sql / Flyway R__seed.sql / data.sql)。 +# +# 使用场景: +# - scripts/test.sh 开头:清空库,让 Spring 启动时 Flyway 从 V1 开始重放所有 migration +# - scripts/test.sh 结尾:清空库,避免测试遗留污染下次运行 +# - 手动调试时:reset 到零状态 +# +# 防护:本脚本只允许在本地 host + 测试库名上执行;非预期目标会被拒绝, +# 避免 .env.local 误指向 staging/prod 时触发不可逆 DROP。 + +set -euo pipefail + +ENV_FILE="$(dirname "$0")/../.env.local" +[ -f "$ENV_FILE" ] || { echo "[setup-test-db] ⚠️ .env.local 不存在($ENV_FILE)" >&2; exit 1; } + +# 用 set -a 加载,让 KEY=VALUE 导出为环境变量;密码中含特殊字符时 .env.local 请用单引号包裹 +set -a; . "$ENV_FILE"; set +a + +# 防护 1:默认只允许本地 host(localhost / 127.0.0.1 / ::1)。 +# 若要为本项目额外允许某些远程 host(如公司测试 MySQL),在 .env.local 里设: +# TEST_DB_ALLOWED_HOSTS="118.178.19.35 test-mysql.internal" # 空格或逗号分隔 +# 被列入者可直接 DROP CREATE,不再需要 TEST_DB_ALLOW_REMOTE=1。 +ALLOWED_HOSTS="localhost 127.0.0.1 ::1 ${TEST_DB_ALLOWED_HOSTS//,/ }" +host_allowed=0 +for h in $ALLOWED_HOSTS; do + [ "${DB_HOST:-}" = "$h" ] && { host_allowed=1; break; } +done +if [ "$host_allowed" -ne 1 ]; then + echo "[setup-test-db] ⚠️ 拒绝在非白名单 host (${DB_HOST}) 上执行 DROP DATABASE" >&2 + echo " 当前白名单:${ALLOWED_HOSTS}" >&2 + echo " 加入 host:在 .env.local 追加 TEST_DB_ALLOWED_HOSTS=\" \"" >&2 + echo " 一次性绕过:TEST_DB_ALLOW_REMOTE=1 $0" >&2 + [ "${TEST_DB_ALLOW_REMOTE:-0}" = "1" ] || exit 1 +fi + +# 防护 2:schema 名需像测试/开发库(含 test / _dev / _local),否则要求显式确认 +case "${DB_SCHEMA:-}" in + *test*|*_dev|*_local|*_ci) + ;; + *) + echo "[setup-test-db] ⚠️ schema '${DB_SCHEMA}' 不像测试库(期望命名含 test / _dev / _local / _ci)" >&2 + echo " 如确为期望行为,请显式声明:TEST_DB_ALLOW_PROD_NAME=1 $0" >&2 + [ "${TEST_DB_ALLOW_PROD_NAME:-0}" = "1" ] || exit 1 + ;; +esac + +# 防护 3:显式 banner,让人看见自己在 drop 什么;远程 host 额外提示白名单内容 +echo "[setup-test-db] 即将 DROP + CREATE \`${DB_SCHEMA}\` on ${DB_HOST}:${DB_PORT}" +case "${DB_HOST:-}" in + localhost|127.0.0.1|::1) ;; + *) + echo "[setup-test-db] ⚠️ 目标是 **远程** host(已在 TEST_DB_ALLOWED_HOSTS 白名单中,每次 test.sh 都会 DROP)" + echo "[setup-test-db] 当前白名单: ${ALLOWED_HOSTS}" + echo "[setup-test-db] 若不希望每次自动 DROP,从 .env.local 的 TEST_DB_ALLOWED_HOSTS 删掉此 host" + ;; +esac + +MYSQL_CMD="mysql -h${DB_HOST} -P${DB_PORT} -u${DB_USER} -p${DB_PASSWORD}" + +$MYSQL_CMD -e "DROP DATABASE IF EXISTS \`${DB_SCHEMA}\`; CREATE DATABASE \`${DB_SCHEMA}\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" + +echo "[setup-test-db] done — schema will be applied by Flyway when Spring Boot starts" diff --git b/scripts/test.sh a/scripts/test.sh new file mode 100755 index 0000000..2841486 --- /dev/null +++ a/scripts/test.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +# scripts/test.sh —— 合并到默认分支(main / master)前的测试闸门。 +# 顺序:detect → setup-db → build → lint → unit+integration → e2e → reset-db +# 由 .githooks/pre-push 和 test-gate skill(通过子会话)调用。 + +set -euo pipefail + +PROJECT_ROOT="$(cd "$(dirname "$0")/.." && pwd)" +cd "$PROJECT_ROOT" + +# Stack detection (runtime, mode-agnostic) +HAS_BACKEND=0; [ -d backend ] && HAS_BACKEND=1 +HAS_FRONTEND=0; [ -d frontend ] && HAS_FRONTEND=1 +if [ $HAS_BACKEND -eq 0 ] && [ $HAS_FRONTEND -eq 0 ]; then + echo "[test.sh] FATAL: neither backend/ nor frontend/ exists" >&2 + exit 1 +fi + +echo "[test.sh] 1/6 setup test db" +./scripts/setup-test-db.sh + +echo "[test.sh] 2/6 build" +if [ $HAS_BACKEND -eq 1 ]; then (cd backend && ./mvnw -B -DskipTests package); else echo "[test.sh] skip backend build"; fi +if [ $HAS_FRONTEND -eq 1 ]; then (cd frontend && pnpm install --frozen-lockfile && pnpm build); else echo "[test.sh] skip frontend build"; fi + +echo "[test.sh] 3/6 lint" +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 +if [ $HAS_FRONTEND -eq 1 ]; then (cd frontend && pnpm lint); else echo "[test.sh] skip frontend lint"; fi + +echo "[test.sh] 4/6 unit + integration" +if [ $HAS_BACKEND -eq 1 ]; then (cd backend && ./mvnw -B test); else echo "[test.sh] skip backend test"; fi +if [ $HAS_FRONTEND -eq 1 ]; then (cd frontend && pnpm test -- --run); else echo "[test.sh] skip frontend test"; fi + +echo "[test.sh] 5/6 E2E" +if [ $HAS_FRONTEND -eq 1 ] && [ -f frontend/playwright.config.ts ]; then (cd frontend && pnpm exec playwright test); else echo "[test.sh] e2e 略"; fi + +echo "[test.sh] 6/6 reset test db" +./scripts/setup-test-db.sh + +echo "[test.sh] GREEN" diff --git b/sql/migrations/.gitkeep a/sql/migrations/.gitkeep new file mode 100644 index 0000000..e69de29 --- /dev/null +++ a/sql/migrations/.gitkeep diff --git b/sql/migrations/V1__initial_schema.sql a/sql/migrations/V1__initial_schema.sql new file mode 100644 index 0000000..c341d90 --- /dev/null +++ a/sql/migrations/V1__initial_schema.sql @@ -0,0 +1,141 @@ +-- Flyway migration V1 — initial schema for 小羚羊 +-- Generated: 2026-05-14T01:37:50Z +-- Source: 由 A4 db-init 从 docs/03-数据库设计文档.md 翻译生成(schema SSoT 是 docs/03) +-- This is the FIRST migration; subsequent schema changes must be written as new files sql/migrations/V2__.sql, V3__... etc. +-- Apply: Flyway runs this automatically at Spring Boot startup. +-- Do not hand-edit this file after it is committed; write a new migration instead. + +-- =========================================================================== +-- t_user — 系统用户主表,承载登录认证与基础属性 +-- =========================================================================== +CREATE TABLE `t_user` ( + `iIncrement` INT NOT NULL AUTO_INCREMENT COMMENT '整数主键 ID(标准列)', + `sId` VARCHAR(100) NULL DEFAULT (UUID()) COMMENT '业务 ID(标准列)', + `sBrandsId` VARCHAR(100) NULL DEFAULT '1111111111' COMMENT '品牌 ID(多租户隔离,标准列)', + `sSubsidiaryId` VARCHAR(100) NULL DEFAULT '1111111111' COMMENT '子公司 ID(组织层级隔离,标准列)', + `tCreateDate` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间(标准列)', + `sUserNo` VARCHAR(50) NOT NULL COMMENT '用户号;关联职员后自动同步员工号;系统内唯一', + `sUserName` VARCHAR(50) NOT NULL COMMENT '登录用户名;系统内唯一;3-50 位', + `iEmployeeId` INT NULL DEFAULT NULL COMMENT '关联职员 t_employee.iIncrement;可空(非员工账号如系统管理员)', + `sPasswordHash` VARCHAR(255) NOT NULL COMMENT '密码哈希(BCrypt / Argon2);禁止明文;初始密码 666666 哈希后存入', + `sUserType` VARCHAR(20) NOT NULL DEFAULT 'NORMAL' COMMENT '用户类型枚举:NORMAL(普通用户)/ SUPER_ADMIN(超级管理员)', + `sLanguage` VARCHAR(10) NOT NULL DEFAULT 'zh-CN' COMMENT '语言枚举:zh-CN(中文)/ en-US(英文)/ zh-TW(繁体)', + `bModifyDoc` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '单据修改权限:0 否 / 1 是', + `bVoid` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '作废标记(软删除):0 启用 / 1 已作废', + `iLoginFailCount` INT NOT NULL DEFAULT 0 COMMENT '连续登录失败次数;达到阈值触发临时锁定;登录成功后清零', + `tLockUntil` DATETIME NULL DEFAULT NULL COMMENT '锁定截止时间;NULL 表示未锁定', + `tLastLoginDate` DATETIME NULL DEFAULT NULL COMMENT '最近一次登录时间', + `sCreator` VARCHAR(100) NULL DEFAULT NULL COMMENT '制单人(创建该账号的操作员用户名)', + PRIMARY KEY (`iIncrement`), + UNIQUE KEY `uk_user_username` (`sUserName`), + UNIQUE KEY `uk_user_userno` (`sUserNo`), + KEY `idx_user_employee` (`iEmployeeId`), + KEY `idx_user_tenant` (`sBrandsId`, `sSubsidiaryId`), + KEY `idx_user_void` (`bVoid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='系统用户主表,承载登录认证与基础属性'; + +-- =========================================================================== +-- t_employee — 公司职员主档 +-- =========================================================================== +CREATE TABLE `t_employee` ( + `iIncrement` INT NOT NULL AUTO_INCREMENT COMMENT '整数主键 ID(标准列)', + `sId` VARCHAR(100) NULL DEFAULT (UUID()) COMMENT '业务 ID(标准列)', + `sBrandsId` VARCHAR(100) NULL DEFAULT '1111111111' COMMENT '品牌 ID(多租户隔离,标准列)', + `sSubsidiaryId` VARCHAR(100) NULL DEFAULT '1111111111' COMMENT '子公司 ID(组织层级隔离,标准列)', + `tCreateDate` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间(标准列)', + `sEmployeeNo` VARCHAR(50) NOT NULL COMMENT '员工号;系统内唯一', + `sName` VARCHAR(100) NOT NULL COMMENT '姓名', + `iDepartmentId` INT NULL DEFAULT NULL COMMENT '部门 ID,关联 t_department.iIncrement', + `sPhone` VARCHAR(20) NULL DEFAULT NULL COMMENT '手机号', + `sEmail` VARCHAR(100) NULL DEFAULT NULL COMMENT '邮箱', + `bDisabled` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否离职:0 在职 / 1 离职', + PRIMARY KEY (`iIncrement`), + UNIQUE KEY `uk_employee_no` (`sEmployeeNo`), + KEY `idx_employee_dept` (`iDepartmentId`), + KEY `idx_employee_name` (`sName`), + KEY `idx_employee_tenant` (`sBrandsId`, `sSubsidiaryId`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='公司职员主档'; + +-- =========================================================================== +-- t_department — 部门组织树 +-- =========================================================================== +CREATE TABLE `t_department` ( + `iIncrement` INT NOT NULL AUTO_INCREMENT COMMENT '整数主键 ID(标准列)', + `sId` VARCHAR(100) NULL DEFAULT (UUID()) COMMENT '业务 ID(标准列)', + `sBrandsId` VARCHAR(100) NULL DEFAULT '1111111111' COMMENT '品牌 ID(多租户隔离,标准列)', + `sSubsidiaryId` VARCHAR(100) NULL DEFAULT '1111111111' COMMENT '子公司 ID(组织层级隔离,标准列)', + `tCreateDate` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间(标准列)', + `sName` VARCHAR(100) NOT NULL COMMENT '部门名称', + `sCode` VARCHAR(50) NOT NULL COMMENT '部门编码;系统内唯一', + `iParentId` INT NULL DEFAULT NULL COMMENT '上级部门 ID,NULL 表示根部门', + `iSortOrder` INT NOT NULL DEFAULT 0 COMMENT '排序值,小者靠前', + PRIMARY KEY (`iIncrement`), + UNIQUE KEY `uk_department_code` (`sCode`), + KEY `idx_department_parent` (`iParentId`), + KEY `idx_department_tenant` (`sBrandsId`, `sSubsidiaryId`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='部门组织树'; + +-- =========================================================================== +-- t_permission — 权限分类字典 +-- =========================================================================== +CREATE TABLE `t_permission` ( + `iIncrement` INT NOT NULL AUTO_INCREMENT COMMENT '整数主键 ID(标准列)', + `sId` VARCHAR(100) NULL DEFAULT (UUID()) COMMENT '业务 ID(标准列)', + `sBrandsId` VARCHAR(100) NULL DEFAULT '1111111111' COMMENT '品牌 ID(多租户隔离,标准列)', + `sSubsidiaryId` VARCHAR(100) NULL DEFAULT '1111111111' COMMENT '子公司 ID(组织层级隔离,标准列)', + `tCreateDate` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间(标准列)', + `sCode` VARCHAR(50) NOT NULL COMMENT '权限码,例如 USR:ADD / USR:EDIT;系统内唯一', + `sName` VARCHAR(100) NOT NULL COMMENT '权限分类名称(展示用)', + `iSortOrder` INT NOT NULL DEFAULT 0 COMMENT '同分类内排序', + PRIMARY KEY (`iIncrement`), + UNIQUE KEY `uk_permission_code` (`sCode`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='权限分类字典'; + +-- =========================================================================== +-- t_user_permission — 用户-权限分类关联表 +-- =========================================================================== +CREATE TABLE `t_user_permission` ( + `iIncrement` INT NOT NULL AUTO_INCREMENT COMMENT '整数主键 ID(标准列)', + `sId` VARCHAR(100) NULL DEFAULT (UUID()) COMMENT '业务 ID(标准列)', + `sBrandsId` VARCHAR(100) NULL DEFAULT '1111111111' COMMENT '品牌 ID(多租户隔离,标准列)', + `sSubsidiaryId` VARCHAR(100) NULL DEFAULT '1111111111' COMMENT '子公司 ID(组织层级隔离,标准列)', + `tCreateDate` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间(标准列)', + `iUserId` INT NOT NULL COMMENT '用户 ID,关联 t_user.iIncrement', + `iPermissionId` INT NOT NULL COMMENT '权限分类 ID,关联 t_permission.iIncrement', + PRIMARY KEY (`iIncrement`), + UNIQUE KEY `uk_user_perm` (`iUserId`, `iPermissionId`), + KEY `idx_user_perm_perm` (`iPermissionId`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户-权限分类关联表'; + +-- =========================================================================== +-- t_company — 公司 / 版本字典 +-- =========================================================================== +CREATE TABLE `t_company` ( + `iIncrement` INT NOT NULL AUTO_INCREMENT COMMENT '整数主键 ID(标准列)', + `sId` VARCHAR(100) NULL DEFAULT (UUID()) COMMENT '业务 ID(标准列)', + `sBrandsId` VARCHAR(100) NULL DEFAULT '1111111111' COMMENT '品牌 ID(多租户隔离,标准列)', + `sSubsidiaryId` VARCHAR(100) NULL DEFAULT '1111111111' COMMENT '子公司 ID(组织层级隔离,标准列)', + `tCreateDate` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间(标准列)', + `sCode` VARCHAR(50) NOT NULL COMMENT '公司 / 版本编码;系统内唯一', + `sName` VARCHAR(100) NOT NULL COMMENT '显示名称', + PRIMARY KEY (`iIncrement`), + UNIQUE KEY `uk_company_code` (`sCode`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='公司 / 版本字典'; + +-- =========================================================================== +-- 外键约束(统一在最后追加,避免建表顺序依赖) +-- =========================================================================== +ALTER TABLE `t_user` + ADD CONSTRAINT `fk_user_employee` FOREIGN KEY (`iEmployeeId`) REFERENCES `t_employee` (`iIncrement`) ON DELETE SET NULL ON UPDATE RESTRICT; + +ALTER TABLE `t_employee` + ADD CONSTRAINT `fk_employee_department` FOREIGN KEY (`iDepartmentId`) REFERENCES `t_department` (`iIncrement`) ON DELETE SET NULL ON UPDATE RESTRICT; + +ALTER TABLE `t_department` + ADD CONSTRAINT `fk_department_parent` FOREIGN KEY (`iParentId`) REFERENCES `t_department` (`iIncrement`) ON DELETE RESTRICT ON UPDATE RESTRICT; + +ALTER TABLE `t_user_permission` + ADD CONSTRAINT `fk_userperm_user` FOREIGN KEY (`iUserId`) REFERENCES `t_user` (`iIncrement`) ON DELETE CASCADE ON UPDATE RESTRICT; + +ALTER TABLE `t_user_permission` + ADD CONSTRAINT `fk_userperm_perm` FOREIGN KEY (`iPermissionId`) REFERENCES `t_permission` (`iIncrement`) ON DELETE RESTRICT ON UPDATE RESTRICT; diff --git b/src/styles/tokens.css a/src/styles/tokens.css new file mode 100644 index 0000000..bc8a542 --- /dev/null +++ a/src/styles/tokens.css @@ -0,0 +1,43 @@ +/* + * src/styles/tokens.css — Design Tokens + * 命名规范见 docs/04-技术规范.md § 2.5 + * 色值锁定见 docs/06-UI交互规范.md § 四 + * + * 命名格式:--color--- + * 组件域:form / table-row / table-header / ... + * 作用:bg(背景)/ fg(前景/字体)/ border + * 状态:edit / readonly / hover / selected(无状态时省略) + * + * 约束: + * - 组件样式中只用 var(--color-xxx),禁止硬编码 hex / rgba + * - 修改色值只改本文件,不允许在组件级覆盖 + * - 新增 token 须先登记到 docs/06 § 4.1 / 4.2,再补到此处 + */ + +:root { + /* === 1. 全局调色板(与 Ant Design 主题对齐) === */ + --color-primary: #1890ff; + --color-success: #52c41a; + --color-warning: #faad14; + --color-error: #ff4d4f; + --color-text: rgba(0, 0, 0, 0.85); + --color-text-secondary: rgba(0, 0, 0, 0.45); + --color-border: #d9d9d9; + --color-bg-base: #f0f2f5; + + /* === 2. 组件级状态色(与 docs/06 § 4.2 一一对应) === */ + + /* form:输入框 / 备注框 / 时间框 / 下拉框共用 */ + --color-form-bg-edit: #ffffff; + --color-form-bg-readonly: #f1f2f8; + --color-form-bg-hover: #f5f5f5; /* 仅下拉框使用 */ + --color-form-fg: #000000; + + /* table */ + --color-table-row-bg-selected: #86d5fb; + --color-table-row-bg-hover: #fff7e6; + --color-table-row-bg-readonly: #f1f2f8; /* = rgb(241, 242, 248) */ + --color-table-row-fg: #000000; + --color-table-header-bg: #f5f5f5; + --color-table-header-fg: rgba(0, 0, 0, 0.85); /* = #000000D9 */ +}