返回所有文章
3647 字13 分钟阅读访问统计加载中

只凭一份设计提炼做 SaaS 后台:把规则锁进主题,把看板骨架搬到新业务

AI前端

我给 Claude Code 一份 33 行的设计提炼,内容是从一张任务看板截图里总结出的尺寸、字号、底色和版面规则,要求它只看这一份文档,用 Next.js 和 shadcn 做一个 SaaS 后台。它没有打开原截图和原项目代码,从建项目到提交用了约 70 分钟,做出概览、客户、账单三个页面,我看完给了 10 分。起作用的是四件事:规则变成数字写死在主题里,看板的骨架整体搬到新业务,一份前后自洽的模拟数据,以及截图和点击脚本的反复核对;提炼没写到的空白,由模型对同类产品的熟悉补上。

成品概览页:四个核心数字做成图表切换页签,下方三列卡片,右侧是收起的「本期已结束」短行和动态

实验设置

提炼文档来自同一仓库里按截图复刻任务看板的另一个实验(sonder-board)。全文 33 行,关键内容都是数字和结构:

  • 骨架:约 38px 的窗口操作栏;240px 固定导航加右侧工作区;工作区里是约 40px 的团队/视图栏、40px 的筛选栏和可滚动看板。
  • 看板:三个展开状态并排,右侧把已结束的状态收成一组短行。任务卡只有三层:编号与负责人,状态与两行以内的标题,优先级和分类标签。
  • 视觉:正文 13/17,辅助 12/16,间隔 8px;背景从浅灰到近白再到白;1px 低对比边框;卡片圆角 6px、控件 5px,图标描边 1.7px;绿色团队、黄色进行中、紫色已结束、彩色标签;字重以 400/500 为主,不添加大标题或统计卡。

文档注明这些数值是对截图的推算。它还列了截图里没有、需要补齐的部分:编辑弹窗、搜索结果、筛选菜单、显示设置、手机侧栏,以及新建时标题为空和存储失败时的反馈。

我的要求只有一句:用这套提炼,只看这个,在同一仓库新建子目录,用 Next.js 加 shadcn 做一个 SaaS 的 dashboard 界面。原项目的截图、源码和提炼里链接的 DESIGN.md 都在范围之外,也没有做竞品调研和生图。

执行方是 Claude Code,模型为 Opus 5.5,单个会话完成,没有派便宜模型当苦工。它先按我的全局规则给了实现方案,方案里点出一处冲突:提炼说不加统计卡,仪表盘却要突出数字。我回复直接做,格式检查、代码规范检查和单元测试都跳过。

最终技术栈是 Next.js 16.3.6(App Router,静态导出)、React 19.2、Tailwind CSS 4.3、shadcn 4.21(Radix 底座、mira 预设)、Recharts 3.8 和 zustand 5,修改存在浏览器本地。虚构业务叫 Tessel,一家向小门店卖预约排班软件的公司,后台看收入、客户和账单。成品源码 96 个文件、9,341 行,其中改过尺寸的 shadcn 组件占 1,690 行。

规则先变成数字,写死在主题里

写页面之前,所有规则先落进一个主题文件。底色四级:窗口和侧栏 #F2F2F3,工作区 #FBFBFB,看板列 #F4F4F5,卡片 #FFFFFF。识别色只有团队绿 #2F9E62、进行中黄 #E5A50A、已结束紫 #6E56CF,加上推导补的风险红 #E5484D。每个数值都在项目的前端设计文档里标了「提炼」或「推导」。

关键一步是直接改 Tailwind 默认档位的定义:

@theme {
  --text-sm: 13px;
  --text-sm--line-height: 17px;
  --text-xs: 12px;
  --text-xs--line-height: 16px;
  --font-weight-semibold: 500;
  --font-weight-bold: 500;
  --radius-md: 5px;
  --radius-lg: 6px;
}

svg.lucide {
  stroke-width: 1.7;
}

shadcn 生成的组件里写满了 text-sm、font-semibold、rounded-lg 这类类名。不改定义,它们会带着 14px 的字、600 的字重和默认圆角进来,页面回到 shadcn 的默认长相;改了定义,按钮、菜单、弹窗不用逐个动就符合提炼,页面代码想违反规则也难。

mira 预设生成的组件底子偏紧,按钮 28px 高、12px 字。其余尺寸用一个脚本批量改:菜单、输入框、选择框在桌面端统一成 13px,弹窗遮罩从 80% 黑加模糊改成 22% 不透明度的近黑色、不加模糊。图标库从预设默认的 Hugeicons 换成 lucide,上面那条样式把所有图标描边统一成 1.7。

