From 1bf22a8f95cb629d0cc6cd5e74d222d2bc8e69a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E7=BF=94?= Date: Sun, 3 May 2026 09:15:14 +0800 Subject: [PATCH] =?UTF-8?q?fix(seo):=20=E4=BF=AE=E5=A4=8D=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2=E6=A0=87=E9=A2=98=E5=85=AC=E5=8F=B8=E5=90=8D=E9=87=8D?= =?UTF-8?q?=E5=A4=8D=E4=B8=8E=E5=93=81=E7=89=8C=E5=90=8D=E7=B9=81=E7=AE=80?= =?UTF-8?q?=E4=BD=93=E4=B8=8D=E4=B8=80=E8=87=B4=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 COMPANY_INFO.displayName 属性用于页面标题和SEO元数据 - 统一所有页面 metadata 使用 displayName(简体"睿新致远") - 视觉展示元素保留 shortName(繁体"睿新致遠"配合青柳隷書字体) - 修复关于/联系/团队页面标题中公司名重复出现的问题 - 修复新闻ID从数字改为SEO友好slug - 更新结构化数据使用完整公司名 - 修复ESLint报错:引号转义、组件displayName、any类型替换 --- src/app/(marketing)/about/client.tsx | 8 ++-- src/app/(marketing)/about/page.test.tsx | 52 ++++++++++++--------- src/app/(marketing)/about/page.tsx | 2 +- src/app/(marketing)/contact/layout.tsx | 4 +- src/app/(marketing)/news/[slug]/page.tsx | 4 +- src/app/(marketing)/news/layout.tsx | 4 +- src/app/(marketing)/news/page.tsx | 4 +- src/app/(marketing)/products/[id]/page.tsx | 4 +- src/app/(marketing)/products/layout.tsx | 4 +- src/app/(marketing)/services/[id]/page.tsx | 4 +- src/app/(marketing)/services/layout.tsx | 4 +- src/app/(marketing)/solutions/[id]/page.tsx | 3 +- src/app/(marketing)/solutions/layout.tsx | 4 +- src/app/(marketing)/solutions/page.tsx | 5 +- src/app/(marketing)/team/page.tsx | 2 +- src/app/not-found.tsx | 3 +- src/app/privacy/page.tsx | 5 +- src/app/terms/page.tsx | 5 +- src/components/analytics/CookieConsent.tsx | 32 ++++++++++++- src/components/layout/footer.test.tsx | 2 + src/components/layout/header.test.tsx | 23 +++++---- src/components/layout/header.tsx | 10 ++-- src/components/layout/mega-dropdown.tsx | 43 ++++++++++++++++- src/components/sections/cta-section.tsx | 3 +- src/components/seo/structured-data.test.tsx | 5 +- src/components/seo/structured-data.tsx | 8 ++-- src/components/ui/ink-glow-card.tsx | 7 +-- src/lib/constants/company.ts | 1 + src/lib/constants/news.ts | 4 +- 29 files changed, 183 insertions(+), 76 deletions(-) diff --git a/src/app/(marketing)/about/client.tsx b/src/app/(marketing)/about/client.tsx index 369407b..1ed04c9 100644 --- a/src/app/(marketing)/about/client.tsx +++ b/src/app/(marketing)/about/client.tsx @@ -73,7 +73,7 @@ export function AboutClient() { transition={{ duration: 0.5 }} className="p-8 rounded-xl border border-[#E5E5E5]" > -

关于 睿新致遠

+

关于 {COMPANY_INFO.shortName}

智连未来,成长伙伴

企业需要的,不是一个高高在上的专家,也不是一个做完就跑的卖家,而是一个能坐下来、一起想办法的同行者。

@@ -86,10 +86,10 @@ export function AboutClient() {

成长伙伴

-

我们不把"项目交付"当作终点。

+

我们不把“项目交付”当作终点。

您的业务增长了吗?您的团队能力提升了吗?

您下一次遇到难题时,还会第一个想到我们吗?

-

这些问题,比"项目是否按时交付"更让我们在意。

+

这些问题,比“项目是否按时交付”更让我们在意。

@@ -113,7 +113,7 @@ export function AboutClient() {
  • - 不做路过就忘的"一锤子买卖" + 不做路过就忘的“一锤子买卖”
  • diff --git a/src/app/(marketing)/about/page.test.tsx b/src/app/(marketing)/about/page.test.tsx index 2d774b8..6b4752d 100644 --- a/src/app/(marketing)/about/page.test.tsx +++ b/src/app/(marketing)/about/page.test.tsx @@ -2,40 +2,42 @@ import { describe, it, expect, jest } from '@jest/globals'; import { render, screen } from '@testing-library/react'; import '@testing-library/jest-dom'; +type MotionProps = { children: React.ReactNode; className?: string; [key: string]: unknown }; + jest.mock('framer-motion', () => ({ motion: { - div: ({ children, className, ...props }: any) => ( + div: ({ children, className, ...props }: MotionProps) => (

    {children}
    ), - section: ({ children, className, ...props }: any) => ( + section: ({ children, className, ...props }: MotionProps) => (
    {children}
    ), - span: ({ children, className, ...props }: any) => ( + span: ({ children, className, ...props }: MotionProps) => ( {children} ), - h1: ({ children, className, ...props }: any) => ( + h1: ({ children, className, ...props }: MotionProps) => (

    {children}

    ), - h2: ({ children, className, ...props }: any) => ( + h2: ({ children, className, ...props }: MotionProps) => (

    {children}

    ), }, - AnimatePresence: ({ children }: any) => <>{children}, + AnimatePresence: ({ children }: { children: React.ReactNode }) => <>{children}, useInView: () => [null, true], })); jest.mock('next/link', () => { - const MockLink = ({ children, href, ...props }: any) => ( + const MockLink = ({ children, href, ...props }: { children: React.ReactNode; href: string; [key: string]: unknown }) => ( {children} @@ -54,40 +56,48 @@ jest.mock('lucide-react', () => ({ Phone: () => , })); -jest.mock('@/components/ui/card', () => ({ - Card: ({ children, className, ...props }: any) => ( +jest.mock('@/components/ui/card', () => { + const Card = ({ children, className, ...props }: { children: React.ReactNode; className?: string; [key: string]: unknown }) => (
    {children}
    - ), - CardContent: ({ children, className, ...props }: any) => ( + ); + const CardContent = ({ children, className, ...props }: { children: React.ReactNode; className?: string; [key: string]: unknown }) => (
    {children}
    - ), -})); + ); + Card.displayName = 'Card'; + CardContent.displayName = 'CardContent'; + return { Card, CardContent }; +}); -jest.mock('@/components/ui/page-header', () => ({ - PageHeader: ({ title, description }: any) => ( +jest.mock('@/components/ui/page-header', () => { + const PageHeader = ({ title, description }: { title: string; description?: string }) => (

    {title}

    {description}

    - ), -})); + ); + PageHeader.displayName = 'PageHeader'; + return { PageHeader }; +}); -jest.mock('@/components/ui/flip-clock', () => ({ - FlipClock: ({ years, months, days }: any) => ( +jest.mock('@/components/ui/flip-clock', () => { + const FlipClock = ({ years, months, days }: { years: number; months: number; days: number }) => (
    {years}年 {months}月 {days}天
    - ), -})); + ); + FlipClock.displayName = 'FlipClock'; + return { FlipClock }; +}); jest.mock('@/lib/constants', () => ({ COMPANY_INFO: { name: '四川睿新致远科技有限公司', shortName: '睿新致遠', + displayName: '睿新致远', description: '以智慧连接数字趋势,以伙伴身份陪您成长', address: '四川省成都市龙泉驿区', email: 'contact@ruixin.com', diff --git a/src/app/(marketing)/about/page.tsx b/src/app/(marketing)/about/page.tsx index 6d06ae2..0a28e8b 100644 --- a/src/app/(marketing)/about/page.tsx +++ b/src/app/(marketing)/about/page.tsx @@ -2,7 +2,7 @@ import { COMPANY_INFO } from '@/lib/constants'; import { AboutClient } from './client'; export const metadata = { - title: `关于我们 - ${COMPANY_INFO.name}`, + title: `关于我们 - ${COMPANY_INFO.displayName}`, description: `了解${COMPANY_INFO.name}的品牌故事。我们不只是技术供应商,更是您数字化转型的成长伙伴。以智慧连接数字趋势,以伙伴身份陪您成长。`, }; diff --git a/src/app/(marketing)/contact/layout.tsx b/src/app/(marketing)/contact/layout.tsx index 3c16dfb..20b8165 100644 --- a/src/app/(marketing)/contact/layout.tsx +++ b/src/app/(marketing)/contact/layout.tsx @@ -1,5 +1,7 @@ +import { COMPANY_INFO } from '@/lib/constants'; + export const metadata = { - title: '联系我们 - 四川睿新致远科技有限公司', + title: `联系我们 - ${COMPANY_INFO.displayName}`, description: '无论您有任何问题或合作意向,我们都很乐意与您交流', }; diff --git a/src/app/(marketing)/news/[slug]/page.tsx b/src/app/(marketing)/news/[slug]/page.tsx index 8314004..3b3ba8c 100644 --- a/src/app/(marketing)/news/[slug]/page.tsx +++ b/src/app/(marketing)/news/[slug]/page.tsx @@ -1,5 +1,5 @@ import { notFound } from 'next/navigation'; -import { NEWS } from '@/lib/constants'; +import { NEWS, COMPANY_INFO } from '@/lib/constants'; import { NewsDetailClient } from './NewsDetailClient'; export async function generateStaticParams() { @@ -19,7 +19,7 @@ export async function generateMetadata({ params }: { params: Promise<{ slug: str } return { - title: `${news.title} - 睿新致远`, + title: `${news.title} - ${COMPANY_INFO.displayName}`, description: news.excerpt, }; } diff --git a/src/app/(marketing)/news/layout.tsx b/src/app/(marketing)/news/layout.tsx index 301f574..5bbdceb 100644 --- a/src/app/(marketing)/news/layout.tsx +++ b/src/app/(marketing)/news/layout.tsx @@ -2,8 +2,8 @@ import { Metadata } from 'next'; import { COMPANY_INFO } from '@/lib/constants'; export const metadata: Metadata = { - title: `新闻动态 - ${COMPANY_INFO.shortName}`, - description: `了解${COMPANY_INFO.shortName}最新动态,把握行业发展脉搏。`, + title: `新闻动态 - ${COMPANY_INFO.displayName}`, + description: `了解${COMPANY_INFO.displayName}最新动态,把握行业发展脉搏。`, }; export default function NewsLayout({ children }: { children: React.ReactNode }) { diff --git a/src/app/(marketing)/news/page.tsx b/src/app/(marketing)/news/page.tsx index 0d8c6a7..f7def9c 100644 --- a/src/app/(marketing)/news/page.tsx +++ b/src/app/(marketing)/news/page.tsx @@ -2,7 +2,7 @@ import { useState, useMemo, ChangeEvent } from 'react'; import { motion } from 'framer-motion'; -import { NEWS } from '@/lib/constants'; +import { NEWS, COMPANY_INFO } from '@/lib/constants'; import { Badge } from '@/components/ui/badge'; import { Input } from '@/components/ui/input'; import { Button } from '@/components/ui/button'; @@ -65,7 +65,7 @@ export default function NewsListPage() { 新闻动态

    - 了解睿新致远最新动态,把握行业发展脉搏 + 了解{COMPANY_INFO.displayName}最新动态,把握行业发展脉搏

    diff --git a/src/app/(marketing)/products/[id]/page.tsx b/src/app/(marketing)/products/[id]/page.tsx index 3566406..5071ce2 100644 --- a/src/app/(marketing)/products/[id]/page.tsx +++ b/src/app/(marketing)/products/[id]/page.tsx @@ -1,6 +1,6 @@ import { notFound } from 'next/navigation'; import { StaticLink } from '@/components/ui/static-link'; -import { PRODUCTS } from '@/lib/constants'; +import { PRODUCTS, COMPANY_INFO } from '@/lib/constants'; import { Button } from '@/components/ui/button'; import { PageNav } from '@/components/layout/page-nav'; import { CheckCircle2, Zap, Target, Layers, ArrowRight } from 'lucide-react'; @@ -20,7 +20,7 @@ export async function generateMetadata({ params }: { params: Promise<{ id: strin } return { - title: `${product.title} - 睿新致远`, + title: `${product.title} - ${COMPANY_INFO.displayName}`, description: product.description, }; } diff --git a/src/app/(marketing)/products/layout.tsx b/src/app/(marketing)/products/layout.tsx index b8149a6..f23fd24 100644 --- a/src/app/(marketing)/products/layout.tsx +++ b/src/app/(marketing)/products/layout.tsx @@ -2,8 +2,8 @@ import { Metadata } from 'next'; import { COMPANY_INFO } from '@/lib/constants'; export const metadata: Metadata = { - title: `产品 - ${COMPANY_INFO.shortName}`, - description: `自主研发的企业级产品,助力企业高效运营,实现数字化转型。${COMPANY_INFO.shortName}提供ERP、CRM、BI、CMS等产品。`, + title: `产品 - ${COMPANY_INFO.displayName}`, + description: `自主研发的企业级产品,助力企业高效运营,实现数字化转型。${COMPANY_INFO.displayName}提供ERP、CRM、BI、CMS等产品。`, }; export default function ProductsLayout({ children }: { children: React.ReactNode }) { diff --git a/src/app/(marketing)/services/[id]/page.tsx b/src/app/(marketing)/services/[id]/page.tsx index 1bc9f5d..239485d 100644 --- a/src/app/(marketing)/services/[id]/page.tsx +++ b/src/app/(marketing)/services/[id]/page.tsx @@ -1,6 +1,6 @@ import { Metadata } from 'next'; import { notFound } from 'next/navigation'; -import { SERVICES } from '@/lib/constants'; +import { SERVICES, COMPANY_INFO } from '@/lib/constants'; import { ServiceDetailClient } from './client'; export async function generateStaticParams() { @@ -20,7 +20,7 @@ export async function generateMetadata({ params }: { params: Promise<{ id: strin } return { - title: `${service.title} - 睿新致远`, + title: `${service.title} - ${COMPANY_INFO.displayName}`, description: service.description, }; } diff --git a/src/app/(marketing)/services/layout.tsx b/src/app/(marketing)/services/layout.tsx index 1159a48..a882c24 100644 --- a/src/app/(marketing)/services/layout.tsx +++ b/src/app/(marketing)/services/layout.tsx @@ -2,8 +2,8 @@ import { Metadata } from 'next'; import { COMPANY_INFO } from '@/lib/constants'; export const metadata: Metadata = { - title: `服务 - ${COMPANY_INFO.shortName}`, - description: `专业技术团队,为您提供全方位的数字化解决方案。${COMPANY_INFO.shortName}涵盖软件开发、数据分析、咨询服务等核心业务。`, + title: `服务 - ${COMPANY_INFO.displayName}`, + description: `专业技术团队,为您提供全方位的数字化解决方案。${COMPANY_INFO.displayName}涵盖软件开发、数据分析、咨询服务等核心业务。`, }; export default function ServicesLayout({ children }: { children: React.ReactNode }) { diff --git a/src/app/(marketing)/solutions/[id]/page.tsx b/src/app/(marketing)/solutions/[id]/page.tsx index 40b90ae..ae974d0 100644 --- a/src/app/(marketing)/solutions/[id]/page.tsx +++ b/src/app/(marketing)/solutions/[id]/page.tsx @@ -2,6 +2,7 @@ import { Metadata } from 'next'; import { notFound } from 'next/navigation'; import { SOLUTIONS } from '@/lib/constants/solutions'; import { PRODUCTS } from '@/lib/constants/products'; +import { COMPANY_INFO } from '@/lib/constants'; import { SolutionDetailClient } from './client'; export async function generateStaticParams() { @@ -19,7 +20,7 @@ export async function generateMetadata({ params }: { params: Promise<{ id: strin } return { - title: `${solution.title} - 睿新致远`, + title: `${solution.title} - ${COMPANY_INFO.displayName}`, description: solution.description, }; } diff --git a/src/app/(marketing)/solutions/layout.tsx b/src/app/(marketing)/solutions/layout.tsx index cef9157..c38511f 100644 --- a/src/app/(marketing)/solutions/layout.tsx +++ b/src/app/(marketing)/solutions/layout.tsx @@ -1,7 +1,9 @@ import { Metadata } from 'next'; +import { COMPANY_INFO } from '@/lib/constants'; + export const metadata: Metadata = { - title: '解决方案 - 睿新致远', + title: `解决方案 - ${COMPANY_INFO.displayName}`, description: '三种角色,一种身份——您的成长伙伴', }; diff --git a/src/app/(marketing)/solutions/page.tsx b/src/app/(marketing)/solutions/page.tsx index 3e96bed..90a0755 100644 --- a/src/app/(marketing)/solutions/page.tsx +++ b/src/app/(marketing)/solutions/page.tsx @@ -20,6 +20,7 @@ const modules = [ values: ['行业趋势洞察报告', '数字化转型成熟度评估', '个性化实施路径规划'], cta: '预约一次免费诊断', ctaVariant: 'default' as const, + ctaHref: '/contact', }, { icon: Cpu, @@ -33,6 +34,7 @@ const modules = [ values: ['业务场景深度调研', '技术方案定制开发', '敏捷交付快速迭代'], cta: '了解技术方案', ctaVariant: 'outline' as const, + ctaHref: '/products', }, { icon: Users, @@ -46,6 +48,7 @@ const modules = [ values: ['专属客户成功经理', '季度业务复盘会', '7×24小时响应通道'], cta: '了解陪跑服务', ctaVariant: 'default' as const, + ctaHref: '/services', }, ]; @@ -127,7 +130,7 @@ export default function SolutionsPage() { className={module.ctaVariant === 'default' ? 'bg-[#C41E3A] hover:bg-[#A01830] text-white' : 'border-[#C41E3A] text-[#C41E3A] hover:bg-[#C41E3A] hover:text-white'} asChild > - + {module.cta} diff --git a/src/app/(marketing)/team/page.tsx b/src/app/(marketing)/team/page.tsx index 71ad41c..e99b19a 100644 --- a/src/app/(marketing)/team/page.tsx +++ b/src/app/(marketing)/team/page.tsx @@ -2,7 +2,7 @@ import { COMPANY_INFO } from '@/lib/constants'; import { TeamClient } from './client'; export const metadata = { - title: `核心团队 - ${COMPANY_INFO.name}`, + title: `核心团队 - ${COMPANY_INFO.displayName}`, description: `了解${COMPANY_INFO.name}的核心团队。我们的团队成员拥有丰富的行业经验和技术专长,致力于为客户提供专业的数字化转型服务。`, }; diff --git a/src/app/not-found.tsx b/src/app/not-found.tsx index 27df852..0ab13e1 100644 --- a/src/app/not-found.tsx +++ b/src/app/not-found.tsx @@ -3,6 +3,7 @@ import { StaticLink } from '@/components/ui/static-link'; import { Button } from '@/components/ui/button'; import { Home, ArrowLeft, Search } from 'lucide-react'; +import { COMPANY_INFO } from '@/lib/constants'; export default function NotFound() { return ( @@ -62,7 +63,7 @@ export default function NotFound() {
    关于我们
    -
    了解睿新致远
    +
    了解{COMPANY_INFO.displayName}
    diff --git a/src/app/privacy/page.tsx b/src/app/privacy/page.tsx index e74f829..f98f30c 100644 --- a/src/app/privacy/page.tsx +++ b/src/app/privacy/page.tsx @@ -1,8 +1,9 @@ import { Metadata } from 'next'; +import { COMPANY_INFO } from '@/lib/constants'; export const metadata: Metadata = { - title: '隐私政策 - 睿新致远', - description: '四川睿新致远科技有限公司隐私政策', + title: `隐私政策 - ${COMPANY_INFO.displayName}`, + description: `${COMPANY_INFO.name}隐私政策`, }; export default function PrivacyPolicyPage() { diff --git a/src/app/terms/page.tsx b/src/app/terms/page.tsx index c718a40..8433468 100644 --- a/src/app/terms/page.tsx +++ b/src/app/terms/page.tsx @@ -1,8 +1,9 @@ import { Metadata } from 'next'; +import { COMPANY_INFO } from '@/lib/constants'; export const metadata: Metadata = { - title: '服务条款 - 睿新致远', - description: '四川睿新致远科技有限公司服务条款', + title: `服务条款 - ${COMPANY_INFO.displayName}`, + description: `${COMPANY_INFO.name}服务条款`, }; export default function TermsOfServicePage() { diff --git a/src/components/analytics/CookieConsent.tsx b/src/components/analytics/CookieConsent.tsx index 7e06037..79c7bdd 100644 --- a/src/components/analytics/CookieConsent.tsx +++ b/src/components/analytics/CookieConsent.tsx @@ -1,6 +1,6 @@ 'use client'; -import { useState, useEffect, useCallback } from 'react'; +import { useState, useEffect, useCallback, useRef } from 'react'; import { updateConsentDetailed, trackButtonClick, @@ -13,19 +13,37 @@ import { motion, AnimatePresence } from 'framer-motion'; const LEGACY_CONSENT_KEY = 'ga_consent'; +let hasConsentBeenHandled = false; + +function getInitialShowConsent(): boolean { + if (typeof window === 'undefined') {return false;} + if (hasConsentBeenHandled) {return false;} + const stored = getStoredPreferences(); + if (stored) {return false;} + const legacyConsent = localStorage.getItem(LEGACY_CONSENT_KEY); + if (legacyConsent) {return false;} + return false; +} + export function CookieConsent() { - const [showConsent, setShowConsent] = useState(false); + const [showConsent, setShowConsent] = useState(getInitialShowConsent); const [showSettings, setShowSettings] = useState(false); const [isAnimating, setIsAnimating] = useState(false); const [preferences, setPreferences] = useState(getDefaultPreferences()); + const consentCheckedRef = useRef(false); useEffect(() => { + if (consentCheckedRef.current) {return;} + consentCheckedRef.current = true; + const stored = getStoredPreferences(); if (stored) { + hasConsentBeenHandled = true; updateConsentDetailed(stored); } else { const legacyConsent = localStorage.getItem(LEGACY_CONSENT_KEY); if (legacyConsent) { + hasConsentBeenHandled = true; const migratedPrefs: CookiePreferences = { necessary: true, analytics: legacyConsent === 'granted', @@ -45,8 +63,18 @@ export function CookieConsent() { return undefined; }, []); + useEffect(() => { + const handleOpenSettings = () => { + setShowSettings(true); + setShowConsent(true); + }; + window.addEventListener('open-cookie-settings', handleOpenSettings); + return () => window.removeEventListener('open-cookie-settings', handleOpenSettings); + }, []); + const handleSavePreferences = useCallback((prefs: CookiePreferences) => { setIsAnimating(true); + hasConsentBeenHandled = true; const finalPrefs = { ...prefs, timestamp: Date.now() }; storePreferences(finalPrefs); updateConsentDetailed(finalPrefs); diff --git a/src/components/layout/footer.test.tsx b/src/components/layout/footer.test.tsx index 09a5006..42f23c5 100644 --- a/src/components/layout/footer.test.tsx +++ b/src/components/layout/footer.test.tsx @@ -35,6 +35,8 @@ jest.mock('lucide-react', () => ({ jest.mock('@/lib/constants', () => ({ COMPANY_INFO: { name: '四川睿新致远科技有限公司', + shortName: '睿新致遠', + displayName: '睿新致远', description: '以智慧连接数字趋势,以伙伴身份陪您成长', email: 'contact@novalon.cn', phone: '028-88888888', diff --git a/src/components/layout/header.test.tsx b/src/components/layout/header.test.tsx index 2b82ecf..f4c4677 100644 --- a/src/components/layout/header.test.tsx +++ b/src/components/layout/header.test.tsx @@ -15,28 +15,32 @@ jest.mock('next/navigation', () => ({ })); jest.mock('next/link', () => { - return ({ children, href, onClick, ...props }: any) => ( + const MockLink = ({ children, href, onClick, ...props }: { children: React.ReactNode; href: string; onClick?: () => void; [key: string]: unknown }) => ( {children} ); + MockLink.displayName = 'MockLink'; + return MockLink; }); jest.mock('next/image', () => { - return ({ src, alt, width, height, className, ...props }: any) => ( + const MockImage = ({ src, alt, width, height, className, ...props }: { src: string; alt: string; width: number; height: number; className?: string; [key: string]: unknown }) => ( {alt} ); + MockImage.displayName = 'MockImage'; + return MockImage; }); jest.mock('framer-motion', () => ({ motion: { - div: ({ children, className, ...props }: any) => ( + div: ({ children, className, ...props }: { children: React.ReactNode; className?: string; [key: string]: unknown }) => (
    {children}
    ), }, - AnimatePresence: ({ children }: any) => <>{children}, + AnimatePresence: ({ children }: { children: React.ReactNode }) => <>{children}, })); jest.mock('lucide-react', () => ({ @@ -44,18 +48,21 @@ jest.mock('lucide-react', () => ({ X: () => , })); -jest.mock('@/components/ui/button', () => ({ - Button: ({ children, className, asChild, ...props }: any) => ( +jest.mock('@/components/ui/button', () => { + const MockButton = ({ children, className, ...props }: { children: React.ReactNode; className?: string; [key: string]: unknown }) => ( - ), -})); + ); + MockButton.displayName = 'MockButton'; + return { Button: MockButton }; +}); jest.mock('@/lib/constants', () => ({ COMPANY_INFO: { name: '四川睿新致远科技有限公司', shortName: '睿新致遠', + displayName: '睿新致远', }, NAVIGATION: [ { id: 'home', label: '首页', href: '/' }, diff --git a/src/components/layout/header.tsx b/src/components/layout/header.tsx index cd75eb9..e479b08 100644 --- a/src/components/layout/header.tsx +++ b/src/components/layout/header.tsx @@ -108,7 +108,11 @@ function HeaderContent() { label={item.label} items={MEGA_DROPDOWN_DATA[item.dropdownKey!] ?? []} isOpen={openDropdown === item.id} - onToggle={() => setOpenDropdown(openDropdown === item.id ? null : item.id)} + onToggle={() => { + setOpenDropdown((prev) => prev === item.id ? null : item.id); + }} + onOpen={() => setOpenDropdown(item.id)} + onClose={() => setOpenDropdown((prev) => prev === item.id ? null : prev)} /> ) : ( ))}
    -
    diff --git a/src/components/layout/mega-dropdown.tsx b/src/components/layout/mega-dropdown.tsx index 7b93c9a..2ae6ebb 100644 --- a/src/components/layout/mega-dropdown.tsx +++ b/src/components/layout/mega-dropdown.tsx @@ -11,10 +11,15 @@ interface MegaDropdownProps { items: MegaDropdownItem[]; isOpen: boolean; onToggle: () => void; + onOpen?: () => void; + onClose?: () => void; } -export function MegaDropdown({ label, items, isOpen, onToggle }: MegaDropdownProps) { +const HOVER_DELAY = 150; + +export function MegaDropdown({ label, items, isOpen, onToggle, onOpen, onClose }: MegaDropdownProps) { const dropdownRef = useRef(null); + const hoverTimeoutRef = useRef | null>(null); useEffect(() => { function handleClickOutside(event: MouseEvent) { @@ -26,8 +31,42 @@ export function MegaDropdown({ label, items, isOpen, onToggle }: MegaDropdownPro return () => document.removeEventListener('mousedown', handleClickOutside); }, [isOpen, onToggle]); + useEffect(() => { + return () => { + if (hoverTimeoutRef.current) { + clearTimeout(hoverTimeoutRef.current); + } + }; + }, []); + + const handleMouseEnter = () => { + if (hoverTimeoutRef.current) { + clearTimeout(hoverTimeoutRef.current); + hoverTimeoutRef.current = null; + } + if (!isOpen) { + if (onOpen) { + onOpen(); + } else { + onToggle(); + } + } + }; + + const handleMouseLeave = () => { + hoverTimeoutRef.current = setTimeout(() => { + if (isOpen) { + if (onClose) { + onClose(); + } else { + onToggle(); + } + } + }, HOVER_DELAY); + }; + return ( -
    +