100 lines
3.0 KiB
TypeScript
100 lines
3.0 KiB
TypeScript
|
|
import { Badge } from "@/shared/ui/badge"
|
||
|
|
|
||
|
|
import type { SelectionPatternScore } from "../api/selection.types"
|
||
|
|
|
||
|
|
interface PatternScoreProps {
|
||
|
|
score: SelectionPatternScore | null
|
||
|
|
}
|
||
|
|
|
||
|
|
const breakdownLabels = {
|
||
|
|
trend_structure: "趋势",
|
||
|
|
kdj_state: "KDJ",
|
||
|
|
volume_pattern: "量能",
|
||
|
|
price_shape: "价格形态",
|
||
|
|
} as const
|
||
|
|
|
||
|
|
export function PatternScoreSummary({ score }: PatternScoreProps) {
|
||
|
|
if (!score) return <span className="text-muted-foreground">未评分</span>
|
||
|
|
if (score.status === "failed") {
|
||
|
|
return <Badge variant="destructive">评分暂不可用</Badge>
|
||
|
|
}
|
||
|
|
if (score.status === "below_threshold") {
|
||
|
|
return (
|
||
|
|
<Badge variant="outline">
|
||
|
|
未匹配到 {formatScore(score.threshold)} 分以上案例
|
||
|
|
</Badge>
|
||
|
|
)
|
||
|
|
}
|
||
|
|
return (
|
||
|
|
<span className="space-y-0.5">
|
||
|
|
<strong className="block tabular-nums text-primary">
|
||
|
|
{formatScore(score.value)} 分
|
||
|
|
</strong>
|
||
|
|
<span className="block text-[10px] text-muted-foreground">
|
||
|
|
{score.case?.name ?? "最佳案例未知"}
|
||
|
|
</span>
|
||
|
|
</span>
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|
||
|
|
export function PatternScoreDetails({ score }: PatternScoreProps) {
|
||
|
|
if (!score) {
|
||
|
|
return (
|
||
|
|
<p className="text-xs text-muted-foreground">本次运行未执行图形评分。</p>
|
||
|
|
)
|
||
|
|
}
|
||
|
|
if (score.status === "failed") {
|
||
|
|
return (
|
||
|
|
<div className="space-y-1">
|
||
|
|
<Badge variant="destructive">评分暂不可用</Badge>
|
||
|
|
<p className="text-xs text-muted-foreground">
|
||
|
|
{score.reason || "案例库或评分计算暂时不可用。"}
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
)
|
||
|
|
}
|
||
|
|
if (score.status === "below_threshold") {
|
||
|
|
return (
|
||
|
|
<div className="space-y-1">
|
||
|
|
<Badge variant="outline">低于匹配阈值</Badge>
|
||
|
|
<p className="text-xs text-muted-foreground">
|
||
|
|
未匹配到 {formatScore(score.threshold)} 分以上案例。
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
)
|
||
|
|
}
|
||
|
|
return (
|
||
|
|
<div className="space-y-3">
|
||
|
|
<div className="flex items-start justify-between gap-3">
|
||
|
|
<div>
|
||
|
|
<p className="text-2xl font-semibold tabular-nums">
|
||
|
|
{formatScore(score.value)} 分
|
||
|
|
</p>
|
||
|
|
<p className="text-xs text-muted-foreground">
|
||
|
|
最佳案例:{score.case?.name ?? "未知"}
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
<Badge variant="default">已匹配</Badge>
|
||
|
|
</div>
|
||
|
|
{score.breakdown ? (
|
||
|
|
<dl className="grid grid-cols-2 gap-2 text-xs">
|
||
|
|
{Object.entries(breakdownLabels).map(([key, label]) => (
|
||
|
|
<div key={key} className="rounded-md border border-border/60 p-2">
|
||
|
|
<dt className="text-muted-foreground">{label}</dt>
|
||
|
|
<dd className="mt-1 font-semibold tabular-nums">
|
||
|
|
{formatScore(
|
||
|
|
score.breakdown?.[key as keyof typeof breakdownLabels],
|
||
|
|
)}
|
||
|
|
</dd>
|
||
|
|
</div>
|
||
|
|
))}
|
||
|
|
</dl>
|
||
|
|
) : null}
|
||
|
|
</div>
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|
||
|
|
function formatScore(value: number | null | undefined) {
|
||
|
|
return value === null || value === undefined ? "—" : value.toFixed(2)
|
||
|
|
}
|