当前位置:   article > 正文

bootstrap后台管理系统前后台实现(含数据库)_bootstrap 后台管理

bootstrap 后台管理

系统版本更新

版本

说明

时间

V1.0.0

菜单管理、角色用户、系统用户管理、权限分配、根据用户角色权限动态加载菜单、密码修改、插件整合等,详见本文说明

20190701

V1.1.0

相对上一版本:

  1. 添加了bootstrap-table插件、实现了对数据表采用此插件进行增删改查的功能;
  2. 添加了layuidate插件,使日期选择功能更完善、UI交互更友好

20190703

V1.2.0

相对上一版本:

1.将项目服务端重构为springboot+mybatis架构;

2.整合wangeditor编辑器;

3.新增两个demo示例方便后续复制粘贴式并最少改动代码方式开发;

4.新增文件管理(普通上传文件与wangeditor编辑器上传与文件访问统一控制器)

20210714

V1.2.1

相对上一版本:
1.添加图片裁剪插件;

2.添加图片压缩处理;

20210720

撰写本文档目的是让后续开发者在理解该系统架构的基础上遵循一定规范保持系统架构的合理性;同时也能够达到允许没有开发经验仅有web基础的入门开发者能够通过复制粘贴的方式仿照demo示例进行开发的目的。

目  录

1       案例调研与选取... 3

1.1       案例调研... 3

1.2       UI选取... 4

2       系统实现... 4

2.1       实现效果... 4

2.2       开发规范... 9

3       详细设计... 10

3.1       框架介绍... 10

3.2       代码说明... 11

3.3       数据库设计... 13

3.4       菜单管理... 13

3.5       权限管理/角色管理... 17

3.6       权限管理/权限分配... 18

3.7       权限管理/用户管理... 19

3.8       Demo示例... 19

3.9       文件管理... 20

4       延伸思考... 20

5       致谢! 20

6       文档说明... 21

7       参考文献... 21

  1. 案例调研与选取
    1. 案例调研

1)easyUI(No1)

图片

2)easyUI(No2)

图片

3)Bootstrap

图片

4)vue/iview

图片

以上四套UI框架对比分析,可以得出如下结论

结论:

easyUI功能完善,界面简洁友好,非常方便后端开发人员开发,是优秀的前端开源框架,尤其适合后台管理,但是非响应式,页面不会随屏幕大小而变化,美观方面(仁者见仁);

bootstrap是响应式开源框架,支持插件广泛,界面相对酷炫,美观方面(有目共睹),同样也非常适合管理平台的开发;

至于Vue和其它一些前端人员自己写的UI框架,必须要承认Vue是不错的开源框架,但是个人感觉对管理平台来说Vue增加了开发人员的学习成本、兼容性、美观等方面的调试成本,开发周期偏长,至于是否适合管理品台开发就……;

综上分析,对于管理平台开发建议选用easyUI或基于bootstrap搭建好的管理平台,本文选择基于bootstrap的管理平台进行说明。

    1. UI选取

经过调研,选取了一套业内好评率较高的UI(ace-master),

Ace是一个基于Twitter bootstrap3开发的后台模板。

GitHub Ace地址:https://github.com/bopoda/ace

Ace演示:http://ace.jeka.by/

主体界面如下:

此框架功能多多,详细功能请看演示。

  1. 系统实现
    1. 实现效果
  1. 登录

2)首页

3)以下是权限管理的具体模块

 

 

    1. 开发规范

下图为框架的三个区域,其中主面板区域采用为一个div(没有采用iframe设计),每个菜单的内容均会更新主面板区域,故在主面板区域内容需要遵循一定的规范:

  1. 建议每个一级菜单在一个包中,每个包中的二级菜单均为一个单独的控制器。建议页面也需按同样方式设计。
  2. 每个页面中的字段必须全局唯一very import

每个页面中的字段可采用驼峰式也可用下划线式,但必须确保每个页面以及下面多级页面中的每个字段名必须不同(即使业务上为同一张表的同一个字段),不可在模态框与主面板区用同一个页面。(如此设计的原因是:由于主页面采用div设计,故为防止js检测异常)

  1. (important)由于主面板区域采用div设计,父页面(main.jsp)中已经有了

<script src="view/manage/assets/js/jquery-2.1.4.min.js"></script>

故子页面面不要引入这个js了(也无需引入),否则会引起密码修改的模态框无法弹出;

