Node.js+Vue全栈开发明星周边商城系统:从环境配置到部署实战 📅 发布时间:2026/9/12 1:21:24 👁 浏览次数: 在粉丝经济越来越成熟的今天“明星周边”已经从单纯的应援物料变成了一个规模可观的垂直电商品类。不管是演唱会荧光棒、签名海报还是联名服饰、限定手办背后都离不开一套能承载商品展示、交易支付、订单管理的小型商城系统。我接到的这个项目——nodejsvue明星周边商城系统au72407e就是这样一套典型的全栈Web应用后端用Node.js提供接口服务前端用Vue构建用户界面面向的是有明星周边售卖需求的商家或个人站长。这篇文章我会从业务建模、技术选型、环境搭建、前后端功能实现、联调部署到问题排查完整梳理一遍这个项目的落地过程。不管你是刚学完Vue和Node基础、准备做毕设或实战项目的学生还是想快速搭建垂直电商系统的开发者这套思路和代码细节都能直接拿去用。1. 明星周边商城靠什么撑起来整体业务与方案选型1.1 核心业务拆解这不是一个“简单增删改查”很多人一听说“商城系统”第一反应就是“不就是商品表加订单表嘛”。真做起来就会发现周边商品和普通标品的差异点非常多业务建模的复杂度远超预期。明星周边商城有几个特殊场景需要单独考虑周边商品天然带强IP属性商品必须关联明星或作品IP用户进店后通常会先按“我家偶像”找商品再按品类筛。所以分类体系得设计成两级一级是明星/IP维度二级才是商品品类维度。周边商品往往有预售、补款、限时抢购、限量编号发货这些玩法订单状态比普通电商多出“待付定金”“待补尾款”“配货中”“已发货”等节点数据模型设计时要预留状态机。周边商品的详情页通常除了图文还有演唱会现场视频、综艺片段等视频物料这就涉及m3u8视频播放、视频与图文混排的问题。再说用户侧。这个系统的目标用户是粉丝群体他们访问时段集中偶像发新物料、开演唱会前后而且活跃度高二创分享、评论互动意愿强。所以除了基础的注册登录、商品浏览、购物车、下单支付还需要考虑公告管理、轮播图运营位、用户留言反馈、日活统计这类运营功能。这个项目实际落地时我把它拆成了前台用户端和后台管理端两块。前台用Vue全家桶实现SPA单页应用后台用Vue Router动态菜单配合权限控制。两个端共用一个Node.js后端服务通过JWT做身份鉴权区分普通用户和管理员接口层面做角色校验。这个结构后面会具体展开。1.2 为什么是 Node.js Vue选型逻辑和取舍这套技术栈没有选Spring Boot也没有选React原因很实际Node.js Vue是当前中小型全栈项目里单个开发者能最快完成从零到一闭环的组合。Node.js这边的优势很明确。它的异步I/O模型特别适合商城这种读多写少、并发集中在商品浏览和图片加载的场景。用Express写RESTful API非常轻量中间件生态成熟文件上传用Multer几行代码搞定JWT鉴权用jsonwebtoken也是开箱即用。而且JavaScript前后端同构前端工程师不需要额外学一门后端语言就能把接口写出来这在单人开发或小团队开发时效率优势巨大。Vue这边的理由更偏工程化。Vue的双向绑定让表单类页面登录注册、结算地址填写、后台商品录入开发效率非常高Vue Router的声明式路由配置和管理端的嵌套路由天然匹配Vuex/Pinia做状态管理可以把用户信息、购物车数据、全局配置统一管理起来。再加上Element Plus或者Ant Design Vue这类组件库后台管理界面的表格、表单、弹窗、分页几乎是现成拼装。有一点要提前说清楚这套组合适合的是中小流量项目。如果你预期日活万级以上或者有分布式事务、高并发秒杀需求那Node.js Vue不是最优解应该考虑Spring Cloud或者Go微服务。但明星周边商城这个体量Node.js单实例完全扛得住。我做压力测试时Node.js MySQL组合下普通商品列表接口QPS能稳定在800以上2核4G机器这对本项目绰绰有余。1.3 数据模型设计从商品 SKU 到订单状态机商城系统的表设计是地基我见过太多项目在这里偷懒后期越改越乱。这个项目的核心表我按“用户体系、商品体系、交易体系、内容体系”四块划分。用户体系相对简单用户表users字段包括openid预留微信登录、nickname、avatar、phone、role、status、create_time。管理员和普通用户都在这张表里用role字段区分省一张表。商品体系是重点需要慎重设计。我用了三层结构明星表starsstar_name、avatar、intro、fans_count这个是周边商品的IP源头。分类表categoriescategory_name、parent_id一级分类放“明星姓名”二级分类放“海报/服饰/手办/应援物/日用”这些品类。用parent_id做自关联前端就能做联动筛选。商品表productsproduct_name、star_id、category_id、cover_image、video_url、imagesJSON数组、detail富文本HTML、price、stock、sales_count、is_hot、is_new、status、create_time。这里有个细节周边商品存在同一款式多规格的情况比如一件应援T恤有S/M/L三个尺码每张海报有签名版和普通版。严格做法是再拆一层SKU子表但考虑到项目体量和管理员的维护成本我选择了“商品主表 JSON规格字段”的方案规格参数以JSON格式存储下单时把所选规格快照写入订单详情表。这个方案在小规模项目里操作成本最低而且灵活性反而更高。交易体系涉及订单生命周期。订单表orders核心字段是order_no、user_id、total_amount、address_snapshot、status、pay_type、pay_time、ship_time、finish_time。订单状态我设计成待支付、已支付待发货、已发货、已完成、已取消、退款中一共6个状态。周边预售场景下可以把“待付定金”作为待支付的子状态处理用定金比例字段区分避免状态过于碎片化。内容体系主要存放轮播图banners、公告notices、用户留言messages、收藏记录favorites等运营数据。数据库我推荐用MySQL 8.0字符集utf8mb4表引擎InnoDB。商品表的text字段存储富文本和图片JSON查询时按需取字段避免全量查大字段拖慢列表接口。2. 开发环境从零到能跑Node.js 与 Vue 环境配置实录2.1 Node.js 安装与版本管理一个重要起点这个项目的第一步先把Node.js环境搞定。“nodejs安装及环境配置”看起来是入门中的入门但我在帮别人看这个项目时遇到最多的问题恰恰都出在这个环节。Node.js下载尽量去官网不要用第三方站点。版本选择上我建议直接装LTS长期支持版本不要追求最新版。为什么Node.js偶数版本是LTS稳定性和依赖兼容性最好国外的Express、Multer这些核心依赖都会优先保证LTS版本的兼容。这个项目我用的Node.js 18.x LTS实测运行一个季度没有任何异常。安装过程有几个关键点安装路径不要带空格和中文。默认的“C:\Program Files\nodejs\”其实是有空格的大部分情况下没影响但偶尔某些原生依赖编译时就会因为路径空格出幺蛾子。我习惯改为“C:\nodejs\”。安装时勾选“Add to PATH”否则后续需要在系统环境变量里手动配置。安装完成后验证是否成功用两个命令node -v npm -v两个命令都有版本号输出说明安装成功。接着设置npm镜像和全局路径这一步很多人会忽略。国内直接拉取npm官方源经常超时所以必须配置镜像源npm config set registry https://registry.npmmirror.com npm config set prefix C:\nodejs\node_global npm config set cache C:\nodejs\node_cache这里有个细节全局安装的包比如npm install -g vue/cli默认放在nodejs安装目录下如果没有设置全局路径权限Windows下经常报EACCES错误。设置prefix之后可执行文件会统一放到node_global目录下需要把这个目录手动加到系统PATH里否则全局命令找不到。装完环境之后我习惯先装一个yarn或者pnpm做备用包管理器。npm在安装依赖多的大型项目时速度慢、依赖树容易出问题pnpm的硬链接机制安装速度更快而且能解决“幽灵依赖”问题。这个项目我最后用的是pnpm但下面示例为了通用性还是用npm。2.2 一个高频踩坑PowerShell禁止运行脚本项目开发中最高频的问题就是启动脚本时报错“npm : 无法加载文件 C:\Program Files\nodejs\npm.ps1因为在此系统上禁止运行脚本”。这个报错的原因跟Node.js无关是PowerShell的执行策略默认限制运行脚本文件。解决办法有两种第一种是临时授权在PowerShell中执行Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass这个命令只对当前窗口生效窗口关闭后恢复限制适合临时跑一下脚本的情况。第二种是永久修改当前用户的执行策略Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSignedRemoteSigned意思是本地脚本可以运行从网上下载的脚本需要签名这是最平衡的安全策略。执行时如果提示确认输入Y回车即可。这个问题之所以高频是因为Vue CLI、Vite、npm脚本本质上都是.js或.ps1文件都要通过脚本解释器执行。遇到这个报错不用慌改个策略就完事。2.3 创建Vue项目与基础目录规划环境准备好之后开始创建前端项目。这个项目我用的是Vue 3 Vite组合不再用Vue CLI。原因很直接Vite基于ESBuild预构建依赖冷启动快到1秒内开发热更新也是毫秒级比Webpack时代的体验舒服太多。如果用的是Vue 2那才需要走Vue CLI这条路但新项目直接Vue 3就好。创建命令npm create vitelatest mall-frontend -- --template vue cd mall-frontend npm install npm install vue-router4 pinia element-plus axios项目结构按功能模块划分方便后续维护src/ |-- api/ # 接口请求封装 |-- assets/ # 静态资源 |-- components/ # 通用组件 |-- router/ # 路由配置 |-- stores/ # Pinia状态 |-- views/ # 页面组件 |-- utils/ # 工具函数 |-- App.vue |-- main.js后端项目我用Express作为HTTP服务框架目录结构如下server/ |-- routes/ # 路由定义 |-- controllers/ # 控制器处理业务逻辑 |-- models/ # 数据库模型SQL语句或ORM映射 |-- middleware/ # 中间件JWT校验、错误处理、上传 |-- config/ # 数据库连接池、常量配置 |-- public/ # 静态资源目录图片、视频 |-- app.js # 入口文件前后端两个项目分开开发、分开部署通过HTTP接口通信这就是典型的前后端分离模式。后面讲跨域和部署时这个结构会发挥很大作用。3. 前端核心功能实现思路商品浏览、登录与购物车3.1 明星专区与商品列表联动筛选怎么做商城首页是整个系统给用户的第一印象对于明星周边商城我把它设计成四个功能模块轮播图运营位展示最新代言/演唱会信息、明星专区横向滑动的明星头像入口、热销商品按销量倒序、最新上架按创建时间倒序。明星专区点击进入的是“明星详情页该明星全部周边”的组合页面。这里前端路由设计成一个嵌套路由{ path: /star/:id, name: StarDetail, component: () import(/views/star/StarDetail.vue), children: [ { path: , name: StarProducts, component: () import(/views/star/StarProducts.vue) } ] }通过路由参数/id获取明星基础信息下方商品列表组件再从API拉取“该明星下的所有商品可选二级分类筛选”。商品列表页的筛选交互是这个项目比较出彩的地方。左侧或顶部显示明星列表点击明星切换时商品分类联动刷新为该明星对应的商品分类。实现方式是每次切换明星时调用一次分类接口再调用一次商品列表接口两个接口并行发送用Promise.all同时处理。const loadProducts async () { const [cateRes, prodRes] await Promise.all([ getCategoriesByStar(starId), getProducts({ starId, categoryId: currentCate.value, page, pageSize }) ]) categories.value cateRes.data products.value prodRes.data.list }列表展示用卡片布局商品封面、名称、价格、销量四个核心信息。封面加载做了懒加载处理用Vue的指令方式实现图片进入可视区才开始加载这样商品多时页面滚动不会卡顿。这里的销量字段是一个小运营技巧列表排序时优先按goods_sales排列而不是按创建时间用户更信任“卖得多”的商品。3.2 登录注册与 Token 管理JWT 的完整落地登录注册模块我用了JWT方案流程是用户提交账号密码后端校验通过后签发一段带过期时间的Token字符串返回前端前端存入localStorage后续每个请求在请求头里带上Authorization字段后端中间件统一校验。注册接口的密码存储这里必须多说一句绝对不能明文存储密码。我用了bcryptjs做哈希加密注册时对明文密码进行加盐哈希即使数据库泄露密码原文也无法被还原。const bcrypt require(bcryptjs) const saltRounds 10 // 注册时 const hashedPassword await bcrypt.hash(password, saltRounds) // 登录校验时 const isMatch await bcrypt.compare(inputPassword, user.password)Token签发的代码const jwt require(jsonwebtoken) const token jwt.sign( { id: user.id, role: user.role }, process.env.JWT_SECRET, { expiresIn: 7d } )这里有一个容易踩的坑JWT_SECRET要放在环境变量文件里不要写死在代码中。项目提交到Git仓库前必须把.env文件加入.gitignore。前端对Token的管理我封装了一个request.js统一在请求拦截器里携带Tokenimport axios from axios const service axios.create({ baseURL: /api, timeout: 10000 }) service.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers.Authorization Bearer ${token} } return config }) service.interceptors.response.use( response response.data, error { if (error.response.status 401) { // Token过期清除登录态跳转登录页 localStorage.removeItem(token) router.push(/login) } return Promise.reject(error) } )后端JWT校验中间件再对每个需要登录的接口做一次Token解密、身份校验。前端有拦截后端有校验双重保护。3.3 购物车、订单提交与库存扣减购物车是电商前端里状态管理最复杂的部分之一。用Pinia维护一个购物车Store涉及的操作有加入购物车、修改数量、勾选/取消勾选、删除商品、计算结算总金额。购物车数据结构// stores/cart.js export const useCartStore defineStore(cart, { state: () ({ items: [] // [{ productId, productName, cover, price, spec, quantity, checked }] }), getters: { totalQuantity: state state.items.reduce((sum, item) sum item.quantity, 0), checkedAmount: state state.items .filter(item item.checked) .reduce((sum, item) sum item.price * item.quantity, 0) } })勾选金额是下单时的实付金额依据用getter计算属性动态计算每次点击勾选或数量增减金额自动刷新响应式的好处在这里体现得淋漓尽致。订单提交是整个交易链路的起点也是最容易出并发问题的环节。用户提交订单后前端调用创建订单接口后端要完成校验商品是否存在和上下架状态、校验库存是否充足、扣减库存、创建订单记录初始状态为待支付、返回订单编号。这几个操作必须放在一个数据库事务里任何一个失败都要回滚。我用了MySQL事务通过封装一个连接池调用BEGIN/COMMIT/ROLLBACK实现。这里关键是先扣库存再创建订单防止超卖。如果用户下单后30分钟未支付用定时任务把库存加回订单标记为已取消。3.4 m3u8视频与图片懒加载商品详情页性能优化明星周边商品详情页尤其是手办、专辑这类商品视频物料是标配。项目里视频文件我用的是m3u8格式这是目前H5播放体验最好的方案支持切片加载、拖动播放不卡顿、手机端兼容性好。Vue里播放m3u8视频用video.js插件安装npm install video.js组件中封装import videojs from video.js import video.js/dist/video-js.css export default { mounted() { this.player videojs(this.$refs.videoPlayer, { sources: [{ src: this.videoUrl, type: application/x-mpegURL }], controls: true, preload: metadata }) }, beforeUnmount() { if (this.player) { this.player.dispose() } } }有两个坑提醒一下。第一m3u8文件本身只是索引文件它内部引用了很多.ts切片文件这些切片文件必须和m3u8放在同一个可访问的目录下并且不能做防盗链限制否则播放器会报错“Failed to fetch segments”。第二播放器实例一定要在组件卸载时用dispose()销毁尤其在列表页和详情页之间反复切换时不销毁会一直占内存最后页面卡死。Vue开发调试时我推荐装Vue Devtools插件可以查看路由状态、Store数据、组件Props排查这类问题非常方便。4. 后端 API 设计从路由到数据库操作的完整闭环4.1 接口规范与统一返回格式一次定义省的是一堆后患前后端联调时最烦的事情就是各自为政A接口返回{code:200, data:{}}B接口返回{success:true, result:{}}前端取数据的时候每个接口都要单独判断异常处理根本没法统一。这个项目一开始我就在后端定义了一套统一的返回格式。所有接口一律返回{ code: 200, message: success, data: {} }code为200表示成功其他业务码含义如下400参数错误401未登录或Token失效403权限不足404资源不存在500服务器内部错误1001业务异常如库存不足、重复下单后端实现一个response中间件在Express中给res挂一个统一方法app.use((req, res, next) { res.ok (data null, message success) { res.json({ code: 200, message, data }) } res.fail (code 500, message error) { res.json({ code, message, data: null }) } next() })所有controller直接调用res.ok()和res.fail()不用重复写JSON结构。RESTful接口按照资源设计本项目核心接口如下POST /api/auth/register 注册POST /api/auth/login 登录GET /api/stars 获取明星列表GET /api/products 获取商品列表支持分页、明星筛选、分类筛选、搜索关键字GET /api/products/:id 获取商品详情POST /api/cart 加入购物车GET /api/cart 获取购物车列表POST /api/orders 创建订单GET /api/orders 获取当前用户订单列表PUT /api/orders/:id/status 更新订单状态发货、确认收货商品列表接口的分页参数我习惯用page和pageSize而不是limit/offset后者虽然更底层但前端的通用性差。4.2 文件上传与静态资源管理商品图片的硬骨头周边商城的商品图片数量非常大一个热门明星的商品可能有几百张图片所以图片上传和管理必须做好。本项目用Multer中间件处理图片上传。const multer require(multer) const path require(path) const storage multer.diskStorage({ destination: function (req, file, cb) { cb(null, public/uploads/) }, filename: function (req, file, cb) { const ext path.extname(file.originalname) const uniqueName Date.now() - Math.round(Math.random() * 1e9) ext cb(null, uniqueName) } }) const upload multer({ storage, limits: { fileSize: 5 * 1024 * 1024 }, // 限制5MB fileFilter: (req, file, cb) { const allowTypes [image/jpeg, image/png, image/webp] if (allowTypes.includes(file.mimetype)) { cb(null, true) } else { cb(new Error(仅支持JPG/PNG/WebP格式)) } } })文件命名用时间戳随机数的组合避免重名覆盖。上传后的文件存到server/public/uploads目录express.static配置为静态资源目录前端通过完整URL直接访问。这里一定要处理上传体积限制。后台管理员上传高清宣传图时经常超过5MB这时前端会收到413错误。需要在前端上传组件里做图片压缩或者修改Multer的limits配置。我这个项目选择的方式是后端限制10MB前端用canvas压缩图片到宽度不超过2000px、质量0.85处理后的图片在保证清晰度的同时大幅减小体积省服务器带宽也省存储。4.3 订单状态流转与后台统计订单状态管理是后台管理端的核心功能。管理员登录管理端后可以查看所有订单列表、按状态筛选、对订单执行发货操作。订单状态流转我用一个简单的状态机控制const ORDER_STATUS { PENDING_PAYMENT: 0, // 待支付 PAID: 1, // 已支付待发货 SHIPPED: 2, // 已发货 COMPLETED: 3, // 已完成 CANCELLED: 4, // 已取消 REFUNDING: 5 // 退款处理中 }后端更新订单状态的接口需要校验当前状态是否允许跳转到目标状态比如待支付订单不能直接变已完成。这种约束放在后端校验前端完全不可信否则用户直接调接口就能把订单改成已完成。后台数据看板也要做管理员进入后台第一个页面是统计面板展示今日订单数、今日销售额、商品总数、用户总数、待发货订单数。SQL统计// 今日销售额 const today new Date().toISOString().split(T)[0] const sql SELECT COUNT(*) as orderCount, IFNULL(SUM(total_amount),0) as totalSales FROM orders WHERE DATE(create_time) ? AND status IN (1,2,3)这几个指标能支撑管理员做日常运营判断又不至于过度设计。5. 联调、打包与部署前后端分离项目上线的那些坑5.1 前后端联调跨域问题的三种解法开发联调阶段最典型的问题是跨域。前端跑在localhost:5173Vite默认端口后端跑在localhost:3000域名不同端口不同浏览器会直接拦截跨域请求。联调时我推荐最轻量的方式Vite开发服务器的代理配置。在vite.config.js中设置export default defineConfig({ server: { proxy: { /api: { target: http://localhost:3000, changeOrigin: true } } } })前端所有请求都写相对路径/api/xxx开发时Vite代理把请求转发到后端3000端口浏览器看到的仍然是同源请求完全绕开跨域限制而且不用后端配置CORS。这个方式的优势是上线后不需要改代码——线上环境通过Nginx反向代理使用同样的路径规则前后端代码和配置完全一致。如果后端需要配置CORS也可以在Express中统一处理app.use((req, res, next) { res.setHeader(Access-Control-Allow-Origin, *) res.setHeader(Access-Control-Allow-Methods, GET, POST, PUT, DELETE, OPTIONS) res.setHeader(Access-Control-Allow-Headers, Content-Type, Authorization) if (req.method OPTIONS) { return res.sendStatus(200) } next() })跨域的三种常见情况里Vite代理适合开发阶段Nginx反向代理适合生产环境CORS配置适合前后端完全分离部署的场景。联调阶段优先用Vite代理能少踩很多坑。5.2 前端打包与部署Vue项目的产物怎么放前端开发完需要打包成静态文件。使用Vite的构建命令npm run build打包完成后在dist目录生成index.html和assets目录下的js/css文件。部署方式有两种选择如果前后端部署在同一台服务器上用Nginx同时托管前端静态文件和后端API代理这是最推荐的方式原因很简单首先避免了跨域问题前端和后端通过同一个域名的不同路径访问浏览器请求都是同源。其次静态文件和API共用80端口不用额外开放端口。Nginx配置如下server { listen 80; server_name your-domain.com; # 前端静态文件 location / { root /var/www/mall-frontend/dist; try_files $uri $uri/ /index.html; } # 后端API代理 location /api/ { proxy_pass http://127.0.0.1:3000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }这里的try_files配置很关键Vue Router用的是history模式路由是前端控制的。用户直接访问/star/1这个URL时服务器上没有这个物理文件必须fallback到index.html由前端路由接管页面渲染。如果不加try_files这行用户刷新非首页就会404。另外前端路由的比较容易忽略的是Vue Router的base配置。如果部署在子目录下比如服务器域名是example.com/mall/就需要在router配置里设置base: /mall/否则所有路由都404。5.3 后端进程守护与数据库连接池后端是Node.js进程开发时用node app.js跑三分钟没输出就心慌生产环境断线重连这个问题必须解决。我用了PM2进程管理器npm install -g pm2 pm2 start app.js --name mall-server pm2 save pm2 startuppm2 startup生成的命令需要在服务器上执行一次之后服务器开机后pm2会自动拉起Node.js服务进程崩溃或内存溢出时pm2会自动重启。这个守护机制是上线必须的否则半夜进程挂了第二天早上用户访问全挂你还在睡觉。数据库连接这块不能用每次请求都创建一个连接的方式那样并发稍微一大MySQL直接报“Too many connections”。我用的是mysql2连接池const mysql require(mysql2/promise) const pool mysql.createPool({ host: localhost, user: root, password: your_password, database: star_mall, waitForConnections: true, connectionLimit: 10, queueLimit: 0 })连接池复用连接把创建/销毁连接的开销降到最低。connectionLimit设为102核4G的机器足够。6. 常见问题排查实录从真实故障到解决套路6.1 npm镜像与依赖安装问题这个项目开发中遇到几个典型问题我记录下来作为排查参考。npm安装依赖时最常见的错误是网络超时比如“ERR! code ETIMEDOUT”。处理办法就是换镜像源前面已经配置过npmmirror如果还有个别包拉不下来可以试试单独设置代理npm install --registryhttps://registry.npmmirror.com安装Element Plus时遇到过版本兼容问题。如果创建Vue项目时默认的Vite版本是5.xElement Plus官方支持Vue 3.3以上版本如果Vue版本偏低可能报“Cannot read properties of undefined (reading install)”。解决办法是把依赖升级到最新版然后重新安装npm install vuelatest element-pluslatest6.2 打包后布局异常一个容易忽略的CSS路径问题开发环境一切正常npm run build打包后部署上线发现页面样式全乱了图片也404了。这个问题我排查了半小时最后发现是资源路径问题。Vite默认构建配置中资源引用使用的是绝对路径“/assets/xxx”如果把前端部署在服务器子目录下绝对路径就从根目录找自然找不到。解决办法是在vite.config.js中设置baseexport default defineConfig({ base: ./, // 使用相对路径 })设置为./后打包产物的资源引用会变成相对路径部署到任意子目录都没问题。这里注意修改base之后需要重新打包而且老版本的index.html有缓存部署时要清一下浏览器缓存再看。6.3 Token过期与页面白屏用户长时间停留在页面突然点击跳转时白屏控制台报“Invalid token”或“Cannot read property name of null”。这个问题根因是localStorage中的Token过期或用户信息被清掉但前端没有做统一处理导致页面尝试读取用户信息时出错。解决思路是路由守卫里统一做登录状态检查未登录或Token过期跳转到登录页并清空本地存储。在Vue Router配置中添加全局前置守卫router.beforeEach((to, from, next) { const token localStorage.getItem(token) const whiteList [/login, /register] if (!token !whiteList.includes(to.path)) { next(/login) } else { next() } })白名单页面不需要登录就能访问其余页面必须验证Token。这个逻辑要放在所有页面访问之前执行。6.4 本地图片可访问、部署后图片404这个坑非常隐蔽。开发环境图片正常部署后全部404而且Nginx配置没问题静态文件路径也没问题找来找去发现是后端接口返回的图片路径是绝对路径“/uploads/xxx”Nginx没有把/uploads这个路径映射到后端的public目录。解决办法是在Nginx配置中增加一条location规则location /uploads/ { alias /var/www/mall-server/public/uploads/; }或者在后端接口返回图片URL时统一拼接上服务器域名前缀返回完整URL。前者更推荐因为图片路径改动不用重新发布代码。还有一点阿里云、腾讯云这类云服务器的安全组默认不开放3000端口如果用“http://服务器IP:3000/uploads/xxx”直接访问图片流量会被安全组拦掉。所以图片资源一律走80端口通过Nginx转发不要直连Node.js端口。6.5 购物车数据不一致问题有用户反馈说“我在电脑上加了购物车手机上看不到”排查之后发现是购物车数据存了localStorage不同设备数据不互通。后来我把购物车改成“登录后同步到后端”的架构用户未登录时购物车存本地登录成功后把本地购物车合并到后端数据库并以后端数据为准。这样跨设备数据一致但代价是后端需要设计一套购物车同步接口。对这个项目来说我最终采取了一个折中方案购物车存储在后端用户登录前不能使用购物车功能但可以浏览商品、收藏商品。浏览器控制台的数据显示访问周边商城的用户登录率在85%以上临时游客的购物车需求不高这种策略带来的体验损失可以接受。7. 几点实操体会这个项目从环境搭建到功能开发、部署上线整个流程走下来我最大的体会是当业务需求足够清晰时Node.js Vue这套技术栈的开发效率真的很高。前端组件化让后台管理界面像搭积木一样快后端的中间件机制让鉴权、日志、异常处理变得非常规整JavaScript全栈让前后端沟通成本降到最低。最后分享一个小技巧项目上线后建议在后台增加一个“每日简报”页面展示昨天和前天的订单量、销售额、新增用户对比数据用简单的SQL聚合即可。很多运营决策并不是靠复杂的商业智能报表而是靠几个核心指标的一致性变化。明星周边这类粉丝属性很强的商城还有一件可以做的事统计各明星维度的销量排行根据榜单决定首页运营位的展示顺序。这个功能我是在第一版上线后两周加的给运营带来了很明显的效率提升。如果你也正准备开发类似的小型电商系统按这个思路先搭骨架、再填业务、最后做优化整个过程会少走很多弯路。这套项目源码的整体思路和核心代码我在实际项目中验证过稳定性直接参考是没有问题的。