实习日志 / 中文 / 界面重构 / 先定主路径,再定外壳:一次前端重构讨论是怎么收敛的
先定主路径,再定外壳:一次前端重构讨论是怎么收敛的
中文 | English
9 月 1 日这天,我决定先不改前端代码,用一整天把报案端和员工端的界面问题摊开来讨论清楚。原因很简单:当时两个前端都已经长到了不敢随便动的程度,改哪里都像在拆东墙补西墙。
这一轮讨论留下了三份东西:一份问题盘点,一份分九组推进的讨论底稿,以及第二天把前面所有材料合并起来的权威任务文档。下面按讨论的顺序,讲清楚每个结论是怎么来的。
先看清楚现状
盘点时我用的标尺是 Refactoring UI 那本书的几条原则:先设计功能再设计外壳,用信息层级代替装饰,限制选择,先建立间距和字号系统。拿这几条去对照,问题很快排了出来。
报案端的首页同时摆着六件事:自然语言输入框、三种理赔类型、只有车险能用的分步引导、恢复已保存的报告、登录、找人工。功能多本身没什么,毛病出在用户不知道现在该先做什么。更糟的是,用户选了 Home 或 Contents,界面接着告诉他引导流程还没配置。这个选项对他毫无价值,只制造了一次预期落差。
车险分步引导的第一屏要填 9 个字段:客户编号、身份、联系偏好、日期、地点、经过、损失、车牌、损坏摘要。仓库规范自己写着"一个表单组最多 5 个字段""一轮只问一个主要问题"。对一个刚出事的人,所谓"智能引导"退化成了一张传统长表单。
一个写死的 Step 1 of 3 进度条。我们的产品是按已确认的事实、缺失的信息、理赔类型和当前状态动态决定下一步的,固定三步会让用户以为所有理赔都只有三步,一旦进入人工协助、等材料或等第三方,这个进度条就表达不了真实状态。
字号普遍太小。规范要求报案端正文至少 16px,代码里消息正文是 14px,作者名和状态说明 11px,表单标签 12px,辅助说明 10px 到 12px。而这些"辅助"文字里,恰恰有错误说明、当前状态、下一步指引和字段来源。
设计值没有形成系统。报案端只定义了六个局部变量,其余颜色、间距、圆角直接写在组件里:#2c806a、#246f5c、#edf8f3,圆角从 7px、8px、9px、10px、14px、15px 一路写到 999px。员工端是一个单文件的静态 HTML,内嵌 CSS 大约 150KB,深紫色侧栏、深蓝灰背景、青绿色强调,看起来和报案端像两家公司的产品。
还有几条交互层面的:日期选择器挂了 role="dialog",却没有 Esc 关闭、焦点进出和完整的键盘操作;员工端的案件卡片要靠鼠标悬停才显示事故摘要和下一步,触屏上根本看不到;员工端侧栏里混着 Load workbench demo queue、Reset and load demo queue 这类开发演示按钮;浮动的员工助手在说明文档里写着"本地、非权威的原型交互",界面上却像一个能操作案件的全局助手。
盘点也记下了值得保留的东西:自然语言开始报案、字段可以检查和修改并显示来源、保存和恢复、人工协助状态、发送中和失败重试的状态、员工发给客户的消息要先编辑确认、写操作带版本号和幂等键。这些功能的含义都对,要重做的是信息层级和视觉表达。
第一组结论:主路径只有一条
讨论从产品方向开始。Agent 在报案端是主要产品体验,用户先用自然语言说发生了什么,Agent 理解、调用已授权的动作和检索、按当前状态生成下一步,再把结果转成用户能理解的内容让他确认或纠正;哪些建议能改变真实业务状态,由运行时和后端决定。
由此得出第一组结论:
- 报案端是一个 Agent 对话工作区,Agent 是主视觉和主要入口。
- 车险分步引导和固定三步流程退出主路径,动态表单嵌进对话,按当前状态生成。
- 理赔类型保留为输入框旁边一个非必选的下拉。它由 Agent 和案件状态控制:用户没选时 Agent 可以根据描述提出类型;Agent 确认案件进入某个分支后,下拉就锁定;用户的选择和已确认的事实冲突时,界面要解释原因、引导处理,不能静默覆盖。
- 员工端要彻底组件化,单文件静态 HTML 不再作为扩展的基础。
- 两端共享基础设计语言,信息密度和布局重点可以不同。
Continue 和 Resume 两个按钮在这一组里被拿掉了。理由是:如果对话历史和报案会话能持久化、能正确恢复,那继续对话本身就是继续报案,打开一段历史本身就是恢复,用不着再设计一组重复的按钮制造额外分支。
首页的大标题保不保留,我们也没有凭审美决定,判断标准是它能不能帮用户理解当前任务。
第二组:输入区和动态字段
主提示的文案讨论得比较久。GPT 类产品常用一句很中性的功能提示,问你需要什么。我们不想照搬,因为这里的提示要同时做两件事:让用户知道可以直接开口,也让他感觉到这是保险场景里专业、友好的帮助。最后用了两层结构:上面一句品牌主张 Understand insurance. Understand you better.(中文是"懂保险,更懂你"),输入框里一句简短的动作提示 Tell us what happened。
输入区是一个统一的工具栏,文字、文件、语音、无障碍模式和发送都在里面。它们是输入方式,各自都不升格成新的入口。无障碍模式开启后默认朗读 Agent 的回复,要有播放、暂停、关闭的控制,还要处理浏览器的自动播放限制。文件上传要显示类型和大小限制、上传中、成功、失败、删除,并告诉用户数据怎么用于这次报案。
动态字段要不要让用户看到,讨论了两轮。结论是看得到,但只给用户能理解、能确认、能修改的字段,附上当前状态和必要的来源;内部规则、置信度、工具调用和员工专用的信息一律不暴露。桌面上是一个可以收起的 What we have so far 面板,手机上是对话里一块可以展开的 Details captured 区域。Agent 每次更新字段,只突出这一轮新增或改动的内容;用户可以点开字段改,也可以直接用自然语言纠正。
日期输入有一条专门的结论:不能阻塞对话。用户可以直接说"昨天傍晚",只有真的需要精确日期时才弹日期控件,而且优先用浏览器原生的日期输入,或者完整遵循 ARIA APG 的控件。只加一个 ARIA role 不算完成可访问性。
传统网页表单和人工协助保留,放在同一个辅助导航区,做成文字链接。视觉上是文字,可点击的区域仍然要满足报案端的触控目标尺寸。
报案端不需要一条大导航栏
导航单独讨论过一次。员工端有多个工作区,需要全局导航;报案端只有一个工作区,Agent 对话本身就是产品入口。按 Fitts 定律,主要的可用区域应该留给对话和输入框,个人资料可以是一个靠近屏幕边缘、触控面积大的头像菜单,历史可以从靠近对话区的抽屉打开。早先提过的"顶部轻量导航"只是放几个工具入口的一种可能,不作为报案端必须采用的结构。
另一条定成了所有页面的验收条件:每个页面、抽屉、标签和详情视图,都要有一个能理解的上一级位置。案件详情回到案件队列,关掉一个案件标签回到上一个案件或队列,当前对话回到历史列表,传统表单回到同一单的对话。离开任何地方,都不能丢掉已经保存的状态,也不能让用户把确认过的信息再收集一遍。返回按钮长什么样可以后定,"能回到流程上或时间上的上一个视图"这一条固定不变。
前端只表达,不判断
讨论到员工端的队列排序、责任方、风险提醒时,我发现前端讨论开始替后端定义业务规则了。所以中途加了一组专门划界:
后端根据代码、规格、持久化模型、API 和测试,维护稳定的业务契约,包括工作流状态、紧急程度、证据、欺诈信号、下一步动作这些定义,字段缺失的优先级和阻塞关系,风险信号、人工审核、队列排序和主下一步之间的计算关系,以及哪些是持久化的事实、哪些是计算出来的投影。
前端根据后端给的投影设计层级和交互,清楚地表达生命周期、责任方、缺失信息、风险提醒和主下一步;后端能力还没好的地方,用明确的 loading、empty 或 unavailable 状态。前端不自己计算优先级、责任方、风险结论、字段阻塞关系或业务状态。
讨论里遇到后端问题时,先查代码、规格、API 和测试,把问题分成三类:已经实现的事实、文档里有但代码还没实现的设计、还没定义需要产品决策的问题。只有第三类需要拿出来讨论。
"可靠"要靠行为来证明
两端共享一套产品性格:专业、友好清晰、有安全感、尊重用户、不制造不必要的紧张。最难落地的是"可靠"这个词。我们给它下的定义是:不承诺不确定的结果,但始终提供清晰、诚实、可以继续的帮助。
这个定义的意思是,可靠感不能靠颜色和文案营造,得靠具体行为建立:显示已经记下的和还缺的信息,解释为什么需要下一步,区分信息、建议、待确认动作和已执行动作,保留对话和案件的上下文,失败时说明限制并给出可继续的路径,对安全、隐私和人工协助保持明确。报案端把这种性格表现为温和、易懂、有耐心,员工端表现为可追溯、可判断、可执行。
登录这件事交给后端做实
登录页还要重新设计,但我们不想因为当时用的是测试账号,就让认证一直停在演示级。当时的身份仓库还是开发测试用的内存实现,完整的用户系统至少要有服务端的用户记录、安全存储的密码凭据、服务端会话记录、过期和撤销、用户资料和通信偏好。登录出错时不能泄露账号是否存在。前端和领域层先依赖一个与身份提供商无关的认证接口,生产用的身份提供商以后可以替换。用户的报案数据通过 customer_id 外联,不塞进身份表。
没登录的用户可以直接开始报案。开始时生成一个随机、不可预测的临时会话 ID,只代表这一次浏览。只有用户在这次会话还有效时主动登录,服务端才把它关联到账户,而且这个关联要经过服务端验证,一次性、幂等、留审计。用户没登录就走了,这个临时会话保持孤立,任何人都不能凭一个随机 ID 认领它;过期或清理以后,也不能再恢复成用户的报案。
合并成一份权威文档
第二天,我把前面所有材料合并成一份权威任务文档:问题盘点、讨论底稿、视觉规范、入口页设计、登录旅程的整改记录、重新评估记录,一共六份。HTML 原型只作为视觉和交互参考,不当业务契约。
合并时最先写下的是冲突规则,按这个顺序取舍:
- 用户在最新讨论中明确确认的决定;
- 这份文档已经整理出的最终结论;
- 还没被推翻的 HTML 原型表现;
- 旧讨论稿里的建议。
紧接着一句:旧文档不能成为第二套规范。实现、测试和代码审查都以这份为准;代码和它不一致,就记成一个缺口,不要继续往上叠覆盖样式。写这句是因为前一周我们吃过亏,旧样式被后来的 CSS 一层层盖住,盖不住的地方就漏出来。
这份文档还做了两件讨论稿里没做完的事。
控件分类和状态模型。 所有控件都必须归到一个类别里,从共享 token 取样式:导航、入口与认证、输入栏、主动作、次要动作、字段编辑器、服务卡片、状态、浮层、员工 Agent,一共十类。每类控件至少定义 default、hover、focus-visible、active、disabled、loading、error、success 八种交互状态,业务状态另外定义 confirmed、proposed、pending、new、incomplete、handoff、unknown。
状态的表达方式固定成一个组合:
状态文字 + 图标或符号 + 语义颜色 + 可执行的下一步
错误提示必须回答三个问题:发生了什么、为什么、现在能做什么。"结果未知"要单独处理:提示用户重新同步或安全重试,不能显示成失败,也不能显示成成功。
把 token 定成可执行的数值。 颜色表在设计稿基础上做了收敛,强调色从 #5F8F80 调到 #7A9E8E,旧的 #2C806A、#246F5C、#EDF8F3 那一套绿色全部作废。其余几组:
| 类别 | Token 和数值 |
|---|---|
| 间距 | --space-1 4px、-2 8px、-3 12px、-4 16px、-5 20px、-6 24px、-8 32px、-10 40px、-12 48px、-16 64px |
| 圆角 | --radius-sm 4px、-md 6px、-lg 8px、-xl 10px;--radius-round 999px 只给头像和状态标签,不用于主要操作 |
| 边框 | --border-region 2px、--border-component 1.5px、--border-focus 1.5px accent、--border-action 1.5px accent-light、--border-danger 1.5px danger |
| 阴影 | --shadow-none;--shadow-subtle 0 1px 3px rgba(26,24,20,.08);--shadow-popover 0 8px 24px rgba(26,24,20,.14);--shadow-modal 0 16px 40px rgba(26,24,20,.18) |
| 字重 | regular 400、medium 500、semibold 600、bold 700(只给强状态和明确警示) |
按钮分六个层级:primary 是当前唯一的主动作,secondary 是次要但可执行的动作,tertiary 低强调,text 用于辅助导航和编辑链接,danger 用于删除和不可逆的动作,disabled 降低对比度但保留原因说明。
业务状态也各配了一组颜色和行为:
| 状态 | 文字 | 背景 | 要求 |
|---|---|---|---|
| confirmed | accent-strong | accent-soft | 表示已记下,可以继续 |
| proposed | text-muted | page 或 surface | 必须允许用户确认或修改 |
| pending | text-faint,斜体 | 透明 | 表示等待,不制造出错的感觉 |
| new / changed | accent-strong 加 new 标记 | accent-soft | 只标本轮新增或改动的 |
| incomplete | text-secondary 加明确的文字 | warning-soft 或 surface | 写出缺什么、下一步是什么,不隐藏 |
| handoff | text-secondary 加状态文字 | surface | 写出谁在负责、保存状态和下一步 |
| error | danger | danger-soft | 写出发生了什么、原因和可执行的修复 |
| unavailable | text-muted | panel 或 page | 明确告诉用户这项能力暂时不可用 |
字号上,14px 不能用于错误说明、当前状态、下一步、字段来源或主要操作;员工端可以把 14px 用于密集的元数据,但业务正文、风险、缺失信息和下一步不得小于 14px;10px 到 13px 不再用于任何主要信息。
员工端:先定层级,布局暂不定稿
员工端在这一轮也讨论了很多,主线是员工打开工作台时第一屏要回答"哪一单需要处理、为什么、现在由谁做什么",信息按"工作摘要、当前可操作的内容、参考资料"三层组织。欺诈只作为风险标签,不单独成一个默认队列,免得 Agent 尚未确认的判断变成员工的先入之见;未完成的报案是正式的案件,优先级低,但不折叠、不隐藏。员工的 Agent 是工作台级别的全局能力,取消了在客户对话里打 @agent 的用法。
这些结论后来另写了一份完整的工作台重构文档。当时有一条我特意留着没定:工作台的最终排版不凭一张示意图锁死成三栏或固定侧栏,先根据功能和状态机确定内容,再决定侧栏、队列、详情、标签页和全局 Agent 的空间关系。
顺序
讨论底稿最后给了一个讨论顺序,我觉得它比任何一条具体结论都重要:先确认产品主张在两端的具体含义,再确认案件分支和状态的整体状态机,然后才是报案端的主路径、员工端的信息层级、双端共享的调性,再往后是 token、状态语义和错误模型,最后才拆实现任务。
权威文档的实施顺序也照着这个思路排:先修页面状态和登录后的去向,再建共享 token,然后拆组件、做报案端工作区、补状态和帮助行为、清理旧代码,最后才轮到员工工作台,以及响应式和无障碍的验收。
这一轮讨论之前,我们习惯先画一个界面,再往里塞功能。这一轮之后,顺序反了过来:主路径先定,外壳最后定。