node+mysql+layui+ejs实现左侧导航栏菜单动态显示

news2024/9/17 7:35:59

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属性
        
    // 检查是否已经有此tab
    var hasTab = $('#top_tabs li').filter(function() {
      return $(this).find('cite').text() === tabTitle;// 使用filter方法筛选出匹配的元素
    });

    if (!hasTab.length) {
      // 如果没有,则添加新的tab
      element.tabAdd('bodyTab', {// 调用element.tabAdd方法添加新的tab
        title: '<cite>' + tabTitle + '</cite>',// 设置tab的标题
        content: '<iframe src="' + tabUrl + '" frameborder="0" scrolling="auto"></iframe>',// 设置tab的内容
        id: tabId // 设置tab的id
      });
    }
    // 切换到该tab
    element.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" 数组保存回 sessionStorage
        sessionStorage.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,
  
    // 方法用于设置 roleId
    setRoleId: function (roleId1) {
      this._roleId = roleId1;
    },
  
    // 方法用于获取 roleId
    getRoleId: function () {
      return this._roleId;
    }
};




router.get('/main', (req, res) => {
    // 获取用户角色 ID
    const roleId = roleManager.getRoleId();

    // 使用数据库连接池执行 SQL 查询,获取与角色 ID 关联的所有资源 ID
    pool.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);

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1958512.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

机器人笛卡尔空间阻抗控制

机器人笛卡尔空间阻抗控制是一种重要的机器人控制策略,它关注于机器人末端执行器在笛卡尔空间(即任务空间)内的动态特性,以实现与环境的柔顺交互。以下是对机器人笛卡尔空间阻抗控制的详细解释: 一、基本概念 笛卡尔空间:指机器人末端执行器(如手爪、工具等)所处的三维…

Hive之扩展函数(UDF)

Hive之扩展函数(UDF) 1、概念讲解 当所提供的函数无法解决遇到的问题时&#xff0c;我们通常会进行自定义函数&#xff0c;即&#xff1a;扩展函数。Hive的扩展函数可分为三种&#xff1a;UDF,UDTF,UDAF。 UDF&#xff1a;一进一出 UDTF&#xff1a;一进多出 UDAF&#xff1a…

YOLO v8目标检测(三)模型训练与正负样本匹配

YOLO v8目标检测 损失函数理论 在YOLO v5模型中&#xff0c;cls, reg, obj代表的是三个不同的预测组成部分&#xff0c;对应的损失函数如下&#xff1a; cls: 这代表类别预测&#xff08;classification&#xff09;。对应的损失是类别预测损失&#xff08;loss_cls&#xff…

Win10出现错误代码0x80004005 一键修复指南

对于 Windows 10 用户来说&#xff0c;错误代码 0x80004005 就是这样一种迷雾&#xff0c;它可能在不经意间出现&#xff0c;阻碍我们顺畅地使用电脑。这个错误通常与组件或元素的缺失有关&#xff0c;它可能源自注册表的错误、系统文件的损坏&#xff0c;或者是软件的不兼容。…

listener监听

背景: 过滤器代码也可实现接口请求次数统计,但会影响过滤器本意;故在dispatcher servlet层进行监听统计 价值: 所有接口的次数统计可适用于系统全天访问量; 单个请求接口的次数统计可在企业中根据接口次数的高低,可分析出接口对应的功能受用户的喜好程度 请求通过过滤器到了s…

common-intellisense:助力TinyVue 组件书写体验更丝滑

本文由体验技术团队Kagol原创~ 前两天&#xff0c;common-intellisense 开源项目的作者 Simon-He95 在 VueConf 2024 群里发了一个重磅消息&#xff1a; common-intellisense 支持 TinyVue 组件库啦&#xff01; common-intellisense 插件能够提供超级强大的智能提示功能&…

c生万物系列(职责链模式与if_else)

从处理器的角度来说&#xff0c;条件分支会导致指令流水线的中断&#xff0c;所以控制语句需要严格保存状态&#xff0c;因为处理器是很难直接进行逻辑判断的&#xff0c;有可能它会执行一段时间&#xff0c;发现出错后再返回&#xff0c;也有可能通过延时等手段完成控制流的正…

skynet 实操篇

文章目录 概述demo启动文件skynet_start配置文件main.luastart函数thread_workerskynet_context_message_dispatchskynet_mq_popdispatch_message 小结 概述 上一篇写完skynet入门篇&#xff0c;这一篇写点实操性质的。 demo 对于一个开源框架&#xff0c;大部分都有他们自己…

《Linux运维总结:基于x86_64架构CPU使用docker-compose一键离线部署zookeeper 3.8.4容器版分布式集群》

总结&#xff1a;整理不易&#xff0c;如果对你有帮助&#xff0c;可否点赞关注一下&#xff1f; 更多详细内容请参考&#xff1a;《Linux运维篇&#xff1a;Linux系统运维指南》 一、部署背景 由于业务系统的特殊性&#xff0c;我们需要面对不同的客户部署业务系统&#xff0…

C++客户端Qt开发——界面优化(美化登录界面)

美化登录界面 在.ui中拖入一个QFream&#xff0c;顶层窗口的QWidget无法设置背景图片&#xff0c;套上一层QFrame将背景图片设置到QFrame上即可 用布局管理器管理元素&#xff1a;用户名LineEdit&#xff0c;密码LineEdit&#xff0c;记住密码ComboBox&#xff0c;登录Button…

ubuntu2204安装elasticsearch7.17.22

下载安装 wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.17.22-amd64.deb wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.17.22-amd64.deb.sha512 shasum -a 512 -c elasticsearch-7.17.22-amd64.deb.sha512 su…

web、http协议、apache服务、nginx服务

web基本概念和常识 概念 web&#xff1a;为用户提供的一种在互联网上浏览信息的服务&#xff0c;是动态的、可交互的、跨平台的和图形化的&#xff1b; 为用户提供各种互联网服务&#xff0c;这些服务包括浏览服务以及各种交互式服务&#xff0c;包括聊天、购物等&#xff1…

windows下,pyrouge安装教程

1.安装perl 1.1 在命令行&#xff0c;检查perl是否安装 perl-v 1.2 安装perl 下载地址 Strawberry Perl for Windows - Releases 1&#xff09;下载msi版本 2&#xff09;双击安装包&#xff0c;傻瓜式安装&#xff0c;一路next&#xff0c;&#xff08;可修改安装路径&am…

Matlab编程资源库(16)数值微分

一、数值差分与差商 在Matlab中&#xff0c;数值差分与差商是数值分析中常用的概念&#xff0c;尤其在求解微分方程、插值、逼近等领域有广泛应用。下面简要介绍这两个概念及其在Matlab中的实现。 数值差分 数值差分是微分运算的离散化形式&#xff0c;用于近似求解导数。给定…

宠物浮毛空气净化器真的有用吗?性价比高的浮毛空气净化器推荐

作为一位5年资深铲屎官&#xff0c;随着养猫的家庭数量不断增加&#xff0c;轻松撸猫虽然很快乐。然而&#xff0c;宠物的存在也可能引发一些问题&#xff0c;比如宠物的体味和脱落的毛发&#xff0c;这些都可能成为影响家庭健康的隐患。特别是宠物排泄物的气味&#xff0c;如果…

C++11中的右值引用以及移动构造等

目录 一、右值引用 1.左值引用和右值引用 2.左值引用与右值引用比较 3.右值引用使用场景和意义 1️⃣ 传返回值 2️⃣ STL中的应用 4.完美转发 模板中的&& 万能引用&#xff08;引用折叠&#xff09; 二、 新的类功能 1.默认成员函数 2.类成员变量初始化 3.…

【找到字符串中所有字母异位词】python刷题记录

R2-滑动窗口篇 滑动窗口哈希表 和之前那道一样 http://t.csdnimg.cn/dpIbt class Solution:def findAnagrams(self, s: str, p: str) -> List[int]:#返回字典记录了每个字符出现的次数counter1collections.Counter(p)#滑动窗口记录counter2即可ret[]num2len(s)num1len(p…

VIM基础配置

1. CTAGS配置 下载 上传虚拟机&#xff0c;解压&#xff0c;进入目录 tar -xzvf ctags-5.8.tar.gz cd ctags-5.8/编译 ./configure sudo make sudo make install查看是否安装成功 ctags --version打印如下 2. 使用Vundle 下载 git clone https://github.com/VundleVim/Vund…

如何将WordPress文章中的外链图片批量导入到本地

在使用采集软件进行内容创作时&#xff0c;很多文章中的图片都是远程链接&#xff0c;这不仅会导致前端加载速度慢&#xff0c;还会在微信小程序和抖音小程序中添加各种域名&#xff0c;造成管理上的麻烦。特别是遇到没有备案的外链&#xff0c;更是让人头疼。因此&#xff0c;…

2024下《系统架构设计师》案例简答题,刷这些就够了!

2024年软考下半年已经越来越近了&#xff0c;不知道今年备考架构的同学们准备得怎么样了呢&#xff1f; 简答题一直是架构拿分的重点区域&#xff0c;对于许多考生来说&#xff0c;也往往是最具挑战性的部分。今天我就把那些重要的案例简答题类型整理汇总给大家&#xff0c;希望…