前言
最近发现主题的目录树很多地方不是很满意,一方面是自己对于php的知识不是很好,大部分都是从网上分析的代码中拿取cv一下就用了,加上重构主题是一个比较大的工程,所以有些地方都是奔着先跑起来再说的思路。
像极了项目开发时的场景,先上线再说,用户量大了我们再优化!
昨晚我看了下typecho的目录树,都使用了一个全局变量:
global $catalog;
$catalog是一个扁平化的数组,里面存放着按顺序提取的页面h1-h6标题数据,由于php的数组可视化巨难看,我们先通过ts的方式去了解和实现这个处理。
教程
$catalog
的数据结构大致如下:
const catalog = [
{
level: 1,
name: "一级目录",
index: 1,
},
{
level: 2,
name: "二级目录",
index: 2,
},
{
level: 3,
name: "三级目录",
index: 3,
},
{
level: 4,
name: "四级目录",
index: 4,
},
{
level: 5,
name: "五级目录",
index: 5,
},
{
level: 6,
name: "六级目录",
index: 6,
},
{
level: 2,
name: "二级目录",
index: 7,
},
{
level: 3,
name: "三级目录",
index: 8,
},
{
level: 3,
name: "三级目录",
index: 9,
},
{
level: 2,
name: "二级目录",
index: 10,
},
{
level: 2,
name: "二级目录",
index: 11,
},
{
level: 3,
name: "三级目录",
index: 12,
},
{
level: 3,
name: "三级目录",
index: 13,
},
{
level: 1,
name: "一级目录",
index: 14,
},
];
数组中的子对象有三个属性,当然这三个属性名我没有完全按照typecho的设置来,我们先用更好理解的字段来实现这个功能先。
level
表示是h1-h6的数字,h1就是1,以此类推name
表示标题元素的文本内容index
表示标题元素的顺序
index这里还用不到,但是在php处理hash跳转的时候我们会用到,所以这里先放着。
现在我们需要实现一个函数,它可以将下一个level大于上一个level时,将下一个作为上一个的children子集数据存起来。以此类推,直到存在同级level或者小于level结束嵌套。
想了很久,我得出一个非常棒的处理方案: 从末尾往前处理,一层一层处理嵌套 。
用代码表示是这样:
//源数据
[1, 2, 3, 4, 5, 6, 2, 3, 3, 2, 2, 3, 3, 1]
//处理第一层
[1, 2, 3, 4, 5, 2, 3, 3, 2, 2, 3, 3, 1]
//处理第二层
[1, 2, 3, 4, 2, 3, 3, 2, 2, 3, 3, 1]
//处理第三层
[1, 2, 3, 2, 3, 3, 2, 2, 3, 3, 1]
//处理第四层
[1, 2, 2, 2, 2, 1]
//处理第五层
[1, 1]
//处理第六层:已经到顶了,原样返回
[1, 1]
非常好理解,我们先从后往前处理,循环到6的时候,我们判断到它的level是6,然后我们获取它的上一级,它的上一级就是它当前的index-1
,当然也不一定是-1,所以我们需要做一个while循环去往前拿,直到拿到的level值是小于6的,然后我们把6作为parent的children值传入,注意这里我们得通过unshift
方法,因为我们是倒序循环,存的时候为了保证顺序就得反着存。
存完我把这个数据splice删除;
当我们倒序循环完毕后,递归自身,处理下一层。
于是我们可以这么写:
type TreeData = Array<{
level: number;
name: string;
index: number;
children?: TreeData;
}>;
const catalog: TreeData = [...];
function generateTree(list: TreeData, level = 6): TreeData {
if (level <= 1) return list;
for (let i = list.length - 1; i >= 0; i--) {
const item = list[i];
if (item.level === level) {
let parentIndex = i - 1;
let parent = list[parentIndex];
while (parent?.level >= level) {
parent = list[--parentIndex];
}
if (!parent) break;
if (!Array.isArray(parent.children)) parent.children = [];
parent.children.unshift(item);
list.splice(i, 1);
}
}
return generateTree(list, level - 1);
}
const tree = generateTree(catalog);
console.log("🚀 ~ file: main.ts:104 ~ tree:", tree);
需要注意的是list[parentIndex]
可能是没有的,所以在下面我用了可选链避免undefined.level
导致的报错。
然后就是break
,跳出本次for循环,之前沙雕了,直接return,return会导致整个for都不走了。
此时我们查看打印可以得到一个完美的嵌套结构:
如果你的目的就是这样,那么看到这里就足够了。
但是,我们的需求还要更近一步,我希望能过滤掉指定的层级,就以掘金来举例,它最大只支持三级层级嵌套,我们也需要实现这个效果。
所以我们实现一个删除指定层级深度的函数:
function removeChildren(list: TreeData, depth: number, currentDepth = 0): TreeData {
list.forEach((item) => {
if (item.children && item.children.length > 0) {
if (currentDepth < depth - 1) {
removeChildren(item.children, depth, currentDepth + 1);
} else {
delete item.children;
}
}
});
return list;
}
效果:
const tree = generateTree(catalog);
const maxTree = removeChildren(tree, 3);
console.log("🚀 ~ file: main.ts:117 ~ maxTree:", maxTree);
至此我们的核心逻辑已经实现,下面我们就需要将其搬运到php上使用,转译一下。
/**
* @description: 将扁平化目录树数组转成结构化目录树数组
* @param {*} $list 目录树数组
* @param {*} $depth 最大层级
* @Date: 2023-06-03 15:42:21
* @Author: mulingyuer
*/
function generateTreeList($list, $depth = 6) {
if (count($list) <= 0 || $depth <= 1) {
return $list;
}
for ($i = count($list) - 1; $i >= 0; $i--) {
$item = $list[$i];
if ($item['depth'] == $depth) {
$parentIndex = $i - 1;
while ($parentIndex >= 0) {
$parent = &$list[$parentIndex];
if ($parent['depth'] < $depth) {
break;
}
$parentIndex--;
}
if ($parentIndex < 0) {
break;
}
if ( ! is_array($parent['children'])) {
$parent['children'] = array();
}
array_unshift($parent['children'], $item);
array_splice($list, $i, 1);
}
}
$list = array_values($list);
return generateTreeList($list, $depth - 1);
}复制代码
这个方法只是生成层级结构数据,我们还需要限制层级数,于是转义第二个函数:
/**
* @description: 删除目录树数组指定层级children
* @param {*} $list 目录树数组
* @param {*} $depth 最大层级
* @param {*} $currentDepth 当前层级
* @Date: 2023-06-03 15:49:03
* @Author: mulingyuer
*/
function removeChildren($list, $depth, $currentDepth = 0) {
foreach ($list as &$item) {
if (isset($item['children']) && count($item['children']) > 0) {
if ($currentDepth < $depth - 1) {
$item['children'] = removeChildren($item['children'], $depth, $currentDepth + 1);
} else {
unset($item['children']);
}
}
}
return $list;
}复制代码
使用这两个函数配合,我们可以生成指定层级数量的目录树结构数据,下面我们就要开始生成HTML,经过我个人测试发现,我们可以在生成HTML结构的时候同时限制层级数量,这样就可以节省一个函数使用,代码如下:
/**
* @description: 生成目录树html
* @param {*} $arr 目录树数组
* @param {*} $depth 最大层级
* @param {*} $currentDepth 当前层级
* @param {*} $isChildren 是否是子级
* @Date: 2023-06-03 16:48:54
* @Author: mulingyuer
*/
function generateTreeTemplate($arr, $depth, $currentDepth = 1, $isChildren = false) {
if (count($arr) <= 0) {
return '<div>暂无目录</div>';
}
if ($currentDepth > $depth) {
return '';
}
$output = ! $isChildren ? '<ul>' : '';
foreach ($arr as $item) {
$output .= '<li><a href="#heading-'.$item['count'].'" title="'.$item['text'].'">'.$item['text'].'</a>';
if ( ! empty($item['children']) && $currentDepth < $depth) {
$output .= '<ul>';
$output .= generateTreeTemplate($item['children'], $depth, $currentDepth + 1, true);
$output .= '</ul>';
}
$output .= '</li>';
}
$output .= ! $isChildren ? '</ul>' : '';
return $output;
}复制代码
通过制定第二个参数,我们可以生成指定层级的HTML。
使用:
/**
* @description: 获取目录树
* @param {*} $maxDirectory 最大层级
* @Date: 2023-06-03 22:30:49
* @Author: mulingyuer
*/
function getJJDirectoryTree($maxDirectory = 3) {
global $catalog;
$treeList = generateTreeList(array_replace_recursive(array(), $catalog));
echo generateTreeTemplate($treeList, $maxDirectory);
}复制代码
在typecho的主题中,我们可以调用该函数就能获取到一个目录树结构。
<?php getJJDirectoryTree();?>复制代码
效果图
这是一个我自己实现的效果,还是很不错的,有兴趣的可以自己研究更多玩法。
Please send your reply to bahmbi3@aghemfondom.com HRM Bah Mbi