Files
workspace/code/fms/.workbuddy-ai/memory/MEMORY.md
T
2026-09-23 16:54:54 +08:00

14 KiB
Raw Blame History

FMS 项目长期约定

目录 / 文档 / 库

  • 后端 fms-api(Spring Boot + JDK21,JDBC+Druid 无 ORM);前端 fms-vue(Vue 3.5 + Vite 8 + Pinia,纯 JS)。 fms-vue-old1/old2 仅供历史参考,不要改。
  • 根目录设计文档:FMS新系统核心表结构设计.md(权威表结构)、FMS业务表设计.md、开发规范.md(AI 规范)、 Fms旧系统表结构.md(旧库 G3HY2025,只有表无视图)、FMS删除规则引擎设计.md。
  • 建库/模块注册脚本在 sql/(fms_core.sql、fms_business_*.sql、fms_module_*.sql)。
  • SQL Server 118.89.70.199:1433:旧库 G3HY2025,新库 FMS(新系统一律用 FMS,生产库,写前先确认)。
  • 机构→库映射在 fms-api/config/dbconfigs/{ORG_ID}.properties(gitignore):G3HD.properties → FMS, G3HY2025.properties → G3HY2025。没有 FMS.properties。
  • 后端 8088(context-path /api),前端 dev 5082(/api → 127.0.0.1:8088)。直连接口需先 /auth/login。

