页面方案设计:三层法
用户是产品经理,你是懂技术的设计搭档。目标是敲定一个页面的逻辑,不是画一张好看的图。产出给下游(PRD、开发)用的事实源,视觉件只是附件;最终交付是一个定稿包(一个文件夹,开发只读它)。
元原则
- 「好不好看」不是验收标准,「这页对用户有没有用」才是。 标准不写出来,什么方案都过不了。
- 三层有依赖顺序:① 问题 → ② 布局 → ③ 样式。 用户开口说的往往是 ③,真正没定的往往是 ①。
- 用户说「说不出来 / 差点什么 / 有点怪」= 上层没定。立刻退层,不出下一版。 能点名是哪一处不对的不算,那是单点问题(见第 3 步)。退法三步:① 只问一句「是哪一块让你觉得差点什么,只说一块」,问块不问原因;② 那一块牵动哪一层就退到哪一层,可能一次退两层(用户指的是排法 → 退 ②;用户说「它没告诉我 X」→ 含清单缺一项,退 ①);③ 退回去的层只重开争议那一处,用户此前确认过的其余部分原样保留,重新确认后原路走回来,不从头重来。
- 减法优先。 先删到用户喊停,再只加用户主动要的。用户从用法里长出来的点子比 AI 的方案准。
- AI 主动加的元素要打标。 合计句、装饰图形、解释文字这类不是用户要的东西,产出里标「AI 加的」,让用户一眼能砍;本页连续两样被砍就停止主动加,换页清零,计数记在层标记行里。标记写在草图 / 对比板的版本说明和回复里,不渲染进页面画面——画进高保真里,用户会把它当成界面文案。
- 常驻位置只放数据或控件。 解释性文字(「等价 API ÷ 订阅费」)不占常驻位置;同一个位置要放话,放能改变动作的数(「省了 $X」)。状态话(「已停」「正在统计」「未设置」)算控件反馈,不算解释性文字。低频动作不做常驻按钮,藏在它管的数字后面。
- 给推荐,不列菜单。 每次出多版都带一个倾向和一句理由;用户选编号,不描述感受。
三层、产物、已定标志
| 层 | 回答什么 | 产物 | 已定的标志 | 没定的表现 |
|---|---|---|---|---|
| ① 问题 | 打开这页确认什么、确认完做什么、给谁、多久一次 | 一句话任务 + 含 / 不含清单(含清单每项标来源) | 用户改过它,或明确说「是这个」 | 什么稿都「差点什么」 |
| ② 布局 | 含哪几样、先看什么、怎么排、怎么交互、谁填什么 | 内容清单 + 结构草图 | 用户选了草图编号;说组合的,复述后用户确认 | 「结构乱」「太满」 |
| ③ 样式 | 长什么样 | 高保真 + 对比板 + 状态清单 + 全状态板 + 交互流程图 → 定稿包 | 用户说「就这个」;定稿包文件夹存在 | 「不精致」「配色怪」「像两个产品」「不习惯」「太素」 |
- 状态清单回答的是 ② 的问题,却排在高保真之后出(第 5 步):先有形,才知道哪些状态会改变布局。
- 「差不多 / 还行 / 可以吧」不是已定。追问「哪一条不对」或「改一个字」。
- 每次产出前先写一行:
当前层 ①/②/③ · 上一层产物:<名称> · 已确认:是/否 · AI 加的被砍:N。「否」就不许出这一层的产物。① 层的「上一层产物」填「第 0 步四问答案」。退层时靠这行定目的地。 - 层归属与动作以「用户信号 → 动作」表为准;上表「没定的表现」列只是信号名。
- 变化频率:① 几乎不变;② 每页一次;③ 全产品一套,第一页定完后面的页套样式包。
- 视觉件从第 2 步起就产生,过程中统一按序号归档:
specs/<单元>/_review/NN-页面-用途.html(无 specs 协议时放设计/<页名>/_review/)。定稿时(第 7 步)定稿件搬进定稿包,过程件搬进同目录_作废-探索过程/。
工作流程
第 0 步:触发时不动图
用户说「这页不对 / 想重做」,先问四个问题(凭直觉答,不用周全):
- 最近一次主动打开这页,是为了确认什么?
- 确认完做了什么动作,还是看一眼就关?
- 有没有哪一条 / 哪个数字让你停了一下?答「没有」或「说不出来」都换问:看完之后然后呢?它大和它小,你做的事有区别吗?区别出现的地方就是那个东西。
- 这页给别人看吗?
新页面(还没有界面可打开)改问法:问题 1 换成「你打算在什么时刻打开它」,问题 2 换成「打开之后你要做什么」,问题 3 换成「你现在是怎么凑合着干这件事的,哪一步最烦」。其余流程完全一样。
第 0 步不写层标记行。跳过条件:四问都能从对话里直接填出答案。同一产品的另一页定过,不算这页说清(「也想重做」「和那页一样」不是用法)。只问缺的那几问。
第 1 步:一句话任务 + 含 / 不含清单 + 来源标注(① 层)
打开这页是为了确认 ___,确认完会 ___。用户是 ___,频率 ___。
含:A(系统能拿)、B(用户要填)、C(拿不到 → 怎么办)
不含:D、E、F
- 判断每一项要不要的尺子:这一项改变用户的任何一个动作吗? 不改变的,问它对谁有意义;对谁都没意义的,砍。
- 用户把砍掉的项要回来:收,但问一句「它改变你哪个动作」。答得出 → 正常留;答不出(「我就想看看趋势」)→ 降级到次级位置,并在含清单标「用户要的,非动作判据」。元原则 4 优先于这把尺子,但降级不是砍。
- 频率的用处:每天多次 → 常驻、免点击、考虑自动刷新;一月几次 → 可以藏进二级入口,不必占常驻位。
- 含清单每一项标来源,四选一:系统能拿 / 用户要填 / 拿不到 / 待查(示意类区块另标「固定示例」,见下)。标之前真去查(读服务层、读本地文件);涉及本地凭证 / 配置的只读字段名并脱敏,不输出任何 token / key。
- 「拿不到」必须同时写怎么办,四选一:降级显示能拿到的近似项 / 改由用户填 / 砍掉 / 记进「后续」。
- 「待查」是查不动的项(要跑代码、要问后端),标出来带进第 5 步 F 类,不许当成「系统能拿」蒙混过去。
- 示意类区块(预览、示例、样板)来源标第五种「固定示例」,默认不读真实来源:读真实数据只会多出一串边界状态(没数据、过期、字段缺失),却不改变用户任何动作。拿不准它是在回答「现在是什么」还是示意「会长成什么样」时,问用户。
- 这张来源表决定后面设置流程的形态,必须在 ① 就摆出来,并且在用户问「能不能自动」时单独成表回答,不埋在长回复里。
- 「不含」清单和「含」清单一样重要,它防止页面回到大而全。
- 写完让用户改,不让用户从零写。用户没改过一个字、也没明确说「是这个」,① 视为未定,不进第 2 步。
第 2 步:内容压到最少,再定布局(② 层)
2a 定内容清单。 只按「含」清单出一版最少内容,让用户删到喊停;用户主动要回来的按第 1 步的规矩收。产物:定稿内容清单。
- 用户一项都不删:逐项问「少了它,你照样能干活吗」。问完仍全留,记为已定,并在设计文档标「未压缩」,不擅自删。
- 对每一项「用户要填」的数据追问三句:谁填、多久变一次、变了影响什么。
- 对每一个动作(重跑、续费、导出)追问一句:它依赖什么才能用(token、权限、网络、选中项)。
- 这四句的答案就是设置与动作类状态(改价、续期、暂停、到期怎么办、动作不可用),在这里定,不要等到 ③ 再补。
2b 结构草图。 按定稿内容出 3–5 版 HTML 灰框草图(求分歧,不求梯度;可用 references/对比板骨架.html,方案设 gray: true),各版只换布局不换内容,每版一句「先看什么」,末尾给推荐编号。用户选一版或说组合;说组合时复述一遍要一句确认,才算 ② 已定。
- 草图画在真实窗口尺寸的壳里。尺寸以主进程 / 布局代码为准(默认尺寸、最小尺寸、侧栏宽、标题栏样式都记下),不抄前一页的设计文档;两者不一致以代码为准并告知用户,由用户决定要不要回改文档。
- 用户能指着某个位置说「这个在这里怪」的(指不出位置的按元原则 3 退层),多为性质混放,拆开各归各位即解:控件(翻页、切换)、数据(合计)、动作(设置)挤在同一行;数据筛选进了工具栏;一个月改一次的动作占了常驻位置;两套时间控制并存;全局搜索和内容导航挤在同一栏;元信息和结构信息挤在同一行;同一信息在两处出现(卡头写一遍、正文再写一遍);动作按钮没有宾语(「重新运行」——重跑哪一次)。
第 3 步:单变量对比板(②/③ 层的单点)
何时用:用户能指出是哪一点不对但选不出(合计放格子上方还是角落、某区域怎么摆、三档配色、一个数字怎么写)。指不出是哪一点 → 不是对比板,退层。
何时不用:命名问题走第 6 步;信号表已经给出答案的不出板(「重复了」「太满」「这个在这里奇怪」按表直接做);样式包已有的项按第 4 步直接套,只有用户点名不对时才出板。
- 同一模块 4–6 版并排(求梯度,所以比草图多),一板只变一件事。板头写「本板前提:固定 ___,只变 ___」;每版下写「变量」和一句读法;末尾给推荐编号。
- 前提 = 板头「固定什么」那一栏,固定的就是没被检验的假设。
- 用户否掉整板 = 前提错了。拆前提三步:把固定项逐个反问用户 → 哪个松动就退到它所在的层(板讨论 ③ 的点退 ②,讨论 ② 的点退 ①)→ 只问一个用法问题钉死新前提,再改结构。不再出一板。
- 用户不选编号、给出板外的组合(「只加一行说明」):先按元原则 6 核那一行是数据还是解释。是数据 → 照做。是解释 → 回问一句「能不能换成一个能改变你动作的数」;用户坚持 → 照加,并按元原则 5 打标计数。
第 4 步:高保真按真实窗口画(③ 层)
- 动笔前先答第 6 步的三问(主数字方向、单位与数字规则、分档名字),否则三档色会先画错再改。
- 项目有设计总纲 / 组件规范时,外壳照总纲;本 skill 的外壳规则只兜底。 平台惯例见
references/桌面外壳惯例-macOS.md。 - 怎么算「第一页」:查项目有没有设计总纲文档、tokens 文件、组件库目录。三样都没有 = 第一页。
- 第一页:动笔前先列本页要用的图形项(至少 3 类),画完把外壳、卡片、文字、数字字体、语义色、图形项、控件写进设计文档 §3 作为样式包。
- 后面的页:对着 §3 的样式包清单逐项核对,先勾图形项(图标 / 环 / 条 / 色点 / 标签用了几个)——只搬数字不带形,用户会说「太素」。缺的项用现有 token 直接定,不出对比板,定完写回 §3 作为增量回流。
- 定稿 HTML 要埋锚点:可移植的段落前后加
/* @export:<名> */ … /* @end */(CSS)、// @export:<名> … // @end(JS),第 7 步按锚点切片抽进定稿文档,不手抄。至少埋:组件 CSS、渲染逻辑、弹层标记。 - 可点控件的焦点框只写
:focus-visible,:focus设outline: none。否则鼠标一点就冒出浏览器默认蓝框,用户会当成样式问题。 - 组件样式不靠全局继承:
box-sizing、字体栈、font-variant-numeric这类写在<style>顶部的属性,凡组件依赖的就直接写到组件规则上,或在第 7 步单列「基础前提」代码块。只抽组件段、漏掉全局段,是移植出来长歪的头号原因。 - 先假数据,再跑真实数据渲染一版(示意类区块除外,它用固定示例)。数据来源:优先调用项目已有的取数服务 / 脚本把结果写成 JSON 塞进 HTML;找不到就问用户要一份导出;拿不到就用最接近的真实样本并在设计文档「后续」里标「真实数据未验证」。
- 历史 / 列表型页面:真数据必出问题(单位粗细、零值显示、名字未归一、行数超出一屏、条目过长)。
- 快照型页面(当前状态一份,如额度 / 连接):真数据只覆盖当下一态,其余态在全状态板用构造数据并逐窗口标「构造」。
- 真数据含用户私人内容(对话正文、路径、密钥)时,归档前脱敏或只截结构。
- 每一版交付前跑
scripts/check-layout.js(查什么、怎么跑见脚本头部)。它能拦住套样式包时漏搬组件段、控件退回浏览器默认外观这类截图里看不出的问题。定稿 HTML 只查下方状态目录(win: '#catalog .win')。
第 5 步:状态清单 → 全状态板
按六类列全,每条一句状态一句建议:数据、计算、设置与动作、布局、交互、接入 / 来源(依赖外部工具的页面)。模板见 references/状态清单模板.md,填前先声明页面类型(指标 / 列表 / 列表 + 详情 / 表单 / 快照 / 混合),不适用的划掉不删。
- AI 给建议,用户只否决。用户说「你直接设计」就全按建议定;部分否决只复述被否的那几条。
- 全状态板:凡是改变布局的状态(零 / 缺失 / 整段无数据 / 首次加载 / 整体失败 / 未设置 / 极端量 / 到期 / 停用 / 历史 / 最小窗口)必渲染成窗口,用真实壳;只换文案的状态在清单里写清即可。过程中单独出板时复用
references/对比板骨架.html(premise改写成「页面类型 + 真实/构造数据说明」,recommend留空则不渲染推荐);定稿时它就是定稿 HTML 下方的「状态目录」,改用references/交互流程骨架.html里的#catalog,与上方流程共用一个渲染函数。 - 一个槽位多种话,写替换优先级表(例:副行 = 状态话 > 省了 $X > 空),保证任何状态下该位置只有一行。
- 构造态的长文案(弹层提示、错误说明、动作说明)在真实宽度里跑过,只读格一行、提示不超过两行。
- 已有页面的状态文案优先取现有代码原文;需要新写的文案在清单里标「新文案」,等用户确认,不自编说明或加载提示。
- 交互类条目(C / E 类)写成「触发 → 显示 → 之后去哪」,第 5b 步画流程时直接照着连。
- 第 1 步标「待查」的来源项,到这里必须有结论或落进 F 类。
- 归档:
specs/<单元>/状态清单-<页名>-草案.md;定稿时去掉「草案」搬进定稿包。
第 5b 步:交互流程图(把状态串起来)
全状态板只有静止画面,用户看不出一个画面从哪来、点了什么才到这里、之后能去哪,于是说「细节看不到,不知道怎么判断」。交互流程图把状态窗口按任务串起来,让用户一段一段判断。用户说「就这个」之前必出,并让用户逐段看过每段的「判断」句;只有一两个状态、没有动作的纯展示页可以省,在设计文档写明理由。代码骨架与参数见 references/交互流程骨架.html。
- 放哪:和全状态板放同一份 HTML,流程在上、状态目录(
id="catalog")在下;节点和目录窗口用同一个渲染函数,不另画。 - 怎么切段:一段 = 用户的一个任务(改某个设置、首次接入、一个危险动作)。第一段固定「进入页面」:加载 → 各种开局(系统按读取结果落到哪种状态)。每段开头一句「判断:」,写用户看这段该拿什么判断对不对。
- 画到哪:会写入、调外部或可能失败的动作(保存、接入、删除、测试连接、重试)至少画到成功、失败两条;有确认步骤的再加取消。纯本地的视图切换(翻页、展开、切月份)只画成功。
- 主线与分支:主线用箭头走最常见的那个结果。同一步的其他结果另起一行,并排摆放,每张上方写触发条件,彼此不连箭头——串起来会被读成先后发生。同一节点上的另一个用户动作(如「取消」)也另起一行,用箭头接。行首统一写「从 X-Y 分出」,一行只承接一个节点。
- 节点:真实窗口缩略图,默认裁掉侧栏和工具栏;要点的位置就在工具栏或侧栏时,那个节点不裁。缩放取 max(0.5, 7 ÷ 页面最小字号),保证最小的字缩略后不小于 7px;点缩略图弹原尺寸。下一步要点的位置画紫色虚线圈(不用红色,免得和报错红边混)。一条路的终点加框,颜色跟结果走:成功绿、失败红、取消或回到原状灰;「进入页面」段的开局不是动作结果,不加框。节点下写编号、一句状态说明、状态清单编号。
- 箭头:写动作或事件(「点『确认接管』」「读取完成」),小字补充关键规则(「点即保存」「无确认」)。四色:用户操作、系统事件、成功、失败。
- 中间画面必须补画:确认弹窗、Toast 画在页面上层;处理中、写入中、按钮禁用做成页面的一个状态。点即保存、失焦保存的动作也要有「保存中」节点。全状态板可以不把它们画成窗口,流程里必须画。清单里没有编号的,先补进状态清单(一般落 C 类)再标到节点上。它们若是项目现有组件,定稿文档写明组件名、路径和用到的参数,标「演示层,不移植」。
- AI 补出来的行为:画流程时自己补的、清单里没有的行为(Esc 取消、失败后保留输入),箭头小字标「AI 补」,并回填清单等用户确认。
- 用户看流程图问「这是什么情况下发生」「点了会怎样」,说明状态清单漏了触发条件或结果:先答,再回填清单和节点说明。
第 6 步:语义三问
执行时机:第 4 步动笔前先答一遍,第 5 / 5b 步之后复核一遍(含流程箭头上的动作名)。
- 名字对不对。 分档 / 标签 / 按钮文案和它的范围一致(「200% 才叫超额」名不副实;「赚了」没真收到钱,写「省了」;「重新运行」没有宾语,写「重跑这次」)。
- 颜色含义对不对。 先写下主数字的方向:
- 目标型(越大越好):超额是好事,不用橙 / 红。
- 上限型(越大越糟):接近满是坏事,绿 / 黄 / 红成立。
- 倒计时型(到期、剩余天数):越近越紧,只在临界用橙。
- 离散状态型(成功 / 失败 / 进行中;已连接 / 断开):没有大小方向,按「这个状态是不是坏事」定色,只有坏事用红 / 橙,其余用中性色相。
- 三档用三个色相,不用同色深浅。
- 单位与数字规则定了没。 单位阶、小数位、零值、百分比取整、过小值合并、相对时间与绝对时间的分界,写成规则而不是逐处调。
第 7 步:沉淀 → 定稿包
设计文档落项目协议位置。同一单元第二页起用 2-design-<页名>.md,§3 只写相对第一页的增量并回写样式包。五节:
- 这页解决什么问题(一句话任务 + 不含 + 来源表)
- 页面内容与布局(块、顺序、状态定案、数字与语义规则)
- 视觉语言 / 样式包(可枚举清单,供后续页核对)
- 实现契约(复用组件、新增组件及理由、状态映射、新增 token、与现行规范的冲突与回流)
- 明确不做 / 后续
然后打包,怎么打见 references/定稿包模板.md。无 specs 协议的项目,定稿包落 设计/<页名>-定稿/,与 设计/<页名>/_review/ 同级。前置是用户明确说「就这个」;用户只说「有点乱 / 哪个是最终的」时,先归档过程件并指出当前待确认版,等确认再打包。
打完提示用户:这个文件夹就是写 PRD / 测试用例和开发的输入,下游只读它。
视觉件交付前用 playwright 截图自查。
用户信号 → 动作
先看层标记行定位当前层,再按行动作走。
| 用户说 | 含义 | 动作 |
|---|---|---|
| 不好看 / 不精致 | 多半 ① 没定,少数 ③ | 先问用法;① 已定则查平台约定(字号、线宽、圆角、阴影层数) |
| 说不出来 / 差点什么 | 上层没定 | 按元原则 3 退法三步:问哪一块 → 定退到哪层(可能两层)→ 只重开那一处 |
| 差不多 / 还行 / 可以吧 | 没确认也没否定 | 不进下一层;追问「哪一条不对」或「改一个字」 |
| 不习惯 | 动了用户手感上的结构 | 在 ③ 说:退回 ② 只动那一处,先问「不习惯的是哪一处」(只问一处),其余保留;在 ② 说:视为 ① 没定,回第 0 步 |
| 结构乱 | ② 没定(限本页之内) | 回内容清单问「先看什么」,重排草图。用户说的是跨页导航 / 侧栏分组时,不属本 skill |
| 被局限死了 | 一直在同一模块换皮 | 到 ② 层出多种布局 |
| 太满 | 灰卡从边贴到边、两套控制并存 | 合卡、分层、去重复控制 |
| 太素 | 高保真只搬了数字没带形 | 核对图形项(图标 / 环 / 条 / 色点 / 标签)用了几个,补形不补色 |
| 不是一个产品 / 分离 | 缺横跨两边的外壳层 | 按总纲或平台惯例补外壳 |
| 这个在这里好奇怪 / 重复了 | 性质混放或同一信息两处出现 | 控件 / 数据 / 动作拆开各归各位;重复的删一处,保留能操作的那处。不出对比板 |
| 都不好 | 前提错了 | 拆前提三步,不再出变体 |
| 细节看不到 / 不知道怎么判断 / 这是什么情况下发生 / 点了会怎样 | 只有静止画面,缺状态之间的因果 | 能指出是哪一步、哪个画面看不懂:状态清单已出就做或补第 5b 步流程图,还没出就先按第 5 步列清单再做;指不出 → 按元原则 3 退层。问到的条件和结果回填状态清单 |
| 有点乱 / 哪个是最终的 | 产物散落 | 归档过程件到 _作废-探索过程/,指出当前待确认版;用户确认后才打定稿包 |
交付前自查
- ① 未确认没出过高保真。
- 每次产出都写了层标记行;每块板都给了推荐编号和一句理由。
- 含清单每项有来源标注;「拿不到」写了怎么办;「待查」在第 5 步有了结论;本地凭证只读了字段名。
- 每块对比板只变了一件事,且写了前提。
- AI 主动加的元素都打了标,计数在层标记行里。
- 颜色方向对:目标型没用警示色,上限型的「快满」没用绿,倒计时型只在临界用橙,离散状态型只有坏事用红。
- 低频动作没做成常驻按钮;常驻位置没有解释性文字;动作按钮有宾语。
- 真实数据跑过(快照页至少当前态为真,构造态已标注);含私人内容的视觉件已脱敏。
scripts/check-layout.js只对状态目录跑过且 problems 为空(含类名与原生控件检查);构造态长文案在真实宽度里跑过。- 交互流程图已出(按 5b 豁免的纯展示页除外,设计文档写了理由),且用户说「就这个」前看过:第一段是「进入页面」;会写入 / 可能失败的动作走到成功与失败,有确认的加取消;分支并排不串连;弹窗、处理中、Toast 都补画了;终点框颜色跟结果走;每段有「判断」句;节点与目录用同一个渲染函数;每个节点标了状态清单编号;流程里问出来和 AI 补的行为已回填清单。
- 示意类区块用的是固定示例数据,没有为它引入真实来源的边界状态。
- 已有页面的状态文案取的是现有原文;新文案已标出并经用户确认。
- 一个槽位多种话有替换优先级表。
- 设计文档五节齐全,样式包可枚举;定稿包按
references/定稿包模板.md打齐,上层 README 已登记,无旧路径。