yii2 kartik Tree Manager (TreeView) 使用小结与一个坑

kartik Tree Manager 是作为一个模块提供的,所以,观察其源代码,有模块入口文件 Module.php,widget组件包括了TreeView和TreeViewInput,前者用来展示和管理树形节点,后者是可以树形方式选择节点作为输入。

TreeView这个widget,不仅在左边显示树形节点,而且在右边以表单形式显示当前选中节点的详情,表单视图用分部视图_form提供,并且它是可扩展的(更多的表单输入和更多的详情展示)。

Tree Manager 提供了完整的数据迁移(创建树形结构的数据库表),支持嵌套集合方式管理树形节点的模型类,管理节点用到的控制器。

示例如下(以部门为例,字段只增加部门名称和创建/修改时间戳):

创建数据迁移,添加 safeUp如下

    public function safeUp()
    {
        $this->createTable('department', [
            'id' => $this->primaryKey(),
            'name' => $this->string(64)->notNull()->comment('部门名称'),  // 除了 name,其余字段都是 kartik要求的
            'root' => $this->integer(),                    // 不要为 root 等后面的字段生成验证规则,因为都是内部字段
            'lft' => $this->integer()->notNull(),
            'rgt' => $this->integer()->notNull(),
            'lvl' => $this->smallInteger(5)->notNull(),
            'icon' => $this->string(255),
            'icon_type' => $this->smallInteger(1)->notNull()->defaultValue(1),
            'active' => $this->boolean()->notNull()->defaultValue(true),
            'selected' => $this->boolean()->notNull()->defaultValue(false),
            'disabled' => $this->boolean()->notNull()->defaultValue(false),
            'readonly' => $this->boolean()->notNull()->defaultValue(false),
            'visible' => $this->boolean()->notNull()->defaultValue(true),
            'collapsed' => $this->boolean()->notNull()->defaultValue(false),
            'movable_u' => $this->boolean()->notNull()->defaultValue(true),
            'movable_d' => $this->boolean()->notNull()->defaultValue(true),
            'movable_l' => $this->boolean()->notNull()->defaultValue(true),
            'movable_r' => $this->boolean()->notNull()->defaultValue(true),
            'removable' => $this->boolean()->notNull()->defaultValue(true),
            'removable_all' => $this->boolean()->notNull()->defaultValue(false),
            'child_allowed' => $this->boolean()->notNull()->defaultValue(true),
            'created_at' => $this->integer()->comment('创建时间'),
            'updated_at' => $this->integer()->comment('修改时间'),
        ]);
        $this->addCommentOnTable('department', '部门');
        $this->createIndex('fk_department_NK1', 'department', 'root');
        $this->createIndex('fk_department_NK2', 'department', 'lft');
        $this->createIndex('fk_department_NK3', 'department', 'rgt');
        $this->createIndex('fk_department_NK4', 'department', 'lvl');
        $this->createIndex('fk_department_NK5', 'department', 'active');

    }

创建模型,并且让它从 Tree Manager 提供的模型类 Tree 继承(Tree 本身是从 ActiveRecord继承的,所以,可以直接套用gii生成的代码,但是有几个注意点:不要绝大部分字段的验证规则,因为它们都是内部工作的,另外,如果使用新的behavior,必须合并父类Tree的behavior!否则嵌套集合的操作无法使用,因为NestedSetsBehavior是作为TreeTrait嵌入到Tree类的)

    public function behaviors()
    {
        return array_merge(parent::behaviors(), [TimestampBehavior::class]); // 必须合并父类的行为
    }


    public function rules()
    {
        return [
            [['name'], 'required'],
            [['created_at', 'updated_at'], 'integer'],
            [['name'], 'string', 'max' => 64],
        ];
    }

为了能用控制台操作节点,我们在控制台console应用中新建一个模型Node,它从Department派生,并且为其添加嵌套集合管理的行为(注意:creocoder中用来表示深度的字段和kartik的不同,必须明确重新定义)。这么做的原因是,默认的Tree依赖Tree Manager模块,它是web方式的模块,控制台不能工作。

