# 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` | | `getFilterParams` | `() => Record` | | `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(新接入页) - 异步导出任务、导出历史、邮件通知 - 自定义列模板编辑器、导入 - 小程序端导出