feat(layout): 重构布局组件体系与数据层

- 重构 Header 导航、Footer 页脚、MobileMenu 移动端菜单
- 新增 MobileTabBar 移动端底部导航栏
- 更新 MegaDropdown 大型下拉导航
- 重构 SEO 结构化数据组件
- 更新数据层常量 (products, services, solutions, navigation 等)
- 新增 useCountUp 数字递增 Hook
- 修复 .gitignore 中 src/lib 被错误忽略的问题
This commit is contained in:
张翔
2026-07-07 06:53:13 +08:00
parent e78df62cd1
commit 767931202d
28 changed files with 898 additions and 468 deletions
+63
View File
@@ -0,0 +1,63 @@
'use client';
import { useState, useEffect, useRef } from 'react';
interface UseCountUpOptions {
end: number;
duration?: number;
start?: number;
decimals?: number;
easing?: (t: number) => number;
enabled?: boolean;
}
const easeOutCubic = (t: number) => 1 - Math.pow(1 - t, 3);
export function useCountUp({
end,
duration = 1800,
start = 0,
decimals = 0,
easing = easeOutCubic,
enabled = true,
}: UseCountUpOptions) {
const [value, setValue] = useState(start);
const startTimeRef = useRef<number | null>(null);
const frameRef = useRef<number>(0);
useEffect(() => {
if (!enabled) {
setValue(end);
return;
}
setValue(start);
startTimeRef.current = null;
const animate = (now: number) => {
if (startTimeRef.current === null) {
startTimeRef.current = now;
}
const progress = Math.min((now - startTimeRef.current) / duration, 1);
const eased = easing(progress);
const current = start + (end - start) * eased;
setValue(Number(current.toFixed(decimals)));
if (progress < 1) {
frameRef.current = requestAnimationFrame(animate);
} else {
setValue(end);
}
};
frameRef.current = requestAnimationFrame(animate);
return () => {
cancelAnimationFrame(frameRef.current);
};
}, [end, duration, start, decimals, easing, enabled]);
return value;
}
+1 -1
View File
@@ -49,7 +49,7 @@ export function useMouseGlow(options: UseMouseGlowOptions = {}) {
pointerEvents: 'none',
transition: 'opacity 0.5s ease',
opacity: glow.isHovered ? 1 : 0,
background: `radial-gradient(${radius}px circle at ${glow.x}px ${glow.y}px, rgba(var(--color-primary-rgb), ${opacity}), transparent 40%)`,
background: `radial-gradient(${radius}px circle at ${glow.x}px ${glow.y}px, rgba(var(--color-brand-rgb), ${opacity}), transparent 40%)`,
};
return {
+4 -4
View File
@@ -200,7 +200,7 @@ export function SwipeNavigation({
className={cn(
'fixed bottom-24 left-1/2 -translate-x-1/2 z-50',
'px-4 py-2 rounded-full shadow-lg backdrop-blur-md',
'bg-[var(--color-primary)] text-white text-sm font-medium',
'bg-[var(--color-brand)] text-white text-sm font-medium',
'flex items-center gap-2 pointer-events-none'
)}
>
@@ -234,13 +234,13 @@ export function SwipeNavigation({
</p>
<div className="flex items-center justify-center gap-4 mt-2">
{prevRoute && (
<div className="flex items-center gap-1 text-[var(--color-brand-primary)]">
<div className="flex items-center gap-1 text-[var(--color-brand)]">
<ArrowLeft className="w-3 h-3" />
<span className="text-xs">{prevLabel}</span>
</div>
)}
{nextRoute && (
<div className="flex items-center gap-1 text-[var(--color-brand-primary)]">
<div className="flex items-center gap-1 text-[var(--color-brand)]">
<span className="text-xs">{nextLabel}</span>
<ArrowRight className="w-3 h-3" />
</div>
@@ -338,7 +338,7 @@ export function PullToRefresh({ onRefresh, children, className }: PullToRefreshP
className={cn(
'w-6 h-6 transition-colors',
pullDistance > 60
? 'text-[var(--color-brand-primary)]'
? 'text-[var(--color-brand)]'
: 'text-[var(--color-text-subtle)]'
)}
fill="none"