feat: 完善员工门店软删除与密码管理

This commit is contained in:
湛兮
2026-05-26 18:01:52 +08:00
parent 6b31ea7bbf
commit e8f05513fd
19 changed files with 597 additions and 73 deletions
+142 -17
View File
@@ -107,13 +107,13 @@ pw111111
| 权限码 | 说明 |
| --- | --- |
| `*` | 超级管理员,拥有全部权限 |
| `store:view` | 查看门店 |
| `store:manage` | 新增、修改、删除门店 |
| `store:view` | 查看门店和门店下员工 |
| `store:manage` | 新增、修改、停用、软删除门店 |
| `role:view` | 查看角色 |
| `role:manage` | 新增、修改、删除自定义角色 |
| `role:manage` | 新增、修改、删除自定义角色 |
| `employee:view:all` | 查看全部门店员工 |
| `employee:view:store` | 查看当前门店员工 |
| `employee:manage` | 新增、修改、删除员工 |
| `employee:manage` | 新增、修改、启停、移除、软删除员工和维护密码 |
| `permission:view` | 查看权限策略 |
| `permission:manage` | 分配角色权限 |
@@ -192,14 +192,23 @@ interface Role extends RoleOption {
```ts
type EmployeeStatus = "ACTIVE" | "INACTIVE";
type EmployeeStoreStatus = "ACTIVE" | "INACTIVE";
interface EmployeeStatusTag {
code: "EMPLOYEE_ACTIVE" | "EMPLOYEE_INACTIVE" | "STORE_INACTIVE";
label: string;
variant: "success" | "warning" | "default";
}
interface Employee {
id: number;
storeId: number;
storeName: string;
storeStatus: EmployeeStoreStatus;
name: string;
phone: string;
status: EmployeeStatus;
statusTags: EmployeeStatusTag[];
remark: string | null;
roles: Array<{
id: number;
@@ -238,20 +247,24 @@ interface PermissionMenu {
| `PUT` | `/api/permissions/roles/:roleId` | 是 | `permission:manage` | 更新角色权限 |
| `GET` | `/api/stores` | 是 | `store:view` | 门店列表或门店下拉选项 |
| `GET` | `/api/stores/:id` | 是 | `store:view` | 门店详情 |
| `GET` | `/api/stores/:id/employees` | 是 | `store:view` | 门店员工列表 |
| `POST` | `/api/stores` | 是 | `store:manage` | 新增门店 |
| `PATCH` | `/api/stores/:id` | 是 | `store:manage` | 修改门店 |
| `DELETE` | `/api/stores/:id` | 是 | `store:manage` | 删除门店 |
| `DELETE` | `/api/stores/:storeId/employees/:employeeId` | 是 | `employee:manage` | 从门店移除员工 |
| `DELETE` | `/api/stores/:id` | 是 | `store:manage` | 软删除门店 |
| `GET` | `/api/roles` | 是 | `role:view` | 角色列表 |
| `GET` | `/api/roles/:id` | 是 | `role:view` | 角色详情 |
| `POST` | `/api/roles` | 是 | `role:manage` | 新增自定义角色 |
| `PATCH` | `/api/roles/:id` | 是 | `role:manage` | 修改自定义角色 |
| `DELETE` | `/api/roles/:id` | 是 | `role:manage` | 删除自定义角色 |
| `DELETE` | `/api/roles/:id` | 是 | `role:manage` | 删除自定义角色 |
| `GET` | `/api/employees` | 是 | `employee:view:all``employee:view:store` | 员工分页列表 |
| `GET` | `/api/employees/:id` | 是 | `employee:view:all` 或当前门店 `employee:view:store` | 员工详情 |
| `POST` | `/api/employees` | 是 | `employee:manage` | 新增员工 |
| `PATCH` | `/api/employees/:id` | 是 | `employee:manage` | 修改员工 |
| `PATCH` | `/api/employees/:id/status` | 是 | `employee:manage` | 修改员工状态 |
| `DELETE` | `/api/employees/:id` | 是 | `employee:manage` | 删除员工 |
| `PATCH` | `/api/employees/:id/password` | 是 | `employee:manage` | 修改员工密码 |
| `PATCH` | `/api/employees/:id/password/reset` | 是 | `employee:manage` | 重置员工为初始密码 |
| `DELETE` | `/api/employees/:id` | 是 | `employee:manage` | 软删除员工 |
## 健康检查
@@ -326,6 +339,8 @@ interface PermissionMenu {
- 超级管理员使用 `super_admins.username` 登录。
- 员工使用手机号登录。
- 登录会先校验密码;密码正确但账号停用时返回 `401 UNAUTHORIZED`,消息为 `账号已被禁用`
- 员工所属门店停用时返回 `401 UNAUTHORIZED`,消息为 `所属门店已被禁用`
- 员工必须拥有后台菜单权限,否则返回 `401 UNAUTHORIZED`,消息为 `当前账号没有后台登录权限`
- `cashier``kitchen``part_time` 默认没有后台登录权限。
@@ -336,6 +351,7 @@ interface PermissionMenu {
### POST /api/auth/employee/login
员工端登录入口。员工使用手机号和密码登录,不要求后台管理权限。
密码正确但员工账号停用时返回 `账号已被禁用`;所属门店停用时返回 `所属门店已被禁用`
请求体:
@@ -555,7 +571,7 @@ Authorization: Bearer <token>
更新指定角色拥有的权限点。需要 `permission:manage`
后端只接受 `GET /api/permissions/definitions` 返回的权限码。保存时会自动补齐依赖权限,例如提交 `permission:manage` 会自动保留 `permission:view`
后端只接受 `GET /api/permissions/definitions` 返回的权限码。保存时会自动补齐依赖权限,例如提交 `permission:manage` 会自动保留 `permission:view`本次保存中被移除的权限关系会写入 `role_permissions.deleted_at`,不会物理删除关系行。
请求:
@@ -694,7 +710,24 @@ Authorization: Bearer <token>
| --- | --- | --- |
| `id` | `number` | 正整数 |
响应 `data` `Store`
响应 `data`门店详情,结构是在 `Store` 基础上增加 `employees: Employee[]`
门店停用后,员工仍保留在该门店下。员工对象会返回:
- `storeStatus: "INACTIVE"`
- `statusTags` 中包含 `{ "code": "STORE_INACTIVE", "label": "门店被禁用", "variant": "warning" }`
### GET /api/stores/:id/employees
查询某个门店下的员工。需要 `store:view`
路径参数:
| 参数 | 类型 | 约束 |
| --- | --- | --- |
| `id` | `number` | 正整数 |
响应 `data``Employee[]`。员工对象会包含 `storeStatus``statusTags`,供前端直接展示员工状态和门店禁用标签。
### POST /api/stores
@@ -760,7 +793,33 @@ Authorization: Bearer <token>
- `404 NOT_FOUND`:门店不存在。
- `409 CONFLICT`:门店名称已存在。
- `409 CONFLICT`:门店下还有员工,不能停用。
业务规则:
- 门店下还有员工时,允许把门店状态改为 `INACTIVE`
- 停用门店后,该门店员工仍可在员工列表和门店详情中查看,并通过 `statusTags` 标识“门店被禁用”。
### DELETE /api/stores/:storeId/employees/:employeeId
从门店详情中移除员工。需要 `employee:manage`
路径参数:
| 参数 | 类型 | 约束 |
| --- | --- | --- |
| `storeId` | `number` | 正整数 |
| `employeeId` | `number` | 正整数 |
成功响应:`204 No Content`
业务规则:
- 只有员工属于该门店时才会移除。
- 移除沿用员工软删除规则:员工状态会变为 `INACTIVE``deleted_at` 写入删除时间,手机号唯一约束释放。
可能的业务错误:
- `404 NOT_FOUND`:门店员工不存在。
### DELETE /api/stores/:id
@@ -889,10 +948,17 @@ Authorization: Bearer <token>
### DELETE /api/roles/:id
删除自定义角色。需要 `role:manage`
删除自定义角色。需要 `role:manage`
成功响应:`204 No Content`
删除后:
- `roles.deleted_at` 会写入删除时间。
- 角色不会再出现在角色列表、角色下拉选项和权限策略中。
- 该角色编码的唯一约束会释放,之后可以重新创建同编码角色。
- 员工角色关系不会被物理删除,解绑和重新绑定通过 `employee_roles.deleted_at` 记录当前关系状态。
可能的业务错误:
- `404 NOT_FOUND`:角色不存在。
@@ -938,9 +1004,17 @@ Authorization: Bearer <token>
"id": 1,
"storeId": 1,
"storeName": "示例门店",
"storeStatus": "ACTIVE",
"name": "张三",
"phone": "13800000001",
"status": "ACTIVE",
"statusTags": [
{
"code": "EMPLOYEE_ACTIVE",
"label": "员工启用",
"variant": "success"
}
],
"remark": null,
"roles": [
{
@@ -963,6 +1037,8 @@ Authorization: Bearer <token>
}
```
如果员工所属门店已停用,`storeStatus` 会返回 `"INACTIVE"`,且 `statusTags` 会同时包含员工自身状态标签和“门店被禁用”标签。
### GET /api/employees/:id
查询员工详情。需要员工查看权限。
@@ -1019,13 +1095,13 @@ Authorization: Bearer <token>
- `storeId` 必须对应启用且未删除门店。
- `phone` 在未删除员工范围内全局唯一。
- `roleIds` 中的角色必须存在;自定义角色可先通过角色接口创建,再通过权限接口分配权限。
- `roleIds` 中的角色必须存在且未软删除;自定义角色可先通过角色接口创建,再通过权限接口分配权限。
- 新员工默认密码为 `pw111111`
可能的业务错误:
- `400 BAD_REQUEST`:门店不存在或已停用。
- `400 BAD_REQUEST`:提交的角色包含不存在的角色。
- `400 BAD_REQUEST`:提交的角色包含不存在或已删除的角色。
- `409 CONFLICT`:员工手机号已存在。
### PATCH /api/employees/:id
@@ -1036,12 +1112,12 @@ Authorization: Bearer <token>
| 字段 | 类型 | 约束 | 说明 |
| --- | --- | --- | --- |
| `storeId` | `number` | 正整数 | 新门店必须启用且未删除 |
| `storeId` | `number` | 正整数 | 更换门店时,新门店必须启用且未删除 |
| `name` | `string` | trim 后 1-50 字符 | 员工姓名 |
| `phone` | `string` | 中国大陆手机号,`/^1[3-9]\d{9}$/` | 员工手机号,未删除员工范围内全局唯一 |
| `status` | `"ACTIVE" \| "INACTIVE"` | 枚举 | 员工状态 |
| `remark` | `string \| null` | trim 后最多 500 字符 | 备注 |
| `roleIds` | `number[]` | 正整数数组,最多 20 个 | 不传表示不修改角色,传空数组表示清空角色 |
| `roleIds` | `number[]` | 正整数数组,最多 20 个 | 不传表示不修改角色,传空数组表示清空角色;解绑会写入 `employee_roles.deleted_at` |
请求示例:
@@ -1057,8 +1133,8 @@ Authorization: Bearer <token>
可能的业务错误:
- `404 NOT_FOUND`:员工不存在。
- `400 BAD_REQUEST`:门店不存在或已停用。
- `400 BAD_REQUEST`:提交的角色包含不存在的角色。
- `400 BAD_REQUEST`更换后的门店不存在或已停用。
- `400 BAD_REQUEST`:提交的角色包含不存在或已删除的角色。
- `409 CONFLICT`:员工手机号已存在。
### PATCH /api/employees/:id/status
@@ -1081,6 +1157,55 @@ Authorization: Bearer <token>
响应 `data``Employee`
### PATCH /api/employees/:id/password
修改员工密码。需要 `employee:manage`
路径参数:
| 参数 | 类型 | 约束 |
| --- | --- | --- |
| `id` | `number` | 正整数 |
请求体:
| 字段 | 类型 | 必填 | 约束 |
| --- | --- | --- | --- |
| `oldPassword` | `string` | 是 | 8-128 字符 |
| `newPassword` | `string` | 是 | 8-128 字符 |
请求示例:
```json
{
"oldPassword": "pw111111",
"newPassword": "NewPw111111"
}
```
后端会先校验旧密码,旧密码不正确时不会写入新密码。
响应 `data``Employee`,不会返回密码或密码哈希。
可能的业务错误:
- `404 NOT_FOUND`:员工不存在。
- `400 BAD_REQUEST`:旧密码不正确。
### PATCH /api/employees/:id/password/reset
重置员工密码为初始密码 `pw111111`。需要 `employee:manage`
路径参数:
| 参数 | 类型 | 约束 |
| --- | --- | --- |
| `id` | `number` | 正整数 |
请求体:不需要请求体。
响应 `data``Employee`,不会返回密码或密码哈希。
### DELETE /api/employees/:id
软删除员工。需要 `employee:manage`