refactor(home): de-productize hero visual to abstract data-driven graphic
- 移除「睿新·经营驾驶舱」产品 UI 外壳(窗口头/侧边栏/表格/产品名) - Hero 视觉改为抽象数据可视化(KPI + 趋势图),眉标「数据驱动 · 让转型可量化」 - 对齐 IHG/字节式品牌矩阵定位:能力表达而非产品展示 - 首页单测 6/6 + 视觉基线三端更新,type-check / lint 0 errors
|
Before Width: | Height: | Size: 751 KiB After Width: | Height: | Size: 724 KiB |
|
Before Width: | Height: | Size: 751 KiB After Width: | Height: | Size: 724 KiB |
|
Before Width: | Height: | Size: 751 KiB After Width: | Height: | Size: 724 KiB |
|
Before Width: | Height: | Size: 2.0 MiB After Width: | Height: | Size: 2.4 MiB |
|
Before Width: | Height: | Size: 1.9 MiB After Width: | Height: | Size: 2.4 MiB |
|
Before Width: | Height: | Size: 1.9 MiB After Width: | Height: | Size: 2.4 MiB |
|
Before Width: | Height: | Size: 1.6 MiB After Width: | Height: | Size: 1.9 MiB |
|
Before Width: | Height: | Size: 1.6 MiB After Width: | Height: | Size: 1.9 MiB |
|
Before Width: | Height: | Size: 1.6 MiB After Width: | Height: | Size: 1.9 MiB |
@@ -86,11 +86,11 @@ jest.mock('lucide-react', () => {
|
||||
});
|
||||
|
||||
describe('HomeContentV14 - 首页 Hero 优化', () => {
|
||||
it('renders an SVG product visual with an interface disclaimer', () => {
|
||||
it('renders an abstract data-driven visual without product UI shell', () => {
|
||||
render(<HomeContentV14 />);
|
||||
|
||||
expect(screen.getByTestId('hero-product-visual')).toBeInTheDocument();
|
||||
expect(screen.getByText('产品界面示意')).toBeInTheDocument();
|
||||
expect(screen.getByText('数据驱动 · 让转型可量化')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('keeps a single primary CTA and demotes the secondary action to a text link', () => {
|
||||
|
||||
@@ -1,26 +1,7 @@
|
||||
import {
|
||||
LayoutDashboard,
|
||||
BarChart3,
|
||||
Package,
|
||||
Users,
|
||||
Settings,
|
||||
Search,
|
||||
Bell,
|
||||
TrendingUp,
|
||||
AlertCircle,
|
||||
ArrowUpRight,
|
||||
ArrowDownRight,
|
||||
} from 'lucide-react';
|
||||
import { TrendingUp, ArrowUpRight, ArrowDownRight } from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
const NAV_ITEMS = [
|
||||
{ label: '经营驾驶舱', icon: LayoutDashboard, active: true },
|
||||
{ label: '财务管理', icon: BarChart3 },
|
||||
{ label: '供应链', icon: Package },
|
||||
{ label: '客户管理', icon: Users },
|
||||
{ label: '系统设置', icon: Settings },
|
||||
];
|
||||
|
||||
// 抽象数据可视化示意(示例数据)——作为品牌「数据驱动」能力的视觉表达,不含具体产品 UI 外壳
|
||||
const KPIS = [
|
||||
{ label: '营收同比', value: '+32.6%', trend: 'up', note: '较上期' },
|
||||
{ label: '订单履约率', value: '98.2%', trend: 'up', note: '目标 95%' },
|
||||
@@ -28,18 +9,6 @@ const KPIS = [
|
||||
{ label: '客户新增', value: '+18.4%', trend: 'up', note: '本月' },
|
||||
] as const;
|
||||
|
||||
const ALERTS = [
|
||||
{ title: '华东区库存预警', desc: '3 个 SKU 低于安全库存', level: 'high' },
|
||||
{ title: '大客户合同待审批', desc: '2 份合同等待审批', level: 'medium' },
|
||||
{ title: '本月目标完成 82%', desc: '距离目标还有 18%', level: 'normal' },
|
||||
];
|
||||
|
||||
const TABLE_ROWS = [
|
||||
{ name: '成都某制造集团', amount: '¥ 1,280,000', status: '已签约', date: '今日' },
|
||||
{ name: '华南零售连锁', amount: '¥ 860,000', status: '洽谈中', date: '昨日' },
|
||||
{ name: '华东物流企业', amount: '¥ 420,000', status: '方案确认', date: '07-18' },
|
||||
];
|
||||
|
||||
function TrendBadge({ trend, label }: { trend: 'up' | 'down'; label: string }) {
|
||||
const Icon = trend === 'up' ? ArrowUpRight : ArrowDownRight;
|
||||
return (
|
||||
@@ -50,209 +19,70 @@ function TrendBadge({ trend, label }: { trend: 'up' | 'down'; label: string }) {
|
||||
);
|
||||
}
|
||||
|
||||
function StatusBadge({ status }: { status: string }) {
|
||||
const tone =
|
||||
status === '已签约'
|
||||
? 'bg-success-bg text-success'
|
||||
: status === '洽谈中'
|
||||
? 'bg-brand-bg text-brand'
|
||||
: 'bg-bg-secondary text-text-secondary';
|
||||
return (
|
||||
<span className={cn('inline-flex rounded px-1.5 py-0.5 text-[10px] font-semibold', tone)}>
|
||||
{status}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
export function HeroProductVisual({ className }: { className?: string }) {
|
||||
return (
|
||||
<div
|
||||
data-testid="hero-product-visual"
|
||||
role="img"
|
||||
aria-label="睿新经营驾驶舱界面示意(示例数据)"
|
||||
aria-label="数据驱动可视化示意(示例数据)"
|
||||
className={cn('relative', className)}
|
||||
>
|
||||
<div aria-hidden="true" className="overflow-hidden rounded-xl border border-border-primary bg-white shadow-xl">
|
||||
{/* 应用窗口头 */}
|
||||
<div className="flex items-center justify-between border-b border-border-primary bg-bg-secondary px-4 py-2.5">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex gap-1.5" aria-hidden="true">
|
||||
<span className="h-2.5 w-2.5 rounded-full bg-border-secondary" />
|
||||
<span className="h-2.5 w-2.5 rounded-full bg-border-secondary" />
|
||||
<span className="h-2.5 w-2.5 rounded-full bg-brand/60" />
|
||||
</div>
|
||||
<span className="text-sm font-semibold text-ink">睿新 · 经营驾驶舱</span>
|
||||
</div>
|
||||
<span className="border border-border-primary bg-white px-2 py-0.5 text-[11px] font-medium text-text-muted">
|
||||
产品界面示意
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex">
|
||||
{/* 侧边导航(桌面) */}
|
||||
<aside className="hidden w-40 shrink-0 flex-col border-r border-border-primary bg-bg-secondary/60 p-3 lg:flex" aria-hidden="true">
|
||||
<div className="mb-5 border-b border-border-primary px-2 pb-4">
|
||||
<div className="text-sm font-black text-ink">睿新</div>
|
||||
<div className="text-[10px] font-medium tracking-[0.18em] text-text-muted">NOVALON</div>
|
||||
</div>
|
||||
<nav className="space-y-1">
|
||||
{NAV_ITEMS.map((item) => {
|
||||
const Icon = item.icon;
|
||||
return (
|
||||
<div
|
||||
key={item.label}
|
||||
className={cn(
|
||||
'flex items-center gap-2 rounded px-2 py-1.5 text-xs font-medium',
|
||||
item.active
|
||||
? 'bg-brand-bg text-brand'
|
||||
: 'text-text-secondary'
|
||||
)}
|
||||
>
|
||||
<Icon className="h-3.5 w-3.5" />
|
||||
{item.label}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
<div className="mt-auto rounded border border-border-primary bg-white p-2.5">
|
||||
<div className="text-[10px] font-semibold text-ink">私有化部署</div>
|
||||
<div className="mt-0.5 text-[10px] leading-snug text-text-muted">数据不出境 · 满足合规</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
{/* 主内容区 */}
|
||||
<div className="min-w-0 flex-1 p-3 sm:p-4">
|
||||
{/* 顶部操作栏 */}
|
||||
<div className="mb-3 flex items-center justify-between gap-3">
|
||||
<div className="flex min-w-0 items-center gap-2 rounded border border-border-primary bg-bg-secondary px-2.5 py-1.5 text-text-muted">
|
||||
<Search className="h-3.5 w-3.5 shrink-0" />
|
||||
<span className="truncate text-xs">搜索订单、客户、SKU…</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="relative flex h-7 w-7 items-center justify-center rounded border border-border-primary bg-white text-text-secondary">
|
||||
<Bell className="h-3.5 w-3.5" />
|
||||
<span className="absolute -right-0.5 -top-0.5 h-2 w-2 rounded-full bg-brand" />
|
||||
</div>
|
||||
<div className="flex h-7 w-7 items-center justify-center rounded-full bg-ink text-[10px] font-bold text-white">
|
||||
睿
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* KPI 卡 */}
|
||||
<div className="grid grid-cols-2 gap-2 sm:gap-2.5 lg:grid-cols-4">
|
||||
{KPIS.map((kpi) => (
|
||||
<div key={kpi.label} className="border border-border-primary bg-bg-secondary p-2.5">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<span className="truncate text-[11px] font-medium text-text-muted">{kpi.label}</span>
|
||||
<TrendingUp className="h-3.5 w-3.5 shrink-0 text-brand" />
|
||||
</div>
|
||||
<div className="mt-1.5 text-lg font-black tabular-nums leading-none text-ink sm:text-xl">
|
||||
{kpi.value}
|
||||
</div>
|
||||
<TrendBadge trend={kpi.trend} label={kpi.note} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* 图表 + 预警 */}
|
||||
<div className="mt-2.5 grid grid-cols-1 gap-2.5 sm:mt-3 lg:grid-cols-3">
|
||||
<div className="border border-border-primary p-3 lg:col-span-2">
|
||||
<div className="mb-3 flex items-center justify-between">
|
||||
<div>
|
||||
<div className="text-xs font-semibold text-ink">营收趋势</div>
|
||||
<div className="text-[10px] text-text-muted">示例数据 · 近 12 个月</div>
|
||||
</div>
|
||||
<div className="flex gap-1">
|
||||
{['月', '季', '年'].map((period, i) => (
|
||||
<span
|
||||
key={period}
|
||||
className={cn(
|
||||
'rounded px-1.5 py-0.5 text-[10px] font-medium',
|
||||
i === 0 ? 'bg-brand text-white' : 'bg-bg-secondary text-text-muted'
|
||||
)}
|
||||
>
|
||||
{period}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<svg
|
||||
viewBox="0 0 360 120"
|
||||
className="h-24 w-full sm:h-28"
|
||||
preserveAspectRatio="none"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<defs>
|
||||
<linearGradient id="hero-visual-fill" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stopColor="var(--color-brand)" stopOpacity="0.16" />
|
||||
<stop offset="100%" stopColor="var(--color-brand)" stopOpacity="0" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
{[24, 56, 88].map((y) => (
|
||||
<line key={y} x1="0" y1={y} x2="360" y2={y} stroke="var(--color-border-light)" strokeWidth="1" />
|
||||
))}
|
||||
<path
|
||||
d="M0,104 C36,96 56,80 88,82 C120,84 144,60 176,58 C208,56 232,38 264,40 C296,42 320,24 360,20 L360,120 L0,120 Z"
|
||||
fill="url(#hero-visual-fill)"
|
||||
/>
|
||||
<path
|
||||
d="M0,104 C36,96 56,80 88,82 C120,84 144,60 176,58 C208,56 232,38 264,40 C296,42 320,24 360,20"
|
||||
fill="none"
|
||||
stroke="var(--color-brand)"
|
||||
strokeWidth="2.5"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
<circle cx="360" cy="20" r="4" fill="var(--color-brand)" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div className="border border-border-primary p-3">
|
||||
<div className="mb-3 flex items-center gap-1.5">
|
||||
<AlertCircle className="h-3.5 w-3.5 text-brand" />
|
||||
<span className="text-xs font-semibold text-ink">待办预警</span>
|
||||
</div>
|
||||
<ul className="space-y-2.5">
|
||||
{ALERTS.map((alert) => (
|
||||
<li key={alert.title} className="flex items-start gap-2">
|
||||
<span
|
||||
className={cn(
|
||||
'mt-1 h-1.5 w-1.5 shrink-0 rounded-full',
|
||||
alert.level === 'high' ? 'bg-brand' : 'bg-border-secondary'
|
||||
)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<div className="min-w-0">
|
||||
<div className="truncate text-xs font-medium text-ink">{alert.title}</div>
|
||||
<div className="truncate text-[10px] text-text-muted">{alert.desc}</div>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 数据表格 */}
|
||||
<div className="mt-2.5 hidden overflow-hidden border border-border-primary 2xl:block">
|
||||
<div className="flex items-center justify-between border-b border-border-primary bg-bg-secondary px-3 py-2">
|
||||
<span className="text-xs font-semibold text-ink">重点商机动态</span>
|
||||
<span className="text-[10px] text-text-muted">示例数据</span>
|
||||
</div>
|
||||
<div className="divide-y divide-border-light">
|
||||
{TABLE_ROWS.map((row) => (
|
||||
<div key={row.name} className="grid grid-cols-[1fr_auto_auto] items-center gap-3 px-3 py-2">
|
||||
<span className="truncate text-xs font-medium text-ink">{row.name}</span>
|
||||
<span className="text-xs font-semibold tabular-nums text-text-secondary">{row.amount}</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<StatusBadge status={row.status} />
|
||||
<span className="text-[10px] text-text-muted">{row.date}</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{/* 眉标:能力定位,非产品名 */}
|
||||
<div className="mb-4 inline-flex items-center gap-2 text-xs font-bold tracking-[0.2em] uppercase text-brand">
|
||||
<span className="h-1.5 w-1.5 rounded-full bg-brand" aria-hidden="true" />
|
||||
数据驱动 · 让转型可量化
|
||||
</div>
|
||||
|
||||
{/* KPI 卡片:抽象数据可视化 */}
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
{KPIS.map((kpi) => (
|
||||
<div key={kpi.label} className="border border-border-primary bg-bg-secondary p-4">
|
||||
<div className="mb-2 flex items-center justify-between gap-2">
|
||||
<span className="text-[11px] font-medium text-text-muted">{kpi.label}</span>
|
||||
<TrendingUp className="h-3.5 w-3.5 text-brand" />
|
||||
</div>
|
||||
<div className="text-xl font-black tabular-nums leading-none text-ink sm:text-2xl">
|
||||
{kpi.value}
|
||||
</div>
|
||||
<TrendBadge trend={kpi.trend} label={kpi.note} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* 趋势图:品牌色曲线 + 渐变填充 */}
|
||||
<div className="mt-3 border border-border-primary bg-white p-4">
|
||||
<div className="mb-3 flex items-center justify-between">
|
||||
<span className="text-xs font-semibold text-ink">数据增长趋势</span>
|
||||
<span className="text-[10px] text-text-muted">示例数据 · 近 12 个月</span>
|
||||
</div>
|
||||
<svg
|
||||
viewBox="0 0 360 120"
|
||||
className="h-24 w-full sm:h-28"
|
||||
preserveAspectRatio="none"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<defs>
|
||||
<linearGradient id="hero-visual-fill" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stopColor="var(--color-brand)" stopOpacity="0.16" />
|
||||
<stop offset="100%" stopColor="var(--color-brand)" stopOpacity="0" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
{[24, 56, 88].map((y) => (
|
||||
<line key={y} x1="0" y1={y} x2="360" y2={y} stroke="var(--color-border-light)" strokeWidth="1" />
|
||||
))}
|
||||
<path
|
||||
d="M0,104 C36,96 56,80 88,82 C120,84 144,60 176,58 C208,56 232,38 264,40 C296,42 320,24 360,20 L360,120 L0,120 Z"
|
||||
fill="url(#hero-visual-fill)"
|
||||
/>
|
||||
<path
|
||||
d="M0,104 C36,96 56,80 88,82 C120,84 144,60 176,58 C208,56 232,38 264,40 C296,42 320,24 360,20"
|
||||
fill="none"
|
||||
stroke="var(--color-brand)"
|
||||
strokeWidth="2.5"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
<circle cx="360" cy="20" r="4" fill="var(--color-brand)" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||