Browse Source

给pc端的权限,仿authrule,融合systemmenu

lizhen_gitee 8 months ago
parent
commit
3797606903

+ 159 - 0
application/admin/controller/pcauth/Rule.php

@@ -0,0 +1,159 @@
+<?php
+
+namespace app\admin\controller\pcauth;
+
+use app\admin\model\PcAuthRule;
+use app\common\controller\Backend;
+use fast\Tree;
+use think\Cache;
+
+/**
+ * 规则管理
+ *
+ * @icon   fa fa-list
+ * @remark 规则通常对应一个控制器的方法,同时左侧的菜单栏数据也从规则中体现,通常建议通过控制台进行生成规则节点
+ */
+class Rule extends Backend
+{
+
+    /**
+     * @var \app\admin\model\PcAuthRule
+     */
+    protected $model = null;
+    protected $rulelist = [];
+    protected $multiFields = 'ismenu,status';
+
+    public function _initialize()
+    {
+        parent::_initialize();
+        if (!$this->auth->isSuperAdmin()) {
+            $this->error(__('Access is allowed only to the super management group'));
+        }
+        $this->model = model('PcAuthRule');
+        // 必须将结果集转换为数组
+        $ruleList = \think\Db::name("pc_auth_rule")->field('type,condition,remark,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 = ['&nbsp;&nbsp;&nbsp;&nbsp;', '&nbsp;&nbsp;&nbsp;&nbsp;', '&nbsp;&nbsp;&nbsp;&nbsp;'];
+        $this->rulelist = Tree::instance()->getTreeList(Tree::instance()->getTreeArray(0), 'title');
+        $ruledata = [0 => __('None')];
+        foreach ($this->rulelist as $k => &$v) {
+            if (!$v['ismenu']) {
+                continue;
+            }
+            $ruledata[$v['id']] = $v['title'];
+            unset($v['spacer']);
+        }
+        unset($v);
+        $this->view->assign('ruledata', $ruledata);
+        $this->view->assign("menutypeList", $this->model->getMenutypeList());
+    }
+
+    /**
+     * 查看
+     */
+    public function index()
+    {
+        if ($this->request->isAjax()) {
+            $list = $this->rulelist;
+            $total = count($this->rulelist);
+            $result = array("total" => $total, "rows" => $list);
+
+            return json($result);
+        }
+        return $this->view->fetch();
+    }
+
+    /**
+     * 添加
+     */
+    public function add()
+    {
+        if ($this->request->isPost()) {
+            $this->token();
+            $params = $this->request->post("row/a", [], 'strip_tags');
+            if ($params) {
+                if (!$params['ismenu'] && !$params['pid']) {
+                    $this->error(__('The non-menu rule must have parent'));
+                }
+                $result = $this->model->validate()->save($params);
+                if ($result === false) {
+                    $this->error($this->model->getError());
+                }
+                Cache::rm('__menu__');
+                $this->success();
+            }
+            $this->error();
+        }
+        return $this->view->fetch();
+    }
+
+    /**
+     * 编辑
+     */
+    public function edit($ids = null)
+    {
+        $row = $this->model->get(['id' => $ids]);
+        if (!$row) {
+            $this->error(__('No Results were found'));
+        }
+        if ($this->request->isPost()) {
+            $this->token();
+            $params = $this->request->post("row/a", [], 'strip_tags');
+            if ($params) {
+                if (!$params['ismenu'] && !$params['pid']) {
+                    $this->error(__('The non-menu rule must have parent'));
+                }
+                if ($params['pid'] == $row['id']) {
+                    $this->error(__('Can not change the parent to self'));
+                }
+                if ($params['pid'] != $row['pid']) {
+                    $childrenIds = Tree::instance()->init(collection(PcAuthRule::select())->toArray())->getChildrenIds($row['id']);
+                    if (in_array($params['pid'], $childrenIds)) {
+                        $this->error(__('Can not change the parent to child'));
+                    }
+                }
+                //这里需要针对name做唯一验证
+                $ruleValidate = \think\Loader::validate('PcAuthRule');
+                $ruleValidate->rule([
+                    'name' => 'require|unique:PcAuthRule,name,' . $row->id,
+                ]);
+                $result = $row->validate()->save($params);
+                if ($result === false) {
+                    $this->error($row->getError());
+                }
+                Cache::rm('__menu__');
+                $this->success();
+            }
+            $this->error();
+        }
+        $this->view->assign("row", $row);
+        return $this->view->fetch();
+    }
+
+    /**
+     * 删除
+     */
+    public function del($ids = "")
+    {
+        if (!$this->request->isPost()) {
+            $this->error(__("Invalid parameters"));
+        }
+        $ids = $ids ? $ids : $this->request->post("ids");
+        if ($ids) {
+            $delIds = [];
+            foreach (explode(',', $ids) as $k => $v) {
+                $delIds = array_merge($delIds, Tree::instance()->getChildrenIds($v, true));
+            }
+            $delIds = array_unique($delIds);
+            $count = $this->model->where('id', 'in', $delIds)->delete();
+            if ($count) {
+                Cache::rm('__menu__');
+                $this->success();
+            }
+        }
+        $this->error();
+    }
+}

+ 32 - 0
application/admin/lang/zh-cn/pcauth/rule.php

@@ -0,0 +1,32 @@
+<?php
+
+return [
+    'Toggle all'                                                => '显示全部',
+    'Condition'                                                 => '规则条件',
+    'Remark'                                                    => '备注',
+    'Icon'                                                      => '图标',
+    'Alert'                                                     => '警告',
+    'Name'                                                      => '规则',
+    'Controller/Action'                                         => '控制器名/方法名',
+    'Ismenu'                                                    => '菜单',
+    'Menutype'                                                  => '菜单类型',
+    'Addtabs'                                                   => '选项卡(默认)',
+    'Dialog'                                                    => '弹窗',
+    'Ajax'                                                      => 'Ajax请求',
+    'Blank'                                                     => '链接',
+    'Extend'                                                    => '扩展属性',
+    'Search icon'                                               => '搜索图标',
+    'Toggle menu visible'                                       => '点击切换菜单显示',
+    'Toggle sub menu'                                           => '点击切换子菜单',
+    'Menu tips'                                                 => '父级菜单无需匹配控制器和方法,子级菜单请使用控制器名',
+    'Node tips'                                                 => '控制器/方法名,如果有目录请使用 目录名/控制器名/方法名',
+    'Url tips'                                                  => '一般情况下留空即可,如果是外部链接或相对链接请输入',
+    'The non-menu rule must have parent'                        => '非菜单规则节点必须有父级',
+    'Can not change the parent to child'                        => '父级不能是它的子级',
+    'Can not change the parent to self'                         => '父级不能是它自己',
+    'Name only supports letters, numbers, underscore and slash' => 'URL规则只能是小写字母、数字、下划线和/组成',
+    'permission'     => '权限标识',
+    'path'           => '路由地址',
+    'component'      => '组件路径',
+    'component_name' => '组件名',
+];

+ 62 - 0
application/admin/model/PcAuthRule.php

@@ -0,0 +1,62 @@
+<?php
+
+namespace app\admin\model;
+
+use think\Cache;
+use think\Model;
+
+class PcAuthRule extends Model
+{
+
+    // 开启自动写入时间戳字段
+    protected $autoWriteTimestamp = 'int';
+    // 定义时间戳字段名
+    protected $createTime = 'createtime';
+    protected $updateTime = 'updatetime';
+    // 数据自动完成字段
+    protected $insert = ['py', 'pinyin'];
+    protected $update = ['py', 'pinyin'];
+    // 拼音对象
+    protected static $pinyin = null;
+
+    protected static function init()
+    {
+        self::$pinyin = new \Overtrue\Pinyin\Pinyin('Overtrue\Pinyin\MemoryFileDictLoader');
+
+        self::beforeWrite(function ($row) {
+            if (isset($_POST['row']) && is_array($_POST['row']) && isset($_POST['row']['condition'])) {
+                $originRow = $_POST['row'];
+                $row['condition'] = $originRow['condition'] ?? '';
+            }
+        });
+        self::afterWrite(function ($row) {
+            Cache::rm('__menu__');
+        });
+    }
+
+    public function getTitleAttr($value, $data)
+    {
+        return __($value);
+    }
+
+    public function getMenutypeList()
+    {
+        return ['addtabs' => __('Addtabs'), 'dialog' => __('Dialog'), 'ajax' => __('Ajax'), 'blank' => __('Blank')];
+    }
+
+    public function setPyAttr($value, $data)
+    {
+        if (isset($data['title']) && $data['title']) {
+            return self::$pinyin->abbr(__($data['title']));
+        }
+        return '';
+    }
+
+    public function setPinyinAttr($value, $data)
+    {
+        if (isset($data['title']) && $data['title']) {
+            return self::$pinyin->permalink(__($data['title']), '');
+        }
+        return '';
+    }
+}

+ 52 - 0
application/admin/validate/PcAuthRule.php

@@ -0,0 +1,52 @@
+<?php
+
+namespace app\admin\validate;
+
+use think\Validate;
+
+class PcAuthRule extends Validate
+{
+
+    /**
+     * 正则
+     */
+    protected $regex = ['format' => '[a-z0-9_\/]+'];
+
+    /**
+     * 验证规则
+     */
+    protected $rule = [
+        'name'  => 'require|unique:PcAuthRule',
+        'title' => 'require',
+    ];
+
+    /**
+     * 提示消息
+     */
+    protected $message = [
+        'name.format' => 'URL规则只能是小写字母、数字、下划线和/组成'
+    ];
+
+    /**
+     * 字段描述
+     */
+    protected $field = [
+    ];
+
+    /**
+     * 验证场景
+     */
+    protected $scene = [
+    ];
+
+    public function __construct(array $rules = [], $message = [], $field = [])
+    {
+        $this->field = [
+            'name'  => __('Name'),
+            'title' => __('Title'),
+        ];
+        $this->message['name.format'] = __('Name only supports letters, numbers, underscore and slash');
+        parent::__construct($rules, $message, $field);
+    }
+
+}

+ 111 - 0
application/admin/view/pcauth/rule/add.html

@@ -0,0 +1,111 @@
+<form id="add-form" class="form-horizontal form-ajax" role="form" data-toggle="validator" method="POST" action="">
+    {:token()}
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Ismenu')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            {:build_radios('row[ismenu]', ['1'=>__('Yes'), '0'=>__('No')])}
+        </div>
+    </div>
+    <div class="form-group">
+        <label  class="control-label col-xs-12 col-sm-2">{:__('Parent')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            {:build_select('row[pid]', $ruledata, null, ['class'=>'form-control', 'required'=>''])}
+        </div>
+    </div>
+    <div class="form-group">
+        <label for="name" 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]" data-placeholder-node="{:__('Node tips')}" data-placeholder-menu="{:__('Menu tips')}" value="" data-rule="required" />
+        </div>
+    </div>
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Title')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <input type="text" class="form-control" id="title" name="row[title]" value="" data-rule="required" />
+        </div>
+    </div>
+    <div class="form-group" data-type="menu">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Url')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <input type="text" class="form-control" id="url" name="row[url]" value="" data-rule="" placeholder="{:__('Url tips')}" />
+        </div>
+    </div>
+    <div class="form-group">
+        <label for="icon" class="control-label col-xs-12 col-sm-2">{:__('Icon')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <div class="input-group input-groupp-md">
+                <span class="input-group-addon"><i class="fa fa-circle-o" id="icon-style"></i></span>
+                <input type="text" class="form-control" id="icon" name="row[icon]" value="fa fa-circle-o" />
+                <a href="javascript:;" class="btn-search-icon input-group-addon">{:__('Search icon')}</a>
+            </div>
+        </div>
+    </div>
+    <div class="form-group">
+        <label for="remark" class="control-label col-xs-12 col-sm-2">{:__('Condition')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <textarea class="form-control" id="condition" name="row[condition]"></textarea>
+        </div>
+    </div>
+    <div class="form-group" data-type="menu">
+        <label for="remark" class="control-label col-xs-12 col-sm-2">{:__('Menutype')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            {:build_radios('row[menutype]', $menutypeList)}
+        </div>
+    </div>
+    <div class="form-group" data-type="menu">
+        <label for="remark" class="control-label col-xs-12 col-sm-2">{:__('Extend')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <textarea class="form-control" id="extend" name="row[extend]"></textarea>
+        </div>
+    </div>
+    <div class="form-group">
+        <label for="remark" class="control-label col-xs-12 col-sm-2">{:__('Remark')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <textarea class="form-control" id="remark" name="row[remark]"></textarea>
+        </div>
+    </div>
+    <div class="form-group">
+        <label for="weigh" class="control-label col-xs-12 col-sm-2">{:__('Weigh')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <input type="text" class="form-control" id="weigh" name="row[weigh]" value="0" data-rule="required" />
+        </div>
+    </div>
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Status')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            {:build_radios('row[status]', ['normal'=>__('Normal'), 'hidden'=>__('Hidden')])}
+        </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">
+            <input type="text" class="form-control" id="permission" name="row[permission]" value="" />
+        </div>
+    </div>
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('path')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <input type="text" class="form-control" id="path" name="row[path]" value="" />
+        </div>
+    </div>
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('component')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <input type="text" class="form-control" id="component" name="row[component]" value="" />
+        </div>
+    </div>
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('component_name')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <input type="text" class="form-control" id="component_name" name="row[component_name]" value="" />
+        </div>
+    </div>
+    <div class="form-group hidden layer-footer">
+        <div class="col-xs-2"></div>
+        <div class="col-xs-12 col-sm-8">
+            <button type="submit" class="btn btn-primary btn-embossed disabled">{:__('OK')}</button>
+            <button type="reset" class="btn btn-default btn-embossed">{:__('Reset')}</button>
+        </div>
+    </div>
+</form>
+{include file="auth/rule/tpl" /}

+ 111 - 0
application/admin/view/pcauth/rule/edit.html

@@ -0,0 +1,111 @@
+<form id="edit-form" class="form-horizontal form-ajax" role="form" method="POST" action="">
+    {:token()}
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Ismenu')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            {:build_radios('row[ismenu]', ['1'=>__('Yes'), '0'=>__('No')], $row['ismenu'])}
+        </div>
+    </div>
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Parent')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            {:build_select('row[pid]', $ruledata, $row['pid'], ['class'=>'form-control', 'required'=>''])}
+        </div>
+    </div>
+    <div class="form-group">
+        <label for="name" 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]" data-placeholder-node="{:__('Node tips')}" data-placeholder-menu="{:__('Menu tips')}" value="{$row.name|htmlentities}" data-rule="required" />
+        </div>
+    </div>
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Title')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <input type="text" class="form-control" id="title" name="row[title]" value="{$row.title|htmlentities}" data-rule="required" />
+        </div>
+    </div>
+    <div class="form-group" data-type="menu">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Url')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <input type="text" class="form-control" id="url" name="row[url]" value="{$row.url|htmlentities}" data-rule="" placeholder="{:__('Url tips')}" />
+        </div>
+    </div>
+    <div class="form-group">
+        <label for="icon" class="control-label col-xs-12 col-sm-2">{:__('Icon')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <div class="input-group input-groupp-md">
+                <span class="input-group-addon"><i class="{$row.icon}" id="icon-style"></i></span>
+                <input type="text" class="form-control" id="icon" name="row[icon]" value="{$row.icon}" />
+                <a href="javascript:;" class="btn-search-icon input-group-addon">{:__('Search icon')}</a>
+            </div>
+        </div>
+    </div>
+    <div class="form-group">
+        <label for="remark" class="control-label col-xs-12 col-sm-2">{:__('Condition')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <textarea class="form-control" id="condition" name="row[condition]">{$row.condition|htmlentities}</textarea>
+        </div>
+    </div>
+    <div class="form-group" data-type="menu">
+        <label for="remark" class="control-label col-xs-12 col-sm-2">{:__('Menutype')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            {:build_radios('row[menutype]', $menutypeList, $row['menutype'])}
+        </div>
+    </div>
+    <div class="form-group" data-type="menu">
+        <label for="remark" class="control-label col-xs-12 col-sm-2">{:__('Extend')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <textarea class="form-control" id="extend" name="row[extend]">{$row.extend|htmlentities}</textarea>
+        </div>
+    </div>
+    <div class="form-group">
+        <label for="remark" class="control-label col-xs-12 col-sm-2">{:__('Remark')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <textarea class="form-control" id="remark" name="row[remark]">{$row.remark|__|htmlentities}</textarea>
+        </div>
+    </div>
+    <div class="form-group">
+        <label for="weigh" class="control-label col-xs-12 col-sm-2">{:__('Weigh')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <input type="text" class="form-control" id="weigh" name="row[weigh]" value="{$row.weigh}" data-rule="required" />
+        </div>
+    </div>
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Status')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            {:build_radios('row[status]', ['normal'=>__('Normal'), 'hidden'=>__('Hidden')], $row['status'])}
+        </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">
+            <input type="text" class="form-control" id="permission" name="row[permission]" value="{$row.permission|htmlentities}" />
+        </div>
+    </div>
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('path')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <input type="text" class="form-control" id="path" name="row[path]" value="{$row.path|htmlentities}" />
+        </div>
+    </div>
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('component')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <input type="text" class="form-control" id="component" name="row[component]" value="{$row.component|htmlentities}" />
+        </div>
+    </div>
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('component_name')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <input type="text" class="form-control" id="component_name" name="row[component_name]" value="{$row.component_name|htmlentities}" />
+        </div>
+    </div>
+    <div class="form-group hidden layer-footer">
+        <div class="col-xs-2"></div>
+        <div class="col-xs-12 col-sm-8">
+            <button type="submit" class="btn btn-primary btn-embossed disabled">{:__('OK')}</button>
+            <button type="reset" class="btn btn-default btn-embossed">{:__('Reset')}</button>
+        </div>
+    </div>
+</form>
+{include file="auth/rule/tpl" /}

+ 35 - 0
application/admin/view/pcauth/rule/index.html

@@ -0,0 +1,35 @@
+<style>
+    .bootstrap-table tr td .text-muted {color:#888;}
+</style>
+<div class="panel panel-default panel-intro">
+    {:build_heading()}
+
+    <div class="panel-body">
+        <div id="myTabContent" class="tab-content">
+            <div class="tab-pane fade active in" id="one">
+                <div class="widget-body no-padding">
+                    <div id="toolbar" class="toolbar">
+                        <a href="javascript:;" class="btn btn-primary btn-refresh" title="{:__('Refresh')}" data-force-refresh="false"><i class="fa fa-refresh"></i> </a>
+                        <a href="javascript:;" class="btn btn-success btn-add {:$auth->check('auth/rule/add')?'':'hide'}" title="{:__('Add')}" ><i class="fa fa-plus"></i> {:__('Add')}</a>
+                        <a href="javascript:;" class="btn btn-success btn-edit btn-disabled disabled {:$auth->check('auth/rule/edit')?'':'hide'}" title="{:__('Edit')}" ><i class="fa fa-pencil"></i> {:__('Edit')}</a>
+                        <a href="javascript:;" class="btn btn-danger btn-del btn-disabled disabled {:$auth->check('auth/rule/del')?'':'hide'}" title="{:__('Delete')}" ><i class="fa fa-trash"></i> {:__('Delete')}</a>
+                        <div class="dropdown btn-group {:$auth->check('auth/rule/multi')?'':'hide'}">
+                            <a class="btn btn-primary btn-more dropdown-toggle btn-disabled disabled" data-toggle="dropdown"><i class="fa fa-cog"></i> {:__('More')}</a>
+                            <ul class="dropdown-menu text-left" role="menu">
+                                <li><a class="btn btn-link btn-multi btn-disabled disabled" href="javascript:;" data-params="status=normal"><i class="fa fa-eye"></i> {:__('Set to normal')}</a></li>
+                                <li><a class="btn btn-link btn-multi btn-disabled disabled" href="javascript:;" data-params="status=hidden"><i class="fa fa-eye-slash"></i> {:__('Set to hidden')}</a></li>
+                            </ul>
+                        </div>
+                        <a href="javascript:;" class="btn btn-danger btn-toggle-all"><i class="fa fa-plus"></i> {:__('Toggle all')}</a>
+                    </div>
+                    <table id="table" class="table table-bordered table-hover"
+                           data-operate-edit="{:$auth->check('auth/rule/edit')}"
+                           data-operate-del="{:$auth->check('auth/rule/del')}"
+                           width="100%">
+                    </table>
+                </div>
+            </div>
+
+        </div>
+    </div>
+</div>

+ 43 - 0
application/admin/view/pcauth/rule/tpl.html

@@ -0,0 +1,43 @@
+<style>
+    #chooseicon {
+        margin:10px;
+    }
+    #chooseicon ul {
+        margin:5px 0 0 0;
+    }
+    #chooseicon ul li{
+        width:41px;height:42px;
+        line-height:42px;
+        border:1px solid #efefef;
+        padding:1px;
+        margin:1px;
+        text-align: center;
+        font-size:18px;
+    }
+    #chooseicon ul li:hover{
+        border:1px solid #2c3e50;
+        cursor:pointer;
+    }
+</style>
+<script id="chooseicontpl" type="text/html">
+    <div id="chooseicon">
+        <div>
+            <form onsubmit="return false;">
+                <div class="input-group input-groupp-md">
+                    <div class="input-group-addon">{:__('Search icon')}</div>
+                    <input class="js-icon-search form-control" type="text" placeholder="">
+                </div>
+            </form>
+        </div>
+        <div>
+            <ul class="list-inline">
+                <% for(var i=0; i<iconlist.length; i++){ %>
+                    <li data-font="<%=iconlist[i]%>" data-toggle="tooltip" title="<%=iconlist[i]%>">
+                    <i class="fa fa-<%=iconlist[i]%>"></i>
+                </li>
+                <% } %>
+            </ul>
+        </div>
+
+    </div>
+</script>

+ 225 - 0
public/assets/js/backend/pcauth/rule.js

@@ -0,0 +1,225 @@
+define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function ($, undefined, Backend, Table, Form, Template) {
+
+    var Controller = {
+        index: function () {
+            // 初始化表格参数配置
+            Table.api.init({
+                extend: {
+                    "index_url": "pcauth/rule/index",
+                    "add_url": "pcauth/rule/add",
+                    "edit_url": "pcauth/rule/edit",
+                    "del_url": "pcauth/rule/del",
+                    "multi_url": "pcauth/rule/multi",
+                    "table": "pc_auth_rule"
+                }
+            });
+
+            var table = $("#table");
+
+            // 初始化表格
+            table.bootstrapTable({
+                url: $.fn.bootstrapTable.defaults.extend.index_url,
+                sortName: '',
+                escape: false,
+                columns: [
+                    [
+                        {field: 'state', checkbox: true,},
+                        {field: 'id', title: 'ID'},
+                        {field: 'title', title: __('Title'), align: 'left', formatter: Controller.api.formatter.title, clickToSelect: !false},
+                        {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: 'permission', title: __('permission')},
+                        {field: 'path', title: __('path')},
+                        {field: 'component', title: __('component')},
+                        {field: 'component_name', title: __('component_name')},
+                        {field: 'status', title: __('Status'), formatter: Table.api.formatter.status},
+                        {
+                            field: 'ismenu',
+                            title: __('Ismenu'),
+                            align: 'center',
+                            table: table,
+                            formatter: Table.api.formatter.toggle
+                        },
+                        {
+                            field: 'operate',
+                            title: __('Operate'),
+                            table: table,
+                            events: Table.api.events.operate,
+                            formatter: Table.api.formatter.operate
+                        }
+                    ]
+                ],
+                pagination: false,
+                search: false,
+                commonSearch: false,
+                rowAttributes: function (row, index) {
+                    return row.pid == 0 ? {} : {style: "display:none"};
+                }
+            });
+
+            // 为表格绑定事件
+            Table.api.bindevent(table);
+
+            var btnSuccessEvent = function (data, ret) {
+                if ($(this).hasClass("btn-change")) {
+                    var index = $(this).data("index");
+                    var row = Table.api.getrowbyindex(table, index);
+                    row.ismenu = $("i.fa.text-gray", this).length > 0 ? 1 : 0;
+                    table.bootstrapTable("updateRow", {index: index, row: row});
+                } else if ($(this).hasClass("btn-delone")) {
+                    if ($(this).closest("tr[data-index]").find("a.btn-node-sub.disabled").length > 0) {
+                        $(this).closest("tr[data-index]").remove();
+                    } else {
+                        table.bootstrapTable('refresh');
+                    }
+                } else if ($(this).hasClass("btn-dragsort")) {
+                    table.bootstrapTable('refresh');
+                }
+                Fast.api.refreshmenu();
+                return false;
+            };
+
+            //表格内容渲染前
+            table.on('pre-body.bs.table', function (e, data) {
+                var options = table.bootstrapTable("getOptions");
+                options.escape = true;
+            });
+
+            //当内容渲染完成后
+            table.on('post-body.bs.table', function (e, data) {
+                var options = table.bootstrapTable("getOptions");
+                options.escape = false;
+
+                //点击切换/排序/删除操作后刷新左侧菜单
+                $(".btn-change[data-id],.btn-delone,.btn-dragsort").data("success", btnSuccessEvent);
+
+            });
+
+            table.on('post-body.bs.table', function (e, settings, json, xhr) {
+                //显示隐藏子节点
+                $(">tbody>tr[data-index] > td", this).on('click', "a.btn-node-sub", function () {
+                    var status = $(this).data("shown") ? true : false;
+                    $("a[data-pid='" + $(this).data("id") + "']").each(function () {
+                        $(this).closest("tr").toggle(!status);
+                    });
+                    if (status) {
+                        $("a[data-pid='" + $(this).data("id") + "']").trigger("collapse");
+                    }
+                    $(this).data("shown", !status);
+                    $("i", this).toggleClass("fa-caret-down").toggleClass("fa-caret-right");
+                    return false;
+                });
+            });
+
+            //隐藏子节点
+            $(document).on("collapse", ".btn-node-sub", function () {
+                if ($("i", this).length > 0) {
+                    $("a[data-pid='" + $(this).data("id") + "']").trigger("collapse");
+                }
+                $("i", this).removeClass("fa-caret-down").addClass("fa-caret-right");
+                $(this).data("shown", false);
+                $(this).closest("tr").toggle(false);
+            });
+
+            //批量删除后的回调
+            $(".toolbar > .btn-del,.toolbar .btn-more~ul>li>a").data("success", function (e) {
+                Fast.api.refreshmenu();
+            });
+
+            //展开隐藏一级
+            $(document.body).on("click", ".btn-toggle", function (e) {
+                $("a[data-id][data-pid][data-pid!=0].disabled").closest("tr").hide();
+                var that = this;
+                var show = $("i", that).hasClass("fa-chevron-down");
+                $("i", that).toggleClass("fa-chevron-down", !show).toggleClass("fa-chevron-up", show);
+                $("a[data-id][data-pid][data-pid!=0]").not('.disabled').closest("tr").toggle(show);
+                $(".btn-node-sub[data-pid=0]").data("shown", show);
+            });
+
+            //展开隐藏全部
+            $(document.body).on("click", ".btn-toggle-all", function (e) {
+                var that = this;
+                var show = $("i", that).hasClass("fa-plus");
+                $("i", that).toggleClass("fa-plus", !show).toggleClass("fa-minus", show);
+                $(".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);
+            });
+        },
+        add: function () {
+            Controller.api.bindevent();
+        },
+        edit: function () {
+            Controller.api.bindevent();
+        },
+        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>';
+                },
+                name: function (value, row, index) {
+                    return !row.ismenu || row.status == 'hidden' ? "<span class='text-muted'>" + value + "</span>" : value;
+                },
+                icon: function (value, row, index) {
+                    return '<span class="' + (!row.ismenu || row.status == 'hidden' ? 'text-muted' : '') + '"><i class="' + value + '"></i></span>';
+                }
+            },
+            bindevent: function () {
+                $(document).on('click', "input[name='row[ismenu]']", function () {
+                    var name = $("input[name='row[name]']");
+                    var ismenu = $(this).val() == 1;
+                    name.prop("placeholder", ismenu ? name.data("placeholder-menu") : name.data("placeholder-node"));
+                    $('div[data-type="menu"]').toggleClass("hidden", !ismenu);
+                });
+                $("input[name='row[ismenu]']:checked").trigger("click");
+
+                var iconlist = [];
+                var iconfunc = function () {
+                    Layer.open({
+                        type: 1,
+                        area: ['99%', '98%'], //宽高
+                        content: Template('chooseicontpl', {iconlist: iconlist})
+                    });
+                };
+                Form.api.bindevent($("form[role=form]"), function (data) {
+                    Fast.api.refreshmenu();
+                });
+                $(document).on('change keyup', "#icon", function () {
+                    $(this).prev().find("i").prop("class", $(this).val());
+                });
+                $(document).on('click', ".btn-search-icon", function () {
+                    if (iconlist.length == 0) {
+                        $.get(Config.site.cdnurl + "/assets/libs/font-awesome/less/variables.less", function (ret) {
+                            var exp = /fa-var-(.*):/ig;
+                            var result;
+                            while ((result = exp.exec(ret)) != null) {
+                                iconlist.push(result[1]);
+                            }
+                            iconfunc();
+                        });
+                    } else {
+                        iconfunc();
+                    }
+                });
+                $(document).on('click', '#chooseicon ul li', function () {
+                    $("input[name='row[icon]']").val('fa fa-' + $(this).data("font")).trigger("change");
+                    Layer.closeAll();
+                });
+                $(document).on('keyup', 'input.js-icon-search', function () {
+                    $("#chooseicon ul li").show();
+                    if ($(this).val() != '') {
+                        $("#chooseicon ul li:not([data-font*='" + $(this).val() + "'])").hide();
+                    }
+                });
+            }
+        }
+    };
+    return Controller;
+});