# 工作进度-5:新增报表「自动配置参数与列 + 默认初始化报表」 > 时间:2026-10-20(承接工作进度-4) > 环境:后端 .NET 8 + FreeSql + MediatR(Windows/SQLite),前端 Vue3 + Element Plus + Vite > 目标:把工作进度-4 删除内嵌设计器时一起丢失的能力补回来 —— **新增报表时自动识别数据源参数与明细列,并默认生成一张可用的报表(模板 + 配置一次落库)** --- ## 一、背景 工作进度-4 移除了自研网页设计器(`ReportDesigner.vue`),报表管理页只保留「配置 / 参数 / 预览 / 模板源码」。 结果: - 新增报表必须先手工上传 `.grf` 才能预览,否则只有 1 个 `Field1` 占位列; - `Config.Parms`(查询参数)全靠手填,控件类型是纯文本; - 数据源有哪些字段(明细列)只能去下载模板里看。 本轮补齐:**选数据源 → 自动带出参数与明细列 → 保存时自动生成 `.grf` → 立即可预览**。 --- ## 二、后端 ### 2.1 新增文件 | 文件 | 说明 | |---|---| | `Application/Dtos/SYS/Reports/ReportScaffoldDtos.cs` | `ReportParameterDefDto`(参数定义) / `ReportDataSourceInfoDto`(数据源) / `ReportFieldDefDto`(明细列) / `ReportProbeRequestDto` / `ReportProbeResultDto` | | `Application/Interfaces/Reports/IReportDataProviderMeta.cs` | **可选**元数据接口:`Label` / `GetParameters()` / `GetFieldLabels()`,未实现的 Provider 行为不变(默认接口方法) | ### 2.2 服务 `IReportService` / `ReportService` 新增两个方法: | 方法 | 说明 | |---|---| | `GetDataSourcesAsync()` | 遍历 DI 注册的 `IReportDataProvider` 字典,返回 `DsName` + 中文名 + 参数定义 | | `ProbeAsync(request)` | 按 `DsName`(优先)或 `DataSql` **执行一次查询**,返回参数定义 + 明细字段(列名/中文名/类型)+ 前 3 行样本;失败时 `Error` 带原因且不抛 500 | 探测细节: - Provider 分支:`provider.GetData(pairs, mediator)` → 取第一个有数据的记录集 → 首行键名即列名 → `IReportDataProviderMeta.GetFieldLabels()` 提供中文标题 - SQL 分支:`ExecuteDataTable(sql)`(只绑定 SQL 里出现的 `@参数`)→ `DataTable.Columns` 即列名 - 参数取值:请求值 > `Parms[].DefaultValue`;缺少默认值直接传空,查询失败只记 `Error` - 错误分级:有 `DsName` 但找不到实现 → `未找到数据源实现:xxx`;两者都空 → `请先选择数据源(DsName)或填写数据 SQL` ### 2.3 接口 | 端点 | 说明 | |---|---| | `GET api/sys/report/datasources` | 数据源清单(参数定义随附) | | `POST api/sys/report/scaffold` | 结构探测:`{ dsName, dataSql, parms, params }` → `{ parameters, fields, rows, total, error }` | > 路由说明:`datasources` 为字面量段,优先于 `{id}` 匹配,不影响原详情接口。 ### 2.4 示例 Provider 补元数据 `RepDt_Sys_Users` 实现 `IReportDataProviderMeta`: - 参数:`q_dep`(部门 / `AntdUI.Select` / `Ds_Service=/sys/department`)、`q_like`(关键字 / `AntdUI.Input`) - 字段中文名:`EmployeeCode=工号, EmployeeName=姓名, Gender=性别, DepName=部门, WorktypeName=工种, Phone=电话, RuzhiDate=入职日期` 新增数据源时实现该接口即可被编辑器自动识别,未实现也能正常查询。 --- ## 三、前端(`components/report/ReportEditor.vue`) ### 3.1 数据源与自动识别 | 项 | 实现 | |---|---| | 数据源输入 | `el-select`(`filterable + allow-create`):列出来自 `/sys/report/datasources` 的「中文名(DsName)」,也可手输未登记名称 | | 自动触发 | 选中数据源即调用 `POST /sys/report/scaffold`(`onDsChange`) | | 手动触发 | 配置页「自动识别参数与列」按钮;参数页 / 明细字段页也可再次识别 | | 结果 | 覆盖 `Parms`(名称/标签/提示/控件类型/Ds_Service/Ds_Static/默认值)+ 填充明细列,并自动切到「明细字段」tab | ### 3.2 明细字段 tab 新增第三个左栏 tab(`明细字段 (n)`): - 列卡片:启用勾选 / 字段名(可改) / 列标题(可改) / 列宽(0.8~8cm)/ 删除 - `+ 添加列` 手工补列(数据源无数据时用) - `生成模板`:按当前字段生成 `.grf`,新建时随保存写入,编辑时点保存才覆盖(有二次确认) - 底部展示探测返回的样本行,便于核对字段含义 编辑已有报表时,若模板是 JSON 格式,会**反解**出 `DetailGrid.Recordset.Field` + `Column.Width` + `ColumnTitleCell.Text` 回填到明细字段;`_WR7_` 压缩模板无法反解(留空,不影响继续使用)。 ### 3.3 默认生成模板(`buildTemplate()`) 结构派生自内置示例模板 `employee_directory.grf`(GR7 兼容 `Version=6.8.5.0`): | 区块 | 内容 | |---|---| | `DetailGrid` | 记录集字段 = 启用的明细列;`Column.Width` 按 17cm 均分(1.2~4);`ColumnTitleCell.Text` = 列标题 | | `ReportHeader[0]` | 报表标题(名称) | | `ReportHeader[1]` | 每个查询参数一组「标签 StaticBox + `Parameter` 绑定 StaticBox」,每行 4 组 | | `Parameter` | 参数名 = `Parms[].Label`(与后端 `Parameter_Data` 键一致,如 `部门` / `关键字`) | | `PageHeader` / `PageFooter` | 系统名水印 + 页码 + 打印时间(与示例一致) | **保存语义**: - 新建:`templateJson = buildTemplate()`(无字段时退化为单列占位,保证不会出现「未配置模板」) - 编辑:默认不提交模板(避免覆盖设计器上传的 `.grf`);只有点了「生成模板」(`templateDirty`)才写入 ### 3.4 顺带修复(工作进度-4 遗留 5 的一部分) 参数「控件类型」由纯文本输入改为**分组下拉 + 可手输**,数据来自 `utils/gridreport/controls` 注册中心(38 个 AntdUI / F9 控件),与报表中心查询面板同源。 --- ## 四、验证记录 ### 4.1 接口层 ``` GET /api/sys/report/datasources → [{ dsName: RepDt_Sys_Users, label: 职工花名册, parameters: [q_dep/部门/AntdUI.Select/Ds_Service=/sys/department, q_like/关键字/AntdUI.Input] }] POST /api/sys/report/scaffold { dsName: RepDt_Sys_Users } → PARMS=2 FIELDS=7 TOTAL=942 ERR=(空) EmployeeCode/工号, EmployeeName/姓名, Gender/性别, DepName/部门, WorktypeName/工种, Phone/电话, RuzhiDate/入职日期 ROW0={EmployeeCode:"",EmployeeName:"付华芳",Gender:"女",DepName:"丝业生产中心",...} POST /api/sys/report/scaffold { dataSql: "SELECT id AS Id, code AS Code, name AS Name FROM sys_rbac_menus LIMIT 5" } → SQL_FIELDS=3 TOTAL=5 ERR=(空) Id,Code,Name POST /api/sys/report/scaffold { dsName: RepDt_Not_Exists } → BAD_ERR=未找到数据源实现:RepDt_Not_Exists ``` ### 4.2 页面全链路(无头 Edge,`/report/manage`) ``` LIST_ITEMS=2 TOPBAR=新增报表 ADD_BTN=true LEFT_TABS=配置,参数 (0),明细字段 (0) ← 新增后进入空表单 DS_OPTIONS=1 [职工花名册(RepDt_Sys_Users)] ← 下拉来自注册表 选中后自动识别: LEFT_TABS2=配置,参数 (2),明细字段 (7) DETECT_INFO=已识别 7 列 / 2 参数 FIELD_NAMES=EmployeeCode,EmployeeName,Gender,DepName,WorktypeName,Phone,RuzhiDate SAMPLE=EmployeeCode= | EmployeeName=付芳华 | Gender=女 | DepName=丝业生产中心 | ... 保存(未上传任何 .grf): AFTER_SAVE_TOPBAR=编辑报表 · 诊断自动配置报表 AFTER_SAVE_TAG=模板已配置 VIEWER_CHILDREN=3 VIEWER_TEXT=诊断自动配置报表 部门:(全部) 关键字:(全部) 工号 姓名 性别 部门 工种 电话 入职日期 付芳华 女 丝业生产中心 15884942961 2012-02-01 何均 女 ... ALERT=保存成功 后端核对: API_ROW={ hasTemplate:true } TPL_FIRST3={ ← 生成的是 JSON 模板(非 _WR) TPL_FIELDS=7 TPL_COLUMNS=工号,姓名,性别,部门,工种,电话,入职日期 TPL_PARAM=部门,关键字 CFG_DS=RepDt_Sys_Users PARMS=q_dep/AntdUI.Select,q_like/AntdUI.Input CLEANUP=200 ← 测试报表已删除 ``` ### 4.3 编译 / 运行 - 后端 `dotnet build`:**0 错误 0 警告**(先停 `F9WebAiTest.Api` 进程再编译,避免 MSB3027) - 前端无 lint 诊断;无头 Edge 实跑整页(识别 → 保存 → GR7 渲染)通过 - 服务:后端 `http://localhost:3000`、前端 `http://localhost:5173` 均在运行 --- ## 五、当前状态 | 项 | 状态 | |---|---| | 新增报表:数据源下拉 + 一键识别参数与列 | ✅ | | 新增报表:保存自动生成 `.grf`(默认初始化),保存后直接可预览 | ✅ | | 明细字段可编辑(字段名 / 标题 / 列宽 / 启用)并据此生成模板 | ✅ | | 编辑已有 JSON 模板:反解明细列回填 | ✅(`_WR7_` 压缩模板无法反解) | | SQL 数据源(无 DsName)也能探测列 | ✅ | | 参数控件类型:38 个注册控件分组下拉 | ✅ | | 探测失败不阻断:提示原因,仍可手工加列保存 | ✅ | --- ## 六、遗留与后续建议 1. **Provider 参数声明需手工补**:新增 `IReportDataProvider` 时建议同时实现 `IReportDataProviderMeta`,否则编辑器只能识别字段、参数要手填。 2. **`_WR7_` 模板不能反解列**:编辑这类报表时「明细字段」为空,重新生成会覆盖设计器排版(已有二次确认)。若需要,可在后端通过 GR7 库或 `gr.wr.decodeWR` 的等价实现解码后再解析。 3. **探测会执行真实查询**:大报表(如 942 条职工)识别需要等待查询完成;后续可给 Provider 增加「仅取结构」的轻量方法(如返回空参数 + `LIMIT 0`)。 4. **列宽均分**:自动生成的列宽按 17cm 均分,超宽字段(地址、备注)仍需到设计器里调整。 5. **进度-4 遗留项仍在**:`Report_FavoriteGroup` 未实现、历史废弃列未 DROP、进度-3 的 `Number()` 精度隐患等。