ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于Django+Vue的运动商城系统开发实战:从数据模型到订单流程

基于Django+Vue的运动商城系统开发实战:从数据模型到订单流程 简介一份面向Python课程设计场景的运动商城系统完整项目基于Django与Vue实现前后端分离后端通过Django编写API接口前端Vue代码已打包并置于static目录可直接运行体验也适合学习商城业务逻辑、接口设计与部署流程。压缩包共119个文件大小12.08MB涵盖Python源码、pyc编译文件、SQL数据库脚本、前端静态资源JS/CSS/HTML/图片以及Markdown格式的说明文档目录结构清晰便于查阅和二次开发。项目内置了运动商城典型功能模块附带详细项目文档与API接口文档数据库脚本可一键导入MySQL运行环境要求与依赖安装均有明确说明能帮助快速搭建完整环境。已有190人学习/下载对正在做课程设计或想掌握DjangoVue整合开发的学习者来说是一份直接可用的实战参考资料。1. 运动商城系统的技术选型与整体架构如果你正卡在 Python 课程设计的最后两周又恰好需要一套能演示、能答辩、能写进文档的项目那“基于 Django Vue 的运动商城系统”几乎是当下最稳妥的组合。Django 负责把后台的数据模型、用户认证、订单逻辑一次理清Vue 负责把商品列表、购物车、结算页面做得有模有样两者通过 RESTful API 通信职责边界清楚既能说明白原理又不至于在一周内把自己写崩。这套方案面向的读者很具体计算机相关专业要做课程设计或毕业设计的学生以及想快速搭建一个“前后端分离商城”做练手的一线开发者。你需要掌握的基础并不高Python 基础语法、Django 的 MTV 模式、Vue 的组件化思维只要这三样各懂六成就能起步。数据库用的是 MySQL 或 SQLite后者更适合本地演示前者更能在答辩时体现“真实项目感”。整套系统的核心链路是Vue 页面通过 Axios 调用 Django 提供的 API 接口Django 收到请求后经过 URL 路由分发到视图函数或类视图视图再通过 ORM 操作数据库最后把 JSON 数据返回给前端渲染。这条链路里最值得花时间打磨的是数据库设计因为它决定了商品、用户、订单、购物车这四张核心表之间的关系也决定了你后续写接口时是顺畅还是到处补丁。2. Django 后端核心模型设计与数据库初始化2.1 为什么先设计数据模型而不是先写页面很多初学者习惯先把页面画出来再回头补后端结果往往是前端写了十几个组件后端还不知道用户表里该放哪些字段。做商城类项目时我会先梳理业务对象用户、商品分类、商品、购物车项、订单、订单项。这六类对象基本覆盖了一个小型电商系统的全部数据需求也是你数据库里的六张核心表。用户表建议直接扩展 Django 内置的AbstractUser不要自己从零写认证逻辑。这样做的好处是 Django 自带登录、注册、会话管理你只需增加手机号、收货地址、头像等业务字段就能满足课程设计的“用户模块”要求。商品分类表用自关联外键实现两级分类运动商城通常分为“跑步、健身、球类、户外”四大类每类下再挂具体商品。2.2 编写 models.py 的完整代码在 Django 项目的models.py中核心模型的写法大致如下from django.db import models from django.contrib.auth.models import AbstractUser # 扩展 Django 自带的用户模型增加商城业务字段 class User(AbstractUser): phone models.CharField(max_length11, uniqueTrue, verbose_name手机号) address models.CharField(max_length255, blankTrue, verbose_name收货地址) avatar models.ImageField(upload_toavatars/, blankTrue, verbose_name头像) class Meta: verbose_name 用户 verbose_name_plural verbose_name # 运动商品分类自关联实现两级分类 class Category(models.Model): name models.CharField(max_length50, verbose_name分类名称) parent models.ForeignKey(self, on_deletemodels.CASCADE, nullTrue, blankTrue, related_namechildren, verbose_name父分类) class Meta: verbose_name 商品分类 verbose_name_plural verbose_name def __str__(self): return self.name # 运动商品表关键字段为价格和库存 class Product(models.Model): name models.CharField(max_length100, verbose_name商品名称) category models.ForeignKey(Category, on_deletemodels.CASCADE, related_nameproducts, verbose_name所属分类) price models.DecimalField(max_digits8, decimal_places2, verbose_name价格) stock models.PositiveIntegerField(default0, verbose_name库存) sales models.PositiveIntegerField(default0, verbose_name销量) description models.TextField(blankTrue, verbose_name商品详情) image models.ImageField(upload_toproducts/, blankTrue, verbose_name商品图片) created_at models.DateTimeField(auto_now_addTrue, verbose_name创建时间) is_on_sale models.BooleanField(defaultTrue, verbose_name是否上架) class Meta: verbose_name 运动商品 verbose_name_plural verbose_name ordering [-created_at] def __str__(self): return self.name # 购物车项一个用户对应多个商品 class CartItem(models.Model): user models.ForeignKey(User, on_deletemodels.CASCADE, related_namecart_items) product models.ForeignKey(Product, on_deletemodels.CASCADE, verbose_name商品) quantity models.PositiveIntegerField(default1, verbose_name数量) selected models.BooleanField(defaultTrue, verbose_name是否选中) created_at models.DateTimeField(auto_now_addTrue) class Meta: verbose_name 购物车项 verbose_name_plural verbose_name # 订单主体记录订单状态和用户信息 class Order(models.Model): STATUS_CHOICES ( (pending, 待支付), (paid, 已支付), (shipped, 已发货), (completed, 已完成), (cancelled, 已取消), ) order_no models.CharField(max_length32, uniqueTrue, verbose_name订单号) user models.ForeignKey(User, on_deletemodels.CASCADE, related_nameorders) total_amount models.DecimalField(max_digits10, decimal_places2, verbose_name订单总金额) status models.CharField(max_length20, choicesSTATUS_CHOICES, defaultpending, verbose_name订单状态) receiver_name models.CharField(max_length50, verbose_name收货人) receiver_phone models.CharField(max_length11, verbose_name收货电话) receiver_address models.CharField(max_length255, verbose_name收货地址) created_at models.DateTimeField(auto_now_addTrue, verbose_name下单时间) paid_at models.DateTimeField(nullTrue, blankTrue, verbose_name支付时间) class Meta: verbose_name 订单 verbose_name_plural verbose_name ordering [-created_at] # 订单项记录订单中每件商品的快照 class OrderItem(models.Model): order models.ForeignKey(Order, on_deletemodels.CASCADE, related_nameitems) product models.ForeignKey(Product, on_deletemodels.SET_NULL, nullTrue) product_name models.CharField(max_length100, verbose_name商品名称快照) product_price models.DecimalField(max_digits8, decimal_places2, verbose_name商品价格快照) quantity models.PositiveIntegerField(default1, verbose_name购买数量) class Meta: verbose_name 订单项 verbose_name_plural verbose_name这套模型设计的核心逻辑有两点。第一订单项中保存了product_name和product_price这两个快照字段好处是即使商品在后续被修改或删除历史订单中的数据仍然完整这在答辩演示时很有说服力。第二商品价格使用DecimalField而不是FloatField避免浮点数精度问题带来的价格计算误差——这属于实战项目中才会注意到的细节写进文档是加分项。2.3 数据库迁移与初始化命令模型定义完成后需要依次执行数据库迁移命令。这里推荐使用 MySQL原因是在课程设计文档中写“使用 MySQL 5.7 构建关系型数据存储”会显得项目更完整。先安装驱动pip install mysqlclient然后在settings.py中配置数据库连接DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: sport_mall, USER: root, PASSWORD: your_password, HOST: 127.0.0.1, PORT: 3306, OPTIONS: { charset: utf8mb4, } } }关键参数说明utf8mb4字符集必须设置否则中文商品名在存储时可能出现编码问题。接着执行迁移命令python manage.py makemigrations python manage.py migrate迁移完成后需要创建超级管理员账号用于登录 Django 后台python manage.py createsuperuser最后启动开发服务器访问http://127.0.0.1:8000/admin就能看到 Django 自带的数据库后台管理界面。建议在后台手动录入几条运动商品数据比如“李宁跑步鞋”、“威尔胜篮球”、“迪卡侬瑜伽垫”这样前端页面有数据可渲染演示效果更直观。3. Vue 前端页面结构与 API 对接实现3.1 创建 Vue 项目并配置开发环境Vue 前端建议使用 Vue CLI 创建项目版本选择 Vue 2因为它与 Element UI 组件库配合最稳定也是当前课程设计中使用最广泛的组合。环境准备包括 Node.js 18安装好之后执行npm install -g vue/cli vue create sport-mall-frontend创建项目时选择 Manually select features勾选 Router 和 Vuex。进入项目目录后安装 UI 组件库和 Axiosnpm install element-ui axios安装 Element UI 后在main.js中全局引入import Vue from vue import ElementUI from element-ui import element-ui/lib/theme-chalk/index.css import App from ./App.vue Vue.use(ElementUI) new Vue({ router, store, render: h h(App) }).$mount(#app)组件库的引入是前端开发效率的分水岭。不引入任何 UI 框架你要花大量时间写 CSS 来让页面“看得过去”引入 Element UI 后商品卡片、导航栏、分页组件都能直接用现成的而且呈现出的视觉效果远好于手写样式。务必在项目文档中写明这一选型理由。3.2 封装 Axios 请求并处理跨域问题前后端分离项目绕不开跨域问题。Django 后端默认只允许同源请求需要安装django-cors-headers来解决pip install django-cors-headers在settings.py中添加应用并配置允许跨域的来源INSTALLED_APPS [ # 其他应用 corsheaders, ] MIDDLEWARE [ corsheaders.middleware.CorsMiddleware, # 其他中间件 ] CORS_ALLOWED_ORIGINS [ http://localhost:8080, ]前端这边把 Axios 封装成统一的请求模块方便所有组件调用。在src/utils/request.js中写入import axios from axios import { Message } from element-ui // 创建 axios 实例配置基础 URL 和超时时间 const service axios.create({ baseURL: http://127.0.0.1:8000/api, timeout: 10000 }) // 请求拦截器从 localStorage 读取 token 并附加到请求头 service.interceptors.request.use( config { const token localStorage.getItem(token) if (token) { config.headers[Authorization] JWT token } return config }, error { return Promise.reject(error) } ) // 响应拦截器统一处理错误状态码 service.interceptors.response.use( response { return response.data }, error { Message.error(error.response?.data?.detail || 请求失败) return Promise.reject(error) } ) export default service参数说明baseURL指向 Django 后端的 API 地址开发阶段用127.0.0.1:8000部署时可以改为服务器域名。请求拦截器的作用是每次请求自动携带 token这样后端能够识别当前登录用户响应拦截器把错误信息统一弹出提示避免每个组件重复处理异常逻辑。3.3 商品列表页与购物车状态管理商品列表页是运动商城的门户也是 Vue 组件化开发的典型场景。在src/views/ProductList.vue中页面结构分为三个部分顶部导航栏、商品分类侧边栏、商品卡片网格。template div classproduct-list el-row :gutter20 el-col :span6 el-menu default-active0 selecthandleCategorySelect el-menu-item index0全部商品/el-menu-item el-menu-item v-forcategory in categories :keycategory.id :indexString(category.id) {{ category.name }} /el-menu-item /el-menu /el-col el-col :span18 el-row :gutter16 el-col v-forproduct in products :keyproduct.id :span8 el-card classproduct-card img :srcproduct.image classproduct-image / div classproduct-name{{ product.name }}/div div classproduct-price¥{{ product.price }}/div el-button typeprimary sizesmall clickaddToCart(product) 加入购物车 /el-button /el-card /el-col /el-row el-pagination layoutprev, pager, next :totaltotal :page-size6 current-changehandlePageChange /el-pagination /el-col /el-row /div /template script import request from ../utils/request export default { name: ProductList, data() { return { categories: [], products: [], currentCategory: 0, currentPage: 1, total: 0 } }, created() { this.fetchCategories() this.fetchProducts() }, methods: { // 获取商品分类列表 async fetchCategories() { const res await request.get(/categories/) this.categories res }, // 获取商品列表支持分类筛选与分页 async fetchProducts() { const params { page: this.currentPage, category: this.currentCategory || undefined } const res await request.get(/products/, { params }) this.products res.results this.total res.count }, handleCategorySelect(index) { this.currentCategory Number(index) this.currentPage 1 this.fetchProducts() }, handlePageChange(page) { this.currentPage page this.fetchProducts() }, // 加入购物车操作需要登录状态 async addToCart(product) { try { await request.post(/cart/, { product_id: product.id, quantity: 1 }) this.$message.success(已加入购物车) } catch (error) { this.$message.error(请先登录) } } } } /script这段代码中request是从前面封装的 Axios 模块导入的所以每个方法都自动携带了 token。fetchProducts中把currentCategory和currentPage作为查询参数传给后端Django 后端的视图通过分页器处理这两个参数返回results和count字段。这种接口结构是 Django REST Framework 分页的默认返回格式前端只需按约定解析。3.4 购物车状态管理与 Vuex 模块化购物车是商城系统的核心交互区域。在 Vuex 中单独建立一个cart模块状态包含购物车商品列表、总数量、总金额三个字段// src/store/modules/cart.js import request from ../../utils/request export default { namespaced: true, state: { items: [], totalCount: 0, totalAmount: 0 }, mutations: { SET_ITEMS(state, items) { state.items items state.totalCount items.reduce((sum, item) sum item.quantity, 0) state.totalAmount items.reduce( (sum, item) sum item.quantity * item.product.price, 0 ) } }, actions: { // 获取当前用户的购物车数据 async fetchCart({ commit }) { const res await request.get(/cart/) commit(SET_ITEMS, res) }, // 更新商品数量 async updateQuantity({ dispatch }, { itemId, quantity }) { await request.patch(/cart/${itemId}/, { quantity }) dispatch(fetchCart) } } }购物车模块的核心技巧是把“总数量”和“总金额”通过 Vuex 的getters计算出来而不是在每个组件里重复写 reduce 逻辑。这样不管是购物车页面还是结算页面只要调用同一个 getter 就能拿到一致的数据避免了多个页面数据不同步的问题。4. 运动商城的订单流程与支付模块实现4.1 订单状态机设计与状态流转控制订单模块是整个商城系统中业务逻辑最复杂、也最值得在答辩时展开讲解的部分。一个完整的运动商城订单至少要经历五个状态待支付、已支付、已发货、已完成、已取消。状态之间的合法流转关系需要在后端用代码约束否则会出现“已取消的订单突然变成已发货”的脏数据。在 Django 的Order模型中定义一个状态流转校验方法class Order(models.Model): # 字段定义见前文此处省略 def can_transition_to(self, new_status): 检查订单状态是否允许从当前状态流转到目标状态 allowed_transitions { pending: [paid, cancelled], paid: [shipped, cancelled], shipped: [completed], completed: [], cancelled: [], } return new_status in allowed_transitions.get(self.status, [])参数说明allowed_transitions字典定义了每个状态可以跳转到的合法状态集合。例如待支付订单可以直接取消也可以支付后变成已支付已支付的订单只能发货或取消不能跳回待支付。这个方法的调用时机是关键——在订单视图更新状态之前必须先调用can_transition_to校验不通过则直接返回错误响应。在订单创建接口中完整的事务逻辑代码如下from django.db import transaction from django.utils import timezone from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status class CreateOrderView(APIView): def post(self, request): user request.user cart_items user.cart_items.filter(selectedTrue) if not cart_items.exists(): return Response({detail: 购物车中没有选中的商品}, statusstatus.HTTP_400_BAD_REQUEST) # 生成订单号时间戳用户ID随机数 order_no f{timezone.now().strftime(%Y%m%d%H%M%S)}{user.id} with transaction.atomic(): # 创建订单主体 order Order.objects.create( order_noorder_no, useruser, total_amount0, statuspending, receiver_namerequest.data.get(receiver_name), receiver_phonerequest.data.get(receiver_phone), receiver_addressrequest.data.get(receiver_address) ) total_amount 0 order_items [] for item in cart_items: product item.product if item.quantity product.stock: raise ValueError(f{product.name} 库存不足) # 扣减库存、增加销量 product.stock - item.quantity product.sales item.quantity product.save() # 创建订单项快照 order_items.append(OrderItem( orderorder, productproduct, product_nameproduct.name, product_priceproduct.price, quantityitem.quantity )) total_amount product.price * item.quantity OrderItem.objects.bulk_create(order_items) order.total_amount total_amount order.save() # 清空购物车中已下单的商品持久化到数据库的 item 对象在这里消费 selected_ids list(cart_items.values_list(id, flatTrue)) user.cart_items.filter(id__inselected_ids).delete() return Response({order_no: order.order_no, total: float(total_amount)}, statusstatus.HTTP_201_CREATED)这段代码中有几个需要重点理解的细节。transaction.atomic()是关键中的关键。订单创建涉及多张表的写入操作——创建订单主体、创建订单项、扣减商品库存、增加商品销量、清空购物车——任何一步失败都会导致数据不一致。使用事务包裹后要么全部成功提交要么全部回滚这是课程设计答辩中最容易被打分老师追问的地方。bulk_create方法用于批量创建订单项避免在循环中逐条执行 SQL性能上比逐个 save 快得多。购物车的删除操作使用filter(id__inselected_ids)批量删除而不是循环删除。这些写法体现了对 ORM 性能的考量。4.2 支付回调接口与订单状态更新真实商城系统中支付功能会对接支付宝或微信支付的开放 API。课程设计阶段不强制要求对接真实支付渠道但需要实现一个模拟支付接口演示完整的支付流程。class MockPayView(APIView): def post(self, request, order_no): try: order Order.objects.get(order_noorder_no) except Order.DoesNotExist: return Response({detail: 订单不存在}, statusstatus.HTTP_404_NOT_FOUND) # 校验当前订单状态必须是待支付 if order.status ! pending: return Response({detail: 订单状态不允许支付}, statusstatus.HTTP_400_BAD_REQUEST) order.status paid order.paid_at timezone.now() order.save() return Response({detail: 支付成功}, statusstatus.HTTP_200_OK)这个模拟支付接口的核心逻辑是状态校验只有待支付订单才能发起支付已经取消或已经支付的订单会被拒绝。真实接入支付宝时订单号会和支付宝交易号做关联并通过异步回调来确认支付结果。建议在项目文档中写明对接真实支付时需要在回调接口中验签、查询订单号、更新订单状态这三个步骤。5. 项目文档组织与后续扩展技巧5.1 课程设计文档的目录结构与写作要点运动商城系统的技术实现只是完成了一半工作课程设计的另一半是写出一份像样的项目文档。文档的作用不仅是给老师看更是帮助你自己理清思路——写文档的过程中往往能发现代码中的逻辑漏洞。一份标准的课程设计文档建议按以下结构组织章节内容要点篇幅参考需求分析功能需求、用户角色、用例图3-5页系统设计架构图、数据库ER图、接口设计5-8页功能实现每个模块的截图代码说明8-12页系统测试测试用例表、测试结果、边界情况3-5页总结与展望遇到的问题与解决方案、可扩展方向1-2页数据库 ER 图可以用 draw.io 或 ProcessOn 画把六张表的字段名和关联关系画清楚这对后续写作和答辩都是重要的可视化材料。5.2 数据库备份与恢复的便捷命令课程设计演示前最好备份一份数据库防止演示现场出现数据丢失的尴尬。MySQL 的备份和恢复命令# 备份整个运动商城数据库 mysqldump -u root -p sport_mall sport_mall_backup.sql # 恢复数据库 mysql -u root -p sport_mall sport_mall_backup.sql如果是使用 SQLite 的项目直接复制db.sqlite3文件即可完成备份。在项目文档中写明备份恢复命令属于答辩时的加分项——它说明你考虑了数据安全性这一实际问题。5.3 前后端联调时的接口调试技巧开发过程中经常遇到前端页面报错但不知道是接口问题还是渲染问题。我的做法是在浏览器开发者工具中打开 Network 面板观察具体请求的响应内容。如果是跨域报错检查 Django 的 CORS 配置如果是 JSON 结构对不上先在后端视图函数里print输出数据再比对前端解析逻辑。在 Django 中开启 SQL 日志的输出能看到每个接口背后的实际数据库查询语句# settings.py 底部添加 LOGGING { version: 1, handlers: { console: { class: logging.StreamHandler, }, }, loggers: { django.db.backends: { handlers: [console], level: DEBUG, }, }, }开启后控制台会输出每次数据库操作对应的 SQL 语句。比如商品列表页如果出现了 N1 查询问题——假设有 10 个商品却执行了 11 条 SQL1条查询商品 10条查询分类——能在日志中直接看到。解决方法是使用 Django ORM 的select_related或prefetch_related优化查询在数据库字段量大的项目中这一步常成为答辩中的亮点。本文还有配套的精品资源点击获取
返回列表