门户网站字体/seo外包公司兴田德润官方地址
node+mysql+layui+ejs实现左侧导航菜单动态显示
- 实现思路
- 效果图
- 数据库
- 技术栈
- 代码实现
- main.html(前端首页页面)
- 查询资源菜单方法 js
- app.js配置ejs模板
node入门到入土项目实战开始,前端篇项目适合node小白入门,因为我也是小白来学习node前端的,代码不是很简洁,优雅,各位读者多多包涵一下。
实现思路
账户表中编写一个字段,role_id(字段)用来存储该账户所拥有的相关角色权限,然后创建资源表用来存储相关项目的菜单资源,创建角色权限表用来存储相关的角色权限,创建角色权限资源中间表用来存储每个角色 拥有哪些资源。
账户在登陆界面输入账户相关信息进行登录时查询该数据库中的相关账户是否存在如果存在且登录成功则将该账户的角色id值提取出来进行菜单资源查询,查询成功以后跳转到系统首页,如果该账户角色id为空或者该角色下没有任何资源菜单时跳转至账户授权提示页面。
效果图
数据库
这里用到四个表进行导航资源的动态显示,资源表(tb_resource),角色表(tb_role)
角色资源中间表(tb_rolr_resource),账户表(tb_account)
技术栈
node.js
layui
layui(消息插件notify)
mysql2
ejs
Express
代码实现
main.html(前端首页页面)
<!DOCTYPE html>
<html>
<head><meta charset="utf-8"><title>暖意书栈-首页</title><!-- 设置系统图标 --><link rel="shortcut icon" href="../icon/main.ico" type="image/x-icon" /><meta name="renderer" content="webkit"><meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"><meta name="viewport" content="width=device-width, initial-scale=1"><link href="../layui/css/layui.css" rel="stylesheet"><link href="../css/main.css" rel="stylesheet"></head>
<body>
<div class="layui-layout layui-layout-admin"><div class="layui-header"><div class="layui-logo layui-hide-xs layui-bg-black"><i class="layui-icon layui-icon-read" style="color: #cff60cd3;font-size: 22px;"></i><strong style="font-family: 华文行楷;font-size: 25px; color: #a6b5afd3;">暖意书栈</strong> </div><!-- 头部区域(可配合layui 已有的水平导航) --><ul class="layui-nav layui-layout-left"><!-- 移动端显示 --><li class="layui-nav-item layui-show-xs-inline-block layui-hide-sm" lay-header-event="menuLeft"><i class="layui-icon layui-icon-spread-left"></i></li><li class="layui-nav-item layui-hide-xs"><a href="javascript:;">书籍借阅</a></li><li class="layui-nav-item layui-hide-xs"><a href="javascript:;">座位预约</a></li><li class="layui-nav-item layui-hide-xs"><a href="javascript:;">贴吧</a></li><li class="layui-nav-item"><a href="javascript:;">更多</a><dl class="layui-nav-child"><dd><a href="javascript:;">意见反馈</a></dd><dd><a href="javascript:;">违规处理</a></dd><dd><a href="javascript:;">联系我们</a></dd></dl></li></ul><ul class="layui-nav layui-layout-right"><li class="layui-nav-item layui-hide layui-show-sm-inline-block"><a href="javascript:;"><img src="../image/admin.jpeg" class="layui-nav-img">我的</a><dl class="layui-nav-child"><dd><a href="javascript:;">我的资料</a></dd><dd><a href="javascript:;">我的借阅</a></dd><dd><a href="javascript:;">我的预约</a></dd><dd><a href="javascript:;">安全管理</a></dd><dd><a href="javascript:;">退出登录</a></dd></dl></li><li class="layui-nav-item" lay-header-event="menuRight" lay-unselect><a href="javascript:;"><i class="layui-icon layui-icon-notice"></i> 通知/公告</a></li></ul></div><div class="layui-side layui-bg-black"><div class="layui-side-scroll"><ul class="layui-nav layui-nav-tree" lay-filter="test"><% Object.keys(navItems).forEach(parentId => { %><% if (parentId === "0") { %><% navItems[parentId].forEach(item => { %><li class="layui-nav-item"><a data-id="<%= item.re_id %>"><i class="layui-icon <%= item.re_icon %>"></i><span> <%= item.re_title %></span></a><% if (navItems[item.re_id] && navItems[item.re_id].length > 0) { %><dl class="layui-nav-child"><% navItems[item.re_id].forEach(subItem => { %><dd><a data-id="<%= subItem.re_id %>" data-url="<%= subItem.re_url %>" href="javascript:void(0);"><i class="layui-icon <%= subItem.re_icon %>"></i> <%= subItem.re_title %></a></dd><% }) %></dl><% } %></li><% }) %><% } %><% }) %></ul></div></div><div class="layui-body layui-form"><div class="layui-tab marg0 layui-tab-brief" lay-filter="bodyTab" id="top_tabs_box" lay-allowclose="true"><ul class="layui-tab-title top_tab" id="top_tabs"><li class="layui-this" lay-allowclose="false"><i class="layui-icon layui-icon-home"></i><cite>首页</cite></li></ul><!-- 当前页面操作 --><ul class="layui-nav closeBox"><li class="layui-nav-item"><a href="javascript:;">页面操作</a><dl class="layui-nav-child"><dd><a href="javascript:;" class="refresh refreshThis"><i class="layui-icon layui-icon-refresh-3"></i> 刷新当前</a></dd><dd><a href="javascript:;" class="closePageOther"><i class="layui-icon layui-icon-clear"></i> 关闭其他</a></dd><dd><a href="javascript:;" class="closePageAll"><strong><i class="layui-icon layui-icon-close"></i></strong> 关闭全部</a></dd></dl></li></ul><div class="layui-tab-content clildFrame"><div class="layui-tab-item layui-show"><iframe src="/index"></iframe></div></div></div></div><div class="layui-footer"><!-- 底部固定区域 -->底部固定区域</div>
</div>
<script src="../jquery/jquery-3.7.1.min.js"></script>
<script src="../layui/layui.js"></script>
<script src="../notify/notify.js"></script>
<script>
//JS
layui.use(['element', 'layer', 'util','notify'], function(){var element = layui.element;var layer = layui.layer;var util = layui.util;var $ = layui.$;var notify = layui.notify;// 监听左侧导航的二级菜单点击事件$('.layui-nav .layui-nav-child').on('click', 'a[data-url]', function(e) {e.preventDefault(); // 阻止默认行为,避免页面跳转var $this = $(this),// 获取当前点击的a元素tabTitle = $this.text().trim(),// 获取当前点击的a元素的文本内容tabId = $this.data('id'),// 获取当前点击的a元素的data-id属性tabUrl = $this.data('url');// 获取当前点击的a元素的data-url属性// 检查是否已经有此tabvar hasTab = $('#top_tabs li').filter(function() {return $(this).find('cite').text() === tabTitle;// 使用filter方法筛选出匹配的元素});if (!hasTab.length) {// 如果没有,则添加新的tabelement.tabAdd('bodyTab', {// 调用element.tabAdd方法添加新的tabtitle: '<cite>' + tabTitle + '</cite>',// 设置tab的标题content: '<iframe src="' + tabUrl + '" frameborder="0" scrolling="auto"></iframe>',// 设置tab的内容id: tabId // 设置tab的id});}// 切换到该tabelement.tabChange('bodyTab', tabId);// 调用element.tabChange方法切换到该tab});//点击刷新当前$(".refresh").on("click",function(){ //if($(this).hasClass("refreshThis")){// 判断是否是点击刷新当前$(this).removeClass("refreshThis");// 移除refreshThis类// 获取当前页面的iframe元素,并调用其contentWindow属性的location属性的reload方法刷新页面$(".clildFrame .layui-tab-item.layui-show").find("iframe")[0].contentWindow.location.reload(true);setTimeout(function(){$(".refresh").addClass("refreshThis");// 添加refreshThis类},2000)}else{notify.info({msg:'您点击的速度超过了服务器的响应速度,还是等两秒再刷新吧!',position:'vcenter',shadow:true, closable:false,duration:1500});}});// 当点击 ".closePageOther" 元素时触发此事件处理程序,关闭其他 就是把除了当前窗口意外的其他窗口关闭 首页除外$(".closePageOther").on("click", function () {// 获取当前激活的标签页(即被选中的标签页)var $currentTab = $("#top_tabs li.layui-this"),// 从当前激活的标签页中获取标题文本currentTitle = $currentTab.find("cite").text(),// 从 sessionStorage 中获取名为 "menu" 的数据,并将其解析为 JavaScript 对象// 如果 sessionStorage 中没有 "menu" 数据,则使用空数组menu = JSON.parse(window.sessionStorage.getItem("menu")) || [],// 计算非首页的标签页数量nonHomeTabsCount = $("#top_tabs li:not(.layui-this)").not("[cite='首页']").length;// 如果当前标签页是 "首页" 并且存在其他非首页标签页if (currentTitle === "首页" && nonHomeTabsCount > 0) {// 关闭所有其他非首页标签页,并清空 sessionStorage$("#top_tabs li[lay-id]").not(".layui-this").each(function() {// 获取当前标签页的 "lay-id" 属性值var layId = $(this).attr("lay-id");// 使用 "element.tabDelete" 方法删除当前标签页,并调用 "init" 方法刷新界面element.tabDelete("bodyTab", layId).init();});// 清除 sessionStorage 中的所有数据sessionStorage.clear();} else if (currentTitle !== "首页" && nonHomeTabsCount > 1) { // 如果当前不是首页并且存在其他非首页标签页// 关闭所有其他非当前标签页$("#top_tabs li[lay-id]").not(".layui-this").each(function() {// 获取当前标签页的 "lay-id" 属性值var layId = $(this).attr("lay-id");// 使用 "element.tabDelete" 方法删除当前标签页,并调用 "init" 方法刷新界面element.tabDelete("bodyTab", layId).init();});// 更新 sessionStorage 中的 "menu" 数组,只包含当前标签页的信息// 使用 Array.prototype.filter 方法过滤数组,只保留当前标签页的项menu = menu.filter(item => item.title === currentTitle);// 将更新后的 "menu" 数组保存回 sessionStoragesessionStorage.setItem("menu", JSON.stringify(menu));} else {// 如果只剩下首页和当前页面时,显示提示信息notify.info({msg: '没有可以关闭的窗口了哦!',position: 'vcenter', // 提示信息的位置设置为中心shadow: true, // 是否启用阴影效果closable: false, // 是否允许手动关闭提示信息duration: 1000 // 提示信息显示的持续时间(毫秒)});}// 调用 "tab.tabMove" 方法重新渲染顶部的标签页tab.tabMove();});//关闭全部窗口 只留下 首页$(".closePageAll").on("click",function(){if($("#top_tabs li").length > 1){$("#top_tabs li").each(function(){if($(this).attr("lay-id") != ''){element.tabDelete("bodyTab",$(this).attr("lay-id")).init();window.sessionStorage.removeItem("menu");menu = [];window.sessionStorage.removeItem("curmenu");}})}else{notify.info({msg:'没有可以关闭的窗口了!',position:'vcenter',shadow:true, closable:false,duration:1000});}//渲染顶部窗口tab.tabMove();})
});
</script>
</body>
</html>
查询资源菜单方法 js
// 创建一个对象来保存 roleId
const roleManager = {// 初始化时可以设定一个默认值_roleId: null,// 方法用于设置 roleIdsetRoleId: function (roleId1) {this._roleId = roleId1;},// 方法用于获取 roleIdgetRoleId: function () {return this._roleId;}
};router.get('/main', (req, res) => {// 获取用户角色 IDconst roleId = roleManager.getRoleId();// 使用数据库连接池执行 SQL 查询,获取与角色 ID 关联的所有资源 IDpool.query(userSQL.queryAllResource, [roleId], (err, resourceIds) => {if (err) throw err; // 如果发生错误,则抛出异常// 如果没有找到任何资源 ID,则重定向到指定页面if (resourceIds.length === 0) {return res.redirect('/forbidden'); // 假设这是跳转到的页面}// 将查询结果中的所有资源 ID 提取到一个数组中const resourceIdsList = resourceIds.map(id => id.resource_id);// 构建 SQL 查询字符串,用于查询具体的资源详情const query = `SELECT * FROM tb_resource WHERE re_id IN (${resourceIdsList.join(',')})`;// 使用数据库连接池执行 SQL 查询,获取具体的资源详情pool.query(query, (err, resources) => {if (err) throw err; // 如果发生错误,则抛出异常// 如果查询到的资源为空,则重定向到指定页面if (resources.length === 0) {return res.redirect('/forbidden'); // 假设这是跳转到的页面}// 对查询到的资源进行分组处理,按父资源 ID 分组const groupedResources = groupResourcesByParentId(resources);// 渲染 'main' 视图,并传递分组后的资源作为数据res.render('main', { navItems: groupedResources });});});
});
/*** 将资源按照其父ID分组。* * @param {Array} resources - 包含资源信息的数组,其中每个资源对象都应包含 re_parentId 属性。* @returns {Object} - 返回一个对象,其中键是父ID,值是一个包含具有相同父ID的资源的数组。*/
function groupResourcesByParentId(resources) {// 使用 reduce 函数对资源数组进行处理。// reduce 接收一个回调函数作为参数,该回调函数定义了如何累积结果。// 第一个参数是累加器(accumulator),初始值为空对象 {}。// 第二个参数是当前元素(current)。return resources.reduce((acc, curr) => {// 检查累加器对象中是否存在当前元素的 re_parentId 键。// 如果不存在,则在累加器对象上创建一个新的空数组。if (!acc[curr.re_parentId]) {acc[curr.re_parentId] = [];}// 将当前元素推入与它的 re_parentId 相关联的数组中。acc[curr.re_parentId].push(curr);// 返回累加器对象,以便 reduce 函数继续处理下一个元素。return acc;}, {});
}
数据库查询语句
//查询每个角色拥有的所有资源queryAllResource: 'SELECT resource_id FROM tb_role_resource WHERE role_id = ?',
app.js配置ejs模板
//安装ejs模板
npm install ejs
// 设置模板引擎
app.set('view engine', 'html');
app.set('views',path.join(__dirname, 'views/login'));
// 设置后缀名的文件使用什么模板引擎
app.engine('html', require('ejs').renderFile);
相关文章:

