feat(layout): 重构布局组件体系与数据层
- 重构 Header 导航、Footer 页脚、MobileMenu 移动端菜单 - 新增 MobileTabBar 移动端底部导航栏 - 更新 MegaDropdown 大型下拉导航 - 重构 SEO 结构化数据组件 - 更新数据层常量 (products, services, solutions, navigation 等) - 新增 useCountUp 数字递增 Hook - 修复 .gitignore 中 src/lib 被错误忽略的问题
This commit is contained in:
@@ -10,12 +10,13 @@ interface MegaDropdownProps {
|
||||
label: string;
|
||||
groups: MegaDropdownGroup[];
|
||||
isOpen: boolean;
|
||||
isActive?: boolean;
|
||||
onToggle: () => void;
|
||||
onOpen: () => void;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export function MegaDropdown({ label, groups, isOpen, onToggle, onOpen, onClose }: MegaDropdownProps) {
|
||||
export function MegaDropdown({ label, groups, isOpen, isActive, onToggle, onOpen, onClose }: MegaDropdownProps) {
|
||||
const dropdownRef = useRef<HTMLDivElement>(null);
|
||||
const timeoutRef = useRef<ReturnType<typeof setTimeout>>(undefined);
|
||||
|
||||
@@ -25,7 +26,7 @@ export function MegaDropdown({ label, groups, isOpen, onToggle, onOpen, onClose
|
||||
};
|
||||
|
||||
const handleMouseLeave = () => {
|
||||
timeoutRef.current = setTimeout(onClose, 150);
|
||||
timeoutRef.current = setTimeout(onClose, 300);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@@ -37,21 +38,38 @@ export function MegaDropdown({ label, groups, isOpen, onToggle, onOpen, onClose
|
||||
<div className="-mx-2 px-2 py-2">
|
||||
<button
|
||||
onClick={onToggle}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
onToggle();
|
||||
}
|
||||
}}
|
||||
className={`
|
||||
flex items-center gap-1 px-3 py-1.5 text-sm font-medium rounded-md
|
||||
transition-all duration-200
|
||||
${isOpen
|
||||
? 'text-[var(--color-brand-primary)] bg-[var(--color-brand-primary-bg)]'
|
||||
: 'text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)] hover:bg-[var(--color-bg-hover)]'
|
||||
relative flex items-center gap-1 px-3 py-2 text-sm font-medium
|
||||
transition-all duration-300 ease-out
|
||||
${isOpen || isActive
|
||||
? 'text-text-primary'
|
||||
: 'text-text-secondary hover:text-text-primary hover:bg-bg-secondary'
|
||||
}
|
||||
`}
|
||||
aria-expanded={isOpen}
|
||||
aria-haspopup="true"
|
||||
aria-current={isActive ? 'page' : undefined}
|
||||
>
|
||||
{label}
|
||||
<ChevronDown
|
||||
className={`w-4 h-4 transition-transform duration-200 ${isOpen ? 'rotate-180' : ''}`}
|
||||
/>
|
||||
<span
|
||||
className={`
|
||||
absolute -bottom-0.5 left-3 right-3 h-0.5 bg-brand
|
||||
transition-all duration-300 ease-out
|
||||
${isActive
|
||||
? 'opacity-100 scale-x-100'
|
||||
: 'opacity-0 scale-x-0'
|
||||
}
|
||||
`}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -64,16 +82,16 @@ export function MegaDropdown({ label, groups, isOpen, onToggle, onOpen, onClose
|
||||
transition={{ duration: 0.15 }}
|
||||
className="absolute top-full left-1/2 -translate-x-1/2 pt-2 w-[640px] z-50"
|
||||
>
|
||||
<div className="bg-[var(--color-bg-primary)] rounded-xl border border-[var(--color-border-primary)] shadow-lg p-6">
|
||||
<div className="bg-white backdrop-blur-xl rounded-none border border-border-primary p-6">
|
||||
{groups.map((group, groupIndex) => (
|
||||
<div key={group.id} className={groupIndex > 0 ? 'mt-6 pt-6 border-t border-[var(--color-border-primary)]' : ''}>
|
||||
<div key={group.id} className={groupIndex > 0 ? 'mt-6 pt-6 border-t border-border-primary' : ''}>
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<h3 className="text-sm font-bold text-[var(--color-text-primary)]">{group.title}</h3>
|
||||
<h3 className="text-sm font-bold text-text-primary">{group.title}</h3>
|
||||
{group.description && (
|
||||
<span className="text-xs text-[var(--color-text-muted)]">{group.description}</span>
|
||||
<span className="text-xs text-text-muted">{group.description}</span>
|
||||
)}
|
||||
{group.highlight && (
|
||||
<Sparkles className="w-3.5 h-3.5 text-[var(--color-brand-primary)]" />
|
||||
<Sparkles className="w-3.5 h-3.5 text-brand" />
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -82,28 +100,28 @@ export function MegaDropdown({ label, groups, isOpen, onToggle, onOpen, onClose
|
||||
<StaticLink
|
||||
key={item.id}
|
||||
href={item.href}
|
||||
className={`block p-3 rounded-lg border transition-all duration-200 group ${
|
||||
className={`block p-3 border transition-all duration-300 ease-out group ${
|
||||
item.href === '#'
|
||||
? 'border-[var(--color-border-primary)]/50 bg-[var(--color-bg-secondary)] cursor-not-allowed opacity-70'
|
||||
: 'border-transparent hover:border-[var(--color-brand-primary)]/10 hover:bg-[var(--color-brand-primary-bg)]'
|
||||
? 'border-border-primary bg-bg-secondary cursor-not-allowed opacity-50'
|
||||
: 'border-transparent hover:border-border-primary hover:bg-bg-secondary'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="text-sm font-semibold text-[var(--color-text-primary)]">{item.title}</div>
|
||||
<div className="text-sm font-semibold text-text-primary">{item.title}</div>
|
||||
{item.badge && (
|
||||
<span className={`shrink-0 rounded-full px-1.5 py-0.5 text-[10px] font-medium ${
|
||||
<span className={`shrink-0 px-1.5 py-0.5 text-[10px] font-medium ${
|
||||
item.badge === '敬请期待'
|
||||
? 'bg-gray-100 text-gray-500'
|
||||
: 'bg-[var(--color-brand-primary-bg)] text-[var(--color-brand-primary)]'
|
||||
? 'bg-bg-tertiary text-text-muted'
|
||||
: 'bg-brand/10 text-brand'
|
||||
}`}>
|
||||
{item.badge === '敬请期待' && <Lock className="w-2.5 h-2.5 inline mr-0.5" />}
|
||||
{item.badge}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="text-xs text-[var(--text-placeholder)] mt-1 leading-relaxed">{item.description}</div>
|
||||
<div className="text-xs text-text-secondary mt-1 leading-relaxed">{item.description}</div>
|
||||
</div>
|
||||
</div>
|
||||
</StaticLink>
|
||||
@@ -112,10 +130,10 @@ export function MegaDropdown({ label, groups, isOpen, onToggle, onOpen, onClose
|
||||
</div>
|
||||
))}
|
||||
|
||||
<div className="mt-4 pt-4 border-t border-[var(--color-border-primary)]">
|
||||
<div className="mt-4 pt-4 border-t border-border-primary">
|
||||
<StaticLink
|
||||
href={`/${label === '产品' ? 'products' : label === '解决方案' ? 'solutions' : 'services'}`}
|
||||
className="flex items-center justify-center gap-1 text-xs font-medium text-[var(--color-brand-primary)] hover:underline"
|
||||
className="flex items-center justify-center gap-1 text-xs font-medium text-text-muted hover:text-text-primary transition-colors"
|
||||
>
|
||||
查看全部{label}
|
||||
<ChevronDown className="w-3 h-3 rotate-[-90deg]" />
|
||||
|
||||
Reference in New Issue
Block a user