看板骨架搬到经营后台

两个产品看起来像一家出的,靠的是结构。提炼里的每个结构都在新业务里找了对应位置:

提炼里的结构在经营后台里
三个展开状态并排,已结束的收成右侧短行概览下方三列是收入、试用、需关注,最右一列「本期已结束」放转付费、试用未转化、已流失、已退款;客户看板默认收起已流失和试用未转化,账单默认收起已支付、已退款、已作废
任务卡三层客户卡三层:编号、月费、负责人;状态图形和两行以内的名称;套餐档位和行业、风险标签。套餐档位借用优先级的三根竖条
黄色进行中、紫色已结束试用中、催收中用黄色半圈;已支付、试用转付费用紫色实心勾
团队/视图栏加筛选栏每个页面都有,筛选条件写进地址栏,刷新和分享都能还原
侧栏只给当前入口整行浅灰照搬;需关注客户、逾期账单等常用视图也做成侧栏入口,计数和目标页一致

概览的列和客户看板的列用的是同一个组件,两个页面的底色、间距和卡片是同一套。

客户看板:试用中、付费中、需关注三列展开,已流失和试用未转化收在最右一列

规则和仪表盘冲突时的处理

「不添加大标题或统计卡」和仪表盘直接冲突。处理办法是把 MRR(每月固定订阅收入)、付费客户、净新增 MRR、月流失率四个数字做成主图卡顶部的切换页签,点哪个,下面的图就画哪个。数字用 20/24、500 字重,是全站唯一大于 13px 的字号;页面名只在视图栏里用 13px 中等字重,没有页面大标题。

颜色按「彩色面积很少,主要用于识别」处理:图表主线用深灰 #2B2B31,上一周期用浅灰虚线,三档套餐用深浅三级灰,净新增为负的柱子用浅灰。其余彩色集中在状态图形、标签前 6px 的小圆点、负责人头像和 12px 的涨跌数字上。

提炼没覆盖的部分按同一套规则推导,包括风险红、20px 的数字字号、图表样式、表单布局、弹窗的 8px 圆角,以及空状态和出错状态的画法。

模拟数据撑起数字

仪表盘看起来真不真,很大程度取决于数字。数据由固定种子的生成器模拟:从 2024 年 6 月 3 日起逐天生成注册,14 天试用,按概率转付费、升级、降级、流失和回流,再按每家客户的订阅记录开出近 100 天的账单。所有指标都从这一份数据现算。

第一次生成后先打印统计,有两处不像真公司:企业版占了 68.9% 的收入;最近 30 天一家流失都没有,流失率会显示 0.0%。调高流失概率、降低企业版比例和扩容幅度,再压低企业版价格区间后,企业版占 54.7%,近 30 天月流失率 2.0%。成品数据是 314 家客户,其中付费中 100 家、需关注 10 家、试用中 5 家、已流失 20 家、试用未转化 179 家,另有 336 张账单,MRR 为 ¥131,797。

数字同源带来联动。概览的套餐构成三行相加等于 MRR;新建一家付费客户,MRR、付费客户数、净新增和动态会一起变;收回一笔欠款,逾期账单和需关注客户会一起减少。

不起服务的截图和点击核对

我的全局规则禁止自行启动开发服务器,所以核对方式是:先静态导出到 out/,再用 Playwright 驱动本机 Edge(channel: "msedge"),把发往 http://tessel.local/* 的请求拦下来,用 route.fulfill 直接返回 out/ 里的文件,全程没有服务进程:

await context.route("http://tessel.local/**", async (route) => {
  let path = decodeURIComponent(new URL(route.request().url()).pathname);
  if (path.endsWith("/")) path += "index.html";
  let file = join(OUT, path);
  if (!extname(path)) file += ".html"; // /customers → customers.html
  const body = await readFile(file);
  await route.fulfill({ status: 200, body, contentType: TYPES[extname(file)] });
});

截图脚本拍 10 张页面图,覆盖 1440、1280、1024、768、390 五种宽度;再拍 15 个交互状态,包括筛选菜单、筛选后的概览、显示设置、搜索结果与空结果、客户详情、新建时名称为空、本地保存失败、重试扣款失败、手机侧栏和空筛选结果。另补拍 4 张:键盘焦点、收起侧栏、超长名称的看板和列表。

