news 2026/4/10 14:02:41

Vue生态拓展与实战案例07,电商项目实战:Vue+Vuex+Vue Router 核心功能实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Vue生态拓展与实战案例07,电商项目实战:Vue+Vuex+Vue Router 核心功能实现

在前端开发领域,电商项目是综合考察 Vue 全家桶技术栈的经典场景。本文将从实战角度出发,基于 Vue 2(兼顾 Vue 3 适配思路),完整讲解如何用 Vuex 做状态管理、Vue Router 实现路由控制,搭建电商项目的核心功能模块,包括商品列表、详情、购物车、登录鉴权等,帮你打通 Vue 全家桶在电商场景的落地应用。

一、项目初始化与技术选型

1. 技术栈确定

  • 核心框架:Vue 2(示例代码)/ Vue 3 + Composition API(附适配说明)
  • 路由:Vue Router 3(Vue2)/ 4(Vue3)
  • 状态管理:Vuex 3(Vue2)/ Pinia(Vue3 替代方案)
  • 构建工具:Vue CLI(快速搭建项目)
  • 辅助:Axios(接口请求)、CSS 预处理器(Scss)

2. 项目初始化

# 安装Vue CLI npm install -g @vue/cli # 创建项目 vue create ecommerce-demo # 选择Manually select features,勾选Router、Vuex、CSS Pre-processors等 # 进入项目 cd ecommerce-demo # 安装axios npm install axios --save

初始化完成后,项目核心目录结构如下:

ecommerce-demo/ ├── src/ │ ├── api/ # 接口请求封装 │ ├── components/ # 公共组件(商品卡片、导航栏等) │ ├── pages/ # 页面组件(首页、详情页、购物车等) │ ├── router/ # 路由配置 │ ├── store/ # Vuex状态管理 │ ├── App.vue # 根组件 │ └── main.js # 入口文件

二、Vue Router:电商路由体系搭建

电商项目的路由需满足「页面跳转、参数传递、登录鉴权、路由懒加载」核心需求,以下是完整实现方案。

1. 路由配置(router/index.js)

import Vue from 'vue' import Router from 'vue-router' Vue.use(Router) // 路由懒加载:优化首屏加载速度 const Home = () => import('@/pages/Home') const GoodsDetail = () => import('@/pages/GoodsDetail') const Cart = () => import('@/pages/Cart') const Login = () => import('@/pages/Login') const Order = () => import('@/pages/Order') const router = new Router({ mode: 'history', // 去除URL中的# routes: [ { path: '/', name: 'Home', component: Home, meta: { title: '首页 - 电商商城' } }, { path: '/goods/:id', // 动态路由:传递商品ID name: 'GoodsDetail', component: GoodsDetail, meta: { title: '商品详情 - 电商商城' } }, { path: '/cart', name: 'Cart', component: Cart, meta: { title: '购物车 - 电商商城', requireAuth: true // 需要登录鉴权 } }, { path: '/order', name: 'Order', component: Order, meta: { title: '订单结算 - 电商商城', requireAuth: true } }, { path: '/login', name: 'Login', component: Login, meta: { title: '登录 - 电商商城' } }, // 404路由 { path: '*', redirect: '/' } ] }) // 路由守卫:登录鉴权 router.beforeEach((to, from, next) => { // 设置页面标题 if (to.meta.title) { document.title = to.meta.title } // 校验登录状态 const isLogin = localStorage.getItem('token') if (to.meta.requireAuth && !isLogin) { next({ path: '/login', query: { redirect: to.fullPath } }) // 记录跳转前路由 } else { next() } }) export default router

2. 路由使用示例

  • 首页跳转商品详情:
<template> <div class="goods-list"> <div class="goods-item" v-for="goods in goodsList" :key="goods.id" @click="toGoodsDetail(goods.id)" > <img :src="goods.img" alt=""> <p>{{ goods.name }}</p> <span>¥{{ goods.price }}</span> </div> </div> </template> <script> export default { data() { return { goodsList: [/* 商品数据 */] } }, methods: { toGoodsDetail(id) { this.$router.push({ path: `/goods/${id}` }) } } } </script>
  • 登录页跳转回原路由:
