feat: 增加分类权限控制

- 新增角色分类授权模型与超级管理员配置接口

- 接入助手、插件、工作流、知识库、素材的分类可见性过滤

- 增加角色页分类权限树与插件多分类可见性支持
This commit is contained in:
2026-03-29 17:16:37 +08:00
parent aaf4c61ff8
commit f49d94e2fe
46 changed files with 1963 additions and 128 deletions

View File

@@ -9,10 +9,15 @@ import { api } from '#/api/request';
import { $t } from '#/locales';
// 定义组件属性
const props = defineProps({
// 直接传入树数据
data: {
type: Array<any>,
default: undefined,
},
// 获取树数据的URL
dataUrl: {
type: String,
required: true,
default: '',
},
// 已选择的节点数组(支持双向绑定)
modelValue: {
@@ -47,6 +52,10 @@ const props = defineProps({
type: Number,
default: 200,
},
disabled: {
type: Boolean,
default: false,
},
// 是否显示子节点数量
showCount: {
type: Boolean,
@@ -117,27 +126,38 @@ const buildNodeMap = (nodes: any) => {
});
};
function applyTreeData(data: any[]) {
treeData.value = Array.isArray(data) ? data : [];
nodeMap.value.clear();
buildNodeMap(treeData.value);
if (props.modelValue && props.modelValue.length > 0) {
nextTick(() => {
if (treeRef.value) {
treeRef.value.setCheckedKeys(props.modelValue);
}
});
}
}
watch(
() => props.data,
(newData) => {
if (newData !== undefined) {
applyTreeData(newData || []);
}
},
{ immediate: true, deep: true },
);
// 获取树数据
const fetchTreeData = async () => {
if (!props.dataUrl) return;
if (props.data !== undefined || !props.dataUrl) return;
loading.value = true;
try {
const res = await api.get(props.dataUrl);
treeData.value = res.data;
// 构建节点映射
nodeMap.value.clear();
buildNodeMap(res.data);
// 数据加载完成后,如果有选中值则设置
if (props.modelValue && props.modelValue.length > 0) {
nextTick(() => {
if (treeRef.value) {
treeRef.value.setCheckedKeys(props.modelValue);
}
});
}
applyTreeData(res.data);
} catch (error) {
console.error('get data error:', error);
ElMessage.error($t('message.getDataError'));
@@ -189,13 +209,14 @@ defineExpose({
// 组件挂载时获取数据
onMounted(() => {
fetchTreeData();
if (props.data === undefined) {
fetchTreeData();
}
});
</script>
<template>
<div class="tree-select">
<div class="tree-header"></div>
<div class="tree-select" :class="{ 'tree-select--disabled': disabled }">
<div class="tree-wrapper">
<ElTreeV2
ref="treeRef"
@@ -231,32 +252,129 @@ onMounted(() => {
<style scoped>
.tree-select {
width: 100%;
background-color: #fff;
border: 1px solid #e4e7ed;
border-radius: 6px;
}
.tree-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 16px;
background-color: #f8f9fa;
border-bottom: 1px solid #e4e7ed;
overflow: hidden;
background:
linear-gradient(
180deg,
color-mix(in srgb, var(--el-color-primary-light-9) 32%, var(--el-bg-color)) 0%,
var(--el-bg-color) 72%
);
border: 1px solid var(--el-border-color-lighter);
border-radius: 14px;
box-shadow: var(--el-box-shadow-lighter);
}
.tree-wrapper {
padding: 8px;
padding: 10px;
}
.tree-select--disabled {
background:
linear-gradient(
180deg,
var(--el-fill-color-extra-light) 0%,
var(--el-fill-color-blank) 72%
);
}
.tree-node {
display: flex;
gap: 8px;
align-items: center;
gap: 8px;
min-width: 0;
}
.node-label {
overflow: hidden;
color: var(--el-text-color-primary);
font-size: 14px;
font-weight: 500;
text-overflow: ellipsis;
white-space: nowrap;
}
.node-count {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 22px;
height: 20px;
padding: 0 6px;
color: var(--el-text-color-secondary);
font-size: 12px;
color: #909399;
line-height: 1;
background: var(--el-fill-color);
border-radius: 999px;
}
:deep(.el-tree) {
background: transparent;
color: inherit;
}
:deep(.el-tree-node__content) {
height: 38px;
margin-bottom: 4px;
padding: 0 10px;
border-radius: 10px;
transition:
background-color 0.18s ease,
color 0.18s ease;
}
:deep(.el-tree-node__content:hover) {
background: var(--el-fill-color-light);
}
:deep(.el-tree-node:focus > .el-tree-node__content) {
background: var(--el-color-primary-light-9);
}
:deep(.el-tree-node.is-current > .el-tree-node__content) {
background: color-mix(
in srgb,
var(--el-color-primary-light-8) 70%,
var(--el-bg-color)
);
}
:deep(.el-tree-node__expand-icon) {
color: var(--el-text-color-secondary);
}
:deep(.el-checkbox) {
margin-right: 8px;
}
:deep(.el-tree-node__children) {
overflow: visible;
}
:deep(.el-tree-node__children .el-tree-node__content) {
padding-left: 12px;
}
:deep(.el-scrollbar__bar.is-vertical) {
width: 8px;
}
:deep(.el-scrollbar__thumb) {
background: color-mix(
in srgb,
var(--el-text-color-secondary) 22%,
transparent
);
border-radius: 999px;
}
.tree-select--disabled :deep(.el-tree-node__content) {
cursor: not-allowed;
opacity: 0.72;
}
.tree-select--disabled :deep(.el-checkbox),
.tree-select--disabled :deep(.el-tree-node__expand-icon),
.tree-select--disabled :deep(.el-tree-node__content) {
pointer-events: none;
}
</style>