☰
基于微信小程序与Django的全栈毕业设计实战:家庭菜谱系统开发指南
2026/10/31 12:19:20 网站建设 项目流程

简介:全栈开发是构建现代Web应用的核心模式,它通过前后端分离的架构,将用户界面与业务逻辑解耦,从而提升开发效率和系统可维护性。其原理在于前端负责数据展示与交互,后端提供API接口进行数据处理,两者通过HTTP协议通信。这种模式的技术价值在于支持团队并行开发、技术栈灵活选型以及更好的用户体验。在应用场景上,全栈开发广泛适用于电商平台、内容管理系统和社交应用等。本文以微信小程序、Django、Vue和MySQL构建的“家庭菜谱分享系统”为例,深入剖析了前后端分离项目实战的具体实现,涵盖了从数据库设计、RESTful API构建到小程序分包异步化等性能优化策略,为计算机专业学生完成毕业设计提供了完整的工程实践路径。

1. 项目缘起:一个“家庭大厨”的诞生记

去年年底,我的一位学弟找到我,说他的毕业设计选题是做一个关于家庭菜谱分享的小程序,技术栈想用微信小程序、Django、Vue和MySQL,走前后端分离的路子。他手头有老师给的一个非常粗略的需求文档,但具体怎么落地,从技术选型到代码实现,再到最后的论文撰写和部署,完全是一头雾水。他问我能不能给点思路,或者有没有现成的项目可以参考。

说实话,市面上教单一技术的教程很多,比如“Django从入门到精通”、“Vue3实战”之类的,但能把微信小程序、Django后端、Vue管理后台、MySQL数据库以及毕业设计特有的文档(论文、源码、部署脚本)全部串起来,形成一个完整闭环的实战项目,确实不多见。大多数教程要么只讲前端,要么只讲后端,对于需要独立完成一个完整应用、尤其是作为毕业设计的同学来说,中间有太多的“沟壑”需要自己填平。

正是基于这个契机,我决定和他一起,把这个“家庭大厨”小程序从零到一地做出来,并且把整个过程、踩过的坑、核心的代码逻辑,以及最终打包好的所有材料(源码、数据库脚本、毕业论文框架、录制的关键环节视频)都系统地整理出来。这不仅仅是一个项目,更是一份为计算机相关专业同学量身定制的“毕业设计实战指南”。无论你是对其中某一项技术不熟,还是对整个全栈开发流程感到迷茫,这个项目都能给你提供一个清晰的、可复现的路径。

2. 技术栈深度剖析:为什么是它们?

在开始敲代码之前,搞清楚为什么选择这套技术组合至关重要。这决定了项目的架构是否合理,以及后续开发是否会顺畅。很多同学做项目是“为了用技术而用技术”,最后发现组合起来特别别扭。我们这个“家庭大厨”的选择,是经过实际场景推敲的。

2.1 微信小程序:触达用户的轻量级入口

家庭菜谱的核心用户是谁?是每一个在厨房里忙碌,或者想学做菜的人。他们需要的是一个能随时打开、快速浏览、收藏菜谱的工具。原生App开发成本高,推广难;纯H5页面体验和能力(如本地存储、扫码)又受限制。微信小程序几乎是完美选择:

  • 无需安装,即用即走:用户扫个码或者搜一下就能用,转化路径极短。
  • 生态成熟,能力丰富:提供了完善的登录(微信一键登录)、支付、云存储、地理位置等API,我们菜谱的“根据定位推荐本地美食”功能就能轻松实现。
  • 开发体验友好:基于JavaScript/TypeScript和自有的组件框架,对于有前端基础的同学上手很快。而且,小程序分包异步化这个特性我们后面会重点用到,它是解决小程序体积限制、提升首次加载速度的关键。

2.2 Django:稳健高效的“后勤部长”

