# 工作进度-2:报表查询控件体系(Web 版 Page_ReportQueryTool 同构化) > 时间:2026-10-19(承接工作进度-1) > 范围:修复报表编辑器 / 报表中心缺陷 + 建立 Web 端「自定义控件注册中心」与「选择页」机制 > 参考实现:WinForms 端 `F9.Common.Report.Pages.Page_ReportQueryTool`、`F9.Common.Report.SelectPages`、`F9GlobalUIHelper` --- ## 一、本轮目标 1. 修复编辑报表页首开报「请求失败」、明细字段 / 模板参数不显示 2. 把 WinForms 端「按 `ControlType` 分发创建控件」的机制,在 Web 端做成**可注册、可扩展的控件模块** 3. 庄口类参数改为**打开选择页**(不再用下拉塞全量数据) 4. 部门控件按**口径**取数(不是一把全量) 5. 修复编辑器「添加参数后看不到新参数」 --- ## 二、缺陷修复清单 | # | 现象 | 根因 | 解决 | |---|---|---|---| | 1 | 首次进编辑报表报「请求失败」,明细字段 / 模板参数暂无数据 | `GET /sys/report/{id}/template` 返回**裸 .grf JSON**(无 `ApiResult` 包装),`request.js` 拦截器强制 `data.code === 200` → 判失败 → 前端兜底空模板 | 拦截器放行非 ApiResult 响应(`!('code' in data)` 时原样返回) | | 2 | 报表中心点击后**整页空白** | `select/index.js` 写了 `export { F9SelectPage } from './F9SelectPage.vue'`;**.vue 只有默认导出** → `SyntaxError` → vue-router 导航抛错 → 白屏 | 先 `import` 再具名导出 | | 3 | 编辑器「添加参数后看不到新参数」 | `.left` 用 `max-height: calc(100% - 320px)` + 内层 `overflow`,两层滚动把新卡片截在可视区外 | 改「tab 头固定 + 内容区独立滚动」;`addParm` 后 `scrollIntoView` | | 4 | 前缫车间控件列出**全部 38 个部门** | 部门控件直接 `GET /sys/department` 全量,未实现 F9 的口径过滤与建树 | 新建 `utils/departments.js`(Web 版 `F9GlobalUIHelper`) | | 5 | 落丝记录庄口用下拉塞 1000 条 | 原实现 `pageSize:1000` 拉全量 | 改为打开「工艺庄口选择页」 | --- ## 三、Web 自定义控件模块 WinForms 端结构: ``` GetParamControl(prm) ├─ ControlType 以 "F9" → GetParamControlOfF9(...) // 业务选择页 / 业务下拉 └─ ControlType 以 "AntdUI" → GetParamControlOfAntdUI(...) // 基础控件 → 每个分支创建控件 + 注册取值函数 _paramGetValudFuncs ``` Web 端同构为「**控件定义注册中心 + 统一渲染组件**」: | 文件 | 说明 | 对应 WinForms | |---|---|---| | `web/src/utils/gridreport/controls/registry.js` | `registerControl / getControlDef / groupedControlOptions / resolveControlDef / fallbackDef` + 取值工具(`toOptions / apiOptions / registryOptions / pickRows / rowLabel`) | `_paramGetValudFuncs` | | `web/src/utils/gridreport/controls/antdControls.js` | 一个 type 一个函数 + `ANTD_BUILDERS` 分发 | `GetParamControlOfAntdUI` | | `web/src/utils/gridreport/controls/f9Controls.js` | 顶部注入数据源,一个 type 一个函数 + `F9_BUILDERS` 分发 | `GetParamControlOfF9` | | `web/src/utils/gridreport/controls/index.js` | 装配注册两组控件;`parseStaticDs / fetchServiceDs / loadParamOptions` | `GetParamControl` | | `web/src/components/report/F9ParamControl.vue` | 统一渲染(input / select / multiselect / date / yearmonth / year / picker / 树形下拉) | 控件实例 | | `web/src/utils/departments.js` | 部门口径 + 建树 + 默认选中 | `F9GlobalUIHelper` | **已注册控件 38 个**(6 个 AntdUI + 32 个 F9),编辑器「控件类型」下拉按 group 分组自动列出。 ### kind 映射与取值口径 | Web kind | 渲染 | F9 对应 | 取值 | |---|---|---|---| | `input` | el-input | `AntdUI.Input` | 文本 | | `select` | el-select / **el-tree-select**(`tree: true`) | `AntdUI.Select`、各 `F9-*-SELECT` | key(部门为 Id) | | `multiselect` | el-select multiple | `AntdUI.SelectMultiple`、`F9-WMS-SS-PICI-KCSTATUS` | `"a,b,c"`(对齐 `string.Join(",")`) | | `date` / `yearmonth` / `year` | el-date-picker | `AntdUI.DatePicker*` | `YYYY-MM-DD` / `YYYY-MM` / `YYYY` | | `picker` | 只读框 + 选择按钮 → 选择页 / 远程搜索 | `ShowModalPage(Page_XXX_Select)` | 记录 **Id** | | `picker-multiple` | 同上多选 | `F9-GYZK-SELECT-MUTIPLE` | `"1,2,3"` | 选项优先级:`Ds_Static` → `Ds_Service` → 控件 `loader` → 静态 `options`; picker 优先级:**选择页 `page`** > 内建远程搜索 `search` > 降级文本输入。 新增控件只需两步,无需改页面: ```js registerControl({ type: 'F9-MY-SELECT', kind: KIND.SELECT, group: 'F9 自定义', label: '我的选择', loader: async () => [{ key: '1', value: '选项一' }], }); ``` --- ## 四、选择页机制(对应 F9.Common.Report.SelectPages) | 文件 | 说明 | |---|---| | `web/src/components/select/F9SelectPage.vue` | 通用选择页:过滤条件(变更即重查)+ 关键字 + 分页表格 + 单选/多选 + 未选校验 —— 对应 `F9ModalPageUserControl_Medium` + `f9AntdUITable` 的 `QueryData` / `ValidCustom` | | `web/src/components/select/CraftZhuangKouSelect.vue` | 工艺庄口选择页 —— 对应 `Page_CraftZhuangKou_SelectV2`(庄口编号 / 名称 / 产地 / 排序 / 备注 / 年度,年度过滤,`GET /api/craftzhuangkou`) | | `web/src/components/select/index.js` | 选择页注册中心 `registerSelectPage / getSelectPage` —— 对应 `ResolvePage()` | 复用方式: ```vue { type: 'F9-GYZK-SELECT', kind: KIND.PICKER, page: 'CraftZhuangKou', valueField: 'id', labelField: 'productionDisplayName' } ``` 已落地:**落丝记录(lsjl)庄口**改为打开选择页;`F9-GYZK-SELECT` / `F9-GYZK-SELECT-MUTIPLE` 绑定该页。 --- ## 五、部门口径(Web 版 `F9GlobalUIHelper`) | F9 方法 / 查询 | Web 实现 | |---|---| | `Department_GetAllCacheAsync` | `getAllDeps()`(缓存) | | `GetDepartmentByDutyTypeWithoutRightQuery(types)` | `depsByDuty()`(只保留自身 DutyType 命中) | | `GetDepartmentOfSiyeQuery(includeSiye)` | `depsOfSiye()`(丝业中心 + 全部子孙) | | `GetDepartmentForEmployee/Kaoqin/XinziQuery` | `depsOfLoginUser()`(本部门 + 子孙;管理员=全部)+ `removeCompany()` | | `Dep_GenerateSelectItems` | `buildDepTree()`(SortOrder → Code → Name,父不在集合内即为根) | | `Dep_Select_SelectDep(sel, depId)` | `defaultFromUser: true` → 默认选中登录用户部门 | | 控件 | 口径 | 实测(38 个部门) | |---|---|---| | `F9-DEP-QS-SELECT` | DutyType=前缫(25) | 前缫车间#26 → 前缫一~四车间#31-34 | | `F9-DEPARTMENT_SIYE` | 丝业 | 丝业生产中心#2 + 全部子孙 | | `F9-DEP-EMPLOYEE-SELECT` / `F9-DEP-XZ-SELECT` / `F9-DEPARTMENT_KAOQIN` | 职工 / 薪资 / 考勤(默认登录部门) | 管理员=全部,否则本部门+子孙 | | `F9-DEP-ALL-SELECT`(新增) | 全部 | 完整部门树 | lsjl 车间下拉同步改为 `depOptions({ duty: [DUTY.前缫] }, { flat: true })`,替换原 `name.includes('前缫')`。 --- ## 六、交互与布局修复(编辑器) ```css .left { display: flex; flex-direction: column; overflow: hidden; } .left :deep(.el-tabs) { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; } .left :deep(.el-tabs__header) { flex: 0 0 auto; } .left :deep(.el-tabs__content) { flex: 1 1 auto; min-height: 0; overflow: auto; } .config-src { flex: 0 0 auto; } ``` `addParm()` 后 `activeLeftTab = 'params'` + `nextTick` 滚动到最后一张卡片(实测:连续添加 5 个后 `LAST_VISIBLE=YES`)。 --- ## 七、调试方法(本轮建立的验证手段) 无 Node/npm 环境,用无头 Edge 验证 SPA 运行时: ```powershell # 1) 临时诊断页 web/public/__diag.html:登录写 localStorage → iframe 打开目标页 → 模拟点击 → 输出 DOM 断言 # 2) 抓控制台 msedge --headless=new --virtual-time-budget=45000 --enable-logging=stderr --v=1 --dump-dom "http://localhost:5173/__diag.html" ``` 用该方法定位了 #2(白屏 SyntaxError)与 #3(滚动)等问题;诊断页用完即删。 另:动态 `import('/src/utils/departments.js')` 可在诊断页内直接单测模块逻辑(验证部门口径即用此法)。 --- ## 八、当前状态 | 项 | 状态 | |---|---| | 报表中心(树 + 查询面板 + 多 Tab 预览) | 正常,切换报表参数随报表切换 ✅ | | 报表管理 / 全屏编辑器 | 正常(配置/参数/设计/源码/预览)✅ | | 自定义控件注册中心 | 38 个控件,编辑器下拉自动分组 ✅ | | 选择页(工艺庄口) | 报表控件 + lsjl 业务页均已接入 ✅ | | 部门口径 | 前缫 / 丝业 / 职工 / 考勤 / 薪资 / 全部 ✅ | | 落丝记录庄口 | 改为选择页 ✅ | ## 九、遗留与后续建议 1. **部门缓存**:`getAllDeps()` 有内存缓存,部门增删后需 `clearDepCache()`(可在部门管理保存后调用) 2. **选择页覆盖**:职工 / 物资目录目前用内建远程搜索弹窗,如需与 F9 一致的选择页可再注册 `EmployeeSelect` / `WuziMuluSelect` 3. **Web 端无对应接口的 F9 控件**:样茧试缫(`F9-QC-YJSS-*`)、生丝批次枚举(`SSPiciKucunStatusEnum` 等)走注册表或 `Ds_Static`,界面上有降级提示 4. **picker 取 Id**:报表 Provider 需按 Id 解析参数(庄口 / 职工 / 物资均为 Id) 5. **规范**:新增 `.vue` 再导出必须 `import X from './X.vue'` 后具名导出,禁止 `export { X } from './X.vue'`