Files
shanxia2.0/docs/superpowers/specs/2026-08-21-web-admin-list-export-design.md
T
zlwuandCursor d8f10b9efe 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>
2026-08-21 16:42:40 +08:00

6.3 KiB
Raw Blame History

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;未选中时前端禁用,不发请求。
[工具栏 ListExportDropdown]
        │  filterParams + columns? + ids?
        ▼
[POST /api/.../export] ──同列表权限──► [查询行] ──ExportColumn──► [openpyxl] ──► StreamingResponse
        ▲                                                                              │
        └──────────────────────── blob download + Message ──────────────────────────────┘

接口约定

请求

{
  // 与对应列表相同的筛选字段
  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(新接入页)
  • 异步导出任务、导出历史、邮件通知
  • 自定义列模板编辑器、导入
  • 小程序端导出