This commit is contained in:
@@ -30,6 +30,8 @@ export default {
|
||||
'SysGroup.move': ['POST', '/api/sysgroup/Move'],
|
||||
'SysGroup.sysGroupEdit': ['POST', '/api/sysgroup/Edit/{id}'],
|
||||
'SysGroup.sysGroupRemove': ['POST', '/api/sysgroup/Delete/{id}'],
|
||||
'SysGroup.disable': ['POST', '/api/sysgroup/Disable/{id}'],
|
||||
'SysGroup.enable': ['POST', '/api/sysgroup/Enable/{id}'],
|
||||
'SysGroup.batchAdd': ['POST', '/api/sysgroup/BatchAdd'],
|
||||
'SysGroup.sysGroupAdd': ['POST', '/api/sysgroup/Add'],
|
||||
'PushSubscription.test': ['POST', '/api/push/Test'],
|
||||
|
||||
Vendored
+46
@@ -1538,6 +1538,52 @@ declare global {
|
||||
>(
|
||||
config: Config
|
||||
): Alova2Method<null, 'SysGroup.sysGroupRemove', Config>;
|
||||
/**
|
||||
* ---
|
||||
*
|
||||
* [POST]
|
||||
*
|
||||
* **path:** /api/sysgroup/Disable/{id}
|
||||
*
|
||||
* ---
|
||||
*
|
||||
* **Response**
|
||||
* ```ts
|
||||
* type Response = null
|
||||
* ```
|
||||
*/
|
||||
disable<
|
||||
Config extends Alova2MethodConfig<null> & {
|
||||
pathParams: {
|
||||
id: number;
|
||||
};
|
||||
}
|
||||
>(
|
||||
config: Config
|
||||
): Alova2Method<null, 'SysGroup.disable', Config>;
|
||||
/**
|
||||
* ---
|
||||
*
|
||||
* [POST]
|
||||
*
|
||||
* **path:** /api/sysgroup/Enable/{id}
|
||||
*
|
||||
* ---
|
||||
*
|
||||
* **Response**
|
||||
* ```ts
|
||||
* type Response = null
|
||||
* ```
|
||||
*/
|
||||
enable<
|
||||
Config extends Alova2MethodConfig<null> & {
|
||||
pathParams: {
|
||||
id: number;
|
||||
};
|
||||
}
|
||||
>(
|
||||
config: Config
|
||||
): Alova2Method<null, 'SysGroup.enable', Config>;
|
||||
/**
|
||||
* ---
|
||||
*
|
||||
|
||||
@@ -48,6 +48,7 @@ export function getMenuList(userRole?: UserRoleEnum): Promise<IModule[]> {
|
||||
{ id: 42, modId: 1104, name: "组授权", page: "/group-auth", roles: [UserRoleEnum.SuperAdmin, UserRoleEnum.Bureau, UserRoleEnum.School] },
|
||||
{ id: 44, modId: 1110, name: "角色授权", page: "/role-authz/list", roles: [UserRoleEnum.SuperAdmin, UserRoleEnum.Bureau, UserRoleEnum.School] },
|
||||
{ id: 43, modId: 1105, name: "权限一览", page: "/permission-view", roles: [UserRoleEnum.SuperAdmin, UserRoleEnum.Bureau, UserRoleEnum.School] },
|
||||
{ id: 45, modId: 1106, name: "部门管理", page: "/dept-manage", roles: [UserRoleEnum.SuperAdmin, UserRoleEnum.Bureau, UserRoleEnum.School] },
|
||||
],
|
||||
},
|
||||
// 4. 日志管理(父目录)
|
||||
|
||||
@@ -12,6 +12,7 @@ import PermissionView from "@/views/PermissionView.vue";
|
||||
import OperationLogList from "@/views/oplog/OperationLogList.vue";
|
||||
import OapiLogList from "@/views/oapilog/OapiLogList.vue";
|
||||
import PushLog from "@/views/push/PushLog.vue";
|
||||
import DeptManage from "@/views/dept-manage/index.vue";
|
||||
import NoAuth from "@/views/NoAuth.vue";
|
||||
|
||||
// 管理端四级访问控制:meta.roles 为空/未声明 = 登录即可访问
|
||||
@@ -105,6 +106,14 @@ const routes: RouteRecordRaw[] = [
|
||||
component: PermissionView,
|
||||
meta: { title: "权限一览", icon: "icon-list", modId: 1105, roles: ALL_ADMIN },
|
||||
},
|
||||
// ===== 部门管理(2026-10-20 部门线 P0 最小批:只读树 + 状态徽标 + 启停)=====
|
||||
// 挂「权限管理」分组(菜单 id=45);无删除/成员/职务/新建入口——建部门=建组回「组,用户管理」
|
||||
{
|
||||
path: "/dept-manage",
|
||||
name: "DeptManage",
|
||||
component: DeptManage,
|
||||
meta: { title: "部门管理", icon: "icon-list", modId: 1106, parentId: 1108, roles: ALL_ADMIN },
|
||||
},
|
||||
{
|
||||
path: "/push-log",
|
||||
name: "PushLog",
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
<template>
|
||||
<div class="page-wrapper tree-wrapper dept-manage-page">
|
||||
<!-- 左侧:只读组树(复用 GroupTreeSearchPanel,无新增/编辑/删除操作栏) -->
|
||||
<div class="tree-container dept-manage-tree">
|
||||
<GroupTreeSearchPanel ref="refTreePanel" v-model:curTreeNode="selectedNode" :treeApi="treeApi"
|
||||
:defaultSearchVisible="true" @on-change="onTreeChange" @on-tree-data="onTreeData" />
|
||||
</div>
|
||||
|
||||
<!-- 右侧:部门管理面板(P0 最小批:只读 + 状态徽标 + 启停) -->
|
||||
<div class="dept-manage-main">
|
||||
<Card dis-hover :bordered="false" class="mb-12">
|
||||
<template #title>
|
||||
<span>部门管理</span>
|
||||
<Tag v-if="deptContainerName" color="primary" class="ml-8">{{ deptContainerName }}</Tag>
|
||||
</template>
|
||||
<Alert v-if="!selectedNode?.id" show-icon type="info">请在左侧选择组织节点</Alert>
|
||||
<template v-else-if="!isDeptContainer(selectedNode)">
|
||||
<Alert show-icon type="warning">
|
||||
当前节点【{{ selectedNode.name }}】不是部门容器(教师组),部门 = 教师组的直接子节点。
|
||||
请选择学校下的「教师组」节点。
|
||||
</Alert>
|
||||
</template>
|
||||
<template v-else>
|
||||
<Alert v-if="deptRows.length === 0" show-icon type="info">
|
||||
该教师组暂无子节点:按口径「空容器自身算部门」,容器本身即可作为部门使用。
|
||||
</Alert>
|
||||
<Table v-else :columns="columns" :data="deptRows" size="small" :border="false">
|
||||
<template #status="{ row }">
|
||||
<Tag :color="isDisabled(row) ? 'error' : 'success'">{{ isDisabled(row) ? '已停用' : '启用' }}</Tag>
|
||||
</template>
|
||||
<template #action="{ row }">
|
||||
<Button v-if="!isDisabled(row)" size="small" type="error" ghost @click="onDisable(row)">停用</Button>
|
||||
<Button v-else size="small" type="success" ghost @click="onEnable(row)">启用</Button>
|
||||
</template>
|
||||
</Table>
|
||||
</template>
|
||||
</Card>
|
||||
<Alert show-icon type="info" class="usage-note">
|
||||
说明:停用只影响「选」(新建事项/流程绑定时选不到),不影响「看」(已有事项展示)与「办」(在途流程照常解析)。
|
||||
新建部门请前往「组,用户管理」建组;本页不提供删除。
|
||||
</Alert>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from "vue";
|
||||
import Apis from "@/api";
|
||||
import GroupTreeSearchPanel from "@/components/GroupTreeSearchPanel.vue";
|
||||
import MyConfirm from "@/components/MyConfirm";
|
||||
import { Message } from "view-ui-plus";
|
||||
|
||||
/**
|
||||
* 部门管理(2026-10-20 部门线 P0 最小批)。
|
||||
* 口径:部门 = type=7(教师组)的直接子节点;空教师组自身算部门(与 flow-center collectDeptGroups 同源)。
|
||||
* 只做:只读组树 + 状态徽标 + 停用(确认弹窗)/启用(免确认)。
|
||||
* 不做(P1~P3):删除入口、占用弹窗、成员区块、「职务」列、新建部门、「已停用」筛选。
|
||||
*/
|
||||
const TEACHER_GROUP_TYPE = 7;
|
||||
|
||||
const treeApi = () => Apis.SysGroup.getTree();
|
||||
const refTreePanel = ref<InstanceType<typeof GroupTreeSearchPanel>>();
|
||||
const selectedNode = ref<any>({});
|
||||
const treeData = ref<any[]>([]);
|
||||
|
||||
const columns = [
|
||||
{ title: '部门名称', key: 'name', tree: true },
|
||||
{ title: '状态', slot: 'status', width: 100 },
|
||||
{ title: '操作', slot: 'action', width: 100 },
|
||||
];
|
||||
|
||||
const deptContainerName = computed(() =>
|
||||
selectedNode.value?.type === TEACHER_GROUP_TYPE ? selectedNode.value.name : '');
|
||||
|
||||
function onTreeChange(node: any) {
|
||||
selectedNode.value = node || {};
|
||||
}
|
||||
|
||||
function onTreeData(tree: any[]) {
|
||||
treeData.value = tree || [];
|
||||
}
|
||||
|
||||
/** 部门口径判定:type=7 教师组是部门容器 */
|
||||
function isDeptContainer(node: any): boolean {
|
||||
return node?.type === TEACHER_GROUP_TYPE;
|
||||
}
|
||||
|
||||
/** 教师组无子节点时空容器自身算部门(flow-center 同口径) */
|
||||
const deptRows = computed<any[]>(() => {
|
||||
if (!isDeptContainer(selectedNode.value)) return [];
|
||||
const children = selectedNode.value?.children || [];
|
||||
if (children.length === 0) return [selectedNode.value];
|
||||
return children;
|
||||
});
|
||||
|
||||
function isDisabled(row: any): boolean {
|
||||
return row?.status === 1;
|
||||
}
|
||||
|
||||
function onDisable(row: any) {
|
||||
MyConfirm({
|
||||
title: '停用部门',
|
||||
msg: `确定停用部门【${row.name}】?`,
|
||||
info: '停用只影响「选」(新建时选不到),不影响「看」和「办」(已有事项展示、在途流程照常解析)。',
|
||||
async onOk() {
|
||||
try {
|
||||
await Apis.SysGroup.disable({ pathParams: { id: row.id } });
|
||||
Message.success('已停用');
|
||||
await refTreePanel.value?.refreshTree();
|
||||
reselect(row.id);
|
||||
} catch (e: any) {
|
||||
console.warn(e?.message || '停用失败');
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function onEnable(row: any) {
|
||||
try {
|
||||
await Apis.SysGroup.enable({ pathParams: { id: row.id } });
|
||||
Message.success('已启用');
|
||||
await refTreePanel.value?.refreshTree();
|
||||
reselect(row.id);
|
||||
} catch (e: any) {
|
||||
console.warn(e?.message || '启用失败');
|
||||
}
|
||||
}
|
||||
|
||||
/** 刷新后尽量回到原选中节点(找不回则清空,用户重选) */
|
||||
function reselect(nodeId: number) {
|
||||
const found = findNodeById(treeData.value, nodeId);
|
||||
if (found) {
|
||||
selectedNode.value = found;
|
||||
refTreePanel.value?.selectNode?.(found);
|
||||
}
|
||||
}
|
||||
|
||||
function findNodeById(list: any[], id: number): any {
|
||||
for (const n of list || []) {
|
||||
if (n?.id === id) return n;
|
||||
const hit = findNodeById(n?.children || [], id);
|
||||
if (hit) return hit;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.dept-manage-page {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: stretch;
|
||||
|
||||
.dept-manage-tree {
|
||||
flex: 0 0 320px;
|
||||
min-height: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.dept-manage-main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
|
||||
.usage-note {
|
||||
margin-top: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user