ARTICLE DETAIL

资讯详情

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

基于Python+Vue的HPV疫苗预约网站开发实战

基于Python+Vue的HPV疫苗预约网站开发实战 我做这个项目的时候周围不少同学都在问同一件事毕业设计选什么题目既能体现技术含量又不会把自己逼到墙角。如果你也刷到过类似问题那我直接说结论——Python Vue 的 HPV 疫苗预约网站就是一个非常典型的“进可攻退可守”的组合。它既有完整的前后端交互链路又能落到预约、排班、审核这类真实业务场景上不管是用 Django 还是 Flask都能把底层的 ORM、接口设计、状态管理、权限控制全部串起来而且讲起来评委一听就懂不需要你反复解释“这个项目到底有什么用”。这篇文章不聊虚的直接把我在实际开发里踩过的坑、验证过的写法、以及为什么这样设计的原因全部拆开讲。核心聚焦在三件事第一怎么在没有头绪的时候快速梳理出这个网站的功能边界和数据模型第二Django 和 Flask 两个方案分别应该在什么场景下用各自的实现关键点在哪第三前端 Vue 怎么跟后端“对齐”让预约、取消、库存变动这些交互不会出现数据对不上的尴尬。无论你是打算拿它做毕设还是单纯想练手前后端分离这篇都应该能帮你省下来不少翻文档的功夫。1. 项目整体设计与技术选型思路1.1 需求拆解是第一步想清楚给谁用、解决什么问题HPV 疫苗预约网站本质上解决的是“排队难、信息不透明”的问题。用户需要知道自己能在哪约、约哪家、还剩多少针管理员需要知道每天放多少号、哪些人爽约了、库存还够不够。所以做这个项目之前先别急着写代码把用户角色和核心场景列出来后面的开发才能少走弯路。我一般习惯分三个角色来画边界普通用户注册登录、浏览疫苗批次与接种点信息、选择日期和时段预约、查看个人预约记录、取消或改签。管理员接种点/系统运营维护疫苗类型二价、四价、九价、维护接种点与开放日期、设置放号数量、审核或确认预约、查看预约统计。游客未登录只能看到公告、接种点介绍、疫苗科普不能触发预约操作。这样一来你再看网上那些“完整源码”一眼就能发现它们到底做到哪一步了。有的项目只有管理员端没有用户端有的只有展示没有真正的预约流程这就是需求拆解不够导致的返工。我自己在这个项目里花的第一个下午就是在纸上画角色用例图画完之后后端要建几张表、前端要写几个页面基本都有了数。1.2 为什么是 Vue Django/Flask前后端分离的核心优势很多教程会让你把 Vue 打包放进 Django 的 templates 里看起来省事但我不推荐。原因很简单这个项目要的是“预约”这种动态交互不是你做一个企业官网。前后端分离之后后端只提供 JSON 接口前端负责页面渲染和用户操作两者通过 HTTP 通信逻辑边界清晰很多。举个例子用户提交一个预约请求时后端只需要拿到的数据是“用户ID 接种点ID 疫苗ID 预约日期 时间段”至于前端弹窗长什么样、按钮是绿色还是蓝色后端一概不关心。Vue 的组件化开发也能让你把“疫苗列表”“预约日历”“个人订单”这些信息拆成独立组件改样式或者改逻辑的时候互不影响。Vue 在这个场景里最舒服的点是它的响应式状态管理。库存数量在用户预约成功后要马上减 1如果后端返回的是最新的可用数前端可以直接更新视图取消预约时同理。你用 jQuery 也能实现但代码会散成一堆 DOM 操作维护起来非常痛苦。Vue 的 v-if、v-for、computed 这些能力天然适合“数据变了页面跟着变”的业务。1.3 Django 还是 Flask我的选型建议这是最多人纠结的地方我的建议非常直接如果你不想在项目答辩时被问懵或者你是第一次完整做前后端分离项目选 Django如果你已经写过不少 Python想快速搭一个轻量接口层或者想跟爬虫、数据分析方向靠拢选 Flask。用一张表来对比我的实际体验对比项DjangoFlask项目结构自带 app 划分适合边界清楚的项目自由度高一切靠手动规划ORM自带 ORM迁移命令好用通常配合 SQLAlchemy需要自己配置Admin 后台自带 admin能快速做数据维护需要扩展或自己写页面学习曲线初期有点陡但上手后很省心简单直接但复杂业务要自己组装适合场景有用户系统、多表关联、权限控制接口数量少、快速迭代原型如果你选了 Django建议搭配 Django REST FrameworkDRF它把序列化器、视图集、路由注册都封装好了做预约接口的效率高很多。如果你选了 Flask那就要用 Flask-SQLAlchemy 管理数据库用 Flask-JWT-Extended 做登录认证这些我在后面会提到具体配置。2. 核心数据模型与预约机制的细节设计2.1 数据库表设计没有这五张表后面寸步难行预约网站最重要的资产就是数据关系。我的设计核心是“疫苗批次—接种点—可预约日期—预约记录”下面是我常用的 Django models 草稿换成 Flask-SQLAlchemy 的写法思路完全一致# Django models 核心字段示例 from django.db import models class Vaccine(models.Model): 疫苗类型二价/四价/九价以及价格与库存总览 name models.CharField(max_length32) # 二价HPV疫苗 dosage_count models.IntegerField(default3) # 完整接种剂次数 price models.DecimalField(max_digits8, decimal_places2) stock_total models.IntegerField(default0) class VaccinationSite(models.Model): 接种点医院/社区服务中心 name models.CharField(max_length64) address models.CharField(max_length128) contact_phone models.CharField(max_length20) class Schedule(models.Model): 排班表某接种点某天某疫苗的可约数量与已约数量 site models.ForeignKey(VaccinationSite, on_deletemodels.CASCADE) vaccine models.ForeignKey(Vaccine, on_deletemodels.CASCADE) date models.DateField() slot models.CharField(max_length16) # 上午/下午 total_quota models.IntegerField(default20) booked_count models.IntegerField(default0) class Meta: unique_together ((site, vaccine, date, slot),) class Appointment(models.Model): 预约记录谁、在哪个点、约的哪针、什么状态 user models.ForeignKey(auth.User, on_deletemodels.CASCADE) schedule models.ForeignKey(Schedule, on_deletemodels.CASCADE) status models.CharField(max_length16, defaultpending) # pending / confirmed / cancelled / completed created_at models.DateTimeField(auto_now_addTrue) remark models.TextField(blankTrue) class Meta: ordering [-created_at]这里 Schedule 表用的是unique_together约束这是保证“同一个接种点同一个疫苗同一天同一个时段不会出现两条排班记录”的关键。很多同学直接在视手里手动判断重复但数据库层面的唯一约束才是最后防线。Appointment 表里 status 字段设计成字符串而不是布尔值也是经验之谈。你后面要做管理端的“审核通过/取消预约/完成接种”一个布尔只够区分两种状态字符串虽然占点空间但扩展性完全不一样。2.2 库存扣减与并发问题预约超卖怎么防预约系统最怕“超卖”。用户明明看到还剩 1 个号提交上去却显示失败或者更糟——两个人都成功了但库存只有 1 个这就是典型的并发问题。我先说 Django 里的推荐写法使用select_for_update()在事务中锁定排班记录。from django.db import transaction transaction.atomic def create_appointment(user_id, schedule_id): schedule Schedule.objects.select_for_update().get(pkschedule_id) if schedule.booked_count schedule.total_quota: raise ValueError(该时段已约满请选择其他时段) appointment Appointment.objects.create( user_iduser_id, scheduleschedule, statuspending ) schedule.booked_count 1 schedule.save(update_fields[booked_count]) return appointmentselect_for_update()的意思是“把这一行锁住”直到事务提交为止。这样两个请求同时进来时第二个会等第一个提交后再读取最新数据看到booked_count已经达到上限就会自然抛出异常。Flask 的写法要稍微注意SQLAlchemy 需要手动开事务from sqlalchemy import func from sqlalchemy.orm import Session def create_appointment(user_id, schedule_id): with Session(engine) as session: schedule session.query(Schedule).filter( Schedule.id schedule_id ).with_for_update().first() if schedule.booked_count schedule.total_quota: raise ValueError(已约满) appointment Appointment(...) session.add(appointment) schedule.booked_count 1 session.commit()补一句如果你的项目用了 Redis也可以走 Lua 脚本或者 Redis 分布式锁但对于预约网站这种量级的毕设/练手项目数据库行锁已经足够而且更容易向别人解释清楚原理。2.3 用户权限与认证JWT 怎么用才不踩坑前后端分离的项目Session 方案不太好使推荐用 JWTJSON Web Token。Django 用djangorestframework-simplejwtFlask 用flask-jwt-extended两者概念一致。核心流程用户登录后端验证用户名和密码发放一个 access token。前端把 token 存在 localStorage 或 pinia 里后续请求在 Header 里带上Authorization: Bearer token。后端接口根据 token 解析出用户身份通过装饰器/权限类控制访问。Django DRF 里的配置片段REST_FRAMEWORK { DEFAULT_AUTHENTICATION_CLASSES: [ rest_framework_simplejwt.authentication.JWTAuthentication, ], DEFAULT_PERMISSION_CLASSES: [ rest_framework.permissions.IsAuthenticated, ], }视图里区分用户端和管理员端时一个常用的做法是给用户加角色字段class UserProfile(models.Model): user models.OneToOneField(auth.User, on_deletemodels.CASCADE) role models.CharField(max_length8, defaultuser) # user / admin id_card models.CharField(max_length18, uniqueTrue)管理员的接口加一层权限判断from rest_framework.permissions import BasePermission class IsAdmin(BasePermission): def has_permission(self, request, view): return request.user.is_authenticated and request.user.userprofile.role admin这里提醒一个容易出错的地方很多初学者会忘记在注册接口里同步创建 UserProfile导致登录成功后访问个人信息接口时报“关联对象不存在”。注册时一定要在一个事务里同时创建User和UserProfile。3. 从头搭建项目的实操过程3.1 环境准备PyCharm、Python、Vue 的三方会师网上关于安装的教程非常多我简单说几个最容易卡住的细节这些都是我在帮同学调试时反复遇到的。第一Python 版本建议直接上 3.10 或者 3.11。Django 最新版本对 3.8 以下的兼容性越来越差没必要给自己埋坑。安装完之后一定要在命令行输入python --version确认路径没错Windows 上特别容易出现“我装了半天结果跑的是微软商店的假 Python”这种问题。第二PyCharm 新建项目时选择虚拟环境。如果你用的是社区版也能流畅开发够用专业版主要是对 Django/Flask 的模板级支持更强比如自带 run configuration。我的建议是用社区版 手动配置解释器完全不影响开发。第三Vue 环境。先装 Node.js再用 npm 全局装 Vue CLI 或者直接npm create vuelatest创建工程。这里我建议你用 Vite 方式创建因为 Vue CLI 维护状态已经进入停滞期Vite 在冷启动速度和热更新体验上都强很多。npm create vuelatest # 按提示选择 TypeScript 与否、Router 与否、Pinia 与否 cd your-project-name npm install npm run dev一个小笔记如果你在npm install时卡到天荒地老记得换国内镜像npm config set registry https://registry.npmmirror.com十秒钟解决问题。3.2 后端接口实现从建项目到跑通第一个接口以 Django 为例我把我实际操作过的流程缩写给你步骤顺序是踩过坑之后优化过的。# 1. 创建虚拟环境并激活 python -m venv venv venv\Scripts\activate # Windows # source venv/bin/activate # macOS/Linux # 2. 安装依赖 pip install django djangorestframework djangorestframework-simplejwt django-cors-headers # 3. 创建项目与核心 app django-admin startproject vaccination_backend cd vaccination_backend python manage.py startapp appointment然后记得去settings.py完成这些关键配置INSTALLED_APPS [ django.contrib.admin, django.contrib.auth, django.contrib.contenttypes, django.contrib.sessions, django.contrib.messages, django.contrib.staticfiles, rest_framework, corsheaders, appointment, ] MIDDLEWARE [ corsheaders.middleware.CorsMiddleware, # 尽量放在前边 ... ] CORS_ALLOW_ALL_ORIGINS True # 开发阶段用 # 生产环境建议写成具体域名 # CORS_ALLOWED_ORIGINS [http://localhost:5173] DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: hpv_vaccine, USER: root, PASSWORD: your_password, HOST: 127.0.0.1, PORT: 3306, } }数据库这块我要多说两句默认 SQLite 做实验没问题但到了要演示项目、答辩展示的时候数据量稍大一点 SQLite 就会表现出锁竞争查询体验也会变差。我建议直接用 MySQL但前提是你本地装好了 MySQL 并创建了同名的数据库否则python manage.py migrate会报连接错误。写完 models 之后执行python manage.py makemigrations python manage.py migrate如果你跟我一样喜欢用自带 admin 管理数据记得在admin.py里注册这两个模型from django.contrib import admin from .models import Vaccine, VaccinationSite, Schedule, Appointment admin.site.register([Vaccine, VaccinationSite, Schedule, Appointment])再创建一个超级用户python manage.py createsuperuser就能在后台上直接添加疫苗和接种点了这比手动往数据库里插数据方便很多。3.3 用 DRF 写一份干净的接口序列化器、路由、视图Django 写接口最舒服的点就是序列化器。列一下我的常用文件组织方式# appointment/serializers.py from rest_framework import serializers from .models import Vaccine, VaccinationSite, Schedule, Appointment class VaccineSerializer(serializers.ModelSerializer): remaining_total serializers.IntegerField(read_onlyTrue) class Meta: model Vaccine fields [id, name, dosage_count, price, stock_total, remaining_total] class ScheduleSerializer(serializers.ModelSerializer): site_name serializers.CharField(sourcesite.name, read_onlyTrue) vaccine_name serializers.CharField(sourcevaccine.name, read_onlyTrue) available serializers.SerializerMethodField() class Meta: model Schedule fields [id, site_name, vaccine_name, date, slot, total_quota, booked_count, available] def get_available(self, obj): return obj.total_quota - obj.booked_count视图层我推荐用APIView而不是ViewSet原因不是 ViewSet 不好而是对新手来说APIView 的流程更直白哪里有疑问一眼就能定位from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.permissions import IsAuthenticated class ScheduleListView(APIView): permission_classes [IsAuthenticated] def get(self, request): schedules Schedule.objects.select_related(site, vaccine).all() serializer ScheduleSerializer(schedules, manyTrue) return Response(serializer.data)这里我特意用了select_related(site, vaccine)因为Schedule表通过外键关联了接种点和疫苗如果你不用 select_related每一条数据都会额外发查询去取站点名称和疫苗名称这会造成 N1 查询。数据量小的时候感觉不出来等接种点和排班日期多了响应时间差距非常明显。最后注册路由from django.urls import path from .views import ScheduleListView, CreateAppointmentView, MyAppointmentView urlpatterns [ path(schedules/, ScheduleListView.as_view(), nameschedule-list), path(appointments/create/, CreateAppointmentView.as_view(), nameappointment-create), path(appointments/mine/, MyAppointmentView.as_view(), nameappointment-mine), ]3.4 前端 Vue 的页面结构与接口联调前端我建了四个核心页面疫苗列表页、接种点/排班查询页、预约提交页、个人预约记录页。再加一个 Layout 壳子放导航栏。Vue Router 配置import { createRouter, createWebHistory } from vue-router const routes [ { path: /, name: home, component: () import(/views/HomeView.vue) }, { path: /vaccines, name: vaccines, component: () import(/views/VaccineListView.vue) }, { path: /schedules, name: schedules, component: () import(/views/ScheduleListView.vue) }, { path: /appointments, name: appointments, component: () import(/views/MyAppointmentView.vue) }, ]前端请求封装用 axios统一带 token 是刚需。我发现很多同学会在每个页面单独写请求方法导致 token 经常忘记加而且改一个 baseURL 要改十处。正确做法是抽一个request.js出来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 }) request.interceptors.response.use( (res) res.data, (err) { if (err.response?.status 401) { // token 过期跳转登录页 } return Promise.reject(err) } ) export default request开发环境的跨域问题我建议用 Vite 的 proxy 而不是后端 CORS 开全白名单。在vite.config.js里配server: { proxy: { /api: { target: http://127.0.0.1:8000, changeOrigin: true, } } }这样前端请求/api/schedules/Vite 会自动转发到http://127.0.0.1:8000/api/schedules/浏览器的同源策略就不会拦截了后端也只需要在 Django 里配置 dev 环境允许的来源即可生产环境安全性更高。预约提交的核心逻辑也很简单用户选择一条排班记录调创建接口成功后把该排班的可约数减 1。我的具体做法是创建完后重新请求一次排班列表虽然会多一次网络请求但能保证数据是最新的。如果你想要体验更好也可以在创建接口的响应里直接返回最新的available数值前端更新那个排班项的状态两者皆可。这里有一个我实际遇到过的坑创建预约时前端传的 schedule_id 是对的但后端一查发现“对应排班不存在”。排查了半天发现是因为 Django 的on_deletemodels.CASCADE在排班记录被管理员删掉之后Appointment 表里的外键也跟着清理了。这个属于正常行为但用户端看到的就是“预约突然消失了”很困惑。所以我的建议是排班记录不要物理删除给 Schedule 加一个is_active布尔字段管理员想下架某天的号就设置为 False而不是删掉记录。3.5 Flask 方案的不一样之处轻量接口手动拼装如果你最终选了 Flask那代码组织方式要跟着变。Django 的项目结构和“app”概念在 Flask 里不存在我一般会按功能建包。目录大概是flask_backend/ ├─ app.py # Flask 工厂入口 ├─ models/ # SQLAlchemy 模型 ├─ api/ │ ├─ auth.py │ ├─ appointment.py │ └─ vaccine.py └─ config.pyFlask 里create_app是我推荐的模式方便以后做测试和扩展from flask import Flask from flask_sqlalchemy import SQLAlchemy from flask_jwt_extended import JWTManager from flask_cors import CORS db SQLAlchemy() jwt JWTManager() def create_app(): app Flask(__name__) app.config[SQLALCHEMY_DATABASE_URI] mysqlpymysql://root:passwordlocalhost/hpv_vaccine app.config[JWT_SECRET_KEY] your-secret-key db.init_app(app) jwt.init_app(app) CORS(app) from .api.auth import auth_bp from .api.appointment import appointment_bp app.register_blueprint(auth_bp, url_prefix/api/auth) app.register_blueprint(appointment_bp, url_prefix/api/appointment) return appFlask 的视图函数是这样from flask_jwt_extended import jwt_required, get_jwt_identity from flask import request appointment_bp.route(/create, methods[POST]) jwt_required() def create_appointment(): user_id get_jwt_identity() data request.get_json() # 校验参数开事务锁排班行创建预约记录Flask 胜在代码少、自由度大但自由度大的另一面是权限控制、序列化、分页都要自己来或者借助第三方库。我的个人感受是Flask 更适合你已经对预约业务流程很熟、想快速做 API 原型的情况如果你希望通过这个项目学习后端开发的规范流程Django 的约束会推着你写出更规整的代码。4. 高频问题与排查技巧实录4.1 一张表看清最常见的五个坑现象可能原因解决方案前端请求接口报 CORS 错误Django 没装 corsheaders 或配置顺序不对安装 django-cors-headers把 CorsMiddleware 放到中间件靠前位置Vue 项目在开发时能打开刷新页面后 404静态托管时没有配置 history fallbacknginx 里 try_files 配置或改用 hash 模式预约提交显示成功但库存没减少并发场景下用了普通读再做写事务 select_for_update()或者用乐观锁版本号中文展示乱码MySQL 表字符集不是 utf8mb4建库时指定DEFAULT CHARACTER SET utf8mb4python manage.py migrate提示 Cant connect to MySQL server数据库没创建或者密码不对先去 MySQL 里创建 database再配置 settings4.2 一个让我花了整个晚上的排查CORS 和 cookie照理说 JWT 模式不需要 cookie但我在开发初期图省事用 admin 后台登录后直接在同一个源下请求接口结果发现 Django session 和 DRF JWT 的认证方式混到了一起。请求头里既有Cookie: sessionidxxx又有Authorization: Bearer xxx而 Django 的认证类默认会先取 session 再取 JWT优先级问题导致某些接口行为诡异。解决方案相对简单在REST_FRAMEWORK配置里只保留 JWT 认证类或者把 session 认证类放到最后。另外开发时把浏览器的 Cookie 清理一下避免旧 session 干扰判断。这种情况属于“环境问题比代码问题更难找”的典型代表遇到类似情况不要急着怀疑代码逻辑先清掉浏览器状态再试。4.3 一个隐藏很深的坑date 字段和时区Django 默认USE_TZ True数据库里存的是 UTC 时间。如果你在前端选择了“2025-06-15 上午”传到后端的字符串是2025-06-15但在生成 Schedule 记录时用了datetime.now()而不是timezone.now()可能会插入一条偏移 8 小时的记录导致日期错位。我的统一方案是所有日期字段用DateField而不是DateTimeField前端传什么存什么所有“创建时间”这种自动字段用timezone.now()。这样做的好处是预约只关心“当天哪个时段”不关心具体秒级时间避免时区换算带来的麻烦。5. 前后端联调与部署上线的关键问题5.1 本地联调容易忽略的 header 与认证流程本地联调的时候最常出问题的地方反而不是接口逻辑而是请求头。你是axios.create({ baseURL: /api })用户登录之后 localStorage 里有 token但如果用户刷新了页面前端状态管理库里的用户信息是空的这时去访问“我的预约”页面会拿到 401。解决方案是做一个“初始化”逻辑应用启动时从 localStorage 读取 token如果有就把用户信息重新拉取一遍如果没有直接跳到登录页。Vue Router 的全局前置守卫可以配合做router.beforeEach((to, from, next) { const token localStorage.getItem(access_token) if (to.meta.requiresAuth !token) { next(/login) } else { next() } })5.2 部署时最容易翻车的三个细节第一前后端要配上不同的端口或域名。我习惯用 nginx 同时托管 Vue 静态文件和反向代理 Django/Flask 接口Vue 构建成 dist 之后直接放/usr/share/nginx/html接口地址通过 nginx 转发到http://127.0.0.1:8000。配置片段看一下server { listen 80; server_name your-domain.com; root /var/www/vaccine_frontend/dist; index index.html; location / { try_files $uri $uri/ /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; } }try_files $uri $uri/ /index.html;这一句很重要解决了我在 4.1 表格里提到的刷新 404 问题。第二Django 部署前要改DEBUG False然后配置ALLOWED_HOSTS。否则你在浏览器访问接口会看到一个很丑的 DisallowedHost 报错页面。严格模式下AllowedHosts 应该写域名或者服务器的公网 IP不推荐用星号通配。第三Flask 部署时不要用自带的开发服务器。开发服务器是单进程单线程的稍微有点并发访问就会出现明显的卡顿。用 Gunicorn 做 WSGI server进程数量按服务器 CPU 核心数乘以 2 加 1 估算至少 4 个 worker 起步。命令参考pip install gunicorn gunicorn -w 4 -b 127.0.0.1:8000 app:app5.3 MySQL 与 Redis 的取舍这个项目需要 Redis 吗问自己一个问题预约请求的并发量有多大如果你的项目是毕设或者练手几十个人同时抢十个号MySQL 的行锁已经稳稳够用Redis 属于锦上添花。但如果你想在项目里体现一下技术深度用 Redis 做“当日可约号数”的缓存也是不错的选择。思路是管理员设置放号时把某一天的排班库存写入 Rediskey 设计为vaccine:schedule:{schedule_id}:remain值为剩余可约数量。用户预约时先用 Redis 的DECR原子减一如果结果不小于 0再去写 MySQL 生成预约记录如果小于 0说明约满了需要回滚。这个方案的优势是性能好但如果 Redis 挂了或者数据不一致排查成本也高。我的建议是项目里先用 MySQL 事务锁方案保证正确性如果篇幅允许再写一节“Redis 缓存放号数量”的扩展说明你答辩时也能展示更高的设计视野。6. 项目后续还可以这样扩展做完基础预约流程这个项目能扩展的方向其实非常自然。最实用的是增加一个“疫苗预约日历”按日期展示各接种点的剩余号数前端用日历组件配合颜色标注绿色代表充足、黄色代表紧张、红色代表约满用户能一眼看到未来七天的整体情况。另一个方向是消息通知。用户预约成功之后给用户发一条短信或者站内信。短信需要找服务商申请签名模板成本不高但是流程繁琐站内信就简单很多在数据库里建一个Notification表关联用户前端在导航栏右上角显示未读小红点。用 Vue 轮询或者 Django Channels 做 WebSocket 推送都行。如果选 WebSocket要注意 Django 的传统 WSGI 架构和切到 ASGI 的兼容问题这是另一个大坑建议单独开个专题来写。还有一点是统计报表。管理员端可以展示“按疫苗类型统计预约人数”“按接种点统计到诊率”“未来七天预约趋势”等图表。后端接口返回聚合数据前端用 ECharts 画折线图和柱状图视觉效果很好答辩展示时非常加分。如果你想把项目做得更完整还可以加“疫苗科普页面”“健康自测小问卷”“接种注意事项提醒”这些属于低风险高展示度的功能代码量不大但能让整个项目显得很有关怀感导师看了也会觉得你考虑问题全面。我在实际开发中的一点体会做个这样的预约网站最难的不是某种技术的语法而是你能不能把“预约”这件事的边界想清楚。我第一版的时候把状态设计得太简单只有“已预约”和“未预约”结果后面做取消功能的时候发现还要区分“已取消”“已完成”“爽约”硬着头皮改数据模型连带前端逻辑一起返工。所以现在做任何系统都会先把状态流转图画出来。另外我强烈建议你在动手写之前先跑通一个最小闭环注册用户、登录拿 token、查询疫苗列表、选择一个排班预约成功。这个闭环通了整个项目的核心已经完成了 80%剩下的都是锦上添花。不要一上来就做权限管理、消息通知、图表统计那些花哨功能基础链路不稳往上长都是空架子。最后再分享一个小技巧如果时间允许把项目的 README 写成一份“部署运行指南”里面写清楚环境版本、数据库初始化命令、前后端启动命令。这样不仅方便你自己隔几个月回来看也能在答辩时直接给评委演示导师问起“这个项目能不能跑起来”你递出这份文档就能省下不少嘴皮子。
返回列表