点击脚本有 13 项核对,全部通过:

  • 套餐构成三行相加等于 MRR 页签,都是 ¥131,797。
  • 侧栏「需关注客户」计数等于概览需关注列,都是 10。
  • 按 C 新建一家团队版付费客户:MRR 从 ¥131,797 变成 ¥132,496,付费客户从 110 变成 111,近 30 天净新增从 ¥14,677 变成 ¥15,376,动态第一条是这家新客户。
  • 把 Sparrow Dance Academy 那张扣款失败的账单确认为已收款:逾期账单从 7 变成 6;这家客户唯一的风险原因是扣款失败,需关注客户随之从 10 变成 9,客户页的需关注分组也是 9。
  • 刷新后新建的客户还在,搜索能找到它,搜不存在的名字会给出空结果提示,控制台没有报错。

核对时发现并修掉的问题

  • 1280 宽时,指标页签里的数字和涨跌挤不下,有的换行、有的不换,四格高低不齐。改成名称和涨跌同一行、数字单独一行。
  • 按状态筛选后看板只剩一列,这一列被拉满整个工作区。给列宽加了 360px 上限。
  • 键盘焦点描边看不见。Tailwind 4 把 outline-none 编译成 --tw-outline-style:none;outline-style:none,而 focus-visible:outline-2 编译成 outline-style:var(--tw-outline-style);outline-width:2px,焦点时读到的样式仍是 none。给所有焦点样式补上 focus-visible:outline-solid 后描边出现。
  • 筛选菜单里的数量、搜索结果里的编号用了 #8F8F98,12px 字在白底上对比度约 3.2:1,低于 4.5:1。改用 #62626B,约 6.0:1。
  • Next.js 16.3.6 在这台 Windows 11 上静态导出时,把分段预取文件写成子目录 customers/__next.customers/__PAGE__.txt,浏览器请求的却是 customers/__next.customers.__PAGE__.txt,控制台报 404。页面跳转走的是 customers.txt,不受影响。构建后加了一个脚本复制一份点号文件名;没在 Linux 上验证是否同样会出现。
  • 数据存在浏览器本地。开发模式下服务端用样例数据渲染,浏览器用本地数据渲染,两边一旦不同,React 接管页面时就会报水合不一致。外框改成等浏览器就绪后再渲染,服务端只输出骨架。这一条是按推理做的预防,没有启动开发服务器验证。

评价和边界

我给了 10 分,前提是它没看过原截图。我追问之后,它列出了会话里读过的全部文件:原项目只打开过 设计提炼.md;另外读过仓库根目录的 readme、路线图和实验模块文档,看过一次提交记录里 airbnb-home 的文件名;看过的图片全是自己导出后截的图。

这个分数有两部分来源。一是提炼写得准:尺寸、字号、圆角、线宽都是数字,还写明了结构和意图。二是模型的先验:Claude Code 自己的判断是,这份提炼明显出自类似 Linear 的看板,导航怎么分区、筛选条长什么样、图表怎么画这些提炼没写的地方,它靠对这类产品的熟悉补上。「只看这个」能挡住文件,挡不住训练里见过的界面。

要把两部分分开测,下一步可以换一张找不到知名原型的截图来提炼,比如小众工具、手绘风或排版不常规的界面,再只凭提炼复刻一次。

上一轮实验的结论是先做信息设计,再把风格写成带数字的规格派活,见AI 做的页面像模板货又空心。这次只拿一份数字规格当全部输入,设计语言带到了另一种产品上;先验在其中占多少,还要靠上面那组对照实验来回答。

这套设计适合什么产品

我又问了一题:这套提炼适合做什么网站。Claude Code 给的判断标准是三个问题:用户是否每天在里面待很久、反复处理同一类东西;这些东西是否有少量明确的状态;是否主要在电脑上用。三个都是,就适合。

  • 适合:任务、项目和缺陷跟踪;按阶段推进的业务工具,如销售线索、招聘流程、客服工单、内容审核与发布排期、采购审批;订单、库存、账单这类运营后台;开发者工具,但要补暗色主题;待办、消息中心、知识库这类个人效率工具。
  • 要改才能用:指标为主的数据看板,图表线一多,灰阶就分不清,要放开一组图表专用色;帮助文档的正文要放大到 15~16px;同一产品的官网首页需要大标题。
  • 不适合:营销落地页和品牌官网,消费者电商前台,新闻、博客这类长文阅读,手机优先的消费类应用,面向老人或低视力人群的产品,作品集和游戏。

参考资料

喜欢这篇文章?

如果这篇文章帮到了你,可以请我喝杯咖啡,支持我继续写下去。

评论