
简介面向高校毕业设计场景一份基于PythonDjangoVueMySql的前后端分离智慧养老服务平台完整项目可供计算机相关专业学生进行项目实战与论文支撑。资源包含886个文件压缩包大小81.05MB覆盖Python后端、Vue前端组件、HTML/JS/CSS静态资源、MySQL数据库脚本、论文docx、答辩PPT及mp4视频教程并附带安装运行批处理脚本方便快速启动与二次开发。平台围绕智慧养老业务设计普通用户可查看入院指南并维护个人中心信息管理员可对家属、护工等信息进行查询、新增、修改与删除功能链条完整。已有110人学习适合用于毕业设计、课程设计或养老信息化项目入门。1. 智慧养老服务平台的定位与技术选型——为什么是PythonDjangoVueMySql和大多数以“智慧”开头的毕设一样这个平台听上去要对接物联网设备、做行为识别但真正动手做你会发现它更像一个典型的web业务系统护工排班、老人档案、健康记录、工单流转。既然标题里锁定了PythonDjangoVueMySql那核心就不是算法而是怎么把“前后端分离”四个字落实——Vue只管渲染数据Django只返回JSONMySQL只负责持久化。这个技术栈胜在稳定、资料多、答辩时每个环节都能讲清楚但联调时跨域、token、时间格式、分页参数这些细节做不好一样会卡住一周。下文按“建表、写接口、接前端、联调验收”的路径走一遍你顺手就能照着搭。2. 用Django定义养老数据模型从老人档案到健康记录的MySQL表设计2.1 前后端分离的边界怎么切前后端分离不是简单地把html文件放到Vue项目里而是要让Django完全不渲染页面只输出JSONVue拿到JSON再决定怎么展示。常见做法是Django按resful风格暴露接口比如/api/elder/返回老人列表Vue用axios访问这个地址拿到数组之后用v-for渲染卡片或表格。这个平台的数据流可以概括为Vue页面加载axios发请求Django的urls接收请求交给view处理view通过ORM操作MySQL序列化后返回JSONVue拿到数据更新视图。一个典型的养老板块“健康记录列表”就是这样跑通的。分离开之后前后端可以并行开发接口写好后用swagger或postman先测再让前端对接代码结构上比django模板渲染要清爽得多。2.2 先列业务表再写models打开一个陌生的毕设源码该先看哪个文件我会先看models.py它直接暴露了这个平台到底存了什么数据。智慧养老平台最少要有五类表用户表护工/管理员、老人档案表、健康记录表、护理工单表、告警记录表。先把业务对象列出来再确定彼此的关系写models才不会回头改表结构。老人档案这个模块比较典型老人属于某个护工或管理员有姓名、年龄、房间号、紧急联系人、病史等信息健康记录一对一关联老人每天可以录入血压、心率、血糖等数据告警记录记录生命体征异常或设备触发的告警关联到老人和处理人。这种一对多关系在MySQL里通过外键实现在Django里就是ForeignKey。2.2.1 老人档案与健康记录模型示例from django.db import models from django.contrib.auth.models import AbstractUser class User(AbstractUser): 护工/管理员用户扩展系统自带用户表 phone models.CharField(max_length11, blankTrue, verbose_name手机号) head_img models.ImageField(upload_tohead/%Y%m, blankTrue, verbose_name头像) class Meta: db_table sys_user verbose_name 用户 ordering [-id] class Elder(models.Model): 老人档案 name models.CharField(max_length30, verbose_name姓名) age models.IntegerField(default0, verbose_name年龄) gender models.CharField(max_length4, choices[(男, 男), (女, 女)], verbose_name性别) room_no models.CharField(max_length20, verbose_name房间号) phone models.CharField(max_length11, blankTrue, verbose_name家属电话) medical_history models.TextField(blankTrue, verbose_name病史) caregiver models.ForeignKey(User, on_deletemodels.SET_NULL, nullTrue, blankTrue, related_nameelders, verbose_name负责护工) created_at models.DateTimeField(auto_now_addTrue, verbose_name建档时间) class Meta: db_table elder_info verbose_name 老人档案 ordering [-id] class HealthRecord(models.Model): 每日健康记录 elder models.ForeignKey(Elder, on_deletemodels.CASCADE, related_namehealth_records, verbose_name老人) blood_pressure models.CharField(max_length20, blankTrue, verbose_name血压) heart_rate models.IntegerField(nullTrue, blankTrue, verbose_name心率) blood_sugar models.FloatField(nullTrue, blankTrue, verbose_name血糖) record_date models.DateField(verbose_name记录日期) remark models.TextField(blankTrue, verbose_name备注) class Meta: db_table health_record verbose_name 健康记录 ordering [-record_date]这段代码里有两个地方值得注意。db_table显式指定了表名避免Django默认生成app名_类名的冗余前缀这批数据之后如果要用原生SQL分析看着方便。on_delete参数决定删除父表记录时子表怎么处理CASCADE表示老人删除时健康记录一并删除SET_NULL表示护工离职时老人的caregiver置空、但老人的档案不能跟着被删这是业务上比较合理的选择。2.3 建表之后的三个必查项models写好后执行python manage.py makemigrations和python manage.py migrateMySQL里会自动建表。建完之后不要急着写接口先用命令行确认三件事索引、时区、字段类型。HealthRecord经常按elder_id和record_date查询MySQL里的多条件查询一旦数据量上百条没有索引会出现明显卡顿。Django里可以直接在Field上挂db_indexTrue也可以在Meta里写indexes联合索引。我一般会用原始SQL检查索引有没有建上SHOW INDEX FROM health_record; SHOW INDEX FROM elder_info;created_at这类自动写入时间的字段用auto_now_addTrue即可。要注意MySQL 8.0默认datetime不带毫秒Django的DateTimeField映射为datetime(6)微秒不显式设置时不会影响查询但做日期范围过滤时要清楚这一点。第三个是字符集务必在建库时用utf8mb4而不是utf8不然前端传入的emoji或者其他生僻字会导致Incorrect string value报错。建库语句是CREATE DATABASE elder_care DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;这个坑等部署到服务器上再发现就要花半个晚上折腾数据了。3. 后端API先跑通serializers、JWT鉴权与跨域配置3.1 项目初始化与MySQL连接配置拿到源码或者从零创建项目时第一件事是配Python环境。建议用python3 -m venv venv建虚拟环境再pip install -r requirements.txt。如果环境是空的常见依赖是django、djangorestframework、django-cors-headers、djangorestframework-simplejwt连接MySQL时还得装mysqlclient。mysqlclient在Windows上安装经常报错省事的办法是装pymysql然后在Django项目的__init__.py里写pymysql.install_as_MySQLdb()中间需要改代码的细节比较少。数据库连接配置写在settings.py的DATABASES里一个能跑通的基础配置如下DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: elder_care, USER: root, PASSWORD: 你的密码, HOST: 127.0.0.1, PORT: 3306, OPTIONS: {charset: utf8mb4}, } }OPTIONS里的charset和建库时的utf8mb4对应少了这一项即使库是utf8mb4Django写入的数据也可能出现字符集转换问题。本地跑通之前不用考虑主从或读写分离这个配置足够支撑整个毕设开发和答辩演示。3.2 统一返回结构与serializer层不统一接口返回结构Vue那边每取一次数据都要讨论字段格式联调成本很高。常见的做法是封装一个统一响应所有接口都返回code、msg、data三层结构。serializer层用restframework实现起来很直接健康记录这个接口可以这样写from rest_framework import serializers from .models import Elder, HealthRecord class HealthRecordSerializer(serializers.ModelSerializer): elder_name serializers.CharField(sourceelder.name, read_onlyTrue) elder_room serializers.CharField(sourceelder.room_no, read_onlyTrue) class Meta: model HealthRecord fields [id, elder, elder_name, elder_room, blood_pressure, heart_rate, blood_sugar, record_date, remark] read_only_fields [id]用source把关联字段的嵌套信息打平前端就不需要自己再去查一遍老人档案。read_only_fields这些字段只返回给前端不允许提交避免用户伪造编号。序列化之后的字段顺序也会影响前端取值有嵌套关系的字段放在前面其它指标类数据按录入习惯排接口的可读性会好很多。3.3 登录与token请求处理前后端分离项目不再使用session而是用token。Django里用rest_framework_simplejwt做登录鉴权是最省事的路子它自动生成access_token和refresh_token只需在urls.py里挂两个路由。然后在视图中对需要登录的接口加上权限校验from rest_framework.permissions import IsAuthenticated from rest_framework.viewsets import ModelViewSet class HealthRecordViewSet(ModelViewSet): queryset HealthRecord.objects.select_related(elder).all() serializer_class HealthRecordSerializer permission_classes [IsAuthenticated] http_method_names [get, post, delete]permission_classes设为IsAuthenticated后不带token请求会直接返回401。select_related会把老人档案join进来避免循环查询。前端在请求头里带上Authorization: Bearer access_token这个平台的登录态就建立起来了token默认5分钟过期过期后带着refresh_token到/api/token/refresh/换新的这是JWT的标准流程也是答辩时高频会问的知识点。3.4 跨域与django admin界面美化分离部署后Vue运行在localhost:8080Django运行在localhost:8000两者端口不同浏览器默认会拦截跨域请求。安装django-cors-headers在settings.py里做两处配置INSTALLED_APPS [ # ... corsheaders, ] MIDDLEWARE [ # ... corsheaders.middleware.CorsMiddleware, django.middleware.common.CommonMiddleware, ] CORS_ALLOWED_ORIGINS [ http://localhost:8080, http://127.0.0.1:8080, ]参数说明CORS_ALLOWED_ORIGINS只允许指定来源访问比直接开CORS_ALLOW_ALL_ORIGINS True安全。开发期前端偶尔会让你配置CORS_ALLOW_CREDENTIALS True如果是纯token鉴权没必要开开了反而在allowed_origins为*时会报错。至于django admin界面美化开发时录数据全靠它在admin.py里给每个Model配置list_display和search_fields就够了答辩演示时看起来专业改起来也快from django.contrib import admin from .models import Elder admin.register(Elder) class ElderAdmin(admin.ModelAdmin): list_display [name, age, gender, room_no, caregiver, created_at] search_fields [name, room_no]4. Vue前端接入环境配置、axios封装与路由守卫里的token处理4.1 Node环境与工程创建前端环境没有捷径先把Node.js装好再用npm install -g vue/cli安装vue脚手架。vue create elder-web创建工程时不建议一路默认在Manually select features里选上Router和Vuex后续做页面跳转和后端对接能省不少事。依赖安装用npm install项目跑起来用npm run serve。这里要留意版本差异Vue CLI创建的工程默认Vue 3写法是组合式API那一套很多毕设源码是Vue 2时代的写法用的是this.$router和this.$axios。拿到别人的源码包时先看package.json里的vue版本再看main.js里怎么挂载的。版本不对直接复制组件代码会报一堆undefined错误。4.2 axios拦截器统一携带token前端请求后端的入口一般封装一个request.js统一配置baseURL和token。vue前后端分离请求token处理几乎每个接口都要带写在拦截器里最省事import axios from axios 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 { return Promise.reject(error) }) request.interceptors.response.use(response { const res response.data if (res.code ! 0) { // 业务错误统一提示 return Promise.reject(new Error(res.msg)) } return res }, error { if (error.response error.response.status 401) { localStorage.removeItem(access_token) window.location.href /login } return Promise.reject(error) }) export default request这段逻辑说明两点config.headers.Authorization在请求发送前把token塞进请求头所有接口一次处理完不用每个页面单独写response拦截器里判断401一旦token过期就清理本地数据并跳转登录页避免用户看到一堆无意义的报错。注意这里的baseURL配了/api开发时还需要加上代理否则会打到前端自己的地址上。4.3 登录页面调通第一个接口写登录页面是验证前后端分离是否打通的最快路径。页面里调用封装的request函数把用户名密码发给Django的/api/token/成功后把token存到localStorage再跳转到首页。一个最小可运行的login部分import request from /utils/request const form reactive({ username: , password: }) const handleLogin async () { const res await request({ url: /token/, method: post, data: form }) const { access, refresh } res.data localStorage.setItem(access_token, access) localStorage.setItem(refresh_token, refresh) window.location.href / }这里的url: /token/要和Django的urls.py匹配simplejwt默认的接口路径是/api/token/。如果你收到的源码里路径写的是/user/login/那大概率是作者自写的token签发逻辑需要对着一看返回的是不是access和refresh两个字段。登录页能正确跳转说明前端到后端、再到MySQL的链路是通的这是整个项目第一个关键里程碑。4.4 路由守卫与打包路径调整只靠请求拦截器把没有token的请求拦下来还不够未登录用户应该根本进不了页面。在router/index.js里注册一个全局前置守卫router.beforeEach((to, from, next) { const token localStorage.getItem(access_token) if (to.path ! /login !token) { next(/login) } else { next() } })beforeEach接收三个参数to是目标路由next决定是否放行。逻辑很简单目标页面不是登录页又没有token一律打回登录页。真正需要用到的场景不止这一处比如护工登录后不能访问管理员页面只需要在这里面再加一层角色判断从token解析出来的is_staff字段和路由元信息里的meta.roles比对。打包后布局异常是很多新手会碰到的问题。npm run build生成dist目录直接放到nginx里页面打开是空白或css全乱原因通常是history路由模式下的刷新404和静态资源路径问题。处理办法是在vue.config.js里设置publicPath: ./路由改成hash模式module.exports { publicPath: ./, devServer: { proxy: { /api: { target: http://127.0.0.1:8000, changeOrigin: true } } } }publicPath改成相对路径后部署到任何子目录都不会出现静态资源404devServer.proxy解决开发环境的跨域让前端项目在8080端口请求/api时自动转发到8000这就绕开了开发期的CORS麻烦。5. 联调验收多条件分页查询与django admin数据维护的小技巧5.1 用admin fixtures撑起演示数据答辩演示最怕空表。先通过admin后台录入几个老人和健康记录再用python manage.py dumpdata elder --indent 2 elder.json导出数据以后重新初始化环境时执行python manage.py loaddata elder.json即可。注意dumpdata加载时外键id也会被固化另一台机器上如果用户id对不上需要先把User表也导出来顺序是先用户后老人。5.2 把健康记录查询做成带过滤的分页接口纯列表接口在演示时不好看导师一句“能不能查某个时间段的数据”就暴露了功能深度。装django-filter在视图类上挂filterset_class前端传record_date__gte这类参数Django会自动拼SQL条件。前端页面我习惯这样拼接查询参数const params { page: currentPage, page_size: 10, elder: elderId.value, record_date__gte: startDate.value, record_date__lte: endDate.value } const res await request.get(/health_records/, { params })5.3 三个在演示前该跑通的验证点验证点执行方式预期结果登录鉴权打开前端页面未登录直接访问首页跳转到 /loginMySQL数据连通后端执行python manage.py showmigrations所有迁移后面显示 [X]打包部署npm run build后启动nginx访问dist目录页面无白屏接口请求正常最后检查一遍前端调用后端的字段名和后端serializer里定义的是否完全一致比如前端要blood_pressure后端写成了bloodPressure接口返回200但页面数据是空的。前后端分离项目的联调大量时间都花在字段名、时间格式、分页参数这些不起眼的地方拿源码做毕设时第一件事不是跑起来而是把接口文档和前端调用对一遍。本文还有配套的精品资源点击获取