feat(dept): 部门管理新增 CUSTOM 自定义部门页签
build / build (push) Successful in 2m7s

- index.vue 来源切换「系统部门|自定义部门」,SYSTEM 原 DOM 原样 v-if 包装零逻辑改动,学校上下文共用
- CustomDeptPanel/CustomDeptCard:分类增删改排序(非空禁删)、部门增改移启停软删(风险二次确认)
- CustomMemberModal:候选走 CandidateUsers(服务端同口径排除学生组),搜索+勾选+批量添加
- apiDefinitions/globals.d.ts 补 17 个 /api 契约(key 与后端 operationId 严格一致)
- 遗留:npm run api:gen 待后端可起环境补跑校验
This commit is contained in:
chenx
2026-10-22 09:36:18 +08:00
parent b8c55a0654
commit 83a73dfcc8
6 changed files with 1660 additions and 16 deletions
+61 -16
View File
@@ -17,28 +17,40 @@
<Select v-if="multiSchool" v-model="currentSchoolId" size="small" class="ml-8 school-select">
<Option v-for="s in schoolOptions" :key="s.id" :value="s.id">{{ s.name }}</Option>
</Select>
<span class="inc-ctrl ml-auto" title="打开后列出该校下所有层级的部门" @click="toggleInclude">
<!-- 来源切换:只切右侧管理视图,学校上下文共用,不改底层组织树 -->
<div class="source-tabs ml-auto">
<span class="source-tab" :class="{ 'is-active': source === 'SYSTEM' }"
@click="source = 'SYSTEM'">系统部门 SYSTEM</span>
<span class="source-tab" :class="{ 'is-active': source === 'CUSTOM' }"
@click="source = 'CUSTOM'">自定义部门 CUSTOM</span>
</div>
<span v-if="source === 'SYSTEM'" class="inc-ctrl" title="打开后列出该校下所有层级的部门"
@click="toggleInclude">
<span class="inc-label">包含子组</span>
<i class="sw-mini" :class="{ 'is-on': includeOn }"></i>
</span>
</div>
<Alert show-icon type="success" class="usage-note">
<b>设置说明:</b>
停用只影响「选」(新建事项 / 流程绑定时选不到),不影响「看」(已有事项展示)与「办」(在途流程照常解析);
关闭「包含子组」只展示一级部门,打开后连同下级部门(如办公室下的校办 / 宣传 / 财务 / 党建)一并列出。
</Alert>
<template v-if="source === 'SYSTEM'">
<Alert show-icon type="success" class="usage-note">
<b>设置说明:</b>
停用只影响「选」(新建事项 / 流程绑定时选不到),不影响「看」(已有事项展示)与「办」(在途流程照常解析);
关闭「包含子组」只展示一级部门,打开后连同下级部门(如办公室下的校办 / 宣传 / 财务 / 党建)一并列出。
</Alert>
<Alert v-if="!currentSchoolName" show-icon type="info" class="state-tip">
{{ showTree ? '请在左侧选择学校节点' : '当前账号没有可管理的学校' }}
</Alert>
<Alert v-else-if="!deptList.length" show-icon type="info" class="state-tip">
该校教师组下暂无部门。部门 = 教师组的直接子节点,可前往「组,用户管理」建组。
</Alert>
<div v-else class="dept-grid">
<DeptCard v-for="d in deptList" :key="d.id" :dept="d" :include-children="includeOn"
@toggle="onToggle" />
</div>
<Alert v-if="!currentSchoolName" show-icon type="info" class="state-tip">
{{ showTree ? '请在左侧选择学校节点' : '当前账号没有可管理的学校' }}
</Alert>
<Alert v-else-if="!deptList.length" show-icon type="info" class="state-tip">
该校教师组下暂无部门。部门 = 教师组的直接子节点,可前往「组,用户管理」建组。
</Alert>
<div v-else class="dept-grid">
<DeptCard v-for="d in deptList" :key="d.id" :dept="d" :include-children="includeOn"
@toggle="onToggle" />
</div>
</template>
<CustomDeptPanel v-else :school-id="currentSchoolId" />
</div>
</template>
</div>
@@ -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<InstanceType<typeof GroupTreeSearchPanel>>();
const selectedNode = ref<any>({});
/** 部门来源页签:SYSTEM=原组织树视图(行为零改动);CUSTOM=学校自建分类/部门 */
const source = ref<"SYSTEM" | "CUSTOM">("SYSTEM");
/** 全量组树:右侧部门数据一律从这里按 id 回查真实 children */
const fullTree = ref<any[]>([]);
/** 当前学校:超管由树选中,学校管理员默认取可管理学校 */
@@ -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 {