306 lines
7.5 KiB
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>
|