博客列表 >laravel实战-通用后台管理系统-菜单管理和角色管理

laravel实战-通用后台管理系统-菜单管理和角色管理

岂几岂几
岂几岂几原创
2020年06月26日 12:53:451453浏览

laravel实战-通用后台管理系统-菜单管理和角色管理

学习心得

  • 控制器部分的编码跟管理员管理很像, 有点机械的写完了.

  • 西门老师说很难理解的递归实现, 自己感觉理解得还行, 就是很难用文字描述出来. 简单粗暴的解释, 就是方法自己调用自己, 但是方法处理的参数变了.

  • 在全选/全消, 本级全选/全消的实现倒花了些时间. jQuery和js方面还得加强学习. 另外, 不需要钻牛角尖, 明明使用自定义属性pid就能轻松实现的功能, 偏偏要用上下级/兄弟选择器/过滤器来实现, 把自己绕晕了.

  • 在参数验证方面, 有些许疑惑. 比如, 想验证管理员账号, 只能是以字母开头, 后续只能接字母, 数字和下划线, 似乎没有现成的方法可以使用, 变量过滤器好像也无法实现. 自己实现, 正则又不会, 且不同的验证, 要写很多验证规则, 也很繁琐. 请教老师, 有没有什么composer插件能简化参数验证的代码编写?

1. 菜单管理

  • 菜单管理基本可以参照管理员管理的编码思路来编码.

  • 根据菜单上下级关系, 设置不同缩进量的伪树形下拉菜单项列表实现

    • 方法实现, 根据传入的父id(从父id=0开始)查找其子菜单项, 对其子菜单项, 再递归调用当前方法, 传入的父id变成子菜单项的id, 一直递归到查找不到子菜单(末级菜单)为止.
  1. /**
  2. * 根据菜单层级关系设置缩进量的伪树形结构下拉菜单项
  3. */
  4. protected function getMenuTree($menus, $pid = 0, &$menuTree = [], $prefix = '      |-- ')
  5. {
  6. // 遍历所有菜单项
  7. foreach ($menus as $menu) {
  8. // 获取当前父id下的所有子选项
  9. if ($menu['pid'] == $pid) {
  10. // 带有缩进量的菜单名
  11. $menu['show_title'] = $prefix . $menu['title'];
  12. // 加入到下拉菜单项数组
  13. $menuTree[] = $menu;
  14. // 下一级子选项, 增加6个空格缩进量
  15. $nextPrefix = str_repeat(' ', 6) . $prefix;
  16. // 递归获取当前菜单项的子菜单
  17. $this->getMenuTree($menus, $menu['mid'], $menuTree, $nextPrefix);
  18. }
  19. }
  20. return $menuTree;
  21. }

