Commit a8687e0a by luoqi

feat(web): PAC 主色换成 PANTONE 286 C(#0032A0),teal-* 整体改名 brand-*

## 换色
globals.css 里新增品牌色阶 brand-50…900(同色相 H=221.25 推出),
主色 #0032A0 坐 600 档 —— 全站 `bg-brand-600` 是主按钮、`--primary` 也指这档,
主色必须落这里才算真换了。--primary / --ring 同步改成 hsl(221.25 100% 31.4%)。
实测 `bg-brand-600` 计算值 rgb(0,50,160),与色卡 RGB 0/50/160 一致。

## 为什么顺手改名
没有走"偷偷把 Tailwind 的 teal 调色板改成蓝色"这条捷径 —— 那样类名写着 teal、
渲染出来是蓝的,下一个人读代码会以为自己看错了。品牌色就该叫 brand:
- 263 处 `teal-N` → `brand-N`(28 个文件,纯类名替换)
- tone 色板键 'teal' → 'brand'(labels.ts 的 PERSONA_FEATURE_META 一并改;
  tone 值全在代码里,不来自 DB,改名没有兼容问题)
- 宠物 SVG 里手挑的 3 个十六进制(11 处)按色阶位置 1:1 映射过去,不然吉祥物
  还是青色、跟全站撞色

以后换色只改 globals.css 那 10 行。

web typecheck + 657 tests / 42 suites 通过;本地实测列表/详情/表单全站已是蓝。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
parent 93b5ccab
Pipeline #3478 failed in 0 seconds
...@@ -92,7 +92,7 @@ function PlanDetailLoader({ planId }: { planId: string }) { ...@@ -92,7 +92,7 @@ function PlanDetailLoader({ planId }: { planId: string }) {
</div> </div>
<div className="absolute inset-0 z-10 flex items-center justify-center bg-white/30"> <div className="absolute inset-0 z-10 flex items-center justify-center bg-white/30">
<div className="flex items-center gap-2 rounded-full bg-white px-3 py-1.5 text-[12px] text-slate-500 shadow-md ring-1 ring-slate-200"> <div className="flex items-center gap-2 rounded-full bg-white px-3 py-1.5 text-[12px] text-slate-500 shadow-md ring-1 ring-slate-200">
<span className="h-3.5 w-3.5 animate-spin rounded-full border-2 border-teal-500 border-t-transparent" /> <span className="h-3.5 w-3.5 animate-spin rounded-full border-2 border-brand-500 border-t-transparent" />
切换中… 切换中…
</div> </div>
</div> </div>
......
...@@ -59,7 +59,7 @@ export default function PlansLayout({ children }: { children: React.ReactNode }) ...@@ -59,7 +59,7 @@ export default function PlansLayout({ children }: { children: React.ReactNode })
<button <button
type="button" type="button"
onClick={() => setRailOpen(true)} onClick={() => setRailOpen(true)}
className="fixed left-0 top-1/2 z-50 -translate-y-1/2 rounded-r-lg bg-teal-600 px-1.5 py-3 text-white shadow-lg hover:bg-teal-700 lg:hidden" className="fixed left-0 top-1/2 z-50 -translate-y-1/2 rounded-r-lg bg-brand-600 px-1.5 py-3 text-white shadow-lg hover:bg-brand-700 lg:hidden"
title="打开患者列表" title="打开患者列表"
> >
<span className="flex flex-col items-center gap-1"> <span className="flex flex-col items-center gap-1">
......
...@@ -62,7 +62,7 @@ function EntryResolver() { ...@@ -62,7 +62,7 @@ function EntryResolver() {
if (phase === 'resolving') { if (phase === 'resolving') {
return ( return (
<div className="flex h-screen items-center justify-center gap-2 text-sm text-muted-foreground"> <div className="flex h-screen items-center justify-center gap-2 text-sm text-muted-foreground">
<span className="h-4 w-4 animate-spin rounded-full border-2 border-teal-500 border-t-transparent" /> <span className="h-4 w-4 animate-spin rounded-full border-2 border-brand-500 border-t-transparent" />
正在进入召回工作台… 正在进入召回工作台…
</div> </div>
); );
...@@ -74,7 +74,7 @@ function EntryResolver() { ...@@ -74,7 +74,7 @@ function EntryResolver() {
{/* 空态 header —— 内容只有 PAC 品牌 + 用户三件套,无功能项;嵌入宿主时整体隐藏 */} {/* 空态 header —— 内容只有 PAC 品牌 + 用户三件套,无功能项;嵌入宿主时整体隐藏 */}
{!isEmbedded() && ( {!isEmbedded() && (
<header className="flex h-12 flex-none items-center gap-2 border-b border-slate-100 bg-white px-4"> <header className="flex h-12 flex-none items-center gap-2 border-b border-slate-100 bg-white px-4">
<span className="inline-flex h-7 w-7 items-center justify-center rounded-md bg-teal-600 text-[12px] font-bold text-white"> <span className="inline-flex h-7 w-7 items-center justify-center rounded-md bg-brand-600 text-[12px] font-bold text-white">
PAC PAC
</span> </span>
<h1 className="text-[14px] font-semibold text-slate-900">召回工作台</h1> <h1 className="text-[14px] font-semibold text-slate-900">召回工作台</h1>
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
* PAC Design Tokens(design-system.md §2-§4) * PAC Design Tokens(design-system.md §2-§4)
* *
* 沿用 shadcn 的 HSL var 模式(方便组件级覆盖),但: * 沿用 shadcn 的 HSL var 模式(方便组件级覆盖),但:
* - primary 改成 teal-600(PAC 品牌) * - primary 改成 brand-600(PAC 品牌 = PANTONE 286 C / #0032A0)
* - border 加深(default shadcn 91.4% 视觉太弱) * - border 加深(default shadcn 91.4% 视觉太弱)
* - 阴影整体放轻(Claude / OpenAI 现代风:border 主导,shadow 仅用于"浮起"场景) * - 阴影整体放轻(Claude / OpenAI 现代风:border 主导,shadow 仅用于"浮起"场景)
* - 字体优先 PingFang(医疗 SaaS 中文场景) * - 字体优先 PingFang(医疗 SaaS 中文场景)
...@@ -27,7 +27,7 @@ ...@@ -27,7 +27,7 @@
--accent-foreground: 222.2 47.4% 11.2%; --accent-foreground: 222.2 47.4% 11.2%;
/* === PAC 品牌色 === */ /* === PAC 品牌色 === */
--primary: 173 80% 36%; /* teal-600 */ --primary: 221.25 100% 31.4%; /* brand-600 = #0032A0(PANTONE 286 C) */
--primary-foreground: 0 0% 100%; --primary-foreground: 0 0% 100%;
--secondary: 210 40% 96.1%; --secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%; --secondary-foreground: 222.2 47.4% 11.2%;
...@@ -37,13 +37,37 @@ ...@@ -37,13 +37,37 @@
/* === 边框 — 加深(91.4% → 87%,1px 视觉更扎实)=== */ /* === 边框 — 加深(91.4% → 87%,1px 视觉更扎实)=== */
--border: 214 24% 87%; --border: 214 24% 87%;
--input: 214 28% 82%; /* 输入框边再深一档,跟 hover 状态拉开 */ --input: 214 28% 82%; /* 输入框边再深一档,跟 hover 状态拉开 */
--ring: 173 80% 36%; /* focus ring 与 primary 同色 */ --ring: 221.25 100% 31.4%; /* focus ring 与 primary 同色 */
--radius: 0.5rem; --radius: 0.5rem;
} }
} }
@theme inline { @theme inline {
/* ⭐ === PAC 品牌色阶 brand-50…900 ===
*
* 主色 = **PANTONE 286 C / #0032A0**(RGB 0/50/160),坐在 600 档 —— 因为
* 全站 `bg-brand-600` 是主按钮、`--primary` 也指这一档,主色必须落在这里才算"改了主色"。
* 其余档由同色相(H=221.25)调明度/饱和推出:浅档做底纹(brand-50 是最常用的选中/命中底),
* 深档做正文链接(brand-700 用得最多)。
*
* ⚠️ 类名从 `teal-*` 整体改名成 `brand-*`(263 处)—— 不是把 Tailwind 的 teal 调色板
* 偷偷改成蓝的:那样类名写着 teal 渲染出蓝,下一个人读代码会以为自己看错了。
* 品牌色就该叫 brand,换色只改这一段。
*
* 换色只需改下面 10 行;别在组件里写死十六进制(宠物 SVG 是唯一例外,见 pet-body.tsx)。
*/
--color-brand-50: #F0F4FF;
--color-brand-100: #DCE7FE;
--color-brand-200: #BACFFD;
--color-brand-300: #85A9FA;
--color-brand-400: #3B76F7;
--color-brand-500: #0043D6;
--color-brand-600: #0032A0; /* ← 主色 PANTONE 286 C */
--color-brand-700: #002985;
--color-brand-800: #01226A;
--color-brand-900: #031C53;
/* === 颜色 token → 自动生成 bg-{name} text-{name} border-{name} === */ /* === 颜色 token → 自动生成 bg-{name} text-{name} border-{name} === */
--color-background: hsl(var(--background)); --color-background: hsl(var(--background));
--color-foreground: hsl(var(--foreground)); --color-foreground: hsl(var(--foreground));
......
...@@ -121,7 +121,7 @@ export default function PetLabPage() { ...@@ -121,7 +121,7 @@ export default function PetLabPage() {
{/* 栖息台:离地 ~120px,在可跳高度内(data-pet-perch 机制演示) */} {/* 栖息台:离地 ~120px,在可跳高度内(data-pet-perch 机制演示) */}
<div <div
data-pet-perch data-pet-perch
className="fixed bottom-[120px] left-1/2 h-3 w-56 -translate-x-1/2 rounded-full bg-teal-100 shadow-inner" className="fixed bottom-[120px] left-1/2 h-3 w-56 -translate-x-1/2 rounded-full bg-brand-100 shadow-inner"
title="栖息台(data-pet-perch)" title="栖息台(data-pet-perch)"
/> />
......
...@@ -80,13 +80,13 @@ function Markdown({ text }: { text: string }) { ...@@ -80,13 +80,13 @@ function Markdown({ text }: { text: string }) {
li: ({ children }) => <li className="leading-relaxed">{children}</li>, li: ({ children }) => <li className="leading-relaxed">{children}</li>,
strong: ({ children }) => <strong className="font-semibold text-slate-900">{children}</strong>, strong: ({ children }) => <strong className="font-semibold text-slate-900">{children}</strong>,
a: ({ children, href }) => ( a: ({ children, href }) => (
<a href={href} className="text-teal-700 underline" target="_blank" rel="noreferrer"> <a href={href} className="text-brand-700 underline" target="_blank" rel="noreferrer">
{children} {children}
</a> </a>
), ),
hr: () => <hr className="my-3 border-slate-100" />, hr: () => <hr className="my-3 border-slate-100" />,
blockquote: ({ children }) => ( blockquote: ({ children }) => (
<blockquote className="my-2 border-l-2 border-teal-300 bg-teal-50/40 py-1 pl-3 text-slate-600"> <blockquote className="my-2 border-l-2 border-brand-300 bg-brand-50/40 py-1 pl-3 text-slate-600">
{children} {children}
</blockquote> </blockquote>
), ),
...@@ -265,7 +265,7 @@ function ArtifactView({ artifact }: { artifact: Artifact }) { ...@@ -265,7 +265,7 @@ function ArtifactView({ artifact }: { artifact: Artifact }) {
return ( return (
<div className="overflow-hidden rounded-lg border border-slate-100 bg-white shadow-sm"> <div className="overflow-hidden rounded-lg border border-slate-100 bg-white shadow-sm">
<div className="flex items-center gap-1.5 border-b border-slate-100 bg-slate-50/60 px-3 py-1.5"> <div className="flex items-center gap-1.5 border-b border-slate-100 bg-slate-50/60 px-3 py-1.5">
<LayoutTemplate className="h-3.5 w-3.5 text-teal-600" /> <LayoutTemplate className="h-3.5 w-3.5 text-brand-600" />
<span className="text-[11.5px] font-medium text-slate-500">{artifact.title || '可视化卡片'}</span> <span className="text-[11.5px] font-medium text-slate-500">{artifact.title || '可视化卡片'}</span>
<button <button
type="button" type="button"
...@@ -288,10 +288,10 @@ function ArtifactView({ artifact }: { artifact: Artifact }) { ...@@ -288,10 +288,10 @@ function ArtifactView({ artifact }: { artifact: Artifact }) {
<div className="absolute inset-0 bg-black/40" onClick={() => setExpanded(false)} /> <div className="absolute inset-0 bg-black/40" onClick={() => setExpanded(false)} />
<div className="relative z-10 flex h-[min(86vh,920px)] w-[min(980px,94vw)] flex-col overflow-hidden rounded-xl border border-slate-100 bg-white shadow-2xl"> <div className="relative z-10 flex h-[min(86vh,920px)] w-[min(980px,94vw)] flex-col overflow-hidden rounded-xl border border-slate-100 bg-white shadow-2xl">
<div className="flex flex-none items-center gap-1.5 border-b border-slate-100 bg-slate-50/60 px-3 py-2"> <div className="flex flex-none items-center gap-1.5 border-b border-slate-100 bg-slate-50/60 px-3 py-2">
<LayoutTemplate className="h-4 w-4 text-teal-600" /> <LayoutTemplate className="h-4 w-4 text-brand-600" />
<span className="text-[12.5px] font-medium text-slate-600">{artifact.title || '可视化卡片'}</span> <span className="text-[12.5px] font-medium text-slate-600">{artifact.title || '可视化卡片'}</span>
{artifact.streaming && ( {artifact.streaming && (
<span className="ml-1 inline-flex items-center gap-1 text-[10.5px] text-teal-600"> <span className="ml-1 inline-flex items-center gap-1 text-[10.5px] text-brand-600">
<Loader2 className="h-3 w-3 animate-spin" /> <Loader2 className="h-3 w-3 animate-spin" />
生成中 生成中
</span> </span>
...@@ -327,7 +327,7 @@ function ArtifactView({ artifact }: { artifact: Artifact }) { ...@@ -327,7 +327,7 @@ function ArtifactView({ artifact }: { artifact: Artifact }) {
function GeneratingHint({ label = '生成中' }: { label?: string }) { function GeneratingHint({ label = '生成中' }: { label?: string }) {
return ( return (
<div className="flex items-center gap-1.5 text-[12px] text-slate-400"> <div className="flex items-center gap-1.5 text-[12px] text-slate-400">
<Loader2 className="h-3.5 w-3.5 animate-spin text-teal-500" /> <Loader2 className="h-3.5 w-3.5 animate-spin text-brand-500" />
<span>{label}</span> <span>{label}</span>
</div> </div>
); );
...@@ -351,7 +351,7 @@ function MessageView({ message, streaming }: { message: ChatMessage; streaming?: ...@@ -351,7 +351,7 @@ function MessageView({ message, streaming }: { message: ChatMessage; streaming?:
last && last.kind === 'tool' && last.step.status === 'running' ? toolMeta(last.step.tool).running : '生成中'; last && last.kind === 'tool' && last.step.status === 'running' ? toolMeta(last.step.tool).running : '生成中';
return ( return (
<div className="flex gap-2.5"> <div className="flex gap-2.5">
<span className="mt-0.5 inline-flex h-7 w-7 flex-none items-center justify-center rounded-full bg-teal-600 text-white"> <span className="mt-0.5 inline-flex h-7 w-7 flex-none items-center justify-center rounded-full bg-brand-600 text-white">
<Bot className="h-4 w-4" /> <Bot className="h-4 w-4" />
</span> </span>
<div className="min-w-0 flex-1 space-y-2.5 pt-0.5"> <div className="min-w-0 flex-1 space-y-2.5 pt-0.5">
...@@ -407,7 +407,7 @@ function ModelSelect({ ...@@ -407,7 +407,7 @@ function ModelSelect({
}} }}
className={cn( className={cn(
'flex w-full items-center gap-2 px-3 py-1.5 text-left text-[12.5px] hover:bg-slate-50', 'flex w-full items-center gap-2 px-3 py-1.5 text-left text-[12.5px] hover:bg-slate-50',
m.value === model ? 'font-medium text-teal-700' : 'text-slate-700', m.value === model ? 'font-medium text-brand-700' : 'text-slate-700',
)} )}
> >
{m.value === model ? <Check className="h-3.5 w-3.5" /> : <span className="w-3.5" />} {m.value === model ? <Check className="h-3.5 w-3.5" /> : <span className="w-3.5" />}
...@@ -577,7 +577,7 @@ export function AssistantChat({ ...@@ -577,7 +577,7 @@ export function AssistantChat({
> >
<span <span
className={cn( className={cn(
'inline-flex flex-none items-center justify-center rounded-lg bg-teal-600 text-white', 'inline-flex flex-none items-center justify-center rounded-lg bg-brand-600 text-white',
isWidget ? 'h-6 w-6' : 'h-7 w-7', isWidget ? 'h-6 w-6' : 'h-7 w-7',
)} )}
> >
...@@ -617,7 +617,7 @@ export function AssistantChat({ ...@@ -617,7 +617,7 @@ export function AssistantChat({
<div className="mx-auto max-w-3xl space-y-5 px-4 py-5"> <div className="mx-auto max-w-3xl space-y-5 px-4 py-5">
{messages.length === 0 ? ( {messages.length === 0 ? (
<div className={cn('flex flex-col items-center justify-center gap-4 text-center', isWidget ? 'py-8' : 'py-20')}> <div className={cn('flex flex-col items-center justify-center gap-4 text-center', isWidget ? 'py-8' : 'py-20')}>
<span className="inline-flex h-12 w-12 items-center justify-center rounded-2xl bg-teal-600 text-white"> <span className="inline-flex h-12 w-12 items-center justify-center rounded-2xl bg-brand-600 text-white">
<Bot className="h-6 w-6" /> <Bot className="h-6 w-6" />
</span> </span>
<div className="max-w-md text-[13.5px] text-slate-500"> <div className="max-w-md text-[13.5px] text-slate-500">
...@@ -629,7 +629,7 @@ export function AssistantChat({ ...@@ -629,7 +629,7 @@ export function AssistantChat({
key={ex} key={ex}
type="button" type="button"
onClick={() => fire(ex)} onClick={() => fire(ex)}
className="rounded-full border border-slate-100 bg-white px-3 py-1.5 text-[11.5px] text-slate-600 transition-colors hover:border-teal-300 hover:bg-teal-50/50 hover:text-teal-700" className="rounded-full border border-slate-100 bg-white px-3 py-1.5 text-[11.5px] text-slate-600 transition-colors hover:border-brand-300 hover:bg-brand-50/50 hover:text-brand-700"
> >
{ex} {ex}
</button> </button>
...@@ -651,7 +651,7 @@ export function AssistantChat({ ...@@ -651,7 +651,7 @@ export function AssistantChat({
{/* Composer */} {/* Composer */}
<div className="flex-none border-t border-slate-100 bg-white"> <div className="flex-none border-t border-slate-100 bg-white">
<div className="mx-auto max-w-3xl px-4 py-3"> <div className="mx-auto max-w-3xl px-4 py-3">
<div className="flex items-end gap-2 rounded-xl border border-slate-100 bg-white px-3 py-2 transition-all focus-within:border-teal-300 focus-within:ring-2 focus-within:ring-teal-200"> <div className="flex items-end gap-2 rounded-xl border border-slate-100 bg-white px-3 py-2 transition-all focus-within:border-brand-300 focus-within:ring-2 focus-within:ring-brand-200">
<textarea <textarea
ref={taRef} ref={taRef}
rows={1} rows={1}
...@@ -679,7 +679,7 @@ export function AssistantChat({ ...@@ -679,7 +679,7 @@ export function AssistantChat({
'mb-0.5 inline-flex h-8 w-8 flex-none items-center justify-center rounded-lg transition-colors', 'mb-0.5 inline-flex h-8 w-8 flex-none items-center justify-center rounded-lg transition-colors',
voice === 'live' voice === 'live'
? 'animate-pulse bg-rose-100 text-rose-600 ring-1 ring-inset ring-rose-300' ? 'animate-pulse bg-rose-100 text-rose-600 ring-1 ring-inset ring-rose-300'
: 'bg-slate-100 text-slate-500 hover:bg-teal-50 hover:text-teal-700', : 'bg-slate-100 text-slate-500 hover:bg-brand-50 hover:text-brand-700',
)} )}
> >
<Mic className="h-4 w-4" /> <Mic className="h-4 w-4" />
...@@ -702,7 +702,7 @@ export function AssistantChat({ ...@@ -702,7 +702,7 @@ export function AssistantChat({
title="发送" title="发送"
className={cn( className={cn(
'mb-0.5 inline-flex h-8 w-8 flex-none items-center justify-center rounded-lg transition-colors', 'mb-0.5 inline-flex h-8 w-8 flex-none items-center justify-center rounded-lg transition-colors',
input.trim() ? 'bg-teal-600 text-white hover:bg-teal-700' : 'bg-slate-100 text-slate-400', input.trim() ? 'bg-brand-600 text-white hover:bg-brand-700' : 'bg-slate-100 text-slate-400',
)} )}
> >
<Send className="h-4 w-4" /> <Send className="h-4 w-4" />
......
...@@ -23,7 +23,7 @@ export function IdentityCluster() { ...@@ -23,7 +23,7 @@ export function IdentityCluster() {
{visibleClinics(user).length} 个诊所 · {roleNameZh(user?.role)} {visibleClinics(user).length} 个诊所 · {roleNameZh(user?.role)}
</div> </div>
</div> </div>
<span className="inline-flex h-8 w-8 flex-none items-center justify-center rounded-full bg-gradient-to-br from-teal-400 to-teal-600 text-[12px] font-bold text-white"> <span className="inline-flex h-8 w-8 flex-none items-center justify-center rounded-full bg-gradient-to-br from-brand-400 to-brand-600 text-[12px] font-bold text-white">
{name.charAt(0).toUpperCase()} {name.charAt(0).toUpperCase()}
</span> </span>
<button <button
......
...@@ -35,7 +35,7 @@ import { cn } from '@/lib/utils'; ...@@ -35,7 +35,7 @@ import { cn } from '@/lib/utils';
type BrandSlug = 'ruier' | 'ruitai'; type BrandSlug = 'ruier' | 'ruitai';
const TENANTS: { slug: BrandSlug; nameZh: string; tone: string }[] = [ const TENANTS: { slug: BrandSlug; nameZh: string; tone: string }[] = [
{ slug: 'ruier', nameZh: '瑞尔', tone: 'border-teal-200 bg-teal-50/40' }, { slug: 'ruier', nameZh: '瑞尔', tone: 'border-brand-200 bg-brand-50/40' },
{ slug: 'ruitai', nameZh: '瑞泰', tone: 'border-sky-200 bg-sky-50/40' }, { slug: 'ruitai', nameZh: '瑞泰', tone: 'border-sky-200 bg-sky-50/40' },
]; ];
...@@ -161,7 +161,7 @@ export function MockLoginDialog({ open }: { open: boolean }) { ...@@ -161,7 +161,7 @@ export function MockLoginDialog({ open }: { open: boolean }) {
> >
<DialogHeader> <DialogHeader>
<DialogTitle className="flex items-center gap-2"> <DialogTitle className="flex items-center gap-2">
<span className="inline-flex h-7 w-7 items-center justify-center rounded-md bg-teal-600 text-[12px] font-bold text-white"> <span className="inline-flex h-7 w-7 items-center justify-center rounded-md bg-brand-600 text-[12px] font-bold text-white">
PAC PAC
</span> </span>
<span>快速登录</span> <span>快速登录</span>
...@@ -192,7 +192,7 @@ export function MockLoginDialog({ open }: { open: boolean }) { ...@@ -192,7 +192,7 @@ export function MockLoginDialog({ open }: { open: boolean }) {
className={cn( className={cn(
'flex flex-col items-start rounded-t-md px-3 py-1.5 text-left transition-colors', 'flex flex-col items-start rounded-t-md px-3 py-1.5 text-left transition-colors',
hostTab === t.key hostTab === t.key
? 'border-b-2 border-teal-600 font-semibold text-teal-700' ? 'border-b-2 border-brand-600 font-semibold text-brand-700'
: 'text-slate-500 hover:text-slate-800', : 'text-slate-500 hover:text-slate-800',
)} )}
> >
...@@ -228,8 +228,8 @@ export function MockLoginDialog({ open }: { open: boolean }) { ...@@ -228,8 +228,8 @@ export function MockLoginDialog({ open }: { open: boolean }) {
className={cn( className={cn(
'flex flex-col items-start gap-0.5 rounded-md border px-2.5 py-1.5 text-left transition-all', 'flex flex-col items-start gap-0.5 rounded-md border px-2.5 py-1.5 text-left transition-all',
role === r.key role === r.key
? 'border-teal-400 bg-teal-50/60 ring-1 ring-teal-200' ? 'border-brand-400 bg-brand-50/60 ring-1 ring-brand-200'
: 'border-slate-100 hover:border-teal-300', : 'border-slate-100 hover:border-brand-300',
)} )}
> >
<span className="text-[12.5px] font-semibold text-slate-800">{r.nameZh}</span> <span className="text-[12.5px] font-semibold text-slate-800">{r.nameZh}</span>
...@@ -270,8 +270,8 @@ export function MockLoginDialog({ open }: { open: boolean }) { ...@@ -270,8 +270,8 @@ export function MockLoginDialog({ open }: { open: boolean }) {
className={cn( className={cn(
'rounded-full border px-2.5 py-0.5 text-[11px] transition-colors', 'rounded-full border px-2.5 py-0.5 text-[11px] transition-colors',
tenantFilter === t.k tenantFilter === t.k
? 'border-teal-400 bg-teal-50 text-teal-700' ? 'border-brand-400 bg-brand-50 text-brand-700'
: 'border-slate-100 text-slate-500 hover:border-teal-300', : 'border-slate-100 text-slate-500 hover:border-brand-300',
)} )}
> >
{t.n} {t.n}
...@@ -281,7 +281,7 @@ export function MockLoginDialog({ open }: { open: boolean }) { ...@@ -281,7 +281,7 @@ export function MockLoginDialog({ open }: { open: boolean }) {
value={query} value={query}
onChange={(e) => setQuery(e.target.value)} onChange={(e) => setQuery(e.target.value)}
placeholder="搜客服姓名…" placeholder="搜客服姓名…"
className="ml-auto w-40 rounded-md border border-slate-100 px-2.5 py-1 text-[12px] outline-none focus:border-teal-400" className="ml-auto w-40 rounded-md border border-slate-100 px-2.5 py-1 text-[12px] outline-none focus:border-brand-400"
/> />
</div> </div>
...@@ -300,8 +300,8 @@ export function MockLoginDialog({ open }: { open: boolean }) { ...@@ -300,8 +300,8 @@ export function MockLoginDialog({ open }: { open: boolean }) {
className={cn( className={cn(
'flex w-full items-center justify-between gap-2 rounded-md border px-2.5 py-1.5 text-left transition-all', 'flex w-full items-center justify-between gap-2 rounded-md border px-2.5 py-1.5 text-left transition-all',
loading loading
? 'border-teal-400 ring-2 ring-teal-200' ? 'border-brand-400 ring-2 ring-brand-200'
: 'border-slate-100 hover:border-teal-400 hover:bg-teal-50/40', : 'border-slate-100 hover:border-brand-400 hover:bg-brand-50/40',
busy && !loading && 'opacity-50', busy && !loading && 'opacity-50',
)} )}
> >
...@@ -347,8 +347,8 @@ export function MockLoginDialog({ open }: { open: boolean }) { ...@@ -347,8 +347,8 @@ export function MockLoginDialog({ open }: { open: boolean }) {
className={cn( className={cn(
'group flex flex-col items-start gap-1 rounded-md border bg-white px-3 py-2 text-left transition-all', 'group flex flex-col items-start gap-1 rounded-md border bg-white px-3 py-2 text-left transition-all',
loading loading
? 'border-teal-400 ring-2 ring-teal-200' ? 'border-brand-400 ring-2 ring-brand-200'
: 'border-slate-100 hover:border-teal-400 hover:bg-teal-50/40', : 'border-slate-100 hover:border-brand-400 hover:bg-brand-50/40',
busy && !loading && 'opacity-50', busy && !loading && 'opacity-50',
)} )}
> >
...@@ -429,8 +429,8 @@ function FridayPanel({ ...@@ -429,8 +429,8 @@ function FridayPanel({
className={cn( className={cn(
'flex flex-col items-start gap-0.5 rounded-md border px-2.5 py-1.5 text-left transition-all', 'flex flex-col items-start gap-0.5 rounded-md border px-2.5 py-1.5 text-left transition-all',
role === r.key role === r.key
? 'border-teal-400 bg-teal-50/60 ring-1 ring-teal-200' ? 'border-brand-400 bg-brand-50/60 ring-1 ring-brand-200'
: 'border-slate-100 hover:border-teal-300', : 'border-slate-100 hover:border-brand-300',
)} )}
> >
<span className="text-[12.5px] font-semibold text-slate-800">{r.nameZh}</span> <span className="text-[12.5px] font-semibold text-slate-800">{r.nameZh}</span>
...@@ -465,7 +465,7 @@ function FridayPanel({ ...@@ -465,7 +465,7 @@ function FridayPanel({
value={query} value={query}
onChange={(e) => setQuery(e.target.value)} onChange={(e) => setQuery(e.target.value)}
placeholder="搜品牌 / 诊所名…" placeholder="搜品牌 / 诊所名…"
className="mb-2 h-7 w-full rounded-md border border-slate-100 bg-slate-50 px-2 text-[12px] outline-none placeholder:text-slate-400 focus:border-teal-300 focus:bg-white" className="mb-2 h-7 w-full rounded-md border border-slate-100 bg-slate-50 px-2 text-[12px] outline-none placeholder:text-slate-400 focus:border-brand-300 focus:bg-white"
/> />
<div className="max-h-[280px] space-y-2 overflow-y-auto"> <div className="max-h-[280px] space-y-2 overflow-y-auto">
{filtered.length === 0 && ( {filtered.length === 0 && (
...@@ -480,7 +480,7 @@ function FridayPanel({ ...@@ -480,7 +480,7 @@ function FridayPanel({
type="button" type="button"
onClick={() => onPick(role, b.brandId)} onClick={() => onPick(role, b.brandId)}
disabled={!!busy} disabled={!!busy}
className="flex w-full items-center justify-between gap-2 rounded-t-md bg-slate-50/60 px-2.5 py-1.5 text-left transition-colors hover:bg-teal-50/60 disabled:opacity-50" className="flex w-full items-center justify-between gap-2 rounded-t-md bg-slate-50/60 px-2.5 py-1.5 text-left transition-colors hover:bg-brand-50/60 disabled:opacity-50"
> >
<span className="truncate text-[12.5px] font-semibold text-slate-800">{b.name}</span> <span className="truncate text-[12.5px] font-semibold text-slate-800">{b.name}</span>
<span className="flex-none text-[10.5px] text-slate-500"> <span className="flex-none text-[10.5px] text-slate-500">
...@@ -495,7 +495,7 @@ function FridayPanel({ ...@@ -495,7 +495,7 @@ function FridayPanel({
type="button" type="button"
onClick={() => onPick(role, c.clinicId)} onClick={() => onPick(role, c.clinicId)}
disabled={!!busy} disabled={!!busy}
className="rounded-full border border-slate-100 px-2 py-0.5 text-[11px] text-slate-600 transition-colors hover:border-teal-300 hover:bg-teal-50 hover:text-teal-700 disabled:opacity-50" className="rounded-full border border-slate-100 px-2 py-0.5 text-[11px] text-slate-600 transition-colors hover:border-brand-300 hover:bg-brand-50 hover:text-brand-700 disabled:opacity-50"
> >
{c.name} {c.name}
</button> </button>
......
...@@ -354,9 +354,9 @@ export function PetBody({ ...@@ -354,9 +354,9 @@ export function PetBody({
)} )}
{vetFluoride && ( {vetFluoride && (
<> <>
<line x1="45" y1="51" x2="37" y2="41" stroke="#0d9488" strokeWidth="2" strokeLinecap="round" /> <line x1="45" y1="51" x2="37" y2="41" stroke="#0032A0" strokeWidth="2" strokeLinecap="round" />
<ellipse className="pac-vet-buzz" cx="35" cy="39" rx="2.4" ry="1.6" fill="#5eead4" stroke="#0d9488" strokeWidth="0.6" /> <ellipse className="pac-vet-buzz" cx="35" cy="39" rx="2.4" ry="1.6" fill="#85A9FA" stroke="#0032A0" strokeWidth="0.6" />
<g fill="#99f6e4" stroke="#5eead4" strokeWidth="0.4"> <g fill="#BACFFD" stroke="#85A9FA" strokeWidth="0.4">
<circle className="pac-pet-rinse-b" cx="33" cy="36" r="1.1" /> <circle className="pac-pet-rinse-b" cx="33" cy="36" r="1.1" />
<circle className="pac-pet-rinse-b" cx="37" cy="35" r="1.4" style={{ animationDelay: '0.5s' }} /> <circle className="pac-pet-rinse-b" cx="37" cy="35" r="1.4" style={{ animationDelay: '0.5s' }} />
</g> </g>
...@@ -366,7 +366,7 @@ export function PetBody({ ...@@ -366,7 +366,7 @@ export function PetBody({
)} )}
{/* 思考:头顶冒点点 */} {/* 思考:头顶冒点点 */}
{thinking && ( {thinking && (
<g fill="#0d9488"> <g fill="#0032A0">
<circle className="pac-pet-dot" cx="46" cy="12" r="2" style={{ animationDelay: '0s' }} /> <circle className="pac-pet-dot" cx="46" cy="12" r="2" style={{ animationDelay: '0s' }} />
<circle className="pac-pet-dot" cx="52" cy="8" r="2.6" style={{ animationDelay: '0.2s' }} /> <circle className="pac-pet-dot" cx="52" cy="8" r="2.6" style={{ animationDelay: '0.2s' }} />
<circle className="pac-pet-dot" cx="58" cy="3.5" r="3.2" style={{ animationDelay: '0.4s' }} /> <circle className="pac-pet-dot" cx="58" cy="3.5" r="3.2" style={{ animationDelay: '0.4s' }} />
...@@ -377,7 +377,7 @@ export function PetBody({ ...@@ -377,7 +377,7 @@ export function PetBody({
<> <>
<g className="pac-pet-scrub"> <g className="pac-pet-scrub">
{/* 刷柄(品牌青) + 刷毛 */} {/* 刷柄(品牌青) + 刷毛 */}
<rect x="22" y="50" width="20" height="3.6" rx="1.8" fill="#0d9488" transform="rotate(-8 32 52)" /> <rect x="22" y="50" width="20" height="3.6" rx="1.8" fill="#0032A0" transform="rotate(-8 32 52)" />
<rect x="38" y="45.5" width="7" height="6" rx="1.2" fill="#ffffff" stroke="#e2e8f0" strokeWidth="0.8" transform="rotate(-8 41 48)" /> <rect x="38" y="45.5" width="7" height="6" rx="1.2" fill="#ffffff" stroke="#e2e8f0" strokeWidth="0.8" transform="rotate(-8 41 48)" />
</g> </g>
<g fill="#a5f3fc" stroke="#67e8f9" strokeWidth="0.5"> <g fill="#a5f3fc" stroke="#67e8f9" strokeWidth="0.5">
...@@ -390,9 +390,9 @@ export function PetBody({ ...@@ -390,9 +390,9 @@ export function PetBody({
{/* 牙线自理:头顶拉一根牙线锯冠缝 */} {/* 牙线自理:头顶拉一根牙线锯冠缝 */}
{flossing && ( {flossing && (
<g className="pac-pet-floss"> <g className="pac-pet-floss">
<path d="M8 13 Q32 22 56 13" stroke="#5eead4" strokeWidth="1.6" fill="none" strokeLinecap="round" /> <path d="M8 13 Q32 22 56 13" stroke="#85A9FA" strokeWidth="1.6" fill="none" strokeLinecap="round" />
<circle cx="8" cy="13" r="2.2" fill="#0d9488" /> <circle cx="8" cy="13" r="2.2" fill="#0032A0" />
<circle cx="56" cy="13" r="2.2" fill="#0d9488" /> <circle cx="56" cy="13" r="2.2" fill="#0032A0" />
</g> </g>
)} )}
{/* 闪亮微笑:牙面 ding 两颗星 */} {/* 闪亮微笑:牙面 ding 两颗星 */}
...@@ -428,7 +428,7 @@ export function PetBody({ ...@@ -428,7 +428,7 @@ export function PetBody({
)} )}
{/* 睡觉:Zzz */} {/* 睡觉:Zzz */}
{sleeping && ( {sleeping && (
<g fill="#0d9488" fontWeight={700} fontFamily="ui-sans-serif"> <g fill="#0032A0" fontWeight={700} fontFamily="ui-sans-serif">
<text className="pac-pet-z" x="46" y="14" fontSize="9" style={{ animationDelay: '0s' }}>z</text> <text className="pac-pet-z" x="46" y="14" fontSize="9" style={{ animationDelay: '0s' }}>z</text>
<text className="pac-pet-z" x="52" y="8" fontSize="12" style={{ animationDelay: '0.6s' }}>Z</text> <text className="pac-pet-z" x="52" y="8" fontSize="12" style={{ animationDelay: '0.6s' }}>Z</text>
</g> </g>
......
...@@ -536,7 +536,7 @@ function ChainListItem({ ...@@ -536,7 +536,7 @@ function ChainListItem({
const activeRing: Record<string, string> = { const activeRing: Record<string, string> = {
rose: 'border-rose-400 bg-rose-50 ring-1 ring-rose-200', rose: 'border-rose-400 bg-rose-50 ring-1 ring-rose-200',
amber: 'border-amber-400 bg-amber-50 ring-1 ring-amber-200', amber: 'border-amber-400 bg-amber-50 ring-1 ring-amber-200',
sky: 'border-teal-400 bg-teal-50 ring-1 ring-teal-200', sky: 'border-brand-400 bg-brand-50 ring-1 ring-brand-200',
emerald: 'border-emerald-400 bg-emerald-50 ring-1 ring-emerald-200', emerald: 'border-emerald-400 bg-emerald-50 ring-1 ring-emerald-200',
slate: 'border-slate-300 bg-slate-50 ring-1 ring-slate-200', slate: 'border-slate-300 bg-slate-50 ring-1 ring-slate-200',
}; };
......
...@@ -85,14 +85,14 @@ export function CloseOpportunityDialog({ ...@@ -85,14 +85,14 @@ export function CloseOpportunityDialog({
'flex items-center gap-2 rounded-md border px-3 py-2.5 text-left text-[12.5px] transition-colors', 'flex items-center gap-2 rounded-md border px-3 py-2.5 text-left text-[12.5px] transition-colors',
r.key === 'other' && 'col-span-2', r.key === 'other' && 'col-span-2',
active active
? 'border-teal-400 bg-teal-50 font-medium text-teal-800' ? 'border-brand-400 bg-brand-50 font-medium text-brand-800'
: 'border-slate-200 bg-white text-slate-700 hover:border-teal-300 hover:bg-teal-50/50', : 'border-slate-200 bg-white text-slate-700 hover:border-brand-300 hover:bg-brand-50/50',
)} )}
> >
<span <span
className={cn( className={cn(
'flex h-3.5 w-3.5 flex-none items-center justify-center rounded-[3px] border', 'flex h-3.5 w-3.5 flex-none items-center justify-center rounded-[3px] border',
active ? 'border-teal-600 bg-teal-600' : 'border-slate-300', active ? 'border-brand-600 bg-brand-600' : 'border-slate-300',
)} )}
> >
{active && ( {active && (
...@@ -124,14 +124,14 @@ export function CloseOpportunityDialog({ ...@@ -124,14 +124,14 @@ export function CloseOpportunityDialog({
onChange={(e) => setNote(e.target.value)} onChange={(e) => setNote(e.target.value)}
rows={3} rows={3}
placeholder="请详细描述关闭机会的具体原因,以便后续分析和改进…" placeholder="请详细描述关闭机会的具体原因,以便后续分析和改进…"
className="w-full resize-y rounded-md border border-slate-200 bg-white px-3 py-2 text-[12.5px] text-slate-900 placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-teal-200 focus:border-teal-400" className="w-full resize-y rounded-md border border-slate-200 bg-white px-3 py-2 text-[12.5px] text-slate-900 placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-brand-200 focus:border-brand-400"
/> />
</div> </div>
)} )}
{selected.length > 0 && ( {selected.length > 0 && (
<div className="rounded-md border border-teal-100 bg-teal-50/60 px-3 py-2"> <div className="rounded-md border border-brand-100 bg-brand-50/60 px-3 py-2">
<div className="text-[11.5px] font-semibold text-teal-800"> <div className="text-[11.5px] font-semibold text-brand-800">
已选关闭原因 · {selected.length} 已选关闭原因 · {selected.length}
</div> </div>
<ul className="mt-0.5 space-y-0.5"> <ul className="mt-0.5 space-y-0.5">
......
...@@ -339,14 +339,14 @@ function ReturnVisitsList({ visits }: { visits: ReturnVisitItem[] }) { ...@@ -339,14 +339,14 @@ function ReturnVisitsList({ visits }: { visits: ReturnVisitItem[] }) {
key={i} key={i}
className={cn( className={cn(
'flex items-start gap-2.5 text-[12.5px] py-1 border-b border-slate-50 last:border-0', 'flex items-start gap-2.5 text-[12.5px] py-1 border-b border-slate-50 last:border-0',
latest && 'bg-teal-50/50 -mx-2 px-2 rounded', latest && 'bg-brand-50/50 -mx-2 px-2 rounded',
)} )}
> >
<span className={cn('flex-none mt-[5px] w-1.5 h-1.5 rounded-full', done ? 'bg-teal-400' : 'bg-slate-300')} /> <span className={cn('flex-none mt-[5px] w-1.5 h-1.5 rounded-full', done ? 'bg-brand-400' : 'bg-slate-300')} />
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<div className="flex items-center gap-1.5 flex-wrap"> <div className="flex items-center gap-1.5 flex-wrap">
{latest && ( {latest && (
<span className="flex-none rounded bg-teal-600 px-1 py-px text-[10px] font-medium text-white"> <span className="flex-none rounded bg-brand-600 px-1 py-px text-[10px] font-medium text-white">
最近一次 最近一次
</span> </span>
)} )}
...@@ -356,7 +356,7 @@ function ReturnVisitsList({ visits }: { visits: ReturnVisitItem[] }) { ...@@ -356,7 +356,7 @@ function ReturnVisitsList({ visits }: { visits: ReturnVisitItem[] }) {
<span className="font-medium text-slate-700">{v.type ?? '回访'}</span> <span className="font-medium text-slate-700">{v.type ?? '回访'}</span>
{v.status && <span className="text-slate-400">· {v.status}</span>} {v.status && <span className="text-slate-400">· {v.status}</span>}
{v.taskDate && <span className="text-slate-400 tabular-nums">{v.taskDate}</span>} {v.taskDate && <span className="text-slate-400 tabular-nums">{v.taskDate}</span>}
{v.treatmentItems && <span className="text-teal-700/70">· {v.treatmentItems}</span>} {v.treatmentItems && <span className="text-brand-700/70">· {v.treatmentItems}</span>}
</div> </div>
{v.followContent && ( {v.followContent && (
<div className="text-slate-600 leading-snug break-words mt-0.5">{v.followContent}</div> <div className="text-slate-600 leading-snug break-words mt-0.5">{v.followContent}</div>
......
...@@ -324,7 +324,7 @@ function EmrSection({ ...@@ -324,7 +324,7 @@ function EmrSection({
const tooth = String(tc.tooth_position ?? ''); const tooth = String(tc.tooth_position ?? '');
return ( return (
<li key={tx.id} className="text-[12px] text-slate-700 leading-relaxed"> <li key={tx.id} className="text-[12px] text-slate-700 leading-relaxed">
<span className="px-1.5 py-px mr-1.5 bg-teal-50 text-teal-700 rounded text-[10.5px] font-medium"> <span className="px-1.5 py-px mr-1.5 bg-brand-50 text-brand-700 rounded text-[10.5px] font-medium">
{badgeText} {badgeText}
</span> </span>
<span className="font-medium">{subtype}</span> <span className="font-medium">{subtype}</span>
......
...@@ -47,7 +47,7 @@ export function factLabel(f: AdaptedFact): FactLabel { ...@@ -47,7 +47,7 @@ export function factLabel(f: AdaptedFact): FactLabel {
const isPlanned = f.kind === 'planned'; const isPlanned = f.kind === 'planned';
return { return {
badge: isPlanned ? '计划' : catZh || '治疗', badge: isPlanned ? '计划' : catZh || '治疗',
badgeTone: isPlanned ? 'bg-slate-100 text-slate-500' : 'bg-teal-50 text-teal-700', badgeTone: isPlanned ? 'bg-slate-100 text-slate-500' : 'bg-brand-50 text-brand-700',
text, text,
}; };
} }
...@@ -95,7 +95,7 @@ export function factLabel(f: AdaptedFact): FactLabel { ...@@ -95,7 +95,7 @@ export function factLabel(f: AdaptedFact): FactLabel {
// ⚠️ 下面三类的 f.title 都是 `<中文> <externalId>`(接诊 16432552 / 挂号 xxx / 前台接待 xxx)—— // ⚠️ 下面三类的 f.title 都是 `<中文> <externalId>`(接诊 16432552 / 挂号 xxx / 前台接待 xxx)——
// 直接显示等于把宿主内部 ID 甩给客服。一律改取 content 里有语义的字段,取不到就只留中文词。 // 直接显示等于把宿主内部 ID 甩给客服。一律改取 content 里有语义的字段,取不到就只留中文词。
case 'encounter_record': { case 'encounter_record': {
const tone = { badge: '就诊', badgeTone: 'bg-teal-50 text-teal-700' }; const tone = { badge: '就诊', badgeTone: 'bg-brand-50 text-brand-700' };
const cc = String(c.chief_complaint ?? '').trim() || String(f.summary ?? '').trim(); const cc = String(c.chief_complaint ?? '').trim() || String(f.summary ?? '').trim();
const doc = String(c.doctor_name ?? '').trim(); const doc = String(c.doctor_name ?? '').trim();
if (cc) return { ...tone, text: cc, hover: doc ? `${cc}(${doc}医生)` : cc }; if (cc) return { ...tone, text: cc, hover: doc ? `${cc}(${doc}医生)` : cc };
...@@ -105,10 +105,10 @@ export function factLabel(f: AdaptedFact): FactLabel { ...@@ -105,10 +105,10 @@ export function factLabel(f: AdaptedFact): FactLabel {
const doc = String(c.doctor_name ?? '').trim(); const doc = String(c.doctor_name ?? '').trim();
const dept = String(c.department ?? c.department_name ?? '').trim(); const dept = String(c.department ?? c.department_name ?? '').trim();
const text = [dept, doc ? `${doc}医生` : ''].filter(Boolean).join(' · ') || '挂号'; const text = [dept, doc ? `${doc}医生` : ''].filter(Boolean).join(' · ') || '挂号';
return { badge: '挂号', badgeTone: 'bg-teal-50 text-teal-700', text }; return { badge: '挂号', badgeTone: 'bg-brand-50 text-brand-700', text };
} }
case 'visit_reception_record': case 'visit_reception_record':
return { badge: '接待', badgeTone: 'bg-teal-50 text-teal-700', text: '前台接待' }; return { badge: '接待', badgeTone: 'bg-brand-50 text-brand-700', text: '前台接待' };
case 'emr_record': { case 'emr_record': {
const tone = { badge: '病历', badgeTone: 'bg-slate-100 text-slate-600' }; const tone = { badge: '病历', badgeTone: 'bg-slate-100 text-slate-600' };
// ⚠️ **不能用 f.summary** —— emr parser 写进去的是 `关联接诊:<encounter_id>`(内部 ID), // ⚠️ **不能用 f.summary** —— emr parser 写进去的是 `关联接诊:<encounter_id>`(内部 ID),
......
...@@ -536,7 +536,7 @@ const TONE_FALLBACK: ToneCls = { bg: 'bg-slate-100', text: 'text-slate-700', bor ...@@ -536,7 +536,7 @@ const TONE_FALLBACK: ToneCls = { bg: 'bg-slate-100', text: 'text-slate-700', bor
const TONE = { const TONE = {
rose: { bg: 'bg-rose-100', text: 'text-rose-700', border: 'border-rose-200' }, rose: { bg: 'bg-rose-100', text: 'text-rose-700', border: 'border-rose-200' },
teal: { bg: 'bg-teal-100', text: 'text-teal-700', border: 'border-teal-200' }, brand: { bg: 'bg-brand-100', text: 'text-brand-700', border: 'border-brand-200' },
indigo: { bg: 'bg-indigo-100', text: 'text-indigo-700', border: 'border-indigo-200' }, indigo: { bg: 'bg-indigo-100', text: 'text-indigo-700', border: 'border-indigo-200' },
slate: TONE_FALLBACK, slate: TONE_FALLBACK,
sky: { bg: 'bg-sky-100', text: 'text-sky-700', border: 'border-sky-200' }, sky: { bg: 'bg-sky-100', text: 'text-sky-700', border: 'border-sky-200' },
......
...@@ -248,7 +248,7 @@ export const mockPersona = { ...@@ -248,7 +248,7 @@ export const mockPersona = {
key: PersonaFeatureKey.LIFECYCLE_STAGE, key: PersonaFeatureKey.LIFECYCLE_STAGE,
label: '生命周期', label: '生命周期',
value: '成熟客 · 末诊140天 · 就诊20次', value: '成熟客 · 末诊140天 · 就诊20次',
tone: 'teal', tone: 'brand',
evidence: ['fact_visit_2026_04'], evidence: ['fact_visit_2026_04'],
data: { stage: 'mature', label: '成熟客', recencyDays: 140, totalVisits: 20 }, data: { stage: 'mature', label: '成熟客', recencyDays: 140, totalVisits: 20 },
}, },
......
...@@ -311,7 +311,7 @@ export function OutcomeForm({ ...@@ -311,7 +311,7 @@ export function OutcomeForm({
value={notes} value={notes}
onChange={(e) => setNotes(e.target.value)} onChange={(e) => setNotes(e.target.value)}
placeholder="患者反馈、关键点、需告知医生的信息……" placeholder="患者反馈、关键点、需告知医生的信息……"
className="flex-1 min-h-[60px] px-2.5 py-1.5 rounded border border-slate-100 bg-white text-[12.5px] resize-none focus:outline-none focus:ring-2 focus:ring-teal-200 focus:border-teal-300" className="flex-1 min-h-[60px] px-2.5 py-1.5 rounded border border-slate-100 bg-white text-[12.5px] resize-none focus:outline-none focus:ring-2 focus:ring-brand-200 focus:border-brand-300"
/> />
</div> </div>
...@@ -329,7 +329,7 @@ export function OutcomeForm({ ...@@ -329,7 +329,7 @@ export function OutcomeForm({
submitted submitted
? 'bg-emerald-600 text-white' ? 'bg-emerald-600 text-white'
: canSubmit : canSubmit
? 'bg-teal-600 text-white hover:bg-teal-700' ? 'bg-brand-600 text-white hover:bg-brand-700'
: 'bg-slate-100 text-slate-400 cursor-not-allowed', : 'bg-slate-100 text-slate-400 cursor-not-allowed',
)} )}
> >
......
...@@ -155,14 +155,14 @@ function FeatureCard({ ...@@ -155,14 +155,14 @@ function FeatureCard({
ref={cardRef} ref={cardRef}
className={cn( className={cn(
'relative rounded-md border p-3 pr-9', 'relative rounded-md border p-3 pr-9',
focused ? 'border-teal-300 bg-teal-50/40 ring-1 ring-teal-200' : 'border-slate-100', focused ? 'border-brand-300 bg-brand-50/40 ring-1 ring-brand-200' : 'border-slate-100',
)} )}
> >
{/* 右上角 ? —— 真 button:可 Tab 聚焦,focus 也能唤出说明卡(触屏/键盘可达) */} {/* 右上角 ? —— 真 button:可 Tab 聚焦,focus 也能唤出说明卡(触屏/键盘可达) */}
<PersonaFeatureHover featureKey={f.key} value={f.value}> <PersonaFeatureHover featureKey={f.key} value={f.value}>
<button <button
type="button" type="button"
className="absolute top-1.5 right-1.5 inline-flex h-6 w-6 items-center justify-center rounded-full text-slate-400 hover:bg-slate-100 hover:text-slate-700 focus:outline-none focus-visible:ring-2 focus-visible:ring-teal-500" className="absolute top-1.5 right-1.5 inline-flex h-6 w-6 items-center justify-center rounded-full text-slate-400 hover:bg-slate-100 hover:text-slate-700 focus:outline-none focus-visible:ring-2 focus-visible:ring-brand-500"
aria-label={`${f.label} 怎么算的`} aria-label={`${f.label} 怎么算的`}
> >
<svg viewBox="0 0 24 24" className="h-3.5 w-3.5" fill="none" stroke="currentColor" strokeWidth="2"> <svg viewBox="0 0 24 24" className="h-3.5 w-3.5" fill="none" stroke="currentColor" strokeWidth="2">
...@@ -211,7 +211,7 @@ function FeatureCard({ ...@@ -211,7 +211,7 @@ function FeatureCard({
<button <button
type="button" type="button"
onClick={() => setShowAll((v) => !v)} onClick={() => setShowAll((v) => !v)}
className="text-[10.5px] text-teal-700 hover:underline" className="text-[10.5px] text-brand-700 hover:underline"
> >
{showAll ? '收起' : `展开全部`} {showAll ? '收起' : `展开全部`}
</button> </button>
......
...@@ -44,7 +44,7 @@ export function PersonaFeatureHover({ ...@@ -44,7 +44,7 @@ export function PersonaFeatureHover({
</div> </div>
{/* 本患者实际取值 */} {/* 本患者实际取值 */}
{value && ( {value && (
<div className="rounded bg-teal-50 px-2 py-1.5 text-[11px] leading-relaxed text-teal-800"> <div className="rounded bg-brand-50 px-2 py-1.5 text-[11px] leading-relaxed text-brand-800">
{value} {value}
</div> </div>
)} )}
......
...@@ -603,7 +603,7 @@ export function PlanDetailApp({ ...@@ -603,7 +603,7 @@ export function PlanDetailApp({
</span> </span>
)} )}
{focusTreatment && ( {focusTreatment && (
<span className="inline-flex items-center rounded px-1.5 py-0.5 text-[10.5px] font-medium bg-teal-50 text-teal-700 border border-teal-100"> <span className="inline-flex items-center rounded px-1.5 py-0.5 text-[10.5px] font-medium bg-brand-50 text-brand-700 border border-brand-100">
目标 · {focusTreatment} 目标 · {focusTreatment}
</span> </span>
)} )}
...@@ -1055,7 +1055,7 @@ function TopBar({ ...@@ -1055,7 +1055,7 @@ function TopBar({
{!embedded && ( {!embedded && (
<> <>
<div className="inline-flex flex-none items-center gap-2"> <div className="inline-flex flex-none items-center gap-2">
<span className="inline-flex h-7 w-7 items-center justify-center rounded-md bg-teal-600 text-[12px] font-bold text-white"> <span className="inline-flex h-7 w-7 items-center justify-center rounded-md bg-brand-600 text-[12px] font-bold text-white">
PAC PAC
</span> </span>
{/* "疗效保障"标题字在窄屏隐藏,腾出空间给关键信息 */} {/* "疗效保障"标题字在窄屏隐藏,腾出空间给关键信息 */}
...@@ -1132,7 +1132,7 @@ function TopBar({ ...@@ -1132,7 +1132,7 @@ function TopBar({
'inline-flex items-center gap-1.5 rounded-md border px-2 sm:px-2.5 py-1 text-[11.5px] font-medium transition-colors', 'inline-flex items-center gap-1.5 rounded-md border px-2 sm:px-2.5 py-1 text-[11.5px] font-medium transition-colors',
refreshing refreshing
? 'cursor-not-allowed border-slate-100 bg-slate-50 text-slate-400' ? 'cursor-not-allowed border-slate-100 bg-slate-50 text-slate-400'
: 'border-slate-100 bg-white text-slate-700 hover:border-teal-300 hover:bg-teal-50 hover:text-teal-700', : 'border-slate-100 bg-white text-slate-700 hover:border-brand-300 hover:bg-brand-50 hover:text-brand-700',
)} )}
> >
<RefreshCw className={cn('h-3.5 w-3.5', refreshing && 'animate-spin')} /> <RefreshCw className={cn('h-3.5 w-3.5', refreshing && 'animate-spin')} />
...@@ -1147,7 +1147,7 @@ function TopBar({ ...@@ -1147,7 +1147,7 @@ function TopBar({
type="button" type="button"
onClick={onOpenPotential} onClick={onOpenPotential}
title="查看潜在治疗机会" title="查看潜在治疗机会"
className="inline-flex items-center gap-1.5 rounded-md border border-slate-100 bg-white px-2 sm:px-2.5 py-1 text-[11.5px] font-medium text-slate-700 transition-colors hover:border-teal-300 hover:bg-teal-50 hover:text-teal-700" className="inline-flex items-center gap-1.5 rounded-md border border-slate-100 bg-white px-2 sm:px-2.5 py-1 text-[11.5px] font-medium text-slate-700 transition-colors hover:border-brand-300 hover:bg-brand-50 hover:text-brand-700"
> >
<Sparkles className="h-3.5 w-3.5" /> <Sparkles className="h-3.5 w-3.5" />
<span className="hidden sm:inline">潜在</span> <span className="hidden sm:inline">潜在</span>
...@@ -1158,7 +1158,7 @@ function TopBar({ ...@@ -1158,7 +1158,7 @@ function TopBar({
type="button" type="button"
onClick={onCreateAppointment} onClick={onCreateAppointment}
title="新建预约" title="新建预约"
className="inline-flex items-center gap-1.5 rounded-md border border-slate-100 bg-white px-2 sm:px-2.5 py-1 text-[11.5px] font-medium text-slate-700 transition-colors hover:border-teal-300 hover:bg-teal-50 hover:text-teal-700" className="inline-flex items-center gap-1.5 rounded-md border border-slate-100 bg-white px-2 sm:px-2.5 py-1 text-[11.5px] font-medium text-slate-700 transition-colors hover:border-brand-300 hover:bg-brand-50 hover:text-brand-700"
> >
<CalendarPlus className="h-3.5 w-3.5" /> <CalendarPlus className="h-3.5 w-3.5" />
<span className="hidden sm:inline">预约</span> <span className="hidden sm:inline">预约</span>
...@@ -1170,7 +1170,7 @@ function TopBar({ ...@@ -1170,7 +1170,7 @@ function TopBar({
onClick={onOpenReturnVisit} onClick={onOpenReturnVisit}
onMouseEnter={onHoverReturnVisit} onMouseEnter={onHoverReturnVisit}
title="回访" title="回访"
className="inline-flex items-center gap-1.5 rounded-md bg-teal-600 px-2 sm:px-2.5 py-1 text-[11.5px] font-medium text-white transition-colors hover:bg-teal-700" className="inline-flex items-center gap-1.5 rounded-md bg-brand-600 px-2 sm:px-2.5 py-1 text-[11.5px] font-medium text-white transition-colors hover:bg-brand-700"
> >
<CalendarClock className="h-3.5 w-3.5" /> <CalendarClock className="h-3.5 w-3.5" />
<span className="hidden sm:inline">回访</span> <span className="hidden sm:inline">回访</span>
...@@ -1373,7 +1373,7 @@ function IdentityCard({ ...@@ -1373,7 +1373,7 @@ function IdentityCard({
<a <a
href={originalArchiveUrl} href={originalArchiveUrl}
{...HOST_LINK_PROPS} {...HOST_LINK_PROPS}
className="text-[10.5px] text-teal-700 hover:underline" className="text-[10.5px] text-brand-700 hover:underline"
> >
原始档案 → 原始档案 →
</a> </a>
...@@ -1409,7 +1409,7 @@ function IdentityCard({ ...@@ -1409,7 +1409,7 @@ function IdentityCard({
className={cn( className={cn(
'inline-flex items-center rounded px-1 text-[10px] font-medium leading-4 ring-1 ring-inset', 'inline-flex items-center rounded px-1 text-[10px] font-medium leading-4 ring-1 ring-inset',
patient.phoneVerified patient.phoneVerified
? 'bg-teal-50 text-teal-700 ring-teal-200' ? 'bg-brand-50 text-brand-700 ring-brand-200'
: 'bg-slate-50 text-slate-400 ring-slate-200', : 'bg-slate-50 text-slate-400 ring-slate-200',
)} )}
> >
...@@ -1421,7 +1421,7 @@ function IdentityCard({ ...@@ -1421,7 +1421,7 @@ function IdentityCard({
title="复制号码" title="复制号码"
className={cn( className={cn(
'inline-flex items-center justify-center w-5 h-5 rounded transition-colors', 'inline-flex items-center justify-center w-5 h-5 rounded transition-colors',
copied ? 'text-emerald-600 bg-emerald-50' : 'text-slate-400 hover:text-teal-700 hover:bg-teal-50', copied ? 'text-emerald-600 bg-emerald-50' : 'text-slate-400 hover:text-brand-700 hover:bg-brand-50',
)} )}
> >
{copied ? ( {copied ? (
...@@ -1493,7 +1493,7 @@ function PersonaTagsCard({ ...@@ -1493,7 +1493,7 @@ function PersonaTagsCard({
title="画像标签" title="画像标签"
meta={features.length > 0 ? `${features.length} 项` : undefined} meta={features.length > 0 ? `${features.length} 项` : undefined}
action={ action={
<button onClick={onOpenDetail} className="text-[10.5px] text-teal-700 hover:underline"> <button onClick={onOpenDetail} className="text-[10.5px] text-brand-700 hover:underline">
详情 → 详情 →
</button> </button>
} }
...@@ -1534,7 +1534,7 @@ function RecallReasonLine({ visibleReasons }: { visibleReasons: PlanReason[] }) ...@@ -1534,7 +1534,7 @@ function RecallReasonLine({ visibleReasons }: { visibleReasons: PlanReason[] })
{visibleReasons.length > 1 && ( {visibleReasons.length > 1 && (
<HoverCard openDelay={120} closeDelay={80}> <HoverCard openDelay={120} closeDelay={80}>
<HoverCardTrigger asChild> <HoverCardTrigger asChild>
<button className="ml-1.5 align-middle rounded px-1 text-[10.5px] text-slate-400 hover:text-teal-700 hover:bg-slate-100 cursor-default"> <button className="ml-1.5 align-middle rounded px-1 text-[10.5px] text-slate-400 hover:text-brand-700 hover:bg-slate-100 cursor-default">
+{visibleReasons.length - 1} +{visibleReasons.length - 1}
</button> </button>
</HoverCardTrigger> </HoverCardTrigger>
...@@ -1596,7 +1596,7 @@ function RecallBriefLine({ ...@@ -1596,7 +1596,7 @@ function RecallBriefLine({
}, [planId]); }, [planId]);
if (summary) { if (summary) {
// 主题色阶背景:左侧 teal 实条 + 向右渐隐的 teal 底,突出"这通电话的由头"。 // 主题色阶背景:左侧品牌色实条 + 向右渐隐的品牌色底,突出"这通电话的由头"。
// 右侧箭头展开 → 看摘要"压掉"的结构化召回原因(=以前的「为什么召回」,样式沿用 RecallReasonLine)。 // 右侧箭头展开 → 看摘要"压掉"的结构化召回原因(=以前的「为什么召回」,样式沿用 RecallReasonLine)。
const canExpand = visibleReasons.length > 0; const canExpand = visibleReasons.length > 0;
return ( return (
...@@ -1606,15 +1606,15 @@ function RecallBriefLine({ ...@@ -1606,15 +1606,15 @@ function RecallBriefLine({
onClick={canExpand ? () => setExpanded((v) => !v) : undefined} onClick={canExpand ? () => setExpanded((v) => !v) : undefined}
aria-expanded={canExpand ? expanded : undefined} aria-expanded={canExpand ? expanded : undefined}
className={cn( className={cn(
'flex w-full items-start gap-2 rounded-md border-l-[3px] border-teal-400 bg-gradient-to-r from-teal-50 via-teal-50/60 to-transparent py-1.5 pl-2 pr-2 text-left', 'flex w-full items-start gap-2 rounded-md border-l-[3px] border-brand-400 bg-gradient-to-r from-brand-50 via-brand-50/60 to-transparent py-1.5 pl-2 pr-2 text-left',
canExpand ? 'cursor-pointer hover:from-teal-100/70' : 'cursor-default', canExpand ? 'cursor-pointer hover:from-brand-100/70' : 'cursor-default',
)} )}
> >
<svg viewBox="0 0 24 24" className="mt-[1px] h-3.5 w-3.5 flex-none text-teal-500" fill="none" stroke="currentColor" strokeWidth="2"> <svg viewBox="0 0 24 24" className="mt-[1px] h-3.5 w-3.5 flex-none text-brand-500" fill="none" stroke="currentColor" strokeWidth="2">
<circle cx="12" cy="12" r="9" /> <circle cx="12" cy="12" r="9" />
<path d="M12 8v5M12 16h.01" strokeLinecap="round" /> <path d="M12 8v5M12 16h.01" strokeLinecap="round" />
</svg> </svg>
<span className="flex-1 min-w-0 font-medium text-teal-900 leading-snug">{summary}</span> <span className="flex-1 min-w-0 font-medium text-brand-900 leading-snug">{summary}</span>
{canExpand && ( {canExpand && (
// 右指箭头(收起)→ 旋转 90° 朝下(展开) // 右指箭头(收起)→ 旋转 90° 朝下(展开)
<svg <svg
...@@ -1622,7 +1622,7 @@ function RecallBriefLine({ ...@@ -1622,7 +1622,7 @@ function RecallBriefLine({
fill="none" fill="none"
stroke="currentColor" stroke="currentColor"
strokeWidth="2.5" strokeWidth="2.5"
className={cn('mt-[1px] h-3.5 w-3.5 flex-none text-teal-500/70 transition-transform', expanded && 'rotate-90')} className={cn('mt-[1px] h-3.5 w-3.5 flex-none text-brand-500/70 transition-transform', expanded && 'rotate-90')}
> >
<path d="M9 6l6 6-6 6" strokeLinecap="round" strokeLinejoin="round" /> <path d="M9 6l6 6-6 6" strokeLinecap="round" strokeLinejoin="round" />
</svg> </svg>
...@@ -1845,11 +1845,11 @@ function KeyFactsCard({ ...@@ -1845,11 +1845,11 @@ function KeyFactsCard({
{/* 「牙位 →」按业务要求隐藏(2026-07-29)。抽屉(kind='teeth')整套留着, {/* 「牙位 →」按业务要求隐藏(2026-07-29)。抽屉(kind='teeth')整套留着,
把 onOpenTeeth 传回来就恢复 —— 不删是因为牙位图本身没问题,只是这一屏不需要。 */} 把 onOpenTeeth 传回来就恢复 —— 不删是因为牙位图本身没问题,只是这一屏不需要。 */}
{onOpenTeeth && ( {onOpenTeeth && (
<button onClick={onOpenTeeth} className="text-[10.5px] text-teal-700 hover:underline"> <button onClick={onOpenTeeth} className="text-[10.5px] text-brand-700 hover:underline">
牙位 → 牙位 →
</button> </button>
)} )}
<button onClick={onOpenDetail} className="text-[10.5px] text-teal-700 hover:underline"> <button onClick={onOpenDetail} className="text-[10.5px] text-brand-700 hover:underline">
详情 → 详情 →
</button> </button>
</span> </span>
...@@ -1946,7 +1946,7 @@ function TreatmentHistoryCard({ ...@@ -1946,7 +1946,7 @@ function TreatmentHistoryCard({
action={ action={
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{onOpenMedical && ( {onOpenMedical && (
<button onClick={onOpenMedical} className="text-[10.5px] text-teal-700 hover:underline"> <button onClick={onOpenMedical} className="text-[10.5px] text-brand-700 hover:underline">
病历快读 → 病历快读 →
</button> </button>
)} )}
...@@ -1955,13 +1955,13 @@ function TreatmentHistoryCard({ ...@@ -1955,13 +1955,13 @@ function TreatmentHistoryCard({
<a <a
href={emrUrl} href={emrUrl}
{...HOST_LINK_PROPS} {...HOST_LINK_PROPS}
className="text-[10.5px] text-teal-700 hover:underline" className="text-[10.5px] text-brand-700 hover:underline"
> >
原始病历 → 原始病历 →
</a> </a>
) : ( ) : (
onOpenTimeline && ( onOpenTimeline && (
<button onClick={onOpenTimeline} className="text-[10.5px] text-teal-700 hover:underline"> <button onClick={onOpenTimeline} className="text-[10.5px] text-brand-700 hover:underline">
详情 → 详情 →
</button> </button>
) )
...@@ -1978,7 +1978,7 @@ function TreatmentHistoryCard({ ...@@ -1978,7 +1978,7 @@ function TreatmentHistoryCard({
<span className="flex-1 min-w-0 truncate text-slate-800" title={treatmentLabel(latestPlan)}> <span className="flex-1 min-w-0 truncate text-slate-800" title={treatmentLabel(latestPlan)}>
{treatmentLabel(latestPlan)} {treatmentLabel(latestPlan)}
</span> </span>
<span className="flex-none rounded bg-teal-100 px-1 text-[10px] font-medium text-teal-700">治疗计划</span> <span className="flex-none rounded bg-brand-100 px-1 text-[10px] font-medium text-brand-700">治疗计划</span>
</div> </div>
)} )}
{history.length === 0 ? ( {history.length === 0 ? (
...@@ -2028,7 +2028,7 @@ function SuggestionCard({ ...@@ -2028,7 +2028,7 @@ function SuggestionCard({
<circle cx="12" cy="12" r="1.5" fill="currentColor" /> <circle cx="12" cy="12" r="1.5" fill="currentColor" />
</svg> </svg>
), ),
tone: 'teal', tone: 'brand',
}, },
{ {
label: '归属诊所', label: '归属诊所',
...@@ -2079,14 +2079,14 @@ function SuggestionCard({ ...@@ -2079,14 +2079,14 @@ function SuggestionCard({
{/* 召回冷静期 / 暂缓 — snoozedUntil 未到期时提示客服"到点再跟,别现在打" */} {/* 召回冷静期 / 暂缓 — snoozedUntil 未到期时提示客服"到点再跟,别现在打" */}
{plan.snoozedUntil && plan.snoozedUntil.getTime() > Date.now() && ( {plan.snoozedUntil && plan.snoozedUntil.getTime() > Date.now() && (
<div className="flex items-start gap-2 text-[11px] py-0.5 mt-1 bg-teal-50/60 rounded px-1 -mx-1"> <div className="flex items-start gap-2 text-[11px] py-0.5 mt-1 bg-brand-50/60 rounded px-1 -mx-1">
<span className="flex-none w-5 h-5 rounded flex items-center justify-center mt-px bg-teal-100 text-teal-700"> <span className="flex-none w-5 h-5 rounded flex items-center justify-center mt-px bg-brand-100 text-brand-700">
<svg viewBox="0 0 24 24" className="w-3 h-3" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"> <svg viewBox="0 0 24 24" className="w-3 h-3" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
<circle cx="12" cy="12" r="9" /><path d="M12 7v5l3 2" /> <circle cx="12" cy="12" r="9" /><path d="M12 7v5l3 2" />
</svg> </svg>
</span> </span>
<span className="w-14 flex-none mt-0.5 text-teal-700 font-semibold">暂缓跟进</span> <span className="w-14 flex-none mt-0.5 text-brand-700 font-semibold">暂缓跟进</span>
<span className="font-medium flex-1 leading-snug min-w-0 text-teal-900 break-words"> <span className="font-medium flex-1 leading-snug min-w-0 text-brand-900 break-words">
{snoozeLabel(plan.snoozedUntil)} {snoozeLabel(plan.snoozedUntil)}
</span> </span>
</div> </div>
...@@ -2105,7 +2105,7 @@ function SuggestionCard({ ...@@ -2105,7 +2105,7 @@ function SuggestionCard({
key={i} key={i}
className={cn( className={cn(
'w-2 h-1.5 rounded-sm', 'w-2 h-1.5 rounded-sm',
// 仅"已触达"染色,不预亮"下一格" — 旧版 0/4 时第一格 teal 高亮被误读为已用 // 仅"已触达"染色,不预亮"下一格" — 旧版 0/4 时第一格高亮被误读为已用
i < plan.contactAttempts ? 'bg-amber-500' : 'bg-slate-200', i < plan.contactAttempts ? 'bg-amber-500' : 'bg-slate-200',
)} )}
/> />
...@@ -2207,7 +2207,7 @@ function ReturnVisitsCard({ ...@@ -2207,7 +2207,7 @@ function ReturnVisitsCard({
meta={`${visits.length} `} meta={`${visits.length} `}
action={ action={
onOpenDetail ? ( onOpenDetail ? (
<button onClick={onOpenDetail} className="text-[10.5px] text-teal-700 hover:underline"> <button onClick={onOpenDetail} className="text-[10.5px] text-brand-700 hover:underline">
详情 → 详情 →
</button> </button>
) : undefined ) : undefined
...@@ -2354,7 +2354,7 @@ function PersonaTagCloud({ ...@@ -2354,7 +2354,7 @@ function PersonaTagCloud({
type="button" type="button"
onClick={() => onSelect?.(f.key)} onClick={() => onSelect?.(f.key)}
aria-label={`${f.label}:${text} —— 查看画像详情`} aria-label={`${f.label}:${text} —— 查看画像详情`}
className="inline-flex max-w-full items-center gap-1 rounded px-1 py-px text-[10px] leading-4 text-slate-600 ring-1 ring-inset ring-slate-200 hover:bg-slate-50 hover:text-slate-900 hover:ring-slate-300 focus:outline-none focus-visible:ring-2 focus-visible:ring-teal-500" className="inline-flex max-w-full items-center gap-1 rounded px-1 py-px text-[10px] leading-4 text-slate-600 ring-1 ring-inset ring-slate-200 hover:bg-slate-50 hover:text-slate-900 hover:ring-slate-300 focus:outline-none focus-visible:ring-2 focus-visible:ring-brand-500"
> >
{inner} {inner}
</button> </button>
...@@ -2415,7 +2415,7 @@ function RegenBtn({ ...@@ -2415,7 +2415,7 @@ function RegenBtn({
'inline-flex items-center gap-0.5 px-1.5 py-0.5 text-[10.5px] border-r transition-colors', 'inline-flex items-center gap-0.5 px-1.5 py-0.5 text-[10.5px] border-r transition-colors',
streaming streaming
? 'text-slate-300 border-slate-100 cursor-not-allowed' ? 'text-slate-300 border-slate-100 cursor-not-allowed'
: 'text-slate-500 border-slate-100 hover:text-teal-700 hover:bg-teal-50', : 'text-slate-500 border-slate-100 hover:text-brand-700 hover:bg-brand-50',
); );
return ( return (
<div className="inline-flex flex-none items-stretch rounded border border-slate-100 overflow-hidden"> <div className="inline-flex flex-none items-stretch rounded border border-slate-100 overflow-hidden">
...@@ -2473,7 +2473,7 @@ function RegenBtn({ ...@@ -2473,7 +2473,7 @@ function RegenBtn({
'inline-flex items-center gap-1 px-2 py-0.5 text-[10.5px] font-medium transition-colors', 'inline-flex items-center gap-1 px-2 py-0.5 text-[10.5px] font-medium transition-colors',
streaming streaming
? 'text-rose-600 bg-rose-50 hover:bg-rose-100' ? 'text-rose-600 bg-rose-50 hover:bg-rose-100'
: 'text-teal-600 hover:text-teal-700 hover:bg-indigo-50', : 'text-brand-600 hover:text-brand-700 hover:bg-indigo-50',
)} )}
> >
{streaming ? ( {streaming ? (
......
...@@ -150,7 +150,7 @@ function IssueList({ ...@@ -150,7 +150,7 @@ function IssueList({
<div className="font-medium text-slate-600">{it.section || `问题 ${i + 1}`}</div> <div className="font-medium text-slate-600">{it.section || `问题 ${i + 1}`}</div>
<div className="break-words text-slate-500">{it.problem}</div> <div className="break-words text-slate-500">{it.problem}</div>
{it.fix && ( {it.fix && (
<div className="mt-0.5 break-words text-teal-600">改法:{it.fix}</div> <div className="mt-0.5 break-words text-brand-600">改法:{it.fix}</div>
)} )}
</li> </li>
))} ))}
......
...@@ -27,7 +27,7 @@ export function ScriptMarkdown({ sections, streaming = false }: { sections: Scri ...@@ -27,7 +27,7 @@ export function ScriptMarkdown({ sections, streaming = false }: { sections: Scri
className="w-full flex items-center justify-between gap-2 px-3 py-2 text-left hover:bg-slate-50" className="w-full flex items-center justify-between gap-2 px-3 py-2 text-left hover:bg-slate-50"
> >
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className="w-5 h-5 rounded text-[10.5px] flex items-center justify-center font-semibold bg-teal-50 text-teal-700"> <span className="w-5 h-5 rounded text-[10.5px] flex items-center justify-center font-semibold bg-brand-50 text-brand-700">
{idx + 1} {idx + 1}
</span> </span>
<span className={cn('text-[13px] font-semibold text-slate-900', streaming && 'shimmer-text')}> <span className={cn('text-[13px] font-semibold text-slate-900', streaming && 'shimmer-text')}>
...@@ -66,7 +66,7 @@ export function ScriptStepCards({ sections, streaming = false }: { sections: Scr ...@@ -66,7 +66,7 @@ export function ScriptStepCards({ sections, streaming = false }: { sections: Scr
<div key={sec.id} className="rounded-md border border-slate-100 bg-white p-3 flex flex-col"> <div key={sec.id} className="rounded-md border border-slate-100 bg-white p-3 flex flex-col">
<div className="flex items-center justify-between mb-2"> <div className="flex items-center justify-between mb-2">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className="w-6 h-6 rounded-full bg-teal-600 text-white text-[11px] font-semibold flex items-center justify-center"> <span className="w-6 h-6 rounded-full bg-brand-600 text-white text-[11px] font-semibold flex items-center justify-center">
{i + 1} {i + 1}
</span> </span>
<span className={cn('text-[13px] font-semibold text-slate-900', streaming && 'shimmer-text')}> <span className={cn('text-[13px] font-semibold text-slate-900', streaming && 'shimmer-text')}>
...@@ -101,9 +101,9 @@ export function ScriptCopilot({ sections, streaming = false }: { sections: Scrip ...@@ -101,9 +101,9 @@ export function ScriptCopilot({ sections, streaming = false }: { sections: Scrip
className={cn( className={cn(
'flex-1 min-w-0 text-[11px] font-medium px-2 py-1.5 rounded transition-colors flex items-center justify-center gap-1.5', 'flex-1 min-w-0 text-[11px] font-medium px-2 py-1.5 rounded transition-colors flex items-center justify-center gap-1.5',
i === active i === active
? 'bg-teal-600 text-white shadow-sm' ? 'bg-brand-600 text-white shadow-sm'
: i < active : i < active
? 'text-teal-700 hover:bg-white' ? 'text-brand-700 hover:bg-white'
: 'text-slate-500 hover:bg-white', : 'text-slate-500 hover:bg-white',
)} )}
> >
...@@ -113,7 +113,7 @@ export function ScriptCopilot({ sections, streaming = false }: { sections: Scrip ...@@ -113,7 +113,7 @@ export function ScriptCopilot({ sections, streaming = false }: { sections: Scrip
i === active i === active
? 'bg-white/20 text-white' ? 'bg-white/20 text-white'
: i < active : i < active
? 'bg-teal-100 text-teal-700' ? 'bg-brand-100 text-brand-700'
: 'bg-slate-200 text-slate-500', : 'bg-slate-200 text-slate-500',
)} )}
> >
...@@ -125,18 +125,18 @@ export function ScriptCopilot({ sections, streaming = false }: { sections: Scrip ...@@ -125,18 +125,18 @@ export function ScriptCopilot({ sections, streaming = false }: { sections: Scrip
</div> </div>
{/* 当前段高亮 */} {/* 当前段高亮 */}
<div className="rounded-md border-2 border-teal-200 bg-teal-50/30 p-4 ring-1 ring-teal-100"> <div className="rounded-md border-2 border-brand-200 bg-brand-50/30 p-4 ring-1 ring-brand-100">
<div className="flex items-center justify-between mb-2"> <div className="flex items-center justify-between mb-2">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className="w-2 h-2 rounded-full bg-teal-500 animate-pulse" /> <span className="w-2 h-2 rounded-full bg-brand-500 animate-pulse" />
<span className="text-[11px] text-teal-700 font-semibold uppercase tracking-wide">当前段</span> <span className="text-[11px] text-brand-700 font-semibold uppercase tracking-wide">当前段</span>
<span className={cn('text-[14px] font-semibold text-slate-900', streaming && 'shimmer-text')}> <span className={cn('text-[14px] font-semibold text-slate-900', streaming && 'shimmer-text')}>
{cur.label} {cur.label}
</span> </span>
</div> </div>
</div> </div>
<MD text={cur.markdown} /> <MD text={cur.markdown} />
<div className="mt-3 flex items-center justify-between pt-2 border-t border-teal-100"> <div className="mt-3 flex items-center justify-between pt-2 border-t border-brand-100">
<button <button
disabled={active === 0} disabled={active === 0}
onClick={() => setActive(Math.max(0, active - 1))} onClick={() => setActive(Math.max(0, active - 1))}
...@@ -150,7 +150,7 @@ export function ScriptCopilot({ sections, streaming = false }: { sections: Scrip ...@@ -150,7 +150,7 @@ export function ScriptCopilot({ sections, streaming = false }: { sections: Scrip
<button <button
disabled={active === sections.length - 1} disabled={active === sections.length - 1}
onClick={() => setActive(Math.min(sections.length - 1, active + 1))} onClick={() => setActive(Math.min(sections.length - 1, active + 1))}
className="text-[12px] text-teal-700 font-medium hover:text-teal-900 disabled:text-slate-300 inline-flex items-center gap-1" className="text-[12px] text-brand-700 font-medium hover:text-brand-900 disabled:text-slate-300 inline-flex items-center gap-1"
> >
下一段 → 下一段 →
</button> </button>
......
...@@ -10,7 +10,7 @@ export const TONE: Record< ...@@ -10,7 +10,7 @@ export const TONE: Record<
string, string,
{ bg: string; text: string; ring: string; dot: string } { bg: string; text: string; ring: string; dot: string }
> = { > = {
teal: { bg: 'bg-teal-50', text: 'text-teal-700', ring: 'ring-teal-200', dot: 'bg-teal-500' }, brand: { bg: 'bg-brand-50', text: 'text-brand-700', ring: 'ring-brand-200', dot: 'bg-brand-500' },
indigo: { bg: 'bg-indigo-50', text: 'text-indigo-700', ring: 'ring-indigo-200', dot: 'bg-indigo-500' }, indigo: { bg: 'bg-indigo-50', text: 'text-indigo-700', ring: 'ring-indigo-200', dot: 'bg-indigo-500' },
emerald: { bg: 'bg-emerald-50', text: 'text-emerald-700', ring: 'ring-emerald-200', dot: 'bg-emerald-500' }, emerald: { bg: 'bg-emerald-50', text: 'text-emerald-700', ring: 'ring-emerald-200', dot: 'bg-emerald-500' },
amber: { bg: 'bg-amber-50', text: 'text-amber-700', ring: 'ring-amber-200', dot: 'bg-amber-500' }, amber: { bg: 'bg-amber-50', text: 'text-amber-700', ring: 'ring-amber-200', dot: 'bg-amber-500' },
...@@ -25,7 +25,7 @@ export const tone = (t?: string) => TONE[t ?? 'slate'] ?? TONE.slate!; ...@@ -25,7 +25,7 @@ export const tone = (t?: string) => TONE[t ?? 'slate'] ?? TONE.slate!;
// Chip // Chip
// ────────────────────────────────────────── // ──────────────────────────────────────────
/** /**
* 语义色小标签 — tone: teal/slate/amber/rose/indigo/emerald/sky…,icon 可选,size: xs/sm。工作台高频原语。 * 语义色小标签 — tone: brand/slate/amber/rose/indigo/emerald/sky…,icon 可选,size: xs/sm。工作台高频原语。
*/ */
export function Chip({ export function Chip({
children, children,
...@@ -230,7 +230,7 @@ export function MD({ text, className }: { text: string; className?: string }) { ...@@ -230,7 +230,7 @@ export function MD({ text, className }: { text: string; className?: string }) {
out.push( out.push(
<blockquote <blockquote
key={key++} key={key++}
className="my-2 pl-3 border-l-2 border-teal-300 bg-teal-50/40 py-2 pr-2 rounded-r text-[13px] text-slate-800 leading-relaxed" className="my-2 pl-3 border-l-2 border-brand-300 bg-brand-50/40 py-2 pr-2 rounded-r text-[13px] text-slate-800 leading-relaxed"
> >
{block.map((b, k) => ( {block.map((b, k) => (
<p key={k} className="m-0" dangerouslySetInnerHTML={{ __html: inlineMD(b) }} /> <p key={k} className="m-0" dangerouslySetInnerHTML={{ __html: inlineMD(b) }} />
......
...@@ -89,7 +89,7 @@ export function TaskDrawer({ currentPlanId }: { currentPlanId: string }) { ...@@ -89,7 +89,7 @@ export function TaskDrawer({ currentPlanId }: { currentPlanId: string }) {
<div className="hidden md:block fixed left-0 top-0 bottom-0 w-3 z-40" onMouseEnter={() => setHover(true)}> <div className="hidden md:block fixed left-0 top-0 bottom-0 w-3 z-40" onMouseEnter={() => setHover(true)}>
{!open && ( {!open && (
<div className="absolute left-0 top-1/2 -translate-y-1/2 flex flex-col items-center gap-1"> <div className="absolute left-0 top-1/2 -translate-y-1/2 flex flex-col items-center gap-1">
<span className="block w-1 h-12 bg-teal-500/40 rounded-r-full hover:bg-teal-600 transition-colors" /> <span className="block w-1 h-12 bg-brand-500/40 rounded-r-full hover:bg-brand-600 transition-colors" />
<span className="text-[9px] text-slate-400 -rotate-90 origin-center mt-4 whitespace-nowrap tracking-widest pointer-events-none"> <span className="text-[9px] text-slate-400 -rotate-90 origin-center mt-4 whitespace-nowrap tracking-widest pointer-events-none">
我的任务 我的任务
</span> </span>
...@@ -110,7 +110,7 @@ export function TaskDrawer({ currentPlanId }: { currentPlanId: string }) { ...@@ -110,7 +110,7 @@ export function TaskDrawer({ currentPlanId }: { currentPlanId: string }) {
<header className="flex-none px-3 pt-3 pb-2 border-b border-slate-100"> <header className="flex-none px-3 pt-3 pb-2 border-b border-slate-100">
<div className="flex items-center justify-between gap-2 mb-2.5"> <div className="flex items-center justify-between gap-2 mb-2.5">
<div className="flex items-center gap-2 min-w-0"> <div className="flex items-center gap-2 min-w-0">
<span className="inline-flex items-center justify-center w-7 h-7 rounded-md bg-teal-50 text-teal-700 ring-1 ring-teal-200 flex-none"> <span className="inline-flex items-center justify-center w-7 h-7 rounded-md bg-brand-50 text-brand-700 ring-1 ring-brand-200 flex-none">
<svg viewBox="0 0 24 24" className="w-3.5 h-3.5" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"> <svg viewBox="0 0 24 24" className="w-3.5 h-3.5" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M9 11l3 3L22 4 M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11" /> <path d="M9 11l3 3L22 4 M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11" />
</svg> </svg>
...@@ -125,7 +125,7 @@ export function TaskDrawer({ currentPlanId }: { currentPlanId: string }) { ...@@ -125,7 +125,7 @@ export function TaskDrawer({ currentPlanId }: { currentPlanId: string }) {
title={pinned ? '取消固定' : '固定打开'} title={pinned ? '取消固定' : '固定打开'}
className={cn( className={cn(
'w-6 h-6 rounded inline-flex items-center justify-center transition-colors flex-none', 'w-6 h-6 rounded inline-flex items-center justify-center transition-colors flex-none',
pinned ? 'bg-teal-600 text-white' : 'text-slate-400 hover:bg-slate-100 hover:text-slate-700', pinned ? 'bg-brand-600 text-white' : 'text-slate-400 hover:bg-slate-100 hover:text-slate-700',
)} )}
> >
<svg viewBox="0 0 24 24" className={cn('w-3.5 h-3.5 transition-transform', pinned && 'rotate-45')} fill="none" stroke="currentColor" strokeWidth="2"> <svg viewBox="0 0 24 24" className={cn('w-3.5 h-3.5 transition-transform', pinned && 'rotate-45')} fill="none" stroke="currentColor" strokeWidth="2">
...@@ -152,7 +152,7 @@ export function TaskDrawer({ currentPlanId }: { currentPlanId: string }) { ...@@ -152,7 +152,7 @@ export function TaskDrawer({ currentPlanId }: { currentPlanId: string }) {
placeholder="搜索 姓名 / 手机 / ID(已加载项)" placeholder="搜索 姓名 / 手机 / ID(已加载项)"
value={search} value={search}
onChange={(e) => setSearch(e.target.value)} onChange={(e) => setSearch(e.target.value)}
className="w-full pl-7 pr-2.5 py-1.5 rounded-md border border-slate-100 bg-slate-50 text-[12px] text-slate-800 focus:outline-none focus:ring-2 focus:ring-teal-200 focus:border-teal-300 focus:bg-white transition-all" className="w-full pl-7 pr-2.5 py-1.5 rounded-md border border-slate-100 bg-slate-50 text-[12px] text-slate-800 focus:outline-none focus:ring-2 focus:ring-brand-200 focus:border-brand-300 focus:bg-white transition-all"
/> />
</div> </div>
<div className="grid grid-cols-3 gap-1"> <div className="grid grid-cols-3 gap-1">
...@@ -165,7 +165,7 @@ export function TaskDrawer({ currentPlanId }: { currentPlanId: string }) { ...@@ -165,7 +165,7 @@ export function TaskDrawer({ currentPlanId }: { currentPlanId: string }) {
onClick={() => setFilter(t.v)} onClick={() => setFilter(t.v)}
className={cn( className={cn(
'px-1 py-1 rounded inline-flex items-center justify-center gap-1 text-[11px] transition-colors', 'px-1 py-1 rounded inline-flex items-center justify-center gap-1 text-[11px] transition-colors',
filter === t.v ? 'bg-teal-600 text-white font-semibold' : 'bg-slate-100 text-slate-600 hover:bg-slate-200', filter === t.v ? 'bg-brand-600 text-white font-semibold' : 'bg-slate-100 text-slate-600 hover:bg-slate-200',
)} )}
> >
<span>{t.label}</span> <span>{t.label}</span>
...@@ -255,7 +255,7 @@ function TaskRow({ ...@@ -255,7 +255,7 @@ function TaskRow({
className={cn( className={cn(
'w-full text-left rounded-md border px-2.5 py-2 transition-colors', 'w-full text-left rounded-md border px-2.5 py-2 transition-colors',
active active
? 'border-teal-400 bg-teal-50/60 ring-1 ring-teal-100' ? 'border-brand-400 bg-brand-50/60 ring-1 ring-brand-100'
: 'border-slate-100 bg-white hover:border-slate-300 hover:bg-slate-50', : 'border-slate-100 bg-white hover:border-slate-300 hover:bg-slate-50',
done && 'opacity-70', done && 'opacity-70',
)} )}
...@@ -280,7 +280,7 @@ function TaskRow({ ...@@ -280,7 +280,7 @@ function TaskRow({
</> </>
)} )}
</div> </div>
{active && <div className="mt-1 text-[10px] text-teal-700 font-medium">● 当前查看</div>} {active && <div className="mt-1 text-[10px] text-brand-700 font-medium">● 当前查看</div>}
</button> </button>
</li> </li>
); );
......
...@@ -146,9 +146,9 @@ export function ToothTimeline({ facts }: { facts: AdaptedFact[] }) { ...@@ -146,9 +146,9 @@ export function ToothTimeline({ facts }: { facts: AdaptedFact[] }) {
<ToothFactRow key={`${k}-${f.id}`} fact={f} /> <ToothFactRow key={`${k}-${f.id}`} fact={f} />
))} ))}
{!isWhole && bridgeNote.has(k) && ( {!isWhole && bridgeNote.has(k) && (
<div className="flex items-baseline gap-2 px-2.5 py-1.5 text-[12px] bg-teal-50/40"> <div className="flex items-baseline gap-2 px-2.5 py-1.5 text-[12px] bg-brand-50/40">
<span className="w-[74px] flex-none" /> <span className="w-[74px] flex-none" />
<span className="flex-none px-1.5 py-px rounded text-[10px] font-medium bg-teal-50 text-teal-700"> <span className="flex-none px-1.5 py-px rounded text-[10px] font-medium bg-brand-50 text-brand-700">
桥体 桥体
</span> </span>
<span className="flex-1 min-w-0 text-slate-600 leading-snug"> <span className="flex-1 min-w-0 text-slate-600 leading-snug">
......
...@@ -202,7 +202,7 @@ export function PatientPickerRail({ ...@@ -202,7 +202,7 @@ export function PatientPickerRail({
className={cn( className={cn(
'rounded-t-md px-3 py-1.5 text-[12.5px] transition-colors', 'rounded-t-md px-3 py-1.5 text-[12.5px] transition-colors',
view === t.v view === t.v
? 'border-b-2 border-teal-600 font-semibold text-teal-700' ? 'border-b-2 border-brand-600 font-semibold text-brand-700'
: 'text-slate-500 hover:text-slate-800', : 'text-slate-500 hover:text-slate-800',
)} )}
> >
...@@ -239,7 +239,7 @@ export function PatientPickerRail({ ...@@ -239,7 +239,7 @@ export function PatientPickerRail({
scheduleKeyword(v); scheduleKeyword(v);
}} }}
placeholder="姓名 / 手机 / 病历号" placeholder="姓名 / 手机 / 病历号"
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-teal-300 focus:bg-white" 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>
<div className="flex items-center gap-1.5"> <div className="flex items-center gap-1.5">
...@@ -251,7 +251,7 @@ export function PatientPickerRail({ ...@@ -251,7 +251,7 @@ export function PatientPickerRail({
className={cn( className={cn(
'inline-flex h-6 items-center gap-1 rounded-md border px-1.5 text-[11px] transition-colors', 'inline-flex h-6 items-center gap-1 rounded-md border px-1.5 text-[11px] transition-colors',
realPhoneOnly realPhoneOnly
? 'border-teal-300 bg-teal-50 font-medium text-teal-700' ? 'border-brand-300 bg-brand-50 font-medium text-brand-700'
: 'border-slate-100 bg-white text-slate-500 hover:bg-slate-50', : 'border-slate-100 bg-white text-slate-500 hover:bg-slate-50',
)} )}
> >
...@@ -282,7 +282,7 @@ export function PatientPickerRail({ ...@@ -282,7 +282,7 @@ export function PatientPickerRail({
key={kv} key={kv}
type="button" type="button"
onClick={() => toggleTag(kv)} onClick={() => toggleTag(kv)}
className="inline-flex items-center gap-0.5 rounded-full bg-teal-50 px-1.5 py-0.5 text-[10.5px] text-teal-700 ring-1 ring-inset ring-teal-200 hover:bg-teal-100" className="inline-flex items-center gap-0.5 rounded-full bg-brand-50 px-1.5 py-0.5 text-[10.5px] text-brand-700 ring-1 ring-inset ring-brand-200 hover:bg-brand-100"
title={`${dim?.nameZh}:${opt?.zh}(点击移除)`} title={`${dim?.nameZh}:${opt?.zh}(点击移除)`}
> >
{opt?.zh ?? v} {opt?.zh ?? v}
...@@ -341,7 +341,7 @@ export function PatientPickerRail({ ...@@ -341,7 +341,7 @@ export function PatientPickerRail({
type="button" type="button"
data-pet-perch data-pet-perch
onClick={loadMore} onClick={loadMore}
className="block w-full py-2 text-center text-[11px] text-teal-700 hover:bg-teal-50/50" className="block w-full py-2 text-center text-[11px] text-brand-700 hover:bg-brand-50/50"
> >
加载更多({items.length}/{total}) 加载更多({items.length}/{total})
</button> </button>
...@@ -384,7 +384,7 @@ function PersonaTagFilter({ ...@@ -384,7 +384,7 @@ function PersonaTagFilter({
className={cn( className={cn(
'inline-flex h-6 items-center gap-1 rounded-md border px-1.5 text-[11px] transition-colors', 'inline-flex h-6 items-center gap-1 rounded-md border px-1.5 text-[11px] transition-colors',
selected.size > 0 selected.size > 0
? 'border-teal-300 bg-teal-50 font-medium text-teal-700' ? 'border-brand-300 bg-brand-50 font-medium text-brand-700'
: 'border-slate-100 bg-white text-slate-500 hover:bg-slate-50', : 'border-slate-100 bg-white text-slate-500 hover:bg-slate-50',
)} )}
> >
...@@ -396,7 +396,7 @@ function PersonaTagFilter({ ...@@ -396,7 +396,7 @@ function PersonaTagFilter({
<div className="mb-1 flex items-center justify-between px-1"> <div className="mb-1 flex items-center justify-between px-1">
<span className="text-[11px] font-medium text-slate-500">按标签圈人</span> <span className="text-[11px] font-medium text-slate-500">按标签圈人</span>
{selected.size > 0 && ( {selected.size > 0 && (
<button type="button" onClick={onClear} className="text-[10.5px] text-teal-700 hover:underline"> <button type="button" onClick={onClear} className="text-[10.5px] text-brand-700 hover:underline">
清空 清空
</button> </button>
)} )}
...@@ -440,8 +440,8 @@ function PersonaTagFilter({ ...@@ -440,8 +440,8 @@ function PersonaTagFilter({
className={cn( className={cn(
'rounded-full border px-2 py-0.5 text-[11px] transition-colors', 'rounded-full border px-2 py-0.5 text-[11px] transition-colors',
on on
? 'border-teal-300 bg-teal-50 font-medium text-teal-700' ? 'border-brand-300 bg-brand-50 font-medium text-brand-700'
: 'border-slate-100 bg-white text-slate-600 hover:border-teal-200 hover:bg-teal-50/40', : 'border-slate-100 bg-white text-slate-600 hover:border-brand-200 hover:bg-brand-50/40',
)} )}
> >
{o.zh} {o.zh}
...@@ -491,7 +491,7 @@ function RailClinicFilter({ ...@@ -491,7 +491,7 @@ function RailClinicFilter({
className={cn( className={cn(
'inline-flex h-6 w-full items-center justify-between rounded-md border px-1.5 text-[11px] transition-colors', 'inline-flex h-6 w-full items-center justify-between rounded-md border px-1.5 text-[11px] transition-colors',
selected.length selected.length
? 'border-teal-300 bg-teal-50 font-medium text-teal-700' ? 'border-brand-300 bg-brand-50 font-medium text-brand-700'
: 'border-slate-100 bg-white text-slate-500 hover:bg-slate-50', : 'border-slate-100 bg-white text-slate-500 hover:bg-slate-50',
)} )}
> >
...@@ -507,7 +507,7 @@ function RailClinicFilter({ ...@@ -507,7 +507,7 @@ function RailClinicFilter({
<div className="mb-1 flex items-center justify-between border-b border-slate-100 px-1.5 py-1"> <div className="mb-1 flex items-center justify-between border-b border-slate-100 px-1.5 py-1">
<span className="text-[10.5px] text-slate-400">按诊所筛选(可多选)</span> <span className="text-[10.5px] text-slate-400">按诊所筛选(可多选)</span>
{selected.length > 0 && ( {selected.length > 0 && (
<button type="button" onClick={() => onChange([])} className="text-[10.5px] text-teal-600 hover:underline"> <button type="button" onClick={() => onChange([])} className="text-[10.5px] text-brand-600 hover:underline">
清空 清空
</button> </button>
)} )}
...@@ -516,7 +516,7 @@ function RailClinicFilter({ ...@@ -516,7 +516,7 @@ function RailClinicFilter({
<label key={o.id} className="flex cursor-pointer items-center gap-2 rounded px-1.5 py-1.5 hover:bg-slate-50"> <label key={o.id} className="flex cursor-pointer items-center gap-2 rounded px-1.5 py-1.5 hover:bg-slate-50">
<input <input
type="checkbox" type="checkbox"
className="h-3.5 w-3.5 accent-teal-600" className="h-3.5 w-3.5 accent-brand-600"
checked={sel.has(o.id)} checked={sel.has(o.id)}
onChange={() => toggle(o.id)} onChange={() => toggle(o.id)}
/> />
...@@ -624,7 +624,7 @@ function DoctorPicker({ ...@@ -624,7 +624,7 @@ function DoctorPicker({
type="button" type="button"
onClick={() => onToggle(kv)} onClick={() => onToggle(kv)}
title="点击移除" title="点击移除"
className="rounded-full border border-teal-300 bg-teal-50 px-2 py-0.5 text-[11px] font-medium text-teal-700" className="rounded-full border border-brand-300 bg-brand-50 px-2 py-0.5 text-[11px] font-medium text-brand-700"
> >
{kv.slice(dimId.length + 1)} × {kv.slice(dimId.length + 1)} ×
</button> </button>
...@@ -635,7 +635,7 @@ function DoctorPicker({ ...@@ -635,7 +635,7 @@ function DoctorPicker({
value={q} value={q}
onChange={(e) => setQ(e.target.value)} onChange={(e) => setQ(e.target.value)}
placeholder={doctors.length ? '输入医生姓名搜索' : '暂无医生名单'} placeholder={doctors.length ? '输入医生姓名搜索' : '暂无医生名单'}
className="w-full rounded border border-slate-200 px-1.5 py-0.5 text-[11px] outline-none placeholder:text-slate-300 focus:border-teal-300" className="w-full rounded border border-slate-200 px-1.5 py-0.5 text-[11px] outline-none placeholder:text-slate-300 focus:border-brand-300"
/> />
{matches.length > 0 && ( {matches.length > 0 && (
<div className="flex flex-wrap gap-1"> <div className="flex flex-wrap gap-1">
...@@ -647,7 +647,7 @@ function DoctorPicker({ ...@@ -647,7 +647,7 @@ function DoctorPicker({
onToggle(`${dimId}:${d}`); onToggle(`${dimId}:${d}`);
setQ(''); setQ('');
}} }}
className="rounded-full border border-slate-100 bg-white px-2 py-0.5 text-[11px] text-slate-600 hover:border-teal-200 hover:bg-teal-50/40" className="rounded-full border border-slate-100 bg-white px-2 py-0.5 text-[11px] text-slate-600 hover:border-brand-200 hover:bg-brand-50/40"
> >
{d} {d}
</button> </button>
...@@ -711,12 +711,12 @@ function PatientRow({ ...@@ -711,12 +711,12 @@ function PatientRow({
onKeyDown={(e) => e.key === 'Enter' && onClick()} onKeyDown={(e) => e.key === 'Enter' && onClick()}
className={cn( className={cn(
'group block w-full cursor-pointer border-b border-slate-100 px-3 py-2 text-left transition-colors', 'group block w-full cursor-pointer border-b border-slate-100 px-3 py-2 text-left transition-colors',
active ? 'bg-teal-50/70 ring-1 ring-inset ring-teal-200' : 'hover:bg-slate-50', active ? 'bg-brand-50/70 ring-1 ring-inset ring-brand-200' : 'hover:bg-slate-50',
)} )}
> >
<div className="flex items-center gap-1.5"> <div className="flex items-center gap-1.5">
{p.lastOutcome && <OutcomeTag outcome={p.lastOutcome} />} {p.lastOutcome && <OutcomeTag outcome={p.lastOutcome} />}
<span className={cn('truncate text-[13px] font-semibold', active ? 'text-teal-800' : 'text-slate-900')}> <span className={cn('truncate text-[13px] font-semibold', active ? 'text-brand-800' : 'text-slate-900')}>
{p.patient.name ?? p.patient.nameMasked ?? '—'} {p.patient.name ?? p.patient.nameMasked ?? '—'}
</span> </span>
{/* 顺序对齐详情页患者卡:姓名 → 病历号 → 性别·年龄。 {/* 顺序对齐详情页患者卡:姓名 → 病历号 → 性别·年龄。
...@@ -730,7 +730,7 @@ function PatientRow({ ...@@ -730,7 +730,7 @@ function PatientRow({
{formatGender(p.patient.gender)} · {p.patient.age ?? '?'} {formatGender(p.patient.gender)} · {p.patient.age ?? '?'}
</span> </span>
{p.patient.phoneVerified && ( {p.patient.phoneVerified && (
<span className="flex-none rounded bg-teal-50 px-1 text-[9.5px] font-medium leading-4 text-teal-700 ring-1 ring-inset ring-teal-200"> <span className="flex-none rounded bg-brand-50 px-1 text-[9.5px] font-medium leading-4 text-brand-700 ring-1 ring-inset ring-brand-200">
</span> </span>
)} )}
...@@ -776,10 +776,10 @@ function PatientRow({ ...@@ -776,10 +776,10 @@ function PatientRow({
)} )}
<span className="ml-auto flex-none" /> <span className="ml-auto flex-none" />
{(() => { {(() => {
// 动作按钮:待认领→「认领」(teal),已认领→「返池」(amber)。二者与状态药丸叠同 grid 格, // 动作按钮:待认领→「认领」(品牌色),已认领→「返池」(amber)。二者与状态药丸叠同 grid 格,
// 槽位宽/高恒为较大值,hover 只切 visibility(非 display)→ 悬停整行不重排、不位移。 // 槽位宽/高恒为较大值,hover 只切 visibility(非 display)→ 悬停整行不重排、不位移。
const act = onClaim const act = onClaim
? { label: '认领', tone: 'bg-teal-600 hover:bg-teal-700', fn: onClaim } ? { label: '认领', tone: 'bg-brand-600 hover:bg-brand-700', fn: onClaim }
: onRecycle : onRecycle
? { label: '返池', tone: 'bg-amber-500 hover:bg-amber-600', fn: onRecycle } ? { label: '返池', tone: 'bg-amber-500 hover:bg-amber-600', fn: onRecycle }
: null; : null;
......
...@@ -240,7 +240,7 @@ export function PlansListApp() { ...@@ -240,7 +240,7 @@ export function PlansListApp() {
<KpiCard tone="amber" icon={<ListChecks className="h-4 w-4" />} label="我的进行中" value={counts.mineAssigned} sub={`共 ${counts.mine} 个工单`} /> <KpiCard tone="amber" icon={<ListChecks className="h-4 w-4" />} label="我的进行中" value={counts.mineAssigned} sub={`共 ${counts.mine} 个工单`} />
<KpiCard tone="rose" icon={<Flame className="h-4 w-4" />} label="召回池余量" value={counts.pool} sub="池中待领取" /> <KpiCard tone="rose" icon={<Flame className="h-4 w-4" />} label="召回池余量" value={counts.pool} sub="池中待领取" />
<KpiCard tone="emerald" icon={<ListChecks className="h-4 w-4" />} label="我完成" value={counts.mineCompleted} sub={successRateText(counts)} /> <KpiCard tone="emerald" icon={<ListChecks className="h-4 w-4" />} label="我完成" value={counts.mineCompleted} sub={successRateText(counts)} />
<KpiCard tone="teal" icon={<Waves className="h-4 w-4" />} label="全部计划" value={counts.all} sub="活跃 + 历史" /> <KpiCard tone="brand" icon={<Waves className="h-4 w-4" />} label="全部计划" value={counts.all} sub="活跃 + 历史" />
</div> </div>
<div className="flex-none bg-white"> <div className="flex-none bg-white">
...@@ -353,7 +353,7 @@ function PageHeader({ ...@@ -353,7 +353,7 @@ function PageHeader({
<header className="flex flex-none items-center justify-between gap-3 border-b border-slate-100 bg-white px-5 py-3"> <header className="flex flex-none items-center justify-between gap-3 border-b border-slate-100 bg-white px-5 py-3">
<div className="flex min-w-0 items-center gap-3"> <div className="flex min-w-0 items-center gap-3">
<div className="inline-flex items-center gap-2"> <div className="inline-flex items-center gap-2">
<span className="inline-flex h-7 w-7 items-center justify-center rounded-md bg-teal-600 text-[12px] font-bold text-white">PAC</span> <span className="inline-flex h-7 w-7 items-center justify-center rounded-md bg-brand-600 text-[12px] font-bold text-white">PAC</span>
<span className="text-[13px] font-semibold text-slate-900">疗效保障</span> <span className="text-[13px] font-semibold text-slate-900">疗效保障</span>
</div> </div>
<span className="text-slate-300">/</span> <span className="text-slate-300">/</span>
...@@ -370,7 +370,7 @@ function PageHeader({ ...@@ -370,7 +370,7 @@ function PageHeader({
<div className="font-medium text-slate-700">{userDisplayName(user)}</div> <div className="font-medium text-slate-700">{userDisplayName(user)}</div>
<div className="nums">{user?.clinicIds?.length ?? 0} 个诊所 · {roleNameZh(user?.role)}</div> <div className="nums">{user?.clinicIds?.length ?? 0} 个诊所 · {roleNameZh(user?.role)}</div>
</div> </div>
<span className="inline-flex h-8 w-8 items-center justify-center rounded-full bg-gradient-to-br from-teal-400 to-teal-600 text-[12px] font-bold text-white"> <span className="inline-flex h-8 w-8 items-center justify-center rounded-full bg-gradient-to-br from-brand-400 to-brand-600 text-[12px] font-bold text-white">
{userDisplayName(user).charAt(0).toUpperCase()} {userDisplayName(user).charAt(0).toUpperCase()}
</span> </span>
<Button <Button
...@@ -399,7 +399,7 @@ const TONE_BG: Record<string, string> = { ...@@ -399,7 +399,7 @@ const TONE_BG: Record<string, string> = {
amber: 'bg-amber-50 text-amber-700 ring-amber-200', amber: 'bg-amber-50 text-amber-700 ring-amber-200',
rose: 'bg-rose-50 text-rose-700 ring-rose-200', rose: 'bg-rose-50 text-rose-700 ring-rose-200',
emerald: 'bg-emerald-50 text-emerald-700 ring-emerald-200', emerald: 'bg-emerald-50 text-emerald-700 ring-emerald-200',
teal: 'bg-teal-50 text-teal-700 ring-teal-200', brand: 'bg-brand-50 text-brand-700 ring-brand-200',
}; };
function KpiCard({ function KpiCard({
...@@ -489,12 +489,12 @@ function ViewTabs({ ...@@ -489,12 +489,12 @@ function ViewTabs({
<span <span
className={cn( className={cn(
'inline-flex h-[18px] min-w-[20px] items-center justify-center rounded px-1 text-[10.5px] font-bold nums', 'inline-flex h-[18px] min-w-[20px] items-center justify-center rounded px-1 text-[10.5px] font-bold nums',
active ? 'bg-teal-600 text-white' : 'bg-slate-100 text-slate-600', active ? 'bg-brand-600 text-white' : 'bg-slate-100 text-slate-600',
)} )}
> >
{count} {count}
</span> </span>
{active && <span className="absolute -bottom-px left-3 right-3 h-[2px] rounded-full bg-teal-600" />} {active && <span className="absolute -bottom-px left-3 right-3 h-[2px] rounded-full bg-brand-600" />}
</button> </button>
); );
})} })}
...@@ -537,7 +537,7 @@ function ClinicFilter({ ...@@ -537,7 +537,7 @@ function ClinicFilter({
<div className="mb-1 flex items-center justify-between border-b border-slate-100 px-1.5 py-1"> <div className="mb-1 flex items-center justify-between border-b border-slate-100 px-1.5 py-1">
<span className="text-[10.5px] text-slate-400">按诊所筛选(可多选)</span> <span className="text-[10.5px] text-slate-400">按诊所筛选(可多选)</span>
{selected.length > 0 && ( {selected.length > 0 && (
<button onClick={() => onChange([])} className="text-[10.5px] text-teal-600 hover:underline"> <button onClick={() => onChange([])} className="text-[10.5px] text-brand-600 hover:underline">
清空 清空
</button> </button>
)} )}
...@@ -549,7 +549,7 @@ function ClinicFilter({ ...@@ -549,7 +549,7 @@ function ClinicFilter({
> >
<input <input
type="checkbox" type="checkbox"
className="h-3.5 w-3.5 accent-teal-600" className="h-3.5 w-3.5 accent-brand-600"
checked={sel.has(o.id)} checked={sel.has(o.id)}
onChange={() => toggle(o.id)} onChange={() => toggle(o.id)}
/> />
...@@ -649,7 +649,7 @@ function FilterBar({ ...@@ -649,7 +649,7 @@ function FilterBar({
className={cn( className={cn(
'inline-flex h-8 items-center gap-1 rounded-md border px-2.5 text-xs transition-colors', 'inline-flex h-8 items-center gap-1 rounded-md border px-2.5 text-xs transition-colors',
realPhoneOnly realPhoneOnly
? 'border-teal-300 bg-teal-50 font-medium text-teal-700' ? 'border-brand-300 bg-brand-50 font-medium text-brand-700'
: 'border-slate-100 bg-white text-slate-600 hover:bg-slate-50', : 'border-slate-100 bg-white text-slate-600 hover:bg-slate-50',
)} )}
> >
...@@ -675,7 +675,7 @@ function FilterBar({ ...@@ -675,7 +675,7 @@ function FilterBar({
<label className="inline-flex cursor-pointer select-none items-center gap-1.5"> <label className="inline-flex cursor-pointer select-none items-center gap-1.5">
<input <input
type="checkbox" type="checkbox"
className="h-4 w-4 accent-teal-600 cursor-pointer" className="h-4 w-4 accent-brand-600 cursor-pointer"
checked={allSelected} checked={allSelected}
ref={(el) => { ref={(el) => {
if (el) el.indeterminate = !allSelected && someSelected; if (el) el.indeterminate = !allSelected && someSelected;
...@@ -707,8 +707,8 @@ function BatchBar({ ...@@ -707,8 +707,8 @@ function BatchBar({
}) { }) {
if (!count) return null; if (!count) return null;
return ( return (
<div className="flex flex-none items-center gap-3 border-y border-teal-200 bg-teal-50/80 px-5 py-2 backdrop-blur"> <div className="flex flex-none items-center gap-3 border-y border-brand-200 bg-brand-50/80 px-5 py-2 backdrop-blur">
<span className="text-[12px] text-teal-800"> <span className="text-[12px] text-brand-800">
已选 <span className="nums font-bold">{count}</span> 已选 <span className="nums font-bold">{count}</span>
</span> </span>
<span className="text-slate-300">·</span> <span className="text-slate-300">·</span>
...@@ -765,7 +765,7 @@ function PatientPlanCard({ ...@@ -765,7 +765,7 @@ function PatientPlanCard({
<article <article
className={cn( className={cn(
'group relative flex flex-col rounded-lg border bg-white transition-all', 'group relative flex flex-col rounded-lg border bg-white transition-all',
selected ? 'border-teal-400 ring-2 ring-teal-100' : 'border-slate-100 hover:border-slate-300 hover:shadow-sm', selected ? 'border-brand-400 ring-2 ring-brand-100' : 'border-slate-100 hover:border-slate-300 hover:shadow-sm',
isClosed && 'opacity-75', isClosed && 'opacity-75',
)} )}
> >
...@@ -780,7 +780,7 @@ function PatientPlanCard({ ...@@ -780,7 +780,7 @@ function PatientPlanCard({
> >
<input <input
type="checkbox" type="checkbox"
className="h-4 w-4 accent-teal-600 cursor-pointer" className="h-4 w-4 accent-brand-600 cursor-pointer"
checked={selected} checked={selected}
onChange={onSelect} onChange={onSelect}
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
...@@ -805,7 +805,7 @@ function PatientPlanCard({ ...@@ -805,7 +805,7 @@ function PatientPlanCard({
{p.patient.phoneVerified && ( {p.patient.phoneVerified && (
<span <span
title="真实手机号(外部对照表已核实)" title="真实手机号(外部对照表已核实)"
className="inline-flex items-center rounded bg-teal-50 px-1 text-[10px] font-medium leading-4 text-teal-700 ring-1 ring-inset ring-teal-200" 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> </span>
...@@ -852,7 +852,7 @@ function PatientPlanCard({ ...@@ -852,7 +852,7 @@ function PatientPlanCard({
<HoverCardTrigger asChild> <HoverCardTrigger asChild>
<button <button
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
className="ml-1.5 align-middle rounded px-1 text-[10.5px] text-slate-400 hover:text-teal-700 hover:bg-slate-100 cursor-default" className="ml-1.5 align-middle rounded px-1 text-[10.5px] text-slate-400 hover:text-brand-700 hover:bg-slate-100 cursor-default"
> >
+{p.reasons.length - 1} +{p.reasons.length - 1}
</button> </button>
...@@ -897,7 +897,7 @@ function PatientPlanCard({ ...@@ -897,7 +897,7 @@ function PatientPlanCard({
{isPool && canAssign && ( {isPool && canAssign && (
<button <button
onClick={onClaim} onClick={onClaim}
className="rounded-md bg-teal-600 px-2.5 py-1 text-[12px] font-semibold text-white transition-colors hover:bg-teal-700" className="rounded-md bg-brand-600 px-2.5 py-1 text-[12px] font-semibold text-white transition-colors hover:bg-brand-700"
> >
认领 → 认领 →
</button> </button>
...@@ -1001,7 +1001,7 @@ function ScenarioChip({ label, scenario }: { label: string; scenario: string }) ...@@ -1001,7 +1001,7 @@ function ScenarioChip({ label, scenario }: { label: string; scenario: string })
const tone = const tone =
scenario === 'treatment_initiation_recall' scenario === 'treatment_initiation_recall'
? 'bg-rose-50 text-rose-700 ring-rose-200' ? 'bg-rose-50 text-rose-700 ring-rose-200'
: 'bg-teal-50 text-teal-700 ring-teal-200'; : 'bg-brand-50 text-brand-700 ring-brand-200';
return <span className={cn('inline-block rounded px-1.5 py-0.5 text-[10.5px] font-medium ring-1 ring-inset', tone)}>{label}</span>; return <span className={cn('inline-block rounded px-1.5 py-0.5 text-[10.5px] font-medium ring-1 ring-inset', tone)}>{label}</span>;
} }
......
...@@ -52,7 +52,7 @@ export function AudioVisualizer({ ...@@ -52,7 +52,7 @@ export function AudioVisualizer({
ref={(el) => { ref={(el) => {
barsRef.current[i] = el; barsRef.current[i] = el;
}} }}
className="w-[3px] rounded-full bg-teal-500/90 transition-[height] duration-75 ease-out" className="w-[3px] rounded-full bg-brand-500/90 transition-[height] duration-75 ease-out"
style={{ height: `${MIN_H}px` }} style={{ height: `${MIN_H}px` }}
/> />
))} ))}
......
...@@ -13,7 +13,7 @@ export function RealtimeCoachButton({ active, onClick }: { active: boolean; onCl ...@@ -13,7 +13,7 @@ export function RealtimeCoachButton({ active, onClick }: { active: boolean; onCl
'cursor-pointer active:scale-95', 'cursor-pointer active:scale-95',
active active
? 'bg-rose-500 hover:bg-rose-600 animate-coachPulse' ? 'bg-rose-500 hover:bg-rose-600 animate-coachPulse'
: 'bg-teal-600 hover:bg-teal-700', : 'bg-brand-600 hover:bg-brand-700',
)} )}
> >
{active ? ( {active ? (
......
...@@ -37,7 +37,7 @@ export interface ButtonProps ...@@ -37,7 +37,7 @@ export interface ButtonProps
} }
/** /**
* 主操作按钮 — variant: default/secondary/outline/ghost/destructive/link,size: sm/default/lg/icon;teal 主色 * 主操作按钮 — variant: default/secondary/outline/ghost/destructive/link,size: sm/default/lg/icon;PAC 品牌色(brand-600 / #0032A0)
* @category actions * @category actions
*/ */
export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>( export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
......
...@@ -19,7 +19,7 @@ const zhFormatters = { ...@@ -19,7 +19,7 @@ const zhFormatters = {
}; };
/// shadcn 官方 Calendar(react-day-picker v10)—— 全 Tailwind classNames,不引 rdp 默认样式表。 /// shadcn 官方 Calendar(react-day-picker v10)—— 全 Tailwind classNames,不引 rdp 默认样式表。
/// 主题走项目 CSS 变量(--primary=teal-600 / --accent / --ring),故选中/今天/焦点自动是 teal /// 主题走项目 CSS 变量(--primary=brand-600 / --accent / --ring),故选中/今天/焦点自动是品牌色
/// 支持 captionLayout="dropdown"(月/年下拉)与默认 label(左右翻页箭头)两种 caption。 /// 支持 captionLayout="dropdown"(月/年下拉)与默认 label(左右翻页箭头)两种 caption。
/** /**
* 日历(react-day-picker 封装)— 单选/范围由 mode 决定。 * 日历(react-day-picker 封装)— 单选/范围由 mode 决定。
......
...@@ -7,7 +7,7 @@ import { cn } from '@/lib/utils'; ...@@ -7,7 +7,7 @@ import { cn } from '@/lib/utils';
* Input — shadcn-new-york 标准 input,统一全站输入框样式。 * Input — shadcn-new-york 标准 input,统一全站输入框样式。
* *
* 风格:无 shadow(扁平),border-input(82% L,1px 视觉扎实), * 风格:无 shadow(扁平),border-input(82% L,1px 视觉扎实),
* hover 加深 border,focus 切 ring(跟 primary 同色 = teal)。 * hover 加深 border,focus 切 ring(跟 primary 同色 = 品牌色)。
* *
* 变体通过 className 覆盖: * 变体通过 className 覆盖:
* <Input className="font-mono text-xs" placeholder="https://..." /> * <Input className="font-mono text-xs" placeholder="https://..." />
......
...@@ -6,11 +6,11 @@ ...@@ -6,11 +6,11 @@
import { PersonaFeatureKey, PlanScenario } from './enums'; import { PersonaFeatureKey, PlanScenario } from './enums';
// ───────────────────────────────────────────────────────── // ─────────────────────────────────────────────────────────
// Tone — UI 色板键(对应前端 Tailwind 色板 teal/indigo/...) // Tone — UI 色板键(brand=PAC 主色,其余对应 Tailwind 色板 indigo/amber/...)
// ───────────────────────────────────────────────────────── // ─────────────────────────────────────────────────────────
export type Tone = export type Tone =
| 'teal' | 'brand'
| 'indigo' | 'indigo'
| 'emerald' | 'emerald'
| 'amber' | 'amber'
...@@ -49,7 +49,7 @@ export const planScenarioLabel = (key: string): string => ...@@ -49,7 +49,7 @@ export const planScenarioLabel = (key: string): string =>
* rose ⚠️ 警示 —— 看到要小心/要避让(禁忌、爽约、敏感、急迫) * rose ⚠️ 警示 —— 看到要小心/要避让(禁忌、爽约、敏感、急迫)
* amber ⏳ 机会 —— 有事可做(潜在治疗、治疗史这类可切入点) * amber ⏳ 机会 —— 有事可做(潜在治疗、治疗史这类可切入点)
* indigo 💰 价值 —— 钱与权益 * indigo 💰 价值 —— 钱与权益
* teal 📈 阶段 —— 客户所处位置 * brand 📈 阶段 —— 客户所处位置
* sky/slate 基础属性 —— 不需要判断,只是背景信息 * sky/slate 基础属性 —— 不需要判断,只是背景信息
*/ */
export const PERSONA_FEATURE_META: Record<string, { label: string; tone: Tone }> = { export const PERSONA_FEATURE_META: Record<string, { label: string; tone: Tone }> = {
...@@ -66,8 +66,8 @@ export const PERSONA_FEATURE_META: Record<string, { label: string; tone: Tone }> ...@@ -66,8 +66,8 @@ export const PERSONA_FEATURE_META: Record<string, { label: string; tone: Tone }>
[PersonaFeatureKey.ENTITLEMENT_STATUS]: { label: '权益身份', tone: 'indigo' }, [PersonaFeatureKey.ENTITLEMENT_STATUS]: { label: '权益身份', tone: 'indigo' },
[PersonaFeatureKey.DISCOUNT_ANCHOR]: { label: '折扣锚点', tone: 'indigo' }, [PersonaFeatureKey.DISCOUNT_ANCHOR]: { label: '折扣锚点', tone: 'indigo' },
// 📈 阶段与关系(转介绍达人是正面信息,同理不该标红) // 📈 阶段与关系(转介绍达人是正面信息,同理不该标红)
[PersonaFeatureKey.LIFECYCLE_STAGE]: { label: '生命周期', tone: 'teal' }, [PersonaFeatureKey.LIFECYCLE_STAGE]: { label: '生命周期', tone: 'brand' },
[PersonaFeatureKey.REFERRAL_CHAMPION]: { label: '转介绍达人', tone: 'teal' }, [PersonaFeatureKey.REFERRAL_CHAMPION]: { label: '转介绍达人', tone: 'brand' },
// 基础属性 // 基础属性
[PersonaFeatureKey.TIME_PREFERENCE]: { label: '时间偏好', tone: 'sky' }, [PersonaFeatureKey.TIME_PREFERENCE]: { label: '时间偏好', tone: 'sky' },
[PersonaFeatureKey.AGE_BRACKET]: { label: '年龄段', tone: 'sky' }, [PersonaFeatureKey.AGE_BRACKET]: { label: '年龄段', tone: 'sky' },
......
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