From 83a73dfcc8e1cee2541b3c3cb704ea0a0b68b8bc Mon Sep 17 00:00:00 2001 From: chenx Date: Thu, 22 Oct 2026 09:36:18 +0800 Subject: [PATCH] =?UTF-8?q?feat(dept):=20=E9=83=A8=E9=97=A8=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E6=96=B0=E5=A2=9E=20CUSTOM=20=E8=87=AA=E5=AE=9A?= =?UTF-8?q?=E4=B9=89=E9=83=A8=E9=97=A8=E9=A1=B5=E7=AD=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - index.vue 来源切换「系统部门|自定义部门」,SYSTEM 原 DOM 原样 v-if 包装零逻辑改动,学校上下文共用 - CustomDeptPanel/CustomDeptCard:分类增删改排序(非空禁删)、部门增改移启停软删(风险二次确认) - CustomMemberModal:候选走 CandidateUsers(服务端同口径排除学生组),搜索+勾选+批量添加 - apiDefinitions/globals.d.ts 补 17 个 /api 契约(key 与后端 operationId 严格一致) - 遗留:npm run api:gen 待后端可起环境补跑校验 --- src/api/apiDefinitions.ts | 17 + src/api/globals.d.ts | 541 +++++++++++++++++++ src/views/dept-manage/CustomDeptCard.vue | 199 +++++++ src/views/dept-manage/CustomDeptPanel.vue | 552 ++++++++++++++++++++ src/views/dept-manage/CustomMemberModal.vue | 290 ++++++++++ src/views/dept-manage/index.vue | 77 ++- 6 files changed, 1660 insertions(+), 16 deletions(-) create mode 100644 src/views/dept-manage/CustomDeptCard.vue create mode 100644 src/views/dept-manage/CustomDeptPanel.vue create mode 100644 src/views/dept-manage/CustomMemberModal.vue diff --git a/src/api/apiDefinitions.ts b/src/api/apiDefinitions.ts index 3116ff5..47be3bd 100644 --- a/src/api/apiDefinitions.ts +++ b/src/api/apiDefinitions.ts @@ -35,6 +35,23 @@ export default { 'SysGroup.setIncludeChildren': ['POST', '/api/sysgroup/SetIncludeChildren/{id}'], 'SysGroup.batchAdd': ['POST', '/api/sysgroup/BatchAdd'], 'SysGroup.sysGroupAdd': ['POST', '/api/sysgroup/Add'], + 'CustomDeptCategory.customDeptCategoryList': ['GET', '/api/deptcategory/List'], + 'CustomDeptCategory.customDeptCategoryAdd': ['POST', '/api/deptcategory/Add'], + 'CustomDeptCategory.customDeptCategoryEdit': ['POST', '/api/deptcategory/Edit/{id}'], + 'CustomDeptCategory.customDeptCategorySort': ['POST', '/api/deptcategory/Sort'], + 'CustomDeptCategory.customDeptCategoryDelete': ['POST', '/api/deptcategory/Delete/{id}'], + 'CustomDept.customDeptList': ['GET', '/api/customdept/List'], + 'CustomDept.customDeptAdd': ['POST', '/api/customdept/Add'], + 'CustomDept.customDeptEdit': ['POST', '/api/customdept/Edit/{id}'], + 'CustomDept.customDeptMove': ['POST', '/api/customdept/Move/{id}'], + 'CustomDept.customDeptSort': ['POST', '/api/customdept/Sort'], + 'CustomDept.customDeptDisable': ['POST', '/api/customdept/Disable/{id}'], + 'CustomDept.customDeptEnable': ['POST', '/api/customdept/Enable/{id}'], + 'CustomDept.customDeptDelete': ['POST', '/api/customdept/Delete/{id}'], + 'CustomDept.customDeptMembers': ['GET', '/api/customdept/Members/{id}'], + 'CustomDept.customDeptCandidateUsers': ['GET', '/api/customdept/CandidateUsers/{id}'], + 'CustomDept.customDeptAddMembers': ['POST', '/api/customdept/AddMembers/{id}'], + 'CustomDept.customDeptRemoveMembers': ['POST', '/api/customdept/RemoveMembers/{id}'], 'PushSubscription.test': ['POST', '/api/push/Test'], 'PushSubscription.retry': ['POST', '/api/push/Retry/{id}'], 'GroupAuthz.setSchoolApp': ['POST', '/api/groupauthz/SetSchoolApp'], diff --git a/src/api/globals.d.ts b/src/api/globals.d.ts index 0ea5a1a..f72e604 100644 --- a/src/api/globals.d.ts +++ b/src/api/globals.d.ts @@ -151,6 +151,56 @@ export interface SysGroupRequest { export interface SysGroupIncludeChildrenRequest { includeChildren: boolean; } +export interface CustomDeptCategoryRequest { + schoolId?: number; + name: string; +} +export interface CustomDeptCategorySortRequest { + schoolId?: number; + ids: number[]; +} +export interface CustomDeptCategoryVo { + id?: number; + schoolId?: number; + name?: string; + sortNo?: number; + /** 该分类下有效部门数;>0 时分类不允许删除 */ + departmentCount?: number; +} +export interface CustomDepartmentRequest { + schoolId?: number; + categoryId?: number; + name: string; +} +export interface CustomDepartmentMoveRequest { + schoolId?: number; + categoryId: number; +} +export interface CustomDepartmentSortRequest { + schoolId?: number; + categoryId?: number; + ids: number[]; +} +export interface CustomDepartmentMemberRequest { + schoolId?: number; + userIds: number[]; +} +export interface CustomDepartmentMemberVo { + userId?: number; + account?: string; + name?: string; +} +export interface CustomDepartmentVo { + id?: number; + schoolId?: number; + categoryId?: number; + categoryName?: string; + name?: string; + sortNo?: number; + /** true=启用 / false=停用(后端 CUSTOM 侧统一输出 boolean) */ + enabled?: boolean; + memberCount?: number; +} export interface PushTestRequest { appId?: number; interfacePath?: string; @@ -247,6 +297,10 @@ export interface SysGroupVo { name?: string; type?: number; sort?: number; + /** 启停状态:0=启用 / 1=停用(存量 null 按启用处理) */ + status?: number; + /** 包含子组:1=含下级部门 / 0=仅本级(仅学校节点有值) */ + includeChildren?: number; children?: SysGroupVo[]; } export interface AppItem { @@ -1783,6 +1837,493 @@ declare global { config?: Config ): Alova2Method; }; + CustomDeptCategory: { + /** + * --- + * + * [GET] + * + * **path:** /api/deptcategory/List + * + * --- + * + * **Query Parameters** + * ```ts + * type QueryParameters = { + * schoolId?: number + * } + * ``` + * + * --- + * + * **Response** + * ```ts + * type Response = Array + * ``` + */ + customDeptCategoryList< + Config extends Alova2MethodConfig & { + params?: { + schoolId?: number; + }; + } + >( + config: Config + ): Alova2Method; + /** + * --- + * + * [POST] + * + * **path:** /api/deptcategory/Add + * + * --- + * + * **Request Body** + * ```ts + * type RequestBody = CustomDeptCategoryRequest + * ``` + * + * --- + * + * **Response** + * ```ts + * type Response = CustomDeptCategoryVo + * ``` + */ + customDeptCategoryAdd< + Config extends Alova2MethodConfig & { + data: CustomDeptCategoryRequest; + } + >( + config: Config + ): Alova2Method; + /** + * --- + * + * [POST] + * + * **path:** /api/deptcategory/Edit/{id} + * + * --- + * + * **Request Body** + * ```ts + * type RequestBody = CustomDeptCategoryRequest + * ``` + * + * --- + * + * **Response** + * ```ts + * type Response = CustomDeptCategoryVo + * ``` + */ + customDeptCategoryEdit< + Config extends Alova2MethodConfig & { + pathParams: { + id: number; + }; + data: CustomDeptCategoryRequest; + } + >( + config: Config + ): Alova2Method; + /** + * --- + * + * [POST] + * + * **path:** /api/deptcategory/Sort + * + * --- + * + * **Request Body** + * ```ts + * type RequestBody = CustomDeptCategorySortRequest + * ``` + * + * --- + * + * **Response** + * ```ts + * type Response = null + * ``` + */ + customDeptCategorySort< + Config extends Alova2MethodConfig & { + data: CustomDeptCategorySortRequest; + } + >( + config: Config + ): Alova2Method; + /** + * --- + * + * [POST] + * + * **path:** /api/deptcategory/Delete/{id} + * + * --- + * + * **Response** + * ```ts + * type Response = null + * ``` + */ + customDeptCategoryDelete< + Config extends Alova2MethodConfig & { + pathParams: { + id: number; + }; + } + >( + config: Config + ): Alova2Method; + }; + CustomDept: { + /** + * --- + * + * [GET] + * + * **path:** /api/customdept/List + * + * --- + * + * **Query Parameters** + * ```ts + * type QueryParameters = { + * categoryId: number + * schoolId?: number + * } + * ``` + * + * --- + * + * **Response** + * ```ts + * type Response = Array + * ``` + */ + customDeptList< + Config extends Alova2MethodConfig & { + params: { + categoryId: number; + schoolId?: number; + }; + } + >( + config: Config + ): Alova2Method; + /** + * --- + * + * [POST] + * + * **path:** /api/customdept/Add + * + * --- + * + * **Request Body** + * ```ts + * type RequestBody = CustomDepartmentRequest + * ``` + * + * --- + * + * **Response** + * ```ts + * type Response = CustomDepartmentVo + * ``` + */ + customDeptAdd< + Config extends Alova2MethodConfig & { + data: CustomDepartmentRequest; + } + >( + config: Config + ): Alova2Method; + /** + * --- + * + * [POST] + * + * **path:** /api/customdept/Edit/{id} + * + * --- + * + * **Request Body** + * ```ts + * type RequestBody = CustomDepartmentRequest + * ``` + * + * --- + * + * **Response** + * ```ts + * type Response = CustomDepartmentVo + * ``` + */ + customDeptEdit< + Config extends Alova2MethodConfig & { + pathParams: { + id: number; + }; + data: CustomDepartmentRequest; + } + >( + config: Config + ): Alova2Method; + /** + * --- + * + * [POST] + * + * **path:** /api/customdept/Move/{id} + * + * --- + * + * **Request Body** + * ```ts + * type RequestBody = CustomDepartmentMoveRequest + * ``` + * + * --- + * + * **Response** + * ```ts + * type Response = CustomDepartmentVo + * ``` + */ + customDeptMove< + Config extends Alova2MethodConfig & { + pathParams: { + id: number; + }; + data: CustomDepartmentMoveRequest; + } + >( + config: Config + ): Alova2Method; + /** + * --- + * + * [POST] + * + * **path:** /api/customdept/Sort + * + * --- + * + * **Request Body** + * ```ts + * type RequestBody = CustomDepartmentSortRequest + * ``` + * + * --- + * + * **Response** + * ```ts + * type Response = null + * ``` + */ + customDeptSort< + Config extends Alova2MethodConfig & { + data: CustomDepartmentSortRequest; + } + >( + config: Config + ): Alova2Method; + /** + * --- + * + * [POST] + * + * **path:** /api/customdept/Disable/{id} + * + * --- + * + * **Response** + * ```ts + * type Response = null + * ``` + */ + customDeptDisable< + Config extends Alova2MethodConfig & { + pathParams: { + id: number; + }; + } + >( + config: Config + ): Alova2Method; + /** + * --- + * + * [POST] + * + * **path:** /api/customdept/Enable/{id} + * + * --- + * + * **Response** + * ```ts + * type Response = null + * ``` + */ + customDeptEnable< + Config extends Alova2MethodConfig & { + pathParams: { + id: number; + }; + } + >( + config: Config + ): Alova2Method; + /** + * --- + * + * [POST] + * + * **path:** /api/customdept/Delete/{id} + * + * --- + * + * **Response** + * ```ts + * type Response = null + * ``` + */ + customDeptDelete< + Config extends Alova2MethodConfig & { + pathParams: { + id: number; + }; + } + >( + config: Config + ): Alova2Method; + /** + * --- + * + * [GET] + * + * **path:** /api/customdept/Members/{id} + * + * --- + * + * **Response** + * ```ts + * type Response = Array + * ``` + */ + customDeptMembers< + Config extends Alova2MethodConfig & { + pathParams: { + id: number; + }; + } + >( + config: Config + ): Alova2Method; + /** + * --- + * + * [GET] + * + * **path:** /api/customdept/CandidateUsers/{id} + * + * --- + * + * **Query Parameters** + * ```ts + * type QueryParameters = { + * keyword?: string + * } + * ``` + * + * --- + * + * **Response** + * ```ts + * type Response = Array + * ``` + */ + customDeptCandidateUsers< + Config extends Alova2MethodConfig & { + pathParams: { + id: number; + }; + params?: { + keyword?: string; + }; + } + >( + config: Config + ): Alova2Method; + /** + * --- + * + * [POST] + * + * **path:** /api/customdept/AddMembers/{id} + * + * --- + * + * **Request Body** + * ```ts + * type RequestBody = CustomDepartmentMemberRequest + * ``` + * + * --- + * + * **Response** + * ```ts + * type Response = null + * ``` + */ + customDeptAddMembers< + Config extends Alova2MethodConfig & { + pathParams: { + id: number; + }; + data: CustomDepartmentMemberRequest; + } + >( + config: Config + ): Alova2Method; + /** + * --- + * + * [POST] + * + * **path:** /api/customdept/RemoveMembers/{id} + * + * --- + * + * **Request Body** + * ```ts + * type RequestBody = CustomDepartmentMemberRequest + * ``` + * + * --- + * + * **Response** + * ```ts + * type Response = null + * ``` + */ + customDeptRemoveMembers< + Config extends Alova2MethodConfig & { + pathParams: { + id: number; + }; + data: CustomDepartmentMemberRequest; + } + >( + config: Config + ): Alova2Method; + }; PushSubscription: { /** * --- diff --git a/src/views/dept-manage/CustomDeptCard.vue b/src/views/dept-manage/CustomDeptCard.vue new file mode 100644 index 0000000..6e875ae --- /dev/null +++ b/src/views/dept-manage/CustomDeptCard.vue @@ -0,0 +1,199 @@ + + + + + diff --git a/src/views/dept-manage/CustomDeptPanel.vue b/src/views/dept-manage/CustomDeptPanel.vue new file mode 100644 index 0000000..f486054 --- /dev/null +++ b/src/views/dept-manage/CustomDeptPanel.vue @@ -0,0 +1,552 @@ + + + + + diff --git a/src/views/dept-manage/CustomMemberModal.vue b/src/views/dept-manage/CustomMemberModal.vue new file mode 100644 index 0000000..f5bf7af --- /dev/null +++ b/src/views/dept-manage/CustomMemberModal.vue @@ -0,0 +1,290 @@ + + + + + diff --git a/src/views/dept-manage/index.vue b/src/views/dept-manage/index.vue index 0fc4a13..f95386b 100644 --- a/src/views/dept-manage/index.vue +++ b/src/views/dept-manage/index.vue @@ -17,28 +17,40 @@ - + +
+ 系统部门 SYSTEM + 自定义部门 CUSTOM +
+ 包含子组 - - 设置说明: - 停用只影响「选」(新建事项 / 流程绑定时选不到),不影响「看」(已有事项展示)与「办」(在途流程照常解析); - 关闭「包含子组」只展示一级部门,打开后连同下级部门(如办公室下的校办 / 宣传 / 财务 / 党建)一并列出。 - + + + @@ -51,6 +63,7 @@ import Apis from "@/api"; import GroupTreeSearchPanel from "@/components/GroupTreeSearchPanel.vue"; import MyConfirm from "@/components/MyConfirm"; import DeptCard from "./DeptCard.vue"; +import CustomDeptPanel from "./CustomDeptPanel.vue"; import { Message } from "view-ui-plus"; import { useDefaultStore } from "@/store"; import { UserRoleEnum } from "@/types"; @@ -73,6 +86,8 @@ const roleReady = computed(() => store.roleLoaded); const refTreePanel = ref>(); const selectedNode = ref({}); +/** 部门来源页签:SYSTEM=原组织树视图(行为零改动);CUSTOM=学校自建分类/部门 */ +const source = ref<"SYSTEM" | "CUSTOM">("SYSTEM"); /** 全量组树:右侧部门数据一律从这里按 id 回查真实 children */ const fullTree = ref([]); /** 当前学校:超管由树选中,学校管理员默认取可管理学校 */ @@ -167,6 +182,8 @@ async function toggleInclude() { school.includeChildren = next ? 1 : 0; Message.success(next ? "已包含下级部门" : "仅显示一级部门"); } catch (e: any) { + // 全局 alova 拦截器(alova-iview 的 onBizError/onHttpError)已统一 Message.error,此处只记日志避免二次弹窗; + // 本地字段只在写成功后改,失败即自然停在原值,无需回滚 console.warn(e?.message || "设置包含子组失败"); } } @@ -283,6 +300,34 @@ async function loadSchoolScope() { width: 180px; } +/* 来源切换:同页两个视图,不新增菜单项 */ +.source-tabs { + display: inline-flex; + align-items: center; + gap: 2px; + padding: 2px; + background: #f2f4f9; + border-radius: 8px; +} + +.source-tab { + padding: 3px 10px; + font-size: 12.5px; + line-height: 18px; + border-radius: 6px; + color: var(--text-2, #5a6478); + cursor: pointer; + user-select: none; + white-space: nowrap; +} + +.source-tab.is-active { + background: var(--panel, #fff); + color: var(--brand, #577afb); + font-weight: 600; + box-shadow: 0 1px 2px rgba(30, 38, 53, .08); +} + /* 右侧卡片面板撑满剩余高度(与其他页面一致),内容超出时在面板内滚动 */ /* 面板内顶部说明条的间距(外观完全交给 iView Alert 自身,不覆写配色) */ .usage-note {