跳到主要内容

产品设计与调试

不是设计师,也能做出好看的产品

说实话,大多数人一开始做产品的时候,最头疼的不是代码,而是——"这也太丑了吧"。

别慌。你不需要成为设计师,你需要的是两个能力:抄得好管住手

"抄得好"不是直接复制粘贴,而是找几个做得好的产品,拆解它的布局、配色、间距。然后用 AI 帮你生成类似的样式。

"管住手"更重要。一个页面最好只用一到两种主色,一种正文字体,按钮统一一种样式。少就是多


设计原则:四条就够了

1. 留白(Whitespace)

留白不是浪费空间,是让页面呼吸。新手最容易犯的错误就是把所有元素挤在一起。

实操建议: 卡片之间至少 16px 间距,段落之间至少 24px,页面主体留足左右边距。不知道留多少?试试扩大一倍,往往效果更好。

<!-- ❌ 太挤 -->
<div class="p-2">
<h2 class="text-sm mb-1">标题</h2>
<p class="text-xs">内容...</p>
<button class="mt-1 text-xs">按钮</button>
</div>

<!-- ✅ 舒服的留白 -->
<div class="p-6">
<h2 class="text-xl mb-4">标题</h2>
<p class="text-base text-gray-600 mb-6">内容...</p>
<button class="px-4 py-2">按钮</button>
</div>

2. 对齐(Alignment)

页面上所有元素都应该沿着某条隐形的线对齐。文字左对齐、按钮左对齐、卡片左对齐——对齐了就整洁了。多用 Tailwind 的 flexgriditems-startgap-4 来解决对齐问题。

3. 一致性(Consistency)

同样的元素在不同地方应该长得一样。全站只用一种主色,按钮样式统一,字体大小只用 3-4 种(标题 24px、副标题 18px、正文 16px、小字 14px),圆角统一用 rounded-lgrounded-md

4. 颜色(Color)

颜色是最容易搞砸的部分。记住这个配色公式:

  • 一个主色:用于按钮、链接、强调元素,推荐 blue-600indigo-600
  • 灰色系gray-900(标题)、gray-600(正文)、gray-300(边框)
  • 状态色:绿色(成功)、黄色(警告)、红色(错误)
  • 背景:纯白或 gray-50

完全不会配色?去 coolors.co 按空格键随机生成方案,选一组顺眼的用。


设计规范速查

这一节是「数字版速查手册」——打开这个页面,所有常用设计数值一目了然。不需要背,用的时候查一下就行。

字体系统(Typography)

字号阶梯

整个产品只用 4-5 种字号,从下面的阶梯里挑:

pxremTailwind 类名中文名用途
12px0.75remtext-xs辅助字标注、版权信息、时间戳
14px0.875remtext-sm小正文次要说明、表格内容、标签
16px1remtext-base正文默认正文大小,最核心的字号
18px1.125remtext-lg大正文副标题、文章引导语
20px1.25remtext-xl小标题卡片标题、段落标题
24px1.5remtext-2xl中标题页面区域标题
30px1.875remtext-3xl大标题页面主标题
36px2.25remtext-4xl展示字营销页大标题
48px3remtext-5xl超大展示Landing Page Hero 文字

建议: 一个页面最多 4 种字号组合。典型的 SaaS 产品只用:正文 16px + 小字 14px + 标题 20px + 大标题 24px。

行高(Line-height)

行高决定文字的「呼吸感」。记住三条规则:

  • 正文line-height: 1.6 或 Tailwind 的 leading-relaxed(约 25.6px),中文内容这个值最舒服
  • 标题line-height: 1.3leading-tight(标题字大,行高可以紧凑些)
  • 小字line-height: 1.5leading-normal,字小了行高反而不能太紧
/* 实际项目中这样定义 */
body { line-height: 1.6; }
h1, h2, h3 { line-height: 1.3; }
small, .text-sm { line-height: 1.5; }

字重(Font-weight)

日常开发只用三种字重就够了:

  • 400(normal):正文、描述文字
  • 500(medium):按钮文字、导航项、强调词
  • 600-700(semibold/bold):标题、重要数字
/* ❌ 到处用 font-bold 会显得粗鲁 */
/* ✅ 大部分文字用 font-normal,关键地方用 font-semibold */
h1 { font-weight: 700; }
h2, h3 { font-weight: 600; }
button { font-weight: 500; }
p { font-weight: 400; }

中文字体栈

不用下载任何字体文件,用系统自带的就好:

