在 PHP 中,实现无限级菜单的方法有多种,常见的方式包括递归处理和利用特定的数据结构(如树形结构)。以下是几种常用方法的说明和实现案例:


方法一:递归方法

递归是一种常见的方式,通过递归函数处理菜单的父子关系,逐层生成菜单。

数据结构示例 (数组格式)

$menu = [
    ['id' => 1, 'name' => 'Home', 'parent_id' => 0],
    ['id' => 2, 'name' => 'About', 'parent_id' => 0],
    ['id' => 3, 'name' => 'Services', 'parent_id' => 0],
    ['id' => 4, 'name' => 'Web Design', 'parent_id' => 3],
    ['id' => 5, 'name' => 'SEO', 'parent_id' => 3],
    ['id' => 6, 'name' => 'Contact', 'parent_id' => 0],
    ['id' => 7, 'name' => 'Support', 'parent_id' => 6],
];

实现代码

function buildMenu($menu, $parentId = 0) {
    $html = '';
    foreach ($menu as $item) {
        if ($item['parent_id'] == $parentId) {
            $html .= '<li>' . $item['name'];
            // 递归调用
            $childMenu = buildMenu($menu, $item['id']);
            if ($childMenu) {
                $html .= '<ul>' . $childMenu . '</ul>';
            }
            $html .= '</li>';
        }
    }
    return $html;
}

// 调用
echo '<ul>' . buildMenu($menu) . '</ul>';

结果输出

<ul>
    <li>Home</li>
    <li>About</li>
    <li>Services
        <ul>
            <li>Web Design</li>
            <li>SEO</li>
        </ul>
    </li>
    <li>Contact
        <ul>
            <li>Support</li>
        </ul>
    </li>
</ul>

方法二:预处理成树形结构后再渲染

将菜单数据从扁平数组处理为树形结构后再进行渲染,效率更高。

数据结构示例

$menu = [
    ['id' => 1, 'name' => 'Home', 'parent_id' => 0],
    ['id' => 2, 'name' => 'About', 'parent_id' => 0],
    ['id' => 3, 'name' => 'Services', 'parent_id' => 0],
    ['id' => 4, 'name' => 'Web Design', 'parent_id' => 3],
    ['id' => 5, 'name' => 'SEO', 'parent_id' => 3],
    ['id' => 6, 'name' => 'Contact', 'parent_id' => 0],
    ['id' => 7, 'name' => 'Support', 'parent_id' => 6],
];

实现代码

function buildTree($menu, $parentId = 0) {
    $tree = [];
    foreach ($menu as $item) {
        if ($item['parent_id'] == $parentId) {
            $item['children'] = buildTree($menu, $item['id']);
            $tree[] = $item;
        }
    }
    return $tree;
}

function renderMenu($tree) {
    $html = '<ul>';
    foreach ($tree as $item) {
        $html .= '<li>' . $item['name'];
        if (!empty($item['children'])) {
            $html .= renderMenu($item['children']);
        }
        $html .= '</li>';
    }
    $html .= '</ul>';
    return $html;
}

// 调用
$tree = buildTree($menu);
echo renderMenu($tree);

结果输出

与方法一的输出结果一致。


方法三:使用数据库存储菜单

当菜单数据量较大时,可以通过数据库存储菜单数据。如果菜单表存储了 idparent_idname 等字段,结合递归查询或 PHP 数据处理,可以实现无限级菜单。

数据表结构

CREATE TABLE menus (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    parent_id INT NOT NULL DEFAULT 0
);

示例数据

INSERT INTO menus (id, name, parent_id) VALUES
(1, 'Home', 0),
(2, 'About', 0),
(3, 'Services', 0),
(4, 'Web Design', 3),
(5, 'SEO', 3),
(6, 'Contact', 0),
(7, 'Support', 6);

实现代码

