Django+Vue+MySQL搭建前后端分离项目申报管理系统全流程实践
2026/9/16 7:31:24 网站建设 项目流程

简介:这是一份面向高校毕业设计场景的创新创业项目申报管理系统完整源码包,基于Python、Django、Vue与MySQL实现前后端分离架构,适合计算机相关专业学生参考学习,也适用于创新创业教育中心实际部署。系统涵盖在线项目申报、多级审核流程、资金管理、进度监控与成果展示等核心模块,能够帮助开发者快速理解企业级Web项目的权限设计与业务流转。压缩包共369个文件,大小22.16MB,主要包含45个Python后端文件、36个Vue组件、18个JavaScript脚本,以及SQL数据库脚本、HTML页面、SVG图标等;另附带安装/运行批处理脚本与MP4视频教程,可快速搭建运行环境。目前已有145人学习,虽然人数不高,但内容完整,入手可获完整前后端源码、数据库初始化脚本、操作演示视频及部署指导,对完成毕业设计或积累Django+Vue全栈经验很有价值。

1. 为什么我用这套技术栈做项目申报管理系统

毕业设计答辩时,评委最常问的不是“页面好不好看”,而是“你这个申报流程的状态是怎么流转的”“数据表怎么设计的”“接口鉴权怎么做的”。创新创业项目申报管理系统听起来是个业务平平的管理系统,实际上它把前后端分离、权限控制、文件上传、状态流转这些高频考点全串起来了,这才是它适合做毕设的原因。

把它拆开看:申报人能提交项目、查审核进度;评委能打分、填意见;管理员能分配专家、导出汇总表。三个角色对应三种权限,状态有草稿、待审、通过、驳回,这就是一套完整的RBAC加状态机模型。用Python+Django+DRF做后端API,Vue做前端页面,MySQL做数据持久化,正好覆盖毕业设计最容易被追问的每一个技术点。

选择这套方案还有一个现实理由:资料多、排错容易。Django的ORM让你不用手写SQL,Vue的生态让前端页面能快速搭出可用界面,MySQL安装配置的资料一搜一大把。这篇文章我会按“选型原因 → 环境搭建 → 后端接口 → 前端对接 → 部署排错”的顺序,把一套能跑通的前后端分离项目申报管理系统讲清楚。

2. 前后端分离架构里,Django、Vue、MySQL各自承担什么

2.1 为什么 Django 只写 API,不渲染页面

很多初学者会问:Django 自带模板系统,能直接返回 HTML,为什么还要单独写 Vue 页面?答案在于前后端分离带来的两个实际收益。

第一,职责边界清楚。Django 后端只负责提供 JSON 数据接口,Vue 前端只负责渲染页面和交互。答辩时你可以直接说“前端和后端通过 RESTful API 通信,后端不关心页面长什么样,前端不关心数据存在哪个表”,这句话本身就值不少分。第二,部署灵活。前端构建后是一堆静态文件,可以用 Nginx 直接托管,也可以扔到对象存储上,后端只需要暴露 8000 或 9000 端口给 API 用。

在这个系统里,Django 关注的业务边界是:用户认证(谁在登录)、申报项目表(申报了什么)、审核记录(谁在什么时候做了什么操作)。而 Vue 关注的边界是:表单收集、列表渲染、路由跳转、Token 存储。分工明确后,排错时能立刻判断问题出在哪一层:返回的是 HTML 就是路由或跨域问题,返回的是 JSON 但字段不对就是序列化器问题。

2.2 版本选型:Django 4.2、Vue 3、MySQL 8.0 的搭配理由

选型要兼顾稳定性和资料数量。我的建议组合是:Python 3.10 + Django 4.2 LTS + Django REST Framework 3.14 + Vue 3 + Vite + MySQL 8.0。这套组合的优点是都有 LTS 或长期维护版本,遇到报错时搜索出来的解决方案基本能直接套用。

不推荐追新。比如 Django 5.x 虽然已经发布,但部分第三方库的兼容性还不齐;Vue 2 虽然资料多,但官方早已停止维护,答辩时用 Vue 2 反而容易被问“为什么不用新版本”。MySQL 8.0 在 Windows 和 Linux 上都有成熟的安装包,注意安装时选择 utf8mb4 字符集,否则存入 emoji 或生僻字时会报编码错误。

2.3 后端项目搭建的最小命令集

假设你已经装好了 Python 和 MySQL,后端搭建的完整命令如下:

