从需求到上线:记录一次完全由 AI 辅助完成的小产品全流程
上周产品突然甩过来一个需求:给销售团队做一个「客户报价计算器」,要能选产品型号、填数量、自动算折扣、导出 PDF,还要在手机上能用。需求文档就三句话,排期却只有两天。
放在以前,这种需求我得先画原型、再写 PRD、切图、写前端、调接口、写后端、配部署,两天连水都顾不上喝。但这一次,我尝试让 AI 深度参与整个流程,从需求理解到代码生成再到上线部署,最终 6 个小时搞定。这篇文章不是吹 AI 多厉害,而是把这次实战中踩过的坑、值得复用的流程、以及我觉得「人不能偷懒」的环节,完整记录下来。
一、需求拆解:先把"人话"变成"结构化输入"
AI 写代码再强,也怕需求模糊。我拿到需求后,没有直接把那三句话丢给 AI,而是先用 10 分钟做了一个最小化的需求清单:
- 核心功能:选择产品型号 → 输入数量 → 自动计算总价 → 应用折扣 → 导出 PDF 报价单
- 用户角色:销售(移动端使用)、管理员(后台维护型号和价格)
- 技术约束:两天上线、无后端预算、要导出 PDF、支持微信内打开
- 数据:5 个产品型号,每个型号有基础价、阶梯折扣、有效期
我把这个清单整理成 Markdown 表格,然后让 AI 帮我生成一份更正式的 PRD。这一步的关键不是让 AI 替代思考,而是让 AI 把模糊需求里的隐藏问题暴露出来。比如 AI 在反问我时提了三个问题:
- 折扣是固定比例还是按数量阶梯?
- PDF 是客户端生成还是服务端生成?
- 管理员修改价格是否需要登录鉴权?
这三个问题如果不提前想清楚,后面一定会返工。所以第一阶段的经验是:人可以偷懒不写文档,但不能偷懒不思考。AI 适合把思考结果结构化,而不是替你做判断。
二、技术方案:用"最小可用架构"砍掉了 80% 的复杂度
确定需求后,我开始和 AI 一起定方案。最初 AI 给了一个很"正规"的架构:Next.js + Prisma + PostgreSQL + Docker + Nginx。这个方案没问题,但两天上线不可能。
我给了它一个约束:「无后端、无数据库、纯前端部署到静态托管」。AI 立刻换了一套更轻量的方案:
- 前端:Vue 3 + Vite + Tailwind CSS,移动端优先
- 数据:产品型号和价格写死在一个 JSON 文件里,管理员直接改 Git
- PDF 导出 :客户端用
html2canvas+jspdf生成 - 部署:Vercel 自动部署,Git 提交即上线
- 版本管理:GitHub 仓库 + 分支保护
这个方案看起来不"企业级",但完全符合"两天上线"的约束。更重要的是,它没有引入不必要的复杂度。销售团队要的是一个能用的报价工具,不是一个需要运维的微服务架构。
代码层面,我把产品数据独立成一个 products.json:
javascript
// src/data/products.json
[
{
"id": "ess-100k",
"name": "工商业储能柜 100kWh",
"basePrice": 128000,
"unit": "台",
"tiers": [
{ "min": 1, "max": 5, "discount": 0.98 },
{ "min": 6, "max": 20, "discount": 0.92 },
{ "min": 21, "discount": 0.85 }
]
}
]
这样的好处是,当销售总监第二天说要调整价格时,我只需要改这个 JSON 文件再 push,2 分钟后线上就生效了。
三、AI 辅助编码:我负责决策,AI 负责实现细节
进入编码阶段后,我把整个项目拆成 6 个任务:
- 项目初始化与路由
- 产品选择与数量输入组件
- 价格计算引擎
- PDF 报价单渲染
- 移动端样式适配
- Vercel 部署配置
每个任务我都会先写一个简短的需求描述,然后让 AI 生成代码。但这里有一个关键操作:我不会直接复制粘贴 AI 的输出,而是让它先解释代码的关键逻辑,我再决定要不要用。
比如价格计算引擎,AI 第一次给出的实现是这样的:
javascript
// src/utils/priceEngine.js
export function calculateQuote(items) {
return items.reduce((total, item) => {
const product = getProductById(item.productId);
const tier = product.tiers
.slice()
.reverse()
.find(t => item.quantity >= t.min);
const discount = tier ? tier.discount : 1;
return total + product.basePrice * item.quantity * discount;
}, 0);
}
这段代码看起来没问题,但我检查后发现两个隐藏 bug:
tiers如果没有排序,.reverse()会改变原数组引用;- 没有处理
max字段为 undefined 的边界情况(最后一个阶梯通常只写min)。
我让 AI 修正后,最终版本加了一层防御性处理:
javascript
// src/utils/priceEngine.js
export function calculateQuote(items) {
return items.reduce((total, item) => {
const product = getProductById(item.productId);
if (!product) throw new Error(`Unknown product: ${item.productId}`);
const sortedTiers = [...product.tiers].sort((a, b) => b.min - a.min);
const tier = sortedTiers.find(t => {
const aboveMin = item.quantity >= t.min;
const belowMax = t.max === undefined || item.quantity <= t.max;
return aboveMin && belowMax;
});
const discount = tier ? tier.discount : 1;
return total + product.basePrice * item.quantity * discount;
}, 0);
}
这个例子说明,AI 能帮你快速写出 80% 的代码,但剩下 20% 的边界情况和业务规则,必须靠人来把关。我这次项目里总共让 AI 生成了大约 1200 行代码,我自己改动了大约 200 行,其中 150 行都是在处理边界情况。
四、PDF 导出:移动端场景下最折腾的一环
报价单最后要导出 PDF,而且要能在微信里长按保存。这个需求听起来简单,实际踩坑最多。
最初我尝试用纯前端方案 html2canvas + jspdf,在电脑上跑得很顺,一到手机上就出问题:中文字体缺失、表格换页错乱、iOS 上 canvas 尺寸超限导致白屏。
和 AI 讨论了三种方案:
- 客户端 canvas 方案:简单,但移动端兼容性差
- 服务端 Puppeteer 截图:需要后端,超出当前约束
- PDF-LIB 纯 JS 生成:不依赖 DOM 转图片,兼容性最好
最终选了 PDF-LIB。虽然它不支持直接写 HTML,但报价单结构固定,完全可以用代码"画"出来。AI 帮我封装了一个 generateQuotePDF 函数:
javascript
// src/utils/pdfGenerator.js
import { PDFDocument, rgb, StandardFonts } from 'pdf-lib';
export async function generateQuotePDF(quote) {
const pdfDoc = await PDFDocument.create();
const page = pdfDoc.addPage([595, 842]); // A4
const { width, height } = page.getSize();
const font = await pdfDoc.embedFont(StandardFonts.Helvetica);
const fontSize = 12;
let y = height - 50;
page.drawText('客户报价单', { x: 50, y, size: 20, font });
y -= 40;
page.drawText(`客户:${quote.customer}`, { x: 50, y, size: fontSize, font });
y -= 25;
page.drawText(`日期:${quote.date}`, { x: 50, y, size: fontSize, font });
y -= 40;
quote.items.forEach((item, index) => {
page.drawText(
`${index + 1}. ${item.name} × ${item.quantity} = ¥${item.subtotal.toLocaleString()}`,
{ x: 50, y, size: fontSize, font }
);
y -= 25;
});
y -= 20;
page.drawText(`合计:¥${quote.total.toLocaleString()}`, {
x: 50,
y,
size: 14,
font,
color: rgb(0.8, 0.2, 0.2)
});
return await pdfDoc.save();
}
这个方案在 iOS 和 Android 上都能稳定运行,而且 PDF 文件大小只有几十 KB。唯一的代价是,样式需要自己用坐标"画",不像 HTML 转 PDF 那么直观。但对于这种固定格式的报价单,反而更可控。
五、部署与复盘:上线不是终点,而是迭代的起点
代码写完后,我把它推到了 GitHub,Vercel 自动完成了构建和部署。整个过程从 push 到线上可访问,花了 1 分 47 秒。
但这还不是结束。我把链接发给销售团队试用后,当天收到了 7 条反馈,其中 3 条是很有价值的:
- 数量输入框在手机上太窄,容易误触
- 折扣后价格没有显示"原价对比",客户不容易感知优惠
- 希望报价单能加上公司 logo 和销售人员签名
这些需求都不复杂,但因为项目结构清晰,我每个改动都在 15 分钟内完成。比如显示原价对比,只需要在计算引擎里返回原始总价:
javascript
export function calculateQuoteDetail(items) {
return items.map(item => {
const product = getProductById(item.productId);
const tier = findTier(product, item.quantity);
const originalSubtotal = product.basePrice * item.quantity;
const subtotal = originalSubtotal * (tier?.discount ?? 1);
return {
...item,
originalSubtotal,
subtotal,
discount: tier?.discount ?? 1
};
});
}
这次经历让我意识到:AI 把「从 0 到 1」的时间压缩到了极致,但「从 1 到好」仍然需要真实的用户反馈和持续的微调。
总结
这次完全由 AI 辅助完成的小产品实战,给我留下几条很深的印象:
- AI 不能替代需求分析,但能帮你把模糊需求结构化。反问过你的问题越多,说明它理解得越到位。
- 约束条件是给 AI 最好的提示词。说"两天上线"比说"做一个报价系统"更能得到可执行的方案。
- AI 生成的代码一定要 Review 边界情况。80% 的功能可能一次对,但剩下 20% 的 bug 往往藏在数据为空、数组越界、移动端兼容这些细节里。
- 移动端优先要考虑真实设备。电脑上跑通的方案,到手机上可能就是另一回事,越早真机测试越好。
- 上线只是开始。小产品最大的价值在于快速拿到反馈、快速迭代,而不是一次性做到完美。
这次项目从需求到上线只花了 6 个小时,放在一年前是不可想象的。但真正让我省下来的时间,不是敲代码那几个小时,而是反复查文档、调样式、配环境这些低价值工作。人的价值,正在从"写代码"转向"定义问题、判断方案、验证结果"。
如果你也在尝试用 AI 做完整项目,我的建议是:不要问"AI 能不能做",而要问"在这个阶段,我该怎么和 AI 分工"。把合适的工作交给合适的对象,才是最优解。