function getMenuFromDatabase($parentId = 0, $pdo) {
    // 获取当前父级菜单
    $stmt = $pdo->prepare("SELECT * FROM menus WHERE parent_id = :parent_id");
    $stmt->execute(['parent_id' => $parentId]);
    $menus = $stmt->fetchAll(PDO::FETCH_ASSOC);

    $tree = [];
    foreach ($menus as $menu) {
        $menu['children'] = getMenuFromDatabase($menu['id'], $pdo);
        $tree[] = $menu;
    }

    return $tree;
}

function renderMenuFromTree($tree) {
    $html = '<ul>';
    foreach ($tree as $item) {
        $html .= '<li>' . $item['name'];
        if (!empty($item['children'])) {
            $html .= renderMenuFromTree($item['children']);
        }
        $html .= '</li>';
    }
    $html .= '</ul>';
    return $html;
}

// 数据库连接 (PDO)
$pdo = new PDO('mysql:host=localhost;dbname=testdb', 'username', 'password');

// 调用
$menuTree = getMenuFromDatabase(0, $pdo);
echo renderMenuFromTree($menuTree);

方法四:借助前端处理

如果后端只需要提供 JSON 数据,前端通过 JavaScript(如 Vue、React)或递归模板渲染,可以极大简化 PHP 的逻辑。

PHP 输出 JSON

function buildMenuTree($menu, $parentId = 0) {
    $tree = [];
    foreach ($menu as $item) {
        if ($item['parent_id'] == $parentId) {
            $item['children'] = buildMenuTree($menu, $item['id']);
            $tree[] = $item;
        }
    }
    return $tree;
}

// 调用
$tree = buildMenuTree($menu);
header('Content-Type: application/json');
echo json_encode($tree);

前端渲染(Vue 示例)

<ul>
    <li v-for="item in menu">
        {{ item.name }}
        <ul v-if="item.children">
            <menu-component :menu="item.children"></menu-component>
        </ul>
    </li>
</ul>

总结

  1. 递归方法:适合小型菜单,代码简单直接。
  2. 预处理为树形结构:适合中型菜单,性能更高。
  3. 数据库方法:适合大型菜单,数据从数据库中动态加载。
  4. 前后端分离:后端只提供 JSON 数据,前端完成渲染,适合现代开发模式。

根据项目需求,选择合适的实现方式!

https://www.cnblogs.com/fps2tao/p/15217729.html

Alpinejs

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Alpine.js 无限级菜单</title>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/cdn.min.js" defer></script>
    <style>
        ul {
            list-style-type: none;
        }

        li {
            margin: 5px 0;
        }

        .menu-item {
            cursor: pointer;
        }
    </style>
</head>
<body>
    <div x-data="menuComponent">
        <ul>
            <!-- 渲染菜单 -->
            <template x-for="item in menu" :key="item.id">
                <li>
                    <div class="menu-item" @click="toggle(item)">
                        <span x-text="item.name"></span>
                        <!-- 如果有子菜单,显示展开/收起符号 -->
                        <span x-show="item.children.length > 0">
                            (<span x-text="item.open ? '-' : '+'"></span>)
                        </span>
                    </div>
                    <!-- 子菜单 -->
                    <ul x-show="item.open" style="margin-left: 20px;">
                        <template x-for="child in item.children" :key="child.id">
                            <li>
                                <div class="menu-item" @click.stop="toggle(child)">
                                    <span x-text="child.name"></span>
                                    <span x-show="child.children.length > 0">
                                        (<span x-text="child.open ? '-' : '+'"></span>)
                                    </span>
                                </div>
                                <!-- 递归子菜单 -->
                                <ul x-show="child.open" style="margin-left: 20px;">
                                    <template x-for="subChild in child.children" :key="subChild.id">
                                        <li>
                                            <span x-text="subChild.name"></span>
                                        </li>
                                    </template>
                                </ul>
                            </li>
                        </template>
                    </ul>
                </li>
            </template>
        </ul>
    </div>

    <script>
        document.addEventListener('alpine:init', () => {
            Alpine.data('menuComponent', () => ({
                // 菜单数据
                menu: [
                    {
                        id: 1,
                        name: 'Home',
                        children: [],
                        open: false,
                    },
                    {
                        id: 2,
                        name: 'About',
                        children: [],
                        open: false,
                    },
                    {
                        id: 3,
                        name: 'Services',
                        children: [
                            {
                                id: 4,
                                name: 'Web Design',
                                children: [],
                                open: false,
                            },
                            {
                                id: 5,
                                name: 'SEO',
                                children: [],
                                open: false,
                            },
                        ],
                        open: false,
                    },
                    {
                        id: 6,
                        name: 'Contact',
                        children: [
                            {
                                id: 7,
                                name: 'Support',
                                children: [],
                                open: false,
                            },
                        ],
                        open: false,
                    },
                ],

                // 切换菜单展开/收起的状态
                toggle(item) {
                    item.open = !item.open;
                },
            }));
        });
    </script>
