发布日期:2024年6月5日
一、系统架构设计
本教程将开发一个完整的电商后台管理系统,主要功能模块包括:
- 多租户架构:支持多店铺独立运营
- 商品中心:复杂SKU管理方案
- 订单系统:全流程状态管理
- 数据看板:实时业务指标可视化
- 权限体系:精细化操作控制
技术栈:Vue2.6 + ElementUI + Vuex + ECharts + WebSocket
二、项目初始化与工程配置
1. 项目创建与基础配置
# 创建Vue2项目
vue create mall-admin
# 安装核心依赖
cd mall-admin
npm install element-ui vuex echarts socket.io-client
npm install axios js-cookie qs -S
# 配置vue.config.js
module.exports = {
devServer: {
proxy: {
'/api': {
target: 'http://backend-api.com',
changeOrigin: true,
pathRewrite: {
'^/api': ''
}
}
}
}
}
2. 目录结构规划
src/
├── api/ # 接口服务
├── components/ # 公共组件
│ ├── Business/ # 业务组件
│ └── Common/ # 通用组件
├── directive/ # 自定义指令
├── filters/ # 全局过滤器
├── icons/ # SVG图标
├── layout/ # 布局组件
├── router/ # 路由配置
├── store/ # Vuex状态
├── styles/ # 全局样式
├── utils/ # 工具函数
├── views/ # 页面组件
│ ├── product/ # 商品管理
│ └── order/ # 订单管理
├── App.vue
└── main.js
三、多租户系统实现
1. 租户上下文管理
// store/modules/tenant.js
const state = {
currentTenant: null,
tenantList: []
}
const mutations = {
SET_TENANT(state, tenant) {
state.currentTenant = tenant
// 设置axios全局请求头
axios.defaults.headers.common['X-Tenant-ID'] = tenant.id
}
}
const actions = {
async fetchTenants({ commit }) {
const res = await api.getTenants()
commit('SET_TENANTS', res.data)
if (res.data.length > 0) {
commit('SET_TENANT', res.data[0])
}
}
}
// 路由守卫检查租户
router.beforeEach((to, from, next) => {
if (to.meta.requiresTenant && !store.state.tenant.currentTenant) {
next('/tenant-select')
} else {
next()
}
})
2. 租户切换组件
<template>
<el-select
v-model="currentTenant"
@change="handleTenantChange"
placeholder="请选择店铺"
>
<el-option
v-for="tenant in tenantList"
:key="tenant.id"
:label="tenant.name"
:value="tenant.id"
></el-option>
</el-select>
</template>
<script>
export default {
computed: {
currentTenant: {
get() {
return this.$store.state.tenant.currentTenant?.id
},
set(id) {
const tenant = this.tenantList.find(t => t.id === id)
this.$store.commit('SET_TENANT', tenant)
}
},
tenantList() {
return this.$store.state.tenant.tenantList
}
},
created() {
this.$store.dispatch('tenant/fetchTenants')
},
methods: {
handleTenantChange(tenantId) {
// 刷新当前路由以加载租户数据
this.$router.go(0)
}
}
}
</script>
四、复杂SKU管理方案
1. SKU规格动态生成
<template>
<div class="sku-editor">
<div v-for="(spec, index) in specs" :key="index">
<h3>{{ spec.name }}</h3>
<el-checkbox-group v-model="spec.selected">
<el-checkbox
v-for="value in spec.values"
:key="value"
:label="value"
></el-checkbox>
</el-checkbox-group>
</div>
<el-table :data="skuList" border>
<el-table-column
v-for="spec in specs"
:key="spec.name"
:label="spec.name"
:prop="spec.name"
></el-table-column>
<el-table-column label="价格">
<template #default="{ row }">
<el-input v-model="row.price"></el-input>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script>
export default {
data() {
return {
specs: [
{ name: '颜色', values: ['红色', '蓝色'], selected: [] },
{ name: '尺寸', values: ['S', 'M', 'L'], selected: [] }
],
skuList: []
}
},
watch: {
specs: {
deep: true,
handler() {
this.generateSkuList()
}
}
},
methods: {
generateSkuList() {
// 计算笛卡尔积生成SKU组合
const selectedSpecs = this.specs
.filter(spec => spec.selected.length > 0)
.map(spec => spec.selected.map(value => ({
[spec.name]: value
})))
if (selectedSpecs.length === 0) {
this.skuList = []
return
}
this.skuList = selectedSpecs.reduce((acc, cur) => {
if (acc.length === 0) return cur
return acc.flatMap(a =>
cur.map(c => ({ ...a, ...c, price: 0 }))
)
}, [])
}
}
}
</script>
2. SKU图片关联
<template>
<div class="sku-image">
<el-upload
action="/api/upload"
:file-list="imageList"
:on-success="handleUploadSuccess"
>
<el-button>上传图片</el-button>
</el-upload>
<el-table :data="skuList">
<el-table-column label="规格组合">
<template #default="{ row }">
<span v-for="(value, key) in row">
{{ key }}:{{ value }}
</span>
</template>
</el-table-column>
<el-table-column label="关联图片">
<template #default="{ row }">
<el-select
v-model="row.imageId"
placeholder="选择图片"
>
<el-option
v-for="img in imageList"
:key="img.id"
:label="img.name"
:value="img.id"
>
<img :src="img.url" class="thumb">
</el-option>
</el-select>
</template>
</el-table-column>
</el-table>
</div>
</template>
五、数据可视化看板
1. 实时数据仪表盘
<template>
<div class="dashboard">
<el-row :gutter="20">
<el-col :span="6" v-for="metric in metrics" :key="metric.name">
<metric-card :data="metric"></metric-card>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<line-chart :data="orderTrend"></line-chart>
</el-col>
<el-col :span="12">
<pie-chart :data="categoryDistribution"></pie-chart>
</el-col>
</el-row>
</div>
</template>
<script>
import { connectWebSocket } from '@/utils/websocket'
export default {
data() {
return {
metrics: [
{ name: '今日订单', value: 0, growth: 0 },
{ name: '今日销售额', value: 0, growth: 0 }
],
orderTrend: {},
categoryDistribution: {}
}
},
created() {
this.fetchData()
this.setupWebSocket()
},
methods: {
async fetchData() {
const res = await this.$api.getDashboardData()
this.metrics = res.metrics
this.orderTrend = res.orderTrend
this.categoryDistribution = res.categoryDistribution
},
setupWebSocket() {
this.socket = connectWebSocket('/dashboard')
this.socket.on('data_update', data => {
this.metrics = data.metrics
})
}
}
}
</script>
2. 自动刷新策略
// utils/autoRefresh.js
export function useAutoRefresh(component, options = {}) {
const { interval = 30000, immediate = true } = options
let timer = null
const startRefresh = () => {
stopRefresh()
timer = setInterval(() => {
component.fetchData()
}, interval)
if (immediate) {
component.fetchData()
}
}
const stopRefresh = () => {
if (timer) {
clearInterval(timer)
timer = null
}
}
onMounted(startRefresh)
onBeforeUnmount(stopRefresh)
return { startRefresh, stopRefresh }
}
// 在组件中使用
export default {
mixins: [useAutoRefresh],
data() { /* ... */ },
methods: {
fetchData() { /* 获取数据 */ }
}
}
六、精细化权限控制
1. 权限指令增强版
// directive/permission.js
const checkPermission = (el, binding, vnode) => {
const { value } = binding
const permissions = vnode.context.$store.getters.permissions
if (value && value instanceof Array) {
if (value.length > 0) {
const hasPermission = permissions.some(perm => {
return value.includes(perm)
})
if (!hasPermission) {
el.parentNode && el.parentNode.removeChild(el)
}
}
} else if (typeof value === 'string') {
const hasPermission = permissions.includes(value)
if (!hasPermission) {
el.parentNode && el.parentNode.removeChild(el)
}
} else {
throw new Error(`权限值需要是数组或字符串,如v-permission="['product:edit']"`)
}
}
export default {
inserted(el, binding, vnode) {
checkPermission(el, binding, vnode)
},
update(el, binding, vnode) {
checkPermission(el, binding, vnode)
}
}
2. 权限按钮组件
<template>
<el-button
v-if="hasPermission"
v-bind="$attrs"
v-on="$listeners"
>
<slot></slot>
</el-button>
</template>
<script>
export default {
name: 'PermissionButton',
props: {
permission: {
type: [String, Array],
required: true
}
},
computed: {
hasPermission() {
if (Array.isArray(this.permission)) {
return this.permission.some(perm =>
this.$store.getters.permissions.includes(perm)
)
}
return this.$store.getters.permissions.includes(this.permission)
}
}
}
</script>
七、性能优化实践
1. 表格渲染优化
<template>
<el-table
:data="tableData"
v-loading="loading"
:row-key="rowKey"
:tree-props="treeProps"
:height="tableHeight"
@scroll="handleScroll"
>
<el-table-column
v-for="col in columns"
:key="col.prop"
v-bind="col"
></el-table-column>
</el-table>
</template>
<script>
export default {
data() {
return {
tableData: [],
loading: false,
visibleData: [],
bufferSize: 20,
lastScrollTop: 0
}
},
computed: {
tableHeight() {
return window.innerHeight - 200
}
},
methods: {
async loadData() {
this.loading = true
try {
const res = await this.$api.getTableData()
this.tableData = res.data
this.updateVisibleData()
} finally {
this.loading = false
}
},
updateVisibleData(scrollTop = 0) {
const rowHeight = 50
const startIdx = Math.floor(scrollTop / rowHeight)
const endIdx = startIdx + Math.ceil(this.tableHeight / rowHeight) + this.bufferSize
this.visibleData = this.tableData.slice(
Math.max(0, startIdx - this.bufferSize),
Math.min(this.tableData.length, endIdx)
},
handleScroll({ scrollTop }) {
if (Math.abs(scrollTop - this.lastScrollTop) > 50) {
this.updateVisibleData(scrollTop)
this.lastScrollTop = scrollTop
}
}
}
}
</script>
2. 内存泄漏防护
// mixins/cleanup.js
export default {
data() {
return {
cleanupCallbacks: []
}
},
methods: {
registerCleanup(callback) {
this.cleanupCallbacks.push(callback)
},
clearEventListeners() {
window.removeEventListener('resize', this.handleResize)
}
},
beforeDestroy() {
this.cleanupCallbacks.forEach(cb => cb())
this.clearEventListeners()
}
}
// 在组件中使用
export default {
mixins: [cleanupMixin],
mounted() {
this.socket = connectWebSocket()
this.registerCleanup(() => {
this.socket.disconnect()
})
window.addEventListener('resize', this.handleResize)
}
}
八、总结与扩展
通过本教程,您已经掌握了:
- 电商后台系统核心架构设计
- 多租户系统实现方案
- 复杂SKU管理逻辑
- 数据可视化看板开发
- 精细化权限控制体系
扩展学习方向:
- 微前端架构改造
- 低代码表单引擎
- 自动化测试体系
- Web Workers性能优化