43 KiB
access-manage 接口文档
本文档按当前后端代码整理,供前端对接使用。接口来源主要是:
src/app.tssrc/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 /healthPOST /api/auth/loginPOST /api/auth/admin/loginPOST /api/auth/employee/login
其他接口都需要 Bearer token。门店、角色、员工管理接口还要求当前账号具备后台管理权限。
通用响应
成功响应
{
"success": true,
"data": {}
}
分页响应
{
"success": true,
"data": {
"items": [],
"pagination": {
"page": 1,
"pageSize": 20,
"total": 0,
"totalPages": 0
}
}
}
错误响应
{
"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 |
员工创建后默认密码为:
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
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
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
interface RoleOption {
id: number;
code: string;
name: string;
description: string | null;
isSystem: boolean;
}
interface Role extends RoleOption {
createdAt: string;
updatedAt: string;
}
Employee
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
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 服务和数据库连接。
响应:
{
"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 字符 | 登录密码 |
请求示例:
{
"username": "admin",
"password": "Admin@123456"
}
响应:
{
"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
获取当前登录用户。
请求头:
Authorization: Bearer <token>
响应:
{
"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
获取当前用户可见菜单和权限码。任意已登录账号都可访问。
响应:
{
"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。
响应:
{
"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。
响应:
{
"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,不会物理删除关系行。
请求:
PUT /api/permissions/roles/5
Authorization: Bearer <token>
Content-Type: application/json
{
"permissions": [
"store:view",
"store:manage",
"role:view",
"role:manage",
"permission:view",
"permission:manage"
]
}
响应:
{
"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
请求示例:
GET /api/stores?status=ACTIVE&keyword=人民&page=1&pageSize=20
Authorization: Bearer <token>
响应示例:
{
"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" |
枚举 | 门店状态 |
请求示例:
{
"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" |
枚举 | 门店状态 |
请求示例:
{
"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[],供角色管理列表使用。
请求示例:
GET /api/roles?keyword=店长&isSystem=true&page=1&pageSize=20
Authorization: Bearer <token>
响应示例:
{
"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 |
请求示例:
{
"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。
请求示例:
GET /api/employees?storeId=1&status=ACTIVE&keyword=张&page=1&pageSize=20
Authorization: Bearer <token>
响应:
{
"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 会自动去重 |
请求示例:
{
"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 |
请求示例:
{
"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" |
是 | 枚举 |
请求示例:
{
"status": "INACTIVE"
}
响应 data 为 Employee。
PATCH /api/employees/:id/password
修改员工密码。需要 employee:manage。
路径参数:
| 参数 | 类型 | 约束 |
|---|---|---|
id |
number |
正整数 |
请求体:
| 字段 | 类型 | 必填 | 约束 |
|---|---|---|---|
oldPassword |
string |
是 | 8-128 字符 |
newPassword |
string |
是 | 8-128 字符 |
请求示例:
{
"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
员工端首屏聚合,返回当前员工、门店、权限、未读公告数、待办任务数、逾期任务数、最近公告、待办任务和今日排班。
响应:
{
"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
为当前员工被分配的任务追加备注。
请求体:
{
"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。
公告请求体核心字段:
{
"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。
任务请求体核心字段:
{
"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。
排班请求体核心字段:
{
"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 只返回一次。
{
"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 字段:
{
"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会写入删除时间。- 该手机号的唯一约束会释放,之后可以重新创建同手机号员工。
前端推荐接入流程
- 调用
POST /api/auth/admin/login完成后台登录。 - 保存
data.token,后续请求统一带Authorization: Bearer <token>。 - 调用
GET /api/auth/me获取账号基础信息。 - 调用
GET /api/permissions/me渲染菜单和按钮权限。 - 员工表单初始化时调用
GET /api/stores获取启用门店选项,调用GET /api/roles获取角色选项。 - 员工列表使用
GET /api/employees,按返回的pagination渲染分页器。
curl 示例
登录:
curl -X POST http://localhost:3500/api/auth/admin/login \
-H 'Content-Type: application/json' \
-d '{"username":"admin","password":"Admin@123456"}'
查询当前用户:
curl http://localhost:3500/api/auth/me \
-H 'Authorization: Bearer <token>'
创建员工:
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]
}'