</body>
</html>

在 PHP 中,我们可以通过递归函数来实现一个无限极菜单,并在前端使用 HTML 进行渲染。以下是一个完整的实现代码示例,包括 PHP 数据处理和菜单的生成。


数据结构

我们假设菜单数据以一种常见的扁平结构存储,例如:

$menuData = [
    ['id' => 1, 'name' => 'Home', 'parent_id' => 0],
    ['id' => 2, 'name' => 'About', 'parent_id' => 0],
    ['id' => 3, 'name' => 'Services', 'parent_id' => 0],
    ['id' => 4, 'name' => 'Web Design', 'parent_id' => 3],
    ['id' => 5, 'name' => 'SEO', 'parent_id' => 3],
    ['id' => 6, 'name' => 'Contact', 'parent_id' => 0],
    ['id' => 7, 'name' => 'Support', 'parent_id' => 6],
    ['id' => 8, 'name' => 'FAQ', 'parent_id' => 7],
];

字段说明:

  • id: 当前菜单项的唯一标识。
  • name: 菜单项的名称。
  • parent_id: 此菜单项的父级菜单 ID(0 表示顶级菜单)。

PHP 无限极菜单实现

我们可以通过递归函数或者引用法构造菜单树,并最终生成 HTML 菜单结构。


方法一:递归法

通过递归函数处理菜单项,自动生成嵌套的 HTML。

<?php

// 样本数据
$menuData = [
    ['id' => 1, 'name' => 'Home', 'parent_id' => 0],
    ['id' => 2, 'name' => 'About', 'parent_id' => 0],
    ['id' => 3, 'name' => 'Services', 'parent_id' => 0],
    ['id' => 4, 'name' => 'Web Design', 'parent_id' => 3],
    ['id' => 5, 'name' => 'SEO', 'parent_id' => 3],
    ['id' => 6, 'name' => 'Contact', 'parent_id' => 0],
    ['id' => 7, 'name' => 'Support', 'parent_id' => 6],
    ['id' => 8, 'name' => 'FAQ', 'parent_id' => 7],
];

/**
 * 构造菜单树
 * 
 * @param array $menuData 菜单数据
 * @param int $parentId 父级菜单 ID
 * @return array 菜单树
 */
function buildMenuTree($menuData, $parentId = 0) {
    $branch = [];
    foreach ($menuData as $menuItem) {
        if ($menuItem['parent_id'] == $parentId) {
            $children = buildMenuTree($menuData, $menuItem['id']);
            if (!empty($children)) {
                $menuItem['children'] = $children;
            }
            $branch[] = $menuItem;
        }
    }
    return $branch;
}

/**
 * 生成菜单的 HTML
 * 
 * @param array $menuTree 菜单树
 * @return string HTML 内容
 */
