history api和后台菜单联动

8 阅读4分钟

实现思路

  1. 菜单点击事件:点击菜单项时,通过pushState更新浏览器历史记录,同时更新菜单选中状态和页面内容。
  2. popstate 事件监听:监听浏览器前进 / 后退触发的popstate事件,根据历史记录中的状态信息,同步更新菜单选中和页面内容。
  3. 状态同步核心:维护一个统一的 “更新页面状态” 函数,无论是菜单点击还是popstate触发,都调用该函数确保状态一致。

完整实现代码

以下是一个可直接运行的示例,包含 HTML 结构、CSS 样式和 JavaScript 逻辑,模拟后台菜单与 History API 的联动:

html

预览

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>后台菜单与History API联动</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        .layout {
            display: flex;
            height: 100vh;
        }
        .sidebar {
            width: 200px;
            background: #2f4050;
            color: #fff;
        }
        .menu {
            list-style: none;
        }
        .menu-item {
            padding: 12px 20px;
            cursor: pointer;
            transition: background 0.2s;
        }
        .menu-item.active {
            background: #1ab394;
            color: #fff;
        }
        .menu-item:hover {
            background: #1c2b36;
        }
        .content {
            flex: 1;
            padding: 20px;
            background: #f3f3f4;
        }
        .content-view {
            background: #fff;
            padding: 20px;
            border-radius: 4px;
            min-height: 200px;
        }
    </style>
</head>
<body>
    <div class="layout">
        <!-- 侧边导航菜单 -->
        <aside class="sidebar">
            <ul class="menu" id="menu">
                <li class="menu-item" data-path="/dashboard">仪表盘</li>
                <li class="menu-item" data-path="/user">用户管理</li>
                <li class="menu-item" data-path="/order">订单管理</li>
                <li class="menu-item" data-path="/setting">系统设置</li>
            </ul>
        </aside>
        <!-- 内容区域 -->
        <main class="content">
            <div class="content-view" id="contentView">
                欢迎来到后台管理系统,请选择左侧菜单
            </div>
        </main>
    </div>

    <script>
        // 核心元素获取
        const menu = document.getElementById('menu');
        const menuItems = menu.querySelectorAll('.menu-item');
        const contentView = document.getElementById('contentView');

        // 1. 初始化:设置默认选中项(比如仪表盘)
        const defaultPath = '/dashboard';
        initPage(defaultPath);

        // 2. 菜单点击事件处理
        menuItems.forEach(item => {
            item.addEventListener('click', function() {
                const path = this.dataset.path;
                // 更新历史记录(pushState:添加新记录,replaceState:替换当前记录)
                history.pushState({ path }, '', path);
                // 更新页面状态(菜单选中 + 内容切换)
                updatePageState(path);
            });
        });

        // 3. 监听popstate事件(浏览器前进/后退触发)
        window.addEventListener('popstate', function(e) {
            // 从历史记录的state中获取路径,若无则用默认值
            const path = e.state?.path || defaultPath;
            updatePageState(path);
        });

        /**
         * 初始化页面
         * @param {string} path - 初始路径
         */
        function initPage(path) {
            // 替换初始历史记录(避免刷新后路径异常)
            history.replaceState({ path }, '', path);
            updatePageState(path);
        }

        /**
         * 统一更新页面状态(核心函数)
         * @param {string} path - 目标路径
         */
        function updatePageState(path) {
            // ① 更新菜单选中状态
            menuItems.forEach(item => {
                if (item.dataset.path === path) {
                    item.classList.add('active');
                } else {
                    item.classList.remove('active');
                }
            });

            // ② 更新内容区域(实际项目中可替换为接口请求/组件渲染)
            updateContentView(path);
        }

        /**
         * 更新内容区域内容
         * @param {string} path - 目标路径
         */
        function updateContentView(path) {
            const contentMap = {
                '/dashboard': '仪表盘页面:展示系统核心数据统计',
                '/user': '用户管理页面:可查看、编辑、删除用户信息',
                '/order': '订单管理页面:处理订单查询、发货、退款等操作',
                '/setting': '系统设置页面:配置网站基础信息、权限等'
            };
            contentView.textContent = contentMap[path] || '页面不存在';
        }
    </script>
</body>
</html>

代码关键部分解释

  1. History API 核心方法

    • pushState(state, title, url):添加一条新的历史记录,不会触发页面刷新,state参数用于存储自定义状态(这里存路径)。
    • replaceState(state, title, url):替换当前历史记录,适合初始化或不需要保留历史的场景。
    • popstate事件:浏览器前进 / 后退时触发,事件对象e.state能获取之前存在pushState/replaceState中的状态数据。
  2. 统一状态更新函数updatePageState是核心,无论是菜单点击还是popstate触发,都通过它同步菜单选中和内容,避免状态不一致。

  3. 初始化处理:页面加载时用replaceState初始化历史记录,确保刷新页面后路径和菜单状态仍匹配。

扩展适配(实际项目场景)

  • 路由框架适配:如果使用 Vue/React 等框架,可结合框架路由(如 Vue Router 的beforeEach、React Router 的useNavigate),本质仍是监听路由变化同步菜单状态。
  • 异步内容加载:实际后台中,updateContentView可替换为接口请求(axios/fetch)加载页面内容,示例中仅用静态文本模拟。
  • 子菜单 / 嵌套路由:可扩展data-path为层级路径(如/user/list),并在updatePageState中处理父菜单展开逻辑。
  • 页面刷新兼容:服务端需配置路由重定向(如 Nginx),确保刷新非根路径时能正常返回页面(否则会 404)。

总结

  1. 核心是通过pushState/replaceState管理历史记录,popstate监听浏览器导航,统一的updatePageState同步状态。
  2. 菜单点击触发pushState + 状态更新,浏览器前进 / 后退触发popstate + 状态更新,确保双向联动。
  3. 实际项目中需结合框架路由或服务端配置,解决刷新 404、嵌套菜单等场景问题。