feat(identity): 补齐部门管理(两级组织树、停用启用)
ci / 质量门禁 (push) Canceled after 0s

背景:
- A1 只实现了部门树读取与新建 / 改名接口,没有部门管理入口,
  负责人、排序、停用状态都无法维护;
- 用户必须关联部门,部门无法维护会让 A2 的数据范围缺少组织基础。

改动:
- shared:DepartmentNode 增加 status / sortOrder / directMemberCount;
  新增 createDepartmentInput / updateDepartmentInput / departmentTreeQuery 契约;
- api:GET /departments/tree?include_disabled;POST/PATCH /departments
  (名称 / 负责人 / 排序);POST /departments/{id}/disable|enable;
  只停用不删除,有在职成员 / 启用子部门 / 下级在职成员时禁止停用;
  新建 / 编辑用户校验部门必须启用;@Audit 与 OpenAPI 同步;
- web:管理中心新增「部门管理」/system/departments,左两级组织树 +
  右详情 / 内联编辑 + 新建部门/小组 + 停用启用二次确认;显示已停用、
  ?id= 选中可恢复、无 user:manage 只读;用户页部门选择仍只取启用部门;
- 测试:shared 契约、api 单测 + int、web 树/表单/接口单测同步。

影响面:
- 新增部门写接口与页面;GET tree 默认仍只返回启用部门,对用户页无破坏;
- 权限复用 user:view / user:manage,不新增权限点;
- 无数据库迁移(status / leader_user_id / sort_order 列已存在)。

回滚方式:
- 回滚本提交即移除部门管理页与写接口,保留原只读树;数据库无需回滚。

验证证据:
- pnpm format:check / lint / typecheck 全绿;
- pnpm test:shared 121 / api 90 / web 120;test:int 4 files / 33 tests;
- pnpm openapi:diff:无破坏性变更;
- 真实 API + 浏览器(jiang.lan):新建一级部门 / 小组、改名、停用空小组、
  重名 40903、树/详情/停用确认均正常;
  截图 design/a1-ui-v1/shots/impl/70-departments.png。
This commit is contained in:
Stev_Wang committed 2026-11-03 15:01:07 +08:00
1 parent f4da1f3c02
commit 6661c42e84
33 files changed
+3237 -125

No files matched your search

@@ -44,6 +44,15 @@ describe('导航配置', () => {
);
});
it('部门管理菜单使用 user:view,仅查看权限也能进入', () => {
const departmentsItem = NAV_MODULES.find((module) => module.key === 'system')?.items.find(
(item) => item.key === 'departments',
);
expect(departmentsItem?.path).toBe('/system/departments');
expect(departmentsItem?.permission).toBe('user:view');
expect(findActiveNav(NAV_MODULES, '/system/departments')?.item?.label).toBe('部门管理');
});
it('按路径定位当前模块与菜单', () => {
const active = findActiveNav(NAV_MODULES, '/material/quick-review');
expect(active?.module.key).toBe('material');
@@ -55,5 +64,7 @@ describe('导航配置', () => {
expect(getBreadcrumbs(NAV_MODULES, '/')).toEqual([{ key: 'home', label: '工作台', path: '/' }]);
const breadcrumbs = getBreadcrumbs(NAV_MODULES, '/system/users');
expect(breadcrumbs.map((entry) => entry.label)).toEqual(['工作台', '管理中心', '用户与组织']);
const deptBreadcrumbs = getBreadcrumbs(NAV_MODULES, '/system/departments');
expect(deptBreadcrumbs.map((entry) => entry.label)).toEqual(['工作台', '管理中心', '部门管理']);
});
});
+9
View File
@@ -1,5 +1,6 @@
import { Permission, type PermissionCode } from '@lingxi/shared';
import {
IconApps,
IconAsset,
IconCheck,
IconClock,
@@ -254,6 +255,14 @@ export const NAV_MODULES: readonly NavModule[] = [
permission: Permission.USER_VIEW,
description: '账号、组织与登录状态',
},
{
key: 'departments',
label: '部门管理',
path: '/system/departments',
icon: IconApps,
permission: Permission.USER_VIEW,
description: '两级组织架构、负责人、排序与停用状态',
},
{
key: 'roles',
label: '角色管理',