Files
F9Web/AI开发日志/2026-08-15.md
T
fanhongcai 1bec470647 feat: 新增IM即时通讯(浮窗)、工作流、打印模块及工作台增强
- IM: 新增浮窗聊天(ImFloatWindow)、管理页(monitor/config/service/message)、SSE推送
- 工作流: 新增待办/我的流程页面及后端服务
- 打印: 新增打印模板、出库单打印(PrintPage)、模板种子脚本
- 工作台: 增强快捷入口与工作台数据
- 修复: TagsView页签关闭、CrudPage通用表格增强
- 移除导航菜单中的即时通讯入口,改为右下角浮窗
2026-08-16 00:19:24 +08:00

202 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# F9 智慧缫丝系统 —— CodeBuddy 持续工作存档
> 本文件为 CodeBuddy 开发日志归档(每日一份),归档日期:2026-08-15。
> 新对话开始后:先读本文件 + `功能清单` + `项目说明`,按「任务状态」继续推进,无需重新调研。
最后更新:2026-08-15
---
## 一、任务状态总览(按用户约定的优先级 1→2→3)
| 序号 | 任务 | 状态 | 说明 |
|---|---|---|---|
| 1 | 通用打印接入主前端 | ✅ **v1 完成(三等分原料出库单可打印)** | 后端取数 + openprint `/print` 外部页 + web 打印按钮 + 三等分模板入库;联调收尾:已造测试数据(庄口 id=3 / 出库单 id=2),打印数据端点返回完整 |
| 2 | 简易工作流(采购/领料审批闭环) | ✅ **v1 完成(原料出库审批闭环)** | WorkflowService + API7 端点)+ 待办中心/我发起的页面 + 原料出库单「审批」按钮;流程定义/菜单已种子入库 |
| 3 | 专业业务逻辑(自动定级/能耗分摊/成本盈亏/断料预警) | ⏳ 待做 | 依赖任务 1/2 部分能力 |
| 4 | 轻量 IM(内部互发 + 系统通知) | ✅ **v1 完成** | ImService + API6 端点)+ IM 聊天页 + 顶栏消息铃铛 + 演示通知种子 |
已完成的里程碑:
- ✅ 动态 CRUD 体系(后端 EntityCatalog + CrudService + DataController,前端 CrudPage + table-map 配置驱动)
- ✅ openprint 打印设计器全链路(模板设计→数据源内省→预览/打印,推 PDF 到本地客户端)
- ✅ 工作台接真实数据(WorkBenchController + workbench/index.vue 重写)
---
## 二、项目架构速查
### 2.1 三端结构
| 端 | 目录 | 技术栈 | 端口 | 说明 |
|---|---|---|---|---|
| PC 前端 | `web/` | Vue3 + Element Plus + ECharts + Pinia | vite dev 5173 | 代理 `/api` → localhost:5136 |
| 后端 | `server/` | .NET 10 + FreeSql (v3.5.311) | 5136 | MySQL 云库 `f9web`116.198.221.105 |
| 打印设计器 | `openprint/` | Vue3 + Naive UI + Vite | 5227 | 对接 F9 的 `PrintApiController`,推 PDF(base64) 到本地客户端 `127.0.0.1:18888` |
| 电视大屏 / 小程序 | — | — | — | 未开始(按项目说明先完成 PC 端) |
### 2.2 后端关键约定
- **表命名**:模块前缀 + 下划线,如 `CRM_Customer``RawMaterial_Instock``Common_Workflow``RepCenter_ReportDesign`;实体类同前缀,如 `RawMaterial_Instock`
- **默认字段**`id``flag`0=删除,>0 有效,<0 无效,-1=作废)、`addtime``adder``updatetime``updater``memo`
- **序列化**`Program.cs` 全局 CamelCase + 忽略 null(即 C# 属性 `OrderNo` → JSON `orderNo`)。
- **响应信封**:普通 API 用 `ApiResult`**`PrintApiController``api/print`)返回裸 JSON** `{items,total}`openprint 的 http-client 已按此解析,主前端 axios 也可透传)。
- **动态查询**`DataController``EntityCatalog`(表名→实体 Type 反射映射)+ `CrudService<T>` 泛型反射调用,`GetList/GetById/Add/Update/Delete` 等通用接口。
- **工作流表**(已有,未做服务):`Common_Workflow`(流程定义)/ `Common_WorkflowNode`(节点)/ `Common_WorkflowInstance`(实例)/ `Common_WorkflowTask`(任务,`Status==0` 待办、`UserId` 当前处理人)。
### 2.3 web 前端关键约定
- **Token 存储**localStorage key = **`f9mes_token`**`web/src/stores/user.js``TOKEN_KEY`)。
- **API 封装**`web/src/api/request.js`axios 拦截器注入 Bearer);`web/src/api/index.js` 集中导出(含已新增的 `getWorkBenchSummary`)。
- **配置驱动 CRUD**`web/src/config/table-map.js` 定义每张表的列/表单配置,`web/src/components/CrudPage.vue` 是通用页面组件,菜单由 `InitDataService` 生成。
### 2.4 openprint 关键约定
- **对接开关**`openprint/src/main.ts` 仅当配置 `VITE_OPENPRINT_API_BASE` 时才切换云端仓库(HttpRepository/HttpDataSource);`.env` 中配 `VITE_OPENPRINT_API_BASE` + `VITE_OPENPRINT_API_TOKEN`
- **HttpClient token**:构造时静态传入(`http-client.ts``HttpOptions.token`),**构造后不可变**;若改运行时 token 需改构造时机或加 setter。
- **绑定 Path 约定**:后端 `PrintService.BuildFields` 生成 `Path = "{TableId}.{PropName}"`(如 `RawMaterial_Instock.OrderNo`);openprint 的 `expression.ts`/`data-binder.ts` 按路径取值。**注意**:序列化后 JSON 为 camelCase,而 Path 是 PascalCase,取值器需兼容(大小写不敏感取 key 或按 camelCase 注入)。
- **打印出口**`PrintDialog.vue``doPrint()``store.buildTemplate()` + `buildPreviewData(dsStore.activeFields)``buildPrintPayload({template, data})` → 推本地客户端(POST `/print``127.0.0.1:18888`)。**当前用假数据**,是接入真实数据的关键改造点。
- **数据源内省**`PrintApiController``data-sources`/`fields``PrintService` 按实体类前缀分组(ModuleNames 字典),字段缓存 TTL 10 分钟。
---
## 三、任务 1:通用打印接入主前端(进行中)
### 3.1 目标
表单通用打印:主前端任意 CRUD 列表 → 选中一行 → 选打印模板 → 打开 openprint 用**该行真实数据**打印(针式/A4 均支持,设计器已具备)。
### 3.2 已实施的三步方案(2026-08-15 落地 ✅,v1 三等分原料出库单)
**Step A — 后端:取行数据接口(已完成)**
- 位置:`PrintApiController.cs`,路由 `GET api/print/data/{table}/{id}`
- 实现:`EntityCatalog` 动态取实体 Type → 反射 `CrudService<T>.GetByIdAsync`(复用 DataController 模式,`ActivatorUtilities.CreateInstance`)→ 输出 `{ "<TableId>": { camelCase 字段 } }` 裸 JSON(信封键=表名,与 BuildFields 的 Path 前缀一致)。
- 增强:① 关联字段补全 `RefMap``*Id``{Xxx}Code`/`{Xxx}Name`,如 ZhuangkouCode、OutOrgName);② 枚举文本注入 `EnumMaps`(如 OutType → OutTypeText 领料/退货/报损)。
- 注意:openprint 侧会为行对象自动补 PascalCase 别名(`withPascalAliases`),因此模板绑定 `RawMaterial_OutStock.BillNo``billNo` 均可取到值。
**Step B — openprint:外部打印模式(已完成)**
- 新增 `openprint/src/print/PrintPage.vue`(独立轻量打印页,不进入设计器),`main.ts` 检测 `?print=1` 时改挂载该页。
- URL 参数:`?print=1&template=<模板id>&table=<表名>&row=<行id>&token=<JWT>`;可选 `api`API 根地址,缺省 `VITE_OPENPRINT_API_BASE`);可选 `data`(base64url 直传数据,绕过数据请求)。
- 流程:解析参数 → `createHttpRepository({baseUrl, token})` → 拉数据(`fetch /api/print/data/{table}/{row}` 带 Bearer,或解码 `data`)→ `withPascalAliases` 归一化 → `createHeadless({repository}).buildRequest(tplId, data)``headless.print()`(浏览器打印对话框,可用三等分纸型)。
- 页面状态机:加载中/打印中/完成/失败 +「重新打印」「关闭」按钮;缺参/HTTP 错误均给出中文提示。
**Step C — webCrudPage 打印按钮(已完成)**
- 新增 `web/src/config/print.js``OPENPRINT_URL`env `VITE_OPENPRINT_URL`,默认 `http://localhost:5227`)、`PRINT_API_BASE`(默认 `http://localhost:5136`)、`buildPrintUrl()`
- `web/src/config/table-map.js``PRINT_MAP`component → `{templateName, title}`+ `resolvePrint()`;当前配置 `RawMaterial/outstock` → 模板名「三等分原料出库单」。
- `web/src/router/index.js`:路由 meta 注入 `printTemplate: resolvePrint(component)`
- `CrudPage.vue`:操作列按 `printTemplate` 条件渲染「打印」按钮 → `GET /print/templates` 按模板名找到 id → `window.open(buildPrintUrl({template, table, row, token}))`。操作列宽度 `opWidth` 随按钮动态计算。
- 模板匹配用**名称**而非硬编码 id(数据库重建也不失效);多模板扩展时改 `PRINT_MAP` 即可。
**三等分模板(已完成)**
- `server/scripts/print-template-outstock.json`:210×99mmA4 三等分)竖版 portrait、边距 8mm;标题/单号/庄口/领料车间/出库重量(kg)/出库日期/出库类型/领用人+仓管员签章区。
- `server/scripts/seed-print-template.mjs`:登录→查重→POST `/api/print/templates` 创建(node 执行,避免 PowerShell 中文编码坑)。
- 已入库:模板 id=2(Name=三等分原料出库单,PaperSize=三等分)。表 `RawMaterial_OutStock` 当前无数据,录入后可验证完整打印。
### 3.3 关键文件清单
- 后端:`server/src/F9MES.Api/Controllers/PrintApiController.cs``server/src/F9MES.Application/Print/PrintService.cs``server/src/F9MES.Api/Common/EntityCatalog.cs``server/src/F9MES.Api/Controllers/DataController.cs`
- openprint`src/main.ts``src/App.vue``src/config/backend.ts``src/repository/http-client.ts`token 构造点)、`src/design/stores/designer.ts`loadTemplate)、`src/design/stores/dataSource.ts``src/design/modals/PrintDialog.vue`doPrint)、`src/design/toolbar/TopToolbar.vue`showPrint 挂载)、`src/design/preview/preview-data.ts`buildPreviewData)、`src/core/layout-engine/expression.ts` + `data-binder.ts`(取值)
- web`src/components/CrudPage.vue``src/api/index.js``src/config/table-map.js``src/stores/user.js`token
---
## 四、任务 2:简易工作流(v1 完成 ✅,原料出库审批闭环)
### 4.1 已实施(2026-08-15 落地)
- **后端**
- `Common_WorkflowInstance` 实体新增 `BizId`(关联业务记录 ID,跳转业务详情用)。
- 新增 `server/src/F9MES.Application/Workflow/WorkflowService.cs` + `WorkflowDtos.cs``server/src/F9MES.Api/Controllers/WorkflowController.cs`DI 注册于 Program.cs)。
- API`api/workflow/*`,均需 JWT):
- `GET definitions?bizType=` 已发布流程定义(含节点)
- `POST start` 发起流程(创建实例 + 首个审批任务,审批人取节点 ApproverJson 数组首个用户,缺省当前操作人)
- `GET todos?page=&size=` 我的待办
- `POST approve` 同意(推进下一审批节点,无则实例通过 Status=1)
- `POST reject` 驳回(实例终止 Status=2
- `GET instances?page=&size=&status=` 我发起的实例
- `GET instance/{id}` 实例详情(含任务轨迹)
- **前端**
- `web/src/api/workflow.js`7 个方法封装)。
- `web/src/views/workflow/todo.vue` 待办中心(同意/驳回带意见、详情抽屉轨迹时间线);`my.vue` 我发起的(状态筛选、详情轨迹)。
- `table-map.js``SPECIAL_PAGES``WorkFlow/todo``WorkFlow/my`;新增 `WORKFLOW_MAP`component → bizType,当前 `RawMaterial/outstock``RawMaterial_OutStock`+ `resolveWorkflow()`
- `router/index.js`:静态路由 `/workflow/todo``/workflow/my`;动态路由 meta 注入 `workflow: resolveWorkflow(component)`
- `CrudPage.vue`:操作列按 `workflowCfg` 条件渲染「审批」按钮 → 弹窗选流程定义 → 发起(opWidth 动态加宽)。
- **种子**`server/scripts/seed-workflow.mjs` 创建「原料出库审批」流程(Code=RM_OUT_APPROVE,节点:发起→车间主管审批[ApproverJson=["1"]]→结束)+ 侧边栏菜单(工作流目录→待办中心/我发起的)。
- **验证**:编译通过(0 错误);端到端 API 闭环通过——发起(instanceId 正常回填)→ 待办(任务归属当前用户)→ 同意(实例 Status=1)→ 驳回(Status=2);菜单树已含待办中心/我发起的;web 全部模块编译 200。
### 4.2 关键坑
- **FreeSql 自增回填**`Insert(entity).ExecuteAffrowsAsync()` **不回填主键**(返回 0),必须用 `ExecuteIdentityAsync()` 并手动赋值(CrudService.AddAsync 已是此写法)。
### 4.3 扩展方式
- 新表单接审批:`WORKFLOW_MAP` 加一行 + 后端种子加流程定义(复用 `seed-workflow.mjs` 模式);审批人可在节点 `ApproverJson` 配置用户ID数组。
- v1 简化:单审批人串行流转;同一节点多审批人(会签/或签)、条件分支、撤回、流程配置 UI 留待后续。
---
## 五、任务 3:专业业务逻辑(待做)
按优先级从高到低建议:
1. **断料预警**(原料库存超期/不足,工作台预警列表已预留位置)
2. **自动定级**(Lims 检验数据 → 调用定级标准自动判定生丝等级,标准表需先建/已有则复用)
3. **能耗分摊**(Enms 抄表 → 按投料量/产量/机台时长分摊到庄口 → 推成本)
4. **成本盈亏**(BizBoss 生产成本分摊 + 分庄口盈亏统计)
---
## 六、工程实践与坑位(必读)
1. **编译锁定**:后端进程运行时 DLL 被锁,`dotnet build` 会报 MSB3027/MSB3021 → 用 `dotnet build -o build_tmp` 验证编译,完成后删临时目录;**改动后端必须重启服务才生效**。
2. **FreeSql 陷阱**:非空 DateTime 没有 `.HasValue`;空集合时 `.Where(x => ids.Contains(...))` 会生成非法 SQL,需先 `if (list.Count > 0)` 守卫。
3. **JSON 大小写**:后端全局 camelCaseopenprint 绑定 Path 是 PascalCase,取值需兼容。
4. **openprint 是独立应用**:改动它用 `npm run dev`(端口 5227)独立调试;主前端 web 是 vite dev5173)。
5. 数据库连接(项目说明):MySQL `116.198.221.105``f9web` 用户 `f9web`;阿里云 OSSbbit-f9-web);天气用 Open-Meteo。
6. 项目要求:类/文件命名带模块前缀、各模块类放各自文件夹、公共类放 Common;界面 MES 风格、高可配置、表单必须支持打印、表单带简易工作流。
7. **PowerShell 中文编码坑**PS 5.1 按 GBK 读 UTF-8 无 BOM 文件(脚本/文件名乱码)、`Invoke-RestMethod` 发中文 body 易 400GBK 发送 vs UTF-8 解析)。对策:① 涉及中文的脚本用 **node(.mjs)** 写(原生 UTF-8);② 文件/命令行尽量用 ASCII 名。
8. **启动服务**:后端中文路径下 `Start-Process dotnet` 传中文路径会损坏 → 用 `cmd /c "cd /d <路径> && dotnet run ..."` 或先取 8.3 短路径;API 冷启动要等 `SyncStructure` 全表同步(114 张远程表,约 1-2 分钟)后才监听端口。
---
## 七、本轮对话已完成的工作(2026-08 中旬)
### 7.1 工作台真实数据(已完成 ✅)
- 新增 `server/src/F9MES.Api/Controllers/WorkBenchController.cs``GET api/workbench/summary`,聚合统计卡(今日产丝=ProHou_Daily.OutputWeight 合计、在线机组=ProQian_Machine 启用且运行、待办=工单+工作流待办、预警数)+ 近 30 天趋势 + 庄口占比 + 待办列表(工单/审批)+ 预警列表。
- `web/src/api/index.js`:新增 `getWorkBenchSummary`
- `web/src/views/workbench/index.vue`:重写,4 统计卡/趋势折线/庄口占比饼图/待办与预警列表全部接真实数据,无数据时占位,保留天气+快捷入口。
- 已用 `-o build_tmp` 验证编译通过;**需重启后端生效**。
### 7.2 openprint 全链路调研(已完成,结论见第三节)
已通读:PrintApiController / PrintService / EntityCatalog / DataController / http-repo / http-datasource / http-client / backend.ts / main.ts / App.vue / TopToolbar / PrintDialog / designer store / dataSource store / preview-data / expression / data-binder / CrudPage / table-map / request.js / user store / 两端 vite 配置。结论已沉淀到第三节。
### 7.3 通用打印接入 v1(已完成 ✅,三等分原料出库单)
- 后端:`GET api/print/data/{table}/{id}`(关联名称 + 枚举文本补全,裸 JSON 信封),CORS 增加 `localhost:5227`
- openprint`/print` 外部打印页(PrintPage.vue + main.ts 挂载逻辑),headless 渲染 + 浏览器打印。
- web:打印按钮(CrudPage + PRINT_MAP + router meta + print.js 配置)。
- 模板:210×99mm 三等分原料出库单已入库(id=2);种子脚本 `server/scripts/seed-print-template.mjs`node)。
- 验证:API/模板/数据端点 404 行为/两端模块编译均通过。
- **联调收尾**:已造测试数据——庄口 id=3YLZ20260815-001 四川凉山-2026春茧)、出库单 id=2CK-20260815-001125.5kg 领料/已出库);`GET api/print/data/RawMaterial_OutStock/2` 返回完整(含 ZhuangkouName、OutTypeText=领料);打印页 URL 已验证(选三等分纸型即可打印)。脚本 `server/scripts/seed-test-outstock.mjs`
### 7.4 简易工作流 v1(已完成 ✅,原料出库审批闭环)
- 后端:WorkflowService + WorkflowController7 端点,`api/workflow/*`),实体加 BizIdDI 注册。
- 前端:workflow.js / todo.vue / my.vue / WORKFLOW_MAP / 静态路由 / CrudPage「审批」按钮。
- 种子:`server/scripts/seed-workflow.mjs`(原料出库审批流程 + 工作流菜单)。
- 验证:编译 0 错误;API 闭环(发起→待办→同意 Status=1;驳回 Status=2);菜单树含待办中心/我发起的;web 模块编译 200。
- 坑:FreeSql `ExecuteAffrowsAsync` 不回填自增主键,须用 `ExecuteIdentityAsync`
### 7.5 轻量 IM v1(已完成 ✅,内部互发 + 系统通知)
- **数据**:复用 `Common_Message` 表,扩展 `SenderId`/`SenderName`(发送人,0=系统),`MsgType` 增加 `4=单聊消息`(原 0=系统 1=业务 2=审批 3=预警)。
- **后端**`server/src/F9MES.Application/Im/ImService.cs` + `ImDtos.cs``server/src/F9MES.Api/Controllers/ImController.cs`DI 注册于 Program.cs)。
- API`api/im/*`,均需 JWT):`GET sessions` 会话列表(按对端聚合最后消息+未读数)、`GET messages?peerId=&page=&size=` 聊天记录(双方)、`POST send` 发送单聊({peerId, content})、`POST read` 标记已读({peerId})、`GET unread-count` 未读总数(顶栏角标)、`POST notify` 系统/业务通知群发({userIds?, msgType, title, content},空 userIds=全部启用用户)。
- **前端**
- `web/src/api/im.js`6 方法)。
- `web/src/views/im/index.vue` 聊天中心(左侧会话列表:头像/最后消息/时间/未读角标,右上 + 发起新会话选用户;右侧消息气泡+Enter 发送;5s 轮询)。
- `layout/index.vue` 顶栏新增消息铃铛(未读角标,30s 轮询,点击跳 `/im`)。
- `table-map.js``SPECIAL_PAGES``Im/index``router/index.js` 加静态路由 `/im`
- **种子**`server/scripts/seed-im.mjs` 创建「协作」目录 →「即时通讯」菜单 + 3 条演示通知(工单变更/工艺单变更/审批通知)。
- **验证**:编译 0 错误;API 闭环通过——管理员↔车间主管李工(测试用户 id=2)互发、未读计数、已读清零、会话聚合、聊天记录含 senderName;菜单树含「即时通讯/待办中心/我发起的」;web 模块编译 200;前端 lint 0 错误。
- **坑(重要)**`InitDataService.InitMenusAsync` 有开发期修复——**只要存在 `MenuType==1 && ParentId==0`(顶层页面节点)就物理删除全部菜单重建**。种子脚本新增菜单**必须用目录节点(MenuType=0, ParentId=0+ 页面子节点(MenuType=1, ParentId=目录id)**结构;否则会连带删掉工作流等全部自定义菜单(本次已踩坑并修复)。
---
## 八、下一步行动(新对话从这里开始)
1. 先读本文件 + `功能清单` + `项目说明`(若工作区未加载)。
2. **人工验证**
- IM:登录 web → 顶栏消息铃铛(未读角标)→ 侧边栏「协作 → 即时通讯」聊天(管理员 ↔ 车间主管李工 13900000001/123456);演示通知已发到管理员账号。
- 工作流:待办中心/我发起的列表 + 原料出库单「审批」按钮(测试数据出库单 id=2,流程 RM_OUT_APPROVE)。
3. **多表单扩展**a) 打印:`PRINT_MAP` 加 component → `{templateName}` + 设计模板;b) 审批:`WORKFLOW_MAP` 加 component → bizType + 种子脚本加流程定义;c) 通知:业务代码调 `POST api/im/notify`(工单/工艺单变更处已留接口)。
4. 后续推进任务 3(专业业务逻辑:断料预警 → 自动定级 → 能耗分摊 → 成本盈亏)。
5. 任何大改动前更新本文件「最后更新」日期与任务状态。
6. **新增菜单铁律**:种子脚本建菜单必须「目录节点(MenuType=0+ 页面子节点(MenuType=1)」结构,禁止顶层页面节点(MenuType=1, ParentId=0),否则 `InitDataService` 孤儿修复会物理删除全部菜单重建。