后端为什么选Django,而不是更“新潮”的FastAPI或Spring Boot?对于毕业设计和一个快速迭代的全栈项目,Django的“开箱即用”和“功能全面”是巨大优势。

  • Admin后台秒出:Django自带的Admin站点,几乎不用写前端代码,就能得到一个功能强大的数据管理后台。在项目初期,我们可以直接用它来管理用户、菜谱、分类等数据,快速验证业务逻辑。虽然我们最终会用Vue写一个更美观的管理后台,但Django Admin在开发阶段的效率无可替代。
  • ORM强大,避免裸写SQL:Django的ORM(对象关系映射)让操作数据库像操作Python对象一样简单。对于大多数毕业设计级别的数据操作,ORM完全够用,而且更安全(避免SQL注入)、更易维护。定义好Recipe(菜谱)、Category(分类)模型,复杂的关联查询几句代码就能搞定。
  • 生态与安全性:Django有成熟的用户认证系统、表单验证、中间件、安全防护(如CSRF、XSS防护)。国内Python Web开发中,Django的占有率非常高,社区活跃,遇到问题容易找到解决方案。很多人问“python django国内使用广泛么”,答案是肯定的,尤其在传统企业、高校和快速开发领域,它依然是主力之一。

2.3 Vue.js:构建优雅的管理后台

既然后端用了Django,为什么管理后台不直接用Django的模板,而要引入Vue做前后端分离?这是为了追求更好的用户体验和开发体验。

  • 前后端职责彻底分离:后端Django只提供纯粹的RESTful API(我们用Django REST framework),前端Vue负责所有页面渲染和交互。这样前后端开发可以并行,API定义好接口文档即可。这也是前后端分离项目实战的核心思想。
  • 响应式与组件化:管理后台通常有复杂的表单、表格和图表。Vue的响应式数据绑定和组件化开发,能让这些交互复杂的页面开发起来更高效,代码也更易复用和维护。比如,一个封装好的“菜谱上传”组件,可以在多个页面使用。
  • 丰富的生态系统:Vue有Vue Router处理路由,Vuex或Pinia管理状态,还有Element Plus、Ant Design Vue等优秀的UI库,能让我们快速搭建出专业美观的管理界面。关于vue播放m3u8,如果未来需要在小程序或后台集成视频教程播放,我们可以将视频转码为M3U8格式,并使用vue-video-player等插件配合hls.js库来实现流媒体播放。

2.4 MySQL:久经考验的数据仓库

数据库的选择相对直接。MySQL作为最流行的开源关系型数据库,稳定、性能好、生态完善。

  • 与Django天生一对:Django官方对MySQL支持非常好,配置几行代码即可连接。
  • 事务与复杂查询支持:菜谱系统可能会有“发布菜谱-上传图片”这样的需要事务保证一致性的操作,MySQL完全胜任。对于菜谱的复杂筛选(按食材、口味、难度),利用好索引,MySQL也能高效处理。
  • 学习与部署成本低:相关教程(mysql安装教程,mysql安装配置教程)遍地都是,云服务商提供的RDS服务也大多以MySQL为主,后期部署上线非常方便。

总结一下技术选型逻辑:微信小程序负责面向用户的轻量级前端;Django作为大后方的API服务器和快速原型工具;Vue构建给管理员用的、交互丰富的运营后台;MySQL作为可靠的数据存储。这套组合兼顾了开发效率、性能、用户体验和学习成本,是完成一个高质量毕业设计的黄金组合。

3. 核心功能设计与数据库建模

一个项目能否立得住,看它的核心功能设计;能否高效运行,看它的数据库模型。我们围绕“家庭大厨”的核心——菜谱,来展开设计。

3.1 用户故事与功能模块

我们从一个普通用户“小王”的视角来梳理功能:

  1. 小王打开小程序:他可以选择微信一键登录,系统会记录他的偏好(比如爱吃辣)。
  2. 浏览与发现:首页给他推荐热门菜谱、今日精选。他可以通过顶部的搜索框(这里会涉及微信小程序顶部导航栏高度的自适应调整)找“红烧肉”,也可以通过分类(家常菜、烘焙、川菜)或标签(快手菜、下饭菜)筛选。
  3. 查看菜谱详情:点开一个菜谱,能看到详细的步骤图、文字说明、所需食材清单(支持一键加入购物车清单)、烹饪技巧。还有点赞、收藏、评论功能。
  4. 互动与创作:小王收藏了喜欢的菜谱。他也可以上传自己的独创菜谱,填写信息、上传步骤图。
  5. 管理后台(管理员视角):管理员登录Vue开发的后台,可以审核用户提交的菜谱、管理用户评论、配置首页轮播图、查看系统数据统计等。

基于以上,我们划分出核心模块:用户模块、菜谱模块(核心)、分类/标签模块、互动(点赞/收藏/评论)模块、后台管理模块。

