<!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数组定义,您可以直接修改此数据结构来增删或调整菜单项,页面会自动重新渲染,便于测试和扩展。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播