Commit 40f578e9 by luoqi

style(web): 隐藏左栏两个筛选 + 电话「假」角标;列表电话改明文

1) 左栏「真」号码筛选 + 「筛选标签」隐藏
   开关 RAIL_FILTERS_VISIBLE=false,置 true 即恢复;PersonaTagFilter 与服务端
   phoneVerified 谓词都留着,不是删除。
   ️ 藏的只是入口:realPhoneOnly / tags 初值都是"不筛",所以请求里不会残留条件。
    别改它们的初值,否则会出现"看不见的筛选"——列表少人而主管找不到原因。

2) 详情页号码角标:只在已核实时打「真」,未核实**不打标**
   原来给未核实号打「假」,而那是绝大多数 —— 整页每个患者都挂着「假」,既是噪音,
   又在客服正要拨号的那一秒告诉他"这号是假的",平白制造犹豫。
   「未核实」≠「假」:只是外部对照表里没有,号码本身可能完全正常。

3) 列表电话明文(PlanPatientBrief 加 phone)
   脱敏号 138****2417 客服拨不出去,等于每次都要点进详情再拨。
   ️ 不是新开暴露面:详情页早就明文下发同一个号(plan-aggregate.phone),
     两处同权限同 tenant scope。
   ️ phoneMasked 保留:配了原始档案(VIEW_PATIENT)的宿主整行隐藏手机号,
     两个字段都不显示 —— 别因为加了明文就把脱敏删了。