如果想要引入的话,请修改密码修改的模态框弹出效果。

命名规范示例:

每个页面中字段命名(包括方法名、表单字段、div等元素的id等):  模块名+页面名+数据库字段名

示例:userManageListAccountName

       或者    user_manage_list_account_name

这样组成了全局唯一字段,防止浏览器缓存导致的页面插件功能异常和表单提交异常等问题;

此问题已经在最新版本中修复

  1. 详细设计
    1. 框架介绍

1)页面布局

页面布局见开发规范中截图,三个主要区域。

2)功能

功能上主要为权限管理和其它的项目相关的开发人员自定义的菜单模块。前后端交互采用ajax技术,页面核心的公共区域的交互在

<script src="view/manage/assets/js-ylz/utils.js"></script> 该文件中封装了菜单url调用和菜单效果切花、主面板区域更新的ajax、菜单树的动态加载、解析、拼装等功能。

3)权限

权限管理中包括用户管理、菜单管理、角色管理、权限分配和密码修改。

4)菜单加载

用户状态为启用时可登录到系统,进入到主板面后根据用户对应角色查找相应的菜单加载到菜单区域,从而实现不同角色的用户能够看到不同的菜单权限。

5)插件

框架中整合的插件主要如下(部分):

Bootstrap-table、bootstrap-datetimepicker、jquery.gritter(右上角弹框插件)、bootstrap-treeview、bootstrap-switch、jQuery.ui(确认框)。

其中datetimepicker样式似乎并没有想象中的好,需要进一步确认原因还是切换其它插件,如果对样式要求不高可临时使用,但考虑到后续扩展,故而建议尽快确认(修复or更换)。

    1. 代码说明

    1. 数据库设计

数据库中权限管理模块设计了用户表、角色表、菜单表、用户角色关联表、角色菜单关联表,共计5张表,sql代码详见数据库文件。

用户表:

角色表:

菜单表:

用户角色关联表:

角色菜单关联表:

    1. 菜单管理

菜单包括主页面菜单区域的菜单也包含“权限管理à菜单管理”中的菜单。

1)主页菜单封装:

菜单表设计,见数据库设计。

菜单最大设计四级菜单,拼装的json格式(下文为大致示例,以实际为准);

读取库中菜单信息,拼接采用何种算法?

方案一:循环遍历,多重循环遍历

方案二:递归

此处选择递归,原因一是效率高(内存需要或许也高,完全可容忍),二是不受限于菜单级别,

