├── README.md ├── autoload.php ├── composer.json ├── composer.lock ├── config └── dendrogram.php ├── database └── migrations │ ├── 2020_02_01_190141_create_dendrogram_adjacency_table.php │ ├── 2020_02_01_190142_create_dendrogram_nested_table.php │ └── china.sql ├── image ├── adjacency.png ├── data.png ├── nested.png ├── select.png └── view.png ├── src ├── Autoloader.php ├── Controller │ ├── AdjacencyList.php │ ├── DenDroGram.php │ ├── NestedSet.php │ └── Structure.php ├── DendrogramServiceProvider.php ├── Helpers │ └── Func.php ├── Model │ ├── AdjacencyListModel.php │ ├── Model.php │ └── NestedSetModel.php ├── Static │ ├── dendrogram.css │ ├── dendrogram.js │ ├── dendrogramUnlimitedSelect.css │ └── dendrogramUnlimitedSelect.js └── ViewModel │ ├── AdjacencyListHorizontalViewModel.php │ ├── AdjacencyListVerticalViewModel.php │ ├── NestedSetHorizontalViewModel.php │ ├── NestedSetVerticalViewModel.php │ └── ViewModel.php └── test └── example.php /README.md: -------------------------------------------------------------------------------- 1 |

PHP无限系统树图

2 | 3 |

4 | v2.0 5 | laravel 5.* 6 | PHP>=5.6 7 |

