增加产品中心二级菜单
This commit is contained in:
Vendored
+1
-1
@@ -194,6 +194,6 @@ declare module 'astro:content' {
|
||||
LiveContentConfig['collections'][C]['loader']
|
||||
>;
|
||||
|
||||
export type ContentConfig = typeof import("./../src/content.config.mjs");
|
||||
export type ContentConfig = typeof import("../src/content.config.mjs");
|
||||
export type LiveContentConfig = never;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"_variables": {
|
||||
"lastUpdateCheck": 1774419633747
|
||||
"lastUpdateCheck": 1776058818761
|
||||
}
|
||||
}
|
||||
+230
-10
@@ -4,24 +4,54 @@ import 'font-awesome/css/font-awesome.min.css';
|
||||
|
||||
const { title = "浙江贝凡网络科技", activeNav = "home" } = Astro.props;
|
||||
|
||||
const navItems = [
|
||||
interface NavChild {
|
||||
id: string;
|
||||
label: string;
|
||||
href: string;
|
||||
icon: string;
|
||||
}
|
||||
|
||||
interface NavItem {
|
||||
id: string;
|
||||
label: string;
|
||||
href: string;
|
||||
icon: string;
|
||||
children?: NavChild[];
|
||||
}
|
||||
|
||||
const navItems: NavItem[] = [
|
||||
{ id: 'home', label: '公司介绍', href: '/', icon: 'fa-building' },
|
||||
{ id: 'qazk', label: '企安智控', href: '/qazk', icon: 'fa-cogs' },
|
||||
{
|
||||
id: 'products', label: '产品中心', href: '/qazk', icon: 'fa-cubes',
|
||||
children: [
|
||||
{ id: 'qazk', label: '企安智控', href: '/qazk', icon: 'fa-cogs' },
|
||||
{ id: 'elderly', label: '智慧养老', href: '/elderly', icon: 'fa-heartbeat' },
|
||||
{ id: 'construction', label: '智慧工地', href: '/construction', icon: 'fa-industry' },
|
||||
{ id: 'kitchen', label: '明厨亮灶', href: '/kitchen', icon: 'fa-cutlery' },
|
||||
{ id: 'education', label: '学校教育', href: '/education', icon: 'fa-graduation-cap' },
|
||||
]
|
||||
},
|
||||
{ id: 'customization', label: '本地化定制', href: '/customization', icon: 'fa-wrench' },
|
||||
{ id: 'partnership', label: '生态合作', href: '/partnership', icon: 'fa-handshake-o' },
|
||||
];
|
||||
|
||||
function getNavLinkClass(itemId: string) {
|
||||
function isActive(item: NavItem): boolean {
|
||||
if (item.id === activeNav) return true;
|
||||
if (item.children) return item.children.some(child => child.id === activeNav);
|
||||
return false;
|
||||
}
|
||||
|
||||
function getNavLinkClass(item: NavItem) {
|
||||
const baseClass = 'flex items-center gap-2 px-4 py-2.5 rounded-full font-medium transition-all duration-300';
|
||||
if (itemId === activeNav) {
|
||||
if (isActive(item)) {
|
||||
return `${baseClass} bg-primary text-white shadow-md`;
|
||||
}
|
||||
return `${baseClass} text-gray-600 hover:text-primary hover:bg-primary/10`;
|
||||
}
|
||||
|
||||
function getMobileNavLinkClass(itemId: string) {
|
||||
function getMobileNavLinkClass(item: NavItem) {
|
||||
const baseClass = 'flex flex-col items-center justify-center py-2 px-2 min-w-[4.5rem] rounded-xl transition-all duration-300';
|
||||
if (itemId === activeNav) {
|
||||
if (isActive(item)) {
|
||||
return `${baseClass} bg-primary text-white`;
|
||||
}
|
||||
return `${baseClass} text-gray-600 hover:bg-primary/10`;
|
||||
@@ -43,8 +73,33 @@ function getMobileNavLinkClass(itemId: string) {
|
||||
</a>
|
||||
<nav class="hidden lg:flex items-center gap-3">
|
||||
{
|
||||
navItems.map(item => (
|
||||
<a href={item.href} class={getNavLinkClass(item.id)}>
|
||||
navItems.map(item => item.children ? (
|
||||
<div class="relative" data-dropdown>
|
||||
<button type="button" class={`${getNavLinkClass(item)} cursor-pointer`}
|
||||
aria-haspopup="true" aria-expanded="false" data-dropdown-trigger>
|
||||
<i class={`fa ${item.icon}`}></i>
|
||||
<span>{item.label}</span>
|
||||
<i class="fa fa-angle-down text-xs ml-0.5 transition-transform duration-200" data-dropdown-chevron></i>
|
||||
</button>
|
||||
<div class="absolute top-full left-1/2 -translate-x-1/2 pt-2 opacity-0 invisible transition-all duration-200 z-50"
|
||||
data-dropdown-panel role="menu">
|
||||
<div class="bg-white rounded-xl shadow-xl border border-gray-100 py-2 min-w-[200px]">
|
||||
{item.children.map(child => (
|
||||
<a href={child.href} role="menuitem"
|
||||
class={`flex items-center gap-3 px-4 py-2.5 transition-colors ${
|
||||
child.id === activeNav
|
||||
? 'bg-primary/10 text-primary font-medium'
|
||||
: 'text-gray-700 hover:bg-primary/10 hover:text-primary'
|
||||
}`}>
|
||||
<i class={`fa ${child.icon} w-5 text-center`}></i>
|
||||
<span>{child.label}</span>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<a href={item.href} class={getNavLinkClass(item)}>
|
||||
<i class={`fa ${item.icon}`}></i>
|
||||
<span>{item.label}</span>
|
||||
</a>
|
||||
@@ -70,8 +125,16 @@ function getMobileNavLinkClass(itemId: string) {
|
||||
<div class="flex items-center justify-between px-2 py-2">
|
||||
<div class="flex items-center gap-1 overflow-x-auto no-scrollbar">
|
||||
{
|
||||
navItems.map(item => (
|
||||
<a href={item.href} class={getMobileNavLinkClass(item.id)}>
|
||||
navItems.map(item => item.children ? (
|
||||
<button type="button"
|
||||
class={getMobileNavLinkClass(item)}
|
||||
aria-haspopup="true" aria-expanded="false"
|
||||
data-mobile-dropdown-trigger>
|
||||
<i class={`fa ${item.icon} text-lg mb-1`}></i>
|
||||
<span class="text-xs font-medium whitespace-nowrap">{item.label}</span>
|
||||
</button>
|
||||
) : (
|
||||
<a href={item.href} class={getMobileNavLinkClass(item)}>
|
||||
<i class={`fa ${item.icon} text-lg mb-1`}></i>
|
||||
<span class="text-xs font-medium whitespace-nowrap">{item.label}</span>
|
||||
</a>
|
||||
@@ -84,6 +147,29 @@ function getMobileNavLinkClass(itemId: string) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Mobile Product Panel -->
|
||||
<div id="mobile-product-panel"
|
||||
class="lg:hidden fixed left-0 right-0 z-30 bg-white shadow-xl border-b border-gray-200 transform -translate-y-full opacity-0 pointer-events-none transition-all duration-300"
|
||||
style="top: 108px;" role="menu">
|
||||
<div class="max-w-md mx-auto px-4 py-4 grid grid-cols-3 gap-3">
|
||||
{navItems.find(item => item.children)?.children?.map(child => (
|
||||
<a href={child.href} role="menuitem"
|
||||
class={`flex flex-col items-center gap-1.5 py-3 px-2 rounded-xl transition-colors ${
|
||||
child.id === activeNav
|
||||
? 'bg-primary/10 text-primary'
|
||||
: 'hover:bg-primary/10'
|
||||
}`}>
|
||||
<i class={`fa ${child.icon} text-xl ${child.id === activeNav ? 'text-primary' : 'text-primary'}`}></i>
|
||||
<span class={`text-xs font-medium ${child.id === activeNav ? 'text-primary' : 'text-gray-700'}`}>{child.label}</span>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Mobile Product Backdrop -->
|
||||
<div id="mobile-product-backdrop"
|
||||
class="lg:hidden fixed inset-0 z-20 bg-black/20 opacity-0 pointer-events-none transition-opacity duration-300"></div>
|
||||
|
||||
<!-- Spacer for mobile to prevent content overlap (header 3.5rem + nav 2.5rem) -->
|
||||
<div class="lg:hidden h-[6rem] transition-all duration-300"></div>
|
||||
|
||||
@@ -148,6 +234,140 @@ function getMobileNavLinkClass(itemId: string) {
|
||||
lastScrollTop = scrollTop;
|
||||
}, { passive: true });
|
||||
})();
|
||||
|
||||
// Desktop dropdown hover + keyboard navigation
|
||||
(function() {
|
||||
var dropdown = document.querySelector('[data-dropdown]');
|
||||
if (!dropdown) return;
|
||||
|
||||
var trigger = dropdown.querySelector('[data-dropdown-trigger]') as HTMLElement;
|
||||
var panel = dropdown.querySelector('[data-dropdown-panel]') as HTMLElement;
|
||||
var chevron = dropdown.querySelector('[data-dropdown-chevron]') as HTMLElement;
|
||||
var menuItems = panel.querySelectorAll('[role="menuitem"]') as NodeListOf<HTMLElement>;
|
||||
var closeTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
function openPanel() {
|
||||
if (closeTimer) { clearTimeout(closeTimer); closeTimer = null; }
|
||||
trigger.setAttribute('aria-expanded', 'true');
|
||||
panel.classList.remove('opacity-0', 'invisible');
|
||||
panel.classList.add('opacity-100', 'visible');
|
||||
if (chevron) chevron.style.transform = 'rotate(180deg)';
|
||||
}
|
||||
|
||||
function closePanel() {
|
||||
trigger.setAttribute('aria-expanded', 'false');
|
||||
panel.classList.add('opacity-0', 'invisible');
|
||||
panel.classList.remove('opacity-100', 'visible');
|
||||
if (chevron) chevron.style.transform = '';
|
||||
}
|
||||
|
||||
function schedulClose() {
|
||||
closeTimer = setTimeout(closePanel, 150);
|
||||
}
|
||||
|
||||
// Click: toggle (touch-friendly)
|
||||
trigger.addEventListener('click', function() {
|
||||
if (trigger.getAttribute('aria-expanded') === 'true') {
|
||||
closePanel();
|
||||
} else {
|
||||
openPanel();
|
||||
}
|
||||
});
|
||||
|
||||
// Hover: mouse users
|
||||
trigger.addEventListener('mouseenter', openPanel);
|
||||
trigger.addEventListener('mouseleave', schedulClose);
|
||||
panel.addEventListener('mouseenter', openPanel);
|
||||
panel.addEventListener('mouseleave', schedulClose);
|
||||
|
||||
// Keyboard
|
||||
trigger.addEventListener('keydown', function(e: KeyboardEvent) {
|
||||
if (e.key === 'ArrowDown') {
|
||||
e.preventDefault();
|
||||
openPanel();
|
||||
menuItems[0]?.focus();
|
||||
}
|
||||
if (e.key === 'Escape') {
|
||||
closePanel();
|
||||
trigger.focus();
|
||||
}
|
||||
});
|
||||
|
||||
panel.addEventListener('keydown', function(e: KeyboardEvent) {
|
||||
var items = Array.from(menuItems);
|
||||
var idx = items.indexOf(document.activeElement as HTMLElement);
|
||||
if (e.key === 'ArrowDown') {
|
||||
e.preventDefault();
|
||||
items[(idx + 1) % items.length].focus();
|
||||
} else if (e.key === 'ArrowUp') {
|
||||
e.preventDefault();
|
||||
items[(idx - 1 + items.length) % items.length].focus();
|
||||
} else if (e.key === 'Escape') {
|
||||
closePanel();
|
||||
trigger.focus();
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('click', function(e) {
|
||||
if (!(dropdown as HTMLElement).contains(e.target as Node)) {
|
||||
closePanel();
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
||||
// Mobile product dropdown
|
||||
(function() {
|
||||
var trigger = document.querySelector('[data-mobile-dropdown-trigger]') as HTMLElement;
|
||||
var panel = document.getElementById('mobile-product-panel');
|
||||
var backdrop = document.getElementById('mobile-product-backdrop');
|
||||
if (!trigger || !panel) return;
|
||||
|
||||
var isOpen = false;
|
||||
|
||||
function updatePanelPosition() {
|
||||
var navContainer = document.getElementById('mobile-nav-container');
|
||||
if (navContainer && panel) {
|
||||
var rect = navContainer.getBoundingClientRect();
|
||||
panel.style.top = (rect.bottom) + 'px';
|
||||
}
|
||||
}
|
||||
|
||||
function openPanel() {
|
||||
isOpen = true;
|
||||
updatePanelPosition();
|
||||
trigger.setAttribute('aria-expanded', 'true');
|
||||
panel!.classList.remove('-translate-y-full', 'opacity-0', 'pointer-events-none');
|
||||
panel!.classList.add('translate-y-0', 'opacity-100', 'pointer-events-auto');
|
||||
if (backdrop) {
|
||||
backdrop.classList.remove('opacity-0', 'pointer-events-none');
|
||||
backdrop.classList.add('opacity-100', 'pointer-events-auto');
|
||||
}
|
||||
}
|
||||
|
||||
function closePanel() {
|
||||
isOpen = false;
|
||||
trigger.setAttribute('aria-expanded', 'false');
|
||||
panel!.classList.add('-translate-y-full', 'opacity-0', 'pointer-events-none');
|
||||
panel!.classList.remove('translate-y-0', 'opacity-100', 'pointer-events-auto');
|
||||
if (backdrop) {
|
||||
backdrop.classList.add('opacity-0', 'pointer-events-none');
|
||||
backdrop.classList.remove('opacity-100', 'pointer-events-auto');
|
||||
}
|
||||
}
|
||||
|
||||
trigger.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
isOpen ? closePanel() : openPanel();
|
||||
});
|
||||
|
||||
if (backdrop) {
|
||||
backdrop.addEventListener('click', closePanel);
|
||||
}
|
||||
|
||||
window.addEventListener('scroll', function() {
|
||||
if (isOpen && window.innerWidth < 1024) closePanel();
|
||||
}, { passive: true });
|
||||
})();
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
Reference in New Issue
Block a user