Restore news and community sections below product matrix (#537)
This commit is contained in:
parent
ef077917ae
commit
98609cb9b0
@ -5,8 +5,6 @@ import Navbar from '@components/Navbar'
|
||||
import { AskAIButton } from '@components/AskAIButton'
|
||||
|
||||
import ArticleFeed from '@components/home/ArticleFeed'
|
||||
import ContactPanel from '@components/home/ContactPanel'
|
||||
import HeroBanner from '@components/home/HeroBanner'
|
||||
import ProductMatrix from '@components/home/ProductMatrix'
|
||||
import Sidebar from '@components/home/Sidebar'
|
||||
|
||||
@ -14,17 +12,19 @@ export default function HomePage() {
|
||||
return (
|
||||
<>
|
||||
<Navbar />
|
||||
<main className="bg-slate-50 pb-16 pt-24">
|
||||
<HeroBanner />
|
||||
<section className="relative z-10 -mt-12 px-4 sm:-mt-20">
|
||||
<div className="mx-auto max-w-6xl">
|
||||
<div className="grid gap-8 lg:grid-cols-[minmax(0,2fr)_minmax(0,1fr)]">
|
||||
<div className="space-y-8">
|
||||
<ProductMatrix />
|
||||
<main className="bg-slate-950">
|
||||
<section className="pb-24 pt-24">
|
||||
<div className="px-4">
|
||||
<div className="mx-auto max-w-6xl">
|
||||
<ProductMatrix />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section className="bg-slate-50 pb-16 pt-20">
|
||||
<div className="px-4">
|
||||
<div className="mx-auto max-w-6xl">
|
||||
<div className="grid gap-8 lg:grid-cols-[minmax(0,2fr)_minmax(0,1fr)]">
|
||||
<ArticleFeed />
|
||||
</div>
|
||||
<div className="space-y-6">
|
||||
<ContactPanel />
|
||||
<Sidebar />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -10,13 +10,11 @@ import {
|
||||
X,
|
||||
type LucideIcon,
|
||||
} from 'lucide-react'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { useMemo, useState } from 'react'
|
||||
import clsx from 'clsx'
|
||||
|
||||
import type { ContactPanelContent, ContactItemContent } from '@lib/homepageContent'
|
||||
|
||||
const STORAGE_KEY = 'xcontrol-homepage-contact-collapsed'
|
||||
|
||||
const iconMap: Record<string, LucideIcon> = {
|
||||
mail: Mail,
|
||||
'life-buoy': LifeBuoy,
|
||||
@ -157,17 +155,6 @@ function InfoCard({ item }: { item: ContactItemContent }) {
|
||||
export default function ContactPanelClient({ panel, className }: ContactPanelClientProps) {
|
||||
const [collapsed, setCollapsed] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
const stored = window.localStorage.getItem(STORAGE_KEY)
|
||||
if (stored === 'true') {
|
||||
setCollapsed(true)
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
window.localStorage.setItem(STORAGE_KEY, collapsed ? 'true' : 'false')
|
||||
}, [collapsed])
|
||||
|
||||
if (!panel.items.length) {
|
||||
return null
|
||||
}
|
||||
|
||||
@ -143,7 +143,8 @@ export default function HeroProductTabs({ items }: HeroProductTabsProps) {
|
||||
/>
|
||||
) : null}
|
||||
{(activeItem.primaryCtaLabel && activeItem.primaryCtaHref) ||
|
||||
(activeItem.secondaryCtaLabel && activeItem.secondaryCtaHref) ? (
|
||||
(activeItem.secondaryCtaLabel && activeItem.secondaryCtaHref) ||
|
||||
(activeItem.tertiaryCtaLabel && activeItem.tertiaryCtaHref) ? (
|
||||
<div className="mt-6 flex flex-wrap gap-3">
|
||||
{activeItem.primaryCtaLabel && activeItem.primaryCtaHref ? (
|
||||
<Link
|
||||
@ -163,6 +164,15 @@ export default function HeroProductTabs({ items }: HeroProductTabsProps) {
|
||||
{activeItem.secondaryCtaLabel}
|
||||
</Link>
|
||||
) : null}
|
||||
{activeItem.tertiaryCtaLabel && activeItem.tertiaryCtaHref ? (
|
||||
<Link
|
||||
prefetch={false}
|
||||
href={activeItem.tertiaryCtaHref}
|
||||
className="inline-flex items-center justify-center rounded-full border border-white/10 px-5 py-2 text-sm font-semibold text-slate-100 transition hover:border-white/40"
|
||||
>
|
||||
{activeItem.tertiaryCtaLabel}
|
||||
</Link>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
@ -1,14 +1,8 @@
|
||||
import Link from 'next/link'
|
||||
import ContactPanel from './ContactPanel'
|
||||
import ProductMatrixClient from './ProductMatrixClient'
|
||||
|
||||
import { getHeroSolutions } from '@lib/homepageContent'
|
||||
|
||||
function truncate(text: string, maxLength: number) {
|
||||
if (text.length <= maxLength) {
|
||||
return text
|
||||
}
|
||||
return `${text.slice(0, maxLength - 1)}…`
|
||||
}
|
||||
|
||||
export default async function ProductMatrix() {
|
||||
const solutions = await getHeroSolutions()
|
||||
|
||||
@ -17,75 +11,9 @@ export default async function ProductMatrix() {
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="space-y-6">
|
||||
<header className="flex flex-col gap-2 sm:flex-row sm:items-end sm:justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-semibold uppercase tracking-[0.2em] text-sky-600">产品矩阵</p>
|
||||
<h2 className="text-2xl font-semibold text-slate-900 sm:text-3xl">旗舰能力一览</h2>
|
||||
</div>
|
||||
<Link href="/docs" className="text-sm font-medium text-sky-600 hover:text-sky-700">
|
||||
查看全部方案 →
|
||||
</Link>
|
||||
</header>
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
{solutions.map((solution) => (
|
||||
<article
|
||||
key={solution.slug}
|
||||
className="group relative overflow-hidden rounded-3xl border border-slate-200 bg-white p-6 shadow-sm transition hover:-translate-y-1 hover:shadow-lg sm:p-7"
|
||||
>
|
||||
<div className="absolute inset-x-0 top-0 h-1 bg-gradient-to-r from-sky-400 via-cyan-400 to-indigo-400 opacity-0 transition group-hover:opacity-100" aria-hidden />
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<h3 className="text-xl font-semibold text-slate-900 transition group-hover:text-sky-600">
|
||||
{solution.title}
|
||||
</h3>
|
||||
{solution.tagline ? (
|
||||
<p className="mt-1 text-sm text-slate-500">{truncate(solution.tagline, 60)}</p>
|
||||
) : null}
|
||||
</div>
|
||||
{solution.features.length ? (
|
||||
<ul className="space-y-2 text-sm text-slate-600">
|
||||
{solution.features.slice(0, 3).map((feature) => (
|
||||
<li key={feature} className="flex items-start gap-2">
|
||||
<span className="mt-1 inline-block h-1.5 w-1.5 flex-shrink-0 rounded-full bg-sky-400" aria-hidden />
|
||||
<span>{feature}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
) : null}
|
||||
{solution.bodyHtml ? (
|
||||
<div
|
||||
className="prose prose-sm max-w-none text-slate-600"
|
||||
dangerouslySetInnerHTML={{ __html: solution.bodyHtml }}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
{(solution.primaryCtaLabel && solution.primaryCtaHref) ||
|
||||
(solution.secondaryCtaLabel && solution.secondaryCtaHref) ? (
|
||||
<div className="mt-5 flex flex-wrap gap-3">
|
||||
{solution.primaryCtaLabel && solution.primaryCtaHref ? (
|
||||
<Link
|
||||
prefetch={false}
|
||||
href={solution.primaryCtaHref}
|
||||
className="inline-flex items-center justify-center rounded-full bg-sky-500 px-4 py-2 text-sm font-semibold text-white shadow-sm transition hover:bg-sky-400"
|
||||
>
|
||||
{solution.primaryCtaLabel}
|
||||
</Link>
|
||||
) : null}
|
||||
{solution.secondaryCtaLabel && solution.secondaryCtaHref ? (
|
||||
<Link
|
||||
prefetch={false}
|
||||
href={solution.secondaryCtaHref}
|
||||
className="inline-flex items-center justify-center rounded-full border border-slate-200 px-4 py-2 text-sm font-semibold text-slate-700 transition hover:border-slate-300"
|
||||
>
|
||||
{solution.secondaryCtaLabel}
|
||||
</Link>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
<div className="grid gap-8 lg:grid-cols-[minmax(0,2.5fr)_minmax(0,1fr)] lg:items-start">
|
||||
<ProductMatrixClient solutions={solutions} />
|
||||
<ContactPanel className="lg:sticky lg:top-6" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
199
ui/homepage/components/home/ProductMatrixClient.tsx
Normal file
199
ui/homepage/components/home/ProductMatrixClient.tsx
Normal file
@ -0,0 +1,199 @@
|
||||
'use client'
|
||||
|
||||
import Link from 'next/link'
|
||||
import { useMemo, useState } from 'react'
|
||||
import clsx from 'clsx'
|
||||
|
||||
import type { HeroSolution } from '@lib/homepageContent'
|
||||
|
||||
const OVERVIEW_HIGHLIGHTS = [
|
||||
'跨集群与多云环境的一体化策略治理',
|
||||
'以策略为核心的安全与合规自动化',
|
||||
'将标准化模板加速落地业务流程',
|
||||
]
|
||||
|
||||
type ProductMatrixClientProps = {
|
||||
solutions: HeroSolution[]
|
||||
}
|
||||
|
||||
export default function ProductMatrixClient({ solutions }: ProductMatrixClientProps) {
|
||||
const [activeIndex, setActiveIndex] = useState(0)
|
||||
|
||||
const activeSolution = useMemo(() => solutions[activeIndex] ?? solutions[0], [solutions, activeIndex])
|
||||
|
||||
if (!solutions.length || !activeSolution) {
|
||||
return null
|
||||
}
|
||||
|
||||
const ctas = [
|
||||
activeSolution.primaryCtaLabel && activeSolution.primaryCtaHref
|
||||
? {
|
||||
label: activeSolution.primaryCtaLabel,
|
||||
href: activeSolution.primaryCtaHref,
|
||||
variant: 'primary' as const,
|
||||
}
|
||||
: null,
|
||||
activeSolution.secondaryCtaLabel && activeSolution.secondaryCtaHref
|
||||
? {
|
||||
label: activeSolution.secondaryCtaLabel,
|
||||
href: activeSolution.secondaryCtaHref,
|
||||
variant: 'secondary' as const,
|
||||
}
|
||||
: null,
|
||||
activeSolution.tertiaryCtaLabel && activeSolution.tertiaryCtaHref
|
||||
? {
|
||||
label: activeSolution.tertiaryCtaLabel,
|
||||
href: activeSolution.tertiaryCtaHref,
|
||||
variant: 'ghost' as const,
|
||||
}
|
||||
: null,
|
||||
].filter(Boolean) as Array<{ label: string; href: string; variant: 'primary' | 'secondary' | 'ghost' }>
|
||||
|
||||
return (
|
||||
<section className="space-y-8">
|
||||
<div className="relative overflow-hidden rounded-3xl border border-slate-800/60 bg-slate-950/90 p-8 text-slate-100 shadow-2xl shadow-sky-900/30 lg:p-10">
|
||||
<div className="absolute inset-0 -z-10 bg-[radial-gradient(circle_at_top,_rgba(56,189,248,0.25),_transparent_65%)]" aria-hidden />
|
||||
<header className="space-y-3">
|
||||
<span className="text-xs font-semibold uppercase tracking-[0.4em] text-sky-300/80">云原生运营中心</span>
|
||||
<h1 className="text-3xl font-semibold leading-tight text-white sm:text-4xl">
|
||||
打造一体化的 XControl 控制平面
|
||||
</h1>
|
||||
<p className="text-sm text-slate-300 sm:text-base">
|
||||
将资产管理、访问控制、可观测与自动化工作流整合到一个响应迅速的体验里,帮助团队高效落地治理策略。
|
||||
</p>
|
||||
</header>
|
||||
<ul className="mt-6 grid gap-3 text-sm text-slate-200 sm:grid-cols-3">
|
||||
{OVERVIEW_HIGHLIGHTS.map((highlight) => (
|
||||
<li
|
||||
key={highlight}
|
||||
className="flex items-start gap-3 rounded-2xl border border-white/10 bg-white/5 p-3"
|
||||
>
|
||||
<span className="mt-1 h-2 w-2 flex-shrink-0 rounded-full bg-sky-400" aria-hidden />
|
||||
<span>{highlight}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<div className="mt-8 rounded-3xl border border-white/10 bg-white/5 p-4">
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{solutions.map((solution, index) => {
|
||||
const isActive = index === activeIndex
|
||||
return (
|
||||
<button
|
||||
key={solution.slug}
|
||||
type="button"
|
||||
onClick={() => setActiveIndex(index)}
|
||||
className={clsx(
|
||||
'flex min-w-[9rem] flex-1 items-center justify-between rounded-2xl border px-4 py-3 text-left text-sm font-semibold transition focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-sky-200/80',
|
||||
isActive
|
||||
? 'border-sky-300/80 bg-sky-300/90 text-slate-900 shadow-lg shadow-sky-500/30'
|
||||
: 'border-white/10 bg-white/5 text-slate-100 hover:border-white/30 hover:bg-white/10',
|
||||
)}
|
||||
>
|
||||
<span className="flex-1">{solution.title}</span>
|
||||
<span
|
||||
className={clsx(
|
||||
'ml-2 text-xs font-medium transition',
|
||||
isActive ? 'text-slate-800/80' : 'text-slate-200/60',
|
||||
)}
|
||||
>
|
||||
{solution.tagline}
|
||||
</span>
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
<div className="mt-6 grid gap-6 lg:grid-cols-[minmax(0,1.5fr)_minmax(0,1fr)]">
|
||||
<div className="space-y-4">
|
||||
{activeSolution.tagline ? (
|
||||
<p className="text-xs font-semibold uppercase tracking-[0.3em] text-sky-200/80">
|
||||
{activeSolution.tagline}
|
||||
</p>
|
||||
) : null}
|
||||
<h2 className="text-3xl font-semibold text-white sm:text-4xl">{activeSolution.title}</h2>
|
||||
{activeSolution.description ? (
|
||||
<div className="space-y-2">
|
||||
<p className="text-xs font-semibold uppercase tracking-[0.35em] text-sky-200/70">产品文案</p>
|
||||
<p className="text-sm text-slate-200/90 sm:text-base">{activeSolution.description}</p>
|
||||
</div>
|
||||
) : null}
|
||||
{activeSolution.bodyHtml ? (
|
||||
<div
|
||||
className="prose prose-invert max-w-none text-sm text-slate-200/90 [&_strong]:text-white"
|
||||
dangerouslySetInnerHTML={{ __html: activeSolution.bodyHtml }}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="space-y-5">
|
||||
{activeSolution.features.length ? (
|
||||
<div className="space-y-3">
|
||||
<p className="text-xs font-semibold uppercase tracking-[0.35em] text-sky-200/70">能力速览</p>
|
||||
<ul className="space-y-2 text-sm text-slate-100">
|
||||
{activeSolution.features.map((feature) => (
|
||||
<li key={feature} className="flex items-start gap-3">
|
||||
<span className="mt-1 h-1.5 w-1.5 flex-shrink-0 rounded-full bg-sky-400" aria-hidden />
|
||||
<span>{feature}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
) : null}
|
||||
{ctas.length ? (
|
||||
<div className="flex flex-wrap gap-3">
|
||||
{ctas.map(({ href, label, variant }) => (
|
||||
<Link
|
||||
key={label}
|
||||
prefetch={false}
|
||||
href={href}
|
||||
className={clsx(
|
||||
'inline-flex items-center justify-center rounded-full px-5 py-2 text-sm font-semibold transition',
|
||||
variant === 'primary'
|
||||
? 'bg-sky-400 text-slate-950 shadow-lg shadow-sky-500/40 hover:bg-sky-300'
|
||||
: variant === 'secondary'
|
||||
? 'border border-white/40 text-white hover:border-white'
|
||||
: 'border border-white/10 text-slate-100 hover:border-white/40',
|
||||
)}
|
||||
>
|
||||
{label}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
{solutions.map((solution, index) => {
|
||||
const isActive = index === activeIndex
|
||||
return (
|
||||
<button
|
||||
key={solution.slug}
|
||||
type="button"
|
||||
onClick={() => setActiveIndex(index)}
|
||||
className={clsx(
|
||||
'relative overflow-hidden rounded-3xl border bg-white/5 p-6 text-left transition focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-sky-200/80',
|
||||
isActive
|
||||
? 'border-sky-400/80 bg-sky-400/10 text-white shadow-lg shadow-sky-900/40'
|
||||
: 'border-white/10 text-slate-200 hover:border-white/30 hover:bg-white/10',
|
||||
)}
|
||||
>
|
||||
<p className="text-sm font-semibold uppercase tracking-[0.25em] text-sky-300/80">{solution.tagline}</p>
|
||||
<h3 className="mt-3 text-xl font-semibold text-white">{solution.title}</h3>
|
||||
{solution.description ? (
|
||||
<p className="mt-2 text-sm text-slate-200/80">{solution.description}</p>
|
||||
) : null}
|
||||
<span
|
||||
className={clsx(
|
||||
'mt-4 inline-flex items-center text-sm font-semibold',
|
||||
isActive ? 'text-sky-200' : 'text-sky-300/90',
|
||||
)}
|
||||
>
|
||||
{isActive ? '正在专题展示' : '点击专题展示'}
|
||||
</span>
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@ -3,10 +3,12 @@ title: XCloudFlow
|
||||
tagline: 多云 IaC
|
||||
order: 1
|
||||
description: 通过声明式模型统一编排多云基础设施,自动化落地资源策略与合规标准。
|
||||
primaryCtaLabel: 了解 XCloudFlow
|
||||
primaryCtaHref: /products/xcloudflow
|
||||
secondaryCtaLabel: 产品文档
|
||||
secondaryCtaHref: /docs/xcloudflow
|
||||
primaryCtaLabel: 立刻体验
|
||||
primaryCtaHref: /demo?product=xcloudflow
|
||||
secondaryCtaLabel: 下载链接
|
||||
secondaryCtaHref: /download?product=xcloudflow
|
||||
tertiaryCtaLabel: 文档链接
|
||||
tertiaryCtaHref: /docs/xcloudflow
|
||||
features:
|
||||
- 跨云资源蓝图与参数化交付
|
||||
- GitOps 工作流驱动基础设施变更
|
||||
|
||||
@ -3,10 +3,12 @@ title: XControl 平台
|
||||
tagline: 云原生治理中枢
|
||||
order: 3
|
||||
description: 为多团队提供统一的权限、策略与工作流编排,让交付与治理协同无缝衔接。
|
||||
primaryCtaLabel: 申请试用
|
||||
primaryCtaHref: /trial
|
||||
secondaryCtaLabel: 查看能力矩阵
|
||||
secondaryCtaHref: /products/xcontrol#capabilities
|
||||
primaryCtaLabel: 立刻体验
|
||||
primaryCtaHref: /demo?product=xcontrol
|
||||
secondaryCtaLabel: 下载链接
|
||||
secondaryCtaHref: /download?product=xcontrol
|
||||
tertiaryCtaLabel: 文档链接
|
||||
tertiaryCtaHref: /docs/xcontrol
|
||||
features:
|
||||
- 一站式权限与合规策略中心
|
||||
- 工作流自动化驱动跨团队协作
|
||||
|
||||
@ -3,10 +3,12 @@ title: XScopeHub
|
||||
tagline: AI & 可观察性
|
||||
order: 2
|
||||
description: 利用 AI 驱动的分析工作台,统一日志、指标与追踪,快速定位异常并推荐修复路径。
|
||||
primaryCtaLabel: 探索 XScopeHub
|
||||
primaryCtaHref: /products/xscopehub
|
||||
secondaryCtaLabel: 体验 Demo
|
||||
secondaryCtaHref: /demo/xscopehub
|
||||
primaryCtaLabel: 立刻体验
|
||||
primaryCtaHref: /demo?product=xscopehub
|
||||
secondaryCtaLabel: 下载链接
|
||||
secondaryCtaHref: /download?product=xscopehub
|
||||
tertiaryCtaLabel: 文档链接
|
||||
tertiaryCtaHref: /docs/xscopehub
|
||||
features:
|
||||
- 全栈可观察性数据联邦检索
|
||||
- 智能告警关联与根因分析
|
||||
|
||||
@ -3,10 +3,12 @@ title: XStream
|
||||
tagline: 网络加速器
|
||||
order: 4
|
||||
description: 按需构建全球传输网络,保障跨地域应用与数据同步的稳定低时延体验。
|
||||
primaryCtaLabel: 查看加速方案
|
||||
primaryCtaHref: /products/xstream
|
||||
secondaryCtaLabel: 下载白皮书
|
||||
secondaryCtaHref: /resources/xstream-whitepaper
|
||||
primaryCtaLabel: 立刻体验
|
||||
primaryCtaHref: /demo?product=xstream
|
||||
secondaryCtaLabel: 下载链接
|
||||
secondaryCtaHref: /download?product=xstream
|
||||
tertiaryCtaLabel: 文档链接
|
||||
tertiaryCtaHref: /docs/xstream
|
||||
features:
|
||||
- 动态最优路径与带宽调度
|
||||
- 内置零信任安全与访问控制
|
||||
|
||||
@ -26,6 +26,8 @@ export interface HeroSolution {
|
||||
primaryCtaHref?: string
|
||||
secondaryCtaLabel?: string
|
||||
secondaryCtaHref?: string
|
||||
tertiaryCtaLabel?: string
|
||||
tertiaryCtaHref?: string
|
||||
}
|
||||
|
||||
export interface HomepagePost {
|
||||
@ -153,6 +155,8 @@ export async function getHeroSolutions(): Promise<HeroSolution[]> {
|
||||
primaryCtaHref: ensureString(file.metadata.primaryCtaHref),
|
||||
secondaryCtaLabel: ensureString(file.metadata.secondaryCtaLabel),
|
||||
secondaryCtaHref: ensureString(file.metadata.secondaryCtaHref),
|
||||
tertiaryCtaLabel: ensureString(file.metadata.tertiaryCtaLabel),
|
||||
tertiaryCtaHref: ensureString(file.metadata.tertiaryCtaHref),
|
||||
order: ensureNumber(file.metadata.order),
|
||||
}))
|
||||
} catch (error) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user