2. 角色管理

  • layui监听某个复选框选择事件的方法:

    • 需监听的复选框添加 lay-filter="filter_tag" 属性, 给它添加一个区别于其他复选框的标签.

    • 使用layui的form组件的on方法, 绑定具有标签名为 filter_tag 的复选框的选择事件及其处理脚本方法.

  1. <!-- 其他布局元素... -->
  2. <div class="layui-form-item">
  3. <label for="all_rights" class="layui-form-label">
  4. <input type="checkbox" name="all" id="all" class="layui-input" title="菜单全选" lay-filter="all" lay-skin="primary">
  5. </label>
  6. </div>
  7. <!-- 其他布局元素... -->
  8. <!-- 绑定事件处理方法 -->
  9. <script>
  10. layui.use(['layer', 'form'], function() {
  11. var layer = layui.layer;
  12. var form = layui.form;
  13. $ = layui.jquery;
  14. form.on('checkbox(sel_all)', function(data) {
  15. var val = data.elem.checked;
  16. // 一定要用prop, 用attr会有bug(jquery的bug)
  17. $(data.elem).parent().parent().find('input[name="rights[]"]').prop('checked', val);
  18. form.render('checkbox');
  19. })
  20. // 其他处理逻辑...
  21. }
  22. </script>
  • 全选/全消当前菜单的所有子菜单

    • 把当前菜单项的选择情况, 赋值给其子菜单项即可.

    • 获取当前父菜单的所有子菜单的方法

      • 方法1: 子菜单增加一个自定义属性 pid 值为父id, 使用 $('input[type="checkbox"][pid="父id"]') 获取.
      • 方法2: 根据当前父菜单和子菜单的层级关系, 使用上下文选择器/过滤器获取.
      • 很明显, 方法1更简单优雅. 注意: 赋值语句一定要用 prop('checked', true/false) 方法, 使用 attr('checked', true/false) 会出现只有前两次点击生效, 第三次点击开始无效的bug.
    • 选中/取消选中子菜单时, 判断兄弟菜单项是否全都已选中, 若是, 则把父菜单的选中情况设置为选中; 否则设为取消选中.
      • 思路: 获取所有选中的兄弟复选框, 获取所有兄弟复选框. 如果两者的数量相等, 代表所有兄弟复选框全都已选中, 把父菜单的复选框选中. 否则,把父菜单的复选框取消选中.

3. 代码清单

3.1 菜单管理

  • 视图文件代码

    • 1-菜单列表
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>菜单列表</title>
  7. <link rel="stylesheet" href="/static/plugin/layui/css/layui.css">
  8. <script src="/static/plugin/layui/layui.js"></script>
  9. </head>
  10. <body>
  11. <div style="text-align: center; color: #666;">
  12. <h2>菜单列表</h2>
  13. </div>
  14. @if(!empty($pid))
  15. <div style="float: left; height: 50px; line-height: 50px; padding: 0 10px;">
  16. <button class="layui-btn layui-btn-primary layui-btn-sm" onclick="returnBack({{$dad_menu['pid']}})">返回上一级</button>
  17. </div>
  18. @endif
  19. <div style="float: right; height: 50px; line-height: 50px; padding: 0 10px;">
  20. <button class="layui-btn layui-btn-success layui-btn-sm" onclick="add()">新增</button>
  21. </div>
  22. <table class="layui-table">
  23. <thead>
  24. <tr style="text-align: center">
  25. <th>排序</th>
  26. <th>ID</th>
  27. <th>图标</th>
  28. <th>菜单名称</th>
  29. <th>控制器</th>
  30. <th>方法</th>
  31. <th>是否隐藏</th>
  32. <th>菜单状态</th>
  33. <th>操作</th>
  34. </tr>
  35. </thead>
  36. <tbody>
  37. @csrf
  38. @foreach($menus as $menu)
  39. <tr>
  40. <td>{{$menu['ord']}}</td>
  41. <td>{{$menu['mid']}}</td>
  42. <td>{{$menu['icon']}}</td>
  43. <td>{{$menu['title']}}</td>
  44. <td>{{$menu['controller']}}</td>
  45. <td>{{$menu['action']}}</td>
  46. <td>{{$menu['ishidden'] ? '隐藏' : '显示'}}</td>
  47. <td>{{$menu['status'] == 0 ? '正常' : '禁用'}}</td>
  48. <td>
  49. <button class="layui-btn layui-btn-primary layui-btn-xs" onclick="getSonMenu({{$menu['mid']}})">子菜单</button>
  50. <button class="layui-btn layui-btn-xs" onclick="edit({{$menu['mid']}})">修&nbsp;&nbsp;&nbsp;改</button>
  51. @if($menu['status'] == 0)
  52. <button class="layui-btn layui-btn-danger layui-btn-xs" onclick="delOrResume(1, {{$menu['mid']}}, '{{$menu['title']}}')">禁&nbsp;&nbsp;&nbsp;用</button>
  53. @else
  54. <button class="layui-btn layui-btn-normal layui-btn-xs" onclick="delOrResume(0, {{$menu['mid']}}, '{{$menu['title']}}')">恢&nbsp;&nbsp;&nbsp;复</button>
  55. @endif
  56. </td>
  57. </tr>
  58. @endforeach
  59. </tbody>
  60. </table>
  61. </body>
  62. <script>
  63. layui.use(['layer'], function() {
  64. layer = layui.layer;
  65. $ = layui.jquery;
  66. });
  67. function edit(id) {
  68. layer.open({
  69. type: 2,
  70. title: '修改管理员信息',
  71. shadeClose: false,
  72. // 应该是阴影区域的透明度
  73. shade: 0.8,
  74. // 分别是宽,高
  75. area: ['400px', '560px'],
  76. content: '/admin/menu/edit?mid=' + id,
  77. btn: ["提交"],
  78. yes: function(index, layero) {
  79. var body = layer.getChildFrame('body', index);
  80. // 得到iframe页的窗口对象
  81. var iframeWin = window[layero.find('iframe')[0]['name']];
  82. // 执行iframe页的方法: iframeWin.要调用的方法名();
  83. iframeWin.save();
  84. }
  85. });
  86. }
  87. function getSonMenu(pid) {
  88. window.location.href = "?pid=" + pid;
  89. }
  90. // 返回上一级: 参数是父菜单的父id
  91. function returnBack(ppid) {
  92. window.location.href="?pid=" + ppid;
  93. }
  94. function add() {
  95. layer.open({
  96. type: 2,
  97. title: '添加新菜单',
  98. // 点击弹出窗口外的阴影区域是否关闭弹出窗口
  99. shadeClose: false,
  100. shade: 0.8,
  101. area: ['400px', '560px'],
  102. content: '/admin/menu/add?pid={{empty($pid) ? 0 : $pid}}' //iframe的url
  103. , btn: ['保存']
  104. // 对应按钮区第一个按钮的执行脚本.
  105. , yes: function(index, layero) {
  106. var body = layer.getChildFrame('body', index);
  107. // 得到iframe页的窗口对象
  108. var iframeWin = window[layero.find('iframe')[0]['name']];
  109. // 执行iframe页的方法: iframeWin.要调用的方法名();
  110. iframeWin.save();
  111. }
  112. });
  113. }
  114. function delOrResume(status, id, title) {
  115. var msg = status == 1 ? '禁用' : '恢复';
  116. //询问框
  117. layer.confirm('确定要' + msg + title + '吗?', {
  118. btn: ['确定','取消'] //按钮
  119. }, function(){
  120. var _token = $('input[name="_token"]').val();
  121. $.post('/admin/menu/del_resume', {id: id, resume: status, _token: _token}, function(res) {
  122. if(res.status != undefined && res.status == "0") {
  123. layer.msg(res.message, {icon: 1});
  124. setTimeout(() => {
  125. window.location.reload();
  126. }, 1000);
  127. } else if(res.status != undefined) {
  128. layer.alert(res.message, {icon: 2});
  129. } else {
  130. layer.alert('操作失败', {icon: 2});
  131. }
  132. // layer.alert(res);
  133. }, 'json');
  134. }, function() {
  135. });
  136. }
  137. </script>
  138. </html>
  • 2-增加菜单
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>添加菜单</title>
  7. <link rel="stylesheet" href="/static/plugin/layui/css/layui.css">
  8. <script src="/static/plugin/layui/layui.js"></script>
  9. <style>
  10. body {
  11. padding: 10px;
  12. }
  13. .cms-label {
  14. text-align-last: justify;
  15. }
  16. </style>
  17. </head>
  18. <body>
  19. <div class="layui-form">
  20. @csrf
  21. <div class="layui-form-item">
  22. <label for="pid" class="layui-form-label cms-label">上级菜单</label>
  23. <div class="layui-input-inline">
  24. <select name="pid" id="pid" value="{{empty($pid) ? 0 : $pid}}">
  25. <option value="0" selected>一级菜单</option>
  26. @foreach($menuTree as $menu)
  27. <option value="{{$menu['mid']}}" {{$menu['mid'] == $pid ? 'selected' : ''}}>{!!$menu['show_title']!!}
  28. <option>
  29. @endforeach
  30. </select>
  31. </div>
  32. </div>
  33. <div class="layui-form-item">
  34. <label for="title" class="layui-form-label cms-label">菜单名称</label>
  35. <div class="layui-input-inline">
  36. <input type="text" class="layui-input" name="title" id="title">
  37. </div>
  38. </div>
  39. <div class="layui-form-item">
  40. <label for="controller" class="layui-form-label cms-label">控制器</label>
  41. <div class="layui-input-inline">
  42. <input type="text" class="layui-input" name="controller" id="controller">
  43. </div>
  44. </div>
  45. <div class="layui-form-item">
  46. <label for="action" class="layui-form-label cms-label">方法名</label>
  47. <div class="layui-input-inline">
  48. <input type="text" class="layui-input" name="action" id="action">
  49. </div>
  50. </div>
  51. <div class="layui-form-item">
  52. <label for="ishidden" class="layui-form-label cms-label">是否隐藏</label>
  53. <div class="layui-inpu-inline">
  54. <input type="checkbox" name="ishidden" id="ishidden" title="隐藏">
  55. </div>
  56. </div>
  57. <div class="layui-form-item">
  58. <label for="status" class="layui-form-label cms-label">状态</label>
  59. <div class="layui-inpu-inline">
  60. <input type="checkbox" name="status" id="status" title="禁用">
  61. </div>
  62. </div>
  63. <div class="layui-form-item">
  64. <label for="icon" class="layui-form-label cms-label">菜单图标</label>
  65. <div class="layui-input-inline">
  66. <input type="text" class="layui-input" name="icon" id="icon">
  67. </div>
  68. </div>
  69. <div class="layui-form-item">
  70. <label for="ord" class="layui-form-label cms-label">菜单排序</label>
  71. <div class="layui-input-inline">
  72. <input type="number" name="ord" id="ord" class="layui-input" value="0" min="0">
  73. </div>
  74. </div>
  75. <!-- <div class="layui-form-item">
  76. <div class="layui-input-block">
  77. <button type="button" class="layui-btn" onclick="save()">保存</button>
  78. </div>
  79. </div> -->
  80. </div>
  81. </body>
  82. <script>
  83. layui.use(['layer', 'form'], function() {
  84. layer = layui.layer;
  85. form = layui.form;
  86. $ = layui.jquery;
  87. });
  88. function save() {
  89. // 数据
  90. var data = {
  91. _token: $('input[name="_token"]').val(),
  92. pid: $.trim($('select[name="pid"]').val()),
  93. title: $.trim($('input[name="title"]').val()),
  94. controller: $.trim($('input[name="controller"]').val()),
  95. action: $.trim($('#action').val()),
  96. icon: $.trim($('#icon').val()),
  97. status: $('#status').prop('checked') ? 1 : 0,
  98. ord: $('#ord').val(),
  99. ishidden: $('#ishidden').prop('checked') ? 1 : 0
  100. };
  101. // 判断数据有消息
  102. var res = dataCheck(data);
  103. if(res.status == 1) {
  104. return layer.alert(res.message, {icon: 2});
  105. }
  106. // 保存数据
  107. $.post('/admin/menu/save', data, function(res){
  108. if(res.status != undefined && res.status == '0') {
  109. layer.msg(res.message, {icon: 1});
  110. setTimeout(() => {
  111. window.parent.location.reload();
  112. }, 1000);
  113. } else if(res.status != undefined) {
  114. return layer.alert(res.message, {icon: 2});
  115. } else {
  116. return layer.alert('提交保存失败');
  117. }
  118. }, 'json');
  119. }
  120. function dataCheck(data) {
  121. var res = {
  122. status: 1
  123. };
  124. if(data.pid == undefined || data.pid == '') {
  125. res.message = '上级菜单id不能为空';
  126. return res;
  127. }
  128. if(data.title == undefined || data.title == '') {
  129. res.message = '菜单标题不能为空';
  130. return res;
  131. }
  132. if(data.controller == undefined || data.controller == '') {
  133. res.message = "控制器不能为空";
  134. return res;
  135. }
  136. if(data.ord == undefined || data.ord == '') {
  137. res.message = '排序不能为空';
  138. return res;
  139. }
  140. if(isNaN(data.ord)) {
  141. res.message = "排序必须为数字";
  142. return res;
  143. }
  144. return {
  145. status: 0
  146. };
  147. }
  148. </script>
  149. </html>
  • 3- 修改菜单
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>修改菜单</title>
  7. <link rel="stylesheet" href="/static/plugin/layui/css/layui.css">
  8. <script src="/static/plugin/layui/layui.js"></script>
  9. <style>
  10. body {
  11. padding: 10px;
  12. }
  13. .cms-label {
  14. text-align-last: justify;
  15. }
  16. </style>
  17. </head>
  18. <body>
  19. <div class="layui-form">
  20. @csrf
  21. <input type="hidden" name="mid" id="mid" value="{{$menu['mid']}}">
  22. <div class="layui-form-item">
  23. <label for="pid" class="layui-form-label cms-label">上级菜单</label>
  24. <div class="layui-input-inline">
  25. <select name="pid" id="pid" value="{{$menu['pid']}}">
  26. <option value="0" selected>一级菜单</option>
  27. @foreach($menuTree as $menuT)
  28. <option value="{{$menuT['mid']}}" {{$menu['pid'] == $menuT['mid'] ? 'selected' : ''}}>{!!$menuT['show_title']!!}
  29. <option>
  30. @endforeach
  31. </select>
  32. </div>
  33. </div>
  34. <div class="layui-form-item">
  35. <label for="title" class="layui-form-label cms-label">菜单名称</label>
  36. <div class="layui-input-inline">
  37. <input type="text" class="layui-input" name="title" id="title" value="{{$menu['title']}}">
  38. </div>
  39. </div>
  40. <div class="layui-form-item">
  41. <label for="controller" class="layui-form-label cms-label">控制器</label>
  42. <div class="layui-input-inline">
  43. <input type="text" class="layui-input" name="controller" id="controller" value="{{$menu['controller']}}">
  44. </div>
  45. </div>
  46. <div class="layui-form-item">
  47. <label for="action" class="layui-form-label cms-label">方法名</label>
  48. <div class="layui-input-inline">
  49. <input type="text" class="layui-input" name="action" id="action" value="{{$menu['action']}}">
  50. </div>
  51. </div>
  52. <div class="layui-form-item">
  53. <label for="ishidden" class="layui-form-label cms-label">是否隐藏</label>
  54. <div class="layui-inpu-inline">
  55. <input type="checkbox" name="ishidden" id="ishidden" title="隐藏" {{$menu['ishidden'] ? 'checked' : ''}}>
  56. </div>
  57. </div>
  58. <div class="layui-form-item">
  59. <label for="status" class="layui-form-label cms-label">是否禁用</label>
  60. <div class="layui-inpu-inline">
  61. <input type="checkbox" name="status" id="status" title="禁用" {{$menu['status'] ? 'checked' : ''}}>
  62. </div>
  63. </div>
  64. <div class="layui-form-item">
  65. <label for="icon" class="layui-form-label cms-label">菜单图标</label>
  66. <div class="layui-input-inline">
  67. <input type="text" class="layui-input" name="icon" id="icon" value="{{$menu['icon']}}">
  68. </div>
  69. </div>
  70. <div class="layui-form-item">
  71. <label for="ord" class="layui-form-label cms-label">菜单排序</label>
  72. <div class="layui-input-inline">
  73. <input type="number" name="ord" id="ord" class="layui-input" value="{{$menu['ord']}}" min="0">
  74. </div>
  75. </div>
  76. </div>
  77. </body>
  78. <script>
  79. layui.use(['layer', 'form'], function() {
  80. layer = layui.layer;
  81. form = layui.form;
  82. $ = layui.jquery;
  83. });
  84. function save() {
  85. // 数据
  86. var data = {
  87. _token: $('input[name="_token"]').val(),
  88. mid: $.trim($('input[name="mid"]').val()),
  89. pid: $.trim($('select[name="pid"]').val()),
  90. title: $.trim($('input[name="title"]').val()),
  91. controller: $.trim($('input[name="controller"]').val()),
  92. action: $.trim($('#action').val()),
  93. icon: $.trim($('#icon').val()),
  94. status: $('#status').prop('checked') ? 1 : 0,
  95. ord: $('#ord').val(),
  96. ishidden: $('#ishidden').prop('checked') ? 1 : 0
  97. };
  98. // 判断数据有消息
  99. var res = dataCheck(data);
  100. if(res.status == 1) {
  101. return layer.alert(res.message, {icon: 2});
  102. }
  103. // 保存数据
  104. $.post('/admin/menu/update', data, function(res){
  105. if(res.status != undefined && res.status == '0') {
  106. layer.msg(res.message, {icon: 1});
  107. setTimeout(() => {
  108. window.parent.location.reload();
  109. }, 1000);
  110. } else if(res.status != undefined) {
  111. return layer.alert(res.message, {icon: 2});
  112. } else {
  113. return layer.alert('提交保存失败');
  114. }
  115. }, 'json');
  116. }
  117. // 验证数据有效性
  118. function dataCheck(data) {
  119. var res = {
  120. status: 1
  121. };
  122. if(data.pid == undefined || data.pid == '') {
  123. res.message = '上级菜单id不能为空';
  124. return res;
  125. }
  126. if(data.title == undefined || data.title == '') {
  127. res.message = '菜单标题不能为空';
  128. return res;
  129. }
  130. if(data.controller == undefined || data.controller == '') {
  131. res.message = "控制器不能为空";
  132. return res;
  133. }
  134. if(data.ord == undefined || data.ord == '') {
  135. res.message = '排序不能为空';
  136. return res;
  137. }
  138. if(isNaN(data.ord)) {
  139. res.message = "排序必须为数字";
  140. return res;
  141. }
  142. return {
  143. status: 0
  144. };
  145. }
  146. </script>
  147. </html>
  • 服务端
    • 4- 菜单中间件
  1. <?php
  2. namespace App\Http\Controllers\admins;
  3. use App\Http\Controllers\Controller;
  4. use Illuminate\Http\Request;
  5. use Illuminate\Support\Facades\DB;
  6. use Illuminate\Support\Facades\Validator;
  7. class Menu extends Controller
  8. {
  9. /**
  10. * 当前层级菜单列表
  11. */
  12. public function index(Request $req)
  13. {
  14. // 获取父id, 如果没有传参, 则返回null, 强转null为整型, 则转为0.
  15. $pid = (int) $req->pid;
  16. // 入参的父id对应的0菜单项
  17. $data['menus'] = DB::table('admin_menu')->where('pid', $pid)->orderBy('controller')->orderBy('ord', 'desc')->lists();
  18. // 父菜单
  19. $data['dad_menu'] = DB::table('admin_menu')->where('mid', $pid)->getFirst();
  20. $data['pid'] = $pid;
  21. return view('/admins/menu/index', $data);
  22. }
  23. /**
  24. * 禁用/恢复菜单项
  25. */
  26. public function delOrResume(Request $req)
  27. {
  28. $mid = $req->id;
  29. $status = $req->resume;
  30. // 菜单id有效性判断
  31. if (!filter_var($mid, FILTER_VALIDATE_INT, ['option' => ['min_range' => 1]])) {
  32. return json_decode(['status' => 1, 'message' => '无效的菜单id']);
  33. };
  34. // 1=禁用; 0=启用
  35. if ($status)
  36. $status = 1;
  37. else
  38. $status = 0;
  39. $opera = $status ? '禁用' : '恢复';
  40. // 开启QueryLog
  41. // DB::connection()->enableQueryLog();
  42. // 执行
  43. $res = DB::table('admin_menu')->where('mid', $mid)->update(['status' => $status]);
  44. // 输出sql
  45. // dump(DB::getQueryLog());die;
  46. if ($res) {
  47. return json_encode(['status' => 0, 'message' => $opera . '成功!']);
  48. } else {
  49. return json_encode(['status' => 1, 'message' => $opera . '失败!']);
  50. }
  51. }
  52. /**
  53. * 添加菜单项页面
  54. */
  55. public function add(Request $req)
  56. {
  57. $menus = DB::table('admin_menu')->lists();
  58. // 获取根据层级关系进行缩进的菜单列表
  59. $data['menuTree'] = $this->getMenuTree($menus);
  60. $data['pid'] = $req->pid;
  61. return view('/admins/menu/add', $data);
  62. }
  63. /**
  64. * 新增保存
  65. */
  66. public function save(Request $req)
  67. {
  68. // 要保存的菜单数据
  69. $data = [
  70. 'title' => trim($req->title),
  71. 'controller' => trim($req->controller),
  72. 'action' => trim($req->action),
  73. 'icon' => trim($req->icon),
  74. 'status' => intval(boolval(trim($req->status))),
  75. 'ord' => intval(trim($req->ord)),
  76. 'pid' => intval(trim($req->pid)),
  77. 'ishidden' => intval(boolval(trim($req->ishidden))),
  78. 'status' => intval(trim($req->status))
  79. ];
  80. // 验证数据有效性
  81. $checked = $this->checkInput($data);
  82. if ($checked['status']) {
  83. return json_encode($checked);
  84. }
  85. // 返回插入的记录的id
  86. $res = DB::table('admin_menu')->insertGetId($data);
  87. // 开发人员默认拥有所有权限, 所以把新增的权限加入到开发人员的权限字段中
  88. $group = DB::table('admin_group')->where('gid', 1)->getFirst();
  89. $rights = json_decode($group['rights'], true);
  90. $rights[] = $res;
  91. $group['rights'] = '[' . implode(',', $rights) . ']';
  92. DB::table('admin_group')->where('gid', 1)->update($group);
  93. if ($res) {
  94. return json_encode(['status' => 0, 'message' => '创建成功']);
  95. }
  96. return json_encode(['status' => 1, 'message' => '创建失败']);
  97. }
  98. public function edit(Request $req)
  99. {
  100. $mid = $req->mid;
  101. if (!is_numeric($mid) || $mid < 1) {
  102. return response('无效的菜单ID: ' . $mid, 200);
  103. }
  104. // 查询待编辑的菜单项
  105. $menu = DB::table('admin_menu')->where('mid', $mid)->getFirst();
  106. if (!$menu) {
  107. return response('菜单项不存在', 200);
  108. }
  109. $data['menu'] = $menu;
  110. // 获取有缩进量的菜单下拉列表项
  111. $menus = DB::table('admin_menu')->lists();
  112. $data['menuTree'] = $this->getMenuTree($menus);
  113. return view('/admins/menu/edit', $data);
  114. }
  115. public function update(Request $req)
  116. {
  117. // 要保存的菜单数据
  118. $data = [
  119. 'mid' => trim($req->mid),
  120. 'title' => trim($req->title),
  121. 'controller' => trim($req->controller),
  122. 'action' => trim($req->action),
  123. 'icon' => trim($req->icon),
  124. 'status' => intval(boolval(trim($req->status))),
  125. 'ord' => intval(trim($req->ord)),
  126. 'pid' => intval(trim($req->pid)),
  127. 'ishidden' => intval(boolval(trim($req->ishidden)))
  128. ];
  129. // 验证数据有效性
  130. $checked = $this->checkInput($data, true);
  131. if ($checked['status']) {
  132. return json_encode($checked);
  133. }
  134. $res = DB::table('admin_menu')->where('mid', $data['mid'])->update($data);
  135. if ($res) {
  136. return json_encode(['status' => 0, 'message' => '修改成功']);
  137. }
  138. return json_encode(['status' => 1, 'message' => '修改失败']);
  139. }
  140. /**
  141. * 数据验证方法
  142. */
  143. protected function checkInput($data, $checkId = false)
  144. {
  145. $res = ['status' => 1];
  146. if($checkId) {
  147. if (empty($data['mid'])) {
  148. $res['message'] = '菜单主键不能为空';
  149. return $res;
  150. }
  151. }
  152. if (empty($data['title'])) {
  153. $res['message'] = '菜单标题不能为空';
  154. return $res;
  155. }
  156. if (empty($data['controller'])) {
  157. $res['message'] = '控制器名称不能为空';
  158. return $res;
  159. }
  160. return ['status' => 0];
  161. }
  162. /**
  163. * 根据菜单层级关系设置缩进量的伪树形结构下拉菜单项
  164. */
  165. protected function getMenuTree($menus, $pid = 0, &$menuTree = [], $prefix = '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|--&nbsp;')
  166. {
  167. // 遍历所有菜单项
  168. foreach ($menus as $menu) {
  169. // 获取当前父id下的所有子选项
  170. if ($menu['pid'] == $pid) {
  171. // 带有缩进量的菜单名
  172. $menu['show_title'] = $prefix . $menu['title'];
  173. // 加入到下拉菜单项数组
  174. $menuTree[] = $menu;
  175. // 下一级子选项, 增加6个空格缩进量
  176. $nextPrefix = str_repeat('&nbsp;', 6) . $prefix;
  177. // 递归获取当前菜单项的子菜单
  178. $this->getMenuTree($menus, $menu['mid'], $menuTree, $nextPrefix);
  179. }
  180. }
  181. return $menuTree;
  182. }
  183. }

3.2 用户组管理

  • 视图文件代码

    • 1- 用户组列表
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>角色组列表</title>
  7. <link rel="stylesheet" href="/static/plugin/layui/css/layui.css">
  8. <script src="/static/plugin/layui/layui.js"></script>
  9. </head>
  10. <body>
  11. <div style="text-align: center; color: #666;">
  12. <h2>角色组列表</h2>
  13. </div>
  14. <div style="float: right; height: 50px; line-height: 50px; padding: 0 10px;">
  15. <button class="layui-btn layui-btn-success layui-btn-sm" onclick="add()">新增</button>
  16. </div>
  17. <table class="layui-table">
  18. <thead>
  19. <tr>
  20. <th>ID</th>
  21. <th>角色组名称</th>
  22. <th>授权菜单</th>
  23. <th>操作</th>
  24. </tr>
  25. </thead>
  26. <tbody>
  27. @foreach($groups as $group)
  28. <tr>
  29. <td>{{$group['gid']}}</td>
  30. <td style="min-width: 100px;">{{$group['title']}}</td>
  31. <td>{{$group['right_title']}}</td>
  32. <td style="min-width: 100px;">
  33. <span class="layui-btn layui-btn-success layui-btn-xs" onclick="setRights({{$group['gid']}})">授权</span>
  34. <span class="layui-btn layui-btn-warm layui-btn-xs">停用</span>
  35. </td>
  36. </tr>
  37. @endforeach
  38. </tbody>
  39. </table>
  40. </body>
  41. <script>
  42. layui.use(['layer', 'form'], function() {
  43. layer = layui.layer;
  44. form = layui.form;
  45. $ = layui.jquery;
  46. });
  47. function add() {
  48. layer.open({
  49. type: 2,
  50. title: '添加角色组',
  51. // 点击弹出窗口外的阴影区域是否关闭弹出窗口
  52. shadeClose: false,
  53. shade: 0.8,
  54. area: ['400px', '85%'],
  55. content: '/admin/groups/add' //iframe的url
  56. , btn: ['保存']
  57. , yes: function(index, layero) {
  58. var body = layer.getChildFrame('body', index);
  59. // 得到iframe页的窗口对象
  60. var iframeWin = window[layero.find('iframe')[0]['name']];
  61. // 执行iframe页的方法: iframeWin.要调用的方法名();
  62. iframeWin.save();
  63. }
  64. });
  65. }
  66. function setRights(gid) {
  67. layer.open({
  68. type: 2,
  69. title: '角色组授权',
  70. shadeClose: false,
  71. shade: 0.8,
  72. area: ['400px', '650px'],
  73. content: '/admin/groups/edit?gid=' + gid,
  74. btn: ['提交'],
  75. yes: function(index, layero) {
  76. var body = layer.getChildFrame('body', index);
  77. var iframeWin = window[layero.find('iframe')[0]['name']];
  78. iframeWin.save();
  79. }
  80. });
  81. }
  82. </script>
  83. </html>
  • 2- 修改用户组/用户组授权
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>角色组授权</title>
  7. <link rel="stylesheet" href="/static/plugin/layui/css/layui.css">
  8. <script src="/static/plugin/layui/layui.js"></script>
  9. <style>
  10. body {padding: 10px;}
  11. .sel_all{
  12. padding-top: 0px;
  13. }
  14. </style>
  15. </head>
  16. <body>
  17. <div class="layui-form">
  18. @csrf
  19. <input type="hidden" name="gid" value="{{$group['gid']}}">
  20. <div class="layui-form-item">
  21. <label for="title" class="layui-form-label">角色组名</label>
  22. <div class="layui-input-inline">
  23. <span class="layui-input">{{$group['title']}}</span>
  24. </div>
  25. </div>
  26. <div class="layui-form-item">
  27. <label for="all_rights" class="layui-form-label">
  28. <input type="checkbox" name="all" id="all" class="layui-input" title="菜单全选" lay-filter="all" lay-skin="primary">
  29. </label>
  30. </div>
  31. @foreach($menus as $menu)
  32. <div class="layui-form-item">
  33. <label for="" class="layui-form-label sel_all">
  34. <input type="checkbox" name="sel_all" class="layui-input" lay-skin="primary" title="本级全选" lay-filter="sel_all">
  35. </label>
  36. @if(!empty($menu['child_menus']))
  37. <div class="layui-input-inline">
  38. <input type="checkbox" {{in_array($menu['mid'], $group['rights']) ? 'checked' : ''}} name="rights[]" data-id="{{$menu['mid']}}" title="{{$menu['title']}}" class="layui-input" lay-skin="primary" lay-filter="sel_one">
  39. <br>
  40. @foreach($menu['child_menus'] as $cmenu)
  41. <input type="checkbox" {{in_array($cmenu['mid'], $group['rights']) ? 'checked' : ''}} name="rights[]" data-id="{{$cmenu['mid']}}" title="{{$cmenu['title']}}" class="layui-input" lay-skin="primary" lay-filter="sel_one">
  42. @endforeach
  43. </div>
  44. @endif
  45. </div>
  46. @endforeach
  47. </div>
  48. </body>
  49. <script>
  50. layui.use(['layer', 'form'], function() {
  51. var layer = layui.layer;
  52. var form = layui.form;
  53. $ = layui.jquery;
  54. form.on('checkbox(sel_all)', function(data) {
  55. var val = data.elem.checked;
  56. // 一定要用prop, 用attr会有bug(jquery的bug)
  57. $(data.elem).parent().parent().find('input[name="rights[]"]').prop('checked', val);
  58. form.render('checkbox');
  59. })
  60. // 子菜单项选装事件
  61. form.on('checkbox(sel_one)', function(data) {
  62. // 当前复选框的选中情况
  63. var val = data.elem.checked;
  64. // $(data.elem).attr('checked', val);
  65. // 获取所有选中的兄弟复选框
  66. var checked = $(data.elem).parent().find('input[type="checkbox"]:checked');
  67. // 获取所有兄弟复选框
  68. var allOne = $(data.elem).parent().find('input[type="checkbox"]');
  69. if(checked.length == allOne.length) {
  70. $(data.elem).parent().prev().children().filter('input').prop('checked', true);
  71. } else {
  72. $(data.elem).parent().prev().children().filter('input').prop('checked', false);
  73. }
  74. form.render('checkbox');
  75. });
  76. form.on('checkbox(all)', function(data) {
  77. var val = data.elem.checked;
  78. $('.layui-form-item:nth-child(n+2) input[type="checkbox"]').prop('checked', val);
  79. form.render('checkbox');
  80. });
  81. });
  82. function save() {
  83. var gid = $.trim($('input[name="gid"]').val());
  84. if(gid == "") {
  85. return layer.alert('用户组id出错', {icon: 2});
  86. }
  87. // 把选中的复选框对应的菜单id放入数组中
  88. var rights = [];
  89. $('input[name="rights[]"]:checked').each(function(index, checkbox) {
  90. rights.push($(checkbox).data('id'));
  91. });
  92. if(rights.length < 1) {
  93. return layer.alert('请选择权限', {icon: 2});
  94. }
  95. var _token = $('input[name="_token"]').val();
  96. var data = {
  97. gid: gid,
  98. _token: _token,
  99. // title: title,
  100. rights: rights
  101. };
  102. $.ajax({
  103. url: '/admin/groups/update',
  104. method: 'POST',
  105. data: data,
  106. dataType: 'json',
  107. success: function(res) {
  108. if(res.status != undefined && res.status == 0) {
  109. layer.msg(res.message, {icon: 1});
  110. setTimeout(() => {
  111. window.parent.location.reload();
  112. }, 1000);
  113. } else if(res.status != undefined) {
  114. return layer.alert(res.message, {icon: 2});
  115. } else {
  116. return layer.alert('保存失败', {icon: 2});
  117. }
  118. }
  119. });
  120. }
  121. </script>
  122. </html>
  • 3- 新增用户组
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>新增角色组</title>
  7. <link rel="stylesheet" href="/static/plugin/layui/css/layui.css">
  8. <script src="/static/plugin/layui/layui.js"></script>
  9. <style>
  10. body {padding: 10px;}
  11. .sel_all{
  12. padding-top: 0px;
  13. }
  14. </style>
  15. </head>
  16. <body>
  17. <div class="layui-form">
  18. @csrf
  19. <div class="layui-form-item">
  20. <label for="title" class="layui-form-label">角色组名</label>
  21. <div class="layui-input-inline">
  22. <input type="text" class="layui-input" name="title" id="title">
  23. </div>
  24. </div>
  25. <div class="layui-form-item">
  26. <label for="all_rights" class="layui-form-label">
  27. <input type="checkbox" name="all" id="all" class="layui-input" title="菜单全选" lay-filter="all" lay-skin="primary">
  28. </label>
  29. </div>
  30. @foreach($menus as $menu)
  31. <div class="layui-form-item">
  32. <label for="" class="layui-form-label sel_all">
  33. <input type="checkbox" name="sel_all" class="layui-input" lay-skin="primary" title="本级全选" lay-filter="sel_all">
  34. </label>
  35. @if(!empty($menu['child_menus']))
  36. <div class="layui-input-inline">
  37. <input type="checkbox" name="rights[]" data-id="{{$menu['mid']}}" title="{{$menu['title']}}" class="layui-input" lay-skin="primary" lay-filter="sel_one">
  38. <br>
  39. @foreach($menu['child_menus'] as $cmenu)
  40. <input type="checkbox" name="rights[]" data-id="{{$cmenu['mid']}}" title="{{$cmenu['title']}}" class="layui-input" lay-skin="primary" lay-filter="sel_one">
  41. @endforeach
  42. </div>
  43. @endif
  44. </div>
  45. @endforeach
  46. </div>
  47. </body>
  48. <script>
  49. layui.use(['layer', 'form'], function() {
  50. var layer = layui.layer;
  51. var form = layui.form;
  52. $ = layui.jquery;
  53. // 本级所有子菜单全选/全消
  54. form.on('checkbox(sel_all)', function(data) {
  55. var val = data.elem.checked;
  56. // 一定要用prop, 用attr会有bug(jquery的bug)
  57. $(data.elem).parent().parent().find('input[name="rights[]"]').prop('checked', val);
  58. form.render('checkbox');
  59. })
  60. // 子菜单选中/取消选中
  61. form.on('checkbox(sel_one)', function(data) {
  62. var val = data.elem.checked;
  63. // $(data.elem).attr('checked', val);
  64. var notChecked = $(data.elem).parent().find('input[type="checkbox"]:checked');
  65. var allOne = $(data.elem).parent().find('input[type="checkbox"]');
  66. if(notChecked.length == allOne.length) {
  67. $(data.elem).parent().prev().children().filter('input').prop('checked', true);
  68. } else {
  69. $(data.elem).parent().prev().children().filter('input').prop('checked', false);
  70. }
  71. form.render('checkbox');
  72. });
  73. // 全选/全消所有菜单
  74. form.on('checkbox(all)', function(data) {
  75. var val = data.elem.checked;
  76. $('.layui-form-item:nth-child(n+2) input[type="checkbox"]').prop('checked', val);
  77. form.render('checkbox');
  78. });
  79. });
  80. function save() {
  81. // 用户组名称
  82. var title = $.trim($('input[name="title"]').val());
  83. if(title == undefined || title == "") {
  84. return layer.alert('请输入用户组名称', {icon: 2});
  85. }
  86. // 获得授权的权限id数组
  87. var rights = [];
  88. $('input[name="rights[]"]:checked').each(function(index, checkbox) {
  89. rights.push($(checkbox).data('id'));
  90. });
  91. if(rights.length < 1) {
  92. return layer.alert('请选择权限', {icon: 2});
  93. }
  94. // token不能漏
  95. var _token = $('input[name="_token"]').val();
  96. var data = {
  97. _token: _token,
  98. title: title,
  99. rights: rights
  100. };
  101. // 提交请求
  102. $.ajax({
  103. url: '/admin/groups/save',
  104. method: 'POST',
  105. data: data,
  106. dataType: 'json',
  107. success: function(res) {
  108. if(res.status != undefined && res.status == 0) {
  109. layer.msg(res.message, {icon: 1});
  110. setTimeout(() => {
  111. window.parent.location.reload();
  112. }, 1000);
  113. } else if(res.status != undefined) {
  114. return layer.alert(res.message, {icon: 2});
  115. } else {
  116. return layer.alert('保存失败', {icon: 2});
  117. }
  118. }
  119. });
  120. }
  121. </script>
  122. </html>

服务端代码

  • 1- 用户组控制器
  1. <?php
  2. namespace App\Http\Controllers\admins;
  3. use App\Http\Controllers\Controller;
  4. use Illuminate\Support\Facades\DB;
  5. use Illuminate\Http\Request;
  6. class Groups extends Controller {
  7. public function index() {
  8. // 角色列表
  9. $groups = DB::table('admin_group')->lists();
  10. // 菜单列表
  11. $menus = DB::table('admin_menu')->where('status', 0)->orderBy('pid')->orderBy('mid')->keyval('mid', 'title');
  12. foreach($groups as &$group) {
  13. // 把json格式的字符串转为菜单id数组
  14. $rights = json_decode($group['rights'], true);
  15. // 拼接已授权的菜单字符串
  16. $rightTitles = '';
  17. foreach($rights as $right) {
  18. // 有效的权限id才拼接
  19. if(key_exists($right, $menus))
  20. $rightTitles .= ($menus[$right] . ',');
  21. }
  22. $group['right_title'] = substr($rightTitles, 0, -1);
  23. }
  24. return view('/admins/groups/index', ['groups' => $groups]);
  25. }
  26. public function add() {
  27. return view('/admins/groups/add', ['menus' => $this->getMenus()]);
  28. }
  29. function save(Request $req) {
  30. $title = $req->title;
  31. $rights = $req->rights;
  32. if(empty($title)) {
  33. return json_encode(['status' => 1, 'message' => '请录入用户组名称']);
  34. }
  35. if(!is_array($rights) || count($rights) < 1) {
  36. return json_encode(['status' => 1, `message` => '请选择权限']);
  37. }
  38. $data = [
  39. 'title' => $title,
  40. // $req传过来的参数都是字符串/字符串数组, 所以不能用json_encode()方法转换.
  41. 'rights' => '[' . implode(',', $rights) . ']'
  42. ];
  43. $res = DB::table('admin_group')->insert($data);
  44. if($res) {
  45. return json_encode(['status' => 0, 'message' => '用户组添加成功']);
  46. } else {
  47. return json_encode(['status' => 1, `message` => '用户组添加失败']);
  48. }
  49. }
  50. function update(Request $req) {
  51. $gid = $req->gid;
  52. // $title = $req->title;
  53. $rights = $req->rights;
  54. if(empty($gid)) {
  55. return json_encode(['status' => 1, 'message' => '请录入用户组名称']);
  56. }
  57. if(!is_array($rights) || count($rights) < 1) {
  58. return json_encode(['status' => 1, `message` => '请选择权限']);
  59. }
  60. $data = [
  61. // 'title' => $title,
  62. 'rights' => '[' . implode(',', $rights) . ']'
  63. ];
  64. $res = DB::table('admin_group')->where('gid', $gid)->update($data);
  65. if($res) {
  66. return json_encode(['status' => 0, 'message' => '权限分配成功']);
  67. } else {
  68. return json_encode(['status' => 1, `message` => '权限分配失败']);
  69. }
  70. }
  71. function edit(Request $req) {
  72. // 要编辑的用户组id
  73. $gid = $req->gid;
  74. if(!is_numeric($gid) || $gid < 1) {
  75. return json_encode(['status' => 1, 'message' => '无效的用户组ID']);
  76. }
  77. // 查询要编辑的用户组数据
  78. $group = DB::table('admin_group')->where('gid', $gid)->getFirst();
  79. if(!$group) {
  80. return json_encode(['status' => 1, 'message' => '没有该用户组']);
  81. }
  82. // 该用户组已被授权的菜单id, 转为数组
  83. $group['rights'] = json_decode($group['rights'], true);
  84. $data['group'] = $group;
  85. // 有层级关系的菜单列表\
  86. $data['menus'] = $this->getMenus();
  87. return view('/admins/groups/edit', $data);
  88. }
  89. /**
  90. * 获取菜单列表
  91. */
  92. protected function getMenus() {
  93. // 一级菜单
  94. $menu1sts = DB::table('admin_menu')->where('status', 0)->where('pid', 0)->keyval('mid');
  95. // 所有菜单
  96. $menus = DB::table('admin_menu')->where('status', 0)->keyval('mid');
  97. // 遍历所有菜单
  98. foreach($menus as $menu) {
  99. $pid = $menu['pid'];
  100. // 找到父级菜单
  101. if(key_exists($pid, $menu1sts)) {
  102. // 为父级菜单添加'child_menus'元素
  103. if(!isset($menu1sts[$pid]['child_menus'])) $menu1sts[$pid]['child_menus'] = [];
  104. $menu1sts[$pid]['child_menus'][] = $menu;
  105. }
  106. }
  107. return $menu1sts;
  108. }
  109. }
声明:本文内容转载自脚本之家,由网友自发贡献,版权归原作者所有,如您发现涉嫌抄袭侵权,请联系admin@php.cn 核实处理。
全部评论
文明上网理性发言,请遵守新闻评论服务协议