Commit 4eb9d804 by luoqi

fix(web): 全局 border 规则放进 @layer base —— 全站 258 处彩色 border 类此前一个都没生效

globals.css 里那条 `* { border-color: hsl(var(--border)) }` **不在任何 @layer 里**。
无层级样式整体胜过 `@layer utilities`(与优先级无关),于是它压过了 Tailwind
**全部** border 颜色工具类:写 `border-l-brand-400` / `border-rose-300` 不报错、
也不生效,算出来永远是那个灰 #D6DDE6。实测登录框 91 个有边框的元素,91 个都是灰的。

被吃掉的不只是"某处颜色不对":
  - `border-t-transparent` —— 两处转圈 loading 的缺口被填成灰,整圈同色,转了看不出来
  - `border-current` —— 本该跟随文字色调,恒为灰
  - ~32 处 hover:/focus: 边框反馈全哑,配着 `transition-colors` 动画一个空
  - chain-viz / facts-timeline 整套语义色板的 border 分支形同虚设

改法:把这条规则包进 `@layer base`。
️ Tailwind v4 没有 `--default-border-color` 主题变量(v3 的 borderColor.DEFAULT
已移除,preflight 改成 `border: 0 solid`),分层的 `*` 规则就是 v4 官方写法。

同时删掉 147 处中性 `border-slate-*` / `divide-slate-*` 类(31 个文件):
它们此前零效果,而分层后会**开始**生效 —— `border-slate-100`(114 处)会从
#D6DDE6 变 #F1F5F9(对白底对比度 1.30→1.06,发丝线几乎消失),`slate-50` 直接看不见。
这些类当初就是对着灰写的(该 `*` 规则从首次提交就在),删掉后落回 base 层默认灰,
**构造上保证零像素变化**。三个页面实测:中性边框数量与颜色与改前逐一对齐,
彩色边框恢复(brand-600 = rgb(0,50,160)、brand-400 = rgb(59,118,247))。

