feat(layout): 重构布局组件体系与数据层
- 重构 Header 导航、Footer 页脚、MobileMenu 移动端菜单 - 新增 MobileTabBar 移动端底部导航栏 - 更新 MegaDropdown 大型下拉导航 - 重构 SEO 结构化数据组件 - 更新数据层常量 (products, services, solutions, navigation 等) - 新增 useCountUp 数字递增 Hook - 修复 .gitignore 中 src/lib 被错误忽略的问题
This commit is contained in:
@@ -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;
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user