Files
2026-06-02 12:23:00 +08:00

1557 lines
43 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# access-manage 接口文档
本文档按当前后端代码整理,供前端对接使用。接口来源主要是:
- `src/app.ts`
- `src/modules/auth/*`
- `src/modules/permissions/*`
- `src/modules/catalog/*`
- `src/modules/employees/*`
## 基础约定
| 项目 | 说明 |
| --- | --- |
| 本地 Base URL | `http://localhost:3500` |
| 业务接口前缀 | `/api` |
| 健康检查 | `/health`,不带 `/api` 前缀 |
| 请求体格式 | `Content-Type: application/json` |
| 鉴权方式 | `Authorization: Bearer <token>` |
| 时间格式 | ISO 字符串,例如 `2026-05-26T08:00:00.000Z` |
| 字段命名 | 请求和响应都使用 `camelCase` |
不需要登录的接口:
- `GET /health`
- `POST /api/auth/login`
- `POST /api/auth/admin/login`
- `POST /api/auth/employee/login`
其他接口都需要 Bearer token。门店、角色、员工管理接口还要求当前账号具备后台管理权限。
## 通用响应
### 成功响应
```json
{
"success": true,
"data": {}
}
```
### 分页响应
```json
{
"success": true,
"data": {
"items": [],
"pagination": {
"page": 1,
"pageSize": 20,
"total": 0,
"totalPages": 0
}
}
}
```
### 错误响应
```json
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "请求参数不合法",
"details": []
}
}
```
常见错误码:
| HTTP 状态码 | `error.code` | 说明 |
| --- | --- | --- |
| 400 | `VALIDATION_ERROR` | zod 参数校验失败 |
| 400 | `BAD_REQUEST` | 业务参数不合法,例如门店不存在或角色不存在 |
| 401 | `UNAUTHORIZED` | 未登录、token 过期、账号密码错误或后台登录权限不足 |
| 403 | `FORBIDDEN` | 已登录但没有访问或操作权限 |
| 404 | `NOT_FOUND` | 资源不存在 |
| 409 | `CONFLICT` | 唯一字段冲突、资源被绑定不能删除等 |
| 500 | `INTERNAL_SERVER_ERROR` | 未处理的服务端异常 |
`DELETE` 成功时返回 `204 No Content`,没有响应体。
## 测试账号
本地迁移会初始化超级管理员:
| 类型 | 账号 | 密码 |
| --- | --- | --- |
| 超级管理员 | `admin` | `Admin@123456` |
员工创建后默认密码为:
```text
pw111111
```
员工登录账号使用员工手机号。
## 权限模型
### 权限码
| 权限码 | 说明 |
| --- | --- |
| `*` | 超级管理员,拥有全部权限 |
| `store:view` | 查看门店和门店下员工 |
| `store:manage` | 新增、修改、停用、软删除门店 |
| `role:view` | 查看角色 |
| `role:manage` | 新增、修改、软删除自定义角色 |
| `employee:view:all` | 查看全部门店员工 |
| `employee:view:store` | 查看当前门店员工 |
| `employee:manage` | 新增、修改、启停、移除和软删除员工 |
| `permission:view` | 查看权限策略 |
| `permission:manage` | 分配角色权限 |
| `announcement:view` | 查看公告 |
| `announcement:manage` | 新增、编辑、发布、归档公告 |
| `task:view` | 查看任务 |
| `task:manage` | 新建、编辑、取消任务 |
| `shift:view` | 查看排班 |
| `shift:manage` | 新增、编辑、取消排班 |
| `credential:reset` | 重置下级员工密码 |
| `credential:audit:view` | 查看凭据操作审计 |
### 角色权限
| 角色 | 作用范围 | 权限 |
| --- | --- | --- |
| `super_admin` 超级管理员 | 全部门店 | `*` |
| `admin` 管理员 | 按权限点控制 | `store:view`, `store:manage`, `role:view`, `role:manage`, `employee:view:all`, `employee:manage`, `permission:view`, `permission:manage` |
| `store_manager` 店长 | 当前门店 | `employee:view:store` |
| `cashier` 收银员 | 默认无后台菜单 | 可通过权限管理动态分配 |
| `kitchen` 后厨 | 默认无后台菜单 | 可通过权限管理动态分配 |
| `part_time` 兼职 | 默认无后台菜单 | 可通过权限管理动态分配 |
角色权限保存在 `role_permissions` 表。前端通过 `GET /api/permissions/definitions` 获取可分配权限点,通过 `PUT /api/permissions/roles/:roleId` 保存角色权限。超级管理员是独立账号类型,固定拥有 `*`,不参与角色权限分配。
## 数据结构
### AuthUser
```ts
interface AuthUser {
id: number;
username: string;
displayName: string;
accountType: "SUPER_ADMIN" | "EMPLOYEE";
storeId?: number;
storeName?: string;
roles: Array<{
id: number;
code: string;
name: string;
}>;
permissions: string[];
canManage: boolean;
mustChangePassword?: boolean;
}
```
### Store
```ts
type StoreStatus = "ACTIVE" | "INACTIVE";
interface StoreOption {
id: number;
name: string;
address: string | null;
phone: string | null;
}
interface Store extends StoreOption {
status: StoreStatus;
createdAt: string;
updatedAt: string;
}
```
### Role
```ts
interface RoleOption {
id: number;
code: string;
name: string;
description: string | null;
isSystem: boolean;
}
interface Role extends RoleOption {
createdAt: string;
updatedAt: string;
}
```
### Employee
```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;
code: string;
name: string;
}>;
createdAt: string;
updatedAt: string;
}
```
### PermissionMenu
```ts
interface PermissionMenu {
key: string;
title: string;
icon?: string;
permission: string;
actions: string[];
}
```
## 接口总览
| 方法 | 路径 | 鉴权 | 权限 | 说明 |
| --- | --- | --- | --- | --- |
| `GET` | `/health` | 否 | 无 | 健康检查 |
| `POST` | `/api/auth/login` | 否 | 无 | 后台登录,兼容入口 |
| `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` | 可分配权限点定义 |
| `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/: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` | 软删除自定义角色 |
| `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` | 修改员工状态 |
| `PATCH` | `/api/employees/:id/password` | 是 | `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` | 凭据审计分页 |
## 健康检查
### GET /health
检查 HTTP 服务和数据库连接。
响应:
```json
{
"success": true,
"data": {
"status": "ok",
"database": "up",
"now": "2026-05-26T08:00:00.000Z"
}
}
```
## 认证接口
### POST /api/auth/login
后台登录兼容入口,逻辑等同于 `POST /api/auth/admin/login`
请求体:
| 字段 | 类型 | 必填 | 约束 | 说明 |
| --- | --- | --- | --- | --- |
| `username` | `string` | 是 | trim 后 1-50 字符 | 超级管理员用户名,或员工手机号 |
| `password` | `string` | 是 | 8-128 字符 | 登录密码 |
请求示例:
```json
{
"username": "admin",
"password": "Admin@123456"
}
```
响应:
```json
{
"success": true,
"data": {
"token": "<jwt-token>",
"tokenType": "Bearer",
"expiresIn": "2h",
"user": {
"id": 1,
"username": "admin",
"displayName": "超级管理员",
"accountType": "SUPER_ADMIN",
"roles": [
{
"id": 0,
"code": "super_admin",
"name": "超级管理员"
}
],
"permissions": ["*"],
"canManage": true
}
}
}
```
后台登录规则:
- 超级管理员使用 `super_admins.username` 登录。
- 员工使用手机号登录。
- 登录会先校验密码;密码正确但账号停用时返回 `401 UNAUTHORIZED`,消息为 `账号已被禁用`
- 员工所属门店停用时返回 `401 UNAUTHORIZED`,消息为 `所属门店已被禁用`
- 员工必须拥有后台菜单权限,否则返回 `401 UNAUTHORIZED`,消息为 `当前账号没有后台登录权限`
- `cashier``kitchen``part_time` 默认没有后台登录权限。
### POST /api/auth/admin/login
后台登录正式入口。请求和响应与 `POST /api/auth/login` 一致。
### POST /api/auth/employee/login
员工端登录入口。员工使用手机号和密码登录,不要求后台管理权限。
密码正确但员工账号停用时返回 `账号已被禁用`;所属门店停用时返回 `所属门店已被禁用`
请求体:
| 字段 | 类型 | 必填 | 约束 | 说明 |
| --- | --- | --- | --- | --- |
| `username` | `string` | 是 | trim 后 1-50 字符 | 员工手机号 |
| `password` | `string` | 是 | 8-128 字符 | 员工密码 |
响应中的 `user.accountType``EMPLOYEE``storeId``storeName` 会返回。
### GET /api/auth/me
获取当前登录用户。
请求头:
```http
Authorization: Bearer <token>
```
响应:
```json
{
"success": true,
"data": {
"id": 1,
"username": "admin",
"displayName": "超级管理员",
"accountType": "SUPER_ADMIN",
"roles": [
{
"id": 0,
"code": "super_admin",
"name": "超级管理员"
}
],
"permissions": ["*"],
"canManage": true
}
}
```
## 权限接口
### GET /api/permissions/me
获取当前用户可见菜单和权限码。任意已登录账号都可访问。
响应:
```json
{
"success": true,
"data": {
"permissions": ["*"],
"menus": [
{
"key": "stores",
"title": "门店管理",
"permission": "store:view",
"actions": ["view", "create", "update", "delete"]
},
{
"key": "roles",
"title": "角色管理",
"permission": "role:view",
"actions": ["view", "create", "update", "delete"]
},
{
"key": "employees",
"title": "员工管理",
"permission": "employee:view:all",
"actions": ["view", "create", "update", "delete"]
},
{
"key": "permissions",
"title": "权限管理",
"icon": "key",
"permission": "permission:view",
"actions": ["view", "update"]
}
]
}
}
```
前端建议用此接口决定菜单显隐和按钮显隐。
### GET /api/permissions/policies
获取当前数据库里的角色权限策略。需要 `permission:view`
响应:
```json
{
"success": true,
"data": [
{
"roleId": 0,
"roleCode": "super_admin",
"roleName": "超级管理员",
"roleDescription": "系统内置最高权限账号,不参与角色权限分配。",
"isSystem": true,
"editable": false,
"scope": "全部门店",
"permissions": ["*"],
"menus": [
{
"key": "stores",
"title": "门店管理",
"actions": ["view", "create", "update", "delete"]
}
]
},
{
"roleId": 5,
"roleCode": "admin",
"roleName": "管理员",
"roleDescription": "系统管理角色,仅授予可信人员",
"isSystem": true,
"editable": true,
"scope": "按权限点控制",
"permissions": [
"store:view",
"store:manage",
"role:view",
"role:manage",
"employee:view:all",
"employee:manage",
"permission:view",
"permission:manage"
],
"menus": [
{
"key": "stores",
"title": "门店管理",
"actions": ["view", "create", "update", "delete"]
}
]
},
{
"roleId": 1,
"roleCode": "store_manager",
"roleName": "店长",
"roleDescription": "负责门店日常管理、排班和权限审批",
"isSystem": true,
"editable": true,
"scope": "当前门店",
"permissions": ["employee:view:store"],
"menus": [
{
"key": "employees",
"title": "员工管理",
"actions": ["view"]
}
]
}
]
}
```
### GET /api/permissions/definitions
获取后端允许分配的权限点定义。需要 `permission:view`
响应:
```json
{
"success": true,
"data": {
"permissions": [
{
"code": "store:view",
"title": "查看门店",
"description": "查看门店列表、门店详情和门店下拉选项。",
"groupKey": "stores",
"groupTitle": "门店管理"
}
],
"groups": [
{
"key": "stores",
"title": "门店管理",
"permissions": [
{
"code": "store:view",
"title": "查看门店",
"description": "查看门店列表、门店详情和门店下拉选项。",
"groupKey": "stores",
"groupTitle": "门店管理"
}
]
}
],
"menus": [
{
"key": "stores",
"title": "门店管理",
"permission": "store:view",
"actions": ["view", "create", "update", "delete"],
"actionLabels": {
"view": "查看",
"create": "新增",
"update": "编辑",
"delete": "删除"
}
}
]
}
}
```
### PUT /api/permissions/roles/:roleId
更新指定角色拥有的权限点。需要 `permission:manage`
后端只接受 `GET /api/permissions/definitions` 返回的权限码。保存时会自动补齐依赖权限,例如提交 `permission:manage` 会自动保留 `permission:view`。本次保存中被移除的权限关系会写入 `role_permissions.deleted_at`,不会物理删除关系行。
请求:
```http
PUT /api/permissions/roles/5
Authorization: Bearer <token>
Content-Type: application/json
```
```json
{
"permissions": [
"store:view",
"store:manage",
"role:view",
"role:manage",
"permission:view",
"permission:manage"
]
}
```
响应:
```json
{
"success": true,
"data": {
"roleId": 5,
"roleCode": "admin",
"roleName": "管理员",
"roleDescription": "系统管理角色,仅授予可信人员",
"isSystem": true,
"editable": true,
"scope": "按权限点控制",
"permissions": [
"store:view",
"store:manage",
"role:view",
"role:manage",
"permission:view",
"permission:manage"
],
"menus": [
{
"key": "stores",
"title": "门店管理",
"actions": ["view", "create", "update", "delete"]
},
{
"key": "roles",
"title": "角色管理",
"actions": ["view", "create", "update", "delete"]
},
{
"key": "permissions",
"title": "权限管理",
"actions": ["view", "update"]
}
]
}
}
```
上面示例中的 `menus` 为截断示例,真实响应会返回完整菜单数组。
## 门店接口
### GET /api/stores
查询门店。需要 `store:view`
查询参数:
| 参数 | 类型 | 必填 | 默认值 | 约束 | 说明 |
| --- | --- | --- | --- | --- | --- |
| `includeInactive` | `boolean` | 否 | 无 | `true``false` | 不传时由接口模式决定;传 `false` 且不传 `status` 时只查启用门店 |
| `status` | `"ACTIVE" \| "INACTIVE"` | 否 | 无 | 枚举 | 按门店状态筛选;优先级高于 `includeInactive` |
| `keyword` | `string` | 否 | 无 | trim 后 1-100 字符 | 按门店名称、地址、电话模糊搜索 |
| `page` | `number` | 否 | `1` | 正整数 | 页码;传了筛选或分页参数时才进入分页列表模式 |
| `pageSize` | `number` | 否 | `20` | 1-100 | 每页数量 |
响应结构会随查询参数变化:
- 不传任何查询参数,或只传 `includeInactive=false`:返回启用门店下拉选项 `StoreOption[]`,供表单选择门店。
- 只传 `includeInactive=true`:返回未删除门店完整数组 `Store[]`,兼容旧调用。
-`status``keyword``page``pageSize` 中任意一个:返回分页结构,`items``Store[]`,供门店管理列表使用。
管理列表常用写法:
- 全部状态:`GET /api/stores?page=1&pageSize=20`
- 只看启用:`GET /api/stores?status=ACTIVE&page=1&pageSize=20`
- 关键词搜索:`GET /api/stores?keyword=人民&page=1&pageSize=20`
请求示例:
```http
GET /api/stores?status=ACTIVE&keyword=&page=1&pageSize=20
Authorization: Bearer <token>
```
响应示例:
```json
{
"success": true,
"data": {
"items": [
{
"id": 1,
"name": "示例门店",
"address": "请改成你的真实门店地址",
"phone": "13800000000",
"status": "ACTIVE",
"createdAt": "2026-05-26T08:00:00.000Z",
"updatedAt": "2026-05-26T08:00:00.000Z"
}
],
"pagination": {
"page": 1,
"pageSize": 20,
"total": 1,
"totalPages": 1
}
}
}
```
### GET /api/stores/:id
查询门店详情。需要 `store:view`
路径参数:
| 参数 | 类型 | 约束 |
| --- | --- | --- |
| `id` | `number` | 正整数 |
响应 `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
新增门店。需要 `store:manage`
请求体:
| 字段 | 类型 | 必填 | 默认值 | 约束 | 说明 |
| --- | --- | --- | --- | --- | --- |
| `name` | `string` | 是 | 无 | trim 后 1-100 字符 | 门店名称,未删除门店内唯一 |
| `address` | `string \| null` | 否 | `null` | trim 后最多 255 字符 | 空字符串会保存为 `null` |
| `phone` | `string \| null` | 否 | `null` | trim 后最多 30 字符 | 空字符串会保存为 `null` |
| `status` | `"ACTIVE" \| "INACTIVE"` | 否 | `"ACTIVE"` | 枚举 | 门店状态 |
请求示例:
```json
{
"name": "人民广场店",
"address": "上海市黄浦区人民广场",
"phone": "021-12345678",
"status": "ACTIVE"
}
```
成功响应:`201 Created``data``Store`
可能的业务错误:
- `409 CONFLICT`:门店名称已存在。
### PATCH /api/stores/:id
修改门店。需要 `store:manage`
路径参数:
| 参数 | 类型 | 约束 |
| --- | --- | --- |
| `id` | `number` | 正整数 |
请求体至少提交一个字段:
| 字段 | 类型 | 约束 | 说明 |
| --- | --- | --- | --- |
| `name` | `string` | trim 后 1-100 字符 | 门店名称 |
| `address` | `string \| null` | trim 后最多 255 字符 | 空字符串会保存为 `null` |
| `phone` | `string \| null` | trim 后最多 30 字符 | 空字符串会保存为 `null` |
| `status` | `"ACTIVE" \| "INACTIVE"` | 枚举 | 门店状态 |
请求示例:
```json
{
"name": "人民广场旗舰店",
"status": "ACTIVE"
}
```
响应 `data``Store`
可能的业务错误:
- `404 NOT_FOUND`:门店不存在。
- `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
软删除门店。需要 `store:manage`
成功响应:`204 No Content`
可能的业务错误:
- `404 NOT_FOUND`:门店不存在。
- `409 CONFLICT`:门店下还有员工,不能删除。
## 角色接口
### GET /api/roles
查询角色列表。需要 `role:view`
查询参数:
| 参数 | 类型 | 必填 | 默认值 | 约束 | 说明 |
| --- | --- | --- | --- | --- | --- |
| `keyword` | `string` | 否 | 无 | trim 后 1-100 字符 | 按角色编码、名称、说明模糊搜索 |
| `isSystem` | `boolean` | 否 | 无 | `true``false` | 是否服务端内置角色 |
| `page` | `number` | 否 | `1` | 正整数 | 页码;传了筛选或分页参数时才进入分页列表模式 |
| `pageSize` | `number` | 否 | `20` | 1-100 | 每页数量 |
响应结构会随查询参数变化:
- 不传任何查询参数:返回 `Role[]`,兼容角色下拉和旧调用。
- 传任意一个查询参数:返回分页结构,`items``Role[]`,供角色管理列表使用。
请求示例:
```http
GET /api/roles?keyword=&isSystem=true&page=1&pageSize=20
Authorization: Bearer <token>
```
响应示例:
```json
{
"success": true,
"data": {
"items": [
{
"id": 1,
"code": "store_manager",
"name": "店长",
"description": "负责门店日常管理、排班和权限审批",
"isSystem": true,
"createdAt": "2026-05-26T08:00:00.000Z",
"updatedAt": "2026-05-26T08:00:00.000Z"
}
],
"pagination": {
"page": 1,
"pageSize": 20,
"total": 1,
"totalPages": 1
}
}
}
```
### GET /api/roles/:id
查询角色详情。需要 `role:view`
路径参数:
| 参数 | 类型 | 约束 |
| --- | --- | --- |
| `id` | `number` | 正整数 |
响应 `data``Role`
### POST /api/roles
新增自定义角色。需要 `role:manage`。当前内置 `admin` 角色没有 `role:manage`,通常只有超级管理员可调用。
请求体:
| 字段 | 类型 | 必填 | 约束 | 说明 |
| --- | --- | --- | --- | --- |
| `code` | `string` | 是 | trim 后 1-50 字符,必须匹配 `^[a-z][a-z0-9_]*$` | 角色编码,全局唯一 |
| `name` | `string` | 是 | trim 后 1-50 字符 | 角色名称 |
| `description` | `string \| null` | 否 | trim 后最多 255 字符 | 空字符串会保存为 `null` |
请求示例:
```json
{
"code": "auditor",
"name": "审计员",
"description": "只用于示例的自定义角色"
}
```
成功响应:`201 Created``data``Role`,其中 `isSystem``false`
可能的业务错误:
- `409 CONFLICT`:角色编码已存在。
### PATCH /api/roles/:id
修改自定义角色。需要 `role:manage`
请求体至少提交一个字段:
| 字段 | 类型 | 约束 | 说明 |
| --- | --- | --- | --- |
| `code` | `string` | trim 后 1-50 字符,必须匹配 `^[a-z][a-z0-9_]*$` | 角色编码 |
| `name` | `string` | trim 后 1-50 字符 | 角色名称 |
| `description` | `string \| null` | trim 后最多 255 字符 | 空字符串会保存为 `null` |
响应 `data``Role`
可能的业务错误:
- `404 NOT_FOUND`:角色不存在。
- `409 CONFLICT`:服务端内置角色不可修改。
- `409 CONFLICT`:角色编码已存在。
### DELETE /api/roles/:id
软删除自定义角色。需要 `role:manage`
成功响应:`204 No Content`
删除后:
- `roles.deleted_at` 会写入删除时间。
- 角色不会再出现在角色列表、角色下拉选项和权限策略中。
- 该角色编码的唯一约束会释放,之后可以重新创建同编码角色。
- 员工角色关系不会被物理删除,解绑和重新绑定通过 `employee_roles.deleted_at` 记录当前关系状态。
可能的业务错误:
- `404 NOT_FOUND`:角色不存在。
- `409 CONFLICT`:服务端内置角色不可删除。
- `409 CONFLICT`:角色已绑定员工,不能删除。
## 员工接口
### GET /api/employees
分页查询员工。需要 `employee:view:all``employee:view:store`
查询参数:
| 参数 | 类型 | 必填 | 默认值 | 约束 | 说明 |
| --- | --- | --- | --- | --- | --- |
| `storeId` | `number` | 否 | 无 | 正整数 | 门店筛选 |
| `status` | `"ACTIVE" \| "INACTIVE"` | 否 | 无 | 枚举 | 员工状态 |
| `keyword` | `string` | 否 | 无 | trim 后 1-100 字符 | 按姓名或手机号模糊搜索 |
| `page` | `number` | 否 | `1` | 正整数 | 页码 |
| `pageSize` | `number` | 否 | `20` | 1-100 | 每页数量 |
权限范围:
- 超级管理员和 `admin`:可看全部员工,`storeId` 参数生效。
- `store_manager`:只能看当前门店员工,即使传了其他 `storeId`,后端也会强制改成当前用户的 `storeId`
请求示例:
```http
GET /api/employees?storeId=1&status=ACTIVE&keyword=&page=1&pageSize=20
Authorization: Bearer <token>
```
响应:
```json
{
"success": true,
"data": {
"items": [
{
"id": 1,
"storeId": 1,
"storeName": "示例门店",
"storeStatus": "ACTIVE",
"name": "张三",
"phone": "13800000001",
"status": "ACTIVE",
"statusTags": [
{
"code": "EMPLOYEE_ACTIVE",
"label": "员工启用",
"variant": "success"
}
],
"remark": null,
"roles": [
{
"id": 1,
"code": "store_manager",
"name": "店长"
}
],
"createdAt": "2026-05-26T08:00:00.000Z",
"updatedAt": "2026-05-26T08:00:00.000Z"
}
],
"pagination": {
"page": 1,
"pageSize": 20,
"total": 1,
"totalPages": 1
}
}
}
```
如果员工所属门店已停用,`storeStatus` 会返回 `"INACTIVE"`,且 `statusTags` 会同时包含员工自身状态标签和“门店被禁用”标签。
### GET /api/employees/:id
查询员工详情。需要员工查看权限。
权限范围:
- `employee:view:all`:可看任意员工。
- `employee:view:store`:只能看当前门店员工。
路径参数:
| 参数 | 类型 | 约束 |
| --- | --- | --- |
| `id` | `number` | 正整数 |
响应 `data``Employee`
可能的业务错误:
- `404 NOT_FOUND`:员工不存在。
- `403 FORBIDDEN`:没有查看该员工的权限。
### POST /api/employees
新增员工。需要 `employee:manage`
请求体:
| 字段 | 类型 | 必填 | 默认值 | 约束 | 说明 |
| --- | --- | --- | --- | --- | --- |
| `storeId` | `number` | 是 | 无 | 正整数 | 必须是启用且未删除的门店 |
| `name` | `string` | 是 | 无 | trim 后 1-50 字符 | 员工姓名 |
| `phone` | `string` | 是 | 无 | 中国大陆手机号,`/^1[3-9]\d{9}$/` | 员工登录账号,未删除员工范围内全局唯一 |
| `status` | `"ACTIVE" \| "INACTIVE"` | 否 | `"ACTIVE"` | 枚举 | 员工状态 |
| `remark` | `string \| null` | 否 | `null` | trim 后最多 500 字符 | 备注 |
| `roleIds` | `number[]` | 否 | `[]` | 正整数数组,最多 20 个 | 绑定角色 ID,重复 ID 会自动去重 |
请求示例:
```json
{
"storeId": 1,
"name": "张三",
"phone": "13800000001",
"status": "ACTIVE",
"remark": null,
"roleIds": [1, 5]
}
```
成功响应:`201 Created``data``Employee`
业务规则:
- `storeId` 必须对应启用且未删除门店。
- `phone` 在未删除员工范围内全局唯一。
- `roleIds` 中的角色必须存在且未软删除;自定义角色可先通过角色接口创建,再通过权限接口分配权限。
- 新员工默认密码为 `pw111111`
可能的业务错误:
- `400 BAD_REQUEST`:门店不存在或已停用。
- `400 BAD_REQUEST`:提交的角色包含不存在或已删除的角色。
- `409 CONFLICT`:员工手机号已存在。
### PATCH /api/employees/:id
修改员工。需要 `employee:manage`
请求体至少提交一个字段:
| 字段 | 类型 | 约束 | 说明 |
| --- | --- | --- | --- |
| `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 个 | 不传表示不修改角色,传空数组表示清空角色;解绑会写入 `employee_roles.deleted_at` |
请求示例:
```json
{
"name": "张三丰",
"roleIds": [5]
}
```
响应 `data``Employee`
可能的业务错误:
- `404 NOT_FOUND`:员工不存在。
- `400 BAD_REQUEST`:更换后的门店不存在或已停用。
- `400 BAD_REQUEST`:提交的角色包含不存在或已删除的角色。
- `409 CONFLICT`:员工手机号已存在。
### PATCH /api/employees/:id/status
修改员工状态。需要 `employee:manage`
请求体:
| 字段 | 类型 | 必填 | 约束 |
| --- | --- | --- | --- |
| `status` | `"ACTIVE" \| "INACTIVE"` | 是 | 枚举 |
请求示例:
```json
{
"status": "INACTIVE"
}
```
响应 `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
兼容旧路径。重置员工临时密码。需要 `credential:reset`
路径参数:
| 参数 | 类型 | 约束 |
| --- | --- | --- |
| `id` | `number` | 正整数 |
请求体:不需要请求体。
响应 `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
软删除员工。需要 `employee:manage`
成功响应:`204 No Content`
删除后:
- 员工 `status` 会改为 `INACTIVE`
- `deleted_at` 会写入删除时间。
- 该手机号的唯一约束会释放,之后可以重新创建同手机号员工。
## 前端推荐接入流程
1. 调用 `POST /api/auth/admin/login` 完成后台登录。
2. 保存 `data.token`,后续请求统一带 `Authorization: Bearer <token>`
3. 调用 `GET /api/auth/me` 获取账号基础信息。
4. 调用 `GET /api/permissions/me` 渲染菜单和按钮权限。
5. 员工表单初始化时调用 `GET /api/stores` 获取启用门店选项,调用 `GET /api/roles` 获取角色选项。
6. 员工列表使用 `GET /api/employees`,按返回的 `pagination` 渲染分页器。
## curl 示例
登录:
```bash
curl -X POST http://localhost:3500/api/auth/admin/login \
-H 'Content-Type: application/json' \
-d '{"username":"admin","password":"Admin@123456"}'
```
查询当前用户:
```bash
curl http://localhost:3500/api/auth/me \
-H 'Authorization: Bearer <token>'
```
创建员工:
```bash
curl -X POST http://localhost:3500/api/employees \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <token>' \
-d '{
"storeId": 1,
"name": "张三",
"phone": "13800000001",
"roleIds": [1, 5]
}'
```