3.2 数据库表结构设计(MySQL)

使用Django的ORM,我们首先在models.py中定义模型,这直接决定了数据库表的结构。以下是几个核心模型的设计思路和部分字段:

# models.py from django.db import models from django.contrib.auth.models import AbstractUser # 1. 用户模型,继承Django内置用户,增加字段 class User(AbstractUser): avatar = models.URLField('头像', blank=True, default='') # 存储微信头像URL wechat_openid = models.CharField('微信OpenID', max_length=100, unique=True, blank=True, null=True) # 微信唯一标识 bio = models.TextField('个人简介', blank=True) favorite_cuisine = models.CharField('偏好口味', max_length=50, blank=True) class Meta: db_table = 'user' # 自定义表名 # 2. 菜谱分类 class Category(models.Model): name = models.CharField('分类名', max_length=20) icon = models.CharField('图标类名', max_length=50, blank=True) # 用于前端显示图标 order = models.IntegerField('排序', default=0) def __str__(self): return self.name # 3. 菜谱标签(多对多) class Tag(models.Model): name = models.CharField('标签名', max_length=10) # 4. 核心:菜谱模型 class Recipe(models.Model): DIFFICULTY_CHOICES = ( (1, '简单'), (2, '中等'), (3, '困难'), ) title = models.CharField('菜谱标题', max_length=100) cover_image = models.URLField('封面图') # 图片上传后得到URL,存到这里 description = models.TextField('描述') ingredients = models.TextField('食材清单') # 可以用JSON格式存储,这里简化为文本 steps = models.JSONField('步骤') # 重要!使用JSONField存储步骤数组,每个步骤包含文字和图片URL tips = models.TextField('小贴士', blank=True) cooking_time = models.IntegerField('烹饪时间(分钟)') difficulty = models.IntegerField('难度', choices=DIFFICULTY_CHOICES, default=1) # 外键关联 author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='recipes', verbose_name='作者') category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True, related_name='recipes', verbose_name='分类') tags = models.ManyToManyField(Tag, blank=True, related_name='recipes', verbose_name='标签') # 统计字段 view_count = models.IntegerField('浏览量', default=0) like_count = models.IntegerField('点赞数', default=0) collect_count = models.IntegerField('收藏数', default=0) # 状态与时间 is_approved = models.BooleanField('审核通过', default=False) # 后台审核用 created_at = models.DateTimeField('创建时间', auto_now_add=True) updated_at = models.DateTimeField('更新时间', auto_now=True) class Meta: db_table = 'recipe' ordering = ['-created_at'] # 默认按创建时间倒序 # 5. 用户-菜谱互动关系(点赞、收藏) class UserRecipeInteraction(models.Model): INTERACTION_TYPE = ( ('like', '点赞'), ('collect', '收藏'), ) user = models.ForeignKey(User, on_delete=models.CASCADE) recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE) interaction_type = models.CharField('互动类型', max_length=10, choices=INTERACTION_TYPE) created_at = models.DateTimeField(auto_now_add=True) class Meta: db_table = 'user_recipe_interaction' unique_together = ('user', 'recipe', 'interaction_type') # 防止重复点赞/收藏

设计要点与避坑指南:

  • steps字段使用JSONField:这是关键。一个菜谱的步骤是动态的,每个步骤有文字和可能的多张图片。用JSON字段存储一个列表[{"text": "第一步...", "images": ["url1", "url2"]}, ...],比再拆一个Step表更灵活,查询也更高效。Django的ORM支持对JSONField进行查询。
  • 使用unique_together:确保同一个用户对同一个菜谱只能点赞或收藏一次。这是业务逻辑的保证,在数据库层面做约束最可靠。
  • 统计字段冗余:like_count,collect_count是冗余字段。每次查询都去UserRecipeInteraction表联表统计count效率太低。我们通过监听点赞/收藏动作的信号(Django Signals)来实时更新这些计数。这是一种“用空间换时间”的常见优化。
  • 图片存储:cover_image和steps里的图片URL,我们通常不上传到Django项目本身,而是上传到云存储(如腾讯云COS、七牛云)。Django端只保存最终的文件访问URL。这涉及文件上传API的设计,后面会讲。

4. 前后端分离架构实战:API、小程序与Admin