<?php

namespace console\models;

use common\models\Department;
use creocoder\nestedsets\NestedSetsBehavior;

class Node extends Department
{
    public function behaviors()
    {
        return [
            'tree' => [
                'class' => NestedSetsBehavior::class,
                // 'treeAttribute' => 'tree',
                // 'leftAttribute' => 'lft',
                // 'rightAttribute' => 'rgt',
                'depthAttribute' => 'lvl',  // kartik 修改了 creocoder 此字段名
            ],
        ];
    }
}

创建新的数据迁移,用Node模型类来添加一些节点

    public function safeUp()
    {
        // 添加 节点数据
        $root = new Node(['name' => '全国NK']); // Node继承自 Department, 必须先创建 Department模型 才能进行此迁移
        $root->makeRoot();
        $guangxi = new Node(['name' => '广西NK']);
        $guangxi->appendTo($root);
        $yongxing = new Node(['name' => 'YX畜牧']);
        $yongxing->appendTo($guangxi);
        $admins = new Node(['name' => 'admins']);
        $admins->appendTo($yongxing);
        $deptNames = ['领导班子成员', '生产技术部', '兽医技术中心', '育种部', '产业化', '猪场'];
        foreach ($deptNames as $name) {
            $dept = new Node(['name' => $name]);
            $dept->appendTo($yongxing);
        }
        $models = Node::find()->orderBy('id')->all();
        echo "\nAll nodes as follows:\n";
        echo "ID\tName\tRoot\tLeft\tRight\tLevel\n";
        foreach ($models as $model) {
            echo $model->id."\t".$model->name."\t".$model->root."\t".$model->lft."\t".$model->rgt."\t".$model->lvl."\n";
        }
        // 获得 指定名字 的节点 对应id
        ...........
        // 建立用户到部门的外键联系
        $this->update('user', ['department_id' => $deptNameId['admins']]);  //此前添加的用户必然是管理员
        $this->addForeignKey(
            'fk-user-department_id',
            'user',
            'department_id',
            'department',
            'id',
            'CASCADE'
        );
        // 导入用户对应部门的数据
        ...............       
    }

 

在应用中为Tree Manager配置模块,从而该模块可以帮我们管理树形节点(相当于树形节点的操作可以被NodeController的有关动作处理)。在common/config/main.php添加下面代码的treemanager部分

'modules' => [
        'gridview' => [
            'class' => '\kartik\grid\Module', 
        ],
        'treemanager' =>  [
            'class' => '\kartik\tree\Module',
            // other module settings, refer detailed documentation
        ]
    ],

