# 列表分页 API 契约 本文描述 `backend-web` 公开端(`/api/v1`)与管理端(`/api/admin`)列表接口的分页约定。实现以仓库内 PHP 控制器为准。 ## 通用响应包装 所有 JSON 接口统一: ```json { "ok": true, "data": { } } ``` 失败时 `ok: false`,含 `error`(机器码)与 `message`(人类可读文案)。 ## 推荐分页形态(page / size / total) **新接口与前端管理列表优先使用此形态。** ### 请求 Query | 参数 | 类型 | 默认 | 上限 | 说明 | |------|------|------|------|------| | `page` | int | `1` | — | 从 1 开始 | | `size` | int | 见各接口 | 见各接口 | 每页条数 | ### 响应 `data` | 字段 | 类型 | 说明 | |------|------|------| | `items` | array | 当前页记录 | | `total` | int | 符合条件的总条数(用于算总页数) | | `page` | int | 当前页码(回显) | | `size` | int | 当前每页条数(回显) | 总页数:`ceil(total / size)`,`total === 0` 时视为 0 条、1 页。 ### 示例 ```http GET /api/admin/users?page=2&size=20&q=test%40local.test Authorization: Bearer ``` ```json { "ok": true, "data": { "items": [ { "id": 1, "email": "..." } ], "total": 42, "page": 2, "size": 20 } } ``` --- ## 管理端分页列表(`/api/admin`) 需管理员 JWT(`role=admin`)。默认 `size` 与上限见下表。 | 方法 | 路径 | 默认 size | size 上限 | 额外筛选 Query | |------|------|-----------|-----------|----------------| | GET | `/api/admin/users` | 20 | 200 | `q` 邮箱模糊 | | GET | `/api/admin/orders` | 20 | 200 | `q` 订单号或邮箱;`status` pending/paid/cancelled/refunded;`refund_status` none/pending/approved/rejected;`channel` alipay/wechat;`from`/`to` 日期 YYYY-MM-DD | | GET | `/api/admin/audits` | 50 | 200 | `q` 操作/目标/管理员邮箱;`action` 精确操作码;`from`/`to` 日期 | 实现参考: - `backend-web/src/Admin/Controllers/UsersController.php` - `backend-web/src/Admin/Controllers/OrdersController.php` - `backend-web/src/Admin/Controllers/AuditsController.php` ### 管理端全量列表(无分页) 以下接口一次返回全部 `items`,数据量预期较小: | 路径 | 说明 | |------|------| | `GET /api/admin/plans` | 套餐配置 | | `GET/POST /api/admin/templates`、`PUT/DELETE /api/admin/templates/{id}` | 模板库 CRUD | | `GET /api/admin/settings` | 系统键值 | | `GET /api/admin/payment/status` | PEM 文件元数据 | ### 管理端固定条数嵌套列表 | 来源 | 条数 | 说明 | |------|------|------| | `GET /api/admin/stats` → `recent_orders` | 5 | 仪表盘最近订单,非翻页接口 | | `GET /api/admin/users/{id}` → `recent_orders` | 5 | 用户详情内嵌 | --- ## 公开端分页列表(`/api/v1`) 需用户 JWT(除 plans 列表可匿名,见各控制器)。 | 方法 | 路径 | 默认 size | size 上限 | 分页参数 | 响应 | |------|------|-----------|-----------|----------|------| | GET | `/api/v1/files` | 50(legacy) | 200 | **推荐** `page`+`size`;**兼容** `limit`+`offset` | 见下文 | | GET | `/api/v1/pay/orders` | 8 | 50 | `page`+`size` | page 形态 | 实现参考: - `backend-web/src/Controllers/FileController.php` - `backend-web/src/Controllers/PayController.php` - `backend-web/src/Services/MembershipService.php`(`listOrders`) ### `GET /api/v1/files` 双模式 **推荐(page 形态)** — 传 `page`≥1 时生效: ```http GET /api/v1/files?page=1&size=12 ``` ```json { "ok": true, "data": { "items": [ { "id": 1, "name": "design.soon", "size": 1024, "version": 1 } ], "total": 25, "page": 1, "size": 12 } } ``` **兼容(offset 形态)** — 未传 `page` 或 `page=0` 时: ```http GET /api/v1/files?limit=50&offset=0 ``` ```json { "ok": true, "data": { "items": [ ], "total": 25, "limit": 50, "offset": 0 } } ``` 两种形态均含 `total`。新前端(首页文件列表、管理端)应使用 `page`+`size`。 ### `GET /api/v1/pay/orders` 会员中心订单历史,默认每页 8 条: ```http GET /api/v1/pay/orders?page=1&size=8 ``` ### 公开端全量 / 非标准列表 | 路径 | 形态 | |------|------| | `GET /api/v1/plans` | `{ items: [...] }` 全量活跃套餐 | | `GET /api/v1/auth/me` | 当前用户 + 会员摘要(`is_member` / `tier` / `name`) | | `GET /api/v1/plans/me` | 完整会员配额信息(云端用量等,按需调用) | | `GET /api/v1/settings` | 扁平 key-value 对象,非 `items` 数组 | | `GET /api/v1/templates` | 首页模板列表(仅 `id/name/type/updated_at`;缩略图按需拉取) | | `GET /api/v1/templates/{id}` | 模板 JSON 数据(门户标准读取端点,见下) | | `GET /api/v1/templates/{id}/thumb` | 模板缩略图(从 `.soon` 内 `frontDisplayPic` 提取) | | `GET /api/v1/templates/{id}/file` | 裸 `.soon` 流(`Cache-Control: max-age=86400`);门户读缓存首选 | | `GET /api/v1/files/{id}/thumb` | 云文件缩略图(需登录;从 `.soon` 内 `frontDisplayPic` 提取) | | `GET /api/v1/soon-models` | 兼容别名,同 `GET /api/v1/templates` | #### `GET /api/v1/templates/{id}`(门户读取模板数据) 与 `GET /api/v1/files/{id}` 对齐,返回 JSON 包装,**非**裸文件流: ```json { "ok": true, "data": { "id": 1, "name": "示例模版", "type": 1, "updated_at": "2026-06-09 00:23:29", "json": "{...}" } } ``` - `data.json` 为 **字符串**(与 `files/{id}` 一致),前端 `JSON.parse(data.json)` 后加载画布 - 公开接口,无需登录 ### Web 虚拟 key 前缀(门户设计页) | 前缀 | 含义 | readJsonFile | 保存 writeFile | |------|------|--------------|----------------| | `soondesign_file:{id}:v{ver}` | 云端已登记文件 | `GET /files/{id}` | `PUT /files/{id}` | | `soondesign_template:{id}` | 云端模板(只读源) | `GET /templates/{id}/file` | `POST /files`(首次保存新建) | | `soondesign_session:...` | 本地/临时会话 | sessionStorage | 已登录 `POST /files`;未登录写 session | - **保存/打开**走 JSON API;**仅**首页「下载」走 `GET /files/{id}/download` 落盘 `.soon` - 门户**读缓存**与最近文件本地优先策略见 [`WEB-LOCAL-CACHE.md`](WEB-LOCAL-CACHE.md);模板首次加载推荐 `GET /templates/{id}/file`(流式 `.soon`,避免 `GET /templates/{id}` 二次包装) --- ## 前端约定 ### 管理后台(`frontend-web/pages/admin`) - 分页状态:`AdminState`(`users` / `orders` / `audits` 的 `page`、`size` 及筛选字段) - 分页 UI:`AdminPager` + `AdminUi.pagerHtml` / `bindPager` - 请求:相对 admin base 的路径,如 `users?page=1&size=20` ### 门户 / 会员 / 首页 | 页面 | 接口 | 默认 size | |------|------|-----------| | 会员订单 | `GET /api/v1/pay/orders` | 8 | | 首页云端文件 | `GET /api/v1/files?page=&size=` | 12 | --- ## 新增列表接口检查清单 1. 响应是否包含 `items` + `total`? 2. 是否使用 `page`(从 1)+ `size`,并在 `data` 中回显? 3. `size` 是否有合理默认值与上限(建议上限 200,管理端;用户订单建议 50)? 4. 筛选参数是否与分页独立(翻页时保留筛选)? 5. 是否在本文档「管理端/公开端」表中登记? --- ## 变更记录 | 日期 | 说明 | |------|------| | 2026-06-08 | 初版:统一 page/size/total;files 兼容 limit/offset;pay/orders 分页 | | 2026-06-08 | 新增 GET /templates/{id} JSON 契约;Web 虚拟 key 前缀;/file 标 legacy | | 2026-06-08 | 新增 GET /files/{id}/thumb;files list 增 `has_thumb`;模板 /file 长缓存 |