这是项目的核心骨架。我们将系统拆分为三个部分:Django API后端、微信小程序前端、Vue管理后台。它们之间通过HTTP API进行通信。

4.1 Django后端:构建RESTful API

我们使用Django REST framework (DRF)这个强力工具来快速构建API。

首先,定义序列化器(Serializer),它负责将模型实例转换为JSON数据,以及将客户端提交的JSON数据验证并转换为模型实例。

# serializers.py from rest_framework import serializers from .models import Recipe, Category, User class CategorySerializer(serializers.ModelSerializer): class Meta: model = Category fields = ['id', 'name', 'icon'] class UserSimpleSerializer(serializers.ModelSerializer): class Meta: model = User fields = ['id', 'username', 'avatar'] class RecipeListSerializer(serializers.ModelSerializer): # 嵌套序列化,在列表页显示分类名和作者名,而不是ID category = CategorySerializer(read_only=True) author = UserSimpleSerializer(read_only=True) # 计算字段:当前登录用户是否已点赞/收藏 is_liked = serializers.SerializerMethodField() is_collected = serializers.SerializerMethodField() class Meta: model = Recipe fields = ['id', 'title', 'cover_image', 'description', 'cooking_time', 'difficulty', 'view_count', 'like_count', 'collect_count', 'category', 'author', 'is_liked', 'is_collected', 'created_at'] def get_is_liked(self, obj): request = self.context.get('request') if request and request.user.is_authenticated: return obj.interactions.filter(user=request.user, interaction_type='like').exists() return False def get_is_collected(self, obj): # 类似上面的逻辑 ... class RecipeDetailSerializer(RecipeListSerializer): # 详情页序列化器,继承列表页的,并增加更多字段 steps = serializers.JSONField() # 直接输出JSON ingredients = serializers.CharField() tags = TagSerializer(many=True, read_only=True) class Meta(RecipeListSerializer.Meta): fields = RecipeListSerializer.Meta.fields + ['steps', 'ingredients', 'tags', 'tips']

接着,编写视图集(ViewSet),它封装了常见的列表、详情、创建、更新、删除等操作。

# views.py from rest_framework import viewsets, permissions, status, filters from rest_framework.decorators import action from rest_framework.response import Response from django_filters.rest_framework import DjangoFilterBackend from .models import Recipe from .serializers import RecipeDetailSerializer, RecipeListSerializer from .permissions import IsAuthorOrReadOnly # 自定义权限:只有作者能修改 class RecipeViewSet(viewsets.ModelViewSet): """ 菜谱视图集。 列表和详情:所有人可读。 创建:需登录。 更新/删除:需是作者本人。 """ queryset = Recipe.objects.filter(is_approved=True) # 默认只返回审核通过的 permission_classes = [permissions.IsAuthenticatedOrReadOnly, IsAuthorOrReadOnly] filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter] filterset_fields = ['category', 'difficulty'] # 精确过滤 search_fields = ['title', 'description', 'ingredients'] # 搜索 ordering_fields = ['created_at', 'like_count', 'view_count'] # 排序 def get_serializer_class(self): # 根据动作选择不同的序列化器 if self.action == 'list': return RecipeListSerializer return RecipeDetailSerializer def perform_create(self, serializer): # 创建菜谱时,自动将当前登录用户设为作者 serializer.save(author=self.request.user) @action(detail=True, methods=['post'], permission_classes=[permissions.IsAuthenticated]) def like(self, request, pk=None): """点赞/取消点赞""" recipe = self.get_object() user = request.user interaction, created = UserRecipeInteraction.objects.get_or_create( user=user, recipe=recipe, interaction_type='like' ) if not created: # 如果已存在,则是取消点赞 interaction.delete() recipe.like_count = max(0, recipe.like_count - 1) liked = False else: recipe.like_count += 1 liked = True recipe.save() return Response({'liked': liked, 'count': recipe.like_count}) # 类似地,可以实现 collect(收藏)action

最后,在urls.py中注册路由。

# urls.py (api app) from django.urls import path, include from rest_framework.routers import DefaultRouter from . import views router = DefaultRouter() router.register(r'recipes', views.RecipeViewSet) # 可以继续注册其他视图集,如 categories, tags urlpatterns = [ path('', include(router.urls)), # 微信登录接口 path('wechat-login/', views.WechatLoginView.as_view(), name='wechat-login'), # 文件上传接口 path('upload-image/', views.UploadImageView.as_view(), name='upload-image'), ]