mkdir project-declaration cd project-declaration python -m venv venv source venv/bin/activate # Windows 下执行 venv\Scripts\activate pip install django==4.2.* djangorestframework django-cors-headers pymysql mysqlclient django-admin startproject config . python manage.py startapp projects python manage.py startapp users

创建完成后,目录结构是:

config/ # 项目配置目录 settings.py # 全局配置 urls.py # 总路由 projects/ # 项目申报业务模块 models.py # 申报表、审核记录 views.py # API 视图 users/ # 用户与角色模块 manage.py

这里的 venv 虚拟环境每台机器都要单独建,不要把 site-packages 里的依赖混进系统 Python。requirements.txt 在部署阶段要锁定版本,在本地开发阶段可以先用范围版本号便于升级。

前端项目单独建在 frontend 目录下:

npm create vite@latest frontend -- --template vue cd frontend npm install axios element-plus vue-router@4 pinia

3. 从数据表到接口:Django 后端完整实现

3.1 数据库表设计:申报项目表、审核记录表、用户表

项目申报系统的核心表是这三张:用户表(区分申报人/评委/管理员)、项目申报表(存储申报的基本信息)、审核记录表(存储每次审核的意见和状态变更)。以下是 Django 模型的定义。

from django.db import models from django.contrib.auth.models import AbstractUser class User(AbstractUser): ROLE_CHOICES = ( ('applicant', '申报人'), ('reviewer', '评委'), ('admin', '管理员'), ) role = models.CharField(max_length=20, choices=ROLE_CHOICES, default='applicant') phone = models.CharField(max_length=11, blank=True) class Project(models.Model): STATUS_CHOICES = ( ('draft', '草稿'), ('pending', '待审核'), ('approved', '已通过'), ('rejected', '已驳回'), ) name = models.CharField(max_length=200, verbose_name='项目名称') applicant = models.ForeignKey(User, on_delete=models.CASCADE, related_name='projects') category = models.CharField(max_length=50, verbose_name='项目类别') budget = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='预算金额') description = models.TextField(verbose_name='项目简介') attachment = models.FileField(upload_to='attachments/', blank=True) status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='draft') created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class ReviewRecord(models.Model): project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name='reviews') reviewer = models.ForeignKey(User, on_delete=models.SET_NULL, null=True) comment = models.TextField(verbose_name='审核意见') score = models.IntegerField(verbose_name='评分', default=0) created_at = models.DateTimeField(auto_now_add=True)

这段代码有三个设计要点。User 继承 AbstractUser 而不是另建一张表存角色,是因为 Django 的认证系统直接复用,登录接口不用自己写 session 逻辑。Project 关联 User 用了外键,on_delete=models.CASCADE表示用户注销时关联的项目一并删除,避免脏数据;而 ReviewRecord 关联到 User 则用SET_NULL,因为审核记录需要保留审计痕迹,即使审核人账号被删也不能丢。状态字段用CharField加 choices,而不是直接用整数枚举,是为了让 API 返回值直接可读,前端不用再做映射。

3.2 用 DRF 提供的 ModelViewSet 快速生成 RESTful 接口

既然用了 Django REST Framework,就不必为每个模型手工写增删改查视图。ModelViewSet 会自动生成 list、create、retrieve、update、partial_update、destroy 六个标准方法,对应 HTTP 的 GET、POST、PUT、PATCH、DELETE。

from rest_framework import viewsets, permissions from .models import Project, ReviewRecord from .serializers import ProjectSerializer, ReviewRecordSerializer class IsApplicantOrReadOnly(permissions.BasePermission): def has_object_permission(self, request, view, obj): if request.method in permissions.SAFE_METHODS: return True return obj.applicant == request.user class ProjectViewSet(viewsets.ModelViewSet): queryset = Project.objects.all().order_by('-created_at') serializer_class = ProjectSerializer permission_classes = [permissions.IsAuthenticated, IsApplicantOrReadOnly] def perform_create(self, serializer): serializer.save(applicant=self.request.user) def get_queryset(self): user = self.request.user if user.role == 'applicant': return Project.objects.filter(applicant=user) return Project.objects.all()

权限控制是这套系统的关键,上面的代码实现了两个层面的限制。第一层是permission_classesIsAuthenticated要求所有请求必须携带有效 Token,未登录用户直接 401;IsApplicantOrReadOnly限制修改操作只能由申报人本人执行,评委只能查看和填写审核意见,不能改申报内容。第二层是get_queryset,申报人接口登录后只能看到自己的项目列表,而评委和管理员能看到全部,这个“数据隔离”比权限控制更细,答辩时一定要能说清楚这两层的区别。