node+mysql+layui+ejs实现左侧导航栏菜单动态显示
nodemysqllayuiejs实现左侧导航菜单动态显示 实现思路效果图数据库技术栈代码实现main.html(前端首页页面)查询资源菜单方法 jsapp.js配置ejs模板 node入门到入土项目实战开始,前端篇项目适合node小白入门,因为我也是小白来学习no…...

FRP配置内网穿透52版本以上适用
简述 适用frp配置内网穿透来说我们需要进行简单的区分,具有公网IP的服务器我们简称为服务端,内网的服务器我们可以简称为客户端,frp需要针对不同的服务器配置不同的文件 下载安装包 Linux下载地址 https://github.com/fatedier/frp/relea…...

IFM易福门LR3000LR3300液位传感器操作说明
IFM易福门LR3000LR3300液位传感器操作说明...

【Python大语言模型系列】基于阿里云人工智能平台采用P-Tuning v2微调ChatGLM2-6B大模型(完整教程)
这是我的第331篇原创文章。 一、引言 P-Tuning 是一种对预训练语言模型进行少量参数微调的技术。所谓预训练语言模型,就是指在大规模的语言数据集上训练好的、能够理解自然语言表达并从中学习语言知识的模型。P-Tuning 所做的就是根据具体的任务,对预训练…...