/* 推荐的跨平台中文字体栈 */
font-family:
-apple-system, /* macOS/iOS 苹方 */
BlinkMacSystemFont, /* macOS Chrome */
"Segoe UI", /* Windows */
"PingFang SC", /* macOS 中文 */
"Microsoft YaHei", /* Windows 中文 */
"Hiragino Sans GB", /* macOS 旧版中文 */
"Noto Sans SC", /* Linux/Android 思源黑体 */
sans-serif;

/* 英文为主的项目,搭配 Inter 字体 */
font-family: "Inter", -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif;

小贴士: 如果你用 Tailwind CSS,它默认的 font-sans 已经包含了不错的字体栈。直接用就行,不用折腾。


颜色系统(Color)

60-30-10 配色法则

这是室内设计界流传的经典法则,做 UI 同样好用:

  • 60% — 背景色:白色、gray-50gray-100,大面积铺底
  • 30% — 文字/辅助色gray-900(标题)、gray-600(正文)、gray-300(边框)
  • 10% — 强调色(主色):按钮、链接、图标高亮——所有可交互元素
举例:一个典型的 SaaS 仪表盘
├── 60% 背景:white / gray-50
├── 30% 文字层次:gray-900 标题 / gray-600 正文 / gray-400 辅助
└── 10% 主色:blue-600 用于所有按钮和链接

怎么选主色

不知道选什么颜色当主色?直接从这几个「安全色」里选一个:

颜色Tailwind 值适合场景
蓝色blue-600 #2563EB通用 SaaS、工具类、科技类
靛蓝indigo-600 #4F46E5创意类、设计类、稍有个性
翠绿emerald-600 #059669金融、健康、环保
紫色violet-600 #7C3AED社交、娱乐、AI 产品
橙色orange-500 #F97316电商、美食、年轻化

选好之后: 主色只用 *-600 这个色阶(按钮背景),*-50 做浅底色(提示条背景),*-700 做 hover 状态。

灰度色板

