feat(cms): 品牌叙事与结构性文案全站 CMS 化并补齐信任证据阶段 0

- 品牌叙事内核(价值主张/定位/承诺/三支柱/语气)下沉 site-config,经 SiteConfigProvider 注入全站
- 新增 page-copy 内容模型承载结构性文案(章节标题/眉标/描述/CTA/空状态),覆盖首页+服务/方案/产品/案例/新闻列表页,「CMS 优先 + 硬编码兜底」不白屏
- 首页「首批客户共创计划」板块(earlyAccessTitle/CtaLabel/Status 驱动)替代单行标签,如实呈现共创/内测/授权公开三档状态
- 关于页资质区数据空时如实展示「建设中」空态
- 零编造:不虚构客户/数据/资质
This commit is contained in:
2026-08-20 09:25:12 +08:00
parent 56be17b0ba
commit 337e1e60c0
34 changed files with 848 additions and 152 deletions
+117
View File
@@ -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,
};
+17 -1
View File
@@ -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', () => {
+14
View File
@@ -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');
}
+1 -1
View File
@@ -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', () => {
+2
View File
@@ -1,5 +1,7 @@
export {
COMPANY_INFO,
BRAND_NARRATIVE,
type BrandPillar,
TRUST_SIGNALS,
EARLY_ACCESS,
NAVIGATION,
+62 -2
View File
@@ -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)。
*
+2 -1
View File
@@ -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';
+13
View File
@@ -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<string, MegaDropdownGroup[]>;
@@ -57,6 +65,11 @@ const DEFAULT_CONFIG: SiteConfig = {
address: '',
icp: '',
police: '',
valueProposition: '',
positioningStatement: '',
brandPromise: '',
pillars: [],
toneOfVoice: '',
mainNav: [],
megaDropdown: {},
};