diff --git a/CONTEXT.md b/CONTEXT.md index a45ff1e..cd80a19 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -141,7 +141,8 @@ Novalon 的 6 个核心产品,互为互补关系,常以组合形式出现在 | Hero 视觉方向修正 | 当前深色渐变风格偏离"水墨雅致"品牌定位,需转向浅色/宣纸色底、深色文字、品牌色点缀;通过 web-design-engineer skill 做原型验证后再落地 | ✅ 2026-06-07 确认 | | 导航体现 HSI 层级 | Products(Hub)视觉权重最高,Solutions(Spoke)次之,Services 最轻;Products 下拉菜单需展示套装区/专业产品区分区 | ✅ 2026-06-07 确认 | | 方案页删除服务方式区域 | Solutions 列表页"服务方式"区域职责越界(属 Services 层),应删除;替换为方案→服务的轻量关联推荐链接 | ✅ 2026-06-07 确认 | -| L3 信任层内容策略 | 初创阶段无真实案例/数据/资质,L3 暂不渲染;组件结构预留,数据就绪后启用。三档策略:有案例→正常展示;有意向→"正在服务中"标签;无数据→不显示 L3。当前全部标记为 TODO | ✅ 2026-06-07 确认(TODO) | +| L3 信任层内容策略 | 初创阶段无真实案例/数据/资质,L3 暂不渲染;组件结构预留,数据就绪后启用。三档策略:有案例→正常展示;有意向→"正在服务中"标签;无数据→不显示 L3。**阶段 0(2026-08-20)已落地**:信任策略从「结果证据」转向「可验证的过程+能力+治理证据」——首页「共创计划」板块(共创进行中/产品内测中/成果授权公开三档如实状态 + 招募 CTA)、关于页资质区如实「建设中」空态 | ✅ 2026-06-07 确认 / 阶段 0 完成 2026-08-20 | +| 首页共创计划板块 | 成立 <1 年无真实案例时,以主动板块替代被动标签:`page-copy` 字段 `earlyAccessTitle`/`earlyAccessCtaLabel`/`earlyAccessStatus` 驱动首页「首批客户共创计划」板块,如实呈现共创/内测/授权公开状态并引导转化(成为共创客户 → /contact)。零编造:不虚构客户/数据/资质 | ✅ 2026-08-20 确认 | | 组件版本统一 | 独立产品迁移后删除 `detail/`(V1),将 `detail-v2/` 重命名为 `detail/`,组件文件名去掉版本后缀(如 `DetailHeroV3` → `DetailHero`) | ✅ 2026-06-07 确认(待 Phase 0 执行) | | 设计定位 | 纯咨询专业风(Accenture + Bain + Porsche 三位一体),水墨元素已移除 | ✅ 2026-07-10 更新(ADR-0006) | | 设计 DNA 框架 | 三维度模型:Design System + Design Style + Visual Effects,基于 Accenture+Bain+Porsche 整合 | ✅ 2026-06-28 确认 | @@ -162,6 +163,9 @@ Novalon 的 6 个核心产品,互为互补关系,常以组合形式出现在 | 品牌故事页品牌名 | 使用与 footer Logo 完全相同的青柳隶书 SVG path(`BrandCalligraphyName`),不加载 4.4MB 的 AoyagiReisho 字体文件,兼顾品牌一致性与性能 | ✅ 2026-08-18 确认 | | 风格主词 | Swiss Modernism 2.0(骨架)+ Bento Box Grid(信息组织)+ Hero-Centric & Conversion-Optimized(首屏转化)+ Trust & Authority & Social Proof(信任层)+ Motion-Driven(差异化记忆点)+ Accessible & Ethical(底线) | ✅ 2026-08-18 确认 | | 官网产品模块定位 | **IHG/字节式品牌矩阵**:官网品牌宣传为主,产品矩阵页保留为聚合入口(每产品一句话定位+指标证据+外链独立站);成熟产品 `externalUrl` 外链独立站(NovaVis → novavis.p.novalon.cn 已实践),未成熟产品官网详情页占位,独立站上线后切换外链。参照案例(已核实):IHG 品牌组合页 / 字节跳动 bytedance.com/products / 腾讯 tencent.com/business.html(模式 B:品牌+品牌矩阵聚合页);对照金蝶/用友(模式 C:产品为主,官网承载详情与转化) | ✅ 2026-08-19 确认 | +| 品牌主口号 | **智连未来 · 成长伙伴 —— 您的数字化转型同行者**(整句全站统一采用)。品牌叙事内核(定位声明 / 承诺 / L0 价值主张 / L1 三支柱 / 语气语调)见 `docs/brand-narrative-core.md`,作为全站文案统一唯一锚点 | ✅ 2026-08-19 确认 | +| 品牌叙事 CMS 化 | 品牌叙事内核全部下沉到 CMS `site-config` 模型(`slogan` / `valueProposition` / `positioningStatement` / `brandPromise` / `toneOfVoice` / `pillars`),后台可编辑;代码侧 `COMPANY_INFO` 与 `BRAND_NARRATIVE`(`src/lib/constants/company.ts`)作为未配置 CMS 时的兜底唯一真源,`layout.tsx` 读取后经 `SiteConfigProvider` 注入全站 | ✅ 2026-08-19 确认 | +| 结构性文案 CMS 化 | 新增 `page-copy` 内容模型承载各业务页面章节标题/眉标/描述/CTA/空状态,覆盖首页 + 服务/方案/产品/案例/新闻列表页,seed 写入 6 条(home/services/solutions/products/cases/news)。实现为「CMS 优先 + 硬编码兜底」:CMS 未配置时回退 `TRUST_SIGNALS`/`EARLY_ACCESS`/`NARRATIVE_ACTS` 等常量,保证任何情况不白屏。数据层新增 `getPageCopy(pageCode)` / `getHomePageCopy()`,各 `page.tsx` 读取后以 `pageCopy` props 传入组件。详情页小节标题、页面内嵌业务常量(服务流程/合作模式/产品组合)、Header/Footer 等 UI 外壳按 UI 职责保留代码。数据实体(服务/产品/方案/案例/新闻/指标/Hero)仍由 `data-server` 从 Prisma 读取 | ✅ 2026-08-19 确认 | ## 页面类型与四层映射 diff --git a/README.md b/README.md index 8133beb..9446340 100644 --- a/README.md +++ b/README.md @@ -176,7 +176,10 @@ - [x] **方法论 CMS 化(Phase 4 信任证据·方法论骨架)**:新增 `methodology` 内容模型 + `/methodology` 页面骨架(Hero / 四阶段 / 空态 / CTA),首页 CTA「了解我们的方法论」指向 /methodology,sitemap 收录,视觉基线已生成 - [x] **产品外链机制补全(对齐 Google / 字节 / 腾讯对标)**:product 模型补 externalUrl 字段 + 企业套装详情页 redirect(复用 standalone 模板)+ 矩阵卡「外部站点」徽标;清理 3 个无引用 AoyagiReisho 字体文件(性能优化) - [x] **首页 Hero 口号恢复(2026-08-19)**:应用户偏好,H1 恢复为「让每一家企业都拥有数据驱动的决策能力」(seed hero-banner + home 兜底默认同步,db:seed 已生效),视觉基线三端更新 -- [ ] **信任证据补齐(Phase 4 内容待办)**:首批客户共创案例与可量化成果——需提供真实素材后实施(零编造原则) +- [x] **结构性文案 CMS 化(全站完成,2026-08-19)**:`page-copy` 内容模型承载全部营销页面的结构性文案(章节标题/眉标/描述/CTA/空状态),覆盖:首页(信任区/叙事情节/服务案例区标题/CTA)+ 服务页(Hero/区块标题/CTA/空状态)+ 方案页(Hero/区块/CTA)+ 产品页(Hero/双体系区块/CTA)+ 案例页(Hero/筛选区)+ 新闻页(Hero/空状态)。实现为「CMS 优先 + 硬编码兜底」,CMS 未配置时回退 `TRUST_SIGNALS`/`EARLY_ACCESS`/`NARRATIVE_ACTS` 等常量,任何情况不白屏。数据流:`page.tsx → getPageCopy(pageCode) → pageCopy props`;seed 写入 6 条 page-copy(home/services/solutions/products/cases/news),RBAC 权限已含 `page-copy`,本地 db:seed 已生效(条目 33→38)。详情页小节标题、页面内嵌业务常量(服务流程/合作模式/产品组合)、Header/Footer UI 外壳按《AGENTS.md·UI 职责》保留代码。通过 type-check / lint(0 errors) / 单元测试 128 suites / 1623 tests / 运行态冒烟(6 页面 HTTP 200 + CMS 文案渲染确认) +- [x] **视觉回归基线更新(CMS 化后,2026-08-19/20)**:CMS 化改造涉及的产品页 CTA/双体系区块品牌红位置、服务页区块标题移动端换行等细微视觉调整已通过 `--update-snapshots` 更新三端(chromium-desktop/tablet/mobile)基线;三端普通视觉验证 66 passed、功能 E2E(@smoke|@critical)45 passed / 9 skipped 全部通过(此前因系统高负载受阻,用户清理 Chrome 进程后完成) +- [x] **信任证据补齐·阶段 0(成立 <1 年策略,2026-08-20)**:遵循零编造原则,将信任策略从「结果证据」转向「可验证的过程 + 能力 + 治理证据」。落地:① 首页信任区「首批客户共创中」单行标签升级为「首批客户共创计划」板块(标题/描述/三条如实状态:共创进行中、产品内测中、成果授权公开/招募 CTA,page-copy 字段 earlyAccessTitle/earlyAccessCtaLabel/earlyAccessStatus 驱动);② 关于页资质区由「数据空则隐藏」改为如实「资质建设中,将随业务开展陆续公示」空态。不虚构任何客户案例/数据/资质。通过 type-check / lint(0 errors) / 单元测试 128 suites / 1623 tests / 运行态冒烟(首页共创板块 + about 资质区渲染)/ 三端视觉基线更新 + 66 passed 验证 +- [ ] **信任证据补齐·阶段 1(待内测/试点客户素材)**:D 类共创证据如实披露「共创中/内测中/已交付」三档状态 + 经授权客户(脱敏);阶段 2 在有交付成果后经授权公示可量化结果(零编造原则) ## 技术栈 diff --git a/docs/brand-narrative-core.md b/docs/brand-narrative-core.md new file mode 100644 index 0000000..4ac5eab --- /dev/null +++ b/docs/brand-narrative-core.md @@ -0,0 +1,136 @@ +# 品牌叙事内核(Brand Narrative Core) + +> 本文档是全站文案(含 CMS seed)统一的唯一锚点。 +> 所有页面、组件、CMS 内容的文案,都必须以本文档定义的**定位声明、信息层级、语气语调**为生长来源,改动前先回到本文件对齐。 + +- 版本:v1.0 +- 确认日期:2026-08-19 +- 状态:✅ 已确认 + +## 0. 落地实现(CMS 化) + +本文所述的品牌叙事内核**已全部下沉到 CMS**,后台可编辑: + +| 叙事内容 | CMS 模型字段(`site-config`,后台站点配置中编辑) | 代码兜底真源 | +|---------|----------------------------------------------|-------------| +| 主口号 slogan | `slogan` | `COMPANY_INFO.slogan` | +| L0 一句话价值主张 | `valueProposition` | `BRAND_NARRATIVE.valueProposition` | +| 品牌定位声明 | `positioningStatement` | `BRAND_NARRATIVE.positioningStatement` | +| 品牌承诺 | `brandPromise` | `BRAND_NARRATIVE.brandPromise` | +| 三支柱(L1) | `pillars`(数组) | `BRAND_NARRATIVE.pillars` | +| 语气语调(写作规范,不展示) | `toneOfVoice` | `BRAND_NARRATIVE.toneOfVoice` | + +- **唯一真源**:`src/lib/constants/company.ts` 的 `COMPANY_INFO` 与 `BRAND_NARRATIVE`。 +- **注入链路**:`prisma/seed.ts` 将上述常量写入 CMS → `src/app/layout.tsx` 读取 `site-config` → `SiteConfigProvider` 注入全站 → 首页 Hero / Footer 等触点消费(后台改值即全站生效,无需改代码)。 +- **新增字段定义**:`prisma/seed.ts` 的 `site-config` 模型字段块,执行 `npm run db:seed` 生效。 + +--- + +## 1. 品牌定位声明(Positioning Statement) + +**睿新致远(Novalon)是中国成长型企业的数字化转型同行者。** + +对 **面临数字化升级但不具备大型 IT 成本承受力的成长型企业**, +我们提供 **从战略诊断到系统落地、再到持续陪跑的一站式服务与产品矩阵**, +凭借 **结果导向的方法论 + 自研可组合的产品 + 复合型工程师团队**, +让企业在可衡量的前提下,把数字化从「投入」变成「成长」。 + +**我们的角色**:不是技术供应商,而是与您同行的伙伴—— +懂业务、说实话、陪到底,以您的业务改善作为我们交付成功与否的唯一标准。 + +**竞争性的差异化**:大型咨询公司「够专业但太贵、太远」;软件厂商「有产品但不管您的业务」。 +我们在两者之间,提供「咨询的严谨 × 工程的落地 × 并肩的陪伴」。 + +> 品牌角色一句话:**我们不只是帮您上线系统,而是陪您把转型做成成长。** + +--- + +## 2. 品牌承诺(Brand Promise) + +> 我们承诺:**每一份方案都源于对您业务的理解;每一次交付都以结果说话;每一段合作都以陪您走到成长为终点。** + +三层兑现路径(对应服务方法论): +1. **懂** — 用诊断而非推销,先理解您的业务、痛点与约束。 +2. **做** — 用结果导向的交付,让每一分投入可衡量、可验证。 +3. **陪** — 不止交付项目,更持续陪跑,助您沉淀团队内生能力。 + +--- + +## 3. 主口号与结构拆解 + +**主口号(全站一致采用整句):** + +> ## 智连未来 · 成长伙伴 —— 您的数字化转型同行者 + +口号是「品牌角色」的精炼,内含三个语义,叙事时分层使用,但**展示时保持整句**: + +| 词 | 定义(在文案中如何解释它) | +|----|--------------------------| +| **智连未来** | 用智能技术与数据,连接您当前业务与下一阶段的增长目标——「连接的不是技术,而是您企业的未来」。 | +| **成长伙伴** | 我们以一荣俱荣的伙伴身份参与,您的成长即我们的成果。不是供应商,是伙伴。 | +| **数字化转型同行者** | 全链路的同路人:从战略、到落地、到陪跑,我们与您并肩而非站在对面。 | + +**展示约束**: +- 作为品牌主口号,全站统一呈现为完整句「智连未来 · 成长伙伴 —— 您的数字化转型同行者」。 +- 在 Hero 标签(badge)、Footer 品牌区、About 愿景区等品牌触点呈现。 +- 单页内不重复罗列三短语三个定义,仅在「为什么是它」的叙事区块(About 愿景层)展开解释。 + +--- + +## 4. 信息层级(Message Map) + +口号向下展开为一套**递进式信息层级**,供不同页面与场景取用。任何文案都应落在其中一层,避免自行发散。 + +### L0 一句话价值主张(Elevator Pitch) +> **让数据连接未来,让伙伴陪您成长——我们是您数字化转型的同行者。** + +### L1 三支柱(核心支撑信息,对应产品 / 方案 / 服务) +1. **战略同行** → 从诊断到蓝图,先想清楚再动手(对应:咨询服务、方法论)。 +2. **落地同行** → 用自研可组合产品与工程交付,让转型真正落地(对应:产品矩阵、行业方案)。 +3. **陪跑同行** → 交付后仍持续陪跑,助您优化结果(对应:数据/AI 应用、持续运维、方法迭代)。 + +### L2 逐条证据链(每条支柱下的可验证支撑,符合「零编造」) +| 支柱 | 证据链(据此撰写具体文案) | +|------|------------------------| +| 战略同行 | 专业核心团队 / 结果导向方法论(诊断→规划→实施→优化)/ 2026 年成立后的首批客户共创 | +| 落地同行 | 6 款自研可组合产品 / 私有化部署 / 本地化服务(成都龙泉驿区)/ 复合型工程师团队 | +| 陪跑同行 | 数据与 AI 应用能力 / 持续运维 / 以业务改善为衡量标准 | + +> 原则:**凡出现数字、资质、案例,必须可溯源;未取得真实成绩前,如实标注「共创中」而非虚构。** + +--- + +## 5. 语气与语调(Tone of Voice) + +**基调一句话:严谨,但不说教;专业,但不冰冷;同行,但不越位。** + +| 维度 | 我们这么做 | 我们不这么做 | +|------|-----------|-------------| +| 专业 | 一句话讲清价值主张,答案优先,数据先行 | 堆砌术语、用行话自我感觉良好 | +| 诚实 | 可衡量的才说,未成的如实「共创中」 | 虚构案例、夸大产能、贩卖焦虑 | +| 伙伴 | 用「我们 / 您」,站在同侧看问题 | 居高临下说教、把您当「客户」对立面 | +| 简洁 | 短句、有力的动词、克制的形容词 | 冗长排比、空洞的口号式修饰 | + +**关键词汇偏好**:同行、陪伴、理解业务、结果导向、可衡量、落地、连接未来、共同成长。 + +--- + +## 6. 一致性护栏(落地后的自洽检查) + +为保障全站 + CMS 文案自洽,执行时统一遵循以下规则: + +1. **单一锚点**:所有品牌口号、公司简介(slogan/description)、Hero 主张,统一取自 `COMPANY_INFO`(代码唯一真源),CMS `site-config` seed 同步引用同一常量,禁止出现第二套口径。 +2. **三层对位**:任何页面文案落位后,可映射到信息层级(L1 三支柱 / L2 证据链)之一;无法映射的文案视为冗余,删除或改写。 +3. **一句话原则**:每页 Hero 只能有一个核心主张,不叠加两套口号。 +4. **品牌红克制**:品牌红 #C41E3A 仅用于关键触点,面积 ≤10%,不用于段落正文。 +5. **「同行者」语义闭环**:首页 Hero、About 愿景、Footer 品牌区三个主要触点必须使用同一句主口号,避免各自解释。 +6. **零编造**:数字、资质、案例必须可溯源;未达成如实标注。 + +--- + +## 7. 待评审确认点 + +评审通过本文档后,将据此执行全站与 CMS 文案统一。请确认以下两点: + +1. 本文「信息层级(L0/L1/L2)」与「语气语调」是否符合你对品牌的口径? +2. 主口号在三支柱中的落位(战略同行 / 落地同行 / 陪跑同行)是否准确? \ No newline at end of file diff --git a/prisma/seed.ts b/prisma/seed.ts index fb08fe3..843d462 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -11,7 +11,8 @@ import { PRODUCTS } from './seeds/products'; import { STANDALONE_PRODUCTS } from './seeds/standalone-products'; import { SOLUTIONS } from './seeds/solutions'; import { STATS } from '../src/lib/constants/stats'; -import { COMPANY_INFO } from '../src/lib/constants/company'; +import { COMPANY_INFO, TRUST_SIGNALS, EARLY_ACCESS } from '../src/lib/constants/company'; +import { BRAND_NARRATIVE } from '../src/lib/constants/company'; import { METHODOLOGY } from '../src/lib/constants/methodology'; import { NAVIGATION_V2, MEGA_DROPDOWN_DATA } from '../src/lib/constants/navigation'; @@ -233,6 +234,7 @@ async function main() { 'standalone-product', 'media', 'methodology', + 'page-copy', ]; // 为 super_admin 之外的角色创建权限 @@ -471,6 +473,16 @@ async function main() { { name: 'displayName', type: 'text', label: '展示名称', required: true }, { name: 'slogan', type: 'text', label: '品牌标语', required: true }, { name: 'description', type: 'textarea', label: '公司简介', required: true }, + { name: 'valueProposition', type: 'textarea', label: '一句话价值主张(L0)', description: '品牌叙事核心,用于首页 Hero / 品牌触点' }, + { name: 'positioningStatement', type: 'textarea', label: '品牌定位声明', description: '品牌叙事核心 — 为谁、解决什么、凭什么' }, + { name: 'brandPromise', type: 'textarea', label: '品牌承诺', description: '品牌叙事核心 — 我们承诺什么' }, + { name: 'toneOfVoice', type: 'textarea', label: '语气语调(写作规范)', description: '内部文案写作参考,不在页面直接展示' }, + { name: 'pillars', type: 'array', label: '信息层级三支柱(L1)', description: '战略同行 / 落地同行 / 陪跑同行,对应产品、方案、服务', fields: [ + { name: 'key', type: 'text', label: '标识(如 strategy)' }, + { name: 'title', type: 'text', label: '标题' }, + { name: 'tagline', type: 'text', label: '一句话副标' }, + { name: 'description', type: 'textarea', label: '描述' }, + ]}, { name: 'founded', type: 'text', label: '成立年份' }, { name: 'location', type: 'text', label: '所在地' }, { name: 'email', type: 'text', label: '联系邮箱', required: true }, @@ -885,6 +897,11 @@ async function main() { displayName: COMPANY_INFO.displayName, slogan: COMPANY_INFO.slogan, description: COMPANY_INFO.description, + valueProposition: BRAND_NARRATIVE.valueProposition, + positioningStatement: BRAND_NARRATIVE.positioningStatement, + brandPromise: BRAND_NARRATIVE.brandPromise, + toneOfVoice: BRAND_NARRATIVE.toneOfVoice, + pillars: BRAND_NARRATIVE.pillars, founded: COMPANY_INFO.founded, location: COMPANY_INFO.location, email: COMPANY_INFO.email, @@ -906,6 +923,103 @@ async function main() { ]); console.log(' ✅ 导航菜单'); + console.log('📝 导入首页文案配置...'); + await seedItems('page-copy', [ + { + slug: 'home', + title: '首页文案配置', + trustEyebrow: '值得信赖', + trustTitle: '以可衡量的成果\n赢得长期信任', + trustSignals: TRUST_SIGNALS.map((s) => ({ value: s.value, label: s.label, desc: s.desc, source: s.source })), + earlyAccessLabel: EARLY_ACCESS.label, + earlyAccessDesc: EARLY_ACCESS.desc, + earlyAccessTitle: '首批客户共创计划', + earlyAccessCtaLabel: '成为共创客户', + earlyAccessStatus: [ + { label: '共创进行中', desc: '我们正与首批客户共同打磨产品与方案,以真实运营数据验证业务改善。' }, + { label: '产品内测中', desc: '自研产品(如睿视 NovaVis)处于内测阶段,成熟后正式发布。' }, + { label: '成果授权公开', desc: '案例与可量化成果在获得客户授权后公示——经得起衡量。' }, + ], + narrativeTitle: '从问题到结果,\n一条可量化的路径', + narrativeActs: [ + { number: '01', title: '诊断现状', desc: '从业务目标出发,梳理流程断点、数据孤岛与瓶颈环节,形成可量化的诊断结论。', href: '/services/consulting', cta: '了解诊断方法' }, + { number: '02', title: '设计路径', desc: '基于诊断结论设计系统架构与实施路线图,明确每一阶段的交付物与衡量指标。', href: '/services/solutions', cta: '查看方案设计' }, + { number: '03', title: '交付成果', desc: '以自研产品组合与专业实施团队完成交付,用真实运营数据验证业务改善,持续迭代。', href: '/products', cta: '浏览产品矩阵' }, + ], + servicesTitle: '从战略到落地,\n可量化的一站式服务', + casesEyebrow: '共创进行时', + casesTitle: '每一个项目,\n都以业务改善为衡量标准', + casesDescription: '我们正与首批客户共同打磨产品与方案,以真实运营数据验证业务改善。公开案例将在验证后发布——合作成果,经得起衡量。', + ctaTitleLine1: '准备好开始了吗?', + ctaTitleLine2: '让数据真正转化为业务增长', + ctaLabel: '预约咨询', + }, + ]); + console.log(' ✅ 首页文案配置'); + + console.log('📝 导入列表页文案配置...'); + await seedItems('page-copy', [ + { + slug: 'services', + title: '服务页文案配置', + servicesHeroTitle1: '从规划到运维', + servicesHeroTitle2: '全程陪伴', + servicesHeroDescription: '不只是技术供应商,更是您数字化转型的同行者。\n从第一次沟通到系统稳定运行,我们始终在您身边。', + servicesCtaPrimary: '聊聊您的需求', + servicesCtaSecondary: '查看产品矩阵', + servicesGridTitle: '四大核心服务,覆盖数字化全链路', + servicesEmptyState: '暂无服务数据', + servicesCtaTitle: '准备好开始了吗?', + servicesCtaDescription: '无论是一个明确的项目,还是一个模糊的想法,我们都愿意坐下来和您一起理清楚。\n首次咨询免费,没有任何销售压力。', + }, + { + slug: 'solutions', + title: '方案页文案配置', + solutionsHeroTitle1: '专注行业场景的', + solutionsHeroTitle2: '解决方案', + solutionsHeroDescription: '端到端交付可量化的业务成果。\n基于自研产品矩阵,为制造、零售、教育、医疗、金融、物流六大行业沉淀最佳实践。', + solutionsCtaPrimary: '立即咨询', + solutionsCtaSecondary: '浏览产品', + solutionsGridTitle: '行业解决方案', + solutionsGridDescription: '每个方案都明确推荐产品组合和服务包,确保落地效果', + solutionsCtaTitle: '告诉我们您的行业场景', + solutionsCtaDescription: '无论您处于哪个行业、哪个数字化阶段,我们都能为您推荐最合适的产品组合和服务包。', + }, + { + slug: 'products', + title: '产品页文案配置', + productsHeroTitle1: '自研产品矩阵', + productsHeroTitle2: '支撑企业', + productsHeroTitle3: '数字化核心系统', + productsHeroDescription: '从业务系统到数据智能,深度自研、互为补充。\n6 大企业套装与专业产品线,覆盖企业数字化核心场景。', + productsCtaPrimary: '了解我们的服务', + productsCtaSecondary: '查看行业方案', + productsBundleTitle: '两套产品体系\n覆盖不同场景', + productsBundleDescription: '企业套装以组合形式解决核心管理问题,专业产品聚焦独立场景深度赋能。', + productsCtaTitle: '以自研产品\n支撑数字化落地', + productsCtaDescription: '从企业套装到专业产品,以自研能力与专业服务,陪伴企业完成数字化升级。', + }, + { + slug: 'cases', + title: '案例页文案配置', + casePageHeroTitle1: '每一个项目,', + casePageHeroTitle2: '都以成果为目标', + casePageHeroDescription: '我们正在与首批客户共同创造价值故事。\n从挑战到方案,从过程到成果——用数据说话,用结果证明。', + casePageFilterTitle: '全部案例', + casePageFilterDescription: '按行业筛选,找到与您业务最相关的成功故事', + }, + { + slug: 'news', + title: '新闻页文案配置', + newsPageHeroEyebrow: 'News & Insights', + newsPageHeroTitle: '新闻与\n洞察', + newsPageHeroDescription: '最新动态、产品更新与行业洞察。了解数字化趋势,把握技术脉搏。', + newsPageEmptyTitle: '没有找到相关新闻', + newsPageEmptyDescription: '试试其他关键词或分类', + }, + ]); + console.log(' ✅ 列表页文案配置'); + // ============ 5. 创建默认内容区域 ============ console.log('📝 创建默认内容区域...'); await seedHomeZones(); diff --git a/src/app/(marketing)/about/about-content-v4.tsx b/src/app/(marketing)/about/about-content-v4.tsx index b9caff8..fed04d1 100644 --- a/src/app/(marketing)/about/about-content-v4.tsx +++ b/src/app/(marketing)/about/about-content-v4.tsx @@ -302,7 +302,38 @@ function CertificationsSection({ data }: { data: AboutData }) { const certifications = data.certifications ?? []; const partners = data.partners ?? []; - if (certifications.length === 0 && partners.length === 0) return null; + // 初创阶段无资质/伙伴数据时,如实呈现「建设中」状态而非隐藏整块(零编造) + if (certifications.length === 0 && partners.length === 0) { + return ( +
+
+ +
+ Certifications +
+

