feat: 增加员工端工作台后端能力

This commit is contained in:
湛兮
2026-06-02 12:23:00 +08:00
parent 667dc411fc
commit 98cea63203
33 changed files with 3021 additions and 18 deletions
+301 -4
View File
@@ -113,9 +113,17 @@ pw111111
| `role:manage` | 新增、修改、软删除自定义角色 |
| `employee:view:all` | 查看全部门店员工 |
| `employee:view:store` | 查看当前门店员工 |
| `employee:manage` | 新增、修改、启停、移除软删除员工和维护密码 |
| `employee:manage` | 新增、修改、启停、移除软删除员工 |
| `permission:view` | 查看权限策略 |
| `permission:manage` | 分配角色权限 |
| `announcement:view` | 查看公告 |
| `announcement:manage` | 新增、编辑、发布、归档公告 |
| `task:view` | 查看任务 |
| `task:manage` | 新建、编辑、取消任务 |
| `shift:view` | 查看排班 |
| `shift:manage` | 新增、编辑、取消排班 |
| `credential:reset` | 重置下级员工密码 |
| `credential:audit:view` | 查看凭据操作审计 |
### 角色权限
@@ -149,6 +157,7 @@ interface AuthUser {
}>;
permissions: string[];
canManage: boolean;
mustChangePassword?: boolean;
}
```
@@ -241,6 +250,7 @@ interface PermissionMenu {
| `POST` | `/api/auth/admin/login` | 否 | 无 | 后台登录 |
| `POST` | `/api/auth/employee/login` | 否 | 无 | 员工端登录 |
| `GET` | `/api/auth/me` | 是 | 登录即可 | 当前用户 |
| `PATCH` | `/api/auth/me/password` | 是 | 登录即可 | 修改本人密码 |
| `GET` | `/api/permissions/me` | 是 | 登录即可 | 当前用户权限和菜单 |
| `GET` | `/api/permissions/policies` | 是 | `permission:view` | 角色权限策略 |
| `GET` | `/api/permissions/definitions` | 是 | `permission:view` | 可分配权限点定义 |
@@ -263,8 +273,34 @@ interface PermissionMenu {
| `PATCH` | `/api/employees/:id` | 是 | `employee:manage` | 修改员工 |
| `PATCH` | `/api/employees/:id/status` | 是 | `employee:manage` | 修改员工状态 |
| `PATCH` | `/api/employees/:id/password` | 是 | `employee:manage` | 修改员工密码 |
| `PATCH` | `/api/employees/:id/password/reset` | 是 | `employee:manage` | 重置员工为初始密码 |
| `PATCH` | `/api/employees/:id/password/reset` | 是 | `credential:reset` | 兼容旧路径,重置临时密码 |
| `DELETE` | `/api/employees/:id` | 是 | `employee:manage` | 软删除员工 |
| `GET` | `/api/mobile/bootstrap` | 是 | 登录即可 | 员工端首屏聚合 |
| `GET` | `/api/mobile/announcements` | 是 | 登录即可 | 当前员工可见公告 |
| `GET` | `/api/mobile/announcements/:id` | 是 | 登录即可 | 员工端公告详情 |
| `POST` | `/api/mobile/announcements/:id/read` | 是 | 登录即可 | 标记公告已读 |
| `GET` | `/api/mobile/tasks` | 是 | 登录即可 | 当前员工任务 |
| `GET` | `/api/mobile/tasks/:id` | 是 | 登录即可 | 员工端任务详情 |
| `POST` | `/api/mobile/tasks/:id/start` | 是 | 登录即可 | 开始处理任务 |
| `POST` | `/api/mobile/tasks/:id/complete` | 是 | 登录即可 | 完成任务 |
| `POST` | `/api/mobile/tasks/:id/comment` | 是 | 登录即可 | 添加任务备注 |
| `GET` | `/api/mobile/shifts` | 是 | 登录即可 | 当前员工排班 |
| `GET` | `/api/mobile/shifts/today` | 是 | 登录即可 | 当前员工今日排班 |
| `GET` | `/api/admin/announcements` | 是 | `announcement:view` | 后台公告分页 |
| `POST` | `/api/admin/announcements` | 是 | `announcement:manage` | 新建公告 |
| `PATCH` | `/api/admin/announcements/:id` | 是 | `announcement:manage` | 编辑公告 |
| `POST` | `/api/admin/announcements/:id/publish` | 是 | `announcement:manage` | 发布公告 |
| `POST` | `/api/admin/announcements/:id/archive` | 是 | `announcement:manage` | 归档公告 |
| `GET` | `/api/admin/tasks` | 是 | `task:view` | 后台任务分页 |
| `POST` | `/api/admin/tasks` | 是 | `task:manage` | 新建任务 |
| `PATCH` | `/api/admin/tasks/:id` | 是 | `task:manage` | 编辑任务 |
| `POST` | `/api/admin/tasks/:id/cancel` | 是 | `task:manage` | 取消任务 |
| `GET` | `/api/admin/shifts` | 是 | `shift:view` | 后台排班分页 |
| `POST` | `/api/admin/shifts` | 是 | `shift:manage` | 新增排班 |
| `PATCH` | `/api/admin/shifts/:id` | 是 | `shift:manage` | 编辑排班 |
| `DELETE` | `/api/admin/shifts/:id` | 是 | `shift:manage` | 取消排班 |
| `POST` | `/api/admin/employees/:id/password/reset` | 是 | `credential:reset` | 重置员工临时密码 |
| `GET` | `/api/admin/credential-audits` | 是 | `credential:audit:view` | 凭据审计分页 |
## 健康检查
@@ -1194,7 +1230,7 @@ Authorization: Bearer <token>
### PATCH /api/employees/:id/password/reset
重置员工密码为初始密码 `pw111111`。需要 `employee:manage`
兼容旧路径。重置员工临时密码。需要 `credential:reset`
路径参数:
@@ -1204,7 +1240,268 @@ Authorization: Bearer <token>
请求体:不需要请求体。
响应 `data``Employee`,不会返回密码或密码哈希
响应 `data.temporaryPassword` 只返回一次,后端只保存哈希,并把员工标记为必须改密
## C 端员工接口
所有 `/api/mobile/*` 接口都只从 Bearer token 推导当前员工,不接受客户端传入 `employeeId` 做越权查询。
### GET /api/mobile/bootstrap
员工端首屏聚合,返回当前员工、门店、权限、未读公告数、待办任务数、逾期任务数、最近公告、待办任务和今日排班。
响应:
```json
{
"success": true,
"data": {
"user": {
"id": 2,
"username": "13800000000",
"displayName": "张三",
"accountType": "EMPLOYEE",
"storeId": 1,
"storeName": "示例门店",
"roles": [{ "id": 1, "code": "cashier", "name": "收银员" }],
"permissions": ["task:view"],
"mustChangePassword": false
},
"store": {
"id": 1,
"name": "示例门店"
},
"permissions": {
"codes": ["task:view"],
"menus": []
},
"counters": {
"unreadAnnouncementCount": 2,
"pendingTaskCount": 3,
"overdueTaskCount": 1
},
"latestAnnouncements": [
{
"id": 10,
"title": "端午排班通知",
"content": "请按新排班表执行。",
"level": "IMPORTANT",
"status": "PUBLISHED",
"targetType": "STORE",
"publishedAt": "2026-06-01T02:00:00.000Z",
"readAt": null,
"createdAt": "2026-06-01T01:00:00.000Z",
"updatedAt": "2026-06-01T02:00:00.000Z",
"targets": []
}
],
"tasks": [
{
"id": 20,
"storeId": 1,
"storeName": "示例门店",
"title": "检查库存",
"description": "盘点饮料区库存",
"status": "PENDING",
"priority": "NORMAL",
"dueAt": "2026-06-01T10:00:00.000Z",
"assignees": [{ "id": 2, "name": "张三", "phone": "13800000000" }],
"createdAt": "2026-06-01T01:00:00.000Z",
"updatedAt": "2026-06-01T01:00:00.000Z"
}
],
"todayShifts": []
}
}
```
字段说明:
- `latestAnnouncements` 最多 3 条,按发布时间倒序返回当前员工可见的已发布公告。
- `tasks` 最多 5 条,按截止时间优先返回当前员工被分配的 `PENDING``IN_PROGRESS` 任务。
- `pendingTaskCount``overdueTaskCount` 当前都只统计已分配给当前员工的未完成任务;暂不把“门店级未指派任务”自动展开给全店员工。
- `todayShifts` 保持数组结构,返回当前员工今日 `SCHEDULED` 班次。
### GET /api/mobile/announcements
当前员工可见公告分页。支持 `status``keyword``page``pageSize` 查询参数;员工端只返回已发布且命中全员、门店、角色或员工目标的公告。
### GET /api/mobile/announcements/:id
当前员工可见公告详情。不可见公告按不存在处理。
### POST /api/mobile/announcements/:id/read
标记当前员工已读公告。
### GET /api/mobile/tasks
当前员工任务分页。支持 `status``keyword``page``pageSize`。当前版本只返回已分配给当前员工的任务,门店级未指派任务不自动出现在员工端。
### GET /api/mobile/tasks/:id
当前员工被分配的任务详情,包含任务事件日志。
### POST /api/mobile/tasks/:id/start
开始处理当前员工被分配的待处理任务。
### POST /api/mobile/tasks/:id/complete
完成当前员工被分配的待处理或处理中任务。
### POST /api/mobile/tasks/:id/comment
为当前员工被分配的任务追加备注。
请求体:
```json
{
"comment": "已完成交接"
}
```
### GET /api/mobile/shifts
当前员工排班分页。支持 `status``startDate``endDate``page``pageSize`,日期格式为 `YYYY-MM-DD`
### GET /api/mobile/shifts/today
当前员工今日有效排班。
## 后台工作台接口
### 公告管理
- `GET /api/admin/announcements`:公告分页,需要 `announcement:view`
- `POST /api/admin/announcements`:新建公告,需要 `announcement:manage`
- `PATCH /api/admin/announcements/:id`:编辑公告,需要 `announcement:manage`
- `POST /api/admin/announcements/:id/publish`:发布公告,需要 `announcement:manage`
- `POST /api/admin/announcements/:id/archive`:归档公告,需要 `announcement:manage`
公告请求体核心字段:
```json
{
"title": "端午排班通知",
"content": "请按新排班表执行。",
"level": "IMPORTANT",
"targetType": "STORE",
"targets": [{ "type": "STORE", "id": 1 }]
}
```
`targetType` 可选 `ALL``STORE``ROLE``EMPLOYEE``ALL``targets` 必须为空,其他范围必须提交同类型目标。
### 任务管理
- `GET /api/admin/tasks`:任务分页,需要 `task:view`
- `POST /api/admin/tasks`:新建任务,需要 `task:manage`
- `PATCH /api/admin/tasks/:id`:编辑任务,需要 `task:manage`
- `POST /api/admin/tasks/:id/cancel`:取消任务,需要 `task:manage`
任务请求体核心字段:
```json
{
"storeId": 1,
"title": "检查库存",
"description": "盘点饮料区库存",
"priority": "NORMAL",
"dueAt": "2026-06-01T10:00:00.000Z",
"assigneeIds": [2, 3]
}
```
### 排班管理
- `GET /api/admin/shifts`:排班分页,需要 `shift:view`
- `POST /api/admin/shifts`:新增排班,需要 `shift:manage`
- `PATCH /api/admin/shifts/:id`:编辑排班,需要 `shift:manage`
- `DELETE /api/admin/shifts/:id`:取消排班,需要 `shift:manage`
排班请求体核心字段:
```json
{
"storeId": 1,
"employeeId": 2,
"roleName": "收银",
"startAt": "2026-06-01T01:00:00.000Z",
"endAt": "2026-06-01T09:00:00.000Z",
"status": "SCHEDULED"
}
```
排班冲突规则:
- `POST /api/admin/shifts``PATCH /api/admin/shifts/:id` 都会校验同一员工在同一时间段不能存在重叠的未取消班次。
- 时间段重叠按 `existing.startAt < new.endAt && existing.endAt > new.startAt` 判断,首尾相接不算冲突。
- 编辑排班时会排除当前排班自身;状态为 `CANCELLED` 的班次不参与冲突校验。
- 发生冲突时返回 `409 CONFLICT`
## 凭据安全接口
后端不提供任何明文密码查看接口。员工密码只保存哈希。
### PATCH /api/auth/me/password
当前员工修改本人密码,需要提交旧密码和新密码。成功后清除 `mustChangePassword` 状态,并写入 `credential_audits` 审计。
### POST /api/admin/employees/:id/password/reset
重置权限范围内员工的临时密码,需要 `credential:reset`。响应中的 `temporaryPassword` 只返回一次。
```json
{
"success": true,
"data": {
"employee": {},
"temporaryPassword": "Tmp-xxxxxx9",
"mustChangePassword": true
}
}
```
### GET /api/admin/credential-audits
凭据审计分页,需要 `credential:audit:view`
查询参数:
| 参数 | 类型 | 必填 | 默认值 | 说明 |
| --- | --- | --- | --- | --- |
| `operatorId` | `number` | 否 | 无 | 操作者 ID,同时匹配 `actorAdminId``actorEmployeeId` |
| `targetEmployeeId` | `number` | 否 | 无 | 被操作员工 ID |
| `storeId` | `number` | 否 | 无 | 被操作员工所属门店 ID |
| `startDate` | `string` | 否 | 无 | 操作开始日期,格式 `YYYY-MM-DD`,包含当天 |
| `endDate` | `string` | 否 | 无 | 操作结束日期,格式 `YYYY-MM-DD`,包含当天 |
| `page` | `number` | 否 | `1` | 页码 |
| `pageSize` | `number` | 否 | `20` | 每页数量,最大 100 |
店长等只有当前门店员工数据范围的账号会被强制限定在自己的 `storeId``operatorId` 不区分超级管理员和员工操作者,后端会同时匹配 `actorAdminId``actorEmployeeId`
响应 `items` 字段:
```json
{
"id": 1,
"actorAccountType": "SUPER_ADMIN",
"actorAdminId": 1,
"actorEmployeeId": null,
"actorName": "超级管理员",
"targetEmployeeId": 2,
"targetEmployeeName": "张三",
"targetEmployeePhone": "13800000000",
"storeName": "示例门店",
"action": "RESET_PASSWORD",
"reason": "员工忘记密码",
"ip": "127.0.0.1",
"userAgent": "Mozilla/5.0",
"createdAt": "2026-06-01T08:00:00.000Z"
}
```
### DELETE /api/employees/:id