关键点解析:

  • 权限控制:使用DRF的permission_classes和自定义权限类(如IsAuthorOrReadOnly),可以精细控制每个API的访问权限。
  • 过滤、搜索、排序:通过配置filter_backends,几行代码就为列表API增加了强大的查询能力,前端传参如?category=1&search=红烧肉&ordering=-like_count即可。
  • 自定义Action:@action装饰器让我们能在视图集上添加像/recipes/{id}/like/这样的自定义端点,处理点赞等特定业务逻辑,非常清晰。
  • 微信登录:WechatLoginView会接收小程序端传来的code,调用微信接口换取openid和session_key,然后在自己的数据库中查找或创建用户,并返回自定义的Token(如JWT)给小程序。这是小程序登录的标准流程。

4.2 微信小程序前端:关键实现与优化

小程序端我们使用原生的JavaScript/TypeScript和WXML/WXSS开发。这里讲几个核心和易错点。

1. 网络请求封装与鉴权: 小程序所有网络请求必须走HTTPS。我们需要封装wx.request,统一添加加载状态、错误处理和请求头(如携带Token)。

// utils/request.js const BASE_URL = 'https://your-api-domain.com/api'; const request = (options) => { // 显示加载中 wx.showLoading({ title: '加载中...', mask: true }); return new Promise((resolve, reject) => { wx.request({ url: BASE_URL + options.url, method: options.method || 'GET', data: options.data, header: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${wx.getStorageSync('token')}` // 从本地存储获取Token }, success: (res) => { wx.hideLoading(); if (res.statusCode === 200) { resolve(res.data); } else { // 处理HTTP错误码,如401跳转登录 handleError(res); reject(res); } }, fail: (err) => { wx.hideLoading(); wx.showToast({ title: '网络错误', icon: 'none' }); reject(err); } }); }); }; // 使用示例:获取菜谱列表 export const getRecipes = (params) => request({ url: '/recipes/', method: 'GET', data: params }); // 使用示例:点赞 export const likeRecipe = (recipeId) => request({ url: `/recipes/${recipeId}/like/`, method: 'POST' });

2. 图片上传与预览: 菜谱创建时的图片上传是重头戏。小程序提供了wx.chooseMedia选择图片,wx.uploadFile上传文件。

// pages/publish/publish.js Page({ data: { steps: [{ text: '', images: [] }], // 步骤数据 }, // 为某个步骤添加图片 addStepImage(stepIndex) { wx.chooseMedia({ count: 3, // 一次最多选3张 mediaType: ['image'], success: (res) => { const tempFiles = res.tempFiles; // 先在前端预览 const newImages = tempFiles.map(file => file.tempFilePath); const key = `steps[${stepIndex}].images`; this.setData({ [key]: this.data.steps[stepIndex].images.concat(newImages) }); // 然后逐一上传到服务器 newImages.forEach(tempFilePath => { this.uploadImage(tempFilePath).then(serverUrl => { // 上传成功,将临时路径替换为服务器URL(这里需要更新data,略) }); }); } }); }, uploadImage(tempFilePath) { return new Promise((resolve, reject) => { wx.uploadFile({ url: `${BASE_URL}/upload-image/`, filePath: tempFilePath, name: 'file', header: { 'Authorization': `Bearer ${wx.getStorageSync('token')}` }, success: (res) => { const data = JSON.parse(res.data); if (data.success) { resolve(data.url); // 服务器返回的图片URL } else { reject(data.message); } }, fail: reject }); }); }, });

注意:小程序上传文件到Django,Django端需要使用request.FILES来接收,并妥善处理文件名、文件大小限制、文件类型验证,最后将文件保存到云存储或本地,返回可访问的URL。

3. 性能优化:分包异步化小程序主包大小限制为2M。随着项目功能增多,很容易超限。分包异步化是必须掌握的技能。

  • 原则:将非首页必需的页面(如个人中心、菜谱发布页、设置页)和其独有的组件、静态资源打到独立的分包中。
  • 配置:在app.json中配置subpackages。