相应的 Serializer 定义如下:

from rest_framework import serializers from .models import Project, ReviewRecord class ProjectSerializer(serializers.ModelSerializer): applicant_name = serializers.CharField(source='applicant.username', read_only=True) class Meta: model = Project fields = ['id', 'name', 'category', 'budget', 'description', 'attachment', 'status', 'applicant_name', 'created_at'] read_only_fields = ['status', 'created_at']

read_only_fields里的 status 字段是关键:申报人在提交时不能自己指定“已通过”或“已驳回”,状态只能由审核接口在后台逻辑中修改,从源头堵住了越权操作。

3.3 注册路由并配置 JWT 登录认证

路由注册在 config/urls.py 中完成,Django 会为每个 ModelViewSet 自动绑定对应的 URL 路径:

from django.contrib import admin from django.urls import path, include from rest_framework.routers import DefaultRouter from projects.views import ProjectViewSet, ReviewRecordViewSet router = DefaultRouter() router.register(r'projects', ProjectViewSet) router.register(r'reviews', ReviewRecordViewSet) urlpatterns = [ path('admin/', admin.site.urls), path('api/', include(router.urls)), path('api/auth/', include('rest_framework_simplejwt.urls')), ]

JWT 认证使用 simplejwt 库,它提供了两个现成的接口:POST /api/auth/token/ 用用户名密码换 access token 和 refresh token,POST /api/auth/token/refresh/ 用 refresh token 换新 access token。在 settings.py 里配置:

REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': [ 'rest_framework_simplejwt.authentication.JWTAuthentication', ], 'DEFAULT_PERMISSION_CLASSES': [ 'rest_framework.permissions.IsAuthenticated', ], } from datetime import timedelta SIMPLE_JWT = { 'ACCESS_TOKEN_LIFETIME': timedelta(hours=2), 'REFRESH_TOKEN_LIFETIME': timedelta(days=7), }

access token 有效期设 2 小时,refresh token 设 7 天。有效期太长有安全风险,太短则用户需要频繁刷新,这两个值是实践下来比较平衡的配置。

4. 前端对接:Vue 3 的请求封装与跨域处理

4.1 axios 实例统一处理 Token 和错误码

前端和后端的连接点是 axios。直接在组件里用 axios 调用接口会让代码重复且难以维护,正确做法是封装一个 request 模块。

import axios from 'axios' import { ElMessage } from 'element-plus' import router from '../router' const request = axios.create({ baseURL: '/api', timeout: 10000 }) request.interceptors.request.use(config => { const token = localStorage.getItem('access_token') if (token) { config.headers.Authorization = `Bearer ${token}` } return config }, error => Promise.reject(error)) request.interceptors.response.use(response => response.data, error => { if (error.response && error.response.status === 401) { localStorage.removeItem('access_token') router.push('/login') } ElMessage.error(error.response?.data?.detail || '请求失败') return Promise.reject(error) }) export default request

这段拦截器的逻辑是:每次发送请求前从 localStorage 拿 Token 拼到请求头的 Authorization 字段上,这样后端 JWT 认证就能识别身份。响应端统一处理 401,Token 过期时自动跳回登录页,不用在每个页面里重复写错误判断。baseURL 设为 '/api' 而不是完整域名,是为了配合开发环境的 Vite 代理——这在下一小节说明。

在 Vue 组件中调用接口,就是:

<script setup> import { ref, onMounted } from 'vue' import request from '../utils/request' const projectList = ref([]) const fetchProjects = async () => { const data = await request.get('/projects/') projectList.value = data } onMounted(fetchProjects)

4.2 开发环境的 Vite 代理解决跨域问题

前后端分离必然遇到跨域。开发环境下后端跑在 localhost:8000,前端跑在 localhost:5173,前端直接请求后端 API 会被浏览器的同源策略拦截。解决方案是在 vite.config.js 里配置代理:

export default defineConfig({ server: { port: 5173, proxy: { '/api': { target: 'http://localhost:8000', changeOrigin: true } } } })

配置后,前端页面发请求到 /api/projects/,Vite 开发服务器会把请求转发给 http://localhost:8000/api/projects/。浏览器看到的所有请求都发往 5173 端口,不存在跨域问题。注意,这个代理只在开发环境生效,打包部署后需要由 Nginx 接管,这点在部署章节再展开。

