ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

SpringBoot+Vue医疗服务系统毕业设计:从架构到实现全解析

SpringBoot+Vue医疗服务系统毕业设计:从架构到实现全解析 简介这是一套面向计算机、通信、人工智能等相关专业学生与教师的高分毕业设计级医疗服务系统实战源码基于SpringBoot后端与Vue前端构建完整覆盖用户挂号、医生问诊、病历管理、药品查询等核心医疗业务流程适用于毕业设计、课程大作业及全栈开发入门与进阶学习。压缩包共141个文件含32个Java后端逻辑类、28个Vue组件页面、8个JS交互脚本、7个XML配置与2个SQL数据库脚本辅以CSS样式、图片资源及项目配置文件yml/properties整体12.72MB结构清晰、模块解耦合理便于理解前后端分离架构与医疗业务建模逻辑。目前已有72人学习下载所有代码均通过本地调试与功能验证附带可直接导入的MySQL数据库支持一键运行项目目录规范、注释完整特别适合零基础开发者快速上手并为进阶者提供二次开发与功能扩展的良好基底。1. 项目概述一个能跑起来的“高分”毕业设计长什么样每年毕业季计算机相关专业的学生最头疼的莫过于毕业设计。选题太简单怕没深度拿不到高分选题太复杂又怕自己搞不定最后连答辩都过不了。如果你正在寻找一个“既有技术含量又能在有限时间内完成”的选题那么一个基于SpringBoot和Vue的医疗服务系统绝对是一个黄金选择。这不仅仅是因为它技术栈主流、资料丰富更重要的是它完美契合了“理论结合实践”的毕业设计要求能让你从需求分析、技术选型、前后端开发、数据库设计到最终部署走完一个完整的软件开发生命周期。这个项目本质上是一个简化版的线上医疗服务平台。想象一下患者可以线上预约挂号、查看医生排班、查询个人病历医生可以管理自己的日程、查看预约列表、为患者录入诊断信息系统管理员则负责管理用户、科室、医生等基础信息。麻雀虽小五脏俱全。它涵盖了Web开发中最核心的CRUD增删改查操作、前后端分离架构、用户权限控制以及数据库设计等关键知识点。使用SpringBootVue这套“黄金搭档”意味着后端API开发高效稳定前端界面交互现代流畅两者通过RESTful API清晰解耦这正是当前企业级应用开发的标准范式。所以这不仅仅是一个为了毕业而做的项目更是一块进入软件开发行业的敲门砖一份能写进简历的实战经验。2. 整体架构与核心技术栈选型解析2.1 为什么是SpringBoot Vue当你决定做这个项目时第一个要回答的问题就是为什么用这些技术这直接关系到项目的技术深度和答辩时的底气。后端SpringBoot是“懒人”的福音也是高手的利器。对于毕业生来说最怕的就是在繁琐的配置上浪费大量时间。SpringBoot的核心理念就是“约定大于配置”它通过自动配置和起步依赖让你几乎不需要写任何XML配置文件就能快速搭建起一个可运行的、生产级别的Spring应用。比如你想集成MyBatis操作数据库只需要在pom.xml里加入mybatis-spring-boot-starter依赖再配一下数据源框架就帮你把一切都组装好了。这让你能把精力集中在业务逻辑的开发上而不是框架的整合上。同时SpringBoot内嵌了Tomcat服务器你的应用打包成一个可执行的JAR文件后直接java -jar就能运行部署极其简单。在答辩时你可以清晰地阐述SpringBoot如何通过自动配置、起步依赖、Actuator监控等特性提升了开发效率和系统的可维护性。前端Vue.js让构建用户界面变得直观。相比于React和AngularVue的学习曲线更为平缓其核心库只关注视图层易于上手并与其它库或已有项目整合。对于医疗服务系统这种以表单、列表、数据展示为主的管理系统Vue的声明式渲染和组件化开发优势明显。你可以将一个挂号表单、一个医生列表卡片都封装成独立的组件代码复用率高结构清晰。特别是配合Vue CLI脚手架可以快速初始化一个包含了Webpack、Babel、ESLint等现代化前端工具链的项目省去了大量环境搭建的麻烦。Vue的响应式数据绑定让你无需直接操作DOM数据变化视图自动更新这能极大提升开发体验和代码质量。前后端分离RESTful API是沟通的桥梁。在这个架构中前端Vue应用和后端SpringBoot应用完全独立。前端运行在用户的浏览器中通过Axios等HTTP客户端向后端发起请求GET/POST/PUT/DELETE。后端不关心前端页面如何渲染它只负责接收请求、处理业务逻辑、操作数据库并返回JSON格式的数据。这种分离的好处是前后端可以并行开发、独立部署任何一方的技术栈变更都不会直接影响另一方。在项目中你需要精心设计这些API接口包括URL路径、请求方法、请求参数和响应格式这是体现你系统设计能力的关键点。2.2 数据库设计系统的基石数据库设计是系统的灵魂一个糟糕的设计会让后续开发举步维艰。对于医疗服务系统核心实体通常包括用户患者、医生、管理员、科室、医生排班、预约挂号、病历等。这里以最核心的几张表为例说明设计思路1. 用户表 (sys_user)这是系统的门户。通常不会为患者、医生、管理员分别建表而是采用一张用户表通过一个user_type字段如0-管理员1-医生2-患者来区分角色。这样设计的好处是登录认证模块可以统一处理。表中至少应包含用户ID、用户名、密码加密存储、真实姓名、手机号、邮箱、头像、用户类型、状态启用/禁用、创建时间等字段。2. 科室表 (department)相对简单主要字段有科室ID、科室名称、科室描述、科室位置如门诊大楼3楼、状态等。3. 医生表 (doctor)这张表与用户表关联。它包含医生ID、关联的用户ID、所属科室ID、职称主任医师、副主任医师等、专长描述、挂号费等。这里为什么需要关联用户ID因为医生的登录账号、密码、联系方式等信息已经在sys_user表中doctor表只存储医生特有的业务属性遵循了数据库设计中的“单一职责”原则也避免了数据冗余。4. 排班表 (schedule)这是实现预约功能的核心。它记录了医生在特定日期的可预约时段。字段包括排班ID、医生ID、排班日期如2023-10-27、时段如“上午”、“下午”或更细粒度的“08:00-09:00”、可预约总数、已预约数、状态如“可预约”、“已停诊”。这里“已预约数”是一个需要高频更新的字段在高并发场景下需要考虑使用数据库的乐观锁或分布式锁来防止超预约但在毕业设计场景下可以暂不考虑极端并发但要在设计文档中提及这个潜在问题和优化方向。5. 预约表 (appointment)记录患者的每一次预约。字段包括预约ID、患者用户ID、排班ID、预约状态待就诊、已就诊、已取消、症状描述、创建时间等。它通过排班ID关联到具体的医生和时段。6. 病历表 (medical_record)在患者就诊后由医生创建。包含病历ID、预约ID关联到某次预约、诊断结果、处方建议、医嘱、创建医生ID、创建时间等。注意在实际设计中密码字段务必使用BCrypt等强哈希算法加密存储绝对禁止明文保存。所有时间字段建议统一使用datetime类型并在后端代码中明确时区处理策略避免出现时间错乱的问题。2.3 技术栈全景图与工具选型一个完整的项目离不开周边工具链的支持。以下是一个推荐的技术栈清单你可以根据熟悉程度微调后端框架SpringBoot 2.7.x (一个长期支持版本稳定且社区资源丰富)持久层框架MyBatis-Plus。它是在MyBatis基础上的增强工具提供了强大的CRUD通用接口和条件构造器能让你少写很多重复的SQL代码大幅提升开发效率。数据库MySQL 8.0。经典的关系型数据库学习资料最多。对于毕业设计其性能完全足够。前端框架Vue 3 Composition API。Vue 3是当前主流其Composition API的逻辑组织方式更适合构建复杂组件。如果学习时间紧张使用Options API的Vue 2也是完全可行的。前端构建工具Vue CLI 或 Vite。Vite启动更快热更新更灵敏体验更佳。UI组件库Element Plus (对应Vue 3) 或 Element UI (对应Vue 2)。它提供了丰富的、美观的桌面端组件如表格、表单、对话框、导航菜单等能让你快速搭建出专业的管理后台界面无需从零编写CSS。HTTP客户端Axios。处理前后端API通信的事实标准。状态管理Pinia (Vue 3推荐) 或 Vuex。对于中型项目集中管理一些全局状态如用户登录信息非常方便。开发工具IntelliJ IDEA (后端)、Visual Studio Code (前端)、Navicat或DBeaver (数据库管理)、Postman或Apifox (API调试)。版本控制Git代码托管平台推荐Gitee或GitHub。3. 核心功能模块拆解与实现要点3.1 用户认证与权限管理系统的守门人这是系统的第一个难关也是体现项目完整性的关键。一个基本的RBAC基于角色的访问控制模型是必须的。1. 登录与令牌Token发放用户提交用户名和密码后后端首先根据用户名查询用户信息然后使用BCrypt的matches方法比对密码。验证通过后生成一个代表用户身份的令牌。这里强烈推荐使用JWTJSON Web Token而不是传统的Session。因为JWT是一种无状态的认证方式Token本身包含了用户ID、角色等信息后端无需在服务器存储会话更符合RESTful风格和分布式架构趋势。 生成JWT后将其返回给前端。前端后续的每一次API请求都需要在HTTP请求头通常是Authorization: Bearer token中携带这个Token。2. 接口访问鉴权后端需要拦截所有请求验证JWT的有效性是否过期、签名是否正确。在SpringBoot中你可以使用拦截器Interceptor或更优雅的过滤器Filter来实现。推荐使用Spring Security框架虽然学习曲线稍陡但它功能强大、社区成熟。你可以配置哪些URL路径需要认证哪些需要特定角色如ROLE_DOCTOR才能访问。 例如/api/admin/**下的所有接口需要管理员角色/api/doctor/**下的接口需要医生角色而/api/patient/appointment可能只需要患者角色。在过滤器中你从JWT里解析出用户角色与当前请求所需的角色进行比对。3. 前端路由守卫光后端保护不够前端也需要控制。在Vue Router中你可以使用“导航守卫”。在用户访问任何一个页面路由前检查本地如Vuex/Pinia或localStorage是否存在有效的Token以及用户角色信息。如果用户未登录却试图访问“医生管理”页面则自动跳转到登录页。如果登录的用户是患者却通过手动修改URL试图进入管理员后台前端路由守卫应能拦截并提示“无权访问”。实操心得在开发初期可以先绕过鉴权集中精力开发业务功能。等核心功能完成后再统一集成Spring Security和JWT。集成时务必编写一个简单的测试接口先验证Token的生成和解析是否正常再逐步增加权限规则。记得将JWT的密钥Secret放在后端配置文件中并设置为强密码切勿硬编码在代码里。3.2 医生排班与预约模块业务逻辑的核心这是系统最具业务特色的部分逻辑相对复杂。1. 排班管理后端实现医生或管理员通过一个表单创建未来一段时间如下一周的排班。后端接口/api/schedulePOST接收一个排班列表。这里的关键是数据校验和排班冲突检测。校验使用SpringBoot的Valid注解配合校验注解如NotNull,Future等确保传入数据的合法性。冲突检测在插入数据库前必须查询该医生在指定日期和时段是否已有排班记录。SQL语句类似于SELECT COUNT(*) FROM schedule WHERE doctor_id ? AND schedule_date ? AND time_slot ?。如果计数大于0则返回“该时段已有排班请勿重复添加”的错误信息。批量操作考虑到一次可能添加多个日期的排班建议在Service层使用事务Transactional管理确保要么全部成功要么全部失败保持数据一致性。2. 预约流程前后端协作前端页面患者登录后首先看到一个科室/医生选择页面。选择医生后展示该医生未来可预约的日期通过日历组件高亮显示。点击某个日期通过AJAX请求获取该医生当天的排班时段列表并实时显示每个时段的“可预约数”。后端接口GET /api/schedule/available?doctorIdxxxdateyyyy-MM-dd返回该医生指定日期的可用排班列表。提交预约患者选择时段填写症状描述点击提交。前端调用POST /api/appointment。后端核心逻辑高并发考点这是整个预约流程最核心的一步必须保证“已预约数”的准确增加且不能超过“可预约总数”。伪代码如下Transactional public AppointmentResult createAppointment(AppointmentRequest request) { // 1. 查询排班信息并锁定该行记录悲观锁防止并发修改 Schedule schedule scheduleMapper.selectForUpdate(request.getScheduleId()); // 2. 检查排班状态是否可用检查已预约数是否已满 if (schedule.getStatus() ! AVAILABLE || schedule.getBooked() schedule.getTotal()) { throw new BusinessException(该时段已约满或不可用); } // 3. 创建预约记录 Appointment appointment new Appointment(); // ... 设置患者ID、排班ID等信息 appointmentMapper.insert(appointment); // 4. 更新排班的已预约数原子操作 scheduleMapper.increaseBookedNumber(request.getScheduleId()); // 5. 返回成功结果 return new AppointmentResult(appointment.getId(), 预约成功); }这里使用了selectForUpdate进行行级锁确保在事务完成前其他并发请求无法修改同一条排班记录这是解决超卖问题的经典方案。在答辩时你需要能清晰地解释为什么这里需要加锁以及如果不加锁会导致什么问题。3.3 病历管理模块数据关联与展示病历是预约的后续。医生在接诊后需要为对应的预约记录创建病历。1. 创建病历后端提供一个POST /api/medical-record接口。请求体中需要包含appointmentId关联的预约ID、诊断信息、处方等。在插入病历前需要验证该预约是否存在且状态为“待就诊”当前登录的医生是否有权限为该患者创建病历例如该预约是否属于该医生。创建成功后通常还需要更新对应预约的状态为“已就诊”。2. 病历查询与展示患者可以查看自己的历史病历。前端调用GET /api/patient/medical-records后端根据当前登录的患者ID查询medical_record表并可能需要联表查询如关联appointment和schedule、doctor表来获取更丰富的信息比如就诊时间、医生姓名、科室等。这里就体现了数据库关联查询的重要性。在MyBatis-Plus中你可以使用自定义SQL或在其Service层进行多次查询再组装。3. 前端页面设计病历列表页通常用一个el-table展示。详情页则可以用多个el-card或el-descriptions组件来清晰地展示诊断详情、处方、医嘱等信息。对于处方可以考虑设计成结构化的数据比如一个药品列表包含药品名称、规格、用法用量等这样比单纯的一大段文字更专业。4. 数据库操作与MyBatis-Plus实战技巧4.1 数据源配置与实体类映射在application.yml中配置数据库连接是第一步spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/medical_service?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: your_password注意serverTimezone参数非常重要它确保Java应用和MySQL服务器之间的时间转换正确避免出现时间差问题。接下来使用MyBatis-Plus你需要为每张表创建一个对应的实体类Entity并使用注解与表字段映射Data // Lombok注解自动生成getter/setter等方法 TableName(sys_user) // 指定表名 public class User { TableId(type IdType.AUTO) // 主键自增 private Long id; private String username; private String password; // 数据库中存储的是加密后的密文 private String realName; private Integer userType; // 0-管理员1-医生2-患者 private String phone; TableField(fill FieldFill.INSERT) // 插入时自动填充 private LocalDateTime createTime; TableField(fill FieldFill.INSERT_UPDATE) // 插入和更新时自动填充 private LocalDateTime updateTime; }这里用到了Lombok简化代码以及MyBatis-Plus的字段自动填充功能需要在配置类中实现一个MetaObjectHandler来定义填充规则如插入时填当前时间。4.2 通用CRUD与条件构造器MyBatis-Plus的强大之处在于其通用的Service和Mapper。你几乎不需要写基础的增删改查SQL。1. 定义Mapper接口Mapper public interface UserMapper extends BaseMapperUser { // 继承BaseMapper后就拥有了selectById, insert, updateById, deleteById等方法 // 复杂查询可以在这里定义方法并配合Select注解写SQL }2. 在Service中使用Service public class UserService { Autowired private UserMapper userMapper; public User getUserById(Long id) { return userMapper.selectById(id); // 一行代码完成按ID查询 } public PageUser getUsersByPage(Integer pageNum, Integer pageSize, String keyword) { PageUser page new Page(pageNum, pageSize); QueryWrapperUser wrapper new QueryWrapper(); if (StringUtils.hasText(keyword)) { wrapper.like(username, keyword).or().like(real_name, keyword); } wrapper.orderByDesc(create_time); return userMapper.selectPage(page, wrapper); // 分页条件查询 } }上面的getUsersByPage方法展示了如何使用QueryWrapper构建动态查询条件以及如何进行分页查询。这在前端表格数据展示中极为常用。4.3 复杂查询与联表操作对于需要关联多张表的查询MyBatis-Plus提供了几种方案方案一在Mapper.xml中写自定义SQL。这是最灵活的方式。你可以编写完整的JOIN语句将结果映射到一个自定义的DTOData Transfer Object对象上。!-- 在UserMapper.xml中 -- select idselectDoctorListWithDept resultTypecom.example.dto.DoctorDTO SELECT d.*, u.real_name as doctor_name, dept.name as dept_name FROM doctor d LEFT JOIN sys_user u ON d.user_id u.id LEFT JOIN department dept ON d.dept_id dept.id where if testdeptId ! null AND d.dept_id #{deptId} /if /where /select方案二使用MyBatis-Plus的Select注解。可以将SQL直接写在Mapper接口的方法上适合较短的SQL。方案三在Service层进行多次查询组装。先查出主体列表再根据ID列表去查询关联数据最后在Java代码里进行组合。这在某些场景下如关联数据需要分页查询可能更清晰但会增多数据库查询次数需要权衡。避坑指南联表查询时要特别注意字段名冲突。如果doctor表和user表都有name字段在SELECT时必须使用别名u.name as user_name来区分。另外复杂的多表关联和嵌套查询可能会影响性能在数据量大时要考虑优化比如添加合适的索引、或拆分成多个简单查询。5. 前端Vue项目搭建与核心组件开发5.1 项目初始化与基础配置使用Vue CLI快速创建项目vue create medical-system-frontend # 选择Manually select features 勾选Babel, Router, Vuex, CSS Pre-processors (如Sass) # 选择Vue 3版本 # 路由模式选择history模式需要后端配合或hash模式默认更简单安装Element Plus和Axioscd medical-system-frontend npm install element-plus axios # 或者在main.js中按需引入Element Plus组件以减少打包体积在src/utils/request.js中封装Axios实例这是项目中的最佳实践import axios from axios; import { ElMessage } from element-plus; import router from /router; const service axios.create({ baseURL: process.env.VUE_APP_BASE_API, // 在.env.development等环境配置文件中定义 timeout: 10000 }); // 请求拦截器在发送请求前统一添加Token service.interceptors.request.use( config { const token localStorage.getItem(token); if (token) { config.headers[Authorization] Bearer ${token}; } return config; }, error { return Promise.reject(error); } ); // 响应拦截器统一处理错误 service.interceptors.response.use( response { const res response.data; // 假设后端统一返回格式为 { code: 200, data: {}, msg: success } if (res.code ! 200) { ElMessage.error(res.msg || Error); // 如果是401未授权跳转到登录页 if (res.code 401) { localStorage.removeItem(token); router.push(/login); } return Promise.reject(new Error(res.msg || Error)); } else { return res.data; // 直接返回后端响应的data部分方便使用 } }, error { ElMessage.error(error.message || 网络请求失败); return Promise.reject(error); } ); export default service;这样封装后在任何一个Vue组件中你只需要import request from /utils/request然后使用request.get(/api/schedule)即可无需再关心Token的添加和基础错误处理。5.2 典型页面组件开发以医生排班管理为例我们以医生或管理员管理排班的页面为例展示如何结合Element Plus和Vue 3 Composition API进行开发。1. 组件模板 (ScheduleManagement.vue模板部分):template div classschedule-management el-card template #header div classcard-header span排班管理/span el-button typeprimary clickdialogVisible true新增排班/el-button /div /template !-- 搜索和筛选区域 -- el-form :inlinetrue :modelqueryParams el-form-item label医生 el-select v-modelqueryParams.doctorId placeholder请选择医生 clearable el-option v-fordoctor in doctorList :keydoctor.id :labeldoctor.realName :valuedoctor.id / /el-select /el-form-item el-form-item label日期范围 el-date-picker v-modelqueryParams.dateRange typedaterange range-separator至 start-placeholder开始日期 end-placeholder结束日期 value-formatYYYY-MM-DD / /el-form-item el-form-item el-button typeprimary clickhandleQuery搜索/el-button el-button clickresetQuery重置/el-button /el-form-item /el-form !-- 数据表格 -- el-table :datascheduleList v-loadingloading el-table-column propscheduleDate label日期 width120 template #default{ row } {{ formatDate(row.scheduleDate) }} /template /el-table-column el-table-column proptimeSlot label时段 width100 / el-table-column propdoctorName label医生 / el-table-column propdeptName label科室 / el-table-column proptotal label总号源 width80 / el-table-column propbooked label已预约 width80 / el-table-column propstatus label状态 width100 template #default{ row } el-tag :typerow.status AVAILABLE ? success : info {{ row.status AVAILABLE ? 可预约 : 已停诊 }} /el-tag /template /el-table-column el-table-column label操作 width180 fixedright template #default{ row } el-button sizesmall clickhandleEdit(row)编辑/el-button el-button sizesmall typedanger clickhandleDelete(row)删除/el-button /template /el-table-column /el-table !-- 分页组件 -- div classpagination-container el-pagination v-model:current-pagequeryParams.pageNum v-model:page-sizequeryParams.pageSize :totaltotal :page-sizes[10, 20, 50] layouttotal, sizes, prev, pager, next, jumper size-changehandleSizeChange current-changehandleCurrentChange / /div /el-card !-- 新增/编辑排班对话框 -- el-dialog v-modeldialogVisible :titledialogTitle width500px el-form refscheduleFormRef :modelscheduleForm :rulesrules label-width80px el-form-item label医生 propdoctorId el-select v-modelscheduleForm.doctorId placeholder请选择 !-- 选项列表 -- /el-select /el-form-item !-- 其他表单字段 -- /el-form template #footer span classdialog-footer el-button clickdialogVisible false取消/el-button el-button typeprimary clicksubmitForm确定/el-button /span /template /el-dialog /div /template2. 组件逻辑 (ScheduleManagement.vue脚本部分):script setup import { ref, reactive, onMounted } from vue; import { ElMessage, ElMessageBox } from element-plus; import request from /utils/request; import { formatDate } from /utils/date-utils; // 一个自定义的日期格式化工具 // 响应式数据 const loading ref(false); const scheduleList ref([]); const doctorList ref([]); const total ref(0); const dialogVisible ref(false); const dialogTitle ref(); const scheduleFormRef ref(); // 查询参数 const queryParams reactive({ doctorId: null, dateRange: [], pageNum: 1, pageSize: 10 }); // 表单数据与校验规则 const scheduleForm reactive({ id: null, doctorId: null, scheduleDate: , timeSlot: , total: 30, status: AVAILABLE }); const rules { doctorId: [{ required: true, message: 请选择医生, trigger: change }], scheduleDate: [{ required: true, message: 请选择日期, trigger: change }], timeSlot: [{ required: true, message: 请选择时段, trigger: change }], total: [{ required: true, message: 请输入号源总数, trigger: blur }] }; // 方法 const fetchDoctorList async () { const res await request.get(/api/doctor/list); doctorList.value res; }; const fetchScheduleList async () { loading.value true; try { // 构造请求参数 const params { ...queryParams }; if (params.dateRange params.dateRange.length 2) { params.startDate params.dateRange[0]; params.endDate params.dateRange[1]; delete params.dateRange; } const res await request.get(/api/schedule/page, { params }); scheduleList.value res.list; total.value res.total; } catch (error) { console.error(获取排班列表失败:, error); } finally { loading.value false; } }; const handleQuery () { queryParams.pageNum 1; // 搜索时回到第一页 fetchScheduleList(); }; const resetQuery () { // 重置查询参数 Object.assign(queryParams, { doctorId: null, dateRange: [], pageNum: 1, pageSize: 10 }); fetchScheduleList(); }; const handleAdd () { dialogTitle.value 新增排班; // 重置表单 Object.keys(scheduleForm).forEach(key { if (key ! total key ! status) scheduleForm[key] null; }); scheduleForm.total 30; scheduleForm.status AVAILABLE; dialogVisible.value true; }; const submitForm async () { // 表单校验 await scheduleFormRef.value.validate(); const url scheduleForm.id ? /api/schedule/${scheduleForm.id} : /api/schedule; const method scheduleForm.id ? put : post; try { await request[method](url, scheduleForm); ElMessage.success(scheduleForm.id ? 更新成功 : 新增成功); dialogVisible.value false; fetchScheduleList(); // 刷新列表 } catch (error) { ElMessage.error(操作失败); } }; const handleDelete async (row) { try { await ElMessageBox.confirm(确定删除该排班吗, 提示, { type: warning }); await request.delete(/api/schedule/${row.id}); ElMessage.success(删除成功); fetchScheduleList(); } catch (error) { if (error ! cancel) { ElMessage.error(删除失败); } } }; // 生命周期钩子 onMounted(() { fetchDoctorList(); fetchScheduleList(); }); /script这个组件几乎涵盖了管理后台页面的所有典型元素搜索表单、数据表格、分页、新增/编辑对话框、删除确认、加载状态等。通过Composition API的setup语法逻辑组织更加清晰。v-loading指令可以方便地控制表格加载状态ElMessage和ElMessageBox提供了友好的用户反馈。5.3 状态管理与路由守卫对于用户登录状态、权限信息等全局数据使用PiniaVue 3推荐进行管理比频繁使用localStorage或事件总线更优雅。1. 创建用户Store (stores/user.js):import { defineStore } from pinia; import { ref, computed } from vue; import request from /utils/request; export const useUserStore defineStore(user, () { const token ref(localStorage.getItem(token) || ); const userInfo ref(JSON.parse(localStorage.getItem(userInfo) || {})); const isLoggedIn computed(() !!token.value); const login async (loginForm) { const res await request.post(/api/auth/login, loginForm); token.value res.token; userInfo.value res.userInfo; // 存储到本地 localStorage.setItem(token, res.token); localStorage.setItem(userInfo, JSON.stringify(res.userInfo)); return res; }; const logout () { token.value ; userInfo.value {}; localStorage.removeItem(token); localStorage.removeItem(userInfo); }; return { token, userInfo, isLoggedIn, login, logout }; });2. 路由守卫 (router/index.js或单独的权限文件):import { createRouter, createWebHistory } from vue-router; import { useUserStore } from /stores/user; const routes [ { path: /login, component: () import(/views/Login.vue) }, { path: /, component: () import(/layouts/MainLayout.vue), meta: { requiresAuth: true }, // 需要认证的元信息 children: [ { path: dashboard, component: () import(/views/Dashboard.vue) }, { path: schedule, component: () import(/views/schedule/Management.vue), meta: { roles: [ROLE_ADMIN, ROLE_DOCTOR] } }, { path: appointment, component: () import(/views/appointment/List.vue), meta: { roles: [ROLE_PATIENT] } }, // ... 其他路由 ] } ]; const router createRouter({ history: createWebHistory(), routes, }); // 全局前置守卫 router.beforeEach((to, from, next) { const userStore useUserStore(); // 判断该路由是否需要认证 if (to.matched.some(record record.meta.requiresAuth)) { if (!userStore.isLoggedIn) { // 未登录重定向到登录页 next({ path: /login, query: { redirect: to.fullPath } }); } else { // 已登录检查角色权限 if (to.meta.roles) { const userRoles userStore.userInfo.roles || []; // 假设用户信息中包含角色数组 const hasRole to.meta.roles.some(role userRoles.includes(role)); if (!hasRole) { ElMessage.error(无权访问); next(from.fullPath); // 无权限留在当前页或跳转到404 } else { next(); } } else { next(); } } } else { next(); // 不需要认证的路由直接放行 } }); export default router;这样一个完整的前后端分离的医疗服务系统核心骨架就搭建起来了。从后端的SpringBoot API设计、数据库操作到前端的Vue组件开发、状态管理和路由控制你不仅实现了一个可运行的系统更深入理解了现代Web应用开发的完整流程和最佳实践。在答辩时你可以清晰地阐述每个技术选型的原因、每个模块的设计思路以及遇到的挑战和解决方案这足以支撑起一个高分的毕业设计。本文还有配套的精品资源点击获取
返回列表