<template> <div class="login"> <input v-model="username" placeholder="用户名"> <input v-model="password" type="password" placeholder="密码"> <button @click="login">登录</button> </div> </template> <script> export default { data() { return { username: '', password: '' } }, methods: { async login() { // 模拟登录请求 const res = await this.$api.login({ username: this.username, password: this.password }) if (res.code === 200) { localStorage.setItem('token', res.token) // 跳回登录前的路由,若无则跳首页 const redirect = this.$route.query.redirect || '/' this.$router.push(redirect) } } } } </script>

三、Vuex:电商核心状态管理

电商项目中,购物车、用户信息、商品列表等状态需要跨组件共享,Vuex 是最佳解决方案。以下是模块化的 Vuex 配置(按功能拆分模块,降低耦合)。

1. Vuex 核心配置(store/index.js)

import Vue from 'vue' import Vuex from 'vuex' import goods from './modules/goods' // 商品模块 import cart from './modules/cart' // 购物车模块 import user from './modules/user' // 用户模块 Vue.use(Vuex) export default new Vuex.Store({ modules: { goods, cart, user }, // 严格模式:开发环境开启,生产环境关闭 strict: process.env.NODE_ENV !== 'production' })

2. 模块实现:购物车(store/modules/cart.js)

购物车是电商核心,需实现「添加、删除、修改数量、全选、计算总价」功能:

// 本地存储封装:持久化购物车数据 const getCartFromLocal = () => { return JSON.parse(localStorage.getItem('cart') || '[]') } export default { namespaced: true, // 开启命名空间,避免模块间命名冲突 state: { cartList: getCartFromLocal() // 购物车列表 }, getters: { // 计算购物车商品总数 cartTotalCount: (state) => { return state.cartList.reduce((total, item) => total + item.count, 0) }, // 计算购物车总价 cartTotalPrice: (state) => { return state.cartList.reduce((total, item) => { return total + item.price * item.count }, 0).toFixed(2) }, // 判断是否全选 isAllChecked: (state) => { return state.cartList.length > 0 && state.cartList.every(item => item.checked) } }, mutations: { // 添加商品到购物车 ADD_TO_CART(state, goods) { // 查找是否已存在该商品 const existGoods = state.cartList.find(item => item.id === goods.id) if (existGoods) { // 存在则数量+1 existGoods.count += 1 } else { // 不存在则添加,默认选中 state.cartList.push({ ...goods, count: 1, checked: true }) } // 同步到本地存储 localStorage.setItem('cart', JSON.stringify(state.cartList)) }, // 修改商品数量 CHANGE_GOODS_COUNT(state, { id, count }) { const goods = state.cartList.find(item => item.id === id) if (goods) { goods.count = count localStorage.setItem('cart', JSON.stringify(state.cartList)) } }, // 切换商品选中状态 TOGGLE_GOODS_CHECKED(state, id) { const goods = state.cartList.find(item => item.id === id) if (goods) { goods.checked = !goods.checked localStorage.setItem('cart', JSON.stringify(state.cartList)) } }, // 全选/取消全选 TOGGLE_ALL_CHECKED(state, isChecked) { state.cartList.forEach(item => { item.checked = isChecked }) localStorage.setItem('cart', JSON.stringify(state.cartList)) }, // 删除购物车商品 DELETE_GOODS_FROM_CART(state, id) { state.cartList = state.cartList.filter(item => item.id !== id) localStorage.setItem('cart', JSON.stringify(state.cartList)) } }, actions: { // 异步添加商品(可扩展:先请求库存再添加) addToCart({ commit }, goods) { return new Promise((resolve) => { // 模拟库存校验 setTimeout(() => { commit('ADD_TO_CART', goods) resolve('添加成功') }, 500) }) } } }

3. 模块实现:商品(store/modules/goods.js)

import api from '@/api' export default { namespaced: true, state: { goodsList: [], // 商品列表 currentGoods: null // 当前选中商品详情 }, mutations: { SET_GOODS_LIST(state, list) { state.goodsList = list }, SET_CURRENT_GOODS(state, goods) { state.currentGoods = goods } }, actions: { // 获取商品列表 async getGoodsList({ commit }) { const res = await api.getGoodsList() commit('SET_GOODS_LIST', res.data) }, // 获取商品详情 async getGoodsDetail({ commit }, id) { const res = await api.getGoodsDetail(id) commit('SET_CURRENT_GOODS', res.data) } } }

4. Vuex 在组件中的使用

  • 商品详情页添加购物车:
<template> <div class="goods-detail" v-if="currentGoods"> <img :src="currentGoods.img" alt=""> <h2>{{ currentGoods.name }}</h2> <p>¥{{ currentGoods.price }}</p> <button @click="addToCart">加入购物车</button> </div> </template> <script> import { mapActions, mapState } from 'vuex' export default { created() { // 获取商品详情 this.getGoodsDetail(this.$route.params.id) }, computed: { ...mapState('goods', ['currentGoods']) }, methods: { ...mapActions('goods', ['getGoodsDetail']), ...mapActions('cart', ['addToCart']), addToCart() { this.addToCart(this.currentGoods).then(msg => { this.$message.success(msg) // 假设引入了ElementUI的消息提示 }) } } } </script>
  • 购物车页面使用 Vuex 数据:
<template> <div class="cart"> <div class="cart-item" v-for="item in cartList" :key="item.id"> <input type="checkbox" v-model="item.checked" @change="$store.commit('cart/TOGGLE_GOODS_CHECKED', item.id)" > <img :src="item.img" alt=""> <p>{{ item.name }}</p> <button @click="changeCount(item.id, item.count - 1)" :disabled="item.count <= 1">-</button> <span>{{ item.count }}</span> <button @click="changeCount(item.id, item.count + 1)">+</button> <span>¥{{ (item.price * item.count).toFixed(2) }}</span> <button @click="deleteGoods(item.id)">删除</button> </div> <div class="cart-footer"> <input type="checkbox" v-model="isAllChecked" @change="$store.commit('cart/TOGGLE_ALL_CHECKED', isAllChecked)" >全选 <span>总价:¥{{ cartTotalPrice }}</span> <button @click="toOrder" :disabled="cartTotalCount === 0">去结算</button> </div> </div> </template> <script> import { mapState, mapGetters } from 'vuex' export default { computed: { ...mapState('cart', ['cartList']), ...mapGetters('cart', ['cartTotalCount', 'cartTotalPrice', 'isAllChecked']) }, methods: { changeCount(id, count) { this.$store.commit('cart/CHANGE_GOODS_COUNT', { id, count }) }, deleteGoods(id) { this.$store.commit('cart/DELETE_GOODS_FROM_CART', id) }, toOrder() { this.$router.push('/order') } } } </script>

四、核心功能整合与优化

1. 接口请求封装(api/index.js)

import axios from 'axios' // 创建axios实例 const service = axios.create({ baseURL: process.env.VUE_APP_BASE_API, // 环境变量配置接口前缀 timeout: 5000 }) // 请求拦截器:添加token service.interceptors.request.use( config => { const token = localStorage.getItem('token') if (token) { config.headers['Authorization'] = `Bearer ${token}` } return config }, error => { return Promise.reject(error) } ) // 响应拦截器:统一处理错误 service.interceptors.response.use( response => { const res = response.data if (res.code !== 200) { // 统一错误提示 console.error(res.msg || '请求失败') return Promise.reject(res) } else { return res } }, error => { console.error('接口请求错误:', error) return Promise.reject(error) } ) // 封装接口方法 export default { // 商品列表 getGoodsList() { return service.get('/goods/list') }, // 商品详情 getGoodsDetail(id) { return service.get(`/goods/detail/${id}`) }, // 登录 login(data) { return service.post('/user/login', data) } }

2. Vue3 + Pinia 适配思路

若使用 Vue3,推荐用 Pinia 替代 Vuex,核心改动如下:

// store/cart.js(Pinia) import { defineStore } from 'pinia' export const useCartStore = defineStore('cart', { state: () => ({ cartList: JSON.parse(localStorage.getItem('cart') || '[]') }), getters: { cartTotalCount: (state) => state.cartList.reduce((t, i) => t + i.count, 0), cartTotalPrice: (state) => state.cartList.reduce((t, i) => t + i.price * i.count, 0).toFixed(2), isAllChecked: (state) => state.cartList.length && state.cartList.every(i => i.checked) }, actions: { addToCart(goods) { const exist = this.cartList.find(i => i.id === goods.id) if (exist) exist.count++ else this.cartList.push({ ...goods, count: 1, checked: true }) localStorage.setItem('cart', JSON.stringify(this.cartList)) }, // 其他方法... } }) // 组件中使用 import { useCartStore } from '@/store/cart' export default { setup() { const cartStore = useCartStore() const addToCart = (goods) => { cartStore.addToCart(goods) } return { addToCart, cartStore } } }

3. 性能优化点

  • 路由懒加载:已在路由配置中实现,减少首屏加载体积;
  • 购物车数据持久化:通过 localStorage 同步,刷新后数据不丢失;
  • 防抖 / 节流:商品列表搜索、数量修改等场景添加防抖,避免频繁请求 / 更新;
  • 虚拟列表:商品列表数据量大时,使用 vue-virtual-scroller 优化渲染性能;
  • 接口缓存:商品详情等高频访问接口,添加缓存避免重复请求。

五、总结

本文基于 Vue+Vuex+Vue Router 完成了电商项目核心功能的落地,涵盖「路由管控、状态管理、跨组件通信、登录鉴权、购物车核心逻辑」等关键场景。核心要点:

  1. Vue Router 通过动态路由、路由守卫实现页面跳转与权限控制;
  2. Vuex 模块化设计降低状态管理耦合,getters 处理派生状态,mutations 同步修改状态,actions 处理异步逻辑;
  3. 结合本地存储实现状态持久化,提升用户体验;
  4. Vue3 场景下可无缝切换为 Pinia+Vue Router 4,核心逻辑一致。

在此基础上,可进一步扩展「订单管理、支付对接、商品分类、搜索筛选」等功能,或结合 ElementUI/vant 等 UI 库完善界面,最终形成完整的电商前端解决方案。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/3/27 10:27:35

ARM 工程初始化中 error: c9511e 的快速理解

深入理解 ARM 工程初始化中的error: c9511e&#xff1a;从报错到掌控构建系统你有没有在启动一个嵌入式项目时&#xff0c;刚敲下make clean all就被一条红色错误拦住去路&#xff1f;error: c9511e: unable to determine the current toolkit. check that arm_tool_path is se…

作者头像 李华
网站建设 2026/4/9 15:41:56

JMeter Prometheus插件完整使用指南:从入门到精通的终极教程

JMeter Prometheus插件完整使用指南&#xff1a;从入门到精通的终极教程 【免费下载链接】jmeter-prometheus-plugin A Prometheus Listener for Apache JMeter that exposes results in an http API 项目地址: https://gitcode.com/gh_mirrors/jm/jmeter-prometheus-plugin …

作者头像 李华
网站建设 2026/4/9 19:37:26

GitHub Releases发布预训练TensorFlow模型权重

GitHub Releases发布预训练TensorFlow模型权重 在深度学习项目中&#xff0c;你是否经历过这样的场景&#xff1a;刚接手一个同事的代码&#xff0c;满怀信心地运行 pip install tensorflow 后却发现版本不兼容&#xff1b;或者为了复现一篇论文的结果&#xff0c;反复尝试下载…

作者头像 李华
网站建设 2026/4/6 14:36:38

GitHub Issue跟踪TensorFlow-v2.9使用过程中遇到的问题

TensorFlow-v2.9 深度学习环境实践&#xff1a;从容器化部署到高效开发 在现代 AI 研发中&#xff0c;一个稳定、可复现的开发环境往往比模型结构本身更早决定项目的成败。我们曾多次遇到这样的场景&#xff1a;同事在本地训练成功的模型&#xff0c;换一台机器却因“版本不兼容…

作者头像 李华
网站建设 2026/4/9 9:57:08

ICU4J完整开发环境搭建指南:从零开始配置Java国际化项目

ICU4J完整开发环境搭建指南&#xff1a;从零开始配置Java国际化项目 【免费下载链接】icu The home of the ICU project source code. 项目地址: https://gitcode.com/gh_mirrors/ic/icu 想要快速搭建ICU4J开发环境却不知从何入手&#xff1f;这份详细配置指南将带你一步…

作者头像 李华
网站建设 2026/4/10 8:42:31

CubeMX配置ADC单通道采样中断模式操作指南

STM32CubeMX配置ADC单通道中断采集实战指南你有没有遇到过这样的场景&#xff1a;系统里接了一个电池电压检测或温湿度传感器&#xff0c;需要定时读取模拟信号&#xff0c;但用轮询方式写代码总觉得“卡主循环”&#xff1f;CPU一直在等ADC完成&#xff0c;效率低得让人心疼。…

作者头像 李华