batch-tracking 那条竖轨收回工具类(`border-l-2 border-l-brand-400`),
连同解释这条地雷的注释一起删;`past` 保留 2px 透明边以维持两组首列对齐。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
parent 73cf9501
...@@ -111,7 +111,7 @@ function EntryResolver() { ...@@ -111,7 +111,7 @@ function EntryResolver() {
<div className="flex h-screen flex-col overflow-hidden bg-slate-50"> <div className="flex h-screen flex-col overflow-hidden bg-slate-50">
{/* 空态 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 bg-white px-4">
<span className="inline-flex h-7 w-7 items-center justify-center rounded-md bg-brand-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>
...@@ -154,7 +154,7 @@ function EntryResolver() { ...@@ -154,7 +154,7 @@ function EntryResolver() {
<button <button
type="button" type="button"
onClick={() => window.location.reload()} onClick={() => window.location.reload()}
className="inline-flex items-center gap-1.5 rounded-md border border-slate-100 bg-white px-3 py-1.5 text-[12.5px] text-slate-600 hover:bg-slate-50" className="inline-flex items-center gap-1.5 rounded-md border bg-white px-3 py-1.5 text-[12.5px] text-slate-600 hover:bg-slate-50"
> >
<RefreshCw className="h-3.5 w-3.5" /> <RefreshCw className="h-3.5 w-3.5" />
刷新 刷新
......
...@@ -114,15 +114,21 @@ ...@@ -114,15 +114,21 @@
/* 全局 border 颜色 — 这样写不用每处都标 border-border /* 全局 border 颜色 — 这样写不用每处都标 border-border
* *
* 🔴 **这条规则不在任何 @layer 里,所以它压过 Tailwind 的 `border-*` 颜色工具类** * ⚠️ **必须待在 `@layer base` 里**(2026-08-11 修)。原来它是**无层级**的裸 `*` 规则,
* (无层级的样式整体胜过 `@layer utilities`,与优先级无关)。 * 而无层级样式整体胜过 `@layer utilities`(与优先级无关)—— 于是它压过了 Tailwind
* ⇒ 组件里写 `border-l-brand-400` / `border-rose-300` 之类**不会报错、也不会生效**, * **全部** `border-*` 颜色工具类:组件里写 `border-l-brand-400` / `border-rose-300`
* 算出来永远是这里这个灰(2026-08-11 在主管工作台那条竖轨上实测踩到)。 * 不报错、也不生效,算出来永远是这个灰。实测全站 258 处彩色 border 类无一生效,
* 要给某处上**彩色边框**,走 inline style 或 `@utility`,⛔ 别指望工具类。 * 连 `border-t-transparent`(转圈 loading 的缺口)和 `border-current` 都被吃掉。
* ⚠️ 想把这条改成分层的(让工具类重新生效)要整站回归:现在全站的 `border-xxx-100` * 放进 base 层后 utilities 重新压得过它,工具类恢复正常。
* 之类**都**落在这个灰上,一改就全变。 */ *
* { * ⛔ 别再把它挪出 `@layer`,也别加 `!important` —— 那等于把这个坑原样刨回来。
* 备注:Tailwind v4 没有 `--default-border-color` 这个主题变量(v3 的
* `borderColor.DEFAULT` 在 v4 已移除,preflight 改成 `border: 0 solid` = currentColor),
* 所以「分层的 `*` 规则」就是 v4 官方给的写法,不存在更声明式的替代。 */
@layer base {
* {
border-color: hsl(var(--border)); border-color: hsl(var(--border));
}
} }
body { body {
......
...@@ -27,7 +27,7 @@ export default function PetLabPage() { ...@@ -27,7 +27,7 @@ export default function PetLabPage() {
<figure <figure
key={p} key={p}
data-pet-perch data-pet-perch
className="flex flex-col items-center gap-2 rounded-xl border border-slate-100 bg-white p-4 shadow-sm" className="flex flex-col items-center gap-2 rounded-xl border bg-white p-4 shadow-sm"
> >
<PetBody pose={p} glanceSeq={p === 'idle' ? glance : 0} size={96} /> <PetBody pose={p} glanceSeq={p === 'idle' ? glance : 0} size={96} />
<figcaption className="text-xs text-slate-500">{p}</figcaption> <figcaption className="text-xs text-slate-500">{p}</figcaption>
...@@ -36,7 +36,7 @@ export default function PetLabPage() { ...@@ -36,7 +36,7 @@ export default function PetLabPage() {
{/* 涂氟护盾(与姿态正交的 buff,叠在 idle 上展示) */} {/* 涂氟护盾(与姿态正交的 buff,叠在 idle 上展示) */}
<figure <figure
data-pet-perch data-pet-perch
className="flex flex-col items-center gap-2 rounded-xl border border-slate-100 bg-white p-4 shadow-sm" className="flex flex-col items-center gap-2 rounded-xl border bg-white p-4 shadow-sm"
> >
<PetBody pose="idle" glanceSeq={0} shield size={96} /> <PetBody pose="idle" glanceSeq={0} shield size={96} />
<figcaption className="text-xs text-slate-500">shield(涂氟护盾)</figcaption> <figcaption className="text-xs text-slate-500">shield(涂氟护盾)</figcaption>
...@@ -136,7 +136,7 @@ function LabBtn({ label, onClick }: { label: string; onClick: () => void }) { ...@@ -136,7 +136,7 @@ function LabBtn({ label, onClick }: { label: string; onClick: () => void }) {
<button <button
type="button" type="button"
onClick={onClick} onClick={onClick}
className="rounded-lg border border-slate-200 bg-white px-3 py-1.5 text-sm text-slate-700 shadow-sm hover:bg-slate-50" className="rounded-lg border bg-white px-3 py-1.5 text-sm text-slate-700 shadow-sm hover:bg-slate-50"
> >
{label} {label}
</button> </button>
......
...@@ -957,7 +957,7 @@ export function AssignmentConfirmSheet({ ...@@ -957,7 +957,7 @@ export function AssignmentConfirmSheet({
className="overflow-hidden border-brand-100 text-[12px] shadow-sm" className="overflow-hidden border-brand-100 text-[12px] shadow-sm"
> >
{/* ① 汇总 */} {/* ① 汇总 */}
<div className="flex items-center gap-2 border-b border-slate-100 bg-brand-50/50 px-3 py-2"> <div className="flex items-center gap-2 border-b bg-brand-50/50 px-3 py-2">
<Users className="h-3.5 w-3.5 flex-none text-brand-600" /> <Users className="h-3.5 w-3.5 flex-none text-brand-600" />
{/* {/*
⚠️ 有待分配时必须**先给总数**,再拆两半。 ⚠️ 有待分配时必须**先给总数**,再拆两半。
...@@ -1040,14 +1040,14 @@ export function AssignmentConfirmSheet({ ...@@ -1040,14 +1040,14 @@ export function AssignmentConfirmSheet({
和这里问的"这活多大"不是一回事。 和这里问的"这活多大"不是一回事。
*/} */}
{sheet.opsNote && ( {sheet.opsNote && (
<div className="whitespace-pre-line border-b border-slate-100 bg-slate-50/70 px-3 py-2 text-[11.5px] leading-relaxed text-slate-700"> <div className="whitespace-pre-line border-b bg-slate-50/70 px-3 py-2 text-[11.5px] leading-relaxed text-slate-700">
<Emphasized text={sheet.opsNote} /> <Emphasized text={sheet.opsNote} />
</div> </div>
)} )}
{/* ② 按客服(产能视图)—— 这一层是主管真正在看的 */} {/* ② 按客服(产能视图)—— 这一层是主管真正在看的 */}
<CardContent className="p-0"> <CardContent className="p-0">
<div className="divide-y divide-slate-50"> <div className="divide-y">
{groups.map((g) => { {groups.map((g) => {
const isOpen = open.has(g.userId); const isOpen = open.has(g.userId);
return ( return (
...@@ -1384,7 +1384,7 @@ export function AssignmentConfirmSheet({ ...@@ -1384,7 +1384,7 @@ export function AssignmentConfirmSheet({
(水位法没轮到)或被精调成 0 名额。 */} (水位法没轮到)或被精调成 0 名额。 */}
{/* ⚠️ 拖过去之后他就有条目了 —— 从这行剔掉,否则同一个人既在上面成组、又在"未分到"里 */} {/* ⚠️ 拖过去之后他就有条目了 —— 从这行剔掉,否则同一个人既在上面成组、又在"未分到"里 */}
{idleAgents.length > 0 && ( {idleAgents.length > 0 && (
<div className="border-t border-slate-100 px-3 py-1.5 text-[10.5px] text-slate-400"> <div className="border-t px-3 py-1.5 text-[10.5px] text-slate-400">
本批未分到:{idleAgents.map((a) => `${a.name ?? a.userId}(在手 ${a.inHand})`).join('、')} 本批未分到:{idleAgents.map((a) => `${a.name ?? a.userId}(在手 ${a.inHand})`).join('、')}
</div> </div>
)} )}
...@@ -1405,14 +1405,14 @@ export function AssignmentConfirmSheet({ ...@@ -1405,14 +1405,14 @@ export function AssignmentConfirmSheet({
⚠️ 只在**还能改**的时候显示:已确认 / 已撤销时它是误导。 ⚠️ 只在**还能改**的时候显示:已确认 / 已撤销时它是误导。
*/} */}
{!readOnly && ( {!readOnly && (
<div className="border-t border-slate-100 bg-slate-50/60 px-3 py-1.5 text-[10.5px] leading-relaxed text-slate-400"> <div className="border-t bg-slate-50/60 px-3 py-1.5 text-[10.5px] leading-relaxed text-slate-400">
可以直接跟我说:「把某某移出这批」「某某的单给 5 天」「这批 200 人」· 可以直接跟我说:「把某某移出这批」「某某的单给 5 天」「这批 200 人」·
「只要商保直付的」「排掉最近来过的」· 「这批带上『老客户复查免挂号费』」 「只要商保直付的」「排掉最近来过的」· 「这批带上『老客户复查免挂号费』」
</div> </div>
)} )}
{/* ④ 确认 */} {/* ④ 确认 */}
<div className="border-t border-slate-100 px-3 py-2"> <div className="border-t px-3 py-2">
{error && <p className="mb-1.5 text-[11px] text-rose-600">{error}</p>} {error && <p className="mb-1.5 text-[11px] text-rose-600">{error}</p>}
{cancelled && !revoked ? ( {cancelled && !revoked ? (
// 助手撤的:本地没有三个数,只说状态 —— 具体数字助手在消息里已经原话转述过 // 助手撤的:本地没有三个数,只说状态 —— 具体数字助手在消息里已经原话转述过
......
...@@ -115,7 +115,7 @@ function Markdown({ text }: { text: string }) { ...@@ -115,7 +115,7 @@ function Markdown({ text }: { text: string }) {
{children} {children}
</a> </a>
), ),
hr: () => <hr className="my-3 border-slate-100" />, hr: () => <hr className="my-3" />,
blockquote: ({ children }) => ( blockquote: ({ children }) => (
<blockquote className="my-2 border-l-2 border-brand-300 bg-brand-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}
...@@ -130,7 +130,7 @@ function Markdown({ text }: { text: string }) { ...@@ -130,7 +130,7 @@ function Markdown({ text }: { text: string }) {
</code> </code>
), ),
pre: ({ children }) => ( pre: ({ children }) => (
<pre className="my-2 overflow-x-auto rounded-md border border-slate-100 bg-slate-50 p-3 text-[12px]"> <pre className="my-2 overflow-x-auto rounded-md border bg-slate-50 p-3 text-[12px]">
{children} {children}
</pre> </pre>
), ),
...@@ -141,9 +141,9 @@ function Markdown({ text }: { text: string }) { ...@@ -141,9 +141,9 @@ function Markdown({ text }: { text: string }) {
), ),
thead: ({ children }) => <thead className="bg-slate-50">{children}</thead>, thead: ({ children }) => <thead className="bg-slate-50">{children}</thead>,
th: ({ children }) => ( th: ({ children }) => (
<th className="border border-slate-100 px-2 py-1 text-left font-medium text-slate-600">{children}</th> <th className="border px-2 py-1 text-left font-medium text-slate-600">{children}</th>
), ),
td: ({ children }) => <td className="border border-slate-100 px-2 py-1 text-slate-700">{children}</td>, td: ({ children }) => <td className="border px-2 py-1 text-slate-700">{children}</td>,
}} }}
> >
{text} {text}
...@@ -324,8 +324,8 @@ function ArtifactView({ artifact }: { artifact: Artifact }) { ...@@ -324,8 +324,8 @@ 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 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 bg-slate-50/60 px-3 py-1.5">
<LayoutTemplate className="h-3.5 w-3.5 text-brand-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
...@@ -347,8 +347,8 @@ function ArtifactView({ artifact }: { artifact: Artifact }) { ...@@ -347,8 +347,8 @@ function ArtifactView({ artifact }: { artifact: Artifact }) {
createPortal( createPortal(
<div className="fixed inset-0 z-[70] flex items-center justify-center p-4"> <div className="fixed inset-0 z-[70] flex items-center justify-center p-4">
<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 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 bg-slate-50/60 px-3 py-2">
<LayoutTemplate className="h-4 w-4 text-brand-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 && (
...@@ -533,14 +533,14 @@ function ModelSelect({ ...@@ -533,14 +533,14 @@ function ModelSelect({
type="button" type="button"
disabled={disabled} disabled={disabled}
onClick={() => setOpen((o) => !o)} onClick={() => setOpen((o) => !o)}
className="inline-flex items-center gap-1.5 rounded-md border border-slate-100 bg-white px-2.5 py-1.5 text-[12.5px] text-slate-700 transition-colors hover:bg-slate-50 disabled:opacity-50" className="inline-flex items-center gap-1.5 rounded-md border bg-white px-2.5 py-1.5 text-[12.5px] text-slate-700 transition-colors hover:bg-slate-50 disabled:opacity-50"
> >
<span className="h-1.5 w-1.5 rounded-full bg-emerald-500" /> <span className="h-1.5 w-1.5 rounded-full bg-emerald-500" />
{label} {label}
<ChevronDown className={cn('h-3.5 w-3.5 text-slate-400 transition-transform', open && 'rotate-180')} /> <ChevronDown className={cn('h-3.5 w-3.5 text-slate-400 transition-transform', open && 'rotate-180')} />
</button> </button>
{open && ( {open && (
<div className="absolute right-0 z-50 mt-1 w-40 rounded-lg border border-slate-100 bg-white py-1 shadow-lg"> <div className="absolute right-0 z-50 mt-1 w-40 rounded-lg border bg-white py-1 shadow-lg">
{MODELS.map((m) => ( {MODELS.map((m) => (
<button <button
key={m.value} key={m.value}
...@@ -798,7 +798,7 @@ export function AssistantChat({ ...@@ -798,7 +798,7 @@ export function AssistantChat({
return ( return (
<div className={cn('flex flex-col bg-slate-50', isWidget ? 'h-full' : 'h-[100dvh]')}> <div className={cn('flex flex-col bg-slate-50', isWidget ? 'h-full' : 'h-[100dvh]')}>
{/* Header */} {/* Header */}
<header className="flex-none border-b border-slate-100 bg-white/90 backdrop-blur"> <header className="flex-none border-b bg-white/90 backdrop-blur">
<div <div
className={cn( className={cn(
'mx-auto flex items-center gap-2.5', 'mx-auto flex items-center gap-2.5',
...@@ -859,7 +859,7 @@ export function AssistantChat({ ...@@ -859,7 +859,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-brand-300 hover:bg-brand-50/50 hover:text-brand-700" className="rounded-full border 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>
...@@ -893,14 +893,14 @@ export function AssistantChat({ ...@@ -893,14 +893,14 @@ export function AssistantChat({
type="button" type="button"
onClick={toBottom} onClick={toBottom}
title="回到最新" title="回到最新"
className="absolute -top-4 left-1/2 z-10 inline-flex h-7 w-7 -translate-x-1/2 items-center justify-center rounded-full border border-slate-200 bg-white text-slate-500 shadow-md transition-colors hover:bg-slate-50 hover:text-slate-700" className="absolute -top-4 left-1/2 z-10 inline-flex h-7 w-7 -translate-x-1/2 items-center justify-center rounded-full border bg-white text-slate-500 shadow-md transition-colors hover:bg-slate-50 hover:text-slate-700"
> >
<ChevronDown className="h-4 w-4" /> <ChevronDown className="h-4 w-4" />
</button> </button>
)} )}
<div className="mx-auto max-w-3xl px-4 pb-3 pt-1"> <div className="mx-auto max-w-3xl px-4 pb-3 pt-1">
{/* 输入框**有边框**(与会话同底,但自己成一块) —— 无边框那版太散,找不到落点 */} {/* 输入框**有边框**(与会话同底,但自己成一块) —— 无边框那版太散,找不到落点 */}
<div className="flex items-end gap-2 rounded-xl border border-slate-200 bg-white px-3 py-2 transition-colors focus-within:border-slate-300"> <div className="flex items-end gap-2 rounded-xl border bg-white px-3 py-2 transition-colors">
<textarea <textarea
ref={taRef} ref={taRef}
rows={1} rows={1}
......
...@@ -117,7 +117,7 @@ export function AssistantFab({ ...@@ -117,7 +117,7 @@ export function AssistantFab({
{bubble && ( {bubble && (
<span <span
className={ className={
'absolute bottom-full mb-1.5 w-max max-w-[230px] whitespace-normal break-words rounded-xl border border-slate-100 bg-white px-3 py-1.5 text-xs leading-snug text-slate-700 shadow-lg ' + 'absolute bottom-full mb-1.5 w-max max-w-[230px] whitespace-normal break-words rounded-xl border bg-white px-3 py-1.5 text-xs leading-snug text-slate-700 shadow-lg ' +
(onLeftHalf ? 'left-0' : 'right-0') (onLeftHalf ? 'left-0' : 'right-0')
} }
> >
......
...@@ -92,7 +92,7 @@ export function AssistantWidget() { ...@@ -92,7 +92,7 @@ export function AssistantWidget() {
<div <div
style={maximized ? undefined : winRect ? { left: winRect.x, top: winRect.y } : undefined} style={maximized ? undefined : winRect ? { left: winRect.x, top: winRect.y } : undefined}
className={cn( className={cn(
'fixed z-[60] flex flex-col overflow-hidden rounded-xl border border-slate-100 shadow-2xl', 'fixed z-[60] flex flex-col overflow-hidden rounded-xl border shadow-2xl',
'transition-[opacity,transform] duration-150', 'transition-[opacity,transform] duration-150',
maximized maximized
? 'inset-3' // 最大化:填满窗口(留 12px 边距),不写死宽高,随窗口自适应 ? 'inset-3' // 最大化:填满窗口(留 12px 边距),不写死宽高,随窗口自适应
......
...@@ -180,7 +180,7 @@ export function MockLoginDialog({ open }: { open: boolean }) { ...@@ -180,7 +180,7 @@ export function MockLoginDialog({ open }: { open: boolean }) {
</DialogHeader> </DialogHeader>
{/* 宿主 tab —— 两宿主数据完全隔离(不同 host/tenant/诊所),切换互不影响 */} {/* 宿主 tab —— 两宿主数据完全隔离(不同 host/tenant/诊所),切换互不影响 */}
<div className="flex gap-1 border-b border-slate-100"> <div className="flex gap-1 border-b">
{([ {([
{ key: 'jvs-dw' as const, label: '瑞尔集团', sub: 'jvs-dw · 客服花名册' }, { key: 'jvs-dw' as const, label: '瑞尔集团', sub: 'jvs-dw · 客服花名册' },
{ key: 'friday' as const, label: 'FRIDAY 市场', sub: 'friday · 多品牌 SaaS' }, { key: 'friday' as const, label: 'FRIDAY 市场', sub: 'friday · 多品牌 SaaS' },
...@@ -229,7 +229,7 @@ export function MockLoginDialog({ open }: { open: boolean }) { ...@@ -229,7 +229,7 @@ export function MockLoginDialog({ open }: { open: boolean }) {
'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-brand-400 bg-brand-50/60 ring-1 ring-brand-200' ? 'border-brand-400 bg-brand-50/60 ring-1 ring-brand-200'
: 'border-slate-100 hover:border-brand-300', : '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>
...@@ -249,7 +249,7 @@ export function MockLoginDialog({ open }: { open: boolean }) { ...@@ -249,7 +249,7 @@ export function MockLoginDialog({ open }: { open: boolean }) {
</div> </div>
{/* ② 选客服 */} {/* ② 选客服 */}
<div className="rounded-lg border border-slate-100 p-3"> <div className="rounded-lg border p-3">
<div className="mb-2 flex items-center justify-between"> <div className="mb-2 flex items-center justify-between">
<div className="text-[12px] font-medium text-slate-600"> <div className="text-[12px] font-medium text-slate-600">
② 数据范围 · 选客服 = <strong>该客服的诊所</strong>(诊所级)+ 上方「{roleNameZh(role)}」角色 ② 数据范围 · 选客服 = <strong>该客服的诊所</strong>(诊所级)+ 上方「{roleNameZh(role)}」角色
...@@ -271,7 +271,7 @@ export function MockLoginDialog({ open }: { open: boolean }) { ...@@ -271,7 +271,7 @@ export function MockLoginDialog({ open }: { open: boolean }) {
'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-brand-400 bg-brand-50 text-brand-700' ? 'border-brand-400 bg-brand-50 text-brand-700'
: 'border-slate-100 text-slate-500 hover:border-brand-300', : '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-brand-400" className="ml-auto w-40 rounded-md border px-2.5 py-1 text-[12px] outline-none focus:border-brand-400"
/> />
</div> </div>
...@@ -301,7 +301,7 @@ export function MockLoginDialog({ open }: { open: boolean }) { ...@@ -301,7 +301,7 @@ export function MockLoginDialog({ open }: { open: boolean }) {
'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-brand-400 ring-2 ring-brand-200' ? 'border-brand-400 ring-2 ring-brand-200'
: 'border-slate-100 hover:border-brand-400 hover:bg-brand-50/40', : 'hover:border-brand-400 hover:bg-brand-50/40',
busy && !loading && 'opacity-50', busy && !loading && 'opacity-50',
)} )}
> >
...@@ -348,7 +348,7 @@ export function MockLoginDialog({ open }: { open: boolean }) { ...@@ -348,7 +348,7 @@ export function MockLoginDialog({ open }: { open: boolean }) {
'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-brand-400 ring-2 ring-brand-200' ? 'border-brand-400 ring-2 ring-brand-200'
: 'border-slate-100 hover:border-brand-400 hover:bg-brand-50/40', : 'hover:border-brand-400 hover:bg-brand-50/40',
busy && !loading && 'opacity-50', busy && !loading && 'opacity-50',
)} )}
> >
...@@ -430,7 +430,7 @@ function FridayPanel({ ...@@ -430,7 +430,7 @@ function FridayPanel({
'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-brand-400 bg-brand-50/60 ring-1 ring-brand-200' ? 'border-brand-400 bg-brand-50/60 ring-1 ring-brand-200'
: 'border-slate-100 hover:border-brand-300', : '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>
...@@ -451,7 +451,7 @@ function FridayPanel({ ...@@ -451,7 +451,7 @@ function FridayPanel({
</div> </div>
{/* ② 真实品牌 / 诊所 */} {/* ② 真实品牌 / 诊所 */}
<div className="rounded-lg border border-slate-100 p-3"> <div className="rounded-lg border p-3">
<div className="mb-2 flex items-center justify-between"> <div className="mb-2 flex items-center justify-between">
<div className="text-[12px] font-medium text-slate-600"> <div className="text-[12px] font-medium text-slate-600">
② 数据范围 · 选<strong>品牌</strong><strong>诊所</strong>(真实摄入数据)+ 上方「 ② 数据范围 · 选<strong>品牌</strong><strong>诊所</strong>(真实摄入数据)+ 上方「
...@@ -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-brand-300 focus:bg-white" className="mb-2 h-7 w-full rounded-md border 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 && (
...@@ -474,7 +474,7 @@ function FridayPanel({ ...@@ -474,7 +474,7 @@ function FridayPanel({
</div> </div>
)} )}
{filtered.map((b) => ( {filtered.map((b) => (
<div key={b.brandId} className="rounded-md border border-slate-100"> <div key={b.brandId} className="rounded-md border">
{/* 品牌级:该品牌全部诊所 */} {/* 品牌级:该品牌全部诊所 */}
<button <button
type="button" type="button"
...@@ -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-brand-300 hover:bg-brand-50 hover:text-brand-700 disabled:opacity-50" className="rounded-full border 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>
......
...@@ -549,7 +549,7 @@ export function PetFab({ ...@@ -549,7 +549,7 @@ export function PetFab({
<span <span
className={cn( className={cn(
// w-max 让短句贴合内容、max-w 封顶,长句自动换行不溢出气泡框 // w-max 让短句贴合内容、max-w 封顶,长句自动换行不溢出气泡框
'absolute bottom-full mb-1.5 w-max max-w-[230px] whitespace-normal break-words rounded-xl border border-slate-100 bg-white px-3 py-1.5 text-xs leading-snug text-slate-700 shadow-lg', 'absolute bottom-full mb-1.5 w-max max-w-[230px] whitespace-normal break-words rounded-xl border bg-white px-3 py-1.5 text-xs leading-snug text-slate-700 shadow-lg',
onLeftHalf ? 'left-0' : 'right-0', onLeftHalf ? 'left-0' : 'right-0',
)} )}
> >
......
...@@ -116,7 +116,7 @@ function TimelineStageNode({ ...@@ -116,7 +116,7 @@ function TimelineStageNode({
? cn(THEME_DOT[theme]) ? cn(THEME_DOT[theme])
: isCurrent : isCurrent
? 'bg-amber-400 border-amber-400 ring-2 ring-amber-100' ? 'bg-amber-400 border-amber-400 ring-2 ring-amber-100'
: 'bg-white border-dashed border-slate-300'; : 'bg-white border-dashed';
const titleCls = isDone || isCurrent ? 'text-slate-900' : 'text-slate-400'; const titleCls = isDone || isCurrent ? 'text-slate-900' : 'text-slate-400';
const detailCls = isDone || isCurrent ? 'text-slate-600' : 'text-slate-400'; const detailCls = isDone || isCurrent ? 'text-slate-600' : 'text-slate-400';
const atCls = isDone || isCurrent ? 'text-slate-500' : 'text-slate-300'; const atCls = isDone || isCurrent ? 'text-slate-500' : 'text-slate-300';
...@@ -275,7 +275,7 @@ function TargetTimelineRow({ chain }: { chain: Chain }) { ...@@ -275,7 +275,7 @@ function TargetTimelineRow({ chain }: { chain: Chain }) {
function HistoryStrip({ chains }: { chains: Chain[] }) { function HistoryStrip({ chains }: { chains: Chain[] }) {
return ( return (
<div className="rounded-lg border border-slate-100 bg-slate-50/60 px-3 py-2"> <div className="rounded-lg border bg-slate-50/60 px-3 py-2">
<div className="flex items-center justify-between mb-1.5"> <div className="flex items-center justify-between mb-1.5">
<div className="flex items-center gap-1.5 text-[11px] text-slate-500"> <div className="flex items-center gap-1.5 text-[11px] text-slate-500">
<svg viewBox="0 0 24 24" className="w-3 h-3" fill="none" stroke="currentColor" strokeWidth="2"> <svg viewBox="0 0 24 24" className="w-3 h-3" fill="none" stroke="currentColor" strokeWidth="2">
...@@ -298,7 +298,7 @@ function HistoryChainCard({ chain }: { chain: Chain }) { ...@@ -298,7 +298,7 @@ function HistoryChainCard({ chain }: { chain: Chain }) {
const lastNode = chain.nodes[4]; const lastNode = chain.nodes[4];
const currNode = chain.nodes[chain.currentStage - 1]; const currNode = chain.nodes[chain.currentStage - 1];
return ( return (
<div className="bg-white rounded border border-slate-100 px-2.5 py-1.5 flex items-center gap-2"> <div className="bg-white rounded border px-2.5 py-1.5 flex items-center gap-2">
<span <span
className={cn( className={cn(
'flex-none w-5 h-5 rounded-full flex items-center justify-center text-[10px] font-bold', 'flex-none w-5 h-5 rounded-full flex items-center justify-center text-[10px] font-bold',
...@@ -460,7 +460,7 @@ const TONE_WRAP: Record<ChainTone, string> = { ...@@ -460,7 +460,7 @@ const TONE_WRAP: Record<ChainTone, string> = {
amber: 'bg-amber-50/40 border-amber-200/70', amber: 'bg-amber-50/40 border-amber-200/70',
sky: 'bg-sky-50/40 border-sky-200/70', sky: 'bg-sky-50/40 border-sky-200/70',
emerald: 'bg-emerald-50/40 border-emerald-200/70', emerald: 'bg-emerald-50/40 border-emerald-200/70',
slate: 'bg-slate-50/40 border-slate-100/70', slate: 'bg-slate-50/40',
}; };
const TONE_DOT: Record<ChainTone, string> = { const TONE_DOT: Record<ChainTone, string> = {
rose: 'bg-rose-500 text-white', rose: 'bg-rose-500 text-white',
...@@ -495,7 +495,7 @@ export function ChainDetailView({ chains }: { chains: Chain[] }) { ...@@ -495,7 +495,7 @@ export function ChainDetailView({ chains }: { chains: Chain[] }) {
)} )}
{/* 下半:全部列表(点击切换上面的明细)*/} {/* 下半:全部列表(点击切换上面的明细)*/}
<div className="pt-3 border-t border-slate-100"> <div className="pt-3 border-t">
<div className="text-[11px] text-slate-500 mb-2"> <div className="text-[11px] text-slate-500 mb-2">
全部 {chains.length} 条治疗链 全部 {chains.length} 条治疗链
</div> </div>
...@@ -538,7 +538,7 @@ function ChainListItem({ ...@@ -538,7 +538,7 @@ function ChainListItem({
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-brand-400 bg-brand-50 ring-1 ring-brand-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: 'bg-slate-50 ring-1 ring-slate-200',
}; };
return ( return (
<button <button
...@@ -548,7 +548,7 @@ function ChainListItem({ ...@@ -548,7 +548,7 @@ function ChainListItem({
'text-left rounded-md border px-2.5 py-2 transition', 'text-left rounded-md border px-2.5 py-2 transition',
active active
? activeRing[v.tone] ? activeRing[v.tone]
: 'border-slate-100 bg-white hover:bg-slate-50', : 'bg-white hover:bg-slate-50',
)} )}
> >
<div className="flex items-center gap-1.5"> <div className="flex items-center gap-1.5">
......
...@@ -110,13 +110,13 @@ export function CloseOpportunityDialog({ ...@@ -110,13 +110,13 @@ export function CloseOpportunityDialog({
r.key === 'other' && 'col-span-2', r.key === 'other' && 'col-span-2',
active active
? 'border-brand-400 bg-brand-50 font-medium text-brand-800' ? 'border-brand-400 bg-brand-50 font-medium text-brand-800'
: 'border-slate-200 bg-white text-slate-700 hover:border-brand-300 hover:bg-brand-50/50', : '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-brand-600 bg-brand-600' : 'border-slate-300', active ? 'border-brand-600 bg-brand-600' : '',
)} )}
> >
{active && ( {active && (
...@@ -156,7 +156,7 @@ export function CloseOpportunityDialog({ ...@@ -156,7 +156,7 @@ export function CloseOpportunityDialog({
'rounded-md border px-2.5 py-1 text-[12px] transition-colors', 'rounded-md border px-2.5 py-1 text-[12px] transition-colors',
on on
? 'border-brand-400 bg-brand-50 font-medium text-brand-800' ? 'border-brand-400 bg-brand-50 font-medium text-brand-800'
: 'border-slate-200 bg-white text-slate-700 hover:border-brand-300 hover:bg-brand-50/50', : 'bg-white text-slate-700 hover:border-brand-300 hover:bg-brand-50/50',
)} )}
> >
{potentialTreatmentCardLabel(code)} {potentialTreatmentCardLabel(code)}
...@@ -177,7 +177,7 @@ export function CloseOpportunityDialog({ ...@@ -177,7 +177,7 @@ 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-brand-200 focus:border-brand-400" className="w-full resize-y rounded-md border 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>
)} )}
......
...@@ -127,10 +127,10 @@ export function Drawer({ ...@@ -127,10 +127,10 @@ export function Drawer({
<div className="fixed inset-0 z-[60] flex" onClick={onClose}> <div className="fixed inset-0 z-[60] flex" onClick={onClose}>
<div className="flex-1 bg-slate-900/30" /> <div className="flex-1 bg-slate-900/30" />
<aside <aside
className={cn('max-w-[92vw] bg-white shadow-2xl border-l border-slate-100 flex flex-col', width)} className={cn('max-w-[92vw] bg-white shadow-2xl border-l flex flex-col', width)}
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
> >
<header className="px-5 py-3.5 border-b border-slate-100 flex items-center justify-between flex-none"> <header className="px-5 py-3.5 border-b flex items-center justify-between flex-none">
<div> <div>
<h3 className="text-[14px] font-semibold text-slate-900">{title}</h3> <h3 className="text-[14px] font-semibold text-slate-900">{title}</h3>
<p className="text-[11px] text-slate-500 mt-0.5">{subtitle}</p> <p className="text-[11px] text-slate-500 mt-0.5">{subtitle}</p>
...@@ -229,7 +229,7 @@ function ImageFactsView({ facts }: { facts: AdaptedFact[] }) { ...@@ -229,7 +229,7 @@ function ImageFactsView({ facts }: { facts: AdaptedFact[] }) {
: ((c.tooth_position as string | undefined) ?? null); : ((c.tooth_position as string | undefined) ?? null);
const encId = c.encounter_external_id as string | undefined; const encId = c.encounter_external_id as string | undefined;
return ( return (
<div key={f.id} className="rounded-md border border-slate-100 p-3"> <div key={f.id} className="rounded-md border p-3">
<div className="flex items-center justify-between mb-1.5"> <div className="flex items-center justify-between mb-1.5">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Chip tone="violet" size="xs"> <Chip tone="violet" size="xs">
...@@ -258,7 +258,7 @@ function ImageFactsView({ facts }: { facts: AdaptedFact[] }) { ...@@ -258,7 +258,7 @@ function ImageFactsView({ facts }: { facts: AdaptedFact[] }) {
</div> </div>
); );
})} })}
<div className="text-[10.5px] text-slate-400 pt-2 border-t border-slate-100"> <div className="text-[10.5px] text-slate-400 pt-2 border-t">
如需查看原始 DICOM,请跳转宿主影像系统(deep-link 待 host actionUrls 接入) 如需查看原始 DICOM,请跳转宿主影像系统(deep-link 待 host actionUrls 接入)
</div> </div>
</div> </div>
...@@ -271,7 +271,7 @@ function ImageFactsView({ facts }: { facts: AdaptedFact[] }) { ...@@ -271,7 +271,7 @@ function ImageFactsView({ facts }: { facts: AdaptedFact[] }) {
function CBCTImageView() { function CBCTImageView() {
return ( return (
<div className="space-y-3"> <div className="space-y-3">
<div className="aspect-[4/3] rounded-lg border border-slate-100 bg-slate-900 relative overflow-hidden"> <div className="aspect-[4/3] rounded-lg border bg-slate-900 relative overflow-hidden">
<div <div
className="absolute inset-0" className="absolute inset-0"
style={{ backgroundImage: 'radial-gradient(circle at 50% 60%, #475569 0%, #0f172a 50%, #000 100%)' }} style={{ backgroundImage: 'radial-gradient(circle at 50% 60%, #475569 0%, #0f172a 50%, #000 100%)' }}
...@@ -338,7 +338,7 @@ function ReturnVisitsList({ visits }: { visits: ReturnVisitItem[] }) { ...@@ -338,7 +338,7 @@ function ReturnVisitsList({ visits }: { visits: ReturnVisitItem[] }) {
<div <div
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 last:border-0',
latest && 'bg-brand-50/50 -mx-2 px-2 rounded', latest && 'bg-brand-50/50 -mx-2 px-2 rounded',
)} )}
> >
......
...@@ -69,7 +69,7 @@ export function EmrSoapView({ facts }: { facts: AdaptedFact[] }) { ...@@ -69,7 +69,7 @@ export function EmrSoapView({ facts }: { facts: AdaptedFact[] }) {
<button <button
onClick={prev} onClick={prev}
aria-label="上一次接诊" aria-label="上一次接诊"
className="inline-flex items-center justify-center w-7 h-7 rounded-md border border-slate-100 bg-white hover:bg-slate-50 hover:border-slate-300 text-slate-600 transition-colors" className="inline-flex items-center justify-center w-7 h-7 rounded-md border bg-white hover:bg-slate-50 text-slate-600 transition-colors"
> >
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" className="w-3.5 h-3.5"> <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" className="w-3.5 h-3.5">
<path d="M15 18l-6-6 6-6" strokeLinecap="round" strokeLinejoin="round" /> <path d="M15 18l-6-6 6-6" strokeLinecap="round" strokeLinejoin="round" />
...@@ -107,7 +107,7 @@ export function EmrSoapView({ facts }: { facts: AdaptedFact[] }) { ...@@ -107,7 +107,7 @@ export function EmrSoapView({ facts }: { facts: AdaptedFact[] }) {
<button <button
onClick={next} onClick={next}
aria-label="下一次接诊" aria-label="下一次接诊"
className="inline-flex items-center justify-center w-7 h-7 rounded-md border border-slate-100 bg-white hover:bg-slate-50 hover:border-slate-300 text-slate-600 transition-colors" className="inline-flex items-center justify-center w-7 h-7 rounded-md border bg-white hover:bg-slate-50 text-slate-600 transition-colors"
> >
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" className="w-3.5 h-3.5"> <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" className="w-3.5 h-3.5">
<path d="M9 18l6-6-6-6" strokeLinecap="round" strokeLinejoin="round" /> <path d="M9 18l6-6-6-6" strokeLinecap="round" strokeLinejoin="round" />
...@@ -181,9 +181,9 @@ function EmrSection({ ...@@ -181,9 +181,9 @@ function EmrSection({
const doctorAdvice = (c.doctor_advice as string | undefined) ?? ''; const doctorAdvice = (c.doctor_advice as string | undefined) ?? '';
return ( return (
<section className="rounded-lg border border-slate-100 bg-white overflow-hidden"> <section className="rounded-lg border bg-white overflow-hidden">
{/* 头:日期 + 医生 + 第几次 */} {/* 头:日期 + 医生 + 第几次 */}
<header className="px-3 py-2 bg-slate-50 border-b border-slate-100 flex items-center justify-between flex-wrap gap-2"> <header className="px-3 py-2 bg-slate-50 border-b flex items-center justify-between flex-wrap gap-2">
<div className="flex items-center gap-2 min-w-0"> <div className="flex items-center gap-2 min-w-0">
<span className="text-[12.5px] font-semibold text-slate-900 tabular-nums">{date}</span> <span className="text-[12.5px] font-semibold text-slate-900 tabular-nums">{date}</span>
<span className="text-[11px] text-slate-500">·</span> <span className="text-[11px] text-slate-500">·</span>
......
...@@ -104,7 +104,7 @@ export function FactsTimeline({ ...@@ -104,7 +104,7 @@ export function FactsTimeline({
// -mx-5 让 sticky 区域延伸到 drawer padding 边缘,bg-white 遮住下方时间轴 // -mx-5 让 sticky 区域延伸到 drawer padding 边缘,bg-white 遮住下方时间轴
return ( return (
<div className="space-y-3"> <div className="space-y-3">
<div className="sticky top-0 z-10 -mx-5 px-5 pt-1 pb-2 bg-white space-y-3 border-b border-slate-100"> <div className="sticky top-0 z-10 -mx-5 px-5 pt-1 pb-2 bg-white space-y-3 border-b">
<StatsSummary facts={facts} /> <StatsSummary facts={facts} />
<div className="flex flex-wrap items-center gap-1.5"> <div className="flex flex-wrap items-center gap-1.5">
<FilterChip label="全部" count={facts.length} active={allOn} onClick={toggleAll} /> <FilterChip label="全部" count={facts.length} active={allOn} onClick={toggleAll} />
...@@ -133,7 +133,7 @@ export function FactsTimeline({ ...@@ -133,7 +133,7 @@ export function FactsTimeline({
'rounded-md border px-2 py-0.5 text-[11px] focus:outline-none focus:ring-1 focus:ring-sky-300', 'rounded-md border px-2 py-0.5 text-[11px] focus:outline-none focus:ring-1 focus:ring-sky-300',
toothFilter toothFilter
? 'border-sky-200 bg-sky-50 text-sky-700 font-medium' ? 'border-sky-200 bg-sky-50 text-sky-700 font-medium'
: 'border-slate-100 bg-white text-slate-600', : 'bg-white text-slate-600',
)} )}
> >
<option value="">全部牙位</option> <option value="">全部牙位</option>
...@@ -219,7 +219,7 @@ function StatsSummary({ facts }: { facts: AdaptedFact[] }) { ...@@ -219,7 +219,7 @@ function StatsSummary({ facts }: { facts: AdaptedFact[] }) {
t ? new Date(t).toLocaleDateString('zh-CN').replace(/\//g, '.') : '—'; t ? new Date(t).toLocaleDateString('zh-CN').replace(/\//g, '.') : '—';
return ( return (
<div className="rounded-lg border border-slate-100 bg-slate-50/60 p-2.5"> <div className="rounded-lg border bg-slate-50/60 p-2.5">
<div className="grid grid-cols-4 gap-3"> <div className="grid grid-cols-4 gap-3">
<StatBlock label="累计净消费"> <StatBlock label="累计净消费">
<div className="text-[13px] font-semibold text-slate-900 tabular-nums"> <div className="text-[13px] font-semibold text-slate-900 tabular-nums">
...@@ -362,7 +362,7 @@ function computeEffectiveTone(fact: AdaptedFact, meta: FactMeta): string { ...@@ -362,7 +362,7 @@ function computeEffectiveTone(fact: AdaptedFact, meta: FactMeta): string {
} }
// 状态 badge 统一样式(slate)— W4 末降噪:语义色集中在时间轴 icon dot,badge 不重复表达 // 状态 badge 统一样式(slate)— W4 末降噪:语义色集中在时间轴 icon dot,badge 不重复表达
const STATUS_BADGE = 'px-1.5 py-px rounded border text-[10.5px] bg-slate-50 text-slate-600 border-slate-100'; const STATUS_BADGE = 'px-1.5 py-px rounded border text-[10.5px] bg-slate-50 text-slate-600';
// 计划治疗专用徽标(sky)— 跟"已完成"(slate)区分:这是还没做的计划,不是已发生的事实 // 计划治疗专用徽标(sky)— 跟"已完成"(slate)区分:这是还没做的计划,不是已发生的事实
const PLAN_BADGE = 'px-1.5 py-px rounded border text-[10.5px] bg-sky-50 text-sky-700 border-sky-200'; const PLAN_BADGE = 'px-1.5 py-px rounded border text-[10.5px] bg-sky-50 text-sky-700 border-sky-200';
...@@ -466,7 +466,7 @@ function FilterChip({ ...@@ -466,7 +466,7 @@ function FilterChip({
onClick={onClick} onClick={onClick}
className={cn( className={cn(
'inline-flex items-center gap-1 px-2 py-0.5 rounded-md text-[11px] border transition-colors', 'inline-flex items-center gap-1 px-2 py-0.5 rounded-md text-[11px] border transition-colors',
active ? cn(T.bg, T.text, T.border, 'font-medium') : 'bg-white text-slate-500 border-slate-100 hover:bg-slate-50', active ? cn(T.bg, T.text, T.border, 'font-medium') : 'bg-white text-slate-500 hover:bg-slate-50',
)} )}
> >
<span>{label}</span> <span>{label}</span>
...@@ -487,7 +487,7 @@ const CHIP_TONE: Record<string, { bg: string; text: string; border: string }> = ...@@ -487,7 +487,7 @@ const CHIP_TONE: Record<string, { bg: string; text: string; border: string }> =
amber: { bg: 'bg-amber-50', text: 'text-amber-800', border: 'border-amber-200' }, amber: { bg: 'bg-amber-50', text: 'text-amber-800', border: 'border-amber-200' },
sky: { bg: 'bg-sky-50', text: 'text-sky-700', border: 'border-sky-200' }, sky: { bg: 'bg-sky-50', text: 'text-sky-700', border: 'border-sky-200' },
indigo: { bg: 'bg-indigo-50', text: 'text-indigo-700', border: 'border-indigo-200' }, indigo: { bg: 'bg-indigo-50', text: 'text-indigo-700', border: 'border-indigo-200' },
slate: { bg: 'bg-slate-100', text: 'text-slate-700', border: 'border-slate-300' }, slate: { bg: 'bg-slate-100', text: 'text-slate-700', border: '' },
}; };
// ───────────────────────────────────────────── // ─────────────────────────────────────────────
...@@ -532,7 +532,7 @@ interface ToneCls { ...@@ -532,7 +532,7 @@ interface ToneCls {
border: string; border: string;
} }
const TONE_FALLBACK: ToneCls = { bg: 'bg-slate-100', text: 'text-slate-700', border: 'border-slate-300' }; const TONE_FALLBACK: ToneCls = { bg: 'bg-slate-100', text: 'text-slate-700', border: '' };
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' },
......
...@@ -190,7 +190,7 @@ export function OutcomeForm({ ...@@ -190,7 +190,7 @@ export function OutcomeForm({
return ( return (
<div className="flex flex-col gap-2.5 h-full min-h-0"> <div className="flex flex-col gap-2.5 h-full min-h-0">
{isTerminal && ( {isTerminal && (
<div className="flex-none rounded bg-slate-100 border border-slate-100 px-2.5 py-1.5 text-[11px] text-slate-500 leading-snug"> <div className="flex-none rounded bg-slate-100 border px-2.5 py-1.5 text-[11px] text-slate-500 leading-snug">
本任务已{plan.status === 'abandoned' ? '放弃' : plan.status === 'superseded' ? '被新版本替代' : '结案'},不可再提交执行;如需重新跟进,等召回重新生成。 本任务已{plan.status === 'abandoned' ? '放弃' : plan.status === 'superseded' ? '被新版本替代' : '结案'},不可再提交执行;如需重新跟进,等召回重新生成。
</div> </div>
)} )}
...@@ -211,7 +211,7 @@ export function OutcomeForm({ ...@@ -211,7 +211,7 @@ export function OutcomeForm({
'px-2 py-1.5 rounded text-[11.5px] border transition-colors inline-flex items-center justify-center gap-1.5', 'px-2 py-1.5 rounded text-[11.5px] border transition-colors inline-flex items-center justify-center gap-1.5',
selected selected
? `${T.bg} ${T.text} border-current font-medium ring-1 ring-current/30` ? `${T.bg} ${T.text} border-current font-medium ring-1 ring-current/30`
: 'bg-white text-slate-700 border-slate-100 hover:border-slate-300 hover:bg-slate-50', : 'bg-white text-slate-700 hover:bg-slate-50',
)} )}
> >
<svg <svg
...@@ -262,7 +262,7 @@ export function OutcomeForm({ ...@@ -262,7 +262,7 @@ export function OutcomeForm({
'px-2 py-1 rounded text-left text-[11px] border transition-colors flex items-center gap-1.5', 'px-2 py-1 rounded text-left text-[11px] border transition-colors flex items-center gap-1.5',
selected selected
? `${T.bg} ${T.text} border-current font-medium ring-1 ring-current/30` ? `${T.bg} ${T.text} border-current font-medium ring-1 ring-current/30`
: 'bg-white text-slate-700 border-slate-100 hover:border-slate-300 hover:bg-slate-50', : 'bg-white text-slate-700 hover:bg-slate-50',
)} )}
> >
<span className={cn('flex-none w-1.5 h-1.5 rounded-full', selected ? T.dot : 'bg-slate-300')} /> <span className={cn('flex-none w-1.5 h-1.5 rounded-full', selected ? T.dot : 'bg-slate-300')} />
...@@ -366,7 +366,7 @@ export function OutcomeForm({ ...@@ -366,7 +366,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-brand-200 focus:border-brand-300" className="flex-1 min-h-[60px] px-2.5 py-1.5 rounded border bg-white text-[12.5px] resize-none focus:outline-none focus:ring-2 focus:ring-brand-200 focus:border-brand-300"
/> />
</div> </div>
......
...@@ -164,7 +164,7 @@ function FeatureCard({ ...@@ -164,7 +164,7 @@ 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-brand-300 bg-brand-50/40 ring-1 ring-brand-200' : 'border-slate-100', focused ? 'border-brand-300 bg-brand-50/40 ring-1 ring-brand-200' : '',
)} )}
> >
{/* 右上角 ? —— 真 button:可 Tab 聚焦,focus 也能唤出说明卡(触屏/键盘可达) */} {/* 右上角 ? —— 真 button:可 Tab 聚焦,focus 也能唤出说明卡(触屏/键盘可达) */}
...@@ -213,7 +213,7 @@ function FeatureCard({ ...@@ -213,7 +213,7 @@ function FeatureCard({
{/* 依据 —— 这个标签是被哪些事实推出来的 */} {/* 依据 —— 这个标签是被哪些事实推出来的 */}
{resolved.length > 0 && ( {resolved.length > 0 && (
<div className="mt-2 border-t border-slate-100 pt-2"> <div className="mt-2 border-t pt-2">
<div className="mb-1 flex items-baseline gap-2"> <div className="mb-1 flex items-baseline gap-2">
<span className="text-[10.5px] text-slate-400">依据 · {resolved.length}</span> <span className="text-[10.5px] text-slate-400">依据 · {resolved.length}</span>
{resolved.length > MAX_EVIDENCE_ROWS && ( {resolved.length > MAX_EVIDENCE_ROWS && (
......
...@@ -36,7 +36,7 @@ export function PersonaFeatureHover({ ...@@ -36,7 +36,7 @@ export function PersonaFeatureHover({
{/* z-[70]:抽屉面板是 z-[60],默认 hovercard z-50 会被抽屉盖住(详情抽屉里 ? hover 不显的根因) */} {/* z-[70]:抽屉面板是 z-[60],默认 hovercard z-50 会被抽屉盖住(详情抽屉里 ? hover 不显的根因) */}
<HoverCardContent align="end" sideOffset={6} className="z-[70] w-80 p-3 text-[11.5px]"> <HoverCardContent align="end" sideOffset={6} className="z-[70] w-80 p-3 text-[11.5px]">
<div className="space-y-2"> <div className="space-y-2">
<div className="flex items-baseline justify-between gap-2 border-b border-slate-100 pb-1.5"> <div className="flex items-baseline justify-between gap-2 border-b pb-1.5">
<span className="text-[13px] font-semibold text-slate-900">{title}</span> <span className="text-[13px] font-semibold text-slate-900">{title}</span>
{display && ( {display && (
<span className="text-right text-[10.5px] text-slate-500">{display.subtitle}</span> <span className="text-right text-[10.5px] text-slate-500">{display.subtitle}</span>
......
...@@ -745,9 +745,9 @@ export function PlanDetailApp({ ...@@ -745,9 +745,9 @@ export function PlanDetailApp({
} }
centerPane={ centerPane={
<main className="relative min-h-0 min-w-0 flex flex-col h-full"> <main className="relative min-h-0 min-w-0 flex flex-col h-full">
<section className="bg-white rounded-lg border border-slate-100 shadow-sm flex flex-col min-h-0 flex-1 overflow-hidden"> <section className="bg-white rounded-lg border shadow-sm flex flex-col min-h-0 flex-1 overflow-hidden">
{/* 窄屏 flex-wrap 自然换行,gap-y 给行间距 */} {/* 窄屏 flex-wrap 自然换行,gap-y 给行间距 */}
<header className="flex-none px-3 sm:px-4 py-2.5 border-b border-slate-100"> <header className="flex-none px-3 sm:px-4 py-2.5 border-b">
{/* 第一行:标题 + 标签 ←→ 控件(模式切换 / 重新生成 / AI 时间戳)*/} {/* 第一行:标题 + 标签 ←→ 控件(模式切换 / 重新生成 / AI 时间戳)*/}
<div className="flex flex-wrap items-center justify-between gap-x-2 gap-y-2"> <div className="flex flex-wrap items-center justify-between gap-x-2 gap-y-2">
<div className="min-w-0"> <div className="min-w-0">
...@@ -930,8 +930,8 @@ export function PlanDetailApp({ ...@@ -930,8 +930,8 @@ export function PlanDetailApp({
className="min-h-0 flex flex-col gap-2.5 overflow-hidden h-full" className="min-h-0 flex flex-col gap-2.5 overflow-hidden h-full"
onMouseEnter={hintRecallFeedback} onMouseEnter={hintRecallFeedback}
> >
<section className="bg-white rounded-lg border border-slate-100 shadow-sm flex flex-col min-h-0 flex-1 overflow-hidden"> <section className="bg-white rounded-lg border shadow-sm flex flex-col min-h-0 flex-1 overflow-hidden">
<header className="flex-none px-4 py-2.5 border-b border-slate-100"> <header className="flex-none px-4 py-2.5 border-b">
{/* 「回访」按钮已上移到顶栏右上角动作区(主按钮) */} {/* 「回访」按钮已上移到顶栏右上角动作区(主按钮) */}
<h2 className="text-[14px] font-semibold text-slate-900 leading-tight">通话结果</h2> <h2 className="text-[14px] font-semibold text-slate-900 leading-tight">通话结果</h2>
<p className="text-[10.5px] text-slate-500 mt-0.5">提交后本次任务自动归档</p> <p className="text-[10.5px] text-slate-500 mt-0.5">提交后本次任务自动归档</p>
...@@ -1100,7 +1100,7 @@ function RecallFeedbackControl({ ...@@ -1100,7 +1100,7 @@ function RecallFeedbackControl({
return ( return (
<> <>
<div <div
className="hidden sm:flex flex-none items-center gap-0.5 rounded-md border border-slate-100 px-1 py-0.5" className="hidden sm:flex flex-none items-center gap-0.5 rounded-md border px-1 py-0.5"
title="召回反馈:这条召回准不准?" title="召回反馈:这条召回准不准?"
> >
<span className="hidden lg:inline px-1 text-[10.5px] text-slate-400">召回反馈</span> <span className="hidden lg:inline px-1 text-[10.5px] text-slate-400">召回反馈</span>
...@@ -1154,7 +1154,7 @@ function RecallFeedbackControl({ ...@@ -1154,7 +1154,7 @@ function RecallFeedbackControl({
type="button" type="button"
title={f.hint} title={f.hint}
onClick={() => quickInsert(f.labelZh)} onClick={() => quickInsert(f.labelZh)}
className="px-2 py-1 rounded-full text-[12px] border border-slate-100 bg-white text-slate-600 hover:border-rose-300 hover:bg-rose-50 hover:text-rose-700 transition-colors" className="px-2 py-1 rounded-full text-[12px] border bg-white text-slate-600 hover:border-rose-300 hover:bg-rose-50 hover:text-rose-700 transition-colors"
> >
{f.labelZh} {f.labelZh}
</button> </button>
...@@ -1165,14 +1165,14 @@ function RecallFeedbackControl({ ...@@ -1165,14 +1165,14 @@ function RecallFeedbackControl({
onChange={(e) => setNote(e.target.value)} onChange={(e) => setNote(e.target.value)}
placeholder="补充说明(选填)…" placeholder="补充说明(选填)…"
rows={3} rows={3}
className="w-full px-2.5 py-2 rounded border border-slate-100 bg-white text-[13px] resize-none focus:outline-none focus:ring-2 focus:ring-rose-200 focus:border-rose-300" className="w-full px-2.5 py-2 rounded border bg-white text-[13px] resize-none focus:outline-none focus:ring-2 focus:ring-rose-200 focus:border-rose-300"
/> />
</div> </div>
<DialogFooter> <DialogFooter>
<button <button
type="button" type="button"
onClick={() => setOpen(false)} onClick={() => setOpen(false)}
className="px-3 py-1.5 rounded-md border border-slate-100 text-[13px] text-slate-600 hover:bg-slate-50" className="px-3 py-1.5 rounded-md border text-[13px] text-slate-600 hover:bg-slate-50"
> >
取消 取消
</button> </button>
...@@ -1269,7 +1269,7 @@ function TopBar({ ...@@ -1269,7 +1269,7 @@ function TopBar({
}; };
return ( return (
<header className="flex flex-none items-center justify-between gap-2 border-b border-slate-100 bg-white px-3 py-2 sm:px-5 sm:py-3 sm:gap-3"> <header className="flex flex-none items-center justify-between gap-2 border-b bg-white px-3 py-2 sm:px-5 sm:py-3 sm:gap-3">
<div className="flex min-w-0 items-center gap-2 sm:gap-3"> <div className="flex min-w-0 items-center gap-2 sm:gap-3">
{/* PAC 品牌 + 面包屑标题 —— 嵌入宿主时隐藏(宿主自有品牌与导航) */} {/* PAC 品牌 + 面包屑标题 —— 嵌入宿主时隐藏(宿主自有品牌与导航) */}
{!embedded && ( {!embedded && (
...@@ -1354,8 +1354,8 @@ function TopBar({ ...@@ -1354,8 +1354,8 @@ function TopBar({
className={cn( className={cn(
'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 bg-slate-50 text-slate-400'
: 'border-slate-100 bg-white text-slate-700 hover:border-brand-300 hover:bg-brand-50 hover:text-brand-700', : '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')} />
...@@ -1370,7 +1370,7 @@ function TopBar({ ...@@ -1370,7 +1370,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-brand-300 hover:bg-brand-50 hover:text-brand-700" className="inline-flex items-center gap-1.5 rounded-md border 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>
...@@ -1382,7 +1382,7 @@ function TopBar({ ...@@ -1382,7 +1382,7 @@ function TopBar({
<button <button
type="button" type="button"
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-brand-300 hover:bg-brand-50 hover:text-brand-700" className="inline-flex items-center gap-1.5 rounded-md border 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>
...@@ -1453,7 +1453,7 @@ function AIDisclaimerFooter({ ...@@ -1453,7 +1453,7 @@ function AIDisclaimerFooter({
onFeedback(v); onFeedback(v);
}; };
return ( return (
<div className="flex-none border-t border-slate-100 px-3 sm:px-4 py-2 flex items-center justify-between gap-2 sm:gap-3"> <div className="flex-none border-t px-3 sm:px-4 py-2 flex items-center justify-between gap-2 sm:gap-3">
<div className="min-w-0 text-[10.5px] text-slate-500 flex items-center gap-1.5 leading-tight"> <div className="min-w-0 text-[10.5px] text-slate-500 flex items-center gap-1.5 leading-tight">
<svg viewBox="0 0 24 24" className="w-3 h-3 flex-none" fill="none" stroke="currentColor" strokeWidth="1.8"> <svg viewBox="0 0 24 24" className="w-3 h-3 flex-none" fill="none" stroke="currentColor" strokeWidth="1.8">
<circle cx="12" cy="12" r="10" /> <circle cx="12" cy="12" r="10" />
...@@ -1648,7 +1648,7 @@ function IdentityCard({ ...@@ -1648,7 +1648,7 @@ function IdentityCard({
medicalRecordNumber: patient.medicalRecordNumber, medicalRecordNumber: patient.medicalRecordNumber,
}); });
return ( return (
<section className="bg-white rounded-lg border border-slate-100 shadow-sm flex-none"> <section className="bg-white rounded-lg border shadow-sm flex-none">
<div className="p-3 flex items-start gap-2.5"> <div className="p-3 flex items-start gap-2.5">
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
<div className="flex items-start justify-between gap-2"> <div className="flex items-start justify-between gap-2">
...@@ -2041,7 +2041,7 @@ function RecallBriefLine({ ...@@ -2041,7 +2041,7 @@ function RecallBriefLine({
</button> </button>
{canExpand && expanded && ( {canExpand && expanded && (
// 展开 = 以前的「为什么召回」结构化原因(RecallReasonLine,样式与以前一致) // 展开 = 以前的「为什么召回」结构化原因(RecallReasonLine,样式与以前一致)
<div className="mt-1.5 rounded-md border border-slate-100 bg-white px-2.5 py-2"> <div className="mt-1.5 rounded-md border bg-white px-2.5 py-2">
<div className="text-[12.5px] text-slate-700 leading-relaxed"> <div className="text-[12.5px] text-slate-700 leading-relaxed">
<RecallReasonLine visibleReasons={visibleReasons} /> <RecallReasonLine visibleReasons={visibleReasons} />
</div> </div>
...@@ -2508,7 +2508,7 @@ function SuggestionCard({ ...@@ -2508,7 +2508,7 @@ function SuggestionCard({
</div> </div>
)} )}
</div> </div>
<div className="mt-2 pt-2 border-t border-slate-100 flex items-center justify-between"> <div className="mt-2 pt-2 border-t flex items-center justify-between">
<span className="text-[10.5px] text-slate-500"> <span className="text-[10.5px] text-slate-500">
触达限额{' '} 触达限额{' '}
<strong className="text-slate-700 tabular-nums"> <strong className="text-slate-700 tabular-nums">
...@@ -2851,11 +2851,11 @@ function RegenBtn({ ...@@ -2851,11 +2851,11 @@ function RegenBtn({
const selectorCls = cn( const selectorCls = cn(
'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 cursor-not-allowed'
: 'text-slate-500 border-slate-100 hover:text-brand-700 hover:bg-brand-50', : 'text-slate-500 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 overflow-hidden">
{/* 档位选择(只选,不触发);企微只有深度档 → 不显示 */} {/* 档位选择(只选,不触发);企微只有深度档 → 不显示 */}
{!hideTier && ( {!hideTier && (
<DropdownMenu> <DropdownMenu>
......
...@@ -100,7 +100,7 @@ export function QuickLogPopover({ ...@@ -100,7 +100,7 @@ export function QuickLogPopover({
value={notes} value={notes}
onChange={(e) => setNotes(e.target.value)} onChange={(e) => setNotes(e.target.value)}
placeholder="约的什么、什么时候" placeholder="约的什么、什么时候"
className="w-full resize-none rounded-md border border-slate-200 px-2 py-1.5 text-[12.5px] outline-none placeholder:text-slate-300 focus:border-brand-400" className="w-full resize-none rounded-md border px-2 py-1.5 text-[12.5px] outline-none placeholder:text-slate-300 focus:border-brand-400"
/> />
) : ( ) : (
<div className="flex items-center gap-1.5"> <div className="flex items-center gap-1.5">
...@@ -109,13 +109,13 @@ export function QuickLogPopover({ ...@@ -109,13 +109,13 @@ export function QuickLogPopover({
type="date" type="date"
value={date} value={date}
onChange={(e) => setDate(e.target.value)} onChange={(e) => setDate(e.target.value)}
className="min-w-0 flex-1 rounded-md border border-slate-200 px-2 py-1.5 text-[12.5px] outline-none focus:border-brand-400" className="min-w-0 flex-1 rounded-md border px-2 py-1.5 text-[12.5px] outline-none focus:border-brand-400"
/> />
<input <input
type="time" type="time"
value={time} value={time}
onChange={(e) => setTime(e.target.value)} onChange={(e) => setTime(e.target.value)}
className="w-[86px] flex-none rounded-md border border-slate-200 px-2 py-1.5 text-[12.5px] outline-none focus:border-brand-400" className="w-[86px] flex-none rounded-md border px-2 py-1.5 text-[12.5px] outline-none focus:border-brand-400"
/> />
</div> </div>
)} )}
......
...@@ -32,7 +32,7 @@ export function ScriptDeepProcess({ steps }: { steps: DeepStep[] }) { ...@@ -32,7 +32,7 @@ export function ScriptDeepProcess({ steps }: { steps: DeepStep[] }) {
const runningStep = steps.find((s) => s.status === 'running')?.step ?? null; const runningStep = steps.find((s) => s.status === 'running')?.step ?? null;
return ( return (
<div className="mb-3 rounded-lg border border-slate-100 bg-slate-50 p-2.5"> <div className="mb-3 rounded-lg border bg-slate-50 p-2.5">
<ol className="space-y-1.5"> <ol className="space-y-1.5">
{steps.map((s) => { {steps.map((s) => {
const hasDetail = stepHasDetail(s); const hasDetail = stepHasDetail(s);
...@@ -146,7 +146,7 @@ function IssueList({ ...@@ -146,7 +146,7 @@ function IssueList({
return ( return (
<ul className="space-y-1"> <ul className="space-y-1">
{issues.map((it, i) => ( {issues.map((it, i) => (
<li key={i} className="rounded border border-slate-100 bg-white px-2 py-1 text-[11px] leading-snug"> <li key={i} className="rounded border bg-white px-2 py-1 text-[11px] leading-snug">
<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 && (
......
...@@ -21,7 +21,7 @@ export function ScriptMarkdown({ sections, streaming = false }: { sections: Scri ...@@ -21,7 +21,7 @@ export function ScriptMarkdown({ sections, streaming = false }: { sections: Scri
{sections.map((sec, idx) => { {sections.map((sec, idx) => {
const isOpen = !collapsed[sec.id]; const isOpen = !collapsed[sec.id];
return ( return (
<div key={sec.id} className="rounded-md border border-slate-100 bg-white overflow-hidden"> <div key={sec.id} className="rounded-md border bg-white overflow-hidden">
<button <button
onClick={() => setCollapsed({ ...collapsed, [sec.id]: !collapsed[sec.id] })} onClick={() => setCollapsed({ ...collapsed, [sec.id]: !collapsed[sec.id] })}
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"
...@@ -45,7 +45,7 @@ export function ScriptMarkdown({ sections, streaming = false }: { sections: Scri ...@@ -45,7 +45,7 @@ export function ScriptMarkdown({ sections, streaming = false }: { sections: Scri
</svg> </svg>
</button> </button>
{isOpen && ( {isOpen && (
<div className="px-3 pb-3 pt-1 border-t border-slate-100"> <div className="px-3 pb-3 pt-1 border-t">
<MD text={sec.markdown} /> <MD text={sec.markdown} />
</div> </div>
)} )}
...@@ -63,7 +63,7 @@ export function ScriptStepCards({ sections, streaming = false }: { sections: Scr ...@@ -63,7 +63,7 @@ export function ScriptStepCards({ sections, streaming = false }: { sections: Scr
return ( return (
<div className="grid grid-cols-1 md:grid-cols-2 gap-3"> <div className="grid grid-cols-1 md:grid-cols-2 gap-3">
{sections.map((sec, i) => ( {sections.map((sec, i) => (
<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 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-brand-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">
...@@ -93,7 +93,7 @@ export function ScriptCopilot({ sections, streaming = false }: { sections: Scrip ...@@ -93,7 +93,7 @@ export function ScriptCopilot({ sections, streaming = false }: { sections: Scrip
return ( return (
<div className="flex flex-col gap-3"> <div className="flex flex-col gap-3">
{/* 进度条 */} {/* 进度条 */}
<div className="flex items-center gap-1.5 rounded-md bg-slate-50 border border-slate-100 p-1.5"> <div className="flex items-center gap-1.5 rounded-md bg-slate-50 border p-1.5">
{sections.map((s, i) => ( {sections.map((s, i) => (
<button <button
key={s.id} key={s.id}
......
...@@ -153,7 +153,7 @@ export function SidebarCard({ ...@@ -153,7 +153,7 @@ export function SidebarCard({
return ( return (
<section <section
className={cn( className={cn(
'bg-white rounded-lg border border-slate-100 shadow-sm flex flex-col min-h-0 flex-none', 'bg-white rounded-lg border shadow-sm flex flex-col min-h-0 flex-none',
className, className,
)} )}
> >
...@@ -179,7 +179,7 @@ export function SidebarCard({ ...@@ -179,7 +179,7 @@ export function SidebarCard({
</button> </button>
{action && <div className="flex-none">{action}</div>} {action && <div className="flex-none">{action}</div>}
</header> </header>
{open && <div className={cn(padding, 'border-t border-slate-100 overflow-y-auto min-h-0')}>{children}</div>} {open && <div className={cn(padding, 'border-t overflow-y-auto min-h-0')}>{children}</div>}
</section> </section>
); );
} }
......
...@@ -131,17 +131,17 @@ export function ToothTimeline({ facts }: { facts: AdaptedFact[] }) { ...@@ -131,17 +131,17 @@ export function ToothTimeline({ facts }: { facts: AdaptedFact[] }) {
); );
const isWhole = k.startsWith('全口'); const isWhole = k.startsWith('全口');
return ( return (
<div key={k} className="rounded-lg border border-slate-100 overflow-hidden"> <div key={k} className="rounded-lg border overflow-hidden">
<div <div
className={cn( className={cn(
'flex items-center gap-2 px-2.5 py-1.5 text-[12px] font-semibold border-b border-slate-100', 'flex items-center gap-2 px-2.5 py-1.5 text-[12px] font-semibold border-b',
isWhole ? 'bg-amber-50 text-amber-800' : 'bg-slate-50 text-slate-700', isWhole ? 'bg-amber-50 text-amber-800' : 'bg-slate-50 text-slate-700',
)} )}
> >
<span className="tabular-nums">{isWhole ? k : `牙位 ${k}`}</span> <span className="tabular-nums">{isWhole ? k : `牙位 ${k}`}</span>
<span className="ml-auto text-[10px] font-normal text-slate-400">{rows.length}</span> <span className="ml-auto text-[10px] font-normal text-slate-400">{rows.length}</span>
</div> </div>
<div className="divide-y divide-slate-50"> <div className="divide-y">
{rows.map((f) => ( {rows.map((f) => (
<ToothFactRow key={`${k}-${f.id}`} fact={f} /> <ToothFactRow key={`${k}-${f.id}`} fact={f} />
))} ))}
......
...@@ -219,9 +219,9 @@ export function PatientPickerRail({ ...@@ -219,9 +219,9 @@ export function PatientPickerRail({
}); });
return ( return (
<aside className="flex h-full w-[320px] flex-none flex-col border-r border-slate-100 bg-white"> <aside className="flex h-full w-[320px] flex-none flex-col border-r bg-white">
{/* view tabs */} {/* view tabs */}
<div className="flex flex-none gap-1 border-b border-slate-100 px-2 pt-2"> <div className="flex flex-none gap-1 border-b px-2 pt-2">
{VIEW_TABS.map((t) => ( {VIEW_TABS.map((t) => (
<button <button
key={t.v} key={t.v}
...@@ -251,7 +251,7 @@ export function PatientPickerRail({ ...@@ -251,7 +251,7 @@ export function PatientPickerRail({
⚠️ 常驻的「把这 N 人移交助手」横幅同样已撤 —— 入口收敛到 tab 行的「分配」一个。 */} ⚠️ 常驻的「把这 N 人移交助手」横幅同样已撤 —— 入口收敛到 tab 行的「分配」一个。 */}
{/* 搜索 + 工具行 */} {/* 搜索 + 工具行 */}
<div className="flex-none space-y-1.5 border-b border-slate-100 p-2"> <div className="flex-none space-y-1.5 border-b p-2">
<div className="relative"> <div className="relative">
<Search className="absolute left-2 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-slate-400" /> <Search className="absolute left-2 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-slate-400" />
<input <input
...@@ -275,7 +275,7 @@ export function PatientPickerRail({ ...@@ -275,7 +275,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-brand-300 focus:bg-white" className="h-7 w-full rounded-md border bg-slate-50 pl-7 pr-2 text-[12px] outline-none placeholder:text-slate-400 focus:border-brand-300 focus:bg-white"
/> />
</div> </div>
{/* ⚠️ 外层条件要跟着两个开关走 —— 只判其中一个,另一个放开时整排还是不显示。 {/* ⚠️ 外层条件要跟着两个开关走 —— 只判其中一个,另一个放开时整排还是不显示。
...@@ -293,7 +293,7 @@ export function PatientPickerRail({ ...@@ -293,7 +293,7 @@ export function PatientPickerRail({
'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-brand-300 bg-brand-50 font-medium text-brand-700' ? 'border-brand-300 bg-brand-50 font-medium text-brand-700'
: 'border-slate-100 bg-white text-slate-500 hover:bg-slate-50', : 'bg-white text-slate-500 hover:bg-slate-50',
)} )}
> >
<PhoneCall className="h-3 w-3" /> <PhoneCall className="h-3 w-3" />
...@@ -435,7 +435,7 @@ function PersonaTagFilter({ ...@@ -435,7 +435,7 @@ function PersonaTagFilter({
'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-brand-300 bg-brand-50 font-medium text-brand-700' ? 'border-brand-300 bg-brand-50 font-medium text-brand-700'
: 'border-slate-100 bg-white text-slate-500 hover:bg-slate-50', : 'bg-white text-slate-500 hover:bg-slate-50',
)} )}
> >
<Filter className="h-3 w-3" /> <Filter className="h-3 w-3" />
...@@ -491,7 +491,7 @@ function PersonaTagFilter({ ...@@ -491,7 +491,7 @@ function PersonaTagFilter({
'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-brand-300 bg-brand-50 font-medium text-brand-700' ? 'border-brand-300 bg-brand-50 font-medium text-brand-700'
: 'border-slate-100 bg-white text-slate-600 hover:border-brand-200 hover:bg-brand-50/40', : 'bg-white text-slate-600 hover:border-brand-200 hover:bg-brand-50/40',
)} )}
> >
{o.zh} {o.zh}
...@@ -504,7 +504,7 @@ function PersonaTagFilter({ ...@@ -504,7 +504,7 @@ function PersonaTagFilter({
))} ))}
</div> </div>
{/* 定高:hover 进出不抖动 */} {/* 定高:hover 进出不抖动 */}
<div className="mt-1 min-h-[30px] border-t border-slate-100 px-1 pt-1.5 text-[10.5px] leading-relaxed"> <div className="mt-1 min-h-[30px] border-t px-1 pt-1.5 text-[10.5px] leading-relaxed">
{hint ? ( {hint ? (
<span className="text-slate-600">{hint}</span> <span className="text-slate-600">{hint}</span>
) : ( ) : (
...@@ -542,7 +542,7 @@ function RailClinicFilter({ ...@@ -542,7 +542,7 @@ function RailClinicFilter({
'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-brand-300 bg-brand-50 font-medium text-brand-700' ? 'border-brand-300 bg-brand-50 font-medium text-brand-700'
: 'border-slate-100 bg-white text-slate-500 hover:bg-slate-50', : 'bg-white text-slate-500 hover:bg-slate-50',
)} )}
> >
<span className="truncate"> <span className="truncate">
...@@ -554,7 +554,7 @@ function RailClinicFilter({ ...@@ -554,7 +554,7 @@ function RailClinicFilter({
</button> </button>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent align="start" className="max-h-72 w-56 overflow-y-auto p-1.5"> <PopoverContent align="start" className="max-h-72 w-56 overflow-y-auto p-1.5">
<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 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-brand-600 hover:underline"> <button type="button" onClick={() => onChange([])} className="text-[10.5px] text-brand-600 hover:underline">
...@@ -731,7 +731,7 @@ function DoctorPicker({ ...@@ -731,7 +731,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-brand-300" className="w-full rounded border 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">
...@@ -743,7 +743,7 @@ function DoctorPicker({ ...@@ -743,7 +743,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-brand-200 hover:bg-brand-50/40" className="rounded-full border 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>
...@@ -808,7 +808,7 @@ function PatientRow({ ...@@ -808,7 +808,7 @@ function PatientRow({
onClick={onClick} onClick={onClick}
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 px-3 py-2 text-left transition-colors',
active ? 'bg-brand-50/70 ring-1 ring-inset ring-brand-200' : 'hover:bg-slate-50', active ? 'bg-brand-50/70 ring-1 ring-inset ring-brand-200' : 'hover:bg-slate-50',
)} )}
> >
......
...@@ -87,7 +87,7 @@ export function ReleaseReasonDialog({ ...@@ -87,7 +87,7 @@ export function ReleaseReasonDialog({
'flex w-full flex-col items-start gap-0.5 rounded-md border px-2.5 py-1.5 text-left transition-colors', 'flex w-full flex-col items-start gap-0.5 rounded-md border px-2.5 py-1.5 text-left transition-colors',
on on
? 'border-brand-400 bg-brand-50/70' ? 'border-brand-400 bg-brand-50/70'
: 'border-slate-100 bg-white hover:border-brand-200 hover:bg-brand-50/30', : 'bg-white hover:border-brand-200 hover:bg-brand-50/30',
)} )}
> >
<span <span
...@@ -114,7 +114,7 @@ export function ReleaseReasonDialog({ ...@@ -114,7 +114,7 @@ export function ReleaseReasonDialog({
value={note} value={note}
onChange={(e) => setNote(e.target.value)} onChange={(e) => setNote(e.target.value)}
placeholder="写清楚具体情况(必填)" placeholder="写清楚具体情况(必填)"
className="w-full resize-none rounded-md border border-slate-200 px-2 py-1.5 text-[12.5px] outline-none placeholder:text-slate-300 focus:border-brand-400" className="w-full resize-none rounded-md border px-2 py-1.5 text-[12.5px] outline-none placeholder:text-slate-300 focus:border-brand-400"
/> />
</div> </div>
)} )}
......
...@@ -111,7 +111,7 @@ function PriorityBreakdownTable({ ...@@ -111,7 +111,7 @@ function PriorityBreakdownTable({
function Header({ disp }: { disp: string }) { function Header({ disp }: { disp: string }) {
return ( return (
<div className="flex items-baseline justify-between border-b border-slate-100 pb-1.5"> <div className="flex items-baseline justify-between border-b pb-1.5">
<span className="text-[13px] font-semibold text-slate-900">优先级 {disp} / 10</span> <span className="text-[13px] font-semibold text-slate-900">优先级 {disp} / 10</span>
<span className="text-[10.5px] text-slate-500">急迫 × 价值 × 意愿</span> <span className="text-[10.5px] text-slate-500">急迫 × 价值 × 意愿</span>
</div> </div>
...@@ -147,7 +147,7 @@ function Row({ ...@@ -147,7 +147,7 @@ function Row({
function Subtotal({ label, value, bold }: { label: string; value: string; bold?: boolean }) { function Subtotal({ label, value, bold }: { label: string; value: string; bold?: boolean }) {
return ( return (
<tr> <tr>
<td colSpan={3} className="border-t border-slate-100 pt-1 pb-0.5"> <td colSpan={3} className="border-t pt-1 pb-0.5">
<span className="flex justify-between"> <span className="flex justify-between">
<span className={`text-slate-700 ${bold ? 'font-semibold' : ''}`}>{label}</span> <span className={`text-slate-700 ${bold ? 'font-semibold' : ''}`}>{label}</span>
<span <span
......
...@@ -49,7 +49,7 @@ export function SessionExpired() { ...@@ -49,7 +49,7 @@ export function SessionExpired() {
type="button" type="button"
onClick={retry} onClick={retry}
disabled={retrying} disabled={retrying}
className="inline-flex items-center gap-1.5 rounded-md border border-slate-200 bg-white px-3 py-1.5 text-[12.5px] text-slate-600 transition-colors hover:bg-slate-50 disabled:opacity-50" className="inline-flex items-center gap-1.5 rounded-md border bg-white px-3 py-1.5 text-[12.5px] text-slate-600 transition-colors hover:bg-slate-50 disabled:opacity-50"
> >
<RefreshCw className={`h-3.5 w-3.5 ${retrying ? 'animate-spin' : ''}`} /> <RefreshCw className={`h-3.5 w-3.5 ${retrying ? 'animate-spin' : ''}`} />
{retrying ? '重试中…' : '重试'} {retrying ? '重试中…' : '重试'}
......
...@@ -186,8 +186,8 @@ export function BatchTracking({ clinicId }: { clinicId: string | null }) { ...@@ -186,8 +186,8 @@ export function BatchTracking({ clinicId }: { clinicId: string | null }) {
}, [shown, running, scope]); }, [shown, running, scope]);
return ( return (
<div className="relative flex min-h-0 w-full flex-col overflow-hidden rounded-lg border border-slate-100 bg-white"> <div className="relative flex min-h-0 w-full flex-col overflow-hidden rounded-lg border bg-white">
<div className="flex flex-none flex-wrap items-center gap-x-2.5 gap-y-1 border-b border-slate-50 px-3.5 py-2.5"> <div className="flex flex-none flex-wrap items-center gap-x-2.5 gap-y-1 border-b px-3.5 py-2.5">
<span className="text-[13.5px] font-semibold text-slate-900">我分的批次</span> <span className="text-[13.5px] font-semibold text-slate-900">我分的批次</span>
<span className="text-[11px] text-slate-400">点一行看这批具体怎么样</span> <span className="text-[11px] text-slate-400">点一行看这批具体怎么样</span>
<span className="ml-auto inline-flex rounded-lg bg-slate-100 p-0.5"> <span className="ml-auto inline-flex rounded-lg bg-slate-100 p-0.5">
...@@ -290,14 +290,12 @@ type BatchGroupData = { key: string; note: string; past: boolean; rows: Assignme ...@@ -290,14 +290,12 @@ type BatchGroupData = { key: string; note: string; past: boolean; rows: Assignme
* 而回顾恰恰就是在比这几列。 * 而回顾恰恰就是在比这几列。
* ⛔ 也别只靠那条竖轨 —— 一条 2px 的线滚起来看不见,底色才是扫视时抓得到的。 * ⛔ 也别只靠那条竖轨 —— 一条 2px 的线滚起来看不见,底色才是扫视时抓得到的。
* *
* 🔴 竖轨的颜色**必须走 inline style**(`rail` 是个 CSS 颜色值不是类名): * ⚠️ `past` 的竖轨是**透明但仍占 2px**(`border-l-transparent`)—— 少了它,历史行会
* globals.css 里那条 `* { border-color: hsl(var(--border)) }` **不在任何 @layer 里**, * 比在跑的行少 2px 左内缩,两组的首列对不齐。⛔ 别图省事删成没有 border-l。
* 压过 Tailwind 的 `border-l-brand-400` —— 写类名不报错、也不生效,
* 算出来是那个灰(2026-08-11 实测:整栏竖轨全是灰的,肉眼以为"没做")。
*/ */
const TONE = { const TONE = {
live: { live: {
rail: 'var(--color-brand-400)', rail: 'border-l-2 border-l-brand-400',
row: 'bg-white hover:bg-slate-50', row: 'bg-white hover:bg-slate-50',
when: 'text-slate-500', when: 'text-slate-500',
cohort: 'text-slate-900', cohort: 'text-slate-900',
...@@ -314,7 +312,7 @@ const TONE = { ...@@ -314,7 +312,7 @@ const TONE = {
headKey: 'text-brand-800', headKey: 'text-brand-800',
}, },
past: { past: {
rail: 'transparent', rail: 'border-l-2 border-l-transparent',
row: 'bg-slate-50/80 hover:bg-slate-100', row: 'bg-slate-50/80 hover:bg-slate-100',
when: 'text-slate-400', when: 'text-slate-400',
cohort: 'text-slate-500', cohort: 'text-slate-500',
...@@ -343,12 +341,10 @@ function BatchGroup({ ...@@ -343,12 +341,10 @@ function BatchGroup({
onOpen: (id: string) => void; onOpen: (id: string) => void;
}) { }) {
const t = group.past ? TONE.past : TONE.live; const t = group.past ? TONE.past : TONE.live;
/// 竖轨 —— ⛔ 只能 inline(见 TONE 上面那段:全局 `*` 规则把 border 颜色类吃掉了)
const rail = { borderLeft: `2px solid ${t.rail}` };
return ( return (
<> <>
<tr> <tr>
<td colSpan={7} style={rail} className={cn('border-t px-3.5 py-1.5', t.head)}> <td colSpan={7} className={cn('border-t px-3.5 py-1.5', t.rail, t.head)}>
<span className={cn('text-[11px] font-semibold', t.headKey)}>{group.key}</span> <span className={cn('text-[11px] font-semibold', t.headKey)}>{group.key}</span>
<span className="ml-1.5 text-[10.5px] text-slate-400">{group.note}</span> <span className="ml-1.5 text-[10.5px] text-slate-400">{group.note}</span>
</td> </td>
...@@ -362,14 +358,14 @@ function BatchGroup({ ...@@ -362,14 +358,14 @@ function BatchGroup({
key={b.id} key={b.id}
onClick={() => onOpen(b.id)} onClick={() => onOpen(b.id)}
className={cn( className={cn(
'cursor-pointer border-t border-slate-50', 'cursor-pointer border-t',
t.row, t.row,
// ⚠️ 选中态压在两套配色**之上** —— 抽屉开着的那一行必须一眼看出来, // ⚠️ 选中态压在两套配色**之上** —— 抽屉开着的那一行必须一眼看出来,
// ⛔ 不能被历史行的灰底吃掉 // ⛔ 不能被历史行的灰底吃掉
openId === b.id && 'bg-brand-50 hover:bg-brand-50', openId === b.id && 'bg-brand-50 hover:bg-brand-50',
)} )}
> >
<td style={rail} className="px-3.5 py-2"> <td className={cn(t.rail, 'px-3.5 py-2')}>
<div className="flex items-baseline gap-2 whitespace-nowrap"> <div className="flex items-baseline gap-2 whitespace-nowrap">
<span className={cn('nums text-[11.5px]', t.when)}>{when}</span> <span className={cn('nums text-[11.5px]', t.when)}>{when}</span>
<span className={cn('font-medium', t.cohort)}>{cohort}</span> <span className={cn('font-medium', t.cohort)}>{cohort}</span>
...@@ -441,8 +437,8 @@ function BatchDrawer({ id, onClose }: { id: string; onClose: () => void }) { ...@@ -441,8 +437,8 @@ function BatchDrawer({ id, onClose }: { id: string; onClose: () => void }) {
className="absolute inset-x-0 bottom-0 top-[3.25rem] z-20 bg-slate-900/10" className="absolute inset-x-0 bottom-0 top-[3.25rem] z-20 bg-slate-900/10"
onClick={onClose} onClick={onClose}
/> />
<aside className="absolute bottom-0 right-0 top-[3.25rem] z-20 flex w-[26rem] max-w-full flex-col overflow-hidden border-l border-slate-100 bg-white shadow-[-12px_0_24px_-6px_rgba(15,23,42,0.10)]"> <aside className="absolute bottom-0 right-0 top-[3.25rem] z-20 flex w-[26rem] max-w-full flex-col overflow-hidden border-l bg-white shadow-[-12px_0_24px_-6px_rgba(15,23,42,0.10)]">
<div className="flex flex-none items-start gap-2.5 border-b border-slate-50 bg-brand-50 px-3.5 py-3"> <div className="flex flex-none items-start gap-2.5 border-b bg-brand-50 px-3.5 py-3">
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
<div className="truncate text-[13px] font-semibold text-slate-900"> <div className="truncate text-[13px] font-semibold text-slate-900">
{d?.label ?? '加载中…'} {d?.label ?? '加载中…'}
...@@ -494,7 +490,7 @@ function BatchDrawer({ id, onClose }: { id: string; onClose: () => void }) { ...@@ -494,7 +490,7 @@ function BatchDrawer({ id, onClose }: { id: string; onClose: () => void }) {
</thead> </thead>
<tbody> <tbody>
{d.agentStats.map((a) => ( {d.agentStats.map((a) => (
<tr key={a.userId} className="border-t border-slate-50"> <tr key={a.userId} className="border-t">
<td className="py-1.5">{a.name ?? a.userId}</td> <td className="py-1.5">{a.name ?? a.userId}</td>
<td className="nums px-1.5 py-1.5 text-right">{a.planned}</td> <td className="nums px-1.5 py-1.5 text-right">{a.planned}</td>
<td className="nums px-1.5 py-1.5 text-right text-emerald-700">{a.done}</td> <td className="nums px-1.5 py-1.5 text-right text-emerald-700">{a.done}</td>
......
...@@ -78,8 +78,8 @@ export function NewBatchPanel({ clinicId }: { clinicId: string | null }) { ...@@ -78,8 +78,8 @@ export function NewBatchPanel({ clinicId }: { clinicId: string | null }) {
const pick = useCallback((c: Picked) => handoff(c), []); const pick = useCallback((c: Picked) => handoff(c), []);
return ( return (
<div className="flex w-full flex-col overflow-hidden rounded-lg border border-slate-100 bg-white"> <div className="flex w-full flex-col overflow-hidden rounded-lg border bg-white">
<div className="flex flex-none flex-wrap items-baseline gap-x-2 gap-y-1 border-b border-slate-50 px-3.5 py-2.5"> <div className="flex flex-none flex-wrap items-baseline gap-x-2 gap-y-1 border-b px-3.5 py-2.5">
<span className="text-[13.5px] font-semibold text-slate-900">分一批新的</span> <span className="text-[13.5px] font-semibold text-slate-900">分一批新的</span>
<span className="text-[11px] text-slate-400"> <span className="text-[11px] text-slate-400">
点一格 = 把这批人交给助手,助手出确认单 点一格 = 把这批人交给助手,助手出确认单
......
...@@ -62,7 +62,7 @@ export function SupervisorHeader({ ...@@ -62,7 +62,7 @@ export function SupervisorHeader({
const current = clinics.find((c) => c.id === clinicId); const current = clinics.find((c) => c.id === clinicId);
return ( return (
<header className="flex h-12 flex-none items-center gap-2.5 border-b border-slate-100 bg-white px-3 sm:px-4"> <header className="flex h-12 flex-none items-center gap-2.5 border-b bg-white px-3 sm:px-4">
{!embedded && ( {!embedded && (
<> <>
<span className="inline-flex h-7 w-7 flex-none items-center justify-center rounded-md bg-brand-600 text-[12px] font-bold text-white"> <span className="inline-flex h-7 w-7 flex-none items-center justify-center rounded-md bg-brand-600 text-[12px] font-bold text-white">
...@@ -84,7 +84,7 @@ export function SupervisorHeader({ ...@@ -84,7 +84,7 @@ export function SupervisorHeader({
disabled={clinics.length < 2} disabled={clinics.length < 2}
onClick={() => setOpen((v) => !v)} onClick={() => setOpen((v) => !v)}
className={cn( className={cn(
'inline-flex h-7 items-center gap-1.5 rounded-md border border-slate-100 px-2 text-[12px] text-slate-600', 'inline-flex h-7 items-center gap-1.5 rounded-md border px-2 text-[12px] text-slate-600',
clinics.length > 1 && 'hover:bg-slate-50', clinics.length > 1 && 'hover:bg-slate-50',
)} )}
> >
...@@ -93,7 +93,7 @@ export function SupervisorHeader({ ...@@ -93,7 +93,7 @@ export function SupervisorHeader({
{clinics.length > 1 && <ChevronDown className="h-3 w-3 flex-none text-slate-300" />} {clinics.length > 1 && <ChevronDown className="h-3 w-3 flex-none text-slate-300" />}
</button> </button>
{open && ( {open && (
<div className="absolute left-0 top-8 z-30 max-h-72 w-56 overflow-auto rounded-lg border border-slate-100 bg-white py-1 shadow-lg"> <div className="absolute left-0 top-8 z-30 max-h-72 w-56 overflow-auto rounded-lg border bg-white py-1 shadow-lg">
{clinics.map((c) => ( {clinics.map((c) => (
<button <button
key={c.id} key={c.id}
......
...@@ -65,8 +65,8 @@ export function TeamStatus({ clinicId }: { clinicId: string | null }) { ...@@ -65,8 +65,8 @@ export function TeamStatus({ clinicId }: { clinicId: string | null }) {
const flagOverdue = top > 0 && top > (sorted[1] ?? 0) ? top : null; const flagOverdue = top > 0 && top > (sorted[1] ?? 0) ? top : null;
return ( return (
<div className="flex min-h-0 w-full flex-col overflow-hidden rounded-lg border border-slate-100 bg-white"> <div className="flex min-h-0 w-full flex-col overflow-hidden rounded-lg border bg-white">
<div className="flex flex-none flex-wrap items-center gap-x-2.5 gap-y-1 border-b border-slate-50 px-3.5 py-2.5"> <div className="flex flex-none flex-wrap items-center gap-x-2.5 gap-y-1 border-b px-3.5 py-2.5">
<span className="text-[13.5px] font-semibold text-slate-900">团队现在什么状态</span> <span className="text-[13.5px] font-semibold text-slate-900">团队现在什么状态</span>
<span className="nums text-[11px] text-slate-400"> <span className="nums text-[11px] text-slate-400">
{rows.length} 位在岗 · 在手 {inHandTotal} {rows.length} 位在岗 · 在手 {inHandTotal}
...@@ -129,7 +129,7 @@ export function TeamStatus({ clinicId }: { clinicId: string | null }) { ...@@ -129,7 +129,7 @@ export function TeamStatus({ clinicId }: { clinicId: string | null }) {
{rows.map((a) => { {rows.map((a) => {
const worst = flagOverdue != null && a.overdue === flagOverdue; const worst = flagOverdue != null && a.overdue === flagOverdue;
return ( return (
<tr key={a.userId} className="border-t border-slate-50"> <tr key={a.userId} className="border-t">
<td className="whitespace-nowrap px-3.5 py-2"> <td className="whitespace-nowrap px-3.5 py-2">
<span className="font-medium text-slate-900">{a.name ?? a.userId}</span> <span className="font-medium text-slate-900">{a.name ?? a.userId}</span>
{worst && ( {worst && (
......
...@@ -31,7 +31,7 @@ const PopoverContent = React.forwardRef< ...@@ -31,7 +31,7 @@ const PopoverContent = React.forwardRef<
align={align} align={align}
sideOffset={sideOffset} sideOffset={sideOffset}
className={cn( className={cn(
'z-[70] w-auto rounded-md border border-slate-100 bg-white p-2 shadow-md outline-none', 'z-[70] w-auto rounded-md border bg-white p-2 shadow-md outline-none',
// ⚠️ 原先挂的是 shadcn 官方那串 `animate-in fade-in-0 …` —— 它们来自 // ⚠️ 原先挂的是 shadcn 官方那串 `animate-in fade-in-0 …` —— 它们来自
// tailwindcss-animate 插件,**本仓没装**,所以一帧动画都没有(class 写了不报错, // tailwindcss-animate 插件,**本仓没装**,所以一帧动画都没有(class 写了不报错,
// 只是不生效,最难发现的那种)。改用 globals.css 里的原生 keyframes。 // 只是不生效,最难发现的那种)。改用 globals.css 里的原生 keyframes。
......
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