当前位置: 首页 > news >正文

Nuxt3 全栈作品【通用信息管理系统】菜单管理

最终效果

在这里插入图片描述

前端代码

pages/system/menu.vue

<script lang="ts" setup>
import type Node from "element-plus/es/components/tree/src/model/node";
import { initMenu } from "@/data/menu";
import { SForm } from "#components";// 使用索引签名定义对象类型
type GenericObject = {[key: string]: any;
};
interface Tree {label: string;path: string;icon?: string;noChild: boolean;children?: Tree[];
}const editMenuFormRef = ref<InstanceType<typeof SForm> | null>(null);
const callbackMessage = useCallbackMessage();
const callbackMessage_dialog = useCallbackMessage();
let old_unique = "";
const PageConfig = {//被操作实体的名称entity: "system",api: {//获取列表数据list: "/list",},
};
const action = ref("list");
const id = ref("");
const addType = ref("");
const Model: {[key: string]: any;
} = {label: {label: "菜单名称",require: true,},path: {label: "菜单路径",require: true,},icon: {label: "菜单图标",},noChild: {label: "页面菜单",type: "switch",},
};
const dataSource = ref<GenericObject[]>([]);
let handle_data: GenericObject = {};
const dialogVisible = ref(false);onMounted(() => {getList();
});const getList = async () => {action.value = "list";const res: {[key: string]: any;}[] = await $fetch(`/api/${PageConfig.entity}${PageConfig.api.list}`, {query: {name: "menu",},});if (Array.isArray(res) && res.length > 0) {dataSource.value = JSON.parse(res[0].content);id.value = res[0]._id;} else {try {await $fetch(`/api/system/add`, {body: {name: "menu",content: JSON.stringify(initMenu),},method: "POST",});saveOK();} catch (e: any) {callbackMessage.value = {show: true,valid: false,content: "菜单初始化失败",};}}
};// 按钮--添加(子菜单)
const handle_add = async (data: Tree, node: Tree) => {action.value = "add";addType.value = "addChild";dialogVisible.value = true;old_unique = "";handle_data = data;await nextTick();if (editMenuFormRef.value) {editMenuFormRef.value.localFormData = {};}
};// 按钮-编辑
const handle_edit = async (data: Tree) => {action.value = "edit";addType.value = "";dialogVisible.value = true;old_unique = data.path;handle_data = data;await nextTick();if (editMenuFormRef.value) {editMenuFormRef.value.localFormData = JSON.parse(JSON.stringify(data));}
};// 按钮--删除
const handle_del = (node: Node, data: Tree) => {const parent = node.parent;const children: Tree[] = parent.data.children || parent.data;const index = children.findIndex((d) => d.path === data.path);children.splice(index, 1);dataSource.value = [...dataSource.value];saveToDB();
};// 按钮--添加顶级菜单
const addTop = () => {action.value = "add";addType.value = "addTop";if (editMenuFormRef.value) {editMenuFormRef.value!.localFormData = {};}dialogVisible.value = true;
};// 弹窗按钮--保存
const save = async () => {if (editMenuFormRef.value) {let new_unique = editMenuFormRef.value.localFormData.path;if (old_unique !== new_unique &&hasTargetValueInTree(dataSource.value, "path", new_unique)) {callbackMessage_dialog.value = {show: true,valid: false,content: "菜单路径已存在,请修改后重试",};return;}editMenuFormRef.value.submitForm(editMenuFormRef.value.formRef);}
};// 保存到数据库
const saveToDB = async (formData?: GenericObject) => {let old_dataSource = JSON.parse(JSON.stringify(dataSource.value));if (formData) {if (addType.value === "addTop") {dataSource.value.unshift(formData);}if (addType.value === "addChild") {const newChild = formData;if (!handle_data.children) {handle_data.children = [];}handle_data.children.push(newChild);}if (action.value === "edit") {handle_data.label = formData.label;handle_data.path = formData.path;handle_data.icon = formData.icon;handle_data.noChild = formData.noChild;}}try {await $fetch(`/api/system/edit`, {body: {_id: id.value,name: "menu",content: JSON.stringify(dataSource.value),},method: "POST",});callbackMessage.value = {show: true,valid: true,content: "操作成功",};saveOK();} catch (e: any) {callbackMessage.value = {show: true,valid: false,content: e.data.message,};dataSource.value = JSON.parse(JSON.stringify(old_dataSource));}
};// 保存成功后的回调 -- 刷新菜单
const saveOK = () => {addType.value === "";getList();setTimeout(() => {dialogVisible.value = false;}, 500);
};// 拖拽菜单后,将新菜单保存到数据库
const handle_drop = async () => {saveToDB();
};
</script>
<template><div class="flex flex-col items-center fullParent overflow-auto pt-10"><el-dialog v-model="dialogVisible" width="600" title="编辑菜单"><S-formref="editMenuFormRef"hideHandle:Model="Model":colNum="1":local_save="saveToDB"/><template #footer><div class="dialog-footer"><el-button @click="dialogVisible = false">取消</el-button><el-button type="primary" @click="save"> 保存 </el-button></div></template><S-msgWin top="20%" :msg="callbackMessage_dialog" /></el-dialog><el-button type="primary" @click="addTop"> 添加顶级菜单 </el-button><el-treeclass="mt4"style="min-width: 600px":data="dataSource"node-key="path"accordiondraggabledefault-expand-all:expand-on-click-node="false":indent="40"@node-drop="handle_drop"><template #default="{ node, data }"><div class="custom-tree-node"><divclass="flex items-center gap-2":class="{'font-bold': node.level === 1,'text-4': node.level === 1,}"><S-icon v-if="data.icon" :icon="data.icon" /><div>{{ node.label }}</div></div><div><el-buttontype="primary"link@click="handle_add(data, node)":disabled="data.noChild"><S-icon icon="material-symbols-light:add-rounded" /><span class="ml2">添加</span></el-button><el-buttonclass="ml4"type="primary"link@click="handle_edit(data)"><S-icon icon="line-md:edit-twotone" /><span class="ml2">修改</span></el-button><el-popconfirmtitle="确定删除吗?"@confirm="handle_del(node, data)"><template #reference><el-button class="ml4" type="danger" link><S-icon icon="wpf:delete" /><span class="ml2">删除</span></el-button></template></el-popconfirm></div></div></template></el-tree><S-msgWin top="20%" :msg="callbackMessage" /></div>
</template>
<style scoped>
.custom-tree-node {flex: 1;display: flex;align-items: center;justify-content: space-between;font-size: 14px;padding-right: 8px;
}
.fullParent {position: absolute;top: 0;right: 0;bottom: 0;left: 0;
}
:deep(.el-tree-node) {margin-top: 12px;
}
</style>

