refactor(role): 重构角色管理界面为标签页形式
- 将角色列表从表格改为左侧标签页导航 - 新增角色表单整合到主页面中,移除独立弹窗 - 增加菜单权限分类展示功能 - 优化页面布局和样式
This commit is contained in:
parent
2ca1a758a4
commit
c73260a207
|
@ -1,5 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from "vue";
|
import { computed, ComputedRef, reactive, ref, watch } from "vue";
|
||||||
import { useRole } from "./utils/hook";
|
import { useRole } from "./utils/hook";
|
||||||
import { PureTableBar } from "@/components/RePureTableBar";
|
import { PureTableBar } from "@/components/RePureTableBar";
|
||||||
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
|
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
|
||||||
|
@ -9,9 +9,9 @@ import EditPen from "@iconify-icons/ep/edit-pen";
|
||||||
import Search from "@iconify-icons/ep/search";
|
import Search from "@iconify-icons/ep/search";
|
||||||
import Refresh from "@iconify-icons/ep/refresh";
|
import Refresh from "@iconify-icons/ep/refresh";
|
||||||
import AddFill from "@iconify-icons/ri/add-circle-line";
|
import AddFill from "@iconify-icons/ri/add-circle-line";
|
||||||
import { getRoleInfoApi, RoleDTO } from "@/api/system/role";
|
import { addRoleApi, AddRoleCommand, getRoleInfoApi, RoleDTO, updateRoleApi, UpdateRoleCommand } from "@/api/system/role";
|
||||||
import RoleFormModal from "@/views/system/role/role-form-modal.vue";
|
import { ElMessage, FormRules } from "element-plus";
|
||||||
import { ElMessage } from "element-plus";
|
import { MenuDTO } from "@/api/system/menu";
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: "SystemRole"
|
name: "SystemRole"
|
||||||
|
@ -23,6 +23,7 @@ const {
|
||||||
loading,
|
loading,
|
||||||
columns,
|
columns,
|
||||||
dataList,
|
dataList,
|
||||||
|
activeTab,
|
||||||
pagination,
|
pagination,
|
||||||
onSearch,
|
onSearch,
|
||||||
resetForm,
|
resetForm,
|
||||||
|
@ -32,10 +33,59 @@ const {
|
||||||
} = useRole();
|
} = useRole();
|
||||||
|
|
||||||
const opType = ref<"add" | "update">("add");
|
const opType = ref<"add" | "update">("add");
|
||||||
const modalVisible = ref(false);
|
|
||||||
const opRow = ref<RoleDTO>();
|
const opRow = ref<RoleDTO>();
|
||||||
async function openDialog(type: "add" | "update", row?: RoleDTO) {
|
|
||||||
debugger;
|
const formData = reactive<AddRoleCommand | UpdateRoleCommand>({
|
||||||
|
roleId: 0,
|
||||||
|
dataScope: "",
|
||||||
|
menuIds: [],
|
||||||
|
remark: "",
|
||||||
|
roleKey: "",
|
||||||
|
roleName: "",
|
||||||
|
roleSort: 1,
|
||||||
|
status: "1"
|
||||||
|
});
|
||||||
|
const resetFromData = () => {
|
||||||
|
Object.assign(formData, {
|
||||||
|
roleId: 0,
|
||||||
|
dataScope: "",
|
||||||
|
menuIds: [],
|
||||||
|
remark: "",
|
||||||
|
roleKey: "",
|
||||||
|
roleName: "",
|
||||||
|
roleSort: 1,
|
||||||
|
status: "1"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const rules: FormRules = {
|
||||||
|
roleName: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: "角色名称不能为空"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
roleKey: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: "权限标识不能为空"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
roleSort: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: "角色序号不能为空"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(activeTab, async (val) => {
|
||||||
|
console.log("activeTab", val); // 输出当前选中的标签的 key 或其他标识符
|
||||||
|
opRow.value = dataList.value.find(item => item.roleKey == val);
|
||||||
|
await getRoleInfo("update", opRow.value);
|
||||||
|
Object.assign(formData, opRow.value);
|
||||||
|
formData.menuIds = opRow.value.selectedMenuList;
|
||||||
|
});
|
||||||
|
async function getRoleInfo(type: "add" | "update", row?: RoleDTO) {
|
||||||
try {
|
try {
|
||||||
await getMenuTree();
|
await getMenuTree();
|
||||||
if (row) {
|
if (row) {
|
||||||
|
@ -49,170 +99,150 @@ async function openDialog(type: "add" | "update", row?: RoleDTO) {
|
||||||
}
|
}
|
||||||
opType.value = type;
|
opType.value = type;
|
||||||
opRow.value = row;
|
opRow.value = row;
|
||||||
modalVisible.value = true;
|
|
||||||
}
|
}
|
||||||
|
const processedMenuOptions = ref<{ categoryName: string; items: MenuDTO[] }[]>([]);
|
||||||
|
|
||||||
|
watch(menuTree, (val) => {
|
||||||
|
const categories = [];
|
||||||
|
|
||||||
|
const collectLeaves = (menu: MenuDTO) => {
|
||||||
|
if (!menu.children) return [];
|
||||||
|
return menu.children.filter(child => child.children?.length ? false : true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const collectMenus = (menuOption: MenuDTO) => {
|
||||||
|
const leaves = collectLeaves(menuOption);
|
||||||
|
leaves.unshift(menuOption);
|
||||||
|
if (leaves.length) {
|
||||||
|
categories.push({
|
||||||
|
categoryName: menuOption.menuName,
|
||||||
|
items: leaves
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (menuOption.children?.length) {
|
||||||
|
const lastMenu = menuOption.children.filter(child => child.children?.length);
|
||||||
|
lastMenu.forEach(child => {
|
||||||
|
collectMenus(child);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
val.forEach(menuOption => {
|
||||||
|
collectMenus(menuOption);
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log("categories", categories); // 输出处理后的菜单选项结构,方便调试和查看结构
|
||||||
|
processedMenuOptions.value = categories;
|
||||||
|
});
|
||||||
|
|
||||||
|
async function handleConfirm() {
|
||||||
|
try {
|
||||||
|
await formRef.value?.validate();
|
||||||
|
loading.value = true;
|
||||||
|
if (opType.value === 'add') {
|
||||||
|
await addRoleApi(formData as AddRoleCommand);
|
||||||
|
} else {
|
||||||
|
await updateRoleApi(formData as UpdateRoleCommand);
|
||||||
|
}
|
||||||
|
ElMessage.info("提交成功");
|
||||||
|
onSearch().then(() => {
|
||||||
|
activeTab.value = dataList.value[0].roleKey;
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
ElMessage.error((e as Error)?.message || "提交失败");
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const addRole = async () => {
|
||||||
|
await getRoleInfo("add");
|
||||||
|
activeTab.value = '';
|
||||||
|
resetFromData();
|
||||||
|
}
|
||||||
|
|
||||||
|
getMenuTree();
|
||||||
|
onSearch().then(() => {
|
||||||
|
activeTab.value = dataList.value[0].roleKey;
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="main">
|
<div class="main">
|
||||||
<el-form
|
|
||||||
ref="formRef"
|
<div class="tab-header">
|
||||||
:inline="true"
|
<el-button :icon="useRenderIcon(AddFill)" @click="addRole" style="margin-bottom: 16px">
|
||||||
:model="form"
|
新建角色
|
||||||
class="search-form bg-bg_color w-[99/100] pl-8 pt-[12px]"
|
</el-button>
|
||||||
>
|
<ul class="role-list">
|
||||||
<el-form-item label="角色名称:" prop="name">
|
<li v-for="role in dataList" :key="role.roleKey" :class="{ 'active': activeTab === role.roleKey }"
|
||||||
<el-input
|
@click="activeTab = role.roleKey">
|
||||||
v-model="form.roleName"
|
{{ role.roleName }}
|
||||||
placeholder="请输入角色名称"
|
</li>
|
||||||
clearable
|
</ul>
|
||||||
class="!w-[200px]"
|
</div>
|
||||||
/>
|
<el-form :model="formData" label-width="120px" :rules="rules" ref="formRef">
|
||||||
|
<el-form-item prop="roleName" label="角色名称" required inline-message>
|
||||||
|
<el-input v-model="formData.roleName" class="form-input" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="角色标识:" prop="code">
|
<el-form-item prop="roleKey" label="权限字符" required>
|
||||||
<el-input
|
<el-input v-model="formData.roleKey" class="form-input" />
|
||||||
v-model="form.roleKey"
|
|
||||||
placeholder="请输入角色标识"
|
|
||||||
clearable
|
|
||||||
class="!w-[180px]"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="状态:" prop="status">
|
<!-- <el-form-item prop="roleSort" label="角色顺序" required>
|
||||||
<el-select
|
<el-input-number :min="1" v-model="formData.roleSort" />
|
||||||
v-model="form.status"
|
</el-form-item> -->
|
||||||
placeholder="请选择状态"
|
<!-- <el-form-item prop="status" label="角色状态">
|
||||||
clearable
|
<el-radio-group v-model="formData.status">
|
||||||
class="!w-[180px]"
|
<el-radio v-for="item in Object.keys(statusList)" :key="item" :label="statusList[item].value">{{
|
||||||
>
|
statusList[item].label }}</el-radio>
|
||||||
<el-option label="已启用" value="1" />
|
</el-radio-group>
|
||||||
<el-option label="已停用" value="0" />
|
</el-form-item> -->
|
||||||
</el-select>
|
<el-form-item label="菜单权限" prop="menuIds" class="form-input">
|
||||||
|
<el-checkbox-group v-model="formData.menuIds" class="checkbox-group">
|
||||||
|
<template v-for="menu in processedMenuOptions" :key="menu.categoryName">
|
||||||
|
<span class="menu-category">{{ menu.categoryName }}:</span>
|
||||||
|
<el-checkbox v-for="item in menu.items" :key="item.id" :label="item.id" class="menu-checkbox">
|
||||||
|
{{ item.menuName }}
|
||||||
|
</el-checkbox>
|
||||||
|
<el-divider class="divider" />
|
||||||
|
</template>
|
||||||
|
</el-checkbox-group>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item>
|
<!-- <el-form-item prop="remark" label="备注" style="margin-bottom: 0">
|
||||||
<el-button
|
<el-input type="textarea" class="form-input" v-model="formData.remark" />
|
||||||
type="primary"
|
</el-form-item> -->
|
||||||
:icon="useRenderIcon(Search)"
|
<el-form-item class="form-actions">
|
||||||
:loading="loading"
|
<el-button type="primary" @click="handleConfirm">确认</el-button>
|
||||||
@click="onSearch"
|
|
||||||
>
|
|
||||||
搜索
|
|
||||||
</el-button>
|
|
||||||
<el-button :icon="useRenderIcon(Refresh)" @click="resetForm(formRef)">
|
|
||||||
重置
|
|
||||||
</el-button>
|
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
|
|
||||||
<PureTableBar
|
|
||||||
title="角色列表"
|
|
||||||
:columns="columns"
|
|
||||||
@refresh="onSearch"
|
|
||||||
>
|
|
||||||
<template #buttons>
|
|
||||||
<el-button
|
|
||||||
type="primary"
|
|
||||||
:icon="useRenderIcon(AddFill)"
|
|
||||||
@click="openDialog('add')"
|
|
||||||
>
|
|
||||||
新增角色
|
|
||||||
</el-button>
|
|
||||||
</template>
|
|
||||||
<template v-slot="{ size, dynamicColumns }">
|
|
||||||
<pure-table
|
|
||||||
border
|
|
||||||
align-whole="center"
|
|
||||||
showOverflowTooltip
|
|
||||||
table-layout="auto"
|
|
||||||
:loading="loading"
|
|
||||||
:size="size"
|
|
||||||
adaptive
|
|
||||||
:data="dataList"
|
|
||||||
:columns="dynamicColumns"
|
|
||||||
:pagination="pagination"
|
|
||||||
:paginationSmall="size === 'small' ? true : false"
|
|
||||||
:header-cell-style="{
|
|
||||||
background: 'var(--el-table-row-hover-bg-color)',
|
|
||||||
color: 'var(--el-text-color-primary)'
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<template #operation="{ row }">
|
|
||||||
<el-button
|
|
||||||
class="reset-margin"
|
|
||||||
link
|
|
||||||
type="primary"
|
|
||||||
:size="size"
|
|
||||||
:icon="useRenderIcon(EditPen)"
|
|
||||||
@click="openDialog('update', row)"
|
|
||||||
>
|
|
||||||
修改
|
|
||||||
</el-button>
|
|
||||||
<el-popconfirm
|
|
||||||
:title="`是否确认删除角色名称为${row.roleName}的这条数据`"
|
|
||||||
@confirm="handleDelete(row)"
|
|
||||||
>
|
|
||||||
<template #reference>
|
|
||||||
<el-button
|
|
||||||
class="reset-margin"
|
|
||||||
link
|
|
||||||
type="primary"
|
|
||||||
:size="size"
|
|
||||||
:icon="useRenderIcon(Delete)"
|
|
||||||
>
|
|
||||||
删除
|
|
||||||
</el-button>
|
|
||||||
</template>
|
|
||||||
</el-popconfirm>
|
|
||||||
<!-- <el-dropdown>
|
|
||||||
<el-button
|
|
||||||
class="ml-3 mt-[2px]"
|
|
||||||
link
|
|
||||||
type="primary"
|
|
||||||
:size="size"
|
|
||||||
:icon="useRenderIcon(More)"
|
|
||||||
/>
|
|
||||||
<template #dropdown>
|
|
||||||
<el-dropdown-menu>
|
|
||||||
<el-dropdown-item>
|
|
||||||
<el-button
|
|
||||||
:class="buttonClass"
|
|
||||||
link
|
|
||||||
type="primary"
|
|
||||||
:size="size"
|
|
||||||
:icon="useRenderIcon(Menu)"
|
|
||||||
@click="handleMenu"
|
|
||||||
>
|
|
||||||
菜单权限
|
|
||||||
</el-button>
|
|
||||||
</el-dropdown-item>
|
|
||||||
<el-dropdown-item>
|
|
||||||
<el-button
|
|
||||||
:class="buttonClass"
|
|
||||||
link
|
|
||||||
type="primary"
|
|
||||||
:size="size"
|
|
||||||
:icon="useRenderIcon(Database)"
|
|
||||||
@click="handleDatabase"
|
|
||||||
>
|
|
||||||
数据权限
|
|
||||||
</el-button>
|
|
||||||
</el-dropdown-item>
|
|
||||||
</el-dropdown-menu>
|
|
||||||
</template>
|
|
||||||
</el-dropdown> -->
|
|
||||||
</template>
|
|
||||||
</pure-table>
|
|
||||||
</template>
|
|
||||||
</PureTableBar>
|
|
||||||
|
|
||||||
<role-form-modal
|
|
||||||
v-model="modalVisible"
|
|
||||||
:type="opType"
|
|
||||||
:row="opRow"
|
|
||||||
:menu-options="menuTree"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
.role-list {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
li {
|
||||||
|
width: 200px;
|
||||||
|
padding: 6px 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s;
|
||||||
|
border-left: 3px solid transparent;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 16px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--el-color-primary-light-9);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background-color: var(--el-color-primary-light-8);
|
||||||
|
/* border-left-color: var(--el-color-primary); */
|
||||||
|
color: var(--el-color-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
:deep(.el-dropdown-menu__item i) {
|
:deep(.el-dropdown-menu__item i) {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
@ -222,4 +252,37 @@ async function openDialog(type: "add" | "update", row?: RoleDTO) {
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.main {
|
||||||
|
display: flex;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-header {
|
||||||
|
height: 86vh;
|
||||||
|
padding: 20px 12px 12px 12px;
|
||||||
|
background-color: #FFFFFF;
|
||||||
|
flex: 0 0 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-form {
|
||||||
|
height: 86vh;
|
||||||
|
padding-top: 20px;
|
||||||
|
background-color: #FFFFFF;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input {
|
||||||
|
width: 40%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.divider {
|
||||||
|
margin: 6px 0;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-actions {
|
||||||
|
margin-top: 20px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -135,8 +135,8 @@ const processedMenuOptions = computed(() => {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<v-dialog show-full-screen fixed-body-height use-body-scrolling :title="type === 'add' ? '新增角色' : '更新角色'"
|
<v-dialog fullScreen show-full-screen use-body-scrolling :title="type === 'add' ? '新增角色' : '更新角色'" v-model="visible"
|
||||||
v-model="visible" :loading="loading" @confirm="handleConfirm" @cancel="visible = false" @opened="handleOpened">
|
@cancel="visible = false" @opened="handleOpened" class="">
|
||||||
<el-form :model="formData" label-width="120px" :rules="rules" ref="formRef">
|
<el-form :model="formData" label-width="120px" :rules="rules" ref="formRef">
|
||||||
<el-form-item prop="roleName" label="角色名称" required inline-message>
|
<el-form-item prop="roleName" label="角色名称" required inline-message>
|
||||||
<el-input v-model="formData.roleName" />
|
<el-input v-model="formData.roleName" />
|
||||||
|
@ -144,9 +144,9 @@ const processedMenuOptions = computed(() => {
|
||||||
<el-form-item prop="roleKey" label="权限字符" required>
|
<el-form-item prop="roleKey" label="权限字符" required>
|
||||||
<el-input v-model="formData.roleKey" />
|
<el-input v-model="formData.roleKey" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item prop="roleSort" label="角色顺序" required>
|
<!-- <el-form-item prop="roleSort" label="角色顺序" required>
|
||||||
<el-input-number :min="1" v-model="formData.roleSort" />
|
<el-input-number :min="1" v-model="formData.roleSort" />
|
||||||
</el-form-item>
|
</el-form-item> -->
|
||||||
<el-form-item prop="status" label="角色状态">
|
<el-form-item prop="status" label="角色状态">
|
||||||
<el-radio-group v-model="formData.status">
|
<el-radio-group v-model="formData.status">
|
||||||
<el-radio v-for="item in Object.keys(statusList)" :key="item" :label="statusList[item].value">{{
|
<el-radio v-for="item in Object.keys(statusList)" :key="item" :label="statusList[item].value">{{
|
||||||
|
|
|
@ -10,7 +10,7 @@ import { getMenuListApi, MenuDTO } from "@/api/system/menu";
|
||||||
import { ElMessage, ElMessageBox } from "element-plus";
|
import { ElMessage, ElMessageBox } from "element-plus";
|
||||||
import { usePublicHooks } from "../../hooks";
|
import { usePublicHooks } from "../../hooks";
|
||||||
import { type PaginationProps } from "@pureadmin/table";
|
import { type PaginationProps } from "@pureadmin/table";
|
||||||
import { onMounted, reactive, ref, toRaw } from "vue";
|
import { onMounted, reactive, ref, toRaw, watch } from "vue";
|
||||||
import { toTree } from "@/utils/tree";
|
import { toTree } from "@/utils/tree";
|
||||||
|
|
||||||
export function useRole() {
|
export function useRole() {
|
||||||
|
@ -19,13 +19,14 @@ export function useRole() {
|
||||||
roleName: "",
|
roleName: "",
|
||||||
status: undefined
|
status: undefined
|
||||||
});
|
});
|
||||||
const dataList = ref([]);
|
const dataList = ref<RoleDTO[]>([]);
|
||||||
const loading = ref(true);
|
const loading = ref(true);
|
||||||
const switchLoadMap = ref({});
|
const switchLoadMap = ref({});
|
||||||
const { switchStyle } = usePublicHooks();
|
const { switchStyle } = usePublicHooks();
|
||||||
|
const activeTab = ref('');
|
||||||
const pagination = reactive<PaginationProps>({
|
const pagination = reactive<PaginationProps>({
|
||||||
total: 0,
|
total: 0,
|
||||||
pageSize: 10,
|
pageSize: 100,
|
||||||
currentPage: 1,
|
currentPage: 1,
|
||||||
background: true
|
background: true
|
||||||
});
|
});
|
||||||
|
@ -190,6 +191,7 @@ export function useRole() {
|
||||||
loading,
|
loading,
|
||||||
columns,
|
columns,
|
||||||
dataList,
|
dataList,
|
||||||
|
activeTab,
|
||||||
pagination,
|
pagination,
|
||||||
onSearch,
|
onSearch,
|
||||||
resetForm,
|
resetForm,
|
||||||
|
|
Loading…
Reference in New Issue