@@ -85,6 +85,7 @@ export default {
|
|||||||
'SysOapiLog.oapiLogDownload': ['GET', '/api/oapilog/Download/{id}'],
|
'SysOapiLog.oapiLogDownload': ['GET', '/api/oapilog/Download/{id}'],
|
||||||
'GroupAuthz.userRoles': ['GET', '/api/groupauthz/UserRoles'],
|
'GroupAuthz.userRoles': ['GET', '/api/groupauthz/UserRoles'],
|
||||||
'GroupAuthz.userPermissionTree': ['GET', '/api/groupauthz/UserPermissionTree'],
|
'GroupAuthz.userPermissionTree': ['GET', '/api/groupauthz/UserPermissionTree'],
|
||||||
|
'GroupAuthz.groupPermissionTree': ['GET', '/api/groupauthz/GroupPermissionTree'],
|
||||||
'GroupAuthz.userGroupTree': ['GET', '/api/groupauthz/UserGroupTree'],
|
'GroupAuthz.userGroupTree': ['GET', '/api/groupauthz/UserGroupTree'],
|
||||||
'GroupAuthz.searchUsers': ['GET', '/api/groupauthz/SearchUsers'],
|
'GroupAuthz.searchUsers': ['GET', '/api/groupauthz/SearchUsers'],
|
||||||
'GroupAuthz.hasModulePerm': ['GET', '/api/groupauthz/HasModulePerm'],
|
'GroupAuthz.hasModulePerm': ['GET', '/api/groupauthz/HasModulePerm'],
|
||||||
|
|||||||
@@ -46,7 +46,8 @@
|
|||||||
<div class="side-body">
|
<div class="side-body">
|
||||||
<div v-if="treeLoading" class="loading-tip small">加载中...</div>
|
<div v-if="treeLoading" class="loading-tip small">加载中...</div>
|
||||||
<div v-else-if="deptTree.length === 0" class="empty-tip small">暂无部门归属</div>
|
<div v-else-if="deptTree.length === 0" class="empty-tip small">暂无部门归属</div>
|
||||||
<MyTree v-else :treeData="deptTree" :setting="treeSetting" />
|
<MyTree v-else :treeData="deptTree" :setting="treeSetting" :autoClick="false"
|
||||||
|
@on-tree-click="onDeptNodeClick" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="side-block">
|
<div class="side-block">
|
||||||
@@ -54,16 +55,17 @@
|
|||||||
<div class="side-body">
|
<div class="side-body">
|
||||||
<div v-if="treeLoading" class="loading-tip small">加载中...</div>
|
<div v-if="treeLoading" class="loading-tip small">加载中...</div>
|
||||||
<div v-else-if="roleTree.length === 0" class="empty-tip small">暂无所属角色</div>
|
<div v-else-if="roleTree.length === 0" class="empty-tip small">暂无所属角色</div>
|
||||||
<MyTree v-else :treeData="roleTree" :setting="treeSetting" />
|
<MyTree v-else :treeData="roleTree" :setting="treeSetting" :autoClick="false"
|
||||||
|
@on-tree-click="onRoleNodeClick" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 右列:已授权应用(原有) -->
|
<!-- 右列:已授权应用(原有);支持三种视图:用户聚合 / 部门树节点 / 角色树节点 -->
|
||||||
<div class="perm-main">
|
<div class="perm-main">
|
||||||
<div class="side-title">已授权应用</div>
|
<div class="side-title">{{ permMainTitle }}</div>
|
||||||
<div class="side-body">
|
<div class="side-body">
|
||||||
<div v-if="treeLoading" class="loading-tip">权限加载中...</div>
|
<div v-if="permLoading" class="loading-tip">权限加载中...</div>
|
||||||
<div v-else-if="permissionTree.length === 0" class="empty-tip">该用户暂无已授权应用模块</div>
|
<div v-else-if="permissionTree.length === 0" class="empty-tip">{{ permEmptyTip }}</div>
|
||||||
<MyTree v-else ref="permTreeRef" :treeData="permissionTree" :setting="treeSetting" />
|
<MyTree v-else ref="permTreeRef" :treeData="permissionTree" :setting="treeSetting" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -74,7 +76,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref } from "vue";
|
import { ref, computed } from "vue";
|
||||||
import { Input } from "view-ui-plus";
|
import { Input } from "view-ui-plus";
|
||||||
import GroupTreeSearchPanel from "@/components/GroupTreeSearchPanel.vue";
|
import GroupTreeSearchPanel from "@/components/GroupTreeSearchPanel.vue";
|
||||||
import MyTree from "@/components/MyTree.vue";
|
import MyTree from "@/components/MyTree.vue";
|
||||||
@@ -146,6 +148,25 @@ const deptTree = ref<any[]>([]);
|
|||||||
const roleTree = ref<any[]>([]);
|
const roleTree = ref<any[]>([]);
|
||||||
const userLoading = ref(false);
|
const userLoading = ref(false);
|
||||||
const treeLoading = ref(false);
|
const treeLoading = ref(false);
|
||||||
|
// 右列「已授权应用」独立 loading:部门/角色节点点击只刷新右列,不能把左列两棵树刷成加载中
|
||||||
|
const permLoading = ref(false);
|
||||||
|
// 右列当前展示上下文:user=选中用户的聚合权限 / group=部门树节点的组授权 / role=角色树节点的角色授权
|
||||||
|
const permScope = ref<{ type: "user" | "group" | "role"; id?: number; label: string }>({
|
||||||
|
type: "user",
|
||||||
|
label: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
const permMainTitle = computed(() => {
|
||||||
|
if (permScope.value.type === "group") return `已授权应用(部门:${permScope.value.label})`;
|
||||||
|
if (permScope.value.type === "role") return `已授权应用(角色:${permScope.value.label})`;
|
||||||
|
return "已授权应用";
|
||||||
|
});
|
||||||
|
|
||||||
|
const permEmptyTip = computed(() => {
|
||||||
|
if (permScope.value.type === "group") return "该部门暂无已授权应用模块";
|
||||||
|
if (permScope.value.type === "role") return "该角色暂无已授权应用模块";
|
||||||
|
return "该用户暂无已授权应用模块";
|
||||||
|
});
|
||||||
|
|
||||||
const treeSetting = ref({
|
const treeSetting = ref({
|
||||||
view: { dblClickExpand: false, showIcon: false, showLine: true },
|
view: { dblClickExpand: false, showIcon: false, showLine: true },
|
||||||
@@ -180,6 +201,7 @@ function clearPermPanels() {
|
|||||||
permissionTree.value = [];
|
permissionTree.value = [];
|
||||||
deptTree.value = [];
|
deptTree.value = [];
|
||||||
roleTree.value = [];
|
roleTree.value = [];
|
||||||
|
permScope.value = { type: "user", label: "" };
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadGroupUsers(groupId?: number) {
|
async function loadGroupUsers(groupId?: number) {
|
||||||
@@ -234,6 +256,8 @@ function onClearUser() {
|
|||||||
async function onSelectUser(user: UserItem) {
|
async function onSelectUser(user: UserItem) {
|
||||||
selectedUser.value = user;
|
selectedUser.value = user;
|
||||||
treeLoading.value = true;
|
treeLoading.value = true;
|
||||||
|
// 切换用户后右列回到「用户聚合权限」视图(覆盖可能存在的部门/角色节点视图)
|
||||||
|
permScope.value = { type: "user", id: user.id, label: "" };
|
||||||
const userId = user.id as number;
|
const userId = user.id as number;
|
||||||
// 行自带组ID(多组用户每个组一行)优先;组列表模式回退左侧选中组
|
// 行自带组ID(多组用户每个组一行)优先;组列表模式回退左侧选中组
|
||||||
const groupId = user.groupId ?? (selectedGroup.value?.id ? Number(selectedGroup.value.id) : undefined);
|
const groupId = user.groupId ?? (selectedGroup.value?.id ? Number(selectedGroup.value.id) : undefined);
|
||||||
@@ -251,10 +275,15 @@ async function onSelectUser(user: UserItem) {
|
|||||||
Apis.GroupAuthz.userRoles({ params: { userId }, cacheFor: null }),
|
Apis.GroupAuthz.userRoles({ params: { userId }, cacheFor: null }),
|
||||||
]);
|
]);
|
||||||
if (permRes.status === "fulfilled") {
|
if (permRes.status === "fulfilled") {
|
||||||
permissionTree.value = formatPermissionTree((permRes.value as CategoryNode[]) || []);
|
// 加载期间用户可能已点了部门/角色节点切走视图,仅在仍处于该用户视图时回填
|
||||||
|
if (permScope.value.type === "user" && permScope.value.id === userId) {
|
||||||
|
permissionTree.value = formatPermissionTree((permRes.value as CategoryNode[]) || []);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
console.warn(permRes.reason?.message || "加载权限树失败");
|
console.warn(permRes.reason?.message || "加载权限树失败");
|
||||||
permissionTree.value = [];
|
if (permScope.value.type === "user" && permScope.value.id === userId) {
|
||||||
|
permissionTree.value = [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (deptRes.status === "fulfilled") {
|
if (deptRes.status === "fulfilled") {
|
||||||
deptTree.value = formatDeptTree((deptRes.value as GroupNode[]) || []);
|
deptTree.value = formatDeptTree((deptRes.value as GroupNode[]) || []);
|
||||||
@@ -271,6 +300,112 @@ async function onSelectUser(user: UserItem) {
|
|||||||
treeLoading.value = false;
|
treeLoading.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 所属部门树节点点击:右列切换为「该组的组授权」视图(开通池=本组+祖先并集、就近继承;不含任何角色授权)
|
||||||
|
function onDeptNodeClick(node: any) {
|
||||||
|
const id = String(node?.id ?? "");
|
||||||
|
if (!id.startsWith("grp_")) return;
|
||||||
|
const groupId = Number(id.slice(4));
|
||||||
|
if (!groupId || (permScope.value.type === "group" && permScope.value.id === groupId)) return;
|
||||||
|
permScope.value = { type: "group", id: groupId, label: String(node.name || "") };
|
||||||
|
permLoading.value = true;
|
||||||
|
Apis.GroupAuthz.groupPermissionTree({ params: { groupId }, cacheFor: null })
|
||||||
|
.then((cats: any) => {
|
||||||
|
if (permScope.value.type === "group" && permScope.value.id === groupId) {
|
||||||
|
permissionTree.value = formatPermissionTree((cats as CategoryNode[]) || []);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((e: any) => {
|
||||||
|
console.warn(e?.message || "加载组权限树失败");
|
||||||
|
if (permScope.value.type === "group" && permScope.value.id === groupId) {
|
||||||
|
permissionTree.value = [];
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
permLoading.value = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 所属角色树节点点击:右列切换为「该角色的授权模块」视图。
|
||||||
|
// 复用角色授权面板的 RoleModuleTree 回显接口,仅保留勾选(checked)模块,无勾选模块的应用整棵不显示。
|
||||||
|
function onRoleNodeClick(node: any) {
|
||||||
|
const id = String(node?.id ?? "");
|
||||||
|
if (!id.startsWith("role_")) return; // 「平台角色」/应用名分组节点不响应
|
||||||
|
const roleId = Number(id.slice(5));
|
||||||
|
if (!roleId || (permScope.value.type === "role" && permScope.value.id === roleId)) return;
|
||||||
|
permScope.value = { type: "role", id: roleId, label: String(node.name || "") };
|
||||||
|
permLoading.value = true;
|
||||||
|
Promise.all([
|
||||||
|
Apis.AppRole.roleModuleTree({ params: { roleId }, cacheFor: null }),
|
||||||
|
Apis.AppSettings.appList({ params: { pageIndex: 1, pageSize: 9999 }, cacheFor: null }),
|
||||||
|
Apis.AppSettings.appTypeList({ cacheFor: null }),
|
||||||
|
])
|
||||||
|
.then(([apps, appListRes, typeRes]: any[]) => {
|
||||||
|
if (permScope.value.type === "role" && permScope.value.id === roleId) {
|
||||||
|
permissionTree.value = formatRoleAuthzTree(apps, appListRes, typeRes);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((e: any) => {
|
||||||
|
console.warn(e?.message || "加载角色权限树失败");
|
||||||
|
if (permScope.value.type === "role" && permScope.value.id === roleId) {
|
||||||
|
permissionTree.value = [];
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
permLoading.value = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 角色授权模块 → 按应用分类分组(应用无 categoryId 时落「未分类」,与角色授权面板同口径)
|
||||||
|
function formatRoleAuthzTree(apps: any[], appListRes: any, typeRes: any): any[] {
|
||||||
|
const appArr: any[] = appListRes?.content ?? appListRes ?? [];
|
||||||
|
const catMap = new Map<number, number>();
|
||||||
|
appArr.forEach((a: any) => {
|
||||||
|
if (a.id != null) catMap.set(Number(a.id), Number(a.categoryId));
|
||||||
|
});
|
||||||
|
const typeMap = new Map<number, string>();
|
||||||
|
(typeRes ?? []).forEach((t: any) => typeMap.set(Number(t.id), String(t.name || `分类${t.id}`)));
|
||||||
|
|
||||||
|
const groups = new Map<number, any>();
|
||||||
|
for (const app of apps || []) {
|
||||||
|
const children = filterCheckedModules(app.modules || []);
|
||||||
|
if (children.length === 0) continue; // 角色未勾选任何模块的应用不展示
|
||||||
|
const cid = catMap.get(Number(app.id)) ?? -1;
|
||||||
|
if (!groups.has(cid)) {
|
||||||
|
groups.set(cid, { id: cid, name: typeMap.get(cid) || "未分类", open: true, isParent: true, apps: [] });
|
||||||
|
}
|
||||||
|
groups.get(cid).apps.push({
|
||||||
|
id: `app_${app.id}_${cid}`,
|
||||||
|
name: app.name || `应用${app.id}`,
|
||||||
|
open: false, // 与用户视图一致:应用一级默认闭合,点击才展开模块
|
||||||
|
children,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 分类按 id 升序,「未分类」(-1) 沉底;应用沿用接口返回顺序(后端已按 sortNo 排好)
|
||||||
|
return [...groups.values()].sort((a, b) => {
|
||||||
|
if (a.id === -1) return 1;
|
||||||
|
if (b.id === -1) return -1;
|
||||||
|
return a.id - b.id;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 仅保留勾选(checked)的模块;父未勾选但子孙有勾选时保留父节点以维持层级
|
||||||
|
function filterCheckedModules(nodes: any[]): any[] {
|
||||||
|
const out: any[] = [];
|
||||||
|
for (const n of nodes || []) {
|
||||||
|
const children = filterCheckedModules(n.children || []);
|
||||||
|
if (n.checked || children.length > 0) {
|
||||||
|
out.push({
|
||||||
|
id: `mod_${n.id}`,
|
||||||
|
name: n.name || `模块${n.id}`,
|
||||||
|
open: true,
|
||||||
|
children,
|
||||||
|
isParent: (n.children || []).length > 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
// 所属部门:祖先链树,用户直接归属的组加「本组」标记
|
// 所属部门:祖先链树,用户直接归属的组加「本组」标记
|
||||||
function formatDeptTree(nodes: GroupNode[]): any[] {
|
function formatDeptTree(nodes: GroupNode[]): any[] {
|
||||||
return (nodes || []).map((n) => ({
|
return (nodes || []).map((n) => ({
|
||||||
|
|||||||
Reference in New Issue
Block a user