基于Spring boot + Vue的加油站系统
项目名称:加油站系统 作者的B站地址:程序员云翼的个人空间-程序员云翼个人主页-哔哩哔哩视频 csdn地址:程序员云翼-CSDN博客 1.项目技术栈: 前后端分离的项目 后端:Springboot MybatisPlus 前端:Vue…...

️RPC协议 --基于TCP实现RPC通信
RPC 协议介绍 RPC(Remote Procedure Call,远程过程调用)协议是一种通信协议,允许一个程序调用另一个地址空间(通常是在网络上)的过程或函数,而不需要显式地处理细节如数据序列化和网络通信。它允许开发人员编写分布式应用程序,就像编写本地应用程序一样。 关键特点和组…...

android(安卓)最简单明了解释版本控制之MinSdkVersion、CompileSdkVersion、TargetSdkVersion
1、先明白几个概念 (1)平台版本(Android SDK版本号) 平台版本也就是我们平时说的安卓8、安卓9、安卓10 (2)API级别(API Level) Android 平台提供的框架 API 被称作“API 级别” …...

Redis缓存穿透、击穿和雪崩的理解和解决思路
Redis的缓存穿透 缓存穿透是指那些查询请求所要获取的数据既不在缓存(Redis)中,也不在数据库(例如:MySQL)中,因此每次请求都会直接访问数据库。这种情况通常由以下几种情形引起: 恶…...

