
简介这是一份面向高校毕业设计场景的创新创业项目申报管理系统完整源码包基于Python、Django、Vue与MySQL实现前后端分离架构适合计算机相关专业学生参考学习也适用于创新创业教育中心实际部署。系统涵盖在线项目申报、多级审核流程、资金管理、进度监控与成果展示等核心模块能够帮助开发者快速理解企业级Web项目的权限设计与业务流转。压缩包共369个文件大小22.16MB主要包含45个Python后端文件、36个Vue组件、18个JavaScript脚本以及SQL数据库脚本、HTML页面、SVG图标等另附带安装/运行批处理脚本与MP4视频教程可快速搭建运行环境。目前已有145人学习虽然人数不高但内容完整入手可获完整前后端源码、数据库初始化脚本、操作演示视频及部署指导对完成毕业设计或积累DjangoVue全栈经验很有价值。1. 为什么我用这套技术栈做项目申报管理系统毕业设计答辩时评委最常问的不是“页面好不好看”而是“你这个申报流程的状态是怎么流转的”“数据表怎么设计的”“接口鉴权怎么做的”。创新创业项目申报管理系统听起来是个业务平平的管理系统实际上它把前后端分离、权限控制、文件上传、状态流转这些高频考点全串起来了这才是它适合做毕设的原因。把它拆开看申报人能提交项目、查审核进度评委能打分、填意见管理员能分配专家、导出汇总表。三个角色对应三种权限状态有草稿、待审、通过、驳回这就是一套完整的RBAC加状态机模型。用PythonDjangoDRF做后端APIVue做前端页面MySQL做数据持久化正好覆盖毕业设计最容易被追问的每一个技术点。选择这套方案还有一个现实理由资料多、排错容易。Django的ORM让你不用手写SQLVue的生态让前端页面能快速搭出可用界面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 django4.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 vitelatest frontend -- --template vue cd frontend npm install axios element-plus vue-router4 pinia3. 从数据表到接口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_length20, choicesROLE_CHOICES, defaultapplicant) phone models.CharField(max_length11, blankTrue) class Project(models.Model): STATUS_CHOICES ( (draft, 草稿), (pending, 待审核), (approved, 已通过), (rejected, 已驳回), ) name models.CharField(max_length200, verbose_name项目名称) applicant models.ForeignKey(User, on_deletemodels.CASCADE, related_nameprojects) category models.CharField(max_length50, verbose_name项目类别) budget models.DecimalField(max_digits10, decimal_places2, verbose_name预算金额) description models.TextField(verbose_name项目简介) attachment models.FileField(upload_toattachments/, blankTrue) status models.CharField(max_length20, choicesSTATUS_CHOICES, defaultdraft) created_at models.DateTimeField(auto_now_addTrue) updated_at models.DateTimeField(auto_nowTrue) class ReviewRecord(models.Model): project models.ForeignKey(Project, on_deletemodels.CASCADE, related_namereviews) reviewer models.ForeignKey(User, on_deletemodels.SET_NULL, nullTrue) comment models.TextField(verbose_name审核意见) score models.IntegerField(verbose_name评分, default0) created_at models.DateTimeField(auto_now_addTrue)这段代码有三个设计要点。User 继承 AbstractUser 而不是另建一张表存角色是因为 Django 的认证系统直接复用登录接口不用自己写 session 逻辑。Project 关联 User 用了外键on_deletemodels.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(applicantself.request.user) def get_queryset(self): user self.request.user if user.role applicant: return Project.objects.filter(applicantuser) return Project.objects.all()权限控制是这套系统的关键上面的代码实现了两个层面的限制。第一层是permission_classesIsAuthenticated要求所有请求必须携带有效 Token未登录用户直接 401IsApplicantOrReadOnly限制修改操作只能由申报人本人执行评委只能查看和填写审核意见不能改申报内容。第二层是get_queryset申报人接口登录后只能看到自己的项目列表而评委和管理员能看到全部这个“数据隔离”比权限控制更细答辩时一定要能说清楚这两层的区别。相应的 Serializer 定义如下from rest_framework import serializers from .models import Project, ReviewRecord class ProjectSerializer(serializers.ModelSerializer): applicant_name serializers.CharField(sourceapplicant.username, read_onlyTrue) 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(rprojects, ProjectViewSet) router.register(rreviews, 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 tokenPOST /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(hours2), REFRESH_TOKEN_LIFETIME: timedelta(days7), }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 认证就能识别身份。响应端统一处理 401Token 过期时自动跳回登录页不用在每个页面里重复写错误判断。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 makemigrations和python manage.py migrate生成数据表然后创建管理员账号python manage.py createsuperuser。导入数据时如果遇到字符集报错检查 MySQL 建库语句里是否指定了charsetutf8mb4。5.2 前端打包与 Nginx 部署完整流程开发完成后前端构建命令是npm run buildVite 会把产物输出到 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_ROOT和MEDIA_URL指向对应路径。生产环境还有两个额外的坑。一个是 Django 的 DEBUG 模式必须关闭否则静态文件路径和错误处理都不对另一个是静态文件和上传路径的写权限Nginx 运行用户需要有对 media 目录的写入权限否则文件上传会 500。5.3 admin 后台扩展给申报管理系统加一个批量审核入口Django 自带的 admin 后台适合做数据管理入口但对审核场景来说默认的表单布局不够直观。通过 ModelAdmin 的 list_display 和 inlines 配置可以把审核工作台做成列表视图。在这个后台里可以直接看到全部待审核项目、点击进入详情核对信息、填写审核意见并提交——这其实是给管理员用的第二套操作界面。推荐在最终交付时保留这个入口答辩演示时比从前端页面逐条点击更有说服力。部署完成后的验证步骤建议从后端到前端检查先确认 Django 的 8000 端口能直接访问 API 文档页再确认 Nginx 的 80 端口能打开前端页面并完成登录、申报、审核全流程。最后用手机浏览器访问一次确认移动端布局正常。整个系统从代码到部署的路径就是本地能跑通 → 打包构建 → Nginx 反向代理 → 数据库迁移建表 → 上传媒体文件配置路径。每一步都有明确的验证点按这个顺序排错会快很多。本文还有配套的精品资源点击获取