From 5c1b883abf40109a6183a17db829212f9844f372 Mon Sep 17 00:00:00 2001
From: zhangxiang
Date: Mon, 31 Aug 2026 17:25:08 +0800
Subject: [PATCH] =?UTF-8?q?feat(ui):=20CTAButton=20=E5=8F=AF=E5=A4=8D?=
=?UTF-8?q?=E7=94=A8=E7=BB=84=E4=BB=B6=20+=20P5=20=E5=85=A8=E7=AB=99=20CTA?=
=?UTF-8?q?=20=E7=AD=BE=E5=90=8D=E5=88=87=E6=8D=A2=EF=BC=8817=20=E9=A1=B5?=
=?UTF-8?q?=E9=9D=A2=2027=20=E5=A4=84=EF=BC=89?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
feat(ui): 抽离 CTAButton 为 src/components/ui/cta-button.tsx
- 4 variant 覆盖全站 CTA 场景:
* primary:品牌红填充 pill(hero 主行动 / 底部大 CTA)
* secondary:描边墨色 pill(浅色 section 次行动)
* dark:描边浅色 pill(深色区块 bg-dark-bg 内次行动,如 brand/team 底部 CTA)
* inline:透明 + 小箭头(卡片内次行动)
- 品牌箭头签名化:内置 ArrowRight + hover translate-x 微交互
- 200-300ms transition,对齐项目动效四原则
- data-testid 透传避免破坏既有测试
style(cta): P5 全站 CTA 签名切换(17 页面 27 处)
- home V15 / news×3 / products×4(含 erp)/ cases / services×2 / solutions×2
- brand / methodology / team(hero 2 + L4 2 = 4 处,含 dark variant)
- error / not-found(保留 onClick 功能按钮与卡片式导航 StaticLink)
- 清理:StaticLink / Button / ArrowRight / ChevronRight unused import
test(marketing): services/solutions lucide-react mock 补 ArrowRight
- CTAButton 内部用 ArrowRight,原 mock 漏导导致渲染崩(4 测试失败已修)
- 对齐 home-content-v15.test.tsx 既有 mock 范式
---
.../(marketing)/about/brand/brand-content.tsx | 20 +----
.../(marketing)/cases/cases-content-v3.tsx | 18 ++--
src/app/(marketing)/home-content-v15.tsx | 27 +-----
.../methodology/methodology-content.tsx | 18 ++--
.../news/[slug]/NewsDetailClient.tsx | 9 +-
src/app/(marketing)/news/news-content-v3.tsx | 9 +-
.../news/news-detail-content-v3.tsx | 11 +--
.../erp-upgrade/erp-upgrade-content-v2.tsx | 27 +++---
.../products/product-detail-content-v3.tsx | 32 +++----
.../products/products-content-v3.tsx | 32 +++----
.../products/standalone/[id]/client.tsx | 13 ++-
.../services/service-detail-content-v4.tsx | 25 ++----
.../services/services-content-v3.test.tsx | 1 +
.../services/services-content-v3.tsx | 31 ++-----
.../solutions/solution-detail-content-v3.tsx | 35 ++++----
.../solutions/solutions-content-v3.test.tsx | 1 +
.../solutions/solutions-content-v3.tsx | 31 ++-----
src/app/(marketing)/team/team-content-v3.tsx | 33 +++-----
src/app/error.tsx | 16 ++--
src/app/not-found-content.tsx | 10 +--
src/components/ui/cta-button.tsx | 84 +++++++++++++++++++
21 files changed, 215 insertions(+), 268 deletions(-)
create mode 100644 src/components/ui/cta-button.tsx
diff --git a/src/app/(marketing)/about/brand/brand-content.tsx b/src/app/(marketing)/about/brand/brand-content.tsx
index db8a18b..303c816 100644
--- a/src/app/(marketing)/about/brand/brand-content.tsx
+++ b/src/app/(marketing)/about/brand/brand-content.tsx
@@ -2,10 +2,9 @@
import { motion } from 'framer-motion';
import { ScrollReveal } from '@/components/ui/scroll-reveal';
-import { StaticLink } from '@/components/ui/static-link';
+import { CTAButton } from '@/components/ui/cta-button';
import { BrandCalligraphyName } from '@/components/ui/brand-calligraphy-name';
import {
- ArrowRight,
Target,
Eye,
Heart,
@@ -14,7 +13,6 @@ import {
Users,
Building2,
Globe,
- ChevronRight,
} from 'lucide-react';
const EASE_OUT = [0.22, 1, 0.36, 1] as const;
@@ -356,20 +354,10 @@ export default function BrandContent() {
从一次深入的业务诊断开始,开启您的数据驱动之旅。
-
- 预约咨询
-
-
-
+ 预约咨询
+
查看客户案例
-
-
+
diff --git a/src/app/(marketing)/cases/cases-content-v3.tsx b/src/app/(marketing)/cases/cases-content-v3.tsx
index a3315e6..0dbe8fd 100644
--- a/src/app/(marketing)/cases/cases-content-v3.tsx
+++ b/src/app/(marketing)/cases/cases-content-v3.tsx
@@ -2,7 +2,7 @@
import { useState, useMemo } from 'react';
import { motion } from 'framer-motion';
-import { StaticLink } from '@/components/ui/static-link';
+import { CTAButton } from '@/components/ui/cta-button';
import { Building2, ArrowUpRight, Target, Lightbulb, Filter } from 'lucide-react';
import {
type CaseStudy,
@@ -309,19 +309,17 @@ export default function CasesContentV3({
让我们聊聊您的挑战,看看能创造什么样的成果。
-
+
预约咨询
-
-
-
+
了解服务
-
+
diff --git a/src/app/(marketing)/home-content-v15.tsx b/src/app/(marketing)/home-content-v15.tsx
index 05a947f..7a76d5a 100644
--- a/src/app/(marketing)/home-content-v15.tsx
+++ b/src/app/(marketing)/home-content-v15.tsx
@@ -10,6 +10,7 @@ import { HeroProductVisual } from '@/components/sections/hero-product-visual';
import { ArrowRight, Lightbulb, Database, Layers, Code, Puzzle, Server, Quote, Calendar } from 'lucide-react';
import { cn } from '@/lib/utils';
import { EASE_OUT } from '@/components/ui/page-decoration';
+import { CTAButton } from '@/components/ui/cta-button';
import { COMPANY_INFO, TRUST_SIGNALS, EARLY_ACCESS } from '@/lib/constants';
import { useSiteConfig } from '@/lib/site-config';
import { type NewsItem } from '@/lib/constants/news';
@@ -133,31 +134,7 @@ function StyledTitle({ lines }: { lines: (string | undefined)[] }) {
);
}
-// ===== 品牌 CTA 按钮(Pill 胶囊化,对标 Accenture > CTA)=====
-function CTAButton({ children, href, variant = 'primary', className, dataTestId }: {
- children: React.ReactNode;
- href: string;
- variant?: 'primary' | 'secondary';
- className?: string;
- dataTestId?: string;
-}) {
- return (
-
- {children}
-
-
- );
-}
+// ===== 品牌 CTA 按钮已抽离为可复用 UI 组件:@/components/ui/cta-button =====
// ===== Hero 区:全出血深色画布(对标 Accenture 纯黑 Hero)+ 品牌红单电压 =====
function HeroSection({ heroData, stats }: {
diff --git a/src/app/(marketing)/methodology/methodology-content.tsx b/src/app/(marketing)/methodology/methodology-content.tsx
index 6cd090a..2dd7c61 100644
--- a/src/app/(marketing)/methodology/methodology-content.tsx
+++ b/src/app/(marketing)/methodology/methodology-content.tsx
@@ -2,10 +2,9 @@
import { motion } from 'framer-motion';
import { ScrollReveal, StaggerReveal } from '@/components/ui/scroll-reveal';
-import { StaticLink } from '@/components/ui/static-link';
+import { CTAButton } from '@/components/ui/cta-button';
import { EASE_OUT } from '@/components/ui/page-decoration';
-import { Button } from '@/components/ui/button';
-import { ArrowRight, CheckCircle2 } from 'lucide-react';
+import { CheckCircle2 } from 'lucide-react';
interface MethodologyPhase {
number?: number;
@@ -154,15 +153,10 @@ export function MethodologyContent({ data }: MethodologyContentProps) {
我们的咨询服务贯穿方法论的每个阶段,从战略规划到落地实施全程陪伴。
-
-
+
了解我们的服务
+
+ 联系我们
+
diff --git a/src/app/(marketing)/news/[slug]/NewsDetailClient.tsx b/src/app/(marketing)/news/[slug]/NewsDetailClient.tsx
index b49f443..d38fb92 100644
--- a/src/app/(marketing)/news/[slug]/NewsDetailClient.tsx
+++ b/src/app/(marketing)/news/[slug]/NewsDetailClient.tsx
@@ -1,6 +1,7 @@
'use client';
import { StaticLink } from '@/components/ui/static-link';
+import { CTAButton } from '@/components/ui/cta-button';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { Card } from '@/components/ui/card';
@@ -139,11 +140,9 @@ export function NewsDetailClient({ news }: NewsDetailClientProps) {
返回新闻列表
-
-
-
+
+ 联系我们
+
diff --git a/src/app/(marketing)/news/news-content-v3.tsx b/src/app/(marketing)/news/news-content-v3.tsx
index b5e925f..49f28f8 100644
--- a/src/app/(marketing)/news/news-content-v3.tsx
+++ b/src/app/(marketing)/news/news-content-v3.tsx
@@ -5,6 +5,7 @@ import { motion } from 'framer-motion';
import { ScrollReveal, StaggerReveal } from '@/components/ui/scroll-reveal';
import { Button } from '@/components/ui/button';
import { StaticLink } from '@/components/ui/static-link';
+import { CTAButton } from '@/components/ui/cta-button';
import { Badge } from '@/components/ui/badge';
import { Input } from '@/components/ui/input';
import { Search, Calendar, ArrowRight, Newspaper, ChevronLeft, ChevronRight } from 'lucide-react';
@@ -249,13 +250,9 @@ export default function NewsContentV3({ news, pageCopy }: { news?: NewsItem[]; p
无论您有合作意向还是想探讨技术趋势,我们都欢迎与您交流。
diff --git a/src/app/(marketing)/products/erp-upgrade/erp-upgrade-content-v2.tsx b/src/app/(marketing)/products/erp-upgrade/erp-upgrade-content-v2.tsx
index d6c7c16..5ad723c 100644
--- a/src/app/(marketing)/products/erp-upgrade/erp-upgrade-content-v2.tsx
+++ b/src/app/(marketing)/products/erp-upgrade/erp-upgrade-content-v2.tsx
@@ -1,11 +1,10 @@
'use client';
import { ScrollReveal, StaggerReveal } from '@/components/ui/scroll-reveal';
-import { StaticLink } from '@/components/ui/static-link';
import { Button } from '@/components/ui/button';
+import { CTAButton } from '@/components/ui/cta-button';
import { Badge } from '@/components/ui/badge';
import {
- ArrowRight,
Zap,
Shield,
TrendingUp,
@@ -153,12 +152,9 @@ export default function ErpUpgradeContentV2({ item }: ErpUpgradeContentV2Props)
-
+
+ 免费诊断咨询
+
@@ -404,15 +400,12 @@ export default function ErpUpgradeContentV2({ item }: ErpUpgradeContentV2Props)
-
-
+
+ 预约免费诊断
+
+
+ 查看产品详情
+
diff --git a/src/app/(marketing)/products/product-detail-content-v3.tsx b/src/app/(marketing)/products/product-detail-content-v3.tsx
index e268df7..d6cf67c 100644
--- a/src/app/(marketing)/products/product-detail-content-v3.tsx
+++ b/src/app/(marketing)/products/product-detail-content-v3.tsx
@@ -3,7 +3,7 @@
import { motion } from 'framer-motion';
import { ScrollReveal, StaggerReveal } from '@/components/ui/scroll-reveal';
import { StaticLink } from '@/components/ui/static-link';
-import { Button } from '@/components/ui/button';
+import { CTAButton } from '@/components/ui/cta-button';
import { ArrowRight, Package, Check, Settings, Users, FileText, Cpu, Shield, TrendingUp } from 'lucide-react';
import { SectionLabel, EASE_OUT } from '@/components/ui/page-decoration';
import type { Product } from '@/lib/constants/products';
@@ -89,15 +89,12 @@ function DetailHero({ product }: DetailHeroProps) {
transition={{ duration: 0.9, delay: 0.55, ease: EASE_OUT }}
className="flex flex-wrap gap-6"
>
-
-
+
+ 预约体验
+
+
+ 查看更多产品
+
@@ -403,15 +400,12 @@ function CTASection({ product }: CTASectionProps) {
-
-
+
+ 预约演示
+
+
+ 查看更多产品
+
{/* 站内入口:ERP 升级专题(与 erp-upgrade-content-v2 的反向链接形成互链闭环) */}
diff --git a/src/app/(marketing)/products/products-content-v3.tsx b/src/app/(marketing)/products/products-content-v3.tsx
index 779145e..a8939a5 100644
--- a/src/app/(marketing)/products/products-content-v3.tsx
+++ b/src/app/(marketing)/products/products-content-v3.tsx
@@ -4,8 +4,8 @@ import { useRef, useState, useEffect } from 'react';
import { motion, useInView } from 'framer-motion';
import { ScrollReveal, StaggerReveal } from '@/components/ui/scroll-reveal';
import { EASE_OUT } from '@/components/ui/page-decoration';
-import { Button } from '@/components/ui/button';
import { StaticLink } from '@/components/ui/static-link';
+import { CTAButton } from '@/components/ui/cta-button';
import { BentoItem } from '@/components/sections/bento-grid';
import { ArrowRight, ArrowUpRight, Package, Cpu, TrendingUp, Users, Settings, FileText } from 'lucide-react';
import type { Product } from '@/lib/constants/products';
@@ -135,15 +135,12 @@ function HeroSection({ pageCopy }: { pageCopy?: PageCopy | null }) {
transition={{ duration: 1, delay: 0.6, ease: EASE_OUT }}
className="flex flex-wrap gap-6"
>
-
-
+
+ {ctaPrimary}
+
+
+ {ctaSecondary}
+
@@ -497,15 +494,12 @@ function CTASection({ pageCopy }: { pageCopy?: PageCopy | null }) {
-
-
+
+ 了解我们的服务
+
+
+ 联系我们
+
diff --git a/src/app/(marketing)/products/standalone/[id]/client.tsx b/src/app/(marketing)/products/standalone/[id]/client.tsx
index 1b89ef3..83789ce 100644
--- a/src/app/(marketing)/products/standalone/[id]/client.tsx
+++ b/src/app/(marketing)/products/standalone/[id]/client.tsx
@@ -2,12 +2,13 @@
import { getHeroTheme } from '@/lib/constants/hero-themes';
import { StaticLink } from '@/components/ui/static-link';
+import { CTAButton } from '@/components/ui/cta-button';
import { DetailHero } from '@/components/detail/detail-hero';
import { DetailTrustSection } from '@/components/detail/detail-trust-section';
import { DetailCTASection } from '@/components/detail/detail-cta-section';
import { DetailSwipeNav } from '@/components/ui/detail-swipe-nav';
import { motion } from 'framer-motion';
-import { CheckCircle2, Shield, Lock, Cpu, HardDrive, ArrowRight, FileCheck, Gauge } from 'lucide-react';
+import { CheckCircle2, Shield, Lock, Cpu, HardDrive, FileCheck, Gauge } from 'lucide-react';
import type { ContentItem } from '@/lib/cms/types';
import type { CaseStudy, DataProof, Certification, ComplianceCertification, TechnicalParameter } from '@/lib/constants/products';
@@ -113,13 +114,9 @@ export function StandaloneProductClient({ item }: StandaloneProductClientProps)
)}
diff --git a/src/app/(marketing)/services/services-content-v3.test.tsx b/src/app/(marketing)/services/services-content-v3.test.tsx
index 27155ca..aedb52c 100644
--- a/src/app/(marketing)/services/services-content-v3.test.tsx
+++ b/src/app/(marketing)/services/services-content-v3.test.tsx
@@ -49,6 +49,7 @@ jest.mock('lucide-react', () => {
return Icon;
};
return {
+ ArrowRight: mockIcon('arrow-right'),
ArrowUpRight: mockIcon('arrow-up-right'),
CheckCircle2: mockIcon('check-circle2'),
};
diff --git a/src/app/(marketing)/services/services-content-v3.tsx b/src/app/(marketing)/services/services-content-v3.tsx
index 73e3876..b5fc8c4 100644
--- a/src/app/(marketing)/services/services-content-v3.tsx
+++ b/src/app/(marketing)/services/services-content-v3.tsx
@@ -3,6 +3,7 @@
import { motion } from 'framer-motion';
import { ArrowUpRight, CheckCircle2 } from 'lucide-react';
import { StaticLink } from '@/components/ui/static-link';
+import { CTAButton } from '@/components/ui/cta-button';
import type { Service } from '@/lib/constants/services';
const EASE_OUT = [0.22, 1, 0.36, 1] as const;
@@ -126,19 +127,12 @@ function HeroSection({ pageCopy }: { pageCopy?: PageCopy | null }) {
transition={{ duration: 0.8, delay: 0.7, ease: EASE_OUT }}
className="flex flex-col sm:flex-row gap-4 sm:gap-6"
>
-
+
{ctaPrimary}
-
-
-
+
+
{ctaSecondary}
-
+
-
+
预约免费咨询
-
-
-
+
+
了解我们的团队
-
+
diff --git a/src/app/(marketing)/solutions/solution-detail-content-v3.tsx b/src/app/(marketing)/solutions/solution-detail-content-v3.tsx
index 061cd90..5feaf2c 100644
--- a/src/app/(marketing)/solutions/solution-detail-content-v3.tsx
+++ b/src/app/(marketing)/solutions/solution-detail-content-v3.tsx
@@ -2,9 +2,8 @@
import { motion } from 'framer-motion';
import { ScrollReveal, StaggerReveal } from '@/components/ui/scroll-reveal';
-import { StaticLink } from '@/components/ui/static-link';
-import { Button } from '@/components/ui/button';
-import { ArrowRight, Factory, ShoppingCart, Heart, GraduationCap, Lightbulb, Settings, Users, TrendingUp, Package, BookOpen, MessageSquare, Calendar, Smartphone, Route, Shield, Truck } from 'lucide-react';import { SectionLabel, EASE_OUT } from '@/components/ui/page-decoration';
+import { CTAButton } from '@/components/ui/cta-button';
+import { Factory, ShoppingCart, Heart, GraduationCap, Lightbulb, Settings, Users, TrendingUp, Package, BookOpen, MessageSquare, Calendar, Smartphone, Route, Shield, Truck } from 'lucide-react';import { SectionLabel, EASE_OUT } from '@/components/ui/page-decoration';
import type { Solution } from '@/lib/constants/solutions';
import type { Product } from '@/lib/constants/products';
@@ -83,15 +82,12 @@ function DetailHero({ solution }: DetailHeroProps) {
transition={{ duration: 0.9, delay: 0.45, ease: EASE_OUT }}
className="flex flex-wrap gap-6"
>
-
-
+
+ 获取方案详情
+
+
+ 查看产品
+
@@ -491,15 +487,12 @@ function CTASection({ solution }: CTASectionProps) {
-
-
+
+ 预约咨询
+
+
+ 查看更多方案
+
diff --git a/src/app/(marketing)/solutions/solutions-content-v3.test.tsx b/src/app/(marketing)/solutions/solutions-content-v3.test.tsx
index 041dad9..a1732ee 100644
--- a/src/app/(marketing)/solutions/solutions-content-v3.test.tsx
+++ b/src/app/(marketing)/solutions/solutions-content-v3.test.tsx
@@ -50,6 +50,7 @@ jest.mock('lucide-react', () => {
return Icon;
};
return {
+ ArrowRight: mockIcon('arrow-right'),
ArrowUpRight: mockIcon('arrow-up-right'),
Factory: mockIcon('factory'),
ShoppingCart: mockIcon('shopping-cart'),
diff --git a/src/app/(marketing)/solutions/solutions-content-v3.tsx b/src/app/(marketing)/solutions/solutions-content-v3.tsx
index f9664c0..2e90542 100644
--- a/src/app/(marketing)/solutions/solutions-content-v3.tsx
+++ b/src/app/(marketing)/solutions/solutions-content-v3.tsx
@@ -3,6 +3,7 @@
import { motion } from 'framer-motion';
import { ArrowUpRight, Factory, ShoppingCart, Heart, GraduationCap, Shield, Truck, type LucideIcon } from 'lucide-react';
import { StaticLink } from '@/components/ui/static-link';
+import { CTAButton } from '@/components/ui/cta-button';
import { type Solution } from '@/lib/constants/solutions';
import type { Product } from '@/lib/constants/products';
@@ -74,19 +75,12 @@ function HeroSection({ pageCopy }: { pageCopy?: PageCopy | null }) {
transition={{ duration: 0.8, delay: 0.7, ease: EASE_OUT }}
className="flex flex-col sm:flex-row gap-4 sm:gap-6"
>
-
+
{ctaPrimary}
-
-
-
+
+
{ctaSecondary}
-
+
-
+
获取定制方案
-
-
-
+
+
浏览产品
-
+
diff --git a/src/app/(marketing)/team/team-content-v3.tsx b/src/app/(marketing)/team/team-content-v3.tsx
index 423a9d9..9c9625a 100644
--- a/src/app/(marketing)/team/team-content-v3.tsx
+++ b/src/app/(marketing)/team/team-content-v3.tsx
@@ -2,7 +2,8 @@
import { motion } from 'framer-motion';
import { ScrollReveal } from '@/components/ui/scroll-reveal';
-import { ArrowRight, Shield, Building2, Users, Code, Target, Layers, BookOpen, Sparkles, type LucideIcon } from 'lucide-react';
+import { CTAButton } from '@/components/ui/cta-button';
+import { Shield, Building2, Users, Code, Target, Layers, BookOpen, Sparkles, type LucideIcon } from 'lucide-react';
import { EASE_OUT } from '@/components/ui/page-decoration';
import { useReducedMotion } from '@/hooks/use-reduced-motion';
@@ -215,20 +216,13 @@ export default function TeamContentV3({ data }: { data: Record
transition={{ duration: 0.7, delay: 0.6, ease: EASE_OUT }}
className="flex flex-col sm:flex-row sm:items-center gap-4 sm:gap-6"
>
-
+
{teamData.heroPrimaryCtaLabel || '加入我们'}
-
-
+
{teamData.heroSecondaryCtaLabel && (
-
+
{teamData.heroSecondaryCtaLabel}
-
+
)}
@@ -347,19 +341,12 @@ export default function TeamContentV3({ data }: { data: Record
{teamData.ctaDescription || '无论您处于数字化转型的哪个阶段,我们都愿意成为您的长期伙伴。'}
diff --git a/src/app/error.tsx b/src/app/error.tsx
index 1eea4a7..6e51443 100644
--- a/src/app/error.tsx
+++ b/src/app/error.tsx
@@ -3,7 +3,8 @@
import { useEffect } from 'react';
import { StaticLink } from '@/components/ui/static-link';
import { Button } from '@/components/ui/button';
-import { Home, RefreshCw, AlertTriangle } from 'lucide-react';
+import { CTAButton } from '@/components/ui/cta-button';
+import { RefreshCw, AlertTriangle } from 'lucide-react';
export default function Error({
error,
@@ -59,16 +60,9 @@ export default function Error({
重试
-
+
+ 返回首页
+
diff --git a/src/app/not-found-content.tsx b/src/app/not-found-content.tsx
index 18b682c..d48f3ce 100644
--- a/src/app/not-found-content.tsx
+++ b/src/app/not-found-content.tsx
@@ -2,7 +2,8 @@
import { StaticLink } from '@/components/ui/static-link';
import { Button } from '@/components/ui/button';
-import { Home, ArrowLeft, Search } from 'lucide-react';
+import { CTAButton } from '@/components/ui/cta-button';
+import { ArrowLeft, Search } from 'lucide-react';
export function NotFoundContent() {
return (
@@ -24,12 +25,7 @@ export function NotFoundContent() {
-
+
返回首页