20260916231505
This commit is contained in:
1 parent
de05498552
commit
182ed86966
126 files changed
+5900
-766
No files matched your search
@@ -141,3 +141,352 @@
|
||||
|
||||
**验证**:`pnpm lint` 无新增问题(唯一 error 是既有 `createCellRenderer.old.js` 的
|
||||
`no-unused-vars`,与本改动无关);`pnpm build --outDir dist-verify --emptyOutDir` 通过。
|
||||
|
||||
## 表单设计器「数据视图」栅格宽度改为普通数字输入
|
||||
|
||||
**需求**:模块管理 → 表单设计 → 数据,`栅格宽度` 列不要下拉、不要「整行 24/24」文案,直接显示数字。
|
||||
|
||||
**改动**(`components/design-editor/FormDesignEditor.vue`):
|
||||
- `formDataColumns()` 里 `b_colspan` 的 `editor` 由
|
||||
`{ type:'select', options: () => COLSPAN_OPTIONS }` 改为
|
||||
`{ type:'input', inputType:'number', onChange: (v, row) => { row.b_colspan = clamp(1..24) } }`,
|
||||
列宽 120 → 100。仅做边界收敛,不做文案换算。
|
||||
- 删掉 `form/useFormGrid.js` 的 `COLSPAN_OPTIONS` 导出(唯一使用点已移除)及对应 import。
|
||||
|
||||
**注意**:`设计`视图属性面板(`FormInspector.vue`)的栅格宽度早就是普通 Input,
|
||||
**数据视图**这一列是最后残留的下拉。`列表配置`(TableDesignEditor,mode=list)的
|
||||
数据视图只有「列宽 b_width」,本来就是数字输入,无同类问题。
|
||||
|
||||
**联动行为未改**:设计视图改栅格宽度仍会压缩同行其它字段(`onColspanChange`),
|
||||
这是保证逻辑行与视觉行一致的必要逻辑;数据视图改值不触发联动。
|
||||
|
||||
**验证**:改动文件 oxlint 0 error;`tests/unit/{form-edit-panel-mount,module-table-edit-panel-dnd,module-list-config-panel-dnd,schema-render}.spec.js` 25 passed。
|
||||
|
||||
## 设计器视图切换改名:「数据」→「批量编辑」
|
||||
|
||||
**背景**:用户问「数据这部分这样显示合适,还是放 modal」。结论是**保持内联切换**,
|
||||
modal 只适合短事务,而这里是 10 列批量编辑网格;`ui/modal/modal.vue` 默认宽 520px
|
||||
且自带全屏按钮,本身就说明装不下。真痛点其实是命名 —— 「数据」会被理解成录入业务数据。
|
||||
|
||||
**改名**:`VIEW_OPTIONS` 第二项 `数据` → `批量编辑`(`value` 仍是 `'data'`,不动状态机)。
|
||||
只改第二项,「设计」保留,减少肌肉记忆成本。没选「属性表」是因为会和设计视图右侧
|
||||
「属性」面板撞概念。
|
||||
|
||||
**落地**:
|
||||
- `FormDesignEditor.vue` / `TableDesignEditor.vue` 的 `VIEW_OPTIONS` 各加 `title`,
|
||||
并用 `Segmented` 的 `#label` 插槽渲染 `<span :title>`(照抄同文件 `EDIT_MODE_OPTIONS` 写法)。
|
||||
`Segmented` 插槽传的是 `option.raw`(原始对象),`segmented-item-text` 这个类**没有任何 CSS**,
|
||||
所以自定义 span 与默认渲染等价,只是多了 title。
|
||||
- 同步注释里的「数据视图」→「批量编辑视图」:两个编辑器 + `EditDataView.vue` + `schemaModel.js`。
|
||||
UI 上的「返回设计视图」按钮文案保留。
|
||||
|
||||
**结论(后续别再提 modal)**:内联切换的另一个硬理由是 `selectedNodeKey ↔ activeKey` 已互通,
|
||||
切视图不丢选中字段;modal 会打断「同一上下文两副镜头」。若哪天真要画布同时可见,
|
||||
正确解法是照 `components/fms-module-list/FmsQuerySettingsDrawer.vue` 用 Drawer,
|
||||
但要把「改完即写草稿」改成「草稿+保存」,代价大于收益。
|
||||
|
||||
**验证**:design-editor 目录 oxlint 0 error;5 个相关测试文件 30 passed。
|
||||
`module-list-components.spec.js` 的 2 个失败是既有基线(高级查询行间连接符,测试只 import
|
||||
fms-module-list 组件,与本次改动无关)。`oxfmt --check` 报 EditDataView/schemaModel/useFormGrid
|
||||
有格式问题,实测是 **CRLF vs LF 的行尾差异**(`diff --strip-trailing-cr` 完全相同),非本次引入。
|
||||
|
||||
## 列表配置的视图切换器移到左边(与表单设计对齐)
|
||||
|
||||
**现象**:两个编辑器都用 `mm-edit__head` 三段式(left/center/right),但只有
|
||||
`TableDesignEditor` 真的有三段 —— `left` 空占位、`center` 放视图切换、`right` 放按钮,
|
||||
`left`/`right` 都是 `flex: 1 1 auto`,于是切换器被挤到**中间**。
|
||||
`FormDesignEditor` 只有 `center` + `right` 两段,切换器实际贴在**最左**。
|
||||
所以同一个控件在两个页签位置不一致。
|
||||
|
||||
**改法**:把 `TableDesignEditor` 的 `mm-edit__view-switch` 移进 `mm-edit__head-left`
|
||||
(放在 `编辑方式` 之前,保证两个页签的**最左控件是同一个**),删掉空的 `head-center` div
|
||||
及其 CSS。`FormDesignEditor` 不动。
|
||||
|
||||
**验证方式(可复用)**:临时写 `tests/unit/.tmp-head-check.spec.js`,用 `mount` 挂
|
||||
`ModuleListConfigPanel` / `ModuleEditPanel`,断言 `.mm-edit__head-left` 内含
|
||||
`.mm-edit__view-switch` 且 `.mm-edit__head-center` 不存在,并 `console.log(head.html())`
|
||||
肉眼核对结构,跑完删除。比开浏览器登录(`g3soft` 密码非空串,空串登录返回 401)快得多。
|
||||
注意:用 `#label` 插槽渲染的 span **没有** `segmented-item-text` 类,选择器要写
|
||||
`.segmented-item-label > span`(写 `.mm-edit__view-switch span` 会把外层 label span 也算进去,
|
||||
每个标签命中两次)。
|
||||
|
||||
**遗留**:`FormDesignEditor.vue` 里那个 `mm-edit__head-center` 类名已名不副实(它是
|
||||
第一个 flex 子元素,视觉上就是左),改名属可选清理。
|
||||
|
||||
### 补:FormDesignEditor 的 `mm-edit__head-center` 改名(上条遗留已清)
|
||||
|
||||
模板类名 `mm-edit__head-center` → `mm-edit__head-left`,删掉原 `.mm-edit__head-center`
|
||||
CSS(`flex: none` + 居中),改由既有的 `.mm-edit__head-left`(`flex: 1 1 auto` +
|
||||
`justify-content: flex-start`)接管;同时改掉上面那段「左右两翼挤出中间位、保证居中」
|
||||
的过期注释。**两个编辑器的 header 骨架现在完全一致**:
|
||||
`head-left`(视图切换) + `head-right`(操作按钮),用临时挂载测试断言过 `LIST === FORM`。
|
||||
|
||||
## 导出 / 批量导出:参考实现调研(讨论阶段,尚未定案)
|
||||
|
||||
**参考实现 `D:/workspace/code/g3hd/frontend` 有两条完全不同的导出路径:**
|
||||
|
||||
1. **前端生成**(`src/utils/excel.js` 的 `ExportVxeTableExcel` / `ExportExcel`):
|
||||
纯浏览器 ExcelJS。数据源是 VXE 表格内存 —— `getCheckboxRecords()` 有勾选就用勾选,
|
||||
否则 `getFullData()`。支持多级表头(手算 merge)、列宽、displayFormat、页脚合计。
|
||||
**无任何权限校验**(数据已在浏览器里)。
|
||||
2. **后端生成**(`src/components/g3-excel-export/index.vue` + 后端 `ExcelController`):
|
||||
「导出所有页」两步向导。步骤1 列配置表存旧表 **`s_columnexcel`**(per view + userid:
|
||||
col_FieldName/col_Caption/col_Width/col_Visible/ShowSummary);步骤2 按 `limit=10000`
|
||||
把 total 切段,逐段 `POST /excel/exportAll`(EasyExcel),前端 JSZip 打包 zip。
|
||||
后端 SQL 用 `select identity(int,1,1),* into #tmptj from view ... where indexid between a and b`
|
||||
拿行号做区间切片。
|
||||
|
||||
**旧实现的坑(别照搬)**:
|
||||
- `handleDownloadZip` 存的是 `mx_mapfilename`,释放时却 `revokeObjectURL(file.url)` → blob URL 从未释放。
|
||||
- 列清单由前端传后端照单全收 → 字段权限形同虚设。
|
||||
- `#tmptj` 临时表并发导出会让 tempdb 膨胀。
|
||||
|
||||
**新系统现状(已核实)**:
|
||||
- 前端**无**任何导出代码;列表页 `FmsModuleListPage.vue` 工具栏已有「更多操作」Dropdown
|
||||
(刷新数据 / 恢复栏位设置 / 保存栏位设置),导出入口可挂这里。
|
||||
- 后端 **无 POI / EasyExcel 依赖**(pom 里没有)。
|
||||
- **后端完全没有权限过滤**:`s_user_field_power` / `s_user_data_power` / `s_power` 在
|
||||
fms-api 里**零命中**。目前只有 JWT 鉴权 + 机构数据源路由,任何登录用户都能直接
|
||||
POST `/api/data/loaddata` 取全表。→ 导出会是第一个把「后端权限出口」变成刚需的功能。
|
||||
- 设计文档**已定义**:`s_user_field_power.b_export`(字段级导出权限)、`b_view=0` 的字段
|
||||
在导出中统一移除(14.3 节);开发规范第 10 条要求字段可见/可查询/可导出权限必须在
|
||||
后端 SQL 落实。`export` 已是标准动作权限点(`ModulePowerPanel.ACTION_NAMES`),
|
||||
编码格式 `action.{moduleId}.export`。
|
||||
- 配置载体现成:`s_module_schema.view`(系统默认,columns 已含 field/visible/width/format,
|
||||
支持 `type:'group'` 分组表头)+ `s_user_module_pref.view`(个人增量覆盖)。
|
||||
**不需要新建 s_columnexcel 之类的表。**
|
||||
|
||||
**推荐方向(待用户拍板)**:后端生成 xlsx(一次请求 + 流式游标 + SXSSF),
|
||||
导出列/合计复用 view schema,前置补 `SqlPermissionBuilder`(动作权限 + 字段白名单 +
|
||||
数据范围 AND)。详见当次回复。
|
||||
|
||||
### 导出设计:用户已明确的四条约束
|
||||
|
||||
1. 「批量导出」= **全量导出**,单模块,不需要多模块同时导。
|
||||
2. **普通导出必须与列表一致**(列/条件/排序沿用列表);**高级导出**可选列并**保存导出列**。
|
||||
3. **先不做行数上限**。
|
||||
4. 合计行 + 多级表头**都必须**。
|
||||
|
||||
### 第二个参考实现:`D:/workspace/code/container-ocr`
|
||||
|
||||
- 前端 `ocr-vue/src/views/container/high-download-dialog.vue`(652 行)「高级下载」:
|
||||
弹窗**自带独立查询条件**(上传日期区间/箱号/操作员,条件为空即全部)、顶部节流 300ms
|
||||
实时 `COUNT(*)` 显示「共 N 条」、模式三选一(全部导出 / 按区间导出 / 分页拆分导出)、
|
||||
重置 + 下载、JSZip + file-saver 打包 zip。
|
||||
- 后端 `ocr-springboot` `POST /data/range`(`DataListController` → `DbUtils.range`):
|
||||
`SELECT <cols> FROM (SELECT ROW_NUMBER() OVER (ORDER BY <order>) AS row_num, t.* FROM view t
|
||||
WHERE 1=1 AND <cond>) sub WHERE sub.row_num BETWEEN ? AND ?`,**1 基闭区间**。
|
||||
**比 g3hd 的 `#tmptj` 临时表干净,可直接借鉴**;无 ORDER BY 时用 `ORDER BY (SELECT NULL)` 兜底。
|
||||
注意它把 `validateFinalSql` 里的非法关键字检查**注释掉了**,别跟着抄。
|
||||
|
||||
### 我给出的方案(待确认)
|
||||
|
||||
- 两个入口:普通导出(不弹窗,列/条件沿用列表,范围=勾选行或当前页);批量导出(弹窗,参考 ocr-vue 形态 + 选列 + 保存列 + 三模式)。
|
||||
- **后端是唯一出口,且后端自己按 moduleId 读 `s_module_schema.view` + `s_user_module_pref` 推导导出列**,不信任前端传的列清单(否则字段权限形同虚设)。这同时天然保证「与列表一致」。
|
||||
- 保存导出列:`s_user_module_pref` 加 `b_schema_type = 'export'`,零新表。
|
||||
- 合计行/分组表头来自 view schema(列节点加 `summary`,group 节点映射 EasyExcel 的 `List<List<String>>` head)。
|
||||
- 不上限的替代保护:全局导出并发闸门 + 每用户 1 个 + 语句超时 + 流式写 + 引导用分卷模式。
|
||||
- 接口 `POST /api/export/list`(moduleId / searchCondition / orderBy / rowKeys+keyField / columns? / rangeStart+rangeEnd? / volumeSize? / withSummary / format)。
|
||||
|
||||
## 导出 P0 落地(后端权限出口 + 导出接口 + 前端导出按钮)
|
||||
|
||||
用户确认了三个设计问题后开工:导出范围规则(有勾选导勾选、否则当前条件全部)、
|
||||
保存导出列存 `s_user_module_pref`(`b_schema_type='export'`,P1 用)、高级导出条件区复用现有查询面板。
|
||||
|
||||
**新增文件**
|
||||
- `fms-api/.../service/SqlPermissionService.java` —— 三层权限(动作/字段/数据范围),
|
||||
是「后端 SQL 权限出口」的第一个实现,后续列表查询复用。
|
||||
- `fms-api/.../service/ExportSchemaService.java` —— 按 moduleId 读 s_module + s_field +
|
||||
s_module_schema.view,按**与列表相同的顺序**还原导出列(含 group 节点 → 多级表头、summary → 合计)。
|
||||
- `fms-api/.../service/ExportService.java` —— 拼 SQL(条件 + 数据范围 + 勾选行全 AND)、
|
||||
SQL 层聚合合计、JDBC 游标 + POI SXSSF 流式写;全局并发 2 / 每用户 1。
|
||||
- `fms-api/.../service/ExportHeaderWriter.java` —— 多级表头:**分组只横向合并、叶子只纵向合并**,
|
||||
靠「分组层级必浅于叶子」保证合并区域永不重叠。
|
||||
- `fms-api/.../controller/ExportController.java` —— `POST /export/list`。
|
||||
- 测试:`ExportServiceTests`(14 项,SQL 拼装 + 合并规则)、`ExportServiceIntegrationTests`(3 项,连真实库)。
|
||||
|
||||
**修改**
|
||||
- `pom.xml` 加 `poi-ooxml:5.2.5`;`DbUtils` 暴露 `buildSafeOrderBy`(原私有 buildPageOrderBy 的薄包装)。
|
||||
- 前端 `services/exportService.js`(blob + `filename*` 解析 + 错误 JSON 识别)、
|
||||
`services/http.js` 响应拦截器加 blob 直通、`FmsModuleListPage` 抽出 `buildSearchCondition()`
|
||||
供取数与导出共用(这才是「与列表一致」的落地方式),工具栏加「导出」按钮。
|
||||
|
||||
**写测试时的两个发现(实现是对的,是我的预期错了)**
|
||||
1. 两级表头里叶子已在最后一行,**不该**再纵向合并 —— 否则会与分组的横向合并区域重叠。
|
||||
2. 叶子标题写在自己所在层级并向下合并,不是写在第一行。
|
||||
|
||||
**验证**
|
||||
- 后端:`ExportServiceTests` 14 passed、`ExportServiceIntegrationTests` 3 passed
|
||||
(真实库导出 → xlsx 可被 POI 解析、表头/数据行正确、勾选行只出 1 行、模块不存在时响应体为空即未写流)。
|
||||
- 全量后端 46 项,仅 `OrgDataSourceFactoryTests.databaseConnectionFailureDoesNotRetry` 失败(既有基线)。
|
||||
- 前端:改动文件 oxlint 0 error;`fms-module-list-page` 3 个失败均为引用已不存在的
|
||||
`.filter-drawer` 的过期断言(既有基线)。
|
||||
|
||||
**未做(留给 P1)**:批量导出弹窗(条件区/条数/三模式/选列/保存列)、前端按 `canPower` 隐藏导出按钮、
|
||||
合计与分组表头在模块管理里的配置入口(现在没有任何模块配 `summary` / `group`,所以线上看不到合计行)。
|
||||
|
||||
## 导出功能交接文档
|
||||
|
||||
新建仓库根 **`FMS导出功能设计与进度.md`**(与其它设计文档同级,226 行 / 9 节),
|
||||
内容:现状一句话 → 已完成文件清单 → 接口契约 → 设计决策与理由 → 怎么验证(含本机 mvn 的可行命令)
|
||||
→ 数据库现状(解释为什么看不到合计行)→ 未完成(P1/P2 + 已知口径取舍)→ 批量导出弹窗设计 → 附元数据事实。
|
||||
|
||||
**用户明确**:合计行与分组表头**以后再说**,所以 P1 第一项(合计/分组的配置入口)暂缓,
|
||||
下一步应优先批量导出弹窗(后端 `columns` / `rangeStart` / `rangeEnd` 参数已实现,弹窗可直接对接)。
|
||||
|
||||
### 澄清:普通导出现在就是「全量」,不是当前页
|
||||
|
||||
用户问「批量导出还没做吗」,核对代码时发现一个**与最初讨论不一致的事实**:
|
||||
`ExportService.buildSelectSql` 只在传了 `rangeStart`/`rangeEnd` 时才加 `OFFSET/FETCH`,
|
||||
不传就是不限行数 → 前端「导出」按钮(不传区间)实际导的是**当前查询条件下的全部行**。
|
||||
已把 `ExportServiceIntegrationTests` 的断言从 `getLastRowNum() >= 1` 收紧为
|
||||
`== rowCountOfModule()`(`b_con_type` 8 行 → 导出 8 行数据),跑通确认。
|
||||
|
||||
**影响**:「批量导出」剩下的差异只有 选列 / 区间 / 分卷 / 保存导出列 / 条数预览,
|
||||
数据范围与普通导出相同。两种走法待产品定:
|
||||
(a) 保留现状(一键全量,批量导出靠选列分卷区分);(b) 普通导出改当前页(前端补传当前页区间,后端已支持)。
|
||||
已写进 `FMS导出功能设计与进度.md` 第 0 节与 6.1 第 0 项,**未定之前不要动**。
|
||||
|
||||
同时给文档补了 **第 9 节「实施过程中踩到的坑」**:环境(start.cmd 路径错 / mvn 起不来 / 8088 是旧实例 /
|
||||
登不进浏览器)、选型(EasyExcel→POI 的原因)、实现(排序校验找错口、多级表头合并规则第一版想错)、
|
||||
数据权限现实(权限表全空、无模块配 summary/group)、顺带发现的既有问题。
|
||||
|
||||
## Excel 库换成 Apache Fesod (Incubating)
|
||||
|
||||
**需求**:把第一版的 POI SXSSF 换成 Apache Fesod(EasyExcel → FastExcel → Apache Fesod 的续作)。
|
||||
|
||||
**坐标**(`org.apache.fesod:fesod-sheet:2.0.2-incubating`,支持 jdk8–jdk25):
|
||||
自带 POI 5.5.1 / commons-csv / ehcache,**不要再单独引 POI**。包名 `org.apache.fesod.sheet.*`,
|
||||
入口类 `FesodSheet`(同时保留 `EasyExcel` / `FastExcel` 兼容类名),API 与 EasyExcel 一致。
|
||||
**查 API 不要靠记忆**:`jar tf` + `javap` 直接看 D:/data/maven 里下下来的 jar 最快。
|
||||
|
||||
**改动**
|
||||
- 删 `ExportHeaderWriter.java`(手写合并区域那段),新增 `ExportHeadBuilder`(表头树 →
|
||||
`List<List<String>>`)、`ExportStyleHandler`(`CellWriteHandler`:表头/数据/合计三样式 + 逐列列宽)、
|
||||
`ExportWorkbookWriter`(表头 + 分批明细 + 合计行 → xlsx,行来源抽象成 `RowSource`,
|
||||
所以能脱离数据库单测)。
|
||||
- `ExportService`:写出层重写;`applyValue` → `normalizeValue`(时间落文本、其余保留原类型);
|
||||
游标改成「先 `executeQuery()` 再设响应头」,这样 SQL 报错仍能返回 JSON。
|
||||
- 多级表头合并交给 `automaticMergeHead(true)`;`ExportServiceTests` 里那 4 个合并区域断言
|
||||
换成表头结构断言;新增 `ExportWorkbookWriterTests`(6 项)。
|
||||
|
||||
**被自己的测试抓到的两个 Fesod 行为坑(已修)**
|
||||
1. **0 行导出会生成「没有任何工作表」的空 xlsx** —— Fesod 只在 `write` 被调用时才创建工作表,
|
||||
所以最后一批即使为空也必须调一次。
|
||||
2. 合计行是拼出来的数据行,「合计」标签要显式构造(落在第一个无聚合值的列)。
|
||||
|
||||
**验证**:导出相关 23 项全过(14 + 6 + 3,含连真实库的端到端);后端全量 52 项,
|
||||
唯一失败仍是既有基线 `OrgDataSourceFactoryTests.databaseConnectionFailureDoesNotRetry`。
|
||||
文档 `FMS导出功能设计与进度.md` 已同步(§1.1 文件清单 / §3.5 §3.7 决策 / §4.2 计数 / §9.2 §9.3 踩坑)。
|
||||
|
||||
## 批量导出弹窗(P1 第 2 项)完成
|
||||
|
||||
**需求**:导出按钮改成只显示图标;然后做批量导出功能。
|
||||
|
||||
**后端新增**
|
||||
- `POST /api/export/count` → `{total}`。**单独做而不是复用列表分页的 total**,因为它必须走
|
||||
与导出完全相同的权限链路(动作权限 + 字段裁剪 + 数据范围),列表分页的 total 不受数据范围约束。
|
||||
- `volumeSize` 分卷:一次请求后端切卷打包 zip(每卷一个 xlsx,命名 `{模块名}_{起}-{止}.xlsx`),
|
||||
与区间互斥、逐卷不输出合计。`ExportRequest` 加了 `volumeSize`(现在 10 个字段)。
|
||||
与旧系统 `g3hd`「前端切段 N 次请求 + 前端 JSZip」的做法不同 —— 前端不用管进度与重试。
|
||||
- `ExportService` 重构出 `prepare()`(权限 + 列推导 + WHERE/ORDER 共用)与
|
||||
`countRows()`;`buildSelectSql` 加 `paged` 参数(区间与分卷共用 OFFSET/FETCH)。
|
||||
|
||||
**前端新增/改动**
|
||||
- `FmsExportDialog.vue`:选列(默认全选或已保存的列,可拖拽排序)+ 三种方式(全部/区间/分卷)+ 保存配置。
|
||||
**条件只读**(沿用列表条件),不另写一套条件表单。
|
||||
- `useModulePref.js`:`loadModulePref`/`saveModulePref` 加 `schemaType` 参数(默认 `query`),
|
||||
供「我的导出列」复用;个人导出列存 `s_user_module_pref` 的 `b_schema_type='export'`,
|
||||
「恢复列表列」= 删除该行覆盖。
|
||||
- `FmsModuleListPage`:导出按钮改纯图标(`class="list-toolbar__icon"`,间距回到 4px);
|
||||
「更多操作」加「批量导出」;抽出 `exportColumns`(列表可见列 + 字段名称)传给弹窗。
|
||||
- 测试 `tests/unit/fms-export-dialog.spec.js`(8 项,一次通过)。
|
||||
|
||||
**踩坑(都写进文档 §9.3 了)**
|
||||
- 测试里用 `b_id = N'绝不存在的值'` 构造「查不到」,SQL Server 报
|
||||
`Error converting data type nvarchar to bigint` —— **`b_con_type.b_id` 是 bigint**(业务表用雪花 ID),
|
||||
不是字典表那种 varchar。教训:构造测试条件前先确认列的真实类型。
|
||||
- 二进制响应不要调 `response.setCharacterEncoding`,否则 content-type 变成
|
||||
`application/zip;charset=UTF-8`。
|
||||
|
||||
**验证**:后端 54 项(唯一失败仍是既有基线 `OrgDataSourceFactoryTests`);
|
||||
导出相关 25 项全过(14 + 6 + 5,含真实库的导出/计数/分卷);前端弹窗 8 项全过。
|
||||
既有前端基线失败:`fms-module-list-page.spec.js` 3 项(`.filter-drawer` 已不存在)、
|
||||
`module-pref.spec.js` 2 项(默认布局断言与实现不一致,测试未跟上)。
|
||||
|
||||
### 批量导出弹窗改版(用户反馈「看起来很乱」)
|
||||
|
||||
**改动**(`FmsExportDialog.vue` 重写)
|
||||
- **删掉全部说明性文字**:模式说明(单个文件 / 只导第 N 到 M 条 / 每 N 条一个文件打包 zip)、
|
||||
条件说明(沿用列表当前条件)、底部那段「分组表头与合计行按列表配置生成…」全删。
|
||||
顶部原来那条 summary 也去掉,条数挪到 footer 左侧。
|
||||
- **导出列改竖排列表 + 可拖拽排序**(`vuedraggable`,项目已有依赖,设计编辑器同款;
|
||||
`handle=".export-dialog__handle"` + GripVertical 图标)。显示顺序即导出顺序。
|
||||
- **动作从 4 个减到 2 个**:「全选」「保存为我的导出列」。
|
||||
关键洞察:候选集就是列表可见列,所以**「全选」等价于原来的「恢复列表列」**(顺手清个人覆盖);
|
||||
「反选」删掉。
|
||||
- **导出方式用 `Segmented`** 替代三个带说明的 radio;参数行只在需要时出现,且去掉「第/条」等冗余字。
|
||||
- 个人保存的列:已保存的按保存顺序排前面并勾上,其余列补在后面(不勾选),这样用户还能勾回来。
|
||||
|
||||
**踩到的坑**:`ui/modal` 的 `footer` prop 为 false 时**整个 footer 容器(含 `#footer` 插槽)都不渲染**,
|
||||
不是「隐藏默认按钮、保留插槽」。用 `#footer` 自定义按钮时必须让 `footer` 保持默认 true。
|
||||
(测试里表现为 `footerButtons()[1]` undefined。)
|
||||
|
||||
**验证**:`tests/unit/fms-export-dialog.spec.js` 8 项全过(新增「保存顺序优先 + 未保存列补在后面」
|
||||
的断言;「全选」断言 emit 空数组)。**拖拽手势本身没法在 jsdom 里模拟** —— 只验证了
|
||||
「渲染顺序 = 数组顺序 → 导出列顺序」这条链路,SortableJS 的实际拖动依赖真浏览器,
|
||||
但它与设计编辑器用的是同一个库同一套写法。
|
||||
|
||||
### 批量导出弹窗:导出方式参数行对齐
|
||||
|
||||
用户反馈「分卷的也出现在下面」。原因是参数行原来作为 inline span 跟在 `Segmented` 后面,
|
||||
靠 flex-wrap 决定换不换行 —— 区间参数短、常留在行内,分卷参数长(「每 5000 条一个文件,共 2 个」)
|
||||
才换行,两种模式位置不一致。
|
||||
|
||||
改法:`导出方式` 改成「左标题 + 右控件列」的 grid,控件列里 `Segmented` 与参数行纵向排列,
|
||||
于是**区间与分卷的参数都固定单独占一行、与 Segmented 同列对齐**。
|
||||
纯布局改动,`tests/unit/fms-export-dialog.spec.js` 8 项仍全过(选择器 `.export-dialog__params input` 未变)。
|
||||
|
||||
### 批量导出弹窗:导出方式布局再修 + 按钮文案
|
||||
|
||||
用户要求:① 导出方式 label 与 Segmented 同一行;② 填写的参数单独一行;③ 主按钮文案「下载」→「导出」。
|
||||
|
||||
上一版把 Segmented 与参数行放进了一个「控件列」div(flex column),再用 `align-items: center`
|
||||
对齐 grid —— 结果标题是相对**整个控件列**(两行高)垂直居中的,看起来就掉到两行中间了。
|
||||
改成**三个 grid 项平铺**:标题(row1 col1)、Segmented(row1 col2)、参数行(row2 col2),
|
||||
各居自己所在行,标题自然与 Segmented 同行,参数行单独一行且与 Segmented 左对齐(无需硬编码缩进)。
|
||||
`tests/unit/fms-export-dialog.spec.js` 补了一条结构断言锁住这个布局(9 项全过)。
|
||||
|
||||
### 批量导出弹窗:Segmented 不要 100% 宽
|
||||
|
||||
`导出方式` 的两列 grid 里,Segmented 作为 grid 项默认 `justify-self: stretch`,
|
||||
被拉伸到整列宽(看起来占满一行)。加 `.export-dialog__modes { justify-self: start }` 按内容宽度靠左;
|
||||
参数行同理(`display: inline-flex` 在 grid 里也会被拉伸),一并加 `justify-self: start`。
|
||||
**通用提醒**:grid 里的 `display:flex/inline-flex` 子元素默认都会被拉伸,想按内容宽度必须显式 `justify-self: start`。
|
||||
|
||||
### 批量导出弹窗:按钮改名 + 列区加高
|
||||
|
||||
- 「保存为我的导出列」→「**保存配置**」(4 个字,与旁边的「全选」等宽,动作区更紧凑)。
|
||||
测试里的按钮文案查找同步改了。
|
||||
- 导出列列表 `max-height` 220px → **340px**(约 10 行)。
|
||||
|
||||
### 列表合计:该页合计 + 总合计(table footer 两行)
|
||||
|
||||
1. **schemaRender.js**:`buildListConfig` 透传 `node.summary`(`sum`/`count`)。
|
||||
2. **FmsTable.vue**:新增 `footerData` / `footerConfig` props 传给 `StkTable`;
|
||||
加 `:deep(.stk-footer) td` 样式(上边框 + 背景 + 加粗)。
|
||||
3. **FmsModuleTable.vue**:
|
||||
- 新增 `grandTotals` prop(后端 `/export/totals` 返回的全量聚合)。
|
||||
- `computed footerData`:按 `listConfig.summary` 从 `dataSource` 实时算「该页合计」;
|
||||
第一列显示标签("该页合计"/"总合计"),其余列按字段类型格式化(money 保留 2 位)。
|
||||
4. **FmsModuleListPage.vue**:
|
||||
- `fetchData` 成功后异步调 `loadTotalsApi` 拿总合计。
|
||||
- 普通导出(toolbar 按钮)改为**仅导出当前页**(无勾选时补传 `rangeStart/rangeEnd`),
|
||||
使「导出合计 = 当页合计」自洽;勾选行时仍导勾选行。
|
||||
5. **exportService.js**:新增 `loadTotalsApi`(调 `POST /export/totals`);顺手修了 `countExportApi`
|
||||
里未定义 `post` 的 bug(改为 `http.post`)。
|
||||
6. **后端**:`ExportService` 已在此前改好(区间合计走 `ROW_NUMBER` 子查询、新增 `totals()` 方法、
|
||||
`ExportController` 新增 `/export/totals` endpoint)。
|
||||
|
||||
测试:前端 lint 0 error,export 对话框 9 项全过,fms-table-virtual 11 项全过;
|
||||
后端 ExportService*Tests 24 项全过;全项目基线 13 失败未增加。
|
||||
@@ -137,3 +137,59 @@
|
||||
复合主键用逗号分隔(如 `s_field` 传 `b_module_id,b_field`)。执行顺序 delete → update → insert。
|
||||
- 模块元数据(`s_module` / `s_field` / `s_module_schema`)**不由后端接口下发**,
|
||||
前端从本地 schema JSON 解析,所以改元数据后无需重启后端。
|
||||
|
||||
## 编译 / 测试 fms-api(本机 mvn 入口全是坏的,用这条)
|
||||
|
||||
`fms-api/start.cmd` 里的 `JAVA_HOME=D:\devtool\jdk\jdk-21` 与
|
||||
`MAVEN_HOME=D:\devtool\maven\apache-maven-3.8.9` **两个路径都不存在**
|
||||
(实际是 `D:\devtool\jdk\jdk21`、`D:\devtool\maven`),照抄必失败。
|
||||
`/d/devtool/maven/bin/mvn`(shell 脚本)在 Git Bash 下报
|
||||
`找不到或无法加载主类 org.codehaus.plexus.classworlds.launcher.Launcher`;
|
||||
从 Bash 调 `cmd.exe` 被安全策略拦;另一个 shell 工具的输出不回流(得写文件再读)。
|
||||
|
||||
**唯一可行做法:直接用 JDK 拉起 Maven 的 classworlds launcher。**
|
||||
|
||||
```bash
|
||||
cd /d/workspace/code/fms/fms-api
|
||||
/d/devtool/jdk/jdk21/bin/java \
|
||||
-Dclassworlds.conf="D:/devtool/maven/bin/m2.conf" \
|
||||
-Dmaven.home="D:/devtool/maven" \
|
||||
-Dmaven.multiModuleProjectDirectory="D:/workspace/code/fms/fms-api" \
|
||||
-cp "D:/devtool/maven/boot/plexus-classworlds-2.11.0.jar" \
|
||||
org.codehaus.plexus.classworlds.launcher.Launcher -o -Dtest=XxxTests test
|
||||
```
|
||||
|
||||
- 本地仓库是 **`D:\data\maven`**(settings.xml 里改过 localRepository),不是 `~/.m2`。
|
||||
- 新依赖要联网首次下载,去掉 `-o` 即可(`repo1.maven.org` 可达)。
|
||||
- 中文乱码时输出里加 `grep -a`。
|
||||
- `target/` 被 git 跟踪,编译后会有一堆 `.class` / surefire 报告显示为 modified,属正常噪音。
|
||||
|
||||
**既有基线失败(不是自己引入的,别去修)**:
|
||||
- `OrgDataSourceFactoryTests.databaseConnectionFailureDoesNotRetry`
|
||||
(断言 Druid `getConnectionErrorRetryAttempts()==0`,实测 1)。
|
||||
- 前端 `tests/unit/fms-module-list-page.spec.js` 里所有引用 `.filter-drawer` /
|
||||
`.filter-row__*` 的用例 —— 这些类名在 `src/` 里**已不存在**,是高级查询面板改版后没更新的过期断言。
|
||||
|
||||
## 导出功能(P0 已落地)
|
||||
|
||||
- 入口:`POST /api/export/list`(`ExportController`),**返回文件流**,不是 ApiResponse。
|
||||
- 分层:`SqlPermissionService`(动作权限 + 字段白名单 + 数据范围)→ `ExportSchemaService`
|
||||
(按 moduleId 读 s_module + s_field + s_module_schema.view,**与列表同源推导导出列**)→
|
||||
`ExportService`(拼 SQL、SQL 层聚合合计、JDBC 游标 + Fesod 流式写)→
|
||||
`ExportWorkbookWriter`(表头/明细/合计 → xlsx)→ `ExportHeadBuilder`(表头树 → `List<List<String>>`)
|
||||
+ `ExportStyleHandler`(三种样式 + 逐列列宽)。
|
||||
- 关键约束:**导出列不由前端传**,否则字段权限(`s_user_field_power.b_export`)形同虚设;
|
||||
**所有校验必须在写 `getOutputStream()` 之前完成**,且游标要「先 `executeQuery()` 再设响应头」,
|
||||
否则 SQL 报错时用户拿到的是半截文件而不是 JSON 错误。
|
||||
- 依赖:**`org.apache.fesod:fesod-sheet:2.0.2-incubating`**(Apache Fesod Incubating = EasyExcel 的
|
||||
Apache 续作,包名 `org.apache.fesod.sheet.*`,入口类 `FesodSheet`,API 与 EasyExcel 一致,
|
||||
自带 POI 5.5.1,**不要再单独引 POI**)。多级表头合并用 `automaticMergeHead(true)`,
|
||||
各列标题层数可不同(Fesod 底部对齐补空,列名落在最后一行)。
|
||||
**两个 Fesod 行为坑**:① 0 行导出必须仍调用一次 `write`(哪怕传空集合),否则生成的 xlsx
|
||||
**没有任何工作表**;② 合计行是拼出来的数据行,「合计」标签要自己构造(落在第一个无聚合值的列)。
|
||||
- 合计行只在 view schema 的列节点带 `summary: sum|count` 时才出现;**目前没有任何模块配了它**,
|
||||
所以线上看不到合计行(多级表头同理,没有模块配 `type:group`)。
|
||||
- 前端:`services/exportService.js` + `FmsModuleListPage` 工具栏「导出」按钮;
|
||||
`services/http.js` 的响应拦截器加了 blob 直通分支(下载类响应要能读 headers)。
|
||||
- 权限现状:`s_power` 只有 2 行(`s_i18n_type`),`s_user_field_power` / `s_user_data_power`
|
||||
都是空表。非超管走白名单语义会被拒(「没有导出权限」),**g3soft 提权可直接用**。
|
||||
Reference in new issue
Block a user