Django+Vue双端协同的电商实战项目:含购物车、支付模拟、订单流与后台管理全套源码
简介:直接可用的电商系统开发参考项目,后端用Django搭建API服务,前端用Vue.js实现响应式商城界面,MySQL存储数据。用户端支持账号注册登录、商品分类浏览、关键词搜索、加入购物车、生成订单、模拟在线支付(含支付状态回调逻辑)、查看订单详情、提交评价、发起退货申请并跟踪处理进度。管理员后台提供商品全生命周期管理——包括分类维护、上下架操作、首页推荐位配置、退货审核、用户与订单数据列表管理,以及基础销售统计图表展示。资源包内含完整前后端分离代码:web-client目录为Vue前端工程,shopping_mall/shopping为Django后端应用;附带shopping.sql数据库初始化脚本、详细部署说明文档、Vue环境安装指引、项目结构说明及技术要点备注。所有内容已整理归档,适合高校课程设计、毕业设计选题或个人学习电商系统开发流程时快速上手验证。
1. 项目概述:为什么这个Django+Vue电商项目值得你花时间细读
我带过三届计算机专业毕业设计,每年都有至少15个学生卡在“电商系统怎么才算真正跑通”这一步。不是写不出登录页,而是写到支付回调时发现订单状态对不上;不是搭不起后台,而是商品上下架后前端列表刷不出来;更常见的是——前后端联调三天,最后发现跨域配置漏了一行,或者Vue里调用的API路径少了个/api/前缀。这套Django+Vue电商项目,就是我从2021年带毕设开始,逐年迭代、踩坑填坑、反复验证后沉淀下来的“可交付原型”。它不追求炫酷的3D商品展示或AI推荐算法,但把电商最核心的业务闭环——从用户点击商品、加购、下单、支付、发货、收货、评价、退货,到管理员侧的商品上架、库存扣减、退货审核、销售统计——全部用最朴实、最贴近生产环境的方式实现了一遍。
关键词里提到的“Django电商”“VUE商城”“Python源码”“后台管理”“在线支付”,每一个都不是虚词。比如“在线支付”,它不是简单弹个alert说“支付成功”,而是完整模拟了真实支付网关的三段式交互:前端发起支付请求 → 后端生成唯一订单号并锁定库存 → 前端跳转至模拟支付页 → 用户点击“确认支付” → 后端接收回调 → 校验签名与金额 → 更新订单状态为“已支付” → 异步触发发货逻辑(模拟)。整个流程里,订单状态机被严格定义为:待支付 → 已支付 → 已发货 → 已签收 → 已完成 / 已取消 / 已退货,每个状态变更都记录操作人、时间戳和来源(是用户主动取消?还是超时自动关闭?还是管理员强制作废?),这些细节在课程设计答辩时,往往是老师追问“你怎么保证数据一致性”的关键得分点。
它适合谁?如果你是大三学生正为课程设计发愁,这套代码能让你三天内跑通首页+登录+购物车,一周内补全订单和后台,答辩PPT里的“系统演示截图”直接截源码运行效果;如果你是刚转行的前端,想理解Vue如何与Django REST Framework协作,你可以重点看web-client/src/api下的请求封装、拦截器统一处理token和错误,以及shopping_mall/shopping/apps/orders/views.py里基于APIView的订单创建逻辑;如果你是Python后端新手,想避开Flask路由混乱或FastAPI依赖注入绕晕的坑,Django的MTV结构在这里体现得非常清晰——模型(Model)定义商品/订单/用户关系,视图(View)专注业务逻辑而非模板渲染,模板(Template)只留给后台管理页用,而所有API都走rest_framework,连分页、过滤、权限控制都开箱即用。它不教你“什么是JWT”,但你在shopping_mall/shopping/apps/users/views.py里能看到TokenObtainPairView如何被继承重写,添加了用户头像和角色字段;它不讲“Vuex状态管理原理”,但你在web-client/src/store/modules/cart.js里能抄到购物车数量实时同步的完整实现。这就是它的价值:不是教科书,而是一份带着体温、留着注释、标着坑位的实战笔记。
2. 整体架构设计与技术选型逻辑拆解
2.1 为什么坚持Django + Vue分离部署,而不是Django模板渲染或Nuxt服务端渲染?
这个问题我被问过不下二十次。答案很实在:为了真实还原现代Web开发分工与部署场景。很多同学用Django写电商,习惯性把前端HTML塞进templates/目录,用{{ product.name }}渲染商品列表。这在单人开发小项目时没问题,但一旦涉及团队协作——前端要改一个按钮样式,得等后端重启服务器;后端要调整API字段,前端得同步改所有.html里的变量名;更别说上线时,静态资源缓存、CDN加速、前后端独立灰度发布这些事,全被模板渲染堵死了路。这套项目强制分离,web-client是纯Vue CLI 4.5构建的标准SPA工程,shopping_mall是标准Django 3.2项目,两者通过axios通信,跨域用Django的django-cors-headers解决。实操中,你甚至可以本地启动Vue开发服务器(npm run serve,端口8080),同时启动Django开发服务器(python manage.py runserver 8000),前端调用http://localhost:8000/api/products/,后端返回JSON,前端用v-for渲染。这种模式下,前端同学专注components/ProductCard.vue的交互细节,后端同学只管shopping_mall/shopping/apps/products/serializers.py里序列化字段是否包含is_on_sale和discount_price,职责边界清清楚楚。
至于为什么不选Nuxt做SSR?因为教学场景下,SSR带来的首屏加载优化收益,远不如它引入的复杂度代价高。Nuxt需要理解asyncData、fetch、服务端渲染生命周期钩子,还要处理window is not defined这类报错。而本项目的目标是让学生快速理解“用户点击下单按钮后,数据怎么从浏览器传到数据库”,SSR反而会模糊这个链条。我们把SEO友好性让渡给后台管理页——那里用Django原生模板渲染,自带<title>和<meta>标签,足够应付课程设计汇报。
2.2 MySQL为何是唯一数据库选择?PostgreSQL或SQLite是否可行?
资源包里只提供了shopping.sql初始化脚本,且明确要求MySQL 5.7+。这不是技术偏见,而是业务需求倒逼的选择。电商系统最怕什么?并发库存扣减导致超卖。比如一件商品库存只剩1件,两个用户同时点击“立即购买”,如果数据库不支持行级锁或事务隔离级别不够,很可能都扣减成功,库存变成-1。MySQL的InnoDB引擎,在REPEATABLE READ隔离级别下,配合SELECT ... FOR UPDATE语句,能完美解决这个问题。你在shopping_mall/shopping/apps/orders/views.py的create_order方法里能看到:
# 锁定商品行,防止并发修改
product = Product.objects.select_for_update().get(id=product_id)
if product.stock < quantity:
raise ValidationError("库存不足")
product.stock -= quantity
product.save()
这段代码执行时,MySQL会为该product_id对应的行加写锁,直到事务提交。另一个并发请求会阻塞等待,而不是读到脏数据。PostgreSQL当然也能做到,但它的语法是SELECT ... FOR UPDATE NOWAIT,且默认隔离级别是READ COMMITTED,需要手动调整,对初学者不友好。SQLite?它根本没真正的并发写入能力,多线程写入会直接报database is locked,完全不适合模拟真实电商流量。所以,MySQL是经过权衡后的务实之选——它够稳定、文档够全、云服务商预装率高(阿里云RDS、腾讯云CDB默认就是MySQL),学生部署时查一篇教程就能搞定。
2.3 “模拟在线支付”的设计哲学:不造轮子,但抠细节
看到“在线支付”四个字,很多人第一反应是“是不是集成了微信或支付宝SDK?”答案是否定的。项目里所谓的“模拟支付”,是指完全复刻真实支付网关的交互协议与安全逻辑,但把资金流转环节替换为内存操作。为什么这么做?因为真实支付接入需要企业资质、域名备案、HTTPS证书、支付平台审核,学生根本搞不定。但若只是弹个alert("支付成功"),又失去了学习价值。所以,我们模拟了三个核心环节:
-
支付请求生成:前端调用
/api/payments/init/,传入订单号、金额、用户ID。后端收到后,生成一个唯一的payment_id(格式如PAY_20231015_9876543210),将订单状态置为“支付中”,并返回payment_id和一个模拟支付页URL(如/pay?pid=PAY_20231015_9876543210)。 -
支付页交互:用户访问
/pay?pid=...,页面显示订单信息和“确认支付”按钮。点击后,前端向/api/payments/confirm/发送POST请求,携带payment_id和一个简单的签名(用订单金额+密钥MD5加密,如md5(f"{amount}_SECRET_KEY"))。这里刻意用了MD5而非SHA256,是因为教学场景下,MD5计算简单,学生能手算验证,理解“签名防篡改”的本质。 -
支付回调处理:
/api/payments/callback/是核心。它接收模拟网关的POST请求(实际是前端自己发的),校验签名是否匹配,检查订单金额是否与数据库一致,然后更新订单状态为“已支付”,并触发库存扣减(如果之前没扣)。关键点在于:回调必须幂等。同一个payment_id可能被重复推送三次,后端必须确保只有第一次生效。代码里用Order.objects.filter(id=order_id, status='pending_payment').update(status='paid'),利用SQL的原子性,避免先查再更新的竞态条件。
这种设计,让学生既避开了支付牌照的门槛,又亲手实现了签名验签、状态机驱动、幂等处理这些高阶技能点。答辩时,老师问“如果支付回调失败怎么办?”,你可以指着shopping_mall/shopping/apps/payments/tasks.py里的Celery定时任务回答:“我们设置了每5分钟扫描一次pending_payment订单,自动重试回调,最多3次,超时则标记为‘支付异常’并通知管理员。”
2.4 后台管理为何混合使用Django Admin与自定义Vue页面?
资源包里后台管理有两个入口:一个是Django自带的/admin/,另一个是Vue构建的/dashboard/。这不是冗余,而是分层治理的体现。/admin/用于基础数据维护:管理员用它批量导入商品CSV、快速编辑用户邮箱、查看所有日志条目。它的优势是零开发成本,Django自动根据Model生成CRUD界面。但它的短板也很明显——UI丑、交互弱、无法嵌入图表、不能做复杂权限控制(比如“只能看自己创建的商品”)。所以,/dashboard/应运而生。它用Vue + ECharts实现销售趋势折线图、商品类目占比饼图、今日订单量卡片;用Element UI的el-table展示带搜索、分页、状态筛选的订单列表;权限控制精确到按钮级别——普通管理员看不到“财务导出”按钮,只有超级管理员有。这种混合模式,教会学生一个真理:没有银弹框架,合适的技术用在合适的场景。就像你不会用Excel做大数据分析,也不会用Hadoop处理一张报销单。
3. 核心模块解析与实操要点精讲
3.1 用户认证体系:从Token到角色权限的落地实践
电商系统的安全基石是用户认证。本项目采用JWT(JSON Web Token)+ Django REST Framework组合,但做了关键改造,让它真正适配电商场景。标准JWT方案里,token只存用户ID,权限靠后端每次查询数据库判断。这对高频访问的电商首页显然不友好。所以我们扩展了token载荷(payload),在用户登录成功后,除了user_id,还塞入role(’customer’/’admin’/’staff’)和permissions(字符串数组,如['view_product', 'edit_order'])。你看shopping_mall/shopping/apps/users/views.py的CustomTokenObtainPairView:
class CustomTokenObtainPairView(TokenObtainPairView):
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.user
# 扩展token载荷
refresh = serializer.validated_data['refresh']
access = serializer.validated_data['access']
access['role'] = user.role
access['permissions'] = [p.codename for p in user.get_all_permissions()]
return Response({
'refresh': str(refresh),
'access': str(access),
})
这样,前端拿到token后,无需额外请求API就能知道当前用户是买家还是管理员,从而动态渲染导航栏——买家看到“我的订单”,管理员看到“商品管理”。更重要的是,权限校验变得极快。Django REST Framework的BasePermission类里,has_permission方法可以直接从request.auth(即解码后的token)里取permissions字段,比查数据库快一个数量级。
实操中,你必须注意两个坑:
提示:JWT token默认有效期是5分钟(
ACCESS_TOKEN_LIFETIME),学生常抱怨“刚登录就失效”。解决方案是:在settings.py里把SIMPLE_JWT['ACCESS_TOKEN_LIFETIME']调成timedelta(hours=24),同时前端用axios拦截器监听401响应,自动用refresh token换新access token。代码在web-client/src/utils/request.js第32行有完整实现。注意:Django Admin后台默认不认JWT,它走的是Session认证。所以管理员登录
/admin/仍需用户名密码,而Vue后台/dashboard/走JWT。这是故意为之——Admin是给运维人员用的紧急通道,Vue后台才是日常运营界面,两者认证方式隔离,安全性更高。
3.2 购物车实现:Redis vs 数据库的抉择与混合方案
购物车是电商最易被低估的模块。很多学生用数据库表存购物车,结果一到促销活动,数据库连接池被打满。本项目采用Redis + 数据库混合存储:未登录用户购物车存在Redis(以session_key为key),登录后自动合并到数据库,并清空Redis。这样既保证了游客体验(关掉浏览器购物车还在),又解决了登录态下的数据持久化问题。
Redis购物车的数据结构设计很讲究。不用HASH存整个商品对象,而是用SET存商品ID,用HASH单独存每个商品的quantity和selected状态。例如:
# Redis key: cart_session_abc123
cart_session_abc123:items -> SET {1001, 1002}
cart_session_abc123:item_1001 -> HASH {quantity:2, selected:true}
cart_session_abc123:item_1002 -> HASH {quantity:1, selected:false}
好处是:SELECT商品列表时,只需SMEMBERS cart_session_abc123:items拿到ID集合,再用MGET批量查商品详情,IO次数最少;更新某商品数量,只需HSET cart_session_abc123:item_1001 quantity 3,原子性好。你在web-client/src/store/modules/cart.js里能看到addProduct方法如何调用/api/cart/add/,后端shopping_mall/shopping/apps/cart/views.py则用redis_client.hset()操作。
数据库购物车表(cart_cartitem)只在用户登录后启用,结构精简:user_id, product_id, quantity, is_selected。合并逻辑在用户登录成功后的信号里触发(shopping_mall/shopping/apps/users/signals.py):
@receiver(user_logged_in)
def merge_cart(sender, request, user, **kwargs):
session_key = request.session.session_key
if not session_key:
return
# 从Redis读取游客购物车
redis_items = redis_client.smembers(f"cart_session_{session_key}:items")
for item_id in redis_items:
qty = int(redis_client.hget(f"cart_session_{session_key}:item_{item_id}", "quantity"))
# 合并到数据库,已存在则quantity相加
CartItem.objects.update_or_create(
user=user,
product_id=item_id,
defaults={'quantity': qty, 'is_selected': True}
)
# 清空Redis
redis_client.delete(f"cart_session_{session_key}:items")
这个设计,让学生直观理解“缓存穿透”(游客购物车无用户ID,无法用用户ID做缓存key)、“缓存雪崩”(大量游客同时登录,Redis合并压力骤增)等概念,并学会用信号机制解耦业务逻辑。
3.3 订单状态机:从“待支付”到“已完成”的七种状态与流转规则
电商订单绝不是简单的“创建-完成”两状态。本项目定义了7种状态,并用Django的状态机库django-fsm严格约束流转。状态定义在shopping_mall/shopping/apps/orders/models.py:
class Order(models.Model):
STATUS_CHOICES = [
('pending_payment', '待支付'),
('paid', '已支付'),
('shipped', '已发货'),
('delivered', '已签收'),
('completed', '已完成'),
('cancelled', '已取消'),
('refunded', '已退款'),
]
status = FSMField(default='pending_payment')
@transition(field=status, source='pending_payment', target='paid')
def mark_as_paid(self):
pass
@transition(field=status, source=['paid', 'shipped'], target='shipped')
def ship(self):
pass
@transition(field=status, source='shipped', target='delivered')
def mark_as_delivered(self):
pass
@transition(field=status, source=['pending_payment', 'paid'], target='cancelled')
def cancel(self):
pass
关键点在于@transition装饰器——它确保mark_as_paid()方法只能在status为pending_payment时调用,否则抛出IllegalAction异常。这比在视图里写if order.status == 'pending_payment': order.status = 'paid'安全得多,因为状态变更逻辑被集中管理,不会散落在各处。
实操中,状态流转必须伴随副作用。比如ship()方法不仅要改状态,还要:
- 减少对应商品的sold_count(销量统计)
- 生成物流单号(模拟为SF{datetime.now().strftime('%Y%m%d%H%M%S')})
- 发送站内信通知用户“您的订单已发货”
- 触发Celery异步任务,3天后检查是否签收,超时则自动标记为delivered
这些都在ship()方法体内实现。你在shopping_mall/shopping/apps/orders/views.py的ShipOrderView里能看到调用链。这种设计,让学生明白:状态机不是炫技,而是把业务规则代码化,避免“忘记更新销量”或“发货后没通知用户”这类低级错误。
3.4 后台管理销售统计:ECharts图表背后的实时数据聚合
后台的销售统计图表(/dashboard/sales)不是静态图片,而是Vue调用/api/dashboard/sales/ API,后端用Django ORM实时聚合数据。比如“近7日销售额折线图”,API返回JSON:
{
"dates": ["2023-10-09", "2023-10-10", "..."],
"amounts": [12500.00, 18700.50, "..."]
}
后端实现(shopping_mall/shopping/apps/dashboard/views.py):
from django.db.models import Sum, Count
from django.db.models.functions import TruncDate
def sales_chart(request):
# 按日期聚合已支付订单的总金额
data = Order.objects.filter(
status='paid',
created_at__gte=timezone.now() - timedelta(days=7)
).annotate(
date=TruncDate('created_at')
).values('date').annotate(
total_amount=Sum('total_amount')
).order_by('date')
dates = [item['date'].strftime('%Y-%m-%d') for item in data]
amounts = [float(item['total_amount']) for item in data]
return JsonResponse({'dates': dates, 'amounts': amounts})
这里用到了TruncDate函数,它把created_at时间戳截断为日期(如2023-10-09 14:30:00 → 2023-10-09),再按日期分组求和。相比用Python循环遍历所有订单,ORM聚合在数据库层面完成,性能提升百倍。学生常犯的错误是:在视图里用for order in orders:手动累加,数据量一大就超时。这个例子,就是活生生的性能优化教案。
ECharts配置也做了教学化处理。web-client/src/views/Dashboard/SalesChart.vue里,option对象被拆解为baseOption(通用配置)和dynamicOption(数据部分),方便学生理解“图表配置”与“业务数据”的分离。当老师问“怎么改成按小时统计?”,你只需把TruncDate换成TruncHour,前端xAxis.type从'category'改成'time',其他代码不动。
4. 完整部署与联调实录:从零到可运行的每一步
4.1 环境准备:避开Windows下MySQL编码的经典陷阱
部署第一步,永远是环境。资源包里Django+Vue的网上购物商城.txt写了基础步骤,但漏了一个Windows用户的致命坑:MySQL中文乱码。当你执行mysql -u root -p < shopping.sql时,如果MySQL配置没改,shopping.sql里的中文商品名会变成????,后台商品列表一片空白。解决方案必须在安装MySQL时就做:
- 编辑MySQL配置文件
my.ini(通常在C:\ProgramData\MySQL\MySQL Server X.X\),在[mysqld]下添加:character-set-server=utf8mb4 collation-server=utf8mb4_unicode_ci - 在
[client]下添加:default-character-set=utf8mb4 - 重启MySQL服务。
为什么是utf8mb4而不是utf8?因为MySQL的utf8其实是阉割版,最多存3字节字符,而emoji和部分生僻汉字需要4字节。utf8mb4才是真正的UTF-8。这个细节,决定了你的商品标题能不能显示“🔥新品首发”这样的图标。
Python环境同样要规范。不要用系统自带的Python,必须用pyenv或conda创建独立环境:
# 创建Python 3.9环境
pyenv install 3.9.18
pyenv virtualenv 3.9.18 shopping-env
pyenv activate shopping-env
# 升级pip,避免依赖冲突
pip install --upgrade pip
然后进入shopping_mall目录,pip install -r requirements.txt。requirements.txt里指定了Django==3.2.23和djangorestframework==3.14.0,版本锁死是为了避免Django 4.x的path()路由语法导致老代码报错。
4.2 数据库初始化:从SQL脚本到Django迁移的衔接
shopping.sql是完整的数据库快照,包含users_user、products_product、orders_order等所有表及测试数据。但Django项目不能只靠SQL脚本,必须有对应的models.py和迁移文件。所以,初始化分两步:
第一步:用SQL脚本建库
# 登录MySQL
mysql -u root -p
# 创建数据库(指定字符集)
CREATE DATABASE shopping DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
# 退出,执行SQL脚本
mysql -u root -p shopping < shopping.sql
第二步:生成Django迁移文件
进入shopping_mall目录,运行:
python manage.py makemigrations --empty shopping
这会生成一个空迁移文件shopping/migrations/0001_initial.py。打开它,把operations列表替换成:
operations = [
migrations.RunSQL(
"SELECT 1;", # 正向SQL,这里写空查询,因为我们用shopping.sql初始化了
reverse_sql="SELECT 1;" # 逆向SQL,同样为空
),
]
为什么这么干?因为shopping.sql已经包含了所有表结构和初始数据,Django的makemigrations会对比models.py和数据库,发现“表已存在”,于是生成一个空迁移,告诉Django:“别建表了,数据库已就绪”。接着运行:
python manage.py migrate
Django会把这次空迁移记录到django_migrations表,后续所有makemigrations都基于此状态。这个技巧,解决了学生“SQL脚本导入后,Django还认为没建表”的困惑。
4.3 前后端联调:跨域、代理与API路径的终极解法
Vue开发服务器(npm run serve)默认跑在http://localhost:8080,Django跑在http://localhost:8000,必然跨域。django-cors-headers是标准解法,但配置有门道。settings.py里:
INSTALLED_APPS += ['corsheaders']
MIDDLEWARE.insert(0, 'corsheaders.middleware.CorsMiddleware')
# 开发环境允许所有源
CORS_ALLOW_ALL_ORIGINS = True
# 生产环境必须精确指定
# CORS_ALLOWED_ORIGINS = ['https://yourdomain.com']
# 关键!允许携带Cookie和Authorization头
CORS_ALLOW_CREDENTIALS = True
CORS_ALLOW_CREDENTIALS = True是必须的,否则前端axios设置withCredentials: true时,浏览器会拒绝发送Cookie(含sessionid),导致登录状态丢失。
但更优雅的方案是Vue CLI代理。在web-client/vue.config.js里:
module.exports = {
devServer: {
proxy: {
'/api': {
target: 'http://localhost:8000',
changeOrigin: true,
pathRewrite: {
'^/api': '/api' // 保持路径不变
}
}
}
}
}
这样,前端代码里写axios.get('/api/products/'),开发时会被代理到http://localhost:8000/api/products/,浏览器地址栏始终显示http://localhost:8080,彻底规避跨域。上线时,Nginx反向代理/api/到Django,前端代码一行不用改。这个代理配置,是前后端分离项目的标配,必须教会学生。
4.4 支付模拟全流程实操:手把手走一遍从下单到支付成功的链路
现在,我们来走一遍最核心的支付链路。打开浏览器,访问http://localhost:8080,注册一个账号(如test@example.com/password123),登录后浏览商品,选一个加入购物车。点击“去结算”,填写收货地址,提交订单。此时,数据库里orders_order表新增一条记录,status='pending_payment'。
前端跳转到/pay?pid=PAY_20231015_9876543210。页面显示订单号、金额、倒计时。点击“确认支付”,前端向/api/payments/confirm/发送POST,携带{payment_id: "PAY_20231015_9876543210", signature: "a1b2c3..."}。
后端payments/views.py的ConfirmPaymentView收到请求,先校验signature:
expected_sig = md5(f"{order.total_amount}_MY_SECRET_KEY").hexdigest()
if signature != expected_sig:
return Response({"error": "签名错误"}, status=400)
校验通过,再查数据库确认订单状态是pending_payment,然后调用order.mark_as_paid()(状态机方法),更新为paid。此时,orders_order表里该订单status变为paid,paid_at字段被赋值。
紧接着,前端轮询/api/orders/{order_id}/status/,直到返回{"status": "paid"},页面跳转到“支付成功”页,并显示订单详情。整个过程,你可以在Django Debug Toolbar里看到SQL查询:一条UPDATE语句更新订单状态,一条INSERT插入支付记录。没有多余的查询,没有N+1问题。这就是一个干净、可追踪的支付闭环。
5. 常见问题与排查技巧实录:那些文档里不会写的坑
5.1 “Vue页面白屏,控制台报错:Failed to resolve component: router-view”
这是新手部署时最高频的问题。原因只有一个:Vue Router版本不匹配。资源包里web-client/package.json指定"vue-router": "^3.5.3",这是Vue 3的Router v4语法(createRouter),但如果你本地全局安装了Vue CLI 4,它默认创建Vue 2项目,router/index.js里还是new Router({})写法。解决方案只有两个:
-
降级Vue Router:在
web-client目录下运行:bash npm install vue-router@3.5.3 --save
然后检查router/index.js,确保是Vue 2风格:javascript import Vue from 'vue' import VueRouter from 'vue-router' Vue.use(VueRouter) export default new VueRouter({ routes }) -
升级Vue CLI(推荐):卸载旧版,安装Vue CLI 5:
bash npm uninstall -g @vue/cli npm install -g @vue/cli@5.0.8 cd web-client vue upgrade --next
这会把项目升级到Vue 3,router/index.js自动改为createRouter语法。白屏问题迎刃而解。
实操心得:遇到白屏,第一反应不是查代码,而是打开浏览器开发者工具的Console和Network面板。Console报什么错?Network里哪个JS文件404?90%的白屏问题,都是资源加载失败,而非逻辑错误。
5.2 “Django启动报错:django.core.exceptions.ImproperlyConfigured: Requested setting DATABASES, but settings are not configured”
这个错误意味着Django找不到settings.py。常见于两种场景:
- 你在shopping_mall目录外执行python manage.py runserver。正确路径是:cd shopping_mall && python manage.py runserver。
- 你的PYTHONPATH环境变量污染了。比如之前装过其他Django项目,PYTHONPATH指向了别的settings.py。临时解决:启动时显式指定:bash cd shopping_mall python manage.py runserver --settings=shopping.settings
5.3 “支付回调后,订单状态没变,还是‘待支付’”
这是签名校验失败的典型表现。排查步骤:
1. 查看Django日志(python manage.py runserver终端输出),找Signature verification failed字样。
2. 检查shopping_mall/shopping/settings.py里的PAYMENT_SECRET_KEY是否和前端web-client/src/utils/payment.js里的SECRET_KEY完全一致(包括大小写、空格)。
3. 检查/api/payments/callback/接口是否被CSRF中间件拦截。Django REST Framework默认禁用CSRF,但如果你在settings.py里误删了'django.middleware.csrf.CsrfViewMiddleware',或者加了@csrf_exempt装饰器,都会导致问题。正确做法是:在payments/views.py的回调视图类上加@method_decorator(csrf_exempt, name='dispatch')。
5.4 “后台商品列表为空,但数据库里有数据”
大概率是Django Admin的QuerySet过滤问题。打开shopping_mall/shopping/apps/products/admin.py,找到ProductAdmin类:
class ProductAdmin(admin.ModelAdmin):
list_display = ['name', 'price', 'stock', 'is_on_sale', 'is_active']
list_filter = ['is_on_sale', 'is_active', 'category']
search_fields = ['name', 'description']
注意list_filter里的is_active。默认情况下,Django Admin会显示所有is_active=True的商品。如果你在shopping.sql里导入的商品is_active字段是NULL或False,它们就不会出现在列表里。解决方案:在Admin页面右上角,把Filter从Active改成All,或者执行SQL更新:
UPDATE products_product SET is_active = 1 WHERE id > 0;
5.5 “ECharts图表不显示,控制台报错:Cannot read property ‘getWidth’ of null”
这是Vue组件生命周期导致的。SalesChart.vue里,ECharts实例在mounted()钩子中初始化,但此时DOM元素可能还未渲染。解决方案:用this.$nextTick()确保DOM更新后再初始化:
mounted() {
this.$nextTick(() => {
this.initChart();
});
},
methods: {
initChart() {
const chartDom = document.getElementById('sales-chart');
if (!chartDom) return; // 安全校验
this.chart = echarts.init(chartDom);
// ...后续配置
}
}
6. 项目扩展与进阶方向:从学习原型到生产可用的跃迁路径
这套代码不是终点,而是起点。如果你已跑通全部功能,下一步可以尝试这些真实生产级改进:
6.1 引入Celery异步任务:解耦耗时操作
当前的“发货”操作是同步的,用户点击按钮后要等几秒才返回成功。生产环境必须异步。在shopping_mall里安装Celery:
pip install celery redis
配置shopping_mall/shopping/celery.py,定义任务send_shipping_notification.delay(order_id),在ship()方法里调用它。这样,发货逻辑在后台队列执行,前端秒回响应。学生能借此理解“同步阻塞”与“异步非阻塞”的本质区别。
6.2 添加Elasticsearch商品搜索
Django ORM的icontains搜索在万级商品时会变慢。集成Elasticsearch,用django-elasticsearch-dsl库,把Product模型同步到ES,搜索接口从/api/products/?q=手机升级为全文检索、拼音搜索、同义词扩展。这会让搜索体验质的飞跃。
6.3 实现WebSocket实时订单通知
用户下单后,后台管理页应该实时弹窗提醒。用Django Channels,在orders/views.py的create_order里触发channel_layer.group_send("orders", {...}),Vue前端用WebSocket连接ws://localhost:8000/ws/orders/监听。从此,告别F5刷新。
6.4 Docker容器化部署
把MySQL、Django、Vue Nginx打包成Docker镜像。docker-compose.yml定义三个服务,一键docker-compose up -d启动全栈。这是DevOps的入门必修课,也是简历上的硬核亮点。
最后分享一个小技巧:每次功能迭代前,先写一个TODO.md文件,列出要改的3个地方——比如加优惠券功能,就写“1. models.py加Coupon模型 2. views.py加apply_coupon接口 3. cart.vue加优惠券输入框”。然后逐个击破。这种清单式开发,能让你在复杂项目中始终保持清醒,不迷失在代码海洋里。这套电商项目,我把它当作一面镜子,照见自己当年写毕设时的笨拙与执着。希望它也能成为你技术成长路上,那个愿意陪你debug到凌晨两点的同行者。
简介:直接可用的电商系统开发参考项目,后端用Django搭建API服务,前端用Vue.js实现响应式商城界面,MySQL存储数据。用户端支持账号注册登录、商品分类浏览、关键词搜索、加入购物车、生成订单、模拟在线支付(含支付状态回调逻辑)、查看订单详情、提交评价、发起退货申请并跟踪处理进度。管理员后台提供商品全生命周期管理——包括分类维护、上下架操作、首页推荐位配置、退货审核、用户与订单数据列表管理,以及基础销售统计图表展示。资源包内含完整前后端分离代码:web-client目录为Vue前端工程,shopping_mall/shopping为Django后端应用;附带shopping.sql数据库初始化脚本、详细部署说明文档、Vue环境安装指引、项目结构说明及技术要点备注。所有内容已整理归档,适合高校课程设计、毕业设计选题或个人学习电商系统开发流程时快速上手验证。
更多推荐




所有评论(0)