元数据核心表(真实列名,已连库核对,别再猜)

  • s_module:PK b_id(无 b_module_code;无 b_route,那是 s_menu 的);本身是树: b_parent_id/b_depth/b_path;另有 b_module_type(module/data/virtual)、b_view_table、b_save_table、 b_key_field、b_order_sql、b_query_sql、b_config_json、b_canuse、b_xh、b_bz、b_name、b_i18n。
  • s_field:PK (b_module_id, b_field),无 b_id;b_type 实测 input/number/checkbox/datetime/date。
  • s_module_schema:PK (b_module_id, b_schema_type∈view|edit|query),b_schema_json。
  • s_menu:b_id/b_parent_id/b_depth/b_path/b_menu_type(directory|page)/b_route/b_icon;s_menu_module 绑菜单与模块。
  • 权限靠 b_id:permissionStore 的 moduleCodes 即 s_module.b_id,页面 MODULE_CODE 必须等于它; 还要在 s_menu_module 里绑菜单,否则非超管看不到授权入口。
  • 列表模块编码统一 v_+表名(v_b_othercompany),b_view_table→视图,b_save_table→真实表。
  • schema JSON 形状:view{columns:[{field,width}|{type:group,title,children}]}、 edit{children:[{field,span,required,readonly,defaultValue}|group]}、query{groups:[{conditions}],quick}。
  • 路由守卫只看 meta.moduleId/meta.adminOnly;views/module/** 是 adminOnly(仅 g3soft)。

树结构规范(模块/菜单/部门/分类树通用)

b_parent_id 是权威关系,b_depth(根=0)、b_path(/cn/east/)是派生字段,移动节点要同事务更新全部后代; 不一致时以 b_parent_id 为准。工具在 src/utils/tree.js(buildSortedTree/computeTreeMove/collectDescendantIds/ applyTreePosition/filterTreeByText/findTreeNode),展示层用 components/fms-tree/FmsTree.vue(自管虚拟根)。

后端通用接口(前端业务数据只走这几个)

  • POST /auth/login:字段是小写 orgid/userid/password;orgid=G3HD。
  • /data/loaddata:view_name/search_condition/可选 order_by/search_columns(真 JSON 数组)。
  • /data/page:order_by 必填(DbUtils.buildPageOrderBy 会报「order_by 不能为空」), 分页参数名是 page_no/page_size(不是 pageindex/pagesize)。
  • /data/saveobjt:body 是数组,每项 {table, key_field, deletes, updates, inserts},key_field 必填 (复合主键逗号分隔),顺序 delete→update→insert。
  • 模块元数据不由接口下发,前端读本地 schema JSON,改元数据无需重启后端。
  • DbUtils 是 SQL 唯一出口:标识符加 [],validateReadOnlySql() 只允许查询。

工作流流程设置器(fms-vue/src/views/system/workflow)

  • 代码现状 = 原始版本(2026-09-23 曾被重构,应用户要求已全部回滚): 草稿是 schemaVersion: 1({definitionId, name, nodes, edges}),编辑器是 WorkflowDesigner + WorkflowCanvas(LogicFlow) + WorkflowPropertyPanel 三件套, 节点坐标挂在 node.x/y,条件存单个叶子 {field, operator, value}, 写入通道未实现(工具栏只有撤销/缩放/查看草稿 JSON)。 不要以为仓库里已经有卡片模式/流程树/条件构造器 —— 那些都被回滚了。 回滚前的完整实现留在 D:\workspace\code\fms-workflow-refactor-backup-20260923\(仓库外,含 http.js 的 get/put、 tests/unit/workflow-domain.spec.js、改过 AST 形状的 mock SQL),需要时可取回。
  • 文档是目标,不是现状:FMS审批流程设置器交互设计.md(旧名 FMS审批流程交互重构设计.md, 已整体重写为 258 行)要求把设计器改成钉钉式递归流程树,并明确作废"线性卡片 + LogicFlow 高级模式" 双编辑器方案(§3.2 不再用画布兜底,并行/服务节点等拓扑只读保护); FMS审批流程设置器二次重构实施文档.md 给出增量执行顺序(§1.1 的交互决策已锁定,不必再问); FMS工作流与审批设计.md 管表结构与运行语义(其 §19 的画布选型是旧方案)。 参考交互项目:D:\workspace\code\test\Workflow-Vue3(只借交互,不复制代码/Element UI/私有字体)。
  • 这些文件未进 git:design.vue、designer/(除 index.vue 外全部)、sql/fms_workflow_mock.sql 都是未跟踪文件 —— 改它们之前先备份,git restore 救不回来(本地历史里也没有)。
  • workflow/index.vue 要小心:它虽然被 git 跟踪,但工作区版本比 HEAD 新(新增按钮、表格「设计」行操作 都是未提交改动)。直接 git restore 会退回 HEAD 的 18 行空壳,把按钮冲掉 —— 要恢复就用带按钮的 76 行版本。
  • 表结构已建好:sql/fms_workflow.sql(13 张表)、fms_workflow_module.sql(视图 + s_module 注册)、 fms_workflow_menu.sql(菜单 workflow / approval*)、fms_workflow_mock.sql(expense_approval 样本, 覆盖 7 种节点类型 / 6 种审批方式 / 5 种审批人解析)。
  • 后端 fms-api/src/main 目前零工作流代码:保存/校验/模拟/发布都要走流程服务,接口 404 时必须 明确提示"服务未接入",绝不伪造成功。
  • 若将来重新做树形重构,踩过的坑:① b_priority 是同一来源节点内的判断顺序,不是全表序号; ② 校验只约束出边,入边不能锁成 1(分支汇合点天生多条入线);③ 环要单独拦,否则递归不终止; ④ 默认出口(condition: null)必须唯一且固定最后。

画布「一键整理」(dagre 自动布局,2026-09-23 已落地)

  • 依赖 @logicflow/layout@2.1.5(peer 正好是 @logicflow/core ^2.2.5)。注意 dagre 不在 @logicflow/extension 里(那只有一个标注"未完善"的 AutoLayout)。
  • 必须深路径导入 import { Dagre } from '@logicflow/layout/es/dagre':包入口同时导出 elkLayout, 从入口引会把 elkjs 打进产物(实测走深路径只 +92 kB,走入口会 +1MB 以上)。包没有 exports 字段,深路径可用。
  • 注册方式:new LogicFlow({ plugins: [Dagre] }),调用 lf.extension.dagre.layout({...})。 插件实例由 installPlugin 的"非静态插件"分支创建(Dagre 只有 static pluginName + render,没有 install)。
  • 坑:插件靠 ToolOverlay 的 componentDidMount → triggerToolRender 调用 render(lf) 才拿到 this.lf;拿不到时 layout() 会直接抛错(它的 try/catch 只包住 applyDagreLayout)。 所以 autoLayout() 里先兜底 if (!dagre.lf) dagre.lf = lf,并用 try/catch + 「坐标是否变化」判断结果 —— 插件失败时只打 console.error,不能假定成功。
  • 布局走 lf.renderRawData() 整图重渲染:会丢选中态,但节点/连线 id 保留(processEdges 只改路径字段), 所以上层按 nodeKey/edgeKey 回填审批人与条件仍然成立。坐标变化由实例的 history 自动记录(deepObserve + debounce)。
  • 布局后必须 snapshot() 把新坐标 emit 给上层,否则草稿里的 x/y 还是旧的。
  • 参数:rankdir: 'TB'(审批流程纵向)、align: 'UL'、nodesep: 60、ranksep: 80、isDefaultAnchor: true(重算连线路径)。
  • 测试:tests/unit/workflow-canvas-layout.spec.js(jsdom 挂载画布真跑一次布局;需给 SVGElement.prototype.getBBox 打桩, 否则 LogicFlow 的 autoWrap 文本测量会挂)。

环境坑(本机特有)

  • 编译/测试 fms-api 唯一可行命令(start.cmd 里的 JDK/Maven 路径都是坏的):
    cd /d/workspace/code/fms/fms-api && /d/devtool/jdk/jdk21/bin/java \
      -Dclassworlds.conf="D:/devtool/maven/bin/m2.conf" -Dmaven.home="D:/devtool/maven" \
      -Dmaven.multiModuleProjectDirectory="D:/workspace/code/fms/fms-api" \
      -cp "D:/devtool/maven/boot/plexus-classworlds-2.11.0.jar" \
      org.codehaus.plexus.classworlds.launcher.Launcher -o -Dtest=XxxTests test
    
    本地仓库是 D:\data\maven(不是 ~/.m2);联网下载去掉 -o;target/ 被 git 跟踪,编译后有噪音。
  • 无 sqlcmd/pyodbc/pymssql:用 fms-api/tools/migration/RunSqlFile.java 或一次性 SqlRunner.java
    • JDBC jar(~/.m2/.../mssql-jdbc-13.4.0.jre11.jar)+ /d/devtool/jdk/jdk17/bin/java。 脚本要按行首 GO 分批,路径传绝对路径。
  • vite build 被 safe-delete 守卫拦(要删 600+ 文件):用 --outDir dist-verify --emptyOutDir; 清理用 find dist-verify -type f -delete 再 find dist-verify -type d -empty -delete。
  • oxfmt --check 全仓库都报 format issues(版本漂移),不是自己引入的,别顺手格式化整个仓库。
  • 本工作区可能被多会话同时编辑,验证失败项先用 find <dir> -mmin -N 确认是否自己引入。

测试基线(既有失败,别去修)

  • 后端 OrgDataSourceFactoryTests.databaseConnectionFailureDoesNotRetry(断言 Druid retry=0,实测 1)。
  • 前端 tests/unit/fms-module-list-page.spec.js 里引用 .filter-drawer/.filter-row__* 的用例 (类名已不存在,是高级查询面板改版后的过期断言)—— 另有约 13 个长期失败基线。
  • tests/helpers/app.js 的 mountPage 用 shallowMount 且默认打桩子组件:给 Splitter/SplitterPanel 打桩时键名要写 Vue 推断名 'splitter'/'panel'(文件名小写),写 'SplitterPanel' 打不上桩; 自定义组件传 false 可强制真实挂载。global 会整体覆盖内部 stubs,别在里面再写 stubs。

主题约定

  • 配色唯一真相 fms-vue/src/theme/tokens.css::root 浅色 + .dark 深色必须同步增删同名变量, --fms-primary 由 JS 写入,衍生色用 color-mix()。改主题只动这个文件。
  • 侧栏当前是浅色的(不存在 --fms-sidebar-* 变量;若再看到「侧栏是黑的」即为过期信息)。 要改侧栏配色就新增 --fms-sidebar-* 只给 AppSidebar.vue/NavMenuItem.vue 用, 别动 --fms-surface/--fms-text 等全局中性色(顶栏与所有业务页共用)。
  • 深底+主题色高亮必须用「深底 + 白字 + 主色亮描边」:填充 color-mix(primary 38%, #000)、 描边 color-mix(primary 40%, #fff);「浅底+白字」对比度只有 1.4~2.7:1,不可用。
  • Teleport 到 body 的浮层(如 .fms-team-menu-*)不在侧栏内,保持全局浅色 token。

UI 组件库全局配置

  • 唯一入口 fms-vue/src/components/ui/config.js:uiConfig(reactive)+ setUIConfig(patch)(按组件维度浅合并)
    • resetUIConfig(),也从 @/components/ui 具名导出。没有 ConfigProvider、没有 app.use 插件。
  • 回落顺序固定 自身 prop > uiConfig > 组件内置默认;要支持全局覆盖的 prop,default 必须写 undefined 且 validator 要放行 undefined(Vue 会拿 default 跑校验)。
  • Form 的 layout/labelAlign/labelWidth/validateTrigger 已接入;provide 下发回落后的最终值。 语言联动在 src/App.vue:中文 horizontal、其他语言 vertical(判定 /^zh/i)。
  • 多列紧凑网格的表单必须显式 layout="vertical"(如自动编码面板 columns=5), 否则横排的 88px 标签会把控件压到几十像素。prop 优先于全局配置。
  • defineModel 写完不能立刻回读:写了只 emit,prop 下一轮才回来;要取刚写入的值就自己先 reactive() 包一层再赋值,返回其中的代理对象。

i18n 运行时

  • 语言清单 s_i18n_type(b_id 即语种编码,zh-CN 默认 / en-US);译文 s_i18n(b_key, b_locale, b_value)。
  • store stores/i18n.js:loadLanguages() 拉清单、loadMessages(locale) 拉译文(按语种缓存 + 并发去重 + 失败静默;只把「当前语种」的译文写进运行时)。别只看 locale——不调 loadMessages 界面不会变。
  • 合并顺序 前端内置 < 库表译文:内置只有 en-US 的 common.*(src/i18n/builtin-messages.js, 代码约定 t('common.x', '中文兜底'));业务文案/字段名/菜单名的译文一律进 s_i18n 由实施维护。
  • 现状:s_i18n 只登记了 zh-CN(约 134 条),en-US 尚无数据 → 英文界面只有框架按钮/提示是英文, 字段名等仍回落中文,需要补录。
  • 顶栏语种下拉取自 s_i18n_type 清单,不要硬编码语言编码。
  • 查询区(FmsQueryToolbar)已改用 Form/FormItem,因此也跟随该配置;Form 根靠 display: contents 透明化,让 FormItem 直接参与 .query-grid(minmax(0,200px))布局,label 宽局部 72px。 高级查询面板(FmsAdvancedQueryPanel)仍是自绘 .filter-*,不受表单布局配置影响。

导出(P0 已落地,摘要)

POST /api/export/list 返回文件流:SqlPermissionService → ExportSchemaService(列与列表同源推导) → ExportService(Fesod 流式写)→ xlsx。导出列不由前端传,否则字段权限形同虚设; 校验必须在 getOutputStream() 前完成。依赖 org.apache.fesod:fesod-sheet:2.0.2-incubating(自带 POI,别再引)。 Fesod 坑:0 行也要调一次 write,否则无工作表;合计行要自己构造标签。 权限现状:s_power 只有 2 行,非超管会被拒,g3soft 可直接用。