feat(P1): 新增 5 个数据展示组件(灵感来自 beautifului.dev)

- FilterTable  带列筛选+排序的增强表格
- InsightCard  指标洞察卡片(趋势+对比+状态色)
- SearchList   即时搜索列表(过滤+选中态)
- EventList    事件流(时间线+类型图标+展开详情)
- LoaderGrid   网格骨架屏(卡片/表格加载态)

均适配 Profeto 报纸风(衬线/纸色/细线),统一收口 components/ui。
This commit is contained in:
shangfangjian
2026-09-23 01:30:32 +08:00
parent e8e626e3e4
commit f33a1d91ff
7 changed files with 627 additions and 0 deletions
@@ -0,0 +1,84 @@
/**
* InsightCard: 指标洞察卡片 —— 数值 + 趋势 + 对比。
*
* 灵感来自 beautifului InsightCard / StatCard,适配 Profeto 报纸风:
* - 大字号主指标(衬线)
* - 趋势箭头(↑↓) + 变化率
* - 可选对比基线(如"较上周 +12%")
* - 状态色(info/warning/positive/negative)
*/
import { TrendingArrow } from './TrendingArrow'
export interface InsightCardProps {
label: string
value: string | number
/** 趋势方向: up/down/flat */
trend?: 'up' | 'down' | 'flat'
/** 变化率文案,如 "+12%" */
trendLabel?: string
/** 对比说明,如 "较昨日" */
compareHint?: string
/** 状态色 */
status?: 'info' | 'warning' | 'positive' | 'negative'
/** 副标题 / 补充说明 */
subtitle?: string
/** 图标(ReactNode) */
icon?: React.ReactNode
}
const STATUS_STYLE: Record<string, { card: string; accent: string }> = {
info: { card: 'border-ink-200', accent: 'text-ink-600' },
warning: { card: 'border-amber-300', accent: 'text-amber-700' },
positive: { card: 'border-emerald-300', accent: 'text-emerald-700' },
negative: { card: 'border-rose-300', accent: 'text-rose-700' },
}
const TREND_COLOR: Record<string, string> = {
up: 'text-emerald-600',
down: 'text-rose-600',
flat: 'text-ink-400',
}
export function InsightCard({ label, value, trend, trendLabel, compareHint, status = 'info', subtitle, icon }: InsightCardProps) {
const style = STATUS_STYLE[status]
return (
<article className={`border bg-paper-50 px-4 py-4 shadow-sm transition-shadow hover:shadow-md ${style.card}`}>
<div className="flex items-start justify-between gap-2">
<div className="min-w-0 flex-1">
<p className="truncate text-xs font-medium uppercase tracking-wider text-ink-500">{label}</p>
<p className={`mt-1 font-serif text-2xl font-bold leading-none sm:text-3xl ${style.accent}`}>
{value}
</p>
{subtitle && (
<p className="mt-1 truncate text-xs text-ink-400">{subtitle}</p>
)}
</div>
{icon && (
<div className={`flex h-9 w-9 flex-shrink-0 items-center justify-center rounded ${style.accent} bg-paper-100`}>
{icon}
</div>
)}
</div>
{(trend || trendLabel) && (
<div className="mt-3 flex items-center gap-1.5 text-xs">
{trend && <TrendingArrow dir={trend} className="h-3 w-3" />}
{trendLabel && (
<span className={`font-medium ${TREND_COLOR[trend ?? 'flat']}`}>{trendLabel}</span>
)}
{compareHint && <span className="text-ink-400">{compareHint}</span>}
</div>
)}
</article>
)
}
/** InsightCard 网格容器 */
export function InsightGrid({ children }: { children: React.ReactNode }) {
return (
<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-4">
{children}
</div>
)
}