电商前台是 Vue 最典型的应用场景之一。本节以一个简化但完整的前端实现为主线,串联商品列表、购物车、路由守卫和下单流程这四个核心模块,演示如何用 Vue 3 组合式 API + Pinia + Vue Router 搭建一个可用的电商系统。
商品列表:数据获取、展示与状态管理
商品列表通常需要处理分页加载、搜索筛选、排序等功能,并保持组件状态可预测。
数据获取与状态定义
建议将商品列表的状态封装为自定义 Hook,这样列表逻辑可在不同页面复用,且页面代码更清爽:
// composables/useProductList.js
import { ref, reactive, watch } from 'vue'
import { getProducts } from '@/api/product'
export function useProductList() {
const list = ref([])
const loading = ref(false)
const query = reactive({ page: 1, size: 20, keyword: '', sort: '' })
async function fetchList() {
loading.value = true
try {
const res = await getProducts(query)
list.value = res.data.records
// 处理分页信息...
} finally {
loading.value = false
}
}
// 当查询条件变化时重新拉取
watch(query, fetchList, { immediate: true })
// 或手动触发: function search(kw) { query.keyword = kw; query.page = 1; }
return { list, loading, query, fetchList }
}
页面组件使用这个 Hook,关注点集中在模板渲染:
<template>
<div>
<SearchBar v-model="query.keyword" @search="query.page = 1" />
<ProductCard v-for="item in list" :key="item.id" :product="item" />
<Pagination v-model:current="query.page" :total="total" />
</div>
</template>
列表渲染性能
若商品数量很大,应考虑 虚拟列表(如 vue-virtual-scroller)或采用 v-memo 标记列表项:
<ProductCard v-for="item in list" :key="item.id" v-memo="[item.id, item.price]" :product="item" />
这样只有价格或 id 变化时该行才会重新渲染,避免无谓开销。
购物车:全局状态与本地持久化
购物车是一个典型的跨页面共享状态,适合用 Pinia 管理。同时,购物车数据一般需要本地持久化(刷新不丢失)。
Pinia Store 设计
// stores/cart.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useCartStore = defineStore('cart', () => {
const items = ref(JSON.parse(localStorage.getItem('cart') || '[]'))
// 计算总价、总数量
const totalCount = computed(() => items.value.reduce((sum, item) => sum + item.quantity, 0))
const totalPrice = computed(() => items.value.reduce((sum, item) => sum + item.price * item.quantity, 0))
function addToCart(product, quantity = 1) {
const exist = items.value.find(i => i.id === product.id)
if (exist) {
exist.quantity += quantity
} else {
items.value.push({ ...product, quantity })
}
saveToLocal()
}
function removeItem(id) {
items.value = items.value.filter(i => i.id !== id)
saveToLocal()
}
function clearCart() {
items.value = []
saveToLocal()
}
function saveToLocal() {
localStorage.setItem('cart', JSON.stringify(items.value))
}
return { items, totalCount, totalPrice, addToCart, removeItem, clearCart }
})
组件中的使用
在商品详情页点击“加入购物车”,或在购物车页面展示列表:
<script setup>
import { useCartStore } from '@/stores/cart'
const cart = useCartStore()
</script>
<template>
<button @click="cart.addToCart(product)">加入购物车</button>
<!-- 购物车页面直接遍历 cart.items -->
</template>
关键细节:购物车的持久化应放在 Store 内部的 $subscribe 或自定义 saveToLocal 中,避免每个组件都写持久化逻辑。
路由守卫:登录鉴权与权限控制
电商前台的核心保护路径:购物车、个人中心、下单等页面必须登录后访问。同时,某些页面(如登录、注册)登录后不应再进入。
全局前置守卫
在路由配置文件(router/index.js)中设置 meta.requiresAuth 属性,并统一拦截:
// 路由定义示例
const routes = [
{ path: '/cart', component: () => import('@/views/Cart.vue'), meta: { requiresAuth: true } },
{ path: '/checkout', component: () => import('@/views/Checkout.vue'), meta: { requiresAuth: true } },
{ path: '/login', component: () => import('@/views/Login.vue') }
]
// 守卫逻辑
router.beforeEach((to, from, next) ={
const userStore = useUserStore()
const isLoggedIn = !!userStore.token
if (to.meta.requiresAuth && !isLoggedIn) {
// 跳转登录,并记录来源路径以便登录后回跳
next({ path: '/login', query: { redirect: to.fullPath } })
} else if (to.path === '/login' && isLoggedIn) {
next({ path: '/' }) // 已登录用户直接跳首页
} else {
next()
}
})
状态持久化与 token 存储
通常将登录后获得的 token 存入 localStorage 并在 Pinia user store 中维护,路由守卫同步检查,避免每次跳转闪白屏。
下单流程:从购物车到订单提交
下单流程一般分为 填写/选择地址 → 确认订单 → 提交 三个视觉步骤,但前端通常将状态集中在一个 Pinia order store 或组件内部,以确保各步骤状态一致。
步骤一:地址管理
地址列表通过接口获取,选中一个默认地址。可以用 provide/inject 在子流程组件间共享选中的地址对象,或直接放 Pinia:
// stores/order.js
import { defineStore } from 'pinia'
import { ref } from 'vue'
export const useOrderStore = defineStore('order', () => {
const selectedAddress = ref(null)
const cartItems = ref([]) // 下单前可从购物车 Store 复制出来
function selectAddress(address) {
selectedAddress.value = address
}
function initOrder(items) {
cartItems.value = items
}
// 提交订单逻辑
async function submitOrder() {
// 调用API,携带 selectedAddress, cartItems
}
return { selectedAddress, cartItems, selectAddress, initOrder, submitOrder }
})
步骤二:确认订单
页面展示快照:商品列表(不可再修改,只读)、收货地址、优惠信息、应付总额。这些数据均从 order store 读取。
步骤三:提交订单
点击提交时做最后校验(地址不为空、库存充足等),调用下单接口,成功后清空购物车并跳转到支付或订单详情。
async function handleSubmit() {
if (!orderStore.selectedAddress) return alert('请选择收货地址')
try {
await orderStore.submitOrder()
// 清空购物车
const cart = useCartStore()
cart.clearCart()
router.push('/order/success')
} catch (e) {
// 错误处理(如库存不足提示)
}
}
重要实践:
- 下单前应从购物车 Store 深拷贝一份商品快照到 Order Store,避免用户在下单过程中修改购物车导致价格不一致。
- 库存校验应在提交时由后端完成,前端仅做友好提示;乐观更新(如扣减库存)在电商中风险较高,谨慎使用。
通过以上四个模块的串联,一个基本的电商前台骨架便搭建完成。Vue 的组合式 API 让每个功能的逻辑保持内聚,Pinia 让跨页面的数据流转清晰可控,而路由守卫则保证了整个流程的安全性。