无限级菜单排序,怎么样写才最优雅?

原数据

$temp = [
        ['id' => 1, 'pid' => 0, 'name' => '商品管理'],
        ['id' => 2, 'pid' => 1, 'name' => '平台自营'],
        ['id' => 3, 'pid' => 2, 'name' => '图书品类'],
        ['id' => 4, 'pid' => 2, 'name' => '3C品类'],
        ['id' => 5, 'pid' => 0, 'name' => '第三方'],
        ['id' => 6, 'pid' => 5, 'name' => '家私用品'],
        ['id' => 7, 'pid' => 5, 'name' => '书法品赏'],
        ['id' => 8, 'pid' => 7, 'name' => '行书'],
        ['id' => 9, 'pid' => 8, 'name' => '行楷'],
        ['id' => 10, 'pid' => 9, 'name' => '张山行楷字帖'],
        ['id' => 11, 'pid' => 22, 'name' => '李四行楷字帖'],
    ];

pid 记录的是 父级 id,当为 0 时为第一级,现在对这段数据进行排序,让菜单层级排序,大家是怎么做的?

方案 1

思想:当拿到一个节点后,判断是否有该节点的子节点,如果有则为它的子菜单,子菜单递归前面的操作。

function te(array $arr, $node = 0)
{
    $tem = []; 
    foreach ($arr as $val) {
        $pid = $val['pid']; // 当前菜单的父级ID
        $id = $val['id']; // 当前菜单ID
        // 当前的父级ID和node节点匹配,说明当前子菜单属于node节点。
        if ($pid == $node) { 
            $tem[$id] = $val; // 保存当前的菜单。
            $son = te($arr, $id); // 递归 当前子菜单是否有子菜单?
            // 当有的时候,保存 。
            if (!empty($son)) {
                $tem[$id]['son'] = $son;
            }
        }
    }
    return $tem;
}

function show(array $arr, $node = 0)
{
    foreach ($arr as $val) {
        if ($val['pid'] == 0) {
            $node = 0;
        }
        for ($i = 0; $i < $node; $i++) {
            echo '----';
        }
        echo $val['name'] . "\n";
        if (isset($val['son'])) {
            $node += 1;
            show($val['son'], $node);
        }
    }
}

$menus = te($temp);
 show($menus);

输出:

商品管理
----平台自营
--------图书品类
--------3C品类
第三方
----家私用品
----书法品赏
--------行书
------------行楷
----------------张山行楷字帖

方案 2

思想和方案1 一样,唯一不一样的地方是需要提前声明接收值。[该方法来之评论区。]

function traverseMenu(array $menus, array &$result, $pid = 0)
{
    foreach ($menus as $child_menu) {
        // 子菜单的 pid = $pid
        // 说明它是 $pid 下面的子菜单
        if ($child_menu['pid'] == $pid) {
            // 包装为成我们需要的格式
            $item = [
                'id' => $child_menu['id'],
                'name' => $child_menu['name'],
                'children' => []
            ];
            // 递归上面的操作,装完 所有 属于 $pid 下面的子菜单
            traverseMenu($menus, $item['children'], $child_menu['id']);
            // 至此完成 $pid 级下的所有子菜单装入 result 中
            $result[] = $item;
        }
    }
}

测试代码

    $result = [];
    traverseMenu($temp, $result, 0);
    print_r($result);

其它说明

  1. 本帖想通过 一个简短的、不超过 50 行的代码,让读者能够领悟其中精华,然后写出自己想要的东西。
  2. 发现有小伙伴直接贴一些 URL 链接,又不说明链接内容,让人费解,希望发言也有头有尾。
莫等闲,白了少年头,空悲切
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
讨论数量: 6
4年前 评论
bigbug-gg (楼主) 4年前
Dennis_Ritchie

$temp = [
    ['id' => 1, 'pid' => 0, 'name' => '商品管理'],
    ['id' => 2, 'pid' => 1, 'name' => '平台自营'],
    ['id' => 3, 'pid' => 2, 'name' => '图书品类'],
    ['id' => 4, 'pid' => 2, 'name' => '3C品类'],
    ['id' => 5, 'pid' => 0, 'name' => '第三方'],
    ['id' => 6, 'pid' => 5, 'name' => '家私用品'],
    ['id' => 7, 'pid' => 5, 'name' => '书法品赏'],
    ['id' => 8, 'pid' => 7, 'name' => '行书'],
    ['id' => 9, 'pid' => 8, 'name' => '行楷'],
    ['id' => 10, 'pid' => 9, 'name' => '张山行楷字帖'],
    ['id' => 11, 'pid' => 22, 'name' => '李四行楷字帖'],
];

function traverseMenu(array $menus, array &$result, $pid = 0)
{
    foreach ($menus as $child_menu) {
        if ($child_menu['pid'] == $pid) {
            $item = ['id' => $child_menu['id'], 'name' => $child_menu['name'], 'children' => []];
            traverseMenu($menus, $item['children'], $child_menu['id']);
            $result[] = $item;
        } else {
            continue;
        }
    }
}

$result = [];
traverseMenu($temp, $result, 0);
print_r($result);

你可以参考我写的,希望可以帮助到你

4年前 评论
bigbug-gg (楼主) 4年前
wanghan 4年前
kopa 4年前

我的写法不是在算法层面
我是使用API资源获取子节点关联
查询效率是不好 不过数据更新后查询缓存一次 应该影响不大
好处是不用另外写代码了

4年前 评论
bigbug-gg (楼主) 4年前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!