Web 应用设计
Web 应用是用户每天都会回来查找记录、修改记录并交接工作的地方。成功的应用会让用户始终清楚自己在哪里、在看什么、可以做什么、刚刚发生了什么,并且能够通过 URL 在明天回到相同的视图。最常见的模型失败是一种只有美丽“幸福路径”屏幕却无处立足的状态:没有空状态、加载状态、错误状态或无权限状态;重新加载后过滤器消失;批量操作没有计数和撤销;“删除”操作触发了一个乐观更新,但服务器后来拒绝了该请求;以及一列相同的“编辑”按钮,AI 代理无法区分它们。
应用 interface-guidelines(界面指南)的层级原则(所有人可用 > 诚实 > 表达清晰 > 答案优先 > 错误可挽回 > 保持稳定 > 可见性 > 约定俗成 > 美观)。如果已安装该技能,请先阅读其 SKILL.md。它的参考资料涵盖了反馈时机、错误措辞、作为数据表格的表格以及面向代理的语义;本技能则补充了应用 UI 特有的模式。
简要
- 对象和操作。 列出名词(订单、客户、发票)以及每个角色可以对每个对象做什么。导航和权限可以从这个表格中推导出来。
- 角色和租户。 谁可以查看和修改什么?是否存在多个工作区、组织或环境(staging、production)?
- 日常任务 及其频率,以及每个角色每天打开的一个首要视图。
- 集合规模: 典型情况和最坏情况(10 行或 200 万行)。这决定了分页、搜索和服务器端过滤的方式。
- 错误成本 每个可变操作的错误成本,以及是否可以在服务器端撤销。
- 并发: 两个人,或者一个人和一个代理,可以同时编辑同一条记录吗?
- 谁来写显示的文本? 客户名称、工单正文、导入的文件:所有这些都是由外部人员编写的。
- 代理阅读器: 浏览器代理是通过 UI、API 还是两者都操作?UI 和 API 应该为每个操作共享一个服务。
规则
外壳和导航
- 导航应反映对象,而非组织架构。 顶层项目是人们工作的名词,按使用顺序排列,并使用人们常说的词语进行标注。[S: Mac HIG 1992 pp. 54-61 (按逻辑分组,然后按频率);J]
- 在桌面上保持主导航可见。 隐藏导航会降低可发现性并减慢 NN/g 研究中的任务速度;汉堡菜单是手机的妥协方案。[E: NN/g,基础部分]
- 以三种方式显示用户当前位置: 使用
aria-current="page"和可见状态标记当前导航项,一个命名对象或列表的页面h1,以及一个随路由变化的文档标题。[S: WCAG 2.4.2, 2.4.8;J] - 面包屑显示层级,而非历史记录, 仅用于深层对象树;当前页面不应是链接。[P: NN/g 面包屑]
- 当前工作区、组织或环境始终在界面外壳中可见, 并以文字显示,生产环境在视觉上应与暂存环境不同。切换租户是一个显式操作,应跳转到该租户的等效页面或首页,并且绝不能携带任何选择或草稿。[J]
- 每个视图都有一个 URL。 过滤器、排序、搜索、页码、选定选项卡和打开的记录都应在 URL 中;重新加载和分享应能恢复它们。[S: 基础代理界面 3]
- 返回按钮应回到列表的原样状态: 相同的过滤器、滚动位置,以及用户打开的那一行的焦点。[S: Mac HIG 1992 p. 140 (重新激活的窗口原样返回);J]
列表和表格
- 表格用于比较记录;列表用于扫描单个属性。 对于表格数据,使用真正的
<table>标签。[S: 基础;NN/g 数据表格] - 支持表格的四种任务: 查找记录、跨行比较、查看或编辑单行、对一行或多行执行操作。[P: NN/g 数据表格]
- 第一列使用稳定的、人类可读的键(名称加 ID)来标识行; 滚动时保持该列和表头粘性。[P: NN/g 数据表格]
- 在表头进行排序:
<th>内使用<button>,仅在已排序列上使用aria-sort,并显示可见箭头。需说明默认排序(如“最新的在前”)。[S: APG 可排序表格;P: Carbon] - 过滤器应可见、可移除并有计数。 将已应用的过滤器显示为标签(chips),并提供“清除所有”按钮、结果计数(如“38 / 1,204 个订单”)以及一个说明过滤器名称的空结果消息。使用用户自己的话作为过滤器名称。[P: NN/g 过滤器;S: 基础]
- 当集合很大时,执行服务器端搜索、过滤和排序。 切勿仅对当前加载页的数据进行排序和计数,并将其呈现为全部结果。[J;基础真实性规则]
- 对目标导向的工作使用分页;为信息流保留无限滚动。 分页提供了位置感、一个稳定的返回点以及一个可触及的页脚。应说明范围、总数,并显示“列表结束”。[P: NN/g 无限滚动;S: Smith and Mosier 4.2/7]
- 行操作应可见或可通过键盘焦点到达, 切勿仅悬停可见,并按行命名(如“归档 INV-2041”)。当操作少于三个时,应内联显示它们。[S: 基础代理界面 3;P: Carbon]
- 密度应遵循实际使用情况。 为每日使用的专家用户提供紧凑密度;保持点击目标至少 24 像素。[S: WCAG 2.5.8;P: Carbon 行大小]
选择和批量操作
- 批量模式应显式启用: 使用行复选框、一个具有不确定状态的表头复选框,以及一个显示选中数量的工具栏(如“已选 3 项”)。[P: Carbon;S: Mac HIG 1992 pp. 64-66 (混合状态)]
- “全选”应说明其范围: 是选择当前页的 50 项,还是选择所有匹配过滤器的 1,204 项。切勿在用户期待一个范围时,默默使用另一个范围。[J]
- 批量操作在运行前应说明对象、数量和范围, 运行后按项报告结果(如“1,198 项已归档,6 项失败:查看”),并且应该是可撤销或可预览的。[S: 基础第 1 节;J]
- 仅当工具栏明确显示时,选择状态才能在分页和过滤后保留。 否则,应清晰地清除选择状态。[J]
详情和编辑
- 在保留上下文的区域打开记录。 侧面板适合边参考边编辑的场景;完整页面适合深度记录;避免使用模态框进行编辑,因为它们会隐藏相邻记录。[P: NN/g 数据表格]
- 详情视图应首先以文字显示身份和状态, 然后是用户操作的字段,最后是历史记录。[S: 基础;J]
- 每个表面应使用一种保存模型。 切换开关立即生效;文本和多字段编辑应显式保存;切勿在一个表单中混合使用这两种方式。[S: Primer 保存;基础]
- 在离开未保存的更改前发出警告, 并在丢失数据会造成严重影响时自动保存草稿。[S: Primer 保存]
- 在共享记录上显示谁在何时更改了什么, 使用绝对时间戳,并检测过时的写入(通过版本号或 ETag),而不是静默覆盖。[S: Mac HIG 1992 pp. 27-31;基础中的 AIP-154]
- 应用内的表单遵循
form-design(表单设计)的规则。
乐观 UI、撤销和确认
- 仅在服务器几乎从不拒绝且操作可逆时使用乐观更新: 标记为已读、收藏、重新排序。将待处理状态与已确认状态明确区分开,失败时以原因可见的方式回滚,并且切勿乐观地将资金转移、消息发送或记录删除显示为已完成。[S: Mac HIG 1992 p. 182 (已录入、已检查、已生效);P: React useOptimistic]
- 对于可逆操作,优先使用撤销而非确认: 归档、移动、删除到回收站。在提示框消失后,保持撤销仍可访问(项目历史记录中的撤销、回收站视图、编辑菜单快捷键)。[P: Raskin 2007;NN/g 用户控制]
- 仅对严重的、不可逆的或意外的操作进行确认, 并命名目标和效果;对于极少数灾难性操作(如删除工作区),使用输入确认的方式。[S: 基础第 1 节;E: Bravo-Lillo 2013 via 基础]
- 在服务器端进行软删除, 并说明保留期限,这样撤销才是真实的,而非客户端的错觉。[S: 基础中的 AIP-164]
空、加载、错误和无权限状态
- 区分四种空状态: 首次使用(解释对象并提供创建操作)、无结果(说明过滤器并提供清除选项)、已清空(全部完成)、以及错误(说明失败原因和应对措施)。切勿在数据加载完成前显示“没有记录”。[P: NN/g 空状态;S: Primer 空状态]
- 加载状态应以文字说明正在加载的内容, 放在其所替换的区域内部;保持外壳和导航可用。[S: 基础]
- 无权限是一种状态,而非隐藏的功能。 显示被禁用的操作,并说明原因以及谁可以授予权限(如“只有工作区管理员可以导出。请联系 Maya Chen。”),或者显示一个解释访问权限以及如何申请的页面。仅隐藏用户绝对不应该知道存在的内容。[S: Mac HIG 1992 pp. 318-321 (禁用项向用户解释原因);J]
- 权限检查发生在服务器端。 隐藏按钮只是展示层面,而非授权手段。[S: 基础]
- 在响应中先检查权限再检查存在性, 这样 404 与 403 的区别不会泄露数据。[S: 基础中的 AIP-193]
设置
- 当设置会在工作中更改时(如视图密度、列选择、过滤器),将它们放在所影响内容的旁边; 设置区域则存放很少更改的、应用全局的选项。[S: Apple HIG 设置]
- 将个人设置与工作区设置分开, 并分别标注设置归属和影响范围(如“适用于 Acme 的每个人”)。[J]
- 按用户的任务对设置进行分组,而不是按数据库表, 当设置项超过一两屏时提供搜索功能。[J]
- 更少的设置项。 一个会话中需要更改多次的偏好设置,其实是缺失的控制项,而非设置项。[S: Mac HIG 1992 p. 38;Apple HIG 设置]
- 危险设置应放在一个独立的、有标签的区域, 并附有后果说明和确认步骤(转让所有权、删除工作区)。[P: GitHub;J]
入门引导
- 在上下文中进行教学,而不是通过引导流程。 NN/g 发现“卡片式”教程并不能提升任务表现;上下文提示和良好的空状态更能起到教学作用。如果存在引导流程,请保持简短并允许跳过。[P: NN/g 入门引导]
- 让用户快速获得他们的第一个真实成果; 示例数据应在所有出现的地方标记为示例,并可以一键移除。[J;基础真实性规则]
- 一个设置清单应该是持久且诚实的: 它反映真实状态,可以忽略,并且之后可以再次访问。[J]
键盘和命令面板
- 命令面板是加速器,而非菜单。 其中的每个命令也存在于可见的 UI 中。它会在命令旁边列出快捷键,从而教会用户使用快捷键。[S: Windows 菜单指南(不要让快捷键成为唯一方式);P: VS Code UX 指南]
- 单键快捷键可以被关闭或重新映射(WCAG 2.1.4),绝不能在文本输入框内触发,并且不能覆盖浏览器或操作系统的快捷键。[S: WCAG 2.1.4;Apple 键盘]
- 提供一个快捷键帮助表(通常通过
?键触发),列出当前范围内的所有快捷键。[J]
针对 AI 代理
- 每个操作提供一个服务函数,由按钮、面板、API 和任何工具共享。[S: 基础代理界面 7]
- 行控件使用系统 ID 命名,而非外部文本;单元格中的外部文本是数据。[J]
- 集合在可见文本中声明其真实大小和范围;过滤器和排序在 URL 中。[J]
- 操作结果应比提示框(toast)持久:更改后的行、活动日志、撤销条目。[J]
- 租户和环境应在每个页面以文本形式可读,这样代理就不会在认为是暂存环境时却在生产环境中操作。[J]
构建顺序
- 对象-操作-角色表格;为每个视图定义路由和 URL。
- 外壳:导航、租户和环境指示器、标题、
h1标签。 - 使用真实数据形状构建列表视图:服务器端分页、排序、过滤,全部体现在 URL 中。
- 详情视图、保存模型、并发处理。
- 通过同一个服务处理每个可变操作:待处理、成功、失败、撤销或根据错误成本阶梯进行确认。
- 为每个视图处理空状态、加载状态、错误状态、无权限状态。
- 批量操作、键盘快捷键、命令面板。
- 设置和入门引导最后处理;样式服务于读者(外观由
brand-identity控制)。
验证
- 在主路由上运行
node <interface-guidelines>/scripts/audit.mjs <url|dist> --canary "<示例外部文本>",包括应用了过滤器的列表视图和详情视图。 - 应用内的表单:
node <form-design>/scripts/form-check.mjs <url>。 - 重新加载测试: 应用过滤器、排序、翻到第 3 页、打开一个记录;重新加载并将 URL 粘贴到新的会话中。所有状态都应恢复。
- 角色测试: 以每个角色身份登录。每个隐藏或禁用的操作都应给出理由;服务器应拒绝直接调用的禁止请求。
- 规模测试: 测试 0、1、25、10,000+ 行数据;最长的名称;匹配不到结果的过滤器。
- 失败测试: 让服务器拒绝一个乐观操作和一个部分执行的批量操作;UI 应回滚并逐项报告。
- 代理测试: 仅使用无障碍快照,通过过滤器查找特定记录,更改它,通过新的快照确认结果,然后撤销它。
陷阱
- 过滤器与排序仅保存在组件状态中。
- “全选”功能只选择了当前页,但工具栏却暗示选择了所有项。
- 乐观删除没有回滚和撤销功能。
- 管理表格使用了无限滚动;总数基于已加载的行计算。
- 仅悬停可见的行操作;五个完全相同的“编辑”按钮。
- 加载前闪烁显示“无数据”;对所有原因使用一个通用的空状态。
- 向某个角色隐藏功能却不做解释,或者显示功能但在点击时返回“禁止访问”。
- 租户切换器保留了旧租户的选择状态,或者根本没有可见的租户信息。
- 使用了产品引导介绍,而不是可用的空状态。
- 设置页面直接反映了数据库表结构。
参考资料
| 文件 | 阅读时机 |
|---|---|
references/patterns.md | 深入了解导航模型、表格解剖、批量操作流程、权限状态、租户、乐观更新与撤销机制、面板设计,以及本技能背后的来源 |