文字和边框全部用灰色系,不要用纯黑(#000),太刺眼:

名称Tailwind色值用途
gray-50bg-gray-50#F9FAFB页面背景
gray-100bg-gray-100#F3F4F6卡片背景、输入框背景
gray-200border-gray-200#E5E7EB边框、分割线
gray-300text-gray-300#D1D5DB占位文字、禁用状态
gray-400text-gray-400#9CA3AF辅助说明文字
gray-500text-gray-500#6B7280次要正文
gray-600text-gray-600#4B5563正文文字
gray-700text-gray-700#374151重要文字
gray-800text-gray-800#1F2937标题文字
gray-900text-gray-900#111827最重要标题

语义色(状态色)

这四个颜色是全球通用约定,不要自己发明

状态颜色Tailwind用途
成功绿色green-600 #16A34A操作成功、在线状态、正增长
警告黄/琥珀色amber-500 #F59E0B需注意、即将到期、余额不足
错误红色red-600 #DC2626操作失败、表单错误、离线
信息蓝色blue-600 #2563EB提示、新功能、帮助
// 表单错误提示的规范用法
<p className="text-red-600 text-sm mt-1">邮箱格式不正确</p>

// 成功提示
<div className="bg-green-50 border border-green-200 text-green-700 rounded-lg p-4">
保存成功!
</div>

暗色模式(Dark Mode)

如果要支持暗色模式,记住这些调整:

亮色模式 → 暗色模式 的对应关系:
├── bg-white → bg-gray-900 (背景)
├── bg-gray-50 → bg-gray-800 (卡片背景)
├── text-gray-900 → text-gray-100 (标题)
├── text-gray-600 → text-gray-400 (正文)
├── border-gray-200→ border-gray-700(边框)
└── 主色不变,但 hover 态用亮一档的色阶

Tailwind 的 dark: 前缀可以直接用:

<div class="bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100">
自动适配亮暗模式
</div>

间距系统(Spacing)

4px 基准网格

所有间距都是 4 的倍数。这是行业标准,Tailwind 默认就是基于 4px 的。

间距阶梯速查

Tailwind常见用途
4pxp-1 gap-1图标和文字之间的间距
8pxp-2 gap-2紧凑组件内部间距、小按钮内边距
12pxp-3 gap-3普通组件内部间距
16pxp-4 gap-4最常用间距,卡片内边距、元素之间默认间距
20pxp-5 gap-5大组件内部间距
24pxp-6 gap-6段落间距、区块之间的分隔
32pxp-8 gap-8大区块间距
40pxp-10页面区域内边距
48pxp-12Section 之间的间距
64pxp-16页面顶部/底部大留白
80pxp-20Landing Page Section 间距
96pxp-24超大留白,Hero 区域

记忆口诀: 16px 是默认,8px 紧凑,24px 宽松。拿不准就用 16px。


布局系统(Layout)

12 列栅格

Web 布局标准是 12 列,因为 12 能被 2、3、4、6 整除,分栏超灵活:

12 列栅格示意:
|1|2|3|4|5|6|7|8|9|10|11|12|

常见分栏:
├── 1 等分:12列(全宽内容)
├── 2 等分:6+6 列(左右两栏)
├── 3 等分:4+4+4 列(三列卡片)
├── 4 等分:3+3+3+3 列(四列图标网格)
├── 左侧栏:3+9 列(侧边栏 + 主内容)
└── 右侧栏:8+4 列(主内容 + 右侧面板)

容器宽度

/* 不同屏幕的最大内容宽度 */
sm: 640px /* 手机横屏 */
md: 768px /* 平板 */
lg: 1024px /* 小桌面 */
xl: 1280px /* 桌面 */
2xl: 1536px /* 大屏 */

/* 通常用这个 */
.container { max-width: 1200px; margin: 0 auto; padding: 0 16px; }

断点系统

Tailwind 默认断点,记不住就查这个:

名称宽度设备
sm:≥ 640px手机横屏
md:≥ 768px平板
lg:≥ 1024px笔记本
xl:≥ 1280px桌面显示器
2xl:≥ 1536px大屏显示器

移动优先写法: 先写手机样式,再用 md:lg: 加桌面样式:

<!-- 手机单栏,平板以上双栏 -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<!-- 卡片们 -->
</div>

常见布局模式

模式适用场景Tailwind 实现
居中单列文章、表单max-w-2xl mx-auto px-4
左侧导航后台管理grid grid-cols-[240px_1fr]
卡片网格产品列表grid grid-cols-1 md:grid-cols-3 gap-6
圣杯布局传统网页grid grid-rows-[auto_1fr_auto] min-h-screen

组件库推荐

别从零写 UI 组件,用现成的组件库能节省 60% 的样式时间。

shadcn/ui ⭐ 首推

是什么: 不是传统 npm 包,而是一组你可以直接复制到项目里的组件代码。基于 Radix UI + Tailwind CSS。

为什么推荐:

  • 代码在你的项目里,想改就改,完全可控
  • 设计简洁现代,开箱即用就很好看
  • 和 Tailwind CSS 完美搭配
  • 社区活跃,组件丰富(按钮、表单、弹窗、表格、图表……)
npx shadcn@latest init # 初始化
npx shadcn@latest add button # 添加按钮组件

Radix UI

是什么: 无样式的可交互组件原语(Primitives),处理了所有无障碍访问(Accessibility)的脏活累活。

适合谁: 想要完全自定义样式,但不想自己处理键盘导航、焦点管理、屏幕阅读器兼容的人。shadcn/ui 底层就是它。

npm install @radix-ui/react-dialog @radix-ui/react-dropdown-menu

Headless UI

是什么: Tailwind CSS 团队出品的无样式组件库,和 Tailwind 配合最丝滑。

适合谁: 重度 Tailwind 用户,想要更轻量的方案。

npm install @headlessui/react

三者对比:

shadcn/uiRadix UIHeadless UI
样式有默认样式(可改)无样式无样式
Tailwind原生搭配需自己配原生搭配
学习成本
组件数量最多较少
适合场景快速出活深度定制Tailwind 项目

我的建议: 新项目直接用 shadcn/ui,省心省力,设计质量有保障。


图标系统

图标不要到处搜零散的 SVG,选定一个图标库全家桶用到底。

Lucide Icons ⭐ 首推

  • 图标数量: 1500+
  • 风格: 线性(outline),简洁统一
  • 特点: Feather Icons 的社区延续版,维护活跃
  • 安装: npm install lucide-react
import { Home, Settings, User, Search } from 'lucide-react'

<Home size={20} strokeWidth={1.5} />
<Settings className="text-gray-500" />

Heroicons

  • 图标数量: 300+
  • 风格: 有 outline 和 solid 两套
  • 特点: Tailwind 团队出品,和 Tailwind 项目天然搭配
  • 安装: npm install @heroicons/react
import { HomeIcon } from '@heroicons/react/24/outline'
import { HomeIcon as HomeSolid } from '@heroicons/react/24/solid'

Phosphor Icons

  • 图标数量: 7000+
  • 风格: 六种粗细可选(thin/light/regular/bold/fill/duotone)
  • 特点: 数量最多,风格多变
  • 安装: npm install phosphor-react
import { House, Gear, User } from 'phosphor-react'
<House weight="bold" size={24} />

对比总结:

LucideHeroiconsPhosphor
数量1500+300+7000+
风格线性线性+填充6 种风格
包大小最小
推荐场景通用首选Tailwind 项目需要大量图标

实操建议: 项目开始时选一个,全站统一。混用不同图标库是最常见的设计事故之一。我个人推荐 Lucide,数量够用、风格统一、包体积小。


从好设计中"偷师"

找灵感的地方:

  • Mobbin:真实 App 页面截图,按类型分类,最适合直接参考
  • Dribbble:设计师作品展示
  • shadcn/ui:免费组件库,设计简洁现代

怎么"抄": 找一个好看的页面截图 → 丢给 AI 分析设计特点 → 让 AI 生成类似页面 → 微调细节。还可以把你的页面截图丢给 AI 做设计评审,它会告诉你间距不一致、对齐有问题、层次感不够等等。


Bug 调试四步法

第一步:复现(Reproduce)。 确保你能稳定触发这个 Bug。记录触发条件:什么操作、什么数据、什么浏览器。

第二步:隔离(Isolate)。 把问题范围缩小。前端还是后端?用 curl 直接测接口排除前端问题。

第三步:定位(Identify)。 看报错信息,加 console.log,找到出问题的具体位置。

第四步:修复并验证(Fix & Verify)。 修完后回到复现步骤确认 Bug 确实被修了,然后检查有没有引入新 Bug。


console.log:最实用的调试工具

80% 的 Bug 靠 console.log 就能定位。关键是会用。

基础和进阶

// 带标签,方便在一堆输出中找到你关心的
console.log('[DEBUG] API response:', response.data)
console.log('[DEBUG] user ID:', userId)

// 打印对象用 JSON.stringify 格式化
console.log('完整数据:', JSON.stringify(data, null, 2))

// console.table 展示数组,比 log 好看 10 倍
console.table(users)

// console.time 计算耗时
console.time('查询耗时')
const data = await fetch('/api/data')
console.timeEnd('查询耗时') // 查询耗时: 123.45ms

调试技巧

// 数据变化前后都打日志,找到临界点
console.log('[BEFORE] state:', state)
setState(newState)
console.log('[AFTER] state:', state)

// 条件输出:只在特定情况下打印
if (userId === 'problem-user-123') {
console.log('问题用户数据:', userData)
}

// 调试 React 渲染次数
useEffect(() => {
console.log('[RENDER] MyComponent rendered, deps:', dep1, dep2)
})

提醒: 上线前记得清理 console.log。生产环境里的 console.log 影响性能还可能泄露敏感信息。


浏览器 DevTools:你的瑞士军刀

Chrome DevTools(按 F12 打开)三个最常用的 Tab:

Elements Tab(元素面板)

  • 查看元素:右键页面元素 → "检查",自动跳到对应 HTML
  • 实时修改样式:右侧面板直接改 CSS,改完立刻生效
  • 查看盒模型:选中元素显示 margin/border/padding/content,间距问题一目了然
  • 强制状态:点 :hov 按钮,强制显示 hover/focus 状态

Console Tab(控制台面板)

// 直接执行代码测试 API
fetch('/api/posts').then(r => r.json()).then(console.log)

// 查看元素数量
document.querySelectorAll('button').length

Network Tab(网络面板)

  • 点 "Fetch/XHR" 只看 API 请求
  • 点击请求查看 Headers、Payload、Response
  • 右侧时间条显示加载耗时
  • "Network throttling" 模拟慢网速测试弱网表现

常见 Next.js 错误和解决方案

错误 1:Hydration Error

Error: Text content does not match server-rendered HTML.

服务端和客户端渲染结果不一致。常见于 Date.now()typeof window 的使用。

// ✅ 用 useEffect 在客户端才渲染
function MyComponent() {
const [time, setTime] = useState('')
useEffect(() => { setTime(new Date().toLocaleString()) }, [])
return <div>{time}</div>
}

错误 2:"use client" 忘记加

Error: useState only works in Client Components.

Next.js 13+ App Router 默认是 Server Component,用 hooks 需要加 "use client"

"use client"
import { useState } from 'react'

export default function Counter() {
const [count, setCount] = useState(0)
return <button onClick={() => setCount(c => c + 1)}>计数: {count}</button>
}

错误 3:图片域名没配置

next.config.js 里加上允许的域名:

const nextConfig = {
images: {
remotePatterns: [
{ protocol: 'https', hostname: '*.supabase.co' },
],
},
}
module.exports = nextConfig

错误 4:useEffect 死循环

useEffect 里改变了作为依赖项的 state,导致无限触发。

// ❌ 无限循环
useEffect(() => {
setItems(data.filter(item => item.active))
}, [items]) // items 变了 → 触发 → 又变 → 又触发...

// ✅ 依赖原始数据
useEffect(() => {
setItems(data.filter(item => item.active))
}, [data])

React Error Boundary:优雅地处理错误

默认情况下,React 组件里的 JS 错误会白屏整个页面。Error Boundary 捕获错误,显示友好的备用 UI。

"use client"
import React from 'react'

export class ErrorBoundary extends React.Component<
{ children: React.ReactNode; fallback?: React.ReactNode },
{ hasError: boolean }
> {
state = { hasError: false }

static getDerivedStateFromError() {
return { hasError: true }
}

componentDidCatch(error: Error, info: React.ErrorInfo) {
console.error('[ErrorBoundary] 捕获到错误:', error, info)
}

render() {
if (this.state.hasError) {
return this.props.fallback || (
<div className="p-6 text-center">
<h2 className="text-xl font-semibold mb-2">出了点问题</h2>
<p className="text-gray-600 mb-4">这个部分加载失败了,请刷新页面试试。</p>
<button onClick={() => this.setState({ hasError: false })}
className="px-4 py-2 bg-blue-600 text-white rounded-lg">重试</button>
</div>
)
}
return this.props.children
}
}

使用方式:

<ErrorBoundary>
<ChartComponent /> {/* 出错不影响其他部分 */}
</ErrorBoundary>

Error Boundary 只捕获渲染过程中的错误,事件处理函数里的错误要用 try-catch。


写好错误信息

好的错误信息告诉用户三件事:发生了什么、为什么、怎么解决

// ❌ "Error 500"、"Something went wrong"、"操作失败"

// ✅ "网络连接失败,请检查网络后重试"
// ✅ "这个邮箱已经被注册了,请直接登录或用其他邮箱"
// ✅ "文件大小超过限制(最大 5MB),请压缩后重新上传"

写法原则: 不用技术术语(别说 "null reference",说 "数据加载失败"),告诉下一步该怎么做,语气友好不责怪用户。

{error && (
<div className="bg-red-50 border border-red-200 rounded-lg p-4 text-red-700">
<p className="font-medium">操作失败</p>
<p className="text-sm mt-1">{getErrorMessage(error)}</p>
<button onClick={retry} className="text-sm mt-2 underline">点击重试</button>
</div>
)}

日志:给未来的自己留线索

日志最佳实践

// ❌ console.log(data) / console.log('here') / console.log('test')

// ✅ 带时间、带标签、带上下文
console.log(`[${new Date().toISOString()}] [API] 用户 ${userId} 请求 /api/posts`)
console.error(`[${new Date().toISOString()}] [ERROR] 数据库查询失败:`, error.message)

日志分级

  • DEBUG:开发调试用,上线后关掉
  • INFO:正常业务流程记录,如登录成功
  • WARN:不影响功能但需要注意,如缓存过期
  • ERROR:出错了,需要排查

线上日志

  • Vercel:Functions → Logs
  • Sentry:前端错误追踪(npm install @sentry/nextjs),免费额度够个人项目用

常见调试模式

"刚才还好的":用 git diff 看最近改动,或 git stash 回退对比。

"本地好使线上不行":检查环境变量。Vercel 的 Environment Variables 面板看有没有漏的。

"只有某些用户报错":边界情况。某用户数据为空、某字段没填、某浏览器不支持某 API。

"改了一个冒出三个":代码耦合太严重,退回来重新想架构。


最后的话

调试是技能,练得越多越熟练。每次调完 Bug 花一分钟想想:什么类型?用什么方法找到的?下次怎么更快定位?

设计也一样——不需要天赋,需要多看好设计、记住四条原则、善用 AI 帮你做评审。用心做出来的产品,一定不会丑。