feat(cms): 品牌叙事与结构性文案全站 CMS 化并补齐信任证据阶段 0
- 品牌叙事内核(价值主张/定位/承诺/三支柱/语气)下沉 site-config,经 SiteConfigProvider 注入全站 - 新增 page-copy 内容模型承载结构性文案(章节标题/眉标/描述/CTA/空状态),覆盖首页+服务/方案/产品/案例/新闻列表页,「CMS 优先 + 硬编码兜底」不白屏 - 首页「首批客户共创计划」板块(earlyAccessTitle/CtaLabel/Status 驱动)替代单行标签,如实呈现共创/内测/授权公开三档状态 - 关于页资质区数据空时如实展示「建设中」空态 - 零编造:不虚构客户/数据/资质
This commit is contained in:
@@ -10,6 +10,7 @@ import { ArrowRight, Lightbulb, Database, Layers, Code, Puzzle, Server } from 'l
|
||||
import { cn } from '@/lib/utils';
|
||||
import { EASE_OUT } from '@/components/ui/page-decoration';
|
||||
import { COMPANY_INFO, TRUST_SIGNALS, EARLY_ACCESS } from '@/lib/constants';
|
||||
import { useSiteConfig } from '@/lib/site-config';
|
||||
|
||||
// ===== 服务图标映射:将字符串键名映射为 Lucide 组件 =====
|
||||
const SERVICE_ICON_MAP: Record<string, React.ReactNode> = {
|
||||
@@ -71,6 +72,23 @@ const FALLBACK_SERVICES = [
|
||||
},
|
||||
];
|
||||
|
||||
// ===== 首页结构性文案类型(page-copy CMS 覆盖,未配置时走硬编码兜底)=====
|
||||
type HomePageCopy = Record<string, any>;
|
||||
|
||||
// 多行标题渲染:按 \n 分段,输出 <br/> 换行
|
||||
function StyledTitle({ lines }: { lines: (string | undefined)[] }) {
|
||||
return (
|
||||
<>
|
||||
{lines.map((line, i) => (
|
||||
<span key={i}>
|
||||
{i > 0 && <br />}
|
||||
{line}
|
||||
</span>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
// ===== 品牌 CTA 按钮 =====
|
||||
function CTAButton({ children, href, variant = 'primary', className, dataTestId }: {
|
||||
children: React.ReactNode;
|
||||
@@ -103,6 +121,7 @@ function HeroSection({ heroData, stats }: {
|
||||
stats?: Record<string, any>[];
|
||||
}) {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const siteConfig = useSiteConfig();
|
||||
|
||||
const heading = heroData?.heading || heroData?.headingBottom || '让每一家企业都拥有数据驱动的决策能力';
|
||||
const subheading = heroData?.subheading || heroData?.description || '';
|
||||
@@ -110,6 +129,7 @@ function HeroSection({ heroData, stats }: {
|
||||
const ctaHref = heroData?.ctaHref || '/contact';
|
||||
const secondaryCtaLabel = heroData?.secondaryCtaLabel || '查看产品矩阵';
|
||||
const secondaryCtaHref = heroData?.secondaryCtaHref || '/products';
|
||||
const slogan = siteConfig.slogan || COMPANY_INFO.slogan;
|
||||
|
||||
const hasValidStats = stats?.length && stats[0]?.value;
|
||||
const displayStats = hasValidStats ? stats!.slice(0, 3) : [];
|
||||
@@ -147,6 +167,20 @@ function HeroSection({ heroData, stats }: {
|
||||
/>
|
||||
</motion.div>
|
||||
|
||||
{/* 品牌口号 Badge:消费站点配置 slogan,后台可改 */}
|
||||
{slogan && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.35, delay: 0.03, ease: EASE_OUT }}
|
||||
className="mb-6"
|
||||
>
|
||||
<span className="inline-flex items-center gap-2 px-3 py-1.5 rounded-full bg-brand/5 text-brand text-sm font-medium border border-brand/15">
|
||||
{slogan}
|
||||
</span>
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
{/* 主标题:大胆、直接 */}
|
||||
<motion.h1
|
||||
initial={{ opacity: 0, y: 40 }}
|
||||
@@ -228,7 +262,23 @@ function HeroSection({ heroData, stats }: {
|
||||
}
|
||||
|
||||
// ===== 信任/成果区:可验证信任信号 + 首批客户共创标签 =====
|
||||
function TrustSection() {
|
||||
// 共创计划默认状态条目(如实呈现,无真实案例时不虚构客户)
|
||||
const DEFAULT_EARLY_ACCESS_STATUS = [
|
||||
{ label: '共创进行中', desc: '我们正与首批客户共同打磨产品与方案,以真实运营数据验证业务改善。' },
|
||||
{ label: '产品内测中', desc: '自研产品(如睿视 NovaVis)处于内测阶段,成熟后正式发布。' },
|
||||
{ label: '成果授权公开', desc: '案例与可量化成果在获得客户授权后公示——经得起衡量。' },
|
||||
];
|
||||
|
||||
function TrustSection({ pageCopy }: { pageCopy?: HomePageCopy | null }) {
|
||||
const eyebrow = pageCopy?.trustEyebrow || '值得信赖';
|
||||
const titleLines = (pageCopy?.trustTitle || '以可衡量的成果\n赢得长期信任').split('\n');
|
||||
const signals = (pageCopy?.trustSignals?.length ? pageCopy.trustSignals : TRUST_SIGNALS) as typeof TRUST_SIGNALS;
|
||||
const earlyLabel = pageCopy?.earlyAccessLabel || EARLY_ACCESS.label;
|
||||
const earlyDesc = pageCopy?.earlyAccessDesc || EARLY_ACCESS.desc;
|
||||
const earlyTitle = (pageCopy?.earlyAccessTitle as string) || '首批客户共创计划';
|
||||
const earlyCtaLabel = (pageCopy?.earlyAccessCtaLabel as string) || '成为共创客户';
|
||||
const earlyStatus = (pageCopy?.earlyAccessStatus?.length ? pageCopy.earlyAccessStatus : DEFAULT_EARLY_ACCESS_STATUS) as typeof DEFAULT_EARLY_ACCESS_STATUS;
|
||||
|
||||
return (
|
||||
<section className="relative py-20 sm:py-28 md:py-36 overflow-hidden bg-bg-secondary" data-testid="trust-section">
|
||||
<div className="max-w-container mx-auto px-4 sm:px-6 lg:px-10 relative z-10">
|
||||
@@ -236,18 +286,16 @@ function TrustSection() {
|
||||
<div className="flex items-center gap-3 mb-6">
|
||||
<div className="w-10 h-px bg-brand" />
|
||||
<span className="text-[13px] tracking-[0.12em] font-semibold text-brand">
|
||||
值得信赖
|
||||
{eyebrow}
|
||||
</span>
|
||||
</div>
|
||||
<h2 className="text-2xl sm:text-3xl md:text-4xl lg:text-5xl font-black text-ink tracking-tight leading-[1.05]">
|
||||
以可衡量的成果
|
||||
<br />
|
||||
赢得长期信任
|
||||
<StyledTitle lines={titleLines} />
|
||||
</h2>
|
||||
</ScrollReveal>
|
||||
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6 mb-10">
|
||||
{TRUST_SIGNALS.map((item, i) => (
|
||||
{signals.map((item, i) => (
|
||||
<StaggerReveal key={i} staggerDelay={0.06}>
|
||||
<div className="bain-card p-6 sm:p-7 h-full flex flex-col">
|
||||
<div className="flex items-baseline gap-2 mb-3">
|
||||
@@ -265,24 +313,33 @@ function TrustSection() {
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* 首批客户共创标签:无真实案例时如实标注,不虚构客户 */}
|
||||
{/* 首批客户共创计划:如实呈现共创/内测/授权公开状态,不虚构客户 */}
|
||||
<StaggerReveal staggerDelay={0.08}>
|
||||
<div className="flex flex-col sm:flex-row sm:items-center gap-4 sm:gap-6 p-6 sm:p-8 bg-brand-bg border border-[rgba(196,30,58,0.15)]" data-testid="early-access-banner">
|
||||
<div>
|
||||
<div className="flex items-center gap-3 mb-1">
|
||||
<span className="text-[13px] tracking-[0.12em] font-semibold text-brand">
|
||||
{EARLY_ACCESS.label}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-sm text-text-secondary leading-relaxed">
|
||||
{EARLY_ACCESS.desc}
|
||||
</p>
|
||||
<div className="p-6 sm:p-8 bg-brand-bg border border-[rgba(196,30,58,0.15)]" data-testid="early-access-banner">
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<span className="text-[13px] tracking-[0.12em] font-semibold text-brand">
|
||||
{earlyLabel}
|
||||
</span>
|
||||
</div>
|
||||
<h3 className="text-xl sm:text-2xl font-bold text-ink mb-3">
|
||||
{earlyTitle}
|
||||
</h3>
|
||||
<p className="text-sm text-text-secondary leading-relaxed mb-6 max-w-2xl">
|
||||
{earlyDesc}
|
||||
</p>
|
||||
<div className="grid sm:grid-cols-3 gap-4 mb-8">
|
||||
{earlyStatus.map((s) => (
|
||||
<div key={s.label} className="p-4 bg-white border border-brand/10">
|
||||
<div className="text-sm font-semibold text-brand mb-1">{s.label}</div>
|
||||
<div className="text-xs text-text-secondary leading-relaxed">{s.desc}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<a
|
||||
href="/contact"
|
||||
className="group inline-flex shrink-0 items-center gap-2 text-sm font-semibold text-ink hover:text-brand transition-colors sm:ml-auto"
|
||||
className="group inline-flex shrink-0 items-center gap-2 text-sm font-semibold text-ink hover:text-brand transition-colors"
|
||||
>
|
||||
<span>成为首批共创客户</span>
|
||||
<span>{earlyCtaLabel}</span>
|
||||
<ArrowRight className="w-4 h-4 transition-transform duration-300 group-hover:translate-x-1" />
|
||||
</a>
|
||||
</div>
|
||||
@@ -317,20 +374,27 @@ const NARRATIVE_ACTS = [
|
||||
},
|
||||
];
|
||||
|
||||
function NarrativeSection() {
|
||||
function NarrativeSection({ pageCopy }: { pageCopy?: HomePageCopy | null }) {
|
||||
const narrativeTitle = (pageCopy?.narrativeTitle || '从问题到结果,\n一条可量化的路径').split('\n');
|
||||
const NARRATIVE_ACTS_CMS = (pageCopy?.narrativeActs?.length ? pageCopy.narrativeActs : NARRATIVE_ACTS) as Array<{
|
||||
number: string;
|
||||
title: string;
|
||||
desc: string;
|
||||
href: string;
|
||||
cta: string;
|
||||
}>;
|
||||
|
||||
return (
|
||||
<section className="relative py-20 sm:py-28 md:py-36 overflow-hidden bg-white" data-testid="narrative-section">
|
||||
<div className="max-w-container mx-auto px-4 sm:px-6 lg:px-10 relative z-10">
|
||||
<ScrollReveal className="mb-16 sm:mb-20">
|
||||
<h2 className="text-2xl sm:text-3xl md:text-4xl lg:text-5xl font-black text-ink tracking-tight leading-[1.05]">
|
||||
从问题到结果,
|
||||
<br />
|
||||
一条可量化的路径
|
||||
<StyledTitle lines={narrativeTitle} />
|
||||
</h2>
|
||||
</ScrollReveal>
|
||||
|
||||
<div className="grid md:grid-cols-3 gap-px bg-border-primary">
|
||||
{NARRATIVE_ACTS.map((act) => (
|
||||
{NARRATIVE_ACTS_CMS.map((act) => (
|
||||
<StaggerReveal key={act.number} staggerDelay={0.08}>
|
||||
<div className="group relative h-full bg-white p-8 sm:p-10 md:p-12 transition-colors duration-300 hover:bg-bg-secondary">
|
||||
<div className="flex items-start justify-between mb-8">
|
||||
@@ -363,7 +427,7 @@ function NarrativeSection() {
|
||||
}
|
||||
|
||||
// ===== 服务区:等宽三列卡片 + 兜底数据 =====
|
||||
function ServicesSection({ services }: { services?: Record<string, any>[] }) {
|
||||
function ServicesSection({ services, pageCopy }: { services?: Record<string, any>[]; pageCopy?: HomePageCopy | null }) {
|
||||
const hasValidData = services?.length && (services![0] as any)?.subtitle;
|
||||
const SERVICE_ITEMS = hasValidData
|
||||
? services!.map((s: Record<string, any>) => ({
|
||||
@@ -371,6 +435,7 @@ function ServicesSection({ services }: { services?: Record<string, any>[] }) {
|
||||
icon: SERVICE_ICON_MAP[s.icon as string] || <Server className="w-7 h-7" />,
|
||||
})) as Record<string, any>[]
|
||||
: FALLBACK_SERVICES;
|
||||
const servicesTitle = (pageCopy?.servicesTitle || '从战略到落地,\n可量化的一站式服务').split('\n');
|
||||
|
||||
return (
|
||||
<section className="relative py-20 sm:py-28 md:py-36 overflow-hidden bg-bg-secondary">
|
||||
@@ -378,9 +443,7 @@ function ServicesSection({ services }: { services?: Record<string, any>[] }) {
|
||||
<div className="max-w-3xl mb-16 sm:mb-20">
|
||||
<ScrollReveal>
|
||||
<h2 className="text-2xl sm:text-3xl md:text-4xl lg:text-5xl font-black text-ink tracking-tight leading-[1.05]">
|
||||
从战略到落地,
|
||||
<br />
|
||||
可量化的一站式服务
|
||||
<StyledTitle lines={servicesTitle} />
|
||||
</h2>
|
||||
</ScrollReveal>
|
||||
</div>
|
||||
@@ -434,9 +497,14 @@ function ServicesSection({ services }: { services?: Record<string, any>[] }) {
|
||||
}
|
||||
|
||||
// ===== 案例区:轻量快照卡片 =====
|
||||
function CasesSection({ cases }: { cases?: Record<string, any>[] }) {
|
||||
function CasesSection({ cases, pageCopy }: { cases?: Record<string, any>[]; pageCopy?: HomePageCopy | null }) {
|
||||
const hasValidData = cases?.length && cases[0]?.metrics;
|
||||
const CASE_ITEMS = hasValidData ? cases! : [];
|
||||
const casesEyebrow = pageCopy?.casesEyebrow || '共创进行时';
|
||||
const casesTitleLines = (pageCopy?.casesTitle || '每一个项目,\n都以业务改善为衡量标准').split('\n');
|
||||
const casesDesc = pageCopy?.casesDescription || '我们正与首批客户共同打磨产品与方案,以真实运营数据验证业务改善。';
|
||||
const earlyLabel = pageCopy?.earlyAccessLabel || EARLY_ACCESS.label;
|
||||
const earlyDesc = pageCopy?.earlyAccessDesc || EARLY_ACCESS.desc;
|
||||
|
||||
if (CASE_ITEMS.length === 0) {
|
||||
return (
|
||||
@@ -447,18 +515,14 @@ function CasesSection({ cases }: { cases?: Record<string, any>[] }) {
|
||||
<div className="flex items-center gap-3 mb-6">
|
||||
<div className="w-10 h-px bg-brand" />
|
||||
<span className="text-[13px] tracking-[0.12em] font-semibold text-brand">
|
||||
共创进行时
|
||||
{casesEyebrow}
|
||||
</span>
|
||||
</div>
|
||||
<h2 className="text-2xl sm:text-3xl md:text-4xl lg:text-5xl font-black text-ink tracking-tight leading-[1.05] mb-8">
|
||||
每一个项目,
|
||||
<br />
|
||||
都以业务改善为衡量标准
|
||||
<StyledTitle lines={casesTitleLines} />
|
||||
</h2>
|
||||
<p className="text-text-secondary leading-relaxed text-lg mb-8">
|
||||
我们正与首批客户共同打磨产品与方案,以真实运营数据验证业务改善。
|
||||
<br className="hidden sm:block" />
|
||||
公开案例将在验证后发布——合作成果,经得起衡量。
|
||||
{casesDesc}
|
||||
</p>
|
||||
<a
|
||||
href="/contact"
|
||||
@@ -471,9 +535,9 @@ function CasesSection({ cases }: { cases?: Record<string, any>[] }) {
|
||||
<ScrollReveal delay={0.15}>
|
||||
<div className="bain-card p-10 sm:p-12 bg-bg-secondary">
|
||||
<div className="text-5xl sm:text-6xl font-black text-brand tabular-nums leading-none mb-4">
|
||||
{EARLY_ACCESS.label}
|
||||
{earlyLabel}
|
||||
</div>
|
||||
<p className="text-text-secondary leading-relaxed">{EARLY_ACCESS.desc}</p>
|
||||
<p className="text-text-secondary leading-relaxed">{earlyDesc}</p>
|
||||
</div>
|
||||
</ScrollReveal>
|
||||
</div>
|
||||
@@ -488,9 +552,7 @@ function CasesSection({ cases }: { cases?: Record<string, any>[] }) {
|
||||
<div className="flex flex-col md:flex-row md:items-end md:justify-between gap-8 mb-16 sm:mb-20">
|
||||
<ScrollReveal className="md:max-w-3xl">
|
||||
<h2 className="text-2xl sm:text-3xl md:text-4xl lg:text-5xl font-black text-ink tracking-tight leading-[1.05]">
|
||||
每一个项目,
|
||||
<br />
|
||||
都以业务改善为衡量标准
|
||||
<StyledTitle lines={casesTitleLines} />
|
||||
</h2>
|
||||
</ScrollReveal>
|
||||
<ScrollReveal delay={0.1}>
|
||||
@@ -553,10 +615,10 @@ function CasesSection({ cases }: { cases?: Record<string, any>[] }) {
|
||||
}
|
||||
|
||||
// ===== CTA 区:克制强调,品牌红仅用于按钮 =====
|
||||
function CTASection({ heroData }: { heroData?: Record<string, any> | null }) {
|
||||
const ctaTitleLine1 = heroData?.ctaTitleLine1 || '准备好开始了吗?';
|
||||
const ctaTitleLine2 = heroData?.ctaTitleLine2 || '让数据真正转化为业务增长';
|
||||
const ctaLabel = heroData?.ctaLabel || '预约咨询';
|
||||
function CTASection({ heroData, pageCopy }: { heroData?: Record<string, any> | null; pageCopy?: HomePageCopy | null }) {
|
||||
const ctaTitleLine1 = heroData?.ctaTitleLine1 || pageCopy?.ctaTitleLine1 || '准备好开始了吗?';
|
||||
const ctaTitleLine2 = heroData?.ctaTitleLine2 || pageCopy?.ctaTitleLine2 || '让数据真正转化为业务增长';
|
||||
const ctaLabel = heroData?.ctaLabel || pageCopy?.ctaLabel || '预约咨询';
|
||||
const ctaHref = heroData?.ctaHref || '/contact';
|
||||
|
||||
return (
|
||||
@@ -576,11 +638,12 @@ function CTASection({ heroData }: { heroData?: Record<string, any> | null }) {
|
||||
}
|
||||
|
||||
// ===== 主页面组件 =====
|
||||
export default function HomeContentV14({ services, cases, stats, heroData }: {
|
||||
export default function HomeContentV14({ services, cases, stats, heroData, pageCopy }: {
|
||||
services?: Record<string, any>[];
|
||||
cases?: Record<string, any>[];
|
||||
stats?: Record<string, any>[];
|
||||
heroData?: Record<string, any> | null;
|
||||
pageCopy?: HomePageCopy | null;
|
||||
}) {
|
||||
return (
|
||||
<main>
|
||||
@@ -588,19 +651,19 @@ export default function HomeContentV14({ services, cases, stats, heroData }: {
|
||||
<HeroSection heroData={heroData} stats={stats} />
|
||||
|
||||
{/* 2. 信任区:量化数据成果展示 */}
|
||||
<TrustSection />
|
||||
<TrustSection pageCopy={pageCopy} />
|
||||
|
||||
{/* 3. 章节式叙事:问题 → 方法 → 结果 */}
|
||||
<NarrativeSection />
|
||||
<NarrativeSection pageCopy={pageCopy} />
|
||||
|
||||
{/* 4. 服务区:等宽卡片 + 兜底数据 */}
|
||||
<ServicesSection services={services} />
|
||||
<ServicesSection services={services} pageCopy={pageCopy} />
|
||||
|
||||
{/* 5. 案例区:轻量快照卡片 */}
|
||||
<CasesSection cases={cases} />
|
||||
<CasesSection cases={cases} pageCopy={pageCopy} />
|
||||
|
||||
{/* 6. CTA:克制强调,品牌红仅用于按钮 */}
|
||||
<CTASection heroData={heroData} />
|
||||
<CTASection heroData={heroData} pageCopy={pageCopy} />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user