feat: collaboration and statistics
This commit is contained in:
@@ -0,0 +1,155 @@
|
||||
# 后端业务功能开发规范
|
||||
|
||||
本文是本项目后端新增或修改业务功能时的权威规范。开发后台管理端业务模块时,必须优先遵循本项目现有架构,而不是套用通用 Spring Boot 三层模板。
|
||||
|
||||
## 架构原则
|
||||
|
||||
后端业务代码按 `Controller -> ApplicationService -> Model -> db Service/Mapper` 组织。
|
||||
|
||||
优先参考这些现有模块:
|
||||
|
||||
- `backend/agileboot-domain/src/main/java/com/agileboot/domain/system/post`
|
||||
- `backend/agileboot-domain/src/main/java/com/agileboot/domain/system/user`
|
||||
- `backend/agileboot-domain/src/main/java/com/agileboot/domain/system/role`
|
||||
- `backend/agileboot-domain/src/main/java/com/agileboot/domain/system/notice`
|
||||
- `backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/system`
|
||||
|
||||
不要让 Controller 直接调用 Mapper,不要把业务规则写在 Controller,也不要直接把 Entity 或 DO 返回给前端。
|
||||
|
||||
## 开发准备
|
||||
|
||||
开发新后端业务功能前,应先确认需求所属领域、涉及的数据表、接口范围、权限标识和相似模块。本文是后端业务功能开发的统一规范。
|
||||
|
||||
开始编码前必须先做一次相似模块调研,确认目标功能最接近 `post`、`user`、`role`、`notice` 还是其他模块,再按相似模块的命名、包路径、注解、分页、异常、权限、日志和测试风格实现。
|
||||
|
||||
## 目录结构
|
||||
|
||||
后台管理端入口放在 `agileboot-admin`:
|
||||
|
||||
```text
|
||||
backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/<area>/
|
||||
└── XxxController.java
|
||||
```
|
||||
|
||||
业务代码放在 `agileboot-domain`:
|
||||
|
||||
```text
|
||||
backend/agileboot-domain/src/main/java/com/agileboot/domain/<area>/xxx/
|
||||
├── XxxApplicationService.java
|
||||
├── command/
|
||||
│ ├── AddXxxCommand.java
|
||||
│ └── UpdateXxxCommand.java
|
||||
├── query/
|
||||
│ └── XxxQuery.java
|
||||
├── dto/
|
||||
│ ├── XxxDTO.java
|
||||
│ └── XxxDetailDTO.java
|
||||
├── model/
|
||||
│ ├── XxxModel.java
|
||||
│ └── XxxModelFactory.java
|
||||
└── db/
|
||||
├── XxxEntity.java
|
||||
├── XxxDO.java
|
||||
├── XxxMapper.java
|
||||
├── XxxService.java
|
||||
└── XxxServiceImpl.java
|
||||
```
|
||||
|
||||
`XxxDO` 是可选文件。只有当查询结果包含 join、聚合、报表字段或其他不完全匹配 Entity 的字段时才创建。
|
||||
|
||||
如果业务属于已有领域,例如 `system`,放到现有领域下;如果是独立业务领域,创建新的 `<area>` 包名,例如 `business`。
|
||||
|
||||
## 各层职责
|
||||
|
||||
Controller 只负责 HTTP 层:
|
||||
|
||||
- 声明 `@RestController`、`@RequestMapping`、`@Validated`、Swagger 注解。
|
||||
- 接收 `Command`、`Query`、路径参数和请求参数。
|
||||
- 使用 `@PreAuthorize("@permission.has('xxx:yyy:zzz')")` 做权限控制。
|
||||
- 对新增、修改、删除、导出等操作使用 `@AccessLog`。
|
||||
- 调用 `XxxApplicationService`。
|
||||
- 使用 `ResponseDTO.ok(...)` 返回结果。
|
||||
|
||||
ApplicationService 负责编排业务用例:
|
||||
|
||||
- 分页查询、详情查询、导出查询、新增、修改、删除。
|
||||
- 创建或加载 `XxxModel`。
|
||||
- 调用领域模型完成业务校验和状态变化。
|
||||
- 调用 `db` 包中的 Service 完成查询和持久化。
|
||||
- 将 Entity 或 DO 转换为 DTO。
|
||||
- 分页结果使用 `PageDTO<T>`。
|
||||
|
||||
Model 承载业务规则:
|
||||
|
||||
- 从 `AddXxxCommand`、`UpdateXxxCommand` 加载字段。
|
||||
- 封装唯一性校验、状态校验、是否允许删除、业务状态流转等规则。
|
||||
- 需要数据库判断时,通过构造注入的 `XxxService` 查询。
|
||||
- 业务失败时抛出 `ApiException`,不要返回布尔值让上层解释。
|
||||
|
||||
ModelFactory 负责创建和加载模型:
|
||||
|
||||
- `create()` 返回带有依赖的空模型。
|
||||
- `loadById(id)` 从数据库加载 Entity,并包装为 Model。
|
||||
- 如果未找到必要数据,按项目现有异常风格处理。
|
||||
|
||||
db 包只负责持久化:
|
||||
|
||||
- `XxxEntity` 映射数据库表。
|
||||
- `XxxDO` 承载复杂查询结果,例如关联表字段、统计字段、报表字段。
|
||||
- `XxxMapper` 继承 MyBatis Plus `BaseMapper<XxxEntity>`。
|
||||
- `XxxService` 继承 `IService<XxxEntity>`,声明复用型查询方法。
|
||||
- `XxxServiceImpl` 继承 `ServiceImpl<XxxMapper, XxxEntity>`,实现唯一性检查、关联检查、复杂查询等。
|
||||
- 复杂 SQL 放到 mapper XML,简单条件优先使用 MyBatis Plus wrapper。
|
||||
|
||||
`XxxDO` 属于数据库查询结果对象,不是前端响应对象,也不是请求对象。Mapper 或 Service 可以返回 `XxxDO`,但 ApplicationService 必须转换为 `XxxDTO` 后再交给 Controller 返回。
|
||||
|
||||
Command、Query、DTO 的边界:
|
||||
|
||||
- `Command` 用于新增、修改等写请求,不要直接接收 Entity。
|
||||
- `Query` 用于列表、搜索、分页条件,并提供 `toPage()`、`toQueryWrapper()` 等项目已有风格的方法。
|
||||
- `DTO` 用于响应前端,不要直接暴露 Entity 或 DO。
|
||||
- 批量删除优先使用 `BulkOperationCommand<Long>`。
|
||||
|
||||
## 开发流程
|
||||
|
||||
1. 检索并阅读一个最相似的现有模块,确认命名、包路径、注解、分页、异常、测试风格。
|
||||
2. 确认数据库表结构,保持主键、软删除、审计字段、字段命名与现有表一致。
|
||||
3. 创建 `db` 包:`Entity`、`Mapper`、`Service`、`ServiceImpl`;如查询结果包含关联表字段、统计字段或报表字段,再增加 `XxxDO`。
|
||||
4. 创建 API 契约:`AddXxxCommand`、`UpdateXxxCommand`、`XxxQuery`、`XxxDTO`,需要详情时添加 `XxxDetailDTO`。
|
||||
5. 创建 `XxxModel` 和 `XxxModelFactory`,把业务校验放进 Model。
|
||||
6. 创建 `XxxApplicationService`,编排查询、新增、修改、删除、导出等用例。
|
||||
7. 创建 `XxxController`,加路由、权限、操作日志和统一响应。
|
||||
8. 如功能出现在后台菜单,补充权限标识、菜单 SQL 或清楚说明需要配置的权限码。
|
||||
9. 增加聚焦测试,至少覆盖核心业务校验和主要用例。
|
||||
10. 运行相关 Maven 测试或编译检查;如果不能运行,说明原因。
|
||||
|
||||
开发过程中如果发现本文未覆盖的模式,优先参考现有模块,而不是引入新的架构风格。确实需要新增模式时,应先更新本文,再按新规范实现。
|
||||
|
||||
## 权限、日志和错误
|
||||
|
||||
权限码必须和前端菜单或按钮权限保持一致,格式参考现有系统功能:
|
||||
|
||||
```java
|
||||
@PreAuthorize("@permission.has('system:post:add')")
|
||||
```
|
||||
|
||||
操作日志按业务动作选择类型:
|
||||
|
||||
```java
|
||||
@AccessLog(title = "业务名称", businessType = BusinessTypeEnum.ADD)
|
||||
@AccessLog(title = "业务名称", businessType = BusinessTypeEnum.MODIFY)
|
||||
@AccessLog(title = "业务名称", businessType = BusinessTypeEnum.DELETE)
|
||||
@AccessLog(title = "业务名称", businessType = BusinessTypeEnum.EXPORT)
|
||||
```
|
||||
|
||||
业务错误使用 `ApiException` 和 `ErrorCode.Business`。新增业务错误时,按现有错误码结构补充枚举或常量,不要在 Controller 中拼接错误响应。
|
||||
|
||||
## 验收清单
|
||||
|
||||
- 新代码目录结构与相似模块一致。
|
||||
- Controller 只有 HTTP 编排逻辑。
|
||||
- 业务校验位于 Model 或 ApplicationService,不散落在前端或 Controller。
|
||||
- 查询返回 DTO,分页返回 `PageDTO<T>`。
|
||||
- 写请求使用 Command。
|
||||
- 复杂查询结果对象使用 DO,并在 ApplicationService 转换为 DTO。
|
||||
- 权限、日志、异常、测试和现有项目风格一致。
|
||||
@@ -0,0 +1,275 @@
|
||||
# Clean Code 契约
|
||||
|
||||
本文约束新增和修改代码时的代码质量形态。它只关注函数设计、命名、控制结构、错误处理、注释、副作用、重复和可读性,不替代前后端各自的架构开发规范。
|
||||
|
||||
## 1. 函数设计
|
||||
|
||||
- 新增或修改的函数默认不超过 20 行,不含空行和注释。
|
||||
- 业务编排函数最多不超过 30 行;超过时必须拆分。
|
||||
- 函数参数不超过 3 个;超过 3 个必须改为对象参数。
|
||||
- 圈复杂度 <= 5。
|
||||
- 嵌套深度 <= 2。
|
||||
- 必须优先使用 guard clause 提前返回,避免主流程被多层 `if` 包裹。
|
||||
- 一个函数只做一件事,不混合校验、转换、查询、保存、渲染等多个职责。
|
||||
|
||||
Bad:
|
||||
|
||||
```ts
|
||||
function submit(user, form, config, notify) {
|
||||
if (user) {
|
||||
if (form.valid) {
|
||||
if (config.enabled) {
|
||||
save(user, form);
|
||||
notify.success();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Good:
|
||||
|
||||
```ts
|
||||
function submit(options: SubmitOptions) {
|
||||
if (!options.user) return;
|
||||
if (!options.form.valid) return;
|
||||
if (!options.config.enabled) return;
|
||||
|
||||
save(options.user, options.form);
|
||||
options.notify.success();
|
||||
}
|
||||
```
|
||||
|
||||
## 2. 命名
|
||||
|
||||
- 布尔变量必须以 `is`、`has`、`can`、`should` 开头。
|
||||
- 执行动作的函数使用动词开头,例如 `getUser`、`validateEmail`、`saveRole`。
|
||||
- 事件处理函数使用 `handle` 或 `on` 前缀,例如 `handleSubmit`、`onDialogClose`。
|
||||
- 有副作用的函数名必须体现动作,例如 `save`、`update`、`delete`、`load`、`fetch`、`sync`。
|
||||
- 避免无意义缩写。
|
||||
- 避免单字母变量,循环索引 `i` 除外。
|
||||
|
||||
Bad:
|
||||
|
||||
```ts
|
||||
const visible = user.status === "enabled";
|
||||
|
||||
function userData() {
|
||||
return api.getUser();
|
||||
}
|
||||
```
|
||||
|
||||
Good:
|
||||
|
||||
```ts
|
||||
const isVisible = user.status === "enabled";
|
||||
|
||||
function getUserData() {
|
||||
return api.getUser();
|
||||
}
|
||||
```
|
||||
|
||||
## 3. 控制结构
|
||||
|
||||
- 禁止深层 `if/else`。
|
||||
- 连续分支超过 3 个时,优先使用对象映射、`switch`、策略方法或枚举方法。
|
||||
- 简单数据转换优先使用 `map`、`filter`。
|
||||
- 不为了“函数式”强行使用难读的 `reduce`。
|
||||
- 当需要提前退出、复杂流程控制或显式循环更清晰时,允许使用 `for` / `for...of`。
|
||||
- 不允许在 `switch` 或长 `if/else` 中堆大量业务逻辑,复杂分支必须拆成独立函数。
|
||||
|
||||
Bad:
|
||||
|
||||
```ts
|
||||
function getStatusLabel(status: string) {
|
||||
if (status === "enabled") return "启用";
|
||||
if (status === "disabled") return "禁用";
|
||||
if (status === "pending") return "待处理";
|
||||
if (status === "locked") return "锁定";
|
||||
return "未知";
|
||||
}
|
||||
```
|
||||
|
||||
Good:
|
||||
|
||||
```ts
|
||||
const statusLabels: Record<string, string> = {
|
||||
enabled: "启用",
|
||||
disabled: "禁用",
|
||||
pending: "待处理",
|
||||
locked: "锁定"
|
||||
};
|
||||
|
||||
function getStatusLabel(status: string) {
|
||||
return statusLabels[status] ?? "未知";
|
||||
}
|
||||
```
|
||||
|
||||
## 4. 错误处理
|
||||
|
||||
- 禁止吞异常。
|
||||
- 禁止空 `catch`。
|
||||
- `catch` 后必须处理、记录、转换或重新抛出异常。
|
||||
- 外部调用必须有明确错误处理,包括网络、数据库、文件、缓存、第三方服务。
|
||||
- 不允许只打印错误但不中断、不反馈、不恢复状态。
|
||||
- 不允许把异常转换成无意义的 `null`、`false` 或空数组,除非调用方能明确区分该状态。
|
||||
|
||||
Bad:
|
||||
|
||||
```ts
|
||||
async function loadUser() {
|
||||
try {
|
||||
return await api.getUser();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Good:
|
||||
|
||||
```ts
|
||||
async function loadUser() {
|
||||
try {
|
||||
return await api.getUser();
|
||||
} catch (error) {
|
||||
showErrorMessage("用户信息加载失败");
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 5. 注释
|
||||
|
||||
- 禁止废话注释,例如 `i++ // 增加 i`。
|
||||
- 代码能清楚表达“做了什么”时,不写解释性注释。
|
||||
- 复杂算法、特殊兼容、业务规则例外必须说明“为什么这么做”。
|
||||
- 临时方案必须标明原因和后续处理方式,避免无上下文的 `TODO`。
|
||||
- 注释必须随代码更新,禁止保留过期注释。
|
||||
|
||||
Bad:
|
||||
|
||||
```ts
|
||||
// 遍历用户
|
||||
users.forEach(user => {
|
||||
// 设置名称
|
||||
user.name = user.name.trim();
|
||||
});
|
||||
```
|
||||
|
||||
Good:
|
||||
|
||||
```ts
|
||||
// 后端历史数据可能包含首尾空格,提交前统一清理,避免唯一性校验误判。
|
||||
const normalizedUsers = users.map(user => ({
|
||||
...user,
|
||||
name: user.name.trim()
|
||||
}));
|
||||
```
|
||||
|
||||
## 6. 副作用
|
||||
|
||||
- 优先编写纯函数:相同输入应产生相同输出。
|
||||
- 数据转换、格式化、校验逻辑应尽量保持无副作用。
|
||||
- 修改外部状态的函数必须在命名中体现副作用。
|
||||
- 不在工具函数中偷偷修改入参、全局状态、缓存或组件状态。
|
||||
- 修改入参前必须显式说明原因;默认应返回新对象或新数组。
|
||||
|
||||
Bad:
|
||||
|
||||
```ts
|
||||
function normalizeUser(user: User) {
|
||||
user.name = user.name.trim();
|
||||
return user;
|
||||
}
|
||||
```
|
||||
|
||||
Good:
|
||||
|
||||
```ts
|
||||
function normalizeUser(user: User) {
|
||||
return {
|
||||
...user,
|
||||
name: user.name.trim()
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## 7. 重复与抽象
|
||||
|
||||
- 禁止复制粘贴相同业务逻辑。
|
||||
- 出现第 2 次重复时可以接受局部重复。
|
||||
- 出现第 3 次重复时必须抽取公共函数、组件、常量或配置。
|
||||
- 不为了“预防未来变化”提前抽象。
|
||||
- 抽象必须基于已经出现的重复或明确稳定的业务概念。
|
||||
- 公共函数必须有清晰输入输出,不能依赖隐式上下文。
|
||||
|
||||
Bad:
|
||||
|
||||
```ts
|
||||
const userStatus = row.status === 1 ? "启用" : "禁用";
|
||||
const roleStatus = role.status === 1 ? "启用" : "禁用";
|
||||
const postStatus = post.status === 1 ? "启用" : "禁用";
|
||||
```
|
||||
|
||||
Good:
|
||||
|
||||
```ts
|
||||
function getEnabledStatusLabel(status: number) {
|
||||
return status === 1 ? "启用" : "禁用";
|
||||
}
|
||||
|
||||
const userStatus = getEnabledStatusLabel(row.status);
|
||||
const roleStatus = getEnabledStatusLabel(role.status);
|
||||
const postStatus = getEnabledStatusLabel(post.status);
|
||||
```
|
||||
|
||||
## 8. 可读性
|
||||
|
||||
- 优先让主流程从上到下阅读。
|
||||
- 变量声明尽量靠近使用位置。
|
||||
- 避免长链式调用;链式调用过长时拆成中间变量。
|
||||
- 避免布尔表达式过长;复杂条件必须提取为语义明确的变量或函数。
|
||||
- 避免魔法值;业务含义明确的数字、字符串应提取为常量。
|
||||
- 不写“聪明但难懂”的代码。
|
||||
|
||||
Bad:
|
||||
|
||||
```ts
|
||||
if ((user.age > 18 && user.status === 1 && !user.deleted) || user.role === "admin") {
|
||||
allowAccess();
|
||||
}
|
||||
```
|
||||
|
||||
Good:
|
||||
|
||||
```ts
|
||||
const isActiveAdult = user.age > 18 && user.status === 1 && !user.deleted;
|
||||
const isAdmin = user.role === "admin";
|
||||
|
||||
if (isActiveAdult || isAdmin) {
|
||||
allowAccess();
|
||||
}
|
||||
```
|
||||
|
||||
## 9. 自动化检查
|
||||
|
||||
完成代码后必须运行与改动相关的检查。
|
||||
|
||||
建议静态检查覆盖:
|
||||
|
||||
- 函数长度
|
||||
- 参数数量
|
||||
- 圈复杂度
|
||||
- 嵌套深度
|
||||
- 空 `catch`
|
||||
- 未使用变量
|
||||
- 重复分支
|
||||
- 基础命名规则
|
||||
|
||||
## 10. 例外规则
|
||||
|
||||
- 如果确实需要突破本契约,必须说明原因。
|
||||
- 例外只能针对具体代码点,不能作为整个模块放宽标准的理由。
|
||||
- 为了满足行数限制而机械拆分、降低可读性的改动不接受。
|
||||
- 可读性优先于形式主义,但必须能解释为什么更清晰。
|
||||
@@ -0,0 +1,198 @@
|
||||
# Web 前端业务功能开发规范
|
||||
|
||||
本文是 `frontend/web` 新增或修改前端业务功能时的权威规范。当前 Web 项目基于 Vue 3、Vite、Element Plus、Pure Admin、Pinia、Vue Router 和项目自有 `@/utils/http` 封装。开发时必须优先复用现有目录结构、组件和工具链。
|
||||
|
||||
本文只覆盖 `frontend/web`,不覆盖 `frontend/app`。
|
||||
|
||||
## 架构原则
|
||||
|
||||
前端业务模块按“页面结构 + API 类型封装 + 业务 hook + 表单/弹窗 + 页面私有组件”组织。
|
||||
|
||||
优先参考这些现有模块:
|
||||
|
||||
- `frontend/web/src/views/system/post`
|
||||
- `frontend/web/src/views/system/user`
|
||||
- `frontend/web/src/views/system/role`
|
||||
- `frontend/web/src/views/system/notice`
|
||||
- `frontend/web/src/views/login`
|
||||
- `frontend/web/src/api/system/post.ts`
|
||||
|
||||
不要绕过 `@/utils/http` 直接使用 Axios。不要在 `index.vue` 中堆积复杂列表逻辑。不要硬编码字典状态文本、值或标签样式。
|
||||
|
||||
## 开发准备
|
||||
|
||||
开发 `frontend/web` 业务功能前,应先确认页面所属业务域、后端接口、菜单路由、权限标识、字典依赖和相似页面。本文是 Web 前端业务功能开发的统一规范。
|
||||
|
||||
开始编码前必须先做一次相似模块调研,确认目标功能最接近 `system/post`、`system/user`、`system/role`、`system/notice`、`login` 还是其他模块,再按相似模块的页面结构、API 类型、hook、表单、字典、表格、分页、排序、删除、导出、权限和组件命名风格实现。
|
||||
|
||||
## 目录结构
|
||||
|
||||
业务页面放在 `frontend/web/src/views/<area>/<module>/`:
|
||||
|
||||
```text
|
||||
frontend/web/src/views/<area>/<module>/
|
||||
├── index.vue
|
||||
├── form.vue 或 xxx-form-modal.vue
|
||||
├── components/
|
||||
│ ├── private-panel.vue
|
||||
│ └── private-fragment.vue
|
||||
└── utils/
|
||||
├── hook.tsx
|
||||
├── rule.ts
|
||||
└── types.ts
|
||||
```
|
||||
|
||||
API 放在 `frontend/web/src/api/<area>/<module>.ts`:
|
||||
|
||||
```text
|
||||
frontend/web/src/api/<area>/<module>.ts
|
||||
```
|
||||
|
||||
`components/`、`rule.ts`、`types.ts` 按需创建,不要求每个模块都存在。
|
||||
|
||||
## 页面私有组件
|
||||
|
||||
只被某个页面或模块使用的组件,放在该模块目录下的 `components/`。现有示例是 `frontend/web/src/views/login/components`。
|
||||
|
||||
页面私有组件适合承载:
|
||||
|
||||
- 页面内局部展示组件。
|
||||
- 局部表单片段。
|
||||
- 局部抽屉、局部面板、局部弹窗。
|
||||
- 只服务当前页面流程的交互单元。
|
||||
|
||||
页面私有组件不应承载整个业务模块的主流程。主列表、主表单、主弹窗仍按现有模块风格放在 `index.vue`、`form.vue` 或 `xxx-form-modal.vue`。
|
||||
|
||||
如果组件开始被第二个业务模块复用,应提升到 `frontend/web/src/components`,不要继续放在某个页面私有目录中。只有在明确属于某个业务域且多个同域模块复用时,才考虑创建 `views/<area>/components`;当前项目还没有这个模式,新增前应谨慎。
|
||||
|
||||
页面私有组件的判断标准是“是否只服务当前页面或当前模块”。如果组件包含通用交互、通用展示、通用上传、通用选择器等能力,应优先设计为公共组件,而不是藏在某个业务页面目录下。
|
||||
|
||||
## 各文件职责
|
||||
|
||||
`index.vue` 负责页面结构:
|
||||
|
||||
- 搜索表单。
|
||||
- 表格和表格插槽。
|
||||
- 工具栏按钮。
|
||||
- 弹窗、抽屉、页面私有组件的挂载。
|
||||
- `defineOptions({ name })`,组件 name 应与菜单表中的 `router_name` 保持一致。
|
||||
|
||||
`utils/hook.tsx` 负责列表业务状态和行为:
|
||||
|
||||
- 查询参数、分页、排序、loading。
|
||||
- 表格列定义。
|
||||
- 列表查询、搜索、重置。
|
||||
- 删除、批量删除、导出。
|
||||
- 字典值渲染,例如 `el-tag`。
|
||||
|
||||
`form.vue` 或 `xxx-form-modal.vue` 负责新增/编辑:
|
||||
|
||||
- 接收 `v-model` 控制显示。
|
||||
- 接收 `type`、`row` 等必要 props。
|
||||
- 维护表单数据和校验状态。
|
||||
- 调用新增/修改 API。
|
||||
- 成功后关闭并向父组件发出 `success` 事件。
|
||||
|
||||
`utils/rule.ts` 负责复杂或可复用的表单校验规则。简单模块也可以把规则放在表单组件内,保持与现有代码一致。
|
||||
|
||||
`utils/types.ts` 只放页面私有类型。跨 API 复用的请求/响应类型优先放在对应 `src/api/<area>/<module>.ts`。
|
||||
|
||||
## API 规范
|
||||
|
||||
每个业务模块在 `src/api/<area>/<module>.ts` 中封装后端接口和 TypeScript 类型。
|
||||
|
||||
接口调用使用项目 HTTP 封装:
|
||||
|
||||
```ts
|
||||
http.request<ResponseData<T>>("get", "/path", { params });
|
||||
http.request<ResponseData<void>>("post", "/path", { data });
|
||||
```
|
||||
|
||||
文件下载使用:
|
||||
|
||||
```ts
|
||||
http.download("/path/excel", fileName, { params });
|
||||
```
|
||||
|
||||
类型命名优先遵循现有风格:
|
||||
|
||||
- `XxxListCommand`:列表查询参数。
|
||||
- `XxxPageResponse`:列表响应行。
|
||||
- `AddXxxCommand`:新增请求。
|
||||
- `UpdateXxxCommand`:修改请求。
|
||||
|
||||
删除接口如果后端接收 `ids` 查询参数,按现有写法把数组转成字符串,避免 Axios 序列化为 `ids[0]`、`ids[1]`。
|
||||
|
||||
## 列表、分页和排序
|
||||
|
||||
列表页优先使用 `PureTableBar + pure-table`。
|
||||
|
||||
分页使用 `PaginationProps`,并通过 `CommonUtils.fillPaginationParams()` 填充查询参数。
|
||||
|
||||
排序使用 Element Plus `Sort`,并通过 `CommonUtils.fillSortParams()` 传给后端。
|
||||
|
||||
时间范围查询使用 `beginTime`、`endTime`,优先复用现有 computed 写法处理 `el-date-picker` 的双向绑定。
|
||||
|
||||
批量操作必须维护 `multipleSelection`,并在空选择时给出提示。
|
||||
|
||||
## 字典和状态
|
||||
|
||||
字典来自登录配置并缓存在用户 store 中。
|
||||
|
||||
下拉选项使用:
|
||||
|
||||
```ts
|
||||
useUserStoreHook().dictionaryList["common.status"]
|
||||
```
|
||||
|
||||
表格状态渲染使用:
|
||||
|
||||
```ts
|
||||
useUserStoreHook().dictionaryMap["common.status"]
|
||||
```
|
||||
|
||||
不要在页面中硬编码状态 label、value、`el-tag` 类型。字典不存在或可能为空时,新增代码应避免直接访问导致运行时报错。
|
||||
|
||||
## 交互和组件
|
||||
|
||||
按钮图标优先使用 `useRenderIcon()` 和现有 iconify 图标。
|
||||
|
||||
新增/编辑弹窗优先复用 `VDialog` 或相似模块的弹窗写法。
|
||||
|
||||
单条删除使用 `el-popconfirm` 或模块现有风格。批量删除使用 `ElMessageBox.confirm` 时,必须处理取消分支并清理选择状态。
|
||||
|
||||
操作成功后刷新列表。弹窗通过 `v-model` 和 `success` 事件与父组件通信。
|
||||
|
||||
跨业务通用能力优先放在 `src/components` 或 `src/utils`;页面私有能力保持在页面目录内。
|
||||
|
||||
## 权限、路由和菜单
|
||||
|
||||
后台业务菜单主要依赖后端菜单和动态路由。新页面的组件 name 必须与菜单表中的 `router_name` 保持一致。
|
||||
|
||||
不要随意新增静态路由。只有登录页、重定向、个人中心等固定页面才参考 `src/router/modules` 中的静态路由模式。
|
||||
|
||||
如需要按钮权限,优先复用项目已有的 `ReAuth` 或 auth directive 模式,不要自行实现一套权限判断。
|
||||
|
||||
## 验证清单
|
||||
|
||||
前端改动后优先运行:
|
||||
|
||||
```bash
|
||||
pnpm --dir frontend/web typecheck
|
||||
pnpm --dir frontend/web lint
|
||||
```
|
||||
|
||||
如果依赖未安装或环境不允许运行,说明原因。
|
||||
|
||||
如果只修改文档,不需要运行前端 typecheck 或 lint;如果修改了 TypeScript、Vue、样式或路由/API 文件,应优先运行上述检查。
|
||||
|
||||
业务页面至少检查:
|
||||
|
||||
- 列表加载。
|
||||
- 搜索和重置。
|
||||
- 分页和排序。
|
||||
- 新增和编辑。
|
||||
- 单条删除和批量删除。
|
||||
- 导出。
|
||||
- 字典下拉和表格状态展示。
|
||||
- 弹窗关闭、成功回调和列表刷新。
|
||||
Reference in New Issue
Block a user