fix(metrics): 数字口径 basis 结构强制,删除详情页虚构佐证
复评 P0 收尾:把「每个数字带口径」从个别页自觉升级为结构性强制。 - metrics-basis.ts:METRIC_BASES / METRIC_BASIS_NOTES + resolveMetricBasis (缺失·非法·大小写错一律回落最弱 target)+ weakestBasis(一组数字取最弱者聚合) + FORBIDDEN_PROOF_PHRASES;MetricsBasisNote 收 basis prop - 类型层 Product.metrics / DataProof / ServiceMetric / Solution.outcomes / DataMetric / about keyMetrics 全部加 basis?;9 处调用点改为 basis 驱动 - content-types.ts 新增共享 metricBasisField(select)展开进 9 处字段定义; trustSignals 有意跳过(已有自己的 source)。seed 显式声明 55 条 target - 删除字面虚构佐证:详情页「每一项指标都源自真实客户案例」、「经过实战验证」×3、 「来自真实客户的使用数据」、「经实际场景验证」;给此前完全裸奔的 3 个共享 数字渲染器(DataProofSection / detail-trust / detail-product-value)补上角注 - metrics-basis.test 3→16 例:basis 解析正控制、weakestBasis 次序、seed 全量 显式声明 + 覆盖证明(防空跑假绿)+ 禁任何条目自称 verified + 禁词全源码扫描 - 文档同源:DESIGN.md 立 The Declared-Basis Rule;PRODUCT.md 常驻禁令; CONTEXT.md 记决策并警示 admin 字段需重跑 seed 才激活 边界:CMS 字段定义存 ContentModel.fields(DB 内 JSON),basis 下拉须重跑 seed 才会出现在 admin 表单;本次 DB 与 seed 数据值零改动,渲染端对缺 basis 兜底 target。 Gates: type-check 0 · jest 1594/132 · contrast 7/7 · headings 10/10 lint 0e/128w(与 stash 基线逐行比对,零新增告警)
This commit is contained in:
@@ -167,6 +167,7 @@ Novalon 的 6 个核心产品,互为互补关系,常以组合形式出现在
|
|||||||
| 方案页删除服务方式区域 | Solutions 列表页"服务方式"区域职责越界(属 Services 层),应删除;替换为方案→服务的轻量关联推荐链接 | ✅ 2026-06-07 确认 |
|
| 方案页删除服务方式区域 | Solutions 列表页"服务方式"区域职责越界(属 Services 层),应删除;替换为方案→服务的轻量关联推荐链接 | ✅ 2026-06-07 确认 |
|
||||||
| L3 信任层内容策略 | 初创阶段无真实案例/数据/资质,L3 暂不渲染;组件结构预留,数据就绪后启用。三档策略:有案例→正常展示;有意向→"正在服务中"标签;无数据→不显示 L3。**阶段 0(2026-08-20)已落地**:信任策略从「结果证据」转向「可验证的过程+能力+治理证据」——首页「共创计划」板块(共创进行中/产品内测中/成果授权公开三档如实状态 + 招募 CTA)、关于页资质区如实「建设中」空态 | ✅ 2026-06-07 确认 / 阶段 0 完成 2026-08-20 |
|
| 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 确认 |
|
| 首页共创计划板块 | 成立 <1 年无真实案例时,以主动板块替代被动标签:`page-copy` 字段 `earlyAccessTitle`/`earlyAccessCtaLabel`/`earlyAccessStatus` 驱动首页「首批客户共创计划」板块,如实呈现共创/内测/授权公开状态并引导转化(成为共创客户 → /contact)。零编造:不虚构客户/数据/资质 | ✅ 2026-08-20 确认 |
|
||||||
|
| 数字口径 basis 结构强制 | 「每个数字带口径」从个别页自觉升级为结构约束:走共享数字样式渲染的指标(`metrics`/`outcomes`/`dataProofs`)必须声明 `basis: target\|team-history\|verified`,渲染端按 basis 自动附角注,缺失/非法保守回落到最弱的 `target`(「目标口径,非既成结果」)。CMS 侧 `basis` 为 select 字段,真实出路由业务在 admin 录入;`metrics-basis.test.ts` 机械校验 seed 全量条目显式声明、禁止条目无据自称 `verified`,并拦截「源自真实客户案例/经过实战验证」式虚构佐证文案。**注意**:字段定义改动需重跑 seed 同步 `ContentModel.fields` 后 admin 才出现该输入项;渲染端已对未同步数据兜底为 `target` | ✅ 2026-09-20 确认(critique P0 收尾) |
|
||||||
| 组件版本统一 | 独立产品迁移后删除 `detail/`(V1),将 `detail-v2/` 重命名为 `detail/`,组件文件名去掉版本后缀(如 `DetailHeroV3` → `DetailHero`) | ✅ 2026-06-07 确认(待 Phase 0 执行) |
|
| 组件版本统一 | 独立产品迁移后删除 `detail/`(V1),将 `detail-v2/` 重命名为 `detail/`,组件文件名去掉版本后缀(如 `DetailHeroV3` → `DetailHero`) | ✅ 2026-06-07 确认(待 Phase 0 执行) |
|
||||||
| 设计定位 | 纯咨询专业风(Accenture + Bain + Porsche 三位一体),水墨元素已移除 | ✅ 2026-07-10 更新(ADR-0006) |
|
| 设计定位 | 纯咨询专业风(Accenture + Bain + Porsche 三位一体),水墨元素已移除 | ✅ 2026-07-10 更新(ADR-0006) |
|
||||||
| 设计 DNA 框架 | 三维度模型:Design System + Design Style + Visual Effects,基于 Accenture+Bain+Porsche 整合 | ✅ 2026-06-28 确认 |
|
| 设计 DNA 框架 | 三维度模型:Design System + Design Style + Visual Effects,基于 Accenture+Bain+Porsche 整合 | ✅ 2026-06-28 确认 |
|
||||||
|
|||||||
@@ -174,6 +174,7 @@ Novalon 的界面是一份可以逐条审计的经营宣言。它模仿顶级咨
|
|||||||
**The Answer-First Rule.** 标题写结论断言,不写问句或口号铺垫;紧随其后的数字条是证据。
|
**The Answer-First Rule.** 标题写结论断言,不写问句或口号铺垫;紧随其后的数字条是证据。
|
||||||
**The -0.04em Floor.** 追踪最紧 -0.04em(现用最紧 -0.03em);display 不超过 6rem。
|
**The -0.04em Floor.** 追踪最紧 -0.04em(现用最紧 -0.03em);display 不超过 6rem。
|
||||||
**The 10px Floor.** 10–11px 大写 tracking 微标签(eyebrow、digest、case 分类签)是 Bain 账本风的有意下限,全站保留、不上坡;绝对像素字号不得 <10px,由 `src/lib/constants/font-floor.test.ts` 机械守卫禁止再引入(rem 因 18px 根字号歧义不纳入)。
|
**The 10px Floor.** 10–11px 大写 tracking 微标签(eyebrow、digest、case 分类签)是 Bain 账本风的有意下限,全站保留、不上坡;绝对像素字号不得 <10px,由 `src/lib/constants/font-floor.test.ts` 机械守卫禁止再引入(rem 因 18px 根字号歧义不纳入)。
|
||||||
|
**The Declared-Basis Rule.** 凡以大数字样式呈现的指标必须声明口径 `basis: target | team-history | verified`(单一真源 `src/lib/constants/metrics-basis.ts`),渲染端据此自动附角注;缺失或非法一律按最弱的 `target`(「目标口径,非既成结果」)处理,宁可多标注也不裸奔。聚合时取一组数字里最弱的那个。禁止任何「数字源自真实客户案例 / 经过实战验证」式佐证文案——本站 2026-01 成立、零公开客户案例,该措辞由 `metrics-basis.test.ts` 全源码扫描拦截。
|
||||||
|
|
||||||
## Layout
|
## Layout
|
||||||
|
|
||||||
|
|||||||
@@ -34,3 +34,4 @@
|
|||||||
- 字距下限 -0.04em,不得更紧
|
- 字距下限 -0.04em,不得更紧
|
||||||
- 暗黑模式由 `html[data-theme='dark']` 覆盖 CSS 变量驱动,组件不得写死颜色字面量
|
- 暗黑模式由 `html[data-theme='dark']` 覆盖 CSS 变量驱动,组件不得写死颜色字面量
|
||||||
- 楷体(`--font-brand`)为遗留例外,仅存量页面保留,新页面禁用
|
- 楷体(`--font-brand`)为遗留例外,仅存量页面保留,新页面禁用
|
||||||
|
- 大数字必须有口径:指标声明 `basis: target|team-history|verified`,未声明按「目标口径,非既成结果」自动标注;禁止宣称数字源自真实客户案例或实战验证(公司 2026-01 成立、零公开客户案例)
|
||||||
|
|||||||
@@ -36,7 +36,8 @@
|
|||||||
- [x] **结构性文案全站 CMS 化(2026-08-19)**:新增 `page-copy` 内容模型承载全部营销页面章节标题/眉标/描述/CTA/空状态(首页 + 服务/方案/产品/案例/新闻列表页),「CMS 优先 + 硬编码兜底」不白屏;seed 写入 6 条,本地 db:seed 已生效
|
- [x] **结构性文案全站 CMS 化(2026-08-19)**:新增 `page-copy` 内容模型承载全部营销页面章节标题/眉标/描述/CTA/空状态(首页 + 服务/方案/产品/案例/新闻列表页),「CMS 优先 + 硬编码兜底」不白屏;seed 写入 6 条,本地 db:seed 已生效
|
||||||
- [x] **信任证据补齐·阶段 0(成立 <1 年策略,2026-08-20)**:信任策略从「结果证据」转向「可验证的过程 + 能力 + 治理证据」——首页「首批客户共创计划」板块(共创进行中/产品内测中/成果授权公开三档如实状态 + 招募 CTA)+ 关于页资质「建设中」如实空态,零编造
|
- [x] **信任证据补齐·阶段 0(成立 <1 年策略,2026-08-20)**:信任策略从「结果证据」转向「可验证的过程 + 能力 + 治理证据」——首页「首批客户共创计划」板块(共创进行中/产品内测中/成果授权公开三档如实状态 + 招募 CTA)+ 关于页资质「建设中」如实空态,零编造
|
||||||
- [x] **生产部署上线(2026-08-20)**:`deploy.sh deploy`(本地 dist → 远端容器重建)+ 生产库 seed(page-copy/methodology 数据)完成;生产目录 Drizzle 残留源码归档清理;站点全路由 200、生产库 Prisma 结构
|
- [x] **生产部署上线(2026-08-20)**:`deploy.sh deploy`(本地 dist → 远端容器重建)+ 生产库 seed(page-copy/methodology 数据)完成;生产目录 Drizzle 残留源码归档清理;站点全路由 200、生产库 Prisma 结构
|
||||||
- [ ] **信任证据补齐·阶段 1/2(待内测/试点客户素材)**:如实披露「共创中/内测中/已交付」三档 + 经授权客户(脱敏);有交付成果后经授权公示可量化结果(零编造原则)
|
- [x] **数字口径结构强制(2026-09-20,critique P0 收尾)**:共享数字样式渲染的指标(`metrics`/`outcomes`/`dataProofs`)统一携带 `basis: target|team-history|verified`,渲染端自动附口径角注、未声明保守回落 `target`;CMS 新增 `basis` select 录入字段;删除产品详情页「每一项指标都源自真实客户案例」「经实际场景验证」等虚构佐证并补注角注;`metrics-basis.test.ts` 机械校验声明完整性并拦截虚构佐证禁词
|
||||||
|
- [ ] **信任证据补齐·阶段 1/2(待内测/试点客户素材)**:如实披露「共创中/内测中/已交付」三档 + 经授权客户(脱敏);有交付成果后经授权公示可量化结果(零编造原则)——口径字段与 admin 录入口已就绪,待真实数据把 `basis` 从 `target` 升级为 `verified`
|
||||||
|
|
||||||
## 技术栈
|
## 技术栈
|
||||||
|
|
||||||
|
|||||||
+36
-36
@@ -14,17 +14,17 @@ export const PRODUCTS: Product[] = [
|
|||||||
heroThemeId: 'erp',
|
heroThemeId: 'erp',
|
||||||
scenario: '面向中大型制造与零售企业的核心运营系统',
|
scenario: '面向中大型制造与零售企业的核心运营系统',
|
||||||
metrics: [
|
metrics: [
|
||||||
{ value: '10 万级', label: 'SKU 支撑' },
|
{ value: '10 万级', label: 'SKU 支撑', basis: 'target' },
|
||||||
{ value: '200+', label: '标准报表模板' },
|
{ value: '200+', label: '标准报表模板', basis: 'target' },
|
||||||
{ value: '99.5%', label: '数据准确率' },
|
{ value: '99.5%', label: '数据准确率', basis: 'target' },
|
||||||
],
|
],
|
||||||
bundle: 'enterprise',
|
bundle: 'enterprise',
|
||||||
capabilities: ['财务核算', '采购管理', '生产计划', '库存控制'],
|
capabilities: ['财务核算', '采购管理', '生产计划', '库存控制'],
|
||||||
caseStudies: [],
|
caseStudies: [],
|
||||||
dataProofs: [
|
dataProofs: [
|
||||||
{ metric: '运营效率提升', value: '40%+', description: '流程自动化减少人工操作' },
|
{ metric: '运营效率提升', value: '40%+', description: '流程自动化减少人工操作', basis: 'target' },
|
||||||
{ metric: '数据准确性', value: '99.2%', description: '实时数据同步消除信息孤岛' },
|
{ metric: '数据准确性', value: '99.2%', description: '实时数据同步消除信息孤岛', basis: 'target' },
|
||||||
{ metric: '部署周期', value: '3-6个月', description: '从需求调研到正式上线' },
|
{ metric: '部署周期', value: '3-6个月', description: '从需求调研到正式上线', basis: 'target' },
|
||||||
],
|
],
|
||||||
certifications: [],
|
certifications: [],
|
||||||
overview: '睿新ERP管理系统是面向企业打造的一体化资源管理平台,打通财务、采购、销售、库存、生产等核心模块,让各部门数据实时流转、业务流程自动协同,帮助企业告别信息孤岛。',
|
overview: '睿新ERP管理系统是面向企业打造的一体化资源管理平台,打通财务、采购、销售、库存、生产等核心模块,让各部门数据实时流转、业务流程自动协同,帮助企业告别信息孤岛。',
|
||||||
@@ -71,17 +71,17 @@ export const PRODUCTS: Product[] = [
|
|||||||
heroThemeId: 'crm',
|
heroThemeId: 'crm',
|
||||||
scenario: '面向销售驱动型企业的一体化客户运营管理平台',
|
scenario: '面向销售驱动型企业的一体化客户运营管理平台',
|
||||||
metrics: [
|
metrics: [
|
||||||
{ value: '360°', label: '客户画像' },
|
{ value: '360°', label: '客户画像', basis: 'target' },
|
||||||
{ value: '3x', label: '转化率提升' },
|
{ value: '3x', label: '转化率提升', basis: 'target' },
|
||||||
{ value: '<2h', label: '线索响应' },
|
{ value: '<2h', label: '线索响应', basis: 'target' },
|
||||||
],
|
],
|
||||||
bundle: 'enterprise',
|
bundle: 'enterprise',
|
||||||
capabilities: ['线索管理', '商机跟进', '合同审批', '售后工单'],
|
capabilities: ['线索管理', '商机跟进', '合同审批', '售后工单'],
|
||||||
caseStudies: [],
|
caseStudies: [],
|
||||||
dataProofs: [
|
dataProofs: [
|
||||||
{ metric: '销售转化率', value: '+113%', description: '从15%提升至32%' },
|
{ metric: '销售转化率', value: '+113%', description: '从15%提升至32%', basis: 'target' },
|
||||||
{ metric: '客户数据整合', value: '100%', description: '多渠道数据统一管理' },
|
{ metric: '客户数据整合', value: '100%', description: '多渠道数据统一管理', basis: 'target' },
|
||||||
{ metric: '响应速度', value: '<2小时', description: '线索到首次联系平均时间' },
|
{ metric: '响应速度', value: '<2小时', description: '线索到首次联系平均时间', basis: 'target' },
|
||||||
],
|
],
|
||||||
certifications: [],
|
certifications: [],
|
||||||
overview: '睿新客户关系管理系统是面向企业销售团队的全渠道客户管理平台,覆盖从线索到成交的全流程可视化管理,让销售团队清楚每一步该做什么、每个机会卡在哪里。系统支持多渠道客户数据整合,提供全面的客户画像和销售漏斗分析。',
|
overview: '睿新客户关系管理系统是面向企业销售团队的全渠道客户管理平台,覆盖从线索到成交的全流程可视化管理,让销售团队清楚每一步该做什么、每个机会卡在哪里。系统支持多渠道客户数据整合,提供全面的客户画像和销售漏斗分析。',
|
||||||
@@ -128,17 +128,17 @@ export const PRODUCTS: Product[] = [
|
|||||||
heroThemeId: 'cms',
|
heroThemeId: 'cms',
|
||||||
scenario: '面向多品牌、多站点企业的统一内容管理平台',
|
scenario: '面向多品牌、多站点企业的统一内容管理平台',
|
||||||
metrics: [
|
metrics: [
|
||||||
{ value: '50+', label: '站点统管' },
|
{ value: '50+', label: '站点统管', basis: 'target' },
|
||||||
{ value: '60%', label: '发布提效' },
|
{ value: '60%', label: '发布提效', basis: 'target' },
|
||||||
{ value: '100%', label: '私有化部署' },
|
{ value: '100%', label: '私有化部署', basis: 'target' },
|
||||||
],
|
],
|
||||||
bundle: 'enterprise',
|
bundle: 'enterprise',
|
||||||
capabilities: ['多站点管理', '工作流审批', '版本控制', 'SEO 优化'],
|
capabilities: ['多站点管理', '工作流审批', '版本控制', 'SEO 优化'],
|
||||||
caseStudies: [],
|
caseStudies: [],
|
||||||
dataProofs: [
|
dataProofs: [
|
||||||
{ metric: '发布效率提升', value: '60%', description: '多站点统一管理' },
|
{ metric: '发布效率提升', value: '60%', description: '多站点统一管理', basis: 'target' },
|
||||||
{ metric: '站点支持数', value: '50+', description: '单系统支持多站点' },
|
{ metric: '站点支持数', value: '50+', description: '单系统支持多站点', basis: 'target' },
|
||||||
{ metric: '部署方式', value: '私有化', description: '数据自主可控' },
|
{ metric: '部署方式', value: '私有化', description: '数据自主可控', basis: 'target' },
|
||||||
],
|
],
|
||||||
certifications: [],
|
certifications: [],
|
||||||
overview: '睿新内容管理系统是企业级内容管理平台,提供所见即所得的编辑体验和多站点统一管理能力,让内容发布像写文档一样简单。系统提供强大的内容编辑功能和灵活的权限管理机制。',
|
overview: '睿新内容管理系统是企业级内容管理平台,提供所见即所得的编辑体验和多站点统一管理能力,让内容发布像写文档一样简单。系统提供强大的内容编辑功能和灵活的权限管理机制。',
|
||||||
@@ -185,17 +185,17 @@ export const PRODUCTS: Product[] = [
|
|||||||
heroThemeId: 'bi',
|
heroThemeId: 'bi',
|
||||||
scenario: '面向业务人员的自助式数据洞察与决策支持平台',
|
scenario: '面向业务人员的自助式数据洞察与决策支持平台',
|
||||||
metrics: [
|
metrics: [
|
||||||
{ value: '70%+', label: '自助分析' },
|
{ value: '70%+', label: '自助分析', basis: 'target' },
|
||||||
{ value: '<1天', label: '决策响应' },
|
{ value: '<1天', label: '决策响应', basis: 'target' },
|
||||||
{ value: '85%', label: '报表减负' },
|
{ value: '85%', label: '报表减负', basis: 'target' },
|
||||||
],
|
],
|
||||||
bundle: 'enterprise',
|
bundle: 'enterprise',
|
||||||
capabilities: ['拖拽分析', '实时看板', '智能预警', '移动报表'],
|
capabilities: ['拖拽分析', '实时看板', '智能预警', '移动报表'],
|
||||||
caseStudies: [],
|
caseStudies: [],
|
||||||
dataProofs: [
|
dataProofs: [
|
||||||
{ metric: '数据整合效率', value: '85%', description: '减少手工报表工作量' },
|
{ metric: '数据整合效率', value: '85%', description: '减少手工报表工作量', basis: 'target' },
|
||||||
{ metric: '决策响应时间', value: '<1天', description: '从周级缩短至实时' },
|
{ metric: '决策响应时间', value: '<1天', description: '从周级缩短至实时', basis: 'target' },
|
||||||
{ metric: '自助分析占比', value: '70%+', description: '业务人员独立完成分析' },
|
{ metric: '自助分析占比', value: '70%+', description: '业务人员独立完成分析', basis: 'target' },
|
||||||
],
|
],
|
||||||
certifications: [],
|
certifications: [],
|
||||||
overview: '睿新商业智能平台是数据可视化分析平台,提供拖拽式自助分析能力,让业务人员无需写SQL也能从数据中找到答案。平台支持自助式数据分析和实时数据监控。',
|
overview: '睿新商业智能平台是数据可视化分析平台,提供拖拽式自助分析能力,让业务人员无需写SQL也能从数据中找到答案。平台支持自助式数据分析和实时数据监控。',
|
||||||
@@ -242,17 +242,17 @@ export const PRODUCTS: Product[] = [
|
|||||||
heroThemeId: 'sds',
|
heroThemeId: 'sds',
|
||||||
scenario: '面向零售与制造企业的智能销量预测与库存优化系统',
|
scenario: '面向零售与制造企业的智能销量预测与库存优化系统',
|
||||||
metrics: [
|
metrics: [
|
||||||
{ value: '73%', label: '缺货率降低' },
|
{ value: '73%', label: '缺货率降低', basis: 'target' },
|
||||||
{ value: '38%', label: '周转提升' },
|
{ value: '38%', label: '周转提升', basis: 'target' },
|
||||||
{ value: '<6个月', label: 'ROI 周期' },
|
{ value: '<6个月', label: 'ROI 周期', basis: 'target' },
|
||||||
],
|
],
|
||||||
bundle: 'enterprise',
|
bundle: 'enterprise',
|
||||||
capabilities: ['销量预测', '智能补货', '库存诊断', '供应商协同'],
|
capabilities: ['销量预测', '智能补货', '库存诊断', '供应商协同'],
|
||||||
caseStudies: [],
|
caseStudies: [],
|
||||||
dataProofs: [
|
dataProofs: [
|
||||||
{ metric: '缺货率降低', value: '73%', description: '从12%降至3.2%' },
|
{ metric: '缺货率降低', value: '73%', description: '从12%降至3.2%', basis: 'target' },
|
||||||
{ metric: '库存周转提升', value: '38%', description: '周转天数从45天降至28天' },
|
{ metric: '库存周转提升', value: '38%', description: '周转天数从45天降至28天', basis: 'target' },
|
||||||
{ metric: 'ROI回报周期', value: '<6个月', description: '投入产出清晰可算' },
|
{ metric: 'ROI回报周期', value: '<6个月', description: '投入产出清晰可算', basis: 'target' },
|
||||||
],
|
],
|
||||||
certifications: [],
|
certifications: [],
|
||||||
overview: '睿新供应链决策支持系统是智能供应链管理平台,自动预测销量、智能补货建议,帮您把库存成本降下来、把缺货损失挽回来。系统采用智能算法,实现从经验驱动到数据驱动的供应链管理升级。',
|
overview: '睿新供应链决策支持系统是智能供应链管理平台,自动预测销量、智能补货建议,帮您把库存成本降下来、把缺货损失挽回来。系统采用智能算法,实现从经验驱动到数据驱动的供应链管理升级。',
|
||||||
@@ -299,17 +299,17 @@ export const PRODUCTS: Product[] = [
|
|||||||
heroThemeId: 'oa',
|
heroThemeId: 'oa',
|
||||||
scenario: '面向中大型组织的一站式流程审批与协同办公平台',
|
scenario: '面向中大型组织的一站式流程审批与协同办公平台',
|
||||||
metrics: [
|
metrics: [
|
||||||
{ value: '95%', label: '审批提效' },
|
{ value: '95%', label: '审批提效', basis: 'target' },
|
||||||
{ value: '100%', label: '移动覆盖' },
|
{ value: '100%', label: '移动覆盖', basis: 'target' },
|
||||||
{ value: '10万+', label: '日流程承载' },
|
{ value: '10万+', label: '日流程承载', basis: 'target' },
|
||||||
],
|
],
|
||||||
bundle: 'enterprise',
|
bundle: 'enterprise',
|
||||||
capabilities: ['流程审批', '公文管理', '知识中心', '移动办公'],
|
capabilities: ['流程审批', '公文管理', '知识中心', '移动办公'],
|
||||||
caseStudies: [],
|
caseStudies: [],
|
||||||
dataProofs: [
|
dataProofs: [
|
||||||
{ metric: '审批效率提升', value: '95%', description: '从4天缩短至4小时' },
|
{ metric: '审批效率提升', value: '95%', description: '从4天缩短至4小时', basis: 'target' },
|
||||||
{ metric: '移动办公覆盖率', value: '100%', description: 'PC+移动端全覆盖' },
|
{ metric: '移动办公覆盖率', value: '100%', description: 'PC+移动端全覆盖', basis: 'target' },
|
||||||
{ metric: '流程电子化率', value: '100%', description: '告别纸质流转' },
|
{ metric: '流程电子化率', value: '100%', description: '告别纸质流转', basis: 'target' },
|
||||||
],
|
],
|
||||||
certifications: [],
|
certifications: [],
|
||||||
overview: '睿新协同办公系统是综合型企业协同办公平台,融合传统办公自动化与现代协作理念。集成流程审批、公文管理、即时通讯、知识管理、会议日程等核心模块,支持PC端与移动端全场景覆盖,帮助企业实现办公数字化、流程规范化、协作高效化。',
|
overview: '睿新协同办公系统是综合型企业协同办公平台,融合传统办公自动化与现代协作理念。集成流程审批、公文管理、即时通讯、知识管理、会议日程等核心模块,支持PC端与移动端全场景覆盖,帮助企业实现办公数字化、流程规范化、协作高效化。',
|
||||||
|
|||||||
+12
-12
@@ -37,8 +37,8 @@ export const SOLUTIONS: Solution[] = [
|
|||||||
},
|
},
|
||||||
painPoints: ['生产数据孤岛,决策缺乏实时数据支撑', '库存管理粗放,资金占用成本高'],
|
painPoints: ['生产数据孤岛,决策缺乏实时数据支撑', '库存管理粗放,资金占用成本高'],
|
||||||
outcomes: [
|
outcomes: [
|
||||||
{ value: '30%', label: '库存周转提升' },
|
{ value: '30%', label: '库存周转提升', basis: 'target' },
|
||||||
{ value: '40%', label: '生产效率提升' },
|
{ value: '40%', label: '生产效率提升', basis: 'target' },
|
||||||
],
|
],
|
||||||
recommendedProducts: ['erp', 'bi', 'sds'],
|
recommendedProducts: ['erp', 'bi', 'sds'],
|
||||||
},
|
},
|
||||||
@@ -77,8 +77,8 @@ export const SOLUTIONS: Solution[] = [
|
|||||||
},
|
},
|
||||||
painPoints: ['线上线下渠道割裂,会员体验碎片化', '供应链响应慢,库存积压与缺货并存'],
|
painPoints: ['线上线下渠道割裂,会员体验碎片化', '供应链响应慢,库存积压与缺货并存'],
|
||||||
outcomes: [
|
outcomes: [
|
||||||
{ value: '25%', label: '营收增长' },
|
{ value: '25%', label: '营收增长', basis: 'target' },
|
||||||
{ value: '99%', label: '全渠道库存准确率' },
|
{ value: '99%', label: '全渠道库存准确率', basis: 'target' },
|
||||||
],
|
],
|
||||||
recommendedProducts: ['crm', 'bi', 'sds'],
|
recommendedProducts: ['crm', 'bi', 'sds'],
|
||||||
},
|
},
|
||||||
@@ -117,8 +117,8 @@ export const SOLUTIONS: Solution[] = [
|
|||||||
},
|
},
|
||||||
painPoints: ['招生线索管理分散,转化率难以提升', '教学资源管理混乱,优质内容难以沉淀'],
|
painPoints: ['招生线索管理分散,转化率难以提升', '教学资源管理混乱,优质内容难以沉淀'],
|
||||||
outcomes: [
|
outcomes: [
|
||||||
{ value: '45%', label: '线索转化率提升' },
|
{ value: '45%', label: '线索转化率提升', basis: 'target' },
|
||||||
{ value: '60%', label: '内容发布效率提升' },
|
{ value: '60%', label: '内容发布效率提升', basis: 'target' },
|
||||||
],
|
],
|
||||||
recommendedProducts: ['cms', 'oa', 'crm'],
|
recommendedProducts: ['cms', 'oa', 'crm'],
|
||||||
},
|
},
|
||||||
@@ -157,8 +157,8 @@ export const SOLUTIONS: Solution[] = [
|
|||||||
},
|
},
|
||||||
painPoints: ['临床数据分散,医生调阅效率低', '运营决策依赖人工统计,时效性差'],
|
painPoints: ['临床数据分散,医生调阅效率低', '运营决策依赖人工统计,时效性差'],
|
||||||
outcomes: [
|
outcomes: [
|
||||||
{ value: '10x', label: '决策效率提升' },
|
{ value: '10x', label: '决策效率提升', basis: 'target' },
|
||||||
{ value: '30%', label: '运营成本下降' },
|
{ value: '30%', label: '运营成本下降', basis: 'target' },
|
||||||
],
|
],
|
||||||
recommendedProducts: ['erp', 'sds', 'bi'],
|
recommendedProducts: ['erp', 'sds', 'bi'],
|
||||||
},
|
},
|
||||||
@@ -197,8 +197,8 @@ export const SOLUTIONS: Solution[] = [
|
|||||||
},
|
},
|
||||||
painPoints: ['监管要求持续升级,合规管理成本高', '数据孤岛严重,客户画像难以落地'],
|
painPoints: ['监管要求持续升级,合规管理成本高', '数据孤岛严重,客户画像难以落地'],
|
||||||
outcomes: [
|
outcomes: [
|
||||||
{ value: '70%', label: '财务效率提升' },
|
{ value: '70%', label: '财务效率提升', basis: 'target' },
|
||||||
{ value: '0', label: '重大合规事故' },
|
{ value: '0', label: '重大合规事故', basis: 'target' },
|
||||||
],
|
],
|
||||||
recommendedProducts: ['bi', 'sds', 'erp'],
|
recommendedProducts: ['bi', 'sds', 'erp'],
|
||||||
},
|
},
|
||||||
@@ -237,8 +237,8 @@ export const SOLUTIONS: Solution[] = [
|
|||||||
},
|
},
|
||||||
painPoints: ['运输过程不透明,客户体验差', '调度依赖人工经验,车辆空驶率高'],
|
painPoints: ['运输过程不透明,客户体验差', '调度依赖人工经验,车辆空驶率高'],
|
||||||
outcomes: [
|
outcomes: [
|
||||||
{ value: '20%', label: '空驶率降低' },
|
{ value: '20%', label: '空驶率降低', basis: 'target' },
|
||||||
{ value: '95%+', label: '全程可视化率' },
|
{ value: '95%+', label: '全程可视化率', basis: 'target' },
|
||||||
],
|
],
|
||||||
recommendedProducts: ['erp', 'bi', 'sds'],
|
recommendedProducts: ['erp', 'bi', 'sds'],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -15,18 +15,18 @@ export const STANDALONE_PRODUCTS: StandaloneProduct[] = [
|
|||||||
heroThemeId: 'novavis',
|
heroThemeId: 'novavis',
|
||||||
scenario: '面向执法机关的离线智能数据分析与资金关系图谱平台',
|
scenario: '面向执法机关的离线智能数据分析与资金关系图谱平台',
|
||||||
metrics: [
|
metrics: [
|
||||||
{ value: '100万+', label: '秒级处理' },
|
{ value: '100万+', label: '秒级处理', basis: 'target' },
|
||||||
{ value: '<30秒', label: '分析耗时' },
|
{ value: '<30秒', label: '分析耗时', basis: 'target' },
|
||||||
{ value: '20层+', label: '资金追踪' },
|
{ value: '20层+', label: '资金追踪', basis: 'target' },
|
||||||
],
|
],
|
||||||
bundle: 'standalone',
|
bundle: 'standalone',
|
||||||
capabilities: ['资金追踪', '关系图谱', 'AI 分析', '离线运行'],
|
capabilities: ['资金追踪', '关系图谱', 'AI 分析', '离线运行'],
|
||||||
caseStudies: [],
|
caseStudies: [],
|
||||||
dataProofs: [
|
dataProofs: [
|
||||||
{ metric: '数据处理能力', value: '100万+', description: '百万级记录秒级处理' },
|
{ metric: '数据处理能力', value: '100万+', description: '百万级记录秒级处理', basis: 'target' },
|
||||||
{ metric: '分析耗时', value: '<30秒', description: '百万级交易完成分析' },
|
{ metric: '分析耗时', value: '<30秒', description: '百万级交易完成分析', basis: 'target' },
|
||||||
{ metric: '内存占用降低', value: '70%', description: '相比传统方案显著优化' },
|
{ metric: '内存占用降低', value: '70%', description: '相比传统方案显著优化', basis: 'target' },
|
||||||
{ metric: '部署时间', value: '5分钟', description: '即插即用,快速启动' },
|
{ metric: '部署时间', value: '5分钟', description: '即插即用,快速启动', basis: 'target' },
|
||||||
],
|
],
|
||||||
certifications: [],
|
certifications: [],
|
||||||
overview: '睿视 NovaVis 是我们即将推出的面向执法机关的新一代智能数据分析平台。以完全离线架构保障数据安全——所有计算均在本地完成,数据不出本地、不触网络。融合 AI 智能分析与专业关系网络图谱,提供资金追踪、团伙分析、时空碰撞等核心能力,让海量涉案数据秒级响应,为经济犯罪侦查、反腐败调查、涉黑涉恶打击等场景提供全链路数据分析支撑。',
|
overview: '睿视 NovaVis 是我们即将推出的面向执法机关的新一代智能数据分析平台。以完全离线架构保障数据安全——所有计算均在本地完成,数据不出本地、不触网络。融合 AI 智能分析与专业关系网络图谱,提供资金追踪、团伙分析、时空碰撞等核心能力,让海量涉案数据秒级响应,为经济犯罪侦查、反腐败调查、涉黑涉恶打击等场景提供全链路数据分析支撑。',
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import { CTAButton } from '@/components/ui/cta-button';
|
import { CTAButton } from '@/components/ui/cta-button';
|
||||||
import { MetricsBasisNote } from '@/components/ui/metrics-basis-note';
|
import { MetricsBasisNote } from '@/components/ui/metrics-basis-note';
|
||||||
|
import { weakestBasis, type MetricBasis } from '@/lib/constants/metrics-basis';
|
||||||
import { ContentUnavailableState } from '@/components/ui/content-unavailable';
|
import { ContentUnavailableState } from '@/components/ui/content-unavailable';
|
||||||
import {
|
import {
|
||||||
Trophy,
|
Trophy,
|
||||||
@@ -35,7 +36,7 @@ interface AboutData {
|
|||||||
promiseText?: string;
|
promiseText?: string;
|
||||||
coreValues?: { number: string; title: string; description: string }[];
|
coreValues?: { number: string; title: string; description: string }[];
|
||||||
milestones?: { year: string; title: string; description: string; highlight: string }[];
|
milestones?: { year: string; title: string; description: string; highlight: string }[];
|
||||||
keyMetrics?: { value: string; label: string; sub: string }[];
|
keyMetrics?: { value: string; label: string; sub: string; basis?: MetricBasis }[];
|
||||||
certifications?: { name: string; description: string }[];
|
certifications?: { name: string; description: string }[];
|
||||||
partners?: string[];
|
partners?: string[];
|
||||||
ctaTitle?: string;
|
ctaTitle?: string;
|
||||||
@@ -147,7 +148,7 @@ function MetricsStrip({ data }: { data: AboutData }) {
|
|||||||
</motion.div>
|
</motion.div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<MetricsBasisNote className="mt-8 text-center lg:text-left" />
|
<MetricsBasisNote basis={weakestBasis(metrics.map((m) => m.basis))} className="mt-8 text-center lg:text-left" />
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { MetricsBasisNote } from '@/components/ui/metrics-basis-note';
|
|||||||
import { COMPANY_INFO, TRUST_SIGNALS, EARLY_ACCESS } from '@/lib/constants';
|
import { COMPANY_INFO, TRUST_SIGNALS, EARLY_ACCESS } from '@/lib/constants';
|
||||||
import { canonicalCtaLabel, CTA_LABELS } from '@/lib/constants/cta-labels';
|
import { canonicalCtaLabel, CTA_LABELS } from '@/lib/constants/cta-labels';
|
||||||
import { canonicalTrustSource } from '@/lib/constants/company';
|
import { canonicalTrustSource } from '@/lib/constants/company';
|
||||||
|
import { weakestBasis } from '@/lib/constants/metrics-basis';
|
||||||
import { useSiteConfig } from '@/lib/site-config';
|
import { useSiteConfig } from '@/lib/site-config';
|
||||||
import { useReducedMotion } from '@/hooks/use-reduced-motion';
|
import { useReducedMotion } from '@/hooks/use-reduced-motion';
|
||||||
import { type NewsItem } from '@/lib/constants/news';
|
import { type NewsItem } from '@/lib/constants/news';
|
||||||
@@ -296,7 +297,7 @@ function HeroStatsBand({ stats }: { stats?: Record<string, any>[] }) {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</ScrollReveal>
|
</ScrollReveal>
|
||||||
<MetricsBasisNote className="mt-6" />
|
<MetricsBasisNote basis={weakestBasis(displayStats.map((s) => s.basis))} className="mt-6" />
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
@@ -558,7 +559,7 @@ function ServicesSection({ services, pageCopy }: { services?: Record<string, any
|
|||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<MetricsBasisNote />
|
<MetricsBasisNote basis={weakestBasis(((service as any).metrics as any[]).map((m) => m.basis))} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="mt-auto pt-2">
|
<div className="mt-auto pt-2">
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import ProductsContentV3 from './products-content-v3';
|
|||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: `产品矩阵 - ${COMPANY_INFO.displayName}`,
|
title: `产品矩阵 - ${COMPANY_INFO.displayName}`,
|
||||||
description: `覆盖企业数字化全场景的产品矩阵,从数据智能到业务协同,每一款产品都经过实战验证。`,
|
description: `覆盖企业数字化核心场景的自研产品矩阵,从业务系统到数据智能,套装与专业产品线互为补充。`,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const revalidate = 3600;
|
export const revalidate = 3600;
|
||||||
|
|||||||
@@ -325,7 +325,7 @@ export default function ProductDetailContentV3({ product }: ProductDetailContent
|
|||||||
{product.methodology && product.methodology.length > 0 && (
|
{product.methodology && product.methodology.length > 0 && (
|
||||||
<MethodologyFramework
|
<MethodologyFramework
|
||||||
title="产品方法论"
|
title="产品方法论"
|
||||||
subtitle="经过实战验证的产品设计理念与实施框架"
|
subtitle="我们做产品的设计理念与实施框架"
|
||||||
layers={product.methodology}
|
layers={product.methodology}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -341,12 +341,13 @@ export default function ProductDetailContentV3({ product }: ProductDetailContent
|
|||||||
<L3EmptyFallback caseStudies={product.caseStudies} certifications={product.certifications} dataProofs={product.dataProofs} />
|
<L3EmptyFallback caseStudies={product.caseStudies} certifications={product.certifications} dataProofs={product.dataProofs} />
|
||||||
{product.dataProofs && product.dataProofs.length > 0 && (
|
{product.dataProofs && product.dataProofs.length > 0 && (
|
||||||
<DataProofSection
|
<DataProofSection
|
||||||
title="可衡量的成果"
|
title="可量化的设计目标"
|
||||||
subtitle="用数据说话,每一项指标都源自真实客户案例"
|
subtitle="以下指标为产品设计目标口径,用于约束自研交付标准;公司尚无已交付客户案例可提供实测数据。"
|
||||||
metrics={product.dataProofs.map((dp) => ({
|
metrics={product.dataProofs.map((dp) => ({
|
||||||
value: dp.value,
|
value: dp.value,
|
||||||
label: dp.metric,
|
label: dp.metric,
|
||||||
description: dp.description,
|
description: dp.description,
|
||||||
|
basis: dp.basis,
|
||||||
}))}
|
}))}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { BentoItem } from '@/components/sections/bento-grid';
|
|||||||
import { ArrowRight, ArrowUpRight, Package, Cpu, TrendingUp, Users, Settings, FileText } from 'lucide-react';
|
import { ArrowRight, ArrowUpRight, Package, Cpu, TrendingUp, Users, Settings, FileText } from 'lucide-react';
|
||||||
import type { Product } from '@/lib/constants/products';
|
import type { Product } from '@/lib/constants/products';
|
||||||
import { CTA_LABELS } from '@/lib/constants/cta-labels';
|
import { CTA_LABELS } from '@/lib/constants/cta-labels';
|
||||||
|
import { weakestBasis } from '@/lib/constants/metrics-basis';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
const EASE_SPRING = { type: 'spring', stiffness: 300, damping: 30 } as const;
|
const EASE_SPRING = { type: 'spring', stiffness: 300, damping: 30 } as const;
|
||||||
@@ -146,7 +147,7 @@ function ProductCard({ product, size = 'small' }: { product: Product; size?: 'la
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
{product.metrics && product.metrics.length > 0 && product.status !== '已发布' && (
|
{product.metrics && product.metrics.length > 0 && product.status !== '已发布' && (
|
||||||
<MetricsBasisNote className="-mt-2 mb-4" />
|
<MetricsBasisNote basis={weakestBasis(product.metrics.map((m) => m.basis))} className="-mt-2 mb-4" />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
@@ -319,7 +320,7 @@ function SuiteCombosSection({ products }: { products: Product[] }) {
|
|||||||
推荐产品组合
|
推荐产品组合
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-text-secondary text-lg mt-6 leading-relaxed">
|
<p className="text-text-secondary text-lg mt-6 leading-relaxed">
|
||||||
经过实战验证的产品组合,发挥 1+1 > 2 的协同效应
|
套装与专业产品线按场景互补,覆盖从核心业务系统到数据分析的关键链路
|
||||||
</p>
|
</p>
|
||||||
</ScrollReveal>
|
</ScrollReveal>
|
||||||
|
|
||||||
|
|||||||
@@ -178,7 +178,7 @@ export function StandaloneProductClient({ item }: StandaloneProductClientProps)
|
|||||||
<h2 className="text-2xl font-bold text-ink">技术参数</h2>
|
<h2 className="text-2xl font-bold text-ink">技术参数</h2>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-text-secondary max-w-2xl">
|
<p className="text-text-secondary max-w-2xl">
|
||||||
关键性能指标与技术指标,经实际场景验证。
|
关键性能指标与技术规格,为产品设计目标口径,尚无第三方实测报告。
|
||||||
</p>
|
</p>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
|
|||||||
@@ -6,12 +6,13 @@ import { StaticLink } from '@/components/ui/static-link';
|
|||||||
import { CTAButton } from '@/components/ui/cta-button';
|
import { CTAButton } from '@/components/ui/cta-button';
|
||||||
import { MetricsBasisNote } from '@/components/ui/metrics-basis-note';
|
import { MetricsBasisNote } from '@/components/ui/metrics-basis-note';
|
||||||
import { CTA_LABELS, canonicalCtaLabel } from '@/lib/constants/cta-labels';
|
import { CTA_LABELS, canonicalCtaLabel } from '@/lib/constants/cta-labels';
|
||||||
import type { Service } from '@/lib/constants/services';
|
import type { Service, ServiceMetric } from '@/lib/constants/services';
|
||||||
|
import { weakestBasis } from '@/lib/constants/metrics-basis';
|
||||||
|
|
||||||
const EASE_OUT = [0.22, 1, 0.36, 1] as const;
|
const EASE_OUT = [0.22, 1, 0.36, 1] as const;
|
||||||
|
|
||||||
/** Fallback metrics keyed by service ID — used when CMS data does not provide metrics */
|
/** Fallback metrics keyed by service ID — used when CMS data does not provide metrics */
|
||||||
const SERVICE_UI_META: Record<string, { metrics: { value: string; label: string }[] }> = {
|
const SERVICE_UI_META: Record<string, { metrics: ServiceMetric[] }> = {
|
||||||
consulting: { metrics: [{ value: '10+', label: '人核心团队' }, { value: '6', label: '行业场景' }] },
|
consulting: { metrics: [{ value: '10+', label: '人核心团队' }, { value: '6', label: '行业场景' }] },
|
||||||
software: { metrics: [{ value: '6', label: '款自研产品' }, { value: 'A+', label: '代码质量' }] },
|
software: { metrics: [{ value: '6', label: '款自研产品' }, { value: 'A+', label: '代码质量' }] },
|
||||||
data: { metrics: [{ value: '99.9%', label: '系统可用性' }, { value: '24/7', label: '运维支持' }] },
|
data: { metrics: [{ value: '99.9%', label: '系统可用性' }, { value: '24/7', label: '运维支持' }] },
|
||||||
@@ -231,7 +232,9 @@ function ServicesGridSection({ services, pageCopy }: { services?: Service[]; pag
|
|||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
{service.metrics.length > 0 && <MetricsBasisNote className="mb-6" />}
|
{service.metrics.length > 0 && (
|
||||||
|
<MetricsBasisNote basis={weakestBasis(service.metrics.map((m) => m.basis))} className="mb-6" />
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="inline-flex items-center gap-2 text-sm font-semibold text-text-secondary group-hover:text-brand-ink transition-colors duration-300">
|
<div className="inline-flex items-center gap-2 text-sm font-semibold text-text-secondary group-hover:text-brand-ink transition-colors duration-300">
|
||||||
<span>了解详情</span>
|
<span>了解详情</span>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { StaticLink } from '@/components/ui/static-link';
|
|||||||
import { CTAButton } from '@/components/ui/cta-button';
|
import { CTAButton } from '@/components/ui/cta-button';
|
||||||
import { MetricsBasisNote } from '@/components/ui/metrics-basis-note';
|
import { MetricsBasisNote } from '@/components/ui/metrics-basis-note';
|
||||||
import { CTA_LABELS, canonicalCtaLabel } from '@/lib/constants/cta-labels';
|
import { CTA_LABELS, canonicalCtaLabel } from '@/lib/constants/cta-labels';
|
||||||
|
import { weakestBasis } from '@/lib/constants/metrics-basis';
|
||||||
import { type Solution } from '@/lib/constants/solutions';
|
import { type Solution } from '@/lib/constants/solutions';
|
||||||
import type { Product } from '@/lib/constants/products';
|
import type { Product } from '@/lib/constants/products';
|
||||||
|
|
||||||
@@ -135,7 +136,7 @@ function SolutionCard({ solution, index, products, featured = false }: { solutio
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
{solution.outcomes && solution.outcomes.length > 0 && (
|
{solution.outcomes && solution.outcomes.length > 0 && (
|
||||||
<MetricsBasisNote className="-mt-2 mb-4" />
|
<MetricsBasisNote basis={weakestBasis(solution.outcomes.map((o) => o.basis))} className="-mt-2 mb-4" />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="pt-4 border-t border-border-primary">
|
<div className="pt-4 border-t border-border-primary">
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import { ScrollReveal, StaggerReveal } from '@/components/ui/scroll-reveal';
|
|||||||
import { EASE_OUT } from '@/components/ui/page-decoration';
|
import { EASE_OUT } from '@/components/ui/page-decoration';
|
||||||
import { CheckCircle2, ChevronDown } from 'lucide-react';
|
import { CheckCircle2, ChevronDown } from 'lucide-react';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
import { MetricsBasisNote } from '@/components/ui/metrics-basis-note';
|
||||||
|
import { weakestBasis, type MetricBasis } from '@/lib/constants/metrics-basis';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
export interface MethodologyLayer {
|
export interface MethodologyLayer {
|
||||||
@@ -318,6 +320,7 @@ export interface DataMetric {
|
|||||||
description?: string;
|
description?: string;
|
||||||
trend?: string;
|
trend?: string;
|
||||||
trendUp?: boolean;
|
trendUp?: boolean;
|
||||||
|
basis?: MetricBasis;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DataProofSectionProps {
|
export interface DataProofSectionProps {
|
||||||
@@ -328,6 +331,7 @@ export interface DataProofSectionProps {
|
|||||||
|
|
||||||
export function DataProofSection({ title, subtitle, metrics }: DataProofSectionProps) {
|
export function DataProofSection({ title, subtitle, metrics }: DataProofSectionProps) {
|
||||||
if (!metrics || metrics.length === 0) return null;
|
if (!metrics || metrics.length === 0) return null;
|
||||||
|
const basis = weakestBasis(metrics.map((m) => m.basis));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="relative py-20 sm:py-28 md:py-32 lg:py-40 overflow-hidden bg-bg-primary">
|
<section className="relative py-20 sm:py-28 md:py-32 lg:py-40 overflow-hidden bg-bg-primary">
|
||||||
@@ -375,6 +379,7 @@ export function DataProofSection({ title, subtitle, metrics }: DataProofSectionP
|
|||||||
))}
|
))}
|
||||||
</StaggerReveal>
|
</StaggerReveal>
|
||||||
</div>
|
</div>
|
||||||
|
<MetricsBasisNote basis={basis} className="mt-6" />
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ import {
|
|||||||
type LucideIcon,
|
type LucideIcon,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import type { Product } from '@/lib/constants/products';
|
import type { Product } from '@/lib/constants/products';
|
||||||
|
import { MetricsBasisNote } from '@/components/ui/metrics-basis-note';
|
||||||
|
import { weakestBasis } from '@/lib/constants/metrics-basis';
|
||||||
|
|
||||||
interface ProductValueSectionProps {
|
interface ProductValueSectionProps {
|
||||||
product: Product;
|
product: Product;
|
||||||
@@ -250,11 +252,11 @@ export function ProductValueSection({ product }: ProductValueSectionProps) {
|
|||||||
<div className="flex items-center gap-3 mb-6">
|
<div className="flex items-center gap-3 mb-6">
|
||||||
<div className="w-10 h-px bg-brand" />
|
<div className="w-10 h-px bg-brand" />
|
||||||
<span className="text-[13px] tracking-[0.12em] font-semibold text-brand-ink">
|
<span className="text-[13px] tracking-[0.12em] font-semibold text-brand-ink">
|
||||||
效果数据
|
设计目标
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-2xl sm:text-3xl md:text-4xl lg:text-5xl font-black text-ink tracking-tight leading-[0.95]">
|
<h3 className="text-2xl sm:text-3xl md:text-4xl lg:text-5xl font-black text-ink tracking-tight leading-[0.95]">
|
||||||
用真实效果证明价值
|
以可量化的目标约束交付
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -275,6 +277,10 @@ export function ProductValueSection({ product }: ProductValueSectionProps) {
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
<MetricsBasisNote
|
||||||
|
basis={weakestBasis(product.dataProofs.map((proof) => proof.basis))}
|
||||||
|
className="mt-8"
|
||||||
|
/>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import { useRef, useEffect, useState } from 'react';
|
|||||||
import { CaseStudyCard } from './detail-case-study';
|
import { CaseStudyCard } from './detail-case-study';
|
||||||
import { CertificationList } from './detail-certification';
|
import { CertificationList } from './detail-certification';
|
||||||
import type { CaseStudy, DataProof, Certification } from '@/lib/constants/products';
|
import type { CaseStudy, DataProof, Certification } from '@/lib/constants/products';
|
||||||
|
import { MetricsBasisNote } from '@/components/ui/metrics-basis-note';
|
||||||
|
import { weakestBasis } from '@/lib/constants/metrics-basis';
|
||||||
|
|
||||||
interface DetailTrustSectionProps {
|
interface DetailTrustSectionProps {
|
||||||
caseStudies?: CaseStudy[];
|
caseStudies?: CaseStudy[];
|
||||||
@@ -84,13 +86,13 @@ export function DetailTrustSection({
|
|||||||
<div className="flex items-center gap-3 mb-6">
|
<div className="flex items-center gap-3 mb-6">
|
||||||
<div className="w-10 h-px bg-brand" />
|
<div className="w-10 h-px bg-brand" />
|
||||||
<span className="text-[13px] tracking-[0.12em] font-semibold text-brand-ink">
|
<span className="text-[13px] tracking-[0.12em] font-semibold text-brand-ink">
|
||||||
用真实效果证明价值
|
目标口径
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<h2 className="text-2xl sm:text-3xl md:text-4xl lg:text-5xl font-black text-ink tracking-tight leading-[0.95]">
|
<h2 className="text-2xl sm:text-3xl md:text-4xl lg:text-5xl font-black text-ink tracking-tight leading-[0.95]">
|
||||||
来自真实客户的使用数据,
|
以设计目标约束交付,
|
||||||
<br />
|
<br />
|
||||||
让效果可衡量、可验证
|
在首批共创中转为可核验数据
|
||||||
</h2>
|
</h2>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
@@ -114,6 +116,10 @@ export function DetailTrustSection({
|
|||||||
</motion.div>
|
</motion.div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
<MetricsBasisNote
|
||||||
|
basis={weakestBasis(dataProofs.map((proof) => proof.basis))}
|
||||||
|
className="mt-8"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -435,14 +435,14 @@ describe('ProductValueSection', () => {
|
|||||||
it('should render data proofs when available', async () => {
|
it('should render data proofs when available', async () => {
|
||||||
const { ProductValueSection } = await import('./detail-product-value');
|
const { ProductValueSection } = await import('./detail-product-value');
|
||||||
render(<ProductValueSection product={mockProduct} />);
|
render(<ProductValueSection product={mockProduct} />);
|
||||||
expect(screen.getByText('用真实效果证明价值')).toBeInTheDocument();
|
expect(screen.getByText('以可量化的目标约束交付')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not render data proofs section when empty', async () => {
|
it('should not render data proofs section when empty', async () => {
|
||||||
const { ProductValueSection } = await import('./detail-product-value');
|
const { ProductValueSection } = await import('./detail-product-value');
|
||||||
const productWithoutData = { ...mockProduct, dataProofs: [] };
|
const productWithoutData = { ...mockProduct, dataProofs: [] };
|
||||||
render(<ProductValueSection product={productWithoutData} />);
|
render(<ProductValueSection product={productWithoutData} />);
|
||||||
expect(screen.queryByText('用真实效果证明价值')).not.toBeInTheDocument();
|
expect(screen.queryByText('以可量化的目标约束交付')).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
@@ -462,8 +462,10 @@ describe('DetailTrustSection', () => {
|
|||||||
dataProofs={mockProduct.dataProofs}
|
dataProofs={mockProduct.dataProofs}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
expect(screen.getByText('用真实效果证明价值')).toBeInTheDocument();
|
expect(screen.getByText('目标口径')).toBeInTheDocument();
|
||||||
expect(screen.getByText(mockProduct.dataProofs[0]!.metric)).toBeInTheDocument();
|
expect(screen.getByText(mockProduct.dataProofs[0]!.metric)).toBeInTheDocument();
|
||||||
|
// 未声明 basis 的 dataProofs 必须自动落最弱口径角注,不得裸奔
|
||||||
|
expect(screen.getByTestId('metrics-basis-note')).toHaveTextContent('目标口径,非既成结果');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should render case studies', async () => {
|
it('should render case studies', async () => {
|
||||||
@@ -500,7 +502,7 @@ describe('DetailTrustSection', () => {
|
|||||||
accentColor="#C41E3A"
|
accentColor="#C41E3A"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
expect(screen.getByText('用真实效果证明价值')).toBeInTheDocument();
|
expect(screen.getByText('目标口径')).toBeInTheDocument();
|
||||||
expect(screen.getByText('他们已经实现了转型突破')).toBeInTheDocument();
|
expect(screen.getByText('他们已经实现了转型突破')).toBeInTheDocument();
|
||||||
expect(screen.getByText('资质认证')).toBeInTheDocument();
|
expect(screen.getByText('资质认证')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
* - 内容源于 `EARLY_ACCESS` 常量(company.ts L119),单一真源。
|
* - 内容源于 `EARLY_ACCESS` 常量(company.ts L119),单一真源。
|
||||||
*
|
*
|
||||||
* 与 L3SignalsSlot 关系:EarlyAccessNotice 是 L3EmptyFallback 的次要兜底块,
|
* 与 L3SignalsSlot 关系:EarlyAccessNotice 是 L3EmptyFallback 的次要兜底块,
|
||||||
* 在 signals(4 类信号卡)下方呈现——回答「为什么没有真实客户案例」这个问题。
|
* 在 signals(4 类信号卡)下方呈现——回答「为什么还没有可公开案例」这个问题。
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
|||||||
@@ -1,14 +1,21 @@
|
|||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { METRICS_BASIS_NOTE } from '@/lib/constants/metrics-basis';
|
import { METRIC_BASIS_NOTES, resolveMetricBasis, type MetricBasis } from '@/lib/constants/metrics-basis';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 结果型数字(提升百分比 / 可用性 / 达标率等)下方的口径角注。
|
* 结果型数字(提升百分比 / 可用性 / 达标率等)下方的口径角注。
|
||||||
* 全站统一文案,见 METRICS_BASIS_NOTE。供 solutions / services / home / about / products 复用。
|
* 文案按数字声明的 `basis` 口径解析,未声明一律按最弱的 target 处理,见 METRIC_BASIS_NOTES。
|
||||||
|
* 供 solutions / services / home / about / products 与详情页 DataProofSection 复用。
|
||||||
*/
|
*/
|
||||||
export function MetricsBasisNote({ className }: { className?: string }) {
|
export function MetricsBasisNote({
|
||||||
|
className,
|
||||||
|
basis = 'target',
|
||||||
|
}: {
|
||||||
|
className?: string;
|
||||||
|
basis?: MetricBasis | string | null;
|
||||||
|
}) {
|
||||||
return (
|
return (
|
||||||
<p className={cn('text-xs text-text-muted', className)} data-testid="metrics-basis-note">
|
<p className={cn('text-xs text-text-muted', className)} data-testid="metrics-basis-note">
|
||||||
{METRICS_BASIS_NOTE}
|
{METRIC_BASIS_NOTES[resolveMetricBasis(basis)]}
|
||||||
</p>
|
</p>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,22 @@
|
|||||||
import type { FieldDefinition } from './types';
|
import type { FieldDefinition } from './types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数字口径声明字段——所有以大字数字样式呈现的指标(metrics / outcomes / dataProofs)必须携带。
|
||||||
|
* 渲染端按此自动附口径角注;留空一律按最弱的 target 处理,见 lib/constants/metrics-basis.ts。
|
||||||
|
* 只有具备可核验实测出处(报告、监控、已交付客户数据)时才应选 verified。
|
||||||
|
*/
|
||||||
|
const metricBasisField: FieldDefinition = {
|
||||||
|
name: 'basis',
|
||||||
|
type: 'select',
|
||||||
|
label: '数据口径',
|
||||||
|
description: '留空按「目标口径」处理并自动标注;仅在有可核验实测出处时选「已验证」',
|
||||||
|
options: [
|
||||||
|
{ label: '目标口径(设计/承诺值,非既成结果)', value: 'target' },
|
||||||
|
{ label: '团队历史项目成果(非本公司交付数据)', value: 'team-history' },
|
||||||
|
{ label: '已验证(有可核验实测出处)', value: 'verified' },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
export interface ContentTypeConfig {
|
export interface ContentTypeConfig {
|
||||||
model: {
|
model: {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -164,6 +181,7 @@ const serviceFields: FieldDefinition[] = [
|
|||||||
fields: [
|
fields: [
|
||||||
{ name: 'value', type: 'text', label: '数值', required: true },
|
{ name: 'value', type: 'text', label: '数值', required: true },
|
||||||
{ name: 'label', type: 'text', label: '标签', required: true },
|
{ name: 'label', type: 'text', label: '标签', required: true },
|
||||||
|
{ ...metricBasisField },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -186,6 +204,7 @@ const serviceFields: FieldDefinition[] = [
|
|||||||
{ name: 'metric', type: 'text', label: '指标名称', required: true },
|
{ name: 'metric', type: 'text', label: '指标名称', required: true },
|
||||||
{ name: 'value', type: 'text', label: '数值', required: true },
|
{ name: 'value', type: 'text', label: '数值', required: true },
|
||||||
{ name: 'description', type: 'text', label: '描述', required: true },
|
{ name: 'description', type: 'text', label: '描述', required: true },
|
||||||
|
{ ...metricBasisField },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -363,6 +382,7 @@ const productFields: FieldDefinition[] = [
|
|||||||
fields: [
|
fields: [
|
||||||
{ name: 'value', type: 'text', label: '数值', required: true },
|
{ name: 'value', type: 'text', label: '数值', required: true },
|
||||||
{ name: 'label', type: 'text', label: '标签', required: true },
|
{ name: 'label', type: 'text', label: '标签', required: true },
|
||||||
|
{ ...metricBasisField },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -385,6 +405,7 @@ const productFields: FieldDefinition[] = [
|
|||||||
{ name: 'metric', type: 'text', label: '指标名称', required: true },
|
{ name: 'metric', type: 'text', label: '指标名称', required: true },
|
||||||
{ name: 'value', type: 'text', label: '数值', required: true },
|
{ name: 'value', type: 'text', label: '数值', required: true },
|
||||||
{ name: 'description', type: 'text', label: '描述', required: true },
|
{ name: 'description', type: 'text', label: '描述', required: true },
|
||||||
|
{ ...metricBasisField },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -548,6 +569,7 @@ const standaloneProductFields: FieldDefinition[] = [
|
|||||||
fields: [
|
fields: [
|
||||||
{ name: 'value', type: 'text', label: '数值', required: true },
|
{ name: 'value', type: 'text', label: '数值', required: true },
|
||||||
{ name: 'label', type: 'text', label: '标签', required: true },
|
{ name: 'label', type: 'text', label: '标签', required: true },
|
||||||
|
{ ...metricBasisField },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -570,6 +592,7 @@ const standaloneProductFields: FieldDefinition[] = [
|
|||||||
{ name: 'metric', type: 'text', label: '指标名称', required: true },
|
{ name: 'metric', type: 'text', label: '指标名称', required: true },
|
||||||
{ name: 'value', type: 'text', label: '数值', required: true },
|
{ name: 'value', type: 'text', label: '数值', required: true },
|
||||||
{ name: 'description', type: 'text', label: '描述', required: true },
|
{ name: 'description', type: 'text', label: '描述', required: true },
|
||||||
|
{ ...metricBasisField },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -755,6 +778,7 @@ const solutionFields: FieldDefinition[] = [
|
|||||||
fields: [
|
fields: [
|
||||||
{ name: 'value', type: 'text', label: '数值', required: true },
|
{ name: 'value', type: 'text', label: '数值', required: true },
|
||||||
{ name: 'label', type: 'text', label: '标签', required: true },
|
{ name: 'label', type: 'text', label: '标签', required: true },
|
||||||
|
{ ...metricBasisField },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -1180,6 +1204,7 @@ const aboutPageFields: FieldDefinition[] = [
|
|||||||
{ name: 'value', type: 'text', label: '数值', required: true },
|
{ name: 'value', type: 'text', label: '数值', required: true },
|
||||||
{ name: 'label', type: 'text', label: '标签', required: true },
|
{ name: 'label', type: 'text', label: '标签', required: true },
|
||||||
{ name: 'sub', type: 'text', label: '副标题' },
|
{ name: 'sub', type: 'text', label: '副标题' },
|
||||||
|
{ ...metricBasisField },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,12 +1,25 @@
|
|||||||
/**
|
/**
|
||||||
* 结果型数字口径机械防线(2026-09-19 critique P0):
|
* 数字口径机械防线(2026-09-19 critique P0 → 结构强制收尾):
|
||||||
* 「目标口径,非既成结果」必须来自 METRICS_BASIS_NOTE 单一真源;
|
* 1) 「目标口径,非既成结果」必须来自 METRICS_BASIS_NOTE 单一真源,不得散写字面量;
|
||||||
* 且每个呈现结果型数字的营销页必须接入 MetricsBasisNote。
|
* 2) 每个呈现结果型数字的渲染源(含详情页共享数字组件)必须接入 MetricsBasisNote;
|
||||||
|
* 3) 走共享数字样式渲染的指标必须声明 basis(target|team-history|verified),
|
||||||
|
* 且口径解析对缺失/非法值保守回落到最弱的 target;
|
||||||
|
* 4) 禁止再引入「数字源自真实客户案例 / 经过实战验证」式虚构佐证(本站零公开客户案例)。
|
||||||
* 扫描范围:src/app + src/components 的 .ts/.tsx(排除测试文件与 _archive 归档)。
|
* 扫描范围:src/app + src/components 的 .ts/.tsx(排除测试文件与 _archive 归档)。
|
||||||
*/
|
*/
|
||||||
import { readdirSync, readFileSync, statSync } from 'fs';
|
import { readdirSync, readFileSync, statSync } from 'fs';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
import { METRICS_BASIS_NOTE } from './metrics-basis';
|
import {
|
||||||
|
METRICS_BASIS_NOTE,
|
||||||
|
METRIC_BASES,
|
||||||
|
METRIC_BASIS_NOTES,
|
||||||
|
FORBIDDEN_PROOF_PHRASES,
|
||||||
|
resolveMetricBasis,
|
||||||
|
weakestBasis,
|
||||||
|
} from './metrics-basis';
|
||||||
|
import { PRODUCTS } from './products';
|
||||||
|
import { SOLUTIONS } from './solutions';
|
||||||
|
import { STANDALONE_PRODUCTS } from '../../../prisma/seeds/standalone-products';
|
||||||
|
|
||||||
const SOURCE_ROOTS = ['src/app', 'src/components'];
|
const SOURCE_ROOTS = ['src/app', 'src/components'];
|
||||||
|
|
||||||
@@ -16,6 +29,10 @@ const REQUIRED_ANNOTATED_SOURCES = [
|
|||||||
'src/app/(marketing)/solutions/solutions-content-v3.tsx',
|
'src/app/(marketing)/solutions/solutions-content-v3.tsx',
|
||||||
'src/app/(marketing)/services/services-content-v3.tsx',
|
'src/app/(marketing)/services/services-content-v3.tsx',
|
||||||
'src/app/(marketing)/home-content-v15.tsx',
|
'src/app/(marketing)/home-content-v15.tsx',
|
||||||
|
// 详情页同样以大数字样式渲染 dataProofs,不得裸奔
|
||||||
|
'src/components/content/sections.tsx',
|
||||||
|
'src/components/detail/detail-trust-section.tsx',
|
||||||
|
'src/components/detail/detail-product-value.tsx',
|
||||||
];
|
];
|
||||||
|
|
||||||
function collectSourceFiles(dir: string, acc: string[] = []): string[] {
|
function collectSourceFiles(dir: string, acc: string[] = []): string[] {
|
||||||
@@ -32,11 +49,29 @@ function collectSourceFiles(dir: string, acc: string[] = []): string[] {
|
|||||||
return acc;
|
return acc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 把三处 seed 里的全部数字条目摊平,用于口径声明完整性断言。 */
|
||||||
|
function allSeedMetricBases(): Array<string | undefined> {
|
||||||
|
const bases: Array<string | undefined> = [];
|
||||||
|
for (const p of [...PRODUCTS, ...STANDALONE_PRODUCTS]) {
|
||||||
|
(p.metrics ?? []).forEach((m) => bases.push(m.basis));
|
||||||
|
(p.dataProofs ?? []).forEach((d) => bases.push(d.basis));
|
||||||
|
}
|
||||||
|
for (const s of SOLUTIONS) {
|
||||||
|
(s.outcomes ?? []).forEach((o) => bases.push(o.basis));
|
||||||
|
(s.dataProofs ?? []).forEach((d) => bases.push(d.basis));
|
||||||
|
}
|
||||||
|
return bases;
|
||||||
|
}
|
||||||
|
|
||||||
describe('结果型数字口径角注防线', () => {
|
describe('结果型数字口径角注防线', () => {
|
||||||
it('METRICS_BASIS_NOTE 固定为「目标口径,非既成结果」', () => {
|
it('METRICS_BASIS_NOTE 固定为「目标口径,非既成结果」', () => {
|
||||||
expect(METRICS_BASIS_NOTE).toBe('目标口径,非既成结果');
|
expect(METRICS_BASIS_NOTE).toBe('目标口径,非既成结果');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('METRICS_BASIS_NOTE 等于最弱口径 target 的文案', () => {
|
||||||
|
expect(METRICS_BASIS_NOTE).toBe(METRIC_BASIS_NOTES.target);
|
||||||
|
});
|
||||||
|
|
||||||
it('生产源码不得硬编码口径角注字面量(必须引用单一真源)', () => {
|
it('生产源码不得硬编码口径角注字面量(必须引用单一真源)', () => {
|
||||||
const violations: string[] = [];
|
const violations: string[] = [];
|
||||||
for (const root of SOURCE_ROOTS) {
|
for (const root of SOURCE_ROOTS) {
|
||||||
@@ -63,3 +98,86 @@ describe('结果型数字口径角注防线', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('basis 口径解析', () => {
|
||||||
|
it('枚举固定为 target / team-history / verified', () => {
|
||||||
|
expect(METRIC_BASES).toEqual(['target', 'team-history', 'verified']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('三个口径均有文案,无空串', () => {
|
||||||
|
for (const basis of METRIC_BASES) {
|
||||||
|
expect(METRIC_BASIS_NOTES[basis].length).toBeGreaterThan(0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('合法口径原样解析(正控制,防匹配零的假绿)', () => {
|
||||||
|
expect(resolveMetricBasis('verified')).toBe('verified');
|
||||||
|
expect(resolveMetricBasis('team-history')).toBe('team-history');
|
||||||
|
expect(resolveMetricBasis('target')).toBe('target');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('缺失/非法/大小写错误一律保守回落到 target', () => {
|
||||||
|
expect(resolveMetricBasis(undefined)).toBe('target');
|
||||||
|
expect(resolveMetricBasis(null)).toBe('target');
|
||||||
|
expect(resolveMetricBasis('VERIFIED')).toBe('target');
|
||||||
|
expect(resolveMetricBasis('已验证')).toBe('target');
|
||||||
|
expect(resolveMetricBasis('verified ')).toBe('target');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('weakestBasis 取最弱口径:任一 target 存在即整体为 target', () => {
|
||||||
|
expect(weakestBasis(['verified', 'team-history'])).toBe('team-history');
|
||||||
|
expect(weakestBasis(['verified', 'target'])).toBe('target');
|
||||||
|
expect(weakestBasis(['verified', 'verified'])).toBe('verified');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('weakestBasis 空数组与含未声明项均落到 target', () => {
|
||||||
|
expect(weakestBasis([])).toBe('target');
|
||||||
|
expect(weakestBasis(['verified', undefined])).toBe('target');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('seed 数字必须显式声明口径', () => {
|
||||||
|
it('存在可检出的数字条目(覆盖证明,防空跑)', () => {
|
||||||
|
expect(allSeedMetricBases().length).toBeGreaterThan(40);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('每条 metrics / outcomes / dataProofs 均声明 basis', () => {
|
||||||
|
const missing = allSeedMetricBases().filter((b) => !b);
|
||||||
|
expect(missing).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('声明的 basis 全部合法', () => {
|
||||||
|
const invalid = allSeedMetricBases().filter(
|
||||||
|
(b) => !(METRIC_BASES as readonly string[]).includes(String(b)),
|
||||||
|
);
|
||||||
|
expect(invalid).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('当前无任何条目自称 verified(零编造:尚无客户实测数据)', () => {
|
||||||
|
const claimed = allSeedMetricBases().filter((b) => b === 'verified');
|
||||||
|
expect(claimed).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('虚构佐证禁词防线', () => {
|
||||||
|
it('正控制:禁词表非空且每条均可被检出', () => {
|
||||||
|
expect(FORBIDDEN_PROOF_PHRASES.length).toBeGreaterThan(0);
|
||||||
|
for (const phrase of FORBIDDEN_PROOF_PHRASES) {
|
||||||
|
expect(`示例文案${phrase}结尾`).toContain(phrase);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('生产源码不含「真实客户案例 / 实战验证」式虚构佐证', () => {
|
||||||
|
const violations: string[] = [];
|
||||||
|
for (const root of SOURCE_ROOTS) {
|
||||||
|
for (const file of collectSourceFiles(root)) {
|
||||||
|
const lines = readFileSync(file, 'utf-8').split('\n');
|
||||||
|
lines.forEach((line, i) => {
|
||||||
|
const hit = FORBIDDEN_PROOF_PHRASES.find((p) => line.includes(p));
|
||||||
|
if (hit) violations.push(`${file}:${i + 1} ${hit}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expect(violations).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,9 +1,59 @@
|
|||||||
/**
|
/**
|
||||||
* METRICS_BASIS_NOTE — 全站「结果型数字」口径角注单一真源。
|
* 数字口径(metric basis)单一真源。
|
||||||
*
|
*
|
||||||
* 2026-09-19 critique P0:solutions / services / home / about 把「30% 库存周转提升」
|
* 2026-09-19 critique P0:solutions / services / home / about / products 把「30% 库存周转提升」
|
||||||
* 「99.9% 可用性」这类目标值/能力值以既成结果口吻呈现,缺出处即构成误导。
|
* 「99.9% 系统可用率」这类目标值/能力值以既成结果口吻呈现,缺出处即构成误导。
|
||||||
* 与 CTA_LABELS 同属渲染入口的诚实性防线:数字保留,但强制附口径说明。
|
*
|
||||||
* 根治方式是补充真实案例数据或 admin 修正内容,此处为最后一道防线。
|
* 本文件把「每个数字必须声明口径」从个别页的自觉升级为结构约束:数字携带 `basis`,
|
||||||
|
* 渲染端据此自动附角注;未声明或声明非法一律按最弱口径 `target` 处理(保守默认,
|
||||||
|
* 宁可多标注也不裸奔)。真实出处由业务在 admin 录入,代码零虚构。
|
||||||
*/
|
*/
|
||||||
export const METRICS_BASIS_NOTE = '目标口径,非既成结果';
|
|
||||||
|
/** 由弱到强:聚合一组数字时取最弱者,保证角注不夸大。 */
|
||||||
|
export const METRIC_BASES = ['target', 'team-history', 'verified'] as const;
|
||||||
|
|
||||||
|
export type MetricBasis = (typeof METRIC_BASES)[number];
|
||||||
|
|
||||||
|
const BASIS_STRENGTH: Record<MetricBasis, number> = {
|
||||||
|
target: 0,
|
||||||
|
'team-history': 1,
|
||||||
|
verified: 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 各口径对应的角注文案。 */
|
||||||
|
export const METRIC_BASIS_NOTES: Record<MetricBasis, string> = {
|
||||||
|
target: '目标口径,非既成结果',
|
||||||
|
'team-history': '团队历史项目成果,非本公司交付数据',
|
||||||
|
verified: '已有可核验的实测出处',
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 全站默认口径角注(= 最弱口径 target 的文案)。 */
|
||||||
|
export const METRICS_BASIS_NOTE = METRIC_BASIS_NOTES.target;
|
||||||
|
|
||||||
|
/** 把数据里的任意值归一为合法口径;缺失/非法一律回落到最弱的 `target`。 */
|
||||||
|
export function resolveMetricBasis(basis?: string | null): MetricBasis {
|
||||||
|
return (METRIC_BASES as readonly string[]).includes(String(basis))
|
||||||
|
? (basis as MetricBasis)
|
||||||
|
: 'target';
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 一组数字的聚合口径:取最弱的一个。 */
|
||||||
|
export function weakestBasis(bases: Array<MetricBasis | string | undefined | null>): MetricBasis {
|
||||||
|
if (bases.length === 0) return 'target';
|
||||||
|
return bases.map(resolveMetricBasis).reduce(
|
||||||
|
(weakest, cur) => (BASIS_STRENGTH[cur] < BASIS_STRENGTH[weakest] ? cur : weakest),
|
||||||
|
resolveMetricBasis(bases[0]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 虚构佐证禁词表:本站 2026-01 成立、零公开客户案例(见 early-access-notice),
|
||||||
|
* 因此任何「数字源自真实客户案例 / 经过实战验证」的措辞都是编造佐证。
|
||||||
|
* 由 metrics-basis.test.ts 在 src/app + src/components 全源码扫描强制。
|
||||||
|
*/
|
||||||
|
export const FORBIDDEN_PROOF_PHRASES = [
|
||||||
|
'源自真实客户案例',
|
||||||
|
'真实客户案例',
|
||||||
|
'经过实战验证',
|
||||||
|
'实战验证',
|
||||||
|
] as const;
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import type { MetricBasis } from './metrics-basis';
|
||||||
|
|
||||||
export interface CaseStudy {
|
export interface CaseStudy {
|
||||||
client: string;
|
client: string;
|
||||||
industry: string;
|
industry: string;
|
||||||
@@ -10,6 +12,7 @@ export interface DataProof {
|
|||||||
metric: string;
|
metric: string;
|
||||||
value: string;
|
value: string;
|
||||||
description: string;
|
description: string;
|
||||||
|
basis?: MetricBasis;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Certification {
|
export interface Certification {
|
||||||
@@ -78,7 +81,7 @@ export interface Product {
|
|||||||
techStack?: TechStackCategory[];
|
techStack?: TechStackCategory[];
|
||||||
faqs?: FAQItem[];
|
faqs?: FAQItem[];
|
||||||
scenario?: string;
|
scenario?: string;
|
||||||
metrics?: { value: string; label: string }[];
|
metrics?: { value: string; label: string; basis?: MetricBasis }[];
|
||||||
bundle: 'enterprise' | 'standalone';
|
bundle: 'enterprise' | 'standalone';
|
||||||
capabilities?: string[];
|
capabilities?: string[];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ export interface FAQItem {
|
|||||||
export interface ServiceMetric {
|
export interface ServiceMetric {
|
||||||
value: string;
|
value: string;
|
||||||
label: string;
|
label: string;
|
||||||
|
basis?: import('./metrics-basis').MetricBasis;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Service {
|
export interface Service {
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export interface Solution {
|
|||||||
dataProofs?: import('./products').DataProof[];
|
dataProofs?: import('./products').DataProof[];
|
||||||
certifications?: import('./products').Certification[];
|
certifications?: import('./products').Certification[];
|
||||||
painPoints?: string[];
|
painPoints?: string[];
|
||||||
outcomes?: { value: string; label: string }[];
|
outcomes?: { value: string; label: string; basis?: import('./metrics-basis').MetricBasis }[];
|
||||||
recommendedProducts?: string[];
|
recommendedProducts?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user