function renderMenu($menuTree) {
    $html = '<ul>';
    foreach ($menuTree as $menuItem) {
        $html .= '<li>' . $menuItem['name'];
        if (!empty($menuItem['children'])) {
            $html .= renderMenu($menuItem['children']); // 递归生成子菜单
        }
        $html .= '</li>';
    }
    $html .= '</ul>';
    return $html;
}

// 构造菜单树
$menuTree = buildMenuTree($menuData);

// 生成菜单 HTML
echo renderMenu($menuTree);

?>

输出结果:

<ul>
    <li>Home</li>
    <li>About</li>
    <li>Services
        <ul>
            <li>Web Design</li>
            <li>SEO</li>
        </ul>
    </li>
    <li>Contact
        <ul>
            <li>Support
                <ul>
                    <li>FAQ</li>
                </ul>
            </li>
        </ul>
    </li>
</ul>

方法二:使用引用法构建树形结构

引用法比递归法更高效,特别是当菜单数据量较大时。通过引用法,可以一次性处理所有数据,无需多次递归调用。

<?php

// 样本数据
$menuData = [
    ['id' => 1, 'name' => 'Home', 'parent_id' => 0],
    ['id' => 2, 'name' => 'About', 'parent_id' => 0],
    ['id' => 3, 'name' => 'Services', 'parent_id' => 0],
    ['id' => 4, 'name' => 'Web Design', 'parent_id' => 3],
    ['id' => 5, 'name' => 'SEO', 'parent_id' => 3],
    ['id' => 6, 'name' => 'Contact', 'parent_id' => 0],
    ['id' => 7, 'name' => 'Support', 'parent_id' => 6],
    ['id' => 8, 'name' => 'FAQ', 'parent_id' => 7],
];

/**
 * 使用引用法构造菜单树
 * 
 * @param array $menuData 菜单数据
 * @return array 菜单树
 */
function buildMenuTreeByReference($menuData) {
    $tree = [];
    $references = [];
    
    // 遍历数据,将每个菜单项通过 ID 引用保存
    foreach ($menuData as &$menuItem) {
        $menuItem['children'] = []; // 初始化子菜单
        $references[$menuItem['id']] = &$menuItem; // 保存引用
    }
    
    // 构建树形结构
    foreach ($menuData as &$menuItem) {
        if ($menuItem['parent_id'] == 0) {
            // 如果是顶级菜单,直接加入树中
            $tree[] = &$menuItem;
        } else {
            // 否则加入父级菜单的 children 中
            $references[$menuItem['parent_id']]['children'][] = &$menuItem;
        }
    }
    
    return $tree;
}

/**
 * 生成菜单的 HTML
 * 
 * @param array $menuTree 菜单树
 * @return string HTML 内容
 */
function renderMenu($menuTree) {
    $html = '<ul>';
    foreach ($menuTree as $menuItem) {
        $html .= '<li>' . $menuItem['name'];
        if (!empty($menuItem['children'])) {
            $html .= renderMenu($menuItem['children']); // 递归生成子菜单
        }
        $html .= '</li>';
    }
    $html .= '</ul>';
    return $html;
}

// 构造菜单树
$menuTree = buildMenuTreeByReference($menuData);

// 生成菜单 HTML
echo renderMenu($menuTree);

?>

区别和对比

方法优点缺点
递归法代码简单,逻辑清晰,适合小型数据集。每次递归会重新遍历整个数据集,性能较差。
引用法性能高效,修改和扩展方便,适合大型数据集。稍微复杂,需理解引用操作。

总结

如果菜单数据量较小,可以直接使用递归法实现,代码简单易读。如果菜单数据量较大,或者需要频繁操作菜单树结构,推荐使用引用法。两种方法都可以生成无限级菜单,并且能够灵活适应不同的项目需求。

你可以将这些代码整合到项目中,例如从数据库中提取菜单数据,动态生成 HTML 菜单,或者结合前端框架(如 Vue.js 或 Alpine.js)进一步增强交互性。

标签: PHP

添加新评论