Commit 3c3c4cf0 by luoqi

feat(web): 首屏关键标签扩到 10 个 + 去 label、改素色

按业务意见调整:
① 治疗史 / 折扣锚点 / 获客渠道 / 权益身份 进首屏(现 10 个,16 个里留 6 个不进:
   时间偏好、治疗敏感 —— 通话中才用;年龄段、性别 —— 姓名行已有;
   家庭构成、转介绍达人 —— 不改变开场)。
② chip 去掉标签名,只留取值:「急迫等级 紧急」→「紧急」。
   取值本身自解释(紧急 / 种植禁忌 / 储值会员),label 是冗余;十来颗标签
   每颗都带名字会把身份卡撑满。要看标签名或口径 → hover / 进抽屉。
③ 去掉按 tone 的多彩着色,统一素色,与左栏列表卡片的通用标签同一套
   (rounded + slate 描边)。首屏十几颗各一种颜色 = 满屏彩条,反而看不出轻重。

新增 plain 模式挂在 PersonaTagCloud 上,画像标签卡兜底/抽屉仍是原来的带 label 彩色版
—— 那两处标签少、要的是辨识度,口径不同不强行统一。

657 tests / 42 suites 通过。本地实测张红渲染 潜在种植+1 / 紧急 / 低活跃 / 流失客 一行素色 chip。
️ 本地库这个宿主没有治疗史/折扣锚点/权益/获客渠道的数据,这四颗的观感未实测。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
parent a21d74c1
......@@ -1366,10 +1366,11 @@ function IdentityCard({
</div>
</div>
{/* ⭐ 关键标签 —— 紧贴姓名下方(业务 2026-07-29:可快速查看,要细看再进画像详情)。
chip 与画像标签卡/抽屉同一套组件,hover 仍能看「怎么算的」,不另起一套渲染。 */}
chip 与画像标签卡/抽屉同一套组件,hover 仍能看「怎么算的」,不另起一套渲染。
plain:只出取值、统一素色(见 PersonaTagCloud 注释)。 */}
{keyFeatures.length > 0 && (
<div className="mt-1.5">
<PersonaTagCloud features={keyFeatures} />
<PersonaTagCloud features={keyFeatures} plain />
</div>
)}
{patient.tags.length > 0 && (
......@@ -2274,8 +2275,18 @@ function computeRisks({
// PersonaTagCloud — 画像标签云(chip + hover「怎么算的」)
// 每个特征 = 一颗 chip(label + 短值),按 tone 着色;hover 展开该标签的算法说明
// (PersonaFeatureHover 读 feature.key)。空值特征不出 chip。
//
// plain 模式(身份卡首屏):**去 label、去彩色**,只留取值,样式对齐左栏列表卡片的通用标签。
// 理由:首屏十来颗标签各自一种颜色 = 满屏彩条,反而看不出哪个要紧;取值本身自解释
// (「紧急」「种植禁忌」「储值会员」),label 是冗余的。要看标签名/口径 → hover 或进抽屉。
// ──────────────────────────────────────────
function PersonaTagCloud({ features }: { features: typeof mockPersona.features }) {
function PersonaTagCloud({
features,
plain = false,
}: {
features: typeof mockPersona.features;
plain?: boolean;
}) {
if (!features.length) {
return <p className="text-[10.5px] text-slate-400">暂无画像标签(数据不足)</p>;
}
......@@ -2287,22 +2298,25 @@ function PersonaTagCloud({ features }: { features: typeof mockPersona.features }
return ga - gb || oa - ob || a.key.localeCompare(b.key);
});
return (
<div className="flex flex-wrap gap-1.5">
<div className={cn('flex flex-wrap', plain ? 'gap-1' : 'gap-1.5')}>
{ordered.map((f) => {
const T = tone(f.tone);
const { text: short, more } = compactPersonaValue(f.value, f.data);
// plain 下取值就是全部内容 —— 取不到值(理论上不该有)兜底显示标签名,不出空 chip
const text = plain ? short || f.label : short;
return (
<PersonaFeatureHover key={f.key} featureKey={f.key} value={f.value}>
<span
className={cn(
'inline-flex items-center gap-1 px-1.5 py-0.5 rounded ring-1 cursor-help max-w-full',
T.bg,
T.ring,
'inline-flex max-w-full cursor-help items-center gap-1',
plain
? 'rounded px-1 py-px text-[10px] leading-4 text-slate-600 ring-1 ring-inset ring-slate-200'
: cn('rounded px-1.5 py-0.5 ring-1', T.bg, T.ring),
)}
>
<span className={cn('flex-none text-[10px] font-semibold', T.text)}>{f.label}</span>
{short && short !== f.label && (
<span className="text-[10.5px] text-slate-700 truncate">{short}</span>
{!plain && <span className={cn('flex-none text-[10px] font-semibold', T.text)}>{f.label}</span>}
{text && (plain || text !== f.label) && (
<span className={cn('truncate', !plain && 'text-[10.5px] text-slate-700')}>{text}</span>
)}
{more > 0 && (
<span className="flex-none text-[9.5px] font-medium text-slate-400 tabular-nums">
......
......@@ -37,28 +37,35 @@ export const PERSONA_FEATURE_GROUP_ORDER: PersonaFeatureGroup[] = ['action', 'va
* 其余标签一个不少,仍在「画像标签 → 详情」抽屉里全量可查 ——
* 这里取舍的是**首屏**,不是信息。
*
* 选这个的理由,按客服打这通电话的顺序:
* 选这个的理由,按客服打这通电话的顺序:
* contraindication 能不能治 —— 安全红线,漏看会出事
* special_attention 能不能打 —— 免打扰 / 屡次爽约,决定要不要拨
* urgency_level 多急
* potential_treatment 聊什么
* rfm / lifecycle_stage 这人值多少、走到哪一步 —— 决定给什么力度
* treatment_history 做过什么 —— 开场认人、避免推已经做过的项目
* rfm / lifecycle_stage 这人值多少、走到哪一步
* entitlement_status 能怎么付 —— 商保直付 / 储值 / 医保,报价前就得知道
* discount_anchor 谈价底线 —— 历史给过的最深折扣
* acquisition_channel 怎么来的 —— 口碑 / 活动,影响开场怎么称呼这段关系
*
* 没进来的:时间偏好 / 折扣锚点 / 治疗敏感(通话**中**才用得上,不是扫一眼要的);
* 年龄段 / 性别 / 家庭构成 / 获客渠道(基础属性,姓名行已经有性别年龄);
* 权益身份 / 转介绍达人 / 治疗史(重要,但不改变这通电话怎么开场)。
* 没进来的六个:时间偏好 / 治疗敏感(通话**中**才用得上,不是扫一眼要的);
* 年龄段 / 性别(姓名行已经有性别年龄,重复占位);家庭构成 / 转介绍达人
* (有价值但不改变这通电话怎么开场)。
*
* ⚠️ 展示顺序不在这里定 —— 渲染按 personaFeatureSortKey(与抽屉同一套序),
* 本常量只管「哪些进首屏」。想加 key 先自问:客服扫一眼真会拿它做决定吗?
* 六个是软上限:再多就不是"关键"了,身份卡也会被挤成标签墙。
*/
export const PERSONA_KEY_FEATURE_KEYS: readonly string[] = [
'contraindication',
'special_attention',
'urgency_level',
'potential_treatment',
'treatment_history',
'rfm',
'lifecycle_stage',
'entitlement_status',
'discount_anchor',
'acquisition_channel',
];
/** 该标签是否进身份卡首屏(未登记的一律不进 —— 新标签得显式选进来) */
......
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