CORS 的处理分两层理解:开发环境用 Vite 代理就能解决,根本不需要 django-cors-headers;但如果你想让其他设备(比如手机)直接访问后端调试,还是要在后端配置 CORS 白名单。

CORS_ALLOWED_ORIGINS = [ "http://localhost:5173", ]

4.3 Vue 页面与后端接口的实际联调示例

以“提交新的项目申报”这个操作为例,完整的前后端数据流转如下。前端代码:

<script setup> import { reactive, ref } from 'vue' import request from '../utils/request' import { ElMessage } from 'element-plus' const formRef = ref(null) const form = reactive({ name: '', category: '', budget: 0, description: '' }) const submitForm = async () => { try { const data = await request.post('/projects/', form) ElMessage.success('提交成功') } catch (e) { console.error(e) } } </script>

这个接口需要注意字段映射:后端 ProjectSerializer 的字段是 name、category、budget、description,前端 form 里的字段必须同名,Django 反序列化时才能正确对齐。提交后返回的数据里包含了 id 和 status,前端应该立即用新的 status 刷新列表状态。

5. 打包部署与避坑指南

5.1 mysqlclient 安装失败的处理方式

Windows 上安装 mysqlclient 经常失败,因为它是 C 扩展包,需要本地有 MySQL C 连接库。最省事的方式是改用它提供的纯 Python 替代方案——PyMySQL。在项目根目录的__init__.py中写入:

import pymysql pymysql.install_as_MySQLdb()

然后在 settings.py 里照常配置数据库连接:

DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'project_declaration', 'USER': 'root', 'PASSWORD': 'yourpassword', 'HOST': '127.0.0.1', 'PORT': '3306', } }

需要注意:改完数据库连接后,执行python manage.py makemigrationspython manage.py migrate生成数据表,然后创建管理员账号python manage.py createsuperuser。导入数据时如果遇到字符集报错,检查 MySQL 建库语句里是否指定了charset=utf8mb4

5.2 前端打包与 Nginx 部署完整流程

开发完成后,前端构建命令是:

npm run build

Vite 会把产物输出到 dist 目录。这个目录里的 index.html 和 assets 文件夹就是需要在 Nginx 里配置的静态资源。生产环境下前端请求的 /api 地址必须指向后端服务,Nginx 配置如下:

server { listen 80; server_name your-domain.com; root /var/www/project-declaration/dist; index index.html; location /api/ { proxy_pass http://127.0.0.1:8000/api/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } location /media/ { alias /var/www/project-declaration/media/; } location / { try_files $uri $uri/ /index.html; } }

这份配置的核心区别在于:location /用了 try_files 把路由重写到 index.html,解决 Vue Router 的 history 模式刷新后 404 问题。location /api/做反向代理,把前端请求转发到 Django 的 8000 端口。如果申报项目要上传附件,还需要单独给 /media/ 目录配置静态文件服务,Django 侧配置MEDIA_ROOTMEDIA_URL指向对应路径。

生产环境还有两个额外的坑。一个是 Django 的 DEBUG 模式必须关闭,否则静态文件路径和错误处理都不对;另一个是静态文件和上传路径的写权限,Nginx 运行用户需要有对 media 目录的写入权限,否则文件上传会 500。

5.3 admin 后台扩展:给申报管理系统加一个批量审核入口

Django 自带的 admin 后台适合做数据管理入口,但对审核场景来说,默认的表单布局不够直观。通过 ModelAdmin 的 list_display 和 inlines 配置,可以把审核工作台做成列表视图。在这个后台里可以直接看到全部待审核项目、点击进入详情核对信息、填写审核意见并提交——这其实是给管理员用的第二套操作界面。推荐在最终交付时保留这个入口,答辩演示时比从前端页面逐条点击更有说服力。

部署完成后的验证步骤建议从后端到前端检查:先确认 Django 的 8000 端口能直接访问 API 文档页,再确认 Nginx 的 80 端口能打开前端页面并完成登录、申报、审核全流程。最后用手机浏览器访问一次,确认移动端布局正常。整个系统从代码到部署的路径就是:本地能跑通 → 打包构建 → Nginx 反向代理 → 数据库迁移建表 → 上传媒体文件配置路径。每一步都有明确的验证点,按这个顺序排错会快很多。

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

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

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

立即咨询