1. 项目概述
这是一个基于Vue.js+Node.js+ElementUI的宠物用品商城销售信息系统,系统设计了三个核心角色:普通消费者用户、后台管理员和商品供应商。作为全栈项目,前端采用Vue3组合式API开发,配合ElementUI组件库实现响应式布局;后端使用Node.js的Express框架搭建RESTful API;数据库选用关系型MySQL存储业务数据。
我在实际开发中发现,这类多角色系统最关键的是权限控制和数据隔离。比如供应商只能看到和管理自己的商品,而管理员可以操作所有数据。这需要通过合理的RBAC(基于角色的访问控制)模型来实现,这也是本项目的技术难点之一。
2. 技术架构设计
2.1 前端技术选型
选择Vue3+ElementUI主要基于以下考虑:
- 开发效率:ElementUI提供丰富的预制组件(如表单、表格、弹窗等),可快速搭建管理后台界面
- 维护性:Vue3的组合式API比选项式API更利于逻辑复用,特别是对于复杂的状态管理
- 性能优化:Vue3的虚拟DOM重写和编译器优化,使运行时性能提升约30%
实际开发中,我推荐使用以下工具链:
bash复制# 项目初始化
npm init vue@latest pet-mall --default
# 必要依赖
npm install element-plus axios vue-router pinia
2.2 后端技术选型
Node.js+Express的组合具有以下优势:
- 开发速度:JavaScript全栈统一语言,前后端开发无需切换语境
- 生态丰富:npm上有大量中间件可供选择(如JWT认证、文件上传等)
- 高并发:事件驱动架构适合I/O密集型场景(如电商系统的订单处理)
关键中间件配置示例:
javascript复制// 身份验证中间件
const authMiddleware = (role) => {
return (req, res, next) => {
if(req.user.role !== role) {
return res.status(403).json({ error: 'Forbidden' })
}
next()
}
}
// 供应商专属路由
router.get('/products',
authMiddleware('supplier'),
(req, res) => {
// 数据自动隔离
const products = await Product.findAll({
where: { supplierId: req.user.id }
})
res.json(products)
}
)
2.3 数据库设计
主要表结构设计考虑点:
- 用户表(users):需要区分角色类型(普通用户、管理员、供应商)
- 商品表(products):包含供应商外键实现数据归属
- 订单表(orders):需要记录购买用户、订单状态和支付信息
sql复制CREATE TABLE products (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
price DECIMAL(10,2) NOT NULL,
stock INT DEFAULT 0,
supplier_id INT NOT NULL,
FOREIGN KEY (supplier_id) REFERENCES users(id)
);
注意:实际项目中建议添加created_at和updated_at时间戳字段,便于后期数据审计
3. 核心功能实现
3.1 用户端功能实现
3.1.1 商品展示系统
采用分页加载+虚拟滚动技术优化性能:
vue复制<template>
<el-table
:data="visibleItems"
row-key="id"
@scroll.passive="handleScroll"
>
<!-- 列定义 -->
</el-table>
</template>
<script setup>
import { computed, ref } from 'vue'
const allItems = ref([]) // 全部商品数据
const scrollTop = ref(0)
const pageSize = 20
// 计算当前可见项
const visibleItems = computed(() => {
const start = Math.floor(scrollTop.value / rowHeight)
return allItems.value.slice(start, start + pageSize)
})
const handleScroll = (e) => {
scrollTop.value = e.target.scrollTop
}
</script>
3.1.2 购物车实现
采用Pinia进行状态管理,结合本地存储持久化:
javascript复制// stores/cart.js
export const useCartStore = defineStore('cart', {
state: () => ({
items: JSON.parse(localStorage.getItem('cart')) || []
}),
actions: {
addItem(product) {
const existing = this.items.find(item => item.id === product.id)
existing ? existing.quantity++ : this.items.push({...product, quantity: 1})
this.persist()
},
persist() {
localStorage.setItem('cart', JSON.stringify(this.items))
}
}
})
3.2 管理员端实现
3.2.1 动态路由方案
根据用户角色动态生成侧边栏菜单:
javascript复制// 路由配置
const routes = [
{
path: '/admin',
component: AdminLayout,
children: [
{ path: 'dashboard', component: Dashboard, meta: { roles: ['admin'] } },
{ path: 'products', component: ProductManage, meta: { roles: ['admin'] } }
]
}
]
// 路由守卫
router.beforeEach(async (to) => {
const user = store.user
if(to.meta.roles && !to.meta.roles.includes(user.role)) {
return '/403'
}
})
3.2.2 数据可视化
使用ECharts实现销售数据展示:
vue复制<template>
<div ref="chart" style="width:600px;height:400px"></div>
</template>
<script setup>
import * as echarts from 'echarts'
import { onMounted, ref } from 'vue'
const chart = ref(null)
onMounted(async () => {
const res = await axios.get('/api/stats/sales')
const myChart = echarts.init(chart.value)
myChart.setOption({
xAxis: { data: res.data.months },
series: [{ data: res.data.values }]
})
})
</script>
3.3 供应商端实现
3.3.1 数据隔离方案
通过中间件自动注入查询条件:
javascript复制// 供应商数据访问中间件
const supplierOnly = (model) => {
return async (req, res, next) => {
req.whereConditions = { supplierId: req.user.id }
next()
}
}
// 在控制器中使用
router.get('/products',
authMiddleware('supplier'),
supplierOnly(Product),
(req, res) => {
const products = await Product.findAll({
where: req.whereConditions
})
res.json(products)
}
)
3.3.2 订单处理流程
mermaid复制graph TD
A[新订单通知] --> B{库存检查}
B -->|充足| C[确认发货]
B -->|不足| D[联系采购]
C --> E[更新物流信息]
E --> F[通知用户]
4. 部署与优化
4.1 容器化部署
使用Docker Compose编排服务:
yaml复制version: '3'
services:
frontend:
build: ./frontend
ports: ["80:80"]
depends_on: [backend]
backend:
build: ./backend
ports: ["3000:3000"]
environment:
DB_HOST: db
db:
image: mysql:8.0
volumes: ["db_data:/var/lib/mysql"]
environment:
MYSQL_ROOT_PASSWORD: petmall123
4.2 性能优化实践
-
前端优化:
- 使用Vue的异步组件实现路由懒加载
- 配置Webpack的SplitChunks拆分代码包
- 对商品图片使用WebP格式并实施懒加载
-
后端优化:
- 数据库查询添加索引(特别是外键字段)
- 使用Redis缓存热点数据(如商品详情)
- 实现JWT的黑名单机制增强安全性
-
监控方案:
- 前端使用Sentry收集错误日志
- 后端使用PM2的监控仪表板
- 数据库配置慢查询日志
5. 常见问题与解决方案
5.1 跨域问题处理
开发环境配置代理,生产环境使用Nginx转发:
nginx复制location /api {
proxy_pass http://backend:3000;
proxy_set_header Host $host;
}
5.2 权限控制漏洞
补充服务端校验,防止前端绕过:
javascript复制// 删除商品接口
router.delete('/products/:id',
authMiddleware('admin'),
async (req, res) => {
// 即使前端是管理员,也要再次验证商品所有权
const product = await Product.findByPk(req.params.id)
if(product.supplierId !== req.user.id && req.user.role !== 'admin') {
return res.status(403).end()
}
await product.destroy()
res.status(204).end()
}
)
5.3 支付对接问题
支付宝沙箱环境常见问题排查:
- 检查应用网关配置是否正确
- 验证RSA密钥匹配(特别是换行符问题)
- 确认异步通知地址可公网访问
6. 开发经验分享
在实际开发过程中,有几个关键点值得特别注意:
-
状态管理策略:
- 简单状态使用组件本地状态即可
- 跨组件状态优先考虑Pinia而非Vuex
- 表单状态建议保持本地,提交时再同步
-
错误处理规范:
javascript复制// 统一的错误处理中间件
app.use((err, req, res, next) => {
console.error(err.stack)
res.status(500).json({
code: err.code || 'INTERNAL_ERROR',
message: process.env.NODE_ENV === 'production'
? '服务器错误'
: err.message
})
})
- 团队协作技巧:
- 使用Git Hooks统一代码风格
- 制定清晰的API文档规范(推荐使用Swagger)
- 前后端约定统一的数据返回格式
这个项目最让我有成就感的是实现了完善的权限控制系统。通过JWT结合RBAC模型,不仅实现了页面级的访问控制,还做到了数据行级的自动过滤。比如供应商登录后,所有商品查询都会自动附加supplier_id条件,这大大减少了业务代码中的权限判断逻辑。