本文目录导读:

我来为你详细介绍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('没有操作权限,请联系管理员');
}
}
}
?>
这个完整的节点与菜单管理方案包含了数据库设计、后端逻辑、前端模板和权限验证等核心部分,你可以根据实际需求进行调整和扩展。