feat(cms): 品牌叙事与结构性文案全站 CMS 化并补齐信任证据阶段 0
- 品牌叙事内核(价值主张/定位/承诺/三支柱/语气)下沉 site-config,经 SiteConfigProvider 注入全站 - 新增 page-copy 内容模型承载结构性文案(章节标题/眉标/描述/CTA/空状态),覆盖首页+服务/方案/产品/案例/新闻列表页,「CMS 优先 + 硬编码兜底」不白屏 - 首页「首批客户共创计划」板块(earlyAccessTitle/CtaLabel/Status 驱动)替代单行标签,如实呈现共创/内测/授权公开三档状态 - 关于页资质区数据空时如实展示「建设中」空态 - 零编造:不虚构客户/数据/资质
This commit is contained in:
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
export {
|
||||
COMPANY_INFO,
|
||||
BRAND_NARRATIVE,
|
||||
type BrandPillar,
|
||||
TRUST_SIGNALS,
|
||||
EARLY_ACCESS,
|
||||
NAVIGATION,
|
||||
|
||||
@@ -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)。
|
||||
*
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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: {},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user