chore: initialize deployable website and CMS
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
import { Link } from 'react-router-dom'
|
||||
import { PRIMARY_NAV } from '@/data/nav'
|
||||
|
||||
export default function Footer() {
|
||||
return (
|
||||
<footer className="site-footer">
|
||||
<div className="container footer-grid">
|
||||
<div>
|
||||
<div className="footer-brand">成都阿美巴企业管理咨询有限公司</div>
|
||||
<p className="footer-desc">
|
||||
以“企业进化”为核心主张,以四大价值坐标与 A/M/B 状态坐标为观察系,陪跑企业体系化增长落地。
|
||||
<br />
|
||||
<span className="footer-note">一期为前端骨架与占位内容,不虚构案例与数据。</span>
|
||||
</p>
|
||||
<p className="footer-meta">V1.1 FINAL 冻结基线 · 导航6项冻结 · CMS预留</p>
|
||||
</div>
|
||||
<div>
|
||||
<h4>一级导航(冻结6项)</h4>
|
||||
<ul>
|
||||
{PRIMARY_NAV.map(n => (
|
||||
<li key={n.key}>
|
||||
<Link to={n.path}>{n.label}</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h4>常驻行动入口</h4>
|
||||
<ul>
|
||||
<li>
|
||||
<Link to="/self-check">看看你的企业(轻量对照)</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link to="/contact">咨询获取方案</Link>
|
||||
</li>
|
||||
</ul>
|
||||
<h4 style={{ marginTop: 16 }}>部署</h4>
|
||||
<p className="footer-desc">复用现有服务器与域名 · 旧站完整备份可回滚</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="container footer-bottom">
|
||||
<p>© 2026 成都阿美巴企业管理咨询有限公司 | aiameiba.com · ameiba.cn</p>
|
||||
<p className="footer-links">
|
||||
<Link to="/contact">联系我们</Link> · <Link to="/self-check">看看你的企业</Link>
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import { Link } from 'react-router-dom'
|
||||
|
||||
interface Props {
|
||||
title?: string
|
||||
desc?: string
|
||||
primaryLabel?: string
|
||||
primaryTo?: string
|
||||
secondaryLabel?: string
|
||||
secondaryTo?: string
|
||||
}
|
||||
|
||||
export default function GlobalCTA({
|
||||
title = '下一步如何行动?',
|
||||
desc = '任意页面均可直接发起咨询,不强制浏览完整链路。一期提供轻量对照与深度陪跑两条路径。',
|
||||
primaryLabel = '咨询获取方案',
|
||||
primaryTo = '/contact',
|
||||
secondaryLabel = '看看你的企业',
|
||||
secondaryTo = '/self-check',
|
||||
}: Props) {
|
||||
return (
|
||||
<section className="cta-band">
|
||||
<div className="container">
|
||||
<div>
|
||||
<strong style={{ fontSize: 16 }}>{title}</strong>
|
||||
<p>{desc}</p>
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: 10, flexWrap: 'wrap' }}>
|
||||
<Link to={primaryTo} className="btn" style={{ background: '#fff', color: '#163E70' }}>
|
||||
{primaryLabel} →
|
||||
</Link>
|
||||
<Link to={secondaryTo} className="btn" style={{ background: 'transparent', color: '#fff', borderColor: 'rgba(255,255,255,0.5)' }}>
|
||||
{secondaryLabel}
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { Link } from 'react-router-dom'
|
||||
|
||||
export default function MobileStickyCTA() {
|
||||
return (
|
||||
<div className="mobile-sticky-cta" aria-label="移动端常驻CTA">
|
||||
<Link to="/self-check" className="btn btn-ghost">
|
||||
看看你的企业
|
||||
</Link>
|
||||
<Link to="/contact" className="btn btn-primary">
|
||||
咨询获取方案
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import { useState } from 'react'
|
||||
import { Link, NavLink, useLocation } from 'react-router-dom'
|
||||
import { PRIMARY_NAV, GLOBAL_RESIDENT } from '@/data/nav'
|
||||
import './layout.css'
|
||||
|
||||
export default function Navbar() {
|
||||
const [open, setOpen] = useState(false)
|
||||
const location = useLocation()
|
||||
|
||||
return (
|
||||
<header className="site-header">
|
||||
<div className="container nav-inner">
|
||||
<Link to="/" className="brand" onClick={() => setOpen(false)}>
|
||||
<span className="brand-mark">阿</span>
|
||||
<span className="brand-text">
|
||||
<strong>阿美巴咨询</strong>
|
||||
<small>企业进化陪跑</small>
|
||||
</span>
|
||||
</Link>
|
||||
|
||||
<nav className={`nav-menu ${open ? 'open' : ''}`} aria-label="主导航(冻结6项)">
|
||||
{PRIMARY_NAV.map(item => (
|
||||
<NavLink
|
||||
key={item.key}
|
||||
to={item.path}
|
||||
className={({ isActive }) => (isActive ? 'nav-link active' : 'nav-link')}
|
||||
onClick={() => setOpen(false)}
|
||||
>
|
||||
{item.label}
|
||||
</NavLink>
|
||||
))}
|
||||
</nav>
|
||||
|
||||
<div className={`nav-resident ${open ? 'open' : ''}`}>
|
||||
{GLOBAL_RESIDENT.map(item => (
|
||||
<Link
|
||||
key={item.key}
|
||||
to={item.path}
|
||||
className={item.key === 'contact-cta' ? 'btn btn-primary btn-sm' : 'btn btn-ghost btn-sm'}
|
||||
onClick={() => setOpen(false)}
|
||||
>
|
||||
{item.label}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<button
|
||||
className="hamburger"
|
||||
aria-label="切换导航"
|
||||
aria-expanded={open}
|
||||
onClick={() => setOpen(!open)}
|
||||
>
|
||||
<span />
|
||||
<span />
|
||||
<span />
|
||||
</button>
|
||||
</div>
|
||||
{/* 当前路径提示,便于验收无死链 */}
|
||||
<div className="nav-current-path" aria-hidden>
|
||||
<div className="container">
|
||||
<span>当前:{location.pathname}</span>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Outlet } from 'react-router-dom'
|
||||
import Navbar from './Navbar'
|
||||
import Footer from './Footer'
|
||||
import MobileStickyCTA from './MobileStickyCTA'
|
||||
|
||||
export default function Layout() {
|
||||
return (
|
||||
<>
|
||||
<Navbar />
|
||||
<main style={{ minHeight: '60vh' }}>
|
||||
<Outlet />
|
||||
</main>
|
||||
<Footer />
|
||||
<MobileStickyCTA />
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
.site-header {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
background: #fff;
|
||||
border-bottom: 1px solid #e2e8f0;
|
||||
}
|
||||
.nav-inner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 64px;
|
||||
gap: 16px;
|
||||
}
|
||||
.brand { display: flex; align-items: center; gap: 10px; color: var(--primary); }
|
||||
.brand-mark {
|
||||
width: 36px; height: 36px;
|
||||
background: var(--primary);
|
||||
color: #fff;
|
||||
border-radius: 8px;
|
||||
display: grid; place-items: center;
|
||||
font-weight: 900;
|
||||
}
|
||||
.brand-text strong { display: block; font-size: 15px; line-height: 1; }
|
||||
.brand-text small { font-size: 11px; color: var(--text-muted); letter-spacing: 0.06em; }
|
||||
|
||||
.nav-menu { display: flex; gap: 4px; align-items: center; }
|
||||
.nav-link {
|
||||
padding: 8px 12px;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
.nav-link.active, .nav-link:hover { background: #f1f5f9; color: var(--primary); }
|
||||
|
||||
.nav-resident { display: flex; gap: 10px; align-items: center; }
|
||||
.btn-sm { padding: 8px 14px; font-size: 13px; }
|
||||
|
||||
.hamburger {
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding: 6px;
|
||||
}
|
||||
.hamburger span { width: 22px; height: 2px; background: var(--primary); border-radius: 2px; display: block; }
|
||||
|
||||
.nav-current-path {
|
||||
background: #f8fafc;
|
||||
border-top: 1px solid #f1f5f9;
|
||||
font-size: 11px;
|
||||
color: #94a3b8;
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.site-footer {
|
||||
background: #0f172a;
|
||||
color: #cbd5e1;
|
||||
padding: 48px 0 24px;
|
||||
margin-top: 0;
|
||||
}
|
||||
.footer-grid { display: grid; grid-template-columns: 1.6fr 0.8fr 0.8fr; gap: 32px; }
|
||||
.footer-brand { color: #fff; font-weight: 800; margin-bottom: 10px; }
|
||||
.footer-desc { font-size: 13px; line-height: 1.7; color: #94a3b8; }
|
||||
.footer-note { color: #f59e0b; }
|
||||
.footer-meta { font-size: 11px; color: #64748b; margin-top: 10px; }
|
||||
.site-footer h4 { color: #fff; font-size: 13px; margin-bottom: 10px; }
|
||||
.site-footer ul { list-style: none; }
|
||||
.site-footer ul a { color: #94a3b8; font-size: 13px; }
|
||||
.site-footer ul a:hover { color: #fff; }
|
||||
.footer-bottom {
|
||||
margin-top: 32px;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid #1e293b;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
font-size: 12px;
|
||||
color: #64748b;
|
||||
}
|
||||
.footer-bottom a { color: #94a3b8; }
|
||||
|
||||
@media (max-width: 960px) {
|
||||
.nav-menu, .nav-resident {
|
||||
display: none;
|
||||
}
|
||||
.nav-menu.open, .nav-resident.open {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
left: 0; right: 0;
|
||||
background: #fff;
|
||||
border-bottom: 1px solid #e2e8f0;
|
||||
padding: 12px 16px;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
.nav-menu.open { top: 64px; }
|
||||
.nav-resident.open { top: 260px; }
|
||||
.nav-link { padding: 12px; }
|
||||
.hamburger { display: flex; }
|
||||
.footer-grid { grid-template-columns: 1fr; }
|
||||
}
|
||||
@@ -0,0 +1,263 @@
|
||||
import type { Service, Case, Insight, FourValue, AmbStatusMeta, MethodCapability, Expert } from '@/models/types'
|
||||
import { PLACEHOLDER, PLACEHOLDER_DETAIL } from '@/models/types'
|
||||
|
||||
// 四大价值 —— 观察坐标,非售卖产品
|
||||
export const FOUR_VALUES: FourValue[] = [
|
||||
{
|
||||
id: 'fv-1',
|
||||
slug: 'strategic-value',
|
||||
key: 'value1',
|
||||
name: '战略价值坐标',
|
||||
title: '战略价值坐标',
|
||||
summary: '观察企业战略清晰度与方向一致性',
|
||||
description: PLACEHOLDER_DETAIL,
|
||||
observationDimensions: ['战略清晰度', '目标一致性', '资源聚焦度', PLACEHOLDER],
|
||||
status: 'published',
|
||||
sortWeight: 100,
|
||||
createdAt: '2026-09-01T00:00:00Z',
|
||||
updatedAt: '2026-09-01T00:00:00Z',
|
||||
},
|
||||
{
|
||||
id: 'fv-2',
|
||||
slug: 'organizational-value',
|
||||
key: 'value2',
|
||||
name: '组织价值坐标',
|
||||
title: '组织价值坐标',
|
||||
summary: '观察组织活力与协同效率',
|
||||
description: PLACEHOLDER_DETAIL,
|
||||
observationDimensions: ['组织适配度', '协同效率', '人才密度', PLACEHOLDER],
|
||||
status: 'published',
|
||||
sortWeight: 90,
|
||||
createdAt: '2026-09-01T00:00:00Z',
|
||||
updatedAt: '2026-09-01T00:00:00Z',
|
||||
},
|
||||
{
|
||||
id: 'fv-3',
|
||||
slug: 'operational-value',
|
||||
key: 'value3',
|
||||
name: '经营价值坐标',
|
||||
title: '经营价值坐标',
|
||||
summary: '观察经营机制与执行落地',
|
||||
description: PLACEHOLDER_DETAIL,
|
||||
observationDimensions: ['机制完整度', '执行闭环', '数据透明', PLACEHOLDER],
|
||||
status: 'published',
|
||||
sortWeight: 80,
|
||||
createdAt: '2026-09-01T00:00:00Z',
|
||||
updatedAt: '2026-09-01T00:00:00Z',
|
||||
},
|
||||
{
|
||||
id: 'fv-4',
|
||||
slug: 'human-value',
|
||||
key: 'value4',
|
||||
name: '人的价值坐标',
|
||||
title: '人的价值坐标',
|
||||
summary: '观察人的成长与经营意识',
|
||||
description: PLACEHOLDER_DETAIL,
|
||||
observationDimensions: ['经营意识', '成长机制', '激励相容', PLACEHOLDER],
|
||||
status: 'published',
|
||||
sortWeight: 70,
|
||||
createdAt: '2026-09-01T00:00:00Z',
|
||||
updatedAt: '2026-09-01T00:00:00Z',
|
||||
},
|
||||
]
|
||||
|
||||
export const AMB_STATUS_META: Record<string, AmbStatusMeta> = {
|
||||
A: {
|
||||
status: 'A',
|
||||
label: 'A 状态|自进化',
|
||||
description: '【占位】组织具备自驱动进化能力,能持续迭代。',
|
||||
traits: ['目标自驱', '机制自洽', '持续迭代', PLACEHOLDER],
|
||||
},
|
||||
M: {
|
||||
status: 'M',
|
||||
label: 'M 状态|待突破',
|
||||
description: '【占位】已建立基础框架,但存在关键卡点需突破。',
|
||||
traits: ['框架已建', '执行有落差', '需关键突破', PLACEHOLDER],
|
||||
},
|
||||
B: {
|
||||
status: 'B',
|
||||
label: 'B 状态|待构建',
|
||||
description: '【占位】基础待构建,需从原点开始系统化搭建。',
|
||||
traits: ['依赖人治', '机制缺失', '需系统构建', PLACEHOLDER],
|
||||
},
|
||||
}
|
||||
|
||||
// 产品服务 —— 禁止电商逻辑,重心是“下一步如何行动”
|
||||
export const SERVICES: Service[] = [
|
||||
{
|
||||
id: 'svc-1',
|
||||
slug: 'diagnosis-workshop',
|
||||
title: '企业进化诊断工作坊',
|
||||
summary: '轻量诊断,定位企业在四大价值与 A/M/B 的当前坐标',
|
||||
category: 'diagnosis',
|
||||
serviceType: 'standard',
|
||||
price: 9800,
|
||||
priceUnit: '/场',
|
||||
priceNote: '标准产品价格为占位,待业务确认',
|
||||
ctaText: '获取方案',
|
||||
ctaLink: '/contact',
|
||||
highlights: ['1天闭门诊断', '四大价值初评', 'A/M/B 定位建议', PLACEHOLDER],
|
||||
status: 'published',
|
||||
sortWeight: 100,
|
||||
createdAt: '2026-09-01T00:00:00Z',
|
||||
updatedAt: '2026-09-01T00:00:00Z',
|
||||
},
|
||||
{
|
||||
id: 'svc-2',
|
||||
slug: 'growth-accompaniment',
|
||||
title: '体系化增长陪跑(季度)',
|
||||
summary: '围绕关键价值维度,陪跑落地经营机制',
|
||||
category: 'accompaniment',
|
||||
serviceType: 'custom',
|
||||
ctaText: '咨询/获取方案',
|
||||
ctaLink: '/contact',
|
||||
highlights: ['机制共创', '例会陪跑', '数据复盘', PLACEHOLDER],
|
||||
status: 'published',
|
||||
sortWeight: 90,
|
||||
createdAt: '2026-09-01T00:00:00Z',
|
||||
updatedAt: '2026-09-01T00:00:00Z',
|
||||
},
|
||||
{
|
||||
id: 'svc-3',
|
||||
slug: 'system-build-annual',
|
||||
title: '年度企业进化系统搭建',
|
||||
summary: '全维度系统化构建,年度陪跑迭代',
|
||||
category: 'system_build',
|
||||
serviceType: 'custom',
|
||||
ctaText: '咨询/获取方案',
|
||||
ctaLink: '/contact',
|
||||
highlights: ['全价值覆盖', '组织+机制+人', '年度迭代', PLACEHOLDER],
|
||||
status: 'published',
|
||||
sortWeight: 80,
|
||||
createdAt: '2026-09-01T00:00:00Z',
|
||||
updatedAt: '2026-09-01T00:00:00Z',
|
||||
},
|
||||
]
|
||||
|
||||
export const CASES: Case[] = [
|
||||
{
|
||||
id: 'case-1',
|
||||
slug: 'manufacturing-evolution',
|
||||
title: '【占位】某制造型企业|从人治到机制驱动的进化',
|
||||
summary: '行业与规模信息已脱敏,聚焦进化路径方法论。',
|
||||
industry: PLACEHOLDER,
|
||||
status: 'published',
|
||||
sortWeight: 100,
|
||||
createdAt: '2026-09-01T00:00:00Z',
|
||||
updatedAt: '2026-09-01T00:00:00Z',
|
||||
narrative: {
|
||||
originalState: PLACEHOLDER_DETAIL,
|
||||
goal: PLACEHOLDER_DETAIL,
|
||||
gap: PLACEHOLDER_DETAIL,
|
||||
rootCause: PLACEHOLDER_DETAIL,
|
||||
amebaIntervention: PLACEHOLDER_DETAIL,
|
||||
evolutionPath: PLACEHOLDER_DETAIL,
|
||||
actions: PLACEHOLDER_DETAIL,
|
||||
phasedResult: PLACEHOLDER_DETAIL,
|
||||
continuousIteration: PLACEHOLDER_DETAIL,
|
||||
},
|
||||
results: [PLACEHOLDER, PLACEHOLDER],
|
||||
},
|
||||
{
|
||||
id: 'case-2',
|
||||
slug: 'service-growth-breakthrough',
|
||||
title: '【占位】某服务型企业|组织协同效率突破',
|
||||
summary: '聚焦组织价值维度 A/M/B 状态迁移。',
|
||||
industry: PLACEHOLDER,
|
||||
status: 'published',
|
||||
sortWeight: 90,
|
||||
createdAt: '2026-09-01T00:00:00Z',
|
||||
updatedAt: '2026-09-01T00:00:00Z',
|
||||
narrative: {
|
||||
originalState: PLACEHOLDER_DETAIL,
|
||||
goal: PLACEHOLDER_DETAIL,
|
||||
gap: PLACEHOLDER_DETAIL,
|
||||
rootCause: PLACEHOLDER_DETAIL,
|
||||
amebaIntervention: PLACEHOLDER_DETAIL,
|
||||
evolutionPath: PLACEHOLDER_DETAIL,
|
||||
actions: PLACEHOLDER_DETAIL,
|
||||
phasedResult: PLACEHOLDER_DETAIL,
|
||||
continuousIteration: PLACEHOLDER_DETAIL,
|
||||
},
|
||||
results: [PLACEHOLDER],
|
||||
},
|
||||
]
|
||||
|
||||
export const INSIGHTS: Insight[] = [
|
||||
{
|
||||
id: 'ins-1',
|
||||
slug: 'evolution-as-coordinate',
|
||||
title: '企业进化不是口号:如何用坐标系观察企业',
|
||||
summary: '四大价值与 A/M/B 的观察视角',
|
||||
category: 'evolution',
|
||||
author: PLACEHOLDER,
|
||||
readingMinutes: 6,
|
||||
status: 'published',
|
||||
sortWeight: 100,
|
||||
createdAt: '2026-09-01T00:00:00Z',
|
||||
updatedAt: '2026-09-01T00:00:00Z',
|
||||
contentBlocks: [
|
||||
{ id: 'b1', type: 'paragraph', text: PLACEHOLDER_DETAIL },
|
||||
{ id: 'b2', type: 'heading', text: '观察,而非评判' },
|
||||
{ id: 'b3', type: 'paragraph', text: PLACEHOLDER_DETAIL },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'ins-2',
|
||||
slug: 'amb-not-size',
|
||||
title: 'A/M/B 不绑定规模:同一企业不同维度可处不同状态',
|
||||
summary: '纠偏“规模决定状态”的常见误解',
|
||||
category: 'amb',
|
||||
author: PLACEHOLDER,
|
||||
readingMinutes: 5,
|
||||
status: 'published',
|
||||
sortWeight: 90,
|
||||
createdAt: '2026-09-01T00:00:00Z',
|
||||
updatedAt: '2026-09-01T00:00:00Z',
|
||||
contentBlocks: [
|
||||
{ id: 'b1', type: 'paragraph', text: PLACEHOLDER_DETAIL },
|
||||
{ id: 'b2', type: 'list', items: [PLACEHOLDER, PLACEHOLDER, PLACEHOLDER] },
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
export const METHODS: MethodCapability[] = [
|
||||
{
|
||||
id: 'm-1',
|
||||
slug: 'diagnosis-method',
|
||||
title: '企业进化诊断方法',
|
||||
summary: '以四大价值为坐标,以 A/M/B 为状态刻度',
|
||||
group: 'method',
|
||||
steps: ['访谈与数据采集', '价值维度定位', 'A/M/B 状态映射', '差距与根因分析', PLACEHOLDER],
|
||||
status: 'published',
|
||||
sortWeight: 100,
|
||||
createdAt: '2026-09-01T00:00:00Z',
|
||||
updatedAt: '2026-09-01T00:00:00Z',
|
||||
},
|
||||
{
|
||||
id: 'm-2',
|
||||
slug: 'accompaniment-capability',
|
||||
title: '陪跑落地能力',
|
||||
summary: '机制共创 + 例会陪跑 + 复盘迭代',
|
||||
group: 'capability',
|
||||
steps: ['机制共创', '试点落地', '例会陪跑', '数据复盘', '迭代推广'],
|
||||
status: 'published',
|
||||
sortWeight: 90,
|
||||
createdAt: '2026-09-01T00:00:00Z',
|
||||
updatedAt: '2026-09-01T00:00:00Z',
|
||||
},
|
||||
]
|
||||
|
||||
export const EXPERTS: Expert[] = [
|
||||
{
|
||||
id: 'exp-1',
|
||||
slug: 'expert-placeholder-1',
|
||||
title: PLACEHOLDER,
|
||||
summary: '专家网络仅作为辅助信任证据,非核心支柱',
|
||||
status: 'published',
|
||||
sortWeight: 10,
|
||||
createdAt: '2026-09-01T00:00:00Z',
|
||||
updatedAt: '2026-09-01T00:00:00Z',
|
||||
isCoreTrustPillar: false,
|
||||
},
|
||||
]
|
||||
@@ -0,0 +1,30 @@
|
||||
import type { NavItem } from '@/models/types'
|
||||
|
||||
// 一级导航严格固定 6 项,不增不减(V1.1 FINAL 冻结)
|
||||
// 首页通过 logo/根路径访问,不占用一级导航计数
|
||||
export const PRIMARY_NAV: NavItem[] = [
|
||||
{ key: 'ameba', label: '阿美巴', path: '/ameba', isPrimary: true },
|
||||
{ key: 'evolution', label: '企业进化', path: '/evolution', isPrimary: true },
|
||||
{ key: 'methods', label: '方法与能力', path: '/methods', isPrimary: true },
|
||||
{ key: 'cases', label: '实践案例', path: '/cases', isPrimary: true },
|
||||
{ key: 'services', label: '产品服务', path: '/services', isPrimary: true },
|
||||
{ key: 'insights', label: '认知观察', path: '/insights', isPrimary: true },
|
||||
]
|
||||
|
||||
// 全局常驻入口:不占用主导航
|
||||
export const GLOBAL_RESIDENT: NavItem[] = [
|
||||
{ key: 'self-check', label: '看看你的企业', path: '/self-check', isPrimary: false, isGlobalResident: true },
|
||||
{ key: 'contact-cta', label: '咨询获取方案', path: '/contact', isPrimary: false, isGlobalResident: true },
|
||||
]
|
||||
|
||||
export const ALL_NAV: NavItem[] = [...PRIMARY_NAV, ...GLOBAL_RESIDENT]
|
||||
|
||||
// 二级页面清单(显式声明,禁止私自合并/拆分)
|
||||
export const SECONDARY_ROUTES = [
|
||||
{ path: '/evolution/values/:key', label: '四大价值详情' },
|
||||
{ path: '/evolution/amb/:status', label: 'A/M/B 状态详情' },
|
||||
{ path: '/cases/:slug', label: '案例详情' },
|
||||
{ path: '/services/:slug', label: '服务详情' },
|
||||
{ path: '/insights/:slug', label: '洞察详情' },
|
||||
{ path: '/methods/:slug', label: '方法详情' },
|
||||
] as const
|
||||
@@ -0,0 +1,11 @@
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom/client'
|
||||
import { RouterProvider } from 'react-router-dom'
|
||||
import { router } from './router'
|
||||
import './styles/global.css'
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||
<React.StrictMode>
|
||||
<RouterProvider router={router} />
|
||||
</React.StrictMode>,
|
||||
)
|
||||
@@ -0,0 +1,166 @@
|
||||
/**
|
||||
* 阿美巴传统官网1.0 数据模型定义
|
||||
* 严格对齐 V1.1 FINAL 冻结基线
|
||||
* 业务数据与页面布局完全解耦,为 CMS 预留接管点位
|
||||
* 所有未确认业务字段使用 placeholder 占位,不虚构
|
||||
*/
|
||||
|
||||
// ========== 通用基类 ==========
|
||||
export type PublishStatus = 'draft' | 'published' | 'archived' // 上下架状态
|
||||
export type ContentSource = 'cms' | 'static' // 未来 CMS 接管标识
|
||||
|
||||
export interface BaseModel {
|
||||
id: string
|
||||
slug: string // 唯一标识,用于路由
|
||||
title: string
|
||||
summary?: string // 摘要,用于列表页
|
||||
coverImage?: string // 封面图
|
||||
status: PublishStatus // 上下架
|
||||
sortWeight: number // 排序权重,数字越大越靠前
|
||||
isTop?: boolean // 置顶
|
||||
tags?: string[]
|
||||
createdAt: string // ISO8601
|
||||
updatedAt: string
|
||||
publishedAt?: string
|
||||
seoTitle?: string
|
||||
seoDescription?: string
|
||||
seoKeywords?: string
|
||||
cmsManaged?: boolean // 标记是否已由 CMS 接管(预留)
|
||||
cmsId?: string // CMS 侧 ID 预留
|
||||
extra?: Record<string, unknown> // 扩展预留
|
||||
}
|
||||
|
||||
// ========== 四大价值坐标 ==========
|
||||
// 定位为“观察坐标”,不能包装成售卖产品
|
||||
export type FourValuesKey = 'value1' | 'value2' | 'value3' | 'value4'
|
||||
|
||||
export interface FourValue extends BaseModel {
|
||||
key: FourValuesKey
|
||||
name: string // 如:战略价值 / 组织价值 / 经营价值 / 人的价值 (占位命名,待业务确认)
|
||||
description: string
|
||||
observationDimensions: string[] // 观察维度
|
||||
icon?: string
|
||||
}
|
||||
|
||||
// ========== A/M/B 状态坐标 ==========
|
||||
// 不绑定企业规模;支持同一企业不同价值维度处于不同状态
|
||||
export type AmbStatus = 'A' | 'M' | 'B'
|
||||
export type AmbStatusMeta = {
|
||||
status: AmbStatus
|
||||
label: string
|
||||
description: string
|
||||
traits: string[] // 特征
|
||||
risks?: string[] // 风险提示
|
||||
suggestions?: string[] // 进化建议(轻量)
|
||||
}
|
||||
|
||||
export interface AmbDimensionMapping {
|
||||
valueKey: FourValuesKey // 四大价值维度
|
||||
valueName: string
|
||||
status: AmbStatus // 该维度所处状态
|
||||
note?: string // 说明,支持跨维度不同状态
|
||||
}
|
||||
|
||||
export interface CompanyAmbProfile {
|
||||
companyId?: string
|
||||
companyNamePlaceholder: string // 【占位|待业务确认】
|
||||
mappings: AmbDimensionMapping[]
|
||||
overallNote?: string
|
||||
}
|
||||
|
||||
// ========== 产品服务 ==========
|
||||
export type ServiceType = 'standard' | 'custom' // 标准产品 / 深度定制
|
||||
export type ServiceCategory = 'diagnosis' | 'accompaniment' | 'system_build' | 'training' | 'other'
|
||||
|
||||
export interface Service extends BaseModel {
|
||||
category: ServiceCategory
|
||||
serviceType: ServiceType
|
||||
price?: number // 标准产品预留价格字段(单位:元);定制服务不展示价格
|
||||
priceUnit?: string // 如:/次 /年 /期
|
||||
priceNote?: string // 价格说明
|
||||
ctaText: string // 行动按钮文案:标准产品“立即咨询/获取方案”;定制“咨询/获取方案”
|
||||
ctaLink: string // 默认 /contact
|
||||
highlights?: string[] // 服务亮点
|
||||
deliverables?: string[] // 交付物
|
||||
suitableFor?: string[] // 适合对象
|
||||
process?: string[] // 服务流程
|
||||
duration?: string // 周期
|
||||
// 禁止电商逻辑:无购物车、无库存、无SKU
|
||||
}
|
||||
|
||||
// ========== 实践案例 ==========
|
||||
export interface CaseNarrativeSlots {
|
||||
originalState: string // 企业原状
|
||||
goal: string // 企业目标
|
||||
gap: string // 现存差距
|
||||
rootCause: string // 根因
|
||||
amebaIntervention: string // 阿美巴介入
|
||||
evolutionPath: string // 进化路径
|
||||
actions: string // 落地动作
|
||||
phasedResult: string // 阶段结果
|
||||
continuousIteration: string // 持续迭代
|
||||
}
|
||||
|
||||
export interface Case extends BaseModel {
|
||||
industry?: string // 行业(占位)
|
||||
companySizeNote?: string // 规模说明(不与 A/M/B 绑定)
|
||||
fourValuesInvolved?: FourValuesKey[] // 关联的价值维度
|
||||
ambBefore?: AmbDimensionMapping[] // 介入前 A/M/B 分布
|
||||
ambAfter?: AmbDimensionMapping[] // 介入后 A/M/B 分布
|
||||
narrative: CaseNarrativeSlots // 固定叙事插槽 9 段
|
||||
results?: string[] // 量化结果(占位,无虚构数据)
|
||||
testimonial?: string // 客户证言(占位)
|
||||
serviceIds?: string[] // 关联服务
|
||||
}
|
||||
|
||||
// ========== 认知观察 ==========
|
||||
export type InsightCategory = 'evolution' | 'value' | 'amb' | 'method' | 'trend' | 'other'
|
||||
|
||||
export interface Insight extends BaseModel {
|
||||
category: InsightCategory
|
||||
author?: string // 作者(占位)
|
||||
readingMinutes?: number
|
||||
contentBlocks: ContentBlock[] // 解耦的内容块,便于 CMS 接管
|
||||
}
|
||||
|
||||
export type ContentBlockType = 'paragraph' | 'heading' | 'list' | 'quote' | 'image' | 'placeholder'
|
||||
|
||||
export interface ContentBlock {
|
||||
id: string
|
||||
type: ContentBlockType
|
||||
text?: string
|
||||
items?: string[]
|
||||
imageUrl?: string
|
||||
caption?: string
|
||||
placeholderNote?: string // 【占位|待业务确认】
|
||||
}
|
||||
|
||||
// ========== 方法与能力 ==========
|
||||
export interface MethodCapability extends BaseModel {
|
||||
group: 'method' | 'capability'
|
||||
steps?: string[]
|
||||
tools?: string[]
|
||||
outcome?: string
|
||||
}
|
||||
|
||||
// ========== 导航与路由元数据 ==========
|
||||
export interface NavItem {
|
||||
key: string
|
||||
label: string
|
||||
path: string
|
||||
isPrimary: boolean // 是否一级导航(冻结6项)
|
||||
isGlobalResident?: boolean // 是否全局常驻入口(不占用主导航)
|
||||
children?: NavItem[]
|
||||
cmsManaged?: boolean
|
||||
}
|
||||
|
||||
// ========== 占位内容标记 ==========
|
||||
export const PLACEHOLDER = '【占位|待业务确认】'
|
||||
export const PLACEHOLDER_DETAIL = '【占位|待业务确认】此模块内容需由知识母盘/业务方确认后灌入,一期仅保留结构与占位,不虚构数据。'
|
||||
|
||||
// ========== 专家网络(辅助信任证据,非核心) ==========
|
||||
export interface Expert extends BaseModel {
|
||||
role?: string
|
||||
expertise?: string[]
|
||||
isCoreTrustPillar?: false // 强制标记:不得作为首页核心信任支柱
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
import { Link } from 'react-router-dom'
|
||||
import GlobalCTA from '@/components/Layout/GlobalCTA'
|
||||
import { useCMS, getCMSAmeba } from '@/services/cms'
|
||||
|
||||
export default function Ameba() {
|
||||
const { data } = useCMS(getCMSAmeba, null)
|
||||
const content = data || {}
|
||||
return (
|
||||
<div>
|
||||
<div className="page-header">
|
||||
<div className="container">
|
||||
<div className="breadcrumb">
|
||||
<Link to="/" style={{ color: 'rgba(255,255,255,0.8)' }}>
|
||||
首页
|
||||
</Link>{' '}
|
||||
/ 阿美巴
|
||||
</div>
|
||||
<h1>阿美巴</h1>
|
||||
<p>{content.brand_position || '以企业进化经营哲学为底色,坚持“利他、务实、陪跑”。不做纸上谈兵,只做可落地、可量化、有结果的实操辅导。'}</p>
|
||||
<div className="placeholder" style={{ marginTop: 16 }}>
|
||||
【占位|待业务确认】团队介绍、资质、里程碑、价值观等由知识母盘灌入。专家网络仅作辅助信任证据,不作为核心支柱。
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section className="section">
|
||||
<div className="container">
|
||||
<div className="grid-2">
|
||||
<div className="card">
|
||||
<h3>经营哲学</h3>
|
||||
<p>{content.philosophy || '阿米巴哲学本土化实践,强调人人经营、数据透明、持续改善。'}</p>
|
||||
<div className="placeholder" style={{ marginTop: 12 }}>
|
||||
【占位】哲学主张、原则、行为准则待确认。
|
||||
</div>
|
||||
</div>
|
||||
<div className="card">
|
||||
<h3>服务理念</h3>
|
||||
<p>{content.practice || '教练式陪跑:顾问入企,手把手带教,体系化交付 SOP 与工具包。'}</p>
|
||||
<div className="placeholder" style={{ marginTop: 12 }}>
|
||||
【占位】陪跑模式、交付标准待确认。
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="section section-muted">
|
||||
<div className="container">
|
||||
<h2 className="h2">我们是谁 · 占位</h2>
|
||||
<div className="card">
|
||||
<p className="desc">
|
||||
成都阿美巴企业管理咨询有限公司,专注体系化增长落地。核心团队十年以上实战经验,覆盖制造、服务、科技、商贸等领域。
|
||||
</p>
|
||||
<div className="placeholder" style={{ marginTop: 12 }}>
|
||||
{content.mission || '【占位|待业务确认】公司简介、团队、资质证书、合作客户 LOGO 墙均待业务方提供定稿素材。'}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<GlobalCTA />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
import { useParams, Link } from 'react-router-dom'
|
||||
import { CASES as FALLBACK_CASES } from '@/data/mock'
|
||||
import { useCMS, getCMSCases } from '@/services/cms'
|
||||
import GlobalCTA from '@/components/Layout/GlobalCTA'
|
||||
|
||||
const LABELS: Record<string, string> = {
|
||||
originalState: '企业原状',
|
||||
goal: '企业目标',
|
||||
gap: '现存差距',
|
||||
rootCause: '根因',
|
||||
amebaIntervention: '阿美巴介入',
|
||||
evolutionPath: '进化路径',
|
||||
actions: '落地动作',
|
||||
phasedResult: '阶段结果',
|
||||
continuousIteration: '持续迭代',
|
||||
}
|
||||
|
||||
export default function CaseDetail() {
|
||||
const { data: CASES } = useCMS(getCMSCases, FALLBACK_CASES)
|
||||
const { slug } = useParams()
|
||||
const c = CASES.find(x => x.slug === slug)
|
||||
|
||||
if (!c)
|
||||
return (
|
||||
<div className="container section">
|
||||
<div className="placeholder">未找到案例:{slug}</div>
|
||||
<Link to="/cases" className="btn btn-secondary" style={{ marginTop: 16 }}>
|
||||
返回案例列表
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="page-header">
|
||||
<div className="container">
|
||||
<div className="breadcrumb">
|
||||
<Link to="/" style={{ color: 'rgba(255,255,255,0.8)' }}>
|
||||
首页
|
||||
</Link>{' '}
|
||||
/{' '}
|
||||
<Link to="/cases" style={{ color: 'rgba(255,255,255,0.8)' }}>
|
||||
实践案例
|
||||
</Link>{' '}
|
||||
/ 详情
|
||||
</div>
|
||||
<h1>{c.title}</h1>
|
||||
<p>{c.summary}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section className="section">
|
||||
<div className="container">
|
||||
<div className="card" style={{ borderLeft: '4px solid #163E70' }}>
|
||||
<h3>案例叙事 · 9段固定插槽</h3>
|
||||
<p style={{ fontSize: 12, color: '#64748b' }}>CMS 仅替换插槽文本,不改结构。未确认字段统一占位。</p>
|
||||
</div>
|
||||
|
||||
<div style={{ marginTop: 20, display: 'grid', gap: 16 }}>
|
||||
{Object.entries(c.narrative).map(([key, val]) => (
|
||||
<div key={key} className="card">
|
||||
<div style={{ fontSize: 11, letterSpacing: '0.08em', color: '#0f6b5b', fontWeight: 800 }}>{LABELS[key]}</div>
|
||||
<p style={{ marginTop: 6, fontSize: 14, color: '#334155' }}>{val as string}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="card" style={{ marginTop: 16, background: '#f8fafc' }}>
|
||||
<h3>关联维度(预留)</h3>
|
||||
<p style={{ fontSize: 13, color: '#64748b' }}>关联价值:{c.fourValuesInvolved?.join(', ') || '【占位】待关联'}</p>
|
||||
<p style={{ fontSize: 13, color: '#64748b' }}>关联服务:{c.serviceIds?.join(', ') || '【占位】待关联'}</p>
|
||||
</div>
|
||||
|
||||
<div style={{ marginTop: 16, display: 'flex', gap: 10, flexWrap: 'wrap' }}>
|
||||
<Link to="/cases" className="btn btn-ghost">
|
||||
← 返回案例列表
|
||||
</Link>
|
||||
<Link to="/contact" className="btn btn-primary">
|
||||
咨询同类方案
|
||||
</Link>
|
||||
<Link to="/services" className="btn btn-ghost">
|
||||
查看产品服务
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<GlobalCTA />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import { Link } from 'react-router-dom'
|
||||
import { CASES as FALLBACK_CASES } from '@/data/mock'
|
||||
import { useCMS, getCMSCases } from '@/services/cms'
|
||||
import GlobalCTA from '@/components/Layout/GlobalCTA'
|
||||
|
||||
export default function Cases() {
|
||||
const { data: CASES } = useCMS(getCMSCases, FALLBACK_CASES)
|
||||
return (
|
||||
<div>
|
||||
<div className="page-header">
|
||||
<div className="container">
|
||||
<div className="breadcrumb">
|
||||
<Link to="/" style={{ color: 'rgba(255,255,255,0.8)' }}>
|
||||
首页
|
||||
</Link>{' '}
|
||||
/ 实践案例
|
||||
</div>
|
||||
<h1>实践案例</h1>
|
||||
<p>固定叙事插槽 9 段式结构,业务数据与布局解耦,未来由 CMS 接管。无虚构数据,未确认统一占位。</p>
|
||||
</div>
|
||||
</div>
|
||||
<section className="section">
|
||||
<div className="container">
|
||||
<div className="grid-2">
|
||||
{CASES.map(c => (
|
||||
<Link key={c.id} to={`/cases/${c.slug}`} className="card" style={{ display: 'block' }}>
|
||||
<div style={{ fontSize: 11, color: '#0f6b5b', fontWeight: 800, letterSpacing: '0.08em' }}>CASE · {c.status.toUpperCase()}</div>
|
||||
<h3 style={{ marginTop: 6 }}>{c.title}</h3>
|
||||
<p>{c.summary}</p>
|
||||
<div className="placeholder" style={{ marginTop: 10, fontSize: 12 }}>
|
||||
权重 {c.sortWeight} · 上下架 {c.status} · 预留排序
|
||||
</div>
|
||||
<span style={{ fontSize: 12, color: '#163E70', fontWeight: 700, marginTop: 10, display: 'inline-block' }}>查看完整叙事 →</span>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
<div className="card" style={{ marginTop: 20, background: '#fff7e6', borderColor: '#f59e0b' }}>
|
||||
<strong style={{ color: '#92400e' }}>案例叙事模板插槽(固定 9 段)</strong>
|
||||
<p style={{ fontSize: 13, color: '#78350f', marginTop: 6 }}>
|
||||
企业原状 → 企业目标 → 现存差距 → 根因 → 阿美巴介入 → 进化路径 → 落地动作 → 阶段结果 → 持续迭代
|
||||
</p>
|
||||
<p style={{ fontSize: 12, color: '#92400e', marginTop: 4 }}>CMS 接管后,编辑仅替换插槽内容,不改页面结构。</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<GlobalCTA />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
import { useState } from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
|
||||
export default function Contact() {
|
||||
const [form, setForm] = useState({ name: '', company: '', phone: '', need: '' })
|
||||
const [submitted, setSubmitted] = useState(false)
|
||||
const [error, setError] = useState('')
|
||||
const [saving, setSaving] = useState(false)
|
||||
|
||||
const submit = async (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
setError('')
|
||||
setSaving(true)
|
||||
try {
|
||||
const response = await fetch('/api/contact/leads', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(form),
|
||||
})
|
||||
const result = await response.json()
|
||||
if (!response.ok || result.code !== 0) throw new Error(result.msg || '提交失败')
|
||||
setSubmitted(true)
|
||||
setForm({ name: '', company: '', phone: '', need: '' })
|
||||
} catch (e) {
|
||||
setError(e instanceof Error ? e.message : '提交失败,请稍后再试')
|
||||
} finally {
|
||||
setSaving(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="page-header">
|
||||
<div className="container">
|
||||
<div className="breadcrumb">
|
||||
<Link to="/" style={{ color: 'rgba(255,255,255,0.8)' }}>
|
||||
首页
|
||||
</Link>{' '}
|
||||
/ 联系我们
|
||||
</div>
|
||||
<h1>联系我们</h1>
|
||||
<p>任意页面均可直达此页发起行动;不强制浏览完整链路。提交后由顾问跟进。</p>
|
||||
</div>
|
||||
</div>
|
||||
<section className="section">
|
||||
<div className="container" style={{ maxWidth: 720 }}>
|
||||
<div className="grid-2">
|
||||
<div className="card">
|
||||
<h3>咨询获取方案</h3>
|
||||
<p style={{ fontSize: 13, color: '#64748b' }}>提交后将进入顾问线索列表,请填写真实联系方式。</p>
|
||||
<form onSubmit={submit} style={{ marginTop: 16, display: 'grid', gap: 12 }}>
|
||||
<label style={{ fontSize: 13 }}>
|
||||
姓名
|
||||
<input
|
||||
required
|
||||
value={form.name}
|
||||
onChange={e => setForm({ ...form, name: e.target.value })}
|
||||
placeholder="请输入姓名"
|
||||
style={{ width: '100%', marginTop: 6, padding: '10px 12px', border: '1px solid #e2e8f0', borderRadius: 8 }}
|
||||
/>
|
||||
</label>
|
||||
<label style={{ fontSize: 13 }}>
|
||||
企业名称
|
||||
<input
|
||||
value={form.company}
|
||||
onChange={e => setForm({ ...form, company: e.target.value })}
|
||||
placeholder="【占位】选填"
|
||||
style={{ width: '100%', marginTop: 6, padding: '10px 12px', border: '1px solid #e2e8f0', borderRadius: 8 }}
|
||||
/>
|
||||
</label>
|
||||
<label style={{ fontSize: 13 }}>
|
||||
联系电话
|
||||
<input
|
||||
required
|
||||
value={form.phone}
|
||||
onChange={e => setForm({ ...form, phone: e.target.value })}
|
||||
placeholder="请输入手机号"
|
||||
style={{ width: '100%', marginTop: 6, padding: '10px 12px', border: '1px solid #e2e8f0', borderRadius: 8 }}
|
||||
/>
|
||||
</label>
|
||||
<label style={{ fontSize: 13 }}>
|
||||
需求简述
|
||||
<textarea
|
||||
value={form.need}
|
||||
onChange={e => setForm({ ...form, need: e.target.value })}
|
||||
placeholder="请简述你的企业现状与期望(占位)"
|
||||
rows={3}
|
||||
style={{ width: '100%', marginTop: 6, padding: '10px 12px', border: '1px solid #e2e8f0', borderRadius: 8 }}
|
||||
/>
|
||||
</label>
|
||||
<button type="submit" className="btn btn-primary" style={{ width: '100%' }} disabled={saving}>
|
||||
{saving ? '提交中…' : '提交咨询'}
|
||||
</button>
|
||||
{submitted && <div style={{ fontSize: 13, color: '#0f6b5b', background: '#ecfdf5', padding: 10, borderRadius: 8 }}>已提交,顾问将尽快联系。</div>}
|
||||
{error && <div style={{ fontSize: 13, color: '#be123c', background: '#fff1f2', padding: 10, borderRadius: 8 }}>{error}</div>}
|
||||
<p style={{ fontSize: 11, color: '#94a3b8' }}>提交即视为同意隐私与合规说明(占位)。</p>
|
||||
</form>
|
||||
</div>
|
||||
<div style={{ display: 'grid', gap: 16, alignContent: 'start' }}>
|
||||
<div className="card">
|
||||
<h3>其他通道</h3>
|
||||
<p style={{ fontSize: 13, color: '#475569' }}>电话、邮箱、地址等待业务确认后灌入。</p>
|
||||
<div className="placeholder" style={{ marginTop: 10 }}>
|
||||
【占位|待业务确认】商务电话 / 企业邮箱 / 办公地址 / 企业微信二维码
|
||||
</div>
|
||||
</div>
|
||||
<div className="card">
|
||||
<h3>快速路径</h3>
|
||||
<p style={{ fontSize: 13, color: '#475569' }}>首页 → 看看你的企业(轻量对照)→ 联系我们 → 顾问跟进</p>
|
||||
<p style={{ fontSize: 13, color: '#475569' }}>首页 → 企业进化 → 价值/A/M/B → 联系我们(深度路径)</p>
|
||||
<Link to="/self-check" className="btn btn-ghost" style={{ width: '100%', marginTop: 10 }}>
|
||||
先看看你的企业 →
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section className="cta-band">
|
||||
<div className="container">
|
||||
<div>
|
||||
<strong>全站任意页面均可发起咨询</strong>
|
||||
<p>无需浏览完整链路,点击常驻 CTA 即可直达此页。</p>
|
||||
</div>
|
||||
<Link to="/self-check" className="btn" style={{ background: '#fff', color: '#163E70' }}>
|
||||
轻量对照 →
|
||||
</Link>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useParams, Link } from 'react-router-dom'
|
||||
|
||||
function Radar({scores}:{scores:Record<string,number>}){
|
||||
const keys=['value1','value2','value3','value4']
|
||||
const labels:{[k:string]:string}={value1:'价值创造',value2:'价值传递',value3:'价值交付',value4:'价值支持'}
|
||||
const vals = keys.map(k=> scores[k]||0)
|
||||
const max=5
|
||||
const size=200, cx=100, cy=100, r=80
|
||||
const points = vals.map((v,i)=>{
|
||||
const angle = -90 + (360/keys.length)*i
|
||||
const rad = angle*Math.PI/180
|
||||
const rr = (v/max)*r
|
||||
return `${cx + rr*Math.cos(rad)},${cy + rr*Math.sin(rad)}`
|
||||
}).join(' ')
|
||||
const grid = [0.25,0.5,0.75,1].map(s=>{
|
||||
const pts = keys.map((_,i)=>{
|
||||
const angle=-90+(360/keys.length)*i
|
||||
const rad=angle*Math.PI/180
|
||||
const rr=r*s
|
||||
return `${cx+rr*Math.cos(rad)},${cy+rr*Math.sin(rad)}`
|
||||
}).join(' ')
|
||||
return <polygon key={s} points={pts} fill="none" stroke="#e2e8f0" strokeWidth={1} />
|
||||
})
|
||||
return (
|
||||
<div style={{textAlign:'center'}}>
|
||||
<svg width={size} height={size} viewBox={`0 0 ${size} ${size}`}>
|
||||
{grid}
|
||||
{keys.map((k,i)=>{
|
||||
const angle=-90+(360/keys.length)*i
|
||||
const rad=angle*Math.PI/180
|
||||
return <line key={k} x1={cx} y1={cy} x2={cx+r*Math.cos(rad)} y2={cy+r*Math.sin(rad)} stroke="#e2e8f0" />
|
||||
})}
|
||||
<polygon points={points} fill="rgba(22,62,112,0.2)" stroke="#163E70" strokeWidth={2} />
|
||||
{vals.map((v,i)=>{
|
||||
const angle=-90+(360/keys.length)*i
|
||||
const rad=angle*Math.PI/180
|
||||
const rr=(v/max)*r
|
||||
return <circle key={i} cx={cx+rr*Math.cos(rad)} cy={cy+rr*Math.sin(rad)} r={4} fill="#163E70" />
|
||||
})}
|
||||
</svg>
|
||||
<div style={{display:'flex', justifyContent:'space-around', fontSize:11, color:'#475569'}}>
|
||||
{keys.map(k=> <span key={k}>{labels[k]}:{scores[k]}</span>)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default function Report(){
|
||||
const {token}=useParams()
|
||||
const [data,setData]=useState<any>(null)
|
||||
const [loading,setLoading]=useState(true)
|
||||
const [err,setErr]=useState('')
|
||||
const [lead,setLead]=useState({name:'',company:'',phone:''})
|
||||
const [leadDone,setLeadDone]=useState(false)
|
||||
const [showFull,setShowFull]=useState(false)
|
||||
|
||||
useEffect(()=>{
|
||||
fetch(`/api/diagnosis/reports/${token}`).then(r=>r.json()).then(j=>{
|
||||
if(j.code!==0) throw new Error(j.msg)
|
||||
setData(j.data)
|
||||
}).catch(e=> setErr(e.message)).finally(()=> setLoading(false))
|
||||
},[token])
|
||||
|
||||
if(loading) return <div className="container section">生成报告中... <5s</div>
|
||||
if(err) return <div className="container section"><div className="placeholder">{err}</div><Link to="/diagnosis" className="btn btn-primary">重新诊断</Link></div>
|
||||
if(!data) return null
|
||||
|
||||
const scores=data.scores, amb=data.amb, gap=data.gap, rc=data.root_cause, ps=data.path_suggestion
|
||||
const shareUrl=`${location.origin}/diagnosis/share/${data.share_token}`
|
||||
|
||||
const submitLead=async()=>{
|
||||
if(lead.phone && !/^1[3-9]\d{9}$/.test(lead.phone)) return alert('手机号格式错误')
|
||||
const r=await fetch(`/api/diagnosis/reports/${token}/lead`,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(lead)}).then(r=>r.json())
|
||||
if(r.code===0){ setLeadDone(true); setShowFull(true) }
|
||||
else alert(r.msg)
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="page-header">
|
||||
<div className="container">
|
||||
<h1>企业进化诊断报告</h1>
|
||||
<p>基于企业自填信息的初步分析,仅供参考,不替代人工咨询</p>
|
||||
<p style={{fontSize:12, color:'rgba(255,255,255,0.7)'}}>报告ID {data.token} · 有效期至 {new Date(data.expires_at).toLocaleDateString()}</p>
|
||||
</div>
|
||||
</div>
|
||||
<section className="section">
|
||||
<div className="container" style={{maxWidth:800}}>
|
||||
<div className="card">
|
||||
<h3>1. 进化坐标总览</h3>
|
||||
<Radar scores={scores} />
|
||||
<div style={{marginTop:12, display:'grid', gridTemplateColumns:'1fr 1fr', gap:10, fontSize:13}}>
|
||||
{Object.keys(amb).map(k=> <div key={k} style={{background:'#f8fafc', padding:8, borderRadius:6}}>{k}:{amb[k]}·{scores[k]}分</div>)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="card">
|
||||
<h3>2. 各维度现状与差距</h3>
|
||||
<table className="table"><thead><tr><th>维度</th><th>得分</th><th>状态</th><th>差距(距5分)</th></tr></thead>
|
||||
<tbody>{Object.keys(scores).map(k=> <tr key={k}><td>{k}</td><td>{scores[k]}</td><td>{amb[k]}</td><td>{gap[k]}</td></tr>)}</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div className="card">
|
||||
<h3>3. 核心根因判断</h3>
|
||||
<p style={{fontSize:14}}>{rc?.label}:{rc?.reason}</p>
|
||||
<p style={{fontSize:12,color:'#64748b'}}>规则:最低得分维度为瓶颈,非随机</p>
|
||||
</div>
|
||||
|
||||
<div className="card">
|
||||
<h3>4. 进化路径建议(分阶段)</h3>
|
||||
<p><strong>{ps?.stage}</strong> - {ps?.recommendation}(综合得分 {ps?.overall})</p>
|
||||
<p style={{fontSize:12,color:'#475569'}}>目标→现状→差距→根因→路径→推进→反馈→再进化</p>
|
||||
</div>
|
||||
|
||||
{!showFull && (
|
||||
<div className="card" style={{border:'2px solid #163E70'}}>
|
||||
<h3>5. 推荐服务(完整版需留资解锁)</h3>
|
||||
<p style={{fontSize:13, color:'#64748b'}}>基础版已展示坐标与差距,留资后解锁:推荐匹配的产品与服务 + 下一步行动建议</p>
|
||||
<div className="form-row" style={{marginTop:12}}>
|
||||
<div className="field"><label>姓名</label><input value={lead.name} onChange={e=> setLead({...lead,name:e.target.value})} /></div>
|
||||
<div className="field"><label>企业名称</label><input value={lead.company} onChange={e=> setLead({...lead,company:e.target.value})} /></div>
|
||||
</div>
|
||||
<div className="field"><label>手机号(选填,不留也可看基础版)</label><input value={lead.phone} onChange={e=> setLead({...lead,phone:e.target.value})} placeholder="11位手机号" /></div>
|
||||
<button className="btn btn-primary" onClick={submitLead}>提交解锁完整版</button>
|
||||
<button className="btn btn-ghost" style={{marginLeft:8}} onClick={()=> setShowFull(true)}>暂不留资,仅看基础版</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showFull && (
|
||||
<>
|
||||
<div className="card">
|
||||
<h3>5. 推荐匹配的产品与服务</h3>
|
||||
<p style={{fontSize:13}}>{ps?.stage==='A态入门'?'推荐:A态入门方案 · 单点突破':'M态进阶'===ps?.stage?'推荐:M态进阶方案 · 机制完善':'推荐:B态全案 · 系统化构建'}</p>
|
||||
<Link to="/services" className="btn btn-secondary">查看产品服务</Link>
|
||||
</div>
|
||||
<div className="card">
|
||||
<h3>6. 下一步行动建议</h3>
|
||||
<p style={{fontSize:13}}>预约企业进化梳理 / 获取定制方案 / 拨打400热线</p>
|
||||
<div style={{display:'flex',gap:10}}>
|
||||
<Link to="/contact" className="btn btn-primary">预约企业进化梳理</Link>
|
||||
<a href={`tel:400`} className="btn btn-ghost">拨打400热线</a>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className="card" style={{background:'#fff7e6'}}>
|
||||
<h3>7. 免责声明</h3>
|
||||
<p style={{fontSize:12,color:'#92400e'}}>本报告基于企业自填信息的初步分析,仅供参考,不替代人工咨询,不做绝对化结论与收益预测。最终方案需顾问介入。</p>
|
||||
</div>
|
||||
|
||||
<div style={{display:'flex', gap:10, flexWrap:'wrap', marginTop:16}}>
|
||||
<a href={`/api/diagnosis/reports/${token}/pdf`} className="btn btn-primary">下载PDF</a>
|
||||
<button className="btn btn-ghost" onClick={()=> navigator.clipboard.writeText(shareUrl).then(()=> alert('分享链接已复制:'+shareUrl))}>复制分享链接</button>
|
||||
<Link to="/diagnosis" className="btn btn-ghost">重新诊断</Link>
|
||||
</div>
|
||||
<div style={{fontSize:11,color:'#94a3b8', marginTop:8}}>分享链接:{shareUrl} · 有效期可配置,过期需重新诊断</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useParams } from 'react-router-dom'
|
||||
|
||||
export default function Share(){
|
||||
const {shareToken}=useParams()
|
||||
const [data,setData]=useState<any>(null)
|
||||
const [err,setErr]=useState('')
|
||||
useEffect(()=>{
|
||||
fetch(`/api/diagnosis/share/${shareToken}`).then(r=>r.json()).then(j=>{
|
||||
if(j.code!==0) throw new Error(j.msg)
|
||||
setData(j.data)
|
||||
}).catch(e=> setErr(e.message))
|
||||
},[shareToken])
|
||||
if(err) return <div className="container section"><div className="placeholder">{err}</div></div>
|
||||
if(!data) return <div className="container section">加载分享报告...</div>
|
||||
return (
|
||||
<div className="container section">
|
||||
<h2>分享的诊断报告</h2>
|
||||
<p style={{fontSize:12,color:'#64748b'}}>基于企业自填信息的初步分析,仅供参考</p>
|
||||
<div className="card"><pre style={{whiteSpace:'pre-wrap',fontSize:12}}>{JSON.stringify({scores:data.scores, amb:data.amb, gap:data.gap}, null, 2)}</pre></div>
|
||||
<a href={`/api/diagnosis/reports/${data.token}/pdf`} className="btn btn-primary">下载PDF</a>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Link, useNavigate } from 'react-router-dom'
|
||||
|
||||
type Q = { id:string, group_key:string, group_name:string, type:string, title:string, options:any[], sort_order:number }
|
||||
const STORAGE_KEY='diagnosis_progress'
|
||||
|
||||
export default function Diagnosis(){
|
||||
const [questions,setQuestions]=useState<Q[]>([])
|
||||
const [answers,setAnswers]=useState<Record<string,any>>({})
|
||||
const [idx,setIdx]=useState(0)
|
||||
const nav=useNavigate()
|
||||
const [loading,setLoading]=useState(true)
|
||||
|
||||
useEffect(()=>{
|
||||
fetch('/api/diagnosis/questions').then(r=>r.json()).then(j=>{
|
||||
const list = (j.data||[]).sort((a:Q,b:Q)=> b.sort_order - a.sort_order)
|
||||
setQuestions(list)
|
||||
// 断点续填
|
||||
const saved = localStorage.getItem(STORAGE_KEY)
|
||||
if(saved){
|
||||
try{ const p=JSON.parse(saved); if(p.answers) setAnswers(p.answers); if(typeof p.idx==='number') setIdx(p.idx)}catch{}
|
||||
}
|
||||
setLoading(false)
|
||||
})
|
||||
},[])
|
||||
useEffect(()=>{
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify({answers, idx}))
|
||||
},[answers, idx])
|
||||
|
||||
const q = questions[idx]
|
||||
const progress = questions.length? Math.round(((idx)/questions.length)*100):0
|
||||
const answeredCount = Object.keys(answers).length
|
||||
|
||||
const handleAnswer=(val:any)=>{
|
||||
const next={...answers, [q.id]:val}
|
||||
setAnswers(next)
|
||||
}
|
||||
const nextQ=()=>{
|
||||
if(idx<questions.length-1) setIdx(idx+1)
|
||||
else submit()
|
||||
}
|
||||
const prevQ=()=> setIdx(Math.max(0, idx-1))
|
||||
|
||||
const submit=async()=>{
|
||||
// 创建会话并提交
|
||||
const sess = await fetch('/api/diagnosis/sessions',{method:'POST'}).then(r=>r.json())
|
||||
const token = sess.data.token
|
||||
await fetch(`/api/diagnosis/sessions/${token}`,{method:'PUT',headers:{'Content-Type':'application/json'},body:JSON.stringify({answers})})
|
||||
const rep = await fetch(`/api/diagnosis/sessions/${token}/submit`,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({answers})}).then(r=>r.json())
|
||||
localStorage.removeItem(STORAGE_KEY)
|
||||
nav(`/diagnosis/report/${rep.data.token}`)
|
||||
}
|
||||
|
||||
if(loading) return <div className="container section">加载问卷... <2s</div>
|
||||
if(!q) return <div className="container section">暂无题目,请联系管理员在CMS后台配置</div>
|
||||
|
||||
const isAnswered = answers[q.id]!==undefined
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="page-header">
|
||||
<div className="container">
|
||||
<h1>企业进化诊断</h1>
|
||||
<p>15-20题 · 按四大价值分组 · 区间选择无需精确填写 · 免责:初步分析仅供参考</p>
|
||||
<div style={{marginTop:12, background:'rgba(255,255,255,0.2)', height:6, borderRadius:6}}>
|
||||
<div style={{width:`${progress}%`, height:'100%', background:'#fff', borderRadius:6}} />
|
||||
</div>
|
||||
<div style={{fontSize:12, color:'rgba(255,255,255,0.8)', marginTop:4}}>{idx+1}/{questions.length} · 已答 {answeredCount} 题 · 本地自动保存,刷新不丢失</div>
|
||||
</div>
|
||||
</div>
|
||||
<section className="section">
|
||||
<div className="container" style={{maxWidth:720}}>
|
||||
<div className="card">
|
||||
<div style={{fontSize:12, color:'#0f6b5b', fontWeight:800}}>{q.group_name} · {q.type==='scale'?'量表 1-5':q.type==='multiple'?'多选':'单选'}</div>
|
||||
<h3 style={{marginTop:6}}>{idx+1}. {q.title}</h3>
|
||||
<div style={{marginTop:12, display:'grid', gap:8}}>
|
||||
{q.options.map((opt:any)=> {
|
||||
const selected = q.type==='multiple' ? (Array.isArray(answers[q.id]) && answers[q.id].includes(opt.label)) : answers[q.id]===opt.label
|
||||
return (
|
||||
<label key={opt.label} style={{border: selected?'2px solid #163E70':'1px solid #e2e8f0', background: selected?'#eff6ff':'#fff', padding:'10px 12px', borderRadius:8, cursor:'pointer', display:'flex', justifyContent:'space-between'}}>
|
||||
<span><input type={q.type==='multiple'?'checkbox':'radio'} checked={!!selected} onChange={()=>{
|
||||
if(q.type==='multiple'){
|
||||
const arr=Array.isArray(answers[q.id])? [...answers[q.id]]:[]
|
||||
if(selected) handleAnswer(arr.filter(v=>v!==opt.label))
|
||||
else handleAnswer([...arr, opt.label])
|
||||
} else handleAnswer(opt.label)
|
||||
}} style={{marginRight:8}} />{opt.label}</span>
|
||||
<span style={{fontSize:11,color:'#64748b'}}>{opt.score}分</span>
|
||||
</label>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
<div style={{display:'flex', gap:10, marginTop:16}}>
|
||||
<button className="btn btn-ghost" onClick={prevQ} disabled={idx===0}>上一步</button>
|
||||
<button className="btn btn-primary" style={{flex:1}} onClick={nextQ} disabled={!isAnswered}>{idx===questions.length-1?'提交生成报告':'下一步'}</button>
|
||||
</div>
|
||||
<div style={{fontSize:11, color:'#94a3b8', marginTop:8}}>禁止开放题必填 · 采用区间选择 · 不收集敏感财务数据</div>
|
||||
</div>
|
||||
<div style={{marginTop:12, display:'flex', gap:10}}>
|
||||
<Link to="/self-check" className="btn btn-ghost">返回轻量对照</Link>
|
||||
<button className="btn btn-ghost" onClick={()=>{localStorage.removeItem(STORAGE_KEY); setAnswers({}); setIdx(0)}}>重置进度</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import { useParams, Link } from 'react-router-dom'
|
||||
import { AMB_STATUS_META } from '@/data/mock'
|
||||
import { useCMS, getCMSAmb } from '@/services/cms'
|
||||
import GlobalCTA from '@/components/Layout/GlobalCTA'
|
||||
|
||||
export default function AmbDetail() {
|
||||
const { status } = useParams()
|
||||
const { data: ambMeta } = useCMS(getCMSAmb, AMB_STATUS_META)
|
||||
const meta = ambMeta[status as string]
|
||||
|
||||
if (!meta) {
|
||||
return (
|
||||
<div className="container section">
|
||||
<div className="placeholder">未找到状态:{status}</div>
|
||||
<Link to="/evolution" className="btn btn-secondary" style={{ marginTop: 16 }}>
|
||||
返回企业进化
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="page-header">
|
||||
<div className="container">
|
||||
<div className="breadcrumb">
|
||||
<Link to="/" style={{ color: 'rgba(255,255,255,0.8)' }}>
|
||||
首页
|
||||
</Link>{' '}
|
||||
/{' '}
|
||||
<Link to="/evolution" style={{ color: 'rgba(255,255,255,0.8)' }}>
|
||||
企业进化
|
||||
</Link>{' '}
|
||||
/ {meta.label}
|
||||
</div>
|
||||
<h1>{meta.label}</h1>
|
||||
<p>{meta.description}</p>
|
||||
<p style={{ fontSize: 12, color: 'rgba(255,255,255,0.6)', marginTop: 8 }}>不绑定规模 · 支持跨维度不同状态</p>
|
||||
</div>
|
||||
</div>
|
||||
<section className="section">
|
||||
<div className="container">
|
||||
<div className="card">
|
||||
<h3>典型特征</h3>
|
||||
<ul style={{ paddingLeft: 18, lineHeight: 2, fontSize: 14 }}>
|
||||
{meta.traits.map(t => (
|
||||
<li key={t}>{t}</li>
|
||||
))}
|
||||
</ul>
|
||||
<div className="placeholder" style={{ marginTop: 12 }}>
|
||||
【占位|待业务确认】该状态的完整特征、风险、进化建议、案例映射待业务确认后灌入。
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ marginTop: 16, display: 'flex', gap: 10 }}>
|
||||
<Link to="/evolution" className="btn btn-ghost">
|
||||
← 返回企业进化
|
||||
</Link>
|
||||
<Link to="/contact" className="btn btn-primary">
|
||||
获取进化方案
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<GlobalCTA />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
import { useParams, Link } from 'react-router-dom'
|
||||
import { FOUR_VALUES as FALLBACK_FOUR } from '@/data/mock'
|
||||
import { useCMS, getCMSFourValues } from '@/services/cms'
|
||||
import GlobalCTA from '@/components/Layout/GlobalCTA'
|
||||
|
||||
export default function ValuesDetail() {
|
||||
const { data: FOUR_VALUES } = useCMS(getCMSFourValues, FALLBACK_FOUR)
|
||||
const { key } = useParams()
|
||||
const value = FOUR_VALUES.find(v => v.key === key)
|
||||
|
||||
if (!value) {
|
||||
return (
|
||||
<div className="container section">
|
||||
<div className="placeholder">未找到该价值坐标:{key}</div>
|
||||
<Link to="/evolution" className="btn btn-secondary" style={{ marginTop: 16 }}>
|
||||
返回企业进化
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="page-header">
|
||||
<div className="container">
|
||||
<div className="breadcrumb">
|
||||
<Link to="/" style={{ color: 'rgba(255,255,255,0.8)' }}>
|
||||
首页
|
||||
</Link>{' '}
|
||||
/{' '}
|
||||
<Link to="/evolution" style={{ color: 'rgba(255,255,255,0.8)' }}>
|
||||
企业进化
|
||||
</Link>{' '}
|
||||
/ {value.name}
|
||||
</div>
|
||||
<h1>{value.name}</h1>
|
||||
<p>{value.summary}</p>
|
||||
<p style={{ fontSize: 12, color: 'rgba(255,255,255,0.6)', marginTop: 8 }}>观察坐标 · 非售卖产品 · CMS预留字段已就绪</p>
|
||||
</div>
|
||||
</div>
|
||||
<section className="section">
|
||||
<div className="container">
|
||||
<div className="card">
|
||||
<h3>观察维度</h3>
|
||||
<div style={{ display: 'flex', gap: 8, flexWrap: 'wrap', marginTop: 8 }}>
|
||||
{value.observationDimensions.map(d => (
|
||||
<span key={d} style={{ background: '#f1f5f9', padding: '6px 12px', borderRadius: 999, fontSize: 12 }}>
|
||||
{d}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
<div className="placeholder" style={{ marginTop: 16 }}>
|
||||
{value.description}
|
||||
<br />
|
||||
【占位|待业务确认】此价值坐标的完整阐述、评估要点、典型表现需由知识母盘灌入。
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ marginTop: 16, display: 'flex', gap: 10 }}>
|
||||
<Link to="/evolution" className="btn btn-ghost">
|
||||
← 返回企业进化
|
||||
</Link>
|
||||
<Link to="/self-check" className="btn btn-primary">
|
||||
看看你的企业
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<GlobalCTA />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
import { useState } from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { FOUR_VALUES as FALLBACK_FOUR, AMB_STATUS_META } from '@/data/mock'
|
||||
import { useCMS, getCMSFourValues, getCMSAmb } from '@/services/cms'
|
||||
import GlobalCTA from '@/components/Layout/GlobalCTA'
|
||||
|
||||
export default function Evolution() {
|
||||
const { data: FOUR_VALUES } = useCMS(getCMSFourValues, FALLBACK_FOUR)
|
||||
const { data: AMB_META } = useCMS(getCMSAmb, AMB_STATUS_META)
|
||||
const [foldValues, setFoldValues] = useState(false)
|
||||
const [foldAmb, setFoldAmb] = useState(false)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="page-header">
|
||||
<div className="container">
|
||||
<div className="breadcrumb">
|
||||
<Link to="/" style={{ color: 'rgba(255,255,255,0.8)' }}>
|
||||
首页
|
||||
</Link>{' '}
|
||||
/ 企业进化
|
||||
</div>
|
||||
<h1>企业进化</h1>
|
||||
<p>
|
||||
内部顺序强制:<strong>企业进化思想 → 四大价值坐标 → A/M/B 状态坐标</strong>,顺序不可调换。
|
||||
</p>
|
||||
<p style={{ fontSize: 12, color: 'rgba(255,255,255,0.6)', marginTop: 8 }}>
|
||||
V1.1 FINAL 冻结顺序,任何调换需登记台账延后二期。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 1. 企业进化思想 */}
|
||||
<section className="section" id="idea">
|
||||
<div className="container">
|
||||
<p className="eyebrow">01 — 企业进化思想</p>
|
||||
<h2 className="h2">进化是持续迭代,而非一次性变革</h2>
|
||||
<p className="desc">
|
||||
企业进化强调在不确定环境中,通过机制与人的双重迭代,实现组织的自适应与自进化。拒绝“规模决定论”,强调价值维度独立观察。
|
||||
</p>
|
||||
<div className="card" style={{ marginTop: 16 }}>
|
||||
<h3>核心主张(占位)</h3>
|
||||
<ul style={{ paddingLeft: 18, fontSize: 14, color: '#475569', lineHeight: 2 }}>
|
||||
<li>经营即进化:战略、组织、机制、人的协同进化</li>
|
||||
<li>坐标而非标签:用价值与状态观察,而非给企业贴标签</li>
|
||||
<li>陪跑而非交付:共创机制,陪跑落地,复盘迭代</li>
|
||||
<li>【占位|待业务确认】更多思想阐述由知识母盘灌入</li>
|
||||
</ul>
|
||||
<div className="placeholder" style={{ marginTop: 12 }}>
|
||||
【占位|待业务确认】此段需业务方确认最终表述,一期仅保留结构。
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* 2. 四大价值坐标 */}
|
||||
<section className="section section-muted" id="values">
|
||||
<div className="container">
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: 12 }}>
|
||||
<div>
|
||||
<p className="eyebrow">02 — 四大价值坐标</p>
|
||||
<h2 className="h2">观察坐标,非售卖产品</h2>
|
||||
<p className="desc">四大价值定位为观察坐标,不能包装成售卖产品。用于看清企业在各维度的真实位置。</p>
|
||||
</div>
|
||||
<button className="foldable-trigger" onClick={() => setFoldValues(!foldValues)} style={{ width: 'auto' }}>
|
||||
{foldValues ? '收起' : '展开'}价值详情 <span>{foldValues ? '−' : '+'}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className={`foldable-content ${foldValues ? 'expanded' : 'collapsed'}`} style={{ display: foldValues ? 'block' : undefined }}>
|
||||
<div className="grid-2" style={{ marginTop: 20 }}>
|
||||
{FOUR_VALUES.map(v => (
|
||||
<div key={v.id} className="card">
|
||||
<h3>{v.name}</h3>
|
||||
<p style={{ fontSize: 13, color: '#64748b' }}>{v.summary}</p>
|
||||
<div style={{ marginTop: 10, display: 'flex', flexWrap: 'wrap', gap: 6 }}>
|
||||
{v.observationDimensions.map(d => (
|
||||
<span key={d} style={{ fontSize: 11, background: '#f1f5f9', padding: '4px 8px', borderRadius: 999 }}>
|
||||
{d}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
<div className="placeholder" style={{ marginTop: 12, fontSize: 12 }}>
|
||||
{v.description}
|
||||
</div>
|
||||
<Link to={`/evolution/values/${v.key}`} className="btn btn-ghost" style={{ marginTop: 12, width: '100%' }}>
|
||||
进入详情 →
|
||||
</Link>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{/* 始终显示在桌面端 */}
|
||||
<div className="grid-2" style={{ marginTop: 20 }} data-desktop-only>
|
||||
{/* 兼容:桌面端始终展开,移动端由折叠控制 */}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 桌面端强制展开 */}
|
||||
<div className="grid-2" style={{ marginTop: 20 }}>
|
||||
{FOUR_VALUES.map(v => (
|
||||
<div key={v.id} className="card" data-desktop-card>
|
||||
<h3>{v.name}</h3>
|
||||
<p style={{ fontSize: 13, color: '#64748b' }}>{v.summary}</p>
|
||||
<div style={{ marginTop: 10, display: 'flex', flexWrap: 'wrap', gap: 6 }}>
|
||||
{v.observationDimensions.map(d => (
|
||||
<span key={d} style={{ fontSize: 11, background: '#f1f5f9', padding: '4px 8px', borderRadius: 999 }}>
|
||||
{d}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
<Link to={`/evolution/values/${v.key}`} className="btn btn-ghost" style={{ marginTop: 12, width: '100%' }}>
|
||||
进入详情 →
|
||||
</Link>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<style>{`@media(max-width:768px){ [data-desktop-card]{display:none} } @media(min-width:769px){ .foldable-trigger{display:none} .foldable-content{display:none!important} }`}</style>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* 3. A/M/B 状态坐标 */}
|
||||
<section className="section" id="amb">
|
||||
<div className="container">
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: 12 }}>
|
||||
<div>
|
||||
<p className="eyebrow">03 — A/M/B 状态坐标</p>
|
||||
<h2 className="h2">A / M / B 不绑定规模</h2>
|
||||
<p className="desc">支持同一企业不同价值维度处于不同状态;拒绝“规模决定状态”的包装。</p>
|
||||
</div>
|
||||
<button className="foldable-trigger" onClick={() => setFoldAmb(!foldAmb)} style={{ width: 'auto' }}>
|
||||
{foldAmb ? '收起' : '展开'}状态详情 <span>{foldAmb ? '−' : '+'}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="grid-3" style={{ marginTop: 20 }}>
|
||||
{Object.values(AMB_META).map(m => (
|
||||
<div key={m.status} className="card">
|
||||
<h3>{m.label}</h3>
|
||||
<p style={{ fontSize: 13 }}>{m.description}</p>
|
||||
<ul style={{ fontSize: 13, color: '#475569', paddingLeft: 18, marginTop: 8 }}>
|
||||
{m.traits.map(t => (
|
||||
<li key={t}>{t}</li>
|
||||
))}
|
||||
</ul>
|
||||
<Link to={`/evolution/amb/${m.status}`} className="btn btn-secondary" style={{ marginTop: 12, width: '100%' }}>
|
||||
查看 {m.status} 详情
|
||||
</Link>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="card" style={{ marginTop: 20, background: '#f8fafc' }}>
|
||||
<h3>跨维度状态示例(占位)</h3>
|
||||
<p style={{ fontSize: 13, color: '#64748b' }}>
|
||||
示例:某企业 · 战略价值 A / 组织价值 M / 经营价值 B / 人的价值 M。说明同一企业可在不同坐标处不同状态,需分维度干预。
|
||||
</p>
|
||||
<div className="placeholder" style={{ marginTop: 10 }}>
|
||||
【占位|待业务确认】真实示例需脱敏后灌入,一期仅示意结构。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ marginTop: 16, display: 'flex', gap: 10, flexWrap: 'wrap' }}>
|
||||
<Link to="/self-check" className="btn btn-primary">
|
||||
看看你的企业(轻量对照)
|
||||
</Link>
|
||||
<Link to="/contact" className="btn btn-ghost">
|
||||
咨询获取方案
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<GlobalCTA title="看清坐标,再谈行动" desc="先完成轻量对照,定位你的价值×状态矩阵,再选择陪跑路径。" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
.hero-home {
|
||||
background: linear-gradient(135deg, #0f172a 0%, #163E70 55%, #0f6b5b 100%);
|
||||
color: #fff;
|
||||
padding: 64px 0 56px;
|
||||
}
|
||||
.hero-home-inner { display: grid; grid-template-columns: 1.2fr 0.8fr; gap: 32px; align-items: center; }
|
||||
.hero-copy h1 { font-size: 40px; font-weight: 900; line-height: 1.15; margin: 8px 0 14px; }
|
||||
.hero-desc { color: rgba(255,255,255,0.82); font-size: 15px; line-height: 1.8; max-width: 560px; }
|
||||
.hero-actions { display: flex; gap: 12px; margin-top: 20px; flex-wrap: wrap; }
|
||||
.hero-trust { margin-top: 16px; font-size: 12px; color: rgba(255,255,255,0.6); border-top: 1px solid rgba(255,255,255,0.12); padding-top: 12px; }
|
||||
.visual-card { background: #fff; color: var(--text); border-radius: 16px; padding: 24px; box-shadow: 0 20px 60px rgba(0,0,0,0.2); }
|
||||
.visual-title { font-weight: 800; color: var(--primary); margin-bottom: 16px; }
|
||||
.visual-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
|
||||
.visual-grid div { background: #f8fafc; border-radius: 10px; padding: 14px; }
|
||||
.visual-grid strong { font-size: 13px; color: var(--primary); }
|
||||
.visual-grid p { font-size: 12px; color: var(--text-secondary); margin-top: 4px; }
|
||||
|
||||
.section-heading { margin-bottom: 20px; }
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.hero-home-inner { grid-template-columns: 1fr; }
|
||||
.hero-copy h1 { font-size: 28px; }
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
import { Link } from 'react-router-dom'
|
||||
import GlobalCTA from '@/components/Layout/GlobalCTA'
|
||||
import { FOUR_VALUES as FALLBACK_FOUR, SERVICES as FALLBACK_SERVICES, CASES as FALLBACK_CASES, INSIGHTS as FALLBACK_INSIGHTS } from '@/data/mock'
|
||||
import { useCMS, getCMSFourValues, getCMSServices, getCMSCases, getCMSInsights } from '@/services/cms'
|
||||
import './home.css'
|
||||
|
||||
export default function Home() {
|
||||
const { data: fourValues } = useCMS(getCMSFourValues, FALLBACK_FOUR)
|
||||
const { data: services } = useCMS(getCMSServices, FALLBACK_SERVICES)
|
||||
const { data: cases } = useCMS(getCMSCases, FALLBACK_CASES)
|
||||
const { data: insights } = useCMS(getCMSInsights, FALLBACK_INSIGHTS)
|
||||
return (
|
||||
<div className="home">
|
||||
{/* 首屏:3秒识别品牌 */}
|
||||
<section className="hero-home">
|
||||
<div className="container hero-home-inner">
|
||||
<div className="hero-copy">
|
||||
<p className="eyebrow">阿美巴 · 企业进化陪跑</p>
|
||||
<h1>
|
||||
让企业进化
|
||||
<br />
|
||||
被看见、被度量、被落地
|
||||
</h1>
|
||||
<p className="hero-desc">
|
||||
以“企业进化”为主线,以<strong>四大价值坐标</strong>与<strong> A/M/B 状态坐标</strong>为观察系,陪跑企业构建可复制、可持续的增长经营体系。
|
||||
<br />
|
||||
<span style={{ color: '#f59e0b' }}>一期为骨架与占位,不虚构业务数据。</span>
|
||||
</p>
|
||||
<div className="hero-actions">
|
||||
<Link to="/diagnosis" className="btn btn-primary">
|
||||
开始企业进化诊断
|
||||
</Link>
|
||||
<Link to="/contact" className="btn btn-secondary">
|
||||
咨询获取方案
|
||||
</Link>
|
||||
<Link to="/self-check" className="btn btn-ghost">
|
||||
轻量对照
|
||||
</Link>
|
||||
</div>
|
||||
<div className="hero-trust">
|
||||
<span>30秒理解主张:进化思想 → 价值坐标 → 状态坐标 → 行动路径</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="hero-visual">
|
||||
<div className="visual-card">
|
||||
<div className="visual-title">企业进化摘要</div>
|
||||
<div className="visual-grid">
|
||||
<div>
|
||||
<strong>思想</strong>
|
||||
<p>进化是持续迭代,非一次性变革</p>
|
||||
</div>
|
||||
<div>
|
||||
<strong>坐标</strong>
|
||||
<p>四大价值 × A/M/B 状态</p>
|
||||
</div>
|
||||
<div>
|
||||
<strong>路径</strong>
|
||||
<p>诊断 → 机制 → 陪跑 → 复盘</p>
|
||||
</div>
|
||||
<div>
|
||||
<strong>行动</strong>
|
||||
<p>轻量对照 / 深度陪跑</p>
|
||||
</div>
|
||||
</div>
|
||||
<Link to="/evolution" className="btn btn-ghost" style={{ width: '100%', marginTop: 16 }}>
|
||||
进入企业进化 →
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* 摘要板块:阿美巴 */}
|
||||
<section className="section section-muted">
|
||||
<div className="container">
|
||||
<div className="section-heading">
|
||||
<p className="eyebrow">Ameba</p>
|
||||
<h2 className="h2">关于阿美巴</h2>
|
||||
<p className="desc">克制理性的企业服务风格,以方法与陪跑见长,非培训话术与AI噱头。</p>
|
||||
</div>
|
||||
<div className="card" style={{ display: 'flex', justifyContent: 'space-between', gap: 16, alignItems: 'center', flexWrap: 'wrap' }}>
|
||||
<p className="desc" style={{ maxWidth: 640 }}>
|
||||
阿美巴以企业进化经营哲学为底色,结合中国中小企业实际,提供从诊断到落地陪跑的全链路服务。
|
||||
<br />
|
||||
<span className="placeholder" style={{ display: 'inline-block', marginTop: 8 }}>
|
||||
【占位|待业务确认】品牌故事、创始团队、使命愿景等由知识母盘灌入。
|
||||
</span>
|
||||
</p>
|
||||
<Link to="/ameba" className="btn btn-secondary">
|
||||
了解阿美巴 →
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* 摘要:企业进化(顺序预告) */}
|
||||
<section className="section">
|
||||
<div className="container">
|
||||
<div className="section-heading">
|
||||
<p className="eyebrow">Enterprise Evolution</p>
|
||||
<h2 className="h2">企业进化 · 观察坐标</h2>
|
||||
<p className="desc">强制顺序:企业进化思想 → 四大价值坐标 → A/M/B 状态坐标。首页仅摘要,详情下沉二级页面。</p>
|
||||
</div>
|
||||
<div className="grid-3">
|
||||
{fourValues.slice(0, 3).map(v => (
|
||||
<div key={v.id} className="card">
|
||||
<h3>{v.name}</h3>
|
||||
<p>{v.summary}</p>
|
||||
<Link to={`/evolution/values/${v.key}`} className="btn btn-ghost" style={{ marginTop: 12, width: '100%' }}>
|
||||
查看价值详情
|
||||
</Link>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div style={{ marginTop: 16, display: 'flex', gap: 10, flexWrap: 'wrap' }}>
|
||||
<Link to="/evolution" className="btn btn-primary">
|
||||
进入企业进化完整页
|
||||
</Link>
|
||||
<Link to="/evolution/amb/A" className="btn btn-ghost">
|
||||
查看 A/M/B 状态
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* 摘要:方法与能力 */}
|
||||
<section className="section section-muted">
|
||||
<div className="container">
|
||||
<div className="section-heading">
|
||||
<p className="eyebrow">Methods & Capabilities</p>
|
||||
<h2 className="h2">方法与能力</h2>
|
||||
<p className="desc">方法 × 能力 × 工具,支撑进化落地。</p>
|
||||
</div>
|
||||
<div className="grid-2">
|
||||
<div className="card">
|
||||
<h3>诊断方法</h3>
|
||||
<p>访谈 + 数据 + 坐标映射,定位差距与根因。</p>
|
||||
</div>
|
||||
<div className="card">
|
||||
<h3>陪跑能力</h3>
|
||||
<p>机制共创、例会陪跑、复盘迭代,保障落地。</p>
|
||||
</div>
|
||||
</div>
|
||||
<Link to="/methods" className="btn btn-secondary" style={{ marginTop: 16 }}>
|
||||
查看方法与能力 →
|
||||
</Link>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* 摘要:实践案例 */}
|
||||
<section className="section">
|
||||
<div className="container">
|
||||
<div className="section-heading">
|
||||
<p className="eyebrow">Cases</p>
|
||||
<h2 className="h2">实践案例(占位)</h2>
|
||||
<p className="desc">固定叙事插槽:原状 → 目标 → 差距 → 根因 → 介入 → 路径 → 动作 → 结果 → 迭代。无虚构数据。</p>
|
||||
</div>
|
||||
<div className="grid-2">
|
||||
{cases.map(c => (
|
||||
<Link key={c.id} to={`/cases/${c.slug}`} className="card" style={{ display: 'block' }}>
|
||||
<h3>{c.title}</h3>
|
||||
<p>{c.summary}</p>
|
||||
<span style={{ fontSize: 12, color: '#0f6b5b', fontWeight: 700 }}>查看案例叙事 →</span>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
<Link to="/cases" className="btn btn-secondary" style={{ marginTop: 16 }}>
|
||||
全部案例 →
|
||||
</Link>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* 摘要:产品服务 */}
|
||||
<section className="section section-muted">
|
||||
<div className="container">
|
||||
<div className="section-heading">
|
||||
<p className="eyebrow">Services</p>
|
||||
<h2 className="h2">产品服务 · 下一步行动</h2>
|
||||
<p className="desc">禁止电商商城逻辑;重心是“下一步如何行动”。标准产品预留价格,定制展示“咨询/获取方案”。</p>
|
||||
</div>
|
||||
<div className="grid-3">
|
||||
{services.map(s => (
|
||||
<div key={s.id} className="card">
|
||||
<h3>{s.title}</h3>
|
||||
<p>{s.summary}</p>
|
||||
{s.serviceType === 'standard' && s.price ? (
|
||||
<div style={{ marginTop: 10, fontWeight: 800, color: 'var(--primary)' }}>
|
||||
¥{s.price} <span style={{ fontWeight: 400, fontSize: 12 }}>{s.priceUnit}</span>
|
||||
</div>
|
||||
) : (
|
||||
<div style={{ marginTop: 10, fontSize: 12, color: '#0f6b5b', fontWeight: 700 }}>咨询/获取方案</div>
|
||||
)}
|
||||
<Link to={`/services/${s.slug}`} className="btn btn-primary" style={{ marginTop: 12, width: '100%' }}>
|
||||
{s.ctaText} →
|
||||
</Link>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* 摘要:认知观察 */}
|
||||
<section className="section">
|
||||
<div className="container">
|
||||
<div className="section-heading">
|
||||
<p className="eyebrow">Insights</p>
|
||||
<h2 className="h2">认知观察</h2>
|
||||
<p className="desc">围绕进化思想、价值坐标、A/M/B 的观察与思考。</p>
|
||||
</div>
|
||||
<div className="grid-2">
|
||||
{insights.map(i => (
|
||||
<Link key={i.id} to={`/insights/${i.slug}`} className="card" style={{ display: 'block' }}>
|
||||
<h3>{i.title}</h3>
|
||||
<p>{i.summary}</p>
|
||||
<span style={{ fontSize: 12, color: '#64748b' }}>{i.readingMinutes} 分钟阅读 · {i.author}</span>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
<Link to="/insights" className="btn btn-secondary" style={{ marginTop: 16 }}>
|
||||
全部观察 →
|
||||
</Link>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* 底部行动层 */}
|
||||
<GlobalCTA />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import { useParams, Link } from 'react-router-dom'
|
||||
import { INSIGHTS as FALLBACK_INSIGHTS } from '@/data/mock'
|
||||
import { useCMS, getCMSInsights } from '@/services/cms'
|
||||
import GlobalCTA from '@/components/Layout/GlobalCTA'
|
||||
|
||||
export default function InsightDetail() {
|
||||
const { data: INSIGHTS } = useCMS(getCMSInsights, FALLBACK_INSIGHTS)
|
||||
const { slug } = useParams()
|
||||
const item = INSIGHTS.find(i => i.slug === slug)
|
||||
if (!item)
|
||||
return (
|
||||
<div className="container section">
|
||||
<div className="placeholder">未找到洞察:{slug}</div>
|
||||
<Link to="/insights" className="btn btn-secondary" style={{ marginTop: 16 }}>
|
||||
返回认知观察
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
return (
|
||||
<div>
|
||||
<div className="page-header">
|
||||
<div className="container">
|
||||
<div className="breadcrumb">
|
||||
<Link to="/" style={{ color: 'rgba(255,255,255,0.8)' }}>
|
||||
首页
|
||||
</Link>{' '}
|
||||
/{' '}
|
||||
<Link to="/insights" style={{ color: 'rgba(255,255,255,0.8)' }}>
|
||||
认知观察
|
||||
</Link>{' '}
|
||||
/ 详情
|
||||
</div>
|
||||
<h1>{item.title}</h1>
|
||||
<p>{item.summary}</p>
|
||||
<p style={{ fontSize: 12, color: 'rgba(255,255,255,0.6)', marginTop: 8 }}>
|
||||
{item.readingMinutes} 分钟 · {item.author} · {item.category}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<section className="section">
|
||||
<div className="container">
|
||||
<article className="card" style={{ maxWidth: 760 }}>
|
||||
{item.contentBlocks.map(b => (
|
||||
<div key={b.id} style={{ marginBottom: 16 }}>
|
||||
{b.type === 'heading' && <h3>{b.text}</h3>}
|
||||
{b.type === 'paragraph' && <p style={{ fontSize: 15, color: '#334155', lineHeight: 1.8 }}>{b.text}</p>}
|
||||
{b.type === 'list' && (
|
||||
<ul style={{ paddingLeft: 18, fontSize: 14, color: '#334155' }}>
|
||||
{b.items?.map(x => (
|
||||
<li key={x}>{x}</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
{b.type === 'placeholder' && <div className="placeholder">{b.placeholderNote}</div>}
|
||||
</div>
|
||||
))}
|
||||
<div className="placeholder" style={{ marginTop: 12 }}>
|
||||
【占位|待业务确认】正文由 CMS 接管,编辑仅替换 contentBlocks,不改布局。
|
||||
</div>
|
||||
</article>
|
||||
<div style={{ marginTop: 16, display: 'flex', gap: 10 }}>
|
||||
<Link to="/insights" className="btn btn-ghost">
|
||||
← 返回观察列表
|
||||
</Link>
|
||||
<Link to="/contact" className="btn btn-primary">
|
||||
咨询获取方案
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<GlobalCTA />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import { Link } from 'react-router-dom'
|
||||
import { INSIGHTS as FALLBACK_INSIGHTS } from '@/data/mock'
|
||||
import { useCMS, getCMSInsights } from '@/services/cms'
|
||||
import GlobalCTA from '@/components/Layout/GlobalCTA'
|
||||
|
||||
export default function Insights() {
|
||||
const { data: INSIGHTS } = useCMS(getCMSInsights, FALLBACK_INSIGHTS)
|
||||
return (
|
||||
<div>
|
||||
<div className="page-header">
|
||||
<div className="container">
|
||||
<div className="breadcrumb">
|
||||
<Link to="/" style={{ color: 'rgba(255,255,255,0.8)' }}>
|
||||
首页
|
||||
</Link>{' '}
|
||||
/ 认知观察
|
||||
</div>
|
||||
<h1>认知观察</h1>
|
||||
<p>围绕企业进化、价值坐标、A/M/B 与方法论的持续观察与沉淀。</p>
|
||||
<div className="placeholder" style={{ marginTop: 12 }}>
|
||||
【占位|待业务确认】文章由知识母盘定稿后灌入,一期仅保留结构与占位。
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<section className="section">
|
||||
<div className="container">
|
||||
<div className="grid-2">
|
||||
{INSIGHTS.map(i => (
|
||||
<Link key={i.id} to={`/insights/${i.slug}`} className="card" style={{ display: 'block' }}>
|
||||
<div style={{ fontSize: 11, color: '#0f6b5b', fontWeight: 800, letterSpacing: '0.08em' }}>{i.category.toUpperCase()}</div>
|
||||
<h3 style={{ marginTop: 6 }}>{i.title}</h3>
|
||||
<p>{i.summary}</p>
|
||||
<div style={{ fontSize: 12, color: '#94a3b8', marginTop: 8 }}>
|
||||
{i.readingMinutes} 分钟 · {i.author} · 权重 {i.sortWeight}
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<GlobalCTA />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import { useParams, Link } from 'react-router-dom'
|
||||
import { METHODS as FALLBACK_METHODS } from '@/data/mock'
|
||||
import { useCMS, getCMSMethods } from '@/services/cms'
|
||||
import GlobalCTA from '@/components/Layout/GlobalCTA'
|
||||
|
||||
export default function MethodDetail() {
|
||||
const { data: METHODS } = useCMS(getCMSMethods, FALLBACK_METHODS)
|
||||
const { slug } = useParams()
|
||||
const item = METHODS.find(m => m.slug === slug)
|
||||
if (!item)
|
||||
return (
|
||||
<div className="container section">
|
||||
<div className="placeholder">未找到:{slug}</div>
|
||||
<Link to="/methods" className="btn btn-secondary" style={{ marginTop: 16 }}>
|
||||
返回方法与能力
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
return (
|
||||
<div>
|
||||
<div className="page-header">
|
||||
<div className="container">
|
||||
<div className="breadcrumb">
|
||||
<Link to="/" style={{ color: 'rgba(255,255,255,0.8)' }}>
|
||||
首页
|
||||
</Link>{' '}
|
||||
/{' '}
|
||||
<Link to="/methods" style={{ color: 'rgba(255,255,255,0.8)' }}>
|
||||
方法与能力
|
||||
</Link>{' '}
|
||||
/ {item.title}
|
||||
</div>
|
||||
<h1>{item.title}</h1>
|
||||
<p>{item.summary}</p>
|
||||
</div>
|
||||
</div>
|
||||
<section className="section">
|
||||
<div className="container">
|
||||
<div className="card">
|
||||
<h3>关键步骤</h3>
|
||||
<ol style={{ paddingLeft: 18, lineHeight: 2, fontSize: 14 }}>
|
||||
{item.steps?.map(s => (
|
||||
<li key={s}>{s}</li>
|
||||
))}
|
||||
</ol>
|
||||
<div className="placeholder" style={{ marginTop: 12 }}>
|
||||
【占位|待业务确认】完整方法阐述、工具清单、交付物清单待灌入。
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ marginTop: 16, display: 'flex', gap: 10 }}>
|
||||
<Link to="/methods" className="btn btn-ghost">
|
||||
← 返回
|
||||
</Link>
|
||||
<Link to="/contact" className="btn btn-primary">
|
||||
咨询获取方案
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<GlobalCTA />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import { Link } from 'react-router-dom'
|
||||
import { METHODS as FALLBACK_METHODS } from '@/data/mock'
|
||||
import { useCMS, getCMSMethods } from '@/services/cms'
|
||||
import GlobalCTA from '@/components/Layout/GlobalCTA'
|
||||
|
||||
export default function Methods() {
|
||||
const { data: METHODS } = useCMS(getCMSMethods, FALLBACK_METHODS)
|
||||
return (
|
||||
<div>
|
||||
<div className="page-header">
|
||||
<div className="container">
|
||||
<div className="breadcrumb">
|
||||
<Link to="/" style={{ color: 'rgba(255,255,255,0.8)' }}>
|
||||
首页
|
||||
</Link>{' '}
|
||||
/ 方法与能力
|
||||
</div>
|
||||
<h1>方法与能力</h1>
|
||||
<p>方法决定路径,能力决定落地。二者闭环,支撑企业进化。</p>
|
||||
<div className="placeholder" style={{ marginTop: 12 }}>
|
||||
【占位|待业务确认】方法论体系、工具、交付标准待知识母盘灌入。
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<section className="section">
|
||||
<div className="container">
|
||||
<div className="grid-2">
|
||||
{METHODS.map(m => (
|
||||
<Link key={m.id} to={`/methods/${m.slug}`} className="card" style={{ display: 'block' }}>
|
||||
<p className="eyebrow">{m.group === 'method' ? '方法' : '能力'}</p>
|
||||
<h3>{m.title}</h3>
|
||||
<p>{m.summary}</p>
|
||||
{m.steps && (
|
||||
<ol style={{ fontSize: 13, color: '#475569', paddingLeft: 18, marginTop: 8 }}>
|
||||
{m.steps.map(s => (
|
||||
<li key={s}>{s}</li>
|
||||
))}
|
||||
</ol>
|
||||
)}
|
||||
<span style={{ fontSize: 12, color: '#0f6b5b', fontWeight: 700, marginTop: 10, display: 'inline-block' }}>查看详情 →</span>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<GlobalCTA />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { Link } from 'react-router-dom'
|
||||
|
||||
export default function NotFound() {
|
||||
return (
|
||||
<div className="container section" style={{ textAlign: 'center', maxWidth: 640 }}>
|
||||
<h1 style={{ fontSize: 48, color: '#163E70' }}>404</h1>
|
||||
<p style={{ color: '#64748b' }}>页面不存在或尚未发布。已为旧站 URL 配置 301 重定向,未匹配时落此页。</p>
|
||||
<div style={{ display: 'flex', gap: 10, justifyContent: 'center', marginTop: 16, flexWrap: 'wrap' }}>
|
||||
<Link to="/" className="btn btn-primary">
|
||||
返回首页
|
||||
</Link>
|
||||
<Link to="/contact" className="btn btn-ghost">
|
||||
联系我们
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
import { useState } from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
import GlobalCTA from '@/components/Layout/GlobalCTA'
|
||||
|
||||
type CheckItem = { id: string; question: string; dimension: string }
|
||||
|
||||
const CHECK_ITEMS: CheckItem[] = [
|
||||
{ id: 'q1', question: '战略方向在团队中是否清晰一致?', dimension: '战略价值' },
|
||||
{ id: 'q2', question: '组织协同是否顺畅、职责是否清晰?', dimension: '组织价值' },
|
||||
{ id: 'q3', question: '经营机制(目标/复盘/数据)是否闭环?', dimension: '经营价值' },
|
||||
{ id: 'q4', question: '团队是否具备经营意识与成长机制?', dimension: '人的价值' },
|
||||
]
|
||||
|
||||
export default function SelfCheck() {
|
||||
const [answers, setAnswers] = useState<Record<string, string>>({})
|
||||
const [showResult, setShowResult] = useState(false)
|
||||
|
||||
const set = (id: string, val: string) => setAnswers(prev => ({ ...prev, [id]: val }))
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="page-header">
|
||||
<div className="container">
|
||||
<div className="breadcrumb">
|
||||
<Link to="/" style={{ color: 'rgba(255,255,255,0.8)' }}>
|
||||
首页
|
||||
</Link>{' '}
|
||||
/ 看看你的企业
|
||||
</div>
|
||||
<h1>看看你的企业</h1>
|
||||
<p>
|
||||
轻量自我对照工具,帮助你快速定位在四大价值 × A/M/B 的大致位置。
|
||||
<br />
|
||||
<strong style={{ color: '#fde68a' }}>一期禁止长问卷、复杂评分、自动诊断报告。</strong>仅做轻量对照,深度诊断需线下陪跑。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section className="section">
|
||||
<div className="container" style={{ maxWidth: 720 }}>
|
||||
<div className="card">
|
||||
<h3>轻量对照(4 题)</h3>
|
||||
<p style={{ fontSize: 13, color: '#64748b' }}>每题选择最贴近现状的选项,结果仅作自我对照参考,不生成诊断报告。</p>
|
||||
<div style={{ marginTop: 16, display: 'grid', gap: 16 }}>
|
||||
{CHECK_ITEMS.map(item => (
|
||||
<div key={item.id} style={{ border: '1px solid #e2e8f0', borderRadius: 10, padding: 16 }}>
|
||||
<div style={{ fontSize: 12, color: '#0f6b5b', fontWeight: 800 }}>{item.dimension}</div>
|
||||
<div style={{ fontWeight: 700, marginTop: 4 }}>{item.question}</div>
|
||||
<div style={{ display: 'flex', gap: 8, marginTop: 10, flexWrap: 'wrap' }}>
|
||||
{['A 较清晰/顺畅', 'M 部分卡点', 'B 待构建'].map(opt => (
|
||||
<label
|
||||
key={opt}
|
||||
style={{
|
||||
border: answers[item.id] === opt ? '1px solid #163E70' : '1px solid #e2e8f0',
|
||||
background: answers[item.id] === opt ? '#eff6ff' : '#fff',
|
||||
padding: '8px 12px',
|
||||
borderRadius: 999,
|
||||
fontSize: 13,
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
>
|
||||
<input type="radio" name={item.id} value={opt} checked={answers[item.id] === opt} onChange={() => set(item.id, opt)} style={{ marginRight: 6 }} />
|
||||
{opt}
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<button className="btn btn-primary" style={{ width: '100%', marginTop: 20 }} onClick={() => setShowResult(true)}>
|
||||
查看轻量对照结果
|
||||
</button>
|
||||
<p style={{ fontSize: 11, color: '#94a3b8', marginTop: 8, textAlign: 'center' }}>一期不做自动诊断报告,结果仅本地展示,不存储。</p>
|
||||
</div>
|
||||
|
||||
{showResult && (
|
||||
<div className="card" style={{ marginTop: 16, background: '#f8fafc' }}>
|
||||
<h3>对照结果(占位示意)</h3>
|
||||
<p style={{ fontSize: 13, color: '#475569' }}>
|
||||
基于你的选择,系统提示关注维度:
|
||||
<br />
|
||||
{Object.entries(answers)
|
||||
.map(([k, v]) => `${CHECK_ITEMS.find(i => i.id === k)?.dimension}:${v}`)
|
||||
.join(';') || '(请选择后再查看)'}
|
||||
</p>
|
||||
<div className="placeholder" style={{ marginTop: 10 }}>
|
||||
【占位|一期不做复杂评分】此处仅为轻量对照提示,不生成诊断报告。深度诊断请预约陪跑。
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: 10, marginTop: 12, flexWrap: 'wrap' }}>
|
||||
<Link to="/contact" className="btn btn-primary">
|
||||
预约深度诊断
|
||||
</Link>
|
||||
<Link to="/evolution" className="btn btn-ghost">
|
||||
了解进化坐标
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="placeholder" style={{ marginTop: 16 }}>
|
||||
快速路径:看看你的企业 → 轻量对照 → 预约咨询 / 深度路径:企业进化 → 四大价值 → A/M/B → 联系我们(全链路已打通)
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<GlobalCTA />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
import { useParams, Link } from 'react-router-dom'
|
||||
import { SERVICES as FALLBACK_SERVICES } from '@/data/mock'
|
||||
import { useCMS, getCMSServices } from '@/services/cms'
|
||||
import GlobalCTA from '@/components/Layout/GlobalCTA'
|
||||
|
||||
export default function ServiceDetail() {
|
||||
const { data: SERVICES } = useCMS(getCMSServices, FALLBACK_SERVICES)
|
||||
const { slug } = useParams()
|
||||
const s = SERVICES.find(x => x.slug === slug)
|
||||
if (!s)
|
||||
return (
|
||||
<div className="container section">
|
||||
<div className="placeholder">未找到服务:{slug}</div>
|
||||
<Link to="/services" className="btn btn-secondary" style={{ marginTop: 16 }}>
|
||||
返回产品服务
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
return (
|
||||
<div>
|
||||
<div className="page-header">
|
||||
<div className="container">
|
||||
<div className="breadcrumb">
|
||||
<Link to="/" style={{ color: 'rgba(255,255,255,0.8)' }}>
|
||||
首页
|
||||
</Link>{' '}
|
||||
/{' '}
|
||||
<Link to="/services" style={{ color: 'rgba(255,255,255,0.8)' }}>
|
||||
产品服务
|
||||
</Link>{' '}
|
||||
/ {s.title}
|
||||
</div>
|
||||
<h1>{s.title}</h1>
|
||||
<p>{s.summary}</p>
|
||||
</div>
|
||||
</div>
|
||||
<section className="section">
|
||||
<div className="container">
|
||||
<div className="card">
|
||||
<h3>服务亮点</h3>
|
||||
<ul style={{ paddingLeft: 18, lineHeight: 2, fontSize: 14 }}>
|
||||
{s.highlights?.map(h => (
|
||||
<li key={h}>{h}</li>
|
||||
))}
|
||||
</ul>
|
||||
{s.serviceType === 'standard' ? (
|
||||
<div style={{ marginTop: 16, background: '#f1f5f9', padding: 16, borderRadius: 10 }}>
|
||||
<strong>标准产品价格</strong>
|
||||
<div style={{ fontSize: 22, fontWeight: 900, color: 'var(--primary)' }}>
|
||||
¥{s.price} <span style={{ fontSize: 13, fontWeight: 400 }}>{s.priceUnit}</span>
|
||||
</div>
|
||||
<p style={{ fontSize: 12, color: '#64748b' }}>{s.priceNote}</p>
|
||||
</div>
|
||||
) : (
|
||||
<div style={{ marginTop: 16, background: '#ecfdf5', padding: 16, borderRadius: 10, color: '#065f46' }}>
|
||||
深度定制服务不展示价格,请 <Link to="/contact">咨询/获取方案</Link>
|
||||
</div>
|
||||
)}
|
||||
<div className="placeholder" style={{ marginTop: 12 }}>
|
||||
【占位|待业务确认】交付物、周期、适合对象、服务流程等待灌入。禁止购物车/库存逻辑。
|
||||
</div>
|
||||
<Link to="/contact" className="btn btn-primary" style={{ marginTop: 16, width: '100%' }}>
|
||||
{s.ctaText} →
|
||||
</Link>
|
||||
</div>
|
||||
<div style={{ marginTop: 16, display: 'flex', gap: 10 }}>
|
||||
<Link to="/services" className="btn btn-ghost">
|
||||
← 返回服务列表
|
||||
</Link>
|
||||
<Link to="/cases" className="btn btn-ghost">
|
||||
查看相关案例
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<GlobalCTA />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import { Link } from 'react-router-dom'
|
||||
import { SERVICES as FALLBACK_SERVICES } from '@/data/mock'
|
||||
import { useCMS, getCMSServices } from '@/services/cms'
|
||||
import GlobalCTA from '@/components/Layout/GlobalCTA'
|
||||
|
||||
export default function Services() {
|
||||
const { data: SERVICES } = useCMS(getCMSServices, FALLBACK_SERVICES)
|
||||
return (
|
||||
<div>
|
||||
<div className="page-header">
|
||||
<div className="container">
|
||||
<div className="breadcrumb">
|
||||
<Link to="/" style={{ color: 'rgba(255,255,255,0.8)' }}>
|
||||
首页
|
||||
</Link>{' '}
|
||||
/ 产品服务
|
||||
</div>
|
||||
<h1>产品服务</h1>
|
||||
<p>
|
||||
禁止电商商城逻辑;重心是“下一步如何行动”,而非货品陈列。标准产品预留价格字段,深度定制展示「咨询/获取方案」。
|
||||
</p>
|
||||
<div className="placeholder" style={{ marginTop: 12 }}>
|
||||
【占位|待业务确认】服务定价、交付清单、周期等需业务确认后灌入。
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<section className="section">
|
||||
<div className="container">
|
||||
<div className="grid-3">
|
||||
{SERVICES.map(s => (
|
||||
<div key={s.id} className="card" style={{ display: 'flex', flexDirection: 'column' }}>
|
||||
<div style={{ fontSize: 11, letterSpacing: '0.08em', color: s.serviceType === 'standard' ? '#163E70' : '#0f6b5b', fontWeight: 800 }}>
|
||||
{s.serviceType === 'standard' ? '标准产品' : '深度定制'}
|
||||
</div>
|
||||
<h3 style={{ marginTop: 6 }}>{s.title}</h3>
|
||||
<p>{s.summary}</p>
|
||||
{s.highlights && (
|
||||
<ul style={{ fontSize: 13, color: '#475569', paddingLeft: 18, marginTop: 8 }}>
|
||||
{s.highlights.map(h => (
|
||||
<li key={h}>{h}</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
<div style={{ marginTop: 'auto', paddingTop: 16 }}>
|
||||
{s.serviceType === 'standard' && s.price ? (
|
||||
<div style={{ fontWeight: 900, color: 'var(--primary)', fontSize: 18 }}>
|
||||
¥{(s.price / 1000).toFixed(2)}k <span style={{ fontSize: 12, fontWeight: 400 }}>{s.priceUnit}</span>
|
||||
</div>
|
||||
) : (
|
||||
<div style={{ fontSize: 13, color: '#0f6b5b', fontWeight: 700 }}>咨询/获取方案(无价格)</div>
|
||||
)}
|
||||
{s.priceNote && <div style={{ fontSize: 11, color: '#94a3b8' }}>{s.priceNote}</div>}
|
||||
<Link to={`/services/${s.slug}`} className="btn btn-primary" style={{ width: '100%', marginTop: 10 }}>
|
||||
{s.ctaText} →
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<GlobalCTA />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import { createBrowserRouter, Navigate } from 'react-router-dom'
|
||||
import Layout from '@/components/Layout'
|
||||
|
||||
// 懒加载页保持首包小
|
||||
import Home from '@/pages/Home'
|
||||
import Ameba from '@/pages/Ameba'
|
||||
import Evolution from '@/pages/Evolution'
|
||||
import ValuesDetail from '@/pages/Evolution/ValuesDetail'
|
||||
import AmbDetail from '@/pages/Evolution/AmbDetail'
|
||||
import Methods from '@/pages/Methods'
|
||||
import MethodDetail from '@/pages/Methods/Detail'
|
||||
import Cases from '@/pages/Cases'
|
||||
import CaseDetail from '@/pages/Cases/Detail'
|
||||
import Services from '@/pages/Services'
|
||||
import ServiceDetail from '@/pages/Services/Detail'
|
||||
import Insights from '@/pages/Insights'
|
||||
import InsightDetail from '@/pages/Insights/Detail'
|
||||
import SelfCheck from '@/pages/SelfCheck'
|
||||
import Contact from '@/pages/Contact'
|
||||
import Diagnosis from '@/pages/Diagnosis'
|
||||
import Report from '@/pages/Diagnosis/Report'
|
||||
import Share from '@/pages/Diagnosis/Share'
|
||||
import NotFound from '@/pages/NotFound'
|
||||
|
||||
export const router = createBrowserRouter([
|
||||
{
|
||||
path: '/',
|
||||
element: <Layout />,
|
||||
children: [
|
||||
// 首页
|
||||
{ index: true, element: <Home /> },
|
||||
|
||||
// 一级导航6项(冻结)
|
||||
{ path: 'ameba', element: <Ameba /> },
|
||||
{ path: 'evolution', element: <Evolution /> },
|
||||
// 企业进化二级
|
||||
{ path: 'evolution/values/:key', element: <ValuesDetail /> },
|
||||
{ path: 'evolution/amb/:status', element: <AmbDetail /> },
|
||||
|
||||
{ path: 'methods', element: <Methods /> },
|
||||
{ path: 'methods/:slug', element: <MethodDetail /> },
|
||||
|
||||
{ path: 'cases', element: <Cases /> },
|
||||
{ path: 'cases/:slug', element: <CaseDetail /> },
|
||||
|
||||
{ path: 'services', element: <Services /> },
|
||||
{ path: 'services/:slug', element: <ServiceDetail /> },
|
||||
|
||||
{ path: 'insights', element: <Insights /> },
|
||||
{ path: 'insights/:slug', element: <InsightDetail /> },
|
||||
|
||||
// 公共页面(全局常驻,不占用主导航)
|
||||
{ path: 'self-check', element: <SelfCheck /> },
|
||||
{ path: 'contact', element: <Contact /> },
|
||||
// 诊断系统(新增模块,不改动6项导航)
|
||||
{ path: 'diagnosis', element: <Diagnosis /> },
|
||||
{ path: 'diagnosis/report/:token', element: <Report /> },
|
||||
{ path: 'diagnosis/share/:shareToken', element: <Share /> },
|
||||
|
||||
// 兼容旧站 URL 重定向(301 清单见 docs)
|
||||
{ path: 'about.html', element: <Navigate to="/ameba" replace /> },
|
||||
{ path: 'cases.html', element: <Navigate to="/cases" replace /> },
|
||||
{ path: 'contact.html', element: <Navigate to="/contact" replace /> },
|
||||
{ path: 'faq.html', element: <Navigate to="/insights" replace /> },
|
||||
{ path: 'services/*', element: <Navigate to="/services" replace /> },
|
||||
|
||||
{ path: '*', element: <NotFound /> },
|
||||
],
|
||||
},
|
||||
])
|
||||
@@ -0,0 +1,186 @@
|
||||
/**
|
||||
* CMS 对接层 - 与布局解耦,仅替换数据源
|
||||
* 前端通过 /api/cms/public 拉取 CMS 内容,失败则 fallback 到本地 mock
|
||||
* 不改动页面架构、路由、组件布局
|
||||
*/
|
||||
import type { FourValue, Service, Case, Insight, MethodCapability, AmbStatusMeta } from '@/models/types'
|
||||
import { FOUR_VALUES as FALLBACK_FOUR, AMB_STATUS_META as FALLBACK_AMB, SERVICES as FALLBACK_SERVICES, CASES as FALLBACK_CASES, INSIGHTS as FALLBACK_INSIGHTS, METHODS as FALLBACK_METHODS } from '@/data/mock'
|
||||
|
||||
const CMS_PUBLIC = (import.meta as any).env?.VITE_CMS_API || '/api/cms/public'
|
||||
|
||||
export interface CMSPublicData {
|
||||
config: any
|
||||
four_values: any[]
|
||||
amb_states: any[]
|
||||
services: any[]
|
||||
cases: any[]
|
||||
articles: any[]
|
||||
ten_systems: any[]
|
||||
home_sections: any[]
|
||||
ameba: any
|
||||
evolution_thought: any
|
||||
self_check: any
|
||||
}
|
||||
|
||||
let cache: CMSPublicData | null = null
|
||||
let pending: Promise<CMSPublicData | null> | null = null
|
||||
|
||||
export async function fetchCMSPublic(): Promise<CMSPublicData | null> {
|
||||
if (cache) return cache
|
||||
if (pending) return pending
|
||||
pending = fetch(CMS_PUBLIC).then(async r=>{
|
||||
if(!r.ok) throw new Error('cms fetch '+r.status)
|
||||
const j=await r.json()
|
||||
if(j.code!==0) throw new Error(j.msg)
|
||||
cache = j.data
|
||||
return cache
|
||||
}).catch(e=>{
|
||||
console.warn('[CMS] fallback to mock', e)
|
||||
return null
|
||||
}).finally(()=>{pending=null})
|
||||
return pending
|
||||
}
|
||||
|
||||
// 归一化:CMS行 -> 前端 Model
|
||||
function normalizeFourValues(raw:any[]): FourValue[]{
|
||||
if(!raw || raw.length===0) return FALLBACK_FOUR
|
||||
return raw.map(r=>({
|
||||
id: r.id,
|
||||
slug: r.key,
|
||||
key: r.key as any,
|
||||
name: r.name,
|
||||
title: r.name,
|
||||
summary: r.definition || r.description || '',
|
||||
description: r.description || r.definition || '',
|
||||
observationDimensions: r.definition ? [r.definition] : ['观察维度待配置'],
|
||||
status: (r.status as any)||'published',
|
||||
sortWeight: r.sort_order||0,
|
||||
cmsManaged: true,
|
||||
cmsId: r.id,
|
||||
createdAt: r.updated_at || new Date().toISOString(),
|
||||
updatedAt: r.updated_at || new Date().toISOString(),
|
||||
}))
|
||||
}
|
||||
function normalizeServices(raw:any[]): Service[]{
|
||||
if(!raw || raw.length===0) return FALLBACK_SERVICES
|
||||
return raw.map(r=>({
|
||||
id: r.id,
|
||||
slug: r.slug,
|
||||
title: r.name,
|
||||
summary: r.intro || r.service_content || '',
|
||||
category: 'other' as any,
|
||||
serviceType: (r.type as any)||'custom',
|
||||
price: r.price || undefined,
|
||||
priceUnit: r.price_unit || '/次',
|
||||
priceNote: r.price ? 'CMS配置价格' : undefined,
|
||||
ctaText: r.type==='standard' ? '获取方案' : '咨询/获取方案',
|
||||
ctaLink: '/contact',
|
||||
highlights: r.advantage ? [r.advantage] : (r.service_content ? [r.service_content] : []),
|
||||
status: (r.status as any)||'published',
|
||||
sortWeight: r.sort_weight||0,
|
||||
cmsManaged: true,
|
||||
cmsId: r.id,
|
||||
createdAt: r.created_at || new Date().toISOString(),
|
||||
updatedAt: r.updated_at || new Date().toISOString(),
|
||||
}))
|
||||
}
|
||||
function normalizeCases(raw:any[]): Case[]{
|
||||
if(!raw || raw.length===0) return FALLBACK_CASES
|
||||
return raw.map(r=>({
|
||||
id: r.id,
|
||||
slug: r.slug,
|
||||
title: r.title,
|
||||
summary: r.industry || '案例',
|
||||
industry: r.industry,
|
||||
status: (r.status as any)||'published',
|
||||
sortWeight: r.sort_weight||0,
|
||||
cmsManaged: true,
|
||||
cmsId: r.id,
|
||||
createdAt: r.created_at||new Date().toISOString(),
|
||||
updatedAt: r.updated_at||new Date().toISOString(),
|
||||
coverImage: r.cover_url,
|
||||
narrative: {
|
||||
originalState: r.original_state || '【占位】',
|
||||
goal: r.goal || '【占位】',
|
||||
gap: r.gap || '【占位】',
|
||||
rootCause: r.root_cause || '【占位】',
|
||||
amebaIntervention: r.ameba_intervention || '【占位】',
|
||||
evolutionPath: r.evolution_path || '【占位】',
|
||||
actions: r.actions || '【占位】',
|
||||
phasedResult: r.phased_result || '【占位】',
|
||||
continuousIteration: r.continuous_iteration || '【占位】',
|
||||
},
|
||||
results: []
|
||||
}))
|
||||
}
|
||||
function normalizeInsights(raw:any[]): Insight[]{
|
||||
if(!raw || raw.length===0) return FALLBACK_INSIGHTS
|
||||
return raw.map(r=>({
|
||||
id: r.id,
|
||||
slug: r.slug,
|
||||
title: r.title,
|
||||
summary: r.subtitle || '',
|
||||
category: (r.category as any)||'other',
|
||||
status: (r.status as any)||'published',
|
||||
sortWeight: r.recommend_weight||0,
|
||||
cmsManaged: true,
|
||||
cmsId: r.id,
|
||||
createdAt: r.created_at||new Date().toISOString(),
|
||||
updatedAt: r.updated_at||new Date().toISOString(),
|
||||
coverImage: r.cover_url,
|
||||
contentBlocks: r.content_html ? [{id:'b1', type:'paragraph' as const, text: r.content_html}] : [{id:'b1', type:'paragraph' as const, text: '【占位】'}],
|
||||
}))
|
||||
}
|
||||
function normalizeMethods(raw:any[]): MethodCapability[]{
|
||||
if(!raw || raw.length===0) return FALLBACK_METHODS
|
||||
return raw.map(r=>({
|
||||
id: r.id,
|
||||
slug: r.id,
|
||||
title: r.name,
|
||||
summary: r.description || '',
|
||||
group: 'method' as any,
|
||||
steps: r.description ? [r.description] : [],
|
||||
status: (r.status as any)||'published',
|
||||
sortWeight: r.sort_order||0,
|
||||
cmsManaged: true,
|
||||
cmsId: r.id,
|
||||
createdAt: r.updated_at||new Date().toISOString(),
|
||||
updatedAt: r.updated_at||new Date().toISOString(),
|
||||
}))
|
||||
}
|
||||
function normalizeAmb(raw:any[]): Record<string, AmbStatusMeta>{
|
||||
if(!raw || raw.length===0) return FALLBACK_AMB
|
||||
return raw.reduce((result, r) => {
|
||||
const status = String(r.key || '').toUpperCase() as AmbStatusMeta['status']
|
||||
if (!['A','M','B'].includes(status)) return result
|
||||
result[status] = {
|
||||
status,
|
||||
label: r.name || `${status} 状态`,
|
||||
description: r.description || r.definition || '',
|
||||
traits: [r.definition, r.fixed_note].filter(Boolean),
|
||||
}
|
||||
return result
|
||||
}, {} as Record<string, AmbStatusMeta>)
|
||||
}
|
||||
|
||||
export async function getCMSFourValues(): Promise<FourValue[]>{ const d=await fetchCMSPublic(); return normalizeFourValues(d?.four_values as any) }
|
||||
export async function getCMSServices(): Promise<Service[]>{ const d=await fetchCMSPublic(); return normalizeServices(d?.services as any) }
|
||||
export async function getCMSCases(): Promise<Case[]>{ const d=await fetchCMSPublic(); return normalizeCases(d?.cases as any) }
|
||||
export async function getCMSInsights(): Promise<Insight[]>{ const d=await fetchCMSPublic(); return normalizeInsights(d?.articles as any) }
|
||||
export async function getCMSMethods(): Promise<MethodCapability[]>{ const d=await fetchCMSPublic(); return normalizeMethods(d?.ten_systems as any) }
|
||||
export async function getCMSAmb(): Promise<Record<string, AmbStatusMeta>>{ const d=await fetchCMSPublic(); return normalizeAmb(d?.amb_states as any) }
|
||||
export async function getCMSConfig(){ const d=await fetchCMSPublic(); return d?.config || null }
|
||||
export async function getCMSAmeba(){ const d=await fetchCMSPublic(); return d?.ameba || null }
|
||||
|
||||
// Hook for React
|
||||
import { useEffect, useState } from 'react'
|
||||
export function useCMS<T>(loader: ()=>Promise<T>, fallback: T){
|
||||
const [data,setData]=useState<T>(fallback)
|
||||
const [loading,setLoading]=useState(true)
|
||||
useEffect(()=>{
|
||||
let mounted=true
|
||||
loader().then(v=>{ if(mounted) setData(v)}).finally(()=> mounted && setLoading(false))
|
||||
return ()=>{mounted=false}
|
||||
},[])
|
||||
return {data, loading}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
:root {
|
||||
--primary: #163E70;
|
||||
--primary-light: #1e5a9e;
|
||||
--accent: #0f6b5b;
|
||||
--bg: #F5F7FA;
|
||||
--bg-white: #ffffff;
|
||||
--text: #1a2332;
|
||||
--text-secondary: #5a6b84;
|
||||
--text-muted: #8a9ab0;
|
||||
--border: #e2e8f0;
|
||||
--radius: 8px;
|
||||
--shadow: 0 2px 12px rgba(22, 62, 112, 0.06);
|
||||
--max-width: 1120px;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
html { scroll-behavior: smooth; }
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Microsoft YaHei", "Helvetica Neue", Arial, sans-serif;
|
||||
color: var(--text);
|
||||
background: var(--bg);
|
||||
line-height: 1.7;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
a { color: var(--primary); text-decoration: none; }
|
||||
a:hover { color: var(--primary-light); }
|
||||
img { max-width: 100%; display: block; }
|
||||
|
||||
/* Utility */
|
||||
.container {
|
||||
max-width: var(--max-width);
|
||||
margin: 0 auto;
|
||||
padding: 0 24px;
|
||||
}
|
||||
.section {
|
||||
padding: 64px 0;
|
||||
}
|
||||
.section-muted { background: var(--bg-white); }
|
||||
.eyebrow {
|
||||
font-size: 12px;
|
||||
letter-spacing: 0.12em;
|
||||
color: var(--accent);
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.h2 {
|
||||
font-size: 28px;
|
||||
font-weight: 800;
|
||||
color: var(--primary);
|
||||
line-height: 1.3;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.desc {
|
||||
font-size: 15px;
|
||||
color: var(--text-secondary);
|
||||
max-width: 720px;
|
||||
}
|
||||
.placeholder {
|
||||
background: #fff7e6;
|
||||
border: 1px dashed #f59e0b;
|
||||
color: #92400e;
|
||||
padding: 12px 16px;
|
||||
border-radius: 8px;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.placeholder strong { color: #b45309; }
|
||||
|
||||
/* Cards */
|
||||
.card {
|
||||
background: var(--bg-white);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 12px;
|
||||
padding: 24px;
|
||||
box-shadow: var(--shadow);
|
||||
transition: box-shadow 0.2s;
|
||||
}
|
||||
.card:hover { box-shadow: 0 6px 24px rgba(22,62,112,0.08); }
|
||||
.card h3 { font-size: 17px; color: var(--primary); margin-bottom: 8px; }
|
||||
.card p { font-size: 14px; color: var(--text-secondary); }
|
||||
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
padding: 10px 20px;
|
||||
border-radius: 999px;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
border: 1px solid transparent;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.btn-primary { background: var(--primary); color: #fff; }
|
||||
.btn-primary:hover { background: var(--primary-light); color: #fff; }
|
||||
.btn-secondary { background: #fff; color: var(--primary); border-color: var(--primary); }
|
||||
.btn-secondary:hover { background: var(--primary); color: #fff; }
|
||||
.btn-ghost { background: transparent; color: var(--primary); border-color: var(--border); }
|
||||
.btn-ghost:hover { border-color: var(--primary); }
|
||||
|
||||
/* Grid helpers */
|
||||
.grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; }
|
||||
.grid-2 { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; }
|
||||
@media (max-width: 900px) {
|
||||
.grid-3, .grid-2 { grid-template-columns: 1fr; }
|
||||
.container { padding: 0 16px; }
|
||||
.section { padding: 40px 0; }
|
||||
.h2 { font-size: 22px; }
|
||||
}
|
||||
|
||||
/* Foldable for mobile complex theory */
|
||||
.foldable-trigger {
|
||||
display: none;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
background: #fff;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
padding: 12px 16px;
|
||||
font-weight: 700;
|
||||
color: var(--primary);
|
||||
cursor: pointer;
|
||||
}
|
||||
.foldable-content { }
|
||||
@media (max-width: 768px) {
|
||||
.foldable-trigger { display: flex; justify-content: space-between; align-items: center; }
|
||||
.foldable-content.collapsed { display: none; }
|
||||
.foldable-content.expanded { display: block; margin-top: 12px; }
|
||||
}
|
||||
|
||||
/* Page header */
|
||||
.page-header {
|
||||
background: linear-gradient(135deg, #163E70 0%, #0f6b5b 100%);
|
||||
color: #fff;
|
||||
padding: 56px 0 48px;
|
||||
}
|
||||
.page-header h1 { font-size: 32px; font-weight: 900; margin-bottom: 10px; }
|
||||
.page-header p { color: rgba(255,255,255,0.85); max-width: 720px; font-size: 15px; }
|
||||
.page-header .placeholder { background: rgba(255,255,255,0.12); border-color: rgba(255,255,255,0.3); color: #fff; }
|
||||
@media (max-width: 768px) { .page-header h1 { font-size: 24px; } .page-header { padding: 32px 0; } }
|
||||
|
||||
/* Breadcrumb */
|
||||
.breadcrumb { font-size: 13px; color: var(--text-muted); margin-bottom: 12px; }
|
||||
.breadcrumb a { color: var(--text-muted); }
|
||||
.breadcrumb a:hover { color: var(--primary); }
|
||||
|
||||
/* CTA slot */
|
||||
.cta-band {
|
||||
background: var(--primary);
|
||||
color: #fff;
|
||||
padding: 28px 0;
|
||||
}
|
||||
.cta-band .container { display: flex; align-items: center; justify-content: space-between; gap: 16px; flex-wrap: wrap; }
|
||||
.cta-band p { color: rgba(255,255,255,0.9); font-size: 15px; }
|
||||
@media (max-width: 768px) { .cta-band .container { flex-direction: column; align-items: flex-start; } }
|
||||
|
||||
/* Sticky mobile CTA */
|
||||
.mobile-sticky-cta {
|
||||
display: none;
|
||||
position: fixed;
|
||||
bottom: 0; left: 0; right: 0;
|
||||
background: #fff;
|
||||
border-top: 1px solid var(--border);
|
||||
padding: 10px 16px;
|
||||
padding-bottom: calc(10px + env(safe-area-inset-bottom));
|
||||
z-index: 99;
|
||||
box-shadow: 0 -4px 20px rgba(0,0,0,0.06);
|
||||
}
|
||||
.mobile-sticky-cta .btn { flex: 1; }
|
||||
@media (max-width: 768px) {
|
||||
.mobile-sticky-cta { display: flex; gap: 10px; }
|
||||
body { padding-bottom: 64px; }
|
||||
}
|
||||
Reference in New Issue
Block a user