简介:面向高校课程设计与毕业设计场景,采用SpringBoot与Vue进行前后端分离开发的中医院问诊系统,是一套可直接运行的完整项目。项目基于JDK1.8、MySQL5.7及以上版本环境搭建,覆盖患者端问诊、医生端接诊等基础业务,能帮助学习者理解从前端页面到后端接口再到数据库访问的完整请求链路。资源包共857个文件,大小约23.37MB,以Java、Vue、JavaScript、CSS等源码文件为主,同时也包含SQL数据库脚本、Windows下的一键安装与启动批处理、以及项目工程配置文件,导入开发工具后即可围绕源码进行二次修改。目前已有78人学习下载,适合作为课设或毕设的参考项目,尤其适合需要快速理解SpringBoot与Vue整合方式的中级Java学习者。包内附带前端编译产物和若干辅助文件,目录结构接近真实业务系统,结合源码与配置逐层阅读,可以较快掌握项目启动流程、分层写法与常见排错切入点,对后续自行扩展模块也有一定帮助。
1. 为什么课设/毕设选「SpringBoot+Vue 中医院问诊系统」:它到底解决什么问题
每年课设或毕设选题季,中医院问诊系统都是常客,但绝大多数人拿到的要么是老旧 SSH 架构,要么是纯前端假数据,答辩时一被问“数据库怎么设计的”“并发挂号怎么处理”就卡壳。这套基于 SpringBoot+Vue 的中医院问诊系统,核心价值在于它是“可运行、能演示、有完整业务闭环”的前后端分离工程:患者能注册登录、按科室挂医生号;医生能拉取待诊队列、写电子病历、开中药处方;管理员能管科室、管药品、管号源。技术栈是当前就业市场最主流的 SpringBoot + Vue 组合,不是冷门框架,答辩时也容易讲清楚。
这套系统的受众很明确:正在做课设或毕设的计算机相关专业学生,以及想快速搭一套“像回事”的管理系统来练手或二次开发的初级工程师。它不涉及复杂的分布式中间件,也不需要高并发架构,重点在于把 RESTful API、JWT 认证、RBAC 权限、表关系设计、前后端联调这一整套“毕业设计必考知识点”走通。下面从工程结构、业务表设计、核心接口、前端页面到部署避坑,一层层拆开讲。
2. 用 SpringBoot+Vue 搭中医院问诊系统:项目结构、技术选型与启动顺序
2.1 前后端分离的工程骨架:目录怎么分,代码放哪里
拿到一个“源码可运行”的 SpringBoot+Vue 工程,第一件事不是双击运行,而是先把目录结构认清楚。常见的做法是后端一个文件夹、前端一个文件夹,根目录放 README 和数据库初始化脚本。以这套系统的典型布局为例:
hospital-tcm/ ├── backend/ # SpringBoot 后端工程 │ ├── src/main/java/com/example/hospital/ │ │ ├── controller/ # 控制层:暴露 REST 接口 │ │ ├── service/ # 业务逻辑层 │ │ ├── mapper/ # MyBatis-Plus 数据访问层 │ │ ├── entity/ # 数据库实体类 │ │ ├── config/ # 跨域、JWT、MyBatis-Plus 配置 │ │ ├── common/ # 统一返回结果、异常处理、工具类 │ │ └── HospitalApplication.java # 启动类 │ ├── src/main/resources/ │ │ ├── application.yml # 数据源、端口、JWT 密钥配置 │ │ └── mapper/ # XML 文件(复杂 SQL 时使用) │ └── pom.xml ├── frontend/ # Vue 前端工程 │ ├── src/ │ │ ├── api/ # 按模块封装的 API 请求 │ │ ├── router/ # 路由配置,含动态路由和守卫 │ │ ├── store/ # Pinia 或 Vuex 状态管理 │ │ ├── views/ # 页面组件 │ │ ├── components/ # 公共组件 │ │ └── utils/request.js # axios 实例与拦截器 │ ├── package.json │ └── vite.config.js # 开发代理配置 └── sql/ └── hospital_tcm.sql # 数据库初始化脚本这个结构本身就有“毕设答辩价值”:目录分层的理由是控制层只做参数接收和响应封装,业务逻辑下沉到 service,数据访问通过 MyBatis-Plus 的 BaseMapper 减少手写 SQL。前端按 api、router、store、views 拆分,是为了让页面组件不直接写 axios,所有请求统一走 request.js 拦截器,方便加 token 和统一错误提示。我一般会建议在 README 里补一张图说明请求流转路径:浏览器 → Vue Router → views → api/request.js → SpringBoot Controller → Service → Mapper → MySQL,答辩讲这张图比背概念管用得多。
2.2 版本选型与启动顺序:为什么推荐 SpringBoot 2.7 + Vue 3 + MyBatis-Plus
技术选型这一块,很多新手上来就追新,结果踩了版本不兼容的坑。SpringBoot 3.x 要求 JDK 17,如果学校的评测环境还是 JDK 8,运行直接报错;Vue 3 配 Vite 开发时要求 Node 14+,但部分国产浏览器内核版本低,页面加载会白屏。做课设毕设,稳定压倒一切,我一般用这套组合:
后端:SpringBoot 2.7.18 + JDK 8 + MyBatis-Plus 3.5.x + MySQL 5.7(或 8.0) 前端:Vue 3.2 + Vite 4 + Element Plus 2.4 + Pinia 2 + vue-router 4选 SpringBoot 2.7 而不是 3.x 的原因:2.7 是最后一个原生支持 JDK 8 的大版本,资料最多,遇到问题百度就能搜到,而且和 MyBatis-Plus 3.5 配合不需要额外适配。Vue 3 + Element Plus 是当前前端的主流组合,组件风格适合做管理系统;如果之前学过 Vue 2 也不用慌,Vue 3 的组合式 API 上手成本不高。MyBatis-Plus 能省掉大量单表 CRUD 的 XML 配置,问诊系统里挂号单、病历、处方这些表的增删改查几乎全是单表操作,用它最省力。
启动顺序是固定的:先启动 MySQL 并导入 sql/hospital_tcm.sql,再启动后端 SpringBoot(默认端口 8080),最后启动前端npm install && npm run dev(Vite 默认端口 5173)。开发环境里前端通过 Vite 代理把/api转发到后端,所以不需要手动处理跨域。后端启动成功的标志是控制台打印 Tomcat started on port 8080,前端启动成功的标志是终端提示 Local: http://localhost:5173。
2.3 配置文件的三个必调参数:数据源、JWT 密钥、文件上传大小
配置是整个系统跑不跑得起来的命门。application.yml 里有三个参数是每次换环境都要动的,而且踩坑概率极高。下面这段配置是我基于常见做法整理的最小模板,照着改就能用:
server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/hospital_tcm?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver servlet: multipart: max-file-size: 20MB max-request-size: 20MB mybatis-plus: configuration: map-underscore-to-camel-case: true global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0 jwt: secret: hospital-tcm-secret-key-2024-please-change expire: 604800 # 7 天,单位秒逻辑说明:数据源 URL 里的serverTimezone=Asia/Shanghai是必填的,MySQL 8.x 驱动器不指定时区会报Cannot create PoolableConnectionFactory;characterEncoding=utf8保证问诊单、药品名称里的中文不乱码。MyBatis-Plus 的map-underscore-to-camel-case把数据库的下划线字段自动映射为实体的驼峰属性,比如real_name对应realName,不用手写 resultMap;逻辑删除配置让挂号单、处方记录不物理删除,只打标,答辩时可以说“这是为了保留医疗操作审计轨迹”。JWT 密钥和过期时间是前后端约定的重点,密钥过期或者临时改了,前端已登录用户会全部 401,排查时最容易忽视。
3. 问诊业务的数据基石:八张核心表与权限模型设计
3.1 数据表总览:从用户到处方,一张图看懂业务流转
中医院问诊系统的业务绕不开这几个核心对象:人(患者、医生、管理员)、科室、排班、挂号、问诊、处方、药品。把这几个对象落成数据表,整个系统的骨架就有了。常见设计是八张表,每张表的职责如下:
| 表名 | 核心字段 | 作用 |
|---|---|---|
| sys_user | id, username, password, real_name, role, phone | 统一登录账号,角色区分患者/医生/管理员 |
| tcm_doctor | id, user_id, dept_id, title, introduction, reg_fee | 医生扩展信息,关联科室与挂号费 |
| tcm_dept | id, dept_name, intro | 科室表,如内科、针灸科 |
| tcm_schedule | id, doctor_id, dept_id, work_date, am_pm, total, remain | 排班表,控制号源数量 |
| tcm_register | id, patient_id, schedule_id, doctor_id, status, create_time | 挂号单,状态流转:待就诊→已就诊→已取消 |
| tcm_medical_record | id, patient_id, doctor_id, register_id, symptom, diagnosis, advice | 问诊记录,核心病历信息 |
| tcm_prescription | id, record_id, doctor_id, total_price, create_time | 处方主表,关联药品明细 |
| tcm_prescription_item | id, prescription_id, drug_id, dosage, quantity | 处方明细,支持一症多药 |
这张表设计最关键的关联链条是:患者先看排班 → 选号挂号生成 tcm_register → 医生接诊后写 tcm_medical_record → 根据诊断开 tcm_prescription → 处方下挂多条 tcm_prescription_item 指向具体药品。这个链路能支撑“我的挂号”“我的病历”“我的处方”这三大查询页面,答辩时顺着链条讲就很有逻辑性。
3.2 权限模型:为什么用 RBAC 而不是给用户表的 role 字段写死
很多毕设项目图省事,直接给 sys_user 一个 role 字段,登录后根据字符串判断权限。这套系统如果也这么干,会有一个显而易见的短板:假如要扩展一个“药师”角色,用来审核处方,就得改前端每个判断用户角色的地方,后端每个 Controller 里的逻辑分支也得加。用 RBAC 模型,加角色只需要在数据库插两条关联记录。常见做法是加两张中间表:
CREATE TABLE sys_role ( id BIGINT PRIMARY KEY AUTO_INCREMENT, role_name VARCHAR(50) NOT NULL, role_key VARCHAR(50) NOT NULL UNIQUE COMMENT '角色标识,如 patient/doctor/admin' ); CREATE TABLE sys_user_role ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL, role_id BIGINT NOT NULL, UNIQUE KEY uk_user_role (user_id, role_id) );实际开发中为了控制工作量,可以简化为 sys_user 表保留 role 字段用于前端菜单显隐,同时用 Spring Security 的注解做接口级权限控制。给一个最小可用的后端权限校验写法:
@RestController @RequestMapping("/api/doctor") public class DoctorWorkbenchController { @GetMapping("/pending-list") @PreAuthorize("hasRole('DOCTOR')") public Result getPendingList(@RequestParam Long doctorId) { // 只允许医生角色访问待问诊列表 return Result.success(registerService.getPendingList(doctorId)); } }逻辑说明:@PreAuthorize("hasRole('DOCTOR')")会在进入 Controller 方法之前检查当前登录用户的角色,没有 DOCTOR 角色直接返回 403。前端配合 vue-router 的路由守卫,根据登录用户角色动态生成菜单,这样后端是数据安全底线,前端是体验优化。如果做毕设觉得 Spring Security 配置太繁琐,可以拿一个简单的 JWT 拦截器替代,但答辩时老师大概率会问“如何防止患者接口访问医生接口”,用 Spring Security 注解回答会更扎实。
3.3 问诊记录表设计:中医辨证字段怎么存才能让答辩加分
中医问诊和西医最大的区别在“辨证分型”和“治法方药”,病历里不能只有“发热、咳嗽”这种症状描述。设计 tcm_medical_record 表时,建议加几个中医特色字段,这也是和普通“医院挂号系统”拉开差距的地方,答辩时容易拿高分:
CREATE TABLE tcm_medical_record ( id BIGINT PRIMARY KEY AUTO_INCREMENT, register_id BIGINT NOT NULL, patient_id BIGINT NOT NULL, doctor_id BIGINT NOT NULL, symptom VARCHAR(500) COMMENT '主诉:患者自述', tongue TEXT COMMENT '舌象:舌质、舌苔', pulse TEXT COMMENT '脉象:如弦脉、滑脉', syndrome_type VARCHAR(100) COMMENT '辨证分型:如风寒束表证、肝郁气滞证', treatment_principle VARCHAR(200) COMMENT '治法:如疏肝理气、活血化瘀', advice VARCHAR(500) COMMENT '医嘱', create_time DATETIME DEFAULT CURRENT_TIMESTAMP );这几句话讲清楚就行:舌象和脉象是中医四诊里的核心信息源,拿TEXT字段存是因为医生可能输入长文本;syndrome_type是辨证论治的核心输出,对应到处方里的方剂与药品;treatment_principle和syndrome_type做到一一对应,比如“风寒束表证”对应“辛温解表”。表结构里一旦出现证型和治法,老师会认为你对业务做了深入研究,这比多写几个 CRUD 接口有价值得多。
4. 把问诊全流程跑通:从挂号到开方的接口设计与 Vue 前端落地
4.1 挂号接口的设计与实现:防超挂与号源扣减
挂号是问诊系统的入口,也是并发场景最容易出问题的地方。毕设阶段不需要引入 Redis 分布式锁,但至少要做数据库层面的扣减保护。挂号接口的核心逻辑是:先查排班的剩余号源,大于 0 才允许生成挂号单,同时把 remain 字段减一。常见做法是使用乐观锁:
@Service public class RegisterServiceImpl implements RegisterService { @Override @Transactional(rollbackFor = Exception.class) public RegisterResult createRegister(RegisterRequest request) { // 1. 查排班,带上版本号做乐观锁 TcmSchedule schedule = scheduleMapper.selectById(request.getScheduleId()); if (schedule == null || schedule.getRemain() <= 0) { throw new BusinessException("号源已满或排班不存在"); } // 2. 更新号源,注意 SQL 条件是 remain > 0 int updated = scheduleMapper.deductRemain(schedule.getId(), schedule.getVersion()); if (updated == 0) { throw new BusinessException("该时段号源已被抢完,请选择其他时段"); } // 3. 生成挂号单 TcmRegister register = new TcmRegister(); register.setPatientId(request.getPatientId()); register.setDoctorId(schedule.getDoctorId()); register.setScheduleId(schedule.getId()); register.setStatus(0); // 0-待就诊 1-已就诊 2-已取消 registerMapper.insert(register); return new RegisterResult(register.getId(), schedule.getRegFee()); } }对应的 Mapper 方法:
@Update("UPDATE tcm_schedule SET remain = remain - 1, version = version + 1 " + "WHERE id = #{id} AND version = #{version} AND remain > 0") int deductRemain(@Param("id") Long id, @Param("version") Integer version);逻辑说明:扣减号源时不使用remain - 1的普通 UPDATE,而是加version = #{version}条件,保证两个用户同时抢最后一个号时,只有一个能更新成功,另一个 updated 为 0 直接提示“号源已满”。@Transactional保证扣号源和插入挂号单要么都成功,要么都回滚,不会出现“号扣了但挂号单没生成”的数据不一致。参数上唯一需要前端配合的是scheduleId,前端要从排班列表里把选中项的 id 带上。
4.2 Vue 3 前端登录态管理与 API 封装:axios 拦截器里的两个关键逻辑
前端项目源码拿到手后,第一个要改的文件永远是src/utils/request.js,它是所有请求的咽喉。一套可运行系统的 axios 封装必须处理两个问题:请求头发 token,响应里统一剥开 Result 壳子。下面是这套系统常见的最小封装:
import axios from 'axios' import { ElMessage } from 'element-plus' import router from '@/router' const request = axios.create({ baseURL: '/api', timeout: 10000 }) // 请求拦截器:从 localStorage 取 token 放到请求头 request.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers['Authorization'] = `Bearer ${token}` } return config }) // 响应拦截器:统一处理错误码与 401 跳登录 request.interceptors.response.use( response => { const res = response.data if (res.code !== 200) { ElMessage.error(res.msg || '请求失败') return Promise.reject(new Error(res.msg)) } return res.data }, error => { if (error.response?.status === 401) { localStorage.removeItem('token') localStorage.removeItem('userInfo') router.push('/login') ElMessage.warning('登录已过期,请重新登录') } else { ElMessage.error('网络异常,请稍后重试') } return Promise.reject(error) } ) export default request逻辑说明:baseURL: '/api'配合 Vite 代理,开发环境请求/api/doctor/pending-list会被转发到http://localhost:8080/api/doctor/pending-list,浏览器控制台不会出现跨域报错。后端统一返回{ code: 200, data: ..., msg: 'success' }结构,拦截器里直接剥出data返回,页面调用不需要每处都写.data.data。401 处理是这套系统的“后悔药”:token 过期一次,全系统自动踢回登录页,不用每个页面手动判断。
4.3 问诊工作台页面:从挂号队列到病历填写表单的组件化思路
医生端的问诊工作台是这套系统的核心页面。页面布局一般分三块:左侧待诊队列,中间患者信息与病历表单,右侧已开处方预览。用 Vue 3 组合式 API 来写,数据流非常清晰:
<template> <div class="workbench"> <el-aside width="280px"> <el-card shadow="never"> <template #header>待诊队列</template> <div v-for="item in pendingList" :key="item.id" class="patient-item" @click="selectPatient(item)"> <span>{{ item.patientName }}</span> <el-tag size="small">{{ item.registerNo }}</el-tag> </div> </el-card> </el-aside> <el-main> <el-form :model="medicalForm" label-width="100px"> <el-form-item label="主诉"> <el-input v-model="medicalForm.symptom" type="textarea" :rows="3" /> </el-form-item> <el-form-item label="舌象"> <el-input v-model="medicalForm.tongue" placeholder="如:舌红苔薄黄" /> </el-form-item> <el-form-item label="脉象"> <el-input v-model="medicalForm.pulse" placeholder="如:弦滑数" /> </el-form-item> <el-form-item label="辨证分型"> <el-select v-model="medicalForm.syndromeType" allow-create filterable> <el-option v-for="t in syndromeTypes" :key="t" :label="t" :value="t" /> </el-select> </el-form-item> <el-button type="primary" @click="submitRecord">提交病历并开方</el-button> </el-form> </el-main> </div> </template> <script setup> import { ref, onMounted } from 'vue' import request from '@/utils/request' const pendingList = ref([]) const medicalForm = ref({ registerId: null, patientId: null, symptom: '', tongue: '', pulse: '', syndromeType: '' }) const loadPendingList = async () => { const data = await request.get('/doctor/pending-list', { params: { doctorId: JSON.parse(localStorage.getItem('userInfo')).id } }) pendingList.value = data } const selectPatient = (item) => { medicalForm.value.registerId = item.registerId medicalForm.value.patientId = item.patientId } const submitRecord = async () => { await request.post('/medical-record/create', medicalForm.value) ElMessage.success('病历已提交') loadPendingList() } onMounted(loadPendingList) </script>这个组件演示了问诊工作台最基本的交互逻辑:点击待诊队列左侧条目,选中患者,表单里录入中医四诊信息,提交后刷新队列。syndromeType用allow-create的 Select 组件,医生可以选预设证型也可以手动输入新证型,这是考虑到中医证型自由度高、预设清单不可能穷尽而做的妥协设计。页面没有放处方明细编辑是因为处方开立一般跳转到独立页面或弹出抽屉组件,避免一个页面过长影响答辩演示效果。
5. 源码可运行的关键一步:编译打包、Nginx 部署与常见问题避坑
5.1 从开发环境到生产环境:后端打包与前端构建的参数调整
拿到源码后,如果只在开发环境跑通还不够,毕设演示时经常要在教室机器上用打包后的版本演示。后端打包用 Maven,前端打包用 Vite,但有两处参数必须改,否则部署上去全是坑。
后端打包前,打开application.yml,把数据库连接改成演示机器的实际地址,然后把 JWT 密钥固定下来,不要用随机生成的值。打包命令:
mvn clean package -DskipTests打包产物在backend/target/hospital-tcm-0.0.1-SNAPSHOT.jar。启动时用java -jar hospital-tcm.jar --spring.profiles.active=prod可以让生产配置生效(前提是 resources 下有application-prod.yml)。前端打包前,要改vite.config.js里的代理路径,改成后端实际地址,再执行构建:
npm run build产物在frontend/dist/下,生成的是纯静态文件 index.html + assets 目录。这时候有一个毕设常见的问题:前端项目源码怎么发给别人——直接发 dist 目录是不够的,接收方如果要二次开发,得拿到完整 src 源码;如果只是运行演示,把 dist 扔给 Nginx 或后端静态资源目录即可。我一般建议交付时把整个 frontend 目录(含 node_modules 可选)和打包说明一起发,这样别人能自己npm run dev也能npm run build。
5.2 用 Nginx 托管前端并转发 API 请求:location 前缀匹配的配置要点
前后端分离部署最经典的方案是 Nginx 托管前端静态文件,同时转发/api到后端服务。这段配置几乎是毕设答辩现场必备,直接抄着改即可:
server { listen 80; server_name localhost; root /usr/share/nginx/html/dist; index index.html; # 前端路由 history 模式支持 location / { try_files $uri $uri/ /index.html; } # API 请求转发到后端 location /api/ { proxy_pass http://127.0.0.1:8080/api/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }这里的核心是try_files $uri $uri/ /index.html。Vue 3 默认使用 createWebHistory 的路由模式,刷新/patient/records页面时若没有这条规则,Nginx 会返回 404;加上后所有未知路径都回退到 index.html,由前端路由自己解析。proxy_pass http://127.0.0.1:8080/api/;末尾的斜杠代表把/api前缀原样传给后端,例如浏览器请求/api/doctor/pending-list,后端收到的是/api/doctor/pending-list。如果去掉斜杠写成不带路径的形式,转发时会把/api去掉,后端接口就得调整前缀,这是最容易翻车的地方。
5.3 避坑专章:中医院问诊系统跑不起来最常见的 5 个问题
问题 1:后端启动直接报 Failed to configure a DataSource 错误。现象是控制台提示无法连接数据库或找不到数据源。原因基本是application.yml里的 MySQL 地址、账号、密码不对,或者 MySQL 服务没启动,更隐蔽的是 MySQL 8.0 以上需要额外引入spring-boot-starter的驱动匹配版本。解决:先确认 MySQL 服务在mysql -uroot -p能登录,再看pom.xml里mysql-connector-j版本是否为 8.0 及以上,最后核对 yml 缩进——Spring Boot 配置文件对缩进极其敏感,tab 和空格混用会直接解析失败。
问题 2:前端 npm run dev 启动正常,但浏览器白屏,控制台报 Cannot read properties of undefined。现象是新拉下来的代码执行npm install后启动,Element Plus 组件全部不渲染。原因多数是依赖版本不一致,比如 package.json 里 Element Plus 版本是 2.4,但 npm 自动安装了 2.5,部分组件 API 有破坏性改动。解决:删除node_modules和package-lock.json,然后npm install --legacy-peer-deps重新安装;如果还不行就手动把 package.json 里 Element Plus 版本改成和源码标注的一致。
问题 3:登录接口返回 401,但数据库里账号密码明明是对的。现象是拿初始化 SQL 里给的 admin 账号登录,前端报“用户名或密码错误”。原因通常是初始化 SQL 里存的密码是 BCrypt 加密串,和 application.yml 里 JWT 密钥或加密盐不匹配,或者用户表的密码字段在 MyBatis-Plus 查询时被逻辑删除过滤掉了。解决:先看 SQL 文件里的 INSERT 语句密码字段是不是$2a$开头的 BCrypt 格式,不是的话重新用后端提供的工具类生成密文;再排查 sys_user 表是否有deleted字段且默认值为 1,导致查询带上了deleted=1条件把所有账号都过滤了。
问题 4:开发环境下前端访问 /api 接口报跨域错误。现象是浏览器控制台显示 Access-Control-Allow-Origin 缺失,明明在 SpringBoot 里写了 CorsFilter。原因很可能是 Vite 代理没有生效,或者后端配置类被 SpringBoot 的自动配置覆盖。解决:先在vite.config.js里确认server.proxy写了对/api的代理且 target 指向http://localhost:8080;如果用了后端 CORS 配置,常见写法是@Configuration类里注册 CorsFilter Bean,注意allowedOriginPatterns不要写成allowedOrigins("*"),后者在携带凭证时会被浏览器拒绝。
问题 5:按医生查看待诊队列查不到数据,但数据库里挂号单明明存在。现象是前端选中科室或医生后列表为空。原因通常是查询参数没对齐:挂号单表里存的 doctor_id 是医生的用户 ID 还是医生扩展表(tcm_doctor)的 ID,前后端没约定一致。解决:在数据库执行一条 SQL 验证关联关系,例如SELECT * FROM tcm_register r JOIN tcm_doctor d ON r.doctor_id = d.user_id看是否匹配;匹配不上就检查后端代码里 pending-list 接口条件用的字段名,要么统一用 user_id,要么在排班表新增时同时冗余一个 doctor_id。
6. 往上再走一步:给问诊系统加模拟数据、验证流程与答辩演示技巧
6.1 注入一套完整的演示数据:写一个 CommandLineRunner 自动填充
答辩前最怕的是演示时发现没有号源可挂或没有病历可看,临时手工往数据库插数据既不专业又容易出错。更稳妥的做法是在后端启动时用一个数据初始化组件自动灌入演示数据,参考 SpringBoot 的ApplicationRunner接口:
@Component public class DataInitializer implements ApplicationRunner { @Override @Transactional public void run(ApplicationArguments args) { // 1. 检查是否存在管理员账号,不存在则创建 if (userMapper.selectCount(new LambdaQueryWrapper<SysUser>() .eq(SysUser::getUsername, "admin")) == 0) { SysUser admin = new SysUser(); admin.setUsername("admin"); admin.setPassword(new BCryptPasswordEncoder().encode("123456")); admin.setRole("ADMIN"); admin.setRealName("系统管理员"); userMapper.insert(admin); } // 2. 检查是否有演示科室,没有则插入内科、针灸科、骨伤科 // 3. 检查是否有演示医生账号和对应排班数据 // 4. 插入一个演示患者账号(patient01 / 123456) } }这套初始化逻辑有讲究:不能无条件插数据,否则每次启动都会重复生成,要用selectCount判断存在性;密码一律用 BCrypt 加密存储,不能存明文;演示排班的日期要动态生成,比如LocalDate.now().plusDays(1)加上上午/下午两个时段,这样无论哪天演示都有可挂的号,不会因为数据过期而“无号可挂”。代码里注释要把每一步的作用点出来,答辩时老师问“数据从哪来”,直接说“启动时自动初始化”,比自己手动拼接 INSERT 语句要显眼很多。
6.2 验收自查清单:从注册到出处的全流程验证
源码是否能运行,不是后端启动就算,也不只是前端页面能打开就算。我给自己定过一套验收流程,建议拿到源码后先按步骤走一遍:
| 步骤 | 操作 | 预期结果 |
|---|---|---|
| 1 | 导入 SQL,启动后端与前端 | 浏览器访问 5173 端口出现登录页 |
| 2 | 用 admin / 123456 登录 | 跳转到管理员后台,能看到科室管理菜单 |
| 3 | 用演示患者账号登录 | 能看到挂号页面,按科室筛选医生,点击挂号成功 |
| 4 | 用医生账号登录 | 待诊队列出现刚才挂号的卡片,点击后能填写病历与处方 |
| 5 | 切回患者账号 | 在“问诊记录”里能看到医生写的病历与处方药品 |
| 6 | 退出登录,再进入后台 | 直接访问路由会被重定向到登录页,token 鉴权生效 |
这套流程覆盖了系统最核心的三角色闭环:管理员维护基础数据,患者挂号,医生接诊开方。验收时如果第 3 步报“号源已满”,检查初始化排班的 remain 字段;如果第 5 步的处方药品明细是空的,检查前端提交处方时有没有把 prescriptionItem 数组一起传,或者后端接收参数时没有加@RequestBody。逐项核对完,才能放心地把它作为可运行的交付物。
6.3 答辩演示的三个现场技巧:弹窗、路由守卫和错误提示的加分用法
最后说点实操层面的“血泪经验”。第一,演示时不要全屏展示代码编辑器,把浏览器窗口固定在 1366 宽度,避免页面上 Element Plus 组件挤压变形;第二,给前端路由加一个全局前置守卫已经是标配,但演示时可以手动清掉 localStorage 里的 token 再刷新页面,让评委看到自动踢回登录页的效果,这一个操作能省下很多关于“安全怎么做的”追问;第三,后端接口被非法访问时,评委可能会顺手输错 URL 或者在控制台改 role 字段,用 Spring Security 的 403 返回加前端统一 ElMessage 提示“无权访问”,比一张白页体面得多。
如果时间有富余,还可以在管理端加一个"问诊统计"页面,按日统计挂号量与处方销售额,用一个简单的SELECT DATE(create_time), COUNT(*) FROM tcm_register GROUP BY DATE(create_time)再配上 ECharts 柱状图。这不算大改造,但对答辩的“工作量感”提升非常明显——它展示的不只是 CRUD,还有数据分析意识。我带学生做这套系统时经常说:源码能跑只是及格线,能把一个流程讲成完整故事才是加分项。从挂号到病历到处方这条链路本身就是中医院问诊系统区别于普通图书管理系统的核心,抓住它,演示就成功了一半。希望帮到你。
本文还有配套的精品资源,点击获取