You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

538 lines
21 KiB
PHP

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<?php
namespace app\admin\controller;
use app\common\builder\ZBuilder;
use app\admin\model\Module as ModuleModel;
use app\admin\model\Menu as MenuModel;
use app\user\model\Role as RoleModel;
use think\facade\Cache;
/**
* 节点管理
* @package app\admin\controller
*/
class Menu extends Admin
{
/**
* 节点首页
* @param string $group 分组
* @author 蔡伟明 <314013107@qq.com>
* @return mixed
* @throws \Exception
*/
public function index($group = 'admin')
{
// 保存模块排序
if ($this->request->isPost()) {
$modules = $this->request->post('sort/a');
if ($modules) {
$data = [];
foreach ($modules as $key => $module) {
$data[] = [
'id' => $module,
'sort' => $key + 1
];
}
$MenuModel = new MenuModel();
if (false !== $MenuModel->saveAll($data)) {
$this->success('保存成功');
} else {
$this->error('保存失败');
}
}
}
cookie('__forward__', $_SERVER['REQUEST_URI']);
// 配置分组信息
$list_group = MenuModel::getGroup();
foreach ($list_group as $key => $value) {
$tab_list[$key]['title'] = $value;
$tab_list[$key]['url'] = url('index', ['group' => $key]);
}
// 模块排序
if ($group == 'module-sort') {
$map['status'] = 1;
$map['pid'] = 0;
$modules = MenuModel::where($map)->order('sort,id')->column('icon,title', 'id');
$this->assign('modules', $modules);
} else {
// 获取节点数据
$data_list = MenuModel::getMenusByGroup($group);
$max_level = $this->request->get('max', 0);
$this->assign('menus', $this->getNestMenu($data_list, $max_level));
}
$this->assign('tab_nav', ['tab_list' => $tab_list, 'curr_tab' => $group]);
$this->assign('page_title', '节点管理');
return $this->fetch();
}
/**
* 新增节点
* @param string $module 所属模块
* @param string $pid 所属节点id
* @author 蔡伟明 <314013107@qq.com>
* @return mixed
* @throws \Exception
*/
public function add($module = 'admin', $pid = '')
{
// 保存数据
if ($this->request->isPost()) {
$data = $this->request->post('', null, 'trim');
// 验证
$result = $this->validate($data, 'Menu');
// 验证失败 输出错误信息
if(true !== $result) $this->error($result);
// 顶部节点url检查
if ($data['pid'] == 0 && $data['url_value'] == '' && ($data['url_type'] == 'module_admin' || $data['url_type'] == 'module_home')) {
$this->error('顶级节点的节点链接不能为空');
}
if ($menu = MenuModel::create($data)) {
// 自动创建子节点
if ($data['auto_create'] == 1 && !empty($data['child_node'])) {
unset($data['icon']);
unset($data['params']);
$this->createChildNode($data, $menu['id']);
}
// 添加角色权限
if (isset($data['role'])) {
$this->setRoleMenu($menu['id'], $data['role']);
}
Cache::clear();
// 记录行为
$details = '所属模块('.$data['module'].'),所属节点ID('.$data['pid'].'),节点标题('.$data['title'].'),节点链接('.$data['url_value'].')';
action_log('menu_add', 'admin_menu', $menu['id'], UID, $details);
$this->success('新增成功', cookie('__forward__'));
} else {
$this->error('新增失败');
}
}
// 使用ZBuilder快速创建表单
return ZBuilder::make('form')
->setPageTitle('新增节点')
->addLinkage('module', '所属模块', '', ModuleModel::getModule(), $module, url('ajax/getModuleMenus'), 'pid')
->addFormItems([
['select', 'pid', '所属节点', '所属上级节点', MenuModel::getMenuTree(0, '', $module), $pid],
['text', 'title', '节点标题'],
['radio', 'url_type', '链接类型', '', ['module_admin' => '模块链接(后台)', 'module_home' => '模块链接(前台)', 'link' => '普通链接'], 'module_admin']
])
->addFormItem(
'text',
'url_value',
'节点链接',
"可留空,如果是模块链接,请填写<code>模块/控制器/操作</code>,如:<code>admin/menu/add</code>。如果是普通链接则直接填写url地址<code>http://www.dolphinphp.com</code>"
)
->addText('params', '参数', '如a=1&b=2')
->addSelect('role', '角色', '除超级管理员外,拥有该节点权限的角色', RoleModel::where('id', 'neq', 1)->column('id,name'), '', 'multiple')
->addRadio('auto_create', '自动添加子节点', '选择【是】则自动添加指定的子节点', ['否', '是'], 0)
->addCheckbox('child_node', '子节点', '仅上面选项为【是】时起作用', ['add' => '新增', 'edit' => '编辑', 'delete' => '删除', 'enable' => '启用', 'disable' => '禁用', 'quickedit' => '快速编辑'], 'add,edit,delete,enable,disable,quickedit')
->addRadio('url_target', '打开方式', '', ['_self' => '当前窗口', '_blank' => '新窗口'], '_self')
->addIcon('icon', '图标', '导航图标')
->addRadio('online_hide', '网站上线后隐藏', '关闭开发模式后,则隐藏该菜单节点', ['否', '是'], 0)
->addText('sort', '排序', '', 100)
->setTrigger('auto_create', '1', 'child_node', false)
->fetch();
}
/**
* 编辑节点
* @param int $id 节点ID
* @author 蔡伟明 <314013107@qq.com>
* @return mixed
* @throws \Exception
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\ModelNotFoundException
* @throws \think\exception\DbException
*/
public function edit($id = 0)
{
if ($id === 0) $this->error('缺少参数');
// 保存数据
if ($this->request->isPost()) {
$data = $this->request->post('', null, 'trim');
// 验证
$result = $this->validate($data, 'Menu');
// 验证失败 输出错误信息
if(true !== $result) $this->error($result);
// 顶部节点url检查
if ($data['pid'] == 0 && $data['url_value'] == '' && ($data['url_type'] == 'module_admin' || $data['url_type'] == 'module_home')) {
$this->error('顶级节点的节点链接不能为空');
}
// 设置角色权限
$this->setRoleMenu($data['id'], isset($data['role']) ? $data['role'] : []);
// 验证是否更改所属模块,如果是,则该节点的所有子孙节点的模块都要修改
$map['id'] = $data['id'];
$map['module'] = $data['module'];
if (!MenuModel::where($map)->find()) {
MenuModel::changeModule($data['id'], $data['module']);
}
if (MenuModel::update($data)) {
Cache::clear();
// 记录行为
$details = '节点ID('.$id.')';
action_log('menu_edit', 'admin_menu', $id, UID, $details);
$this->success('编辑成功', cookie('__forward__'));
} else {
$this->error('编辑失败');
}
}
// 获取数据
$info = MenuModel::get($id);
// 拥有该节点权限的角色
$info['role'] = RoleModel::getRoleWithMenu($id);
// 使用ZBuilder快速创建表单
return ZBuilder::make('form')
->setPageTitle('编辑节点')
->addFormItem('hidden', 'id')
->addLinkage('module', '所属模块', '', ModuleModel::getModule(), '', url('ajax/getModuleMenus'), 'pid')
->addFormItem('select', 'pid', '所属节点', '所属上级节点', MenuModel::getMenuTree(0, '', $info['module']))
->addFormItem('text', 'title', '节点标题')
->addFormItem('radio', 'url_type', '链接类型', '', ['module_admin' => '模块链接(后台)', 'module_home' => '模块链接(前台)', 'link' => '普通链接'], 'module_admin')
->addFormItem(
'text',
'url_value',
'节点链接',
"可留空,如果是模块链接,请填写<code>模块/控制器/操作</code>,如:<code>admin/menu/add</code>。如果是普通链接则直接填写url地址<code>http://www.dolphinphp.com</code>"
)
->addText('params', '参数', '如a=1&b=2')
->addSelect('role', '角色', '除超级管理员外,拥有该节点权限的角色', RoleModel::where('id', 'neq', 1)->column('id,name'), '', 'multiple')
->addRadio('url_target', '打开方式', '', ['_self' => '当前窗口', '_blank' => '新窗口'], '_self')
->addIcon('icon', '图标', '导航图标')
->addRadio('online_hide', '网站上线后隐藏', '关闭开发模式后,则隐藏该菜单节点', ['否', '是'])
->addText('sort', '排序', '', 100)
->setFormData($info)
->fetch();
}
/**
* 设置角色权限
* @param string $role_id 角色id
* @param array $roles 角色id
* @author 蔡伟明 <314013107@qq.com>
* @throws \Exception
*/
private function setRoleMenu($role_id = '', $roles = [])
{
$RoleModel = new RoleModel();
// 该节点的所有子节点,包括本身节点
$menu_child = MenuModel::getChildsId($role_id);
$menu_child[] = (int)$role_id;
// 该节点的所有上下级节点
$menu_all = MenuModel::getLinkIds($role_id);
$menu_all = array_map('strval', $menu_all);
if (!empty($roles)) {
// 拥有该节点的所有角色id及节点权限
$role_menu_auth = RoleModel::getRoleWithMenu($role_id, true);
// 已有该节点权限的角色id
$role_exists = array_keys($role_menu_auth);
// 新节点权限的角色
$role_new = $roles;
// 原有权限角色差集
$role_diff = array_diff($role_exists, $role_new);
// 新权限角色差集
$role_diff_new = array_diff($role_new, $role_exists);
// 新节点角色权限
$role_new_auth = RoleModel::getAuthWithRole($roles);
// 删除原先角色的该节点权限
if ($role_diff) {
$role_del_auth = [];
foreach ($role_diff as $role) {
$auth = json_decode($role_menu_auth[$role], true);
$auth_new = array_diff($auth, $menu_child);
$role_del_auth[] = [
'id' => $role,
'menu_auth' => array_values($auth_new)
];
}
if ($role_del_auth) {
$RoleModel->saveAll($role_del_auth);
}
}
// 新增权限角色
if ($role_diff_new) {
$role_update_auth = [];
foreach ($role_new_auth as $role => $auth) {
$auth = json_decode($auth, true);
if (in_array($role, $role_diff_new)) {
$auth = array_unique(array_merge($auth, $menu_all));
}
$role_update_auth[] = [
'id' => $role,
'menu_auth' => array_values($auth)
];
}
if ($role_update_auth) {
$RoleModel->saveAll($role_update_auth);
}
}
} else {
$role_menu_auth = RoleModel::getRoleWithMenu($role_id, true);
$role_del_auth = [];
foreach ($role_menu_auth as $role => $auth) {
$auth = json_decode($auth, true);
$auth_new = array_diff($auth, $menu_child);
$role_del_auth[] = [
'id' => $role,
'menu_auth' => array_values($auth_new)
];
}
if ($role_del_auth) {
$RoleModel->saveAll($role_del_auth);
}
}
}
/**
* 删除节点
* @param array $record 行为日志内容
* @author 蔡伟明 <314013107@qq.com>
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\ModelNotFoundException
* @throws \think\exception\DbException
*/
public function delete($record = [])
{
$id = $this->request->param('id');
$menu = MenuModel::where('id', $id)->find();
if ($menu['system_menu'] == '1') $this->error('系统节点,禁止删除');
// 获取该节点的所有后辈节点id
$menu_childs = MenuModel::getChildsId($id);
// 要删除的所有节点id
$all_ids = array_merge([(int)$id], $menu_childs);
// 删除节点
if (MenuModel::destroy($all_ids)) {
Cache::clear();
// 记录行为
$details = '节点ID('.$id.'),节点标题('.$menu['title'].'),节点链接('.$menu['url_value'].')';
action_log('menu_delete', 'admin_menu', $id, UID, $details);
$this->success('删除成功');
} else {
$this->error('删除失败');
}
}
/**
* 保存节点排序
* @author 蔡伟明 <314013107@qq.com>
*/
public function save()
{
if ($this->request->isPost()) {
$data = $this->request->post();
if (!empty($data)) {
$menus = $this->parseMenu($data['menus']);
foreach ($menus as $menu) {
if ($menu['pid'] == 0) {
continue;
}
MenuModel::update($menu);
}
Cache::clear();
$this->success('保存成功');
} else {
$this->error('没有需要保存的节点');
}
}
$this->error('非法请求');
}
/**
* 添加子节点
* @param array $data 节点数据
* @param string $pid 父节点id
* @author 蔡伟明 <314013107@qq.com>
*/
private function createChildNode($data = [], $pid = '')
{
$url_value = substr($data['url_value'], 0, strrpos($data['url_value'], '/')).'/';
$child_node = [];
$data['pid'] = $pid;
foreach ($data['child_node'] as $item) {
switch ($item) {
case 'add':
$data['title'] = '新增';
break;
case 'edit':
$data['title'] = '编辑';
break;
case 'delete':
$data['title'] = '删除';
break;
case 'enable':
$data['title'] = '启用';
break;
case 'disable':
$data['title'] = '禁用';
break;
case 'quickedit':
$data['title'] = '快速编辑';
break;
}
$data['url_value'] = $url_value.$item;
$data['create_time'] = $this->request->time();
$data['update_time'] = $this->request->time();
$child_node[] = $data;
}
if ($child_node) {
$MenuModel = new MenuModel();
$MenuModel->insertAll($child_node);
}
}
/**
* 递归解析节点
* @param array $menus 节点数据
* @param int $pid 上级节点id
* @author 蔡伟明 <314013107@qq.com>
* @return array 解析成可以写入数据库的格式
*/
private function parseMenu($menus = [], $pid = 0)
{
$sort = 1;
$result = [];
foreach ($menus as $menu) {
$result[] = [
'id' => (int)$menu['id'],
'pid' => (int)$pid,
'sort' => $sort,
];
if (isset($menu['children'])) {
$result = array_merge($result, $this->parseMenu($menu['children'], $menu['id']));
}
$sort ++;
}
return $result;
}
/**
* 获取嵌套式节点
* @param array $lists 原始节点数组
* @param int $pid 父级id
* @param int $max_level 最多返回多少层0为不限制
* @param int $curr_level 当前层数
* @author 蔡伟明 <314013107@qq.com>
* @return string
*/
private function getNestMenu($lists = [], $max_level = 0, $pid = 0, $curr_level = 1)
{
$result = '';
foreach ($lists as $key => $value) {
if ($value['pid'] == $pid) {
$disable = $value['status'] == 0 ? 'dd-disable' : '';
// 组合节点
$result .= '<li class="dd-item dd3-item '.$disable.'" data-id="'.$value['id'].'">';
$result .= '<div class="dd-handle dd3-handle">拖拽</div><div class="dd3-content"><i class="'.$value['icon'].'"></i> '.$value['title'];
if ($value['url_value'] != '') {
$result .= '<span class="link"><i class="fa fa-link"></i> '.$value['url_value'].'</span>';
}
$result .= '<div class="action">';
$result .= '<a href="'.url('add', ['module' => $value['module'], 'pid' => $value['id']]).'" data-toggle="tooltip" data-original-title="新增子节点"><i class="list-icon fa fa-plus fa-fw"></i></a><a href="'.url('edit', ['id' => $value['id']]).'" data-toggle="tooltip" data-original-title="编辑"><i class="list-icon fa fa-pencil fa-fw"></i></a>';
if ($value['status'] == 0) {
// 启用
$result .= '<a href="javascript:void(0);" data-ids="'.$value['id'].'" class="enable" data-toggle="tooltip" data-original-title="启用"><i class="list-icon fa fa-check-circle-o fa-fw"></i></a>';
} else {
// 禁用
$result .= '<a href="javascript:void(0);" data-ids="'.$value['id'].'" class="disable" data-toggle="tooltip" data-original-title="禁用"><i class="list-icon fa fa-ban fa-fw"></i></a>';
}
$result .= '<a href="'.url('delete', ['id' => $value['id'], 'table' => 'admin_menu']).'" data-toggle="tooltip" data-original-title="删除" class="ajax-get confirm"><i class="list-icon fa fa-times fa-fw"></i></a></div>';
$result .= '</div>';
if ($max_level == 0 || $curr_level != $max_level) {
unset($lists[$key]);
// 下级节点
$children = $this->getNestMenu($lists, $max_level, $value['id'], $curr_level + 1);
if ($children != '') {
$result .= '<ol class="dd-list">'.$children.'</ol>';
}
}
$result .= '</li>';
}
}
return $result;
}
/**
* 启用节点
* @param array $record 行为日志
* @author 蔡伟明 <314013107@qq.com>
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\ModelNotFoundException
* @throws \think\exception\DbException
*/
public function enable($record = [])
{
$id = input('param.ids');
$menu = MenuModel::where('id', $id)->find();
$details = '节点ID('.$id.'),节点标题('.$menu['title'].'),节点链接('.$menu['url_value'].')';
$this->setStatus('enable', ['menu_enable', 'admin_menu', $id, UID, $details]);
}
/**
* 禁用节点
* @param array $record 行为日志
* @author 蔡伟明 <314013107@qq.com>
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\ModelNotFoundException
* @throws \think\exception\DbException
*/
public function disable($record = [])
{
$id = input('param.ids');
$menu = MenuModel::where('id', $id)->find();
$details = '节点ID('.$id.'),节点标题('.$menu['title'].'),节点链接('.$menu['url_value'].')';
$this->setStatus('disable', ['menu_disable', 'admin_menu', $id, UID, $details]);
}
/**
* 设置状态
* @param string $type 类型
* @param array $record 行为日志
* @author 小乌 <82950492@qq.com>
*/
public function setStatus($type = '', $record = [])
{
$id = input('param.ids');
$status = $type == 'enable' ? 1 : 0;
if (false !== MenuModel::where('id', $id)->setField('status', $status)) {
Cache::clear();
// 记录行为日志
if (!empty($record)) {
call_user_func_array('action_log', $record);
}
$this->success('操作成功');
} else {
$this->error('操作失败');
}
}
}