跳转至

第 6 章:UI 设计:把体验做成可维护资产

第 6 章封面

好看不难,难的是经得起折腾。在 0 到 1 阶段,UI 的第一任务是收敛不确定性,不需要追求“惊艳”。让用户闭着眼都知道下一步点哪,让开发者哪怕把代码删了一半还能跑通主流程。

如果你上一章的验证闭环已经跑通,接下来的最大风险就是“UI 腐烂”:入口越藏越深,状态越补越乱,每次改个颜色都要改十几个文件。本章不教配色,只教你如何把 UI 从“画图”变成“资产”:一套能复用、能自动回归、能让 AI 帮你维护的工程系统。

你的交付物

读完本章,你必须能拿出以下资产,而不是几张 Figma 截图: 1. 文字规格(Text Spec):在画图前,先定义任务流、信息层级和异常出口。 2. 状态矩阵(State Matrix):不仅有成功态,还有空、加载、失败、无权限和流式传输态。 3. Design Tokens:把颜色和间距变成 JSON 约束,而不是散落在 CSS 里的魔法值。 4. A11y 门禁:把可访问性(Accessibility)变成 CI 里的红绿灯,而不是上线后的补丁。

核心逻辑:UI 的本质是降低认知与维护成本

1. 为什么先写文字规格?

因为 Figma 画得越快,逻辑漏洞埋得越深。在像素级设计之前,你必须先用文字“拷问”你的界面。

UI 必须回答的三个问题: 1. 下一步是什么?(主行动按钮明确吗?) 2. 失败了怎么办?(有重试、回退或人工介入入口吗?) 3. 我在哪?(导航和状态反馈清晰吗?)

2. UI 资产化链路

从灵感到代码,必须经过这层过滤,否则就是给未来埋雷。

图 6-1:UI 资产化链路

步骤一:文字规格与状态矩阵

别让 AI 直接生成界面,它会给你堆砌一堆看起来很美但没法用的占位符。你得先给它“骨架”。

模板 1:关键页面文字规格

把这个表格填好,比画 10 张草图都管用。

维度 必须定义的内容 验收标准(门槛)
页面目标 用户来这只为了做哪一件事 删掉其他所有元素,这事还能做成吗?
主 CTA 点击后发生什么?是跳转、提交还是展开? 屏幕眯着眼看,它是最显眼的吗?
信息层级 谁是主角(H1)?谁是配角(H2)?谁是噪音? 第一眼只能看到 3 个以内的重点。
错误恢复 如果接口挂了/没权限/数据错了,用户能干嘛? 必须提供至少一个动作:重试、返回、联系支持。

模板 2:全状态矩阵(State Matrix)

只画成功态是掩耳盗铃。对 AI 产品,流式状态更是重灾区。

状态 (State) 系统表现 必备元素 证据点 (Evidence)
Empty (空) 引导任务开始 提示文案 + 1-3 个示例卡片 ui_state_empty_render
Loading (载入) 降低等待焦虑 骨架屏 / 耗时预估 (>1s) ui_p95_loading_time
Streaming (流式) 实时反馈中间态 停止按钮 + 已生成片段 + 自动滚动 ui_stream_chunk_latency
Partial (部分成功) 主结果正常但引用/工具失败 主内容正常展示 + 降级提示条 ui_partial_success_rate
Rate Limited (限流) 告知用户配额状态 剩余次数/冷却时间 + 升级入口 ui_rate_limit_hit_count
Error (失败) 提供自救路径 人话原因 + 重试/降级/人工入口 ui_error_impact_score
Success (成功) 交付价值并引导下一步 结果展示 + 复制/导出/追问 ui_success_exit_path

可执行动作:让模型生成补丁 当你有了规格,用 AI 直接生成代码补丁,而不是全文件覆盖。

# 示例:把规格转为状态完备的组件代码
cat <<'PROMPT' | <LLM_CLI> > ui_component.tsx
你是一个资深前端工程师。基于以下 UI 规格,生成 React 组件代码。

规格:
1. 目标:展示 AI 生成的分析报告。
2. 状态要求:
   - Loading: 显示骨架屏。
   - Streaming: 显示打字机效果,必须有'停止生成'按钮。
   - Error: 显示红色错误框,必须有'重试'按钮。
   - Empty: 显示'暂无报告',提供'新建分析'按钮。
3. 约束:
   - 使用 Tailwind CSS。
   - 严禁硬编码颜色,只能用 bg-blue-500 等标准类。
   - 必须包含所有状态的分支逻辑。

输出要求:
- 只输出组件代码,不要解释。
- 使用 TypeScript 接口定义 Props。
PROMPT

步骤二:Design Tokens(设计即约束)

当你的项目超过 5 个页面,一致性维护成本就会指数级上升。解决办法可以落到 Design Tokens,它是一份定义 UI 唯一事实来源的 JSON 文件。

图 6-2:Design Tokens 出码流水线

为什么是 JSON?

因为 JSON 机器可读。你可以写个脚本自动生成 CSS、iOS 变量,甚至自动生成 AI 的 Prompt 约束。

模板 3:最小 Token 结构

存为 tokens.json

