feat(P1): 新增 5 个数据展示组件(灵感来自 beautifului.dev)
- FilterTable 带列筛选+排序的增强表格 - InsightCard 指标洞察卡片(趋势+对比+状态色) - SearchList 即时搜索列表(过滤+选中态) - EventList 事件流(时间线+类型图标+展开详情) - LoaderGrid 网格骨架屏(卡片/表格加载态) 均适配 Profeto 报纸风(衬线/纸色/细线),统一收口 components/ui。
This commit is contained in:
@@ -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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user