基于SpringBoot的养老院管理信息系统设计与实现 做毕设选管理系统类题目最怕的就是“看起来简单写起来没料”。养老院管理信息系统这个题目是我觉得在SpringBoot方向里性价比很高的一个选题业务上覆盖了老人档案、护理工单、床位分配、费用结算、家属查询这些真实场景技术上又刚好能把SpringBoot、MyBatis、MySQL、RBAC权限、定时任务、报表统计这些东西串起来既能体现工作量又不会做到一半发现做不完。这篇文章我就以“养老院管理信息系统”为例把这类项目的完整落地过程掰开讲清楚。从头到尾用的是一个能直接运行的方案包含技术选型理由、数据库设计思路、核心模块实现细节以及我实际开发中踩过的坑和排查方法。不管你是正在选毕设题目、还是想快速搭一个Java Web管理平台做练手都可以直接照着这套思路来抄。1. 项目定位与整体设计思路1.1 这类管理系统题目为什么值得做养老院管理信息系统不是一个新题目但它一直很稳。稳在两点第一业务边界清晰不会像电商、社交那样越做越复杂第二业务闭环完整从老人入住、护理服务、费用产生到退住结算每个环节都有明确的数据流转和状态变化非常适合用来展示“业务理解能力”和“工程实现能力”。很多同学会纠结要不要选“校园二手交易平台”“失物招领系统”这类题目。说实话这些题目的核心逻辑和养老院管理系统高度相似都是用户管理加业务实体的增删改查再套一层状态流转和统计报表。区别只在于你选的业务场景能不能把功能做深。养老院场景有一个天然优势它天然包含“人”和“服务”两个核心维度老人是档案主体护工是服务主体工单把两者串起来费用又和工单、床位挂钩这种多层关联关系在答辩时非常好讲。另外这个选题也踩在“智慧养老”“养老服务数字化”的行业趋势上。哪怕你只是做一个基础版答辩时往“后续可以对接智能设备、家属小程序、健康监测预警”方向扩展评委也容易接受。1.2 技术选型SpringBoot带来的Java Web开发方式变化题目里写的是“Java Web”很多同学以为是传统的Servlet JSP那套实际上现在做Java Web项目主流方式已经变成了SpringBoot 前端模板引擎或者前后端分离。SpringBoot把Spring的繁琐配置大幅简化内嵌Tomcat打包成jar后一条命令就能启动这对开发和演示来说都太友好了。我建议的选型组合是技术栈选用方案选型理由后端框架SpringBoot 2.x简化配置社区资料多和JDK8配合稳定ORM框架MyBatisSQL可控报表统计方便写复杂SQL数据库MySQL 5.7/8.0生态成熟部署简单前端Thymeleaf jQuery Bootstrap非前后端分离部署简单演示稳定权限控制手动拦截器 RBAC表设计比Spring Security更好理解代码量适中报表图表ECharts图表效果好API简单这里特别说一下版本问题。现在SpringBoot 3.x已经发布但3.x要求JDK17起步很多同学的开发环境还停留在JDK8学校机房、答辩电脑也不一定能装新版JDK。所以这类毕设项目最稳的组合就是JDK8 SpringBoot 2.x比如2.7.18是2.x最后版本用起来没毛病。别一上来就追新版本给自己挖坑。前端为什么不用Vue如果你做的是前后端分离那意味着你要维护两个项目、处理跨域、额外部署前端静态文件。对于以展示业务逻辑为主的毕业设计直接用Thymeleaf模板加jQuery操作AJAX一套代码跑到底部署和演示都简单得多。等你真正理解了这类系统的数据流再升级成前后端分离也来得及。1.3 整体功能模块怎么划分才算合理模块划分是答辩时会被重点问的地方划分得合理说明你有全局设计能力。养老院管理系统我建议按角色和业务域混合划分做成下面几大块系统管理模块用户管理、角色管理、菜单管理、操作日志承载整个系统的权限框架。老人管理模块老人入住登记、档案维护、健康记录、退住申请、家属信息维护。护理管理模块护工排班、护理工单创建与派发、护理记录回写、工单状态跟踪。床位管理模块楼栋楼层床位数据维护、床位状态查询、入住分配与退住释放。费用管理模块费用项目定义、月度账单生成、缴费记录、欠费统计。统计分析模块入住率统计、老人年龄分布、费用收入趋势、工单完成情况。家属端功能可选公共页面按登记手机号查询老人基础动态。这样划分的好处是每个模块都对应清晰的业务场景实现上可以用“基础CRUD 状态流转 统计查询”这套统一结构去推进开发效率高答辩时也容易把系统讲成一个完整故事而不是零散的功能堆砌。2. 数据库表设计与权限模型管理系统的地基2.1 核心业务表与关系梳理数据库设计做得好不好直接决定后面写代码的体验。我先说最核心的几张表再讲它们之间的关系。老人信息表是绝对的核心字段不要只放“姓名、身份证、电话”就完事要面向业务场景去设计CREATE TABLE elder ( id INT(11) NOT NULL AUTO_INCREMENT, elder_no VARCHAR(32) DEFAULT NULL COMMENT 老人编号, name VARCHAR(50) NOT NULL COMMENT 姓名, gender TINYINT(1) DEFAULT 1 COMMENT 性别 1男 2女, id_card VARCHAR(18) DEFAULT NULL COMMENT 身份证号, birthday DATE DEFAULT NULL COMMENT 出生日期, phone VARCHAR(20) DEFAULT NULL COMMENT 联系电话, health_level VARCHAR(20) DEFAULT NULL COMMENT 健康等级 自理/半自理/全护理, chronic_disease VARCHAR(255) DEFAULT NULL COMMENT 慢性病标签, emergency_contact VARCHAR(50) DEFAULT NULL COMMENT 紧急联系人, emergency_phone VARCHAR(20) DEFAULT NULL COMMENT 紧急联系电话, bed_id INT(11) DEFAULT NULL COMMENT 床位ID, status TINYINT(1) DEFAULT 1 COMMENT 状态 1在住 2退住 3待入住, remark VARCHAR(500) DEFAULT NULL COMMENT 备注, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, deleted TINYINT(1) DEFAULT 0 COMMENT 逻辑删除, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT老人信息表;这里有两个设计细节很关键。一是逻辑删除字段deleted管理系统的数据不能物理删除否则历史记录会断链。二是把bed_id直接放进老人表而不是单独建一张“入住记录表”虽然看起来不太规范但对毕设来说够用且查询方便。如果你想让数据模型更经得起追问可以再加一张elder_bed_log表记录入住历史和调床记录。其他核心表包括sys_user登录用户表和员工/家属关联。sys_role/sys_menu/sys_user_role/sys_role_menu权限相关四张表。bed床位表字段包含楼栋、楼层、床位号、床位类型、状态空闲/占用/维修。care_worker护工表包含姓名、电话、负责区域、服务等级。care_order护理工单表包含老人ID、护工ID、工单类型、要求完成时间、状态。care_record护理执行记录表记录实际护理内容和完成时间。health_record健康记录表包含体温、血压、心率、体检结论等。fee_item费用项目表如床位费、护理费、伙食费。fee_bill账单表按月生成记录项目费用明细和应收总额。fee_payment缴费记录表记录实缴金额和缴费时间。表关系上老人和床位是一对一老人和健康记录是一对多护理工单和老人、护工都是多对一账单和老人是多对一。画ER图时按这个关系来逻辑很顺。2.2 RBAC角色权限模型怎么落地权限设计我强烈建议用RBAC模型不要每个角色写一遍判断代码。RBAC的表结构是经典的“用户-角色-菜单”三件套外加用户角色关联表和角色菜单关联表。核心思路是先定义菜单表sys_menu每条菜单对应一个URL或按钮标识比如“老人管理-列表”“老人管理-新增”再把菜单挂给角色最后把角色挂给用户。用户登录后通过角色查到他能看到的所有菜单和接口权限存入Session或Redis。后端拦截器做统一校验时只需要两步Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session request.getSession(); SysUser user (SysUser) session.getAttribute(loginUser); if (user null) { response.sendRedirect(/login); return false; } // 从session中取出当前用户拥有的URL权限集合 SetString perms (SetString) session.getAttribute(perms); if (perms.contains(request.getRequestURI())) { return true; } response.setContentType(text/html;charsetutf-8); response.getWriter().write(无权限访问); return false; }有人会问为什么不用Spring Security能用但我建议第一次做管理系统先手动实现一遍拦截器加RBAC。因为Spring Security的过滤器链、登录流程、密码加密那套对新手来说黑盒感太强出了问题很难排查。手动实现虽然代码多一些但每一步都清楚答辩时能讲明白原理这比“我引了Spring Security会自动拦截”要强得多。密码存储切记不要用明文。至少用MD5加盐进阶可以用BCrypt。毕设里用MD5加盐已经能过关但如果你写的是“BCrypt加密”在评委眼里会是一个加分项。2.3 状态字段设计让业务闭环跑起来管理系统最容易做成“纯增删改查”就是因为缺少状态流转。状态字段是让业务跑起来的发动机。以护理工单为例我设计的状态流转是待派单老人或护士发起护理申请系统生成工单。待执行管理员把工单派给指定护工护工端可见。执行中护工点击开始服务。已完成护工填写护理记录工单完成。已取消超时未执行或误派后取消。每次状态变更都把操作人、操作时间、变更前后状态写入一张单独的order_log表这样不仅能看到当前状态还能追踪完整链路答辩时被问“工单状态怎么管理”就有话说。老人状态也一样待入住、在住、退住。退住这个动作特别能检验设计水平因为退住不是简单把status改一下需要同时检查该老人有没有未结清账单、床位关联是否释放、护理工单是否还有未完成的。所以退住操作要放在一个事务里处理先做校验再改老人状态再释放床位再加一条退住记录。3. 核心功能模块实现从老人档案到统计报表3.1 老人档案与健康管理模块细节老人档案模块看是基础增删改查但有几个细节值得做好。列表页要支持姓名、健康等级、入住状态的条件筛选后端用MyBatis的动态SQL实现。分页直接用PageHelper插件两行代码搞定PageHelper.startPage(pageNum, pageSize); ListElderVO list elderMapper.selectElderList(queryDTO); PageInfoElderVO pageInfo new PageInfo(list);这里有个值得注意的点列表查询不要直接返回实体类要返回VO对象。因为列表页往往需要显示“床位号”和“所属护工”这些信息不在老人表里需要关联查询。写一个selectElderList方法用LEFT JOIN把床位号和护工姓名带出来比查出老人数据后再循环查关联表要高效得多代码也干净。健康管理模块可以做成一个Tab页老人详情里展示体检记录列表支持新增体温、血压、心率等数据。更进阶一点可以对慢性病标签做“用药提醒”每天早上通过定时任务扫描“需要服药的老人”生成当日待办提醒。这部分放到后面的定时任务章节细讲。照片和附件上传也是档案模块经常要的功能。SpringBoot里做文件上传不难但要处理好“上传文件存到哪里”和“怎么访问”两个问题。建议把文件存到本地磁盘的指定目录不是项目内部目录否则重新打包会丢然后配置虚拟路径映射spring: servlet: multipart: max-file-size: 10MB max-request-size: 20MB再加一个配置类映射Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/files/**) .addResourceHandler(file: uploadPath /); }这样上传的图片上传到uploadPath目录后就能通过http://localhost:8080/files/xxx.jpg直接访问。3.2 护理工单与任务派发模块的实现思路护理工单是养老院系统的核心业务模块也是最值得花篇幅讲的部分。工单来源有两种方式一种是由护士在系统中录入创建另一种是老人或家属在小程序/电话申请后由管理员代录。创建工单时需要选择老人、护理类型生活照料、医疗护理、康复训练等、期望完成时间、备注信息。提交后工单状态为“待派单”。派单操作是管理员把工单分配给具体护工。派单时前端页面要展示当前可用的护工列表后端根据“负责区域”和“当前待执行工单数量”做初步筛选。这里可以做一个简单的工作负载排序优先派给工单数较少的护工答辩时可以解释这是一种轻量的负载均衡思想。护工收到工单后执行护理操作需要填写护理记录回执实际护理内容、用时、老人状态变化、签名。这些信息保存在care_record表ID关联到工单ID。工单状态变更为“已完成”后这笔服务的计费信息就可以流转到费用模块了。如果想让系统更有“审批流”的味道可以在工单上加一个审核环节护工完成后先提交给护士长审核审核通过才算最终完成。这样就能在答辩时介绍“我实现的是一个简化的审批流用状态机和操作日志保证流程可追溯”。不需要引入Flowable这类重框架免得给自己增加太多复杂度。3.3 费用账单与报表统计的算法设计费用模块是养老院系统的“钱袋子”也是最容易出现逻辑漏洞的地方。我的建议是月度账单生成时采用“费用快照”思路。什么叫费用快照就是每月1号生成账单时把当月的费用项目、单价、计算方式固定到账单明细表里而不是在查询时临时算。为什么要这样因为养老院的服务价格可能调整如果查询时实时算历史账单会被当前价格影响造成账目混乱。快照能保证“当月账单反映当月价格”这个是财务上很重要的原则。账单金额计算示例床位费 床位单价 × 入住天数。如果老人当月中途入住按天折算。护理费 护理等级单价 × 当月天数。中途调整护理等级需要按调整前后天数分段计算。伙食费 固定月费按天折算。其他费用 药品费、日用品费等按实际发生记录累加。生成账单的代码可以放在一个generateMonthlyBill方法里每月1号通过Quartz定时任务调用。为了防止重复生成账单表要加“账期”字段如202506按月唯一索引生成前先查一下当月是否已有账单。统计报表模块建议用ECharts画三张图老人年龄分布柱状图按年龄段统计人数反映入住人群结构。入住率折线图按月份统计实际入住床位数占总床位数比例。费用收入趋势图按月份统计应收费用和实收费用。统计SQL是这类模块的难点核心就是GROUP BY加日期函数。比如统计每月入住率可以先用LEFT JOIN把床位数和老人数按月份关联再用DATE_FORMAT格式化月份字段分组。3.4 前端交互jQuery Ajax怎么和SpringBoot配合这个项目做成非前后端分离前端页面用Thymeleaf渲染动态交互用jQuery的AJAX请求后端接口。这种模式的好处是部署简单打一个jar包直接跑Thymeleaf模板会被编译到resources目录下。前后端交互时我建议定义统一返回结构让所有接口返回一致的数据格式前端拿到后统一处理。最简单的封装public class R { private Integer code; // 200成功 500失败 private String msg; private Object data; // 静态方法 success() / error() }前端页面里提交表单用$.ajax或$.post示例$.ajax({ url: /elder/save, type: POST, contentType: application/json, data: JSON.stringify(formData), success: function (res) { if (res.code 200) { layer.msg(保存成功); window.location.reload(); } else { layer.msg(res.msg); } } });这里有个容易踩的坑后端Controller接收JSON对象时必须用RequestBody注解而且前端要设置contentType: application/json否则SpringBoot不认识你传的是什么。很多新手卡在“前端明明传了数据后端却拿不到”八成就是这里出了问题。操作类按钮删除、派单、退住等建议用layer.confirm弹窗确认避免误操作。列表页用Bootstrap的表格样式加个搜索区域整体观感就足够正式了。4. 上手实操从0到1搭建项目并调通核心链路4.1 初始化SpringBoot项目与基础配置我建议用IDEA直接创建SpringBoot项目也可以去Spring Initializr网站生成后导入。核心依赖选这几个Spring Web、MyBatismybatis-spring-boot-starter、MySQL Driver、Thymeleaf、Lombok可选如果用了要确保团队环境都有插件。创建完成后pom.xml里的关键依赖parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version relativePath/ /parent dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-thymeleaf/artifactId /dependency dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version2.3.2/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope /dependency dependency groupIdcom.github.pagehelper/groupId artifactIdpagehelper-spring-boot-starter/artifactId version1.4.7/version /dependency /dependencies配置文件application.ymlserver: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/elder_care?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456 thymeleaf: cache: false mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.eldercare.entity configuration: map-underscore-to-camel-case: truemap-underscore-to-camel-case: true这个配置特别有用数据库字段是create_time实体类是createTime如果不开这个配置查出来的时间字段全是null排查半天找不到原因。启动类上别忘了加MapperScan注解指定Mapper接口所在包。很多同学忘了这一步启动时不会报错但运行到Mapper注入时才报“No qualifying bean”排查起来很耗时。4.2 MyBatis映射与动态SQL的落地细节MyBatis的XML文件是这套系统的核心写得好能解决很多复杂查询。文件放在resources/mapper目录命名和Mapper接口一致。先说常见的“Mapper接口找不到实现”问题。如果配置了mapper-locations: classpath:mapper/*.xml一定要确保XML文件被打包进target目录。IDEA的Maven编译偶尔不会把resources下的文件复制进去如果运行时报Invalid bound statement (not found)先检查target/classes里有没有对应的XML文件。动态SQL的写法是条件查询的关键。比如老人列表的条件筛选select idselectElderList resultTypecom.example.eldercare.vo.ElderVO SELECT e.*, b.bed_no, b.floor_name, w.name AS worker_name FROM elder e LEFT JOIN bed b ON e.bed_id b.id LEFT JOIN care_worker w ON e.care_worker_id w.id where if testname ! null and name ! AND e.name LIKE CONCAT(%, #{name}, %) /if if testhealthLevel ! null and healthLevel ! AND e.health_level #{healthLevel} /if if teststatus ! null AND e.status #{status} /if AND e.deleted 0 /where ORDER BY e.create_time DESC /selectwhere标签会自动去掉第一个多余的AND这个细节是动态SQL的精髓。批量操作比如批量派工单可以用foreach标签update idbatchDispatch UPDATE care_order SET worker_id #{workerId}, status 2, dispatch_time NOW() WHERE id IN foreach collectionorderIds itemid open( separator, close) #{id} /foreach /update使用MyBatis时建议把resultType写成全限定类名或者使用type-aliases-package配置包名。不要为了省事在XML里写resultMap除非字段映射确实复杂。自动驼峰转换已经能解决绝大多数情况。4.3 事务控制与定时任务最容易翻车的两个点事务是这类管理系统里绝对不能错的部分。上面说到的“退住操作”就是一个典型的需要事务的场景修改老人状态、释放床位、生成退住记录这三步必须同时成功或同时失败否则数据就乱了。实现很简单在Service方法上加Transactional注解Spring会自动管理事务。但我见过太多事务失效的案例主要因为以下几个原因方法不是public的Spring的声明式事务基于代理实现private方法不走代理事务必然失效。自调用同一个类里this.method()调用带事务的方法不走代理事务失效。异常被catch住事务默认只在RuntimeException时回滚如果方法内catch住了异常没有抛出事务就感知不到。数据库引擎不支持事务MySQL的MyISAM引擎不支持事务建表时一定用InnoDB。解决思路很简单事务方法单独拆到Service类里用public方法内部不要吞异常实在需要处理就throw new RuntimeException()。定时任务我用的是QuartzSpringBoot集成不需要太复杂引入spring-boot-starter-quartz后写一个Job类再配置Trigger即可。用法示例每月1号凌晨生成账单、每天早上8点生成用药提醒。Component public class DailyRemindJob extends QuartzJobBean { Autowired private CareOrderService careOrderService; Override protected void executeInternal(JobExecutionContext context) { careOrderService.generateDailyRemind(); log.info(每日提醒任务执行完成); } }配置类注册TriggerConfiguration public class QuartzConfig { Bean public JobDetail dailyRemindJobDetail() { return JobBuilder.newJob(DailyRemindJob.class) .withIdentity(dailyRemindJob) .storeDurably() .build(); } Bean public Trigger dailyRemindTrigger() { return TriggerBuilder.newTrigger() .forJob(dailyRemindJobDetail()) .withSchedule(CronScheduleBuilder.cronSchedule(0 0 8 * * ?)) .build(); } }Cron表达式“0 0 8 * * ?”表示每天早上8点执行。如果你在本地测试时不想等定时触发可以在代码里临时调用对应Service方法验证逻辑正确后再改回定时执行。5. 常见问题与排查技巧实录5.1 Maven下载、SpringBoot版本与JDK环境的坑每个做SpringBoot毕设的人几乎都会遇到IDEA右下角一直卡在“downloading Maven dependencies”的情况。这个问题的根源是Maven中央仓库在国外访问慢或者被墙。解决办法是在settings.xml里配置阿里云镜像mirror idaliyunmaven/id mirrorOfcentral/mirrorOf name阿里云公共仓库/name urlhttps://maven.aliyun.com/repository/public/url /mirror配置完记得在IDEA的Maven设置里勾选“User settings file”并重新导入项目。版本问题也很常见。如果新建项目时选择了SpringBoot 3.x那么你的JDK必须是17及以上但很多毕设教程、网上资料都是基于JDK8的写法比如javax.*包和jakarta.*包的差异会让代码复制过来后一片标红。最稳妥的办法是手动把SpringBoot版本改成2.7.18配合JDK8使用。强烈建议在项目一开始就固定这个组合不要中途切换。如果你用的是Eclipse遇到集成MyBatis时无限提示downloading...大概率也是Maven仓库问题。同样先检查Maven镜像再做一次Maven Clean和Maven Update。5.2 IDEA里application.yml不提示等配置问题开发中经常遇到IDEA里application.yml不自动提示写配置全靠手敲还容易写错。这个问题的常见原因有两个第一项目里可能生成的是application.properties你手动改成application.yml后IDEA没有识别文件类型。解决方法是右键文件 - “Override File Type” - 选择YAML格式。第二IDEA没有安装或启用Spring Boot插件。2020.2以后版本一般自带但如果被禁用配置提示就会失效。检查Settings - Plugins确保Spring Boot和Spring Assistant插件已启用。另外application.yml的缩进是个大坑。YAML对缩进非常敏感同一个层级必须对齐。很多“配置没生效”的诡异问题最后发现都是缩进错了导致解析异常启动时虽然不报错但配置项根本没加载进去。5.3 事务失效、循环依赖、Mapper绑定异常这三个问题是面试和答辩的高频考点也是实际开发中最常翻车的三个点。事务失效的几种场景上面已经说过最隐蔽的是“自调用”。举个例子public void saveOrder() { this.updateStatus(); // 自调用updateStatus上的Transactional失效 } Transactional public void updateStatus() { // ... }解决办法是注入自己的代理对象或者把updateStatus方法拆到另一个Service里调用。循环依赖在SpringBoot 2.6以后默认是不允许的。A依赖B、B依赖A启动直接报错。网上的临时解决方案是在配置里加spring.main.allow-circular-referencestrue但我建议用构造器注入 重构的方式把循环依赖拆掉比如把公共逻辑抽到第三个Service里。这比修改配置优雅得多。Mapper绑定异常的表现是“Invalid bound statement (not found)”。排查顺序是先看接口和XML的namespace是否一致再看方法id是否对应最后检查XML是否被打包。按这个顺序排查绝大多数问题都能解决。5.4 答辩/演示前的自检清单项目做完到答辩之前建议按下面这个清单完整过一遍能避免大部分尴尬场面数据库脚本是否能在一台全新电脑上跑通。提前导出一份完整的init.sql包含建库、建表、初始化管理员账号。不要到答辩现场才手忙脚乱建表。项目打包成jar后是否能正常启动。重点检查配置文件里的数据库密码是否写死、上传路径是否存在。演示数据是否充分。准备10位以上的老人数据、5位以上护工数据、近三个月的账单数据这样展示列表和图表时才有人气。权限功能是否生效。用普通护工账号登录确认看不到系统管理菜单点管理接口时会被拦截。核心链路的演示脚本要提前走一遍登录 - 新增老人 - 分配床位 - 创建工单 - 派单 - 护工执行 - 生成费用 - 统计报表。这个主流程一定要全程畅通答辩时按这个顺序讲逻辑特别清晰。这套自检清单我自己每次做项目都会用不只是为了答辩而是确保系统真正能给别人用起来。把它当成“交付前的冒烟测试”养成习惯之后很受益。最后再分享一个我自己的习惯项目做完后把整个系统的菜单结构和演示数据截图存下来做成一个简单的README文档放进项目里。一方面是给自己留个操作手册另一方面如果你后面要写软著申请材料、项目说明书或者做技术分享这些素材直接就能用上。做这类管理系统的思路是相通的把一套骨架吃透从养老院换到校园失物招领、闲置物品交易、实验室管理本质上都是替换业务表、调整状态流转的事。第一次做把主流程走通比追求花哨重要得多。