+ 资质与认证 +

+
+
+ + 资质建设中 +
+

+ 公司资质与认证信息建设中,将随业务开展与取得进度陆续公示。 +

+
+
+
+
+ ); + } return (
diff --git a/src/app/(marketing)/cases/cases-content-v3.tsx b/src/app/(marketing)/cases/cases-content-v3.tsx index fe78217..bf08128 100644 --- a/src/app/(marketing)/cases/cases-content-v3.tsx +++ b/src/app/(marketing)/cases/cases-content-v3.tsx @@ -95,14 +95,23 @@ function CaseCard({ caseItem, index }: { caseItem: CaseStudy; index: number }) { export default function CasesContentV3({ cases: casesProp, industries: industriesProp, + pageCopy, }: { cases?: CaseStudy[]; industries?: { id: string; label: string }[]; + pageCopy?: Record | null; }) { const [selectedIndustry, setSelectedIndustry] = useState('all'); const industries = industriesProp ?? []; + const heroTitle1 = (pageCopy?.casePageHeroTitle1 as string) || '每一个项目,'; + const heroTitle2 = (pageCopy?.casePageHeroTitle2 as string) || '都以成果为目标'; + const heroDescription = (pageCopy?.casePageHeroDescription as string) || '我们正在与首批客户共同创造价值故事。\n从挑战到方案,从过程到成果——用数据说话,用结果证明。'; + const heroDescriptionLines = heroDescription.split('\n'); + const filterTitle = (pageCopy?.casePageFilterTitle as string) || '全部案例'; + const filterDescription = (pageCopy?.casePageFilterDescription as string) || '按行业筛选,找到与您业务最相关的成功故事'; + const filteredCases = useMemo(() => { const allCases = casesProp ?? []; if (selectedIndustry === 'all') return allCases; @@ -130,11 +139,11 @@ export default function CasesContentV3({ transition={{ duration: 1, delay: 0.2, ease: EASE_OUT }} className="text-4xl sm:text-5xl md:text-6xl lg:text-7xl xl:text-8xl font-black text-ink leading-[0.88] tracking-tightest mb-10 sm:mb-12 md:mb-16" > -
每一个项目,
+
{heroTitle1}
- 都以成果为目标 + {heroTitle2} - 我们正在与首批客户共同创造价值故事。 -
- 从挑战到方案,从过程到成果——用数据说话,用结果证明。 + {heroDescriptionLines.map((line, i) => ( + + {i > 0 &&
} + {line} +
+ ))}

- 全部案例 + {filterTitle}

- 按行业筛选,找到与您业务最相关的成功故事 + {filterDescription}

diff --git a/src/app/(marketing)/cases/client.tsx b/src/app/(marketing)/cases/client.tsx index 267b6a2..eac62dc 100644 --- a/src/app/(marketing)/cases/client.tsx +++ b/src/app/(marketing)/cases/client.tsx @@ -6,8 +6,9 @@ import CasesContentV3 from './cases-content-v3'; interface CasesClientProps { cases?: CaseStudy[]; industries?: { id: string; label: string }[]; + pageCopy?: Record | null; } -export function CasesClient({ cases, industries }: CasesClientProps) { - return ; +export function CasesClient({ cases, industries, pageCopy }: CasesClientProps) { + return ; } \ No newline at end of file diff --git a/src/app/(marketing)/cases/page.test.tsx b/src/app/(marketing)/cases/page.test.tsx index b23ac29..2cb6f3f 100644 --- a/src/app/(marketing)/cases/page.test.tsx +++ b/src/app/(marketing)/cases/page.test.tsx @@ -4,6 +4,7 @@ import CasesPage from './page'; jest.mock('@/lib/cms/data-server', () => ({ getPublishedItems: jest.fn(), + getPageCopy: jest.fn().mockResolvedValue(null), })); jest.mock('./cases-content-v3', () => ({ diff --git a/src/app/(marketing)/cases/page.tsx b/src/app/(marketing)/cases/page.tsx index 491a118..33d53a3 100644 --- a/src/app/(marketing)/cases/page.tsx +++ b/src/app/(marketing)/cases/page.tsx @@ -1,7 +1,7 @@ import { Metadata } from 'next'; import { COMPANY_INFO } from '@/lib/constants'; import { CASE_INDUSTRIES } from '@/lib/constants/cases'; -import { getPublishedItems } from '@/lib/cms/data-server'; +import { getPublishedItems, getPageCopy } from '@/lib/cms/data-server'; import type { CaseStudy } from '@/lib/constants/cases'; import { CasesClient } from './client'; @@ -13,7 +13,10 @@ export const metadata: Metadata = { export const revalidate = 3600; export default async function CasesPage() { - const items = await getPublishedItems('case-study'); + const [items, pageCopy] = await Promise.all([ + getPublishedItems('case-study'), + getPageCopy('cases').catch(() => null), + ]); const cases = items.map((item) => ({ ...item.data, id: item.data.id || item.id, slug: item.data.slug || item.slug, title: item.data.title || item.title } as unknown as CaseStudy)); - return ; + return ; } \ No newline at end of file diff --git a/src/app/(marketing)/home-content-v14.tsx b/src/app/(marketing)/home-content-v14.tsx index 2a21ed3..38f2471 100644 --- a/src/app/(marketing)/home-content-v14.tsx +++ b/src/app/(marketing)/home-content-v14.tsx @@ -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 = { @@ -71,6 +72,23 @@ const FALLBACK_SERVICES = [ }, ]; +// ===== 首页结构性文案类型(page-copy CMS 覆盖,未配置时走硬编码兜底)===== +type HomePageCopy = Record; + +// 多行标题渲染:按 \n 分段,输出
换行 +function StyledTitle({ lines }: { lines: (string | undefined)[] }) { + return ( + <> + {lines.map((line, i) => ( + + {i > 0 &&
} + {line} +
+ ))} + + ); +} + // ===== 品牌 CTA 按钮 ===== function CTAButton({ children, href, variant = 'primary', className, dataTestId }: { children: React.ReactNode; @@ -103,6 +121,7 @@ function HeroSection({ heroData, stats }: { stats?: Record[]; }) { const containerRef = useRef(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 }: { /> + {/* 品牌口号 Badge:消费站点配置 slogan,后台可改 */} + {slogan && ( + + + {slogan} + + + )} + {/* 主标题:大胆、直接 */}
@@ -236,18 +286,16 @@ function TrustSection() {
- 值得信赖 + {eyebrow}

- 以可衡量的成果 -
- 赢得长期信任 +

- {TRUST_SIGNALS.map((item, i) => ( + {signals.map((item, i) => (
@@ -265,24 +313,33 @@ function TrustSection() { ))}
- {/* 首批客户共创标签:无真实案例时如实标注,不虚构客户 */} + {/* 首批客户共创计划:如实呈现共创/内测/授权公开状态,不虚构客户 */} -
-
-
- - {EARLY_ACCESS.label} - -
-

- {EARLY_ACCESS.desc} -

+
+
+ + {earlyLabel} + +
+

+ {earlyTitle} +

+

+ {earlyDesc} +

+
+ {earlyStatus.map((s) => ( +
+
{s.label}
+
{s.desc}
+
+ ))}
- 成为首批共创客户 + {earlyCtaLabel}
@@ -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 (

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

- {NARRATIVE_ACTS.map((act) => ( + {NARRATIVE_ACTS_CMS.map((act) => (
@@ -363,7 +427,7 @@ function NarrativeSection() { } // ===== 服务区:等宽三列卡片 + 兜底数据 ===== -function ServicesSection({ services }: { services?: Record[] }) { +function ServicesSection({ services, pageCopy }: { services?: Record[]; pageCopy?: HomePageCopy | null }) { const hasValidData = services?.length && (services![0] as any)?.subtitle; const SERVICE_ITEMS = hasValidData ? services!.map((s: Record) => ({ @@ -371,6 +435,7 @@ function ServicesSection({ services }: { services?: Record[] }) { icon: SERVICE_ICON_MAP[s.icon as string] || , })) as Record[] : FALLBACK_SERVICES; + const servicesTitle = (pageCopy?.servicesTitle || '从战略到落地,\n可量化的一站式服务').split('\n'); return (
@@ -378,9 +443,7 @@ function ServicesSection({ services }: { services?: Record[] }) {

- 从战略到落地, -
- 可量化的一站式服务 +

@@ -434,9 +497,14 @@ function ServicesSection({ services }: { services?: Record[] }) { } // ===== 案例区:轻量快照卡片 ===== -function CasesSection({ cases }: { cases?: Record[] }) { +function CasesSection({ cases, pageCopy }: { cases?: Record[]; 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[] }) {
- 共创进行时 + {casesEyebrow}

- 每一个项目, -
- 都以业务改善为衡量标准 +

- 我们正与首批客户共同打磨产品与方案,以真实运营数据验证业务改善。 -
- 公开案例将在验证后发布——合作成果,经得起衡量。 + {casesDesc}

[] }) {
- {EARLY_ACCESS.label} + {earlyLabel}
-

{EARLY_ACCESS.desc}

+

{earlyDesc}

@@ -488,9 +552,7 @@ function CasesSection({ cases }: { cases?: Record[] }) {

- 每一个项目, -
- 都以业务改善为衡量标准 +

@@ -553,10 +615,10 @@ function CasesSection({ cases }: { cases?: Record[] }) { } // ===== CTA 区:克制强调,品牌红仅用于按钮 ===== -function CTASection({ heroData }: { heroData?: Record | null }) { - const ctaTitleLine1 = heroData?.ctaTitleLine1 || '准备好开始了吗?'; - const ctaTitleLine2 = heroData?.ctaTitleLine2 || '让数据真正转化为业务增长'; - const ctaLabel = heroData?.ctaLabel || '预约咨询'; +function CTASection({ heroData, pageCopy }: { heroData?: Record | 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 | null }) { } // ===== 主页面组件 ===== -export default function HomeContentV14({ services, cases, stats, heroData }: { +export default function HomeContentV14({ services, cases, stats, heroData, pageCopy }: { services?: Record[]; cases?: Record[]; stats?: Record[]; heroData?: Record | null; + pageCopy?: HomePageCopy | null; }) { return (
@@ -588,19 +651,19 @@ export default function HomeContentV14({ services, cases, stats, heroData }: { {/* 2. 信任区:量化数据成果展示 */} - + {/* 3. 章节式叙事:问题 → 方法 → 结果 */} - + {/* 4. 服务区:等宽卡片 + 兜底数据 */} - + {/* 5. 案例区:轻量快照卡片 */} - + {/* 6. CTA:克制强调,品牌红仅用于按钮 */} - +
); } \ No newline at end of file diff --git a/src/app/(marketing)/news/news-content-v3.tsx b/src/app/(marketing)/news/news-content-v3.tsx index 5ceca6d..9c389c3 100644 --- a/src/app/(marketing)/news/news-content-v3.tsx +++ b/src/app/(marketing)/news/news-content-v3.tsx @@ -74,12 +74,18 @@ function NewsCard({ newsItem, index }: { newsItem: NewsItem; index: number }) { ); } -export default function NewsContentV3({ news }: { news?: NewsItem[] }) { +export default function NewsContentV3({ news, pageCopy }: { news?: NewsItem[]; pageCopy?: Record | null }) { const NEWS = news ?? []; const [selectedCategory, setSelectedCategory] = useState('全部'); const [searchQuery, setSearchQuery] = useState(''); const [currentPage, setCurrentPage] = useState(1); + const heroEyebrow = (pageCopy?.newsPageHeroEyebrow as string) || 'News & Insights'; + const heroTitleLines = ((pageCopy?.newsPageHeroTitle as string) || '新闻与\n洞察').split('\n'); + const heroDescription = (pageCopy?.newsPageHeroDescription as string) || `${COMPANY_INFO.displayName}最新动态、产品更新与行业洞察。了解数字化趋势,把握技术脉搏。`; + const emptyTitle = (pageCopy?.newsPageEmptyTitle as string) || '没有找到相关新闻'; + const emptyDescription = (pageCopy?.newsPageEmptyDescription as string) || '试试其他关键词或分类'; + const filteredNews = useMemo(() => { return NEWS.filter((newsItem) => { const matchesCategory = selectedCategory === '全部' || newsItem.category === selectedCategory; @@ -118,19 +124,22 @@ export default function NewsContentV3({ news }: { news?: NewsItem[] }) {
- News & Insights + {heroEyebrow}

- 新闻与 - 洞察 + {heroTitleLines.map((line, i) => ( + + {line} + + ))}

- {COMPANY_INFO.displayName}最新动态、产品更新与行业洞察。了解数字化趋势,把握技术脉搏。 + {heroDescription}

@@ -173,8 +182,8 @@ export default function NewsContentV3({ news }: { news?: NewsItem[] }) {
-

没有找到相关新闻

-

试试其他关键词或分类

+

{emptyTitle}

+

{emptyDescription}

) : ( diff --git a/src/app/(marketing)/news/page.test.tsx b/src/app/(marketing)/news/page.test.tsx index f1c88a7..7f8fe23 100644 --- a/src/app/(marketing)/news/page.test.tsx +++ b/src/app/(marketing)/news/page.test.tsx @@ -4,6 +4,7 @@ import NewsListPage from './page'; jest.mock('@/lib/cms/data-server', () => ({ getPublishedItems: jest.fn(), + getPageCopy: jest.fn().mockResolvedValue(null), })); jest.mock('./news-content-v3', () => ({ diff --git a/src/app/(marketing)/news/page.tsx b/src/app/(marketing)/news/page.tsx index cd21177..3d96897 100644 --- a/src/app/(marketing)/news/page.tsx +++ b/src/app/(marketing)/news/page.tsx @@ -1,6 +1,6 @@ import { Metadata } from 'next'; import { COMPANY_INFO } from '@/lib/constants'; -import { getPublishedItems } from '@/lib/cms/data-server'; +import { getPublishedItems, getPageCopy } from '@/lib/cms/data-server'; import type { NewsItem } from '@/lib/constants/news'; import NewsContentV3 from './news-content-v3'; @@ -13,7 +13,10 @@ export const metadata: Metadata = { export const revalidate = 3600; export default async function NewsListPage() { - const items = await getPublishedItems('news'); + const [items, pageCopy] = await Promise.all([ + getPublishedItems('news'), + getPageCopy('news').catch(() => null), + ]); const news = items.map((item) => ({ ...item.data, title: item.data.title || item.title } as unknown as NewsItem)); - return ; + return ; } \ No newline at end of file diff --git a/src/app/(marketing)/page.test.tsx b/src/app/(marketing)/page.test.tsx index a576d80..39b72f9 100644 --- a/src/app/(marketing)/page.test.tsx +++ b/src/app/(marketing)/page.test.tsx @@ -5,6 +5,7 @@ import HomePage from './page'; jest.mock('@/lib/cms/data-server', () => ({ getPublishedItems: jest.fn(), getResolvedHomeZones: jest.fn(), + getHomePageCopy: jest.fn(), })); jest.mock('./home-content-v14', () => ({ @@ -16,15 +17,17 @@ jest.mock('./home-content-v14', () => ({
{(props.cases as unknown[])?.length ?? 0}
{(props.stats as unknown[])?.length ?? 0}
{props.heroData ? JSON.stringify(props.heroData) : 'null'}
+
{props.pageCopy ? JSON.stringify(props.pageCopy) : 'null'}
); }, })); -import { getPublishedItems, getResolvedHomeZones } from '@/lib/cms/data-server'; +import { getPublishedItems, getResolvedHomeZones, getHomePageCopy } from '@/lib/cms/data-server'; const mockedGetResolvedHomeZones = getResolvedHomeZones as jest.MockedFunction; const mockedGetPublishedItems = getPublishedItems as jest.MockedFunction; +const mockedGetHomePageCopy = getHomePageCopy as jest.MockedFunction; const createMockItem = (modelCode: string, data: Record) => ({ id: `${modelCode}-id`, @@ -48,6 +51,7 @@ describe('HomePage', () => { jest.clearAllMocks(); mockedGetResolvedHomeZones.mockResolvedValue({}); mockedGetPublishedItems.mockResolvedValue([]); + mockedGetHomePageCopy.mockResolvedValue(null); }); it('renders home content from resolved zones', async () => { diff --git a/src/app/(marketing)/page.tsx b/src/app/(marketing)/page.tsx index 5579c6e..67bfb47 100644 --- a/src/app/(marketing)/page.tsx +++ b/src/app/(marketing)/page.tsx @@ -1,4 +1,4 @@ -import { getPublishedItems, getResolvedHomeZones } from '@/lib/cms/data-server'; +import { getPublishedItems, getResolvedHomeZones, getHomePageCopy } from '@/lib/cms/data-server'; import HomeContentV14 from './home-content-v14'; export const revalidate = 3600; @@ -31,5 +31,14 @@ export default async function HomePage() { heroData = heroItems[0]?.data ?? null; } - return ; + // 首页各章节结构性文案(信任信号/叙事情节/标题/CTA),CMS 未配置时为 null(组件内部兜底) + const homePageCopy = await getHomePageCopy().catch(() => null); + + return ; } diff --git a/src/app/(marketing)/products/page.test.tsx b/src/app/(marketing)/products/page.test.tsx index d300d68..335159a 100644 --- a/src/app/(marketing)/products/page.test.tsx +++ b/src/app/(marketing)/products/page.test.tsx @@ -4,6 +4,7 @@ import ProductsPage from './page'; jest.mock('@/lib/cms/data-server', () => ({ getPublishedItems: jest.fn(), + getPageCopy: jest.fn().mockResolvedValue(null), })); jest.mock('./products-content-v3', () => ({ diff --git a/src/app/(marketing)/products/page.tsx b/src/app/(marketing)/products/page.tsx index 4a8243d..4ce6d38 100644 --- a/src/app/(marketing)/products/page.tsx +++ b/src/app/(marketing)/products/page.tsx @@ -1,6 +1,6 @@ import { Metadata } from 'next'; import { COMPANY_INFO } from '@/lib/constants'; -import { getPublishedItems } from '@/lib/cms/data-server'; +import { getPublishedItems, getPageCopy } from '@/lib/cms/data-server'; import type { Product } from '@/lib/constants/products'; import ProductsContentV3 from './products-content-v3'; @@ -12,9 +12,10 @@ export const metadata: Metadata = { export const revalidate = 3600; export default async function ProductsPage() { - const [items, standaloneItems] = await Promise.all([ + const [items, standaloneItems, pageCopy] = await Promise.all([ getPublishedItems('product'), getPublishedItems('standalone-product'), + getPageCopy('products').catch(() => null), ]); const products = items.map((item) => ({ ...item.data, id: item.data.id || item.id, slug: item.data.slug || item.slug, title: item.data.title || item.title } as unknown as Product)); const standaloneProducts = standaloneItems.map((item) => ({ @@ -25,5 +26,5 @@ export default async function ProductsPage() { categoryId: 'specialized', bundle: 'standalone', } as unknown as Product)); - return ; + return ; } \ No newline at end of file diff --git a/src/app/(marketing)/products/products-content-v3.tsx b/src/app/(marketing)/products/products-content-v3.tsx index 1f51403..af8b903 100644 --- a/src/app/(marketing)/products/products-content-v3.tsx +++ b/src/app/(marketing)/products/products-content-v3.tsx @@ -14,6 +14,8 @@ import { cn } from '@/lib/utils'; const EASE_SPRING = { type: 'spring', stiffness: 300, damping: 30 } as const; +type PageCopy = Record; + const PRODUCT_ICONS: Record = { '睿新ERP管理系统': , '睿新商业智能分析平台': , @@ -75,10 +77,18 @@ function StatItem({ value, suffix, label, start, delay }: { value: number; suffi ); } -function HeroSection() { +function HeroSection({ pageCopy }: { pageCopy?: PageCopy | null }) { const statsRef = useRef(null); const isInView = useInView(statsRef, { once: true, margin: '-100px' }); + const heroTitle1 = (pageCopy?.productsHeroTitle1 as string) || '自研产品矩阵'; + const heroTitle2 = (pageCopy?.productsHeroTitle2 as string) || '支撑企业'; + const heroTitle3 = (pageCopy?.productsHeroTitle3 as string) || '数字化核心系统'; + const heroDescriptionLines = ((pageCopy?.productsHeroDescription as string) || + '从业务系统到数据智能,深度自研、互为补充。\n6 大企业套装与专业产品线,覆盖企业数字化核心场景。').split('\n'); + const ctaPrimary = (pageCopy?.productsCtaPrimary as string) || '了解我们的服务'; + const ctaSecondary = (pageCopy?.productsCtaSecondary as string) || '查看行业方案'; + return (
@@ -89,10 +99,10 @@ function HeroSection() { transition={{ duration: 1.2, delay: 0.15, ease: EASE_OUT }} className="text-4xl sm:text-5xl md:text-6xl lg:text-7xl font-bold text-ink leading-[0.92] tracking-tighter mb-8 sm:mb-10 md:mb-12" > -
自研产品矩阵
+
{heroTitle1}
- 支撑企业 + {heroTitle2}
-
数字化核心系统
+
{heroTitle3}
- 从业务系统到数据智能,深度自研、互为补充。 - 6 大企业套装与专业产品线,覆盖企业数字化核心场景。 + {heroDescriptionLines.map((line, i) => ( + + {i > 0 &&
} + {line} +
+ ))}
@@ -214,9 +228,12 @@ function ProductCard({ product, size = 'small' }: { product: Product; size?: 'la ); } -function BundleOverview({ products }: { products: Product[] }) { +function BundleOverview({ products, pageCopy }: { products: Product[]; pageCopy?: PageCopy | null }) { const enterpriseProducts = products.filter((p) => p.bundle === 'enterprise'); const standaloneProducts = products.filter((p) => p.bundle === 'standalone'); + const bundleTitle = (pageCopy?.productsBundleTitle as string) || '两套产品体系\n覆盖不同场景'; + const bundleTitleLines = bundleTitle.split('\n'); + const bundleDescription = (pageCopy?.productsBundleDescription as string) || '企业套装以组合形式解决核心管理问题,专业产品聚焦独立场景深度赋能。'; return (
@@ -225,10 +242,14 @@ function BundleOverview({ products }: { products: Product[] }) {

- 两套产品体系,覆盖不同场景 + {bundleTitleLines.map((line, i) => ( + + {line} + + ))}

- 企业套装以组合形式解决核心管理问题,专业产品聚焦独立场景深度赋能。 + {bundleDescription}

@@ -453,7 +474,9 @@ function SuiteCombosSection({ products }: { products: Product[] }) { ); } -function CTASection() { +function CTASection({ pageCopy }: { pageCopy?: PageCopy | null }) { + const ctaTitleLines = ((pageCopy?.productsCtaTitle as string) || '以自研产品\n支撑数字化落地').split('\n'); + const ctaDescription = (pageCopy?.productsCtaDescription as string) || '从企业套装到专业产品,以自研能力与专业服务,陪伴企业完成数字化升级。'; return (
@@ -462,13 +485,15 @@ function CTASection() {

- 以自研产品
- 支撑数字化落地 + {ctaTitleLines.map((line, i) => ( + + {line} + + ))}

- 从企业套装到专业产品,以自研能力与专业服务, - 陪伴企业完成数字化升级。 + {ctaDescription}

@@ -488,17 +513,17 @@ function CTASection() { ); } -export default function ProductsContentV3({ products: productsProp }: { products?: Product[] }) { +export default function ProductsContentV3({ products: productsProp, pageCopy }: { products?: Product[]; pageCopy?: PageCopy | null }) { const products = productsProp ?? []; return (
- - + + - +
); } diff --git a/src/app/(marketing)/services/page.test.tsx b/src/app/(marketing)/services/page.test.tsx index bedc1d1..31227e7 100644 --- a/src/app/(marketing)/services/page.test.tsx +++ b/src/app/(marketing)/services/page.test.tsx @@ -4,6 +4,7 @@ import ServicesPage from './page'; jest.mock('@/lib/cms/data-server', () => ({ getPublishedItems: jest.fn(), + getPageCopy: jest.fn().mockResolvedValue(null), })); jest.mock('./services-content-v3', () => ({ diff --git a/src/app/(marketing)/services/page.tsx b/src/app/(marketing)/services/page.tsx index 5019afd..247b498 100644 --- a/src/app/(marketing)/services/page.tsx +++ b/src/app/(marketing)/services/page.tsx @@ -1,6 +1,6 @@ import { Metadata } from 'next'; import { COMPANY_INFO } from '@/lib/constants'; -import { getPublishedItems } from '@/lib/cms/data-server'; +import { getPublishedItems, getPageCopy } from '@/lib/cms/data-server'; import type { Service } from '@/lib/constants/services'; import ServicesContentV3 from './services-content-v3'; @@ -15,5 +15,7 @@ export const revalidate = 3600; export default async function ServicesPage() { const items = await getPublishedItems('service'); const services = items.map((item) => ({ ...item.data, id: item.data.id || item.id, slug: item.data.slug || item.slug, title: item.data.title || item.title } as unknown as Service)); - return ; + // 服务页结构性文案(Hero/区块标题/CTA/空状态),CMS 未配置时组件内部兜底 + const pageCopy = await getPageCopy('services').catch(() => null); + return ; } \ No newline at end of file diff --git a/src/app/(marketing)/services/services-content-v3.tsx b/src/app/(marketing)/services/services-content-v3.tsx index eb230ff..0f4e83c 100644 --- a/src/app/(marketing)/services/services-content-v3.tsx +++ b/src/app/(marketing)/services/services-content-v3.tsx @@ -69,7 +69,16 @@ const SERVICE_MODES = [ }, ]; -function HeroSection() { +type PageCopy = Record; + +function HeroSection({ pageCopy }: { pageCopy?: PageCopy | null }) { + const heroTitle1 = (pageCopy?.servicesHeroTitle1 as string) || '从规划到运维'; + const heroTitle2 = (pageCopy?.servicesHeroTitle2 as string) || '全程陪伴'; + const heroDescriptionLines = ((pageCopy?.servicesHeroDescription as string) || + '不只是技术供应商,更是您数字化转型的同行者。\n从第一次沟通到系统稳定运行,我们始终在您身边。').split('\n'); + const ctaPrimary = (pageCopy?.servicesCtaPrimary as string) || '聊聊您的需求'; + const ctaSecondary = (pageCopy?.servicesCtaSecondary as string) || '查看产品矩阵'; + return (
@@ -80,11 +89,11 @@ function HeroSection() { transition={{ duration: 1, delay: 0.2, ease: EASE_OUT }} className="text-4xl sm:text-5xl md:text-6xl lg:text-7xl xl:text-8xl font-black text-ink leading-[0.92] tracking-tightest mb-10 sm:mb-12 md:mb-16" > -
从规划到运维
+
{heroTitle1}
- 全程陪伴 + {heroTitle2} - 不只是技术供应商,更是您数字化转型的同行者。 -
- 从第一次沟通到系统稳定运行,我们始终在您身边。 + {heroDescriptionLines.map((line, i) => ( + + {i > 0 &&
} + {line} +
+ ))} - 聊聊您的需求 + {ctaPrimary} - 查看产品矩阵 + {ctaSecondary} @@ -160,15 +172,17 @@ function HeroSection() { ); } -function ServicesGridSection({ services }: { services?: Service[] }) { +function ServicesGridSection({ services, pageCopy }: { services?: Service[]; pageCopy?: PageCopy | null }) { const displayServices = services?.length ? mapServicesToCards(services) : []; + const gridTitle = (pageCopy?.servicesGridTitle as string) || '四大核心服务,覆盖数字化全链路'; + const emptyState = (pageCopy?.servicesEmptyState as string) || '暂无服务数据'; if (displayServices.length === 0) { return (
-

暂无服务数据

+

{emptyState}

); @@ -187,7 +201,7 @@ function ServicesGridSection({ services }: { services?: Service[] }) { className="max-w-3xl mb-16 sm:mb-20 md:mb-24" >

- 四大核心服务,
覆盖数字化全链路 + {gridTitle}

@@ -321,7 +335,11 @@ function ProcessModesSection() { ); } -function CTASection() { +function CTASection({ pageCopy }: { pageCopy?: PageCopy | null }) { + const ctaTitle = (pageCopy?.servicesCtaTitle as string) || '准备好开始了吗?'; + const ctaDescriptionLines = ((pageCopy?.servicesCtaDescription as string) || + '无论是一个明确的项目,还是一个模糊的想法,我们都愿意坐下来和您一起理清楚。\n首次咨询免费,没有任何销售压力。').split('\n'); + return (
@@ -335,12 +353,15 @@ function CTASection() { className="max-w-3xl" >

- 准备好开始了吗? + {ctaTitle}

- 无论是一个明确的项目,还是一个模糊的想法,我们都愿意坐下来和您一起理清楚。 -
- 首次咨询免费,没有任何销售压力。 + {ctaDescriptionLines.map((line, i) => ( + + {i > 0 &&
} + {line} +
+ ))}

- - + + - + ); } diff --git a/src/app/(marketing)/solutions/page.test.tsx b/src/app/(marketing)/solutions/page.test.tsx index 4024ad3..91b161f 100644 --- a/src/app/(marketing)/solutions/page.test.tsx +++ b/src/app/(marketing)/solutions/page.test.tsx @@ -4,6 +4,7 @@ import SolutionsPage from './page'; jest.mock('@/lib/cms/data-server', () => ({ getPublishedItems: jest.fn(), + getPageCopy: jest.fn().mockResolvedValue(null), })); jest.mock('./solutions-content-v3', () => ({ diff --git a/src/app/(marketing)/solutions/page.tsx b/src/app/(marketing)/solutions/page.tsx index 2afc83c..1807045 100644 --- a/src/app/(marketing)/solutions/page.tsx +++ b/src/app/(marketing)/solutions/page.tsx @@ -1,6 +1,6 @@ import { Metadata } from 'next'; import { COMPANY_INFO } from '@/lib/constants'; -import { getPublishedItems } from '@/lib/cms/data-server'; +import { getPublishedItems, getPageCopy } from '@/lib/cms/data-server'; import type { Solution } from '@/lib/constants/solutions'; import type { Product } from '@/lib/constants/products'; import SolutionsContentV3 from './solutions-content-v3'; @@ -13,11 +13,12 @@ export const metadata: Metadata = { export const revalidate = 3600; export default async function SolutionsPage() { - const [solutionItems, productItems] = await Promise.all([ + const [solutionItems, productItems, pageCopy] = await Promise.all([ getPublishedItems('solution'), getPublishedItems('product'), + getPageCopy('solutions').catch(() => null), ]); const solutions = solutionItems.map((item) => ({ ...item.data, id: item.data.id || item.id, slug: item.data.slug || item.slug, title: item.data.title || item.title } as unknown as Solution)); const products = productItems.map((item) => ({ ...item.data, id: item.data.id || item.id, slug: item.data.slug || item.slug, title: item.data.title || item.title } as unknown as Product)); - return ; + return ; } \ No newline at end of file diff --git a/src/app/(marketing)/solutions/solutions-content-v3.tsx b/src/app/(marketing)/solutions/solutions-content-v3.tsx index 35e8704..f9e5320 100644 --- a/src/app/(marketing)/solutions/solutions-content-v3.tsx +++ b/src/app/(marketing)/solutions/solutions-content-v3.tsx @@ -8,6 +8,8 @@ import type { Product } from '@/lib/constants/products'; const EASE_OUT = [0.22, 1, 0.36, 1] as const; +type PageCopy = Record; + const INDUSTRY_ICONS: Record = { 制造业: Factory, 贸易零售: ShoppingCart, @@ -17,7 +19,14 @@ const INDUSTRY_ICONS: Record = { 物流运输: Truck, }; -function HeroSection() { +function HeroSection({ pageCopy }: { pageCopy?: PageCopy | null }) { + const heroTitle1 = (pageCopy?.solutionsHeroTitle1 as string) || '专注行业场景的'; + const heroTitle2 = (pageCopy?.solutionsHeroTitle2 as string) || '解决方案'; + const heroDescriptionLines = ((pageCopy?.solutionsHeroDescription as string) || + '端到端交付可量化的业务成果。\n基于自研产品矩阵,为制造、零售、教育、医疗、金融、物流六大行业沉淀最佳实践。').split('\n'); + const ctaPrimary = (pageCopy?.solutionsCtaPrimary as string) || '立即咨询'; + const ctaSecondary = (pageCopy?.solutionsCtaSecondary as string) || '浏览产品'; + return (
@@ -28,11 +37,11 @@ function HeroSection() { transition={{ duration: 1, delay: 0.2, ease: EASE_OUT }} className="text-4xl sm:text-5xl md:text-6xl lg:text-7xl xl:text-8xl font-black text-ink leading-[0.92] tracking-tightest mb-10 sm:mb-12 md:mb-16" > -
专注行业场景的
+
{heroTitle1}
- 解决方案 + {heroTitle2} - 端到端交付可量化的业务成果。 -
- 基于自研产品矩阵,为制造、零售、教育、医疗、金融、物流六大行业沉淀最佳实践。 + {heroDescriptionLines.map((line, i) => ( + + {i > 0 &&
} + {line} +
+ ))} - 立即咨询 + {ctaPrimary}
- 浏览产品 + {ctaSecondary} @@ -174,7 +186,9 @@ function SolutionCard({ solution, index, products, featured = false }: { solutio ); } -function SolutionsGridSection({ solutions, products }: { solutions: Solution[]; products?: Product[] }) { +function SolutionsGridSection({ solutions, products, pageCopy }: { solutions: Solution[]; products?: Product[]; pageCopy?: PageCopy | null }) { + const gridTitle = (pageCopy?.solutionsGridTitle as string) || '行业解决方案'; + const gridDescription = (pageCopy?.solutionsGridDescription as string) || '每个方案都明确推荐产品组合和服务包,确保落地效果'; return (
@@ -189,10 +203,10 @@ function SolutionsGridSection({ solutions, products }: { solutions: Solution[]; className="max-w-3xl mb-16 sm:mb-20 md:mb-24" >

- 行业解决方案 + {gridTitle}

- 每个方案都明确推荐产品组合和服务包,确保落地效果 + {gridDescription}

@@ -206,7 +220,9 @@ function SolutionsGridSection({ solutions, products }: { solutions: Solution[]; ); } -function CTASection() { +function CTASection({ pageCopy }: { pageCopy?: PageCopy | null }) { + const ctaTitle = (pageCopy?.solutionsCtaTitle as string) || '告诉我们您的行业场景'; + const ctaDescription = (pageCopy?.solutionsCtaDescription as string) || '无论您处于哪个行业、哪个数字化阶段,我们都能为您推荐最合适的产品组合和服务包。'; return (
@@ -218,10 +234,10 @@ function CTASection() { className="max-w-3xl" >

- 告诉我们您的
行业场景 + {ctaTitle}

- 无论您处于哪个行业、哪个数字化阶段,我们都能为您推荐最合适的产品组合和服务包。 + {ctaDescription}

- - - + + + ); } diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 23afa7a..119fd21 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -15,7 +15,7 @@ import { BackToTop } from "@/components/ui/back-to-top"; import { ClientLayout } from "@/components/layout/client-layout"; import { getPublishedItems } from "@/lib/cms/data-server"; import { SiteConfigProvider, type SiteConfig, type NavigationItem, type MegaDropdownGroup, type MegaDropdownItem } from "@/lib/site-config"; -import { COMPANY_INFO } from "@/lib/constants/company"; +import { COMPANY_INFO, BRAND_NARRATIVE, type BrandPillar } from "@/lib/constants/company"; import { NAVIGATION_V2, MEGA_DROPDOWN_DATA } from "@/lib/constants/navigation"; export const metadata: Metadata = { @@ -127,6 +127,11 @@ export default async function RootLayout({ displayName: (configData?.displayName as string) || COMPANY_INFO.displayName, slogan: (configData?.slogan as string) || COMPANY_INFO.slogan, description: (configData?.description as string) || COMPANY_INFO.description, + valueProposition: (configData?.valueProposition as string) || BRAND_NARRATIVE.valueProposition, + positioningStatement: (configData?.positioningStatement as string) || BRAND_NARRATIVE.positioningStatement, + brandPromise: (configData?.brandPromise as string) || BRAND_NARRATIVE.brandPromise, + toneOfVoice: (configData?.toneOfVoice as string) || BRAND_NARRATIVE.toneOfVoice, + pillars: (Array.isArray(configData?.pillars) ? configData.pillars : BRAND_NARRATIVE.pillars) as BrandPillar[], founded: (configData?.founded as string) || COMPANY_INFO.founded, location: (configData?.location as string) || COMPANY_INFO.location, email: (configData?.email as string) || COMPANY_INFO.email, diff --git a/src/components/layout/footer.tsx b/src/components/layout/footer.tsx index fa4217e..b799373 100644 --- a/src/components/layout/footer.tsx +++ b/src/components/layout/footer.tsx @@ -33,6 +33,11 @@ export function Footer() {

+ {config.slogan && ( + <> + {config.slogan} + + )} {config.description}

diff --git a/src/lib/cms/content-types.ts b/src/lib/cms/content-types.ts index fb426d9..fb7d225 100644 --- a/src/lib/cms/content-types.ts +++ b/src/lib/cms/content-types.ts @@ -1578,6 +1578,122 @@ const methodologyFields: FieldDefinition[] = [ }, ]; +const pageCopyFields: FieldDefinition[] = [ + // === 首页信任区 === + { name: 'trustEyebrow', type: 'text', label: '信任区眉标' }, + { name: 'trustTitle', type: 'text', label: '信任区标题' }, + { name: 'trustDescription', type: 'textarea', label: '信任区描述' }, + { + name: 'trustSignals', + type: 'array', + label: '信任信号', + description: '可验证信任信号卡片(数值/标签/描述/来源)', + fields: [ + { name: 'value', type: 'text', label: '数值', required: true }, + { name: 'label', type: 'text', label: '标签', required: true }, + { name: 'desc', type: 'textarea', label: '描述', required: true }, + { name: 'source', type: 'text', label: '来源', required: true }, + ], + }, + { name: 'earlyAccessLabel', type: 'text', label: '共创状态标签' }, + { name: 'earlyAccessDesc', type: 'textarea', label: '共创状态说明' }, + { name: 'earlyAccessTitle', type: 'text', label: '共创计划标题' }, + { name: 'earlyAccessCtaLabel', type: 'text', label: '共创计划 CTA 文案' }, + { + name: 'earlyAccessStatus', + type: 'array', + label: '共创计划状态条目', + description: '如实呈现共创/内测/授权公开等当前状态', + fields: [ + { name: 'label', type: 'text', label: '状态名称', required: true }, + { name: 'desc', type: 'textarea', label: '状态说明', required: true }, + ], + }, + // === 首页叙事区 === + { name: 'narrativeTitle', type: 'textarea', label: '叙事区标题' }, + { + name: 'narrativeActs', + type: 'array', + label: '叙事情节(问题→方法→结果)', + fields: [ + { name: 'number', type: 'text', label: '序号', required: true }, + { name: 'title', type: 'text', label: '标题', required: true }, + { name: 'desc', type: 'textarea', label: '描述', required: true }, + { name: 'href', type: 'text', label: '链接' }, + { name: 'cta', type: 'text', label: '按钮文案' }, + ], + }, + // === 首页服务区 === + { name: 'servicesTitle', type: 'textarea', label: '服务区标题' }, + // === 首页案例区 === + { name: 'casesEyebrow', type: 'text', label: '案例区眉标' }, + { name: 'casesTitle', type: 'textarea', label: '案例区标题' }, + { name: 'casesDescription', type: 'textarea', label: '案例区空状态描述' }, + // === 首页 CTA === + { name: 'ctaTitleLine1', type: 'text', label: 'CTA 标题第一行' }, + { name: 'ctaTitleLine2', type: 'text', label: 'CTA 标题第二行' }, + { name: 'ctaLabel', type: 'text', label: 'CTA 按钮文案' }, + // === 服务列表页 === + { name: 'servicesHeroTitle1', type: 'text', label: '服务页 Hero 标题第一行' }, + { name: 'servicesHeroTitle2', type: 'text', label: '服务页 Hero 标题第二行(品牌红)' }, + { name: 'servicesHeroDescription', type: 'textarea', label: '服务页 Hero 描述' }, + { name: 'servicesCtaPrimary', type: 'text', label: '服务页主 CTA 文案' }, + { name: 'servicesCtaSecondary', type: 'text', label: '服务页次 CTA 文案' }, + { name: 'servicesGridTitle', type: 'text', label: '服务页区块标题' }, + { name: 'servicesEmptyState', type: 'text', label: '服务页空状态文案' }, + { name: 'servicesCtaTitle', type: 'text', label: '服务页 CTA 标题' }, + { name: 'servicesCtaDescription', type: 'textarea', label: '服务页 CTA 描述' }, + // === 方案列表页 === + { name: 'solutionsHeroTitle1', type: 'text', label: '方案页 Hero 标题第一行' }, + { name: 'solutionsHeroTitle2', type: 'text', label: '方案页 Hero 标题第二行(品牌红)' }, + { name: 'solutionsHeroDescription', type: 'textarea', label: '方案页 Hero 描述' }, + { name: 'solutionsCtaPrimary', type: 'text', label: '方案页主 CTA 文案' }, + { name: 'solutionsCtaSecondary', type: 'text', label: '方案页次 CTA 文案' }, + { name: 'solutionsGridTitle', type: 'text', label: '方案页区块标题' }, + { name: 'solutionsGridDescription', type: 'textarea', label: '方案页区块描述' }, + { name: 'solutionsCtaTitle', type: 'text', label: '方案页 CTA 标题' }, + { name: 'solutionsCtaDescription', type: 'textarea', label: '方案页 CTA 描述' }, + // === 产品列表页 === + { name: 'productsHeroTitle1', type: 'text', label: '产品页 Hero 标题第一行' }, + { name: 'productsHeroTitle2', type: 'text', label: '产品页 Hero 标题第二行(品牌红)' }, + { name: 'productsHeroTitle3', type: 'text', label: '产品页 Hero 标题第三行' }, + { name: 'productsHeroDescription', type: 'textarea', label: '产品页 Hero 描述' }, + { name: 'productsCtaPrimary', type: 'text', label: '产品页主 CTA 文案' }, + { name: 'productsCtaSecondary', type: 'text', label: '产品页次 CTA 文案' }, + { name: 'productsBundleTitle', type: 'text', label: '产品页双体系区块标题' }, + { name: 'productsBundleDescription', type: 'textarea', label: '产品页双体系区块描述' }, + { name: 'productsCtaTitle', type: 'text', label: '产品页 CTA 标题' }, + { name: 'productsCtaDescription', type: 'textarea', label: '产品页 CTA 描述' }, + // === 案例列表页 === + { name: 'casePageHeroTitle1', type: 'text', label: '案例页 Hero 标题第一行' }, + { name: 'casePageHeroTitle2', type: 'text', label: '案例页 Hero 标题第二行(品牌红)' }, + { name: 'casePageHeroDescription', type: 'textarea', label: '案例页 Hero 描述' }, + { name: 'casePageFilterTitle', type: 'text', label: '案例页筛选区块标题' }, + { name: 'casePageFilterDescription', type: 'textarea', label: '案例页筛选区块描述' }, + // === 新闻列表页 === + { name: 'newsPageHeroEyebrow', type: 'text', label: '新闻页 Hero 眉标' }, + { name: 'newsPageHeroTitle', type: 'textarea', label: '新闻页 Hero 标题(多行,最后一行品牌红)' }, + { name: 'newsPageHeroDescription', type: 'textarea', label: '新闻页 Hero 描述' }, + { name: 'newsPageEmptyTitle', type: 'text', label: '新闻页空状态标题' }, + { name: 'newsPageEmptyDescription', type: 'textarea', label: '新闻页空状态描述' }, +]; + +const pageCopyConfig: ContentTypeConfig = { + model: { + id: 'model-page-copy', + code: 'page-copy', + name: '页面文案', + description: '各业务页面结构性文案配置(章节标题/眉标/描述/CTA/空状态)', + fields: pageCopyFields, + isPageType: false, + hasVersions: false, + hasDraft: true, + icon: 'file-text', + createdAt: '2024-01-01T00:00:00Z', + updatedAt: '2024-01-01T00:00:00Z', + }, +}; + const newsConfig: ContentTypeConfig = { model: { id: 'model-news', @@ -1851,4 +1967,5 @@ export const CONTENT_TYPE_CONFIGS = { 'contact-page': contactPageConfig, 'legal-page': legalPageConfig, methodology: methodologyConfig, + 'page-copy': pageCopyConfig, }; diff --git a/src/lib/cms/data-server.test.ts b/src/lib/cms/data-server.test.ts index e935cfb..c2c672b 100644 --- a/src/lib/cms/data-server.test.ts +++ b/src/lib/cms/data-server.test.ts @@ -414,11 +414,27 @@ describe('data-server - CMS 数据访问层', () => { it('getSolutionBySlug should call getPublishedItemBySlug with solution', async () => { const spy = jest.spyOn(dataServer, 'getPublishedItemBySlug'); - spy.mockResolvedValue(null); + spy.mockResolvedValue(null as never); await dataServer.getSolutionBySlug('test-solution'); expect(spy).toHaveBeenCalledWith('solution', 'test-solution'); }); + + it('getPageCopy should call getPublishedItemBySlug with page-copy model', async () => { + const spy = jest.spyOn(dataServer, 'getPublishedItemBySlug'); + spy.mockResolvedValue(null as never); + + await dataServer.getPageCopy('home'); + expect(spy).toHaveBeenCalledWith('page-copy', 'home'); + }); + + it('getHomePageCopy should fetch page-copy for home page code', async () => { + const spy = jest.spyOn(dataServer, 'getPublishedItemBySlug'); + spy.mockResolvedValue(null as never); + + await dataServer.getHomePageCopy(); + expect(spy).toHaveBeenCalledWith('page-copy', 'home'); + }); }); describe('getResolvedHomeZones', () => { diff --git a/src/lib/cms/data-server.ts b/src/lib/cms/data-server.ts index 3509e16..3bf2ea3 100644 --- a/src/lib/cms/data-server.ts +++ b/src/lib/cms/data-server.ts @@ -157,6 +157,20 @@ export async function getSolutionBySlug(slug: string) { return getPublishedItemBySlug('solution', slug); } +/** + * 获取指定页面的结构性文案配置(page-copy 模型,按页面 slug) + */ +export async function getPageCopy(pageCode: string) { + return getPublishedItemBySlug('page-copy', pageCode); +} + +/** + * 获取首页结构性文案配置(章节标题/信任信号/叙事情节/CTA 的 CMS 覆盖) + */ +export async function getHomePageCopy() { + return getPageCopy('home'); +} + export async function getHomePageZones() { return getPageZones('home'); } diff --git a/src/lib/constants.test.ts b/src/lib/constants.test.ts index 67a9d1c..59fb032 100644 --- a/src/lib/constants.test.ts +++ b/src/lib/constants.test.ts @@ -19,7 +19,7 @@ describe('Constants', () => { }); it('should have slogan', () => { - expect(COMPANY_INFO.slogan).toBe('企业数字化转型的同行者'); + expect(COMPANY_INFO.slogan).toBe('智连未来 · 成长伙伴 —— 您的数字化转型同行者'); }); it('should have contact information', () => { diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 2e89d47..c46caa0 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -1,5 +1,7 @@ export { COMPANY_INFO, + BRAND_NARRATIVE, + type BrandPillar, TRUST_SIGNALS, EARLY_ACCESS, NAVIGATION, diff --git a/src/lib/constants/company.ts b/src/lib/constants/company.ts index bffdfb2..e0f744f 100644 --- a/src/lib/constants/company.ts +++ b/src/lib/constants/company.ts @@ -2,8 +2,15 @@ export const COMPANY_INFO = { name: '四川睿新致远科技有限公司', shortName: '睿新致遠', displayName: '睿新致远', - slogan: '企业数字化转型的同行者', - description: '专业核心团队,以结果导向的服务理念,做企业数字化转型的同行者与成长伙伴', + /** + * 品牌主口号(整句采用)。 + * ★ 全站唯一真源:Hero / Footer / About 等品牌触点统一引用本值, + * 并通过 CMS `site-config` 的 slogan 字段覆盖(后台可编辑)。 + */ + slogan: '智连未来 · 成长伙伴 —— 您的数字化转型同行者', + /** 公司简介:结果导向 + 同行者定位,与 slogan 语义一致。CMS site-config 可覆盖。 */ + description: + '专业核心团队,以结果导向的服务理念,做中国企业数字化转型的同行者与成长伙伴,从战略诊断到系统落地、再到持续陪跑,让每一份技术投入都转化为可衡量的成长。', founded: '2026', foundedFull: '2026 年 1 月', location: '四川省成都市', @@ -13,6 +20,59 @@ export const COMPANY_INFO = { police: '川公网安备51010602003285号', } as const; +export interface BrandPillar { + key: string; + title: string; + tagline: string; + description: string; +} + +/** + * 品牌叙事内核(Brand Narrative Core)—— 代码侧唯一真源。 + * + * 定义见 docs/brand-narrative-core.md。以下字段均通过 CMS `site-config` + * 模型在后台可编辑,代码常量作为未配置 CMS 时的兜底默认值。 + * + * 零编造原则延续:定位、承诺、三支柱均围绕「同行者 + 结果导向 + 成长伙伴」 + * 展开,不虚构资质/案例/数据。 + */ +export const BRAND_NARRATIVE = { + /** L0 一句话价值主张(elevator pitch) */ + valueProposition: '让数据连接未来,让伙伴陪您成长——我们是您数字化转型的同行者。', + /** 品牌定位声明 */ + positioningStatement: + '睿新致远是中国成长型企业的数字化转型同行者:对需要数字化升级但缺乏大型 IT 投入的成长型企业,我们以结果导向的方法论、自研可组合的产品与复合型工程师团队,提供从战略诊断到系统落地、再到持续陪跑的一站式服务,让数字化从「投入」真正变成「成长」。', + /** 品牌承诺 */ + brandPromise: + '我们承诺:每一份方案都源于对您业务的理解;每一次交付都以结果说话;每一段合作都以陪您走到成长为终点。', + /** + * L1 三支柱(品牌叙事信息层级),对应产品 / 方案 / 服务。 + * HTML 提示:CMS 中以数组形式编辑。 + */ + pillars: [ + { + key: 'strategy', + title: '战略同行', + tagline: '从诊断到蓝图,先想清楚再动手', + description: '以诊断代替推销,先理解您的业务、痛点与约束,再给出可落地的数字化转型路线图。', + }, + { + key: 'delivery', + title: '落地同行', + tagline: '让转型真正落地,让投入可衡量', + description: '用自研可组合产品与工程化交付,把蓝图变成可运行的系统,每一分投入都可验证。', + }, + { + key: 'accompany', + title: '陪跑同行', + tagline: '交付不是终点,成长才是成果', + description: '交付后仍持续陪跑,助您沉淀团队能力,让数字化持续产生业务价值。', + }, + ] satisfies BrandPillar[], + /** 语气语调(内部写作规范,供内容/文案参考,不在页面直接展示) */ + toneOfVoice: '严谨不说教、专业不冰冷、同行不越位;答案优先、数据先行;可衡量的才说,未成的如实「共创中」。', +} as const; + /** * 首页信任层可验证信任信号(L3 Trust & Authority)。 * diff --git a/src/lib/constants/index.ts b/src/lib/constants/index.ts index 4cf9999..8f144ff 100644 --- a/src/lib/constants/index.ts +++ b/src/lib/constants/index.ts @@ -1,4 +1,5 @@ -export { COMPANY_INFO, TRUST_SIGNALS, EARLY_ACCESS } from './company'; +export { COMPANY_INFO, BRAND_NARRATIVE, TRUST_SIGNALS, EARLY_ACCESS } from './company'; +export type { BrandPillar } 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'; diff --git a/src/lib/site-config.tsx b/src/lib/site-config.tsx index 8fd38be..60932db 100644 --- a/src/lib/site-config.tsx +++ b/src/lib/site-config.tsx @@ -1,6 +1,7 @@ 'use client'; import { createContext, useContext, type ReactNode } from 'react'; +import type { BrandPillar } from '@/lib/constants'; export interface NavigationItem { id: string; @@ -40,6 +41,13 @@ export interface SiteConfig { icp: string; police: string; + /** Brand narrative core (CMS editable via site-config, fallback to BRAND_NARRATIVE) */ + valueProposition: string; + positioningStatement: string; + brandPromise: string; + pillars: BrandPillar[]; + toneOfVoice: string; + /** Navigation */ mainNav: NavigationItem[]; megaDropdown: Record; @@ -57,6 +65,11 @@ const DEFAULT_CONFIG: SiteConfig = { address: '', icp: '', police: '', + valueProposition: '', + positioningStatement: '', + brandPromise: '', + pillars: [], + toneOfVoice: '', mainNav: [], megaDropdown: {}, };