交互式菜单脚本?

wen 实用脚本 42
<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">交互式菜单脚本 · 可视化演示</title>
    <style>
        * {
            box-sizing: border-box;
            font-family: system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif;
        }
        body {
            background: #f4f6f9;
            display: flex;
            justify-content: center;
            align-items: center;
            min-height: 100vh;
            margin: 0;
            padding: 20px;
        }
        .card {
            max-width: 600px;
            width: 100%;
            background: #ffffff;
            border-radius: 24px;
            box-shadow: 0 20px 40px rgba(0,0,0,0.08), 0 8px 16px rgba(0,0,0,0.04);
            padding: 28px 24px;
            transition: all 0.2s ease;
        }
        h1 {
            font-size: 1.7rem;
            font-weight: 600;
            margin-top: 0;
            margin-bottom: 0.3rem;
            color: #1e293b;
            display: flex;
            align-items: center;
            gap: 8px;
        }
        .subhead {
            color: #64748b;
            font-size: 0.95rem;
            margin-bottom: 1.5rem;
            padding-bottom: 0.8rem;
            border-bottom: 1px solid #e9edf2;
            display: flex;
            justify-content: space-between;
            align-items: center;
        }
        .menu-container {
            background: #fafbfc;
            border-radius: 16px;
            padding: 12px 8px;
            border: 1px solid #edf0f5;
        }
        .menu-item {
            display: flex;
            flex-direction: column;
            border-radius: 10px;
            transition: background 0.1s ease;
        }
        .menu-row {
            display: flex;
            align-items: center;
            gap: 8px;
            padding: 8px 12px;
            cursor: pointer;
            border-radius: 8px;
            transition: background 0.1s ease;
            user-select: none;
        }
        .menu-row:hover {
            background: #eef2f8;
        }
        .menu-row:active {
            background: #e2e8f0;
        }
        .expand-icon {
            font-size: 0.8rem;
            width: 20px;
            text-align: center;
            color: #64748b;
            flex-shrink: 0;
            transition: transform 0.2s ease, opacity 0.2s ease;
        }
        .expand-icon.leaf {
            opacity: 0.4;
            transform: none;
        }
        .menu-label {
            flex: 1;
            font-weight: 500;
            color: #0f172a;
            letter-spacing: 0.01em;
        }
        .badge {
            background: #e2e8f0;
            color: #334155;
            font-size: 0.7rem;
            font-weight: 600;
            padding: 2px 10px;
            border-radius: 30px;
            letter-spacing: 0.3px;
        }
        .children-wrapper {
            display: flex;
            flex-direction: column;
            margin-left: 28px;
            padding-left: 12px;
            border-left: 2px solid #dce2ec;
        }
        .action-bar {
            margin-top: 24px;
            display: flex;
            flex-wrap: wrap;
            gap: 10px;
            justify-content: center;
        }
        .btn {
            background: white;
            border: 1px solid #d1d9e6;
            padding: 8px 20px;
            border-radius: 40px;
            font-weight: 500;
            font-size: 0.9rem;
            color: #1e293b;
            cursor: pointer;
            transition: 0.15s ease;
            display: inline-flex;
            align-items: center;
            gap: 6px;
            box-shadow: 0 1px 2px rgba(0,0,0,0.02);
            flex: 1 0 auto;
            justify-content: center;
        }
        .btn-primary {
            background: #1e293b;
            border: 1px solid #1e293b;
            color: white;
        }
        .btn-primary:hover {
            background: #0f172a;
            border-color: #0f172a;
            box-shadow: 0 4px 8px rgba(15, 23, 42, 0.15);
        }
        .btn-outline {
            background: white;
        }
        .btn-outline:hover {
            background: #f1f5f9;
            border-color: #b9c5d6;
        }
        .btn:active {
            transform: scale(0.96);
        }
        .status-area {
            margin-top: 20px;
            background: #f1f5f9;
            border-radius: 40px;
            padding: 10px 18px;
            font-size: 0.85rem;
            color: #1e293b;
            display: flex;
            align-items: center;
            gap: 10px;
            flex-wrap: wrap;
            border: 1px solid #e2e8f0;
        }
        .status-icon {
            font-size: 1.1rem;
        }
        #statusText {
            font-weight: 500;
            font-family: 'SF Mono', 'Fira Code', monospace;
            background: #e2e8f0;
            padding: 2px 12px;
            border-radius: 40px;
            color: #0f172a;
            word-break: break-word;
        }
        .empty-hint {
            color: #94a3b8;
            font-style: italic;
            padding: 6px 0 6px 16px;
            font-size: 0.85rem;
        }
        /* 折叠状态 */
        .children-wrapper.collapsed {
            display: none;
        }
        .rotate-icon {
            transform: rotate(90deg);
        }
        .footer-note {
            color: #94a3b8;
            font-size: 0.75rem;
            text-align: center;
            margin-top: 20px;
            border-top: 1px solid #edf0f5;
            padding-top: 16px;
        }
        @media (max-width: 480px) {
            .card { padding: 20px 16px; }
            .btn { padding: 6px 14px; font-size: 0.8rem; }
        }
    </style>
