- 新建 components/ui/(Button/Input/Select/Modal/Tabs/Spinner/Skeleton 等),变体用联合类型约束,根治幻影变体 - admin/components.tsx 与 matches/ui.tsx 改为再导出壳,消除 Spinner 三份重复 - 日期工具收口到 lib/date.ts,滚动监听收口到 lib/useScroll.ts - index.css 引入 :root RGB 三元组令牌,tailwind.config 接 <alpha-value>(修复透明度修饰符静默失效) - 刘建毛草 5MB 全字库(jsDelivr 未锁版) → 1KB 自托管子集(仅「先知」二字,unicode-range 限定)
68 lines
2.4 KiB
TypeScript
68 lines
2.4 KiB
TypeScript
/**
|
|
* 统计与进度可视化:StatCard / ProgressBar / AgentWeightsBar。
|
|
* 原位于 admin/components.tsx。
|
|
*/
|
|
|
|
export function StatCard({
|
|
label,
|
|
value,
|
|
hint,
|
|
}: {
|
|
label: string
|
|
value: string | number
|
|
hint?: string
|
|
}) {
|
|
return (
|
|
<div className="border border-ink-900 bg-paper-50 px-4 py-3.5">
|
|
<span className="text-2xs tracking-[0.2em] text-ink-400">{label}</span>
|
|
<div className="mt-1.5 font-serif text-3xl font-bold tabular-nums leading-none text-ink-900">
|
|
{value}
|
|
</div>
|
|
{hint && <div className="mt-1.5 text-2xs text-ink-400">{hint}</div>}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
/** 进度条:同前台置信度细线 */
|
|
export function ProgressBar({ value, className = '' }: { value: number; className?: string }) {
|
|
const clamped = Math.max(0, Math.min(100, value))
|
|
return (
|
|
<div className={`h-2 w-full overflow-hidden rounded-full bg-ink-200 ${className}`} role="progressbar" aria-valuenow={clamped}>
|
|
<div
|
|
className="h-full rounded-full bg-press transition-[width] duration-500"
|
|
style={{ width: `${clamped}%` }}
|
|
/>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
/** Agent 权重条形图 */
|
|
export function AgentWeightsBar({ weights, okCount }: { weights: Record<string, number>; okCount: number }) {
|
|
const entries = Object.entries(weights).filter(([, w]) => w > 0)
|
|
if (entries.length === 0) return null
|
|
const total = entries.reduce((s, [, w]) => s + w, 0) || 1
|
|
const colors = ['bg-ink-900', 'bg-ink-700', 'bg-ink-500', 'bg-press', 'bg-ink-300']
|
|
return (
|
|
<div className="mt-2 border-t border-ink-200 pt-2">
|
|
<div className="mb-1 text-2xs text-ink-400">终裁专家权重</div>
|
|
<div className="space-y-1">
|
|
{entries.map(([k, w], i) => (
|
|
<div key={k} className="flex items-center gap-2 text-2xs">
|
|
<div className="h-3.5 flex-1 overflow-hidden rounded-sm bg-ink-200/60">
|
|
<div
|
|
className={`h-full ${colors[i % colors.length]} transition-all duration-500`}
|
|
style={{ width: `${Math.max(3, Math.round((w / total) * 100))}%` }}
|
|
/>
|
|
</div>
|
|
<span className="w-12 text-right tabular-nums text-ink-500">
|
|
{Math.round((w / total) * 100)}%
|
|
</span>
|
|
<span className="w-24 truncate text-ink-400" title={k}>{k}</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
<div className="mt-1 text-2xs text-ink-400">有效专家:{okCount}/{entries.length}</div>
|
|
</div>
|
|
)
|
|
}
|