8 | 9 | PHP系统树图可快速的处理无限极分类的业务需求 提供两种不同的数据结构和三种视图类型 10 | 11 | 2.1: 12 | 1.版本修视图图标bug 13 | 2.视图 方法名称修改 buildCatalog buildRhizome ==》 buildHorizontal buildVertical 14 | 3.buildHorizontal buildVertical buildSelect getTreeData 方法增加数据缓存 默认:-1不缓存 0永久缓存 0>缓存n秒 15 | 4.视图路由参数router变更为非必要 传递router路由参数视图会自动绑定点击节点修改,增加按钮时的弹窗表单 16 | 5.可绑定的按钮事件 节点标签按钮dendrogram.bindClassEnvent('dendrogram-tab',事件,回调方法) 节点新增按钮dendrogram.bindClassEnvent('dendrogram-grow',事件,回调方法) 17 | 6.弹窗增加可自定义配置项dendrogram.form.settings 18 | 7.无刷新的视图增删改节点操作 修复多种视图bug 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 28 | 29 | 30 | 31 |
数据结构adjacency listnested sets
视图类型 27 | 横向视图 Horizontal竖向视图 Vertical级联下拉列表 select
32 | 33 | ![example](https://github.com/ydtg1993/dendrogram/blob/master/image/view.png) 34 | ![example](https://github.com/ydtg1993/dendrogram/blob/master/image/select.png) 35 | 36 | ### 1.安装 37 | `composer require dendrogram/dendrogram:v2.1` 38 | 39 | ### 2.配置 40 | 首先往Laravel应用中注册ServiceProvider,打开文件config/app.php,在providers中添加一项: 41 | 42 | 'providers' => [ 43 | DenDroGram\DendrogramServiceProvider::class 44 | ] 45 | 46 | ### 3.发布 47 | 然后发布拓展包的配置文件,使用如下命令: 48 | 49 | `php artisan vendor:publish` 50 | 51 | 会在config目录下会生成dendrogram.php的配置文件 52 | 53 | ### 4.数据导入 54 | `php artisan migrate` 55 | 56 | 两表四个自定义函数 表名可先行在配置文件中修改.以保持与自定义函数内的表名一致 57 | 58 | migrations下增加中国城市sql文件 59 | 由于查询节点过多需要配置mysql 60 | SET GLOBAL group_concat_max_len = 20460; 61 | 62 | 63 | ### 数据结构概述 64 | 65 | ##### adjacency结构 以父节点为基准的链式查询 增删容易 查询不便 66 | 67 | ![config](https://github.com/ydtg1993/dendrogram/blob/master/image/adjacency.png) 68 | 69 | ##### nested结构 以左右值包容形式 增删不便 查询容易 70 | 71 | ![config](https://github.com/ydtg1993/dendrogram/blob/master/image/nested.png) 72 | 73 | ### code说明 74 | 75 | ##### 1.生成对象 76 | /*adjacency list数据结构*/ 77 | new DenDroGram(AdjacencyList::class) 78 | 79 | /*nested set数据结构*/ 80 | new DenDroGram(NestedSet::class) 81 | 82 | 两种不同数据结构分别对应两张表,请根据实际业务场景选择 83 | ##### 2.调用方法 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 141 | 142 | 143 |
调用方法方法说明方法参数返回内容
buildHorizontal生成横向视图id:根节点id
column: 节点展示记录字段名
cache: 缓存时间
文件缓存需要开启目录权限chmod -R 0777 vendor/dendrogram/
router: 修改节点数据路由[POST方式]
html文本
如果没有传递router路由参数不会自动绑定点击节点修改,增加按钮时的弹窗表单 99 |
自定义js代码块
100 | 1.绑定事件到节点的标签tab,添加按钮grow:dendrogram.bindClassEnvent('dendrogram-tab',事件,回调方法)
dendrogram.bindClassEnvent('dendrogram-grow',事件,回调方法)


101 | 2.可以对表单内容自定义的设置:dendrogram.form.settings = [{配置1},{配置2}...]
102 |
* settings结构为数组对象: [setting,setting,...] 103 |
* 输入框setting 普通对象: 104 |
{ 105 |
column:记录列明 必填, 106 |
label:输入框标签 选填, 107 |
type:输入框类型 选填, 108 |
attribute:输入框属性参数 选填, 109 |
options:当类setting的类型type为radio或者checkbox时的选项参数 选填 110 |
} 111 |
112 |
* setting中 type类型:text textarea hidden disable radio checkbox 默认text 113 |
* options结构为数组对象 [] option为普通对象 {label:选项标签 必填,value:选项值 必填} 114 |
buildVertical生成竖向视图id: 根节点id
column: 节点展示记录字段名
cache: 缓存时间
router: 修改节点数据路由[POST方式]
同上 [参考示例]
buildSelect生成级联下拉列表id: 根节点id 根节点id
label: 列表选项显示值(记录字段名)
value: 列表选项值(记录字段名)
default : 列表选项默认值(级联数组对应值)
cache: 缓存时间 -1不缓存 0永久缓存 0>缓存n秒
html文本
获取选项结果事件的值:js中调用dendrogramUS.storage()获取选择结果值的数组 127 |
点击选项事件回调方法:js中调用dendrogramUS.callback = function(){}
operateNode节点操作action: 增删改标识 [添加记录:add 修改: update 删除: delete]
data: 修改节点记录的传参[post方式]
新增返回id 修改删除返回bool 返回的result用json输出到客户端
getTreeData获取结构型数据id: 根节点id
cache: 缓存时间 [-1不缓存 0永久缓存 0>缓存n秒]
array
140 | 结构参见下图
144 | 145 | ##### 获取数据示例 146 | ![example](https://github.com/ydtg1993/dendrogram/blob/master/image/data.png) 147 | 148 | 149 | [更多参考测试样例](https://github.com/ydtg1993/dendrogram/blob/master/test/example.php) 150 | -------------------------------------------------------------------------------- /autoload.php: -------------------------------------------------------------------------------- 1 | 7 | * monitor data stream of code segment 8 | */ 9 | 10 | require __DIR__.'/src/Autoloader.php'; 11 | DenDroGram\Autoloader::register(); 12 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dendrogram/dendrogram", 3 | "description": "php对关系型数据转换成树状图", 4 | "authors": [ 5 | { 6 | "name": "Hikki", 7 | "email": "946818508@qq.com" 8 | } 9 | ], 10 | "license": "MIT", 11 | "require": { 12 | 13 | }, 14 | "autoload": { 15 | "psr-4": { 16 | "DenDroGram\\": "src/" 17 | } 18 | }, 19 | "autoload-dev": { 20 | "psr-4": { 21 | 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "6ce13d5aa5b1c9b8674e5674b8b37064", 8 | "packages": [], 9 | "packages-dev": [], 10 | "aliases": [], 11 | "minimum-stability": "stable", 12 | "stability-flags": [], 13 | "prefer-stable": false, 14 | "prefer-lowest": false, 15 | "platform": { 16 | "php": ">=7.0" 17 | }, 18 | "platform-dev": [] 19 | } 20 | -------------------------------------------------------------------------------- /config/dendrogram.php: -------------------------------------------------------------------------------- 1 | true, 13 | 14 | /*adjacency结构默认表名*/ 15 | 'adjacency_table'=>'dendrogram_adjacency', 16 | 17 | /*nested结构默认表名*/ 18 | 'nested_table'=>'dendrogram_nested' 19 | ]; 20 | -------------------------------------------------------------------------------- /database/migrations/2020_02_01_190141_create_dendrogram_adjacency_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 19 | $table->integer('p_id')->default(0)->comment('父节点id'); 20 | $table->string('name')->default('')->comment('节点名称'); 21 | $table->integer('layer')->default(0)->comment('层级'); 22 | $table->integer('sort')->default(0)->comment('排序 同级有效'); 23 | }); 24 | 25 | \Illuminate\Support\Facades\DB::table($table)->insert([ 26 | ["id"=>1,"p_id"=>0,"layer"=>0,"name"=>"中国"], 27 | ["id"=>2,"p_id"=>1,"layer"=>1,"name"=>"四川"], 28 | ["id"=>3,"p_id"=>1,"layer"=>1,"name"=>"北京"], 29 | ["id"=>4,"p_id"=>2,"layer"=>2,"name"=>"成都"], 30 | ["id"=>5,"p_id"=>2,"layer"=>2,"name"=>"绵阳"] 31 | ]); 32 | 33 | $sql = <<0; 47 | END WHILE; 48 | RETURN sTemp; 49 | END 50 | EOF; 51 | \Illuminate\Support\Facades\DB::unprepared($sql); 52 | } 53 | 54 | /** 55 | * Reverse the migrations. 56 | * 57 | * @return void 58 | */ 59 | public function down() 60 | { 61 | $table = config('dendrogram.adjacency_table','dendrogram_adjacency'); 62 | Schema::dropIfExists($table); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /database/migrations/2020_02_01_190142_create_dendrogram_nested_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 19 | $table->string('name')->default('')->comment('节点名称'); 20 | $table->integer('left')->default(0)->comment('左值'); 21 | $table->integer('right')->default(0)->comment('右值'); 22 | $table->integer('layer')->default(0)->comment('层级'); 23 | $table->integer('sort')->default(0)->comment('排序 同级有效'); 24 | }); 25 | 26 | \Illuminate\Support\Facades\DB::table($table)->insert([ 27 | ["id"=>1,"left"=>1,"right"=>22,"layer"=>0,"name"=>"衣服"], 28 | ["id"=>2,"left"=>2,"right"=>9,"layer"=>1,"name"=>"男衣"], 29 | ["id"=>3,"left"=>10,"right"=>21,"layer"=>1,"name"=>"女衣"], 30 | ["id"=>4,"left"=>3,"right"=>8,"layer"=>2,"name"=>"正装"], 31 | ["id"=>5,"left"=>4,"right"=>5,"layer"=>3,"name"=>"衬衫"], 32 | ["id"=>6,"left"=>6,"right"=>7,"layer"=>3,"name"=>"夹克"], 33 | ["id"=>7,"left"=>11,"right"=>16,"layer"=>2,"name"=>"裙子"], 34 | ["id"=>8,"left"=>17,"right"=>18,"layer"=>2,"name"=>"短裙"], 35 | ["id"=>9,"left"=>19,"right"=>20,"layer"=>2,"name"=>"开衫"], 36 | ]); 37 | 38 | $sql = << rgt; 48 | UPDATE $table SET `right`=`right`+2 WHERE `right`>= rgt AND `left` <= lft; 49 | RETURN rgt; 50 | END 51 | EOF; 52 | \Illuminate\Support\Facades\DB::unprepared($sql); 53 | 54 | $sql = << rgt AND `left` < lft; 60 | UPDATE $table SET `left`=`left` - distance,`right`=`right` - distance WHERE `left` > rgt; 61 | RETURN rgt; 62 | END 63 | EOF; 64 | \Illuminate\Support\Facades\DB::unprepared($sql); 65 | 66 | $sql = <<= rgt; 75 | return result;END; 76 | END IF; 77 | RETURN 0; 78 | END 79 | EOF; 80 | \Illuminate\Support\Facades\DB::unprepared($sql); 81 | } 82 | 83 | /** 84 | * Reverse the migrations. 85 | * 86 | * @return void 87 | */ 88 | public function down() 89 | { 90 | $table = config('dendrogram.nested_table','dendrogram_nested'); 91 | Schema::dropIfExists($table); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /image/adjacency.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ydtg1993/dendrogram/98e04b208aa248fcf5b2d41e0fc9f80b6e161b35/image/adjacency.png -------------------------------------------------------------------------------- /image/data.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ydtg1993/dendrogram/98e04b208aa248fcf5b2d41e0fc9f80b6e161b35/image/data.png -------------------------------------------------------------------------------- /image/nested.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ydtg1993/dendrogram/98e04b208aa248fcf5b2d41e0fc9f80b6e161b35/image/nested.png -------------------------------------------------------------------------------- /image/select.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ydtg1993/dendrogram/98e04b208aa248fcf5b2d41e0fc9f80b6e161b35/image/select.png -------------------------------------------------------------------------------- /image/view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ydtg1993/dendrogram/98e04b208aa248fcf5b2d41e0fc9f80b6e161b35/image/view.png -------------------------------------------------------------------------------- /src/Autoloader.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | */ 9 | 10 | namespace DenDroGram; 11 | 12 | /** 13 | * Implements a lightweight PSR-0 compliant autoloader for Predis. 14 | */ 15 | class Autoloader 16 | { 17 | private $directory; 18 | private $prefix; 19 | private $prefixLength; 20 | 21 | /** 22 | * @param string $baseDirectory Base directory where the source files are located. 23 | */ 24 | public function __construct($baseDirectory = __DIR__) 25 | { 26 | $this->directory = $baseDirectory; 27 | $this->prefix = __NAMESPACE__.'\\'; 28 | $this->prefixLength = strlen($this->prefix); 29 | } 30 | 31 | /** 32 | * Registers the autoloader class with the PHP SPL autoloader. 33 | * 34 | * @param bool $prepend Prepend the autoloader on the stack instead of appending it. 35 | */ 36 | public static function register($prepend = false) 37 | { 38 | spl_autoload_register(array(new self(), 'autoload'), true, $prepend); 39 | } 40 | 41 | /** 42 | * Loads a class from a file using its fully qualified name. 43 | * 44 | * @param string $className Fully qualified name of a class. 45 | */ 46 | public function autoload($className) 47 | { 48 | if (0 === strpos($className, $this->prefix)) { 49 | $parts = explode('\\', substr($className, $this->prefixLength)); 50 | $filepath = $this->directory.DIRECTORY_SEPARATOR.implode(DIRECTORY_SEPARATOR, $parts).'.php'; 51 | 52 | if (is_file($filepath)) { 53 | require_once $filepath; 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Controller/AdjacencyList.php: -------------------------------------------------------------------------------- 1 | 缓存n秒 24 | * @param string $router 操作节点路由 25 | * @return mixed 26 | * @throws \Exception 27 | */ 28 | public function buildHorizontal($id, array $column = ['name'], $cache = -1, $router = '') 29 | { 30 | $css = file_get_contents(__DIR__ . '/../Static/dendrogram.css'); 31 | $js = file_get_contents(__DIR__ . '/../Static/dendrogram.js'); 32 | $js = sprintf($js, $router); 33 | 34 | $data = Func::getCache("AdjacencyList-Horizontal-{$id}", $cache, function () use ($id) { 35 | return AdjacencyListModel::getChildren($id); 36 | }); 37 | $html = (new AdjacencyListHorizontalViewModel($column))->index($data); 38 | $view = <<%s 40 | 41 | %s 42 |
43 | 44 | EOF; 45 | return sprintf($view, $css, $js, $html); 46 | } 47 | 48 | /** 49 | * 生成竖向视图 50 | * 51 | * @param int $id 根节点ID 52 | * @param array $column 显示的字段 53 | * @param int $cache 缓存时间 默认:-1不缓存 0永久缓存 0>缓存n秒 54 | * @param string $router 操作节点路由 55 | * @return mixed 56 | * @throws \Exception 57 | */ 58 | public function buildVertical($id, array $column = ['name'], $cache = -1, $router = '') 59 | { 60 | $css = file_get_contents(__DIR__ . '/../Static/dendrogram.css'); 61 | $js = file_get_contents(__DIR__ . '/../Static/dendrogram.js'); 62 | $js = sprintf($js, $router); 63 | 64 | $data = Func::getCache("AdjacencyList-Vertical-{$id}", $cache, function () use ($id) { 65 | return AdjacencyListModel::getChildren($id); 66 | }); 67 | $html = (new AdjacencyListVerticalViewModel($column))->index($data); 68 | $view = <<%s 70 | 71 |
72 | %s 73 |
74 |
75 | 76 | EOF; 77 | return sprintf($view, $css, $js, $html); 78 | } 79 | 80 | /** 81 | * 生成级联下拉列表 82 | * 83 | * @param int $id 根节点ID 84 | * @param string $label 列表选项显示字段 [对应记录字段] 85 | * @param string $value 列表选项值 [对应记录字段] 86 | * @param array $default 显示的字段默认值 [根据数据维度填入相应元素个数] 87 | * @param int $cache 缓存时间 默认:-1不缓存 0永久缓存 0>缓存n秒 88 | * @return mixed 89 | * @throws \Exception 90 | */ 91 | public function buildSelect($id, $label, $value, array $default = [], $cache = -1) 92 | { 93 | $css = file_get_contents(__DIR__ . '/../Static/dendrogramUnlimitedSelect.css'); 94 | $js = file_get_contents(__DIR__ . '/../Static/dendrogramUnlimitedSelect.js'); 95 | foreach ($default as $k=>$v){ 96 | $default[$k] = (int)$v; 97 | } 98 | $js = sprintf($js, $label, $value, json_encode($default)); 99 | 100 | $data = Func::getCache("AdjacencyList-Select-{$id}", $cache, function () use ($id) { 101 | return AdjacencyListModel::getChildren($id, 'DESC'); 102 | }); 103 | $tree = json_encode(self::makeTeeData($data)); 104 | $view = <<%s 106 |
107 | 108 | EOF; 109 | return sprintf($view, $css, $js, $tree); 110 | } 111 | 112 | /** 113 | * 获取数据结构 114 | * 115 | * @param int $id 根节点ID 116 | * @param int $cache 缓存时间 默认:-1不缓存 0永久缓存 0>缓存n秒 117 | * @return mixed 118 | * @throws \Exception 119 | */ 120 | public function getTreeData($id, $cache = -1) 121 | { 122 | $data = Func::getCache("AdjacencyList-TreeData-{$id}", $cache, function () use ($id) { 123 | return AdjacencyListModel::getChildren($id, 'DESC'); 124 | }); 125 | return self::makeTeeData($data); 126 | } 127 | 128 | private static function makeTeeData($data) 129 | { 130 | $tempDeepArray = []; 131 | foreach ($data as $item) { 132 | $item['children'] = []; 133 | $tempDeepArray[$item['layer']][$item['p_id']][] = $item; 134 | } 135 | foreach ($tempDeepArray as $layer => $boundary) { 136 | $nextLayer = $layer - 1; 137 | foreach ($tempDeepArray[$layer] as $p_id => $list) { 138 | if (!isset($tempDeepArray[$nextLayer])) { 139 | break; 140 | } 141 | 142 | foreach ($tempDeepArray[$nextLayer] as $b_k => $nextBoundaryList) { 143 | foreach ($nextBoundaryList as $i_k => $item) { 144 | if (empty($tempDeepArray[$layer])) { 145 | break(2); 146 | } 147 | if ($item['id'] !== $p_id) { 148 | continue; 149 | } 150 | $tempDeepArray[$nextLayer][$b_k][$i_k]['children'] = $list; 151 | unset($tempDeepArray[$layer][$p_id]); 152 | } 153 | } 154 | } 155 | } 156 | return current(current(current(array_filter($tempDeepArray)))); 157 | } 158 | 159 | /** 160 | * 操作节点方法 161 | * 162 | * @param string $action 增删改标识 [添加记录:add 修改: update 删除: delete] 163 | * @param array $data 修改节点记录的传参[post方式] 164 | * @return mixed 165 | * @throws \Exception 166 | */ 167 | public function operateNode($action, $data) 168 | { 169 | $dir = __DIR__ . '/../../cache/'; 170 | foreach (scandir($dir) as $file){ 171 | if(preg_match('/^AdjacencyList-/',$file)){ 172 | @unlink($dir.$file); 173 | } 174 | } 175 | 176 | if ($action == 'add') { 177 | $parent = AdjacencyListModel::where('id', $data['p_id'])->first(); 178 | if (!$parent) { 179 | $data['layer'] = 0; 180 | } else { 181 | $data['layer'] = $parent->layer + 1; 182 | } 183 | return AdjacencyListModel::insertGetId($data); 184 | } elseif ($action == 'update' && isset($data['id'])) { 185 | $id = $data['id']; 186 | unset($data['id']); 187 | return AdjacencyListModel::where('id', $id)->update($data); 188 | } elseif ($action == 'delete' && isset($data['id'])) { 189 | return AdjacencyListModel::deleteAll($data['id']); 190 | } 191 | return false; 192 | } 193 | 194 | } 195 | -------------------------------------------------------------------------------- /src/Controller/DenDroGram.php: -------------------------------------------------------------------------------- 1 | instance = new $structure; 21 | if (!($this->instance instanceof Structure)) { 22 | throw new \Exception('import instance is not instanceof structure'); 23 | } 24 | 25 | } 26 | 27 | /** 28 | * 生成横向视图 29 | * 30 | * @param int $id 根节点ID 31 | * @param array $column 显示的字段 32 | * @param int $cache 缓存时间 默认:-1不缓存 0永久缓存 0>缓存n秒 33 | * @param string $router 操作节点路由 34 | * @return mixed 35 | * @throws \Exception 36 | */ 37 | public function buildHorizontal($id, array $column = ['name'], $cache = -1, $router = '') 38 | { 39 | try { 40 | $result = $this->instance->buildHorizontal($id, $column, $cache, $router); 41 | } catch (\Exception $e) { 42 | throw new \Exception($e->getMessage()); 43 | } 44 | return $result; 45 | } 46 | 47 | /** 48 | * 生成竖向视图 49 | * 50 | * @param int $id 根节点ID 51 | * @param array $column 显示的字段 52 | * @param int $cache 缓存时间 默认:-1不缓存 0永久缓存 0>缓存n秒 53 | * @param string $router 操作节点路由 54 | * @return mixed 55 | * @throws \Exception 56 | */ 57 | public function buildVertical($id, array $column = ['name'], $cache = -1, $router = '') 58 | { 59 | try { 60 | $result = $this->instance->buildVertical($id, $column, $cache, $router); 61 | } catch (\Exception $e) { 62 | throw new \Exception($e->getMessage()); 63 | } 64 | return $result; 65 | } 66 | 67 | /** 68 | * 生成级联下拉列表 69 | * 70 | * @param int $id 根节点ID 71 | * @param string $label 列表选项显示字段 [对应记录字段] 72 | * @param string $value 列表选项值 [对应记录字段] 73 | * @param array $default 显示的字段默认值 [根据数据维度填入相应元素个数] 74 | * @param int $cache 缓存时间 默认:-1不缓存 0永久缓存 0>缓存n秒 75 | * @return mixed 76 | * @throws \Exception 77 | */ 78 | public function buildSelect($id, $label = 'name', $value = 'id', array $default = [], $cache = -1) 79 | { 80 | try { 81 | $result = $this->instance->buildSelect($id, $label, $value, $default, $cache); 82 | } catch (\Exception $e) { 83 | throw new \Exception($e->getMessage()); 84 | } 85 | return $result; 86 | } 87 | 88 | /** 89 | * 获取数据结构 90 | * 91 | * @param int $id 根节点ID 92 | * @param int $cache 缓存时间 默认:-1不缓存 0永久缓存 0>缓存n秒 93 | * @return mixed 94 | * @throws \Exception 95 | */ 96 | public function getTreeData($id, $cache = -1) 97 | { 98 | try { 99 | $result = $this->instance->getTreeData($id, $cache); 100 | } catch (\Exception $e) { 101 | throw new \Exception($e->getMessage()); 102 | } 103 | return $result; 104 | } 105 | 106 | /** 107 | * 操作节点方法 108 | * 109 | * @param string $action 增删改标识 [添加记录:add 修改: update 删除: delete] 110 | * @param array $data 修改节点记录的传参[post方式] 111 | * @return mixed 112 | * @throws \Exception 113 | */ 114 | public function operateNode($action, $data) 115 | { 116 | try { 117 | $result = $this->instance->operateNode($action, $data); 118 | } catch (\Exception $e) { 119 | throw new \Exception($e->getMessage()); 120 | } 121 | return $result; 122 | } 123 | 124 | } 125 | -------------------------------------------------------------------------------- /src/Controller/NestedSet.php: -------------------------------------------------------------------------------- 1 | 缓存n秒 24 | * @param string $router 操作节点路由 25 | * @return mixed 26 | * @throws \Exception 27 | */ 28 | public function buildHorizontal($id, array $column = ['name'], $cache = -1, $router = '') 29 | { 30 | $css = file_get_contents(__DIR__ . '/../Static/dendrogram.css'); 31 | $js = file_get_contents(__DIR__ . '/../Static/dendrogram.js'); 32 | $js = sprintf($js, $router); 33 | 34 | $data = Func::getCache("NestedSet-Horizontal-{$id}", $cache, function () use ($id) { 35 | return NestedSetModel::getChildren($id); 36 | }); 37 | $html = (new NestedSetHorizontalViewModel($column))->index($data); 38 | $view = <<%s 40 | 41 | %s 42 |
43 | 44 | EOF; 45 | return sprintf($view, $css, $js, $html); 46 | } 47 | 48 | /** 49 | * 生成竖向视图 50 | * 51 | * @param int $id 根节点ID 52 | * @param array $column 显示的字段 53 | * @param int $cache 缓存时间 默认:-1不缓存 0永久缓存 0>缓存n秒 54 | * @param string $router 操作节点路由 55 | * @return mixed 56 | * @throws \Exception 57 | */ 58 | public function buildVertical($id, array $column = ['name'], $cache = -1, $router = '') 59 | { 60 | $css = file_get_contents(__DIR__ . '/../Static/dendrogram.css'); 61 | $js = file_get_contents(__DIR__ . '/../Static/dendrogram.js'); 62 | $js = sprintf($js, $router); 63 | 64 | $data = Func::getCache("NestedSet-Vertical-{$id}", $cache, function () use ($id) { 65 | return NestedSetModel::getChildren($id); 66 | }); 67 | $html = (new NestedSetVerticalViewModel($column))->index($data); 68 | $view = <<%s 70 | 71 |
72 | %s 73 |
74 |
75 |
76 | 77 | EOF; 78 | return sprintf($view, $css, $js, $html); 79 | } 80 | 81 | /** 82 | * 生成级联下拉列表 83 | * 84 | * @param int $id 根节点ID 85 | * @param string $label 列表选项显示字段 [对应记录字段] 86 | * @param string $value 列表选项值 [对应记录字段] 87 | * @param array $default 显示的字段默认值 [根据数据维度填入相应元素个数] 88 | * @param int $cache 缓存时间 默认:-1不缓存 0永久缓存 0>缓存n秒 89 | * @return mixed 90 | * @throws \Exception 91 | */ 92 | public function buildSelect($id, $label, $value, array $default = [], $cache = -1) 93 | { 94 | $css = file_get_contents(__DIR__ . '/../Static/dendrogramUnlimitedSelect.css'); 95 | $js = file_get_contents(__DIR__ . '/../Static/dendrogramUnlimitedSelect.js'); 96 | foreach ($default as $k=>$v){ 97 | $default[$k] = (int)$v; 98 | } 99 | $js = sprintf($js, $label, $value, json_encode($default)); 100 | 101 | $data = Func::getCache("NestedSet-Select-{$id}", $cache, function () use ($id) { 102 | return NestedSetModel::getChildren($id); 103 | }); 104 | self::makeTeeData($data, $tree); 105 | $tree = json_encode(current($tree)); 106 | $view = <<%s 108 |
109 | 110 | EOF; 111 | return sprintf($view, $css, $js, $tree); 112 | } 113 | 114 | /** 115 | * 获取数据结构 116 | * 117 | * @param int $id 根节点ID 118 | * @param int $cache 缓存时间 默认:-1不缓存 0永久缓存 0>缓存n秒 119 | * @return mixed 120 | * @throws \Exception 121 | */ 122 | public function getTreeData($id, $cache = -1) 123 | { 124 | $data = Func::getCache("NestedSet-TreeData-{$id}", $cache, function () use ($id) { 125 | return NestedSetModel::getChildren($id); 126 | }); 127 | self::makeTeeData($data, $tree); 128 | return current($tree); 129 | } 130 | 131 | private static function makeTeeData(&$array, &$branch = []) 132 | { 133 | if (empty($array)) { 134 | return; 135 | } 136 | 137 | if (empty($branch)) { 138 | $item = array_shift($array); 139 | $item['children'] = []; 140 | $branch[] = $item; 141 | if (!empty($array)) { 142 | self::makeTeeData($array, $branch); 143 | } 144 | return; 145 | } 146 | 147 | foreach ($branch as $k => &$b) { 148 | $b['children'] = []; 149 | $shoot = []; 150 | foreach ($array as $key => $value) { 151 | if (($b['layer'] + 1) == $value['layer'] && $b['left'] < $value['left'] && $b['right'] > $value['left']) { 152 | $value['children'] = []; 153 | $shoot[] = $value; 154 | unset($array[$key]); 155 | } 156 | } 157 | 158 | if (!empty($array) && !empty($shoot)) { 159 | self::makeTeeData($array, $shoot); 160 | $b['children'] = $shoot; 161 | } elseif (empty($array) && !empty($shoot)) { 162 | self::makeTeeData($array, $shoot); 163 | $b['children'] = $shoot; 164 | } 165 | } 166 | } 167 | 168 | /** 169 | * 操作节点方法 170 | * 171 | * @param string $action 增删改标识 [添加记录:add 修改: update 删除: delete] 172 | * @param array $data 修改节点记录的传参[post方式] 173 | * @return mixed 174 | * @throws \Exception 175 | */ 176 | public function operateNode($action, $data) 177 | { 178 | $dir = __DIR__ . '/../../cache/'; 179 | foreach (scandir($dir) as $file){ 180 | if(preg_match('/^NestedSet-/',$file)){ 181 | @unlink($dir.$file); 182 | } 183 | } 184 | 185 | if ($action == 'add' && isset($data['p_id'])) { 186 | return NestedSetModel::add($data); 187 | } elseif ($action == 'update' && isset($data['id'])) { 188 | $id = $data['id']; 189 | unset($data['id']); 190 | return NestedSetModel::where('id', $id)->update($data); 191 | } elseif ($action == 'delete' && isset($data['id'])) { 192 | return NestedSetModel::deleteAll($data['id']); 193 | } 194 | return false; 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /src/Controller/Structure.php: -------------------------------------------------------------------------------- 1 | 缓存n秒 19 | * @param string $router 操作节点路由 20 | * @return mixed 21 | * @throws \Exception 22 | */ 23 | public function buildHorizontal($id, array $column = ['name'], $cache = -1, $router = ''); 24 | 25 | /** 26 | * 生成竖向视图 27 | * 28 | * @param int $id 根节点ID 29 | * @param array $column 显示的字段 30 | * @param int $cache 缓存时间 默认:-1不缓存 0永久缓存 0>缓存n秒 31 | * @param string $router 操作节点路由 32 | * @return mixed 33 | * @throws \Exception 34 | */ 35 | public function buildVertical($id, array $column = ['name'], $cache = -1, $router = ''); 36 | 37 | /** 38 | * 生成级联下拉列表 39 | * 40 | * @param int $id 根节点ID 41 | * @param string $label 列表选项显示字段 [对应记录字段] 42 | * @param string $value 列表选项值 [对应记录字段] 43 | * @param array $default 显示的字段默认值 [根据数据维度填入相应元素个数] 44 | * @param int $cache 缓存时间 默认:-1不缓存 0永久缓存 0>缓存n秒 45 | * @return mixed 46 | * @throws \Exception 47 | */ 48 | public function buildSelect($id, $label, $value, array $default = [], $cache = -1); 49 | 50 | /** 51 | * 获取数据结构 52 | * 53 | * @param int $id 根节点ID 54 | * @param int $cache 缓存时间 默认:-1不缓存 0永久缓存 0>缓存n秒 55 | * @return mixed 56 | * @throws \Exception 57 | */ 58 | public function getTreeData($id, $cache = -1); 59 | 60 | /** 61 | * 操作节点方法 62 | * 63 | * @param string $action 增删改标识 [添加记录:add 修改: update 删除: delete] 64 | * @param array $data 修改节点记录的传参[post方式] 65 | * @return mixed 66 | * @throws \Exception 67 | */ 68 | public function operateNode($action, $data); 69 | } 70 | -------------------------------------------------------------------------------- /src/DendrogramServiceProvider.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace DenDroGram; 12 | 13 | use Illuminate\Contracts\Auth\Guard; 14 | use Illuminate\Support\Facades\Blade; 15 | use Illuminate\Support\ServiceProvider; 16 | 17 | class DendrogramServiceProvider extends ServiceProvider 18 | { 19 | /** 20 | * Indicates if loading of the provider is deferred. 21 | * 22 | * @var bool 23 | */ 24 | protected $defer = false; 25 | 26 | /** 27 | * Boot the service provider. 28 | * 29 | * @return void 30 | */ 31 | public function boot() 32 | { 33 | $this->publishConfig(); 34 | $this->loadMigrations(); 35 | $this->setupConfig(); 36 | } 37 | 38 | /** 39 | * Register the service provider. 40 | * 41 | * @return void 42 | */ 43 | public function register() 44 | { 45 | return ['dendrogram']; 46 | } 47 | 48 | /** 49 | * Get the services provided by the provider. 50 | * 51 | * @return array 52 | */ 53 | public function provides() 54 | { 55 | 56 | } 57 | 58 | /** 59 | * Publish config. 60 | */ 61 | protected function publishConfig() 62 | { 63 | $path = $this->getConfigPath(); 64 | $this->publishes([$path => config_path('dendrogram.php')], 'config'); 65 | } 66 | 67 | /** 68 | * Load migrations. 69 | */ 70 | protected function loadMigrations() 71 | { 72 | $path = $this->getMigrationsPath(); 73 | $this->loadMigrationsFrom($path); 74 | } 75 | 76 | /** 77 | * Setup config. 78 | */ 79 | protected function setupConfig() 80 | { 81 | $path = $this->getConfigPath(); 82 | $this->mergeConfigFrom($path, 'dendrogram'); 83 | } 84 | 85 | /** 86 | * @return string 87 | */ 88 | protected function getMigrationsPath() 89 | { 90 | return realpath(__DIR__ . '/../database/migrations/'); 91 | } 92 | 93 | /** 94 | * @return string 95 | */ 96 | protected function getConfigPath() 97 | { 98 | return realpath(__DIR__ . '/../config/dendrogram.php'); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/Helpers/Func.php: -------------------------------------------------------------------------------- 1 | $item) { 18 | $add = true; 19 | foreach ($params as $field => $value) { 20 | if ($item[$field] != $value) { 21 | $add = false; 22 | } 23 | } 24 | if ($add) { 25 | $index = $key; 26 | break; 27 | } 28 | } 29 | 30 | return $index; 31 | } 32 | 33 | /** 34 | * 首位sprintf 35 | * @param $string 36 | * @param string $aim 37 | * @param $value 38 | * @return bool|string 39 | */ 40 | public static function firstSprintf($string, $value, $aim = '%s') 41 | { 42 | $position = strpos($string, $aim); 43 | $len = strlen($aim); 44 | $left = substr($string, 0, $position + $len); 45 | $right = substr($string, $position + $len); 46 | return sprintf($left, $value) . $right; 47 | } 48 | 49 | public static function getCache($file, $cache, $func) 50 | { 51 | if($cache < 0){ 52 | return $func(); 53 | } 54 | $dir = __DIR__ . '/../../cache/'; 55 | if(!is_dir($dir)){ 56 | mkdir($dir); 57 | } 58 | $file = $dir . $file; 59 | $now = time(); 60 | if (file_exists($file)) { 61 | $content = (array)json_decode(file_get_contents($file),true); 62 | if ($cache == 0) { 63 | return $content['data']; 64 | } 65 | if($now > $content['expire']){ 66 | $data = $func(); 67 | $content = ['data' => $data, 'expire' => $now + $cache]; 68 | file_put_contents($file,json_encode($content)); 69 | return $data; 70 | } 71 | return $content['data']; 72 | } 73 | $data = $func(); 74 | $content = ['data' => $data, 'expire' => $now + $cache]; 75 | file_put_contents($file,json_encode($content)); 76 | return $data; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/Model/AdjacencyListModel.php: -------------------------------------------------------------------------------- 1 | table = config('dendrogram.adjacency_table','dendrogram_adjacency'); 39 | } 40 | 41 | public static function getChildren($id,$order = 'ASC') 42 | { 43 | $data = self::whereRaw("FIND_IN_SET(id,(select * from (select dendrogramAdjacencyGetChildren($id) as ids) ids))")->orderBy('layer', $order)->orderBy('sort', 'DESC')->orderBy('id', 'ASC')->get(); 44 | if(!$data){ 45 | return []; 46 | } 47 | return $data->toArray(); 48 | } 49 | 50 | public static function deleteAll($id) 51 | { 52 | return self::whereRaw("FIND_IN_SET(id,dendrogramAdjacencyGetChildren($id))")->delete(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Model/Model.php: -------------------------------------------------------------------------------- 1 | connection = config('dendrogram.connection','mysql'); 32 | } 33 | } -------------------------------------------------------------------------------- /src/Model/NestedSetModel.php: -------------------------------------------------------------------------------- 1 | table = config('dendrogram.nested_table','dendrogram_nested'); 41 | } 42 | 43 | public static function add($data) 44 | { 45 | $p_id = $data['p_id']; 46 | unset($data['p_id']); 47 | DB::beginTransaction(); 48 | $result = (array)DB::selectOne( 49 | "SELECT dendrogramNestedIncrement(?) as p_right,dendrogramNestedLayer(?) as layer", 50 | [$p_id,$p_id] 51 | ); 52 | 53 | if(!$result){ 54 | DB::rollBack(); 55 | return false; 56 | } 57 | $right = $result['p_right']; 58 | $layer = $result['layer']; 59 | $data['left'] = $right; 60 | $data['right'] = $right + 1; 61 | $data['layer'] = $layer; 62 | $result = self::insertGetId($data); 63 | if(!$result){ 64 | DB::rollBack(); 65 | return false; 66 | } 67 | DB::commit(); 68 | return $result; 69 | } 70 | 71 | public static function getChildren($id) 72 | { 73 | $mine = self::where('id',$id)->first(); 74 | if(!$mine){ 75 | return []; 76 | } 77 | $left = $mine->left; 78 | $right = $mine->right; 79 | $children = self::whereBetween('left', [$left, $right])->orderBy('layer')->get(); 80 | if(!$children){ 81 | return [$mine->toArray()]; 82 | } 83 | $children = $children->toArray(); 84 | return $children; 85 | } 86 | 87 | public static function deleteAll($id) 88 | { 89 | DB::beginTransaction(); 90 | $mine = self::where('id',$id)->first(); 91 | if(!$mine){ 92 | DB::rollBack(); 93 | return false; 94 | } 95 | $left = $mine->left; 96 | $right = $mine->right; 97 | $distance = $right - $left + 1; 98 | $result = self::whereBetween('left', [$left, $right])->delete(); 99 | if(!$result){ 100 | DB::rollBack(); 101 | return false; 102 | } 103 | $result = (array)DB::selectOne( 104 | "SELECT dendrogramNestedReduction(?,?,?)", 105 | [$distance,$left,$right] 106 | ); 107 | if(!$result){ 108 | DB::rollBack(); 109 | return false; 110 | } 111 | DB::commit(); 112 | return true; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/Static/dendrogram.css: -------------------------------------------------------------------------------- 1 | .dendrogram{width:max-content}.dendrogram *{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;font-size:14px;line-height:.5}.dendrogram a{font-size:14px !important}.dendrogram li{display:list-item !important}.dendrogram input{margin:0!important;}.dendrogram input:disabled,.dendrogram textarea:disabled{background-color:#f8f8f8;color:#999;border-color:#e5e5e5;}.dendrogram label{margin-right:5px;display:inline-block;max-width:100%;margin-bottom:3px;font-weight:700;height:14px;}.dendrogram .text{width:80px;height:40px;line-height:40px;white-space:nowrap;text-overflow:ellipsis;overflow:hidden;display:inline-block}.dendrogram .text:nth-child(2){border-left:1px solid #e5e5e5}.dendrogram-icon{display:inline-block}.dendrogram-tab{line-height:60px !important;cursor:pointer;height:40px;padding:0 10px;background-color:transparent;color:#222;border:1px solid #e5e5e5;margin:0;overflow:visible;font:inherit;display:inline-block;box-sizing:border-box;vertical-align:middle;font-size:14px;text-align:center;text-decoration:none;text-transform:none;transition:.2s ease-in-out;transition-property:color,background-color,border-color}.dendrogram-tab:hover{border:1px solid #bababa}.dendrogram-switch{text-align:center;display:flex;width:20px;height:40px;background-color:#1e87f0;border-radius:10px 0 0 10px;justify-content:center;align-items:center}.dendrogram-ban{text-align:center;display:flex;width:20px;height:40px;background-color:#000;border-radius:10px 0 0 10px;justify-content:center;align-items:center}.dendrogram-grow{text-align:center;display:flex;width:20px;height:40px;background-color:#0e4e8e;border-radius:0 10px 10px 0;justify-content:center;align-items:center}.dendrogram-switch:hover,.dendrogram-switch:focus,.dendrogram-grow:hover,.dendrogram-grow:focus{background-color:#0f7ae5;color:#fff}.dendrogram-switch:active,.dendrogram-grow:active{background-color:#0e6dcd;color:#fff}.dendrogram-horizontal-node{position:relative;z-index:1;display:flex;display:-webkit-flex;flex-direction:row;flex-wrap:nowrap}.dendrogram-horizontal-branch{border-left:1px solid #c6c6c6;margin-left:.8em;position:relative;top:-20px}.dendrogram-horizontal-branch > li{position:relative;top:20px}.dendrogram-horizontal-node:before{display:inline-block;content:"";border-top:1px solid #c6c6c6;float:left;width:30px;margin-left:-30px;margin-top:20px}.dendrogram-horizontal-switch:hover,.dendrogram-horizontal-grow:hover{background-color:#0f7ae5}.dendrogram-horizontal-list{padding:0;list-style:none}.dendrogram-horizontal-list > li > ul{margin-top:10px}.dendrogram-horizontal-list ul{margin-top:10px;padding-left:30px;list-style:none}.dendrogram-horizontal-list li:nth-child(n+2){margin-top:10px}.dendrogram-vertical > ul ul{padding:20px 0 0 0;position:relative}.dendrogram-vertical li{float:left;text-align:center;list-style-type:none;position:relative;padding:20px 5px 0 5px;transition:all 0.5s;-webkit-transition:all 0.5s;-moz-transition:all 0.5s}.dendrogram-vertical li::before,.dendrogram-vertical li::after{content:'';position:absolute;top:0;right:50%;border-top:1px solid #ccc;width:50%;height:20px}.dendrogram-vertical li::after{right:auto;left:50%;border-left:1px solid #ccc}.dendrogram-vertical li:only-child::after,.dendrogram-vertical li:only-child::before{display:none}.dendrogram-vertical li:only-child{padding-top:0}.dendrogram-vertical li:first-child::before,.dendrogram-vertical li:last-child::after{border:0 none}.dendrogram-vertical li:last-child::before{border-right:1px solid #ccc;border-radius:0 5px 0 0;-webkit-border-radius:0 5px 0 0;-moz-border-radius:0 5px 0 0}.dendrogram-vertical li:first-child::after{border-radius:5px 0 0 0;-webkit-border-radius:5px 0 0 0;-moz-border-radius:5px 0 0 0}.dendrogram-vertical ul ul::before{content:'';position:absolute;top:0;left:50%;border-left:1px solid #ccc;width:0;height:20px}.dendrogram-vertical-branch{display:flex;flex-direction:row;flex-wrap:nowrap;justify-content:center}.dendrogram-vertical .dendrogram-icon{left:0}.dendrogram-animation-fade{animation-name:dendrogram-fade;animation-duration:0.8s;animation-timing-function:linear}.dendrogram-animation-reverse{animation-name:dendrogram-fade-top-small;animation-duration:.2s;animation-direction:reverse;animation-timing-function:ease-in}.dendrogram-animation-slide-top-small{animation-name:dendrogram-fade-top-small;animation-duration:.2s;animation-timing-function:ease-out}#mongolia{display:none;position:fixed;top:0;right:0;bottom:0;left:0;z-index:1000;overflow-y:auto;-webkit-overflow-scrolling:touch;padding:15px 15px;background:rgba(0,0,0,.6);opacity:0;transition:opacity .15s linear}#dendrogram-form{z-index:10000;visibility:hidden;position:fixed;top:calc(50% - 100px);left:calc(50% - 300px);box-sizing:border-box;width:420px;max-width:calc(100% - 0.01px) !important;background:#fff;opacity:0;transform:translateY(-100px);transition:.3s linear;transition-property:opacity,transform}#dendrogram-form-close{cursor:pointer !important;position:absolute;z-index:1010;top:10px;right:10px;padding:5px;margin:0;border:none;border-radius:0;overflow:visible;font:inherit;color:inherit;text-transform:none;background-color:transparent;display:inline-block;fill:currentcolor;line-height:0}.dendrogram-form-header{padding:15px 30px;background:#fff;border-bottom:1px solid #e5e5e5}.dendrogram-form-header h2{font-size:20px;line-height:1.3;margin:0;font-family:ProximaNova,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;font-weight:100;color:#222;text-transform:none}.dendrogram-form-body{padding:20px;max-height:300px;overflow-y:auto;}.dendrogram-form-footer{text-align:right !important;padding:15px 30px;background:#fff;border-top:1px solid #e5e5e5}.dendrogram-form-delete{cursor:pointer !important;background-color:#f0506e;color:#fff !important;border:1px solid transparent !important;margin:0;overflow:visible;font:inherit;display:inline-block;box-sizing:border-box;padding:0 30px;vertical-align:middle;font-size:12px;line-height:30px;text-align:center;text-decoration:none;text-transform:uppercase;transition:.1s ease-in-out;transition-property:color,background-color,border-color}.dendrogram-form-delete:hover,.dendrogram-form-delete:focus{background-color:#ee395b;color:#fff}.dendrogram-form-delete:active{background-color:#ec2147;color:#fff}.dendrogram-form-conserve{cursor:pointer !important;background-color:#1e87f0;color:#fff !important;border:1px solid transparent !important;margin:0;overflow:visible;font:inherit;display:inline-block;box-sizing:border-box;padding:0 30px;vertical-align:middle;font-size:12px;line-height:30px;text-align:center;text-decoration:none;text-transform:uppercase;transition:.1s ease-in-out;transition-property:color,background-color,border-color}.dendrogram-form-conserve:hover,.dendrogram-form-conserve:focus{background-color:#0f7ae5;color:#fff}.dendrogram-form-conserve:active{background-color:#0e6dcd;color:#fff}.dendrogram-form-preference{margin-bottom:20px;}.dendrogram-input{height:40px;vertical-align:middle;display:inline-block;max-width:100%;width:100%;border:0 none;padding:0 10px;background:#fff;color:#666;border:1px solid #e5e5e5;transition:.2s ease-in-out;transition-property:color,background-color,border;}.dendrogram-textarea{vertical-align:top;max-width:100%;width:100%;border:0 none;padding:6px 10px 6px 10px;background:#fff;color:#666;border:1px solid #e5e5e5;transition:.2s ease-in-out;transition-property:color,background-color,border;font-size:15px;font-weight:400;line-height:1.5;}.dendrogram-radio{border-radius:50%;}.dendrogram-radio,.dendrogram-checkbox{cursor:pointer;display:inline-block;height:16px;width:16px;overflow:hidden;margin-top:-4px;vertical-align:middle;-webkit-appearance:none;-moz-appearance:none;background-color:transparent;background-repeat:no-repeat;background-position:50% 50%;border:1px solid #ccc;transition:.2s ease-in-out;transition-property:background-color,border;}.option_label{font-weight:300!important;font-size:12px;}.dendrogram-radio:checked,.dendrogram-checkbox:checked{background-color:#1e87f0;border-color:transparent;background-image:url(data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D%2214%22%20height%3D%2211%22%20viewBox%3D%220%200%2014%2011%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%0A%20%20%20%20%3Cpolygon%20fill%3D%22%23fff%22%20points%3D%2212%201%205%207.5%202%205%201%205.5%205%2010%2013%201.5%22%20%2F%3E%0A%3C%2Fsvg%3E%0A);}.dendrogram-input:focus,.dendrogram-textarea:focus{outline:none;background-color:#fff;color:#666;border-color:#1e87f0}@keyframes dendrogram-fade{0%{opacity:0}100%{opacity:1}}@keyframes dendrogram-fade-top-small{0%{opacity:0;transform:translateY(-10px)}100%{opacity:1;transform:translateY(0)}} -------------------------------------------------------------------------------- /src/Static/dendrogram.js: -------------------------------------------------------------------------------- 1 | (function(window){'use strict';var dendrogram={icon_data:{expand:' <\/circle> <\/line> <\/line><\/svg>',shrink:' <\/circle> <\/line><\/svg>',grow:'<\/line><\/line><\/circle><\/circle><\/circle><\/svg>',ban:'<\/circle><\/line><\/svg>'},requestFlag:false,requestEvent:function(url,params){if(!url){return;}if(dendrogram.requestFlag){return;}dendrogram.requestFlag=true;var xhr=null;if(window.ActiveXObject){xhr=new ActiveXObject("Microsoft.XMLHTTP");}else if(window.XMLHttpRequest){xhr=new XMLHttpRequest();}if(xhr==null){return;}xhr.open('POST',url,true);xhr.setRequestHeader("X-CSRF-TOKEN",document.querySelector('meta[name="csrf-token"]').getAttribute('content'));xhr.setRequestHeader('Cache-Control','no-cache');xhr.onreadystatechange=function(){if(xhr.readyState===XMLHttpRequest.DONE&&xhr.status==200){dendrogram.requestFlag=false;var response=xhr.responseText;if(dendrogram.form.conserve_action=='delete'){var li=dendrogram.form.nodeElement.parentElement;if(dendrogram.checkNodeNum('LI',li.parentNode)>1){li.parentNode.removeChild(li);dendrogram.form.mongolia(false);return;}var parentNode=dendrogram.form.nodeElement.parentElement.parentElement.previousElementSibling;parentNode.removeChild(parentNode.firstElementChild);var banNode=document.createElement('a');banNode.className='dendrogram-ban';banNode.setAttribute('href','javascript:void(0);');banNode.innerHTML=dendrogram.icon_data.ban;parentNode.prepend(banNode);li.parentElement.parentElement.removeChild(li.parentElement);dendrogram.form.mongolia(false);return;}if(dendrogram.form.conserve_action=='update'){var dom=dendrogram.form.nodeElement;dom.setAttribute('data-v',JSON.stringify(params));dom.children[1].children[0].innerText=params.name;dendrogram.form.mongolia(false);return;}if(dendrogram.form.conserve_action=='add'){var tabNode=document.createElement('button');tabNode.className='dendrogram-tab';tabNode.setAttribute('href','javascript:void(0);');tabNode.innerHTML='
'+params.name+'<\/div>';tabNode.addEventListener('click',dendrogram.form.upForm);var addNode=document.createElement('a');addNode.className='dendrogram-grow';addNode.setAttribute('href','javascript:void(0);');addNode.innerHTML=dendrogram.icon_data.grow;addNode.addEventListener('click',dendrogram.form.addForm);var li=dendrogram.form.nodeElement.parentElement;var liNode=document.createElement('li');var divNode=document.createElement('div');divNode.innerHTML=''+dendrogram.icon_data.ban+'<\/a>';if(dendrogram.form.nodeElement.className=='dendrogram-horizontal-node'){divNode.className='dendrogram-horizontal-node';}else{divNode.className='dendrogram-vertical-branch';}dendrogram.form.nodeElement.setAttribute('data-sign','1');divNode.setAttribute('data-sign','0');params['id']=parseInt(response);divNode.setAttribute('data-v',JSON.stringify(params));divNode.append(tabNode);divNode.append(addNode);liNode.append(divNode);if(dendrogram.checkNodeNum('UL',dendrogram.form.nodeElement.parentNode)==0){var ulNode=document.createElement('ul');if(dendrogram.form.nodeElement.className=='dendrogram-horizontal-node'){ulNode.className='dendrogram dendrogram-horizontal-branch dendrogram-animation-slide-top-small';}else{ulNode.className='dendrogram-animation-slide-top-small';}ulNode.setAttribute('style','display:block');ulNode.append(liNode);li.append(ulNode);}else{var ulNode=dendrogram.form.nodeElement.nextElementSibling;ulNode.setAttribute('style','display:block');ulNode.append(liNode);}dendrogram.form.nodeElement.removeChild(dendrogram.form.nodeElement.firstElementChild);var switchNode=document.createElement('a');switchNode.className='dendrogram-switch';switchNode.setAttribute('href','javascript:void(0);');switchNode.addEventListener('click',dendrogram.tree.switch);switchNode.innerHTML=dendrogram.icon_data.shrink;dendrogram.form.nodeElement.prepend(switchNode);dendrogram.form.mongolia(false);return;}return;}if(xhr.readyState===XMLHttpRequest.DONE&&xhr.status!=200){dendrogram.requestFlag=false;alert('后台接口出错 错误码: '+xhr.status);}};var formData=new FormData();formData.append('action',dendrogram.form.conserve_action);for(let k in params){formData.append('data['+k+']',params[k]);}xhr.send(formData);},bindClassEvent:function(className,event,func){var objs=document.getElementsByClassName(className);for(var i=0;i3){window.location.reload();}dendrogram.tree.switchAnimeErroNum++;return;}dendrogram.tree.shrinkAnimeFlag=true;if(sign==0){dendrogram.replaceChild(this,dendrogram.icon_data.shrink);node.setAttribute('data-sign',1);children.setAttribute('style','display:block');children.classList.remove('dendrogram-animation-reverse');children.classList.add('dendrogram-animation-slide-top-small');dendrogram.tree.shrinkAnimeFlag=false;}else{dendrogram.replaceChild(this,dendrogram.icon_data.expand);node.setAttribute('data-sign',0);children.classList.remove('dendrogram-animation-slide-top-small');setTimeout(function(){children.classList.add('dendrogram-animation-reverse');setTimeout(function(){children.setAttribute('style','display:none');dendrogram.tree.shrinkAnimeFlag=false;},100);},0);}}},form:{id:0,nodeElement:'',settings:[],conserve_action:'add',form_action:'%s',formContentTemplate:{input:'