ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Vue+Node.js茶叶商城项目源码解析:从目录结构到前后端联调

2026/9/14 13:59:56 拓冰建站 浏览量
Vue+Node.js茶叶商城项目源码解析:从目录结构到前后端联调 简介一套基于 Vue 与 Node.js 开发的茶叶商城平台系统源码配套运行说明适合计算机相关专业学生用于课程设计、大作业或毕业设计也适合前端与全栈初学者学习项目工程化与商城业务逻辑。资源共 79 个文件压缩包仅 1.04MB其中 17 个 vue 组件覆盖首页、搜索、购物车、个人中心等核心页面15 个 png 与 14 个 jpeg 等多类图片资源用于商品展示和界面装饰11 个 js 负责路由、状态管理及后端接口另有 json、css、ejs 等文件支撑配置、样式与页面渲染。已有 116 人学习下载。代码经过运行测试目录结构清晰前端 src 与后端 server 分离可在此基础上二次开发快速理解商城系统的数据交互流程是入门 Vue 全家桶与 Node.js 服务端开发的实用范例。1. 从 zip 压缩包到一个能演示的茶叶商城先想清楚它解决什么问题拿到这个茶叶商城的 zip 源码包第一步不是急着解压跑npm install而是先弄清楚你手里这套 Vue node.js 方案到底能交付什么。课程设计和毕业设计场景里最缺的不是功能是一个「能演示、能讲清、能应对追问」的完整闭环。这套系统的价值在于前端用 Vue 组织页面、路由和状态后端用 node.js 暴露接口商品列表、购物车、搜索、个人中心这些商城基础模块都能找到对应代码。适合计科、物联网、大数据、电子信息这类专业的学生直接拿去做课程大作业或毕设初稿也适合想补前后端联调经验的人当练习素材。它的运行说明文档覆盖了启动步骤但文档不会告诉你路由为什么要这样拆、接口数据是怎么流动的、跨域代理在哪配这些才是答辩时真正会被追问的地方。本文把这层间隙补上。2. Vue 侧源码骨架拆解路由、组件目录与状态管理的协作关系2.1 先看目录模板工程与业务代码怎么区分解压 zip 后顶层文件里babel.config.js、vue.config.js、jsconfig.json是 Vue CLI 生成的工程配置直接改的机会不多src目录才是业务代码集中地。你打开package.json能确认这几点是不是 Vue 2 生态、有没有引入 vue-router 和 vuex、scripts 里的 dev 命令叫什么。这个判断很重要后面所有启动命令都依赖它。src/ ├── main.js # 入口文件创建 Vue 实例 ├── App.vue # 根组件 ├── router/index.js # 路由表 ├── store/index.js # Vuex 状态管理 ├── components/ # 业务组件 │ ├── home/ │ ├── search/ │ └── common/ └── views/ # 页面级组件 ├── Home.vue ├── List.vue ├── Cart.vue ├── Search.vue └── My.vuemain.js是整个应用的启动入口它负责加载 Vue 根实例、挂载 router 和 store然后把 App.vue 渲染到public/index.html里。views目录下放页面级组件一个文件对应一个路由视图components目录里的组件则按业务场景继续拆比如首页轮播、商品卡片、搜索框这些可复用模块。判断一个文件应该放views还是components标准就是看它是否直接对应路由。对应路由的就是视图否则就是组件。public目录存放不需要经过 Webpack 处理的静态资源像favicon.ico、首页轮播图swiper1.jpeg、商品图goods1.jpg这些引用时用/images/xxx.jpg绝对路径。这里有个容易忽略的点Vue CLI 会把public下的文件原样复制到打包输出目录所以模板里直接写死路径的图片资源放在这里最合适不需要走 import 导入。2.2 路由配置与页面跳转逻辑商城的底部导航一般有首页、分类、购物车、搜索、我的五个入口views下恰好对应五个页面文件。路由表的配置逻辑很清晰router/index.js里把路径和组件做映射import Vue from vue import VueRouter from vue-router Vue.use(VueRouter) const routes [ { path: /, name: Home, component: () import(../views/Home.vue) }, { path: /list, name: List, component: () import(../views/List.vue) }, { path: /cart, name: Cart, component: () import(../views/Cart.vue) }, { path: /search, name: Search, component: () import(../views/Search.vue) }, { path: /my, name: My, component: () import(../views/My.vue) } ] const router new VueRouter({ mode: hash, routes }) export default router这段配置使用的是路由懒加载写法component用箭头函数返回import()动态引入Webpack 会把五个页面拆成独立 chunk首页加载时只请求路由对应的那个文件。懒加载在课程设计阶段看不出性能差异但如果你在 demo 里被问到「为什么不用全量引入」这是一个标准的加分回答。路由模式选的是hashURL 里会出现#号不会因为页面刷新导致 404适合本地演示和静态托管。五个路由的 name 字段要留意后面跳转如果用router.push({ name: Search })这种命名路由写法name 错了会直接报错。2.3 组件拆分与静态资源组织方式components目录里分了home、search、common三个子目录这种按业务域组织的习惯比按类型组织比如全部塞进components/Button.vue更容易维护。home目录下大概率放着首页专属组件比如轮播图、推荐位、热销榜单common目录放跨页面复用的通用组件比如价格标签、数量选择器search目录放搜索框和搜索结果项。src/assets下面是js、css、images、fonts这部分需要经过 Webpack 处理引入方式通常是import logo from /assets/images/logo.png。一个比较实用的分工是模板中动态绑定的图片走assets的 import 引入纯静态的装饰图放public/images。目录里出现的recommend.jpeg、dhp.jpeg、tgy.jpeg这类带语义的命名多半是首页活动位或推广位图片替换自己的茶叶品牌时只需要按同名文件覆盖即可。目录或文件职责关键说明src/views路由页面组件一个 .vue 文件对应一个路由src/components业务组件按 home/search/common 拆分src/router/index.js路由表hash 模式 懒加载五个页面src/store/index.js全局状态购物车、用户信息等跨页数据public静态资源商品图、轮播图、faviconsrc/assets构建资源js/css/fontsimport 引入购物车和用户信息这类跨页面需要共享的数据如果只在组件内部用data维护切换页面后状态就丢了。store/index.js的 Vuex 仓库解决的正是这个问题它把购物车条目数、用户登录态放到了全局状态里。课程设计的购物车逻辑一般不会太复杂核心就是把「加入购物车」「修改数量」「清空」这几个 action 和 state 对应清楚。3. node.js 接口层实现思路与前后端联调的请求链路3.1 server 目录一个最小可用的 node.js 服务端server目录就是整个商城的数据源。它的结构是典型的 Express 应用组织方式app.js负责创建应用实例、注册中间件、挂载路由bin目录下是启动脚本真正监听端口routes目录放接口定义public目录放后端托管的静态文件。前端 Vue 只负责渲染商品数据从哪里来、购物车存在哪都由这一层决定。const express require(express) const path require(path) const routes require(./routes) const app express() app.use(express.json()) app.use(express.urlencoded({ extended: true })) app.use(express.static(path.join(__dirname, public))) app.use(/api, routes) module.exports appexpress.json()和express.urlencoded({ extended: true })是两个必备的 body 解析中间件前端通过 POST 提交 JSON 或表单数据时靠它们把请求体解析到req.body不加这两行的话取到的是 undefined。express.static将 public 目录暴露为静态资源服务这样前端就能通过http://localhost:3000/images/goods1.jpg直接访问到后端的商品图片。app.use(/api, routes)把 routes 里定义的所有接口统一挂载到/api前缀下前端请求路径写成/api/goods而不是/goods接口分组清晰也方便在生产环境加一层反向代理。bin目录下的启动脚本通常是读取这个 app 实例调用app.listen(port)监听端口。这个脚本是命令行启动后端时直接执行的文件你在运行说明里看到的「启动后端服务」的指令就是指向它。3.2 接口设计思路商城场景下需要哪些端点一个茶叶商城的数据需求可以归纳成四类商品浏览、商品检索、购物车操作、用户信息。对应到接口设计上routes 目录里应该至少包含这几个端点接口路径方法作用对应前端页面/api/goodsGET获取商品列表Home.vue 首页列表/api/goods/:idGET获取商品详情List.vue 商品详情/api/search?keywordGET按关键词搜索商品Search.vue 搜索结果/api/cartGET/POST查看与添加购物车Cart.vue 购物车/api/userGET获取当前用户信息My.vue 个人中心实际写法上routes 目录下的文件会把同类型接口聚合到同一个 router 上例如商品相关接口在一个模块里购物车相关接口在另一个模块里最后在 index.js 里统一组装导出。一个商品列表接口的最小实现大致是这样const express require(express) const router express.Router() const goods [ { id: 1, name: 明前龙井, price: 128, image: /images/goods1.jpg }, { id: 2, name: 武夷岩茶, price: 88, image: /images/goods2.jpg }, { id: 3, name: 白毫银针, price: 168, image: /images/goods3.jpg } ] router.get(/goods, (req, res) { res.json({ code: 0, data: goods }) }) router.get(/goods/:id, (req, res) { const item goods.find(g g.id Number(req.params.id)) res.json({ code: 0, data: item }) }) module.exports router这里的商品数组属于内存模拟数据后端重启后修改会丢失但课程设计阶段足够了。req.params.id拿到的 URL 路径参数是字符串类型用Number()转换后再和商品 id 做严格比较这是个很常见的低级坑。返回结构统一用{ code, data }封装前端只需要判断code 0就认为请求成功比直接返回散乱的字段结构更规范。3.3 前端发起请求从组件到接口的完整链路前端的请求链路是Vue 组件在created或mounted生命周期里调用 axios 发送请求拿到数据后再赋值给 data 中的响应式字段模板渲染时自动更新。以首页 Home.vue 为例请求商品列表的写法一般如下import axios from axios export default { name: Home, data() { return { goodsList: [] } }, created() { this.fetchGoods() }, methods: { async fetchGoods() { const { data } await axios.get(/api/goods) this.goodsList data.data } } }created钩子里调用fetchGoods是因为这个阶段组件实例已经创建但尚未挂载数据请求可以尽早发出。请求路径写的是相对路径/api/goods省略了域名和端口这部分由开发服务器的代理在转发时补全。如果后端接口返回的图片字段只是/images/goods1.jpg模板中直接绑定就能显示因为 public 目录和接口服务跑在同一个端口下。这里要注意后端端口与前端端口不一致的问题。Vue CLI 开发服务器默认跑在 8080node.js 服务大概率跑在 3000 或其他端口浏览器直接访问localhost:8080/api/goods时会因为跨域被拦截。解决办法不在 axios 里改完整 URL而在vue.config.js里配代理把这层问题隔离在开发服务器层面。4. 本地运行与排错依赖安装、启动顺序与代理配置4.1 环境检查node.js 版本与 npm 源整条启动链路从环境开始。先打开终端确认 node 和 npm 是否就绪node -v npm -vnode.js 版本直接决定依赖能不能装成功。这个项目的前端基于 Vue CLIvue 2 版本配上 node 14 到 node 18 之间最稳妥node 20 以上建议先跑一遍npm install如果报ERESOLVE再考虑降级。代码里如果出现 Optional Chaining?.或空值合并??这类语法node 版本低于 14 直接解析失败。npm 源的问题在下载依赖时才会暴露。默认官方源在国内环境下经常超时执行安装前可以把 registry 临时切换到国内镜像装完再切回或者直接写入当前项目的.npmrc文件里npm config get registry npm install --registryhttps://registry.npmmirror.com参数--registry只对当前命令生效不会污染全局 npm 配置。镜像源在依赖下载阶段能明显提速但个别小众包在镜像上同步不及时如果某个包下载后校验失败优先尝试恢复官方源重装。4.2 两次 npm install前端与后端要分开处理这个项目的依赖不是一份前端根目录有一个package.jsonserver目录也有一个package.json两者依赖完全独立必须分别安装。根目录的 package.json 管理 Vue、vue-router、vuex、axios 这类前端依赖server 目录的 package.json 管理 express 等后端依赖。# 终端 1安装前端依赖并启动 npm install npm run serve # 终端 2安装后端依赖并启动 cd server npm install node bin/www如果根目录的 package.json 里 scripts 恰好不叫serve而是dev或start那就执行对应的npm run dev这个以压缩包里的原始配置为准。node bin/www是直接运行后端启动脚本的方式比npm start更直观报错也会直接打印在终端里。两个终端窗口不要关一个跑前端脚手架一个跑后端服务哪个崩了都能立刻看到日志。启动顺序上先起后端再起前端原因在于 Vue 开发服务器启动时会代理请求到后端端口如果后端端口还没监听前端页面能打开但所有商品数据拉取失败控制台一片红色报错容易让人误判成前端代码有问题。4.3 跨域处理vue.config.js 里的代理配置Vue CLI 项目里解决开发环境跨域的通用做法是利用devServer.proxy。在项目根目录的vue.config.js中增加如下配置module.exports { devServer: { port: 8080, proxy: { /api: { target: http://localhost:3000, changeOrigin: true } } } }这段配置的含义是开发服务器的所有请求只要路径以/api开头就自动转发到http://localhost:3000浏览器端看到请求还是发给 8080 端口所以不会出现跨域错误。changeOrigin: true将请求头中的 Host 字段改为目标地址防止后端服务做域名白名单校验时被拒。如果你的后端端口不是 3000把 target 改成实际端口即可后端启动脚本会打印监听端口直接看终端输出比猜快得多。配置修改后要重启npm run serve才生效热更新不会加载新的 devServer 配置。这是个非常容易踩的坑改了vue.config.js然后刷新页面发现代理没起作用其实 devServer 进程都没重新加载配置。4.4 启动现场常见问题对照表把运行说明里没展开的报错场景列成一张表排查时对着看效率比逐个搜索引擎查快很多。报错信息原因处理方式npm ERR! ERESOLVE unable to resolve dependency tree依赖树冲突node 版本或 npm 版本导致执行npm install --legacy-peer-deps重装Error: Cannot find module expressserver 目录的依赖没安装cd server后重新执行npm installCannot find module vue-template-compilervue 与 vue-template-compiler 版本不匹配将两者版本改成完全一致后重装ERR_CONNECTION_REFUSED后端没启动或端口不一致确认后端进程在跑检查 vue.config.js 的 target 端口[Vue Router warn] No match found for location with path路由路径写错或路由表未定义检查 router/index.js 中的 path 是否匹配跳转路径TypeError: Cannot read properties of undefined接口返回结构不符合预期网络面板看响应体确认是data.data还是data提示修改vue.config.js、package.json或路由表之后必须重启开发服务器单纯刷新页面不会生效。这一条能帮你排除掉一半的诡异问题。端口被占用是另一类高频问题。macOS 和 Linux 上可以用lsof -i :8080查看端口占用Windows 用netstat -ano | findstr 8080。如果 8080 被占直接在vue.config.js的devServer.port改成 8090 再重启后端端口同理。确认端口在监听用curl http://localhost:3000/api/goods在后端目录下打一发能返回 JSON 说明后端没问题问题只在前端联调层。5. 从能跑到能讲答辩讲解主线与项目个性化改造技巧5.1 答辩讲解的 3 条主线演示完之后老师一般会问「这个项目你负责了哪些部分」。三条主线能帮你把回答组织得有条理第一条是从main.js到router再到views的前端启动链路讲清楚页面是如果被加载出来的第二条是用户从点击商品到加入购物车的数据流讲清楚 Vuex 里 state、mutation、action 各自承担什么第三条是一个完整请求从 Vue 组件发出、经过 devServer 代理、到达 node.js 路由、返回响应的全过程。这三条线覆盖了 vue-router、Vuex、axios、Express 四个核心考点语法题问不出新意但数据流这类逻辑题能看出你确实跑通了项目。5.2 把商品数据从接口改成前端静态数据答辩现场最怕的意外是后端服务没起来页面全空。一个实用的降级方案是把商品列表数据本地化让页面依赖接口前先兜底一份静态数据。// 改造前依赖后端接口 async fetchGoods() { const { data } await axios.get(/api/goods) this.goodsList data.data } // 改造后接口失败时回退到本地数据 import goodsMock from /assets/mock/goods.json async fetchGoods() { try { const { data } await axios.get(/api/goods) this.goodsList data.data } catch (e) { this.goodsList goodsMock } }这段代码在try/catch中捕获接口异常后端挂了页面依然有数据展示演示不会中断。goods.json放在src/assets/mock目录下可以通过 import 直接引入并被 Webpack 打包。要注意 JSON 里图片字段如果引用的是/images/goods1.jpg前端静态数据模式下图片能否显示取决于 public 目录是否被正确加载解压后的 zip 包里这些图片本身就在不影响效果。5.3 打包产物与验收前的最后检查npm run build之后生成dist目录这是整个项目的生产版本。把dist目录整个交给后端部署或者本地用npx serve dist起一个静态服务验证打包结果。验收清单就两项登录页和商品列表页在打包后是否空白底部导航五个 tab 能否正常切换。hash 路由模式打包后直接双击index.html也能打开页面这是选 hash 模式的一个隐性好处也是本地演示时最省事的预览方式。打包产物体积如果偏大回到router/index.js把页面级组件改成懒加载就是前面第 2 章讲过的() import()写法改动最小效果也最直接。本文还有配套的精品资源点击获取