feat(ui): CTAButton 可复用组件 + P5 全站 CTA 签名切换(17 页面 27 处)
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 范式
This commit is contained in:
@@ -2,10 +2,9 @@
|
|||||||
|
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import { ScrollReveal } from '@/components/ui/scroll-reveal';
|
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 { BrandCalligraphyName } from '@/components/ui/brand-calligraphy-name';
|
||||||
import {
|
import {
|
||||||
ArrowRight,
|
|
||||||
Target,
|
Target,
|
||||||
Eye,
|
Eye,
|
||||||
Heart,
|
Heart,
|
||||||
@@ -14,7 +13,6 @@ import {
|
|||||||
Users,
|
Users,
|
||||||
Building2,
|
Building2,
|
||||||
Globe,
|
Globe,
|
||||||
ChevronRight,
|
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
|
|
||||||
const EASE_OUT = [0.22, 1, 0.36, 1] as const;
|
const EASE_OUT = [0.22, 1, 0.36, 1] as const;
|
||||||
@@ -356,20 +354,10 @@ export default function BrandContent() {
|
|||||||
从一次深入的业务诊断开始,开启您的数据驱动之旅。
|
从一次深入的业务诊断开始,开启您的数据驱动之旅。
|
||||||
</p>
|
</p>
|
||||||
<div className="flex flex-col sm:flex-row gap-4 sm:gap-6">
|
<div className="flex flex-col sm:flex-row gap-4 sm:gap-6">
|
||||||
<StaticLink
|
<CTAButton href="/contact">预约咨询</CTAButton>
|
||||||
href="/contact"
|
<CTAButton href="/cases" variant="dark">
|
||||||
className="group inline-flex items-center justify-center gap-3 px-10 py-5 font-semibold text-base bg-brand text-white rounded-full transition-all duration-300 hover:bg-brand/90 min-h-[52px]"
|
|
||||||
>
|
|
||||||
预约咨询
|
|
||||||
<ArrowRight className="w-5 h-5 transition-transform duration-300 group-hover:translate-x-1" />
|
|
||||||
</StaticLink>
|
|
||||||
<StaticLink
|
|
||||||
href="/cases"
|
|
||||||
className="group inline-flex items-center justify-center gap-2 px-10 py-5 font-semibold text-base text-dark-text-primary border border-dark-border rounded-full hover:border-dark-text-muted transition-all duration-300 min-h-[52px]"
|
|
||||||
>
|
|
||||||
查看客户案例
|
查看客户案例
|
||||||
<ChevronRight className="w-4 h-4 transition-transform duration-300 group-hover:translate-x-1" />
|
</CTAButton>
|
||||||
</StaticLink>
|
|
||||||
</div>
|
</div>
|
||||||
</ScrollReveal>
|
</ScrollReveal>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import { useState, useMemo } from 'react';
|
import { useState, useMemo } from 'react';
|
||||||
import { motion } from 'framer-motion';
|
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 { Building2, ArrowUpRight, Target, Lightbulb, Filter } from 'lucide-react';
|
||||||
import {
|
import {
|
||||||
type CaseStudy,
|
type CaseStudy,
|
||||||
@@ -309,19 +309,17 @@ export default function CasesContentV3({
|
|||||||
让我们聊聊您的挑战,看看能创造什么样的成果。
|
让我们聊聊您的挑战,看看能创造什么样的成果。
|
||||||
</p>
|
</p>
|
||||||
<div className="flex flex-col sm:flex-row gap-4 sm:gap-6">
|
<div className="flex flex-col sm:flex-row gap-4 sm:gap-6">
|
||||||
<StaticLink
|
<CTAButton href="/contact" dataTestId="cases-bottom-primary-cta" className="min-h-[52px]">
|
||||||
href="/contact"
|
|
||||||
className="group inline-flex items-center justify-center gap-3 px-10 py-5 font-semibold text-base bg-brand text-white rounded-full transition-all duration-300 hover:bg-brand/90 min-h-[52px] touch-manipulation"
|
|
||||||
>
|
|
||||||
预约咨询
|
预约咨询
|
||||||
<ArrowUpRight className="w-4 h-4 transition-transform duration-300 group-hover:translate-x-0.5 group-hover:-translate-y-0.5" />
|
</CTAButton>
|
||||||
</StaticLink>
|
<CTAButton
|
||||||
<StaticLink
|
|
||||||
href="/services"
|
href="/services"
|
||||||
className="inline-flex items-center justify-center gap-3 px-10 py-5 font-semibold text-base text-ink border border-border-primary rounded-full hover:border-border-secondary hover:bg-white transition-all duration-300 min-h-[52px] touch-manipulation"
|
variant="secondary"
|
||||||
|
dataTestId="cases-bottom-secondary-cta"
|
||||||
|
className="min-h-[52px]"
|
||||||
>
|
>
|
||||||
了解服务
|
了解服务
|
||||||
</StaticLink>
|
</CTAButton>
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -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 { ArrowRight, Lightbulb, Database, Layers, Code, Puzzle, Server, Quote, Calendar } from 'lucide-react';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { EASE_OUT } from '@/components/ui/page-decoration';
|
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 { COMPANY_INFO, TRUST_SIGNALS, EARLY_ACCESS } from '@/lib/constants';
|
||||||
import { useSiteConfig } from '@/lib/site-config';
|
import { useSiteConfig } from '@/lib/site-config';
|
||||||
import { type NewsItem } from '@/lib/constants/news';
|
import { type NewsItem } from '@/lib/constants/news';
|
||||||
@@ -133,31 +134,7 @@ function StyledTitle({ lines }: { lines: (string | undefined)[] }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== 品牌 CTA 按钮(Pill 胶囊化,对标 Accenture > CTA)=====
|
// ===== 品牌 CTA 按钮已抽离为可复用 UI 组件:@/components/ui/cta-button =====
|
||||||
function CTAButton({ children, href, variant = 'primary', className, dataTestId }: {
|
|
||||||
children: React.ReactNode;
|
|
||||||
href: string;
|
|
||||||
variant?: 'primary' | 'secondary';
|
|
||||||
className?: string;
|
|
||||||
dataTestId?: string;
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<a
|
|
||||||
href={href}
|
|
||||||
data-testid={dataTestId}
|
|
||||||
className={cn(
|
|
||||||
'group inline-flex items-center gap-3 px-10 py-4 font-semibold text-base transition-all duration-200 rounded-full',
|
|
||||||
variant === 'primary'
|
|
||||||
? 'bg-brand text-white hover:bg-brand-hover'
|
|
||||||
: 'text-ink border border-ink/20 hover:border-ink/40 hover:bg-ink/5',
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<span>{children}</span>
|
|
||||||
<ArrowRight className="w-5 h-5 transition-transform duration-300 group-hover:translate-x-2" />
|
|
||||||
</a>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ===== Hero 区:全出血深色画布(对标 Accenture 纯黑 Hero)+ 品牌红单电压 =====
|
// ===== Hero 区:全出血深色画布(对标 Accenture 纯黑 Hero)+ 品牌红单电压 =====
|
||||||
function HeroSection({ heroData, stats }: {
|
function HeroSection({ heroData, stats }: {
|
||||||
|
|||||||
@@ -2,10 +2,9 @@
|
|||||||
|
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import { ScrollReveal, StaggerReveal } from '@/components/ui/scroll-reveal';
|
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 { EASE_OUT } from '@/components/ui/page-decoration';
|
||||||
import { Button } from '@/components/ui/button';
|
import { CheckCircle2 } from 'lucide-react';
|
||||||
import { ArrowRight, CheckCircle2 } from 'lucide-react';
|
|
||||||
|
|
||||||
interface MethodologyPhase {
|
interface MethodologyPhase {
|
||||||
number?: number;
|
number?: number;
|
||||||
@@ -154,15 +153,10 @@ export function MethodologyContent({ data }: MethodologyContentProps) {
|
|||||||
我们的咨询服务贯穿方法论的每个阶段,从战略规划到落地实施全程陪伴。
|
我们的咨询服务贯穿方法论的每个阶段,从战略规划到落地实施全程陪伴。
|
||||||
</p>
|
</p>
|
||||||
<div className="flex flex-wrap justify-center gap-6">
|
<div className="flex flex-wrap justify-center gap-6">
|
||||||
<Button size="xl" asChild>
|
<CTAButton href="/services">了解我们的服务</CTAButton>
|
||||||
<StaticLink href="/services">
|
<CTAButton href="/contact" variant="secondary">
|
||||||
了解我们的服务
|
联系我们
|
||||||
<ArrowRight className="w-5 h-5 ml-2" />
|
</CTAButton>
|
||||||
</StaticLink>
|
|
||||||
</Button>
|
|
||||||
<Button size="xl" variant="outline" asChild>
|
|
||||||
<StaticLink href="/contact">联系我们</StaticLink>
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</ScrollReveal>
|
</ScrollReveal>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { StaticLink } from '@/components/ui/static-link';
|
import { StaticLink } from '@/components/ui/static-link';
|
||||||
|
import { CTAButton } from '@/components/ui/cta-button';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Card } from '@/components/ui/card';
|
import { Card } from '@/components/ui/card';
|
||||||
@@ -139,11 +140,9 @@ export function NewsDetailClient({ news }: NewsDetailClientProps) {
|
|||||||
返回新闻列表
|
返回新闻列表
|
||||||
</Button>
|
</Button>
|
||||||
</StaticLink>
|
</StaticLink>
|
||||||
<StaticLink href="/contact">
|
<CTAButton href="/contact" dataTestId="news-slug-detail-cta" className="px-8 py-3 text-base font-medium">
|
||||||
<Button size="lg" className="bg-[var(--color-brand)] hover:bg-[var(--color-brand-hover)] text-white">
|
联系我们
|
||||||
联系我们
|
</CTAButton>
|
||||||
</Button>
|
|
||||||
</StaticLink>
|
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { motion } from 'framer-motion';
|
|||||||
import { ScrollReveal, StaggerReveal } from '@/components/ui/scroll-reveal';
|
import { ScrollReveal, StaggerReveal } from '@/components/ui/scroll-reveal';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { StaticLink } from '@/components/ui/static-link';
|
import { StaticLink } from '@/components/ui/static-link';
|
||||||
|
import { CTAButton } from '@/components/ui/cta-button';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { Search, Calendar, ArrowRight, Newspaper, ChevronLeft, ChevronRight } from 'lucide-react';
|
import { Search, Calendar, ArrowRight, Newspaper, ChevronLeft, ChevronRight } from 'lucide-react';
|
||||||
@@ -249,13 +250,9 @@ export default function NewsContentV3({ news, pageCopy }: { news?: NewsItem[]; p
|
|||||||
无论您有合作意向还是想探讨技术趋势,我们都欢迎与您交流。
|
无论您有合作意向还是想探讨技术趋势,我们都欢迎与您交流。
|
||||||
</p>
|
</p>
|
||||||
<div className="mt-12 flex flex-col sm:flex-row justify-center gap-4 sm:gap-6">
|
<div className="mt-12 flex flex-col sm:flex-row justify-center gap-4 sm:gap-6">
|
||||||
<a
|
<CTAButton href="/contact" dataTestId="news-bottom-cta">
|
||||||
href="/contact"
|
|
||||||
className="group inline-flex items-center justify-center gap-3 px-12 py-6 font-semibold text-base bg-brand text-white rounded-full transition-all duration-300 hover:bg-brand/90"
|
|
||||||
>
|
|
||||||
联系我们
|
联系我们
|
||||||
<ArrowRight className="w-4 h-4 transition-transform duration-300 group-hover:translate-x-1" />
|
</CTAButton>
|
||||||
</a>
|
|
||||||
<a
|
<a
|
||||||
href="/about"
|
href="/about"
|
||||||
className="inline-flex items-center justify-center gap-3 px-12 py-6 font-semibold text-base text-ink border border-border-primary rounded-full hover:border-border-secondary hover:bg-bg-secondary transition-all duration-300"
|
className="inline-flex items-center justify-center gap-3 px-12 py-6 font-semibold text-base text-ink border border-border-primary rounded-full hover:border-border-secondary hover:bg-bg-secondary transition-all duration-300"
|
||||||
|
|||||||
@@ -2,8 +2,9 @@
|
|||||||
|
|
||||||
import { ScrollReveal } from '@/components/ui/scroll-reveal';
|
import { ScrollReveal } from '@/components/ui/scroll-reveal';
|
||||||
import { StaticLink } from '@/components/ui/static-link';
|
import { StaticLink } from '@/components/ui/static-link';
|
||||||
|
import { CTAButton } from '@/components/ui/cta-button';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Calendar, ArrowLeft, Newspaper, ArrowRight } from 'lucide-react';
|
import { Calendar, ArrowLeft, Newspaper } from 'lucide-react';
|
||||||
import { SectionLabel, EASE_OUT } from '@/components/ui/page-decoration';
|
import { SectionLabel, EASE_OUT } from '@/components/ui/page-decoration';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import type { NewsItem } from '@/lib/constants/news';
|
import type { NewsItem } from '@/lib/constants/news';
|
||||||
@@ -153,13 +154,9 @@ export default function NewsDetailContentV3({ news, relatedNews = [] }: NewsDeta
|
|||||||
<ArrowLeft className="w-4 h-4" />
|
<ArrowLeft className="w-4 h-4" />
|
||||||
返回新闻列表
|
返回新闻列表
|
||||||
</StaticLink>
|
</StaticLink>
|
||||||
<StaticLink
|
<CTAButton href="/contact" dataTestId="news-detail-bottom-cta" className="px-8 py-4 font-medium text-base">
|
||||||
href="/contact"
|
|
||||||
className="group inline-flex items-center justify-center gap-2 px-8 py-4 font-medium text-base bg-brand text-white rounded-full transition-all duration-300 hover:bg-brand/90"
|
|
||||||
>
|
|
||||||
联系我们
|
联系我们
|
||||||
<ArrowRight className="w-4 h-4 transition-transform duration-300 group-hover:translate-x-1" />
|
</CTAButton>
|
||||||
</StaticLink>
|
|
||||||
</div>
|
</div>
|
||||||
</ScrollReveal>
|
</ScrollReveal>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { ScrollReveal, StaggerReveal } from '@/components/ui/scroll-reveal';
|
import { ScrollReveal, StaggerReveal } from '@/components/ui/scroll-reveal';
|
||||||
import { StaticLink } from '@/components/ui/static-link';
|
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { CTAButton } from '@/components/ui/cta-button';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import {
|
import {
|
||||||
ArrowRight,
|
|
||||||
Zap,
|
Zap,
|
||||||
Shield,
|
Shield,
|
||||||
TrendingUp,
|
TrendingUp,
|
||||||
@@ -153,12 +152,9 @@ export default function ErpUpgradeContentV2({ item }: ErpUpgradeContentV2Props)
|
|||||||
|
|
||||||
<ScrollReveal delay={0.3}>
|
<ScrollReveal delay={0.3}>
|
||||||
<div className="mt-12 flex flex-wrap gap-4">
|
<div className="mt-12 flex flex-wrap gap-4">
|
||||||
<Button size="xl" asChild>
|
<CTAButton href="/contact" dataTestId="erp-upgrade-hero-primary">
|
||||||
<StaticLink href="/contact">
|
免费诊断咨询
|
||||||
免费诊断咨询
|
</CTAButton>
|
||||||
<ArrowRight className="w-5 h-5 ml-2" />
|
|
||||||
</StaticLink>
|
|
||||||
</Button>
|
|
||||||
<Button size="xl" variant="outline" asChild>
|
<Button size="xl" variant="outline" asChild>
|
||||||
<a href="#features">了解功能模块</a>
|
<a href="#features">了解功能模块</a>
|
||||||
</Button>
|
</Button>
|
||||||
@@ -404,15 +400,12 @@ export default function ErpUpgradeContentV2({ item }: ErpUpgradeContentV2Props)
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="flex flex-wrap gap-6 justify-center">
|
<div className="flex flex-wrap gap-6 justify-center">
|
||||||
<Button size="xl" asChild>
|
<CTAButton href="/contact" dataTestId="erp-upgrade-bottom-primary">
|
||||||
<StaticLink href="/contact">
|
预约免费诊断
|
||||||
预约免费诊断
|
</CTAButton>
|
||||||
<ArrowRight className="w-5 h-5 ml-2" />
|
<CTAButton href="/products/erp" variant="secondary" dataTestId="erp-upgrade-bottom-secondary">
|
||||||
</StaticLink>
|
查看产品详情
|
||||||
</Button>
|
</CTAButton>
|
||||||
<Button size="xl" variant="outline" asChild>
|
|
||||||
<StaticLink href="/products/erp">查看产品详情</StaticLink>
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</ScrollReveal>
|
</ScrollReveal>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import { ScrollReveal, StaggerReveal } from '@/components/ui/scroll-reveal';
|
import { ScrollReveal, StaggerReveal } from '@/components/ui/scroll-reveal';
|
||||||
import { StaticLink } from '@/components/ui/static-link';
|
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 { ArrowRight, Package, Check, Settings, Users, FileText, Cpu, Shield, TrendingUp } from 'lucide-react';
|
||||||
import { SectionLabel, EASE_OUT } from '@/components/ui/page-decoration';
|
import { SectionLabel, EASE_OUT } from '@/components/ui/page-decoration';
|
||||||
import type { Product } from '@/lib/constants/products';
|
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 }}
|
transition={{ duration: 0.9, delay: 0.55, ease: EASE_OUT }}
|
||||||
className="flex flex-wrap gap-6"
|
className="flex flex-wrap gap-6"
|
||||||
>
|
>
|
||||||
<Button size="xl" asChild>
|
<CTAButton href="/contact" dataTestId="product-detail-hero-primary">
|
||||||
<StaticLink href="/contact">
|
预约体验
|
||||||
预约体验
|
</CTAButton>
|
||||||
<ArrowRight className="w-5 h-5 ml-2" />
|
<CTAButton href="/products" variant="secondary" dataTestId="product-detail-hero-secondary">
|
||||||
</StaticLink>
|
查看更多产品
|
||||||
</Button>
|
</CTAButton>
|
||||||
<Button size="xl" variant="outline" asChild>
|
|
||||||
<StaticLink href="/products">查看更多产品</StaticLink>
|
|
||||||
</Button>
|
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@@ -403,15 +400,12 @@ function CTASection({ product }: CTASectionProps) {
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="flex flex-wrap justify-center gap-6">
|
<div className="flex flex-wrap justify-center gap-6">
|
||||||
<Button size="xl" asChild>
|
<CTAButton href="/contact" dataTestId="product-detail-bottom-primary">
|
||||||
<StaticLink href="/contact">
|
预约演示
|
||||||
预约演示
|
</CTAButton>
|
||||||
<ArrowRight className="w-5 h-5 ml-2" />
|
<CTAButton href="/products" variant="secondary" dataTestId="product-detail-bottom-secondary">
|
||||||
</StaticLink>
|
查看更多产品
|
||||||
</Button>
|
</CTAButton>
|
||||||
<Button size="xl" variant="outline" asChild>
|
|
||||||
<StaticLink href="/products">查看更多产品</StaticLink>
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 站内入口:ERP 升级专题(与 erp-upgrade-content-v2 的反向链接形成互链闭环) */}
|
{/* 站内入口:ERP 升级专题(与 erp-upgrade-content-v2 的反向链接形成互链闭环) */}
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import { useRef, useState, useEffect } from 'react';
|
|||||||
import { motion, useInView } from 'framer-motion';
|
import { motion, useInView } from 'framer-motion';
|
||||||
import { ScrollReveal, StaggerReveal } from '@/components/ui/scroll-reveal';
|
import { ScrollReveal, StaggerReveal } from '@/components/ui/scroll-reveal';
|
||||||
import { EASE_OUT } from '@/components/ui/page-decoration';
|
import { EASE_OUT } from '@/components/ui/page-decoration';
|
||||||
import { Button } from '@/components/ui/button';
|
|
||||||
import { StaticLink } from '@/components/ui/static-link';
|
import { StaticLink } from '@/components/ui/static-link';
|
||||||
|
import { CTAButton } from '@/components/ui/cta-button';
|
||||||
import { BentoItem } from '@/components/sections/bento-grid';
|
import { BentoItem } from '@/components/sections/bento-grid';
|
||||||
import { ArrowRight, ArrowUpRight, Package, Cpu, TrendingUp, Users, Settings, FileText } from 'lucide-react';
|
import { ArrowRight, ArrowUpRight, Package, Cpu, TrendingUp, Users, Settings, FileText } from 'lucide-react';
|
||||||
import type { Product } from '@/lib/constants/products';
|
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 }}
|
transition={{ duration: 1, delay: 0.6, ease: EASE_OUT }}
|
||||||
className="flex flex-wrap gap-6"
|
className="flex flex-wrap gap-6"
|
||||||
>
|
>
|
||||||
<Button size="xl" asChild>
|
<CTAButton href="/services" dataTestId="products-hero-primary">
|
||||||
<StaticLink href="/services">
|
{ctaPrimary}
|
||||||
{ctaPrimary}
|
</CTAButton>
|
||||||
<ArrowRight className="w-5 h-5 ml-2" />
|
<CTAButton href="/solutions" variant="secondary" dataTestId="products-hero-secondary">
|
||||||
</StaticLink>
|
{ctaSecondary}
|
||||||
</Button>
|
</CTAButton>
|
||||||
<Button size="xl" variant="outline" asChild>
|
|
||||||
<StaticLink href="/solutions">{ctaSecondary}</StaticLink>
|
|
||||||
</Button>
|
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
<div ref={statsRef} className="grid grid-cols-3 gap-12 mt-24 pt-16 border-t border-border-primary max-w-2xl">
|
<div ref={statsRef} className="grid grid-cols-3 gap-12 mt-24 pt-16 border-t border-border-primary max-w-2xl">
|
||||||
@@ -497,15 +494,12 @@ function CTASection({ pageCopy }: { pageCopy?: PageCopy | null }) {
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="flex flex-wrap justify-center gap-6">
|
<div className="flex flex-wrap justify-center gap-6">
|
||||||
<Button size="xl" asChild>
|
<CTAButton href="/services" dataTestId="products-cta-section-primary">
|
||||||
<StaticLink href="/services">
|
了解我们的服务
|
||||||
了解我们的服务
|
</CTAButton>
|
||||||
<ArrowRight className="w-5 h-5 ml-2" />
|
<CTAButton href="/contact" variant="secondary" dataTestId="products-cta-section-secondary">
|
||||||
</StaticLink>
|
联系我们
|
||||||
</Button>
|
</CTAButton>
|
||||||
<Button size="xl" variant="outline" asChild>
|
|
||||||
<StaticLink href="/contact">联系我们</StaticLink>
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</ScrollReveal>
|
</ScrollReveal>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,12 +2,13 @@
|
|||||||
|
|
||||||
import { getHeroTheme } from '@/lib/constants/hero-themes';
|
import { getHeroTheme } from '@/lib/constants/hero-themes';
|
||||||
import { StaticLink } from '@/components/ui/static-link';
|
import { StaticLink } from '@/components/ui/static-link';
|
||||||
|
import { CTAButton } from '@/components/ui/cta-button';
|
||||||
import { DetailHero } from '@/components/detail/detail-hero';
|
import { DetailHero } from '@/components/detail/detail-hero';
|
||||||
import { DetailTrustSection } from '@/components/detail/detail-trust-section';
|
import { DetailTrustSection } from '@/components/detail/detail-trust-section';
|
||||||
import { DetailCTASection } from '@/components/detail/detail-cta-section';
|
import { DetailCTASection } from '@/components/detail/detail-cta-section';
|
||||||
import { DetailSwipeNav } from '@/components/ui/detail-swipe-nav';
|
import { DetailSwipeNav } from '@/components/ui/detail-swipe-nav';
|
||||||
import { motion } from 'framer-motion';
|
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 { ContentItem } from '@/lib/cms/types';
|
||||||
import type { CaseStudy, DataProof, Certification, ComplianceCertification, TechnicalParameter } from '@/lib/constants/products';
|
import type { CaseStudy, DataProof, Certification, ComplianceCertification, TechnicalParameter } from '@/lib/constants/products';
|
||||||
|
|
||||||
@@ -113,13 +114,9 @@ export function StandaloneProductClient({ item }: StandaloneProductClientProps)
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="flex flex-col sm:flex-row items-center justify-center gap-4">
|
<div className="flex flex-col sm:flex-row items-center justify-center gap-4">
|
||||||
<StaticLink
|
<CTAButton href="/contact" dataTestId="standalone-hero-primary" className="px-8 py-3 text-sm">
|
||||||
href="/contact"
|
获取上线通知
|
||||||
className="inline-flex items-center gap-2 rounded-full bg-brand px-8 py-3 text-sm font-semibold text-white shadow-md transition-all hover:bg-brand/90 hover:shadow-lg"
|
</CTAButton>
|
||||||
>
|
|
||||||
获取上线通知
|
|
||||||
<ArrowRight className="h-4 w-4" />
|
|
||||||
</StaticLink>
|
|
||||||
<StaticLink
|
<StaticLink
|
||||||
href="/products"
|
href="/products"
|
||||||
className="inline-flex items-center rounded-full border border-border-primary px-6 py-3 text-sm font-semibold text-text-secondary transition-all hover:border-border-secondary hover:bg-bg-secondary"
|
className="inline-flex items-center rounded-full border border-border-primary px-6 py-3 text-sm font-semibold text-text-secondary transition-all hover:border-border-secondary hover:bg-bg-secondary"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import { StaticLink } from '@/components/ui/static-link';
|
import { CTAButton } from '@/components/ui/cta-button';
|
||||||
import { ArrowUpRight, CheckCircle2, Zap, Clock, Users, Award, TrendingUp, Shield } from 'lucide-react';
|
import { ArrowUpRight, CheckCircle2, Zap, Clock, Users, Award, TrendingUp, Shield } from 'lucide-react';
|
||||||
import type { LucideIcon } from 'lucide-react';
|
import type { LucideIcon } from 'lucide-react';
|
||||||
import type { Service, CaseStudy } from '@/lib/constants/services';
|
import type { Service, CaseStudy } from '@/lib/constants/services';
|
||||||
@@ -81,13 +81,9 @@ function DetailHero({ service }: { service: Service }) {
|
|||||||
transition={{ duration: 0.8, delay: 0.9, ease: EASE_OUT }}
|
transition={{ duration: 0.8, delay: 0.9, ease: EASE_OUT }}
|
||||||
className="flex flex-col sm:flex-row gap-4 sm:gap-6"
|
className="flex flex-col sm:flex-row gap-4 sm:gap-6"
|
||||||
>
|
>
|
||||||
<StaticLink
|
<CTAButton href="/contact" dataTestId="service-detail-hero-primary" className="min-h-[52px]">
|
||||||
href="/contact"
|
|
||||||
className="group inline-flex items-center justify-center gap-3 px-10 py-5 font-semibold text-base bg-brand text-white rounded-full transition-all duration-300 hover:bg-brand/90 min-h-[52px] touch-manipulation"
|
|
||||||
>
|
|
||||||
免费咨询
|
免费咨询
|
||||||
<ArrowUpRight className="w-4 h-4 transition-transform duration-300 group-hover:translate-x-0.5 group-hover:-translate-y-0.5" />
|
</CTAButton>
|
||||||
</StaticLink>
|
|
||||||
<a
|
<a
|
||||||
href="#cases"
|
href="#cases"
|
||||||
className="inline-flex items-center justify-center gap-3 px-10 py-5 font-semibold text-base text-ink border border-border-primary rounded-full hover:border-ink/30 transition-all duration-300 min-h-[52px] touch-manipulation"
|
className="inline-flex items-center justify-center gap-3 px-10 py-5 font-semibold text-base text-ink border border-border-primary rounded-full hover:border-ink/30 transition-all duration-300 min-h-[52px] touch-manipulation"
|
||||||
@@ -337,19 +333,12 @@ function CTASection() {
|
|||||||
首次咨询免费,没有任何销售压力。
|
首次咨询免费,没有任何销售压力。
|
||||||
</p>
|
</p>
|
||||||
<div className="flex flex-col sm:flex-row gap-4 sm:gap-6">
|
<div className="flex flex-col sm:flex-row gap-4 sm:gap-6">
|
||||||
<StaticLink
|
<CTAButton href="/contact" dataTestId="service-detail-bottom-primary" className="min-h-[52px]">
|
||||||
href="/contact"
|
|
||||||
className="group inline-flex items-center justify-center gap-3 px-10 py-5 font-semibold text-base bg-brand text-white rounded-full transition-all duration-300 hover:bg-brand/90 min-h-[52px] touch-manipulation"
|
|
||||||
>
|
|
||||||
预约免费咨询
|
预约免费咨询
|
||||||
<ArrowUpRight className="w-4 h-4 transition-transform duration-300 group-hover:translate-x-0.5 group-hover:-translate-y-0.5" />
|
</CTAButton>
|
||||||
</StaticLink>
|
<CTAButton href="/services" variant="secondary" dataTestId="service-detail-bottom-secondary" className="min-h-[52px]">
|
||||||
<StaticLink
|
|
||||||
href="/services"
|
|
||||||
className="inline-flex items-center justify-center gap-3 px-10 py-5 font-semibold text-base text-ink border border-border-primary rounded-full hover:border-ink/30 transition-all duration-300 min-h-[52px] touch-manipulation"
|
|
||||||
>
|
|
||||||
返回服务列表
|
返回服务列表
|
||||||
</StaticLink>
|
</CTAButton>
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ jest.mock('lucide-react', () => {
|
|||||||
return Icon;
|
return Icon;
|
||||||
};
|
};
|
||||||
return {
|
return {
|
||||||
|
ArrowRight: mockIcon('arrow-right'),
|
||||||
ArrowUpRight: mockIcon('arrow-up-right'),
|
ArrowUpRight: mockIcon('arrow-up-right'),
|
||||||
CheckCircle2: mockIcon('check-circle2'),
|
CheckCircle2: mockIcon('check-circle2'),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import { ArrowUpRight, CheckCircle2 } from 'lucide-react';
|
import { ArrowUpRight, CheckCircle2 } from 'lucide-react';
|
||||||
import { StaticLink } from '@/components/ui/static-link';
|
import { StaticLink } from '@/components/ui/static-link';
|
||||||
|
import { CTAButton } from '@/components/ui/cta-button';
|
||||||
import type { Service } from '@/lib/constants/services';
|
import type { Service } from '@/lib/constants/services';
|
||||||
|
|
||||||
const EASE_OUT = [0.22, 1, 0.36, 1] as const;
|
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 }}
|
transition={{ duration: 0.8, delay: 0.7, ease: EASE_OUT }}
|
||||||
className="flex flex-col sm:flex-row gap-4 sm:gap-6"
|
className="flex flex-col sm:flex-row gap-4 sm:gap-6"
|
||||||
>
|
>
|
||||||
<StaticLink
|
<CTAButton href="/contact" dataTestId="services-hero-primary" className="min-h-[52px]">
|
||||||
href="/contact"
|
|
||||||
className="group inline-flex items-center justify-center gap-3 px-10 py-5 font-semibold text-base bg-brand text-white rounded-full transition-all duration-300 hover:bg-brand/90 min-h-[52px] touch-manipulation"
|
|
||||||
>
|
|
||||||
{ctaPrimary}
|
{ctaPrimary}
|
||||||
<ArrowUpRight className="w-4 h-4 transition-transform duration-300 group-hover:translate-x-0.5 group-hover:-translate-y-0.5" />
|
</CTAButton>
|
||||||
</StaticLink>
|
<CTAButton href="/products" variant="secondary" dataTestId="services-hero-secondary" className="min-h-[52px]">
|
||||||
<StaticLink
|
|
||||||
href="/products"
|
|
||||||
className="inline-flex items-center justify-center gap-3 px-10 py-5 font-semibold text-base text-ink border border-border-primary rounded-full hover:border-ink/30 transition-all duration-300 min-h-[52px] touch-manipulation"
|
|
||||||
>
|
|
||||||
{ctaSecondary}
|
{ctaSecondary}
|
||||||
</StaticLink>
|
</CTAButton>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
<motion.div
|
<motion.div
|
||||||
@@ -364,19 +358,12 @@ function CTASection({ pageCopy }: { pageCopy?: PageCopy | null }) {
|
|||||||
))}
|
))}
|
||||||
</p>
|
</p>
|
||||||
<div className="flex flex-col sm:flex-row gap-4 sm:gap-6">
|
<div className="flex flex-col sm:flex-row gap-4 sm:gap-6">
|
||||||
<StaticLink
|
<CTAButton href="/contact" dataTestId="services-bottom-primary" className="min-h-[52px]">
|
||||||
href="/contact"
|
|
||||||
className="group inline-flex items-center justify-center gap-3 px-10 py-5 font-semibold text-base bg-brand text-white rounded-full transition-all duration-300 hover:bg-brand/90 min-h-[52px] touch-manipulation"
|
|
||||||
>
|
|
||||||
预约免费咨询
|
预约免费咨询
|
||||||
<ArrowUpRight className="w-4 h-4 transition-transform duration-300 group-hover:translate-x-0.5 group-hover:-translate-y-0.5" />
|
</CTAButton>
|
||||||
</StaticLink>
|
<CTAButton href="/team" variant="secondary" dataTestId="services-bottom-secondary" className="min-h-[52px]">
|
||||||
<StaticLink
|
|
||||||
href="/team"
|
|
||||||
className="inline-flex items-center justify-center gap-3 px-10 py-5 font-semibold text-base text-ink border border-border-primary rounded-full hover:border-ink/30 transition-all duration-300 min-h-[52px] touch-manipulation"
|
|
||||||
>
|
|
||||||
了解我们的团队
|
了解我们的团队
|
||||||
</StaticLink>
|
</CTAButton>
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,9 +2,8 @@
|
|||||||
|
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import { ScrollReveal, StaggerReveal } from '@/components/ui/scroll-reveal';
|
import { ScrollReveal, StaggerReveal } from '@/components/ui/scroll-reveal';
|
||||||
import { StaticLink } from '@/components/ui/static-link';
|
import { CTAButton } from '@/components/ui/cta-button';
|
||||||
import { Button } from '@/components/ui/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 { 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 type { Solution } from '@/lib/constants/solutions';
|
import type { Solution } from '@/lib/constants/solutions';
|
||||||
import type { Product } from '@/lib/constants/products';
|
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 }}
|
transition={{ duration: 0.9, delay: 0.45, ease: EASE_OUT }}
|
||||||
className="flex flex-wrap gap-6"
|
className="flex flex-wrap gap-6"
|
||||||
>
|
>
|
||||||
<Button size="xl" asChild>
|
<CTAButton href="/contact" dataTestId="solution-detail-hero-primary">
|
||||||
<StaticLink href="/contact">
|
获取方案详情
|
||||||
获取方案详情
|
</CTAButton>
|
||||||
<ArrowRight className="w-5 h-5 ml-2" />
|
<CTAButton href="/products" variant="secondary" dataTestId="solution-detail-hero-secondary">
|
||||||
</StaticLink>
|
查看产品
|
||||||
</Button>
|
</CTAButton>
|
||||||
<Button size="xl" variant="outline" asChild>
|
|
||||||
<StaticLink href="/products">查看产品</StaticLink>
|
|
||||||
</Button>
|
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@@ -491,15 +487,12 @@ function CTASection({ solution }: CTASectionProps) {
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="flex flex-wrap justify-center gap-6">
|
<div className="flex flex-wrap justify-center gap-6">
|
||||||
<Button size="xl" asChild>
|
<CTAButton href="/contact" dataTestId="solution-detail-bottom-primary">
|
||||||
<StaticLink href="/contact">
|
预约咨询
|
||||||
预约咨询
|
</CTAButton>
|
||||||
<ArrowRight className="w-5 h-5 ml-2" />
|
<CTAButton href="/solutions" variant="secondary" dataTestId="solution-detail-bottom-secondary">
|
||||||
</StaticLink>
|
查看更多方案
|
||||||
</Button>
|
</CTAButton>
|
||||||
<Button size="xl" variant="outline" asChild>
|
|
||||||
<StaticLink href="/solutions">查看更多方案</StaticLink>
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</ScrollReveal>
|
</ScrollReveal>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ jest.mock('lucide-react', () => {
|
|||||||
return Icon;
|
return Icon;
|
||||||
};
|
};
|
||||||
return {
|
return {
|
||||||
|
ArrowRight: mockIcon('arrow-right'),
|
||||||
ArrowUpRight: mockIcon('arrow-up-right'),
|
ArrowUpRight: mockIcon('arrow-up-right'),
|
||||||
Factory: mockIcon('factory'),
|
Factory: mockIcon('factory'),
|
||||||
ShoppingCart: mockIcon('shopping-cart'),
|
ShoppingCart: mockIcon('shopping-cart'),
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import { ArrowUpRight, Factory, ShoppingCart, Heart, GraduationCap, Shield, Truck, type LucideIcon } from 'lucide-react';
|
import { ArrowUpRight, Factory, ShoppingCart, Heart, GraduationCap, Shield, Truck, type LucideIcon } from 'lucide-react';
|
||||||
import { StaticLink } from '@/components/ui/static-link';
|
import { StaticLink } from '@/components/ui/static-link';
|
||||||
|
import { CTAButton } from '@/components/ui/cta-button';
|
||||||
import { type Solution } from '@/lib/constants/solutions';
|
import { type Solution } from '@/lib/constants/solutions';
|
||||||
import type { Product } from '@/lib/constants/products';
|
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 }}
|
transition={{ duration: 0.8, delay: 0.7, ease: EASE_OUT }}
|
||||||
className="flex flex-col sm:flex-row gap-4 sm:gap-6"
|
className="flex flex-col sm:flex-row gap-4 sm:gap-6"
|
||||||
>
|
>
|
||||||
<StaticLink
|
<CTAButton href="/contact" dataTestId="solutions-hero-primary" className="min-h-[52px]">
|
||||||
href="/contact"
|
|
||||||
className="group inline-flex items-center justify-center gap-3 px-10 py-5 font-semibold text-base bg-brand text-white rounded-full transition-all duration-300 hover:bg-brand/90 min-h-[52px] touch-manipulation"
|
|
||||||
>
|
|
||||||
{ctaPrimary}
|
{ctaPrimary}
|
||||||
<ArrowUpRight className="w-4 h-4 transition-transform duration-300 group-hover:translate-x-0.5 group-hover:-translate-y-0.5" />
|
</CTAButton>
|
||||||
</StaticLink>
|
<CTAButton href="/products" variant="secondary" dataTestId="solutions-hero-secondary" className="min-h-[52px]">
|
||||||
<StaticLink
|
|
||||||
href="/products"
|
|
||||||
className="inline-flex items-center justify-center gap-3 px-10 py-5 font-semibold text-base text-ink border border-border-primary rounded-full hover:border-ink/30 transition-all duration-300 min-h-[52px] touch-manipulation"
|
|
||||||
>
|
|
||||||
{ctaSecondary}
|
{ctaSecondary}
|
||||||
</StaticLink>
|
</CTAButton>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
<motion.div
|
<motion.div
|
||||||
@@ -240,19 +234,12 @@ function CTASection({ pageCopy }: { pageCopy?: PageCopy | null }) {
|
|||||||
{ctaDescription}
|
{ctaDescription}
|
||||||
</p>
|
</p>
|
||||||
<div className="flex flex-col sm:flex-row gap-4 sm:gap-6">
|
<div className="flex flex-col sm:flex-row gap-4 sm:gap-6">
|
||||||
<StaticLink
|
<CTAButton href="/contact" dataTestId="solutions-bottom-primary" className="min-h-[52px]">
|
||||||
href="/contact"
|
|
||||||
className="group inline-flex items-center justify-center gap-3 px-10 py-5 font-semibold text-base bg-brand text-white rounded-full transition-all duration-300 hover:bg-brand/90 min-h-[52px] touch-manipulation"
|
|
||||||
>
|
|
||||||
获取定制方案
|
获取定制方案
|
||||||
<ArrowUpRight className="w-4 h-4 transition-transform duration-300 group-hover:translate-x-0.5 group-hover:-translate-y-0.5" />
|
</CTAButton>
|
||||||
</StaticLink>
|
<CTAButton href="/products" variant="secondary" dataTestId="solutions-bottom-secondary" className="min-h-[52px]">
|
||||||
<StaticLink
|
|
||||||
href="/products"
|
|
||||||
className="inline-flex items-center justify-center gap-3 px-10 py-5 font-semibold text-base text-ink border border-border-primary rounded-full hover:border-ink/30 transition-all duration-300 min-h-[52px] touch-manipulation"
|
|
||||||
>
|
|
||||||
浏览产品
|
浏览产品
|
||||||
</StaticLink>
|
</CTAButton>
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
|
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import { ScrollReveal } from '@/components/ui/scroll-reveal';
|
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 { EASE_OUT } from '@/components/ui/page-decoration';
|
||||||
import { useReducedMotion } from '@/hooks/use-reduced-motion';
|
import { useReducedMotion } from '@/hooks/use-reduced-motion';
|
||||||
|
|
||||||
@@ -215,20 +216,13 @@ export default function TeamContentV3({ data }: { data: Record<string, unknown>
|
|||||||
transition={{ duration: 0.7, delay: 0.6, ease: EASE_OUT }}
|
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"
|
className="flex flex-col sm:flex-row sm:items-center gap-4 sm:gap-6"
|
||||||
>
|
>
|
||||||
<a
|
<CTAButton href={teamData.heroPrimaryCtaHref || '/contact'}>
|
||||||
href={teamData.heroPrimaryCtaHref || '/contact'}
|
|
||||||
className="group inline-flex items-center justify-center gap-3 px-10 py-5 font-semibold text-base bg-brand text-white rounded-full transition-all duration-300 hover:bg-brand/90 min-h-[52px]"
|
|
||||||
>
|
|
||||||
{teamData.heroPrimaryCtaLabel || '加入我们'}
|
{teamData.heroPrimaryCtaLabel || '加入我们'}
|
||||||
<ArrowRight className="w-4 h-4 transition-transform duration-300 group-hover:translate-x-1" />
|
</CTAButton>
|
||||||
</a>
|
|
||||||
{teamData.heroSecondaryCtaLabel && (
|
{teamData.heroSecondaryCtaLabel && (
|
||||||
<a
|
<CTAButton href={teamData.heroSecondaryCtaHref || '#'} variant="secondary">
|
||||||
href={teamData.heroSecondaryCtaHref || '#'}
|
|
||||||
className="inline-flex items-center justify-center gap-3 px-10 py-5 font-semibold text-base text-ink border border-border-primary rounded-full hover:border-border-secondary hover:bg-bg-secondary transition-all duration-300 min-h-[52px]"
|
|
||||||
>
|
|
||||||
{teamData.heroSecondaryCtaLabel}
|
{teamData.heroSecondaryCtaLabel}
|
||||||
</a>
|
</CTAButton>
|
||||||
)}
|
)}
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
@@ -347,19 +341,12 @@ export default function TeamContentV3({ data }: { data: Record<string, unknown>
|
|||||||
{teamData.ctaDescription || '无论您处于数字化转型的哪个阶段,我们都愿意成为您的长期伙伴。'}
|
{teamData.ctaDescription || '无论您处于数字化转型的哪个阶段,我们都愿意成为您的长期伙伴。'}
|
||||||
</p>
|
</p>
|
||||||
<div className="flex flex-col sm:flex-row gap-4 sm:gap-6">
|
<div className="flex flex-col sm:flex-row gap-4 sm:gap-6">
|
||||||
<a
|
<CTAButton href={teamData.ctaPrimaryHref || '/contact'}>
|
||||||
href={teamData.ctaPrimaryHref || '/contact'}
|
|
||||||
className="group inline-flex items-center justify-center gap-3 px-10 py-5 font-semibold text-base bg-brand text-white rounded-full transition-all duration-300 hover:bg-brand/90 min-h-[52px]"
|
|
||||||
>
|
|
||||||
{teamData.ctaPrimaryLabel || '预约咨询'}
|
{teamData.ctaPrimaryLabel || '预约咨询'}
|
||||||
<ArrowRight className="w-5 h-5 transition-transform duration-300 group-hover:translate-x-1" />
|
</CTAButton>
|
||||||
</a>
|
<CTAButton href={teamData.ctaSecondaryHref || '/cases'} variant="dark">
|
||||||
<a
|
|
||||||
href={teamData.ctaSecondaryHref || '/cases'}
|
|
||||||
className="group inline-flex items-center justify-center gap-3 px-10 py-5 font-semibold text-base text-dark-text-primary border border-dark-border rounded-full hover:border-dark-text-muted transition-all duration-300 min-h-[52px]"
|
|
||||||
>
|
|
||||||
{teamData.ctaSecondaryLabel || '查看客户案例'}
|
{teamData.ctaSecondaryLabel || '查看客户案例'}
|
||||||
</a>
|
</CTAButton>
|
||||||
</div>
|
</div>
|
||||||
</ScrollReveal>
|
</ScrollReveal>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+5
-11
@@ -3,7 +3,8 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { StaticLink } from '@/components/ui/static-link';
|
import { StaticLink } from '@/components/ui/static-link';
|
||||||
import { Button } from '@/components/ui/button';
|
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({
|
export default function Error({
|
||||||
error,
|
error,
|
||||||
@@ -59,16 +60,9 @@ export default function Error({
|
|||||||
重试
|
重试
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button
|
<CTAButton href="/" variant="secondary">
|
||||||
size="lg"
|
返回首页
|
||||||
variant="outline"
|
</CTAButton>
|
||||||
asChild
|
|
||||||
>
|
|
||||||
<StaticLink href="/">
|
|
||||||
<Home className="w-5 h-5 mr-2" />
|
|
||||||
返回首页
|
|
||||||
</StaticLink>
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-[var(--color-bg-section)] rounded-lg p-8">
|
<div className="bg-[var(--color-bg-section)] rounded-lg p-8">
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
|
|
||||||
import { StaticLink } from '@/components/ui/static-link';
|
import { StaticLink } from '@/components/ui/static-link';
|
||||||
import { Button } from '@/components/ui/button';
|
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() {
|
export function NotFoundContent() {
|
||||||
return (
|
return (
|
||||||
@@ -24,12 +25,7 @@ export function NotFoundContent() {
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="flex flex-col sm:flex-row gap-4 justify-center mb-12">
|
<div className="flex flex-col sm:flex-row gap-4 justify-center mb-12">
|
||||||
<Button size="lg" asChild>
|
<CTAButton href="/">返回首页</CTAButton>
|
||||||
<StaticLink href="/">
|
|
||||||
<Home className="w-5 h-5 mr-2" />
|
|
||||||
返回首页
|
|
||||||
</StaticLink>
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button size="lg" variant="outline" onClick={() => window.history.back()}>
|
<Button size="lg" variant="outline" onClick={() => window.history.back()}>
|
||||||
<ArrowLeft className="w-5 h-5 mr-2" />
|
<ArrowLeft className="w-5 h-5 mr-2" />
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import * as React from 'react';
|
||||||
|
import { ArrowRight } from 'lucide-react';
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CTAButton — 全站统一的「行动召唤」组件
|
||||||
|
*
|
||||||
|
* 设计意图(见 .impeccable.md / Phase B P5):
|
||||||
|
* - Pill 化(rounded-full),与项目 Button 组件 size 体系一致
|
||||||
|
* - 品牌箭头签名化:内置 ArrowRight,hover 时 translate-x 形成微交互
|
||||||
|
* - 四变体覆盖全站 CTA 场景,避免"全是大红 pill"的 UI 噪音:
|
||||||
|
* - primary:品牌红填充 pill + 大箭头(hero 主行动 / 底部大 CTA)
|
||||||
|
* - secondary:描边墨色 pill + 大箭头(浅色 section 次行动 / 跳转按钮)
|
||||||
|
* - dark:描边浅色 pill + 大箭头(深色区块(bg-dark-bg)内的次行动,如 brand/team 底部 CTA)
|
||||||
|
* - inline:透明 + 小箭头 + 文字色(卡片内"了解更多"轻量次行动)
|
||||||
|
*
|
||||||
|
* dark 变体于 2026-08-31 P5 批次 4 新增:brand/team 底部深色 CTA 区块的次行动此前用
|
||||||
|
* text-dark-text-primary + border-dark-border 手写描边,现统一进组件,避免深色背景上误用
|
||||||
|
* secondary(text-ink)导致不可见。
|
||||||
|
* - 200-300ms transition(动效四原则区间内)
|
||||||
|
* - 主行动(primary)触达品牌红 #C41E3A 单电压,遵循"每页 ≥ 3 处品牌红触达点,面积 ≤ 10%"约束
|
||||||
|
*
|
||||||
|
* 抽离自 src/app/(marketing)/home-content-v15.tsx (2026-08-31),原 primary/secondary 行为完全保留;
|
||||||
|
* 新增 inline 变体支持次行动场景;data-testid 透传避免破坏既有测试快照。
|
||||||
|
*/
|
||||||
|
export type CTAButtonVariant = 'primary' | 'secondary' | 'dark' | 'inline';
|
||||||
|
|
||||||
|
export interface CTAButtonProps {
|
||||||
|
children: React.ReactNode;
|
||||||
|
href: string;
|
||||||
|
variant?: CTAButtonVariant;
|
||||||
|
className?: string;
|
||||||
|
dataTestId?: string;
|
||||||
|
/** 内部 span 的额外类名(用于覆盖默认间距/字号等极端定制场景) */
|
||||||
|
innerClassName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const VARIANT_STYLES: Record<CTAButtonVariant, string> = {
|
||||||
|
primary:
|
||||||
|
'bg-brand text-white hover:bg-brand-hover px-10 py-4 font-semibold text-base',
|
||||||
|
secondary:
|
||||||
|
'text-ink border border-ink/20 hover:border-ink/40 hover:bg-ink/5 px-10 py-4 font-semibold text-base',
|
||||||
|
dark:
|
||||||
|
'text-dark-text-primary border border-dark-border hover:border-dark-text-muted hover:bg-white/5 px-10 py-4 font-semibold text-base',
|
||||||
|
inline:
|
||||||
|
'text-text-primary hover:text-brand px-0 py-0 font-medium text-sm bg-transparent border-0',
|
||||||
|
};
|
||||||
|
|
||||||
|
const ARROW_STYLES: Record<CTAButtonVariant, string> = {
|
||||||
|
primary: 'w-5 h-5 transition-transform duration-300 group-hover:translate-x-2',
|
||||||
|
secondary: 'w-5 h-5 transition-transform duration-300 group-hover:translate-x-2',
|
||||||
|
dark: 'w-5 h-5 transition-transform duration-300 group-hover:translate-x-2',
|
||||||
|
inline: 'w-4 h-4 transition-transform duration-300 group-hover:translate-x-1',
|
||||||
|
};
|
||||||
|
|
||||||
|
export function CTAButton({
|
||||||
|
children,
|
||||||
|
href,
|
||||||
|
variant = 'primary',
|
||||||
|
className,
|
||||||
|
dataTestId,
|
||||||
|
innerClassName,
|
||||||
|
}: CTAButtonProps) {
|
||||||
|
const isPill = variant !== 'inline';
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
href={href}
|
||||||
|
data-testid={dataTestId}
|
||||||
|
className={cn(
|
||||||
|
'group inline-flex items-center gap-3 transition-all duration-200',
|
||||||
|
isPill ? 'rounded-full' : '',
|
||||||
|
VARIANT_STYLES[variant],
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<span className={cn(innerClassName)}>{children}</span>
|
||||||
|
<ArrowRight className={ARROW_STYLES[variant]} />
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CTAButton;
|
||||||
Reference in New Issue
Block a user