三级导航菜单系统实现
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
yiqiu
2026-01-10 16:45:20 +08:00
parent e72fbaf98a
commit b90b24a0d7
6 changed files with 617 additions and 145 deletions

View File

@@ -130,8 +130,70 @@ class ThemeConfigModel
'keywords' => '云服务器,主题云,BlackFruit',
'description' => 'BlackFruit-UI 默认站点说明',
],
// 顶部/底部导航
'header_nav' => [],
// 顶部/底部导航(第一个默认为产品中心,支持三级菜单)
'header_nav' => [
[
'name' => '产品中心',
'file_address' => '',
'blank' => false,
'children' => [
[
'name' => 'SAS轻量云服务器',
'file_address' => '/cloud.html',
'icon' => '/web/BlackFruit-web/assets/img/index/cloud-icon.png',
'description' => '高性能SSD云服务器',
'blank' => false,
'children' => [
[
'name' => '香港SAS',
'file_address' => '/cloud.html?region=hk',
'description' => '香港数据中心',
'blank' => false
],
[
'name' => '美国SAS',
'file_address' => '/cloud.html?region=us',
'description' => '美国数据中心',
'blank' => false
],
[
'name' => '日本SAS',
'file_address' => '/cloud.html?region=jp',
'description' => '日本数据中心',
'blank' => false
]
]
],
[
'name' => 'ECS精品云服务器',
'file_address' => '/dedicated.html',
'icon' => '/web/BlackFruit-web/assets/img/index/server-icon.png',
'description' => '企业级云服务器',
'blank' => false,
'children' => [
[
'name' => '宁波ECS',
'file_address' => '/dedicated.html?region=nb',
'description' => '宁波数据中心',
'blank' => false
],
[
'name' => '镇江ECS',
'file_address' => '/dedicated.html?region=zj',
'description' => '镇江数据中心',
'blank' => false
],
[
'name' => '北京ECS',
'file_address' => '/dedicated.html?region=bj',
'description' => '北京数据中心',
'blank' => false
]
]
]
]
]
],
'footer_nav' => [],
// 站点基础信息
'site_config' => [

View File

@@ -718,25 +718,109 @@
container.innerHTML = '';
children.forEach((child, childIndex) => {
const hasGrandChildren = Array.isArray(child.children) && child.children.length > 0;
const item = document.createElement('div');
item.style.cssText = 'padding:8px; margin-bottom:8px; background:#f9f9f9; border-radius:4px;';
item.style.cssText = 'padding:12px; margin-bottom:12px; background:#f9f9f9; border-radius:4px; border-left:3px solid #1890ff;';
item.innerHTML = `
<div style="display:flex; justify-content:space-between; align-items:start;">
<div style="flex:1; display:grid; gap:8px;">
<input type="text" class="form-control" data-hnav-child="${navIndex}.${childIndex}" data-field="name" value="${child.name || ''}" placeholder="子菜单名称">
<input type="text" class="form-control" data-hnav-child="${navIndex}.${childIndex}" data-field="file_address" value="${child.file_address || ''}" placeholder="链接地址">
<input type="text" class="form-control" data-hnav-child="${navIndex}.${childIndex}" data-field="icon" value="${child.icon || ''}" placeholder="图标URL(可选)">
<input type="text" class="form-control" data-hnav-child="${navIndex}.${childIndex}" data-field="description" value="${child.description || ''}" placeholder="描述(可选)">
<label style="font-size:12px;"><input type="checkbox" data-hnav-child="${navIndex}.${childIndex}" data-field="blank" ${child.blank ? 'checked' : ''}> 新窗口打开</label>
<div style="margin-bottom:8px; display:flex; justify-content:space-between; align-items:center;">
<strong style="color:#1890ff;">二级菜单 ${childIndex + 1}</strong>
<div style="display:flex; gap:4px;">
<button class="btn btn-info btn-sm" onclick="toggleThirdLevel(${navIndex}, ${childIndex})" style="font-size:11px;">
${hasGrandChildren ? '收起三级' : '展开三级'}
</button>
<button class="btn-icon btn-icon-danger btn-sm" onclick="removeHeaderNavChild(${navIndex}, ${childIndex})">×</button>
</div>
<button class="btn-icon btn-icon-danger" onclick="removeHeaderNavChild(${navIndex}, ${childIndex})" style="margin-left:8px;">×</button>
</div>
<div style="display:grid; gap:8px;">
<input type="text" class="form-control" data-hnav-child="${navIndex}.${childIndex}" data-field="name" value="${child.name || ''}" placeholder="二级菜单名称SAS轻量云服务器">
<input type="text" class="form-control" data-hnav-child="${navIndex}.${childIndex}" data-field="file_address" value="${child.file_address || ''}" placeholder="链接地址(可选)">
<input type="text" class="form-control" data-hnav-child="${navIndex}.${childIndex}" data-field="icon" value="${child.icon || ''}" placeholder="图标URL可选">
<input type="text" class="form-control" data-hnav-child="${navIndex}.${childIndex}" data-field="description" value="${child.description || ''}" placeholder="描述文字高性能SSD云服务器">
<label style="font-size:12px;"><input type="checkbox" data-hnav-child="${navIndex}.${childIndex}" data-field="blank" ${child.blank ? 'checked' : ''}> 新窗口打开</label>
</div>
<div id="third-level-${navIndex}-${childIndex}" style="display:${hasGrandChildren ? 'block' : 'none'}; margin-top:12px; padding:12px; background:#fff; border-radius:4px; border:1px dashed #d9d9d9;">
<h6 style="margin:0 0 8px; font-size:12px; color:#666;">三级菜单</h6>
<div id="third-level-list-${navIndex}-${childIndex}"></div>
<button class="btn btn-secondary btn-sm" onclick="addThirdLevelItem(${navIndex}, ${childIndex})" style="margin-top:8px; font-size:11px;">+ 添加三级菜单</button>
</div>
`;
container.appendChild(item);
// 渲染三级菜单
if (hasGrandChildren) {
renderThirdLevel(navIndex, childIndex, child.children);
}
});
}
// 渲染三级菜单
function renderThirdLevel(navIndex, childIndex, grandChildren) {
const container = document.getElementById(`third-level-list-${navIndex}-${childIndex}`);
if (!container) return;
container.innerHTML = '';
grandChildren.forEach((grandChild, grandIndex) => {
const item = document.createElement('div');
item.style.cssText = 'padding:8px; margin-bottom:6px; background:#fafafa; border-radius:3px; border-left:2px solid #52c41a;';
item.innerHTML = `
<div style="display:flex; gap:6px; align-items:start;">
<div style="flex:1; display:grid; gap:6px;">
<input type="text" class="form-control form-control-sm" data-hnav-grandchild="${navIndex}.${childIndex}.${grandIndex}" data-field="name" value="${grandChild.name || ''}" placeholder="三级菜单名称香港SAS" style="font-size:12px;">
<input type="text" class="form-control form-control-sm" data-hnav-grandchild="${navIndex}.${childIndex}.${grandIndex}" data-field="file_address" value="${grandChild.file_address || ''}" placeholder="链接地址" style="font-size:12px;">
<input type="text" class="form-control form-control-sm" data-hnav-grandchild="${navIndex}.${childIndex}.${grandIndex}" data-field="description" value="${grandChild.description || ''}" placeholder="描述(可选)" style="font-size:12px;">
<label style="font-size:11px;"><input type="checkbox" data-hnav-grandchild="${navIndex}.${childIndex}.${grandIndex}" data-field="blank" ${grandChild.blank ? 'checked' : ''}> 新窗口</label>
</div>
<button class="btn-icon btn-icon-danger btn-sm" onclick="removeThirdLevelItem(${navIndex}, ${childIndex}, ${grandIndex})" style="font-size:11px;">×</button>
</div>
`;
container.appendChild(item);
});
}
// 切换三级菜单显示
window.toggleThirdLevel = function (navIndex, childIndex) {
const container = document.getElementById(`third-level-${navIndex}-${childIndex}`);
const btn = event.target;
if (container) {
const isHidden = container.style.display === 'none';
container.style.display = isHidden ? 'block' : 'none';
btn.textContent = isHidden ? '收起三级' : '展开三级';
}
};
// 添加三级菜单项
window.addThirdLevelItem = function (navIndex, childIndex) {
const navs = collectHeaderNav();
if (!navs[navIndex]) return;
if (!navs[navIndex].children[childIndex]) return;
if (!navs[navIndex].children[childIndex].children) {
navs[navIndex].children[childIndex].children = [];
}
navs[navIndex].children[childIndex].children.push({
name: '',
file_address: '',
description: '',
blank: false
});
renderHeaderNav(navs);
// 确保展开
document.getElementById(`third-level-${navIndex}-${childIndex}`).style.display = 'block';
};
// 删除三级菜单项
window.removeThirdLevelItem = function (navIndex, childIndex, grandIndex) {
const navs = collectHeaderNav();
if (navs[navIndex] && navs[navIndex].children[childIndex] && navs[navIndex].children[childIndex].children) {
navs[navIndex].children[childIndex].children.splice(grandIndex, 1);
renderHeaderNav(navs);
}
};
function collectHeaderNav() {
const navs = [];
// 收集主导航
@@ -754,7 +838,7 @@
if (!navs[navIndex]) navs[navIndex] = { children: [] };
if (!navs[navIndex].children[childIndex]) {
navs[navIndex].children[childIndex] = {};
navs[navIndex].children[childIndex] = { children: [] };
}
if (input.type === 'checkbox') {
@@ -764,6 +848,45 @@
}
});
// 收集三级菜单
document.querySelectorAll('[data-hnav-grandchild]').forEach(input => {
const [navIndex, childIndex, grandIndex] = input.dataset.hnavGrandchild.split('.').map(Number);
const field = input.dataset.field;
if (!navs[navIndex]) navs[navIndex] = { children: [] };
if (!navs[navIndex].children[childIndex]) {
navs[navIndex].children[childIndex] = { children: [] };
}
if (!navs[navIndex].children[childIndex].children) {
navs[navIndex].children[childIndex].children = [];
}
if (!navs[navIndex].children[childIndex].children[grandIndex]) {
navs[navIndex].children[childIndex].children[grandIndex] = {};
}
if (input.type === 'checkbox') {
navs[navIndex].children[childIndex].children[grandIndex][field] = input.checked;
} else {
navs[navIndex].children[childIndex].children[grandIndex][field] = input.value;
}
});
// 清理空的children数组
navs.forEach(nav => {
if (nav && nav.children) {
nav.children = nav.children.filter(child => child);
nav.children.forEach(child => {
if (child && child.children) {
child.children = child.children.filter(gc => gc);
// 如果三级菜单为空删除children属性
if (child.children.length === 0) {
delete child.children;
}
}
});
}
});
return navs.filter(n => n);
}