ReactHooks(完结)
上期戳here ReactHooks[三] 一.memo 函数1.1 语法格式 二. useMemo2.1 问题引入2.2 语法格式2.3 使用 useMemo 解决刚才的问题 三.useCallback3.1 useMemo和useCallback区别3.2 语法格式 四.useTransition4.1 问题引入4.2 语法格式4.3 使用 isPending 展示加载状态4.4 注意事项…...

【数据中台】大数据管理平台建设方案(原件资料)
建设大数据管理中台,按照统一的数据规范和标准体系,构建统一数据采集﹣治理﹣共享标准、统一技术开发体系、统一接口 API ,实现数据采集、平台治理,业务应用三层解耦,并按照统一标准格式提供高效的…...

UE5+OpenCV配置(Windows11系统)
一、概述 因为需要在UE5中使用OpenCV这些工具进行配置,所以在网络上参考借鉴一些资料进行配置。查询到不少的资料,最后将其配置成功。在这里顺便记录一下自己的配置成功的过程。 二、具体过程 (一)版本 使用Windows11系统、UE5.…...

自研Vue3开源Tree组件:节点拖拽bug修复
当dropType为after,且dropNode为父节点时,bug出现了: bug原因:插入扁平化列表的位置insertIndex计算的不对: 正确的逻辑,同inner要算上子孙节点所占的位置: bug修复!...

