树形下拉框,应用场景大多都是后台栏目添加或者是后台栏目展示的时候;
可以一目了然,非常直观的明确得知每一个分类的父级以及子级关系;
从最顶级开始衍生拓展,下面衍生出n个子级。
示例图:
后台示例图:
ok,上述的后台示例图也就是要实现的最终效果图。
测试数据:
const arr = [
['id' => 1, 'pid' => 0, 'city' => '江西'],
['id' => 2, 'pid' => 0, 'city' => '广东'],
['id' => 3, 'pid' => 1, 'city' => '宜春'],
['id' => 4, 'pid' => 3, 'city' => '丰城'],
['id' => 5, 'pid' => 4, 'city' => '尚庄'],
['id' => 6, 'pid' => 2, 'city' => '深圳'],
['id' => 7, 'pid' => 6, 'city' => '翻身'],
#以下是追加的同级元素数据,用来试验论证,也是增强干扰性
['id' => 8, 'pid' => 1, 'city' => '南昌'],
['id' => 9, 'pid' => 8, 'city' => '高新区'],
];
思路分析:
要展示上述树形下拉框,就需要把这堆关系凌乱的数据整合成有关系的数组,并且每一个分类都必须有一个值来表示该分类当前处在的层级关系(当前处在第几级,父级是什么?子级又是什么?这点至关重要,是后续分隔符所需要用到的),最后将处理完毕的数组遍历拼接分隔符渲染到页面上,即可完成效果展示。
传入顶级pid,如你所见,顶级pid是0。找到pid为0的数据是江西和广东。
这时出现了多个子级,循环会按照顺序依次处理每个子级的数据;也就是说会先处理江西的数据,待江西的子级找完且处理完了之后程序才会去找广东的子级并处理数据。
江西的id是1,再把江西的id当成江西子级的pid传入,江西的id是1,那么pid为1的就是宜春和南昌;
这时又出现了多个子级,和上述一样,程序还是会按照顺序依次处理当前层级的每个子级数据;也就是说先处理宜春的子级和数据,再处理南昌的子级和顺序。
宜春就是江西的子级;再找到宜春的id,宜春的id是3,那么把id为3当宜春子级的pid传入,得到丰城,丰城的id是4,再把丰城id为3当成丰城的子级的pid传入,得到尚庄,尚庄的id是5,数组中没有pid为5的数据,跳出此次循环;
来到上一层关系继续处理,上一层的关系是什么呢?是南昌。
为什么呢?还记得刚刚说的吗?程序会依次按照顺序处理子级数据,宜春和南昌为同级数据,宜春的数据处理完了,那么会继续处理与之同级的南昌子级数据。
南昌的id是8,那么把id为8当南昌子级的pid传入,得到高新区;
高新区的id为9,数组中没有pid为9的数据,继续跳出此次循环。
ok,这里就来到了上上层关系了,这里你可能已经晕掉了,建议拿个草稿纸画图记录一下更容易理解。这里的上上层关系就是最开始的同级元素:“江西和广东”。在这里江西的子级数据已经处理完毕,那么需要处理广东的子级数据了。
广东的id为2,把id为2当作广东子级的pid传入,找到子级是深圳;深圳的id为6,那么把id为6当作深圳子级的pid传入,找到子级是翻身;翻身的id为7,接着把id为7当作翻身子级的pid传入,发现翻身没有子级,至此整个循环处理完毕。
整体思路下可以得知:拿到每次循环后找到的当前级别id当成它的子级的pid来传入,以此实现不断循环,也就是递归,不断调用自身,以达到最终效果。
代码实践:
思路是很重要的,但光看思路,不看代码永远也无法理解;下面我将一代一代把最终函数关系写出来,希望能有助于你理解。
版本一:体验递归
这个版本只是实现了上述思路分析(即不断循环找到子级),并没有把每一个层级的父与子关系理清,也没有处理数据存储到新数组中。
/**
* 体验递归
* @param array $data
* @param int $pid
* @return array
*/
public function tree1(array $data = [], int $pid = 0): array
{
//1.
foreach ($data as $key => &$value) {
if ($value['pid'] == $pid) {
$this->tree1($data, $value['id']);
}
}
return $data;
}
版本二:存储数据
该版本在版本1的不断找到子级的基础上,增添了存储数据功能;即找到了当前级别的每一个父级与子级,并且使用引用传值将每一级关系存入了新数组$array中。
/**
* 经过1,得知需用新数组存储处理后的数组
* @param array $data
* @param int $pid
* @param array $array
* @return array
*/
public function tree2(array $data = [], int $pid = 0, &$array = []): array
{
//2.
foreach ($data as $key => &$value) {
if ($value['pid'] == $pid) {
$array[] = $value;
$this->tree2($data, $value['id'], $array);
}
}
return $array;
}
版本三:增添层级
tips: |----> 这个东西叫做分隔符,根据分隔符你可以从视觉上明确知道每个分类所处的位置以及它的父级与子级。
该版本在版本2的基础上,增添了层级,层级的作用就是用来决定要渲染几个分隔符的。比方说有个层级处在顶级,那么只需要渲染1个分隔符,例如上述后台示例图中的江西;那假如有个层级处在第4级,那么就需要渲染4个分隔符,也就是上述后台示例图中的尚庄。
/**
* 经过2,得知下拉框还需要一个层级用于展示html分隔符,层级默认设置为1代表默认有一个分隔符被渲染到页面上
* @param array $data
* @param int $pid
* @param array $array
* @param int $level
* @return array
*/
public function tree3(array $data = [], int $pid = 0, &$array = [], $level = 1): array
{
//3.
foreach ($data as $key => &$value) {
if ($value['pid'] == $pid) {
$value['level'] = $level;
$array[] = $value;
$level += 1; //这里不能使用$level + = 1;来改变$level本身的值,不然层级关系会完全乱套,只能使用这种加法去表示每一级的关系
$this->tree3($data, $value['id'], $array, $level);
}
}
return $array;
}
版本四:增添分隔符
/**
* 经过3已经得到需要的正确格式层级关系数据,只剩下利用层级去渲染分隔符
* @param array $data
* @param int $pid
* @param array $array
* @param int $level
* @return array
*/
public function tree4(array $data = [], int $pid = 0, &$array = [], $level = 1): array
{
//4.
foreach ($data as $key => &$value) {
if ($value['pid'] == $pid) {
//这里使用str_repeat去根据当前层级重复渲染字符串' |---->'出来,并追加一个level键,把对应值赋给当前的level键(对应值就是版本3中说到的要渲染几个分隔符到页面上)
$value['level'] = str_repeat(' |---->', $level);
$array[] = $value;
$this->tree4($data, $value['id'], $array, $level + 1);
}
}
return $array;
}
版本五:增添分隔符形参,以实现自定义分隔符
/**
* 经过4,得知分隔符 |---->可以当作形参,以实现自定义分割符目的,便是最终完整版,这里的分隔符其实并不美观,你们可以自定义修改即可
* @param array $data
* @param int $pid
* @param array $array
* @param int $level
* @param string $separator
* @return array
*/
public function tree(array $data = [], int $pid = 0, array &$array = [], $level = 1, $separator = ' |---->'): array
{
//5.
foreach ($data as $key => $value) {
if ($value['pid'] == $pid) {
$value['level'] = str_repeat($separator, $level);//分隔符修改处,并非一定要当形参引用传入,也可以写死来提升美观性,这里为了便于理解所以当作了形参处理
$array[] = $value;
$this->tree($data, $value['id'], $array, $level + 1);
}
}
return $array;
}
到这里整个树形下拉菜单已经完善的差不多了,至此也可以结束了...
但这并不意味着这是最完美的,实现的方式有很多,我这里只是列举了其中一种方法。
并且这里的自定义分隔符其实并不美观,也可以不当成形参使用,只是为了方便理解拓宽思路才这样写的。
最后把完整代码类附上:
<?php
/**
* Created by phpStrom
* User: Anbin
* Date: 2022/8/29
* Time: 16:23
*/
declare(strict_types=1);
/**
* 无限分类之树形菜单下拉
* Class Select
*/
class Select
{
//测试数据
const arr = [
['id' => 1, 'pid' => 0, 'city' => '江西'],
['id' => 2, 'pid' => 0, 'city' => '广东'],
['id' => 3, 'pid' => 1, 'city' => '宜春'],
['id' => 4, 'pid' => 3, 'city' => '丰城'],
['id' => 5, 'pid' => 4, 'city' => '尚庄'],
['id' => 6, 'pid' => 2, 'city' => '深圳'],
['id' => 7, 'pid' => 6, 'city' => '翻身'],
#以下是追加的同级元素数据,用来试验论证,也是增强干扰性
['id' => 8, 'pid' => 1, 'city' => '南昌'],
['id' => 9, 'pid' => 8, 'city' => '高新区'],
];
/**
* php无限分类之树形下拉框
* @return string
*/
public function select()
{
$array = $this->tree(self::arr);
return $this->dataToSelect($array, 7);
}
/**
* 体验递归
* @param array $data
* @param int $pid
* @return array
*/
public function tree1(array $data = [], int $pid = 0): array
{
//1.
foreach ($data as $key => &$value) {
if ($value['pid'] == $pid) {
$this->tree($data, $value['id']);
}
}
return $data;
}
/**
* 经过1,得知需用新数组存储处理后的数组
* @param array $data
* @param int $pid
* @param array $array
* @return array
*/
public function tree2(array $data = [], int $pid = 0, &$array = []): array
{
//2.
foreach ($data as $key => &$value) {
if ($value['pid'] == $pid) {
$array[] = $value;
$this->tree2($data, $value['id'], $array);
}
}
return $array;
}
/**
* 经过2,得知下拉框还需要一个层级用于展示html分隔符,层级默认设置为1代表默认有一个分隔符被渲染到页面上
* @param array $data
* @param int $pid
* @param array $array
* @param int $level
* @return array
*/
public function tree3(array $data = [], int $pid = 0, &$array = [], $level = 1): array
{
//3.
foreach ($data as $key => &$value) {
if ($value['pid'] == $pid) {
$value['level'] = $level;
$array[] = $value;
$level += 1; //这里不能使用$level + = 1;来改变$level本身的值,不然层级关系会完全乱套,只能使用这种加法去表示每一级的关系
$this->tree3($data, $value['id'], $array, $level);
}
}
return $array;
}
/**
* 经过3已经得到需要的正确格式层级关系数据,只剩下利用层级去渲染分隔符
* @param array $data
* @param int $pid
* @param array $array
* @param int $level
* @return array
*/
public function tree4(array $data = [], int $pid = 0, &$array = [], $level = 1): array
{
//4.
foreach ($data as $key => &$value) {
if ($value['pid'] == $pid) {
//这里使用str_repeat去根据当前层级重复渲染字符串' |---->'出来,并追加一个level键,把对应值赋给当前的level键(对应值就是版本3中说到的要渲染几个分隔符到页面上)
$value['level'] = str_repeat(' |---->', $level);
$array[] = $value;
$this->tree4($data, $value['id'], $array, $level + 1);
}
}
return $array;
}
/**
* 经过4,得知分隔符 |---->可以当作形参,以实现自定义分割符目的,便是最终完整版,这里的分隔符其实并不美观,你们可以自定义修改即可
* @param array $data
* @param int $pid
* @param array $array
* @param int $level
* @param string $separator
* @return array
*/
public function tree(array $data = [], int $pid = 0, array &$array = [], $level = 1, $separator = ' |---->'): array
{
//5.
foreach ($data as $key => $value) {
if ($value['pid'] == $pid) {
$value['level'] = str_repeat($separator, $level);//分隔符修改处,并非一定要当形参引用传入,也可以写死来提升美观性,这里为了便于理解所以当作了形参处理
$array[] = $value;
$this->tree($data, $value['id'], $array, $level + 1);
}
}
return $array;
}
/**
* 将无限分类后的数组转换成下拉框html
* @param array $data
* @return string
*/
public function dataToSelect(array $data, int $id = 0): string
{
$str = '<select><option>请选择';
foreach ($data as $key => $value) {
$judge = $id == $value['id'] ? 'selected' : ''; //判断是否有默认选中值
$str .= "<option {$judge}>" . $value['level'] . $value['city'] . '</option>';
}
$str .= '</option>';
return $str;
}
}
$select = new Select();
echo $select->select();