三级导航菜单系统实现
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

@@ -127,50 +127,76 @@ $(function () {
}; };
function initHeader() { function initHeader() {
let showIndex = 0; let showIndex = 0;
let hoverTimer = null;
let leaveTimer = null;
$(".nav-menu .nav-item").hover( $(".nav-menu .nav-item").hover(
function () { function () {
const index = $(".nav-menu .nav-item").index($(this)); const index = $(".nav-menu .nav-item").index($(this));
$(".nav-cont .nav-cont-menu")
.eq(index)
.attr("style", "display: block;");
// $('.nav-cont').attr('style','display: block;') // 清除离开定时器
if ( if (leaveTimer) {
!$(".nav-cont .nav-cont-menu").eq(index).hasClass("nav-cont-empty") clearTimeout(leaveTimer);
) { leaveTimer = null;
const height = $(".nav-cont .nav-cont-menu").eq(index).height();
$(".nav-cont").attr("style", `height: ${height}px;`);
} }
showIndex = index;
// 添加150ms延迟防止误触
hoverTimer = setTimeout(() => {
$(".nav-cont .nav-cont-menu")
.eq(index)
.css("display", "block");
if (
!$(".nav-cont .nav-cont-menu").eq(index).hasClass("nav-cont-empty")
) {
const height = $(".nav-cont .nav-cont-menu").eq(index).outerHeight(true);
$(".nav-cont").css("height", height + "px");
}
showIndex = index;
}, 150);
}, },
function () { function () {
const index = $(".nav-menu .nav-item").index($(this)); // 清除悬停定时器
$(".nav-cont ").eq(index).attr("style", "display: none;"); if (hoverTimer) {
$(".nav-cont .nav-cont-menu").eq(index).attr("style", "display: none;"); clearTimeout(hoverTimer);
$(".nav-cont").attr("style", "height:0"); hoverTimer = null;
}
leaveTimer = setTimeout(() => {
const index = $(".nav-menu .nav-item").index($(this));
$(".nav-cont .nav-cont-menu").eq(index).css("display", "none");
$(".nav-cont").css("height", "0");
}, 100);
} }
); );
$(".nav-cont").hover( $(".nav-cont").hover(
function () { function () {
//$('.nav-cont ').attr('style','display: block;') // 清除离开定时器
if (leaveTimer) {
clearTimeout(leaveTimer);
leaveTimer = null;
}
$(".nav-cont .nav-cont-menu") $(".nav-cont .nav-cont-menu")
.eq(showIndex) .eq(showIndex)
.attr("style", "display: block;"); .css("display", "block");
//if (showIndex != 0) {
if (!$(this).hasClass("nav-cont-empty")) { if (!$(".nav-cont .nav-cont-menu").eq(showIndex).hasClass("nav-cont-empty")) {
const height = $(".nav-cont .nav-cont-menu").eq(showIndex).height(); const height = $(".nav-cont .nav-cont-menu").eq(showIndex).outerHeight(true);
$(".nav-cont").attr("style", `height: ${height}px;`); $(".nav-cont").css("height", height + "px");
} }
}, },
function () { function () {
//$('.nav-cont ').attr('style','display: none;') leaveTimer = setTimeout(() => {
$(".nav-cont .nav-cont-menu") $(".nav-cont .nav-cont-menu")
.eq(showIndex) .eq(showIndex)
.attr("style", "display: none;"); .css("display", "none");
$(".nav-cont").attr("style", "height:0"); $(".nav-cont").css("height", "0");
}, 100);
} }
); );
if (localStorage.jwt) { if (localStorage.jwt) {
if (sessionStorage.accountInfo) { if (sessionStorage.accountInfo) {
const obj = JSON.parse(sessionStorage.accountInfo); const obj = JSON.parse(sessionStorage.accountInfo);

222
css/nav-mega-menu.css Normal file
View File

@@ -0,0 +1,222 @@
/* 三级导航菜单样式 - Mega Menu风格 */
/* 导航容器 */
.nav-cont {
position: absolute;
top: 100%;
left: 0;
width: 100%;
background: #fff;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
z-index: 1000;
overflow: hidden;
transition: height 0.3s ease-out;
}
/* Mega Menu 内容区 */
.nav-cont-menu.mega-menu {
display: none;
padding: 24px 0;
}
.nav-cont-menu.mega-menu .nav-content {
display: flex;
flex-wrap: wrap;
gap: 32px;
max-width: 1400px;
margin: 0 auto;
padding: 0 20px;
}
/* 二级菜单项(带三级菜单的分类) */
.nav-category {
flex: 0 0 auto;
min-width: 200px;
max-width: 280px;
}
.nav-category-header {
display: flex;
align-items: center;
gap: 12px;
padding: 8px 12px;
margin-bottom: 12px;
border-radius: 6px;
background: linear-gradient(135deg, #f0f7ff 0%, #e6f3ff 100%);
transition: all 0.2s ease;
}
.nav-category-header:hover {
background: linear-gradient(135deg, #e6f3ff 0%, #d9edff 100%);
transform: translateX(2px);
}
.nav-category-icon {
flex-shrink: 0;
width: 32px;
height: 32px;
border-radius: 6px;
overflow: hidden;
}
.nav-category-icon img {
width: 100%;
height: 100%;
object-fit: contain;
}
.nav-category-info {
flex: 1;
min-width: 0;
}
.nav-category-title {
font-size: 14px;
font-weight: 600;
color: #1890ff;
margin-bottom: 2px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.nav-category-desc {
font-size: 12px;
color: #8c8c8c;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* 三级菜单列表 */
.nav-third-level {
padding-left: 12px;
}
.nav-third-item {
margin-bottom: 8px;
}
.nav-third-link {
display: flex;
align-items: center;
gap: 8px;
padding: 6px 12px;
border-radius: 4px;
color: #333;
font-size: 13px;
text-decoration: none;
transition: all 0.2s ease;
position: relative;
}
.nav-third-link::before {
content: '';
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 3px;
height: 0;
background: #1890ff;
border-radius: 2px;
transition: height 0.2s ease;
}
.nav-third-link:hover {
background: #f5f5f5;
color: #1890ff;
padding-left: 16px;
}
.nav-third-link:hover::before {
height: 16px;
}
.nav-third-name {
flex: 1;
font-weight: 500;
}
.nav-third-desc {
font-size: 11px;
color: #999;
margin-left: auto;
}
/* 没有三级菜单的二级菜单项(旧版兼容) */
.nav-item-box {
display: flex;
align-items: flex-start;
gap: 12px;
padding: 12px 16px;
border-radius: 6px;
transition: all 0.2s ease;
text-decoration: none;
color: #333;
}
.nav-item-box:hover {
background: #f5f5f5;
transform: translateY(-2px);
}
.nav-item-box img {
width: 40px;
height: 40px;
flex-shrink: 0;
border-radius: 4px;
}
.item-box-title .title {
font-size: 14px;
font-weight: 600;
color: #333;
margin-bottom: 4px;
}
.item-box-title .desc {
font-size: 12px;
color: #8c8c8c;
}
/* 空菜单占位 */
.nav-cont-menu.nav-cont-empty {
display: none;
}
/* 响应式调整 */
@media (max-width: 768px) {
.nav-cont {
display: none !important;
}
.nav-cont-menu.mega-menu {
padding: 16px 0;
}
.nav-cont-menu.mega-menu .nav-content {
flex-direction: column;
gap: 16px;
}
.nav-category {
max-width: 100%;
}
}
/* 加载动画 */
@keyframes fadeInDown {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.nav-cont-menu.animated {
animation: fadeInDown 0.3s ease-out;
}

View File

@@ -2,112 +2,113 @@
<html lang="en" theme-color="default" theme-mode id="addons_js" addons_js='{:json_encode($addons)}'> <html lang="en" theme-color="default" theme-mode id="addons_js" addons_js='{:json_encode($addons)}'>
<head> <head>
{php} {php}
// 在渲染阶段从主题配置插件读取配置,并覆盖 $data 与 SEO 变量,实现 SSR // 在渲染阶段从主题配置插件读取配置,并覆盖 $data 与 SEO 变量,实现 SSR
if (class_exists('\\addon\\theme_configurator\\model\\ThemeConfigModel')) { if (class_exists('\\addon\\theme_configurator\\model\\ThemeConfigModel')) {
$cfgModel = new \addon\theme_configurator\model\ThemeConfigModel(); $cfgModel = new \addon\theme_configurator\model\ThemeConfigModel();
$themeCfg = $cfgModel->getConfig(); $themeCfg = $cfgModel->getConfig();
if (is_array($themeCfg)) { if (is_array($themeCfg)) {
$site = isset($themeCfg['site_config']) && is_array($themeCfg['site_config']) $site = isset($themeCfg['site_config']) && is_array($themeCfg['site_config'])
? $themeCfg['site_config'] ? $themeCfg['site_config']
: []; : [];
if (!isset($data) || !is_array($data)) { if (!isset($data) || !is_array($data)) {
$data = []; $data = [];
} }
// 覆盖 / 补充首页相关结构数据 // 覆盖 / 补充首页相关结构数据
if (isset($themeCfg['banner'])) { if (isset($themeCfg['banner'])) {
$data['banner'] = $themeCfg['banner']; $data['banner'] = $themeCfg['banner'];
} }
if (isset($themeCfg['honor'])) { if (isset($themeCfg['honor'])) {
$data['honor'] = $themeCfg['honor']; $data['honor'] = $themeCfg['honor'];
} }
if (isset($themeCfg['friendly_link'])) { if (isset($themeCfg['friendly_link'])) {
$data['friendly_link'] = $themeCfg['friendly_link']; $data['friendly_link'] = $themeCfg['friendly_link'];
} }
if (isset($themeCfg['header_nav'])) { if (isset($themeCfg['header_nav'])) {
$data['header_nav'] = $themeCfg['header_nav']; $data['header_nav'] = $themeCfg['header_nav'];
} }
if (isset($themeCfg['footer_nav'])) { if (isset($themeCfg['footer_nav'])) {
$data['footer_nav'] = $themeCfg['footer_nav']; $data['footer_nav'] = $themeCfg['footer_nav'];
} }
if (isset($themeCfg['side'])) { if (isset($themeCfg['side'])) {
$data['side_floating_window'] = $themeCfg['side']; $data['side_floating_window'] = $themeCfg['side'];
} }
// 站点基础信息,供 header/footer 与其他模板直接使用 // 站点基础信息,供 header/footer 与其他模板直接使用
$data['enterprise_name'] = $site['enterprise_name'] ?? ($data['enterprise_name'] ?? ''); $data['enterprise_name'] = $site['enterprise_name'] ?? ($data['enterprise_name'] ?? '');
$data['enterprise_telephone'] = $site['enterprise_telephone'] ?? ($data['enterprise_telephone'] ?? ''); $data['enterprise_telephone'] = $site['enterprise_telephone'] ?? ($data['enterprise_telephone'] ?? '');
$data['enterprise_mailbox'] = $site['enterprise_mailbox'] ?? ($data['enterprise_mailbox'] ?? ''); $data['enterprise_mailbox'] = $site['enterprise_mailbox'] ?? ($data['enterprise_mailbox'] ?? '');
$data['enterprise_qrcode'] = $site['enterprise_qrcode'] ?? ($data['enterprise_qrcode'] ?? ''); $data['enterprise_qrcode'] = $site['enterprise_qrcode'] ?? ($data['enterprise_qrcode'] ?? '');
$data['official_website_logo']= $site['official_website_logo']?? ($data['official_website_logo']?? ''); $data['official_website_logo']= $site['official_website_logo']?? ($data['official_website_logo']?? '');
$data['online_customer_service_link'] = $data['online_customer_service_link'] =
$site['online_customer_service_link'] ?? ($data['online_customer_service_link'] ?? ''); $site['online_customer_service_link'] ?? ($data['online_customer_service_link'] ?? '');
$data['icp_info'] = $site['icp_info'] ?? ($data['icp_info'] ?? ''); $data['icp_info'] = $site['icp_info'] ?? ($data['icp_info'] ?? '');
$data['icp_info_link'] = $site['icp_info_link'] ?? ($data['icp_info_link'] ?? ''); $data['icp_info_link'] = $site['icp_info_link'] ?? ($data['icp_info_link'] ?? '');
$data['public_security_network_preparation'] = $data['public_security_network_preparation'] =
$site['public_security_network_preparation'] ?? $site['public_security_network_preparation'] ??
($data['public_security_network_preparation'] ?? ''); ($data['public_security_network_preparation'] ?? '');
$data['public_security_network_preparation_link'] = $data['public_security_network_preparation_link'] =
$site['public_security_network_preparation_link'] ?? $site['public_security_network_preparation_link'] ??
($data['public_security_network_preparation_link'] ?? ''); ($data['public_security_network_preparation_link'] ?? '');
$data['telecom_appreciation'] = $site['telecom_appreciation'] ?? ($data['telecom_appreciation'] ?? ''); $data['telecom_appreciation'] = $site['telecom_appreciation'] ?? ($data['telecom_appreciation'] ?? '');
$data['copyright_info'] = $site['copyright_info'] ?? ($data['copyright_info'] ?? ''); $data['copyright_info'] = $site['copyright_info'] ?? ($data['copyright_info'] ?? '');
$data['terms_service_url'] = $site['terms_service_url'] ?? ($data['terms_service_url'] ?? ''); $data['terms_service_url'] = $site['terms_service_url'] ?? ($data['terms_service_url'] ?? '');
$data['terms_privacy_url'] = $site['terms_privacy_url'] ?? ($data['terms_privacy_url'] ?? ''); $data['terms_privacy_url'] = $site['terms_privacy_url'] ?? ($data['terms_privacy_url'] ?? '');
$data['cloud_product_link'] = $site['cloud_product_link'] ?? ($data['cloud_product_link'] ?? ''); $data['cloud_product_link'] = $site['cloud_product_link'] ?? ($data['cloud_product_link'] ?? '');
$data['dcim_product_link'] = $site['dcim_product_link'] ?? ($data['dcim_product_link'] ?? ''); $data['dcim_product_link'] = $site['dcim_product_link'] ?? ($data['dcim_product_link'] ?? '');
// SEO如插件配置了 SEO则覆盖控制器传入的标题/关键词/描述 // SEO如插件配置了 SEO则覆盖控制器传入的标题/关键词/描述
if (!empty($themeCfg['seo']['title'])) { if (!empty($themeCfg['seo']['title'])) {
$title = $themeCfg['seo']['title']; $title = $themeCfg['seo']['title'];
} }
if (!empty($themeCfg['seo']['keywords'])) { if (!empty($themeCfg['seo']['keywords'])) {
$keywords = $themeCfg['seo']['keywords']; $keywords = $themeCfg['seo']['keywords'];
} }
if (!empty($themeCfg['seo']['description'])) { if (!empty($themeCfg['seo']['description'])) {
$description = $themeCfg['seo']['description']; $description = $themeCfg['seo']['description'];
} }
} }
} }
{/php} {/php}
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>{$title}</title> <title>{$title}</title>
<meta name="keywords" content="{$keywords}" /> <meta name="keywords" content="{$keywords}" />
<meta name="description" content="{$description}" /> <meta name="description" content="{$description}" />
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="Content-Language" content="zh-cn"> <meta http-equiv="Content-Language" content="zh-cn">
<!-- 公共区域 --> <!-- 公共区域 -->
<link rel="icon" type="image/x-icon" href="/favicon.ico"> <link rel="icon" type="image/x-icon" href="/favicon.ico">
<link rel="stylesheet" href="/web/BlackFruit-web/assets/font/iconfont.css"> <link rel="stylesheet" href="/web/BlackFruit-web/assets/font/iconfont.css">
<link rel="stylesheet" href="/web/BlackFruit-web/common/reset.css"> <link rel="stylesheet" href="/web/BlackFruit-web/common/reset.css">
<link rel="stylesheet" href="/web/BlackFruit-web/common/style.css"> <link rel="stylesheet" href="/web/BlackFruit-web/common/style.css">
<link rel="stylesheet" href="/web/BlackFruit-web/common/theme.css"> <link rel="stylesheet" href="/web/BlackFruit-web/common/theme.css">
<link rel="stylesheet" href="/web/BlackFruit-web/common/common.css"> <link rel="stylesheet" href="/web/BlackFruit-web/common/common.css">
<!-- Modular header/footer/nav styles --> <!-- Modular header/footer/nav styles -->
<link rel="stylesheet" href="/web/BlackFruit-web/css/common.css"> <link rel="stylesheet" href="/web/BlackFruit-web/css/common.css">
<link rel="stylesheet" href="/web/BlackFruit-web/vender/animate/animate.css"> <link rel="stylesheet" href="/web/BlackFruit-web/css/nav-mega-menu.css">
<script src="/web/BlackFruit-web/vender/jQuery/jquery-3.5.1.min.js"></script> <link rel="stylesheet" href="/web/BlackFruit-web/vender/animate/animate.css">
<script src="/web/BlackFruit-web/vender/jQuery/jquery-3.5.1.min.js"></script>
<!-- bootstrap --> <!-- bootstrap -->
<link rel="stylesheet" href="/web/BlackFruit-web/vender/bootstrap/css/bootstrap.min.css"> <link rel="stylesheet" href="/web/BlackFruit-web/vender/bootstrap/css/bootstrap.min.css">
<script src="/web/BlackFruit-web/vender/bootstrap/js/bootstrap.min.js"></script> <script src="/web/BlackFruit-web/vender/bootstrap/js/bootstrap.min.js"></script>
<!-- swiper --> <!-- swiper -->
<link rel="stylesheet" href="/web/BlackFruit-web/vender/swiper/swiper-bundle.min.css"> <link rel="stylesheet" href="/web/BlackFruit-web/vender/swiper/swiper-bundle.min.css">
<script src="/web/BlackFruit-web/vender/swiper/swiper-bundle.min.js"></script> <script src="/web/BlackFruit-web/vender/swiper/swiper-bundle.min.js"></script>
<script src="/web/BlackFruit-web/common/common.js"></script> <script src="/web/BlackFruit-web/common/common.js"></script>
<script src="/web/BlackFruit-web/js/ai.js"></script> <script src="/web/BlackFruit-web/js/ai.js"></script>
<link rel="alternate" hreflang="zh-Hans" href="{$url}"> <link rel="alternate" hreflang="zh-Hans" href="{$url}">
<link rel="canonical" href="{$url}"> <link rel="canonical" href="{$url}">
<script type="application/ld+json"> <script type="application/ld+json">
{ {
"@context": "https://ziyuan.baidu.com/contexts/cambrian.jsonld", "@context": "https://ziyuan.baidu.com/contexts/cambrian.jsonld",
"@id": "{$url}", "@id": "{$url}",
@@ -117,12 +118,12 @@
"upDate": "{$up_date}" "upDate": "{$up_date}"
} }
</script> </script>
<script> <script>
// 将后端注入的配置映射到前端变量,供旧脚本兼容使用,避免再次通过接口请求主题配置 // 将后端注入的配置映射到前端变量,供旧脚本兼容使用,避免再次通过接口请求主题配置
window.__themeCommon = {: json_encode(isset($data) ? $data : [])}; window.__themeCommon = {: json_encode(isset($data) ? $data : [])};
try { try {
if (!sessionStorage.commentData) { if (!sessionStorage.commentData) {
sessionStorage.commentData = JSON.stringify(window.__themeCommon || {}); sessionStorage.commentData = JSON.stringify(window.__themeCommon || {});
} }
} catch (e) { } } catch (e) { }
</script> </script>

View File

@@ -130,8 +130,70 @@ class ThemeConfigModel
'keywords' => '云服务器,主题云,BlackFruit', 'keywords' => '云服务器,主题云,BlackFruit',
'description' => 'BlackFruit-UI 默认站点说明', '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' => [], 'footer_nav' => [],
// 站点基础信息 // 站点基础信息
'site_config' => [ 'site_config' => [

View File

@@ -718,25 +718,109 @@
container.innerHTML = ''; container.innerHTML = '';
children.forEach((child, childIndex) => { children.forEach((child, childIndex) => {
const hasGrandChildren = Array.isArray(child.children) && child.children.length > 0;
const item = document.createElement('div'); 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 = ` item.innerHTML = `
<div style="display:flex; justify-content:space-between; align-items:start;"> <div style="margin-bottom:8px; display:flex; justify-content:space-between; align-items:center;">
<div style="flex:1; display:grid; gap:8px;"> <strong style="color:#1890ff;">二级菜单 ${childIndex + 1}</strong>
<input type="text" class="form-control" data-hnav-child="${navIndex}.${childIndex}" data-field="name" value="${child.name || ''}" placeholder="子菜单名称"> <div style="display:flex; gap:4px;">
<input type="text" class="form-control" data-hnav-child="${navIndex}.${childIndex}" data-field="file_address" value="${child.file_address || ''}" placeholder="链接地址"> <button class="btn btn-info btn-sm" onclick="toggleThirdLevel(${navIndex}, ${childIndex})" style="font-size:11px;">
<input type="text" class="form-control" data-hnav-child="${navIndex}.${childIndex}" data-field="icon" value="${child.icon || ''}" placeholder="图标URL(可选)"> ${hasGrandChildren ? '收起三级' : '展开三级'}
<input type="text" class="form-control" data-hnav-child="${navIndex}.${childIndex}" data-field="description" value="${child.description || ''}" placeholder="描述(可选)"> </button>
<label style="font-size:12px;"><input type="checkbox" data-hnav-child="${navIndex}.${childIndex}" data-field="blank" ${child.blank ? 'checked' : ''}> 新窗口打开</label> <button class="btn-icon btn-icon-danger btn-sm" onclick="removeHeaderNavChild(${navIndex}, ${childIndex})">×</button>
</div> </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> </div>
`; `;
container.appendChild(item); 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() { function collectHeaderNav() {
const navs = []; const navs = [];
// 收集主导航 // 收集主导航
@@ -754,7 +838,7 @@
if (!navs[navIndex]) navs[navIndex] = { children: [] }; if (!navs[navIndex]) navs[navIndex] = { children: [] };
if (!navs[navIndex].children[childIndex]) { if (!navs[navIndex].children[childIndex]) {
navs[navIndex].children[childIndex] = {}; navs[navIndex].children[childIndex] = { children: [] };
} }
if (input.type === 'checkbox') { 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); return navs.filter(n => n);
} }

View File

@@ -64,21 +64,59 @@
{if isset($data.header_nav) && !empty($data.header_nav)} {if isset($data.header_nav) && !empty($data.header_nav)}
{foreach $data.header_nav as $k=>$item} {foreach $data.header_nav as $k=>$item}
{if isset($item.children) && !empty($item.children)} {if isset($item.children) && !empty($item.children)}
<div class="nav-cont-menu animated slideInDown"> <div class="nav-cont-menu mega-menu animated">
<div class="nav-content"> <div class="nav-content">
{foreach $item.children as $child} {foreach $item.children as $child}
{* 判断是否有三级菜单 *}
{if isset($child.children) && !empty($child.children)}
{* 有三级菜单的二级分类 *}
<div class="nav-category">
<a href="{if !empty($child.file_address)}{$child.file_address}{else/}javascript:;{/if}"
class="nav-category-header" {if !empty($child.blank)}target="_blank" {/if}>
{if !empty($child.icon)}
<div class="nav-category-icon">
<img src="{$child.icon}" alt="{$child.name}">
</div>
{/if}
<div class="nav-category-info">
<div class="nav-category-title">{$child.name}</div>
{if !empty($child.description)}
<div class="nav-category-desc">{$child.description}</div>
{/if}
</div>
</a>
{* 三级菜单列表 *}
<div class="nav-third-level">
{foreach $child.children as $grandchild}
<div class="nav-third-item">
<a href="{if !empty($grandchild.file_address)}{$grandchild.file_address}{else/}javascript:;{/if}"
class="nav-third-link" {if !empty($grandchild.blank)}target="_blank" {/if}>
<span class="nav-third-name">{$grandchild.name}</span>
{if !empty($grandchild.description)}
<span class="nav-third-desc">{$grandchild.description}</span>
{/if}
</a>
</div>
{/foreach}
</div>
</div>
{else /}
{* 没有三级菜单的二级项(旧版样式兼容) *}
<a href="{if !empty($child.file_address)}{$child.file_address}{else/}javascript:;{/if}" {if <a href="{if !empty($child.file_address)}{$child.file_address}{else/}javascript:;{/if}" {if
!empty($child.blank)}target="_blank" {/if}> !empty($child.blank)}target="_blank" {/if} style="text-decoration: none;">
<div class="nav-item-box"> <div class="nav-item-box">
{if !empty($child.icon)} {if !empty($child.icon)}
<img src="{$child.icon}" alt=""> <img src="{$child.icon}" alt="{$child.name}">
{/if} {/if}
<div class="item-box-title"> <div class="item-box-title">
<div class="title">{$child.name}</div> <div class="title">{$child.name}</div>
{if !empty($child.description)}
<div class="desc">{$child.description}</div> <div class="desc">{$child.description}</div>
{/if}
</div> </div>
</div> </div>
</a> </a>
{/if}
{/foreach} {/foreach}
</div> </div>
</div> </div>