diff --git a/e2e/uj-11-home-conversion.spec.ts b/e2e/uj-11-home-conversion.spec.ts new file mode 100644 index 0000000..e626827 --- /dev/null +++ b/e2e/uj-11-home-conversion.spec.ts @@ -0,0 +1,197 @@ +import { test, expect, type Page } from '@playwright/test'; + +/** + * UJ-11: 首页转化旅程(设计优化专项) + * + * 覆盖首页 Vibe Design 优化后的关键转化路径,作为封版验收依据: + * UJ-11a: 首屏说服链路 — Hero 产品视觉 + 单一 CTA + 数据条 → 信任层(可验证信号)→ 叙事区 → 转化 CTA + * UJ-11b: 信任验证旅程 — 数据来源标注 + 首批客户共创标签 → 联系转化 + * UJ-11c: 移动端首屏转化旅程 — 390px 下 CTA 首屏可见、无横向滚动 + * + * 关联设计优化计划:docs/plans/2026-08-18-vibe-design-optimization-plan.md + * 关联 critique:.impeccable/critique/ + */ + +test.setTimeout(90000); + +// ==================== 辅助函数 ==================== + +async function closeCookieBanner(page: Page) { + const acceptAllButton = page.locator('button:has-text("接受所有")').first(); + if ((await acceptAllButton.count()) > 0 && (await acceptAllButton.isVisible())) { + await acceptAllButton.click(); + await page.waitForTimeout(500); + } +} + +/** + * 断言页面无横向滚动(scrollWidth <= clientWidth + 1px 容差) + */ +async function expectNoHorizontalOverflow(page: Page) { + const overflow = await page.evaluate(() => { + const doc = document.documentElement; + return { scrollWidth: doc.scrollWidth, clientWidth: doc.clientWidth }; + }); + expect(overflow.scrollWidth).toBeLessThanOrEqual(overflow.clientWidth + 1); +} + +// ==================== UJ-11a: 首屏说服链路 ==================== +test.describe('UJ-11a: 首屏说服链路(Hero → 信任 → 叙事 → CTA)', () => { + test('@journey @critical 首页首屏产品视觉、单一 CTA、数据条渲染正常', async ({ page }) => { + await page.goto('/', { waitUntil: 'domcontentloaded', timeout: 30000 }); + await page.waitForTimeout(2500); + await closeCookieBanner(page); + + // === L1 Hero:产品视觉 Mockup 可见(设计优化核心交付) === + const productVisual = page.locator('[data-testid="hero-product-visual"]').first(); + await expect(productVisual).toBeVisible({ timeout: 15000 }); + + // 诚实标签「产品界面示意」「示例数据」存在(零编造原则) + const visualText = await productVisual.textContent(); + expect(visualText).toContain('产品界面示意'); + expect(visualText).toContain('示例数据'); + + // 产品视觉含可识别产品模块命名 + expect(visualText).toContain('经营驾驶舱'); + + // === 单一主 CTA === + const primaryCta = page.locator('[data-testid="hero-primary-cta"]').first(); + await expect(primaryCta).toBeVisible(); + await expect(primaryCta).toHaveAttribute('href', '/contact'); + + // 次级 CTA 降级为文字链接 + const secondaryLink = page.locator('[data-testid="hero-secondary-link"]').first(); + await expect(secondaryLink).toBeVisible(); + + // === 主标题可见 === + const heroTitle = page.locator('h1').first(); + await expect(heroTitle).toBeVisible(); + + // 无横向滚动 + await expectNoHorizontalOverflow(page); + }); + + test('@journey @critical 首页信任层展示可验证信号与来源标注', async ({ page }) => { + await page.goto('/', { waitUntil: 'domcontentloaded', timeout: 30000 }); + await page.waitForTimeout(2500); + await closeCookieBanner(page); + + // 滚动到信任层 + const trustSection = page.locator('[data-testid="trust-section"]').first(); + await trustSection.scrollIntoViewIfNeeded(); + await expect(trustSection).toBeVisible({ timeout: 10000 }); + + const trustText = await trustSection.textContent(); + // 可验证信任信号存在(私有化部署 / 本地化 / 全流程 / 共创) + expect(trustText).toContain('100%'); + expect(trustText).toContain('成都'); + expect(trustText).toContain('全流程'); + expect(trustText).toContain('共创'); + // 数据来源标注存在(零编造,可追溯) + expect(trustText).toContain('数据来源'); + }); + + test('@journey @critical 首页叙事区按 01/02/03 章节呈现问题→方法→结果', async ({ page }) => { + await page.goto('/', { waitUntil: 'domcontentloaded', timeout: 30000 }); + await page.waitForTimeout(2500); + await closeCookieBanner(page); + + const narrativeSection = page.locator('[data-testid="narrative-section"]').first(); + await narrativeSection.scrollIntoViewIfNeeded(); + await expect(narrativeSection).toBeVisible({ timeout: 10000 }); + + const narrativeText = await narrativeSection.textContent(); + // 三幕章节编号存在 + expect(narrativeText).toContain('01'); + expect(narrativeText).toContain('02'); + expect(narrativeText).toContain('03'); + // 叙事主题存在 + expect(narrativeText).toContain('诊断现状'); + expect(narrativeText).toContain('设计路径'); + expect(narrativeText).toContain('交付成果'); + // 每章保留详情入口 + const narrativeLinks = narrativeSection.locator('a'); + expect(await narrativeLinks.count()).toBeGreaterThanOrEqual(3); + }); +}); + +// ==================== UJ-11b: 信任验证旅程 ==================== +test.describe('UJ-11b: 信任验证旅程(共创标签 → 转化)', () => { + test('@journey @critical 首页首批客户共创标签引导至联系转化', async ({ page }) => { + await page.goto('/', { waitUntil: 'domcontentloaded', timeout: 30000 }); + await page.waitForTimeout(2500); + await closeCookieBanner(page); + + // 首批客户共创横幅存在(无虚构客户,如实标注) + const earlyAccessBanner = page.locator('[data-testid="early-access-banner"]').first(); + await earlyAccessBanner.scrollIntoViewIfNeeded(); + await expect(earlyAccessBanner).toBeVisible({ timeout: 10000 }); + + const bannerText = await earlyAccessBanner.textContent(); + expect(bannerText).toContain('首批客户共创中'); + + // 引导至联系转化 + const cta = earlyAccessBanner.locator('a[href="/contact"]').first(); + await expect(cta).toBeVisible(); + await expect(cta).toHaveAttribute('href', '/contact'); + }); + + test('@journey @critical 空案例区呈现共创内容块而非裸空态', async ({ page }) => { + await page.goto('/', { waitUntil: 'domcontentloaded', timeout: 30000 }); + await page.waitForTimeout(2500); + await closeCookieBanner(page); + + const bodyText = await page.locator('body').textContent(); + // 无「暂无案例数据」裸空态 + expect(bodyText).not.toContain('暂无案例数据'); + // 出现「共创进行时」前瞻内容块 + expect(bodyText).toContain('共创进行时'); + // 共创块包含转化 CTA + expect(bodyText).toContain('成为首批共创客户'); + }); +}); + +// ==================== UJ-11c: 移动端首屏转化旅程 ==================== +test.describe('UJ-11c: 移动端首屏转化旅程', () => { + test('@mobile @journey @critical 移动端 CTA 首屏可见且无横向滚动', async ({ page }) => { + // 模拟移动端视口 + await page.setViewportSize({ width: 390, height: 844 }); + await page.goto('/', { waitUntil: 'domcontentloaded', timeout: 30000 }); + await page.waitForTimeout(2500); + await closeCookieBanner(page); + + // 无横向滚动 + await expectNoHorizontalOverflow(page); + + // CTA 首屏可达(在当前视口内可见) + const primaryCta = page.locator('[data-testid="hero-primary-cta"]').first(); + await expect(primaryCta).toBeVisible({ timeout: 15000 }); + + // 产品视觉渲染正常 + const productVisual = page.locator('[data-testid="hero-product-visual"]').first(); + await expect(productVisual).toBeVisible(); + }); + + test('@mobile @journey @critical 移动端信任层与叙事区单列堆叠无溢出', async ({ page }) => { + await page.setViewportSize({ width: 390, height: 844 }); + await page.goto('/', { waitUntil: 'domcontentloaded', timeout: 30000 }); + await page.waitForTimeout(2500); + await closeCookieBanner(page); + + // 信任层 + const trustSection = page.locator('[data-testid="trust-section"]').first(); + await trustSection.scrollIntoViewIfNeeded(); + await expect(trustSection).toBeVisible(); + const trustText = await trustSection.textContent(); + expect(trustText).toContain('数据来源'); + + // 叙事区 + const narrativeSection = page.locator('[data-testid="narrative-section"]').first(); + await narrativeSection.scrollIntoViewIfNeeded(); + await expect(narrativeSection).toBeVisible(); + expect(await narrativeSection.textContent()).toContain('01'); + + // 全程无横向滚动 + await expectNoHorizontalOverflow(page); + }); +}); diff --git a/e2e/visual-snapshots/visual-chromium-desktop/visual-regression.spec.ts/about-fullpage-visual-chromium-desktop.png b/e2e/visual-snapshots/visual-chromium-desktop/visual-regression.spec.ts/about-fullpage-visual-chromium-desktop.png index d24c35a..c06da12 100644 Binary files a/e2e/visual-snapshots/visual-chromium-desktop/visual-regression.spec.ts/about-fullpage-visual-chromium-desktop.png and b/e2e/visual-snapshots/visual-chromium-desktop/visual-regression.spec.ts/about-fullpage-visual-chromium-desktop.png differ diff --git a/e2e/visual-snapshots/visual-chromium-desktop/visual-regression.spec.ts/contact-fullpage-visual-chromium-desktop.png b/e2e/visual-snapshots/visual-chromium-desktop/visual-regression.spec.ts/contact-fullpage-visual-chromium-desktop.png index 85bcbd0..288070f 100644 Binary files a/e2e/visual-snapshots/visual-chromium-desktop/visual-regression.spec.ts/contact-fullpage-visual-chromium-desktop.png and b/e2e/visual-snapshots/visual-chromium-desktop/visual-regression.spec.ts/contact-fullpage-visual-chromium-desktop.png differ diff --git a/e2e/visual-snapshots/visual-chromium-desktop/visual-regression.spec.ts/footer-section-visual-chromium-desktop.png b/e2e/visual-snapshots/visual-chromium-desktop/visual-regression.spec.ts/footer-section-visual-chromium-desktop.png index 72227ca..127176c 100644 Binary files a/e2e/visual-snapshots/visual-chromium-desktop/visual-regression.spec.ts/footer-section-visual-chromium-desktop.png and b/e2e/visual-snapshots/visual-chromium-desktop/visual-regression.spec.ts/footer-section-visual-chromium-desktop.png differ diff --git a/e2e/visual-snapshots/visual-chromium-desktop/visual-regression.spec.ts/home-fullpage-visual-chromium-desktop.png b/e2e/visual-snapshots/visual-chromium-desktop/visual-regression.spec.ts/home-fullpage-visual-chromium-desktop.png index 27fb130..cf4fdc4 100644 Binary files a/e2e/visual-snapshots/visual-chromium-desktop/visual-regression.spec.ts/home-fullpage-visual-chromium-desktop.png and b/e2e/visual-snapshots/visual-chromium-desktop/visual-regression.spec.ts/home-fullpage-visual-chromium-desktop.png differ diff --git a/e2e/visual-snapshots/visual-chromium-desktop/visual-regression.spec.ts/news-detail-fullpage-visual-chromium-desktop.png b/e2e/visual-snapshots/visual-chromium-desktop/visual-regression.spec.ts/news-detail-fullpage-visual-chromium-desktop.png index 68af482..9ad2662 100644 Binary files a/e2e/visual-snapshots/visual-chromium-desktop/visual-regression.spec.ts/news-detail-fullpage-visual-chromium-desktop.png and b/e2e/visual-snapshots/visual-chromium-desktop/visual-regression.spec.ts/news-detail-fullpage-visual-chromium-desktop.png differ diff --git a/e2e/visual-snapshots/visual-chromium-desktop/visual-regression.spec.ts/news-fullpage-visual-chromium-desktop.png b/e2e/visual-snapshots/visual-chromium-desktop/visual-regression.spec.ts/news-fullpage-visual-chromium-desktop.png index 615ef8b..4015f86 100644 Binary files a/e2e/visual-snapshots/visual-chromium-desktop/visual-regression.spec.ts/news-fullpage-visual-chromium-desktop.png and b/e2e/visual-snapshots/visual-chromium-desktop/visual-regression.spec.ts/news-fullpage-visual-chromium-desktop.png differ diff --git a/e2e/visual-snapshots/visual-chromium-desktop/visual-regression.spec.ts/product-erp-fullpage-visual-chromium-desktop.png b/e2e/visual-snapshots/visual-chromium-desktop/visual-regression.spec.ts/product-erp-fullpage-visual-chromium-desktop.png index 9a69c5d..330cf17 100644 Binary files a/e2e/visual-snapshots/visual-chromium-desktop/visual-regression.spec.ts/product-erp-fullpage-visual-chromium-desktop.png and b/e2e/visual-snapshots/visual-chromium-desktop/visual-regression.spec.ts/product-erp-fullpage-visual-chromium-desktop.png differ diff --git a/e2e/visual-snapshots/visual-chromium-desktop/visual-regression.spec.ts/products-fullpage-visual-chromium-desktop.png b/e2e/visual-snapshots/visual-chromium-desktop/visual-regression.spec.ts/products-fullpage-visual-chromium-desktop.png index b6eadba..8ffcaef 100644 Binary files a/e2e/visual-snapshots/visual-chromium-desktop/visual-regression.spec.ts/products-fullpage-visual-chromium-desktop.png and b/e2e/visual-snapshots/visual-chromium-desktop/visual-regression.spec.ts/products-fullpage-visual-chromium-desktop.png differ diff --git a/e2e/visual-snapshots/visual-chromium-desktop/visual-regression.spec.ts/service-software-fullpage-visual-chromium-desktop.png b/e2e/visual-snapshots/visual-chromium-desktop/visual-regression.spec.ts/service-software-fullpage-visual-chromium-desktop.png index 3cffe40..aad5ef7 100644 Binary files a/e2e/visual-snapshots/visual-chromium-desktop/visual-regression.spec.ts/service-software-fullpage-visual-chromium-desktop.png and b/e2e/visual-snapshots/visual-chromium-desktop/visual-regression.spec.ts/service-software-fullpage-visual-chromium-desktop.png differ diff --git a/e2e/visual-snapshots/visual-chromium-desktop/visual-regression.spec.ts/services-fullpage-visual-chromium-desktop.png b/e2e/visual-snapshots/visual-chromium-desktop/visual-regression.spec.ts/services-fullpage-visual-chromium-desktop.png index ca31daf..214310a 100644 Binary files a/e2e/visual-snapshots/visual-chromium-desktop/visual-regression.spec.ts/services-fullpage-visual-chromium-desktop.png and b/e2e/visual-snapshots/visual-chromium-desktop/visual-regression.spec.ts/services-fullpage-visual-chromium-desktop.png differ diff --git a/e2e/visual-snapshots/visual-chromium-desktop/visual-regression.spec.ts/solution-manufacturing-fullpage-visual-chromium-desktop.png b/e2e/visual-snapshots/visual-chromium-desktop/visual-regression.spec.ts/solution-manufacturing-fullpage-visual-chromium-desktop.png index 30d1d99..b49ac4d 100644 Binary files a/e2e/visual-snapshots/visual-chromium-desktop/visual-regression.spec.ts/solution-manufacturing-fullpage-visual-chromium-desktop.png and b/e2e/visual-snapshots/visual-chromium-desktop/visual-regression.spec.ts/solution-manufacturing-fullpage-visual-chromium-desktop.png differ diff --git a/e2e/visual-snapshots/visual-chromium-desktop/visual-regression.spec.ts/solutions-fullpage-visual-chromium-desktop.png b/e2e/visual-snapshots/visual-chromium-desktop/visual-regression.spec.ts/solutions-fullpage-visual-chromium-desktop.png index 27c8257..e39a010 100644 Binary files a/e2e/visual-snapshots/visual-chromium-desktop/visual-regression.spec.ts/solutions-fullpage-visual-chromium-desktop.png and b/e2e/visual-snapshots/visual-chromium-desktop/visual-regression.spec.ts/solutions-fullpage-visual-chromium-desktop.png differ diff --git a/e2e/visual-snapshots/visual-chromium-desktop/visual-regression.spec.ts/team-fullpage-visual-chromium-desktop.png b/e2e/visual-snapshots/visual-chromium-desktop/visual-regression.spec.ts/team-fullpage-visual-chromium-desktop.png index e89141a..d34fa01 100644 Binary files a/e2e/visual-snapshots/visual-chromium-desktop/visual-regression.spec.ts/team-fullpage-visual-chromium-desktop.png and b/e2e/visual-snapshots/visual-chromium-desktop/visual-regression.spec.ts/team-fullpage-visual-chromium-desktop.png differ diff --git a/e2e/visual-snapshots/visual-chromium-desktop/visual-regression.spec.ts/theme-dark-main-visual-chromium-desktop.png b/e2e/visual-snapshots/visual-chromium-desktop/visual-regression.spec.ts/theme-dark-main-visual-chromium-desktop.png index 27fb130..cf4fdc4 100644 Binary files a/e2e/visual-snapshots/visual-chromium-desktop/visual-regression.spec.ts/theme-dark-main-visual-chromium-desktop.png and b/e2e/visual-snapshots/visual-chromium-desktop/visual-regression.spec.ts/theme-dark-main-visual-chromium-desktop.png differ diff --git a/e2e/visual-snapshots/visual-chromium-desktop/visual-regression.spec.ts/theme-light-main-visual-chromium-desktop.png b/e2e/visual-snapshots/visual-chromium-desktop/visual-regression.spec.ts/theme-light-main-visual-chromium-desktop.png index 27fb130..cf4fdc4 100644 Binary files a/e2e/visual-snapshots/visual-chromium-desktop/visual-regression.spec.ts/theme-light-main-visual-chromium-desktop.png and b/e2e/visual-snapshots/visual-chromium-desktop/visual-regression.spec.ts/theme-light-main-visual-chromium-desktop.png differ diff --git a/src/app/(marketing)/about/about-content-v4.tsx b/src/app/(marketing)/about/about-content-v4.tsx index d0eaf46..b9caff8 100644 --- a/src/app/(marketing)/about/about-content-v4.tsx +++ b/src/app/(marketing)/about/about-content-v4.tsx @@ -173,8 +173,14 @@ function WhoWeAreSection({ data }: { data: AboutData }) { transition={{ duration: 0.8, ease: EASE_OUT }} className="lg:col-span-5" > -
- Who We Are +
+ 01 +
+
+ Why We Started +
+
为什么成立
+