// app.json { "pages": [ "pages/index/index", // 主包页面 "pages/recipeDetail/recipeDetail" ], "subpackages": [ { "root": "packageUser", "pages": [ "pages/profile/profile", "pages/myRecipes/myRecipes" ] }, { "root": "packagePublish", "pages": [ "pages/publish/publish" ] } ] }
  • 异步化:在需要跳转到分包页面时,使用wx.navigateTo正常跳转即可,小程序会在需要时自动下载该分包。对于分包内的自定义组件,在引用时也需要在对应页面的json文件中声明usingComponents,并确保组件在分包目录内。

4. 样式与布局:导航栏高度适配不同手机型号的顶部导航栏(包括状态栏)高度不同。为了做出沉浸式效果或精确布局,我们需要动态获取这个高度。

// app.js 或工具函数中 const getSystemInfo = () => { const systemInfo = wx.getSystemInfoSync(); const statusBarHeight = systemInfo.statusBarHeight; // 状态栏高度 const menuButtonInfo = wx.getMenuButtonBoundingClientRect(); // 胶囊按钮信息 // 导航栏总高度 ≈ 状态栏高度 + (胶囊按钮上边距 - 状态栏高度) * 2 + 胶囊按钮高度 const navBarHeight = statusBarHeight + (menuButtonInfo.top - statusBarHeight) * 2 + menuButtonInfo.height; return { statusBarHeight, navBarHeight }; }; // 在页面中设置到data,用于WXSS样式 Page({ data: { navBarHeight: 0, statusBarHeight: 0 }, onLoad() { const { navBarHeight, statusBarHeight } = getSystemInfo(); this.setData({ navBarHeight, statusBarHeight }); } })
/* 页面WXSS */ .custom-nav-bar { height: {{navBarHeight}}px; padding-top: {{statusBarHeight}}px; box-sizing: content-box; }

4.3 Vue管理后台:高效的数据驾驶舱

管理后台我们使用Vue 3 + Element Plus + Axios。它通过调用相同的Django REST API来管理数据。

1. 核心页面:菜谱管理这是一个典型的CRUD列表页,包含搜索、筛选、分页、审核、编辑、删除等操作。