递归核心代码如下(详见源码

  1. /**
  2.       * 递归查找子菜单
  3.       * @param id
  4.       * @param rootMenu
  5.       * @return
  6.       */
  7.      private List<Map> getChilds(Long id, List<SysMenu> rootMenu){
  8.          // 子菜单
  9.          List<Map> childList = new ArrayList<Map>();
  10.         
  11.          Iterator<SysMenu> it = rootMenu.iterator();
  12.          while(it.hasNext()) {
  13.               SysMenu menu = it.next();
  14.               // 遍历所有节点,将父菜单id与传过来的id比较
  15.               if (String.valueOf(menu.getLong("menu_pa_id")).equals(String.valueOf(id))){
  16.                    Map<String,Object> mapcc = new HashMap<String,Object>();
  17.                    mapcc.put("id", menu.getLong("id"));
  18.                    mapcc.put("menu_name", menu.getStr("menu_name"));
  19.                    mapcc.put("menu_url", menu.getStr("menu_url"));
  20.                    childList.add(mapcc);
  21.                    it.remove();
  22.               }
  23.          }
  24.          // 把子菜单的子菜单再循环一遍
  25.          for (Map map : childList) {
  26.               //递归
  27.               map.put("childMenus", getChilds(Long.parseLong(map.get("id").toString()), rootMenu));
  28.          }
  29.          //递归退出条件
  30.          if (childList.size() == 0) {
  31.               return null;
  32.          }
  33.          return childList;
  34.      }
  35. 拼接得到json菜单格式为(如下为二级菜单示例,其它详见源码、界面操作):
  36. {
  37.     "status":"200",
  38.     "data":[
  39.         {
  40.             "id":1,
  41.             "childMenus":null,
  42.             "menu_url":"/manage/systemInfo.do",
  43.             "menu_name":"系统信息"
  44.         },
  45.         {
  46.             "id":2,
  47.             "childMenus":[
  48.                 {
  49.                     "id":5,
  50.                     "childMenus":null,
  51.                     "menu_url":"/redis/main.do",
  52.                     "menu_name":"查询redis"
  53.                 },
  54.                 {
  55.                     "id":6,
  56.                     "childMenus":null,
  57.                     "menu_url":"/redis/writeRdisPage.do",
  58.                     "menu_name":"写redis"
  59.                 }
  60.             ],
  61.             "menu_url":null,
  62.             "menu_name":"redis管理"
  63.         },
  64.         {
  65.             "id":3,
  66.             "childMenus":[
  67.                 {
  68.                     "id":7,
  69.                     "childMenus":null,
  70.                     "menu_url":null,
  71.                     "menu_name":"榴莲短视频"
  72.                 },
  73.                 {
  74.                     "id":8,
  75.                     "childMenus":null,
  76.                     "menu_url":"/haohuolab/haohuoPage.do",
  77.                     "menu_name":"东龙实验室"
  78.                 }
  79.             ],
  80.             "menu_url":null,
  81.             "menu_name":"东龙实验室"
  82.         },
  83.         {
  84.             "id":4,
  85.             "childMenus":[
  86.                 {
  87.                     "id":9,
  88.                     "childMenus":null,
  89.                     "menu_url":"/system/users/userPages.do",
  90.                     "menu_name":"用户管理"
  91.                 },
  92.                 {
  93.                     "id":10,
  94.                     "childMenus":null,
  95.                     "menu_url":null,
  96.                     "menu_name":"角色管理"
  97.                 },
  98.                 {
  99.                     "id":11,
  100.                     "childMenus":null,
  101.                     "menu_url":null,
  102.                     "menu_name":"权限分配"
  103.                 },
  104.                 {
  105.                     "id":12,
  106.                     "childMenus":null,
  107.                     "menu_url":null,
  108.                     "menu_name":"菜单管理"
  109.                 },
  110.                 {
  111.                     "id":13,
  112.                     "childMenus":null,
  113.                     "menu_url":"/manage/updatePwdPages.do",
  114.                     "menu_name":"密码修改"
  115.                 }
  116.             ],
  117.             "menu_url":null,
  118.             "menu_name":"权限管理"
  119.         }
  120.     ],
  121.     "msg":"success"
  122. }

2)菜单解析

上面是拼接菜单代码,下面是解析菜单代码:

解析菜单的时候踩了一个坑,就是二级菜单在浏览器端始终无法打开,反复调试到晚上十一二点始终不知道什么原因,第二天来了之后继续调试,最终发现是a标签上缺少了一个属性(class='dropdown-toggle'),有时可能就是很小的一个问题让你耽误很长很长的时间还弄的自己焦头烂额,深感有时阻碍你前进的可能不是那万里征程(搭建整套完善框架),却可能是脚下的一粒沙子。

废话少说,直接上递归核心代码(实际略有改动,详见源码):

  1. /**
  2.  * 递归遍历子树
  3.  * @param list
  4.  */
  5. function mainPageRecurSubTree(ctx,list){
  6.          if(null==list || ""==list || "null"==list){
  7.                    return "";
  8.          }
  9.          var mainPageRecurSubTreeStr = "";
  10.          mainPageRecurSubTreeStr +=
  11.                    "<ul class='submenu'>";
  12.          for(var m=0;m<list.length;m++){
  13.                    var menuSub = list[m];
  14.                    mainPageRecurSubTreeStr +=
  15.                             "<li class='' id='mainPageMenuTreeId"+menuSub.id+"'>"+
  16.                                      "<a onclick=\"javascript:updateMenuTree('mainPageMenuTreeId"+menuSub.id+"','"+ctx+"','"+menuSub.menu_url+"');\" class='dropdown-toggle'>"+
  17.                                                "<i class='menu-icon fa fa-caret-right'></i>"+
  18.                                                menuSub.menu_name;
  19.                                                if(null!=menuSub.childMenus && ""!=menuSub.childMenus && "null"!=menuSub.childMenus){
  20.                                                         mainPageRecurSubTreeStr +=
  21.                                                         "<b class='arrow fa fa-angle-down'></b>";
  22.                                                }
  23.                    mainPageRecurSubTreeStr +=
  24.                                      "</a>"+
  25.                                      "<b class='arrow'></b>"+
  26.                                      mainPageRecurSubTree(ctx,menuSub.childMenus)+
  27.                             "</li>";
  28.          }
  29.          mainPageRecurSubTreeStr +=
  30.                    "</ul>";
  31.          return mainPageRecurSubTreeStr;
  32. }