{data.whoWeAreTitle || ''} @@ -196,6 +202,15 @@ function WhoWeAreSection({ data }: { data: AboutData }) { transition={{ duration: 0.8, delay: 0.15, ease: EASE_OUT }} className="lg:col-span-7" > +
+ 02 +
+
+ How We Work +
+
怎么做事
+
+
{values.map((value) => (
-
- Our Journey +
+ 03 +
+
+ Where We Are Heading +
+
走到哪里
+

一路走来,
步步扎实 diff --git a/src/app/(marketing)/home-content-v14.test.tsx b/src/app/(marketing)/home-content-v14.test.tsx new file mode 100644 index 0000000..738b984 --- /dev/null +++ b/src/app/(marketing)/home-content-v14.test.tsx @@ -0,0 +1,138 @@ +import { describe, it, expect, jest } from '@jest/globals'; +import { render, screen } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import HomeContentV14 from './home-content-v14'; + +jest.mock('framer-motion', () => ({ + motion: { + div: ({ children, initial, animate, className, style, ...props }: any) => ( +
+ {children} +
+ ), + h1: ({ children, className, ...props }: any) => ( +

{children}

+ ), + p: ({ children, className, ...props }: any) => ( +

{children}

+ ), + span: ({ children, className, ...props }: any) => ( + {children} + ), + a: ({ children, href, className, ...props }: any) => ( + {children} + ), + }, + useInView: jest.fn(() => true), + AnimatePresence: ({ children }: any) => <>{children}, +})); + +jest.mock('next/image', () => ({ + __esModule: true, + default: ({ src, alt, className }: any) => ( + // eslint-disable-next-line @next/next/no-img-element + {alt} + ), +})); + +jest.mock('@/components/ui/scroll-reveal', () => ({ + ScrollReveal: ({ children, className }: any) => ( +
{children}
+ ), + StaggerReveal: ({ children, className }: any) => ( +
{children}
+ ), +})); + +jest.mock('@/components/ui/badge', () => ({ + Badge: ({ children, className }: any) => ( + {children} + ), +})); + +jest.mock('lucide-react', () => { + const mockIcon = (name: string) => { + const Icon = (props: any) => ; + Icon.displayName = name; + return Icon; + }; + return { + ArrowRight: mockIcon('arrow-right'), + ArrowUpRight: mockIcon('arrow-up-right'), + ArrowDownRight: mockIcon('arrow-down-right'), + Lightbulb: mockIcon('lightbulb'), + Database: mockIcon('database'), + Layers: mockIcon('layers'), + Code: mockIcon('code'), + Puzzle: mockIcon('puzzle'), + Server: mockIcon('server'), + LayoutDashboard: mockIcon('layout-dashboard'), + BarChart3: mockIcon('bar-chart3'), + Package: mockIcon('package'), + Users: mockIcon('users'), + Settings: mockIcon('settings'), + Search: mockIcon('search'), + Bell: mockIcon('bell'), + TrendingUp: mockIcon('trending-up'), + AlertCircle: mockIcon('alert-circle'), + }; +}); + +describe('HomeContentV14 - 首页 Hero 优化', () => { + it('renders an SVG product visual with an interface disclaimer', () => { + render(); + + expect(screen.getByTestId('hero-product-visual')).toBeInTheDocument(); + expect(screen.getByText('产品界面示意')).toBeInTheDocument(); + }); + + it('keeps a single primary CTA and demotes the secondary action to a text link', () => { + render(); + + expect(screen.getByTestId('hero-primary-cta')).toBeInTheDocument(); + expect(screen.getByTestId('hero-primary-cta')).toHaveAttribute('href', '/contact'); + expect(screen.getByTestId('hero-secondary-link')).toBeInTheDocument(); + }); + + it('keeps the answer-first stats bar visible', () => { + render(); + + expect(screen.getByTestId('hero-stats')).toBeInTheDocument(); + }); +}); + +describe('HomeContentV14 - 信任层与章节式叙事', () => { + it('renders verifiable trust signals with a source annotation', () => { + render(); + + expect(screen.getByTestId('trust-section')).toBeInTheDocument(); + expect(screen.getByText('100%')).toBeInTheDocument(); + expect(screen.getByText('成都')).toBeInTheDocument(); + // 可验证来源标注存在(每个数据卡一个) + expect(screen.getAllByText(/数据来源:/i).length).toBeGreaterThanOrEqual(4); + }); + + it('renders an early-access co-creation label instead of fabricated testimonials', () => { + render(); + + expect(screen.getByTestId('early-access-banner')).toBeInTheDocument(); + expect(screen.getAllByText('首批客户共创中').length).toBeGreaterThanOrEqual(1); + }); + + it('renders the problem-method-result narrative with chapter numbers', () => { + render(); + + expect(screen.getByTestId('narrative-section')).toBeInTheDocument(); + expect(screen.getByText('01')).toBeInTheDocument(); + expect(screen.getByText('02')).toBeInTheDocument(); + expect(screen.getByText('03')).toBeInTheDocument(); + expect(screen.getAllByText(/从问题到结果/).length).toBeGreaterThan(0); + }); +}); diff --git a/src/app/(marketing)/home-content-v14.tsx b/src/app/(marketing)/home-content-v14.tsx index 0680dac..c53058e 100644 --- a/src/app/(marketing)/home-content-v14.tsx +++ b/src/app/(marketing)/home-content-v14.tsx @@ -5,10 +5,11 @@ import Image from 'next/image'; import { motion } from 'framer-motion'; import { ScrollReveal, StaggerReveal } from '@/components/ui/scroll-reveal'; import { Badge } from '@/components/ui/badge'; +import { HeroProductVisual } from '@/components/sections/hero-product-visual'; import { ArrowRight, Lightbulb, Database, Layers, Code, Puzzle, Server } from 'lucide-react'; import { cn } from '@/lib/utils'; import { EASE_OUT } from '@/components/ui/page-decoration'; -import { COMPANY_INFO } from '@/lib/constants'; +import { COMPANY_INFO, TRUST_SIGNALS, EARLY_ACCESS } from '@/lib/constants'; // ===== 服务图标映射:将字符串键名映射为 Lucide 组件 ===== const SERVICE_ICON_MAP: Record = { @@ -18,7 +19,7 @@ const SERVICE_ICON_MAP: Record = { Puzzle: , }; -// ===== 服务数据降级兜底 ===== +// ===== 服务数据降级兜底(4 条,保证 sm:grid-cols-2 网格完整,避免孤儿格)===== const FALLBACK_SERVICES = [ { subtitle: 'Strategy Consulting', @@ -29,9 +30,21 @@ const FALLBACK_SERVICES = [ { value: '40%', label: '平均运营效率提升' }, { value: '6个月', label: '战略到试点周期' }, ], - href: '/services/strategy', + href: '/services/consulting', icon: , }, + { + subtitle: 'Software Delivery', + title: '企业软件定制开发', + desc: '基于自研产品矩阵与工程化交付流程,提供从需求分析、架构设计到持续运维的全栈软件开发服务,支撑业务数字化落地。', + highlights: ['需求分析', '架构设计', '敏捷开发', '持续运维'], + metrics: [ + { value: '6', label: '款自研产品' }, + { value: '24/7', label: '运维支持' }, + ], + href: '/services/software', + icon: , + }, { subtitle: 'Data Intelligence', title: '数据智能与 AI 应用', @@ -53,21 +66,23 @@ const FALLBACK_SERVICES = [ { value: '6', label: '行业场景覆盖' }, { value: '99.9%', label: '系统可用性' }, ], - href: '/services/solution', + href: '/services/solutions', icon: , }, ]; // ===== 品牌 CTA 按钮 ===== -function CTAButton({ children, href, variant = 'primary', className }: { +function CTAButton({ children, href, variant = 'primary', className, dataTestId }: { children: React.ReactNode; href: string; variant?: 'primary' | 'secondary'; className?: string; + dataTestId?: string; }) { return ( -
+
+ ); } -// ===== 信任/成果区:Bain 式量化数据展示 ===== +// ===== 信任/成果区:可验证信任信号 + 首批客户共创标签 ===== function TrustSection() { - const items = [ - { value: '2026', label: '年正式成立', desc: '1 月 15 日于成都龙泉驿区启航' }, - { value: '10+', label: '人核心团队', desc: '复合型技术团队,覆盖咨询与研发' }, - { value: '6', label: '款自研产品', desc: '覆盖核心数字化业务场景' }, - { value: '100%', label: '私有化部署', desc: '核心数据不出境,满足合规要求' }, - ]; - return ( -
+
@@ -224,15 +246,113 @@ function TrustSection() {

-
- {items.map((item, i) => ( +
+ {TRUST_SIGNALS.map((item, i) => ( -
-
- {item.value} +
+
+ + {item.value} + + {item.label} +
+

{item.desc}

+
+ 数据来源:{item.source} +
+
+ + ))} +
+ + {/* 首批客户共创标签:无真实案例时如实标注,不虚构客户 */} + +
+ +
+ + ); +} + +// ===== 章节式叙事区:问题 → 方法 → 结果(Storytelling-Driven)===== +const NARRATIVE_ACTS = [ + { + number: '01', + title: '诊断现状', + desc: '从业务目标出发,梳理流程断点、数据孤岛与瓶颈环节,形成可量化的诊断结论。', + href: '/services/consulting', + cta: '了解诊断方法', + }, + { + number: '02', + title: '设计路径', + desc: '基于诊断结论设计系统架构与实施路线图,明确每一阶段的交付物与衡量指标。', + href: '/services/solutions', + cta: '查看方案设计', + }, + { + number: '03', + title: '交付成果', + desc: '以自研产品组合与专业实施团队完成交付,用真实运营数据验证业务改善,持续迭代。', + href: '/products', + cta: '浏览产品矩阵', + }, +]; + +function NarrativeSection() { + return ( +
+
+ +

+ 从问题到结果, +
+ 一条可量化的路径 +

+
+ +
+ {NARRATIVE_ACTS.map((act) => ( + +
+
+ + {act.number} + +
+

+ {act.title} +

+

+ {act.desc} +

+ -
{item.label}
-

{item.desc}

))} @@ -265,7 +385,7 @@ function ServicesSection({ services }: { services?: Record[] }) {
-
+
{SERVICE_ITEMS.map((service, i) => ( @@ -291,8 +411,8 @@ function ServicesSection({ services }: { services?: Record[] }) {
{(service as any).metrics.map((m: any, j: number) => (
-
{m.value}
-
{m.label}
+
{m.value}
+
{m.label}
))}
@@ -320,9 +440,43 @@ function CasesSection({ cases }: { cases?: Record[] }) { if (CASE_ITEMS.length === 0) { return ( -
-
-

暂无案例数据

+
+
); @@ -436,13 +590,16 @@ export default function HomeContentV14({ services, cases, stats, heroData }: { {/* 2. 信任区:量化数据成果展示 */} - {/* 3. 服务区:等宽卡片 + 兜底数据 */} + {/* 3. 章节式叙事:问题 → 方法 → 结果 */} + + + {/* 4. 服务区:等宽卡片 + 兜底数据 */} - {/* 4. 案例区:轻量快照卡片 */} + {/* 5. 案例区:轻量快照卡片 */} - {/* 5. CTA:克制强调,品牌红仅用于按钮 */} + {/* 6. CTA:克制强调,品牌红仅用于按钮 */} ); diff --git a/src/app/(marketing)/products/products-content-v3.test.tsx b/src/app/(marketing)/products/products-content-v3.test.tsx index 6ca5a1e..a775c07 100644 --- a/src/app/(marketing)/products/products-content-v3.test.tsx +++ b/src/app/(marketing)/products/products-content-v3.test.tsx @@ -144,6 +144,33 @@ const mockEnterpriseProducts: Product[] = [ dataProofs: [], certifications: [], }, + { + id: 'bi', + title: '睿新商业智能分析平台', + category: '企业旗舰系列', + categoryId: 'enterprise', + description: '企业级数据智能分析平台', + overview: 'BI 平台概述', + features: ['数据建模', '可视化分析'], + benefits: ['数据驱动决策'], + tags: ['BI', '数据分析'], + status: '已发布', + image: '/bi.png', + heroThemeId: 'bi', + bundle: 'enterprise', + scenario: '面向中大型企业的数据驱动决策平台', + metrics: [ + { value: '10x', label: '分析效率提升' }, + { value: '98%', label: '数据质量达标率' }, + { value: '24h', label: '实时更新' }, + ], + capabilities: ['数据建模', '可视化报表', '智能预警', '移动看板'], + process: [], + specs: [], + caseStudies: [], + dataProofs: [], + certifications: [], + }, ]; const mockSpecializedProducts: Product[] = [ @@ -251,6 +278,22 @@ describe('ProductsContentV3 - 产品列表页集成测试', () => { expect(erpDetailLink).toBeTruthy(); }); + it('should render ERP as the 2x2 Bento large card', async () => { + const { default: ProductsContentV3 } = await import('./products-content-v3'); + render(); + + const erpCard = screen.getByTestId('bento-product-card-erp'); + expect(erpCard).toHaveAttribute('data-bento-size', 'large'); + }); + + it('should render BI as the 1x2 Bento wide card', async () => { + const { default: ProductsContentV3 } = await import('./products-content-v3'); + render(); + + const biCard = screen.getByTestId('bento-product-card-bi'); + expect(biCard).toHaveAttribute('data-bento-size', 'wide'); + }); + it('should render suite combos section', async () => { const { default: ProductsContentV3 } = await import('./products-content-v3'); render(); diff --git a/src/app/(marketing)/products/products-content-v3.tsx b/src/app/(marketing)/products/products-content-v3.tsx index 11b0db7..d16e38f 100644 --- a/src/app/(marketing)/products/products-content-v3.tsx +++ b/src/app/(marketing)/products/products-content-v3.tsx @@ -6,9 +6,11 @@ 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 { BentoItem } from '@/components/sections/bento-grid'; import { ArrowRight, Package, Cpu, TrendingUp, Users, Settings, FileText } from 'lucide-react'; import type { Product } from '@/lib/constants/products'; import { useReducedMotion } from '@/hooks/use-reduced-motion'; +import { cn } from '@/lib/utils'; const EASE_SPRING = { type: 'spring', stiffness: 300, damping: 30 } as const; @@ -146,40 +148,45 @@ function HeroSection() { ); } -function ProductCard({ product }: { product: Product }) { +function ProductCard({ product, size = 'small' }: { product: Product; size?: 'large' | 'wide' | 'small' }) { const bundleLabel = product.bundle === 'enterprise' ? '企业套装' : '专业产品'; const linkHref = product.externalUrl || `/products/${product.id}`; const isExternal = !!product.externalUrl; + const isLarge = size === 'large'; return ( - + + -
+
{bundleLabel}
-

{product.title}

+

+ {product.title} +

-

+

{product.scenario || product.description}

{product.metrics?.map((m) => (
-
{m.value}
+
{m.value}
{m.label}
))} @@ -194,7 +201,8 @@ function ProductCard({ product }: { product: Product }) {
- + + ); } @@ -299,15 +307,15 @@ function EnterpriseProductsSection({ products }: { products: Product[] }) {
- +
{enterpriseProducts.map((product) => ( - + ))} - +
); diff --git a/src/app/(marketing)/services/services-content-v3.test.tsx b/src/app/(marketing)/services/services-content-v3.test.tsx new file mode 100644 index 0000000..27155ca --- /dev/null +++ b/src/app/(marketing)/services/services-content-v3.test.tsx @@ -0,0 +1,105 @@ +import { describe, it, expect, jest } from '@jest/globals'; +import { render, screen } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import ServicesContentV3 from './services-content-v3'; +import type { Service } from '@/lib/constants/services'; + +jest.mock('framer-motion', () => ({ + motion: { + div: ({ children, initial, animate, whileInView, viewport, className, ...props }: any) => ( +
+ {children} +
+ ), + h1: ({ children, className, ...props }: any) => ( +

{children}

+ ), + p: ({ children, className, ...props }: any) => ( +

{children}

+ ), + span: ({ children, className, ...props }: any) => ( + {children} + ), + a: ({ children, href, className, ...props }: any) => ( + {children} + ), + }, + useInView: jest.fn(() => true), + AnimatePresence: ({ children }: any) => <>{children}, +})); + +jest.mock('@/components/ui/static-link', () => ({ + StaticLink: ({ children, href, className, ...props }: any) => ( + {children} + ), +})); + +jest.mock('lucide-react', () => { + const mockIcon = (name: string) => { + const Icon = (props: any) => ; + Icon.displayName = name; + return Icon; + }; + return { + ArrowUpRight: mockIcon('arrow-up-right'), + CheckCircle2: mockIcon('check-circle2'), + }; +}); + +const mockServices: Service[] = [ + { + id: 'consulting', + title: '数字化转型战略咨询', + description: '从业务诊断到数字化路线图', + icon: 'Lightbulb', + overview: '战略咨询概述', + features: ['业务诊断'], + benefits: ['ROI 评估'], + process: ['诊断'], + heroThemeId: 'consulting', + caseStudies: [], + dataProofs: [], + capabilities: ['业务诊断', '数字化蓝图'], + metrics: [{ value: '40%', label: '运营效率提升' }], + }, + { + id: 'data', + title: '数据智能与 AI 应用', + description: '构建企业级数据平台', + icon: 'Database', + overview: '数据智能概述', + features: ['数据中台'], + benefits: ['数据驱动'], + process: ['建模'], + heroThemeId: 'data', + caseStudies: [], + dataProofs: [], + capabilities: ['数据中台', 'AI 建模'], + metrics: [{ value: '98%', label: '数据质量达标率' }], + }, +]; + +describe('ServicesContentV3 - 非对称服务卡片', () => { + it('makes the first service a featured full-width card', () => { + render(); + + expect(screen.getByTestId('service-card-consulting')).toHaveAttribute('data-featured', 'true'); + expect(screen.getByTestId('service-card-consulting').className).toContain('md:col-span-2'); + expect(screen.getByTestId('service-card-data')).toHaveAttribute('data-featured', 'false'); + }); + + it('renders service titles and descriptions', () => { + render(); + + expect(screen.getByText('数字化转型战略咨询')).toBeInTheDocument(); + expect(screen.getByText('数据智能与 AI 应用')).toBeInTheDocument(); + }); +}); diff --git a/src/app/(marketing)/services/services-content-v3.tsx b/src/app/(marketing)/services/services-content-v3.tsx index 3271161..eb230ff 100644 --- a/src/app/(marketing)/services/services-content-v3.tsx +++ b/src/app/(marketing)/services/services-content-v3.tsx @@ -195,6 +195,9 @@ function ServicesGridSection({ services }: { services?: Service[] }) { {displayServices.map((service, i) => (

diff --git a/src/app/(marketing)/solutions/solution-detail-content-v3.test.tsx b/src/app/(marketing)/solutions/solution-detail-content-v3.test.tsx new file mode 100644 index 0000000..8498d5e --- /dev/null +++ b/src/app/(marketing)/solutions/solution-detail-content-v3.test.tsx @@ -0,0 +1,125 @@ +import { describe, it, expect, jest } from '@jest/globals'; +import { render, screen } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import SolutionDetailContentV3 from './solution-detail-content-v3'; +import type { Solution } from '@/lib/constants/solutions'; + +jest.mock('framer-motion', () => ({ + motion: { + div: ({ children, className, ...props }: any) => ( +
{children}
+ ), + h1: ({ children, className, ...props }: any) => ( +

{children}

+ ), + p: ({ children, className, ...props }: any) => ( +

{children}

+ ), + span: ({ children, className, ...props }: any) => ( + {children} + ), + a: ({ children, href, className, ...props }: any) => ( + {children} + ), + }, + useInView: jest.fn(() => true), + AnimatePresence: ({ children }: any) => <>{children}, +})); + +jest.mock('@/components/ui/scroll-reveal', () => ({ + ScrollReveal: ({ children, className }: any) => ( +
{children}
+ ), + StaggerReveal: ({ children, className }: any) => ( +
{children}
+ ), +})); + +jest.mock('@/components/ui/button', () => ({ + Button: ({ children, ...props }: any) => , +})); + +jest.mock('lucide-react', () => { + const mockIcon = (name: string) => { + const Icon = (props: any) => ; + Icon.displayName = name; + return Icon; + }; + return { + ArrowRight: mockIcon('arrow-right'), + Factory: mockIcon('factory'), + ShoppingCart: mockIcon('shopping-cart'), + Heart: mockIcon('heart'), + GraduationCap: mockIcon('graduation-cap'), + Lightbulb: mockIcon('lightbulb'), + Settings: mockIcon('settings'), + Users: mockIcon('users'), + TrendingUp: mockIcon('trending-up'), + Package: mockIcon('package'), + BookOpen: mockIcon('book-open'), + MessageSquare: mockIcon('message-square'), + Calendar: mockIcon('calendar'), + Smartphone: mockIcon('smartphone'), + Route: mockIcon('route'), + Shield: mockIcon('shield'), + Truck: mockIcon('truck'), + }; +}); + +const baseSolution = { + id: 'manufacturing', + industry: '制造业', + title: '智能制造解决方案', + subtitle: '从传统制造到智慧工厂', + description: '测试描述', + challenges: ['数据孤岛'], + solutions: ['建设 MES'], + relatedProducts: ['erp'], + valueProposition: { + headline: '全链路数字化赋能', + points: [{ icon: 'Factory', title: '生产透明化', description: '实时掌握' }], + }, + suiteCombination: { + primaryProducts: ['erp'], + complementaryServices: ['consulting'], + rationale: 'ERP 打通核心链路', + }, +} as Solution; + +describe('SolutionDetailContentV3 - L3 信任层三档策略', () => { + it('does not render L3 sections when no case/certification data exists', () => { + render(); + + expect(screen.queryByText('同行业落地案例')).not.toBeInTheDocument(); + expect(screen.queryByText('资质认证')).not.toBeInTheDocument(); + }); + + it('renders case studies when data is available', () => { + const withCases: Solution = { + ...baseSolution, + caseStudies: [ + { + client: '某制造集团', + industry: '制造业', + challenge: '生产数据孤岛', + solution: '部署 MES 与 BI', + result: '生产效率提升 40%', + }, + ] as Solution['caseStudies'], + }; + render(); + + expect(screen.getByText('同行业落地案例')).toBeInTheDocument(); + expect(screen.getByText('某制造集团')).toBeInTheDocument(); + }); + + it('renders certifications when data is available', () => { + const withCerts: Solution = { + ...baseSolution, + certifications: [{ name: 'ISO 27001', issuer: '认证机构' }], + }; + render(); + + expect(screen.getByText('ISO 27001')).toBeInTheDocument(); + }); +}); diff --git a/src/app/(marketing)/solutions/solution-detail-content-v3.tsx b/src/app/(marketing)/solutions/solution-detail-content-v3.tsx index 0df7ae6..9b6f0b1 100644 --- a/src/app/(marketing)/solutions/solution-detail-content-v3.tsx +++ b/src/app/(marketing)/solutions/solution-detail-content-v3.tsx @@ -3,8 +3,7 @@ import { motion } from 'framer-motion'; import { ScrollReveal, StaggerReveal } from '@/components/ui/scroll-reveal'; 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 { 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 { Product } from '@/lib/constants/products'; @@ -341,6 +340,128 @@ interface CTASectionProps { solution: Solution; } +// ===== L3 信任层:客户案例(有数据才渲染,无数据不显示,不虚构)===== +interface CaseStudiesSectionProps { + caseStudies: Solution['caseStudies']; +} + +function CaseStudiesSection({ caseStudies }: CaseStudiesSectionProps) { + if (!caseStudies || caseStudies.length === 0) return null; + + return ( +
+
+
+ +
+ + +
+
+ + 客户案例 + +
+
+ +

+ 同行业落地案例 +

+ + +
+ + {caseStudies.map((study) => ( +
+
+
+ + {study.client.charAt(0)} + +
+
+ + {study.industry} + +
+
+ +
+

+ {study.client} +

+

+ {study.challenge} +

+
+
{study.result}
+
核心成果
+
+
+
+ ))} +
+
+
+
+ ); +} + +// ===== L3 信任层:资质认证(有数据才渲染,无数据不显示)===== +interface CertificationsSectionProps { + certifications: Solution['certifications']; +} + +function CertificationsSection({ certifications }: CertificationsSectionProps) { + if (!certifications || certifications.length === 0) return null; + + return ( +
+
+
+ +
+ + +
+
+ + 资质认证 + +
+
+ +

+ 资质认证 +

+ + +
+ + {certifications.map((cert, idx) => ( +
+
+ +
+
+
{cert.name}
+
{cert.issuer}
+
+
+ ))} +
+
+
+
+ ); +} + function CTASection({ solution }: CTASectionProps) { return (
@@ -398,6 +519,8 @@ export default function SolutionDetailContentV3({ solution, products = [] }: Sol + + ); diff --git a/src/app/(marketing)/solutions/solutions-content-v3.test.tsx b/src/app/(marketing)/solutions/solutions-content-v3.test.tsx new file mode 100644 index 0000000..041dad9 --- /dev/null +++ b/src/app/(marketing)/solutions/solutions-content-v3.test.tsx @@ -0,0 +1,148 @@ +import { describe, it, expect, jest } from '@jest/globals'; +import { render, screen } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import SolutionsContentV3 from './solutions-content-v3'; +import type { Solution } from '@/lib/constants/solutions'; +import type { Product } from '@/lib/constants/products'; + +jest.mock('framer-motion', () => ({ + motion: { + div: ({ children, initial, animate, whileInView, viewport, className, ...props }: any) => ( +
+ {children} +
+ ), + h1: ({ children, className, ...props }: any) => ( +

{children}

+ ), + p: ({ children, className, ...props }: any) => ( +

{children}

+ ), + span: ({ children, className, ...props }: any) => ( + {children} + ), + a: ({ children, href, className, ...props }: any) => ( + {children} + ), + }, + useInView: jest.fn(() => true), + AnimatePresence: ({ children }: any) => <>{children}, +})); + +jest.mock('@/components/ui/static-link', () => ({ + StaticLink: ({ children, href, className, ...props }: any) => ( + {children} + ), +})); + +jest.mock('lucide-react', () => { + const mockIcon = (name: string) => { + const Icon = (props: any) => ; + Icon.displayName = name; + return Icon; + }; + return { + ArrowUpRight: mockIcon('arrow-up-right'), + Factory: mockIcon('factory'), + ShoppingCart: mockIcon('shopping-cart'), + Heart: mockIcon('heart'), + GraduationCap: mockIcon('graduation-cap'), + Shield: mockIcon('shield'), + Truck: mockIcon('truck'), + }; +}); + +const mockSolutions: Solution[] = [ + { + id: 'manufacturing', + industry: '制造业', + title: '智能制造数字化转型方案', + subtitle: '端到端制造数字化', + description: '面向制造企业的整体转型方案', + challenges: ['数据孤岛', '库存积压'], + solutions: ['ERP 集成', 'BI 分析'], + relatedProducts: ['erp', 'bi'], + valueProposition: { + headline: '价值主张', + points: [{ icon: 'Factory', title: '降本增效', description: '描述' }], + }, + suiteCombination: { + primaryProducts: ['erp'], + complementaryServices: ['consulting'], + rationale: '理由', + }, + painPoints: ['数据孤岛'], + outcomes: [{ value: '30%', label: '效率提升' }], + recommendedProducts: ['erp', 'bi'], + }, + { + id: 'retail', + industry: '贸易零售', + title: '零售数字化解决方案', + subtitle: '全渠道零售数字化', + description: '面向零售企业的数字化方案', + challenges: ['渠道割裂'], + solutions: ['CRM 统一'], + relatedProducts: ['crm', 'bi'], + valueProposition: { + headline: '价值主张', + points: [{ icon: 'ShoppingCart', title: '提升转化', description: '描述' }], + }, + suiteCombination: { + primaryProducts: ['crm'], + complementaryServices: ['consulting'], + rationale: '理由', + }, + painPoints: ['渠道割裂'], + outcomes: [{ value: '3x', label: '转化率提升' }], + recommendedProducts: ['crm', 'bi'], + }, +]; + +const mockProducts: Product[] = [ + { + id: 'erp', + title: '睿新ERP管理系统', + description: 'ERP', + image: '/erp.png', + category: '企业旗舰系列', + categoryId: 'enterprise', + status: '已发布', + overview: '概述', + features: [], + benefits: [], + process: [], + specs: [], + tags: [], + heroThemeId: 'erp', + caseStudies: [], + dataProofs: [], + certifications: [], + bundle: 'enterprise', + }, +]; + +describe('SolutionsContentV3 - 非对称方案卡片', () => { + it('makes the first solution a featured full-width card', () => { + render(); + + expect(screen.getByTestId('solution-card-manufacturing')).toHaveAttribute('data-featured', 'true'); + expect(screen.getByTestId('solution-card-manufacturing').className).toContain('md:col-span-2'); + expect(screen.getByTestId('solution-card-retail')).toHaveAttribute('data-featured', 'false'); + }); + + it('renders solution industries', () => { + render(); + + expect(screen.getByText('制造业')).toBeInTheDocument(); + expect(screen.getByText('贸易零售')).toBeInTheDocument(); + }); +}); diff --git a/src/app/(marketing)/solutions/solutions-content-v3.tsx b/src/app/(marketing)/solutions/solutions-content-v3.tsx index 11c00ee..35e8704 100644 --- a/src/app/(marketing)/solutions/solutions-content-v3.tsx +++ b/src/app/(marketing)/solutions/solutions-content-v3.tsx @@ -108,7 +108,7 @@ function HeroSection() { ); } -function SolutionCard({ solution, index, products }: { solution: Solution; index: number; products?: Product[] }) { +function SolutionCard({ solution, index, products, featured = false }: { solution: Solution; index: number; products?: Product[]; featured?: boolean }) { const productLookup = products ?? []; const Icon = INDUSTRY_ICONS[solution.industry] || Factory; const recommendedProductNames = (solution.recommendedProducts ?? []) @@ -117,6 +117,9 @@ function SolutionCard({ solution, index, products }: { solution: Solution; index return (
@@ -195,7 +198,7 @@ function SolutionsGridSection({ solutions, products }: { solutions: Solution[];
{solutions.map((solution, index) => ( - + ))}
diff --git a/src/components/layout/footer.tsx b/src/components/layout/footer.tsx index 9644100..fa4217e 100644 --- a/src/components/layout/footer.tsx +++ b/src/components/layout/footer.tsx @@ -38,7 +38,7 @@ export function Footer() {
-

产品

+
产品
    {productItems.map((item) => (
  • @@ -54,7 +54,7 @@ export function Footer() {
-

解决方案

+
解决方案
    {solutionItems.map((item) => (
  • @@ -70,7 +70,7 @@ export function Footer() {
-

联系我们

+
联系我们
  • diff --git a/src/components/layout/header.tsx b/src/components/layout/header.tsx index 97d580e..100330c 100644 --- a/src/components/layout/header.tsx +++ b/src/components/layout/header.tsx @@ -158,7 +158,7 @@ function HeaderContent() { asChild className="" > - + 立即咨询 diff --git a/src/components/sections/bento-grid.test.tsx b/src/components/sections/bento-grid.test.tsx new file mode 100644 index 0000000..8b2d024 --- /dev/null +++ b/src/components/sections/bento-grid.test.tsx @@ -0,0 +1,55 @@ +import { describe, it, expect } from '@jest/globals'; +import { render, screen } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import { BentoGrid, BentoItem } from './bento-grid'; + +describe('BentoGrid', () => { + it('renders a list container with the 1px gap grid styling', () => { + render( + + 内容 + + ); + + const grid = screen.getByRole('list'); + expect(grid).toBeInTheDocument(); + expect(grid).toHaveClass('grid'); + expect(grid).toHaveClass('gap-px'); + expect(grid).toHaveClass('bg-border-primary'); + }); +}); + +describe('BentoItem', () => { + it('renders listitem with default small size classes', () => { + render(默认); + + const item = screen.getByRole('listitem'); + expect(item).toHaveAttribute('data-bento-size', 'small'); + expect(item).toHaveClass('md:col-span-1'); + expect(item).toHaveClass('lg:col-span-1'); + }); + + it('applies large size spanning classes', () => { + render(大卡); + + const item = screen.getByRole('listitem'); + expect(item).toHaveAttribute('data-bento-size', 'large'); + expect(item).toHaveClass('lg:col-span-2'); + expect(item).toHaveClass('lg:row-span-2'); + }); + + it('applies wide size spanning classes', () => { + render(宽卡); + + const item = screen.getByRole('listitem'); + expect(item).toHaveAttribute('data-bento-size', 'wide'); + expect(item).toHaveClass('md:col-span-2'); + expect(item).toHaveClass('lg:col-span-2'); + }); + + it('forwards a custom test id', () => { + render(内容); + + expect(screen.getByTestId('custom-id')).toBeInTheDocument(); + }); +}); diff --git a/src/components/sections/bento-grid.tsx b/src/components/sections/bento-grid.tsx new file mode 100644 index 0000000..e930e97 --- /dev/null +++ b/src/components/sections/bento-grid.tsx @@ -0,0 +1,61 @@ +'use client'; + +import { type ReactNode } from 'react'; +import { cn } from '@/lib/utils'; + +export type BentoSize = 'large' | 'wide' | 'small'; + +/** + * 通用 Bento 非对称网格容器(Bento Box Grid)。 + * + * 设计约定(CONTEXT.md): + * - `large`:占 2 列 × 2 行,用于旗舰/主推内容 + * - `wide`:占 2 列 × 1 行,用于次重点内容 + * - `small`:占 1 列 × 1 行,用于普通条目 + * - 使用 `gap-px + bg-border-primary` 实现 1px 细分隔线,延续全站网格语言 + * + * 品牌约束:容器本身不上色,品牌红由卡片内元素控制(≤10% 面积)。 + */ +export function BentoGrid({ children, className }: { children: ReactNode; className?: string }) { + return ( +
    + {children} +
    + ); +} + +/** + * Bento 网格中的单个卡片容器。 + * 通过 `size` 控制跨列/跨行,配合统一的 hover 微交互(上移 + 品牌色条延展)。 + */ +export function BentoItem({ + children, + size = 'small', + className, + testId, +}: { + children: ReactNode; + size?: BentoSize; + className?: string; + testId?: string; +}) { + return ( +
    + {children} +
    + ); +} diff --git a/src/components/sections/hero-product-visual.tsx b/src/components/sections/hero-product-visual.tsx new file mode 100644 index 0000000..69f1c35 --- /dev/null +++ b/src/components/sections/hero-product-visual.tsx @@ -0,0 +1,259 @@ +import { + LayoutDashboard, + BarChart3, + Package, + Users, + Settings, + Search, + Bell, + TrendingUp, + AlertCircle, + ArrowUpRight, + ArrowDownRight, +} from 'lucide-react'; +import { cn } from '@/lib/utils'; + +const NAV_ITEMS = [ + { label: '经营驾驶舱', icon: LayoutDashboard, active: true }, + { label: '财务管理', icon: BarChart3 }, + { label: '供应链', icon: Package }, + { label: '客户管理', icon: Users }, + { label: '系统设置', icon: Settings }, +]; + +const KPIS = [ + { label: '营收同比', value: '+32.6%', trend: 'up', note: '较上期' }, + { label: '订单履约率', value: '98.2%', trend: 'up', note: '目标 95%' }, + { label: '库存周转', value: '5.8 天', trend: 'down', note: '提速 1.2 天' }, + { label: '客户新增', value: '+18.4%', trend: 'up', note: '本月' }, +] as const; + +const ALERTS = [ + { title: '华东区库存预警', desc: '3 个 SKU 低于安全库存', level: 'high' }, + { title: '大客户合同待审批', desc: '2 份合同等待审批', level: 'medium' }, + { title: '本月目标完成 82%', desc: '距离目标还有 18%', level: 'normal' }, +]; + +const TABLE_ROWS = [ + { name: '成都某制造集团', amount: '¥ 1,280,000', status: '已签约', date: '今日' }, + { name: '华南零售连锁', amount: '¥ 860,000', status: '洽谈中', date: '昨日' }, + { name: '华东物流企业', amount: '¥ 420,000', status: '方案确认', date: '07-18' }, +]; + +function TrendBadge({ trend, label }: { trend: 'up' | 'down'; label: string }) { + const Icon = trend === 'up' ? ArrowUpRight : ArrowDownRight; + return ( + + + {label} + + ); +} + +function StatusBadge({ status }: { status: string }) { + const tone = + status === '已签约' + ? 'bg-success-bg text-success' + : status === '洽谈中' + ? 'bg-brand-bg text-brand' + : 'bg-bg-secondary text-text-secondary'; + return ( + + {status} + + ); +} + +export function HeroProductVisual({ className }: { className?: string }) { + return ( +
    + +
    + ); +} diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 3372c51..2e89d47 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -1,5 +1,7 @@ export { COMPANY_INFO, + TRUST_SIGNALS, + EARLY_ACCESS, NAVIGATION, NAVIGATION_V2, MEGA_DROPDOWN_DATA, diff --git a/src/lib/constants/company.ts b/src/lib/constants/company.ts index 28548e5..bffdfb2 100644 --- a/src/lib/constants/company.ts +++ b/src/lib/constants/company.ts @@ -5,9 +5,58 @@ export const COMPANY_INFO = { slogan: '企业数字化转型的同行者', description: '专业核心团队,以结果导向的服务理念,做企业数字化转型的同行者与成长伙伴', founded: '2026', + foundedFull: '2026 年 1 月', location: '四川省成都市', email: 'contact@novalon.cn', address: '中国四川省成都市龙泉驿区幸福路12号', icp: '蜀ICP备2026013658号', police: '川公网安备51010602003285号', } as const; + +/** + * 首页信任层可验证信任信号(L3 Trust & Authority)。 + * + * 零编造原则:以下每一项均可追溯来源,未取得真实资质/案例前不编造。 + * - 私有化部署:产品规格(私有化部署能力) + * - 本地化服务:工商注册地址(成都龙泉驿区) + * - 全流程方法:服务方法论(诊断→设计→交付→运维) + * - 首批客户共创:无公开案例,如实标注「共创中」而非虚构客户 + * + * 说明:2026 年成立 / 核心团队 / 自研产品数等基础信号由 Hero 数据条承载, + * 信任区聚焦「来源可验证 + 共创状态」,避免全站数字重复(critique P1)。 + */ +export const TRUST_SIGNALS = [ + { + value: '100%', + label: '私有化部署', + desc: '核心数据不出境,满足合规要求', + source: '产品规格', + }, + { + value: '成都', + label: '本地化服务', + desc: '立足龙泉驿区,贴近服务西南企业', + source: '工商注册地址', + }, + { + value: '全流程', + label: '陪伴式交付', + desc: '诊断 → 设计 → 交付 → 运维,全程陪伴', + source: '服务方法论', + }, + { + value: '共创', + label: '首批客户共创', + desc: '与早期客户共同打磨产品,成果可验证', + source: '如实标注', + }, +] as const; + +/** + * 首批客户共创状态(无真实 quote 时的如实标注)。 + * 有真实客户 quote / 共创伙伴后,可在 `src/components/content/testimonials.tsx` 启用 Testimonial 区块替换本标签。 + */ +export const EARLY_ACCESS = { + label: '首批客户共创中', + desc: '我们正与首批客户共同打磨产品,公开案例将在验证后发布。', +} as const; diff --git a/src/lib/constants/index.ts b/src/lib/constants/index.ts index a17f6fd..4cf9999 100644 --- a/src/lib/constants/index.ts +++ b/src/lib/constants/index.ts @@ -1,4 +1,4 @@ -export { COMPANY_INFO } from './company'; +export { COMPANY_INFO, TRUST_SIGNALS, EARLY_ACCESS } from './company'; export { NAVIGATION, NAVIGATION_V2, MEGA_DROPDOWN_DATA } from './navigation'; export type { NavigationItem, NavigationItemV2, MegaDropdownItem, MegaDropdownGroup, MegaDropdownData } from './navigation'; export { STATS, type StatItem } from './stats';