feat(cms): integrate global layout with CMS and document decision
- Move site-config and navigation to CMS-backed SiteConfigProvider - Keep static constants as fallback for dev and degraded modes - Add ADR-0005 recording the global layout CMS strategy Refs ADR-0005
This commit is contained in:
@@ -0,0 +1,147 @@
|
|||||||
|
# ADR 0005: 全局布局内容 CMS 化——导航、页脚与站点配置
|
||||||
|
|
||||||
|
## 状态
|
||||||
|
|
||||||
|
已接受
|
||||||
|
|
||||||
|
## 日期
|
||||||
|
|
||||||
|
2026-07-08
|
||||||
|
|
||||||
|
## 上下文
|
||||||
|
|
||||||
|
Novalon 网站已逐步实现全站 CMS 化:产品、解决方案、服务、案例、新闻、关于我们、联系我们、法律页面等均已从 CMS 数据库读取内容。然而,全局布局(Header 导航、Footer 页脚信息、站点元信息)的数据来源边界尚未明确记录,存在以下问题:
|
||||||
|
|
||||||
|
1. 营销/运营团队是否能不经过发版就修改导航菜单、页脚联系方式或备案信息?
|
||||||
|
2. 如果 CMS 读取失败,全局布局是否应该有兜底?兜底数据的优先级是什么?
|
||||||
|
3. 导航下拉菜单中的产品/方案条目应该引用已有 CMS 内容,还是作为独立 JSON blob 维护?
|
||||||
|
|
||||||
|
需要决定全局布局中哪些内容由 CMS 管理、哪些由代码控制,以及数据模型如何设计。
|
||||||
|
|
||||||
|
## 决策
|
||||||
|
|
||||||
|
### 1. 全局内容由 CMS 管理
|
||||||
|
|
||||||
|
以下全局内容统一纳入 CMS,使用 Prisma + SQLite 中的内容模型存储:
|
||||||
|
|
||||||
|
| 内容 | CMS 模型 | 关键字段 | 用途 |
|
||||||
|
|------|---------|---------|------|
|
||||||
|
| 站点基本信息 | `site-config` | `name`, `shortName`, `displayName`, `slogan`, `description`, `founded`, `location`, `email`, `address`, `icp`, `police` | SEO、页脚公司信息、备案、联系方式 |
|
||||||
|
| 主导航与下拉菜单 | `navigation` | `mainNav`, `megaDropdown` | Header 导航、Mega Dropdown、移动端菜单 |
|
||||||
|
|
||||||
|
前端通过 `SiteConfigProvider` 在 `src/app/layout.tsx` 中从 CMS 读取并注入全局,Header、Footer、MobileMenu 均通过 `useSiteConfig()` 消费。
|
||||||
|
|
||||||
|
### 2. 内容页面继续由各自 CMS 模型驱动
|
||||||
|
|
||||||
|
产品、解决方案、服务、案例、新闻、关于我们、团队、联系我们、法律页面等继续由各自的内容模型(`product`, `solution`, `service`, `case-study`, `news`, `about-page`, `team-page`, `contact-page`, `legal-page`)驱动。导航中链接到这些页面的条目,其 `href` 必须与代码中定义的路由保持一致。
|
||||||
|
|
||||||
|
### 3. 保留代码层兜底,但 CMS 为唯一真相源
|
||||||
|
|
||||||
|
`src/app/layout.tsx` 在 CMS 读取失败或数据为空时,回退到 `src/lib/constants/company.ts` 和 `src/lib/constants/navigation.ts` 中的静态数据。该 fallback 仅用于:
|
||||||
|
|
||||||
|
- 开发环境未初始化数据库时的本地启动体验
|
||||||
|
- 极端异常(数据库不可用)下的降级展示
|
||||||
|
|
||||||
|
正常生产环境下,CMS 数据为唯一真相源;不应长期依赖 fallback 作为主要内容来源。
|
||||||
|
|
||||||
|
### 4. 路由定义保留在代码层
|
||||||
|
|
||||||
|
URL 路由模式(如 `/products/{slug}`、`/solutions/{slug}`、`/contact`)由 Next.js 的目录结构和 `generateStaticParams` 决定,不由 CMS 控制。CMS 只控制“显示哪些链接”以及“链接的文案、顺序、分组”。
|
||||||
|
|
||||||
|
### 5. 当前阶段采用 JSON Blob 模型,未来可演进为引用关系
|
||||||
|
|
||||||
|
`navigation` 模型当前将 `mainNav` 和 `megaDropdown` 存储为 JSON 数组。这是第一阶段的平衡方案,原因是:
|
||||||
|
|
||||||
|
- 实现成本低,能立即满足非技术人员的修改需求
|
||||||
|
- 与现有 `ContentItem.data` JSON 存储机制一致
|
||||||
|
- 不阻塞全站 CMS 化的整体进度
|
||||||
|
|
||||||
|
未来如果运营团队需要频繁调整导航结构,或需要下拉项自动同步产品/方案的增删,可演进为:
|
||||||
|
|
||||||
|
- `nav-item` 独立内容模型,支持排序、启用/禁用、权限
|
||||||
|
- `mega-dropdown-group` 模型,支持引用 `product`/`solution`/`service` 条目
|
||||||
|
- 在 CMS 管理后台提供可视化菜单编辑器
|
||||||
|
|
||||||
|
## 备选方案
|
||||||
|
|
||||||
|
### 方案 A:全局布局完全静态
|
||||||
|
|
||||||
|
Header、Footer、站点信息全部硬编码在 `src/lib/constants/` 中。
|
||||||
|
|
||||||
|
- **优点**:实现最简单,无 CMS 依赖,无异常风险
|
||||||
|
- **缺点**:
|
||||||
|
- 每次修改导航、联系方式、备案信息都需要发版
|
||||||
|
- 与“全站 CMS 化”目标冲突
|
||||||
|
- 营销/运营团队无法自主更新
|
||||||
|
- **否决原因**:不满足官方网站长期运营需求,也违反项目已确定的“全站 CMS 化”方向
|
||||||
|
|
||||||
|
### 方案 B:导航条目拆分为独立可引用模型
|
||||||
|
|
||||||
|
每个导航项、每个下拉分组都是独立 `ContentItem`,并通过 reference 字段关联到 `product`/`solution`/`service`。
|
||||||
|
|
||||||
|
- **优点**:
|
||||||
|
- 粒度最细,运营体验最好
|
||||||
|
- 新增产品时可在 CMS 中直接勾选加入导航
|
||||||
|
- 符合 Enterprise CMS(Contentful/Sanity/Strapi)的最佳实践
|
||||||
|
- **缺点**:
|
||||||
|
- 当前 CMS 管理后台尚未建成,reference 字段的编辑体验需要额外开发
|
||||||
|
- 实现周期更长,会阻塞当前阶段目标
|
||||||
|
- **否决原因**:当前阶段过度设计;保留为后续演进方向
|
||||||
|
|
||||||
|
### 方案 C:仅站点信息 CMS 化,导航仍静态
|
||||||
|
|
||||||
|
`site-config` 进 CMS,但 `navigation` 继续用静态常量。
|
||||||
|
|
||||||
|
- **优点**:改动小,页脚信息可随时改
|
||||||
|
- **缺点**:导航菜单仍是发版瓶颈;导航作为官网最高频变更的全局元素之一,静态化不可接受
|
||||||
|
- **否决原因**:不满足“全局布局 CMS 化”的完整性要求
|
||||||
|
|
||||||
|
## 理由
|
||||||
|
|
||||||
|
选择“JSON Blob 模型的 CMS 化”作为当前方案的核心论据:
|
||||||
|
|
||||||
|
1. **符合行业标准**:企业官网普遍使用 Headless CMS 管理 `site-config` + `navigation`(Contentful 的 Site Configuration、Sanity 的 siteConfig、Strapi 的 Global single type 等)
|
||||||
|
2. **业务匹配度高**:Novalon 作为 B2B 企业,导航菜单、联系方式、备案信息需要灵活调整,CMS 化能显著降低运营成本
|
||||||
|
3. **工程效率平衡**:复用现有 `ContentModel`/`ContentItem` 机制,无需新建数据库表或 API,一周内可落地
|
||||||
|
4. **与现有架构一致**:`site-config` 和 `navigation` 模型已存在于 `prisma/seed.ts`,只需明确其职责边界和 fallback 策略
|
||||||
|
5. **可演进**:JSON blob 方案不排斥未来拆分为更细粒度的 `nav-item` 模型,改动范围可控
|
||||||
|
|
||||||
|
## 后果
|
||||||
|
|
||||||
|
### 正面
|
||||||
|
|
||||||
|
- 运营团队可在不发版的情况下修改导航、页脚联系信息、备案号等全局内容
|
||||||
|
- 全局布局与内容页面统一使用 CMS 作为真相源,架构一致
|
||||||
|
- 新增页面时,只需在 CMS 中添加导航链接即可上线,无需改代码
|
||||||
|
- 开发环境未初始化数据库时仍可正常启动(依赖 fallback)
|
||||||
|
|
||||||
|
### 负面
|
||||||
|
|
||||||
|
- CMS 成为全局布局的强依赖:CMS 数据错误会直接影响全站所有页面
|
||||||
|
- `navigation` 以 JSON blob 存储,后台编辑体验不如可视化菜单编辑器友好
|
||||||
|
- 需要为 CMS 内容变更设计缓存失效/重新生成策略(ISR/revalidate)
|
||||||
|
|
||||||
|
### 风险
|
||||||
|
|
||||||
|
- **风险 1**:CMS 中误删 `site-config` 或 `navigation` 条目导致全站 fallback 到旧静态数据
|
||||||
|
- 缓解措施:seed 脚本保证基础数据存在;生产环境限制删除单例配置
|
||||||
|
- **风险 2**:导航链接的 `href` 与代码路由不匹配,导致 404
|
||||||
|
- 缓解措施:建立路由检查清单;E2E 测试覆盖所有导航链接可访问性
|
||||||
|
- **风险 3**:JSON blob 编辑出错导致导航解析失败
|
||||||
|
- 缓解措施:在 CMS 管理后台增加 JSON Schema 校验;关键字段必填校验
|
||||||
|
|
||||||
|
## 相关决策
|
||||||
|
|
||||||
|
- ADR-0002: HSI 信息架构与四层叙事模型——导航是 HSI 架构的关键入口
|
||||||
|
- `docs/superpowers/plans/2026-07-01-novalon-cms-system.md`: Novalon CMS 自研系统实现计划
|
||||||
|
- `docs/cms/api-contract.md`: CMS 后端 API 设计规范
|
||||||
|
- `CONTEXT.md`: 全局设计系统、品牌色与导航层级定义
|
||||||
|
|
||||||
|
## 实施状态
|
||||||
|
|
||||||
|
- [x] 创建 `site-config` 内容模型并填充种子数据
|
||||||
|
- [x] 创建 `navigation` 内容模型并填充种子数据
|
||||||
|
- [x] `src/app/layout.tsx` 从 CMS 读取并注入 `SiteConfigProvider`
|
||||||
|
- [x] `Header`、`Footer`、`MobileMenu` 通过 `useSiteConfig()` 消费
|
||||||
|
- [x] 保留静态 fallback 用于开发环境和异常降级
|
||||||
|
- [ ] 后续演进:将 `megaDropdown` JSON blob 拆分为可引用 `product`/`solution`/`service` 的独立模型(可选)
|
||||||
+79
-4
@@ -317,6 +317,24 @@ async function main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ============ 3. 导入内容数据 ============
|
// ============ 3. 导入内容数据 ============
|
||||||
|
console.log('📝 导入首页 Hero Banner...');
|
||||||
|
await seedItems('hero-banner', [
|
||||||
|
{
|
||||||
|
slug: 'home',
|
||||||
|
title: '首页 Hero Banner',
|
||||||
|
headingTop: '四川睿新致远科技有限公司',
|
||||||
|
headingBottom: '让技术成为业务增长引擎',
|
||||||
|
description: '12 年企业数字化深耕,从战略规划到系统落地,陪伴制造、零售、医疗、金融、教育、物流六大行业客户实现可衡量的业务改善。',
|
||||||
|
ctaLabel: '预约咨询',
|
||||||
|
ctaHref: '/contact',
|
||||||
|
statValue: '500+',
|
||||||
|
statSubtext: '企业数字化转型实践',
|
||||||
|
ctaTitleLine1: '每一次合作,',
|
||||||
|
ctaTitleLine2: '真正转化为业务增长',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
console.log(' ✅ 首页 Hero Banner');
|
||||||
|
|
||||||
console.log('📝 导入案例研究...');
|
console.log('📝 导入案例研究...');
|
||||||
await seedItems('case-study', CASE_STUDIES as unknown as Record<string, unknown>[]);
|
await seedItems('case-study', CASE_STUDIES as unknown as Record<string, unknown>[]);
|
||||||
console.log(` ✅ ${CASE_STUDIES.length} 个案例`);
|
console.log(` ✅ ${CASE_STUDIES.length} 个案例`);
|
||||||
@@ -326,7 +344,53 @@ async function main() {
|
|||||||
console.log(` ✅ ${NEWS.length} 条新闻`);
|
console.log(` ✅ ${NEWS.length} 条新闻`);
|
||||||
|
|
||||||
console.log('📝 导入服务...');
|
console.log('📝 导入服务...');
|
||||||
await seedItems('service', SERVICES as unknown as Record<string, unknown>[]);
|
const homeServiceFields: Record<string, { subtitle: string; desc: string; highlights: string[]; metrics: { value: string; label: string }[]; href: string }> = {
|
||||||
|
consulting: {
|
||||||
|
subtitle: '战略咨询',
|
||||||
|
desc: '数字化转型战略规划与落地路径设计,让技术投资真正驱动业务增长。从愿景制定到执行落地,陪伴企业穿越转型周期。',
|
||||||
|
highlights: ['数字化成熟度评估', '转型路线图设计', '技术选型评估', '组织变革管理'],
|
||||||
|
metrics: [
|
||||||
|
{ value: '50+', label: '咨询项目' },
|
||||||
|
{ value: '90%+', label: '方案落地率' },
|
||||||
|
],
|
||||||
|
href: '/services/consulting',
|
||||||
|
},
|
||||||
|
software: {
|
||||||
|
subtitle: '企业软件',
|
||||||
|
desc: 'ERP、CRM、BI 等核心系统的评估、实施与定制化开发,基于行业最佳实践打造适配企业实际的数字化底座。',
|
||||||
|
highlights: ['ERP 实施优化', 'CRM 客户管理', 'BI 商业智能', '系统集成'],
|
||||||
|
metrics: [
|
||||||
|
{ value: '95%+', label: '准时交付率' },
|
||||||
|
{ value: '<4h', label: '平均响应' },
|
||||||
|
],
|
||||||
|
href: '/services/software',
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
subtitle: '技术服务',
|
||||||
|
desc: '系统集成、数据中台、云原生架构设计与技术外包服务,以工程化能力保障系统的稳定性、可扩展性与持续迭代。',
|
||||||
|
highlights: ['数据中台建设', '云原生迁移', '研发效能提升', '7x24 运维'],
|
||||||
|
metrics: [
|
||||||
|
{ value: '20+', label: '数据源对接' },
|
||||||
|
{ value: '99.9%', label: '系统可用率' },
|
||||||
|
],
|
||||||
|
href: '/services/data',
|
||||||
|
},
|
||||||
|
solutions: {
|
||||||
|
subtitle: 'AI 赋能',
|
||||||
|
desc: 'AI 成熟度评估、场景落地与大模型应用开发,帮助企业找到 AI 与业务的最佳结合点,实现智能化升级。',
|
||||||
|
highlights: ['AI 战略规划', '大模型应用开发', '智能流程自动化', 'AI 模型训练'],
|
||||||
|
metrics: [
|
||||||
|
{ value: '30+', label: 'AI 场景落地' },
|
||||||
|
{ value: '85%', label: '平均效率提升' },
|
||||||
|
],
|
||||||
|
href: '/services/solutions',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const servicesForSeed = SERVICES.map((s) => ({
|
||||||
|
...s,
|
||||||
|
...homeServiceFields[s.id],
|
||||||
|
}));
|
||||||
|
await seedItems('service', servicesForSeed as unknown as Record<string, unknown>[]);
|
||||||
console.log(` ✅ ${SERVICES.length} 个服务`);
|
console.log(` ✅ ${SERVICES.length} 个服务`);
|
||||||
|
|
||||||
console.log('📝 导入产品...');
|
console.log('📝 导入产品...');
|
||||||
@@ -377,7 +441,7 @@ async function main() {
|
|||||||
{
|
{
|
||||||
slug: 'default',
|
slug: 'default',
|
||||||
title: '关于我们',
|
title: '关于我们',
|
||||||
heroSubtitle: 'About Us',
|
heroSubtitle: '关于我们',
|
||||||
heroTitle: '12 年深耕\n500+ 企业\n的转型伙伴',
|
heroTitle: '12 年深耕\n500+ 企业\n的转型伙伴',
|
||||||
heroDescription: '我们不把项目完成当作终点。您的业务增长了吗?您的团队能力提升了吗?\n这才是我们真正在意的事。',
|
heroDescription: '我们不把项目完成当作终点。您的业务增长了吗?您的团队能力提升了吗?\n这才是我们真正在意的事。',
|
||||||
heroCtaText: '预约咨询',
|
heroCtaText: '预约咨询',
|
||||||
@@ -426,7 +490,7 @@ async function main() {
|
|||||||
{
|
{
|
||||||
slug: 'default',
|
slug: 'default',
|
||||||
title: '团队介绍',
|
title: '团队介绍',
|
||||||
heroSubtitle: 'Our Team',
|
heroSubtitle: '团队介绍',
|
||||||
heroTitle: '既懂技术又懂业务的\n复合型团队',
|
heroTitle: '既懂技术又懂业务的\n复合型团队',
|
||||||
heroDescription: '我们的成员不只是写代码的工程师——我们是能理解您业务场景、能和您一起想清楚问题的合作伙伴。',
|
heroDescription: '我们的成员不只是写代码的工程师——我们是能理解您业务场景、能和您一起想清楚问题的合作伙伴。',
|
||||||
heroCtaText: '与我们交流',
|
heroCtaText: '与我们交流',
|
||||||
@@ -475,7 +539,7 @@ async function main() {
|
|||||||
{
|
{
|
||||||
slug: 'default',
|
slug: 'default',
|
||||||
title: '联系我们',
|
title: '联系我们',
|
||||||
heroSubtitle: 'Contact Us',
|
heroSubtitle: '联系我们',
|
||||||
heroTitle: '随时欢迎\n聊一聊',
|
heroTitle: '随时欢迎\n聊一聊',
|
||||||
heroDescription: '工作日 2 小时内快速响应,首次咨询免费。\n没有销售压力,只是坐下来把问题理清楚。',
|
heroDescription: '工作日 2 小时内快速响应,首次咨询免费。\n没有销售压力,只是坐下来把问题理清楚。',
|
||||||
companyName: '四川睿新致远科技有限公司',
|
companyName: '四川睿新致远科技有限公司',
|
||||||
@@ -488,7 +552,18 @@ async function main() {
|
|||||||
{ text: '提供免费的业务咨询和方案评估服务' },
|
{ text: '提供免费的业务咨询和方案评估服务' },
|
||||||
{ text: '根据您的需求量身定制最优解决方案' },
|
{ text: '根据您的需求量身定制最优解决方案' },
|
||||||
],
|
],
|
||||||
|
contactTitle: '联系方式',
|
||||||
|
emailLabel: '邮箱',
|
||||||
|
addressLabel: '地址',
|
||||||
|
workHoursTitle: '工作时间',
|
||||||
|
dayLabel: '工作日',
|
||||||
|
hours: '9:00 - 18:00',
|
||||||
|
commitmentTitle: '服务承诺',
|
||||||
formTitle: '发送消息',
|
formTitle: '发送消息',
|
||||||
|
submitLabel: '发送消息',
|
||||||
|
submittingLabel: '发送中…',
|
||||||
|
successTitle: '消息已发送',
|
||||||
|
successMessage: '感谢您的留言,我们会尽快与您联系。',
|
||||||
ctaTitle: '还是想直接聊?',
|
ctaTitle: '还是想直接聊?',
|
||||||
ctaDescription: '表单填起来太麻烦?直接发邮件到 contact@novalon.cn,我们同样会快速回复。',
|
ctaDescription: '表单填起来太麻烦?直接发邮件到 contact@novalon.cn,我们同样会快速回复。',
|
||||||
},
|
},
|
||||||
|
|||||||
+15
-13
@@ -15,6 +15,8 @@ import { BackToTop } from "@/components/ui/back-to-top";
|
|||||||
import { ClientLayout } from "@/components/layout/client-layout";
|
import { ClientLayout } from "@/components/layout/client-layout";
|
||||||
import { getPublishedItems } from "@/lib/cms/data-server";
|
import { getPublishedItems } from "@/lib/cms/data-server";
|
||||||
import { SiteConfigProvider, type SiteConfig, type NavigationItem, type MegaDropdownGroup } from "@/lib/site-config";
|
import { SiteConfigProvider, type SiteConfig, type NavigationItem, type MegaDropdownGroup } from "@/lib/site-config";
|
||||||
|
import { COMPANY_INFO } from "@/lib/constants/company";
|
||||||
|
import { NAVIGATION_V2, MEGA_DROPDOWN_DATA } from "@/lib/constants/navigation";
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
metadataBase: new URL("https://www.novalon.cn"),
|
metadataBase: new URL("https://www.novalon.cn"),
|
||||||
@@ -90,19 +92,19 @@ export default async function RootLayout({
|
|||||||
const configData = configItems[0]?.data as Record<string, unknown> | undefined;
|
const configData = configItems[0]?.data as Record<string, unknown> | undefined;
|
||||||
|
|
||||||
const siteConfig: SiteConfig = {
|
const siteConfig: SiteConfig = {
|
||||||
name: (configData?.name as string) || '',
|
name: (configData?.name as string) || COMPANY_INFO.name,
|
||||||
shortName: (configData?.shortName as string) || '',
|
shortName: (configData?.shortName as string) || COMPANY_INFO.shortName,
|
||||||
displayName: (configData?.displayName as string) || '',
|
displayName: (configData?.displayName as string) || COMPANY_INFO.displayName,
|
||||||
slogan: (configData?.slogan as string) || '',
|
slogan: (configData?.slogan as string) || COMPANY_INFO.slogan,
|
||||||
description: (configData?.description as string) || '',
|
description: (configData?.description as string) || COMPANY_INFO.description,
|
||||||
founded: (configData?.founded as string) || '',
|
founded: (configData?.founded as string) || COMPANY_INFO.founded,
|
||||||
location: (configData?.location as string) || '',
|
location: (configData?.location as string) || COMPANY_INFO.location,
|
||||||
email: (configData?.email as string) || '',
|
email: (configData?.email as string) || COMPANY_INFO.email,
|
||||||
address: (configData?.address as string) || '',
|
address: (configData?.address as string) || COMPANY_INFO.address,
|
||||||
icp: (configData?.icp as string) || '',
|
icp: (configData?.icp as string) || COMPANY_INFO.icp,
|
||||||
police: (configData?.police as string) || '',
|
police: (configData?.police as string) || COMPANY_INFO.police,
|
||||||
mainNav: (navData?.mainNav as NavigationItem[]) || [],
|
mainNav: (navData?.mainNav as NavigationItem[]) || NAVIGATION_V2,
|
||||||
megaDropdown: (navData?.megaDropdown as Record<string, MegaDropdownGroup[]>) || {},
|
megaDropdown: (navData?.megaDropdown as Record<string, MegaDropdownGroup[]>) || MEGA_DROPDOWN_DATA,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { useRef, useEffect } from 'react';
|
|||||||
import { ChevronDown, Lock, Sparkles } from 'lucide-react';
|
import { ChevronDown, Lock, Sparkles } from 'lucide-react';
|
||||||
import { StaticLink } from '@/components/ui/static-link';
|
import { StaticLink } from '@/components/ui/static-link';
|
||||||
import { AnimatePresence, motion } from 'framer-motion';
|
import { AnimatePresence, motion } from 'framer-motion';
|
||||||
import type { MegaDropdownGroup } from '@/lib/constants/navigation';
|
import type { MegaDropdownGroup } from '@/lib/site-config';
|
||||||
|
|
||||||
interface MegaDropdownProps {
|
interface MegaDropdownProps {
|
||||||
label: string;
|
label: string;
|
||||||
|
|||||||
@@ -233,17 +233,19 @@ const solutionFields: FieldDefinition[] = [
|
|||||||
|
|
||||||
const heroBannerFields: FieldDefinition[] = [
|
const heroBannerFields: FieldDefinition[] = [
|
||||||
{
|
{
|
||||||
name: 'heading',
|
name: 'headingTop',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
label: '主标题',
|
label: '顶部小标题',
|
||||||
required: true,
|
required: true,
|
||||||
|
description: '显示在主标题上方的引导语',
|
||||||
ui: { width: 'full' },
|
ui: { width: 'full' },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'subheading',
|
name: 'headingBottom',
|
||||||
type: 'textarea',
|
type: 'text',
|
||||||
label: '副标题',
|
label: '主标题',
|
||||||
required: true,
|
required: true,
|
||||||
|
description: 'Hero 区核心大标题',
|
||||||
ui: { width: 'full' },
|
ui: { width: 'full' },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -252,36 +254,48 @@ const heroBannerFields: FieldDefinition[] = [
|
|||||||
label: '描述文本',
|
label: '描述文本',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'primaryCtaText',
|
name: 'ctaLabel',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
label: '主按钮文案',
|
label: '主按钮文案',
|
||||||
defaultValue: '了解更多',
|
defaultValue: '预约咨询',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'primaryCtaLink',
|
name: 'ctaHref',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
label: '主按钮链接',
|
label: '主按钮链接',
|
||||||
defaultValue: '/solutions',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'secondaryCtaText',
|
|
||||||
type: 'text',
|
|
||||||
label: '副按钮文案',
|
|
||||||
defaultValue: '联系我们',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'secondaryCtaLink',
|
|
||||||
type: 'text',
|
|
||||||
label: '副按钮链接',
|
|
||||||
defaultValue: '/contact',
|
defaultValue: '/contact',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'statValue',
|
||||||
|
type: 'text',
|
||||||
|
label: '统计数值',
|
||||||
|
description: 'Hero 区右侧可选数据指标,如「500+」',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'statSubtext',
|
||||||
|
type: 'text',
|
||||||
|
label: '统计说明',
|
||||||
|
description: '与统计数值配套的说明文字',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'ctaTitleLine1',
|
||||||
|
type: 'text',
|
||||||
|
label: 'CTA 标题第一行',
|
||||||
|
description: '底部 CTA 区域第一行标题',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'ctaTitleLine2',
|
||||||
|
type: 'text',
|
||||||
|
label: 'CTA 标题第二行',
|
||||||
|
description: '底部 CTA 区域第二行标题(通常使用品牌红色)',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'theme',
|
name: 'theme',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
label: '主题风格',
|
label: '主题风格',
|
||||||
defaultValue: 'brand',
|
defaultValue: 'brand',
|
||||||
options: [
|
options: [
|
||||||
{ label: '品牌绿', value: 'brand' },
|
{ label: '品牌红', value: 'brand' },
|
||||||
{ label: '蓝', value: 'blue' },
|
{ label: '蓝', value: 'blue' },
|
||||||
{ label: '青', value: 'teal' },
|
{ label: '青', value: 'teal' },
|
||||||
{ label: '琥珀', value: 'amber' },
|
{ label: '琥珀', value: 'amber' },
|
||||||
|
|||||||
@@ -14,6 +14,11 @@ export interface CaseStudyData {
|
|||||||
testimonial?: CaseTestimonial;
|
testimonial?: CaseTestimonial;
|
||||||
color: 'brand' | 'blue' | 'teal' | 'amber' | 'purple';
|
color: 'brand' | 'blue' | 'teal' | 'amber' | 'purple';
|
||||||
featured?: boolean;
|
featured?: boolean;
|
||||||
|
projectDuration?: string;
|
||||||
|
departments?: string[];
|
||||||
|
dataScale?: string;
|
||||||
|
businessProblem?: string;
|
||||||
|
verified?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type FieldType =
|
export type FieldType =
|
||||||
|
|||||||
Reference in New Issue
Block a user