{
  "color": {
    "primary": { "value": "#2563EB", "comment": "主品牌色" },
    "error": { "value": "#DC2626", "comment": "错误提示背景" },
    "text": {
      "main": { "value": "#1F2937", "comment": "正文颜色" },
      "sub": { "value": "#6B7280", "comment": "次要信息颜色" }
    }
  },
  "spacing": {
    "unit": { "value": "4px", "comment": "基准单位" },
    "md": { "value": "16px", "comment": "标准间距" }
  }
}

可执行示例:Token 转 CSS 变量

别手动抄写 CSS 变量,用脚本转。

脚本: tools/tokens_to_css.py (假设文件内容如下)

# gate_ui.py - UI 资产哨兵
import sys
from pathlib import Path

def validate_ui_assets(file_path):
    required_checks = {
        "tokens.json": "必须使用 Design Tokens。严禁硬编码颜色值。",
        "aria-label": "必须包含 A11y 标签。确保读屏器与自动化脚本可识别。",
        "证据点": "必须定义 UI 埋点证据。确保体验可量化。",
        "重试": "必须提供失败后的恢复入口。严禁死循环或死胡同。"
    }

    content = Path(file_path).read_text(encoding='utf-8')
    missing = [v for k, v in required_checks.items() if k not in content]

    if missing:
        print("❌ FAILED: UI 资产不规范。缺失以下关键要素:")
        for m in missing:
            print(f"  - {m}")
        sys.exit(1)

    print(f"✅ PASS: {file_path} UI 资产校验通过。准许进入前端实现。")

if __name__ == "__main__":
    validate_ui_assets(sys.argv[1])

步骤三:可访问性(A11y)作为门禁

把 A11y 当作“底线”而不是“爱心工程”。如果键盘选不中按钮,说明你的 DOM 结构是乱的;如果读屏器读不出来,说明你的语义标签是错的。这些问题不仅影响视障用户,也影响自动化测试脚本的稳定性。

最小验收清单(Fail 一项即回滚): 1. 键盘可达:丢掉鼠标,只用 Tab 能不能走完主流程?Enter 能不能提交?Esc 能不能关弹窗? 2. 焦点可见:当前选中的元素有没有明显的轮廓?(别为了美观把 outline: none 全局干掉)。 3. 标签完整:所有 input 都有 label 吗?所有图标按钮都有 aria-label 吗? 4. 对比度:灰底上的灰字,你自己看得清吗?(工具:Lighthouse 自带检查)。

自动化策略: 在 CI/CD 里集成 axe-corepa11y。不要试图人工检查每一个页面,那是浪费生命。

步骤四:流式渲染的 UI 模式

AI 产品的流式输出是最容易被忽略的 UI 重灾区。用户在等待流式响应时会经历三个心理阶段:焦虑(TTFT 之前)、期待(内容开始出现)、判断(内容足够做决策)。

流式 UI 必须满足的四条规则

  1. 首 Token 前有占位:TTFT(Time to First Token)可能长达 2-5 秒,必须有骨架屏或脉冲动画。
  2. 停止即保留:用户点击"停止生成"后,已输出的内容必须保留并可复制。
  3. 滚动可中断:自动滚动到最新内容,但用户手动向上滚动时必须暂停自动滚动。
  4. Markdown 渐进渲染:不要等全部输出完再渲染,而是逐块解析(避免未闭合标签导致的闪烁)。

流式渲染的状态转换

IDLE → WAITING (用户发送) → STREAMING (首 Token 到达) → DONE / STOPPED / ERROR
                                   ↑                        ↓
                                   └── RETRY ←──────────────┘

每个状态必须有对应的 UI 表现:WAITING 显示骨架屏,STREAMING 显示打字机效果 + 停止按钮,STOPPED 显示"已停止,内容可能不完整"提示条。

步骤五:回归策略与 AI 辅助

UI 回归最怕“温水煮青蛙”:今天歪 1 像素,明天色号偏一点,一个月后这就成了个山寨站。

1. 组件级回归(Storybook)

对原子组件(Button, Input, Card),用 Storybook 录入所有状态(特别是 Error 和 Loading 态)。 * 门槛:每个组件必须有一个 Story 展示其“最丑”的状态(文案超长、容器极窄、报错红框)。

2. 页面级回归(AI 视觉比对)

传统 diff 工具太敏感,稍微改个 padding 就报错。用 AI 做语义级视觉回归。

Prompt 示例(用于审视截图差异):

对比这两张 UI 截图(Base vs Current)。
忽略 2px 以内的像素偏移和渲染抗锯齿差异。
重点检查:
1. 是否有文字重叠或遮挡?
2. 关键按钮(提交、取消)是否还在可视区域?
3. 颜色对比度是否明显下降?
如果发现上述严重问题,请输出 FAIL 并说明原因;否则输出 PASS。

交付物自检清单

在把 UI 代码合入主分支前,请对着镜子问自己:

  1. 规格查了吗? 失败状态和恢复入口写进代码了吗?
  2. 约束守了吗? 新增的颜色都在 Token 列表里吗?
  3. 键盘试了吗? 不用鼠标能跑通这个功能吗?
  4. 极端情况测了吗? 断网、报错、返回空数据,页面崩了吗?

下一章

UI 只是皮囊,工程才是骨骼。当你把界面变成可管理的资产后,我们需要谈谈如何用工程手段保证这一切能持续运转。下一章:07-engineering.md

参考

详见本书统一参考文献列表:references.md