feat(角色管理): 添加数据权限控制功能

- 新增数据权限字段并与菜单权限联动
- 重构菜单权限界面为左右布局
- 添加数据权限与菜单权限的双向绑定逻辑
- 优化菜单选项数据结构,增加数据权限字段
This commit is contained in:
dzq 2025-06-20 08:41:37 +08:00
parent a2a615d18d
commit 977615baed
1 changed files with 98 additions and 23 deletions

View File

@ -34,6 +34,8 @@ const {
const opType = ref<"add" | "update">("add"); const opType = ref<"add" | "update">("add");
const opRow = ref<RoleDTO>(); const opRow = ref<RoleDTO>();
const dataScope = ref<Record<number, string>>({});
(window as any).dataScope = dataScope;
const formData = reactive<AddRoleCommand | UpdateRoleCommand>({ const formData = reactive<AddRoleCommand | UpdateRoleCommand>({
roleId: 0, roleId: 0,
@ -87,6 +89,7 @@ watch(activeTab, async (val) => {
await getRoleInfo("update", opRow.value); await getRoleInfo("update", opRow.value);
Object.assign(formData, opRow.value); Object.assign(formData, opRow.value);
formData.menuIds = opRow.value.selectedMenuList; formData.menuIds = opRow.value.selectedMenuList;
updateDataScope();
}); });
async function getRoleInfo(type: "add" | "update", row?: RoleDTO) { async function getRoleInfo(type: "add" | "update", row?: RoleDTO) {
try { try {
@ -103,7 +106,8 @@ async function getRoleInfo(type: "add" | "update", row?: RoleDTO) {
opType.value = type; opType.value = type;
opRow.value = row; opRow.value = row;
} }
const processedMenuOptions = ref<{ categoryName: string; items: MenuDTO[] }[]>([]); const processedMenuOptions = ref<{ id:number, categoryName: string; items: MenuDTO[]; dataScope: string }[]>([]);
(window as any).processedMenuOptions = processedMenuOptions;
// //
watch(menuTree, (val) => { watch(menuTree, (val) => {
@ -127,8 +131,9 @@ watch(menuTree, (val) => {
// //
if (leaves.length) { if (leaves.length) {
categories.push({ categories.push({
categoryName: menuOption.menuName, // 使 id: menuOption.id,
items: leaves // categoryName: menuOption.menuName,
items: leaves
}); });
} }
@ -154,10 +159,45 @@ watch(menuTree, (val) => {
processedMenuOptions.value = categories; processedMenuOptions.value = categories;
}); });
//
const updateDataScope = () => {
dataScope.value = {};
processedMenuOptions.value.forEach(category => {
const selectedItem = category.items.find(item => item.menuName == '读写' && formData.menuIds.includes(item.id));
const permission = selectedItem ? '1' : '0';
dataScope.value[category.id] = `${category.id}-${permission}`;
});
};
// dataScopemenuIds
watch(
dataScope,
(newDataScope) => {
Object.entries(newDataScope).forEach(([categoryId, scopeValue]) => {
const [id, permission] = scopeValue.split('-');
const category = processedMenuOptions.value.find(cat => cat.id === Number(id));
if (!category) return;
const writeReadItem = category.items.find(item => item.menuName === '读写');
if (!writeReadItem) return;
if (permission === '1') {
if (!formData.menuIds.includes(writeReadItem.id)) {
formData.menuIds.push(writeReadItem.id);
}
} else {
formData.menuIds = formData.menuIds.filter(id => id !== writeReadItem.id);
}
});
},
{ deep: true }
);
async function handleConfirm() { async function handleConfirm() {
try { try {
await formRef.value?.validate(); await formRef.value?.validate();
loading.value = true; loading.value = true;
formData.dataScope = Object.values(dataScope.value).join(',');
console.log("opType", opType.value); console.log("opType", opType.value);
if (opType.value === 'add') { if (opType.value === 'add') {
await addRoleApi(formData as AddRoleCommand); await addRoleApi(formData as AddRoleCommand);
@ -216,27 +256,42 @@ onSearch().then(() => {
statusList[item].label }}</el-radio> statusList[item].label }}</el-radio>
</el-radio-group> </el-radio-group>
</el-form-item> --> </el-form-item> -->
<el-form-item prop="menuIds" class="form-input"> <div style="display: flex;">
<template #label> <div class="form-input-data">
<div class="menu-container">
<div class="menu-label"> <div class="menu-label">
<span>菜单权限</span> <label class="el-form-item__label">数据权限</label>
<!-- <el-checkbox-group v-model="formData.menuIds" class="checkbox-group">
<el-checkbox v-model="formData.menuIds" label="all" class="menu-checkbox">
全部
</el-checkbox>
</el-checkbox-group> -->
</div> </div>
<template v-for="menu in processedMenuOptions" :key="menu.categoryName">
<div v-if="menu.items.some(item => item.menuName === '读写')">
<el-radio-group v-model="dataScope[menu.id]">
<el-radio :key="`${menu.id}-0`" :label="`${menu.id}-0`">只读</el-radio>
<el-radio :key="`${menu.id}-1`" :label="`${menu.id}-1`">读写</el-radio>
</el-radio-group>
</div>
<div v-else class="empty-placeholder"></div>
<div class="empty-box"></div>
<el-divider class="divider" />
</template> </template>
</div>
</div>
<div prop="menuIds" class="form-input" style="flex: 1;">
<div class="menu-container">
<div class="menu-label">
<label class="el-form-item__label">菜单权限</label>
</div>
<el-checkbox-group v-model="formData.menuIds" class="checkbox-group"> <el-checkbox-group v-model="formData.menuIds" class="checkbox-group">
<template v-for="menu in processedMenuOptions" :key="menu.categoryName"> <template v-for="menu in processedMenuOptions" :key="menu.categoryName">
<!-- <span class="menu-category">{{ menu.categoryName }}</span> --> <el-checkbox v-for="item in menu.items.filter(item => item.menuName != '读写')" :key="item.id" :label="item.id" class="menu-checkbox">
<el-checkbox v-for="item in menu.items" :key="item.id" :label="item.id" class="menu-checkbox">
{{ item.menuName }} {{ item.menuName }}
</el-checkbox> </el-checkbox>
<el-divider class="divider" /> <el-divider class="divider" />
</template> </template>
</el-checkbox-group> </el-checkbox-group>
</el-form-item> </div>
</div>
</div>
<!-- <el-form-item prop="remark" label="备注" style="margin-bottom: 0"> <!-- <el-form-item prop="remark" label="备注" style="margin-bottom: 0">
<el-input type="textarea" class="form-input" v-model="formData.remark" /> <el-input type="textarea" class="form-input" v-model="formData.remark" />
</el-form-item> --> </el-form-item> -->
@ -303,6 +358,13 @@ onSearch().then(() => {
flex: 1; flex: 1;
} }
.form-input-data {
margin-left: 50px;
.el-radio {
margin-right: 12px;
}
}
.form-input { .form-input {
width: 40%; width: 40%;
} }
@ -316,4 +378,17 @@ onSearch().then(() => {
margin-top: 20px; margin-top: 20px;
text-align: right; text-align: right;
} }
.menu-container {
border-radius: 4px;
}
.menu-label {
padding-bottom: 8px;
margin-bottom: 10px;
border-bottom: 1px solid #e4e7ed;
}
.empty-placeholder {
height: 32px;
}
</style> </style>