<!-- RecipeManagement.vue --> <template> <div> <el-card> <!-- 搜索和筛选区域 --> <el-form :inline="true" :model="searchForm"> <el-form-item label="标题"> <el-input v-model="searchForm.title" placeholder="请输入标题" clearable /> </el-form-item> <el-form-item label="分类"> <el-select v-model="searchForm.category" placeholder="请选择分类" clearable> <el-option v-for="cat in categories" :key="cat.id" :label="cat.name" :value="cat.id" /> </el-select> </el-form-item> <el-form-item label="审核状态"> <el-select v-model="searchForm.is_approved" placeholder="请选择状态" clearable> <el-option label="已审核" :value="true" /> <el-option label="待审核" :value="false" /> </el-select> </el-form-item> <el-form-item> <el-button type="primary" @click="handleSearch">搜索</el-button> <el-button @click="resetSearch">重置</el-button> </el-form-item> </el-form> </el-card> <el-card style="margin-top: 20px;"> <!-- 数据表格 --> <el-table :data="recipeList" v-loading="loading"> <el-table-column prop="id" label="ID" width="80" /> <el-table-column prop="title" label="标题" /> <el-table-column prop="author.username" label="作者" /> <el-table-column prop="category.name" label="分类" /> <el-table-column prop="view_count" label="浏览量" width="100" /> <el-table-column label="审核状态" width="100"> <template #default="{ row }"> <el-tag :type="row.is_approved ? 'success' : 'warning'"> {{ row.is_approved ? '已通过' : '待审核' }} </el-tag> </template> </el-table-column> <el-table-column label="操作" width="200" fixed="right"> <template #default="{ row }"> <el-button size="small" @click="handleView(row)">查看</el-button> <el-button size="small" type="primary" @click="handleApprove(row)" v-if="!row.is_approved">通过</el-button> <el-button size="small" type="danger" @click="handleDelete(row)">删除</el-button> </template> </el-table-column> </el-table> <!-- 分页 --> <el-pagination style="margin-top: 20px; justify-content: center;" v-model:current-page="currentPage" v-model:page-size="pageSize" :page-sizes="[10, 20, 50]" layout="total, sizes, prev, pager, next, jumper" :total="total" @size-change="handleSizeChange" @current-change="handleCurrentChange" /> </el-card> <!-- 详情/编辑对话框 --> <el-dialog v-model="dialogVisible" title="菜谱详情" width="80%"> <!-- 对话框内容,展示或编辑菜谱详情 --> <div v-if="currentRecipe"> <h2>{{ currentRecipe.title }}</h2> <p>作者:{{ currentRecipe.author.username }}</p> <!-- 更多详情展示 --> <div v-for="(step, index) in currentRecipe.steps" :key="index"> <h4>步骤 {{ index + 1 }}</h4> <p>{{ step.text }}</p> <img v-for="img in step.images" :src="img" :key="img" style="width: 100px; margin: 5px;" /> </div> </div> <template #footer> <span class="dialog-footer"> <el-button @click="dialogVisible = false">关闭</el-button> <el-button type="primary" @click="dialogVisible = false">确定</el-button> </span> </template> </el-dialog> </div> </template> <script setup> import { ref, onMounted } from 'vue'; import { getRecipeList, approveRecipe, deleteRecipe } from '@/api/recipe'; import { ElMessage, ElMessageBox } from 'element-plus'; const searchForm = ref({ title: '', category: '', is_approved: '' }); const recipeList = ref([]); const loading = ref(false); const currentPage = ref(1); const pageSize = ref(10); const total = ref(0); const dialogVisible = ref(false); const currentRecipe = ref(null); const loadData = async () => { loading.value = true; try { const params = { page: currentPage.value, page_size: pageSize.value, ...searchForm.value }; // 移除空值参数 Object.keys(params).forEach(key => { if (params[key] === '' || params[key] === null || params[key] === undefined) { delete params[key]; } }); const res = await getRecipeList(params); recipeList.value = res.results; // DRF分页返回 results 和 count total.value = res.count; } catch (error) { ElMessage.error('获取数据失败'); } finally { loading.value = false; } }; const handleSearch = () => { currentPage.value = 1; loadData(); }; const resetSearch = () => { searchForm.value = { title: '', category: '', is_approved: '' }; handleSearch(); }; const handleSizeChange = (val) => { pageSize.value = val; loadData(); }; const handleCurrentChange = (val) => { currentPage.value = val; loadData(); }; const handleApprove = async (row) => { try { await approveRecipe(row.id); ElMessage.success('审核通过'); row.is_approved = true; // 更新本地数据 } catch (error) { ElMessage.error('操作失败'); } }; const handleDelete = (row) => { ElMessageBox.confirm('确认删除该菜谱吗?', '警告', { type: 'warning' }) .then(async () => { await deleteRecipe(row.id); ElMessage.success('删除成功'); loadData(); // 重新加载列表 }) .catch(() => {}); }; const handleView = (row) => { currentRecipe.value = row; dialogVisible.value = true; }; onMounted(() => { loadData(); }); </script>

2. 文件导出功能实现毕业设计常要求将数据导出为PDF或Excel。这里以前后端分离详情导出pdf实现步骤为例,简述思路:

  • 后端:提供一个API端点(如/api/recipes/{id}/export-pdf/),接收菜谱ID。使用像ReportLab或WeasyPrint这样的Python PDF库,根据菜谱数据生成PDF文件,然后返回文件流或一个临时下载链接。
  • 前端:在管理后台的“操作”列添加一个“导出PDF”按钮。点击后调用上述API。由于API返回的是文件流,前端可以通过创建隐藏的<a>标签并设置download属性,或者使用window.open(url)的方式来触发浏览器下载。
// 前端调用示例 const exportPdf = (recipeId) => { const token = localStorage.getItem('token'); const url = `/api/recipes/${recipeId}/export-pdf/`; const link = document.createElement('a'); link.href = url; link.setAttribute('download', `recipe_${recipeId}.pdf`); link.style.display = 'none'; document.body.appendChild(link); link.click(); document.body.removeChild(link); };

5. 项目部署与毕业设计材料整合

项目开发完成,最后一步是让它跑起来,并整理成合格的毕业设计材料。

