From 0cfefaa937f290825f53afd1a649d2743852c88a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E7=BF=94?= Date: Sat, 28 Feb 2026 13:09:07 +0800 Subject: [PATCH] =?UTF-8?q?refactor(ui):=20=E4=BC=98=E5=8C=96=E5=AF=BC?= =?UTF-8?q?=E8=88=AA=E7=BB=84=E4=BB=B6=E5=92=8C=E9=A1=B5=E9=9D=A2=E5=B8=83?= =?UTF-8?q?=E5=B1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 移除多个页面的面包屑导航组件 - 添加统一的返回按钮组件替代各页面独立实现 - 优化导航栏滚动检测逻辑和动画效果 - 更新常量类型定义和统计数据 - 调整动态导入的SSR配置为false - 添加FlipClock组件展示公司运营时长 - 优化新闻列表页的类型安全和响应式设计 --- ...2-27-website-comprehensive-optimization.md | 10 +- package.json | 4 +- src/app/(marketing)/about/client.tsx | 75 +++++---- src/app/(marketing)/cases/[id]/client.tsx | 17 +- src/app/(marketing)/cases/page.tsx | 2 - src/app/(marketing)/contact/page.tsx | 2 - .../news/[slug]/NewsDetailClient.tsx | 23 +-- src/app/(marketing)/news/page.tsx | 12 +- src/app/(marketing)/page.tsx | 12 +- src/app/(marketing)/products/page.tsx | 2 - src/app/(marketing)/services/[id]/client.tsx | 16 +- src/app/(marketing)/services/page.tsx | 2 - src/app/(marketing)/solutions/page.tsx | 2 - src/components/layout/header.tsx | 118 ++++++++++---- src/components/sections/news-section.tsx | 91 +++++------ src/components/ui/back-button.tsx | 3 +- src/components/ui/flip-clock.tsx | 149 ++++++++++++++++++ src/lib/constants.ts | 44 ++++-- 18 files changed, 389 insertions(+), 195 deletions(-) create mode 100644 src/components/ui/flip-clock.tsx diff --git a/docs/plans/2026-02-27-website-comprehensive-optimization.md b/docs/plans/2026-02-27-website-comprehensive-optimization.md index 64fba9d..122b895 100644 --- a/docs/plans/2026-02-27-website-comprehensive-optimization.md +++ b/docs/plans/2026-02-27-website-comprehensive-optimization.md @@ -23,7 +23,7 @@ - Modify: `src/app/(marketing)/products/page.tsx:1-20` - Modify: `src/app/(marketing)/solutions/page.tsx:1-20` - Modify: `src/app/(marketing)/contact/page.tsx:1-20` -- Modify: `src/app/news/page.tsx:1-20` +- Modify: `src/app/(marketing)/news/page.tsx:1-20` **Step 1: 创建面包屑导航组件** @@ -713,7 +713,7 @@ export const TouchButton = forwardRef< +
{caseItem.industry} diff --git a/src/app/(marketing)/cases/page.tsx b/src/app/(marketing)/cases/page.tsx index 29ed69f..edb688f 100644 --- a/src/app/(marketing)/cases/page.tsx +++ b/src/app/(marketing)/cases/page.tsx @@ -7,7 +7,6 @@ import { useRef } from 'react'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { PageHeader } from '@/components/ui/page-header'; -import { Breadcrumb } from '@/components/layout/breadcrumb'; import { ArrowLeft, ArrowRight, Building2, Calendar, TrendingUp } from 'lucide-react'; import { CASES } from '@/lib/constants'; @@ -17,7 +16,6 @@ export default function CasesPage() { return (
- - n.id !== news.id && n.category === news.category) @@ -26,18 +24,9 @@ export function NewsDetailClient({ news }: NewsDetailClientProps) { return (
- -
+
- +
{news.category} @@ -63,7 +52,7 @@ export function NewsDetailClient({ news }: NewsDetailClientProps) { className="max-w-4xl" >
-
+
📰
@@ -85,7 +74,7 @@ export function NewsDetailClient({ news }: NewsDetailClientProps) { {relatedNews.map((related) => (
-
+
📰
diff --git a/src/app/(marketing)/news/page.tsx b/src/app/(marketing)/news/page.tsx index 453a549..f20adc8 100644 --- a/src/app/(marketing)/news/page.tsx +++ b/src/app/(marketing)/news/page.tsx @@ -1,14 +1,13 @@ 'use client'; -import { useState, useMemo, useRef } from 'react'; +import { useState, useMemo, useRef, ChangeEvent } from 'react'; import { useInView } from 'framer-motion'; -import { NEWS } from '@/lib/constants'; +import { NEWS, NewsItem } from '@/lib/constants'; import { Card, CardContent } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; import { Input } from '@/components/ui/input'; import { Button } from '@/components/ui/button'; import { PageHeader } from '@/components/ui/page-header'; -import { Breadcrumb } from '@/components/layout/breadcrumb'; import { Search, Calendar, ArrowRight, ArrowLeft, Filter } from 'lucide-react'; import Link from 'next/link'; import { motion } from 'framer-motion'; @@ -33,7 +32,6 @@ export default function NewsListPage() { return (
- setSearchQuery(e.target.value)} + onChange={(e: ChangeEvent) => setSearchQuery(e.target.value)} className="pl-10" />
@@ -91,7 +89,7 @@ export default function NewsListPage() {
) : (
- {filteredNews.map((news, index) => ( + {filteredNews.map((news: NewsItem, index: number) => ( -
+
📰
diff --git a/src/app/(marketing)/page.tsx b/src/app/(marketing)/page.tsx index bbd1ec7..69526cd 100644 --- a/src/app/(marketing)/page.tsx +++ b/src/app/(marketing)/page.tsx @@ -8,7 +8,7 @@ const ServicesSection = dynamic( () => import('@/components/sections/services-section').then(mod => ({ default: mod.ServicesSection })), { loading: () => , - ssr: true + ssr: false } ); @@ -16,7 +16,7 @@ const ProductsSection = dynamic( () => import('@/components/sections/products-section').then(mod => ({ default: mod.ProductsSection })), { loading: () => , - ssr: true + ssr: false } ); @@ -24,7 +24,7 @@ const CasesSection = dynamic( () => import('@/components/sections/cases-section').then(mod => ({ default: mod.CasesSection })), { loading: () => , - ssr: true + ssr: false } ); @@ -32,7 +32,7 @@ const AboutSection = dynamic( () => import('@/components/sections/about-section').then(mod => ({ default: mod.AboutSection })), { loading: () => , - ssr: true + ssr: false } ); @@ -40,7 +40,7 @@ const NewsSection = dynamic( () => import('@/components/sections/news-section').then(mod => ({ default: mod.NewsSection })), { loading: () => , - ssr: true + ssr: false } ); @@ -48,7 +48,7 @@ const ContactSection = dynamic( () => import('@/components/sections/contact-section').then(mod => ({ default: mod.ContactSection })), { loading: () => , - ssr: true + ssr: false } ); diff --git a/src/app/(marketing)/products/page.tsx b/src/app/(marketing)/products/page.tsx index d65f06e..76e926a 100644 --- a/src/app/(marketing)/products/page.tsx +++ b/src/app/(marketing)/products/page.tsx @@ -8,7 +8,6 @@ import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card'; import { PageHeader } from '@/components/ui/page-header'; -import { Breadcrumb } from '@/components/layout/breadcrumb'; import { ArrowRight, ArrowLeft, Check, TrendingUp } from 'lucide-react'; import { PRODUCTS } from '@/lib/constants'; @@ -18,7 +17,6 @@ export default function ProductsPage() { return (
- (null); const serviceChallenges = challenges[service.id as keyof typeof challenges] ?? []; @@ -107,18 +104,9 @@ export function ServiceDetailClient({ service }: ServiceDetailClientProps) { return (
-
- +
diff --git a/src/app/(marketing)/services/page.tsx b/src/app/(marketing)/services/page.tsx index e5474c0..572fccc 100644 --- a/src/app/(marketing)/services/page.tsx +++ b/src/app/(marketing)/services/page.tsx @@ -8,7 +8,6 @@ import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { PageHeader } from '@/components/ui/page-header'; import { ServiceCardSkeleton } from '@/components/ui/loading-skeleton'; -import { Breadcrumb } from '@/components/layout/breadcrumb'; import { ArrowRight, ArrowLeft, Code, Cloud, BarChart3, Shield } from 'lucide-react'; import { SERVICES } from '@/lib/constants'; @@ -31,7 +30,6 @@ export default function ServicesPage() { return (
- - (isOpen); + const sectionCacheRef = useRef(new Map()); + const activeSectionRef = useRef(activeSection); + const isManualNavigationRef = useRef(false); + const manualNavTimeoutRef = useRef(null); useEffect(() => { + activeSectionRef.current = activeSection; + }, [activeSection]); + + useEffect(() => { + let ticking = false; + + const updateSectionCache = () => { + const sections = ['home', 'services', 'products', 'cases', 'about', 'news', 'contact']; + sections.forEach(sectionId => { + const element = document.getElementById(sectionId); + if (element) { + sectionCacheRef.current.set(sectionId, { + offsetTop: element.offsetTop, + offsetHeight: element.offsetHeight + }); + } + }); + }; + + updateSectionCache(); + const handleScroll = () => { - setIsScrolled(window.scrollY > 20); - - if (pathname === '/') { - const sections = ['home', 'services', 'products', 'cases', 'about', 'news', 'contact']; - const scrollPosition = window.scrollY + 100; - - for (const sectionId of sections) { - const element = document.getElementById(sectionId); - if (element) { - const { offsetTop, offsetHeight } = element; - if (scrollPosition >= offsetTop && scrollPosition < offsetTop + offsetHeight) { - setActiveSection(sectionId); - break; + if (!ticking) { + requestAnimationFrame(() => { + setIsScrolled(window.scrollY > 20); + + if (pathname === '/' && !isManualNavigationRef.current) { + const scrollPosition = window.scrollY + 100; + const sections = ['home', 'services', 'products', 'cases', 'about', 'news', 'contact']; + let currentSection = 'home'; + + for (const sectionId of sections) { + const cached = sectionCacheRef.current.get(sectionId); + if (cached && scrollPosition >= cached.offsetTop && scrollPosition < cached.offsetTop + cached.offsetHeight) { + currentSection = sectionId; + break; + } + } + + if (currentSection !== activeSectionRef.current) { + setActiveSection(currentSection); } } - } + ticking = false; + }); + ticking = true; } }; @@ -45,11 +78,16 @@ export function Header() { window.addEventListener('scroll', handleScroll, { passive: true }); window.addEventListener('keydown', handleGlobalKeyDown); + window.addEventListener('resize', updateSectionCache); handleScroll(); return () => { window.removeEventListener('scroll', handleScroll); window.removeEventListener('keydown', handleGlobalKeyDown); + window.removeEventListener('resize', updateSectionCache); + if (manualNavTimeoutRef.current) { + clearTimeout(manualNavTimeoutRef.current); + } }; }, [pathname, isOpen]); @@ -63,14 +101,32 @@ export function Header() { } }, [isOpen]); - const isActive = (item: typeof NAVIGATION[number]) => { + const handleNavClick = useCallback((item: NavigationItem) => { + if (pathname === '/' && item.href.startsWith('/#')) { + setActiveSection(item.id); + isManualNavigationRef.current = true; + + if (manualNavTimeoutRef.current) { + clearTimeout(manualNavTimeoutRef.current); + } + + manualNavTimeoutRef.current = setTimeout(() => { + isManualNavigationRef.current = false; + }, 800); + } + setIsOpen(false); + }, [pathname]); + + const isActive = useCallback((item: NavigationItem) => { if (pathname === '/') { return activeSection === item.id; } const navPath = item.href.split('#')[0]; return pathname === navPath || pathname.startsWith(navPath + '/'); - }; + }, [pathname, activeSection]); + + const navigationItems = useMemo(() => NAVIGATION, []); return ( <> @@ -98,10 +154,11 @@ export function Header() { @@ -172,7 +232,7 @@ export function Header() { aria-label="移动端导航" >