Learn.Archives.Web/src/views/menu/index.vue

306 lines
7.5 KiB
Vue

<template>
<div>
<div style="padding-bottom: 5px">
<el-button type="success" v-show="!isAuthorized" @click="() => showDialog(null)">
新增根菜单
</el-button>
<el-button type="primary" v-show="isAuthorized" @click="callBack">
分配权限
</el-button>
</div>
<el-tree
:data="treeData"
:props="treeProps"
:default-checked-keys="defaultCheckedKeys"
node-key="id"
:default-expand-all="true"
:highlight-current="true"
:expand-on-click-node="false"
show-checkbox
ref="treeRef"
:class="isAuthorized ? `menu-tree menu-tree1` : `menu-tree`"
>
<template #default="{ node, data }">
<div v-if="!data.isButton" class="menu-node">
<div class="menu-node">
<i v-if="data.icon" :class="data.icon" class="menu-icon"></i>
<span class="menu-title">{{ data.title }}</span>
<span class="menu-path-Rank" v-if="data.rank">排序[{{ data.rank }}]</span>
<span class="menu-path" v-if="data.path">{{ data.path }}</span>
</div>
<div style="display: flex; gap: 6px" v-show="!isAuthorized">
<el-button
type="success"
link
@click="() => showDialog({ parentId: data.id } as MenuItem)"
>
添加
</el-button>
<el-button type="primary" link @click="() => showDialog(data)">
编辑
</el-button>
<el-button type="danger" link @click.stop="() => delMenu(data.id)">
删除
</el-button>
</div>
</div>
<div v-else class="menu-node" style="background-color: #f7f7f7">
<div>
<i v-if="data.icon" :class="data.icon" class="menu-icon"></i>
<span>菜单按钮权限: </span>
<el-button type="primary">
{{ data.title }}
</el-button>
</div>
<div style="display: flex; gap: 6px" v-show="!isAuthorized">
<el-button type="primary" link @click="() => showDialog(data)">
编辑
</el-button>
<el-button type="danger" link @click.stop="() => delMenu(data.id)">
删除
</el-button>
</div>
</div>
</template>
</el-tree>
<el-dialog
v-if="dialogData.visible"
v-model="dialogData.visible"
title="菜单编辑"
width="800"
:before-close="handleClose"
>
<MenuEdit
@callbackFun="EditCallback"
:info="dialogData.info"
:treeData="treeData"
></MenuEdit>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { MenuAll, MenuItem, Del, RoleMenu, SetMenu } from "@/api/menu";
import { ElMessage, ElMessageBox } from "element-plus";
import MenuEdit from "./edit.vue";
import { ref, computed, onMounted } from "vue";
defineOptions({
name: "Menu",
});
const props = defineProps<{
data: any;
}>();
const isAuthorized = props.data != null && props.data.length > 0;
const treeRef = ref();
// 默认选中的节点ID数组
const defaultCheckedKeys = ref([0]);
/** 显示弹窗 */
const dialogData = ref({
visible: false,
info: null,
});
function showDialog(info: MenuItem) {
dialogData.value.visible = true;
dialogData.value.info = info;
}
async function EditCallback(info: MenuItem) {
await fetchInitData();
dialogData.value.visible = false;
}
const handleClose = (done: () => void) => {
done();
// ElMessageBox.confirm("确定要关闭对话框?")
// .then(() => {
// done();
// })
// .catch(() => {
// // catch error
// });
};
async function callBack() {
// 获取所有完全选中的节点
const checkedNodes = treeRef.value.getCheckedNodes();
// 获取所有半选中的节点(如果需要)
const halfCheckedNodes = treeRef.value.getHalfCheckedNodes();
//提交
SetMenu({
roleId: props.data[0].id,
menuId: checkedNodes
.map((node: MenuItem) => node.id)
.concat(halfCheckedNodes.map((node: MenuItem) => node.id)),
})
.then((res) => {
if (res.code === 200) {
ElMessage.success("分配权限成功");
} else {
ElMessage.error(res.message);
}
})
.catch((error) => {
ElMessage.error("分配权限失败");
});
}
async function delMenu(menuId: number) {
try {
await ElMessageBox.confirm("确定要删除此菜单?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
});
const res = await Del([menuId]);
if (res.code === 200) {
ElMessage.success(res.message);
await fetchInitData();
} else {
ElMessage.error(res.message);
}
} catch (error) {}
}
// 将扁平数据转换为树形结构
const convertToTree = (menus: MenuItem[]): MenuItem[] => {
const menuMap = new Map<number, MenuItem>();
const tree: MenuItem[] = [];
// 创建映射并初始化children
menus.forEach((menu) => {
menuMap.set(menu.id, { ...menu, children: [] });
});
// 构建树形结构
menuMap.forEach((menu) => {
if (menu.parentId === 0) {
tree.push(menu);
} else {
const parent = menuMap.get(menu.parentId);
if (parent) {
parent.children!.push(menu);
}
}
});
// 对同级菜单排序
const sortChildren = (nodes: MenuItem[]) => {
nodes.sort((a, b) => a.rank - b.rank);
nodes.forEach((node) => {
if (node.children && node.children.length > 0) {
sortChildren(node.children);
}
});
};
sortChildren(tree);
return tree;
};
// 树形结构数据
const treeData = ref<MenuItem[]>([]);
// 树组件配置
const treeProps = {
label: "title",
children: "children",
};
async function fetchInitData() {
const flatData = await MenuAll();
if (flatData.code === 200) {
treeData.value = convertToTree(flatData.data);
} else {
ElMessage.error(flatData.message);
}
if (isAuthorized) defaultCheckedKeys.value = (await RoleMenu(props.data[0].id)).data;
}
onMounted(async () => {
await fetchInitData();
});
</script>
<style scoped>
.menu-tree {
padding: 15px;
background: #fff;
border-radius: 4px;
max-height: calc(88vh - 48px - 42px);
overflow-y: auto;
}
.menu-tree1 {
max-height: calc(88vh - 120px);
}
.menu-node {
display: flex;
align-items: center;
width: 100%;
padding: 5px 0;
}
.menu-icon {
margin-right: 8px;
font-size: 16px;
color: #409eff;
}
.menu-title {
font-size: 14px;
margin-right: 10px;
}
.menu-path-Rank {
font-size: 13px;
margin-right: 10px;
color: #909399;
}
.menu-path {
font-size: 12px;
color: #909399;
margin-right: 10px;
flex-grow: 0;
}
.menu-tree {
padding: 20px; /* 增加内边距 */
background: #fff;
border-radius: 4px;
font-size: 16px; /* 增大整体字体 */
}
/* 使用深度选择器修改树节点样式 */
:deep(.el-tree-node) {
margin-bottom: 12px; /* 增加节点间距 */
}
:deep(.el-tree-node__content) {
height: 48px; /* 增加节点高度 */
padding: 10px 0; /* 增加垂直内边距 */
}
.menu-node {
display: flex;
align-items: center;
width: 100%;
padding: 8px 0; /* 增加内边距 */
}
.menu-icon {
margin-right: 12px; /* 增加图标右边距 */
font-size: 20px; /* 增大图标 */
color: #409eff;
}
.menu-title {
font-size: 18px; /* 增大标题字体 */
margin-right: 15px; /* 增加右边距 */
font-weight: 500; /* 加粗字体 */
}
.menu-path {
font-size: 16px; /* 增大路径字体 */
color: #909399;
margin-right: 15px; /* 增加右边距 */
flex-grow: 1;
}
</style>