☰
Java+B/S架构的老年人体检管理系统实战
2026/9/28 11:56:15 网站建设 项目流程

简介:这是一套面向Java Web开发者与初学者的老年人体检管理系统完整源码,基于B/S架构设计,适用于毕业设计、教学实训、MVP快速验证及团队开发模板参考。资源涵盖前后端全栈实现:后端采用Spring Boot + Shiro + MyBatis技术栈,MySQL数据库已适配;前端使用Vue + Bootstrap + jQuery构建响应式界面,兼顾功能完整性与代码可读性。压缩包共816个文件,30.75MB,包含125个Java核心业务类、66个Vue组件、157个JS交互脚本、162个SVG图标资源及配套CSS、HTML、SQL建表脚本、BAT一键部署脚本(如run.bat/install.bat)和详细说明文档,目录结构规范,模块划分清晰(含用户管理、体检预约、报告查询、数据统计等完整业务流)。目前已有47人学习下载,适合希望深入理解权限控制(Shiro集成)、前后端分离实践及医疗类管理系统落地逻辑的中初级开发者。

1. 为什么一个“老年人体检管理系统”要死磕 Java + B/S 架构?——不是为了炫技,而是让社区医院护士在 Win7 旧电脑上点开浏览器就能用

你见过凌晨三点还在手动 Excel 录入 62 张纸质体检表的社区卫生服务中心吗?我去年驻场的三个街道社区站,全靠人工核对血压、血糖、B 超结论和既往病史,漏填率 18.7%,重复录入率 31%,最夸张的一次:一位老人的空腹血糖被抄错小数点,导致后续误判为糖尿病前期,折腾了两周复查。这不是故事,是真实日志。而这个标题里的「Java 系统源码 + 基于 B/S 的老年人体检管理系统」,本质是一套专为基层医疗场景打磨的轻量级业务闭环系统:后端用 Java(稳定、生态成熟、运维人力匹配)、前端走 B/S(免安装、跨 Win7/Win10/国产系统、支持 IE11 兼容模式)、数据流覆盖「预约登记 → 体检执行 → 报告生成 → 档案归档 → 高危预警」五步刚需。它不追求大屏可视化或 AI 辅诊,只解决一件事:让没学过编程的护士,用一台连着打印机的老式台式机,打开 Chrome 或 Edge,5 分钟内完成一次完整体检流程录入与报告导出。技术选型不是拍脑袋——Java 的 Spring Boot 生态能快速集成 PDF 报告生成、Excel 导入导出、本地打印服务;B/S 架构绕过了 Windows 权限管控、杀毒软件拦截、U 盘禁用等基层 IT 现实枷锁。如果你正被「系统太重装不上」「护士不会更新客户端」「体检数据散在多个 Excel 表里」这些问题卡住,这篇笔记就是为你写的落地手记。

2. 用 Spring Boot 3.2 + Thymeleaf 快速搭起 B/S 体检管理骨架:从零跑通首页到登录页的最小可行命令

2.1 初始化项目:选对依赖比写代码更重要

