资讯动态

10分钟用Vue3+Express搭建生鲜电商实战项目

发布时间:2026/10/24 3:05:58 来源:尧图企业网站定制
“10分钟快速搭建实战Web项目生鲜电商”听起来像是标题党但我自己真做过很多次这类快速交付的活儿。当有人要求“今天给我一个能演示、能操作、能继续二次开发的电商项目”我脑子里第一反应就是把生鲜电商拎出来当模板因为它的业务链路足够典型数据模型也足够有辨识度商品带分类、带单位、带库存订单要汇总、要落库、要能流转这几乎是所有电商系统的通用骨架。这次我就用这套方案跑一遍完整项目前端用 Vue 3 加 Vite 加一个成熟组件库后端用 Node.js 的 Express数据层不装数据库直接用 JSON 文件做持久化。核心链路就是商品浏览、加入购物车、提交订单、后台简单增删改。整个过程我从空目录开始不依赖可视化生成器每一步都手动敲出来实测 10 分钟能跑通到“前端页面能下单、订单落到后端文件”。这个方案非常适合几种人想快速交课程设计或竞赛原型的学生需要给客户做小样看效果的自由开发者以及想从“只写页面”跨到“前后端全打通”的新手。看完这篇你不仅能复现一个能用的生鲜商城更能摸清拆解业务、设计接口、联调部署这套方法换成图书、服装、二手交易也照样套得上。1. 开建前先做的事把生鲜电商的业务边界和方案定死1.1 生鲜电商真正绕不开的只有三件事很多人在动手前容易陷入功能清单的泥潭想着首页轮播图要几个、优惠券要不要做、支付接哪家、会员积分怎么发结果做了一周还卡在登录注册。我的经验是电商系统能不能成立不看附加功能只看主链路通不通。所谓主链路对生鲜电商而言就是三件事看商品、加购物车、提交订单。为什么生鲜品类尤其适合这个案例因为它的字段设计比服装、3C 更能体现行业细节。生鲜按“斤”“盒”“份”卖不是按件卖生鲜有保鲜期所以库存和销量要单独拿出来生鲜分类感强水果、蔬菜、肉禽蛋、海鲜水产用户找东西靠分类比靠搜索更常用。这些业务特性直接决定数据模型长什么样也是这个项目比普通“图书管理系统”更能让人学到东西的地方。所以我在最前面就把范围划死了不做登录鉴权不做真实支付不做营销活动把全部精力压到商品、购物车、订单三个核心实体上。这样既能保证 10 分钟完成又不会让人觉得“这是玩具”因为增删改查、搜索排序、前端交互、后端接口该有的实战要素一样不少。1.2 技术选型为什么是 Vue 3 Vite Express技术栈的选择逻辑其实很简单能不能快速把页面搭出来依赖安装顺不顺启动快不快调试方不方便。我最终选的是 Vue 3 Vite 做前端Express 做后端两个都是生态成熟且有大量既有资料参考的方案。Vite 的好处非常直观创建项目只需要一条命令开发服务器秒启热更新快不用像老式 Webpack 那样等半天。Vue 3 的组合式 API 写业务逻辑很顺手加上配套的组件库表格、表单、弹窗、消息提示全是现成的十分钟里省掉的是造轮子的时间。前端选成熟组件库不是偷懒而是把人力集中到业务逻辑上管理页的商品表格、编辑弹窗、删除确认这些通用交互交给现成组件最稳。后端为什么选 Express 而不是同样流行的 Spring Boot因为这次定位是“快速搭建”Express 的中间件机制和路由写法对新手极友好一个文件里就能完成全部 API。Node.js 跟前端同为 JavaScript前端工程师没有第二语言成本数据格式天然统一。如果你后续要转 Java 体系这套 API 设计思路也能平移过去因为接口设计跟语言无关业务逻辑才跟语言有关。1.3 数据存储不上 MySQL是不是偷工减料这是我最想解释清楚的一点。数据层用 JSON 文件不是因为它适合生产而是因为它能把“安装数据库、建库建表、配置连接”这段耗时流程从 10 分钟里剥掉。本地演示阶段用 Node 的 fs 模块读写一个 JSON 文件效果和真实数据库在业务层几乎一致能查、能增、能改、能删数据重启后还在。更重要的是这个替换成本极低。我设计的后端代码里所有数据操作都集中在一个db.js模块里将来换成 MySQL 或 MongoDB只需要重写这个模块里的读写函数路由层的代码一行都不用动。这其实是软件工程里的“关注点分离”比直接教人装 MySQL 再连更有教学价值。先把业务跑通再谈底层存储学习曲线平滑得多。当然如果你本机本来就装了 MySQL或者项目要求必须用真数据库那也很简单把db.js里的方法一个个改成 SQL 查询就行接口和前端完全不受影响这个升级路径我放在后面第五节细说。2. 数据模型和接口清单动手前先定好全局骨架2.1 商品模型字段宁可少而准不要多而废商品是这个系统里最重要的实体。我设计了七个字段id、name、category、price、unit、stock、sales外加一个image给前端展示用。为什么是这个组合每个都有自己的理由。category决定用户怎么逛店我按生鲜习惯分成了水果、蔬菜、肉禽蛋品、海鲜水产四类前端分类栏直接循环这个字段就能渲染price用数字类型而不是字符串是为了排序和计算总价时不用再做转换unit是很多人容易漏掉的字段它解决“19.9 到底是 19.9 元一斤还是 19.9 元一盒”这个展示问题也影响购物车里的数量表述stock是库存管理后台调整库存时用sales是销量前端可以做“按销量排序”的热销榜。先看种子数据长什么样{ products: [ { id: 1, name: 红颜草莓, category: 水果, price: 29.9, unit: 斤, stock: 200, sales: 128, image: /images/strawberry.png, desc: 当日现摘甜度适中 }, { id: 2, name: 山东红富士苹果, category: 水果, price: 12.8, unit: 斤, stock: 500, sales: 356, image: /images/apple.png, desc: 脆甜带酸皮薄多汁 }, { id: 3, name: 云南有机生菜, category: 蔬菜, price: 6.5, unit: 份, stock: 300, sales: 89, image: /images/lettuce.png, desc: 基地直供新鲜水嫩 } ], orders: [] }这里的image字段我先填了本地路径实际项目中你完全可以换成对象存储的 CDN 地址或者干脆用一个随机占位图服务。字段别贪多像产地、规格、保质期这些以后需要再加字段就行初期塞太多只是给自己找麻烦。2.2 订单模型一定要存“商品快照”不能只存商品ID订单模型是这个项目最容易犯错的地方我当年第一次设计时就把订单项直接关联商品表结果商品一改价历史订单跟着变闹了个大笑话。正确做法是下单时把商品名称、单价、数量这些信息原样复制一份存进订单里这叫快照。订单数据分两层订单主档和订单明细。主档记录顾客信息和总金额明细记录每一件商品的下单时快照。因为这是一个快速演示项目我把两层合并成了一个数组每笔订单里有items字段里面存的就是快照数组。{ id: 1718000000001, customer: 张女士, phone: 13800000000, address: 某某小区 3 栋 102, items: [ { name: 红颜草莓, price: 29.9, unit: 斤, qty: 2 }, { name: 云南有机生菜, price: 6.5, unit: 份, qty: 1 } ], total: 66.3, status: 已提交, createdAt: 2024-06-10 14:30:00 }快照的优点很明确客户历史订单永远真实商品改名、改价都不影响已下单数据这是电商系统的从业者常识也是面试官爱问的点。作为快速项目可能看不出差别但如果你把这个项目写到简历上能讲出这一步设计含金量立刻不一样。2.3 接口清单前后端都照着这张表开发动工之前我把接口清单也定好。所有接口都挂在/api下方便前端代理也方便以后拆分服务。方法路径功能关键参数GET/api/products商品列表支持搜索、分类、排序keyword, category, sortPOST/api/products新增商品商品字段PUT/api/products/:id更新商品商品字段DELETE/api/products/:id删除商品无POST/api/orders创建订单customer, phone, address, itemsGET/api/orders查询订单列表无商品列表接口的sort我支持两个值price_asc按价格升序sales按销量降序这样前端“价格从低到高”和“销量优先”两个排序按钮就能直接对接。查询条件用 query 参数而不是 REST 路径参数一是写起来方便二是 Axios 传参不用拼字符串前端代码会清爽很多。2.4 定好项目边界哪些砍掉、哪些留坑我做快速项目有个原则能砍的功能全部砍掉但砍掉的位置要留扩展点。登录验证砍掉但订单里保留customer、phone、address字段以后接入登录只要从用户信息里自动填就行真实支付砍掉但订单保留status字段“已提交”“已完成”“已取消”的状态流转可以先做权限管理砍掉但管理页的接口我会单独放到/api/products的写操作里以后想加管理员身份验证在这几个写接口上加中间件即可。这样做的意思是项目很小但边界干净别人接手能顺着字段知道下一步往哪长。这不是过度设计这是“有控制的简化”。3. 前半场实操四分钟把 Express 后端和 API 拉起来3.1 初始化目录并准备数据文件后端部分我建议直接手工建目录不用脚手架因为 Express 项目本身就那几样东西。打开终端执行mkdir fresh-ecommerce cd fresh-ecommerce npm init -y npm install express mkdir datanpm init -y会生成默认的 package.json后面我们只需要知道node server.js能启动就行。接着把上面那段db.json内容保存到data/db.json里这就是我们的“数据库”。这里有个实操心得如果你只想先看效果可以只留三四条商品数据前端页面照样展示得很完整。但如果你想测试分类筛选和搜索那就必须保证每个分类至少一条数据否则分类点了没反应会误以为代码写错了。3.2 核心服务端代码Express 五分钟搞定全部接口在项目根目录创建server.js这是后端唯一的核心文件const express require(express); const fs require(fs); const path require(path); const app express(); const PORT 3000; const DB_FILE path.join(__dirname, data, db.json); app.use(express.json()); function readDB() { const raw fs.readFileSync(DB_FILE, utf-8); return JSON.parse(raw); } function writeDB(db) { fs.writeFileSync(DB_FILE, JSON.stringify(db, null, 2), utf-8); } // 商品列表支持 keyword / category / sort 三个查询参数 app.get(/api/products, (req, res) { const db readDB(); let list db.products; const { keyword, category, sort } req.query; if (keyword) { list list.filter(p p.name.includes(keyword)); } if (category) { list list.filter(p p.category category); } if (sort price_asc) { list.sort((a, b) a.price - b.price); } else if (sort sales) { list.sort((a, b) b.sales - a.sales); } res.json(list); }); // 新增商品 app.post(/api/products, (req, res) { const db readDB(); const newProduct { id: Date.now(), ...req.body }; db.products.push(newProduct); writeDB(db); res.json(newProduct); }); // 更新商品 app.put(/api/products/:id, (req, res) { const db readDB(); const id Number(req.params.id); const idx db.products.findIndex(p p.id id); if (idx -1) { return res.status(404).json({ msg: 商品不存在 }); } db.products[idx] { ...db.products[idx], ...req.body, id }; writeDB(db); res.json(db.products[idx]); }); // 删除商品 app.delete(/api/products/:id, (req, res) { const db readDB(); db.products db.products.filter(p p.id ! Number(req.params.id)); writeDB(db); res.json({ success: true }); }); // 创建订单 app.post(/api/orders, (req, res) { const db readDB(); const order { id: Date.now(), customer: req.body.customer, phone: req.body.phone, address: req.body.address, items: req.body.items, total: req.body.items.reduce((sum, it) sum it.price * it.qty, 0), status: 已提交, createdAt: new Date().toLocaleString(zh-CN) }; db.orders.push(order); writeDB(db); res.json(order); }); // 订单列表 app.get(/api/orders, (req, res) { const db readDB(); res.json(db.orders); }); app.listen(PORT, () { console.log(API 服务已启动: http://localhost:${PORT}); });这段代码有四个点值得拎出来讲。第一express.json()这个中间件必须加否则req.body永远是 undefined很多新手在这里卡住半天第二商品新增时id: Date.now()是偷懒但有效的做法保证每次新增都是唯一值不必自增计数第三订单总价在后端算一遍而不是相信前端传过来的数字这是基本的安全意识防止有人改请求伪造低价第四所有写操作都调writeDB把数据立刻落盘保证服务重启后数据还在。有人会问用fs.writeFileSync每次全量写文件性能会不会太差对于演示项目完全够用同时我要提醒你真实项目中这层后面会换成数据库写操作会交给数据库处理所以这个环节不需要优化只需要跑通。3.3 启动服务并验证接口后端写完执行启动命令node server.js看到控制台输出API 服务已启动就成功了。然后开另一个终端窗口验证一下curl http://localhost:3000/api/products能看到 JSON 数组就是正常的。再测一下创建订单curl -X POST http://localhost:3000/api/orders \ -H Content-Type: application/json \ -d {customer:测试用户,phone:13900000000,address:某地址,items:[{name:红颜草莓,price:29.9,unit:斤,qty:2}]}如果返回的订单里有total: 59.8和status: 已提交后端就完全通了。后端高峰期也就花三四分钟剩下的时间全留给前端页面。4. 后半场实操五分钟让 Vue 首页、购物车、订单全部跑通4.1 创建 Vue 项目并配置代理后端跑通了前端就用 Vite 生成。回到项目根目录的上层执行npm create vitelatest fresh-web -- --template vue cd fresh-web npm install npm install element-plus axios vue-router注意我特意把前后端分成两个目录fresh-ecommerce放后端fresh-web放前端这样结构清楚将来部署也可以拆开。接下来做三件基础配置。第一件改vite.config.js加开发代理让前端请求/api自动转发到3000端口import { defineConfig } from vite import vue from vitejs/plugin-vue export default defineConfig({ plugins: [vue()], server: { proxy: { /api: http://localhost:3000 } } })配置代理比在代码里写死http://localhost:3000要好得多因为代码里的请求路径始终是相对的/api/xxx将来部署到同域名下不用改代码这个习惯越早养成越好。第二件在main.js里全局注册 Element Plusimport { createApp } from vue import ElementPlus from element-plus import element-plus/dist/index.css import App from ./App.vue import router from ./router const app createApp(App) app.use(ElementPlus) app.use(router) app.mount(#app)第三件建src/router/index.js两条路由一个首页一个管理页import { createRouter, createWebHistory } from vue-router import Home from ../views/Home.vue import Admin from ../views/Admin.vue const router createRouter({ history: createWebHistory(), routes: [ { path: /, component: Home }, { path: /admin, component: Admin } ] }) export default router4.2 首页商品列表与分类筛选首页是这个项目的门面我把它做成一个单页顶部搜索框加排序按钮中间分类标签下面是商品卡片。核心逻辑在Home.vue里。script setup import { ref, onMounted } from vue import { getProducts } from ../api/index import ProductCard from ../components/ProductCard.vue import CartPanel from ../components/CartPanel.vue const products ref([]) const keyword ref() const activeCategory ref(全部) const sort ref() const categories [全部, 水果, 蔬菜, 肉禽蛋品, 海鲜水产] async function loadProducts() { const params {} if (keyword.value) params.keyword keyword.value if (activeCategory.value ! 全部) params.category activeCategory.value if (sort.value) params.sort sort.value products.value await getProducts(params) } onMounted(loadProducts) /script对应的src/api/index.js封装是这样的import axios from axios const api axios.create({ baseURL: /api }) export const getProducts (params) api.get(/products, { params }).then(res res.data) export const createOrder (data) api.post(/orders, data).then(res res.data) export const getOrders () api.get(/orders).then(res res.data) export const addProduct (data) api.post(/products, data).then(res res.data) export const updateProduct (id, data) api.put(/products/${id}, data).then(res res.data) export const deleteProduct (id) api.delete(/products/${id}).then(res res.data)Axios 封装我是故意这么写的每个方法直接返回res.data这样组件里调用时就很干净不需要每个地方都写解包逻辑。分类标签点击时更新activeCategory再调用loadProducts()搜索框回车触发同样的流程前端到这里的核心就是“改参数、重新请求、重新渲染”这个循环。4.3 购物车和下单把交互链路真正串起来购物车我建议用 Vue 的reactive做一个全局状态而不是放在某个组件里。我在src/store/cart.js里写import { reactive, computed } from vue export const cart reactive({ items: [] }) export const cartTotal computed(() cart.items.reduce((sum, it) sum it.price * it.qty, 0) ) export function addToCart(product) { const exist cart.items.find(it it.id product.id) if (exist) { exist.qty } else { cart.items.push({ id: product.id, name: product.name, price: product.price, unit: product.unit, qty: 1 }) } } export function removeFromCart(index) { cart.items.splice(index, 1) }为什么不用 localStorage因为这是演示项目刷新页面清空购物车反而更像真实场景。如果你想保存购物车状态就在addToCart里同步写一下 localStorage启动时再读回来十行代码的事。下单逻辑放在CartPanel.vue里填一个收货人姓名、电话、地址点击提交调createOrder把cart.items作为items传过去成功后清空购物车并提示“订单提交成功”。这样整个交易链路就是闭环了商品来自后端、购物车在前端组装、订单回写后端文件。4.4 后台商品管理表格加对话框就是最快的后台管理页不用另起炉灶一个表格加一个对话框就能实现全部功能。Admin.vue里放一个 Element Plus 的表格列显示商品名称、分类、价格、库存、销量操作列放“编辑”和“删除”按钮。页面顶部放“新增商品”按钮点击后打开同一个表单对话框编辑和新增共用这个表单组件。这个做法的重点是后台管理页的本质就是 CRUDCRUD 用现成组件最省时间。整个页面代码量不大但效果很完整。新增和编辑调用addProduct与updateProduct删除前用ElMessageBox.confirm做确认弹窗删除成功后重新加载表格。这又是一次典型的“用组件省心”的实践。4.5 前端联调完成的判断标准前端做到什么程度算“跑通”我的验收标准只有一条打开浏览器从首页浏览商品加两件商品到购物车填写收货信息提交订单然后去查看订单列表能看到这笔订单金额正确。如果这串动作走通项目就拿到了“及格分”。事实上这个标准我一直沿用到正式项目因为主链路不通做得再花哨也白搭。5. 联调时最容易踩的坑以及上线前怎么收尾5.1 本地跑不通时的排查顺序我做快速项目时基本不会遇到玄学问题跑不通只要按顺序查就能定位。下面这几个问题出现的频率最高现象原因处理方式前端请求提示 404Vite 代理没配置或后端没启动检查 vite.config.js 的 proxy确认 3000 端口服务在跑提交订单后 req.body 都是空后端没加 express.json()在 app 里补上app.use(express.json())商品乱码或 JSON 解析报错文件读写编码问题读写都指定 utf-8确认文件内容是合法 JSON端口被占用3000 或 5173 被其他进程抢占换端口或杀掉占用进程改完数据刷新页面又恢复原样写操作没调用 writeDB检查所有 post/put/delete 是否都落了盘如果你遇到“前端能打开但接口全部失败”先别怀疑代理配置先开另一个窗口直接访问http://localhost:3000/api/products这一步就能把问题分成“后端问题”还是“代理问题”比瞎猜高效得多。5.2 部署上线最简单的两种做法演示还是要给别人看的所以部署这步我补上。最简单的做法是让 Express 同时托管前端静态文件一条命令搞定整个系统。在前端项目里执行npm run build前端会生成dist目录把dist文件夹复制到后端项目根目录再在server.js末尾加上app.use(express.static(path.join(__dirname, dist))); app.get(*, (req, res) { res.sendFile(path.join(__dirname, dist, index.html)); });这样只要启动node server.js前端页面和后端 API 就在同一个服务上了访问http://你的服务器IP:3000就是整个项目。注意app.get(*)必须放在所有 API 路由之后否则/api/xxx也会被前端路由拦截返回 HTML 而不是 JSON。如果想要更正规的做法可以前端部署到 Nginx后端跑 Node 服务Nginx 里配一条/api的反向代理规则。但快速项目我觉得用第一种就够了省一台服务器也少踩跨域坑。5.3 从“10分钟版”到“生产版”的升级路线这个演示项目做完要往生产方向走时我推荐按下面这个顺序升级每步都是独立任务风险可控第一步把 JSON 数据层换成 MySQL 或 MongoDB接口完全不用动。第二步给订单接口增加库存扣减逻辑下单时检查stock是否足够成功后stock减掉对应数量这一步是从“能下单”到“真的能卖货”的关键。第三步引入简单的登录注册把订单里的customer从手填改成从登录态自动带出。第四步接真实支付这步涉及商户资质但接口设计上订单status字段已经从“已提交”改成“已支付”用了。这个升级顺序对新手特别友好因为每步都可以独立验证不会推倒重来。我见过太多人一上来就接支付、做秒杀最后项目烂尾问题不在能力而在步子太大。5.4 快速项目的“防盗翻车”心得最后分享几点我用这类快速项目总结出的实操心得。第一先定接口再写页面永远不要反过来。接口一旦定好前端就可以并行开发不会出现后端改了字段、前端所有页面跟着改的惨剧。第二数据文件一定要在动手前就准备一份像样的种子数据没有数据的页面很难看出效果也很难测试筛选排序。第三写代码时每个接口都用 curl 或在浏览器里验证一次再往下走别攒到最后统一测那样出错根本不知道是谁的问题。第四也是最实用的一条这类快速项目一定要写个一页纸的 README记录启动命令、接口清单、项目结构。因为快速项目的寿命往往比你想的长可能下周你就要打开它给客户演示也可能下个月你就要在上面二次开发没有文档的时候再简单的代码也会让你想半天“我当时是怎么启动的”。这个生鲜电商项目如果继续往下扩展我建议先做订单管理页面把订单列表从数据里渲染出来再加库存联动。不过那是后话了当前的 10 分钟版本已经足够让你完整走一遍电商链路也足够让你把它变成课程作业、作品集甚至接单起点就看你要不要动手敲下第一行命令了。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