Compare commits

...

2 Commits

Author SHA1 Message Date
Karson 92e722c674 优化权限角色组列表 2025-09-11 14:24:50 +08:00
Karson 82386cc9d8 优化权限菜单规则列表 2025-09-11 14:24:22 +08:00
8 changed files with 175 additions and 76 deletions

View File

@ -258,7 +258,10 @@ class Group extends Backend
}
if (($pid || $parentGroupModel) && (!$id || $currentGroupModel)) {
$id = $id ? $id : null;
$ruleList = collection(model('AuthRule')->order('weigh', 'desc')->order('id', 'asc')->select())->toArray();
$ruleList = Db::name("auth_rule")
->field('id,pid,name,title,icon,ismenu,status,weigh')
->order('weigh DESC,id ASC')
->select();
//读取父类角色所有节点列表
$parentRuleList = [];
if (in_array('*', explode(',', $parentGroupModel->rules))) {

View File

@ -2,12 +2,10 @@
namespace app\admin\controller\auth;
use app\admin\model\AuthRule;
use app\common\controller\Backend;
use fast\Tree;
use think\Cache;
use think\db\Query;
use think\exception\HttpResponseException;
use think\Db;
/**
* 规则管理
@ -32,24 +30,45 @@ class Rule extends Backend
$this->error(__('Access is allowed only to the super management group'));
}
$this->model = model('AuthRule');
// 必须将结果集转换为数组
$ruleList = \think\Db::name("auth_rule")->field('type,condition,remark,menutype,extend,pinyin,py,createtime,updatetime', true)->order('weigh DESC,id ASC')->select();
foreach ($ruleList as $k => &$v) {
$v['title'] = __($v['title']);
}
unset($v);
Tree::instance()->init($ruleList)->icon = ['    ', '    ', '    '];
$this->rulelist = Tree::instance()->getTreeList(Tree::instance()->getTreeArray(0), 'title');
$ruledata = [0 => __('None')];
foreach ($this->rulelist as $k => &$v) {
if (!$v['ismenu']) {
continue;
$actionName = $this->request->action();
$isAddEdit = in_array($actionName, ['add', 'edit']);
// 优化加载
if ($isAddEdit || ($actionName == 'index' && $this->request->isAjax())) {
// 必须将结果集转换为数组
$ruleList = Db::name("auth_rule")
->where(function ($query) use ($isAddEdit) {
if ($isAddEdit) {
$query->where('ismenu', 1);
}
})->field('id,pid,name,title,icon,ismenu,status,weigh')
->order('weigh DESC,id ASC')
->select();
array_walk($ruleList, function (&$v) {
$v['title'] = __($v['title']);
});
// 读取规则菜单
$this->rulelist = Tree::instance()->init($ruleList)->getTreeArrayList(0);
// 只有编辑页才需要渲染
if ($isAddEdit) {
$ruledata = [0 => __('None')];
foreach ($this->rulelist as $k => &$v) {
if (!$v['ismenu']) {
continue;
}
$ruledata[$v['id']] = str_repeat(' ', $v['level'] * 4) . $v['title'];
}
unset($v);
$this->view->assign('ruledata', $ruledata);
}
$ruledata[$v['id']] = $v['title'];
unset($v['spacer']);
}
unset($v);
$this->view->assign('ruledata', $ruledata);
$this->view->assign("menutypeList", $this->model->getMenutypeList());
}
@ -111,7 +130,8 @@ class Rule extends Backend
$this->error(__('Can not change the parent to self'));
}
if ($params['pid'] != $row['pid']) {
$childrenIds = Tree::instance()->init(collection(AuthRule::select())->toArray())->getChildrenIds($row['id']);
$tree = Tree::instance()->init(Db::name('auth_rule')->select());
$childrenIds = $tree->getChildrenIds($row['id']);
if (in_array($params['pid'], $childrenIds)) {
$this->error(__('Can not change the parent to child'));
}
@ -141,11 +161,12 @@ class Rule extends Backend
if (!$this->request->isPost()) {
$this->error(__("Invalid parameters"));
}
$ids = $ids ? $ids : $this->request->post("ids");
$ids = $ids ?: $this->request->post("ids");
if ($ids) {
$delIds = [];
$tree = Tree::instance()->init(Db::name('auth_rule')->select());
foreach (explode(',', $ids) as $k => $v) {
$delIds = array_merge($delIds, Tree::instance()->getChildrenIds($v, true));
$delIds = array_merge($delIds, $tree->getChildrenIds($v, true));
}
$delIds = array_unique($delIds);
$count = $this->model->where('id', 'in', $delIds)->delete();
@ -170,4 +191,4 @@ class Rule extends Backend
parent::dragsort();
}
}
}

View File

@ -1,6 +1,6 @@
<form id="add-form" class="form-horizontal form-ajax" role="form" data-toggle="validator" method="POST" action="">
{:token()}
<input type="hidden" name="row[rules]" value="" />
<input type="hidden" name="row[rules]" value=""/>
<div class="form-group">
<label class="control-label col-xs-12 col-sm-2">{:__('Parent')}:</label>
<div class="col-xs-12 col-sm-8">
@ -10,14 +10,16 @@
<div class="form-group">
<label class="control-label col-xs-12 col-sm-2">{:__('Name')}:</label>
<div class="col-xs-12 col-sm-8">
<input type="text" class="form-control" id="name" name="row[name]" value="" data-rule="required" />
<input type="text" class="form-control" id="name" name="row[name]" value="" data-rule="required"/>
</div>
</div>
<div class="form-group">
<label class="control-label col-xs-12 col-sm-2">{:__('Permission')}:</label>
<div class="col-xs-12 col-sm-8">
<span class="text-muted"><input type="checkbox" name="" id="checkall" /> <label for="checkall"><span>{:__('Check all')}</span></label></span>
<span class="text-muted"><input type="checkbox" name="" id="expandall" /> <label for="expandall"><span>{:__('Expand all')}</span></label></span>
<div style="margin-top:8px;">
<span class="text-muted"><input type="checkbox" name="" id="checkall"/> <label for="checkall"><span>{:__('Check all')}</span></label></span>
<span class="text-muted"><input type="checkbox" name="" id="expandall"/> <label for="expandall"><span>{:__('Expand all')}</span></label></span>
</div>
<div id="treeview"></div>
</div>

View File

@ -1,6 +1,6 @@
<form id="edit-form" class="form-horizontal form-ajax" role="form" method="POST" action="">
{:token()}
<input type="hidden" name="row[rules]" value="" />
<input type="hidden" name="row[rules]" value=""/>
<div class="form-group">
<label class="control-label col-xs-12 col-sm-2">{:__('Parent')}:</label>
<div class="col-xs-12 col-sm-8">
@ -10,14 +10,16 @@
<div class="form-group">
<label class="control-label col-xs-12 col-sm-2">{:__('Name')}:</label>
<div class="col-xs-12 col-sm-8">
<input type="text" class="form-control" id="name" name="row[name]" value="{$row.name|htmlentities}" data-rule="required" />
<input type="text" class="form-control" id="name" name="row[name]" value="{$row.name|htmlentities}" data-rule="required"/>
</div>
</div>
<div class="form-group">
<label class="control-label col-xs-12 col-sm-2">{:__('Permission')}:</label>
<div class="col-xs-12 col-sm-8">
<span class="text-muted"><input type="checkbox" name="" id="checkall" /> <label for="checkall"><span>{:__('Check all')}</span></label></span>
<span class="text-muted"><input type="checkbox" name="" id="expandall" /> <label for="expandall"><span>{:__('Expand all')}</span></label></span>
<div style="margin-top:8px;">
<span class="text-muted"><input type="checkbox" name="" id="checkall"/> <label for="checkall"><span>{:__('Check all')}</span></label></span>
<span class="text-muted"><input type="checkbox" name="" id="expandall"/> <label for="expandall"><span>{:__('Expand all')}</span></label></span>
</div>
<div id="treeview"></div>
</div>

View File

@ -1,5 +1,6 @@
<style>
.bootstrap-table tr td .text-muted {color:#888;}
.bootstrap-table tr td .fa.fa-caret-right {width: 8px;text-align: center;}
</style>
<div class="panel panel-default panel-intro">
{:build_heading()}

View File

@ -55,15 +55,15 @@ class Tree
/**
* 初始化方法
* @param array $arr 2维数组,例如:
* array(
* 1 => array('id'=>'1','pid'=>0,'name'=>'一级栏目一'),
* 2 => array('id'=>'2','pid'=>0,'name'=>'一级栏目二'),
* 3 => array('id'=>'3','pid'=>1,'name'=>'二级栏目一'),
* 4 => array('id'=>'4','pid'=>1,'name'=>'二级栏目二'),
* 5 => array('id'=>'5','pid'=>2,'name'=>'二级栏目三'),
* 6 => array('id'=>'6','pid'=>3,'name'=>'三级栏目一'),
* 7 => array('id'=>'7','pid'=>3,'name'=>'三级栏目二')
* )
* array(
* 1 => array('id'=>'1','pid'=>0,'name'=>'一级栏目一'),
* 2 => array('id'=>'2','pid'=>0,'name'=>'一级栏目二'),
* 3 => array('id'=>'3','pid'=>1,'name'=>'二级栏目一'),
* 4 => array('id'=>'4','pid'=>1,'name'=>'二级栏目二'),
* 5 => array('id'=>'5','pid'=>2,'name'=>'二级栏目三'),
* 6 => array('id'=>'6','pid'=>3,'name'=>'三级栏目一'),
* 7 => array('id'=>'7','pid'=>3,'name'=>'三级栏目二')
* )
* @param string $pidname 父字段名称
* @param string $nbsp 空格占位符
* @return Tree
@ -435,4 +435,55 @@ class Tree
}
return $arr;
}
public function getTreeArrayList($pid = 0)
{
$map = [];
$data = [];
$items = $this->arr;
foreach ($items as &$item) {
$item['children'] = []; // 初始化子节点
$map[$item['id']] = &$item;
}
unset($item);
foreach ($items as &$item) {
if ($item[$this->pidname] == $pid) {
$data[] = &$item;
} else {
if (isset($map[$item[$this->pidname]])) {
$map[$item[$this->pidname]]['children'][] = &$item;
}
}
}
unset($item);
return $this->getFlattenTree($data);
}
/**
* 将多维树形结构数据扁平化处理
*
* 该函数递归遍历树形结构,将嵌套的节点展开为一维数组,
* 并为每个节点添加层级标识
*
* @param array $data 树形结构数据每个节点可能包含children子节点
* @param int $level 当前节点的层级深度默认为0根节点
* @param array &$result 引用传递的结果数组,用于存储扁平化后的所有节点
*
* @return array 扁平化后的节点数组每个节点包含原始数据和level层级信息
*/
public function getFlattenTree($data, $level = 0, &$result = [])
{
foreach ($data as $node) {
$node['level'] = $level;
$result[] = $node;
if (!empty($node['children'])) {
$this->getFlattenTree($node['children'], $level + 1, $result);
}
}
return $result;
}
}

View File

@ -47,7 +47,8 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'jstree'], function (
{field: 'state', checkbox: true,},
{field: 'id', title: 'ID'},
{field: 'pid', title: __('Parent')},
{field: 'name', title: __('Name'), align: 'left', formatter:function (value, row, index) {
{
field: 'name', title: __('Name'), align: 'left', formatter: function (value, row, index) {
return value.toString().replace(/(&|&amp;)nbsp;/g, '&nbsp;');
}
},
@ -79,14 +80,17 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'jstree'], function (
},
api: {
bindevent: function () {
var treeview = $("#treeview");
// 表单提交前设定规则集合
Form.api.bindevent($("form[role=form]"), null, null, function () {
if ($("#treeview").length > 0) {
var r = $("#treeview").jstree("get_all_checked");
if (treeview.length > 0) {
var r = treeview.jstree("get_all_checked");
$("input[name='row[rules]']").val(r.join(','));
}
return true;
});
//渲染权限节点树
//变更级别后需要重建节点树
$(document).on("change", "select[name='row[pid]']", function () {
var pid = $(this).data("pid");
@ -96,37 +100,21 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'jstree'], function (
Backend.api.toastr.error(__('Can not change the parent to self'));
return false;
}
$.ajax({
url: "auth/group/roletree",
type: 'post',
dataType: 'json',
data: {id: id, pid: $(this).val()},
success: function (ret) {
if (ret.hasOwnProperty("code")) {
var data = ret.hasOwnProperty("data") && ret.data != "" ? ret.data : "";
if (ret.code === 1) {
//销毁已有的节点树
$("#treeview").jstree("destroy");
Controller.api.rendertree(data);
} else {
Backend.api.toastr.error(ret.msg);
}
}
}, error: function (e) {
Backend.api.toastr.error(e.message);
}
});
treeview.jstree(true).refresh(false);
});
//全选和展开
$(document).on("click", "#checkall", function () {
$("#treeview").jstree($(this).prop("checked") ? "check_all" : "uncheck_all");
treeview.jstree($(this).prop("checked") ? "check_all" : "uncheck_all");
});
$(document).on("click", "#expandall", function () {
$("#treeview").jstree($(this).prop("checked") ? "open_all" : "close_all");
treeview.jstree($(this).prop("checked") ? "open_all" : "close_all");
});
$("select[name='row[pid]']").trigger("change");
//首次渲染
Controller.api.rendertree();
},
rendertree: function (content) {
rendertree: function () {
$("#treeview")
.on('redraw.jstree', function (e) {
$(".layer-footer").attr("domrefresh", Math.random());
@ -150,7 +138,32 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'jstree'], function (
"plugins": ["checkbox", "types"],
"core": {
'check_callback': true,
"data": content
'strings': {
'Loading ...': __('Loading')
},
"data": function (obj, callback) {
var pidObj = $("select[name='row[pid]']");
$.ajax({
url: "auth/group/roletree",
type: 'post',
dataType: 'json',
data: {id: pidObj.data('id'), pid: pidObj.val()},
success: function (ret) {
if (ret.hasOwnProperty("code")) {
var data = ret.hasOwnProperty("data") && ret.data != "" ? ret.data : "";
if (ret.code === 1) {
callback(data);
} else {
Backend.api.toastr.error(ret.msg);
callback([]);
}
}
}, error: function (e) {
Backend.api.toastr.error(e.message);
callback([]);
}
});
}
}
});
}

View File

@ -14,6 +14,8 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
}
});
var isExpanded = false;
var table = $("#table");
// 初始化表格
@ -29,7 +31,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
{field: 'icon', title: __('Icon'), formatter: Controller.api.formatter.icon},
{field: 'name', title: __('Name'), align: 'left', formatter: Controller.api.formatter.name},
{field: 'weigh', title: __('Weigh')},
{field: 'status', title: __('Status'), formatter: Table.api.formatter.status},
{field: 'status', title: __('Status'), formatter: Table.api.formatter.status, operate: false},
{
field: 'ismenu',
title: __('Ismenu'),
@ -50,8 +52,13 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
search: false,
commonSearch: false,
rowAttributes: function (row, index) {
var expanded = $(".btn-toggle-all i").hasClass("fa-minus");
return row.pid == 0 || expanded ? {} : {style: "display:none"};
return row.pid == 0 || isExpanded ? {} : {style: "display:none"};
}
});
table.on('post-body.bs.table', function (e, data) {
if (data.length > 2000) {
$(".btn-dragsort").addClass("disabled");
}
});
@ -142,6 +149,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
$(".btn-node-sub:not([data-pid=0])").closest("tr").toggle(show);
$(".btn-node-sub").data("shown", show);
$(".btn-node-sub > i").toggleClass("fa-caret-down", show).toggleClass("fa-caret-right", !show);
isExpanded = show;
});
},
add: function () {
@ -153,13 +161,11 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
api: {
formatter: {
title: function (value, row, index) {
value = value.toString().replace(/(&|&amp;)nbsp;/g, '&nbsp;');
var caret = row.haschild == 1 || row.ismenu == 1 ? '<i class="fa fa-caret-right"></i>' : '';
value = value.indexOf("&nbsp;") > -1 ? value.replace(/(.*)&nbsp;/, "$1" + caret) : caret + value;
value = !row.ismenu || row.status == 'hidden' ? "<span class='text-muted'>" + value + "</span>" : value;
return '<a href="javascript:;" data-id="' + row.id + '" data-pid="' + row.pid + '" class="'
+ (row.haschild == 1 || row.ismenu == 1 ? 'text-primary' : 'disabled') + ' btn-node-sub">' + value + '</a>';
+ (row.haschild == 1 || row.ismenu == 1 ? 'text-primary' : 'disabled') + ' btn-node-sub">' + ('&nbsp'.repeat(row.level)) + ('&nbsp;'.repeat(parseInt(row.level) * 4)) + caret + '&nbsp;' + value + '</a>';
},
name: function (value, row, index) {
return !row.ismenu || row.status == 'hidden' ? "<span class='text-muted'>" + value + "</span>" : value;