1020 tests,两个 tsc + next build 干净。按你说的没进浏览器调试。
parent 9456b544
......@@ -196,6 +196,9 @@ export class PlanService {
name: patient?.name ?? null,
nameMasked: maskName(patient?.name ?? null),
phoneMasked: maskPhone(patient?.phone ?? null),
// 明文号:列表直接显示(客服照着拨)。与详情页 plan-aggregate 下发的是同一个字段、
// 同权限同 scope —— ⚠️ 但 phoneMasked 仍要留(配了原始档案的宿主走隐藏整行那条路)
phone: patient?.phone ?? null,
phoneVerified: patient?.phoneVerified ?? false,
gender: patient?.gender ?? null,
age: patient?.birthDate ? calcAge(patient.birthDate) : null,
......
......@@ -1551,18 +1551,21 @@ function IdentityCard({
<span className="text-[12px] tabular-nums font-mono text-slate-700">
{patient.phone ?? patient.phoneMasked}
</span>
{/* 号码真实性角标:真 = 外部对照表已核实;假 = 宿主同步号(测试库造数) */}
<span
title={patient.phoneVerified ? '真实手机号(外部对照表已核实)' : '未核实号码(测试库为造数假号)'}
className={cn(
'inline-flex items-center rounded px-1 text-[10px] font-medium leading-4 ring-1 ring-inset',
patient.phoneVerified
? 'bg-brand-50 text-brand-700 ring-brand-200'
: 'bg-slate-50 text-slate-400 ring-slate-200',
)}
>
{patient.phoneVerified ? '真' : '假'}
</span>
{/*
号码真实性角标 —— ⭐ **只在已核实时才打「真」**,未核实的**不打标**(2026-08-04)。
原来给未核实号打一个「假」:那是绝大多数,于是整页每个患者都挂着「假」——
既是噪音,又在客服正要拨号的那一秒告诉他"这号是假的",平白制造犹豫。
⚠️ 「未核实」≠「假」:只是外部对照表里没有,号码本身可能完全正常。
⛔ 别把这里改回三态或恢复「假」文案。
*/}
{patient.phoneVerified && (
<span
title="真实手机号(外部对照表已核实)"
className="inline-flex items-center rounded bg-brand-50 px-1 text-[10px] font-medium leading-4 text-brand-700 ring-1 ring-inset ring-brand-200"
>
</span>
)}
{/* 复制号码明文 */}
<button
onClick={copyPhone}
......
......@@ -56,6 +56,16 @@ const VIEW_TABS: Array<{ v: View; label: string; requires?: Permission }> = [
{ v: 'mine', label: '我的' },
];
/**
* 左栏那一排筛选(「真」号码 + 「筛选标签」)—— 暂时隐藏(2026-08-04 产品走查)。
* 置 `true` 即恢复;⛔ 不是删除,`PersonaTagFilter` 与 `phoneVerified` 那条服务端谓词都留着。
*
* ⚠️ 隐藏的只是**入口**。`realPhoneOnly` / `tags` 两个 state 初值都是"不筛",
* 所以藏起来之后请求里不会残留任何筛选条件 —— ⛔ 别改它们的初值,
* 否则会出现"看不见的筛选"(列表少人而主管找不到原因)。
*/
const RAIL_FILTERS_VISIBLE: boolean = false;
export function PatientPickerRail({
currentPlanId,
onNavigated,
......@@ -374,24 +384,26 @@ export function PatientPickerRail({
className="h-7 w-full rounded-md border border-slate-100 bg-slate-50 pl-7 pr-2 text-[12px] outline-none placeholder:text-slate-400 focus:border-brand-300 focus:bg-white"
/>
</div>
<div className="flex items-center gap-1.5">
{!hasPatientArchive && (
<button
type="button"
onClick={() => setRealPhoneOnly((v) => !v)}
title="只看已核实的真实手机号"
className={cn(
'inline-flex h-6 items-center gap-1 rounded-md border px-1.5 text-[11px] transition-colors',
realPhoneOnly
? 'border-brand-300 bg-brand-50 font-medium text-brand-700'
: 'border-slate-100 bg-white text-slate-500 hover:bg-slate-50',
)}
>
<PhoneCall className="h-3 w-3" />
</button>
)}
<PersonaTagFilter selected={tags} onToggle={toggleTag} onClear={() => setTags(new Set())} />
</div>
{RAIL_FILTERS_VISIBLE && (
<div className="flex items-center gap-1.5">
{!hasPatientArchive && (
<button
type="button"
onClick={() => setRealPhoneOnly((v) => !v)}
title="只看已核实的真实手机号"
className={cn(
'inline-flex h-6 items-center gap-1 rounded-md border px-1.5 text-[11px] transition-colors',
realPhoneOnly
? 'border-brand-300 bg-brand-50 font-medium text-brand-700'
: 'border-slate-100 bg-white text-slate-500 hover:bg-slate-50',
)}
>
<PhoneCall className="h-3 w-3" />
</button>
)}
<PersonaTagFilter selected={tags} onToggle={toggleTag} onClear={() => setTags(new Set())} />
</div>
)}
{clinicOptions.length > 1 && (
<RailClinicFilter options={clinicOptions} selected={clinics} onChange={setClinics} />
)}
......@@ -950,7 +962,10 @@ function PatientRow({
<div className="mt-0.5 flex items-center gap-1.5">
{!hidePhone && (
<span className="flex-none font-mono text-[10.5px] tabular-nums text-slate-400">
{p.patient.phoneMasked ?? ''}
{/* ⭐ 明文优先 —— 客服要照着这个拨,`138****2417` 拨不出去。
⚠️ 仍留 phoneMasked 兜底:老接口/缓存响应里可能没有 phone 字段。
⚠️ 配了原始档案的宿主整行不显示(hidePhone),两个都不会露。 */}
{p.patient.phone ?? p.patient.phoneMasked ?? ''}
</span>
)}
{/* 诊所名:单诊所权限的人每行都是同一家,纯噪音 → 隐藏;跨诊所才有区分价值 */}
......
......@@ -195,6 +195,15 @@ export const PlanPatientBriefSchema = z.object({
name: z.string().nullable(),
nameMasked: z.string().nullable(),
phoneMasked: z.string().nullable(),
/**
* 手机号**明文** —— 列表直接显示(2026-08-04 产品定:客服要照着拨,脱敏号没法用)。
*
* ⚠️ 这**不是**新开的暴露面:详情页早就明文下发同一个号(plan-aggregate 的 `phone`),
* 两处同权限、同 tenant scope。列表脱敏而详情明文,只是让客服多点一次。
* ⚠️ `phoneMasked` **保留**:宿主配了原始档案(VIEW_PATIENT)时 PAC 侧是造数假号,
* 那种场景下前端整行隐藏手机号,两个字段都不显示 —— ⛔ 别因为加了明文就把脱敏删了。
*/
phone: z.string().nullable(),
/// 号码真实性(外部对照表核实过 = true);列表给"真"角标 + 筛选用
phoneVerified: z.boolean().optional(),
gender: z.string().nullable(),
......
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