chore: sync marketing pages, CMS extensions, tests and project docs

同步工作区剩余变更,主要包括:
- 营销页面组件与布局持续优化(about/news/services/solutions/team 等)
- 详情页四层叙事组件、布局组件、UI 组件调整
- CMS 数据模型、API 路由、权限、工作流、站内通知、媒体管理扩展
- 新增/补充单元测试与 E2E 测试(cms-workflow.spec.ts 等)
- ESLint 9 迁移、jest/tsconfig 配置更新、依赖调整
- 新增 ADR、CMS 评估文档、Release Review / Acceptance 报告
- 移除水墨装饰组件与大体积未使用字体文件
This commit is contained in:
张翔
2026-07-25 08:04:01 +08:00
parent e35090b914
commit 10404dbb36
208 changed files with 18107 additions and 8330 deletions
+109 -129
View File
@@ -10,8 +10,6 @@ import {
TrendingUp,
type LucideIcon,
} from 'lucide-react';
import { TiltCard, InkGlowCard } from './micro-interactions';
import { SectionHeader, InkDivider } from './brand-elements';
import type { Service } from '@/lib/constants/services';
interface ServiceValueSectionProps {
@@ -22,164 +20,146 @@ export function ServiceValueSection({ service }: ServiceValueSectionProps) {
const icons: LucideIcon[] = [Zap, Clock, Users, Award, TrendingUp];
return (
<section className="relative bg-white py-24 lg:py-32 overflow-hidden">
<div className="container-wide relative">
<SectionHeader
title={`为什么选择我们的${service.title}?`}
subtitle={service.overview}
/>
<section className="relative bg-white py-20 sm:py-28 md:py-36 overflow-hidden">
<div className="max-w-container mx-auto px-4 sm:px-6 lg:px-10 relative">
<motion.div
initial={{ opacity: 0, y: 32 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: '-120px' }}
transition={{ duration: 0.8, ease: [0.22, 1, 0.36, 1] }}
className="mb-16 sm:mb-20"
>
<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">
服务能力
</span>
</div>
<h2 className="text-2xl sm:text-3xl md:text-4xl lg:text-5xl font-black text-ink tracking-tight leading-[0.95] max-w-3xl">
为什么选择我们的{service.title}?
</h2>
<p className="text-lg text-text-secondary max-w-2xl mt-6 leading-relaxed">
{service.overview}
</p>
</motion.div>
<div className="mt-16">
<div className="flex items-center gap-4 mb-8">
<div className="w-12 h-12 rounded-2xl bg-gradient-to-br from-[#C41E3A] to-[#99182d] flex items-center justify-center shadow-md shadow-brand/20">
<Zap className="w-6 h-6 text-white" />
</div>
<div>
<h3 className="text-2xl font-bold text-ink">核心能力</h3>
<p className="text-sm text-text-muted mt-0.5">我们提供的服务特色</p>
</div>
{/* 核心能力 */}
<div className="mb-16">
<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">
核心能力
</span>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-5">
{service.features.map((feature, index) => {
const Icon = icons[index % icons.length]!;
return (
<InkGlowCard
<motion.div
key={feature}
active={index === 0}
className="group p-6 hover:border-brand/20"
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{
delay: index * 0.06,
duration: 0.5,
ease: [0.22, 1, 0.36, 1],
}}
className="bain-card p-6"
>
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{
delay: index * 0.08,
duration: 0.5,
ease: [0.22, 1, 0.36, 1] as const,
}}
>
<div className="flex items-start gap-4">
<div
className={`shrink-0 w-11 h-11 rounded-xl flex items-center justify-center transition-all duration-300 shadow-sm ${
index === 0
? 'bg-gradient-to-br from-[#C41E3A] to-[#99182d] text-white'
: 'bg-bg-secondary text-text-secondary group-hover:from-[#C41E3A] group-hover:to-[#99182d] group-hover:text-white'
}`}
>
<Icon className="w-5.5 h-5.5" />
</div>
<p className="text-sm font-medium text-text-secondary leading-relaxed pt-2">
{feature.split(':')[1] || feature}
</p>
<div className="flex items-start gap-4">
<div className={`shrink-0 w-11 h-11 flex items-center justify-center ${
index === 0
? 'bg-brand text-white'
: 'bg-bg-secondary text-text-secondary'
}`}>
<Icon className="w-5 h-5" />
</div>
</motion.div>
</InkGlowCard>
<p className="text-sm font-medium text-text-secondary leading-relaxed pt-2">
{feature.split(':')[1] || feature}
</p>
</div>
</motion.div>
);
})}
</div>
</div>
<motion.div
initial={{ opacity: 0, y: 32 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: '-80px' }}
transition={{ duration: 0.75, delay: 0.3 }}
className="mt-16"
>
<InkDivider variant="subtle" />
<div className="flex items-center gap-4 mb-8">
<div className="w-12 h-12 rounded-2xl bg-gradient-to-br from-green-500 to-emerald-600 flex items-center justify-center shadow-md shadow-green-500/20">
<Award className="w-6 h-6 text-white" />
{/* 服务优势 + 服务流程:不对称布局 */}
<div className="asymmetric-wide-narrow">
{/* 服务优势 */}
<div>
<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">
服务优势
</span>
</div>
<div>
<h3 className="text-2xl font-bold text-ink">服务优势</h3>
<p className="text-sm text-text-muted mt-0.5">选择我们的理由</p>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-5">
{service.benefits.map((benefit, index) => (
<motion.div
key={index}
initial={{ opacity: 0, x: index % 2 === 0 ? -20 : 20 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
transition={{
delay: 0.4 + index * 0.06,
duration: 0.5,
ease: [0.22, 1, 0.36, 1] as const,
}}
className="group p-6 rounded-2xl bg-bg-secondary border border-border-primary hover:border-brand/30 hover:shadow-md hover:-translate-y-1 transition-all duration-300"
>
<div className="flex items-start gap-3">
<CheckCircle2 className="w-5 h-5 text-green-600 mt-0.5 shrink-0 group-hover:scale-110 transition-transform" />
<div className="space-y-4">
{service.benefits.map((benefit, index) => (
<motion.div
key={index}
initial={{ opacity: 0, x: -20 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
transition={{
delay: index * 0.06,
duration: 0.4,
ease: [0.22, 1, 0.36, 1],
}}
className="bain-card p-5 flex items-start gap-3"
>
<CheckCircle2 className="w-5 h-5 text-brand mt-0.5 shrink-0" />
<p className="text-sm font-medium text-text-secondary leading-relaxed">{benefit}</p>
</div>
</motion.div>
))}
</div>
</motion.div>
{service.process && service.process.length > 0 && (
<motion.div
initial={{ opacity: 0, y: 32 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: '-80px' }}
transition={{ duration: 0.75, delay: 0.45 }}
className="mt-20"
>
<InkDivider variant="decorative" />
<div className="text-center mb-12">
<h3 className="text-2xl md:text-3xl font-bold tracking-tight text-ink">
服务流程
</h3>
<p className="text-text-secondary mt-3">标准化流程保障服务质量</p>
</motion.div>
))}
</div>
</div>
<div className="relative">
<div className="absolute left-8 top-0 bottom-0 w-0.5 bg-gradient-to-b from-[#C41E3A] via-blue-400 to-green-400 hidden md:block" />
<div className="space-y-6">
{/* 服务流程 */}
{service.process && service.process.length > 0 && (
<div>
<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">
服务流程
</span>
</div>
<div className="space-y-4">
{service.process.map((step, index) => (
<motion.div
key={step}
initial={{ opacity: 0, x: -30 }}
initial={{ opacity: 0, x: 20 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
transition={{
delay: 0.5 + index * 0.1,
duration: 0.55,
ease: [0.22, 1, 0.36, 1] as const,
delay: index * 0.08,
duration: 0.45,
ease: [0.22, 1, 0.36, 1],
}}
className="relative flex gap-6 group"
className="bain-card p-5"
>
<div className="relative z-10 shrink-0 w-16 flex justify-center">
<div className="w-4 h-4 rounded-full bg-brand group-hover:scale-110 transition-transform duration-300" />
<div className="flex items-start gap-3">
<div className="w-6 h-6 rounded-full bg-brand text-white flex items-center justify-center shrink-0 mt-0.5">
<span className="text-xs font-bold">{index + 1}</span>
</div>
<div>
<h4 className="font-bold text-ink text-sm mb-1">
{step.split(':')[0]}
</h4>
<p className="text-xs text-text-secondary leading-relaxed">
{step.split(':')[1] || step}
</p>
</div>
</div>
<TiltCard
tiltAmount={5}
glareEnable={false}
className="flex-1 p-6 rounded-2xl bg-white border border-border-primary hover:border-brand/30 hover:shadow-md transition-all"
>
<h4 className="font-bold text-ink mb-2">
{step.split(':')[0]}
</h4>
<p className="text-sm text-text-secondary leading-relaxed">
{step.split(':')[1] || step}
</p>
</TiltCard>
</motion.div>
))}
</div>
</div>
</motion.div>
)}
)}
</div>
</div>
</section>
);
}
}