ThinkPHP项目节点与菜单管理

wen PHP项目 6

本文目录导读:

ThinkPHP项目节点与菜单管理

  1. 数据库设计
  2. Node模型
  3. 菜单管理控制器
  4. 前端模板
  5. 后台菜单渲染
  6. 前端菜单渲染模板
  7. 权限验证

我来为你详细介绍ThinkPHP项目中的节点与菜单管理实现方案:

数据库设计

节点表(node)

CREATE TABLE `node` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(50) NOT NULL COMMENT '节点名称', varchar(50) NOT NULL COMMENT '菜单标题',
  `pid` int(11) NOT NULL DEFAULT '0' COMMENT '父级ID',
  `level` tinyint(1) NOT NULL DEFAULT '0' COMMENT '层级',
  `sort` int(11) NOT NULL DEFAULT '0' COMMENT '排序',
  `icon` varchar(50) DEFAULT NULL COMMENT '图标',
  `status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '状态',
  `remark` varchar(255) DEFAULT NULL COMMENT '备注',
  `create_time` int(11) DEFAULT NULL,
  `update_time` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `pid` (`pid`),
  KEY `status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='节点表';

角色-节点关联表

CREATE TABLE `role_node` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `role_id` int(11) NOT NULL COMMENT '角色ID',
  `node_id` int(11) NOT NULL COMMENT '节点ID',
  PRIMARY KEY (`id`),
  KEY `role_id` (`role_id`),
  KEY `node_id` (`node_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色节点关联表';

Node模型

<?php
namespace app\common\model;
use think\Model;
use think\model\concern\SoftDelete;
class Node extends Model
{
    use SoftDelete;
    protected $name = 'node';
    protected $deleteTime = 'delete_time';
    // 自动写入时间戳
    protected $autoWriteTimestamp = true;
    // 关联角色
    public function roles()
    {
        return $this->belongsToMany(Role::class, 'role_node');
    }
    // 子节点
    public function children()
    {
        return $this->hasMany(Node::class, 'pid')->order('sort asc');
    }
    // 获取树形结构
    public static function getTree()
    {
        $nodes = self::order('sort asc')->select()->toArray();
        return self::buildTree($nodes);
    }
    // 构建树形结构
    protected static function buildTree($items, $pid = 0)
    {
        $tree = [];
        foreach ($items as $item) {
            if ($item['pid'] == $pid) {
                $item['children'] = self::buildTree($items, $item['id']);
                $tree[] = $item;
            }
        }
        return $tree;
    }
    // 获取用户权限节点
    public static function getUserNodes($roleIds)
    {
        $nodeIds = RoleNode::whereIn('role_id', $roleIds)
            ->column('node_id');
        return self::whereIn('id', $nodeIds)
            ->where('status', 1)
            ->order('sort asc')
            ->select()
            ->toArray();
    }
}
?>

菜单管理控制器

<?php
namespace app\admin\controller;
use think\Controller;
use think\Request;
use app\common\model\Node;
class Menu extends Base
{
    protected $nodeModel;
    public function __construct()
    {
        parent::__construct();
        $this->nodeModel = new Node();
    }
    // 菜单列表
    public function index()
    {
        $list = Node::getTree();
        return view('index', ['list' => $list]);
    }
    // 添加菜单
    public function add()
    {
        if (request()->isPost()) {
            $data = request()->post();
            $validate = new \think\Validate([
                'name' => 'require|max:50',
                'title' => 'require|max:50',
            ]);
            if (!$validate->check($data)) {
                return json(['code' => 0, 'msg' => $validate->getError()]);
            }
            $result = Node::create($data);
            if ($result) {
                return json(['code' => 1, 'msg' => '添加成功']);
            } else {
                return json(['code' => 0, 'msg' => '添加失败']);
            }
        }
        $pid = input('pid', 0);
        $nodeList = Node::getTree();
        return view('add', ['pid' => $pid, 'nodeList' => $nodeList]);
    }
    // 编辑菜单
    public function edit($id)
    {
        $node = Node::find($id);
        if (request()->isPost()) {
            $data = request()->post();
            $validate = new \think\Validate([
                'name' => 'require|max:50',
                'title' => 'require|max:50',
            ]);
            if (!$validate->check($data)) {
                return json(['code' => 0, 'msg' => $validate->getError()]);
            }
            $result = $node->save($data);
            if ($result) {
                return json(['code' => 1, 'msg' => '更新成功']);
            } else {
                return json(['code' => 0, 'msg' => '更新失败']);
            }
        }
        $nodeList = Node::getTree();
        return view('edit', ['node' => $node, 'nodeList' => $nodeList]);
    }
    // 删除菜单
    public function delete($id)
    {
        // 检查是否有子节点
        $hasChildren = Node::where('pid', $id)->find();
        if ($hasChildren) {
            return json(['code' => 0, 'msg' => '请先删除子菜单']);
        }
        $result = Node::destroy($id);
        if ($result) {
            return json(['code' => 1, 'msg' => '删除成功']);
        } else {
            return json(['code' => 0, 'msg' => '删除失败']);
        }
    }
    // 菜单排序
    public function sort()
    {
        $data = request()->post('sort');
        foreach ($data as $id => $sort) {
            Node::where('id', $id)->update(['sort' => $sort]);
        }
        return json(['code' => 1, 'msg' => '排序成功']);
    }
}
?>

前端模板

菜单列表模板 (index.html)

<!DOCTYPE html>
<html>
<head>菜单管理</title>
    <link rel="stylesheet" href="...">
</head>
<body>
<div class="container-fluid">
    <div class="panel panel-default">
        <div class="panel-heading">
            菜单管理
            <a href="{:url('menu/add')}" class="btn btn-primary btn-sm">
                <i class="glyphicon glyphicon-plus"></i> 添加菜单
            </a>
        </div>
        <div class="panel-body">
            <table class="table table-hover">
                <thead>
                    <tr>
                        <th width="50">ID</th>
                        <th>菜单名称</th>
                        <th>节点标识</th>
                        <th>图标</th>
                        <th>排序</th>
                        <th>状态</th>
                        <th>操作</th>
                    </tr>
                </thead>
                <tbody>
                    {volist name="list" id="vo"}
                    <tr>
                        <td>{$vo.id}</td>
                        <td style="padding-left:{$vo.level*20}px">
                            {if $vo.level > 0}├─{/if}
                            {$vo.title}
                        </td>
                        <td>{$vo.name}</td>
                        <td>{$vo.icon}</td>
                        <td>
                            <input type="number" value="{$vo.sort}" 
                                   class="form-control sort-input" 
                                   data-id="{$vo.id}" 
                                   style="width:60px;display:inline-block">
                        </td>
                        <td>
                            <a href="javascript:;" 
                               onclick="changeStatus({$vo.id}, {$vo.status})">
                                {if $vo.status == 1}
                                <span class="label label-success">启用</span>
                                {else}
                                <span class="label label-danger">禁用</span>
                                {/if}
                            </a>
                        </td>
                        <td>
                            <a href="{:url('menu/edit', ['id' => $vo.id])}" 
                               class="btn btn-info btn-xs">编辑</a>
                            <a href="javascript:;" 
                               onclick="deleteNode({$vo.id})" 
                               class="btn btn-danger btn-xs">删除</a>
                        </td>
                    </tr>
                    {/volist}
                </tbody>
            </table>
        </div>
    </div>
</div>
<script>
// 删除节点
function deleteNode(id) {
    if (confirm('确定要删除该菜单吗?')) {
        $.post('{:url("menu/delete")}', {id: id}, function(res) {
            if (res.code == 1) {
                alert(res.msg);
                location.reload();
            } else {
                alert(res.msg);
            }
        }, 'json');
    }
}
// 修改状态
function changeStatus(id, status) {
    $.post('{:url("menu/status")}', {
        id: id,
        status: status == 1 ? 0 : 1
    }, function(res) {
        if (res.code == 1) {
            location.reload();
        } else {
            alert(res.msg);
        }
    }, 'json');
}
// 排序保存
$('.sort-input').change(function() {
    var id = $(this).data('id');
    var sort = $(this).val();
    $.post('{:url("menu/sort")}', {id: id, sort: sort}, function(res) {
        if (res.code == 1) {
            alert('排序成功');
        }
    }, 'json');
});
</script>
</body>
</html>

菜单添加模板 (add.html)

<form id="menuForm">
    <div class="form-group">
        <label>父级菜单</label>
        <select name="pid" class="form-control">
            <option value="0">顶级菜单</option>
            {volist name="nodeList" id="vo"}
            <option value="{$vo.id}" {if $vo.id == $pid}selected{/if}>
                {$vo.title}
            </option>
            {/volist}
        </select>
    </div>
    <div class="form-group">
        <label>菜单名称</label>
        <input type="text" name="title" class="form-control" required>
    </div>
    <div class="form-group">
        <label>节点标识</label>
        <input type="text" name="name" class="form-control" 
               placeholder="如:admin/user/index">
    </div>
    <div class="form-group">
        <label>图标</label>
        <input type="text" name="icon" class="form-control" 
               placeholder="如:fa-user">
    </div>
    <div class="form-group">
        <label>排序</label>
        <input type="number" name="sort" class="form-control" value="0">
    </div>
    <div class="form-group">
        <label>状态</label>
        <select name="status" class="form-control">
            <option value="1">启用</option>
            <option value="0">禁用</option>
        </select>
    </div>
    <button type="submit" class="btn btn-primary">保存</button>
</form>
<script>
$('#menuForm').on('submit', function(e) {
    e.preventDefault();
    $.post('{:url("menu/add")}', $(this).serialize(), function(res) {
        if (res.code == 1) {
            alert('添加成功');
            location.href = '{:url("menu/index")}';
        } else {
            alert(res.msg);
        }
    }, 'json');
});
</script>

后台菜单渲染

// 在Base控制器中获取菜单
public function getMenuList()
{
    // 获取用户角色
    $roleIds = $this->userInfo->roles()->column('id');
    // 获取权限节点
    $nodes = Node::getUserNodes($roleIds);
    // 如果是超级管理员,获取全部
    if ($this->isSuperAdmin()) {
        $nodes = Node::where('status', 1)->order('sort asc')->select()->toArray();
    }
    // 构建菜单树
    return Node::buildTree($nodes);
}

前端菜单渲染模板

<!-- 侧边栏菜单 -->
<ul class="sidebar-menu">
    <li class="header">功能菜单</li>
    {volist name="menuList" id="menu"}
    <li class="treeview {if empty($menu['children'])}single{/if}">
        <a href="
            {if empty($menu['children'])}
                {$menu['name']|url}
            {else}
                javascript:void(0)
            {/if}
        ">
            <i class="fa {$menu.icon}"></i>
            <span>{$menu.title}</span>
            {if !empty($menu['children'])}
            <span class="pull-right-container">
                <i class="fa fa-angle-left pull-right"></i>
            </span>
            {/if}
        </a>
        {if !empty($menu['children'])}
        <ul class="treeview-menu">
            {volist name="menu['children']" id="sub"}
            <li>
                <a href="{$sub.name|url}">
                    <i class="fa fa-circle-o"></i> {$sub.title}
                </a>
            </li>
            {/volist}
        </ul>
        {/if}
    </li>
    {/volist}
</ul>

权限验证

<?php
namespace app\admin\controller;
use think\Controller;
class Base extends Controller
{
    protected $userInfo;
    public function initialize()
    {
        parent::initialize();
        // 验证登录
        $this->checkLogin();
        // 验证权限
        $this->checkAuth();
    }
    // 权限验证
    protected function checkAuth()
    {
        if (session('is_super_admin')) {
            return true;
        }
        $route = strtolower(request()->module() . '/' . 
                            request()->controller() . '/' . 
                            request()->action());
        // 获取用户权限节点
        $userNodes = session('user_nodes');
        if (!in_array($route, $userNodes)) {
            $this->error('没有操作权限,请联系管理员');
        }
    }
}
?>

这个完整的节点与菜单管理方案包含了数据库设计、后端逻辑、前端模板和权限验证等核心部分,你可以根据实际需求进行调整和扩展。

抱歉,评论功能暂时关闭!