在平时项目开发中经常会遇到分类、行业等,父级下有子集孙集并且为保证后续项目稳定运行,这种层级一般我们不能写死,只能采集递归动态的处理这种不确定会有多少级层级的关系;然而在数据库中我们一般是二维数组形式存储的,在取出数据就得面临它得层级关系处理,这个处理要么服务端处理要么前端处理必须有一方要处理
案例测试数据
var testJson = [{"id":1,"pid":0,"name":"管理员登录"},{"id":2,"pid":0,"name":"系统设置"},{"id":3,"pid":2,"name":"菜单管理"},{"id":4,"pid":3,"name":"新增页面"},{"id":5,"pid":3,"name":"保存数据"},{"id":6,"pid":3,"name":"编辑页面"},{"id":7,"pid":3,"name":"更新数据"},{"id":8,"pid":3,"name":"删除数据"},{"id":9,"pid":2,"name":"网站信息"},{"id":10,"pid":2,"name":"邮件设置"},{"id":11,"pid":2,"name":"短信设置"},{"id":12,"pid":0,"name":"权限管理"},{"id":15,"pid":10,"name":"邮件模板-列表"},{"id":16,"pid":11,"name":"短信模板-列表"}]
前端处理代码
class SlTree{
constructor(_option){
if(typeof _option !== 'object'){
_option = {};
}
let that = this;
let defaultOption = {
id: 'id'
,pid: 'pid'
,path: 'path'
,son: 'son'
,child: 'childs'
,level: 'level'
,ps: ','
,order: 'orders'
,levelStart: 0
}
for(let item of Object.entries(defaultOption)){
if( _option.hasOwnProperty(item[0]) ){
that[item[0]] = _option[item[0]];
}else{
that[item[0]] = item[1];
}
}
this.narr = {};
}
// 获取树json
getTree(_data){
if( (_data instanceof Array) && _data.length === 0){
return [];
}
_data = JSON.parse(JSON.stringify(_data));// js对象都是指针所有需要重构对象,防止直接改变原始传参
let that = this;
let _tree = []; //格式化的树
let _tmpMap = {}; //临时扁平数据
// 判断是否有排序字段
if(_data[0].hasOwnProperty(that.order)){
_data.sort(that._sortby.bind(that));
}
// 将数据处理进临时数据
for(let item of _data){
_tmpMap[item[that.id] ] = item;
}
// 处理层级关系
for(let item of _data){
if(_tmpMap.hasOwnProperty(item[that.pid] )){
if( !_tmpMap[item[that.pid] ].hasOwnProperty(that.son) ){
_tmpMap[item[that.pid] ][that.son] = [];
}
_tmpMap[item[that.pid] ][that.son].push(_tmpMap[item[that.id] ]);
}else{
_tree.push(_tmpMap[item[that.id] ]);
}
}
return that._pathchild(_tree);
}
getList(_data){
if( !(typeof this.levelStart === 'number' && !isNaN(this.levelStart) )){
this.levelStart = 0;// 如果level不是数字类型则默认处理从0级开始
}
return this._clevel(this.getTree(_data,this.levelStart));
}
// 递归处理路径问题
_pathchild( _arr, _path = ''){
let that = this;
let currentArr = [];// 当前函数里的数组容器
for(let item of _arr){
item[that.path] = _path?_path + that.ps + item[that.pid]:item[that.pid];
item[that.child] = '';
if(item.hasOwnProperty(that.son) && item[that.son].length > 0){
item[that.son] = that._pathchild(item[that.son],item[that.path]);
for(let val of item[that.son]){
item[that.child] += val[that.id];
item[that.child] += that.ps + val[that.child];
}
}
currentArr.push(item);
}
return currentArr;
}
// 转多层数组为二维数组,并加上层数组别
_clevel(_arr, _num = 0){
let that = this;
for(let item of _arr){
if(item.hasOwnProperty(that.son)){
item[that.level] = _num;
let subcat = item[that.son];
delete(item[that.son]);
that.narr[ item[that.id] ] = item;
that._clevel(subcat, _num+1);// 调用处理子函数,因为采用类的全局变量所以无需返回结果
}else{
item[that.level] = _num;
that.narr[item[that.id]] = item;
}
}
return that.narr;
}
// 排序
_sortby(_a,_b){
let that = this;
let order = that.order;
if(_a[order] == _b[order]){
return 0;
}else if(_a[order] > _b[order]){
return 1;
}else{
return -1;
}
}
}
后端(PHP)处理代码:
<?php
namespace Slong;
Class SlTree{
private $id = 'id';
private $pid = 'pid';
private $path = 'path';
private $child = 'childs';
private $son = 'subcat';
private $level = 'level';
private $order = '';
private $sort = true;// 默认正序
private $ps = ',';
private $levelStart = 0;
private static $narr;
public function __construct($option = null){
if(!empty($option) && is_array($option)){
foreach($option as $key => $item){
if(isset($this->$key) && !in_array($key,['narr'])){
$this->$key = $item;
}
}
}
// 判断正序还是倒叙,true正序,false倒序
if($this->sort){
$this->sort = 1;
}else{
$this->sort = -1;
}
}
// 处理树形祖孙关系
public function getTree($data = null){
if(empty($data) || !is_array($data)){
return [];
}
// 判断一下是否需要排序
if(array_key_exists($this->order, $data[0] )){
usort($data,array(__CLASS__,'sortBy'));
}
$tree = [];// 树形数组容器
$tempArr = [];// 临时扁平数据容器
foreach($data as $key => $item){
$tempArr[$item[$this->id]] = $item;
}
// 处理关系层级数据,采用指针方案
foreach($data as $key => $item){
if(empty($tempArr[ $item[$this->pid] ])){
$tree[] = &$tempArr[ $item[$this->id] ];
}else{
if(!isset($tempArr[ $item[$this->pid] ][$this->son])){
$tempArr[ $item[$this->pid] ][$this->son] = [];
}
$tempArr[ $item[$this->pid] ][$this->son][] = &$tempArr[$item[$this->id]];
}
}
return $this->pathchild($tree);
}
// 处理path和child关系
private function pathchild($arr,$path = ''){
$xarr = [];
foreach($arr as $key => $item){
$item[$this->path] = $path === ''?$item[$this->pid] : $path.$this->ps.$item[$this->pid];
$item[$this->child] = '';
if(!empty($item[$this->son])){
$item[$this->son] = $this->pathchild($item[$this->son],$item[$this->path]);
foreach($item[$this->son] as $idx => $val){
$item[$this->child] .= $val[$this->id].$this->ps.$val[$this->child];
}
}
$xarr[] = $item;
}
return $xarr;
}
// 处理排序问题
private function sortBy($a,$b){
$order = $this->order;
if($a[$order] == $b[$order]){
return 0;
}else{
if($a[$order] > $b[$order]){
return $this->sort;
}else{
return -$this->sort;
}
}
}
// 获取带有层级关系的扁平二维数组
public function getList($data){
self::$narr = [];// 每次获取扁平数据,都重置一下静态寄存变量narr防止旧数据bug
return $this->clevel($this->getTree($data,$this->levelStart));
}
/**
* 处理层级level关系
* 转多层数组为二维数组, 并加上层数组别
*/
private function clevel($arr,$num = 0){
foreach($arr as $key => $item){
$item[$this->path] = trim($item[$this->path],$this->ps);// 移除两边分隔符
$item[$this->child] = trim($item[$this->child],$this->ps);
$item[$this->level] = $num;
self::$narr[$item[$this->id]] = $item;
// 判断是否存在子集
if(!empty($item[$this->son])){
unset(self::$narr[$item[$this->id]][$this->son]);
$this->clevel($item[$this->son],$num+1);
}
}
return self::$narr;
}
}