一、电商平台架构设计
本教程将基于Vue2构建一个完整的大型电商平台前端,包含商品展示、购物车、订单管理等核心模块。
技术架构:
- 核心框架:Vue 2.6 + Composition API插件
- 状态管理:Vuex模块化架构
- 路由方案:Vue-router动态路由
- UI组件:Element UI + 自定义主题
- 工程化:Webpack深度优化
- 接口方案:RESTful API规范
核心功能模块:
- 多级商品分类系统
- 商品搜索与筛选
- 购物车状态管理
- 订单流程控制
- 支付系统集成
- 用户中心模块
- 性能监控体系
二、项目初始化与工程配置
1. 项目创建与基础配置
# 创建项目
vue create vue2-ecommerce
# 添加必要依赖
cd vue2-ecommerce
vue add router
vue add vuex
npm install element-ui @vue/composition-api axios lodash moment
# 配置vue.config.js
module.exports = {
chainWebpack: config => {
config.optimization.splitChunks({
chunks: 'all',
maxSize: 244 * 1024,
automaticNameDelimiter: '-'
})
},
productionSourceMap: false
}
2. 项目目录结构优化
src/
├── api/ # API模块化
├── assets/ # 静态资源
├── components/ # 公共组件
│ ├── business/ # 业务组件
│ └── common/ # 通用组件
├── composables/ # Composition API
├── directives/ # 自定义指令
├── filters/ # 全局过滤器
├── router/ # 路由配置
│ ├── guard/ # 路由守卫
│ └── modules/ # 路由模块
├── store/ # Vuex状态
│ ├── modules/ # 模块化store
│ └── plugins/ # Vuex插件
├── styles/ # 全局样式
├── utils/ # 工具函数
├── views/ # 页面组件
│ ├── product/ # 商品模块
│ ├── order/ # 订单模块
│ └── ...
├── App.vue # 根组件
└── main.js # 应用入口
三、核心模块实现
1. 商品分类系统
实现多级分类组件:
<template>
<div class="category-system">
<div class="category-level" v-for="(level, index) in categoryTree" :key="index">
<div
class="category-item"
v-for="item in level"
:key="item.id"
@mouseenter="loadChildren(item, index)"
:class="{ active: activeMap[index] === item.id }">
{{ item.name }}
</div>
</div>
</div>
</template>
<script>
import { getCategoryTree } from '@/api/product'
export default {
data() {
return {
categoryTree: [[]], // 三级分类树
activeMap: {} // 记录每级激活项
}
},
async created() {
await this.loadRootCategories()
},
methods: {
async loadRootCategories() {
const res = await getCategoryTree()
this.categoryTree[0] = res.data
},
async loadChildren(item, level) {
this.$set(this.activeMap, level, item.id)
if (!item.children && level < 2) {
const res = await getCategoryTree({ parentId: item.id })
item.children = res.data
// 确保有下一级数组
if (!this.categoryTree[level + 1]) {
this.$set(this.categoryTree, level + 1, [])
}
// 更新下一级显示
this.$set(this.categoryTree, level + 1, item.children)
// 清空更下级
for (let i = level + 2; i < this.categoryTree.length; i++) {
this.$set(this.categoryTree, i, [])
this.$set(this.activeMap, i, null)
}
}
}
}
}
</script>
2. 商品搜索与筛选
<template>
<div class="product-filter">
<el-input
v-model="searchQuery"
placeholder="搜索商品"
@keyup.enter="handleSearch">
<el-button slot="append" @click="handleSearch">
<i class="el-icon-search"></i>
</el-button>
</el-input>
<div class="filter-options">
<el-select v-model="filterParams.category" clearable placeholder="商品分类">
<el-option
v-for="item in categories"
:key="item.id"
:label="item.name"
:value="item.id">
</el-option>
</el-select>
<el-cascader
v-model="filterParams.attributes"
:options="attributeOptions"
:props="{ multiple: true }"
placeholder="商品属性">
</el-cascader>
<el-button type="primary" @click="applyFilters">筛选</el-button>
</div>
</div>
</template>
<script>
import { debounce } from 'lodash'
export default {
data() {
return {
searchQuery: '',
filterParams: {
category: null,
attributes: [],
priceRange: [0, 10000]
},
categories: [],
attributeOptions: []
}
},
created() {
this.loadFilterOptions()
this.debouncedSearch = debounce(this.handleSearch, 500)
},
watch: {
searchQuery(val) {
this.debouncedSearch()
}
},
methods: {
async loadFilterOptions() {
const [catRes, attrRes] = await Promise.all([
this.$api.getCategories(),
this.$api.getAttributes()
])
this.categories = catRes.data
this.attributeOptions = attrRes.data
},
handleSearch() {
this.$emit('search', {
...this.filterParams,
keyword: this.searchQuery
})
},
applyFilters() {
this.handleSearch()
}
}
}
</script>
四、状态管理设计
1. 购物车模块实现
// store/modules/cart.js
const state = {
items: [],
selected: []
}
const mutations = {
ADD_TO_CART(state, product) {
const existing = state.items.find(item => item.id === product.id)
if (existing) {
existing.quantity += product.quantity || 1
} else {
state.items.push({
...product,
quantity: product.quantity || 1,
selected: true
})
}
localStorage.setItem('cart', JSON.stringify(state.items))
},
UPDATE_QUANTITY(state, { id, quantity }) {
const item = state.items.find(item => item.id === id)
if (item) {
item.quantity = quantity
localStorage.setItem('cart', JSON.stringify(state.items))
}
},
TOGGLE_SELECT(state, id) {
const item = state.items.find(item => item.id === id)
if (item) {
item.selected = !item.selected
localStorage.setItem('cart', JSON.stringify(state.items))
}
},
INIT_CART(state) {
const saved = localStorage.getItem('cart')
if (saved) {
state.items = JSON.parse(saved)
}
}
}
const getters = {
totalCount: state => {
return state.items.reduce((sum, item) => sum + item.quantity, 0)
},
selectedItems: state => {
return state.items.filter(item => item.selected)
},
totalPrice: (state, getters) => {
return getters.selectedItems.reduce(
(sum, item) => sum + (item.price * item.quantity), 0
)
}
}
export default {
namespaced: true,
state,
mutations,
getters
}
2. Composition API封装
// composables/useCart.js
import { computed } from '@vue/composition-api'
export default function useCart(store) {
const cartItems = computed(() => store.state.cart.items)
const selectedItems = computed(() => store.getters['cart/selectedItems'])
const cartTotal = computed(() => store.getters['cart/totalPrice'])
const addToCart = (product) => {
store.commit('cart/ADD_TO_CART', product)
}
const updateQuantity = (id, quantity) => {
store.commit('cart/UPDATE_QUANTITY', { id, quantity })
}
return {
cartItems,
selectedItems,
cartTotal,
addToCart,
updateQuantity
}
}
五、性能优化实践
1. 图片懒加载指令
// directives/lazyload.js
const LazyLoad = {
inserted(el, binding) {
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target
img.src = img.dataset.src
observer.unobserve(img)
}
})
}, {
rootMargin: '0px 0px 100px 0px'
})
observer.observe(el)
}
}
export default LazyLoad
2. 组件级代码分割
// router配置修改
const ProductDetail = () => import(
/* webpackChunkName: "product" */
'@/views/product/Detail.vue'
)
const routes = [
{
path: '/product/:id',
component: ProductDetail,
meta: { preload: true }
}
]
// 路由预加载逻辑
router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.preload)) {
const matched = to.matched.map(record => record.components.default)
Promise.all(matched.map(component => {
if (typeof component === 'function') {
return component()
}
return Promise.resolve()
}))
}
next()
})
六、支付流程实现
1. 订单创建组件
<template>
<div class="checkout-container">
<el-steps :active="activeStep" finish-status="success">
<el-step title="确认订单"></el-step>
<el-step title="支付"></el-step>
<el-step title="完成"></el-step>
</el-steps>
<div class="step-content">
<order-confirm
v-if="activeStep === 0"
@next="handleNext" />
<payment-method
v-if="activeStep === 1"
@next="handleNext"
@prev="activeStep--" />
<order-result
v-if="activeStep === 2"
:order="currentOrder"
@finish="handleFinish" />
</div>
</div>
</template>
<script>
import OrderConfirm from './OrderConfirm'
import PaymentMethod from './PaymentMethod'
import OrderResult from './OrderResult'
export default {
components: { OrderConfirm, PaymentMethod, OrderResult },
data() {
return {
activeStep: 0,
currentOrder: null
}
},
methods: {
handleNext(orderData) {
if (this.activeStep === 0) {
this.currentOrder = orderData
}
this.activeStep++
},
handleFinish() {
this.$router.push('/user/orders')
}
}
}
</script>
2. 支付方法组件
<template>
<div class="payment-methods">
<el-radio-group v-model="selectedMethod">
<el-radio
v-for="method in paymentMethods"
:key="method.value"
:label="method.value">
<div class="method-item">
<img :src="method.icon" :alt="method.label">
{{ method.label }}
</div>
</el-radio>
</el-radio-group>
<div class="payment-action">
<el-button @click="$emit('prev')">上一步</el-button>
<el-button
type="primary"
@click="handlePayment"
:loading="paying">
立即支付
</el-button>
</div>
</div>
</template>
<script>
export default {
data() {
return {
selectedMethod: 'alipay',
paying: false,
paymentMethods: [
{ value: 'alipay', label: '支付宝', icon: '/assets/payment/alipay.png' },
{ value: 'wechat', label: '微信支付', icon: '/assets/payment/wechat.png' }
]
}
},
methods: {
async handlePayment() {
this.paying = true
try {
const res = await this.$api.createPayment({
orderId: this.$route.params.id,
method: this.selectedMethod
})
if (res.data.payUrl) {
window.location.href = res.data.payUrl
}
} finally {
this.paying = false
}
}
}
}
</script>
七、项目部署与监控
1. Nginx生产配置
server {
listen 80;
server_name yourdomain.com;
root /var/www/ecommerce/dist;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
location /api {
proxy_pass http://backend:3000;
proxy_set_header Host $host;
}
gzip on;
gzip_types text/plain text/css application/json application/javascript;
# 静态资源缓存
location ~* .(jpg|jpeg|png|gif|ico|css|js)$ {
expires 365d;
add_header Cache-Control "public";
}
}
2. 前端监控集成
// main.js
import * as Sentry from '@sentry/browser'
import * as Integrations from '@sentry/integrations'
if (process.env.NODE_ENV === 'production') {
Sentry.init({
dsn: 'YOUR_DSN_HERE',
integrations: [
new Integrations.Vue({
Vue,
attachProps: true
})
],
release: 'ecommerce@' + process.env.VUE_APP_VERSION
})
}
// 全局错误处理
Vue.config.errorHandler = (err, vm, info) => {
console.error('Vue error:', err)
if (process.env.NODE_ENV === 'production') {
Sentry.captureException(err, {
extra: {
component: vm.$options.name,
propsData: vm.$options.propsData,
info: info
}
})
}
}
八、总结与扩展
本教程构建了一个完整的电商平台前端:
- 设计了模块化项目架构
- 实现了核心电商功能
- 优化了前端性能表现
- 完善了支付流程
- 配置了生产环境部署
扩展方向:
- 微前端架构改造
- SSR服务端渲染
- AB测试系统集成
- 大数据可视化分析
完整项目代码已上传GitHub:https://github.com/example/vue2-ecommerce