新增质量模块(工艺执行书 / 样茧试缫)与报表中心
质量模块: - 11 张工艺执行书表:封面、试产、选剥茧(含混茧明细)、试缫与大样、煮茧(含分车间配温、副煮车间)、自动缫(含大样)、后缫 - 10 张样茧试缫表:原料小样/综合样主表 + 选茧、解舒、茧幅、切剖、清洁洁净、茧丝百回纤度 - 服务与接口、主控指针回写(列表 √ 标记)、封面编号自增、报表数据包 - 前端 8 张报表打印版式、7 类工艺单据与 6 类调查录入弹窗、字段驱动表单/表格组件 - 菜单种子新增「质量」目录,样茧子表增加 YjssType 判别列,避免原料小样与综合样自增 Id 重号串数据 报表中心: - 报表模板管理、锐浪 grhtml5 加载与预览、全屏设计面板、数据源 Provider(落丝记录、职工花名册) 其他:同步工作进度 1-11 文档、开发数据库快照、.gitignore 忽略运行日志目录
This commit is contained in:
+337
@@ -0,0 +1,337 @@
|
||||
# 工作进度-1:报表中心模块(Grid++Report 6 集成)
|
||||
|
||||
> 时间:2026-10-19
|
||||
> 环境:后端 .NET 8 + FreeSql + MediatR(Windows),前端 Vue3 + Element Plus + Vite
|
||||
> 本机 Grid++Report 版本:**6.8.23.0618**(设计器 `grdesigner6.exe`)
|
||||
|
||||
---
|
||||
|
||||
## 一、目标
|
||||
|
||||
在系统(智慧缫丝 / F9WebAiTest)中嵌入报表系统:
|
||||
|
||||
1. 报表中心作为菜单页面,支持报表的**新增 / 删除 / 修改**
|
||||
2. 每张报表有独立权限,管理员需在「角色管理 → 权限分配」中给角色授权后,用户才能看到
|
||||
3. 报表支持 F9 的 `ReportConfig` 约定:动态查询控件 + 按 `DsName` 解析后端查询
|
||||
|
||||
---
|
||||
|
||||
## 二、Grid++Report 6 Web 嵌入方案调研(结论)
|
||||
|
||||
本机安装目录 `C:\Grid++Report 6` 关键发现:
|
||||
|
||||
| 项 | 结论 |
|
||||
|---|---|
|
||||
| 浏览器插件方案(NPAPI/ActiveX) | Chrome / Edge 已不支持,**弃用** |
|
||||
| `grhtml5-6.8-min.js` | HTML5 浏览器内渲染,免插件、跨浏览器 ✅ |
|
||||
| `grwebapp.js` + `grwebapp6.exe` | 本机 WEB 报表客户端,支持 preview/print/pdf/xls/csv/img/grd 等 |
|
||||
| `.grf` 文件格式 | **UTF-8 BOM + JSON 文本**(可程序化生成) |
|
||||
| `.grf` 版本 | 官方设计器保存为 `6.8.5.0`;`WebSamples` 里的样本是 `6.6.8.1` |
|
||||
| `.grf` 文件关联 | `GR.Template` → `"C:\Grid++Report 6\grdesigner6.exe" "%1"` |
|
||||
| 已有 URL 协议 | `grwebapp://` → `grwebapp6.exe` |
|
||||
|
||||
采用方案:**HTML5 网页内预览为主 + 本机客户端打印导出为辅 + f9designer 协议唤起设计器编辑**。
|
||||
|
||||
---
|
||||
|
||||
## 三、后端实现
|
||||
|
||||
### 1. 领域实体
|
||||
|
||||
| 文件 | 说明 |
|
||||
|---|---|
|
||||
| `Domain/Entities/SYS/Reports/Report.cs` | 报表实体(表 `sys_reports`),继承 `AggregateRoot` 自动建表 |
|
||||
| `Domain/Events/SYS/Reports/Report_ChangedDomainEvent.cs` | 报表变更领域事件 |
|
||||
|
||||
`Report` 字段:`Code / Name / Category / Description / DataSql / DataParams / Config / TemplateData(byte[]) / TemplateName / TemplateSize / SortOrder / IsActive`
|
||||
|
||||
### 2. 权限模型(与现有 RBAC 完全契合)
|
||||
|
||||
现有 `Resource_Type_Enum` 已内置 `报表 = 3`,直接复用:
|
||||
|
||||
- 新建报表 → 自动创建 `Permission`(`ResourceType=报表`, `ResourceId=Report.Id`, `Code=report:{code}:visble`)
|
||||
- 改编码 → 同步权限 Code;删除报表 → 级联删除权限单元 + `RolePermission` 关联
|
||||
- 授权入口:角色管理 → 权限分配 → 「报表权限」tab(复用现有 `resourceType=3` 接口)
|
||||
- 用户可见性:`AuthService.GetUserPermissionsAsync` 原生返回所有类型权限码 → 前端按 `report:{code}:visble` 过滤
|
||||
|
||||
### 3. 数据查询架构(F9 同构)
|
||||
|
||||
```
|
||||
报表 Config.DsName
|
||||
→ IReportDataProvider 注册表(静态 ReportName 解析)
|
||||
→ MediatR CQRS Query → QueryHandler(注入业务读仓储)
|
||||
→ ReportDto_DataSource(Report_Data: 数据集名 → ReportDataDto_Data)
|
||||
→ ToGridReportJson() 转成 Grid++ JSON 数据
|
||||
```
|
||||
|
||||
| 文件 | 说明 |
|
||||
|---|---|
|
||||
| `Application/Dtos/SYS/Reports/ReportDataSourceDto.cs` | `ReportDto_DataSource` + `ReportDataDto_Data`(`Field_Data` / `Parameter_Data`) |
|
||||
| `Application/Interfaces/Reports/IReportDataProvider.cs` | `ReportName` + `GetData(pairs, mediator)` |
|
||||
| `Application/CQRS/SYS/Reports/Queries/GetEmployeeDirectoryRepQuery.cs` | 职工花名册查询(注入 `IEmployeeReadRepository`) |
|
||||
| `Application/Services/Reports/Providers/RepDt_Sys_Users.cs` | 示例 Provider(DsName = `RepDt_Sys_Users`) |
|
||||
| `Application/Services/Reports/ReportService.cs` | CRUD + 权限联动 + 数据执行 + JSON 转换 |
|
||||
| `Application/Services/Reports/IReportService.cs` | 服务接口 |
|
||||
| `Application/Dtos/SYS/Reports/ReportDtos.cs` | 列表/详情/视图/增删改 DTO |
|
||||
| `Api/Controllers/SysReportController.cs` | REST 控制器(详见下) |
|
||||
|
||||
### 4. 控制器端点
|
||||
|
||||
| 端点 | 说明 |
|
||||
|---|---|
|
||||
| `GET api/sys/report` | 分页(管理) |
|
||||
| `GET api/sys/report/all` | 全部启用报表(角色授权树用) |
|
||||
| `GET api/sys/report/{id}` | 详情(含 DataSql / Config) |
|
||||
| `POST api/sys/report` | 新增(自动创建权限单元) |
|
||||
| `PUT api/sys/report/{id}` | 修改(同步权限 Code) |
|
||||
| `DELETE api/sys/report/{id}` | 删除(级联清理权限) |
|
||||
| `POST api/sys/report/{id}/template` | 上传 .grf(multipart,限 .grf,50MB) |
|
||||
| `GET api/sys/report/{id}/template/download` | 下载 .grf |
|
||||
| `GET api/sys/report/{id}/template[.json\|.grf]` | 模板下载(供 HTML5 / 客户端,支持 `?access_token=`) |
|
||||
| `GET/POST api/sys/report/{id}/data[.json]` | 报表数据(Grid++ JSON 格式) |
|
||||
| `GET api/sys/report/visible` | 当前用户可见报表(按权限过滤) |
|
||||
| `GET api/sys/report/{id}/preview` | 预览(模板 URL + 数据,包 `ApiResult`) |
|
||||
|
||||
### 5. 种子
|
||||
|
||||
`Infrastructure/Data/DbSeeder.cs`:
|
||||
- `SeedReportMenus()`:`/report` 目录 + `/report/center`(报表中心)+ `/report/manage`(报表管理),自动授权 admin
|
||||
- `SeedSampleReport()`:示例报表「职工花名册」(code `employee_directory`)
|
||||
- `LoadEmbeddedTemplate()`:从嵌入资源读取 `.grf`
|
||||
|
||||
### 6. 其他后端改动
|
||||
|
||||
| 文件 | 改动 |
|
||||
|---|---|
|
||||
| `Api/Program.cs` | JwtBearer `OnMessageReceived` 支持 `/api/sys/report` 路径的 query token |
|
||||
| `Application/DependencyInjection.cs` | 注册 `IReportService` + 自动扫描注册所有 `IReportDataProvider` |
|
||||
| `Infrastructure/F9WebAiTest.Infrastructure.csproj` | `.grf` 模板作为嵌入资源 |
|
||||
| `Application/F9WebAiTest.Application.csproj` | 新增 `System.Data.SQLite.Core`(与 FreeSql 驱动一致) |
|
||||
| `Infrastructure/Assets/ReportTemplates/employee_directory.grf` | 基于官方 6.8.5.0 结构派生的示例模板 |
|
||||
|
||||
---
|
||||
|
||||
## 四、前端实现
|
||||
|
||||
| 文件 | 说明 |
|
||||
|---|---|
|
||||
| `web/public/grlib/grhtml5-6.8-min.js` | 官方 HTML5 渲染库(246KB) |
|
||||
| `web/src/utils/gridreport/grwebapp.js` | 官方 WEB 客户端通信库 |
|
||||
| `web/src/utils/gridreport/loadGrhtml5.js` | 动态加载 HTML5 库脚本 |
|
||||
| `web/src/utils/gridreport/reportConfig.js` | F9 ReportConfig 解析 + 动态控件渲染映射 |
|
||||
| `web/src/views/report/center/index.vue` | 报表中心(左侧分类树 + 动态查询面板,右侧多 Tab 报表区) |
|
||||
| `web/src/views/report/manage/index.vue` | 报表管理(列表 + 打开全屏编辑器) |
|
||||
| `web/src/views/report/manage/editor.vue` | 全屏报表编辑器(配置 / 设计 / 源码 / 预览) |
|
||||
| `web/src/router/index.js` | componentMap 增加 `report/center/index`、`report/manage/index` |
|
||||
| `web/src/views/sys/role/index.vue` | 权限分配对话框增加「报表权限」tab(`RESOURCE_TYPE.report = 3`) |
|
||||
|
||||
### 报表编辑器(editor.vue)页面结构
|
||||
|
||||
```
|
||||
┌─────────────┬──────────────────────────────────────────┐
|
||||
│ [配置][参数] │ [设计][源码 JSON][预览视图] │
|
||||
│ │ 本机设计器 | 上传.grf | 下载.grf | 保存 │
|
||||
│ 分组 │ 设计视图:报表标题 + 明细字段表格 │
|
||||
│ 编号 │ 源码视图:完整 .grf JSON │
|
||||
│ 名称 │ 预览视图:grhtml5 实时渲染(切换即渲染) │
|
||||
│ 顺序 │ │
|
||||
│ 数据源DsName │ │
|
||||
│ 数据SQL │ │
|
||||
│ 备注 │ │
|
||||
│ Config JSON │ │
|
||||
└─────────────┴──────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 五、本机设计器直连(f9designer:// 协议)
|
||||
|
||||
| 文件 | 说明 |
|
||||
|---|---|
|
||||
| `tools/report-designer/F9ReportDesignerHandler.ps1` | 协议处理器:下载模板 → 打开设计器 → 关闭后自动回传 |
|
||||
| `tools/report-designer/install-f9designer-protocol.reg` | 注册协议(**HKCU 用户级,无需管理员**,已在本机注册) |
|
||||
|
||||
流程:
|
||||
```
|
||||
浏览器 --f9designer://open?base=..&rid=..&token=..&code=..--> Windows Shell
|
||||
--> .ps1:curl 下载 .grf 到 %TEMP%\F9ReportDesigner\
|
||||
--> 启动 grdesigner6.exe(等待关闭)
|
||||
--> 关闭后 curl POST 回传模板 + 弹窗提示
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 六、踩坑与解决方案(重要)
|
||||
|
||||
| # | 问题 | 根因 | 解决 |
|
||||
|---|---|---|---|
|
||||
| 1 | 点击报表报「报表加载失败 请求失败」 | `/preview` 返回裸对象,而 `request.js` 拦截器要求 `data.code === 200` | 后端 Preview 包 `ApiResult` |
|
||||
| 2 | 报表中心页面白屏 | `buildTree` 中根节点未初始化 `reports`,`node.reports.forEach` 抛 TypeError | 根/目录节点补 `reports: []` + 防御性兜底;目录 id 改为基于路径稳定值 |
|
||||
| 3 | HTML5 取模板报 400「请选择要上传的 .grf 模板文件」 | grhtml5 库约定:**URL 不含 `.json` 就用 POST**(`/\.json/.test(url) ? "GET" : "POST"`),POST 撞上上传接口 | 模板增加 `.json` 路由别名,前端 URL 以 `.json` 结尾 |
|
||||
| 4 | 客户端打印导出报「报表模板已经被破坏」 | grwebapp.js 同样约定(`.grf/.txt/.xml/.json` 才用 GET),且传给客户端的是相对 URL | 增加 `template.grf` / `data.json` 别名;前端传绝对 URL(`location.origin`) |
|
||||
| 5 | 下载的 .grf 设计器打不开 | 手写模板标 `Version: 6.6.8.1` 且结构不完整,设计器 6.8 无法解析 | 改为基于官方 6.8.5.0 模板派生,作为嵌入资源 |
|
||||
| 6 | 报表参数不生效 | 键名写成小写 `parameter`;且 HTML5 不解析文本内 `[#Parameter(xx)#]` 表达式 | 数据键名改 **`Parameter`**(大写);模板改用 `StaticBox` 的 `"Parameter":"关键字"` 属性绑定,并在模板顶层声明 `Parameter` 节点 |
|
||||
| 7 | 字段值渲染为空 | ASP.NET 默认 camelCase 序列化把 `EmployeeName` 输出成 `employeeName`,与模板字段不符 | 数据行改用 `Dictionary` 承载(字典键不受命名策略影响),反射 + PropertyInfo 缓存 |
|
||||
| 8 | 切到预览视图空白 | `watch(activeView)` 未触发渲染 | 切换到 preview 时自动调用 `renderPreview()` |
|
||||
| 9 | 协议唤起脚本不执行 | `.ps1` 为 UTF-8 **无 BOM**,PowerShell 5.1 按 GBK 读取致中文乱码、语法破坏 | 转为 **UTF-8 BOM** |
|
||||
| 10 | 注册表写入失败 | `HKEY_CLASSES_ROOT` 需管理员 | 改注册到 `HKEY_CURRENT_USER\Software\Classes`(免管理员) |
|
||||
| 11 | 第一次进编辑报表页报「请求失败」,明细字段/模板参数暂无数据 | 编辑器 `GET /sys/report/{id}/template` 返回**裸 .grf JSON**(无 ApiResult 包装),`request.js` 拦截器要求 `data.code === 200`,判定失败 reject → 前端兜底为空模板(字段/参数全空) | 拦截器增加放行:响应无 `code` 字段(非 ApiResult 包装,如模板原文/文件流)直接原样返回 |
|
||||
|
||||
### Grid++ 数据 JSON 格式约定(已验证)
|
||||
|
||||
```jsonc
|
||||
// 单数据集
|
||||
{ "recordset": [ {...}, {...} ] }
|
||||
|
||||
// 多数据集(顶层键 = 数据集名)
|
||||
{ "Master": [ {...} ], "Detail": [ {...} ] }
|
||||
|
||||
// 报表参数(键名必须与模板 Parameter 节点一致,渲染器取数组第一项)
|
||||
{ "recordset": [...], "Parameter": [ { "账期": "2026年10月", "车间": "前缫" } ] }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 七、新增报表的标准流程
|
||||
|
||||
1. 建 CQRS:`XXXRepQuery` + Handler,Handler 内注入业务**读仓储**查询,组装 `ReportDto_DataSource`
|
||||
- `Field_Data`:每行匿名对象的属性名 = 模板字段名
|
||||
- `Parameter_Data`:账期 / 车间 / 查询时间等报表参数
|
||||
2. 建 Provider:`RepDt_XXX : IReportDataProvider`,`ReportName` 常量 + 解析 `pairs` → `mediator.Send(query)`(DI 自动发现,无需注册)
|
||||
3. 报表管理 → 新增报表,填 `Config`:
|
||||
```json
|
||||
{
|
||||
"DsName": "RepDt_XXX",
|
||||
"ViewerType": 0,
|
||||
"Parms": [
|
||||
{ "Name": "q_yearmonth", "Label": "账期", "Prompt": "", "IsRequired": false,
|
||||
"ControlType": "AntdUI.DatePickerYearMonth", "Ds_Static": null, "Ds_Service": null, "DefaultValue": "默认值" }
|
||||
]
|
||||
}
|
||||
```
|
||||
4. 用设计器做 `.grf`(记录集字段对齐 `Field_Data`;显示查询条件的控件设 `"Parameter":"账期"`,模板顶层声明 `Parameter` 节点)
|
||||
5. 上传模板 → 角色管理分配报表权限 → 打开即可
|
||||
|
||||
**ControlType 支持**:`AntdUI.Input` / `AntdUI.Select` / `AntdUI.SelectMultiple` / `AntdUI.DatePicker` / `AntdUI.DatePickerYearMonth` / `AntdUI.DatePickerYear`;`F9-*` 业务控件见下方「自定义控件模块」。
|
||||
|
||||
---
|
||||
|
||||
## 七之二、Web 自定义控件模块(对应 F9 `Page_ReportQueryTool.GetParamControl`)
|
||||
|
||||
WinForms 端按 `ControlType` 字符串 if/else 分发创建控件(`GetParamControlOfF9` / `GetParamControlOfAntdUI`),
|
||||
Web 端同构为**注册中心 + 统一渲染组件**:新增控件只注册一次,查询面板与编辑器下拉同时生效。
|
||||
|
||||
| 文件 | 说明 | 对应 WinForms |
|
||||
|---|---|---|
|
||||
| `web/src/utils/gridreport/controls/registry.js` | 注册中心:`registerControl / getControlDef / groupedControlOptions / resolveControlDef / fallbackDef` + 取值工具(`toOptions / apiOptions / registryOptions / pickRows`) | `_paramGetValudFuncs` 注册表 |
|
||||
| `web/src/utils/gridreport/controls/antdControls.js` | AntdUI 基础控件(一个 type 一个函数 + `ANTD_BUILDERS` 分发) | `GetParamControlOfAntdUI` |
|
||||
| `web/src/utils/gridreport/controls/f9Controls.js` | F9 业务控件(一个 type 一个函数 + `F9_BUILDERS` 分发) | `GetParamControlOfF9` |
|
||||
| `web/src/utils/gridreport/controls/index.js` | 装配入口:注册 AntdUI + F9 两组控件;`parseStaticDs / fetchServiceDs / loadParamOptions` | `GetParamControl` |
|
||||
| `web/src/components/report/F9ParamControl.vue` | 统一渲染:input / select / multiselect / date / yearmonth / year / picker(选择页 / 远程搜索 / 降级输入) | 控件实例 |
|
||||
|
||||
### 选择页(对应 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` |
|
||||
|
||||
控件绑定选择页:控件定义里写 `page: 'CraftZhuangKou'`,`F9ParamControl` 即打开该页(优先级:选择页 > 内建远程搜索 > 降级文本输入)。
|
||||
业务页面直接复用:`<CraftZhuangKouSelect v-model="visible" @confirm="onPicked" />`(**落丝记录 lsjl 的庄口已改为打开选择页**)。
|
||||
|
||||
控件定义字段:`{ type, kind, label, group, hint, useDs, options, loader(prm), search(kw), columns, valueField, labelField, multiple }`
|
||||
|
||||
### kind 与 F9 的映射
|
||||
|
||||
| Web kind | 渲染 | F9 对应 | 取值 |
|
||||
|---|---|---|---|
|
||||
| `input` | el-input | `AntdUI.Input` | 文本 |
|
||||
| `select` | el-select | `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**(与 F9 一致) |
|
||||
| `picker-multiple` | 同上多选 | `F9-GYZK-SELECT-MUTIPLE` | `"1,2,3"` |
|
||||
|
||||
选项优先级:`Ds_Static` → `Ds_Service` → 控件 `loader` → 静态 `options`;picker 无 `search` 实现时自动降级为文本输入。
|
||||
|
||||
### 部门控件:Web 版 F9GlobalUIHelper
|
||||
|
||||
WinForms 端部门下拉不是一把全量,而是**按口径取数 + ParentId 建树**(`F9GlobalUIHelper` + 各 `GetDepartment*Query`)。
|
||||
Web 端同构实现:`web/src/utils/departments.js`
|
||||
|
||||
| F9 方法 / 查询 | Web 实现 |
|
||||
|---|---|
|
||||
| `Department_GetAllCacheAsync` | `getAllDeps()`(缓存,`GET /api/sys/department`) |
|
||||
| `GetDepartmentByDutyTypeWithoutRightQuery(types)` | `depsByDuty(deps, types)`(只保留自身 DutyType 命中) |
|
||||
| `GetDepartmentOfSiyeQuery(includeSiye)` | `depsOfSiye(deps, true)`(丝业中心 + 全部子孙) |
|
||||
| `GetDepartmentForEmployee/Kaoqin/XinziQuery` | `depsOfLoginUser()`(本部门 + 子孙;管理员=全部)+ `removeCompany()` |
|
||||
| `Dep_GenerateSelectItems` | `buildDepTree()`(SortOrder → Code → Name,父不在集合内即为根) |
|
||||
| `Dep_Select_SelectDep(sel, depId)` | `defaultFromUser: true` → 默认选中登录用户部门 |
|
||||
|
||||
| 控件 | 口径 | 实测结果 |
|
||||
|---|---|---|
|
||||
| `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`(新增) | 全部 | 完整部门树 |
|
||||
|
||||
渲染:控件定义 `tree: true` → `F9ParamControl` 用 `el-tree-select`(层级展示,取值仍为部门 Id)。
|
||||
落丝记录(lsjl)的车间下拉也改为 `depOptions({ duty: [DUTY.前缫] }, { flat: true })`,替换掉原来的 `name.includes('前缫')`。
|
||||
|
||||
### 已内置的 F9 控件
|
||||
|
||||
- 庄口:`F9-GYZK-SELECT`(**打开工艺庄口选择页**)、`F9-GYZK-SELECT-MUTIPLE`(多选,取值 `"1,2,3"`)
|
||||
- 部门/职工:`F9-DEP-QS-SELECT`、`F9-DEPARTMENT_SIYE`、`F9-DEP-EMPLOYEE-SELECT`、`F9-DEP-XZ-SELECT`、`F9-DEPARTMENT_KAOQIN`、`F9-EMPLOYEE-SELECT`(picker)、`F9-EMPLOYEE-WORKSTATUS`、`F9-EMPLOYEE-CHANGE_TYPE_SELECT`、`F9-Employee-isXuegong`
|
||||
- 物资/原料:`F9-WZ-SS/FP/ZJP/WJ-SELECT`(picker)、`F9-YLZK-SELECT`(picker)、`F9-WMS-YL-CD/JIBIE/CANPINZHONG`(注册表)
|
||||
- 生产/质量:`F9-YEXIAN-SOURCE-SELECT`、`F9-GUIGE-SELECT`、`F9-PROD-QS-LSCD-YICHANG`、`F9-PROD-QS-ZHJJ-COUNT`、`F9-QC-YJSS-YLXY/ZHY-SELECT`(降级输入)
|
||||
- 仓储:`F9-WMS-CRKTYPE`、`F9-WMS-SS-PICI-KCSTATUS`(多选)、`F9-WMS-SS-PICI-SSTYPE`、`F9-WMS-YL-RK/CK-TYPE-SELECT`
|
||||
|
||||
Web 端无对应枚举/接口的走注册表或 `Ds_Static`,并在编辑器/查询面板显示降级提示。
|
||||
|
||||
### 扩展新控件(两步,无需改页面)
|
||||
|
||||
```js
|
||||
import { registerControl, KIND } from '@/utils/gridreport/controls';
|
||||
registerControl({
|
||||
type: 'F9-MY-SELECT', kind: KIND.SELECT, group: 'F9 自定义', label: '我的选择',
|
||||
loader: async () => [{ key: '1', value: '选项一' }],
|
||||
});
|
||||
// 弹窗选择类:再给 search / columns / valueField / labelField
|
||||
```
|
||||
|
||||
报表管理 → 参数 → 「控件类型」下拉自动按 group 分组列出所有已注册控件。
|
||||
|
||||
---
|
||||
|
||||
## 八、当前状态
|
||||
|
||||
| 项 | 状态 |
|
||||
|---|---|
|
||||
| 后端编译 | 0 错误 0 警告 ✅ |
|
||||
| 后端服务 | 运行中 `http://localhost:3000` ✅ |
|
||||
| 前端服务 | 运行中 `http://localhost:5173` ✅ |
|
||||
| 报表中心页面 | 正常(分类树 + 查询面板 + 多 Tab 预览)✅ |
|
||||
| 报表管理 + 全屏编辑器 | 正常(配置/设计/源码/预览)✅ |
|
||||
| 职工花名册示例 | 942 条职工数据 + 报表参数正常渲染 ✅ |
|
||||
| 本机设计器协议 | 已注册,下载→编辑→自动回传全链路验证通过 ✅ |
|
||||
| 角色授权(报表权限 tab) | 已实现 ✅ |
|
||||
|
||||
### 启动方式
|
||||
|
||||
```powershell
|
||||
# 后端
|
||||
cd backend/F9WebAiTest.Api
|
||||
dotnet run
|
||||
|
||||
# 前端
|
||||
cd web
|
||||
npm run dev
|
||||
```
|
||||
|
||||
默认账号:`admin / 123456`
|
||||
|
||||
### 备注
|
||||
|
||||
- 日志目录 `.run-logs/`(backend.log、frontend.log、start-web.bat)
|
||||
- 协议脚本日志:`%TEMP%\F9ReportDesigner\handler.log`
|
||||
@@ -0,0 +1,69 @@
|
||||
# 工作进度-10:设计面板改为弹窗、移除模板源码、模板入口去重
|
||||
|
||||
> 时间:2026-10-21(承接工作进度-9)
|
||||
> 触发:用户反馈三点
|
||||
> 1. 「模板源码」这个功能不需要
|
||||
> 2. 点「设计页」不要跳到单独页面,直接打开设计页
|
||||
> 3. 左边(列表行)的上传/下载模板和右边(编辑器)的上传/下载 .grf 是否重复
|
||||
|
||||
---
|
||||
|
||||
## 一、改动一览
|
||||
|
||||
| 问题 | 处理 |
|
||||
|---|---|
|
||||
| 模板源码功能多余 | **整体删除**:去掉「模板源码」页签、可编辑 textarea、格式化/撤销/放大编辑、`PUT /template/content` 的前端入口(后端接口保留,供设计面板使用) |
|
||||
| 设计页要跳路由 | 设计 UI 抽成组件 `ReportDesign.vue`,在报表编辑器里用**全屏弹窗**打开,不跳页、不改 URL;独立路由 `/report/design/:id` 与对应 view 已删除 |
|
||||
| 上传/下载重复 | 列表行只留「设计 / 删除」;上传/下载 .grf 统一放在**设计面板**里,编辑器工具栏不再重复放(编辑器「未配置模板」空状态仍保留一个上传按钮做兜底) |
|
||||
|
||||
---
|
||||
|
||||
## 二、文件变化
|
||||
|
||||
| 文件 | 变化 |
|
||||
|---|---|
|
||||
| `web/src/components/report/ReportDesign.vue` | 新增(由原 `views/report/design/index.vue` 改造) |
|
||||
| `web/src/views/report/design/index.vue` | 删除 |
|
||||
| `web/src/router/index.js` | 删除 `report/design/:id` 静态路由 |
|
||||
| `web/src/components/report/ReportEditor.vue` | 删源码视图与相关状态/函数;新增设计弹窗;顶栏按钮精简 |
|
||||
| `web/src/views/report/manage/index.vue` | 删列表行的上传/下载模板、隐藏 file input 及相关函数;「设计」改为在右侧弹窗打开 |
|
||||
|
||||
### 2.1 `ReportDesign.vue`(组件化)
|
||||
|
||||
- 入参 `reportId`,事件 `close` / `saved`
|
||||
- 顶栏左侧由「← 返回管理」改为「关闭」(`emit('close')`),有未保存修改时二次确认
|
||||
- 保存模板成功后 `emit('saved')`,报表编辑器收到后:重载模板文本 → 反解明细列 → 重置预览 → 通知管理页刷新列表
|
||||
- `_WR7_` 提示里的「去模板源码页」入口已删除,改为「真实预览 / 用锐浪 7 设计器修改 / 上传 .grf」
|
||||
|
||||
### 2.2 `ReportEditor.vue`
|
||||
|
||||
- 删除:`activeView`、`templateSaved`、`templateSourceDirty`、`wrUnlocked`、`sourceMaximized`、`saveTemplateSource`、`formatTemplateSource`、`revertTemplateSource`、模板源码视图与放大编辑弹窗、保存前「模板未保存」确认
|
||||
- 保留:`loadTemplateSource()`(只用于反解明细列/报表参数与预览)
|
||||
- 新增:`designVisible`、`openDesignPage()`、`onDesignSaved()`、`<el-dialog fullscreen destroy-on-close>` 包 `<ReportDesign>`
|
||||
- 顶栏:`删除 / 生成模板 / 设计 / 刷新预览 / 保存`
|
||||
- `props.openSource` → `props.openDesign`(管理页点「设计」直接弹窗)
|
||||
|
||||
### 2.3 `manage/index.vue`
|
||||
|
||||
- 列表行:`设计 / 删除`
|
||||
- 删除 `pickUpload`、`onFilePicked`、`downloadTemplate`、隐藏 `<input type=file>`、`getAccessToken` 引用
|
||||
- `designOnOpen` + `designerKey++`:选中报表并让右侧编辑器重新挂载后自动弹出设计面板(同一行重复点击也能重开)
|
||||
|
||||
---
|
||||
|
||||
## 三、验证
|
||||
|
||||
| 项 | 结果 |
|
||||
|---|---|
|
||||
| Vite 编译 `ReportDesign.vue` / `ReportEditor.vue` / `manage/index.vue` / `router/index.js` | ✅ 全部 HTTP 200 |
|
||||
| IDE 诊断 | ✅ 0 |
|
||||
| 后端 | 未改动,无需重启 |
|
||||
| 浏览器点击 | ⚠ 未自动化验证(环境无 node/browser 自动化),建议人工过一遍:管理页→设计→改列宽→保存→关闭→刷新预览 |
|
||||
|
||||
---
|
||||
|
||||
## 四、遗留
|
||||
|
||||
1. 设计面板是弹窗形态,`Esc` 也能关闭;关闭时若有未保存修改只弹一次确认(点 × 无确认,因为隐藏了关闭按钮,实际只能点「关闭」)。
|
||||
2. 编辑器工具栏去掉下载 .grf 后,取模板文件需进设计面板;若觉得不便可再加回。
|
||||
3. 工作进度-8 描述的「模板源码在线编辑」UI 已废弃,但后端 `PUT /sys/report/{id}/template/content` 仍在使用(设计面板保存走它)。
|
||||
+152
@@ -0,0 +1,152 @@
|
||||
# 工作进度-11:新增「质量」模块(工艺执行书 + 样茧试缫)
|
||||
|
||||
> 时间:2026-10-22(承接工作进度-10)
|
||||
> 需求:把 `D:\AntaiProject - 副本`(F9 智慧缫丝 WinForms 版)里 **质量模块 → 工艺执行书** 与 **质量模块 → 样茧试缫** 迁移进本系统,并还原随附的 8 张报表版式。
|
||||
> 参考实现位置:
|
||||
> - 客户端:`WinClient\F9.Business.Prod\Gyzxs\`、`WinClient\F9.Business.Prod\QC\YJSS\`
|
||||
> - 服务端:`F9Servic\BBIT.F9\BBIT.F9.Domain\Entities\Business\PROD\QC\`、`BBIT.F9.Application\CQRS\Business\PROD\QC\YJSS\`、`Application\Implements\Reports\Prod\QC\RepDt_Qc_Yjss_*`
|
||||
> - 报表样例(本次已按 RTF 还原):封面 / 工艺执行书 / 混剥选茧工艺执行书 / 试缫工艺单 / 试缫与大样模板 / 煮茧工艺执行书 / 自动缫车间生产工艺执行书 / 自动缫大样
|
||||
|
||||
---
|
||||
|
||||
## 一、功能地图
|
||||
|
||||
```
|
||||
质量(新一级菜单,sort=7,图标 Medal)
|
||||
├── 工艺执行书 /qc/gyzxs qc/gyzxs/index
|
||||
│ 一行 = 一个工艺庄口;√ 标记 7 张子单据是否已录入
|
||||
│ ├── 封面 (桑蚕丝工艺设计书:编号/规格/原料总量/生产丝量/生产线时间)
|
||||
│ ├── 试产 (试缫工艺单:茧质调查 + 自动缫试缫指标 + 试缫煮茧工艺)
|
||||
│ ├── 选剥茧 (混剥选茧:混茧工艺明细 + 生产/质量/质量控制指标)
|
||||
│ ├── 试缫与大样 (试缫 vs 大样 两行对比 + 技术措施 + 签署)
|
||||
│ ├── 煮茧 (庄口原料性能 + 工艺技术指标 + 真空渗透 + 分车间配温 + 副煮车间)
|
||||
│ ├── 自动缫 (表一:茧质概况 + 生产指标 + 工艺技术指标 + 表二大样)
|
||||
│ └── 后缫 (工艺执行书:生产指标 9 项 + 工艺条件及控制指标 18 项)
|
||||
│ 打印下拉 = 8 张报表任意一张
|
||||
└── 样茧试缫 /qc/yjss qc/yjss/index
|
||||
页签:原料小样 / 综合样
|
||||
├── 选茧调查 11 个茧别 重量 + 比例(按茧重重算)
|
||||
├── 解舒调查 多条记录(缫丝参数/纤度丝长/落绪/蛹体/附煮/屑物)
|
||||
├── 茧幅调查 仅综合样(按茧幅分档,算平均/整齐率/均方差)
|
||||
├── 切剖调查 仅综合样(原量/干量/粒数比例,算茧层率/脱胶率)
|
||||
├── 清洁洁净 匀度 + 洁净档位 + 疵点,自动算清洁/洁净/片数
|
||||
└── 茧丝百回纤度 仅综合样(按回数批量生成 + 自动汇总标注值)
|
||||
打印下拉 = 原料小样报表 / 综合样报表 / 解舒调查报表
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 二、后端
|
||||
|
||||
### 2.1 领域实体(`F9WebAiTest.Domain/Entities/QC/`)
|
||||
|
||||
工艺执行书(`Gyzxs/`,表前缀 `qc_gyzxs_`):
|
||||
|
||||
| 文件 | 表 | 说明 |
|
||||
|---|---|---|
|
||||
| `GyzxsDocVersion.cs` | `qc_gyzxs_doc_version` | 主控:以庄口为键,记录 7 张子单据 Id(列表 √ 来源) |
|
||||
| `GyzxsFengMian.cs` | `qc_gyzxs_fengmian` | 封面(含编号 SJZ-{年}-工艺-{序}、生产时间区间) |
|
||||
| `GyzxsShichan.cs` | `qc_gyzxs_shichan` | 试产:茧质 7 项 + 自动缫 16 项 + 煮茧 11 项 |
|
||||
| `GyzxsXbj.cs` | `qc_gyzxs_xbj` / `qc_gyzxs_xbj_hunjian` | 选剥茧主表 + 混茧比例明细(庄别/重量/包数/比例/混茧包数) |
|
||||
| `GyzxsXbjSsDy.cs` | `qc_gyzxs_xbj_ssdy` | 试缫与大样(Ss*/Dy* 各 10 项 + 技术措施 + 签署) |
|
||||
| `GyzxsZhuJian.cs` | `qc_gyzxs_zhujian` / `_chejian` / `_fuzhu` | 煮茧主表 + 分车间配温 + 副煮车间结果 |
|
||||
| `GyzxsZds.cs` | `qc_gyzxs_zds` / `qc_gyzxs_zds_dyzl` | 自动缫表一 + 大样表二 |
|
||||
| `GyzxsHs.cs` | `qc_gyzxs_hs` | 后缫(工艺执行书本体) |
|
||||
|
||||
样茧试缫(`Yjss/`,表前缀 `qc_yjss_`):`main_ylxy`、`main_zhy`、`dc_xjdc`、`dc_jsdc`、`dc_jfdc_main`、`dc_jfdc_item`、`dc_jsbhxd_main`、`dc_jsbhxd`、`dc_qpdc`、`dc_qjjj`。
|
||||
|
||||
> **与 F9 的差异(有意为之)**:F9 子表只用 `yjss_id` 外键,依赖雪花全局唯一 Id;本系统主表用自增 Id,原料小样与综合样会重号,因此所有子表增加 **`YjssType`(1 原料小样 / 2 综合样)** 判别列,读写一律按 `(YjssId, YjssType)` 定位。
|
||||
> 另:F9 的「综合样茧质」在本系统未单独立表,其指标以冗余字段直接落在选剥茧 / 煮茧 / 自动缫 / 试产表上,报表照常可打。
|
||||
|
||||
### 2.2 服务与接口
|
||||
|
||||
| 服务 | 主要方法 |
|
||||
|---|---|
|
||||
| `GyzxsService` | `GetDocVersionPagedAsync` / `CreateDocVersionAsync` / 7 组 `Get*Async`+`Save*Async` / `GetPrintDataAsync` |
|
||||
| `YjssService` | `GetPagedAsync` / `SaveYlxyAsync` / `SaveZhyAsync` / 6 组子单据读写 / `GetFullAsync` / `GetJsdcSummaryAsync` |
|
||||
|
||||
设计要点:
|
||||
1. **指针回写**:任一张子单据保存后 upsert `qc_gyzxs_doc_version` 对应列(`ZdsDyId` 早前漏检已修);解舒/百回纤度保存后回写主表记录数 `JsdcCt`/`JsbhxdCt`。
|
||||
2. **按「庄口 + 规格」唯一**:试产、试缫与大样、自动缫、大样、后缫均以 `(GyzkId, Guige)` 为业务键;封面、选剥茧、煮茧按庄口唯一。
|
||||
3. **报表数据包**:`GetPrintDataAsync(type, gyzkId, guige)` 返回 `Parameters`(中文键)+ `Main/Sub1/Sub2`,结构与 Grid++ 的 `Report_Data` 对齐,后续可直接被报表中心 Provider 复用。
|
||||
4. **编号自增**:封面新建时按 `SJZ-{年度}-工艺-{n}` 预生成。
|
||||
|
||||
接口一览(均需登录):
|
||||
|
||||
```
|
||||
GET /api/qc/gyzxs?year=&keyword=&pageIndex=&pageSize=
|
||||
POST /api/qc/gyzxs/version?gyzkId= DELETE /api/qc/gyzxs/version/{id}
|
||||
GET|POST /api/qc/gyzxs/fengmian (gyzkId)
|
||||
GET|POST /api/qc/gyzxs/shichan (gyzkId + guige)
|
||||
GET|POST /api/qc/gyzxs/xbj (主表 + hunJian 明细数组)
|
||||
GET|POST /api/qc/gyzxs/ssdy (gyzkId + guige)
|
||||
GET|POST /api/qc/gyzxs/zhujian (主表 + cheJian + fuZhu)
|
||||
GET|POST /api/qc/gyzxs/zds (zds + dyzl)
|
||||
GET|POST /api/qc/gyzxs/hs (gyzkId + guige)
|
||||
GET /api/qc/gyzxs/print/{type}?gyzkId=&guige= type = fengmian|gyzxs|xbj|shichan|ssdy|zhujian|zds|zdsdy
|
||||
|
||||
GET /api/qc/yjss?type=1|2&year=&keyword=
|
||||
POST /api/qc/yjss/ylxy | /zhy DELETE /api/qc/yjss/{type}/{id}
|
||||
GET|POST /api/qc/yjss/xjdc/{yjssId}?type= /api/qc/yjss/xjdc?type=
|
||||
GET|POST /api/qc/yjss/jsdc/{yjssId}?type= /api/qc/yjss/jsdc?type=
|
||||
DELETE /api/qc/yjss/jsdc/{type}/{id}
|
||||
GET|POST /api/qc/yjss/jfdc|qpdc|qjjj|jsbhxd (GET 带 ?type=)
|
||||
GET /api/qc/yjss/full/{type}/{id}
|
||||
GET /api/qc/yjss/jsdc-summary/{yjssId}?type=
|
||||
```
|
||||
|
||||
### 2.3 菜单种子
|
||||
|
||||
`DbSeeder.SeedQcMenus()`:幂等创建 `/qc` 目录 + 两个页面菜单,admin 角色授权,并把一级目录顺序校正为
|
||||
`基础(3) < 员工(4) < 前缫(5) < 后缫(6) < 质量(7) < 消息(8) < 系统(9) < 报表(10)`。
|
||||
|
||||
---
|
||||
|
||||
## 三、前端
|
||||
|
||||
| 文件 | 作用 |
|
||||
|---|---|
|
||||
| `views/qc/components/SchemaForm.vue` | 字段驱动表单(text/number/date/textarea,支持 `dyzl.xxx` 嵌套键) |
|
||||
| `views/qc/components/QcGrid.vue` | 字段驱动可编辑表格(对应 WinForms 的 f9AntdUITable) |
|
||||
| `views/qc/gyzxs/schema.js` | 7 张子单据 + 8 种报表的字段/版式元数据 |
|
||||
| `views/qc/gyzxs/index.vue` | 庄口列表(√ 标记、按年度筛、打印下拉、按庄口新增) |
|
||||
| `views/qc/gyzxs/DocDialog.vue` | 子单据录入弹窗(含「从指定庄口读取数据」跨庄口复制) |
|
||||
| `views/qc/gyzxs/PrintDialog.vue` | 8 张报表版式 + A4 横向打印 |
|
||||
| `views/qc/yjss/index.vue` | 原料小样 / 综合样双页签列表 |
|
||||
| `views/qc/yjss/schema.js` | 6 类调查字段 + 计算规则(比例 / 茧幅 / 茧层率 / 清洁洁净) |
|
||||
| `views/qc/yjss/YjssDocDialog.vue` | 6 类调查录入(解舒为「列表 + 明细弹窗」) |
|
||||
| `views/qc/yjss/YjssPrintDialog.vue` | 原料小样 / 综合样 / 解舒调查 三张报表 |
|
||||
|
||||
业务算法(前端即时计算,保存落库):
|
||||
- 选剥茧:按重量重算比例,总官堆 40 包分摊混茧包数,末行取差额。
|
||||
- 茧幅:`平均 = Σ(茧幅×数量)/Σ数量`,整齐率 = `|茧幅-平均| ≤ 1mm` 占比,均方差按加权。
|
||||
- 茧丝百回纤度:外/中/内三百回为分区间平均,最大/内外开差取极值差。
|
||||
- 清洁洁净:`片数 = Σ洁净档位片数`;`洁净 = Σ(分值×片数)/片数`;`清洁 = 100 - (特大糙×3 + 废丝×3 + 大糙×2 + 黏附糙 + 大长结 + 重螺旋)/片数×100`。
|
||||
- 解舒:`解舒率 = 解舒丝长 / 一茧丝长`,`落绪总粒数 = 外+中+内`。
|
||||
|
||||
---
|
||||
|
||||
## 四、验证记录
|
||||
|
||||
| 项 | 结果 |
|
||||
|---|---|
|
||||
| `dotnet build F9WebAiTest.sln` | ✅ 0 错误(120 条既有 CS8632 可空注解警告) |
|
||||
| `vite build` | ✅ 通过(仅既有 chunk 体积/动态导入警告) |
|
||||
| IDE 诊断(`views/qc/**`、`router/index.js`) | ✅ 0 |
|
||||
| 菜单 | ✅ `质量`(sort=7) → `工艺执行书` / `样茧试缫`,admin 自动授权 |
|
||||
| 工艺执行书 7 张子单据保存 + 回读 | ✅ 封面/试产/选剥茧(2 行混茧)/试缫与大样/煮茧(1 分车间+1 副煮)/自动缫(含大样)/后缫 |
|
||||
| 主控 √ 标记 | ✅ 7 个指针全部回写(`zdsDyId` 漏检缺陷已修) |
|
||||
| 8 张报表数据 | ✅ 每张均返回 title + 参数区(如「工艺执行书」28 个参数),xbj/zhujian/ssdy 有结果集 |
|
||||
| 封面编号 | ✅ 新庄口预生成 `SJZ-2026-工艺-1` |
|
||||
| 样茧试缫(原料小样) | ✅ 主表 + 选茧 + 解舒(1 条) + 清洁洁净,解舒汇总正确 |
|
||||
| 样茧试缫(综合样) | ✅ 主表 + 茧幅(3 档) + 切剖 + 百回纤度(2 行),整页参数区完整 |
|
||||
| 两类样品隔离 | ✅ 同一 `yjssId` 下 `type=1` 有数据、`type=2` 返回空 |
|
||||
|
||||
---
|
||||
|
||||
## 五、后续可做
|
||||
|
||||
1. **报表中心对接**:8 张报表目前用 HTML 打印(版式已按 RTF 还原,A4 横向)。若要进「报表中心」,可基于 `/api/qc/gyzxs/print/{type}` 写 8 个 `RepDt_*` Provider 并制作 .grf 模板,数据结构已预留 `Parameters/Main/Sub1/Sub2`。
|
||||
2. **综合样茧质单独立表**:目前选剥茧/煮茧/自动缫/试产的茧质指标是冗余存储;若需要「综合样茧质」独立维护与联动,可补一张主表并由保存时回填。
|
||||
3. **审批状态机**:设计/审核/审批目前是文本字段,可升级为流程(参考考勤模块的审批流)。
|
||||
4. **单位与精度**:部分指标沿用 F9 的字符串区间表达(如 `185±5`、`20~38`),如后续需要统计,可拆分为「下限/上限/目标值」三列。
|
||||
+167
@@ -0,0 +1,167 @@
|
||||
# 工作进度-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<T>()` |
|
||||
|
||||
复用方式:
|
||||
|
||||
```vue
|
||||
<CraftZhuangKouSelect v-model="visible" @confirm="onPicked" /> <!-- 业务页面 -->
|
||||
{ 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'`
|
||||
+207
@@ -0,0 +1,207 @@
|
||||
# 工作进度-3:部门管理补齐管理/职责类型绑定 + 角色授权「勾了不生效」根治
|
||||
|
||||
> 时间:2026-10-20(承接工作进度-2)
|
||||
> 环境:后端 .NET 8 + FreeSql + MediatR(Windows),前端 Vue3 + Element Plus + Vite
|
||||
> 范围:①部门管理页支持绑定 `ManageType` / `DutyType`;②定位并修复角色管理「分配权限保存后不生效、该角色登录看不到菜单」
|
||||
|
||||
---
|
||||
|
||||
## 一、本轮目标
|
||||
|
||||
1. 部门管理页面支持给部门绑定 `Department_ManageType_Enum` 与 `Department_DutyType_Enum`
|
||||
2. 修复「角色管理 → 分配权限 → 勾选菜单 → 确认保存 → 重新打开仍是未勾选、该角色账号也加载不到菜单」
|
||||
3. 补齐工作进度-2 遗留第 1 条:部门增删改后刷新部门缓存
|
||||
4. 顺手修掉部门编辑会把「禁用」状态改回「启用」的连带问题
|
||||
|
||||
---
|
||||
|
||||
## 二、部门管理(ManageType / DutyType 绑定)
|
||||
|
||||
### 2.1 定位
|
||||
|
||||
后端**原本就支持**两个字段:
|
||||
|
||||
| 层 | 文件 | 说明 |
|
||||
|---|---|---|
|
||||
| 实体 | `Domain/Entities/SYS/Departments/Department.cs` | `manage_type` / `duty_type` 私有 set |
|
||||
| 命令 | `CQRS/SYS/Departments/Commands/Department_CreateCommand.cs` / `_UpdateCommand.cs` | 均带两个枚举参数 |
|
||||
| 读模型 | `Dtos/SYS/Departments/DepartmentReadDto_View.cs` | 已返回 `ManageType` / `DutyType` |
|
||||
|
||||
问题全在前端 `web/src/views/sys/department/index.vue`:
|
||||
|
||||
| 项 | 原状态 |
|
||||
|---|---|
|
||||
| 表单 | 只有「类型」= `ManageType`;`DutyType` 藏在 `form` 里固定为 5,**没有任何控件** |
|
||||
| 列表 | 只有管理类型列,看不到职责类型 |
|
||||
| 上级部门 | 平铺下拉,看不出层级;`clearable` 还会把值置空,与 `long ParentId` 不匹配 |
|
||||
| 缓存 | 保存后不刷新 `utils/departments.js` 的部门缓存 |
|
||||
|
||||
> 注:菜单里生效的是 `/sys/department` → `sys/department/index`;`/system/department` → `system/department/index` 是死代码(它调用的 `/api/department` 已被下线,菜单也被 seeder 清掉)。
|
||||
|
||||
### 2.2 改动
|
||||
|
||||
**`web/src/views/sys/department/index.vue`(重写)**
|
||||
|
||||
- 表单新增「职责类型」下拉;「管理类型」改用共享枚举,删除本地硬编码的 `manageTypeMap`
|
||||
- 列表改**树形表格**(`row-key` + `tree-props`),新增「管理类型」「职责类型」两个标签列,新增「添加子部门」
|
||||
- 上级部门改 `el-tree-select`,编辑时排除自身及子孙(防成环)
|
||||
- 保存 / 删除 / 启停后调用 `clearDepCache()`,报表部门控件立即生效
|
||||
- `parentId` 按**字符串**回传(部门 Id 是雪花 Id,`Number()` 会丢精度)
|
||||
|
||||
**`web/src/utils/departments.js`**
|
||||
|
||||
- `DUTY` 补上缺失的 `后缫整理: 115`
|
||||
- 新增 `enumOptions(map)`(枚举 → 下拉选项,按数值升序)与 `enumLabel(map, value)`(值 → 中文名)
|
||||
|
||||
**`Infrastructure/FreeSqls/Repositories/SYS/Departments/DepartmentWriteRepository.cs`**
|
||||
|
||||
- `UpdateAsync` 增加 `.IgnoreColumns(x => new { x.IsActive, x.IsDeleted, x.CreatedAt, x.CreatedBy })`
|
||||
- 原因:`Department_UpdateCommand` 用 `new Department(...)` 重建对象,构造函数里 `IsActive = true`,`SetSource` 会把「禁用」的部门一编辑就改回「启用」;启用状态本应由独立的 `/active` 命令维护
|
||||
|
||||
### 2.3 实测
|
||||
|
||||
```
|
||||
POST /api/sys/department {manageType:15(车间), dutyType:25(前缫)} → 创建成功
|
||||
PUT /api/sys/department/{id} {dutyType:115(后缫整理)} → 修改成功
|
||||
读回 => manageType=15 dutyType=115 isActive=False ✅
|
||||
```
|
||||
|
||||
运行时(无头 Edge,`/sys/department`):
|
||||
|
||||
```
|
||||
TABLE_ROWS=38
|
||||
ROW0=123 / 安泰茧丝绸 / 部门 / 其他 / 0 / 启用 / 添加子部门 编辑 禁用 删除
|
||||
FORM_ITEMS=编码|部门名称|上级部门|管理类型|职责类型|排序|描述
|
||||
SELECT_1(管理类型)=公司,部门,车间,科室,岗位,虚拟
|
||||
SELECT_2(职责类型)=其他,仓储,选剥茧,煮茧,前缫,后缫,设备办,副产品,质检,丝绵,人力资源,丝业中心,前缫管理,后缫检验,后缫整理
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 三、角色授权「勾了不生效」根治(本轮重点)
|
||||
|
||||
### 3.1 现象
|
||||
|
||||
```
|
||||
角色管理 → 分配权限 → 勾选菜单 → 确认保存(提示"权限保存成功")
|
||||
→ 再次点分配权限:之前勾的菜单仍是未勾选
|
||||
→ 用该角色的账号登录:菜单也加载不出来
|
||||
```
|
||||
|
||||
### 3.2 根因:雪花 Id 被前端 `Number()` 转成 double 后精度丢失
|
||||
|
||||
```
|
||||
真实菜单 id : 134905585693888512
|
||||
Number() 后 : 134905585693888510 ← 少 2
|
||||
Number.isSafeInteger(...) = false
|
||||
```
|
||||
|
||||
`web/src/views/sys/role/index.vue` 的 `savePermType()` 里写的是 `resourceId: Number(key)`。后端本来已经用 `LongToStringJsonConverter` 把 `long` 序列化成字符串(避免 JS 精度问题),前端又把它转回数字,等于白折腾。
|
||||
|
||||
| 环节 | 表现 |
|
||||
|---|---|
|
||||
| 保存 | `sys_rbac_permissions.resource_id` 写成 `...510`,菜单表里不存在这个 id |
|
||||
| 重新打开 | `setCheckedKeys(['...510'])` 在树里找不到节点 → 勾不上 → 显示未勾选 |
|
||||
| 登录 | `GetUserMenusAsync` 拿 `...510` 查菜单表 → 查不到 → 菜单不出现 |
|
||||
| 再点确认 | 回显是空的 → 删除逻辑把这些权限彻底删掉 |
|
||||
|
||||
**只有 id 很小的老数据(11/12/13…)不暴露此问题**。第一轮用「普通用户」角色验证时,恰好勾的都是小 id 菜单,所以误判成"功能正常"——这是本次排查的教训。
|
||||
|
||||
### 3.3 修复(4 处)
|
||||
|
||||
| # | 文件 | 改动 |
|
||||
|---|---|---|
|
||||
| 1 | `web/src/views/sys/role/index.vue` | `resourceId: Number(key)` → `resourceId: key`(字符串原样回传)。菜单 / 部门 / 报表三类授权共用此函数,一并修好 |
|
||||
| 2 | `Application/CQRS/SYS/RBAC/Roles/Commands/RolePermission_AssignCommand.cs` | 新建菜单/操作类权限单元时,用菜单自己声明的 `Permission`(如 `base:worktype:list`)作为 `Code`;原来只有消息订阅会补 `Code`,其余会退化成 `menu:{id}:visble`,导致 `v-permission` 永远匹配不上。为此注入 `IRepository<Menu>` |
|
||||
| 3 | `Infrastructure/Data/DbSeeder.cs`(`FixMenuPermissions`) | 增加**自愈**:启动时删除 `ResourceId` 不对应任何菜单的失效权限单元及其 `role_permission` 关联,修复历史脏数据 |
|
||||
| 4 | `web/src/views/sys/department/index.vue` | `parentId` 同样改为字符串回传(同类隐患) |
|
||||
|
||||
> 后端 `LongToStringJsonConverter` 的 `Read` 已兼容字符串,所以前端传字符串可以直接绑定 `long`(实测 `parentId:"1"`、`resourceId:"134905585693888512"` 均正常)。
|
||||
|
||||
### 3.4 验证
|
||||
|
||||
修复前(前缫主任,勾「工种管理」):
|
||||
|
||||
```
|
||||
API_AFTER(18)=...,42,134905585693888510 ← 尾数错
|
||||
REOPEN_CHECKED=17 MISSING_ON_REOPEN=工种管理
|
||||
```
|
||||
|
||||
修复后:
|
||||
|
||||
```
|
||||
TICKED=工种管理 now=18
|
||||
API_AFTER(18)=11,12,17,13,14,18,15,16,19,20,21,22,23,24,30,31,42,134905585693888512
|
||||
REOPEN_CHECKED=18
|
||||
PERSISTED=YES(工种管理) ✅
|
||||
```
|
||||
|
||||
用该角色真实账号(余华 / 13547542219)登录:
|
||||
|
||||
```
|
||||
LOGIN code=200 roles=1000
|
||||
MENU_TREE:
|
||||
- 考勤管理 [/attendance] → 考勤打卡 / 我的考勤 / 请假申请 / 考勤记录 / 请假审批
|
||||
- 基础数据 [/base] → 工艺庄口 / 生丝规格 / 生丝定级指标 / 机组车台 / 工种管理 ← 新加的已生效
|
||||
- 生产-前缫 [/prod/qianxian] → 落丝记录
|
||||
- 消息中心 [/notification] → 我的消息 / 消息订阅
|
||||
PERMS 含 base:worktype:list
|
||||
```
|
||||
|
||||
重启后端时的自愈日志侧证:`前缫主任` 菜单权限中「不在菜单树内」的资源 Id 由 1 条降为 **0**;`后缫检验员` / `后缫主任` 的菜单权限数由 10 → 8(清掉 2 条无效单元)。
|
||||
|
||||
---
|
||||
|
||||
## 四、验证手段(沿用工作进度-2)
|
||||
|
||||
无 Node/npm 全局环境,前端用项目内置 node:
|
||||
|
||||
```batch
|
||||
:: .run-logs\start-web.bat 内部就是这两步
|
||||
set PATH=C:\Users\32453\.workbuddy\binaries\node\versions\22.22.2-6;%PATH%
|
||||
cd /d d:\f9webaitest\f9-web-ai-test\web
|
||||
node node_modules\vite\bin\vite.js
|
||||
```
|
||||
|
||||
后端:`dotnet run --project backend\F9WebAiTest.Api\F9WebAiTest.Api.csproj`
|
||||
|
||||
SPA 运行时验证(无头 Edge):
|
||||
|
||||
```powershell
|
||||
# 1) 诊断页放 web/public/__diag_xxx.html:login 写 localStorage → history.replaceState 改路由
|
||||
# → 等 el-table 出现 → 模拟点击 → 把断言写进 <div class="L">,用完即删
|
||||
msedge --headless=new --user-data-dir=$prof --virtual-time-budget=150000 --disable-gpu --dump-dom "http://localhost:5173/__diag_xxx.html"
|
||||
```
|
||||
|
||||
要点:
|
||||
- `--virtual-time-budget` 会让 `setTimeout` 与 `fetch` 都按虚拟时钟推进,可放心写 `await sleep()`
|
||||
- 想直接渲染某路由,在**模块脚本之前**插一段 `history.replaceState({}, '', '/sys/role')`(本项目是 `createWebHistory`,不是 hash 路由)
|
||||
- 诊断页里的内联 `<script>` 必须语法正确——本轮曾因多一个 `catch` 导致整段不执行、表现为"页面没反应",排查成本很高
|
||||
- 统计勾选状态要用 `:scope > .el-tree-node__content .el-checkbox`,否则父节点会把子节点的 `is-checked` 也算进来
|
||||
|
||||
---
|
||||
|
||||
## 五、当前状态
|
||||
|
||||
| 项 | 状态 |
|
||||
|---|---|
|
||||
| 部门新增/编辑绑定管理类型、职责类型 | 正常,列表以标签展示 ✅ |
|
||||
| 部门树形列表 / 添加子部门 / 上级树选择(排除自身子孙) | 正常 ✅ |
|
||||
| 部门编辑不再覆盖启用状态 | 已修 ✅ |
|
||||
| 部门缓存(报表部门控件)随部门增删改刷新 | 已接入 `clearDepCache()` ✅(工作进度-2 遗留 1 关闭) |
|
||||
| 角色授权:勾选 → 保存 → 回显 → 该角色登录加载菜单 | 全链路打通(含雪花 Id 菜单)✅ |
|
||||
|
||||
---
|
||||
|
||||
## 六、遗留与后续建议
|
||||
|
||||
1. **同类 `Number()` 隐患**(新数据 id 变大后会踩同一个坑,尚未动)
|
||||
- `web/src/views/base/chejianbanci/index.vue`(`depId`)
|
||||
- `web/src/views/base/jizuchetai/index.vue`(`departmentId`)
|
||||
- `web/src/views/base/employee/index.vue`(`depId` / `worktypeId`,含 `depIds.map(Number)`)
|
||||
- `web/src/views/prod/lsjl/index.vue`(`depOptions` 的 `id: Number(o.key)`)
|
||||
2. **超级管理员 `RolePermission` 重复**:`sys_rbac_role_permissions` 中 admin 有 481 条、只对应 49 个不同资源(历史 seeding 重复插入)。不影响鉴权(admin 直接放行),但角色列表「菜单权限」数字虚高,可去重。
|
||||
3. **部门权限无消费方**:授权会写 `department:{id}:visble`,但前后端没有任何地方按此做数据/可见性过滤,勾选当前无实际效果——要么接上数据范围,要么先从 UI 移除。
|
||||
4. **「操作」权限维度未启用**:`Resource_Type_Enum.操作 = 2` 与 `Permission_Type_Enum.Enable = 2` 已定义但未使用;保存时 `permissionType` 硬编码为 1(Visble)。按钮类节点混在「菜单权限」树里,无法单独授予「可操作」。
|
||||
5. **新菜单的权限单元**:现由 `FixMenuPermissions` 在启动时补建,`AssignRolePermission` 兜底时也会按菜单的 `Permission` 补 `Code`;建议后续把「建 Permission」直接并入菜单 CRUD,避免依赖启动时机。
|
||||
6. **死代码**:`web/src/views/system/{department,role,menu,employee}` 与路由表里的 `system/*/index` 已无菜单指向(`/system%` 菜单被 seeder 删除,对应后端接口也已下线),可整批删掉。
|
||||
+271
@@ -0,0 +1,271 @@
|
||||
# 工作进度-4:Grid++Report 6 → 7 替换 + 模板实例化(ReportFile)+ 页面内嵌报表设计工作台
|
||||
|
||||
> 时间:2026-10-20(承接工作进度-3)
|
||||
> 环境:后端 .NET 8 + FreeSql + MediatR(Windows/SQLite),前端 Vue3 + Element Plus + Vite
|
||||
> 本机 Grid++Report:**7.0**(`D:\GridReport7`,设计器 `gr7designer64.exe`,WEB 客户端 `gr7webapp64.exe`)
|
||||
> 参考:锐浪 GR7 官方 `Samples/Web/vue`、`Samples/Web/html/html5`;F9 实体 `Report.cs` / `ReportFile.cs` / `Report_FavoriteGroup.cs`
|
||||
|
||||
---
|
||||
|
||||
## 一、本轮目标
|
||||
|
||||
1. 把报表渲染链路从 **Grid++Report 6** 整体换成 **Grid++Report 7**(GR6 的浏览器插件方案已被 Chrome/Edge 移除)
|
||||
2. 模板不再"改完先保存再上传 .grf",而是**作为报表的一个属性实例**,与配置/参数**一次保存**
|
||||
3. 参考 F9 实体设计,把模板从报表主表拆成独立实体 **`ReportFile`**(`Report.ReportFileId` 关联)
|
||||
4. 按 WinForms「编辑报表」的嵌入形态,做**页面内嵌的报表设计工作台**,不再唤起外部设计器程序
|
||||
|
||||
---
|
||||
|
||||
## 二、GR7 替换(技术结论先行)
|
||||
|
||||
| 项 | GR6 | GR7 |
|
||||
|---|---|---|
|
||||
| 网页内渲染库 | `grhtml5-6.8-min.js`(6.8.24.0119) | **`grhtml5-7-min.js`(7.0.26.0930)** |
|
||||
| 渲染入口 | `rubylong.grhtml5.insertReportViewer(id, tpl, data)` | **API 完全一致**(可直接替换) |
|
||||
| Web 报表客户端 | `grwebapp6.exe`,协议 `grwebapp://`,WS 端口 **22333** | `gr7webapp64.exe`,协议 **`gr7webapp://`**,WS 端口 **22337** |
|
||||
| 设计器 | `grdesigner6.exe` | `gr7designer64.exe` |
|
||||
| `.grf` 格式 | UTF-8 BOM + JSON | **新增 `_WR7_` 压缩格式**(`EF BB BF` + `_WR7_w...`),也兼容旧 JSON;库内 `gr.wr.decodeWR()` 解码 |
|
||||
|
||||
**关键判断**:`.grf` 里的 `Version` 保持 `6.8.5.0` 不动——GR7 自带样例全是 6.6.8.1,说明向下兼容确定;`grhtml5-7-min.js` 中 `Version` 只是 `grconst.VERSION` 常量,不校验文件版本。
|
||||
|
||||
### 改动清单
|
||||
|
||||
| 文件 | 改动 |
|
||||
|---|---|
|
||||
| `web/public/grlib/grhtml5-7-min.js` | 新增(官方 270KB),删除 `grhtml5-6.8-min.js` |
|
||||
| `web/src/utils/gridreport/loadGrhtml5.js` | 加载 `/grlib/grhtml5-7-min.js` |
|
||||
| `web/src/utils/gridreport/grwebapp.js` | 整份换 GR7 官方版(`gr7webapp://` / 22337 / v7 客户端下载 URL),启用 ESM 导出 |
|
||||
| `tools/report-designer/F9ReportDesignerHandler.ps1` | 设计器优先 `D:\GridReport7\gr7designer64.exe`,其次 .grf 关联(只认 gr7designer),最后回退 GR6 |
|
||||
| `tools/report-designer/install-gr7-grf-assoc.reg` | 新增:`.grf` → `GR7.Template` → `gr7designer64.exe`(HKCU 用户级,已导入生效) |
|
||||
|
||||
### 顺带修掉的两个真缺陷
|
||||
|
||||
| # | 问题 | 说明 |
|
||||
|---|---|---|
|
||||
| 1 | `viewer.Stop()` 一直是空操作 | 官方 API 是**小写 `stop()`**(GR6/GR7 都是),原来被 `try/catch` 吞掉 → 切 Tab / 关预览从未真正销毁渲染实例。改为 `(viewer.stop \|\| viewer.Stop).call(viewer)` |
|
||||
| 2 | `urlAddRandomNo()` 污染 token | URL 已带 `?access_token=xxx` 时再拼 `?id=`,浏览器把 `xxx?id=123` 整体当 token → 客户端打印/导出 401。改为已有查询串时用 `&` |
|
||||
|
||||
---
|
||||
|
||||
## 三、模板保存方式:随报表保存 + 上传/下载 .grf(两种并存)
|
||||
|
||||
> 中途做过"只随报表保存、去掉上传/下载"的版本,后按需求**恢复上传/下载 .grf 的方式**,并保留"随报表保存"用于页面内画布编辑落库。
|
||||
|
||||
### 3.1 后端
|
||||
|
||||
`ReportCreateDto` / `ReportUpdateDto` 增加 `TemplateJson`(.grf 的 JSON 文本);`CreateAsync/UpdateAsync` 内一并落库,**不传则保留原模板**(启停等只改配置的请求不会清模板)。
|
||||
|
||||
模板文本规范化 `NormalizeTemplateJson()`:去 BOM → trim → 校验必须是 JSON 对象 → 否则 400「报表模板(.grf)不是合法 JSON」。
|
||||
|
||||
独立上传/下载接口保持不变:
|
||||
- `POST /api/sys/report/{id}/template`(multipart,`IFormFile`)→ 解码 UTF-8、去 BOM → 写 `ReportFile.DesignContent`
|
||||
- `GET /api/sys/report/{id}/template/download` → 输出带 UTF-8 BOM 的 `.grf`,文件名 `{编码}.grf`
|
||||
|
||||
### 3.2 前端(两条路径)
|
||||
|
||||
| 路径 | 入口 | 提交方式 |
|
||||
|---|---|---|
|
||||
| A. 页面内编辑 | 中栏「页面设计」画布 / 「源码 JSON」 | `save()` 时随配置一起 `templateJson` 提交 |
|
||||
| B. 锐浪设计器设计好后上传 | 工作台顶栏「上传 .grf / 下载 .grf」;列表卡片「上传模板 / 下载模板」 | 走独立接口,**不走保存**;上传成功后画布自动重新载入 |
|
||||
|
||||
- 工作台:`uploadGrf()` 选本地 `.grf` → `POST /{id}/template` → `reloadTemplateFromServer()` 重载画布;`downloadGrf()` 用 `access_token` 直开下载地址
|
||||
- 列表页:隐藏 `input[type=file][accept=".grf"]` + `pickUpload(row)` / `downloadTemplate(row)`;若该报表正在右侧编辑,上传后 `designerKey++` 强制工作台重新载入
|
||||
|
||||
---
|
||||
|
||||
## 四、实体重构:Report + ReportFile(对齐 F9)
|
||||
|
||||
### 4.1 新实体
|
||||
|
||||
`Domain/Entities/SYS/Reports/ReportFile.cs`(表 `sys_report_files`,基类 `BusinessEntity`):
|
||||
|
||||
```csharp
|
||||
public class ReportFile : BusinessEntity
|
||||
{
|
||||
public long ReportId { get; private set; }
|
||||
public string DesignContent { get; private set; } = ""; // .grf 的 JSON 文本
|
||||
public void UpdateContent(string designContent) { ... }
|
||||
}
|
||||
```
|
||||
|
||||
`Report` 增加 `ReportFileId`(对齐 F9 `Report.ReportFileId`)+ `BindReportFile(long)`,原 `TemplateData/TemplateName/TemplateSize` 标记**废弃**(仅供启动迁移读取,不再写入),新增 `ClearLegacyTemplate()`。
|
||||
|
||||
### 4.2 服务层
|
||||
|
||||
| 方法 | 说明 |
|
||||
|---|---|
|
||||
| `GetTemplateContentAsync(Report)` | 读 `ReportFile.DesignContent`;无模板返回 null |
|
||||
| `SaveTemplateAsync(Report, json)` | 有实例则 `UpdateContent`,否则新建实例并 `BindReportFile`;**只改内存**,由调用方统一保存 |
|
||||
| `FindTemplateFileAsync(Report)` | 优先按 `ReportFileId` 查,兜底按 `ReportId` 查 |
|
||||
| `DeleteAsync` | 级联删除模板实例 |
|
||||
| `HasTemplate(r)` | `r.ReportFileId != 0` |
|
||||
|
||||
Controller 侧模板输出统一补 **UTF-8 BOM**(`ToGrfBytes()`),保证下载给设计器/浏览器渲染都符合 .grf 约定;**数据库里存纯文本**(不带 BOM),避免"套娃 BOM"。
|
||||
|
||||
### 4.3 旧数据自愈
|
||||
|
||||
`DbSeeder.MigrateReportTemplateToFile()` 在启动时把 `sys_reports.template_data`(旧内嵌列)迁移成 `ReportFile` 并回填 `ReportFileId`,随后清空废弃字段。同时 `EnsureColumn(fsql, "sys_reports", "report_file_id", ...)` 兜底旧库加列(`SyncStructure` 不会为已存在表加列)。
|
||||
|
||||
实测启动日志:
|
||||
|
||||
```
|
||||
[DbSeeder] 报表「职工花名册」模板已迁移到 ReportFile#139204832664752128
|
||||
[DbSeeder] 报表「落丝记录」模板已迁移到 ReportFile#139204832731860992
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 五、报表编辑页(配置 + 参数 + 模板预览)
|
||||
|
||||
> **重要变更**:先用"自研网页可视化设计器"做过一版(画布 + 对象树 + 属性面板),因达不到锐浪设计器的排版能力,**已整体移除**,模板改回"上传/下载维护"。
|
||||
|
||||
### 5.1 页面结构
|
||||
|
||||
`/report/manage` 为 **左列表 + 右编辑面板**:
|
||||
|
||||
```
|
||||
┌──────────────┬──────────────────────────────────────────────────────┐
|
||||
│ 报表列表 │ 顶栏:编辑报表·名称 [模板已配置] [删除][上传 .grf][下载 .grf]
|
||||
│ ┌──────────┐ ├─────────────┬────────────────────────────────────────┤
|
||||
│ │落丝记录 │ │ 配置 | 参数 │ [预览视图][模板源码] │
|
||||
│ │PROD_QS_1 │ │ │ │
|
||||
│ │模板已配置 │ │ 分组/编号 │ ┌──── grhtml5 网页内渲染 ────┐ │
|
||||
│ │上传 下载 删除│ │ 名称/顺序 │ │ 日期 车间 机组 庄口 回数 ... │ │
|
||||
│ └──────────┘ │ 数据源/SQL │ └─────────────────────────────┘ │
|
||||
│ ┌──────────┐ │ 备注 │ │
|
||||
│ │职工花名册 │ │ Config JSON │ 模板源码:只读查看 .grf JSON │
|
||||
│ └──────────┘ │ │ │
|
||||
└──────────────┴─────────────┴────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 5.2 功能边界
|
||||
|
||||
| 区域 | 内容 |
|
||||
|---|---|
|
||||
| 左栏·配置 | 分组 / 编号 / 名称 / 顺序 / 数据源 `DsName` / 数据 SQL / 备注 |
|
||||
| 左栏·参数 | 查询参数列表(参数名、标签、控件类型、默认值、`Ds_Service`、`Ds_Static`、必填) |
|
||||
| 左栏·Config JSON | 自动模式随表单一同生成,可切「源码」直接写 |
|
||||
| 右栏·预览视图 | grhtml5 渲染真实数据;未上传模板时显示占位提示 + 上传按钮 |
|
||||
| 右栏·模板源码 | 只读查看 `.grf` JSON,用于核对字段名 |
|
||||
| 顶栏 | 删除 / 上传 .grf / 下载 .grf / 刷新预览 / 保存 |
|
||||
|
||||
模板本身**不在网页里编辑**——用 Grid++Report 7 设计器排版 `.grf`,再上传。
|
||||
|
||||
### 5.3 文件
|
||||
|
||||
| 文件 | 说明 |
|
||||
|---|---|
|
||||
| `web/src/components/report/ReportEditor.vue` | 新增:配置 + 参数 + 预览 + 模板源码(只读) |
|
||||
| `web/src/components/report/ReportDesigner.vue` | **已删除**(自研画布/对象树/属性面板) |
|
||||
| `web/src/views/report/manage/index.vue` | 重写:左列表卡片(名称/编码/分组/模板状态/上传/下载/删除)+ 右侧编辑面板 |
|
||||
| `web/src/views/report/manage/editor.vue` | **已删除**(旧的全屏抽屉版) |
|
||||
|
||||
保存语义:编辑已有报表时**不提交模板**,避免覆盖刚上传的 `.grf`;仅新建时写入一份最小兜底模板。
|
||||
|
||||
---
|
||||
|
||||
## 六、踩坑记录(本轮)
|
||||
|
||||
| # | 问题 | 根因 | 解决 |
|
||||
|---|---|---|---|
|
||||
| 1 | 后端改完编译报 `MSB3027/MSB3021` 文件被锁 | `dotnet run` 进程占用 `bin` 下 dll | 先 `Stop-Process` 停后端再 build(编译本身 0 错误,只是拷贝失败) |
|
||||
| 2 | 模板读回来变空、字段列表丢失 | 服务端存 .grf 带 UTF-8 BOM,axios 的 `JSON.parse` 失败后**静默返回字符串**,前端再 `JSON.parse` 遇 BOM 抛错 → 走 `catch` 得到空模板 | 存储改纯文本,输出时补 BOM;前端 `parseTemplatePayload()` 统一去 BOM |
|
||||
| 3 | 旧库加了字段但服务端读不到 | FreeSql `CodeFirst.SyncStructure` **不会给已存在表加列** | `EnsureColumn(fsql, "sys_reports", "report_file_id", "INTEGER NOT NULL DEFAULT 0")` |
|
||||
| 4 | 迁移后再也读不到旧模板 | 实体属性删了就读不到 `template_data` 列 | 保留 `TemplateData` 属性标废弃,仅供迁移读取,迁移后 `ClearLegacyTemplate()` |
|
||||
| 5 | 保存后模板体积从 4300B 变 2911B | 前端 `JSON.stringify(tpl)` 无缩进,缩进空白被去掉 | 非缺陷,属预期(`JSON.stringify(obj, null, '\t')` 才带缩进) |
|
||||
| 6 | 画布拖动无效 | `startDrag(e, path, mode, event)` 定义 4 参,模板只传 3 参 | 签名改为 `startDrag(path, mode, event)` |
|
||||
| 7 | 对象树 `default-expanded-keys` 不生效 | 节点没有 `node-key` | 每个节点补 `pathKey = JSON.stringify(path)` |
|
||||
| 8 | **上传 .grf 报「不是合法 JSON」** | **GR7 设计器保存的 .grf 已不是 JSON**:`EF BB BF` + `_WR7_w...` 压缩文本(抽样 D:\GridReport7\Samples 1102 个 .grf:970 个 `_WR7_` 格式,130 个旧 JSON 格式) | 后端不再做 JSON 校验,只去 BOM/空白原样存储;`DecodeTemplateBytes()` 按 BOM 识别 UTF-8/UTF-16LE/UTF-16BE |
|
||||
| 9 | 上传后预览报「获取报表模板失败 URL: 乱码」 | GR7 库用 `F(e)` 判断模板来源:只有 `_WR_`(4 字符,下划线)开头才当"内容",而设计器存的是 `_WR7_` → 被判为 URL 去请求 | 预览改传**模板 URL**(`/template.json?access_token=`),走库的 ajax → `Report.load()` 分支,内部 `"_WR"==substr(0,3)` 命中后 `decodeWR()` 正常解码 |
|
||||
| 10 | `/template.json` 返回带 BOM 会破坏前缀判断 | `\uFEFF` 开头使 `"_WR"==substr(0,3)` 与 `"{"===charAt(0)` 双双失效 | `template.json` **不输出 BOM**;仅 `/template/download`(给设计器的文件)带 BOM |
|
||||
|
||||
---
|
||||
|
||||
## 七、验证记录
|
||||
|
||||
### 7.1 接口层(node 脚本直连 `localhost:3000`)
|
||||
|
||||
```
|
||||
CREATE_HAS_TPL=true NAME=tmp_tpl_351543.grf SIZE=626
|
||||
TPL_BOM=efbbbf TPL_TITLE=TMP 随报表保存 FIELDS=A,B
|
||||
NO_TPL_UPDATE_HAS_TPL=true SIZE=626 ← 不带模板的修改不会清模板
|
||||
WITH_TPL_UPDATE_TITLE=改过的标题 ← 带模板一起改
|
||||
BAD_TPL_CODE=400 MSG=报表模板(.grf)不是合法 JSON
|
||||
```
|
||||
|
||||
迁移 + 模板随报表保存(重启后):
|
||||
|
||||
```
|
||||
LIST=PROD_QS_1(file=139204832731860992,has=true) | employee_directory(file=139204832664752128,has=true)
|
||||
TPL_BOM=efbbbf LEN=4300
|
||||
UPDATE_CODE=200
|
||||
AFTER_AUTHOR=F9 内嵌设计器
|
||||
KEEP_TPL_LEN=2911 ← 不带模板的 PUT 后模板仍在
|
||||
```
|
||||
|
||||
### 7.2 报表中心(GR7 渲染)
|
||||
|
||||
```
|
||||
GR_SCRIPTS=["/grlib/grhtml5-7-min.js"]
|
||||
HOLDER_HTML_LEN=240770 HOLDER_CHILDREN=3
|
||||
职工花名册 部门:(全部) 关键字:(全部) 记录数:942
|
||||
工号 职工姓名 性别 部门 工种 电话 入职日期 / 付华芳 女 丝业生产中心 ...
|
||||
ALERT=NONE
|
||||
```
|
||||
|
||||
### 7.3 内嵌设计工作台
|
||||
|
||||
```
|
||||
LIST_ITEMS=2 TOPBAR=编辑报表 · 落丝记录
|
||||
VIEW_TABS=["页面设计","明细字段","源码 JSON","预览视图"]
|
||||
LEFT_TABS=["配置","参数 (0)"]
|
||||
PAPER_EXISTS=true W=793.8px ← A4 21cm
|
||||
SECTIONS=["报表头","页眉","明细网格","页脚"]
|
||||
CONTROLS=4 DETAIL_CELLS=36
|
||||
PROPS_TITLE=属性 · StaticBox#0
|
||||
PROPS=["Type","Name","Center","Left","Top","Width","Height","Text"]
|
||||
CONTROLS_AFTER_INSERT=5 → CONTROLS_AFTER_DELETE=4
|
||||
PREVIEW_TEXT=日期 车间 机组 庄口 回数 颜色 半车 落丝时间 缫丝桶数
|
||||
```
|
||||
|
||||
### 7.4 编译
|
||||
|
||||
- 后端 `dotnet build`:**0 错误**(仅有既存 CS8632 nullable 注释警告)
|
||||
- 前端 `vite build`:通过(仅 `@microsoft/signalr` 的既有 Rollup 注释警告)
|
||||
|
||||
---
|
||||
|
||||
## 八、当前状态
|
||||
|
||||
| 项 | 状态 |
|
||||
|---|---|
|
||||
| 报表渲染库 | GR7(`grhtml5-7-min.js` 7.0.26.0930)✅ |
|
||||
| WEB 客户端打印/PDF/Excel | GR7(`gr7webapp://` / 22337)✅ |
|
||||
| 本机设计器协议 | 指向 `gr7designer64.exe`,`.grf` 关联已切 GR7 ✅ |
|
||||
| 模板存储 | 独立实体 `ReportFile.DesignContent`,`Report.ReportFileId` 关联 ✅ |
|
||||
| 新增/编辑报表 | 配置 + 参数 + 模板一次保存;**同时保留 .grf 上传/下载** ✅ |
|
||||
| 旧数据 | 启动自动迁移到 `ReportFile`,日志可查 ✅ |
|
||||
| 报表管理页 | 左列表 + 右编辑面板(配置/参数/预览/模板源码只读),无自研设计器 ✅ |
|
||||
| 报表中心 | 多 Tab 预览正常(942 条职工)✅ |
|
||||
| 后端服务 | 运行中 `http://localhost:3000`(本轮重启后可正常工作)✅ |
|
||||
| 前端服务 | 运行中 `http://localhost:5173` ✅ |
|
||||
|
||||
启动方式不变:
|
||||
|
||||
```powershell
|
||||
cd backend/F9WebAiTest.Api && dotnet run
|
||||
cd web && npm run dev
|
||||
```
|
||||
|
||||
默认账号:`admin / 123456`
|
||||
|
||||
---
|
||||
|
||||
## 九、遗留与后续建议
|
||||
|
||||
1. **`Report_FavoriteGroup` 未实现**:用户提供的 F9 实体(`User_Id` / `Parent_Id` / `Name` / `Sort`)对应「报表收藏分组」,本轮聚焦模板实例化与内嵌设计器,未落地。
|
||||
2. **模板编辑能力边界**:锐浪设计器是 COM 组件,浏览器无法嵌入;自研的网页画布在排版精度上达不到要求,已移除。模板排版统一在 Grid++Report 7 设计器完成 → 上传。
|
||||
3. **无网页端模板编辑**:如需在系统内改模板,需下载 `.grf` → 本机设计器修改 → 上传覆盖;或走 `f9designer://` 本机设计器协议(自动回传)。
|
||||
4. **历史废弃列**:`sys_reports.template_data / template_name / template_size` 仍在表中(值已清空),确认无回滚需求后可 `DROP COLUMN`。
|
||||
5. **编辑器与报表中心分叉**:报表中心的参数控件配置(左栏「参数」tab)目前是纯文本填 `ControlType`,未接 `utils/gridreport/controls` 注册中心的分组下拉(原 `editor.vue` 有,随删除一起丢失,可补回)。
|
||||
6. **工作进度-3 遗留仍在**:`Number()` 精度隐患、admin `RolePermission` 重复、部门权限无消费方、`system/*` 死代码。
|
||||
+189
@@ -0,0 +1,189 @@
|
||||
# 工作进度-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()` 精度隐患等。
|
||||
+177
@@ -0,0 +1,177 @@
|
||||
# 工作进度-6:落丝记录报表(生产-前缫)落地实现
|
||||
|
||||
> 时间:2026-10-20(承接工作进度-5)
|
||||
> 环境:后端 .NET 8 + FreeSql + MediatR(Windows/SQLite),前端 Vue3 + Element Plus + Vite
|
||||
> 目标:把「落丝记录」从占位报表补成**可查询、可打印的正式报表**(DsName + 参数 + 明细列 + 模板全链路)
|
||||
|
||||
---
|
||||
|
||||
## 一、改造前的状态(问题定位)
|
||||
|
||||
数据库里已有报表 `PROD_QS_1 / 落丝记录`,但只是占位:
|
||||
|
||||
| 项 | 改造前 |
|
||||
|---|---|
|
||||
| `Config.DsName` | 空(`"DsName":""`) |
|
||||
| `Config.Parms` | 仅 1 个 `q_date`,`Label` 为空、控件为 `AntdUI.DatePicker` |
|
||||
| `DataSql` | `SELECT 1` |
|
||||
| 模板 | 早期「上传测试」产物:`Title` 空、报表头标题为空文本、无参数区、9 列列宽全 2.5cm |
|
||||
| 数据链路 | `DsName` 为空 → 落回 `SELECT 1` → 报表永远空 |
|
||||
|
||||
---
|
||||
|
||||
## 二、后端实现
|
||||
|
||||
### 2.1 新增文件
|
||||
|
||||
| 文件 | 说明 |
|
||||
|---|---|
|
||||
| `Application/CQRS/SYS/Reports/Queries/GetLsjlRepQuery.cs` | 报表查询:日期范围 / 车间(含子车间)/ 机组 / 庄口 / 关键字过滤,输出中文列名记录集 + 报表参数 |
|
||||
| `Application/Services/Reports/Providers/RepDt_Prod_Lsjl.cs` | Provider(`ReportName = RepDt_Prod_Lsjl`),实现 `IReportDataProviderMeta`(6 个参数声明 + 13 个字段中文名) |
|
||||
| `Infrastructure/Assets/ReportTemplates/lsjl.grf` | 内置模板(嵌入资源):标题 + 参数区 + 9 列明细网格 + 页码/打印时间页脚 |
|
||||
| `DbSeeder.SeedLsjlReport(...)` | 幂等种子:报表定义 + 查询配置 + 权限单元 + 模板 |
|
||||
|
||||
### 2.2 查询参数
|
||||
|
||||
| 参数名 | 标签 | 控件 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `q_date_start` | 开始日期 | `AntdUI.DatePicker` | 默认值 `不限`(不预填今天) |
|
||||
| `q_date_end` | 结束日期 | `AntdUI.DatePicker` | 默认值 `不限` |
|
||||
| `q_dep` | 车间 | `F9-DEP-QS-SELECT` | 前缫口径部门树,**含子车间** |
|
||||
| `q_jizu` | 机组 | `AntdUI.Input` | 数字,留空为全部 |
|
||||
| `q_zk` | 庄口 | `F9-GYZK-SELECT` | 打开工艺庄口选择页,取值 = 庄口 Id |
|
||||
| `q_like` | 关键字 | `AntdUI.Input` | 匹配 庄口名 / 规格 / 颜色 |
|
||||
|
||||
### 2.3 明细列(键名与 F9 落丝记录模板一致,中文)
|
||||
|
||||
| 报表列 | 取值 |
|
||||
|---|---|
|
||||
| 日期 | `WorkDate.ToString("yyyy-MM-dd")` |
|
||||
| 车间 | 部门名(DeptId → Department.Name) |
|
||||
| 机组 | `Jizu` |
|
||||
| 庄口 | 工艺庄口名(GyzkId → CraftZhuangKou.ZhuangKouName) |
|
||||
| 回数 | `Huishu` |
|
||||
| 颜色 | `Yanse` |
|
||||
| 半车 | `Isbanche ? "半车" : "整车"` |
|
||||
| 落丝时间 | `LuosiTime` |
|
||||
| 缫丝桶数 | `Chetaishu`(车台数) |
|
||||
| 规格 / 车台范围 / 缫丝时长 / 车速 | 备选列(模板未用不显示,自定义模板可用) |
|
||||
|
||||
报表参数(模板头展示):开始日期 / 结束日期 / 车间 / 机组 / 庄口 / 记录数 / 查询时间。
|
||||
|
||||
### 2.4 种子策略(幂等,不覆盖用户成果)
|
||||
|
||||
| 场景 | 行为 |
|
||||
|---|---|
|
||||
| 报表不存在 | 创建报表 + 写入内置模板 `lsjl.grf` + 创建权限单元并授权 admin |
|
||||
| 已存在且 `Config` 不含 `RepDt_Prod_Lsjl` | 只补齐查询配置,`DataSql` 置空(避免与 DsName 混淆) |
|
||||
| 已存在且模板是占位模板 | 替换为内置模板。判定:JSON 模板 + `Title` 为空 + 未声明任何 `Parameter`;`_WR7_` 压缩模板一律不覆盖 |
|
||||
|
||||
> 用户在设计器里做过正式模板(有标题/参数)后,重启不会被动覆盖。
|
||||
|
||||
---
|
||||
|
||||
## 三、顺带修掉的两个真缺陷
|
||||
|
||||
| # | 问题 | 根因 | 解决 |
|
||||
|---|---|---|---|
|
||||
| 1 | 0 行数据时报表参数区错位(`(全部)2(全部)` 跑到明细网格下方) | `ToGridReportJson` 只输出有数据的数据集,0 行时**连 `recordset` 键都不输出** → 渲染库找不到主记录集,参数绑定错乱 | 后端 0 行时输出 `"recordset": []`;前端报表中心 + 编辑器预览改走「当前查询条件没有数据」空态,不再交给渲染库 |
|
||||
| 2 | 日期范围参数默认预填「今天」,落丝记录默认只查到当天 1 条 | `paramInitialValue` 对空 `DefaultValue` 的日期控件一律返回今天(为单日期参数设计) | 新增 `DefaultValue = "不限"` 约定(`不限/不填/none/null/empty` → 空值),仅日期范围参数使用;其他报表行为不变 |
|
||||
|
||||
---
|
||||
|
||||
## 四、验证记录
|
||||
|
||||
### 4.1 接口层(参数过滤矩阵)
|
||||
|
||||
```
|
||||
全部 rows=2 dates=2026-09-09,2026-10-20
|
||||
机组=1 rows=2 dates=2026-09-09,2026-10-20
|
||||
机组=2 rows=0
|
||||
开始日期≥2026-10-01 rows=1 dates=2026-10-20
|
||||
结束日期≤2026-09-30 rows=1 dates=2026-09-09
|
||||
关键字=蓝色 rows=2 dates=2026-09-09,2026-10-20
|
||||
关键字=20/22 rows=2 dates=2026-09-09,2026-10-20
|
||||
车间=前缫车间(26) rows=2 dates=2026-09-09,2026-10-20
|
||||
车间=后缫(27) rows=0
|
||||
日期范围 09-01~09-30 rows=1 dates=2026-09-09
|
||||
PARAMETER={"开始日期":"","结束日期":"","车间":"(全部)","机组":"(全部)","庄口":"(全部)","记录数":2,"查询时间":"..."}
|
||||
```
|
||||
|
||||
数据源探测(进度-5 的自动配置接口):
|
||||
|
||||
```
|
||||
DS_LIST: 落丝记录 / RepDt_Prod_Lsjl / params=6
|
||||
PROBE total=2 fields=13 parms=6
|
||||
FIELDS=日期,车间,机组,庄口,回数,颜色,半车,落丝时间,缫丝桶数,规格,车台范围,缫丝时长,车速
|
||||
```
|
||||
|
||||
### 4.2 报表中心(GR7 渲染)
|
||||
|
||||
```
|
||||
TREE=生产 / 前缫 / 落丝记录 / 用户 / 职工花名册
|
||||
QP_LABELS=开始日期,结束日期,车间,机组,庄口,关键字
|
||||
QP_KINDS=date,date,select,input,picker,input
|
||||
VIEWER_TEXT=落丝记录 开始日期: 结束日期: 车间:(全部) 机组:(全部) 庄口:(全部)
|
||||
记录数:2 查询时间:2026-10-20 14:26:00
|
||||
日期 车间 机组 庄口 回数 颜色 半车 落丝时间 缫丝桶数
|
||||
2026-09-09 前缫车间 1 2026会东二类茧(7532)宁兴 1 蓝色 整车 16:21:32 20
|
||||
2026-10-20 前缫车间 1 2026会东二类茧(7532)宁兴 1 蓝色 整车 14:16:35 20
|
||||
```
|
||||
|
||||
界面交互(改控件 → 点查询):
|
||||
|
||||
```
|
||||
INIT_ROWS=2 机组=1 → 2 机组=2 → 无数据(显示「当前查询条件没有数据」)
|
||||
重置 → 2 关键字=宁兴 → 2
|
||||
```
|
||||
|
||||
### 4.3 报表管理页(编辑器)
|
||||
|
||||
```
|
||||
TOPBAR=编辑报表 · 落丝记录 TAG=模板已配置
|
||||
LEFT_TABS=配置,参数 (6),明细字段 (9)
|
||||
FIELD_NAMES=日期,车间,机组,庄口,回数,颜色,半车,落丝时间,缫丝桶数
|
||||
VIEWER_TEXT=落丝记录 开始日期: 结束日期: 车间:(全部)… 2 行数据
|
||||
```
|
||||
|
||||
### 4.4 编译 / 运行
|
||||
|
||||
- 后端 `dotnet build`:**0 错误**
|
||||
- 前端无 lint 诊断
|
||||
- 服务:后端 `http://localhost:3000`、前端 `http://localhost:5173` 运行中
|
||||
|
||||
---
|
||||
|
||||
## 五、当前状态
|
||||
|
||||
| 项 | 状态 |
|
||||
|---|---|
|
||||
| 落丝记录报表数据链路(DsName → Provider → CQRS) | ✅ |
|
||||
| 6 个查询参数(日期范围 / 车间含子车间 / 机组 / 庄口 / 关键字) | ✅ 界面控件类型正确、过滤生效 |
|
||||
| 明细 9 列(标题、列宽、中文数据) | ✅ |
|
||||
| 报表头:标题 + 参数区(查询条件回显) | ✅ 内置模板 `lsjl.grf` |
|
||||
| 0 行查询 | ✅ 显示「没有数据」,不再错位 |
|
||||
| 报表中心 / 报表管理两个入口 | ✅ 均可正常渲染与打印导出 |
|
||||
| 模板可继续设计器维护 | ✅ 下载 `.grf` → 改 → 上传;占位判定不会误覆盖正式模板 |
|
||||
|
||||
---
|
||||
|
||||
## 六、遗留与后续建议
|
||||
|
||||
1. **列宽与纸张**:内置模板 9 列合计约 17.3cm,横向内容紧张时会在 `ResizeToFit` 下缩放;若要更宽可改模板 `Printer` 或减少列。
|
||||
2. **车间默认值**:`F9-DEP-QS-SELECT` 会默认选中登录用户部门(F9 口径),因此非 admin 用户打开默认只看本车间;如需默认全部,可把该参数控件换成 `F9-DEP-ALL-SELECT` 或清空 `defaultFromUser`。
|
||||
3. **班组 / 班次维度**:落丝记录暂无班次字段,若后续按班次统计需在 `LsjlRecord` 补字段并同步报表。
|
||||
4. **同类报表复用**:`纤度检验 / 黑板检验` 可按本轮的「Query + Provider + 内置模板 + 种子」模板照做,或直接用进度-5 的新增报表自动配置(选 DsName 即可识别参数与列)。
|
||||
5. **进度-5 遗留仍在**:`_WR7_` 模板不能反解列、探测需真实执行查询、进度-3 的 `Number()` 精度隐患等。
|
||||
|
||||
---
|
||||
|
||||
## 七、工作进度文档约定(协作规则)
|
||||
|
||||
工作进度文档**不再每轮对话都生成**,按以下规则执行:
|
||||
|
||||
- **只在上下文即将结束**(或用户明确要求「记一下进度」)时,才把这一阶段的改动一次性汇总成当轮进度文档
|
||||
- 中间过程只在对话里说明改动与验证结果,不落盘成文档
|
||||
- 已产出的 `工作进度-1 ~ 6.md` 保留不动;本篇作为落丝记录报表的阶段成果留档
|
||||
- 若某次改动比较关键、需要立刻留档,由用户明确说「记一下进度」后再生成
|
||||
+167
@@ -0,0 +1,167 @@
|
||||
# 工作进度-7:报表参数与查询条件分离(概念校准 + 参数区修复)
|
||||
|
||||
> 时间:2026-10-20(承接工作进度-6)
|
||||
> 触发:用户指出「`Parameter_Data` 里的这些才是参数,`Config.Parms` 的 q_* 是查询条件,不是报表参数」
|
||||
|
||||
---
|
||||
|
||||
## 一、问题
|
||||
|
||||
之前的模板生成把 **查询条件**(`Config.Parms` 的 `q_date_start` / `q_dep`…)直接写进了模板的 `Parameter` 节点与报表头,导致:
|
||||
|
||||
- 模板参数变成 `q_date_start, q_date_end, q_dep`(业务上毫无意义的内部键)
|
||||
- 报表头显示的是"查询条件的名字",不是"本次查询的统计口径"
|
||||
- 日期参数空值被默认成"今天",报表默认只查当天
|
||||
|
||||
正确的分层:
|
||||
|
||||
| 概念 | 代码位置 | 用途 | 示例 |
|
||||
|---|---|---|---|
|
||||
| **查询条件** | `Config.Parms`(`q_*`) | 渲染查询面板,值随请求传给后端 | `q_date_start` / `q_dep` / `q_like` |
|
||||
| **报表参数** | 后端 `DataSource.Parameter_Data` | 查询执行后返回,供模板头/合计区展示 | `开始日期` / `车间` / `记录数` / `查询时间` |
|
||||
|
||||
---
|
||||
|
||||
## 二、后端
|
||||
|
||||
### 2.1 探测结果带回报表参数(数据驱动,不是硬编码)
|
||||
|
||||
`ReportScaffoldDtos.cs` 新增:
|
||||
|
||||
```csharp
|
||||
public class ReportParamValueDto { public string Name; public string Value; }
|
||||
// ReportProbeResultDto 增加 List<ReportParamValueDto> Parameters
|
||||
```
|
||||
|
||||
`ReportService.FillFromProviderAsync`:
|
||||
|
||||
```csharp
|
||||
var paramDatum = dataSource.Report_Data.FirstOrDefault(kv => kv.Value?.Parameter_Data != null);
|
||||
result.Parameters = ToNameValueDict(paramDatum.Value.Parameter_Data)
|
||||
.Select(kv => new ReportParamValueDto { Name = kv.Key, Value = kv.Value?.ToString() ?? "" })
|
||||
.ToList();
|
||||
```
|
||||
|
||||
> 仍然遵守进度-6 的结论:Provider 不声明元数据,参数与列**都从查询结果里取**。
|
||||
|
||||
### 2.2 内置模板 `lsjl.grf`
|
||||
|
||||
- 参数绑定由 `"Parameter": "开始日期"` 改为文本标记 `"Text": "开始日期:[#{开始日期}#]"`
|
||||
- 控件类型必须是 **MemoBox**(实测见第五节)
|
||||
- 顶层 `Parameter` 声明为 7 个报表参数:开始日期 / 结束日期 / 车间 / 机组 / 庄口 / 记录数 / 查询时间
|
||||
|
||||
### 2.3 种子自愈(`DbSeeder.SeedLsjlReport`)
|
||||
|
||||
旧库里的模板/配置会被自动升级,且**不会覆盖用户手工配置**:
|
||||
|
||||
| 判定 | 行为 |
|
||||
|---|---|
|
||||
| `Config.Parms` 为空,或所有 `Label` 都为空(程序生成的占位配置) | 重写为规范的 6 个查询条件(中文标签、日期默认值「不限」) |
|
||||
| 模板是我们生成的(`Author = F9 报表中心`)且含 `"Parameter": "开始日期"` | 替换为内置模板 |
|
||||
| 模板是我们生成的且 `Parameter` 里出现 `q_*`(查询条件混入) | 替换为内置模板 |
|
||||
| 模板是我们生成的且存在 `StaticBox` + `[#{...}#]`(渲染不解析) | 替换为内置模板 |
|
||||
|
||||
用户自己设计/上传的模板(`_WR7_` 或 Author 不是我们)一律不动。
|
||||
|
||||
---
|
||||
|
||||
## 三、前端(`ReportEditor.vue`)
|
||||
|
||||
### 3.1 左栏 tab 拆分
|
||||
|
||||
从「配置 / 参数 / 明细字段」改为:
|
||||
|
||||
| tab | 内容 |
|
||||
|---|---|
|
||||
| 配置 | 编号、名称、分组、数据源、数据 SQL、备注 |
|
||||
| **查询条件 (n)** | `Config.Parms`:参数名 / 标签 / 控件类型 / 默认值 / 提示,附说明"不进入模板" |
|
||||
| **报表参数 (n)** | 只读列表:后端 `Parameter_Data` 的键与当前值,附说明"来自后端 Parameter_Data" |
|
||||
| 明细字段 (n) | 列名 / 列标题 / 列宽,可生成模板 |
|
||||
|
||||
### 3.2 模板生成改用报表参数
|
||||
|
||||
```js
|
||||
// 之前:parms.value(查询条件)→ 模板 Parameter + Parameter 属性绑定
|
||||
// 现在:reportParams.value(报表参数)→ 模板 Parameter + MemoBox 文本 [#{名称}#]
|
||||
Parameter: reportParams.value.map((p) => ({ Name: p.name, DataType: 'String' }))
|
||||
Text: `${p.name}:[#{${p.name}}#]` // 控件 Type = MemoBox
|
||||
```
|
||||
|
||||
- 识别数据源时(`/scaffold`)同步填充报表参数
|
||||
- 点「刷新预览」后用 `/preview` 返回的 `Parameter` 覆盖一次,保证与真实查询口径一致
|
||||
- 编辑已有报表时从模板 `Parameter` 节点反解(`_WR7_` 无法反解,显示提示)
|
||||
|
||||
---
|
||||
|
||||
## 四、验证记录
|
||||
|
||||
### 4.1 接口
|
||||
|
||||
```
|
||||
POST /sys/report/scaffold { dsName: RepDt_Prod_Lsjl }
|
||||
FIELDS=13 PARAMS=7
|
||||
开始日期 = ""
|
||||
结束日期 = ""
|
||||
车间 = (全部)
|
||||
机组 = (全部)
|
||||
庄口 = (全部)
|
||||
记录数 = 3
|
||||
查询时间 = 2026-10-20 15:52:47
|
||||
```
|
||||
|
||||
### 4.2 报表中心渲染(GR7 HTML5)
|
||||
|
||||
```
|
||||
落丝记录
|
||||
开始日期: 结束日期: 车间:(全部) 机组:(全部) 庄口:(全部)
|
||||
记录数:3 查询时间:2026-10-20 15:52:47
|
||||
日期 车间 机组 庄口 回数 颜色 半车 落丝时间 缫丝桶数
|
||||
2026-09-09 前缫车间 1 … 16:21:32 20
|
||||
2026-10-20 前缫车间 1 … 14:16:35 20
|
||||
2026-10-20 前缫一车间 2 … 14:30:06 20
|
||||
```
|
||||
|
||||
### 4.3 报表管理页(新建 → 识别 → 生成模板)
|
||||
|
||||
```
|
||||
TABS=配置, 查询条件 (0), 报表参数 (7), 明细字段 (13)
|
||||
报表参数 = 开始日期— / 结束日期— / 车间(全部) / 机组(全部) / 庄口(全部) / 记录数3 / 查询时间…
|
||||
生成模板后入库:
|
||||
TPL_PARAMS=开始日期,结束日期,车间,机组,庄口,记录数,查询时间
|
||||
HEADER_TEXT=开始日期:[#{开始日期}#] | … | 查询时间:[#{查询时间}#]
|
||||
```
|
||||
|
||||
### 4.4 关键实测:`[#{参数名}#]` 只在 MemoBox 生效
|
||||
|
||||
用两张最小模板对照渲染(同一份数据 `{ Parameter: [{ 车间: "(全部)" }] }`):
|
||||
|
||||
| 模板 | 控件 | 写法 | 渲染结果 |
|
||||
|---|---|---|---|
|
||||
| A1 | StaticBox | `[#{车间}#]` | `A1-[#{车间}#]`(**原样,未替换**) |
|
||||
| A2 | MemoBox | `[#{车间}#]` | `A2-(全部)` ✅ |
|
||||
| B1 | StaticBox | `[#车间#]`(无花括号) | `B1-[#车间#]`(不替换) |
|
||||
| B2 | StaticBox | `Parameter` 属性 | `(全部)` ✅(旧方式仍可用) |
|
||||
|
||||
结论:**参数显示控件必须用 MemoBox + `[#{参数名}#]`**;StaticBox 只认 `Parameter` 属性。
|
||||
|
||||
---
|
||||
|
||||
## 五、当前状态
|
||||
|
||||
| 项 | 状态 |
|
||||
|---|---|
|
||||
| 「查询条件」与「报表参数」在 UI/模板/接口上彻底分开 | ✅ |
|
||||
| 模板 `Parameter` 只包含报表参数(中文口径) | ✅ |
|
||||
| 报表头参数区正确显示值(含空日期、记录数、查询时间) | ✅ |
|
||||
| 查询条件不再进入模板,也不参与模板参数渲染 | ✅ |
|
||||
| 旧库模板/配置自动升级(限于我们生成的模板) | ✅ |
|
||||
| 日期范围参数默认「不限」,不再默认当天 | ✅ |
|
||||
|
||||
---
|
||||
|
||||
## 六、遗留
|
||||
|
||||
1. **`_WR7_` 模板无法反解参数**:设计器模板的「报表参数」页为空,需点「识别明细列与报表参数」重新获取。
|
||||
2. **报表参数目前只读**:如需要"某些参数不打印""参数排序",可在报表参数页加勾选/拖动。
|
||||
3. **合计/小计**:`Parameter_Data` 已能承载汇总口径,但模板还没有 `Groups` + `SummaryBox` 合计区,下一步可加。
|
||||
4. **报表中心参数默认值语义**:`DefaultValue="不限"` 是约定字符串,后续应改为显式的 `DefaultValueType` 字段。
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
# 工作进度-8:模板源码在线编辑(免下载 / 上传)
|
||||
|
||||
> 时间:2026-10-20(承接工作进度-7)
|
||||
> 触发:用户提出「能不能直接编辑对应报表的模板文件然后保存,而不是下载 → 改 → 上传 → 保存」
|
||||
|
||||
---
|
||||
|
||||
## 一、结论
|
||||
|
||||
可以。模板本来就以文本存在库里(`sys_report_files.design_content`,TEXT),
|
||||
之前只是没有「写回文本」的入口,只能整文件上传。本次新增:
|
||||
|
||||
- 后端一个接口:`PUT /api/sys/report/{id}/template/content`,请求体 `{ "content": "<模板全文>" }`
|
||||
- 前端「模板源码」由**只读**改为**可编辑**,配「保存模板 / 格式化 / 撤销修改 / 放大编辑」
|
||||
|
||||
原来的「上传 .grf / 下载 .grf」保留不动(用于本机设计器排版、外部模板导入)。
|
||||
|
||||
---
|
||||
|
||||
## 二、方案取舍
|
||||
|
||||
| 方案 | 说明 | 结论 |
|
||||
|---|---|---|
|
||||
| 现状:下载 → 本地改 → 上传 | 需要本地装锐浪设计器或文本编辑器,来回一次文件 | 保留,不再作为主路径 |
|
||||
| **在线编辑模板源码 + 保存** | 文本表单直接改 `.grf` 内容,保存即入库,刷新预览立即生效 | ✅ 本次实现 |
|
||||
| 内嵌可视化设计器(拖控件、改属性) | 需要对 `.grf` 全量解析 / 回写;GR7 设计器格式是 `_WR` 压缩文本,无解码器 | 不做(见工作进度-5 遗留) |
|
||||
|
||||
---
|
||||
|
||||
## 三、后端
|
||||
|
||||
### 3.1 DTO(`ReportDtos.cs`)
|
||||
|
||||
```csharp
|
||||
public class ReportTemplateContentDto { public string? Content { get; set; } }
|
||||
```
|
||||
|
||||
### 3.2 Service(`IReportService` / `ReportService.SaveTemplateContentAsync`)
|
||||
|
||||
```csharp
|
||||
public async Task<int> SaveTemplateContentAsync(long id, string content)
|
||||
{
|
||||
var entity = ... ?? throw new F9NotFoundException("报表不存在");
|
||||
var text = NormalizeTemplateContent(content); // 去 BOM / trim,空内容拦截
|
||||
if (head.StartsWith('{') || head.StartsWith('['))
|
||||
JsonDocument.Parse(text); // JSON 语法校验,失败抛 F9OperationException
|
||||
await SaveTemplateAsync(entity, text); // 复用「有则更新 ReportFile,无则新建并绑定」
|
||||
...
|
||||
return text.Length;
|
||||
}
|
||||
```
|
||||
|
||||
- 与上传接口走同一套落库逻辑(`SaveTemplateAsync`),因此「新建 / 更新 / 绑定 `report_file_id`」行为一致
|
||||
- 校验规则:
|
||||
|
||||
| 内容形态 | 服务端行为 |
|
||||
|---|---|
|
||||
| `{...}` / `[...]`(GR6 JSON 模板) | 语法校验,不合法直接 400「模板 JSON 不合法:…」 |
|
||||
| `_WR...`(GR7 设计器压缩文本) | 原样保存(无法在服务端校验) |
|
||||
| 空 / 仅空白 | 400「报表模板内容为空」 |
|
||||
|
||||
### 3.3 接口(`SysReportController`)
|
||||
|
||||
```
|
||||
PUT /api/sys/report/{id}/template/content
|
||||
Body: { "content": "..." }
|
||||
200 → { code: 200, data: { id, size, hasTemplate: true }, message: "模板已保存" }
|
||||
404 → 报表不存在 400 → 内容为空 / JSON 不合法
|
||||
```
|
||||
|
||||
与已有接口的关系:
|
||||
|
||||
| 方法 | 路由 | 用途 |
|
||||
|---|---|---|
|
||||
| GET | `/{id}/template` | 渲染库 / 前端读模板 |
|
||||
| POST | `/{id}/template` | 上传 .grf 文件(设计器回传) |
|
||||
| **PUT** | **`/{id}/template/content`** | **在线编辑后保存文本(本次新增)** |
|
||||
| GET | `/{id}/template/download` | 下载 .grf 给本机设计器 |
|
||||
|
||||
---
|
||||
|
||||
## 四、前端
|
||||
|
||||
### 4.1 `ReportEditor.vue` —— 「模板源码」页
|
||||
|
||||
- 去掉 `readonly`;`v-model="templateSource"` 直接编辑
|
||||
- 新增状态:`templateSaved`(已入库内容,用于判定「未保存」)、`templateSaving`、`wrUnlocked`、`sourceMaximized`
|
||||
- 工具条:
|
||||
|
||||
| 按钮 / 标签 | 行为 |
|
||||
|---|---|
|
||||
| `JSON 模板` / `_WR 压缩模板` | 当前模板形态提示 |
|
||||
| `未保存` | `templateSource !== templateSaved` 时显示 |
|
||||
| 格式化 | `JSON.parse` + `JSON.stringify(…, '\t')`;非 JSON 禁用 |
|
||||
| 撤销修改 | 确认后恢复为上次保存内容 |
|
||||
| 放大编辑 | 整屏弹窗编辑同一份文本(同一 v-model) |
|
||||
| 保存模板 | 前端先校验 JSON,再 `PUT /sys/report/{id}/template/content` |
|
||||
|
||||
- 保存成功后:重新拉取模板 → `hasTemplate = true` → 重置预览(提示点「刷新预览」)→ `emit('saved')`
|
||||
- 「保存报表」时若模板源码有未保存修改,会先弹确认:`先保存模板` / `忽略模板修改`,避免手改内容被静默丢弃
|
||||
- `_WR` 压缩模板默认**只读**,需勾「允许编辑」才可改,保存前再二次确认(无本地校验能力)
|
||||
- 报表未保存时提示「请先保存报表,再编辑模板源码」;已保存但无模板时也可以直接粘贴 JSON 保存(相当于无文件新建模板)
|
||||
|
||||
### 4.2 预览缓存
|
||||
|
||||
`grhtml5` 拉模板是普通 GET,容易被浏览器缓存。预览 URL 追加 `_t=Date.now()`(编辑器与报表中心都加),保证改完模板点「刷新预览」能看到新内容。
|
||||
|
||||
### 4.3 `views/report/manage/index.vue` —— 列表页入口
|
||||
|
||||
- 每行新增「编辑模板」:选中该报表并让右侧工作台**直接打开「模板源码」页**(`ReportEditor` 新增 `openSource` prop,配合 `designerKey` 强制重载)
|
||||
- 「上传模板 / 下载模板」保留
|
||||
|
||||
---
|
||||
|
||||
## 五、验证
|
||||
|
||||
| 项 | 结果 |
|
||||
|---|---|
|
||||
| 后端 `dotnet build`(输出到独立目录避开运行中进程锁定) | ✅ 0 错误 |
|
||||
| `IReportService` / `ReportService` / DTO / Controller 改动 | ✅ 编译通过 |
|
||||
| 前端 IDE 诊断(`ReportEditor.vue`、`manage/index.vue`、`center/index.vue`) | ✅ 0 诊断 |
|
||||
| 前端 `npm run build` | ⚠ 当前环境无 node/npm,未执行;需在本机 `cd web && npm run build` 复核 |
|
||||
|
||||
> 运行中的 `F9WebAiTest.Api` 仍在用旧程序集,**需重启后端**后新接口才生效。
|
||||
|
||||
---
|
||||
|
||||
## 六、遗留
|
||||
|
||||
1. **无语法高亮 / 行号**:目前是 `el-input textarea`,后续可换 CodeMirror / Monaco(需新增前端依赖)。
|
||||
2. **无模板历史版本**:手工改坏后无法一键回滚,只能重新「生成模板」或重新上传;建议后续给 `sys_report_files` 加版本表。
|
||||
3. **`_WR` 仍不能结构化编辑**:需要锐浪的解码 / 编码算法,暂以「允许编辑 + 风险提示」兜底。
|
||||
4. **保存模板不刷新明细字段 / 报表参数页签**:手改 `DetailGrid.Recordset.Field` 后,左栏字段列表仍是旧值(点「识别明细列与报表参数」可同步)。
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
# 工作进度-9:报表设计页(可视化画布 + 官方设计器入口 + 本系统保存)
|
||||
|
||||
> 时间:2026-10-20(承接工作进度-8)
|
||||
> 触发:用户希望「做一个页面,嵌入 Grid++Report 7 的设计页面,并加上本系统的保存按钮」
|
||||
|
||||
---
|
||||
|
||||
## 一、结论:官方设计器无法嵌入网页
|
||||
|
||||
先确认事实,避免做出错误承诺:
|
||||
|
||||
| 检查项 | 结果 |
|
||||
|---|---|
|
||||
| `web/public/grlib/grhtml5-7-min.js` 是否含设计器 API(`insertReportDesigner` 等) | ❌ 无,只有 `insertReportViewer` |
|
||||
| `D:\GridReport7` 安装目录 | 只有查看器库、`gr7designer64.exe`(Win32 桌面程序)、`gr7webapp64.exe`(打印客户端)、`server\gr7svr*.dll`(服务端生成 DLL) |
|
||||
| `D:\GridReport7\Samples\Web`(html / vue / asp.net / jsp) | 只有 HTML5 查看器示例,无网页设计器 |
|
||||
|
||||
原因:GR7 的设计器是 WinForms/Win32 程序,浏览器无法 iframe 内嵌,也没有 ActiveX/JS 设计器接口。
|
||||
|
||||
因此本页提供两条**可用**的设计路径,并在同一页面里放了本系统的「保存模板」按钮:
|
||||
|
||||
| 路径 | 排版能力 | 产物格式 | 本页能力 |
|
||||
|---|---|---|---|
|
||||
| A. 页面内可视化画布(自研) | 中(页眉/标题/参数行/明细列/页脚/边距/列宽) | JSON(`6.8.5.0`) | 直接编辑 + 保存 |
|
||||
| B. 唤起本机锐浪 7 设计器 | 强(官方全功能:分组/合计/图表/条码/套打) | `_WR7_` 压缩文本 | 关闭设计器自动回传,本页自动重载 |
|
||||
|
||||
> 关键取舍:用官方设计器保存后模板变成 `_WR7_` 压缩文本,**网页画布无法再解析**(只能看真实预览、原样保存、继续用设计器)。页面在唤起设计器前会明确提示这一点。
|
||||
|
||||
---
|
||||
|
||||
## 二、页面
|
||||
|
||||
### 2.1 路由(静态注册,不依赖菜单权限)
|
||||
|
||||
`web/src/router/index.js`:
|
||||
|
||||
```js
|
||||
{ path: 'report/design/:id', name: 'ReportDesign',
|
||||
component: () => import('../views/report/design/index.vue'),
|
||||
meta: { title: '报表设计', icon: 'Edit' } }
|
||||
```
|
||||
|
||||
入口:
|
||||
- 报表管理页列表行「设计」
|
||||
- 报表编辑器顶栏「设计页」
|
||||
|
||||
新增文件:`web/src/views/report/design/index.vue`。
|
||||
|
||||
### 2.2 页面结构
|
||||
|
||||
```
|
||||
┌ 返回管理 | 报表名 编号 [JSON/_WR7_] [未保存] 撤销 重新载入 真实预览 上传 下载 [锐浪7设计器] [保存模板] ┐
|
||||
├ 左(画布,自适应缩放 + 10% 档位缩放) │ 右(属性面板) │
|
||||
│ A4 白纸:页眉 → 报表头(标题/参数行)→ 明细网格 → 页脚 │ 明细列 / 报表标题 / │
|
||||
│ 表头单元格可点选,右边缘拖拽改列宽(cm 实时写回模板) │ 页眉页脚 / 行高边距 │
|
||||
└────────────────────────────────────────────────────────────────────┴──────────────────────────┘
|
||||
```
|
||||
|
||||
### 2.3 画布渲染(只读近似 + 真实数据)
|
||||
|
||||
- 单位:模板里的 `Left/Top/Width/Height` 是 **cm**,`Font.Size` 是 **1/10000 pt**;画布按 `1cm = 37.795px` 换算,再用 `transform: scale()` 适配容器
|
||||
- 版块顺序与 GR 一致:`PageHeader` → `ReportHeader[]`(逐节)→ `DetailGrid`(表头 + 数据行)→ `PageFooter`
|
||||
- 文本替换:
|
||||
- `[#{报表参数}#]` → `/sys/report/{id}/preview` 返回的 `Parameter[0]` 实际值
|
||||
- `[#SystemVar(PageNumber)#] / PageCount / CurrentDateTime` → 示例值
|
||||
- 明细行取 `preview.data.recordset` 前 8 行;无数据时渲染 3 行空行占位,保证排版可见
|
||||
- 画布上的控件 hover 有虚线框,方便确认位置;真正的字体/边距是否精确以「真实预览」为准
|
||||
|
||||
### 2.4 可编辑项(写回模板 JSON)
|
||||
|
||||
| 面板 | 修改的节点 | 说明 |
|
||||
|---|---|---|
|
||||
| 明细列 · 列标题 | `DetailGrid.ColumnTitle.ColumnTitleCell[].Text` | 缺节点时自动补 |
|
||||
| 明细列 · 列宽 | `DetailGrid.Column[].Width` | 数字框或画布拖拽,0.4–14cm |
|
||||
| 明细列 · 添加/删除 | `Recordset.Field` / `Column` / `ColumnContentCell` / `ColumnTitleCell` | 四处同步增删,避免结构不一致 |
|
||||
| 报表标题 | 报表头第一节里字号最大的文本控件 `Text` + 顶层 `Title` | 找不到控件时给出提示 |
|
||||
| 标题字号 | 该控件 `Font.Size` | 6–36pt |
|
||||
| 页眉/页脚 | `PageHeader/PageFooter.Control[].Text` | 逐控件文本框 |
|
||||
| 页眉/页脚高度 | `PageHeader/PageFooter.Height` | 0–10cm |
|
||||
| 行高 / 表头高 | `ColumnContent.Height` / `ColumnTitle.Height` | 0.3–4cm |
|
||||
| 上下边距 | `Printer.TopMargin / BottomMargin` | 0–6cm |
|
||||
|
||||
**只改已知且我们已在用、能被 GR 正确识别的键**,不写未知属性(例如单元格对齐、合并、边框),避免模板被 GR 判为损坏。分组、合计、图表、条码等请用官方设计器。
|
||||
|
||||
### 2.5 保存
|
||||
|
||||
复用工作进度-8 新增的接口,不改后端:
|
||||
|
||||
```
|
||||
PUT /api/sys/report/{id}/template/content { "content": "<JSON 文本>" }
|
||||
```
|
||||
|
||||
- 保存前序列化 `JSON.stringify(doc, null, '\t')`;服务端做 JSON 语法校验后落库
|
||||
- 有未保存修改时顶栏显示红色「未保存」,离开页面会二次确认
|
||||
- `撤销修改` = 重新解析已入库文本
|
||||
|
||||
### 2.6 真实预览 / 上传下载 / 官方设计器
|
||||
|
||||
- **真实预览**:全屏弹窗内用官方 `rubylong.grhtml5.insertReportViewer` 渲染(模板 URL 带 `_t` 时间戳防缓存);有未保存修改时提示「预览的是已保存模板」
|
||||
- **上传 .grf / 下载 .grf**:复用既有接口
|
||||
- **锐浪 7 设计器**:
|
||||
```
|
||||
f9designer://open?base=<origin>&rid=<id>&token=<jwt>&code=<报表编号>
|
||||
```
|
||||
由 `tools/report-designer/F9ReportDesignerHandler.ps1` 处理:下载模板 → 启动 `gr7designer64.exe` → 关闭后自动 `POST /{id}/template` 回传
|
||||
本页在 `window.focus` 后延迟 2s 自动重新载入模板(也有「重新载入」按钮兜底)
|
||||
|
||||
### 2.7 `_WR7_` / 非 JSON 模板兜底
|
||||
|
||||
- 检测到 `_WR` 前缀:不渲染画布,显示说明和三个入口(真实预览 / 用设计器修改 / 去模板源码页)
|
||||
- JSON 解析失败:显示错误 + 引导上传或去源码页修复
|
||||
|
||||
---
|
||||
|
||||
## 三、验证
|
||||
|
||||
| 项 | 结果 |
|
||||
|---|---|
|
||||
| Vite 编译 `views/report/design/index.vue`(请求 `http://localhost:5173/src/views/report/design/index.vue`) | ✅ HTTP 200(SFC 编译通过) |
|
||||
| 同法验证 `ReportEditor.vue` / `manage/index.vue` / `router/index.js` | ✅ HTTP 200 |
|
||||
| IDE 诊断(4 个改动文件) | ✅ 0 |
|
||||
| 后端 | 未改动(复用 `PUT /template/content`),无需重新编译 |
|
||||
| 浏览器实操(登录点一遍) | ⚠ 未执行:当前环境无 node / playwright-cli,无法自动化;Vite 已确认可编译,建议人工点一遍 |
|
||||
|
||||
> 保存模板需要后端已重启(工作进度-8 的接口)。
|
||||
|
||||
---
|
||||
|
||||
## 四、遗留
|
||||
|
||||
1. **`_WR7_` 无解码器**:用过官方设计器后画布失效,这是格式决定的;若要两者兼得,需要锐浪的 `decodeWR` 算法或官方提供 JSON 导出。
|
||||
2. **画布不是 100% 像素级还原**:字体度量、单元格对齐、自动行高与 GR 有差异,最终以「真实预览」为准。
|
||||
3. **未支持控件级拖拽/属性编辑**:位置 `Left/Top`、边框、颜色、对齐暂不开放,避免写入未经验证的键。
|
||||
4. **样本数据依赖 `/preview`**:需要参数才能出数的报表会显示空行占位;后续可加「手工填参数再取样本」。
|
||||
5. **无模板版本历史**:画布改坏后只能重新生成模板或用设计器重排。
|
||||
Reference in New Issue
Block a user