SSM学习9:SpringBoot简介、创建项目、配置文件、多环节配置
简介 SpringBoot式用来简化Spring应用的初始搭建以及开发过程的一个框架 项目搭建 File -> New -> Project 选中pom.xml文件,设置为maven项目 项目启动成功 可以访问BasicController中的路径 配置文件 在resources目录下 application.properties 默…...

Java面试题---索引
什么是索引 索引是用来高效获取数据的存储结构如同字典的目录一样,数据库的索引通常使用btree来实现,索引树的节点和数据地址相关联,查询的时候在索引树种进行高效搜索,然后根据数据地址获取数据。索引提高了搜索的效率同时增加了…...

ollama本地部署大语言模型记录
目录 安装Ollama更改模型存放位置 拉取模型GemmaMistralQwen1.5(通义千问)codellama 部署Open webui测试性能知识广度问题1问题2 代码能力总结 最近突然对大语言模型感兴趣 同时在平时的一些线下断网的CTF比赛中,大语言模型也可以作为一个能对话交互的高级知识检索…...

【C++红黑树应用】模拟实现STL中的map与set
目录 🚀 前言一: 🔥 红黑树的修改二: 🔥 红黑树的迭代器 三: 🔥 perator() 与 operator--() 四: 🔥 红黑树相关接口的改造✨ 4.1 Find 函数的改造✨ 4.2 Insert 函数的改…...