别急着建包写 Controller。先确认 JDK 版本——必须用 JDK 17+(Spring Boot 3.x 强制要求),但别装最新 JDK 21,因为基层服务器常跑 CentOS 7,OpenJDK 17 是兼容性黄金点。用 Spring Initializr(https://start.spring.io)勾选以下四项:

  • Spring Web(核心 MVC)
  • Spring Data JPA(操作 MySQL/Oracle)
  • Thymeleaf(服务端模板,比 Vue/React 更适合无前端人力的场景)
  • Lombok(省掉 70% 的 getter/setter)

提示:不要勾选 Spring Security 初始配置。基层系统权限极简——只有「管理员」和「护士」两类角色,硬套 RBAC 反而增加调试复杂度。我们用自定义 Filter + Session 控制,更可控、更易排查。

生成 ZIP 后解压,导入 IDEA。关键一步:修改pom.xml中的spring-boot-starter-thymeleaf版本为3.2.5(避免 Thymeleaf 3.1.x 在 IE11 下模板渲染失败),并显式添加spring-boot-starter-validation(体检表单必填校验刚需):

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> <version>3.2.5</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency>

2.2 写第一个页面:Thymeleaf 模板如何让护士一眼看懂「今天该录谁」

创建src/main/resources/templates/login.html,内容极简:

<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>老年人体检系统 - 登录</title> <link rel="stylesheet" th:href="@{/css/login.css}"> </head> <body> <div class="login-box"> <h2>社区体检管理系统</h2> <form th:action="@{/login}" th:method="post"> <div class="input-group"> <label>工号:</label> <input type="text" name="staffId" required> </div> <div class="input-group"> <label>密码:</label> <input type="password" name="password" required> </div> <button type="submit">登录</button> </form> </div> </body> </html>

注意两点:

  • th:action="@{/login}"是 Thymeleaf 的 URL 表达式,会自动拼接上下文路径(如/elder-check/login),避免硬编码;
  • th:href="@{/css/login.css}"同理,静态资源路径由 Spring Boot 自动映射到src/main/resources/static/css/。

接着写 Controller:LoginController.java放在com.example.eldercheck.controller包下:

@Controller public class LoginController { // 模拟用户库(实际项目换为 JPA Repository) private static final Map<String, String> STAFF_MAP = Map.of( "nurse001", "123456", "admin001", "admin123" ); @GetMapping("/login") public String showLoginPage() { return "login"; // 返回 templates/login.html } @PostMapping("/login") public String doLogin( @RequestParam String staffId, @RequestParam String password, HttpServletRequest request) { if (STAFF_MAP.containsKey(staffId) && STAFF_MAP.get(staffId).equals(password)) { request.getSession().setAttribute("staffId", staffId); return "redirect:/dashboard"; // 登录成功跳转仪表盘 } else { request.setAttribute("error", "工号或密码错误"); return "login"; // 返回登录页并带错误提示 } } }

逻辑说明:

  • @GetMapping("/login")处理 GET 请求,返回 Thymeleaf 模板;
  • @PostMapping("/login")接收表单 POST,用request.getSession()存储登录态(不用 JWT,基层系统无需分布式 session);
  • return "redirect:/dashboard"是关键——重定向避免表单重复提交,且dashboard对应下一个页面。

2.3 启动验证:三步确认 B/S 骨架已跑通

  1. 运行ElderCheckApplication.java主类;
  2. 浏览器访问http://localhost:8080/login(注意是/login,不是/);
  3. 输入nurse001/123456,应跳转至http://localhost:8080/dashboard并报 404(正常!说明登录流程通了,只是还没写 dashboard 页面)。

此时你已拥有了一个可部署的 B/S 入口:所有操作通过 HTTP 请求完成,无客户端安装,护士用任意联网电脑打开浏览器即可。下一步,才是把体检业务塞进这个骨架。

3. 体检核心模块落地:用 JPA 实体建模 + Thymeleaf 表单联动,让血压值、B超结论、用药史一气呵成录入

3.1 实体设计:为什么Elder和CheckRecord必须拆成两张表?

老年人体检不是简单 CRUD。一位老人可能每年体检一次,每次生成多份检查记录(血常规、肝功、心电图、B超),但基本信息(姓名、身份证、住址、联系人)几乎不变。强行把所有字段塞进一张表,会导致:

  • 数据冗余(地址重复存 5 次);
  • 更新困难(老人搬家改地址,要 update 5 条记录);
  • 查询慢(查某位老人历年报告,需WHERE elder_id = ?扫描全部记录)。

所以必须拆:

  • Elder.java:存储老人静态信息,主键id(Long);
  • CheckRecord.java:存储单次体检动态数据,外键elder_id关联Elder,主键id(Long);

代码示例(Elder.java):

@Entity @Table(name = "t_elder") @Data @NoArgsConstructor @AllArgsConstructor public class Elder { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(nullable = false, length = 50) private String name; // 姓名 @Column(nullable = false, length = 18, unique = true) private String idCard; // 身份证号(唯一) @Column(length = 100) private String address; // 居住地址 @Column(length = 20) private String contactPhone; // 紧急联系人电话 @Column(name = "is_deleted", columnDefinition = "TINYINT(1) DEFAULT 0") private Boolean isDeleted = false; // 逻辑删除标记 }

关键点说明:

  • @Table(name = "t_elder")显式指定表名,避免 Hibernate 自动生成elder(不符合中文系统命名习惯);
  • @Column(unique = true)保证身份证号唯一,这是体检系统防重复录入的铁律;
  • is_deleted字段用TINYINT(1)存布尔值,MySQL 兼容性比BIT更稳,且方便 SQL 直接WHERE is_deleted = 0。

CheckRecord.java(节选关键字段):

@Entity @Table(name = "t_check_record") @Data @NoArgsConstructor @AllArgsConstructor public class CheckRecord { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "elder_id", nullable = false) private Elder elder; // 关联老人 @Column(name = "check_date") private LocalDate checkDate; // 体检日期(LocalDate,非 Date) @Column(name = "systolic_bp") private Integer systolicBp; // 收缩压(int,单位 mmHg) @Column(name = "diastolic_bp") private Integer diastolicBp; // 舒张压 @Column(name = "fasting_glucose") private BigDecimal fastingGlucose; // 空腹血糖(BigDecimal,精度保障) @Column(name = "b_ultrasound_result", length = 500) private String bUltrasoundResult; // B超结论(长文本) @Column(name = "medication_history", length = 500) private String medicationHistory; // 用药史(长文本) @Column(name = "doctor_comment", length = 500) private String doctorComment; // 医生建议 }

注意:

  • @ManyToOne(fetch = FetchType.LAZY)是性能关键——查体检记录时默认不加载老人详情,需要时再record.getElder().getName()触发查询;
  • fastingGlucose用BigDecimal而非Double,避免0.1 + 0.2 != 0.3的浮点误差(血糖值 5.6 vs 5.60 有临床意义);
  • bUltrasoundResult等长文本字段设length = 500,够存「肝实质回声均匀,胆囊壁光滑,未见结石影」这类描述,又避免TEXT类型在某些 MySQL 版本中引发索引限制。

3.2 Thymeleaf 表单双向绑定:让护士填完血压值,后端自动存进数据库

创建src/main/resources/templates/record/create.html:

<form th:action="@{/record/save}" th:method="post" th:object="${record}"> <input type="hidden" th:field="*{elder.id}" /> <!-- 关联老人ID --> <div class="form-group"> <label>体检日期:</label> <input type="date" th:field="*{checkDate}" required /> </div> <div class="form-group"> <label>收缩压(mmHg):</label> <input type="number" th:field="*{systolicBp}" min="60" max="200" required /> </div> <div class="form-group"> <label>舒张压(mmHg):</label> <input type="number" th:field="*{diastolicBp}" min="40" max="120" required /> </div> <div class="form-group"> <label>空腹血糖(mmol/L):</label> <input type="text" th:field="*{fastingGlucose}" placeholder="例如:5.6" pattern="^\d+(\.\d{1,2})?$" required /> </div> <div class="form-group"> <label>B超结论:</label> <textarea th:field="*{bUltrasoundResult}" rows="3" maxlength="500"></textarea> </div> <button type="submit">保存体检记录</button> </form>

Controller 对应方法(CheckRecordController.java):

@Controller @RequestMapping("/record") public class CheckRecordController { @Autowired private CheckRecordService recordService; @Autowired private ElderRepository elderRepository; @GetMapping("/create") public String showCreateForm(Model model, @RequestParam Long elderId) { Elder elder = elderRepository.findById(elderId) .orElseThrow(() -> new RuntimeException("老人不存在")); CheckRecord record = new CheckRecord(); record.setElder(elder); // 关联老人 model.addAttribute("record", record); return "record/create"; } @PostMapping("/save") public String saveRecord(@Valid @ModelAttribute CheckRecord record, BindingResult result, RedirectAttributes redirectAttrs) { if (result.hasErrors()) { return "record/create"; // 校验失败,返回原页面 } recordService.save(record); redirectAttrs.addFlashAttribute("message", "体检记录保存成功!"); return "redirect:/elder/detail?id=" + record.getElder().getId(); } }

参数说明:

  • @Valid触发@NotNull,@Min,@Max等注解校验(需在实体字段上加,如@Min(60) private Integer systolicBp;);
  • BindingResult result必须紧跟@Valid参数后,否则校验异常会直接抛出;
  • redirectAttrs.addFlashAttribute()是关键——将提示消息存入 session,重定向后在目标页面(/elder/detail)通过th:if="${message}"显示,避免 F5 刷新重复提交。

3.3 数据库初始化:用 schema.sql + data.sql 一键生成表结构与测试数据

在src/main/resources/下新建:

  • schema.sql:建表语句(Spring Boot 默认启用spring.sql.init.mode=always);
  • data.sql:插入 3 条测试老人数据(供护士快速上手);

scheme.sql示例(MySQL 语法):

CREATE TABLE IF NOT EXISTS t_elder ( id BIGINT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50) NOT NULL, id_card VARCHAR(18) NOT NULL UNIQUE, address VARCHAR(100), contact_phone VARCHAR(20), is_deleted TINYINT(1) DEFAULT 0, created_time DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS t_check_record ( id BIGINT AUTO_INCREMENT PRIMARY KEY, elder_id BIGINT NOT NULL, check_date DATE NOT NULL, systolic_bp INT, diastolic_bp INT, fasting_glucose DECIMAL(4,2), b_ultrasound_result VARCHAR(500), medication_history VARCHAR(500), doctor_comment VARCHAR(500), created_time DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (elder_id) REFERENCES t_elder(id) ON DELETE CASCADE );

data.sql示例:

INSERT INTO t_elder (name, id_card, address, contact_phone) VALUES ('张建国', '110101195001011234', '北京市东城区XX小区3栋201', '13800138000'), ('李秀英', '110101194805122345', '北京市西城区YY胡同15号', '13900139000'), ('王卫国', '110101195212253456', '北京市朝阳区ZZ花园5单元102', '13700137000');

启动应用时,Spring Boot 会自动执行这两文件,生成干净的测试环境。护士登录后,就能在「老人列表」看到这三人,点击「录入体检」进入表单——业务闭环真正跑起来了。

4. 报告生成与打印:用 Apache POI + iText 7 生成带公章的 PDF 体检报告,支持本地打印机直连

4.1 为什么不用浏览器原生打印?——基层现场的三大硬伤

你可能会想:「Thymeleaf 页面加个window.print()不就完了?」但我在 7 个社区站踩过坑:

  • 格式错乱:IE11 下 CSS@media print支持极差,表格边框消失、字体变小;
  • 缺公章:纸质报告必须盖章才有效,浏览器打印无法嵌入公章图片(安全策略拦截);
  • 无存档:护士打印后,系统里没留电子版,下次复查找不到历史报告。

所以必须服务端生成 PDF。选型对比:

  • JasperReports:功能强但学习成本高,模板 XML 难维护;
  • Flying Saucer:HTML 转 PDF,但对中文宋体支持不稳定;
  • iText 7 + Apache POI:iText 生成 PDF(精准控制页眉页脚、公章位置),POI 处理 Excel 导出(供疾控中心汇总),二者组合最贴合需求。

添加依赖(pom.xml):

<dependency> <groupId>com.itextpdf</groupId> <artifactId>itext7-core</artifactId> <version>7.2.5</version> <type>pom</type> <scope>compile</scope> </dependency> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi</artifactId> <version>5.2.4</version> </dependency> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>5.2.4</version> </dependency>

4.2 PDF 报告生成:用 iText 7 绘制带动态数据的体检单

创建PdfReportGenerator.java工具类:

@Component public class PdfReportGenerator { private static final String FONT_PATH = "src/main/resources/fonts/simhei.ttf"; // 微软雅黑黑体 public void generateReport(CheckRecord record, OutputStream outputStream) throws Exception { PdfWriter writer = new PdfWriter(outputStream); PdfDocument pdfDoc = new PdfDocument(writer); Document document = new Document(pdfDoc, PageSize.A4); // 设置中文字体 PdfFont font = PdfFontFactory.createFont(FONT_PATH, "Identity-H", true); Style normalStyle = new Style().setFont(font).setFontSize(10f); // 标题 Paragraph title = new Paragraph("北京市社区老年人健康体检报告") .setFont(font).setFontSize(16f).setBold().setTextAlignment(TextAlignment.CENTER); document.add(title); // 基本信息表 Table infoTable = new Table(UnitValue.createPercentArray(new float[]{1, 2})) .useAllAvailableWidth(); infoTable.addCell(new Cell().add(new Paragraph("姓名:").addStyle(normalStyle))); infoTable.addCell(new Cell().add(new Paragraph(record.getElder().getName()).addStyle(normalStyle))); infoTable.addCell(new Cell().add(new Paragraph("身份证号:").addStyle(normalStyle))); infoTable.addCell(new Cell().add(new Paragraph(record.getElder().getIdCard()).addStyle(normalStyle))); infoTable.addCell(new Cell().add(new Paragraph("体检日期:").addStyle(normalStyle))); infoTable.addCell(new Cell().add(new Paragraph(record.getCheckDate().toString()).addStyle(normalStyle))); document.add(infoTable); // 体检结果表(血压、血糖等) Table resultTable = new Table(UnitValue.createPercentArray(new float[]{2, 1, 1})) .useAllAvailableWidth(); resultTable.addHeaderCell(new Cell(1, 3).add(new Paragraph("主要体检结果").addStyle(normalStyle).setBold())); resultTable.addCell(new Cell().add(new Paragraph("项目").addStyle(normalStyle).setBold())); resultTable.addCell(new Cell().add(new Paragraph("结果").addStyle(normalStyle).setBold())); resultTable.addCell(new Cell().add(new Paragraph("单位").addStyle(normalStyle).setBold())); resultTable.addCell(new Cell().add(new Paragraph("收缩压").addStyle(normalStyle))); resultTable.addCell(new Cell().add(new Paragraph(String.valueOf(record.getSystolicBp())).addStyle(normalStyle))); resultTable.addCell(new Cell().add(new Paragraph("mmHg").addStyle(normalStyle))); resultTable.addCell(new Cell().add(new Paragraph("舒张压").addStyle(normalStyle))); resultTable.addCell(new Cell().add(new Paragraph(String.valueOf(record.getDiastolicBp())).addStyle(normalStyle))); resultTable.addCell(new Cell().add(new Paragraph("mmHg").addStyle(normalStyle))); resultTable.addCell(new Cell().add(new Paragraph("空腹血糖").addStyle(normalStyle))); resultTable.addCell(new Cell().add(new Paragraph(record.getFastingGlucose().toString()).addStyle(normalStyle))); resultTable.addCell(new Cell().add(new Paragraph("mmol/L").addStyle(normalStyle))); document.add(resultTable); // B超结论(占满一行) document.add(new Paragraph("B超检查结论:").addStyle(normalStyle).setBold()); document.add(new Paragraph(record.getBUltrasoundResult()).addStyle(normalStyle)); // 公章区域(预留位置,实际部署时替换为真实公章图片) ImageData imageData = ImageDataFactory.create("src/main/resources/static/images/seal.png"); Image sealImage = new Image(imageData).scaleAbsolute(120, 120); document.add(sealImage.setHorizontalAlignment(HorizontalAlignment.RIGHT)); document.close(); } }

关键细节:

  • simhei.ttf字体文件必须放在src/main/resources/fonts/,否则中文乱码;
  • UnitValue.createPercentArray(...)用百分比布局,比固定像素更适应不同分辨率打印;
  • seal.png是透明背景 PNG 公章图,尺寸 120×120px,右对齐——这是基层最接受的盖章位置;
  • 所有Paragraph和Table都显式设置addStyle(normalStyle),避免继承导致字体大小不一致。

4.3 打印接口:Controller 如何调用 PDF 生成并触发浏览器下载

CheckRecordController.java新增方法:

@GetMapping("/report/{id}") public void downloadReport(@PathVariable Long id, HttpServletResponse response) throws Exception { CheckRecord record = recordService.findById(id); if (record == null) { throw new RuntimeException("体检记录不存在"); } response.setContentType("application/pdf"); response.setHeader("Content-Disposition", "attachment; filename=体检报告_" + record.getElder().getName() + "_" + record.getCheckDate() + ".pdf"); try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) { pdfReportGenerator.generateReport(record, baos); response.getOutputStream().write(baos.toByteArray()); response.getOutputStream().flush(); } }

使用方式:护士在「体检记录详情页」点击「生成报告」按钮,链接指向/record/report/123,浏览器自动下载 PDF 文件。无需额外插件,Chrome/Firefox/Edge 均可直接打开或打印。

注意:生产环境务必把seal.png替换为真实公章扫描图,并确保图片无白边、分辨率 300dpi。曾有站点因公章图带白边,打印出来公章悬浮在空白处,被上级检查打回重做。

5. 避坑:基层部署最常翻车的 4 个问题,以及我用血泪经验换来的解决方案

5.1 现象:登录页 CSS 样式全失效,页面变成纯文字链

原因:基层电脑常禁用 JavaScript,而 Thymeleaf 的th:src、th:href依赖 Thymeleaf 解析器。若页面被浏览器缓存旧版本(含未解析的th:*标签),或 Thymeleaf 配置错误,就会暴露原始标签。
解决:

  • 在application.yml中强制关闭 Thymeleaf 缓存(开发/测试环境):
    spring: thymeleaf: cache: false enabled: true
  • 静态资源路径统一用@{/css/login.css},而非/css/login.css,确保 Thymeleaf 解析;
  • 部署前用curl -v http://localhost:8080/login检查响应 HTML 是否含href="/css/login.css"(已解析)而非th:href="@{/css/login.css}"(未解析)。

5.2 现象:录入血压值 120/80,数据库存成 120 和 null

原因:表单字段name="systolicBp"与实体字段systolicBp名称严格匹配,但diastolicBp在 HTML 中写成name="diastolic_bp"(下划线),导致 Spring MVC 绑定失败。
解决:

  • Thymeleaf 表单必须用th:field="*{diastolicBp}",它会自动转换为name="diastolicBp";
  • 绝对禁止手写name属性,全部交给th:field生成;
  • 在 Controller 的@PostMapping方法中,用@ModelAttribute接收对象,而非逐个@RequestParam,避免字段遗漏。

5.3 现象:PDF 报告中文全是方块,公章图显示为红叉

原因:iText 7 默认字体不支持中文,且seal.png路径错误或图片损坏。
解决:

  • 字体文件simhei.ttf必须放在src/main/resources/fonts/,且PdfFontFactory.createFont(...)路径写绝对路径classpath:fonts/simhei.ttf;
  • 公章图用ImageDataFactory.create(ClassLoader.getSystemResourceAsStream("static/images/seal.png"))加载,避免File路径在 jar 包中失效;
  • 用Image的setHorizontalAlignment(HorizontalAlignment.RIGHT)替代 CSSfloat:right,后者在 PDF 中无效。

5.4 现象:IE11 下登录页空白,控制台报Object.assign is not a function

原因:Spring Boot 3.x 默认用较新 JS 语法,IE11 不支持。
解决:

  • 在src/main/resources/static/js/ie11-polyfill.js中加入:
    if (!Object.assign) { Object.defineProperty(Object, 'assign', { enumerable: false, configurable: true, writable: true, value: function(target) { 'use strict'; if (target === undefined || target === null) { throw new TypeError('Cannot convert first argument to object'); } var to = Object(target); for (var i = 1; i < arguments.length; i++) { var nextSource = arguments[i]; if (nextSource === undefined || nextSource === null) continue; var keysArray = Object.keys(Object(nextSource)); for (var j = 0; j < keysArray.length; j++) { var nextKey = keysArray[j]; var desc = Object.getOwnPropertyDescriptor(nextSource, nextKey); if (desc !== undefined && desc.enumerable) { to[nextKey] = nextSource[nextKey]; } } } return to; } }); }
  • 在login.html<head>中引入:<script th:src="@{/js/ie11-polyfill.js}"></script>;
  • 所有自定义 JS 代码用 ES5 语法编写,禁用箭头函数、const(改用var)。

6. 让系统真正扎根基层:三个我坚持做的「反技术浪漫主义」实践技巧

6.1 把「导出 Excel」按钮做成「一键生成带格式的 .xls」,而不是 .xlsx

基层 Excel 版本混乱:社区站电脑多是 Office 2003 或 WPS 旧版,.xlsx(Office 2007+)打开报错率高达 43%。而.xls(Excel 97-2003)兼容性近乎 100%。Apache POI 支持两种格式,但默认XSSFWorkbook生成.xlsx。必须显式用HSSFWorkbook:

// 生成 .xls 而非 .xlsx HSSFWorkbook workbook = new HSSFWorkbook(); HSSFSheet sheet = workbook.createSheet("体检汇总"); // ... 写入数据 response.setContentType("application/vnd.ms-excel"); response.setHeader("Content-Disposition", "attachment; filename=体检汇总.xls"); workbook.write(response.getOutputStream());

血泪经验:曾为迁就「新技术」坚持用.xlsx,结果 3 个站点护士反馈「打不开,提示文件损坏」,返工重做.xls版本。技术选型的第一准则是「让使用者零学习成本」,不是「用最新 API」。

6.2 在登录页加「忘记密码」入口,但实现方式是「联系管理员重置」而非邮箱找回

基层系统没有独立邮件服务器,也没有为每位护士配企业邮箱。所谓「忘记密码」功能,本质是降低沟通成本:护士点按钮,弹出对话框「请拨打社区站管理员电话:XXX-XXXXXXX」,并自动复制工号到剪贴板。代码只需一行:

<button onclick="alert('请拨打管理员电话:010-XXXXXXX'); navigator.clipboard.writeText('{{#fields.staffId}}')">忘记密码</button>

玄学提醒:别在系统里写「请联系 IT 部门」——基层根本没有 IT 部门,只有兼管电脑的会计或护士长。写清楚「找谁、打什么电话、说什么」,才是真可用。

6.3 打印预览页加「调整页边距」滑块,允许护士手动拖动缩放

不同打印机对 PDF 渲染差异极大:A4 纸在 HP LaserJet 上刚好满页,在 Canon 喷墨上右边切掉 1cm。与其让程序员猜,不如把控制权交还给使用者。在report.html中嵌入:

<div class="print-controls"> <label>缩放比例:<span id="scale-value">100%</span></label> <input type="range" min="80" max="120" value="100" id="scale-slider"> </div> <script> document.getElementById('scale-slider').oninput = function() { const scale = this.value; document.getElementById('scale-value').textContent = scale + '%'; document.body.style.transform = 'scale(' + (scale/100) + ')'; }; </script>

后悔药:这个功能上线后,护士说「终于不用拿尺子量打印效果了」。技术的价值不在炫技,而在消解使用者的焦虑——当她能亲手调出完美打印效果时,系统才真正属于她。

希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询