Parcourir la source

docs: add design for web-admin unified list Excel export

Capture backend export infrastructure, ListExportDropdown UX, and
per-resource /export conventions for filtered and selected rows.

Co-authored-by: Cursor <cursoragent@cursor.com>
zlwu il y a 2 mois
Parent
commit
d8f10b9efe
1 fichiers modifiés avec 140 ajouts et 0 suppressions
  1. 140 0
      docs/superpowers/specs/2026-08-21-web-admin-list-export-design.md

+ 140 - 0
docs/superpowers/specs/2026-08-21-web-admin-list-export-design.md

@@ -0,0 +1,140 @@
+# web-admin 列表统一后端导出
+
+**日期:** 2026-08-21  
+**状态:** 已确认  
+**范围:** `web-admin` 所有带筛选/分页的业务列表(系统管理、物流、报销、CRM、地磅等);小程序不在本期。
+
+## 背景
+
+多数列表页缺少导出;少数页(CRM 等)已有后端 Excel 导出,Demo 商品等则用前端 `ArtExcelExport` 分页拉全量再生成。筛选结果达数万行时,前端全量拉取不合理。需要统一为**后端生成 xlsx、前端下载**,并在工具栏提供一致交互。
+
+## 决策摘要
+
+| 项 | 结论 |
+|----|------|
+| 覆盖范围 | 所有带筛选/分页的业务列表;已有导出逻辑保留,仅对齐交互 |
+| 实现方式 | 后端导出(方案 2:共享基建 + 各页注册) |
+| 导出列 | 与当前列表可见列一致;中文表头;状态等为文案非 code |
+| 主操作 | 导出筛选结果 |
+| 次操作 | 导出已选(仅有多选勾选的列表;下拉菜单) |
+| 无多选列表 | 仅单个「导出」按钮(等同导出筛选结果) |
+| 大数据 | 同步流式下载;硬上限 50000 行 |
+| 非目标 | 异步任务、导出历史、邮件、导入、小程序 |
+
+## 架构
+
+### 后端
+
+- 复用并扩展 `backend/app/utils/excel_export.py`(`ExportColumn`、`DictLabelResolver`、`export_rows_to_xlsx_response`)。
+- 各资源仍暴露独立 `POST …/export`(已有 GET 的保持兼容);**不做**全局万能导出 API。
+- 共享约定:
+  - 查询复用对应列表(租户隔离、数据权限、软删、筛选条件一致)
+  - 可选 `ids`:有则只导出这些行(仍叠加权限过滤)
+  - 可选 `columns`:前端传入当前可见列 `prop`;缺省用服务端默认列(对齐列表默认可见列)
+  - 硬上限 **50000**;0 条返回业务错误,不生成空文件
+- 二进制响应路径须在 `API_ENCRYPT_EXCLUDE_PATHS` 中(与现有 CRM 导出一致)。
+
+### 前端
+
+- 新组件 `ListExportDropdown`(中性 `btn-ghost` + `Download` 图标):
+  - `hasSelection=true`:下拉——主按钮「导出筛选结果」,菜单项「导出已选」(无选中则禁用)
+  - `hasSelection=false`:单按钮「导出」
+- 位置:列表工具栏主操作之后、`ListToolbarEndActions` 之前。
+- 统一 blob 下载工具(解析 `Content-Disposition`、loading、错误提示)。
+- 有列显隐时,请求携带当前 `visibleColumns`。
+
+### 数据流
+
+1. **导出筛选结果**:当前筛选参数 + 可选 `columns`,不传 `ids` → 后端查全量筛选结果 → xlsx → 下载。
+2. **导出已选**:同上 + `ids: selectedRowIds`;未选中时前端禁用,不发请求。
+
+```text
+[工具栏 ListExportDropdown]
+        │  filterParams + columns? + ids?
+        ▼
+[POST /api/.../export] ──同列表权限──► [查询行] ──ExportColumn──► [openpyxl] ──► StreamingResponse
+        ▲                                                                              │
+        └──────────────────────── blob download + Message ──────────────────────────────┘
+```
+
+## 接口约定
+
+### 请求
+
+```ts
+{
+  // 与对应列表相同的筛选字段
+  ids?: string[]      // 导出已选
+  columns?: string[]  // 当前可见列 prop;省略则服务端默认列
+}
+```
+
+### 响应
+
+- 成功:xlsx 流;`Content-Disposition` 含中文文件名(沿用 `workbook_to_streaming_response`)。
+- 失败(建议):
+
+| 场景 | 处理 |
+|------|------|
+| 0 条可导出 | 400「没有可导出的数据」 |
+| 超过 50000 | 400「导出超过 50000 条,请缩小筛选范围」 |
+| 无权限 | 与列表一致 401/403 |
+| `columns` 含未知 prop | 忽略未知项;仅导出已登记字段 |
+| `ids` 权限过滤后为空 | 同 0 条 |
+
+### 列定义
+
+- 使用 `ExportColumn(title, field, dict_type?, formatter?)`。
+- 树形路径等展示规则与列表/规范一致(全路径、` / ` 分隔)。
+- 「可见列」:有 `usePersistedListColumns` 时跟当前勾选;否则跟页面表格列定义(排除 selection/index/操作列)。
+
+### 已有导出
+
+- **已有后端导出**(CRM 等):保留既有列与查询;按需补 `ids` / `columns`;前端改为 `ListExportDropdown`(或等价交互)。
+- **现有前端 `ArtExcelExport` 页**(如 Demo 商品):批处理时迁为后端 `/export` + `ListExportDropdown`,不再用浏览器拉全量生成。
+
+## 前端组件草案
+
+**`ListExportDropdown` props**
+
+| Prop | 说明 |
+|------|------|
+| `exportFn` | `(payload) => Promise<Blob>` |
+| `getFilterParams` | `() => Record<string, any>` |
+| `selectedIds` | 已选 id;无多选可不传 |
+| `hasSelection` | 是否支持多选 UI |
+| `visibleColumns` | 可选可见列 |
+| `filename` | 兜底文件名(优先响应头) |
+| `disabled` / `loading` | 外部控制 |
+
+页面接入:声明导出 API、列与表格对齐、挂组件;有勾选则传 `selectedIds`。
+
+## 落地节奏
+
+1. **基建**:后端上限/空数据约定文档化进工具函数;前端组件 + 下载工具。
+2. **样板页**:选一标准列表(如 `system/user` 或 `demo/unit`)端到端打通。
+3. **批处理**:按模块为缺导出页补 `/export` + 挂组件;已有导出页对齐下拉。
+4. **回归**:筛选一致性、可见列、已选、空数据、超限、加密排除路径。
+
+## 权限与安全
+
+- 导出 SHALL 与列表同一鉴权、租户、数据权限;MUST NOT 因导出绕过范围。
+- `ids` MUST 仍叠加租户 + 数据权限,禁止仅凭 id 跨范围导出。
+- `columns` 仅允许该资源已登记 field。
+
+## 验收标准
+
+- [ ] 目标列表工具栏均有导出入口(中性按钮 + 图标)。
+- [ ] 有多选:下拉含「导出筛选结果」「导出已选」;未选中时后者禁用。
+- [ ] 无多选:仅「导出」单按钮。
+- [ ] 导出结果与当前筛选一致;可见列与 Excel 列一致;状态为中文文案。
+- [ ] 0 条 / 超 50000 有明确提示且不下载损坏文件。
+- [ ] 已有导出页交互对齐,业务列含义不回退。
+- [ ] 导出路径不受 API 加密中间件破坏二进制。
+
+## 明确不做
+
+- 前端拉全量再生成 Excel(新接入页)
+- 异步导出任务、导出历史、邮件通知
+- 自定义列模板编辑器、导入
+- 小程序端导出