</head>
<body>
<div class="card">
    <h1>
        <span>📂 菜单脚本</span>
        <span style="font-size:0.9rem; font-weight:400; color:#64748b; margin-left:auto;">交互式</span>
    </h1>
    <div class="subhead">
        <span>点击展开 / 折叠</span>
        <span style="display:flex; gap:4px;">🔽 嵌套 · 实时</span>
    </div>
    <!-- 菜单渲染容器 -->
    <div id="menuRoot" class="menu-container">
        <!-- 动态由 js 生成 -->
    </div>
    <!-- 操作按钮组 -->
    <div class="action-bar">
        <button class="btn btn-outline" id="expandAllBtn" title="展开所有节点">➕ 全部展开</button>
        <button class="btn btn-outline" id="collapseAllBtn" title="折叠所有节点">➖ 全部折叠</button>
        <button class="btn btn-primary" id="getStateBtn">📋 获取状态</button>
    </div>
    <!-- 状态显示 -->
    <div class="status-area">
        <span class="status-icon">📌</span>
        <span>当前选中:</span>
        <span id="statusText">(暂无)</span>
    </div>
    <div class="footer-note">
        💡 点击菜单行 · 递归组件 / 动态状态读取
    </div>
</div>
<script>
    (function() {
        // ----- 定义菜单数据结构 (嵌套) -----
        const menuData = [
            {
                label: '📄 文件',
                children: [
                    {
                        label: '📁 新建',
                        children: [
                            { label: '📝 文本文档', children: [] },
                            { label: '📊 电子表格', children: [] },
                            { label: '📽️ 演示文稿', children: [] }
                        ]
                    },
                    {
                        label: '📂 打开',
                        children: [
                            { label: '🖼️ 图片浏览器', children: [] },
                            { label: '📖 最近文件', children: [
                                { label: 'report_final.pdf', children: [] },
                                { label: 'notes.md', children: [] }
                            ]}
                        ]
                    },
                    { label: '💾 保存', children: [] },
                    {
                        label: '⚙️ 导出',
                        children: [
                            { label: '🌐 HTML', children: [] },
                            { label: '📄 PDF', children: [] }
                        ]
                    }
                ]
            },
            {
                label: '✏️ 编辑',
                children: [
                    { label: '↩️ 撤销', children: [] },
                    { label: '↪️ 重做', children: [] },
                    {
                        label: '✂️ 剪切', children: []
                    },
                    {
                        label: '📋 剪贴板',
                        children: [
                            { label: '📑 复制', children: [] },
                            { label: '📌 粘贴', children: [] },
                            { label: '🔪 剪下', children: [] }
                        ]
                    }
                ]
            },
            {
                label: '👁️ 视图',
                children: [
                    {
                        label: '🔍 缩放',
                        children: [
                            { label: '🔎 放大', children: [] },
                            { label: '🔍 缩小', children: [] },
                            { label: '🔄 重置', children: [] }
                        ]
                    },
                    { label: '📏 标尺', children: [] },
                    { label: '🧩 侧边栏', children: [] }
                ]
            },
            {
                label: '❓ 帮助',
                children: [
                    { label: '📖 文档', children: [] },
                    { label: '💬 #39;, children: [] }
                ]
            }
        ];
        // ----- 渲染函数 (递归) -----
        function renderMenu(menuItems, parentElement, depth = 0) {
            menuItems.forEach(item => {
                const itemDiv = document.createElement('div');
                itemDiv.className = 'menu-item';
                // 行容器 (点击交互)
                const row = document.createElement('div');
                row.className = 'menu-row';
                // 展开图标
                const iconSpan = document.createElement('span');
                iconSpan.className = 'expand-icon';
                const hasChildren = item.children && item.children.length > 0;
                if (hasChildren) {
                    iconSpan.textContent = '▶';  // 右三角 (折叠状态)
                    iconSpan.classList.add('expandable');
                } else {
                    iconSpan.textContent = '·';
                    iconSpan.classList.add('leaf');
                }
                row.appendChild(iconSpan);
                // 标签
                const labelSpan = document.createElement('span');
                labelSpan.className = 'menu-label';
                labelSpan.textContent = item.label;
                row.appendChild(labelSpan);
                // 如果有子节点,加一个小 badge 数量
                if (hasChildren) {
                    const badge = document.createElement('span');
                    badge.className = 'badge';
                    badge.textContent = `${item.children.length} 项`;
                    row.appendChild(badge);
                }
                itemDiv.appendChild(row);
                // 子节点容器 ( children-wrapper )
                const childrenWrapper = document.createElement('div');
                childrenWrapper.className = 'children-wrapper collapsed'; // 默认折叠
                if (hasChildren) {
                    renderMenu(item.children, childrenWrapper, depth + 1);
                } else {
                    // 没有子节点,可以留空或加占位
                    // 保持干净
                }
                itemDiv.appendChild(childrenWrapper);
                // ----- 点击事件 (展开/折叠 + 状态更新) -----
                row.addEventListener('click', function(e) {
                    e.stopPropagation();  // 防止向上冒泡触发父级的点击
                    // 1. 切换展开/折叠 (仅当有子节点)
                    if (hasChildren) {
                        const isCollapsed = childrenWrapper.classList.contains('collapsed');
                        if (isCollapsed) {
                            childrenWrapper.classList.remove('collapsed');
                            iconSpan.classList.add('rotate-icon'); // 旋转箭头
                        } else {
                            childrenWrapper.classList.add('collapsed');
                            iconSpan.classList.remove('rotate-icon');
                        }
                    }
                    // 2. 更新状态栏: 显示当前点击的菜单项
                    const statusText = document.getElementById('statusText');
                    if (statusText) {
                        statusText.textContent = `${item.label}`;
                    }
                });
                parentElement.appendChild(itemDiv);
            });
        }
        // ----- 初始化渲染 -----
        const menuRoot = document.getElementById('menuRoot');
        menuRoot.innerHTML = ''; 
        renderMenu(menuData, menuRoot, 0);
        // ----- 工具函数:获取所有可折叠容器 (children-wrapper) 和图标 -----
        function getAllTogglers() {
            const wrappers = Array.from(document.querySelectorAll('#menuRoot .children-wrapper'));
            const icons = Array.from(document.querySelectorAll('#menuRoot .expand-icon.expandable'));
            return { wrappers, icons };
        }
        // ----- 全部展开 -----
        document.getElementById('expandAllBtn').addEventListener('click', function() {
            const { wrappers, icons } = getAllTogglers();
            wrappers.forEach(w => w.classList.remove('collapsed'));
            icons.forEach(icon => icon.classList.add('rotate-icon'));
            // 更新状态 (不改变选中文本)
            const statusText = document.getElementById('statusText');
            if (statusText && !statusText.textContent.startsWith('全部')) {
                statusText.textContent = '🔽 全部展开';
            } else {
                statusText.textContent = '🔽 全部展开';
            }
        });
        // ----- 全部折叠 -----
        document.getElementById('collapseAllBtn').addEventListener('click', function() {
            const { wrappers, icons } = getAllTogglers();
            wrappers.forEach(w => w.classList.add('collapsed'));
            icons.forEach(icon => icon.classList.remove('rotate-icon'));
            const statusText = document.getElementById('statusText');
            statusText.textContent = '🔼 全部折叠';
        });
        // ----- 获取状态:读取所有菜单项的展开/折叠状态 -----
        document.getElementById('getStateBtn').addEventListener('click', function() {
            const menuItems = document.querySelectorAll('#menuRoot .menu-item');
            const stateLines = [];
            let idCounter = 0;
            // 递归遍历实际DOM来获取结构和状态 (与数据同步)
            function walkDOM(itemElement, depth = 0) {
                const row = itemElement.querySelector(':scope > .menu-row');
                const wrapper = itemElement.querySelector(':scope > .children-wrapper');
                if (!row) return;
                const label = row.querySelector('.menu-label')?.textContent || '未知';
                const hasChildren = wrapper && wrapper.querySelector('.menu-item');
                const isExpanded = wrapper ? !wrapper.classList.contains('collapsed') : false;
                // 缩进
                const indent = '  '.repeat(depth);
                const expandStatus = hasChildren ? (isExpanded ? '📂 展开' : '📁 折叠') : '▪️ 叶子';
                stateLines.push(`${indent}- ${label}  [${expandStatus}]`);
                // 递归子项
                if (hasChildren && wrapper) {
                    const childItems = wrapper.querySelectorAll(':scope > .menu-item');
                    childItems.forEach(child => walkDOM(child, depth + 1));
                }
            }
            const topItems = document.querySelectorAll('#menuRoot > .menu-item');
            topItems.forEach(item => walkDOM(item, 0));
            const statusText = document.getElementById('statusText');
            if (stateLines.length > 0) {
                // 显示概要 + 前几行
                const summary = `📋 共 ${stateLines.length} 项菜单状态`;
                // 展示前3行,并用...表示折叠(为了界面清爽)
                let detail = stateLines.slice(0, 4).join(' · ');
                if (stateLines.length > 4) detail += ' …';
                statusText.textContent = `${summary} : ${detail}`;
            } else {
                statusText.textContent = '📭 未检测到菜单项';
            }
        });
        // 点击空白处重置状态 (可选)
        document.addEventListener('click', function(e) {
            // 如果点击的不是菜单行或者按钮,可以选择不清空,但不做额外操作
            // 保持状态为上一次点击的菜单项
        });
        // 设置初始状态: 默认全折叠,但为了展示交互,我们展开第一个顶级菜单“文件”作为示例
        // 这样用户一打开就能看到交互效果
        (function setInitialDemoState() {
            const topItems = document.querySelectorAll('#menuRoot > .menu-item');
            if (topItems.length > 0) {
                // 展开第一个“文件”菜单及其直接子项 (仅演示)
                const firstItem = topItems[0];
                const row = firstItem.querySelector('.menu-row');
                if (row) {
                    // 模拟点击第一个文件菜单
                    row.click();  // 触发点击,展开文件,并更新状态
                }
                // 再展开文件下的“新建”子项 (手动操作)
                const fileChildrenWrapper = firstItem.querySelector('.children-wrapper');
                if (fileChildrenWrapper) {
                    const newItem = fileChildrenWrapper.querySelector('.menu-item');
                    if (newItem) {
                        const newRow = newItem.querySelector('.menu-row');
                        if (newRow) {
                            setTimeout(() => {
                                newRow.click();
                            }, 60);
                        }
                    }
                }
                // 状态文字改成 “文件”
                const statusText = document.getElementById('statusText');
                if (statusText) {
                    statusText.textContent = '📄 文件';
                }
            }
        })();
    })();
</script>
</body>
</html>

菜单交互与状态管理

这个脚本的核心是让您能像操作真实菜单一样,直观地查看和管理嵌套结构,您可以轻松展开/折叠分支,并随时获取当前菜单的完整状态。

交互式菜单脚本?

  • 展开与折叠:点击任意带有子菜单的菜单项(右侧带有“▶”图标和数量标记),即可展开或收起其子菜单,展开时图标会旋转,视觉反馈清晰。
  • 状态查看与操作:页面底部的状态栏会实时显示您最后点击的菜单项,您还可以使用“全部展开”和“全部折叠”按钮快速控制整体视图,或通过“获取状态”按钮查看当前所有菜单项的展开/折叠情况。
  • 数据驱动渲染由 JavaScript 中的 menuData 数组定义,您可以直接修改此数据结构来增删或调整菜单项,页面会自动重新渲染,便于测试和扩展。

抱歉,评论功能暂时关闭!