Blogger Information
Blog 57
fans 3
comment 0
visits 60347
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template
laravel实战-通用后台管理系统-菜单管理和角色管理
岂几岂几
Original
1454 people have browsed it

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. }
Correcting teacher:天蓬老师天蓬老师

Correction status:qualified

Teacher's comments:编程是有套路的, 有规律的
Statement of this Website
The copyright of this blog article belongs to the blogger. Please specify the address when reprinting! If there is any infringement or violation of the law, please contact admin@php.cn Report processing!
All comments Speak rationally on civilized internet, please comply with News Comment Service Agreement
0 comments
Author's latest blog post
About us Disclaimer Sitemap
php.cn:Public welfare online PHP training,Help PHP learners grow quickly!