前端实习手计(5):班味十足?!
自我感觉没有班味!!!每天还是快快乐乐上班哇,是愉快的一周~这周没有太多活咯,基本就是修修改改改代码学习。真的感觉自己写的代码就是乱七八糟,只要能跑起来有效果就行(我不是合格的处女座哈哈哈…...

Duix AI 太上瘾,让我熬夜体验的AI女友
✨点击这里✨:🚀原文链接:(更好排版、视频播放、社群交流、最新AI开源项目、AI工具分享都在这个公众号!) Duix AI 太上瘾,让我熬夜体验的AI女友 开启 Duix AI 女友的奇妙之旅_ Hi,这…...

php判断某个目录下是否存在文件
/*** 判断字符串是否以什么结尾* param String $haystack 字符串* param String $needle 结尾* return Boolean*/ function endWith($haystack, $needle) {$length strlen($needle);if ($length 0) {return true;}return (substr($haystack, -$length) $needle); } /***…...

重塑互联网生态:探索Web 3.0、大数据与隐私保护的新篇章
引言:互联网的新纪元 随着互联网技术的日新月异,我们正迈入一个全新的时代,其中Web 3.0、大数据以及隐私保护成为塑造未来互联网生态的三大核心力量。它们不仅改变了我们与互联网交互的方式,更深刻地影响着社会的方方面面。 Web…...

HR模块中PA信息类型的相关函数
目录 1、新增、删除,修改:HR_INFOTYPE_OPERATION新增:INS删除:DEL修改:MOD 2、读取PA信息类型:HR_READ_INFOTYPE3、入职,生成新工号用:HR_PAD_HIRE_EMPLOYEE4、加锁:BAPI…...

c# 日期类型变量默认值
DateTime类型是比较常用的变量类型,但是以前处理都比较业余,下面总结2中常用方式 这次把它总结下: DateTime t1 default(DateTime); DateTime t2 DateTime.MinValue; 这样t1,t2 的值都是 {0001/1/1 0:00:00} PS: 由于DateTi…...

设计模式实战:任务调度系统的设计与实现
问题描述 设计一个任务调度系统,支持任务的创建、调度、执行和状态管理。系统需要确保任务的执行过程可以被灵活调度,并且支持任务状态的跟踪和通知功能。 设计分析 命令模式 命令模式用于将请求封装成对象,从而使我们可以用不同的请求、队列或日志来参数化其他对象。任…...

代码中的特殊注释
代码中特殊注释——TODO、FIXME、XXX、HACK_fix me todo hack-CSDN博客 代码中特殊注释——TODO、FIXME、XXX、HACK TODO:英语翻译为待办事项,备忘录。如果代码中有该标识,说明在标识处有功能代码待编写,待实现的功能在说明中会…...

ubuntu20.04.6 安装Skywalking 10.0.1
1.前置准备 1.1. **jdk17(Skywalking10 jdk22不兼容,用17版本即可)**安装: https://blog.csdn.net/CsethCRM/article/details/140768670 1.2. elasticsearch安装: https://blog.csdn.net/CsethCRM/article/details…...

C++:map和set
hello,各位小伙伴,本篇文章跟大家一起学习《C:map和set》,感谢大家对我上一篇的支持,如有什么问题,还请多多指教 ! 如果本篇文章对你有帮助,还请各位点点赞!!…...

深入理解二叉搜索树:定义、操作及平衡二叉树
引言 二叉搜索树(Binary Search Tree,BST)是一种特殊的二叉树结构,每个节点的左子树节点值小于根节点值,而右子树节点值大于根节点值。二叉搜索树在计算机科学中有着广泛的应用,尤其在动态查找表和优先队列…...

vue3组件通信(二)
组件通信 一.$attrs(祖>孙间接)二、$refs()父>子, $parent()子>父三.provide,inject(祖>孙直接)四.pinia五.slot1.默认插槽2.具名插槽3.作用域插槽 一.$attrs(祖>孙间接) $attrs用于实现当前组件的父组…...

关键词查找【Boyer-Moore 算法】
1、【Boyer-Moore 算法】 【算法】哪种算法有分数复杂度?- BoyerMoore字符串匹配_哔哩哔哩_bilibili BM算法的精华就在于BM(text, pattern),也就是BM算法当不匹配的时候一次性可以跳过不止一个字符。即它不需要对被搜索的字符串中的字符进行逐一比较,而…...

【前端手写代码】手写Object.create
思路:将传入的对象作为原型 // 思路:将传入的对象作为原型 function create(obj) {function F() { }F.prototype objreturn new F() }...