
客戶需要這種左側樹形分類,點擊子節點后 顯示對應分類下列表的表格
我看海豚自帶jstree, 就研究了一下官網demo sitebrowser。
## 樹的問題
### 樹的展示
首先復制一份common下的builder table對應的layout,在js_list那塊后面加上樹的js
~~~
<script src="__LIBS__/jstree/jstree.min.js"></script>
<script>
$(document).ready(function(){
var post_url = '{:url("material/operation", [], false, false)}';
$('#jstree').jstree({
core : {
'data' : {
url : post_url+'?operation=get_node&prop=1&cid={:input("cid", 0)}',
data : function (node) {
return { 'id' : node.id };
}
},
check_callback : true,
themes : {
responsive : false
},
},
force_text : true,
plugins: ["contextmenu", "dnd"],
contextmenu: {
"items": {
"新建菜單": {
"label": "新增",
"action": function(data) {
var ref = $('#jstree').jstree(true),
sel = ref.get_selected();
if(!sel.length) {
return false;
}
sel = sel[0];
sel = ref.create_node(sel, {
// type: "file",
// icon: "glyphicon glyphicon-folder-open",
});
if(sel) {
ref.edit(sel);
}
}
},
"編輯菜單": {
"label": "編輯",
"action": function(data) {
console.log(data);
var ref = $('#jstree').jstree(true),
sel = ref.get_selected();
if(!sel.length) {
return false;
}
sel = sel[0];
ref.edit(sel);
}
},
"刪除菜單": {
"label": "刪除",
"action": function(data) {
console.log(data);
var ref = $('#jstree').jstree(true),
sel = ref.get_selected();
if(!sel.length) {
return false;
}
ref.delete_node(sel);
}
},
},
select_mode:false
}
})
.on("loaded.jstree", function (event, data) {
data.instance.open_node(-1);
})
.on('click.jstree', function (e, data) {
console.log(e);
})
.on('changed.jstree', function (e, data) {
console.log(e);
console.log(data)
if(data && data.selected && data.selected.length) {
// 非右鍵時跳轉
if(data.event.which !== 3){
location.href = dolphin.curr_url+'/cid/'+data.selected;
}
}else {
location.href = dolphin.curr_url;
}
return ;
})
.on('delete_node.jstree', function (e, data) {
$.get(post_url+'?operation=delete_node', { 'id' : data.node.id })
.fail(function () {
data.instance.refresh();
});
})
.on('create_node.jstree', function (e, data) {
$.get(post_url+'?operation=create_node', { 'id' : data.node.parent, 'position' : data.position, 'text' : data.node.text })
.done(function (d) {
data.instance.set_id(data.node, d.id);
})
.fail(function () {
data.instance.refresh();
});
})
.on('rename_node.jstree', function (e, data) {
$.get(post_url+ '?operation=rename_node', { 'id' : data.node.id, 'text' : data.text })
.fail(function () {
data.instance.refresh();
});
})
;
});
</script>
~~~
這個其實海豚自帶的util/Tree類的toLayer方法就能實現
但是遇到一個問題,如何加載樹就全展開,試了好多參數都不行,干脆sql查詢時加上 state:{opened:true}
### 點擊分類后跳轉
demo里有change 事件,想法是拿到節點id 自己拼url,然后跳轉。
但是遇到問題是,點右鍵也觸發change,看上面代碼,排除which == 3 的情況。
### 右鍵的定制
從網上找了個文章覆蓋 開了右鍵插件后的 contextmenu 屬性
### 樹的節點操作
先將官方的類修改了一通,改成tp5的db操作。不過沒有實現剪切、粘貼
~~~
<?php
namespace util;
use think\Db;
/**
* 樹結構生成類
* @author CaiWeiMing <314013107@qq.com>
*/
class JsTree
{
protected static $instance;
/**
* 架構函數
* @param array $config
*/
public function __construct($config = [])
{
self::$config = array_merge(self::$config, $config);
// trace(self::$config);
}
/**
* 配置參數
* @var array
*/
protected static $config = [
'table' => '',
'id' => 'id', // id名稱
'pid' => 'pid', // pid名稱
'left' => 'left',
'right' => 'right',
'level' => 'level',
'title' => 'title', // 標題名稱
'child' => 'children', // 子元素鍵名
'position' => 'pos',
'html' => '┝ ', // 層級標記
'step' => 4, // 層級步進數量
];
/**
* 配置參數
* @param array $config
* @return object
*/
public static function config($config = [])
{
if (!empty($config)) {
$config = array_merge(self::$config, $config);
}
if (is_null(self::$instance)) {
self::$instance = new static($config);
}
return self::$instance;
}
/**
* 將數據集格式化成層次結構
* @param array/object $lists 要格式化的數據集,可以是數組,也可以是對象
* @param int $pid 父級id
* @param int $max_level 最多返回多少層,0為不限制
* @param int $curr_level 當前層數
* @author 蔡偉明 <314013107@qq.com>
* @return array
*/
public static function toLayer($lists = [], $pid = 0, $max_level = 0, $curr_level = 0)
{
$trees = [];
$lists = array_values($lists);
foreach ($lists as $key => $value) {
if ($value[self::$config['pid']] == $pid) {
if ($max_level > 0 && $curr_level == $max_level) {
return $trees;
}
unset($lists[$key]);
$child = self::toLayer($lists, $value[self::$config['id']], $max_level, $curr_level + 1);
if (!empty($child)) {
$value[self::$config['child']] = $child;
}
$trees[] = $value;
}
}
return $trees;
}
public function get_node($id, $options = ['with_children'=>true]) {
$node = db(self::$config['table'])->where(self::$config['id'], $id)->find();
if(!$node) {
throw new \Exception('Node does not exist');
}
if(isset($options['with_children'])) {
$node['children'] = $this->get_children($id, isset($options['deep_children']));
}
if(isset($options['with_path'])) {
$node['path'] = $this->get_path($id);
}
return $node;
}
public function get_children($id, $recursive = false) {
$config = self::$config;
if($recursive) {
$node = $this->get_node($id);
return db(self::$config['table'])
->where($config['left'], 'gt', (int)$node[$config['left']])
->where($config['right'], 'lt', (int)$node[$config['right']])
->order($config['left'])
->select();
}else {
return db($config['table'])
->where($config['pid'], (int)$id)
->order($config['position'])
->select();
}
}
public function get_path($id) {
$node = $this->get_node($id);
$config = self::$config;
if($node) {
return db($config['table'])
->where($config['left'], 'lt', (int)$node[$config['left']])
->where($config['right'], 'gt', (int)$node[$confg['right']])
->order($config['left'])
->select();
}else{
return false;
}
}
public function mk($parent, $position = 0, $data = []) {
$parent = (int)$parent;
if($parent == 0) {
throw new Exception('Parent is 0');
}
$parent = $this->get_node($parent, ['with_children'=> true]);
if(!$parent['children']) { $position = 0; }
if($parent['children'] && $position >= count($parent['children'])) {
$position = count($parent['children']);
}
$sql = [];
$par = [];
// PREPARE NEW PARENT
// update positions of all next elements
$config = self::$config;
db($config['table'])->where($config['position'], 'gt', $position)
->setInc($config['position'], 1);
// update left indexes
$ref_lft = false;
if(!$parent['children']) {
$ref_lft = $parent[$config['right']];
}else if(!isset($parent['children'][$position])) {
$ref_lft = $parent[$config['right']];
}else {
$ref_lft = $parent['children'][(int)$position][$config['left']];
}
db($config['table'])->where($config['left'], 'egt', (int)$ref_lft)
->setInc($config['left'], 2);
// update right indexes
$ref_rgt = false;
if(!$parent['children']) {
$ref_rgt = $parent[$config['right']];
}else if(!isset($parent['children'][$position])) {
$ref_rgt = $parent[$config['right']];
}else {
$ref_rgt = $parent['children'][(int)$position][$config['left']] + 1;
}
db($config['table'])->where($config['right'], 'egt', (int)$ref_rgt)->setInc($config['right'], 2);
$tmp = [
$config['left'] => (int)$ref_lft,
$config['right'] => (int)$ref_lft+1,
$config['level'] => (int)$parent[$config['level']]+1,
$config['pid'] => $parent[$config['id']],
$config['position'] => $position,
];
$id = db($config['table'])->insertGetId($tmp);
if($data && count($data)) {
$node = $id;
if(!$this->rn($node, $data)) {
$this->rm($node);
throw new \Exception('Could not rename after create');
}
}
return $node;
}
public function mv($id, $parent, $position = 0) {
$id = (int)$id;
$parent = (int)$parent;
if($parent == 0 || $id == 0 || $id == 1) {
throw new \Exception('Cannot move inside 0, or move root node');
}
$config = self::$config;
$parent = $this->get_node($parent, ['with_children'=> true, 'with_path' => true]);
$id = $this->get_node($id, ['with_children'=> true, 'deep_children' => true, 'with_path' => true]);
if(!$parent['children']) {
$position = 0;
}
if($id[$config['pid']] == $parent[$config['id']] && $position > $id[$config['position']]) {
$position ++;
}
if($parent['children'] && $position >= count($parent['children'])) {
$position = count($parent['children']);
}
if($id[$config['left']] < $parent[$config['left']] && $id[$config['right']] > $parent[$config['right']]) {
throw new \Exception('Could not move parent inside child');
}
$tmp = [];
$tmp[] = (int)$id[$config['id']];
if($id['children'] && is_array($id['children'])) {
foreach($id['children'] as $c) {
$tmp[] = (int)$c[$config['id']];
}
}
$width = (int)$id[$config['right']] - (int)$id[$config['left']] + 1;
// PREPARE NEW PARENT
// update positions of all next elements
db($config['table'])
->where($config['id'], 'neq', (int)$id[$config['id']])
->where($config['pid'], 'eq', (int)$parent[$config['id']])
->where($config['position'], 'egt', $position)
->setInc($config['position'], 1);
// update left indexes
$ref_lft = false;
if(!$parent['children']) {
$ref_lft = $parent[$config['right']];
}else if(!isset($parent['children'][$position])) {
$ref_lft = $parent[$config['right']];
}else {
$ref_lft = $parent['children'][(int)$position][$config['left']];
}
db($config['table'])
->where($config['left'], 'egt', (int)$ref_lft)
->where($config['id'], 'not in', $tmp)
->setInc($config['left'], $width);
// update right indexes
$ref_rgt = false;
if(!$parent['children']) {
$ref_rgt = $parent[$cofnig['right']];
}else if(!isset($parent['children'][$position])) {
$ref_rgt = $parent[$cofnig['right']];
}else {
$ref_rgt = $parent['children'][(int)$position][$config['left']] + 1;
}
db($config['table'])
->where($config['right'], 'egt', (int)$ref_rgt)
->where($config['id'], 'not in', $tmp)
->setInc($config['right'], $width);
// MOVE THE ELEMENT AND CHILDREN
// left, right and level
$diff = $ref_lft - (int)$id[$config['left']];
if($diff > 0) { $diff = $diff - $width; }
$ldiff = ((int)$parent[$config['level']] + 1) - (int)$id[$config['level']];
db($config['table'])
->where($config['id'], 'in'. $tmp)
->update([
$config['right'] => Db::raw("{$config['right']}+{$diff}"),
$config['left'] => Db::raw("{$config['left']}+{$diff}"),
$config['level'] => Db::raw("{$config['level']}+{$ldiff}")
]);
// position and parent_id
db($config['table'])
->where($config['id'], (int)$id[$config['id']])
->update([
$config['position'] => $position,
$config['pid'] => (int)$parent[$config['id']]
]);
// CLEAN OLD PARENT
// position of all next elements
db($config['table'])
->where($config['pid'], (int)$id[$config['pid']])
->where($config['position'], (int)$config['position'])
->setDec($config['position'], 1);
// left indexes
db($config['table'])
->where($config['left'], 'gt', (int)$id[$config['right']])
->where($config['id'], 'not in', $tmp)
->setDec($config['left'], $width);
// right indexes
db($config['table'])
->where($config['right'], 'gt', (int)$id[$config['right']])
->where($config['id'], 'not in', $tmp)
->setDec($config['right'], $width);
return true;
}
public function rm($id) {
$id = (int)$id;
if(!$id || $id === 1) { throw new \Exception('Could not create inside roots'); }
$config = self::$config;
$data = $this->get_node($id, ['with_children' => true, 'deep_children' => true]);
$lft = (int)$data[$config['left']];
$rgt = (int)$data[$config['right']];
$pid = (int)$data[$config['pid']];
$pos = (int)$data[$config['position']];
$dif = $rgt - $lft + 1;
// deleting node and its children from structure
db($config['table'])
->where($config['left'], 'egt', (int)$lft)
->where($config['right'], '<=', (int)$rgt)
->delete();
// shift left indexes of nodes right of the node
db($config['table'])
->where($config['left'], 'gt', (int)$rgt)
->setDec($config['left'], (int)$dif);
// shift right indexes of nodes right of the node and the node's parents
db($config['table'])
->where($config['right'], 'gt', (int)$lft)
->setDec($config['right'], (int)$dif);
// Update position of siblings below the deleted node
db($config['table'])
->where($config['pid'], $pid)
->where($config['position'], 'gt', (int)$pos)
->setDec($config['position'], 1);
return true;
}
public function rn($id, $data) {
$config = self::$config;
if(!$exist = db($config['table'])->find($id)) {
throw new \Exception('Could not rename non-existing node');
}
$data = array_merge($exist, $data);
$ret = db($config['table'])->insertAll([$data], true);
if(false === $ret){
throw new \Exception('Could not rename');
}
return true;
}
}
~~~
然后一個控制的方法實現operation
~~~
public function operation(){
$fs = new JsTree(['table' => 'document_category', 'title'=>'text']);
if(isset($_GET['operation'])) {
try {
$rslt = null;
switch($_GET['operation']) {
case 'get_node':
$node = isset($_GET['id']) && $_GET['id'] !== '#' ? (int)$_GET['id'] : 0;
if($node == 0){
$list = db('document_category')->field('*,title text')->where('prop', $_GET['prop'])->select();
if($list){
foreach ($list as &$li) {
$li['state']['opened'] = true;
$cid = input('cid', 0);
if($cid && $cid == $li['id']){
$li['state']['selected'] = true;
}
}
}
$rslt = JsTree::config(['child'=>'children', 'title'=>'text'])->toLayer($list);
}else{
$temp = $fs->get_children($node);
$rslt = [];
foreach($temp as $v) {
$rslt[] = [
'id' => $v['id'],
'text' => $v['text'],
'children' => ($v['right'] - $v['left'] > 1)
];
}
}
break;
case 'create_node':
$node = isset($_GET['id']) && $_GET['id'] !== '#' ? (int)$_GET['id'] : 0;
$temp = $fs->mk($node, isset($_GET['position']) ? (int)$_GET['position'] : 0, ['nm' => isset($_GET['text']) ? $_GET['text'] : 'New node']);
$rslt = ['id' => $temp];
break;
case 'rename_node':
$node = isset($_GET['id']) && $_GET['id'] !== '#' ? (int)$_GET['id'] : 0;
$rslt = $fs->rn($node, ['title' => isset($_GET['text']) ? $_GET['text'] : 'Renamed node']);
break;
case 'delete_node':
$node = isset($_GET['id']) && $_GET['id'] !== '#' ? (int)$_GET['id'] : 0;
$rslt = $fs->rm($node);
break;
default:
throw new \Exception('Unsupported operation: ' . $_GET['operation']);
break;
}
header('Content-Type: application/json; charset=utf-8');
return json($rslt);
}catch (\Exception $e) {
header($_SERVER["SERVER_PROTOCOL"] . ' 500 Server Error');
header('Status: 500 Server Error');
return $e->getMessage().PHP_EOL.$e->getTraceAsString();
}
}
}
~~~
對應表結構
```[sql]
CREATE TABLE `document_category` (
`id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`pid` int(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT '上級菜單id',
`title` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '菜單標題',
`prop` int(1) UNSIGNED NULL DEFAULT 1 COMMENT '1-招標 2-投標',
`icon` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '菜單圖標',
`online_hide` tinyint(4) UNSIGNED NOT NULL DEFAULT 0 COMMENT '網站上線后是否隱藏',
`create_time` int(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT '創建時間',
`update_time` int(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT '更新時間',
`status` tinyint(2) NOT NULL DEFAULT 1 COMMENT '狀態',
`left` int(10) UNSIGNED NOT NULL DEFAULT 0,
`right` int(10) UNSIGNED NOT NULL DEFAULT 0,
`level` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '層級',
`pos` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '位置 相當于順序',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = MyISAM AUTO_INCREMENT = 18 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
```
前端部分:on相關事件,直接參照官方示列。一定要實現get_node方法。