Node.js+Vue全栈家装商城实战:从业务建模到视频点播
做家装商城这件事,和我以前做的普通电商完全不是一个套路。普通电商把商品挂上去、购物车结算、发货就完事了,家装平台得把"效果图展示、主材选购、设计师预约、施工订单跟踪"这一整条链条在同一个站点里跑通。这个项目严格来说是Node.js + Vue的全栈项目,后端用Node.js提供API,前端用Vue 3搭单页应用,面向的是一站式的家装在线服务。这篇文章我会把整个项目的设计思路、核心业务建模、前后端实现、以及踩过的那些坑都过一遍,基本按照我实际开发的顺序来写,如果你手头也打算做类似的行业商城,可以直接对着这套方案抄作业。
1. 项目定位与整体设计思路
1.1 家装商城不是普通电商,先想清楚业务模型
家装行业和快消品电商最大的区别在于:客单价高、消费频次极低、决策周期长、参与角色多。用户买一件衣服可能三分钟下单,装修一套房子从浏览案例、预约设计、选材料到最终签施工合同,可能要花一到两个月。这就决定了平台的业务模型不能是"一件商品卖出去就结束",而是要从内容种草到交易履约,全程都在平台上形成闭环。
我最初设计这个项目时,和团队反复对过需求,最后把核心流程收敛成五条主线:
- 装修案例浏览:用户通过真实完工案例了解平台水平,案例以图文和视频形式展示。
- 设计预约:用户在线预约设计师,填写房屋信息、风格偏好,设计师后台接单。
- 主材与套餐选购:卫浴、瓷砖、地板、全屋套餐等以标准SKU形式在线售卖。
- 订单履约:用户下单施工服务或材料商品,后端生成订单并跟踪状态。
- 会员与营销:积分、优惠券、限时折扣辅助转化,这个对家装类平台非常有效。
把这五条主线拆清楚以后,我再去设计数据库、接口和前端页面,整个项目的边界就非常明确。很多新手做项目最忌讳一上来就写代码,结果业务边界模糊,改到最后代码和需求互相打架。家装平台尤其要注意,因为它牵扯线下服务,模型没理清楚,后面订单、财务、结算全部都要返工。
1.2 技术选型:为什么是Node.js + Vue,而不是Java + React?
我在这套项目里选择Node.js + Vue,不是跟风,而是从实际约束条件出发做的一个理性选择。
先说后端。Node.js最大的优势是开发效率高,JS采用异步非阻塞的I/O模型,在处理高并发I/O密集场景时非常擅长。商城系统80%的接口本质上是数据库读写和文件访问,比如商品列表、订单查询、图片加载,这些都是典型的I/O密集型操作。虽然Java在后端生态上更成熟,但对于一个中小型家装商城项目来说,Spring Boot的初始化成本和部署成本都更高,Node.js配合Express框架可以用更短的代码量实现同样的接口。
再说前端。Vue在国内的普及率相当高,上手曲线比React平缓,模板语法和原生HTML高度接近,配合Element Plus组件库做后台管理页面非常顺手。Vue的响应式原理使得页面状态管理更简单,性能上也足够满足商城前端的需求。如果你是个人开发者或者小团队接活,Vue + Node.js这套组合几乎是效率最优解,前后端都用JS,还能省掉语言切换的认知负载。
选型的时候也考虑过其他方案,我把当时的对比整理成一张表:
| 维度 | Node.js + Vue | Java + Spring Boot + Vue | Python + Django + Vue |
|---|---|---|---|
| 开发效率 | 高,前后端同语言 | 中,样板代码较多 | 高,内置功能丰富 |
| 性能水平 | 高并发I/O表现优秀 | 稳定,适合大型系统 | 中等,适合中小型项目 |
| 学习成本 | 低,JS门槛低 | 高,Java体系庞大 | 低,Python易上手 |
| 部署运维 | 轻量,单进程即可 | 较重,依赖JVM和容器 | 中等,依赖Python环境 |
| 适合场景 | 中小型电商、全栈应用 | 企业级复杂系统 | 内容站、工具站、后台 |
考虑到项目定位是"能跑起来的完整商城"且要求快速交付,Node.js + Vue是性价比最高的选择。如果你追求极致性能或者公司有Java技术栈沉淀,那选Spring Boot也没问题,但本文后面的设计思路和数据结构仍然可以复用。
1.3 工程目录与前后端架构规划
这个项目我采用了前后端分离架构,仓库分为两个工程目录:
mall-server
(后端API服务)和
mall-web
(前端单页应用)。后端负责业务逻辑、数据库操作和文件服务,前端负责页面渲染和用户交互,两者通过RESTful API通信。
先看后端目录结构:
mall-server/
├─ app.js // 服务入口文件
├─ config/
│ ├─ db.js // 数据库连接配置
│ └─ env.js // 环境变量配置
├─ models/ // Sequelize数据模型
│ ├─ user.js
│ ├─ product.js
│ ├─ sku.js
│ ├─ order.js
│ └─ case.js
├─ routes/ // 路由模块
│ ├─ user.routes.js
│ ├─ product.routes.js
│ ├─ order.routes.js
│ └─ case.routes.js
├─ middleware/ // 中间件
│ ├─ auth.js // JWT鉴权
│ └─ upload.js // 文件上传
└─ utils/
├─ response.js // 统一响应格式
└─ logger.js // 日志工具
前端目录结构:
mall-web/
├─ public/
└─ src/
├─ api/ // 接口请求封装
├─ assets/
├─ components/ // 公共组件
├─ router/ // 路由配置
├─ store/ // Pinia状态库
├─ views/ // 页面
│ ├─ home/
│ ├─ product/
│ ├─ cart/
│ ├─ order/
│ ├─ user/
│ └─ admin/
└─ utils/
这套结构的核心逻辑是"模块按业务划分,而不是按技术类型划分"。比如商品相关的页面都放在
views/product
下面,组件就放在
components/product
下面,这样做的好处是业务范围清晰,后期扩展新功能时找文件非常快。我见过很多项目按
views/page1
、
views/page2
这样命名,项目一大就完全失控,改一个需求要好几个文件来回跳。
2. 核心功能模块设计与数据建模
2.1 商品中心:普通电商的商品模型对装修套餐不够用
做家装商城,最核心的模块是商品中心,但它的商品模型和普通电商有本质差异。普通电商只需要SPU(标准商品单元)+ SKU(库存量单位)两级模型,比如一件T恤,SPU是"白色圆领T恤",SKU是"白色-XXL"。家装平台除了卖标准材料,还卖装修套餐,一个套餐可能包含地板、墙面、卫浴、主灯等几十个SKU,而且每个SKU有品牌、型号、面积单价这些属性。
我在这套项目里设计了三张基础表:
| 表名 | 字段示例 | 说明 |
|---|---|---|
| products | id, title, category_id, type | 商品主表,type区分单商品还是套餐 |
| skus | id, product_id, name, price, stock | 商品规格表,套餐SKU可以关联多个子项 |
| product_items | id, package_id, sku_id, quantity | 套餐明细,存一个套餐包含哪些商品 |
商品主表的
type
字段很关键,它把"单商品"和"套餐"分成两类。普通商品点开直接下单购买,套餐商品则展示套餐明细,购买后平台需要安排配送或施工服务。这个设计让后台可以灵活配置,商家上架单个卫浴产品,或者上架一个"厨房焕新套餐"都走同一套逻辑。
装修商品还有一个特点是规格属性复杂,比如瓷砖要有规格、材质、风格、适用空间四个维度,卫浴要有安装方式、排水类型等。我额外建了一张
product_attrs
表,用JSON字段存动态属性,这样不用为每种商品类型建一张属性表,后端接口返回时直接透传给前端渲染。实际跑下来挺稳的,即便是后期加了智能家居这个新品类,也只是改JSON结构,不用动表结构。
2.2 预约设计与服务流程闭环
家装商城和普通电商另一个显著的差异,是有线下服务环节。用户不会直接在一个页面上下单"装修一套房子",他更希望能先约设计师聊一聊,了解报价和方案,然后才决定是否合作。所以平台必须有一套预约管理系统。
我的做法是建一张
appointments
表,字段包括:用户ID、设计师ID、预约时间、房屋地址、面积、风格偏好、描述信息、状态(待接单/已确认/已完成/已取消)。前端在设计师详情页展示一个预约表单,后端收到请求后,在设计师的后台面板生成一条待接单记录。设计师接单之后,双方可以在平台消息里沟通,这个流程就把"线上浏览"和"线下量房"串起来了。
接口设计上我提供了两个视角的查询接口:用户端按手机号或用户ID查自己的预约记录,设计师端按设计师ID查待办列表。核心是后端的
status
状态机要设计好,因为预约状态直接影响后续订单生成,
待确认 -> 已确认 -> 已生成订单 -> 已完成
这条链路要保证数据一致性。
2.3 购物车、订单与支付状态机
购物车模块我采用的是"前端本地存储 + 后端校验"方案。用户未登录时,购物车数据存在浏览器localStorage里,登录后前端会把本地购物车同步到后端表
cart_items
。这个方案实施起来比纯后端购物车简单很多,而且用户不会因为换设备就丢失购物车,体验很好。后端校验的作用是防止用户篡改价格,每次提交购物车时,后端都会重新查询数据库中的最新价格和库存。
订单模块是整个系统的重点,我设计了一张订单主表
orders
,字段包括:订单编号、用户ID、总金额、支付方式、收货地址、订单状态、创建时间。另外有一张
order_items
子表,存订单包含的商品明细,这样订单生成后即便商品价格变动,订单详情里的快照数据也不会变,这是电商系统很基础但很多人会忽略的设计。
订单状态变化我整理为:
| 状态值 | 含义 | 触发动作 |
|---|---|---|
| 0 | 待支付 | 用户提交订单,等待付款 |
| 1 | 已支付/待发货 | 支付成功,商家后台可看到 |
| 2 | 已发货/施工中 | 商品出库或施工进场 |
| 3 | 已签收/已完成 | 用户确认收货或验收完成 |
| 4 | 已取消 | 用户或后台取消订单 |
支付环节我接入了微信和支付宝的沙箱环境,生产环境替换成正式商户号即可。注意支付回调要幂等处理,同一个支付回调通知可能因为网络原因发送多次,后端必须判断该订单是否已经是"已支付"状态,否则会产生重复发货的问题。我当时还额外写了一个定时任务,超过30分钟未支付的订单自动取消并释放库存,避免资源被无效占用。
2.4 会员、优惠券与装修进度跟踪
营销模块对家装平台很重要,因为用户决策周期长,需要用优惠手段持续触达。我实现了三类基本玩法:
- 会员积分:注册、下单、评价都可获得积分,积分可以在商城兑换小礼品或抵扣现金。
-
优惠券:后台创建满减券,比如满5000减200,领取后存在
coupons表,下单时校验是否满足使用条件。 - 限时秒杀:针对热门主材设置秒杀活动,秒杀接口用Redis做库存预扣减,防止超卖。
装修进度跟踪是这个项目的亮点功能。用户签订施工订单后,后台可以按阶段更新工程状态,如"拆改阶段""水电阶段""泥瓦阶段""木工阶段""油漆阶段""竣工阶段",每个阶段可以上传现场照片和文字说明。用户在个人中心可以看到一个进度时间轴,直观了解装修进展。这个功能对平台的价值在于,它大幅增强了用户对平台的信任感,也降低了客服沟通成本。
数据表设计上,
renovation_progress
表包含订单ID、阶段名、描述、现场图片URL、更新时间。每次更新相当于往表里插入一条新记录,前端按时间倒序渲染,天然形成一条时间轴,实现起来非常简单但效果很好。
3. 环境搭建到前后端联调全流程
3.1 Node.js安装与npm配置,先把开发环境踩平
先聊环境。很多新人在第一个环节就卡住了,而且卡的点特别集中。Node.js安装本身不难,去官网下载LTS版本,Windows用户直接双击安装包即可,一路下一步。需要注意两点:
第一,建议下载LTS(长期支持版)而不是Current(最新版)。LTS版本经过了更长时间的稳定性验证,兼容性更好,很多第三方依赖对最新版本的支持是滞后的,用LTS能省掉大量莫名其妙的兼容性问题。我当时用的Node 18 LTS,到现在项目跑得非常稳定。
第二,安装路径最好不要带空格和中文,否则后续一些工具链会出问题。推荐直接装在
D:\nodejs
或者
C:\nodejs
这种干净路径下,避免踩坑。
装完以后,在命令行里执行:
node -v
npm -v
能看到版本号就说明安装成功。如果用Windows PowerShell,很大概率会遇到下面这个报错:
npm.ps1无法加载文件...因为在此系统上禁止运行脚本
这个问题的原因我简单说一下:PowerShell的安全策略默认是Restricted,禁止执行任何.ps1脚本,而npm在Windows上的可执行入口恰好是一个
npm.ps1
文件。解决办法是修改PowerShell的执行策略,用管理员身份打开PowerShell,执行:
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
然后输入Y确认即可。
RemoteSigned
的含义是:本地脚本可以运行,从网上下载的脚本必须有信任签名。这个是微软官方推荐的策略,不会降低安全性,日常开发完全够用。如果你不想改策略,另外一个简单粗暴的办法是换CMD来执行npm命令,CMD不检查执行策略,完全绕开这个问题。
npm安装依赖慢的问题,推荐切换淘宝镜像源:
npm config set registry https://registry.npmmirror.com
切换完用
npm config get registry
验证一下,以后下载依赖的速度会明显提升。
3.2 Vue项目初始化与UI组件库集成
Vue项目我推荐用Vite作为构建工具,它比Webpack冷启动快很多,几十毫秒就能起服务,开发体验完全不是一个档次。创建项目的命令:
npm create vue@latest
按提示选择Vue Router、Pinia、ESLint等选项,对你需要的功能都选Yes,其他可以No。新版脚手架会生成一个干净的Vue 3项目,然后安装Element Plus作为UI组件库:
npm install element-plus
npm install axios vue-router@4 pinia
在
main.js
里全局注册Element Plus:
import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import App from './App.vue'
const app = createApp(App)
app.use(ElementPlus)
app.mount('#app')
这里我多说一句:Element Plus的组件按需引入和全量引入选择上,项目初期直接用全量引入就够,因为Element Plus的包体积虽然不小,但通过Vite构建的Tree Shaking机制,实际生产包的体积是可接受的。等业务增长到需要关心首屏加载速度时,再切换成按需自动导入方案。全量引入的优点是省心,不需要额外配置插件,对中小型项目来说,少一个配置项就少一个踩坑点。
3.3 基于Express搭建后端接口服务
后端我用Express框架。初始化也很简单:
mkdir mall-server
cd mall-server
npm init -y
npm install express mysql2 sequelize cors jsonwebtoken bcryptjs
数据库选用MySQL,我用Sequelize作为ORM。选Sequelize的原因是我懒得手写SQL,而且它可以做数据库表结构迁移,团队协作时每个人在本地跑一次迁移就能生成同样的表结构,非常省心。
创建连接:
const { Sequelize } = require('sequelize')
const sequelize = new Sequelize('mall', 'root', 'password', {
host: 'localhost',
dialect: 'mysql',
logging: false
})
module.exports = sequelize
定义一个简单的商品模型:
const { DataTypes } = require('sequelize')
const sequelize = require('../config/db')
const Product = sequelize.define('Product', {
id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
title: { type: DataTypes.STRING, allowNull: false },
categoryId: { type: DataTypes.INTEGER, allowNull: false },
type: { type: DataTypes.INTEGER, defaultValue: 1 }, // 1单商品 2套餐
price: { type: DataTypes.DECIMAL(10, 2), allowNull: false },
stock: { type: DataTypes.INTEGER, defaultValue: 0 },
cover: { type: DataTypes.STRING },
status: { type: DataTypes.INTEGER, defaultValue: 1 }
}, {
tableName: 'products',
timestamps: true
})
module.exports = Product
商品查询接口是一个典型的分页列表接口,实现如下:
const { Op } = require('sequelize')
const Product = require('../models/product')
router.get('/list', async (req, res) => {
try {
const page = parseInt(req.query.page) || 1
const pageSize = parseInt(req.query.pageSize) || 20
const { list, count } = await Product.findAndCountAll({
where: { status: 1 },
order: [['id', 'DESC']],
offset: (page - 1) * pageSize,
limit: pageSize
})
res.json({ code: 0, data: { list, total: count } })
} catch (err) {
res.status(500).json({ code: 500, message: err.message })
}
})
findAndCountAll
会同时返回数据列表和总条数,一次查询就满足前端分页组件的需求,不需要额外发请求。这里注意,所有查询条件最好都放在
where
里,不要在前端取全量数据再过滤,数据量一大页面必卡。
3.4 JWT鉴权和Axios请求封装,把登录体系串起来
登录模块是整个商城系统的基础,前后端联调时最先要打通的就是登录接口。我用JWT(JSON Web Token)实现无状态认证,这个方案的好处是后端不用维护Session,服务器扩展时不需要共享会话状态,非常适合前后端分离架构。
登录成功后端返回一个签名后的Token:
const jwt = require('jsonwebtoken')
const token = jwt.sign(
{ userId: user.id, username: user.username },
process.env.JWT_SECRET,
{ expiresIn: '7d' }
)
前端的Axios请求拦截器统一在请求头上加上Token:
import axios from 'axios'
const request = axios.create({
baseURL: '/api',
timeout: 10000
})
request.interceptors.request.use(config => {
const token = localStorage.getItem('token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
})
request.interceptors.response.use(
response => {
return response.data
},
error => {
if (error.response && error.response.status === 401) {
localStorage.removeItem('token')
window.location.href = '/login'
}
return Promise.reject(error)
}
)
export default request
后端写一个鉴权中间件:
const jwt = require('jsonwebtoken')
module.exports = function auth(req, res, next) {
const authHeader = req.headers.authorization || ''
const token = authHeader.startsWith('Bearer ') ? authHeader.slice(7) : ''
if (!token) {
return res.status(401).json({ code: 401, message: '未登录' })
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET)
req.userId = decoded.userId
next()
} catch (err) {
return res.status(401).json({ code: 401, message: '登录已过期' })
}
}
跨域问题上,前端开发环境通过Vite的proxy代理绕开跨域;生产环境用Nginx将
/api
反向代理到后端服务,也不存在跨域问题。开发环境的
vite.config.js
里这样配置:
export default defineConfig({
server: {
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true
}
}
}
})
联调时最容易出错的就是接口路径不一致。建议前后端把接口文档放在一起维护,哪怕是简单的Excel表格,也要保证"路径、入参、出参"三块完全对齐,否则传输层永远在报错。
4. 装修案例视频:m3u8点播方案落地
4.1 为什么视频案例要用m3u8切片播放
家装平台的内容展示,图片只能展示静态效果,视频才能把空间感、采光、材质质感完整体现出来。我当时在装修案例详情页集成了视频播放能力,方案选的是HLS协议对应的m3u8格式。
m3u8是Apple推出的HTTP Live Streaming(HLS)协议里的索引文件,它把一段完整视频切成了很多个小的.ts分片文件,通过一个.m3u8索引文件按顺序播放。相比直接播放一个完整MP4文件,m3u8有几个明显的优势:
第一,播放器可以边下边播,用户拖动进度条时只需下载对应的分片,首屏加载更快。第二,服务端方便做码率自适应,网络好的时候播高清分片,网络差自动切低码率分片。第三,防盗链能力更强,因为分片文件是动态请求的,可以在服务端做校验。
实测下来,对于一个几百MB的装修案例视频,切成10秒一个的分片,用户在4G网络下开播只要1到2秒,体验比直出MP4好很多。后来我还用同一套方案做了工地直播回放,把监控摄像头录制的视频直接切片存档,代码几乎可以复用。
4.2 前端用video.js接入HLS播放
前端播放m3u8,我用的方案是video.js。Vue 3项目里先安装依赖:
npm install video.js
在组件里引入样式和播放器初始化逻辑:
<template>
<div>
<video
id="caseVideo"
ref="videoElement"
class="video-js vjs-big-play-centered"
controls
preload="auto"
style="width: 100%; height: 100%"
></video>
</div>
</template>
<script setup>
import { onMounted, ref } from 'vue'
import videojs from 'video.js'
import 'video.js/dist/video-js.css'
const videoElement = ref(null)
let player = null
onMounted(() => {
player = videojs(videoElement.value, {
controls: true,
autoplay: false,
preload: 'auto',
sources: [
{
src: 'https://your-cdn.com/video/2025/demo.m3u8',
type: 'application/x-mpegURL'
}
]
})
})
onBeforeUnmount(() => {
if (player) {
player.dispose()
}
})
</script>
关键点是
sources
里的
type
必须设置为
application/x-mpegURL
,video.js才能识别并调用HLS引擎进行分片请求。如果不设置这个type,有些版本的video.js会尝试按MP4格式解析,结果视频黑屏不出画面。
实际开发中我还遇到了一个情况:点击切换不同案例视频时,直接修改
sources
数组不够稳健,正确做法是先调用
player.src()
设置新地址,再调用
player.load()
重新加载:
function playNewVideo(url) {
player.src({ src: url, type: 'application/x-mpegURL' })
player.load()
player.play()
}
不重新加载的话,播放器可能残留上一个视频的分片缓存,导致画面与标题不匹配,这个坑我在联调时排查了半天,最后加了
load()
就解决了。
4.3 视频处理与防盗链思路
服务端要生成m3u8索引和ts分片,我推荐直接用FFmpeg做转码切分,命令如下:
ffmpeg -i demo.mp4 -profile:v baseline -level 3.0 -start_number 0 -hls_time 10 -hls_list_size 0 -f hls demo.m3u8
几个参数简单解释一下:
-
-profile:v baseline:使用H.264 Baseline画质档位,兼容性最广,老手机也能播。 -
-hls_time 10:每10秒切一个分片,这个值根据视频时长调整,短一些加载更快,长一些请求数更少,我建议10到15秒。 -
-hls_list_size 0:生成的m3u8索引文件包含所有分片,默认值只会保留最近几个分片的记录,不加这个参数会出现视频播到一半就停止的问题。
转码完成后,把生成的.m3u8文件和.ts分片文件上传到对象存储服务并开启CDN分发即可。防盗链方面,我在生产环境做了两重措施:一是设置CDN的Referer白名单,只允许官网域名来源的请求访问视频资源;二是对部分付费内容的视频做签名URL校验,URL里带一个有时效的token参数,过期就返回403,这样即使别人拿到视频链接也无法直接播放。
5. 问题排查与避坑记录
5.1 npm.ps1无法加载脚本的系列问题
这个问题出现的频率太高了,几乎每天都能在各种开发群里看到。前面说过核心原因是PowerShell执行策略限制,但很多人改了策略以后还是遇到问题,这里我再补充几个排查方向:
第一,确认当前使用的是哪个shell。如果你在VS Code里用的是PowerShell终端,即使之前用CMD装好了Node,依然会触发这个报错。VS Code默认终端可能是PowerShell,可以在
Ctrl + Shift + P
里搜"Terminal: Select Default Profile",把默认切换成"Command Prompt"。
第二,执行策略修改后要重新打开终端窗口才会生效,不要在一个已经打开的终端里测试。第三,如果公司电脑有域策略锁死了执行策略,可以试试用
npx
代替
npm
运行某些命令,比如
npx vite
,因为npx走的执行链路不太一样,有时能绕过去。
我再提供一个终极方案:安装多版本管理工具
nvm-windows
,用它来安装Node。nvm安装的Node会把npm的软链接放到自己的目录,而且nvm自带的命令窗口不会触发PowerShell的脚本限制,治标又治本。更重要的是,nvm可以让你在多个Node版本之间自由切换,项目A需要Node 14、项目B需要Node 20的时候,一条命令就能搞定。
5.2 Vue打包后布局异常
开发环境一切正常,
npm run build
之后打包上传到服务器,打开页面发现布局乱了、图片不显示、样式丢失,这是Vue新手最常遇到的问题。我结合自己的排查经验,把原因和解决方式整理了一下。
最常见的原因是静态资源路径配置不对。Vite默认的
base
是
/
,也就是说打包后的资源引用路径是
/assets/index-xxx.js
,但如果你把前端文件部署在服务器的子目录下,比如
https://example.com/mall/
,这个绝对路径就会指向错误的位置。解决方法是在
vite.config.js
里设置:
export default defineConfig({
base: './' // 使用相对路径
})
这样构建出的
index.html
里资源引用会是相对路径,部署到任意子目录都不怕。
另一个常见原因是路由使用history模式后部署到Nginx时,刷新子页面出现404。这是SPA应用的通病——Nginx找不到
/mall/product/123
这个物理文件,需要配置try_files回退到index.html:
location /mall/ {
try_files $uri $uri/ /mall/index.html;
}
这样无论用户刷新哪个路由,Nginx都会把请求转发到前端入口,再由Vue Router接管路由解析。
还有一类布局异常是CSS样式错乱,通常是Element Plus按需引入配置不正确,组件样式没有全量加载导致。检查
main.js
里是否完整引入了
element-plus/dist/index.css
,如果只引用了部分组件的样式,页面就会出现"有结构没样式"的诡异效果。
5.3 路由参数与页面数据刷新问题
开发过程中还遇到过路由参数跳转后页面数据不更新的情况。比如从装修案例列表页进入详情页,第一次打开数据正常,返回列表再点进另一个案例,详情页显示的还是上一个案例的内容。
这个问题的根源是Vue组件复用了:当路由参数变化但组件实例没有销毁时,Vue不会重新执行组件的
onMounted
钩子,所以数据不会重新拉取。解决方式有两个:
第一种,在组件内监听路由变化,通过
watch
触发数据请求:
import { watch } from 'vue'
import { useRoute } from 'vue-router'
const route = useRoute()
watch(() => route.params.id, (newId) => {
if (newId) {
fetchData(newId)
}
}, { immediate: true })
第二种,给路由跳转加上
:key
强制组件重建:
<router-view :key="$route.fullPath" />
两方案对比,我推荐第一种。因为第二种会销毁重建整个页面,虽然能解决数据刷新问题,但页面会闪一下,而且组件内部的状态(比如滚动位置)都会丢失,体验不太好。
5.4 项目实战的延伸价值
这个项目做完以后,我最大的感受是它把前端和后端知识整个串起来了。之前学Vue可能停留在组件怎么写、路由怎么配,学Node可能停留在回调函数和模块化,但当你要做一个真实可运行的商城系统时,必须自己打通数据库设计、接口签名、鉴权、跨域、部署这一整条链路。
我后来面试也经常拿这套项目做案例来聊。面试官问Vue生命周期,我就说商品列表页从
onMounted
里拉数据到组件销毁时取消请求的完整流程;面试官问Node的事件循环,我就说高并发下订单接口的I/O阻塞怎么影响性能。有了真实项目背书,面试题不再是无源之水,答起来会很扎实。
写在最后的个人经验
这套项目从立项到跑通核心流程,我前前后后用了大概两个月,其中数据库设计和业务建模花的时间比写代码多得多。我最想分享的一个经验是:做全栈项目,一定要先花时间把业务模型画清楚,再去写代码。很多人在数据库建表阶段偷懒,表结构缺字段,后期就要靠加冗余字段或者拆表来补,改起来痛苦指数翻倍。
另外,Node.js做后端不是只能做Demo,配合Express、Sequelize和Redis,它完全有能力支撑一个日活几万的小型电商平台。选技术栈不用盲从大厂,符合自己团队能力和项目规模的就是最好的。
如果后续要继续扩展这个平台,我个人觉得有两个方向很值得投入:一个是接入即时通讯,让用户和设计师能在站内直接沟通;另一个是工地施工现场直播,用之前说的m3u8切片方案把监控画面实时推给业主。这两个功能对家装平台的信任提升比任何营销活动都管用。
火山引擎视频云技术社区,是面向 AI 音视频开发者的技术交流平台。这里汇聚源自抖音、豆包等亿级 DAU 产品的 RTC、直播、点播、AI 媒体处理、音视频互动技术,提供接入指南、最佳实践、性能调优、场景案例、Demo 代码、开源项目、白皮书和 API 文档。社区汇聚官方工程师与一线开发者,为 AI 视频通话、数字人、AI 视频处理等应用的开发与落地提供技术支持。
更多推荐
所有评论(0)