前后端(PHP+JS)处理无限分类、无限行业、无限层级、树形、列表案例

在平时项目开发中经常会遇到分类、行业等,父级下有子集孙集并且为保证后续项目稳定运行,这种层级一般我们不能写死,只能采集递归动态的处理这种不确定会有多少级层级的关系;然而在数据库中我们一般是二维数组形式存储的,在取出数据就得面临它得层级关系处理,这个处理要么服务端处理要么前端处理必须有一方要处理

案例测试数据

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;
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值