Commit 1b846327 by luoqi

feat(web): 画像卡去分割线 + hover 全中文/精简 + 优先级改 10 分制两位小数

按反馈:
- 身份卡画像标签去掉上方空分割线(border-t),直接贴手机号下。
- 标签 hovercard:① 英文术语全改中文(种植史/正畸史/冠桥贴面嵌体…,去 implant/orthodontic/channel= 等);
  ② 删'不打标签'等'不显示时'的说明(没标签就没 hover,无意义);③ 精简解释性 note(去 valueTier/
  Layer C/snapshot/DW/喂优先级 等内部术语),只留客服有用的。
- 优先级分数:0-100 → 10 分制两位小数(优先用 breakdown.raw 真精度,无则 score/10)。
  列表 PriorityBar、两页共用的 PriorityHover(头部 + 总分 + /10)同步。
- web tsc 通过。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
parent f138e5a3
...@@ -1066,9 +1066,9 @@ function IdentityCard({ ...@@ -1066,9 +1066,9 @@ function IdentityCard({
</span> </span>
)} )}
</div> </div>
{/* 画像标签(并入身份卡)— hover 看"是什么 + 怎么算的";无标题/详情入口(内部细节不外露)*/} {/* 画像标签(并入身份卡)— hover 看"是什么 + 怎么算的";无标题/分割线/详情入口(内部细节不外露)*/}
{features.length > 0 && ( {features.length > 0 && (
<div className="mt-2 pt-2 border-t border-slate-100"> <div className="mt-2">
<PersonaTagCloud features={features} /> <PersonaTagCloud features={features} />
</div> </div>
)} )}
......
...@@ -765,7 +765,10 @@ function PatientPlanCard({ ...@@ -765,7 +765,10 @@ function PatientPlanCard({
breakdown={(p.reasons[0]?.breakdown as { priority?: PriorityBreakdown } | null | undefined)?.priority} breakdown={(p.reasons[0]?.breakdown as { priority?: PriorityBreakdown } | null | undefined)?.priority}
> >
<span className="inline-flex items-center gap-1 cursor-help group/score"> <span className="inline-flex items-center gap-1 cursor-help group/score">
<PriorityBar score={p.priorityScore} /> <PriorityBar
score={p.priorityScore}
raw={(p.reasons[0]?.breakdown as { priority?: PriorityBreakdown } | null | undefined)?.priority?.raw}
/>
<svg className="w-3 h-3 text-slate-400 opacity-0 group-hover/score:opacity-100 transition-opacity" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"> <svg className="w-3 h-3 text-slate-400 opacity-0 group-hover/score:opacity-100 transition-opacity" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<circle cx="12" cy="12" r="10" /> <circle cx="12" cy="12" r="10" />
<path d="M12 16v-4M12 8h.01" strokeLinecap="round" /> <path d="M12 16v-4M12 8h.01" strokeLinecap="round" />
...@@ -866,7 +869,7 @@ function PatientPlanCard({ ...@@ -866,7 +869,7 @@ function PatientPlanCard({
// 原子组件 // 原子组件
// ───────────────────────────────────────────────────────────────── // ─────────────────────────────────────────────────────────────────
function PriorityBar({ score }: { score: number }) { function PriorityBar({ score, raw }: { score: number; raw?: number }) {
const pct = Math.max(0, Math.min(1, score / 100)); const pct = Math.max(0, Math.min(1, score / 100));
const filled = Math.max(1, Math.round(pct * 5)); const filled = Math.max(1, Math.round(pct * 5));
const colors = ['bg-emerald-400', 'bg-emerald-500', 'bg-amber-400', 'bg-amber-500', 'bg-rose-500']; const colors = ['bg-emerald-400', 'bg-emerald-500', 'bg-amber-400', 'bg-amber-500', 'bg-rose-500'];
...@@ -876,17 +879,19 @@ function PriorityBar({ score }: { score: number }) { ...@@ -876,17 +879,19 @@ function PriorityBar({ score }: { score: number }) {
: pct >= 0.4 ? 'text-amber-700' : pct >= 0.4 ? 'text-amber-700'
: pct >= 0.2 ? 'text-emerald-700' : pct >= 0.2 ? 'text-emerald-700'
: 'text-slate-500'; : 'text-slate-500';
// 10 分制(保留两位小数):优先 breakdown.raw,无则 score/10
const disp = (raw ?? score / 10).toFixed(2);
return ( return (
<span <span
className="inline-flex flex-none items-center gap-1" className="inline-flex flex-none items-center gap-1"
title={`优先级 ${Math.round(score)} / 100`} title={`优先级 ${disp} / 10`}
> >
<span className="inline-flex items-center gap-0.5"> <span className="inline-flex items-center gap-0.5">
{Array.from({ length: 5 }).map((_, i) => ( {Array.from({ length: 5 }).map((_, i) => (
<span key={i} className={cn('h-2 w-[7px] rounded-sm', i < filled ? colors[i] : 'bg-slate-200')} /> <span key={i} className={cn('h-2 w-[7px] rounded-sm', i < filled ? colors[i] : 'bg-slate-200')} />
))} ))}
</span> </span>
<span className={cn('text-[10.5px] font-semibold nums', labelTone)}>{Math.round(score)}</span> <span className={cn('text-[10.5px] font-semibold nums', labelTone)}>{disp}</span>
</span> </span>
); );
} }
......
...@@ -53,12 +53,13 @@ function PriorityBreakdownTable({ ...@@ -53,12 +53,13 @@ function PriorityBreakdownTable({
score: number; score: number;
breakdown?: PriorityBreakdown | null; breakdown?: PriorityBreakdown | null;
}) { }) {
const total = Math.round(score); // 10 分制(保留两位小数):优先用 breakdown.raw(真 2 位精度),无则 score/10
const disp = (breakdown?.raw ?? score / 10).toFixed(2);
if (!breakdown || breakdown.urgency === undefined) { if (!breakdown || breakdown.urgency === undefined) {
// 老数据 / 异常兜底(旧 6 因子 breakdown 也走这里) // 老数据 / 异常兜底(旧 6 因子 breakdown 也走这里)
return ( return (
<div className="space-y-1.5"> <div className="space-y-1.5">
<Header total={total} /> <Header disp={disp} />
<p className="text-slate-500 leading-relaxed"> <p className="text-slate-500 leading-relaxed">
三维加权:急迫性 ×0.4 + 价值性 ×0.3 + 意愿度 ×0.3,再 × 新鲜度 × 置信度。 三维加权:急迫性 ×0.4 + 价值性 ×0.3 + 意愿度 ×0.3,再 × 新鲜度 × 置信度。
<br /> <br />
...@@ -78,7 +79,7 @@ function PriorityBreakdownTable({ ...@@ -78,7 +79,7 @@ function PriorityBreakdownTable({
const base = breakdown.base ?? urgency * 0.4 + value * 0.3 + willing * 0.3; const base = breakdown.base ?? urgency * 0.4 + value * 0.3 + willing * 0.3;
return ( return (
<div className="space-y-1.5"> <div className="space-y-1.5">
<Header total={total} /> <Header disp={disp} />
<table className="w-full tabular-nums"> <table className="w-full tabular-nums">
<tbody> <tbody>
<Row label="急迫性" value={`${urgency.toFixed(0)} × 0.4`} hint="病情多急(末诊/超期)" /> <Row label="急迫性" value={`${urgency.toFixed(0)} × 0.4`} hint="病情多急(末诊/超期)" />
...@@ -101,17 +102,17 @@ function PriorityBreakdownTable({ ...@@ -101,17 +102,17 @@ function PriorityBreakdownTable({
hint="医生 1.0 / 影像·建议 0.9" hint="医生 1.0 / 影像·建议 0.9"
tone={conf < 1 ? 'amber' : undefined} tone={conf < 1 ? 'amber' : undefined}
/> />
<Subtotal label="= 总分" value={total.toString()} bold /> <Subtotal label="= 总分" value={disp} bold />
</tbody> </tbody>
</table> </table>
</div> </div>
); );
} }
function Header({ total }: { total: number }) { function Header({ disp }: { disp: string }) {
return ( return (
<div className="flex items-baseline justify-between border-b border-slate-200 pb-1.5"> <div className="flex items-baseline justify-between border-b border-slate-200 pb-1.5">
<span className="text-[13px] font-semibold text-slate-900">优先级 {total} / 100</span> <span className="text-[13px] font-semibold text-slate-900">优先级 {disp} / 10</span>
<span className="text-[10.5px] text-slate-500">急迫 × 价值 × 意愿</span> <span className="text-[10.5px] text-slate-500">急迫 × 价值 × 意愿</span>
</div> </div>
); );
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment