# 工作进度-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