接下来就可以设置控制器并在视图中用TreeView来显示树形节点了(记住 class DepartmentController extends NodeController  // 从 NodeController继承)

    public function actionIndex()
    {
        $rootDept = Department::findOne(['lvl' => 0]);  // 0 = 全国NK
        $yxDept = Department::findOne(['lvl' => 2]);  // YX畜牧
        $seeDept = $yxDept;
        $currentDept = Yii::$app->session->get('current_dept', $seeDept);

        // add children and itself
        $query = $seeDept ? $seeDept->children()->orWhere(['id' => $seeDept->id])
            ->andFilterCompare('name', 'admins', '<>')
            ->addOrderBy('root, lft') :
            Department::find();

        return $this->render('index', [
            'query' => $query,
            'currentDept' => $currentDept,
            'rootDept' => $rootDept
        ]);
    }
<div class="department-index">
<?= TreeView::widget([
    'query' => $query,
    'headingOptions' => ['label' => '部门'],
    'breadcrumbs' => [
        'depth' => '2',
    ],
    'allowNewRoots' => $rootDept == null,
    'nodeView' => '@kvtree/views/_form',  // 此为默认 form
    'showIDAttribute' => false,  // 不显示 ID
    'iconEditSettings' => ['show' => 'none'],  // 取消 图标 编辑
    'showFormButtons' => true,
    'nodeAddlViews' => [
        \kartik\tree\Module::VIEW_PART_5 => '@backend/views/department/_grid',
    ],
    'nodeActions' => [
        \kartik\tree\Module::NODE_REMOVE => \yii\helpers\Url::to(['remove']),
    ],
    'fontAwesome' => true,     // 图标选择
    'isAdmin' => false,         // optional (toggle to enable admin mode)
    'rootOptions' => ['label' => '您可以查阅的范围'],
    'displayValue' => $currentDept->id,        // initial display value
    'toolbar' =>  [
//            TreeView::BTN_REFRESH => true,
//            TreeView::BTN_CREATE => true,
//            TreeView::BTN_CREATE_ROOT => true,
//            TreeView::BTN_REMOVE => true,
//            TreeView::BTN_MOVE_UP => false,
//            TreeView::BTN_MOVE_DOWN => false,
        TreeView::BTN_MOVE_LEFT => false,
        TreeView::BTN_MOVE_RIGHT => false,
        TreeView::BTN_SEPARATOR => false,
    ],
    'treeOptions' => ['style' => 'height: 500px'],
    'detailOptions' => ['style' => 'height: 590px'],
    'softDelete' => true,       // defaults to true
    'cacheSettings' => [
        'enableCache' => true   // defaults to true
    ]
])
?>
</div>

我们使用了默认的详情表单,并为详情表单添加了额外的分部视图_grid(当点击节点时,总是会post一堆数据,我们可以利用post的数据,在详情表单显示所需的数据)

$posted = Yii::$app->request->post();
//var_dump($posted);

$users = null;
if (array_key_exists('id', $posted)) {
    $currentDeptId = $posted['id'];
    $users = User::find()->where(['department_id' => $currentDeptId])->all();
}

?>
    <div>部门人员:</div>
<?php if (empty($users)) : ?>
    <div class="text-danger">(无)</div>
<?php else: ?>
    <div class="card">
        <div class="card-body">
            <?php foreach ($users as $user) : ?>
                <span class="badge badge-info"><?= $user->name . '(' . $user->username . ')' ?></span>
            <?php endforeach; ?>
        </div>
    </div>
<?php endif; ?>

对于移除节点,我们在DepartmentController中添加了对应的action,它是从NodeController的对应代码抄过来略加改写的(即实现override),防止部门下有用户时删除部门

    public function actionRemove()  // 从 kartik 源码改写
    {
        static::checkValidRequest();
        $data = static::getPostData();
        $nodeTitles = TreeSecurity::getNodeTitles($data);
        $callback = function () use ($data) {
            $id = ArrayHelper::getValue($data, 'id', null);
            $parsedData = TreeSecurity::parseRemoveData($data);
            $out = $parsedData['out'];
            $oldHash = $parsedData['oldHash'];
            $newHash = $parsedData['newHash'];
            /**
             * @var Tree $treeClass
             * @var Tree $node
             */
            $treeClass = $out['treeClass'];
            TreeSecurity::checkSignature('remove', $oldHash, $newHash);
            /**
             * @var Tree $node
             */
            $node = $treeClass::findOne($id);
            return $node->canDelete() && $node->removeNode($out['softDelete']);
        };
        return self::process($callback, '移除节点时发生错误(可能节点对应记录非空)。请稍后再试。', '节点删除成功');
    }

如果详情表单中用GridView来显示数据,要注意,GridView的分页组件,默认的基础目录并非我们的控制器,而是TreeView默认的那些action,所以用GridView显示数据,需要自己“管理”节点(自己写actionManage)。可以自己参考这个帖子 https://www.yiichina.com/tutorial/653

 

这里说的坑,就是

  'breadcrumbs' => [
        'depth' => '2',
    ],

这里的'2',如果设置为2,就会遇到问题。因为TreeSecurity组件初始用TreeView设置的值来计算hash值(我们设置为2,就是整形),后期用post传递的值计算hash值,post时,2总是字符串类型,两种类型在json并hash后,得到的是不同结果,就会遭遇签名错误(给作者github提了个issue,不知道作者会修改代码还是修改使用文档说明)。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值