3)权限管理/菜单管理

菜单插件采用bootstrap-treeview插件,

开关按钮采用bootstrap-switch插件;注意:将所需的开关插件放入到main.jsp中,开关样式方可正常引用,否则如果放到每个功能模块的子页面中会导致插件无法完全加载而导致的样式显示的问题。

菜单管理页面中添加根菜单,也可添加子菜单,选择/修改菜单的上级菜单,修改菜单信息等功能。

    1. 权限管理/角色管理

权限管理à角色管理:

角色管理中的菜单权限修改(修改角色对应的菜单信息):

也采用了bootstrap-TreeView插件,但是此原生的树菜单没有级联选项,故添加级联选择的js,核心代码如下:

  1. //级联选择:选中
  2.                       $("#roleManageUpdateRoleMenuAllTreeMes").on('nodeChecked',function(event,node){
  3.                                      roleManageUpdateRoleMenuNodeChecked(event, node);;
  4.                                  });
  5.                                  //级联选择:取消选中
  6.                                  $("#roleManageUpdateRoleMenuAllTreeMes").on('nodeUnchecked',function(event,node){
  7.                                      roleManageUpdateRoleMenuNodeUnchecked(event, node);;
  8.                                  });
  9. //如下是响应菜单选中和取消选中的操作
  10.               var roleManageUpdateRoleMenuNodeCheckedSilent = false;
  11.               function roleManageUpdateRoleMenuNodeChecked(event, node){
  12.                    if(roleManageUpdateRoleMenuNodeCheckedSilent){
  13.                         return;
  14.                    }
  15.                    roleManageUpdateRoleMenuNodeCheckedSilent = true;
  16.                    roleManageUpdateRoleMenuCheckAllParent(node);
  17.                    roleManageUdpateRoleMenuCheckAllSon(node);
  18.                    roleManageUpdateRoleMenuNodeCheckedSilent = false;
  19.               }
  20.               var roleManageUpdateRoleMenuNodeUncheckedSilent = false;
  21.               function roleManageUpdateRoleMenuNodeUnchecked(event, node){
  22.                    if(roleManageUpdateRoleMenuNodeUncheckedSilent){
  23.                        return;
  24.                    }
  25.                    roleManageUpdateRoleMenuNodeUncheckedSilent = true;
  26.                    roleManageUpdateRoleMenuUncheckAllParent(node);
  27.                    roleManageUpdateRoleMenuUncheckAllSon(node);
  28.                    roleManageUpdateRoleMenuNodeUncheckedSilent = false;
  29.               }
  30.               //选中全部父节点
  31.               function roleManageUpdateRoleMenuCheckAllParent(node){
  32.                    $("#roleManageUpdateRoleMenuAllTreeMes").treeview('checkNode',node.nodeId,{silent:true});
  33.                    var parentNode = $("#roleManageUpdateRoleMenuAllTreeMes").treeview('getParent',node.nodeId);
  34.                    if(!("nodeId" in parentNode)){
  35.                        return;
  36.                    }else{
  37.                        roleManageUpdateRoleMenuCheckAllParent(parentNode);
  38.                    }
  39.               }
  40.               //取消全部父节点
  41.               function roleManageUpdateRoleMenuUncheckAllParent(node){
  42.                    $("#roleManageUpdateRoleMenuAllTreeMes").treeview('uncheckNode',node.nodeId,{silent:true});
  43.                    var siblings = $("#roleManageUpdateRoleMenuAllTreeMes").treeview('getSiblings', node.nodeId);
  44.                    var parentNode = $("#roleManageUpdateRoleMenuAllTreeMes").treeview('getParent',node.nodeId);
  45.                    if(!("nodeId" in parentNode)) {
  46.                        return;
  47.                    }
  48.                    var isAllUnchecked = true//是否全部没选中
  49.                    for(var i in siblings){
  50.                        if(siblings[i].state.checked){
  51.                             isAllUnchecked=false;
  52.                             break;
  53.                        }
  54.                    }
  55.                    if(isAllUnchecked){
  56.                        roleManageUpdateRoleMenuUncheckAllParent(parentNode);
  57.                    }
  58.               }
  59.               //级联选中所有子节点
  60.               function roleManageUdpateRoleMenuCheckAllSon(node){
  61.                    $("#roleManageUpdateRoleMenuAllTreeMes").treeview('checkNode',node.nodeId,{silent:true});
  62.                    if(node.nodes!=null&&node.nodes.length>0){
  63.                        for(var i in node.nodes){
  64.                             roleManageUdpateRoleMenuCheckAllSon(node.nodes[i]);
  65.                        }
  66.                    }
  67.               }
  68.               //级联取消所有子节点
  69.               function roleManageUpdateRoleMenuUncheckAllSon(node){
  70.                    $("#roleManageUpdateRoleMenuAllTreeMes").treeview('uncheckNode',node.nodeId,{silent:true});
  71.                    if(node.nodes!=null&&node.nodes.length>0){
  72.                        for(var i in node.nodes){
  73.                             roleManageUpdateRoleMenuUncheckAllSon(node.nodes[i]);
  74.                        }
  75.                    }
  76.               }

    1. 权限管理/权限分配

