# FMS 项目长期约定 ## 目录与文件 - 后端 `fms-api`,前端 `fms-vue`(Vue 3.5 + Vite 8 + Pinia 4 + pnpm,**纯 JS 无 TS**)。 - 设计文档集中在仓库根:`FMS新系统核心表结构设计.md`(新库 FMS)、`FMS业务表设计.md`、 `Fms旧系统表结构.md`(旧库 G3HY2025,**只有表、不含视图**)、`开发规范.md`、`FMS删除规则引擎设计.md`。 - 建库/注册脚本在 `sql/`:`fms_core.sql`、`fms_business_*.sql`(建表+视图)、`fms_module_*.sql`(模块元数据)。 ## 数据库 - 服务器 `118.89.70.199:1433`;**旧库 `G3HY2025`**、**新库 `FMS`**(新系统一律用 FMS)。 - 机构与库的映射写在 `fms-api/config/dbconfigs/{ORG_ID}.properties`(该目录被 gitignore)。 已存在:`G3HD.properties` → databaseName=**FMS**;`G3HY2025.properties` → databaseName=G3HY2025。 **没有 FMS.properties**,G3HD 就是指向新库的那份。 - 后端 `fms-api` 监听 **8088**(context-path `/api`);前端 dev server **5082**(proxy `/api` → 127.0.0.1:8088)。 - 直接 curl 后端接口会返回 `401 未登录或登录已失效`,需先 `/auth/login`。 ## 元数据驱动的核心约定 - 模块树 `s_module`(`b_module_type` ∈ module/data/virtual),字段 `s_field`, 界面配置 `s_module_schema`(PK = `b_module_id` + `b_schema_type` ∈ view/edit/query,JSON 存在 `b_schema_json`)。 - **这三张表的真实列名(已连库核对,写脚本时别再猜)**: - `s_module`:PK 是 **`b_id`**(**没有** `b_module_code` 列);且它**本身就是树** —— `b_parent_id` / `b_depth` / `b_path`;另有 `b_module_type`(`module` / `data`)、 `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`。**没有 `b_route`**(那是 `s_menu` 的列)。 - `s_field`:PK 是 **`(b_module_id, b_field)`**,**没有 `b_id`**;列为 `b_field` / `b_name` / `b_i18n` / `b_type` / `b_canuse` / `b_xh`。`b_type` 实测取值: `input` / `number` / `checkbox` / `datetime` / `date`。 - `s_module_schema`:PK `(b_module_id, b_schema_type)`,另有 `b_schema_json` / `b_canuse`。 - `s_menu`:`b_id` / `b_parent_id` / `b_depth` / `b_path` / `b_name` / `b_i18n` / `b_menu_type`(`directory` 或 `page`)/ `b_route` / `b_icon` / `b_xh` / `b_canuse`。 - `s_menu_module`:`b_menu_id` / `b_module_id` / `b_xh` / `b_canuse`。 - **模块编码写法**:列表模块统一 `v_` + 表名(如 `v_b_contact`),`b_view_table` 指向查询视图, `b_save_table` 指向真实表。数据模块挂在分组 module 节点下(如 `base`、`system`)。 - **前端权限靠 `b_id`**:`usePermissionStore` 的 `moduleCodes` 就是 `s_module.b_id`; `canPower(code, power)` 查 `${b_id}.${power}`。所以页面里写的 `MODULE_CODE` 必须等于模块 `b_id`。 另外要记得在 `s_menu_module` 里把菜单绑到模块,否则侧栏入口不会出现在角色的「菜单权限」树中,非超管无法授权。 - **s_module_schema JSON 真实形状**(与 `schemaRender.js` 一一对应,别写错): - `view`:`{ schemaVersion, columns: [ {field,width} | {type:'group',title,children} ] }` - `edit`:`{ schemaVersion, children: [ {field,span,required,readonly,defaultValue} | {type:'group',...} ] }` - `query`:`{ schemaVersion, groups: [ { conditions: [ {field,operator} ] } ], quick: [field] }` - **s_menu 无种子数据**,业务菜单由「菜单管理」页面维护。`s_menu_module` 把菜单关联到 data/virtual 模块。 - 路由守卫只看 `meta.moduleId` / `meta.adminOnly`(`to.matched.some`),**没有 moduleId 的路由不需要模块记录即可访问**。 ## 树结构规范(模块树 / 菜单树 / 部门树通用) 见 `FMS新系统核心表结构设计.md` 第 1.7 节:**邻接表为主 + `b_depth`/`b_path` 冗余**。 - `b_parent_id` 是**权威关系**;`b_depth`、`b_path` 是业务层维护的**派生字段**。 - 根节点 `b_parent_id` 为 `NULL`,`b_depth` 从 0 起算;`b_path` 形如 `/cn/east/`(前后都带 `/`)。 - 新增/移动节点时必须在同一事务中同步当前节点及**全部后代**的 `b_depth`/`b_path`。 - 二者不一致时**以 `b_parent_id` 为准**,并提供按父子关系重建冗余字段的能力。 - 全项目通用的树工具在 **`fms-vue/src/utils/tree.js`**(`buildSortedTree` / `computeTreeMove` / `collectDescendantIds` / `applyTreePosition` / `filterTreeByText` 等)。新增一棵树优先复用它。 - 展示层适配器 **`fms-vue/src/components/fms-tree/FmsTree.vue`**:自管 `loading` 遮罩与 「全部分类」虚拟根(空选中键 ⟺ 虚拟根,调用方不需要知道 `rootKey`)。 ## 主题与样式约定 - 全站配色唯一真相是 **`fms-vue/src/theme/tokens.css`**:浅色定义在 `:root`, 深色覆盖在 `.dark`,**两组必须同步增删同名变量**;`--fms-primary` 由 JS 写入, 衍生色一律用 `color-mix()` 派生。改主题只动这个文件,不在组件里硬编码颜色。 - **侧栏当前是浅色的**(`--fms-surface` 白底 + `box-shadow: var(--fms-shadow-right)`), 与顶栏同为「框架面」。2026-09-16 曾改造为黑色并已**按用户要求全部回退**, 现存代码里**没有** `--fms-sidebar-*` 变量。若再看到「侧栏是黑的」的描述即为过期信息。 - 主色预设(`src/theme/presets.js`)**默认是「墨蓝」`#0f172b`,本身就是近黑**。 所以任何深色底上做主题色元素时,别直接套浅底模板(见下条实测结论)。 - **深色底 + 主题色高亮态的实测结论(回退前验证过,重做深色侧栏时直接复用)**: 「浅底 + 白字」走不通 —— 主色向白混到能看清色相时已成 pastel,白字对比度实测仅 **1.4~2.7:1**(9 个预设色全不合格)。必须用「深底 + 白字 + 主色亮描边」三件套: 填充 = `color-mix(primary 38%, #000)`(白字 5.1~19.2:1 达标), 描边 = `color-mix(primary 40%, #fff)`(近黑主色填充与黑底仅 1.04:1,全靠描边勾边界)。 即**文字可读性靠填充、可辨识性靠描边,二者分离**才能覆盖全部预设色。 - **要改侧栏配色时的正确做法**:新增一组 `--fms-sidebar-*` 专用 token, 只给 `layouts/components/AppSidebar.vue` 与 `NavMenuItem.vue` 用。 **不要改 `--fms-surface` / `--fms-text` 等全局中性色** —— 那些是顶栏、内容卡、 全部业务页面共用的,改了会让整个主区一起变。 - **Teleport 到 `body` 的浮层不算侧栏内元素**(如团队下拉 `.fms-team-menu-*`): 它们渲染在浅色浮层容器里,改侧栏配色时必须保持全局浅色 token,不能跟着一起改。 ## 测试 - 测试 harness 在 `fms-vue/tests/helpers/app.js`:`mountPage` 内部用 **shallowMount**,默认打桩所有子组件。 - **陷阱**:给布局容器(`Splitter`/`SplitterPanel`)打桩时,键名要写 **Vue 推断出的组件名**。 这两个 SFC 文件名是 `splitter.vue` / `panel.vue`,推断名是 `'splitter'` / `'panel'`, **不是** `'SplitterPanel'`;写错键就打不上桩,容器成空壳、插槽不渲染。 自定义组件(如 `DeptTreePanel`)传 `false` 可强制真实挂载。 - `mountPage` 的 `global` 选项会整体覆盖内部 `stubs`,不要在 `global` 里再写 `stubs`。 - 既有 13 个失败用例(query-UI / router-auth / login / scratch repro)是长期基线,非新改动引入。 ## 环境坑 - `vite build` 会被本机 safe-delete 守卫拦下(默认输出目录要删 672 个文件 > 阈值 50), 验证构建请用 `--outDir dist-verify --emptyOutDir`,用完清理。 - 清理 `dist-verify`(600+ 文件)时 `rm -rf` 同样被 safe-delete 拦(genie-trash 报 `Some operations were aborted`)。可行做法:`find dist-verify -type f -delete`, 再 `find dist-verify -type d -empty -delete`。 - 本工作区**可能被多个会话同时编辑**,验证失败项前先用 `find -mmin -N` 按 mtime 确认是否自己引入。 ## 直连 FMS 库执行 SQL(本机无 sqlcmd / pymssql / pyodbc 时) 本机**没有** `sqlcmd`,Python 也**没有** `pymssql` / `pyodbc`。但有两样现成资源可拼出可用客户端: - JDBC 驱动:`C:/Users/admin/.m2/repository/com/microsoft/sqlserver/mssql-jdbc/13.4.0.jre11/mssql-jdbc-13.4.0.jre11.jar` - JDK:`/d/devtool/jdk/jdk17/bin/java`(另有 jdk21 / jdk8 目录) 做法:写一个一次性 `SqlRunner.java`,读脚本文件 → 按**独立成行的 `GO`** 分批 → 逐批 `Statement.execute` 并把结果集打成对齐表格。编译运行: ``` /d/devtool/jdk/jdk17/bin/javac -d . SqlRunner.java /d/devtool/jdk/jdk17/bin/java -cp ".;" SqlRunner "D:/abs/path/to.sql" ``` 注意: - **脚本没有 `GO` 就会被当成一个批次整体发送**,中途一句报错会导致后面全不执行; 自检/探查脚本记得用 `GO` 分开。 - `SqlRunner` 传相对路径会以 `cd` 后的目录解析,**一律传绝对路径**避免踩坑。 - 连接串取自 `fms-api/config/dbconfigs/G3HD.properties`(gitignored),指向**远端生产库** `118.89.70.199:1433` / `FMS`。**改动生产库前先探查现状、并清理自己造的测试数据。** - 该库是**生产环境**,执行任何写操作前先确认清楚。 ## 直连后端 API(比查库更接近真实行为) 后端 `fms-api` 跑在 **8088**(context-path `/api`)。验证数据/权限链路时优先走它: - 登录 `POST /api/auth/login`:字段是**小写** `orgid` / `userid` / `password`。 `orgid` 传 `G3HD`(对应 `dbconfigs/G3HD.properties` → `FMS` 库);缺了报「机构码不能为空」。 - `POST /api/data/loaddata`:`view_name` / `search_condition` / `order_by` / `search_columns`(JSON 数组)。 - `POST /api/data/page`:追加 `page_no` / `page_size`。 **参数名是 `page_no`/`page_size`,不是 `pageindex`/`pagesize`**;`search_columns` 必须是**真 JSON 数组**, 传字符串会报「search_columns 参数类型不正确」。 - `POST /api/data/saveobjt`:body 是**数组**,每项形如 `{table, key_field, deletes, updates, inserts}` —— **`key_field` 必填**,缺了报「key_field 不能为空」; 复合主键用逗号分隔(如 `s_field` 传 `b_module_id,b_field`)。执行顺序 delete → update → insert。 - 模块元数据(`s_module` / `s_field` / `s_module_schema`)**不由后端接口下发**, 前端从本地 schema JSON 解析,所以改元数据后无需重启后端。