5.1 后端部署(以Linux服务器为例)

  1. 环境准备:在服务器上安装Python、MySQL、Nginx、Supervisor。
  2. 项目上传与依赖安装:
    # 克隆项目或上传代码 cd /var/www/ git clone your-project.git cd family-chef-backend # 创建虚拟环境并激活 python3 -m venv venv source venv/bin/activate # 安装依赖 pip install -r requirements.txt
  3. 数据库配置:在MySQL中创建数据库,修改Django的settings.py中的DATABASES配置,然后执行迁移。
    python manage.py makemigrations python manage.py migrate python manage.py createsuperuser # 创建管理员
  4. 收集静态文件:如果使用了Django的静态文件,需要收集。
    python manage.py collectstatic
  5. 使用Gunicorn运行Django:Gunicorn是一个WSGI HTTP服务器。
    pip install gunicorn gunicorn --workers 3 --bind 0.0.0.0:8000 your_project.wsgi:application
  6. 使用Supervisor管理进程:让Gunicorn在后台稳定运行,并开机自启。创建配置文件/etc/supervisor/conf.d/family-chef.conf。
    [program:family-chef] command=/var/www/family-chef-backend/venv/bin/gunicorn --workers 3 --bind 0.0.0.0:8000 your_project.wsgi:application directory=/var/www/family-chef-backend user=www-data autostart=true autorestart=true redirect_stderr=true stdout_logfile=/var/log/supervisor/family-chef.log
    然后更新Supervisor:sudo supervisorctl reread && sudo supervisorctl update && sudo supervisorctl start family-chef。
  7. 配置Nginx反向代理:让Nginx处理静态文件,并将动态请求转发给Gunicorn。配置/etc/nginx/sites-available/family-chef。
    server { listen 80; server_name your-api-domain.com; # 你的API域名 location /static/ { alias /var/www/family-chef-backend/static/; # Django静态文件目录 } location /media/ { # 如果你有用户上传的媒体文件 alias /var/www/family-chef-backend/media/; } location / { proxy_pass http://127.0.0.1:8000; # 转发给Gunicorn proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } }
    启用配置并重启Nginx:sudo ln -s /etc/nginx/sites-available/family-chef /etc/nginx/sites-enabled/,sudo nginx -t && sudo systemctl restart nginx。

5.2 前端部署

  • 微信小程序:直接在微信开发者工具中上传代码,提交审核即可。
  • Vue管理后台:运行npm run build生成dist静态文件。可以将dist目录整个上传到服务器,用Nginx配置一个静态网站来访问;或者使用Netlify、Vercel等静态站点托管平台,部署非常方便。

5.3 毕业设计材料打包

这是体现你项目完整性和专业性的关键。一个标准的毕业设计材料包应该包含:

  1. 源代码:包含完整的Django后端、微信小程序前端、Vue管理后台前端三个目录。确保.gitignore文件正确,没有包含敏感信息(如settings.py中的密钥,应使用环境变量),但可以提供一个settings.example.py。
  2. 数据库脚本:
    • database_schema.sql:纯SQL文件,包含所有建表语句。可以通过python manage.py sqlmigrate app_name migration_number命令生成,或者用MySQL的mysqldump -d导出结构。
    • sample_data.sql:可选的示例数据插入脚本,方便答辩时演示。
  3. 毕业论文:这是重头戏。论文结构应包含:摘要、绪论(背景与意义)、相关技术介绍(对微信小程序、Django、Vue、MySQL等技术进行分析)、系统分析与设计(需求分析、功能模块、数据库设计)、系统实现(详细阐述关键模块的实现,配核心代码截图)、系统测试、总结与展望。切忌直接贴大段代码,用流程图、类图、序列图和关键代码片段来说明问题。你的项目实现过程,就是论文最好的素材。
  4. 部署文档:一个清晰的DEPLOYMENT.md文件,写明从零开始部署整个系统所需的步骤、命令和配置。这能极大提升项目的可复现性。
  5. 视频教程/演示录像:录制一个10-15分钟的视频,涵盖:系统主要功能演示(小程序端和管理后台)、关键代码讲解、部署流程简介。这是答辩时的加分项,能让老师快速了解你的工作。

最后的叮嘱:做毕业设计,技术实现固然重要,但清晰的文档、完整的材料、流畅的演示同样关键。这个“家庭大厨”项目,从技术选型到最终打包,提供了一条被验证过的路径。希望这份超详细的拆解,能帮你不仅做出一个能跑的项目,更能交出一份让自己和导师都满意的毕业答卷。过程中遇到的具体问题,比如微信登录的细节、Django REST framework更复杂的序列化、Vue路由的权限控制,都可以在这个坚实的基础上继续深入探索。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询