权限管理à权限分配:为不同用户分配不同角色。

在权限分配è角色分配  页面中,使用了一个多选框的插件(用于选择多个角色),如下图:

在使用中发现这个选择框第一次打开时正常,之后再打开始终不正常,调试发现是因为select中动态添加了一个div并且这个div的style=“width:0px”;导致select的展示出现问题。试了各种方式,网上搜了各种方案均失败。最终万般无奈之下猜想可能是引用的js文件中生成这个内部div时导致宽度获取的问题,于是便修改引用的js源码,修改如下:

<script src="view/manage/assets/js/chosen.jquery.min.js"></script>

至此问题解决。

问:如何知道要修改这个宽度(如何定位到是要修改这个宽度的)?

答:在浏览器中调试时发现在select中生成的div的id是在select的id的基础之上添加了“_chosen”,于是搜索“_chosen”发现整个js源码中只有一个“_chosen”,于是在此附近寻找width相关的代码,终于发现了并尝试修改成功。

    1. 权限管理/用户管理

权限管理à用户管理:功能为对能够登录到系统的管理用户的增、查、修改、停用操作。

此页面中的table采用bootstrap原生表格,增改查的逻辑完全为自己实现。没有使用bootstrap-table插件。

本系统设计者建议使用bootstrap-table插件(why? 我也不知道,就是感觉成熟开源的业内小有名气的东东一定比自己写的好),插件demo见系统demo示例。

此模块中的用户为系统的管理人员。

    1. 文件管理

此项目将文件全部存储到了域项目同级目录下并提供文件访问的controller,这么设计的好处:1)避免因为项目发布导致文件误删;2)项目迁移同步拷贝文件即可,无需关心机器、地址等问题;

文件结构:

  1. 延伸思考

  1. Iframe为子页面,可不受父页面干扰,但是究竟用还是不用好,有待深思的问题;
  2. bootstrap-switch boostrap的开关按钮插件并未能初始化开或者关状态,此功能关乎开关的美观,需探究如何修改或选用其它插件待进一步深思。

3)系统待优化之处:

权限访问控制该如何优化(内存?redis?)

  1. 致谢!

初级入门:泽宇、老申、昊总

前端顾问:张走召

问题修复:金凤

问题修复:系统参与的全体java同仁

开发者:战神,彭玉,ylz,东龙;

零碎不重要的整理工作(高大上的名字:秘书):本人。

向以上四位开发者致敬!

  1. 文档说明

本文档由四位开发者在开发过程中心得记录,由秘书整理汇总而成。最终解释权归四位开发者所有。

  1. 参考文献

https://blog.csdn.net/weixin_41981080/article/details/81912941

文末福利:

想要与大家一起交流,可进企鹅群:589847567

群共享文件中有源码下载。

说明:该系统由以上四位开发者在工作之余抽时间分工开发完成,因工作较忙,时间有限等原因,系统中难免有瑕疵不足之处需进一步完善,望大家谅解,在后续版本中我们会不断完善。同时诚邀大才雄心的有志之士加入我们共同开发。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Monodyee/article/detail/460397
推荐阅读
相关标签
  

闽ICP备14008679号