UI 改进方案 — 真实代码 Audit¶
状态:草稿待评审 · 日期:2026-05-06 · 范围:
studio-website-monorepo/apps/web真实生产前端⚠️ 本方案基于真实代码扫描(543 个 .vue 组件 + 16 个 theme + 6 对新旧路由)。之前基于 prototype HTML 的版本已作废。
一、量化诊断¶
| 维度 | 真实值 | 评价 |
|---|---|---|
| Vue 组件数 | 543 | — |
Inline style="..." |
88 处(其中 11 含 hex) | ✅ 健康(多为动态 width / chart tooltip) |
| 独立 hex 颜色(非 token) | 55 种 | 🟡 业务组件大量 hardcode |
| Tailwind 颜色 class hardcode | 数百处 bg-blue-100、bg-emerald-50 等 |
🔴 绕过 CSS token |
| Theme 数量 | 8 套 × light/dark = 16 个 | 🔴 全部失效,见问题 #4 |
| 卡片实现方式 | shadcn Card 72 处 vs 自定义 rounded+border 104 处 |
🔴 自定义比标准还多 |
| 新旧版本并存路由 | 6 对(v3 / -old / 旧版同时注册) | 🔴 最大根源 |
整体判断:技术栈是行业一线(Tailwind 4 + shadcn-vue + lucide + OKLCH),但执行上累积了大量不一致——主要源于"重构未完成 + theme 系统未上线 + hardcode 颜色泛滥"。
二、用户感受到的问题(症状)¶
工程问题不是用户最先感知到的——用户感受到的是视觉和情绪层面的不适。下面是基于你给出的反馈整理的 7 类用户痛点(如果还有别的,告诉我补上):
| # | 用户感受 | 你怎么形容它的 |
|---|---|---|
| 1 | 🎨 没有品牌识别度 | "没有一个品牌色调"——打开 app 看不出这是 retaintive 还是某个 SaaS 模板 |
| 2 | 🌀 视觉调性混乱 | "没有统一的调性"——不同页面风格突变(旧 /calls 是蓝 shadow 卡片,新 /calls-v3 是 border 列表,像两个产品) |
| 3 | 🔤 字体大小不统一 | "字体大小没有统一"——同一种信息(如标题、metadata)在不同页面用不同字号 |
| 4 | 🔘 按钮样式不统一 | "按钮的样式没有统一"——主要按钮 / 次要按钮 / 危险按钮看起来同等重要,不知道点哪个 |
| 5 | 📦 卡片界限不清 | "卡片界限不清, 背景色是白色, 很多卡片还用了淡黄色, 卡片的边框也是淡黄色或者灰色"——眼睛找不到模块边界 |
| 6 | 👁️ 对眼睛不友好 | "对眼睛不友好"——hardcode 多色 + 同时多种卡片底色(emerald-50 / blue-50 / amber-50 同屏混用),疲劳感 |
| 7 | ✨ 缺失美感 | "缺失美感"——比对 Linear / Notion / Stripe 这种"完成度高"的产品有明显差距 |
| 8 | 🎯 缺失选中态反馈 | 点击列表行打开右侧详情后,列表里"我刚点的那条"没有任何视觉标识(无 highlight、无 border-left、无底色变化)——眼睛要在右侧详情和左侧列表间来回扫才知道在看哪条 |
| 9 | 🤔 不知道哪里能点 | "有些图标可以点击, 有些不是按钮, 不能点击, 我区分不出"——icon 没有 button 的 affordance(无边框 / 无 hover bg / 无 cursor 提示),用户要靠试错才知道哪些可交互 |
| 10 | ❓ 图标看不懂含义 | "有些图标我看不出来是什么东西, 就像这个学士帽, 完全不知道是什么"——纯 icon button 没有 label / tooltip / 文字辅助;lucide 库 800+ icon 里很多语义是抽象的,业务功能(Coaching? Training? Notes?)用户猜不出 |
| 11 | 🏷️ 不知道标签能不能点 | "因为它用的这个淡黄色, 我都没有看出来这是个可以点击的标签"——chip / badge / tag 视觉上 readonly(status badge)和 interactive(filter chip)没区分,都是淡色底 + 圆角,用户不知道点了会发生什么 |
症状 → 根因映射¶
| 用户感受 | 根因(见下章节工程问题) |
|---|---|
| U1 没有品牌色调 | → #4 themes 全部失效(实际只跑 zinc 中性灰,没有品牌色) |
| U2 视觉调性混乱 | → #1 新旧路由并存 + #2 自定义卡片混乱 |
| U3 字体大小不统一 | → 缺设计规范约束(不是字号阶不够,是没有人指定"标题用哪个字号、metadata 用哪个") |
| U4 按钮样式不统一 | → 同上(shadcn Button 已有 variant 但被绕过) |
| U5 卡片界限不清 | → #2 自定义卡片 104 > shadcn Card 72 + #3 hardcode 多种底色(emerald-50 / blue-50) |
| U6 对眼睛不友好 | → #3 业务组件 hardcode 12 种颜色 + #5 ECharts hardcode |
| U7 缺失美感 | → 以上所有问题叠加导致 |
| U8 缺失选中态反馈 | → #6 列表交互态(selected / hover / active)规范缺失,组件未绑定 selected state 到视觉 class |
| U9 不知道哪里能点 | → #7 Icon button 缺规范(无 affordance:无 border / 无 hover bg / 无 cursor 视觉提示) |
| U10 图标看不懂 | → #7 Icon button 缺规范(无 aria-label / 无 tooltip / 纯 icon 业务语义不明) |
| U11 不知道标签能不能点 | → #8 Chip/Badge/Tag 缺 readonly vs interactive 区分(视觉一致 → 用户分不清装饰 vs 按钮) |
关键:U1(缺品牌色)和 U2(视觉混乱)是 100% 物理可解的——分别对应工程问题 #4 和 #1,修起来都是 0.5-2 天的事。U3-U7 是衍生问题,根因修了它们大部分跟着改善。U8 是独立 UX 问题——和样式系统无关,是组件层面缺 selection state 绑定。
三、8 个工程根因(按严重度排)¶
🔴 #1 新旧版本路由并存(最大根源)¶
vue-router/auto-routes 把 pages/ 下所有目录全部注册成路由。6 对旧/新版本同时活着,用户能访问到两套完全不同的 UI:
| 旧版 | 新版 | 视觉差异 |
|---|---|---|
/calls |
/calls-v3 |
旧:table + 蓝 shadow 卡片;新:list + border 无 shadow |
/contacts |
/contacts-v3 |
圆角不同:rounded-lg vs rounded-xl |
/leads |
/leads-v3 |
同上 |
/conversations |
/conversations-v3 |
同上 |
/lead-tracker |
/lead-tracker-old |
旧版还在被引用 |
/activity-timeline |
/activity-timeline-old |
同上 |
证据:pages/calls/components/call-card.vue:186-192 用 rounded-lg bg-card shadow-sm shadow-blue-200/50;pages/calls-v3/index.vue:162-163 用 rounded-xl border hover:bg-accent/50——两套并存。
🔴 #2 自定义卡片 (104) > shadcn Card (72)¶
pages/leads-v3/components/lead-funnel.vue 单文件混用 3 种卡片风格:
rounded-lg ring-1 ring-inset ring-border ← neutral
rounded-lg bg-emerald-50 ring-emerald-200 ← 绿
rounded-lg bg-blue-50 ring-blue-200 ← 蓝
应该用 <Card variant="..."> 统一,每种 variant 内部走 token。
🔴 #3 业务组件大量 hardcode 颜色(绕过 token)¶
pages/calls/components/call-card.vue:113-146 一个组件用了 12 种 hardcode tailwind 颜色:
text-blue-600 bg-blue-100 text-purple-600 bg-purple-100
text-green-600 bg-green-100 text-red-600 bg-red-100
text-amber-600 bg-amber-100 text-gray-600 bg-gray-100
每种还要单独写 dark: —— 任何 theme 切换对这些颜色完全无效。
pages/contacts-v3/index.vue:232-233 头像也是写死的 bg-green-500 / bg-purple-500 / bg-blue-500。
🔴 #4 致命:16 个 theme 全部死代码¶
assets/themes.css 定义了 8 套 theme × light/dark = 16 个 .theme-xxx class,stores/theme.ts 也有 setTheme()。
但 main.ts:14 注释 // Note: themes.css removed for dynamic loading,整个 src 里没有任何代码把 theme-xxx class 写到 <html> / <body>:
| 状态 | 现实 |
|---|---|
| themes.css 加载 | ✅ |
useThemeStore.setTheme() 可调用 |
✅ |
| Class 写到 DOM | ❌ 从未发生 |
| 实际生效的 theme | 只有 zinc 默认(:root 里),其他 7 套 + 全部 dark mode 都是死代码 |
这是 ROI 最高的修复点——一行代码(document.documentElement.className = 'theme-' + activeTheme)就能让 16 个 theme 全部活过来。
🟡 #5 ECharts 不响应 dark mode¶
components/dashboard/RevenueWaterfallSankey.vue:199 color: '#333' 写死黑色,dark mode 下几乎看不见。Chart palette 第 207-215 行用 hsl(220, 100%, 60%) 等 HSL 裸值。
🔴 #6 列表 selection state 视觉反馈缺失(master-detail 模式)¶
典型场景:Calls / Contacts / Leads 等列表页都是 master-detail 布局——左侧列表 + 右侧详情。点击列表某行 → 右侧打开对应详情。
问题:左侧列表的"被选中那行"没有视觉标识: - 无 background highlight - 无 border-left accent - 无 chevron / arrow 指向右侧 - 不区分于其他普通 row 的 hover
结果:用户失去上下文。要回头数行号、对照名字才能确认"右侧详情对应的是哪一行"。在长列表(如 100 条 calls)里这是严重影响效率的问题。
根因:组件层面缺 selection state 绑定。data 层可能有 selectedCallId 但 template 里没把它绑定到 row 的 conditional class(如 :class="{ 'bg-accent border-l-4 border-l-primary': call.id === selectedCallId }")。
影响范围:所有 master-detail 列表页(calls / calls-v3 / contacts-v3 / leads-v3 / lead-tracker / messages 等)。
🟡 #7 Icon button 缺规范(affordance + 语义)¶
典型场景(截图证据 — calls 详情面板顶部):
💬 Transcript 一看就是 button(圆角 + 边框 + 文字),但右侧两个图标(网格 和 学士帽)没有 button 视觉特征——用户既看不出是按钮,又看不出是什么功能(学士帽 = Coaching? Training? Knowledge base?)。
两类问题分别:
| 类型 | 现状 | 修复方向 |
|---|---|---|
| Affordance 缺失 | icon 没有 border / hover bg / cursor 提示 | 统一用 shadcn <Button variant="ghost" size="icon"> 包装,hover 时浅底高亮 |
| 语义识别失败 | 纯 icon 不带文字辅助;lucide 800+ icon 多数是抽象图形 | 所有 icon button 必须加 aria-label + <Tooltip>;高频功能可考虑 icon + 极简文字 label |
应用层面规则建议: - icon-only button 必须 配 tooltip - 业务核心动作(如 Coaching / Save / Edit)应优先 icon + 文字而非纯 icon - 装饰图标(不可点击的 icon)必须视觉上明显不同于 icon button(不要边框、不要 hover)
影响范围:整个应用所有 icon button——sidebar / table 行操作 / 详情面板工具栏 / dashboard widget header 等。
🟡 #8 Chip / Badge / Tag 缺 readonly vs interactive 区分¶
典型场景:calls 列表里 "Revenue" / "Intro Booking" / "Voicemail" / "Live Call" 都是 chip 形式(淡色底 + 圆角),但同时存在两种用途: - Readonly status badge:显示当前状态(如 "Voicemail" 是 call 的 state,纯展示) - Interactive filter chip:点击后筛选/编辑(如 Category 列的 chip 可能可点击切换分类)
问题:两类视觉一致——都是淡色底 + 圆角 + 文字。用户: - 看到淡黄底的 chip 不知道是不是按钮 - 不知道点了会发生什么(filter? edit? noop?) - 必须靠 hover 试探,但 hover 反馈也常常不存在
修复方向: | 类型 | 视觉规则 | |---|---| | Readonly badge(纯展示)| 实底 / 浅底,无 hover 变化,无 cursor pointer | | Interactive chip(可点击)| 边框 + hover 加深 + cursor pointer;最好带一个小 icon(× 删除 / ▾ 下拉 / ✓ 选中)暗示可操作 |
额外考虑:避免淡黄色 chip 单独使用——视觉上和 disabled state 太像;用 token 里的 --warning 而非 hardcode bg-amber-100。
影响范围:所有列表页(calls / contacts / leads / lead-tracker)+ task page + dashboard 上的 status 显示。
四、视觉最差的 5 个页面/组件¶
pages/calls/components/call-card.vue— 12 种 hardcode +rounded-[5px]异形圆角 +shadow-blue-200/50硬 shadowpages/leads-v3/components/lead-funnel.vue— 单文件 3 种 ring 卡片风格混用pages/contacts-v3/index.vue— 头像 hardcode + 容器圆角与 calls-v3 不一致(rounded-lgvsrounded-xl)components/dashboard/RevenueWaterfallSankey.vue—#333写死 + HSL 裸值 palettepages/calls/index.vue和pages/calls-v3/index.vue同时活——同一路由层级两套交互范式
五、调性方向(必须先定)¶
当前所有 theme 失效 → 实际跑的是 zinc(中性灰)默认。修复 theme 系统时同时决定走哪个方向:
| 方向 | 参考 | 当前 themes.css 是否已存在 | 适合理由 |
|---|---|---|---|
| A 极简专业 | Linear / Stripe | ✅ theme-neutral / theme-zinc |
数据驱动 SaaS 主流;现在已是默认;最稳 |
| ✅ B 温暖人性(推荐) | Notion / Airtable | ✅ theme-orange / theme-yellow |
健身行业不冷冰冰;和 Mindbody/Glofox 老旧表格风差异化 |
| C 科技深色 | Vercel / Sentry | ✅ 所有 theme 都有 dark mode 定义 | "AI 感"强;Manager 长盯不友好 |
好消息:themes.css 已经把 8 个 theme + dark 全部预定义好了(包括 chart palette)—— 只要修好 theme apply 逻辑,立刻可以试用任何方向。
六、改进优先级¶
| 优先级 | 范围 | 内容 | 工作量 | 见效 |
|---|---|---|---|---|
| P0 | 问题 #4 — 修 theme apply | 在 App.vue 或 main.ts 里把 useThemeStore 的 active theme apply 到 <html> class;同时定默认走方向 A/B/C |
0.5 天 | 立即——所有用了 token 的组件统一变化 |
| P0 | 问题 #1 — 删旧路由 | 选定 v3 / 新版作为 SoT,删 pages/calls/、pages/contacts/、pages/leads/、pages/conversations/、-old 目录 |
1-2 天(含回归测试) | 立即——视觉风格统一一半 |
| P1 | 问题 #3 — 业务组件颜色 hardcode 替换 | call-card.vue 12 种 hardcode → text-info-foreground bg-info 等 token;contacts 头像同;lead-funnel 同 |
1-2 天 | 立即——theme 切换全店生效 |
| P1 | 问题 #2 — 自定义卡片 → shadcn Card | 104 处 rounded+border 重写为 <Card>;自定义需求加到 Card 的 variant prop |
2 天 | 卡片节奏感统一 |
| P1 | 问题 #6 — 列表 selection state | 所有 master-detail 列表(calls / contacts-v3 / leads-v3 / lead-tracker)的 row 加 selected 视觉绑定(bg-accent + border-l-4 border-l-primary);同时统一 hover/active 规范 |
0.5-1 天 | 立即——长列表场景效率明显改善 |
| P1 | 问题 #7 — Icon button 规范化 | 全应用 icon-only button 替换为 <Button variant="ghost" size="icon"> + <Tooltip>;装饰 icon 移除 button 样式;业务核心动作改 icon + 文字 |
1-2 天 | 立即——用户能识别哪些可点 + 看懂含义 |
| P1 | 问题 #8 — Chip/Badge 区分规范 | 把 <Badge> 拆 readonly / interactive 两种 variant;interactive 加 hover + cursor + 暗示性小 icon;废止淡黄色单独 chip |
1 天 | 用户立刻看懂"哪些标签能点" |
| P2 | 问题 #5 — ECharts dark mode 适配 | chart 配置读 CSS variable;用 --foreground --chart-1..5 替代 hex |
1 天 | dark mode 可用 |
总计 7.5-11 天 全部修完。
最大杠杆是 P0 #4 —— 一行代码修好 theme apply,所有走 token 的组件立刻统一到品牌色调;这一步做完再决定方向。
七、待确认¶
- 方向:A / B / C(默认 B,等你确认)
- 新旧路由怎么删:v3 替换原版?还是 v3 直接合并回原路径(
/calls-v3→/calls)? - 是否给用户开放 theme 切换 UI:还是固定一套?
- 执行节奏:P0 #4 + P0 #1 先做(约 1.5-2.5 天),review 看效果,再决定 P1/P2?
八、说明¶
- 本方案 5 个问题 + 5 个最差页面全部基于真实代码引用(543 vue 组件全扫)
- 之前基于 prototype HTML 的 26 项 audit 不再适用
- 具体 token 替换映射、删除哪些文件、修改哪些行 —— 属于实施阶段,方向定了再写