初始菜单

data/menu.ts

// 无菜单数据时,初始化菜单
export const initMenu = [{id: "1",label: "用户管理",path: "/user/list",icon: "mdi:user",},{id: "2",label: "个人中心",path: "/me",icon: "tdesign:personal-information-filled",children: [{id: "2-1",label: "修改资料",path: "/me/editInfo",},],},{id: "3",label: "系统管理",path: "/system",icon: "mdi:settings",children: [{id: "3-1",label: "菜单管理",path: "/system/menu",},{id: "3-5",label: "角色管理",path: "/system/role/list",},{id: "3-4",label: "权限管理",path: "/system/permission/list",},{id: "3-2",label: "字典管理",path: "/system/dic/list",},{id: "3-6",label: "部门管理",path: "/system/department",},{id: "3-7",label: "岗位管理",path: "/system/position/list",},{id: "3-3",label: "操作日志",path: "/system/log/list",},],},
];

依赖组件 S-form

https://blog.csdn.net/weixin_41192489/article/details/149717692

依赖组件 S-msgWin

https://blog.csdn.net/weixin_41192489/article/details/149717948

注意事项

  • 删除并没有专门的接口,直接更新菜单的json数据。

接口开发

查询 /api/system/list

见 https://blog.csdn.net/weixin_41192489/article/details/149712067

新增 /api/system/add

server/api/system/add.post.ts

import { systemCreateSchema } from "~/validators/system";
export default defineEventHandler(async (event) => {const result = await runValidate(systemCreateSchema, event);const { name } = result.data;const system = await SystemSchema.findOne({ name: name }).lean();if (system) {throw createError({statusCode: 409,statusMessage: "系统配置已存在,请修改后重试",});}const newData = await SystemSchema.create(result.data);return newData;
});

修改 /api/system/edit

server/api/system/edit.post.ts

import { systemUpdateSchema } from "~/validators/system";
export default defineEventHandler(async (event) => {const result = await runValidate(systemUpdateSchema, event);const newData = SystemSchema.findByIdAndUpdate(result.data._id, result.data, {new: true,});return newData;
});
http://www.lryc.cn/news/603957.html

相关文章:

  • 比肩 7B 表现!Ovis-U1-3B 集多模态理解、图像生成与编辑于一体
  • 《嵌入式C语言笔记(十五):字符串操作与多维指针深度解析》
  • Go进阶:流程控制(if/for/switch)与数组切片
  • ORACLE的用户维护与权限操作
  • 火山方舟使用豆包基模 —— 基础流程
  • 什么是ios企业签名?
  • ROUGE-WE:词向量化革新的文本生成评估框架
  • H.264视频的RTP有效载荷格式(翻译自:RFC6184 第5节 RTP有效载荷格式)
  • 自然语言处理NLP(3)
  • 烟草复杂包装识别准确率↑31%!陌讯多模态SKU检测算法在零售终端的实战解析
  • CMake 完全实战指南:从入门到精通
  • MySQL的JDBC编程
  • Seq2Seq学习笔记
  • 【绘制图像轮廓】——图像预处理(OpenCV)
  • idea运行tomcat日志乱码问题
  • CentOS安装ffmpeg并转码视频为mp4
  • 编程算法在金融、医疗、教育、制造业等领域的落地案例
  • 单片机(STM32-WIFI模块)
  • windows电脑如何截屏 windows电脑截屏教程汇总
  • 【机器学习深度学习】DeepSpeed框架:高效分布式训练的开源利器
  • Python Flask: Windows 2022 server SMB账户(共享盘账户)密码修改
  • 影刀RPA_初级课程_玩转影刀自动化_EXCEL操作自动化
  • 数据结构(5)单链表算法题(中)
  • 第二十二天(数据结构,无头节点的单项链表)
  • 期刊基础学习
  • 抵御酒店管理系统收银终端篡改攻击 API 加密的好处及实现——仙盟创梦IDE
  • 携全双工语音通话大模型亮相WAIC,Soul重塑人机互动新范式
  • BitMart 启动中文品牌“币市”:引领加密资产本地化发展新篇章
  • 【Linux】批量处理多个用户的 sudo 权限问题
  • 01背包问题:Python动态规划深度解析与工程实践