# 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-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 -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。 ## 导出(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 可直接用。