产品设计与调试
不是设计师,也能做出好看的产品
说实话,大多数人一开始做产品的时候,最头疼的不是代码,而是——"这也太丑了吧"。
别慌。你不需要成为设计师,你需要的是两个能力:抄得好和管住手。
"抄得好"不是直接复制粘贴,而是找几个做得好的产品,拆解它的布局、配色、间距。然后用 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 的 flex、grid、items-start、gap-4 来解决对齐问题。
3. 一致性(Consistency)
同样的元素在不同地方应该长得一样。全站只用一种主色,按钮样式统一,字体大小只用 3-4 种(标题 24px、副标题 18px、正文 16px、小字 14px),圆角统一用 rounded-lg 或 rounded-md。
4. 颜色(Color)
颜色是最容易搞砸的部分。记住这个配色公式:
- 一个主色:用于按钮、链接、强调元素,推荐
blue-600或indigo-600 - 灰色系:
gray-900(标题)、gray-600(正文)、gray-300(边框) - 状态色:绿色(成功)、黄色(警告)、红色(错误)
- 背景:纯白或
gray-50
完全不会配色?去 coolors.co 按空格键随机生成方案,选一组顺眼的用。
设计规范速查
这一节是「数字版速查手册」——打开这个页面,所有常用设计数值一目了然。不需要背,用的时候查一下就行。
字体系统(Typography)
字号阶梯
整个产品只用 4-5 种字号,从下面的阶梯里挑:
| px | rem | Tailwind 类名 | 中文名 | 用途 |
|---|---|---|---|---|
| 12px | 0.75rem | text-xs | 辅助字 | 标注、版权信息、时间戳 |
| 14px | 0.875rem | text-sm | 小正文 | 次要说明、表格内容、标签 |
| 16px | 1rem | text-base | 正文 | 默认正文大小,最核心的字号 |
| 18px | 1.125rem | text-lg | 大正文 | 副标题、文章引导语 |
| 20px | 1.25rem | text-xl | 小标题 | 卡片标题、段落标题 |
| 24px | 1.5rem | text-2xl | 中标题 | 页面区域标题 |
| 30px | 1.875rem | text-3xl | 大标题 | 页面主标题 |
| 36px | 2.25rem | text-4xl | 展示字 | 营销页大标题 |
| 48px | 3rem | text-5xl | 超大展示 | Landing Page Hero 文字 |
建议: 一个页面最多 4 种字号组合。典型的 SaaS 产品只用:正文 16px + 小字 14px + 标题 20px + 大标题 24px。
行高(Line-height)
行高决定文字的「呼吸感」。记住三条规则:
- 正文:
line-height: 1.6或 Tailwind 的leading-relaxed(约 25.6px),中文内容这个值最舒服 - 标题:
line-height: 1.3或leading-tight(标题字大,行高可以紧凑些) - 小字:
line-height: 1.5或leading-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-50或gray-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-50 | bg-gray-50 | #F9FAFB | 页面背景 |
| gray-100 | bg-gray-100 | #F3F4F6 | 卡片背景、输入框背景 |
| gray-200 | border-gray-200 | #E5E7EB | 边框、分割线 |
| gray-300 | text-gray-300 | #D1D5DB | 占位文字、禁用状态 |
| gray-400 | text-gray-400 | #9CA3AF | 辅助说明文字 |
| gray-500 | text-gray-500 | #6B7280 | 次要正文 |
| gray-600 | text-gray-600 | #4B5563 | 正文文字 |
| gray-700 | text-gray-700 | #374151 | 重要文字 |
| gray-800 | text-gray-800 | #1F2937 | 标题文字 |
| gray-900 | text-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 | 常见用途 |
|---|---|---|
| 4px | p-1 gap-1 | 图标和文字之间的间距 |
| 8px | p-2 gap-2 | 紧凑组件内部间距、小按钮内边距 |
| 12px | p-3 gap-3 | 普通组件内部间距 |
| 16px | p-4 gap-4 | 最常用间距,卡片内边距、元素之间默认间距 |
| 20px | p-5 gap-5 | 大组件内部间距 |
| 24px | p-6 gap-6 | 段落间距、区块之间的分隔 |
| 32px | p-8 gap-8 | 大区块间距 |
| 40px | p-10 | 页面区域内边距 |
| 48px | p-12 | Section 之间的间距 |
| 64px | p-16 | 页面顶部/底部大留白 |
| 80px | p-20 | Landing Page Section 间距 |
| 96px | p-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/ui | Radix UI | Headless 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} />
对比总结:
| Lucide | Heroicons | Phosphor | |
|---|---|---|---|
| 数量 | 1500+ | 300+ | 7000+ |
| 风格 | 线性 | 线性+填充 | 6 种风格 |
| 包大小 | 小 | 最小 | 中 |
| 推荐场景 | 通用首选 | Tailwind 项目 | 需要大量图标 |
实操建议: 项目开始时选一个,全站统一。混用不同图标库是最常见的设计事故之一。我个人推荐 Lucide,数量够用、风格统一、包体积小。
从好设计中"偷师"
找灵感的地方:
怎么"抄": 找一个好看的页面截图 → 丢给 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 帮你做评审。用心做出来的产品,一定不会丑。