# 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 ` | | 时间格式 | 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": "", "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 ``` 响应: ```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 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 ``` 响应示例: ```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 ``` 响应示例: ```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 ``` 响应: ```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 `。 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 ' ``` 创建员工: ```bash curl -X POST http://localhost:3500/api/employees \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer ' \ -d '{ "storeId": 1, "name": "张三", "phone": "13800000001", "roleIds": [1, 5] }' ```