组件库改版怕截图标不清?用 Storybook 搭本地组件预览页,再借 cpolar 给前端和测试临时验收

发布时间:2026/8/9 15:17:29
组件库改版怕截图标不清?用 Storybook 搭本地组件预览页,再借 cpolar 给前端和测试临时验收 组件库改版怕截图标不清用 Storybook 搭本地组件预览页再借 cpolar 给前端和测试临时验收组件库一改版最怕的不是写代码而是对齐细节按钮 hover 后颜色深一点、禁用态文字灰一点、卡片间距差 4px、弹窗遮罩透明度不对。这些东西靠截图聊很快就变成“你看我标红的这里”“我手机上看到不是这样”。我更推荐的做法是本地用 Storybook 把组件状态整理成一页页可交互 Demo再用 cpolar 临时开一个 HTTPS 地址让前端、UI、测试同事短时间在线验收。这篇不讲复杂工程化也不讲生产部署。目标很明确在本机跑一个只监听127.0.0.1的 Storybook 预览页里面只放按钮、卡片、表单、弹窗这些演示组件然后用 cpolar 短时开放给同事看验收结束马上关掉。为什么截图越发越乱组件库改版后的验收经常卡在“状态”上。按钮不是只有默认态还有 primary、danger、disabled、loading、hover、focus表单不是只有空白态还有必填提示、长度校验、提交中弹窗也不只是打开那一刻还包括遮罩、关闭、确认、取消、滚动内容。我踩过最典型的坑是一轮改版里大家在群里来回发了十几张图。UI 说按钮圆角不对前端说自己看到的是新样式测试又补了一张低分辨率截图最后谁也说不清当前讨论的是哪一次提交。等问题定位清楚真正要改的只是一行 CSS。截图能说明静态布局但说明不了交互。更麻烦的是同一个组件在不同页面里被业务样式污染后截图里到底是组件库的问题还是业务页面的问题很难一句话讲清楚。Storybook 的价值就在这里把组件从业务页面里拿出来只看组件本身。每个状态都是一个 story同事点开链接就能切换、操作、复现不用拉代码也不用搭完整业务环境。我会把它当成“组件验收桌面”按钮放按钮区卡片放卡片区表单和弹窗各自独立。谁反馈问题就直接报 story 名称和状态名。这样改版讨论会从“看这张图的左下角”变成“Button / Loading 的禁用颜色偏浅”沟通质量完全不一样。准备一个干净的示例项目这里用 Vite React 做演示。真实项目里你可以把步骤放在组件库仓库的临时分支也可以单独起一个 demo 项目。安全起见我建议演示项目只放公开样式和示例组件不要直接导入真实业务接口。mkdir storybook-component-review cd storybook-component-review npm create vitelatest . -- --template react-ts npm install安装 Storybooknpx storybooklatest init初始化完成后项目里会出现.storybook目录和src/stories示例文件。为了让预览页只在本机监听启动时加上 host 参数npm run storybook -- --host 127.0.0.1 --port 6006浏览器打开http://127.0.0.1:6006这里有个边界要守住不要为了“方便同事访问”把 Storybook 直接监听到所有网卡。本地预览就是本地预览对外访问交给 cpolar 的临时隧道处理范围更清楚也更方便收尾。写一个按钮组件把状态摆出来先创建组件目录mkdir -p src/components/Button新建src/components/Button/Button.tsximport ./Button.css; type ButtonProps { children: string; variant?: primary | secondary | danger; disabled?: boolean; loading?: boolean; onClick?: () void; }; export function Button({ children, variant primary, disabled false, loading false, onClick, }: ButtonProps) { return ( button className{demo-button demo-button--${variant}} disabled{disabled || loading} onClick{onClick} {loading ? 处理中... : children} /button ); }新建src/components/Button/Button.css.demo-button { border: 0; border-radius: 8px; padding: 10px 18px; color: #fff; cursor: pointer; font-size: 14px; } .demo-button--primary { background: #2563eb; } .demo-button--secondary { background: #64748b; } .demo-button--danger { background: #dc2626; } .demo-button:hover:not(:disabled) { filter: brightness(0.92); } .demo-button:disabled { cursor: not-allowed; opacity: 0.55; }再写 storysrc/components/Button/Button.stories.tsximport type { Meta, StoryObj } from storybook/react; import { Button } from ./Button; const meta: Metatypeof Button { title: 组件验收/Button 按钮, component: Button, args: { children: 保存设置 }, }; export default meta; type Story StoryObjtypeof Button; export const Primary: Story { args: { variant: primary } }; export const Danger: Story { args: { variant: danger, children: 删除 } }; export const Disabled: Story { args: { disabled: true, children: 不可点击 } }; export const Loading: Story { args: { loading: true, children: 提交 } };这样 UI 同事验收按钮时不需要你发四张图。一个页面里就能看到默认、危险、禁用、加载状态。卡片布局把间距和内容长度测清楚卡片最容易出现“设计稿看着挺好真实文案一长就炸”的问题。我们给卡片写两种状态短标题和长标题。src/components/Card/Card.tsximport ./Card.css; type CardProps { title: string; desc: string; tag?: string; }; export function Card({ title, desc, tag 组件库 }: CardProps) { return ( section classNamedemo-card span classNamedemo-card__tag{tag}/span h3{title}/h3 p{desc}/p /section ); }src/components/Card/Card.css.demo-card { width: 320px; border: 1px solid #e2e8f0; border-radius: 14px; padding: 18px; background: #fff; box-shadow: 0 8px 24px rgba(15, 23, 42, 0.08); } .demo-card__tag { display: inline-block; margin-bottom: 10px; color: #2563eb; font-size: 12px; } .demo-card h3 { margin: 0 0 8px; font-size: 18px; } .demo-card p { margin: 0; color: #64748b; line-height: 1.7; }src/components/Card/Card.stories.tsximport type { Meta, StoryObj } from storybook/react; import { Card } from ./Card; const meta: Metatypeof Card { title: 组件验收/Card 卡片, component: Card, }; export default meta; type Story StoryObjtypeof Card; export const Normal: Story { args: { title: 基础信息卡片, desc: 用于展示一段简短说明。 }, }; export const LongText: Story { args: { title: 组件库改版后的长标题卡片展示效果, desc: 这里放一段稍长的演示文案用来检查换行、间距、阴影和边框是否符合验收要求。, }, };这一步很适合给 UI 看。对方可以直接指出标题行高、卡片宽度、阴影强度、标签颜色哪里不对。比在聊天窗口里圈图要省心很多。表单校验别只截空表单表单验收一定要看错误态。下面写一个最小登录表单不接真实接口只在前端本地做演示校验。src/components/LoginForm/LoginForm.tsximport { useState } from react; import ./LoginForm.css; export function LoginForm() { const [email, setEmail] useState(); const [touched, setTouched] useState(false); const invalid touched !email.includes(); return ( form classNamedemo-form onSubmit{(e) e.preventDefault()} label邮箱/label input value{email} onBlur{() setTouched(true)} onChange{(e) setEmail(e.target.value)} placeholderdemoexample.com / {invalid p classNamedemo-form__error请输入正确的邮箱格式/p} button typesubmit提交演示/button /form ); }src/components/LoginForm/LoginForm.stories.tsximport type { Meta, StoryObj } from storybook/react; import { LoginForm } from ./LoginForm; const meta: Metatypeof LoginForm { title: 组件验收/LoginForm 表单, component: LoginForm, }; export default meta; type Story StoryObjtypeof LoginForm; export const Basic: Story {};测试同事打开后可以亲自输入一段错误邮箱检查提示文案、红色样式、输入框焦点状态。这里全程没有真实登录没有请求后端也没有 token更适合作为远程临时验收页面。弹窗交互把打开和关闭跑一遍弹窗截图最容易漏掉遮罩、关闭按钮、确认按钮这类细节。写一个本地 Demo 就够了。src/components/ConfirmModal/ConfirmModal.tsximport { useState } from react; import { Button } from ../Button/Button; import ./ConfirmModal.css; export function ConfirmModal() { const [open, setOpen] useState(false); return ( Button onClick{() setOpen(true)}打开弹窗/Button {open ( div classNamedemo-modal__mask div classNamedemo-modal h3确认提交改版方案/h3 p这是演示弹窗只用于检查遮罩、间距和按钮布局。/p div classNamedemo-modal__actions button onClick{() setOpen(false)}取消/button button onClick{() setOpen(false)}确认/button /div /div /div )} / ); }src/components/ConfirmModal/ConfirmModal.stories.tsximport type { Meta, StoryObj } from storybook/react; import { ConfirmModal } from ./ConfirmModal; const meta: Metatypeof ConfirmModal { title: 组件验收/ConfirmModal 弹窗, component: ConfirmModal, }; export default meta; type Story StoryObjtypeof ConfirmModal; export const Basic: Story {};跑到这里本地 Storybook 已经能覆盖按钮状态、卡片布局、表单校验、弹窗交互四类高频验收点。本机先验一遍再发给别人发送链接前我会先在本机做一遍快速检查npm run storybook -- --host 127.0.0.1 --port 6006检查清单很简单Storybook 左侧分组是否清楚比如统一放在“组件验收”下面按钮的禁用、加载、危险状态是否都能看到卡片长文案是否撑破布局表单错误提示是否能触发弹窗打开、取消、确认是否能正常关闭页面里没有真实客户名称、内部域名、接口地址和凭据。确认没问题后再进入 cpolar 环节。用 cpolar 生成临时 HTTPS 地址cpolar 的作用不是替代部署而是给本机服务开一个短时入口。Storybook 仍然只监听127.0.0.1:6006对外访问通过 cpolar 转发。这个区别要讲清楚Storybook 不变成线上站点cpolar 也不承担长期访问。它只是把你本机那一份演示页面临时递给远程同事验收。验收窗口结束隧道关闭链接失效事情就收住了。如果本机已经安装并登录 cpolar直接执行cpolar http 127.0.0.1:6006命令运行后终端会显示一个 HTTPS 访问地址格式类似https://xxxx.cpolar.top把这个地址发给前端、UI、测试同事即可。建议附上一句说明减少误用这是组件库改版验收临时链接只包含按钮、卡片、表单、弹窗 Demo。 请在今天 18:00 前查看交互状态验收结束后链接会关闭。手机端同事也可以直接打开这个 HTTPS 地址检查移动端宽度下的卡片和弹窗。测试同事可以点击表单、触发错误提示前端同事可以核对按钮状态和组件行为。这里一定要守住安全边界这类临时预览页最容易犯的错是顺手把真实业务环境也带进来。我的规则很硬只放演示组件和公开样式不接真实业务接口。具体边界如下不导入真实客户数据文案全部用demoexample.com、示例标题、虚构描述不调用登录、订单、支付、用户资料等真实接口不把数据库端口、缓存服务、Docker 控制接口、后台管理页挂到 cpolar不在 story 里写 token、cookie、内部域名、源码仓库私密路径不把完整业务页面当作“组件预览”直接开放cpolar 链接只给参与验收的人限定时间使用。Storybook 适合展示组件不适合展示秘密。把这个边界讲清楚团队用起来会更放心。远程验收时怎么收反馈我一般会让同事按组件分组反馈不要混在一条消息里Button危险按钮 hover 后颜色过深disabled 透明度 OK。 Card长标题两行时底部间距偏小。 LoginForm错误提示文案 OK输入框红框需要加粗。 ConfirmModal遮罩透明度 OK确认按钮位置需要和设计稿对齐。这里还有一个小技巧每次改动后在群里只说“已更新 Button / Danger 和 Card / LongText”。不要把整个组件库都重新拉进讨论。验收对象越小反馈越准返工越少。这样前端改起来很快。每改一轮本地热更新会刷新 Storybook同事刷新临时链接就能继续看。整个过程不需要反复导出截图也不需要让测试拉分支启动项目。如果验收跨设备记得让手机端同事也看一次。组件库的问题经常藏在小屏幕里尤其是弹窗宽度、按钮换行和卡片内容溢出。临时开放后的收尾清单验收结束后不要把链接晾着。我的收尾动作固定做一遍关闭 cpolar 进程撤回这次临时 HTTPS 链接停止 Storybook 服务确认127.0.0.1:6006不再提供预览在群里说明临时链接已关闭旧链接不再用于验收清理演示账号、演示分支、演示数据和临时文案检查 story 文件保留本地自用边界只留下组件演示和公开样式如果组件 Demo 还要长期保留把它纳入正常代码评审不把 cpolar 链接当长期入口。说白了Storybook 负责把组件状态讲清楚cpolar 负责把本机预览短时间递到远程同事面前。两者配合起来刚好解决“截图标不清、交互验不了”的老问题。这套流程不重但很实用。尤其是组件库改版这种细节密集的活把按钮、卡片、表单、弹窗拆开给大家看反馈会清楚很多沟通成本也会降下来。