处方签的模板填充+PDF签名——一次医疗场景的打印设计

处方签的模板填充+PDF签名——一次医疗场景的打印设计

处方签的模板填充+PDF签名——一次医疗场景的打印设计

文章目录

  • 处方签的模板填充+PDF签名——一次医疗场景的打印设计
    • 一、处方签不是普通的打印页面
    • 二、模板填充:用$占位符替代硬编码
    • 三、合并单元格的边框陷阱
    • 四、签名的位置:不是"居中"那么简单
    • 五、模板驱动的完整链路
    • 六、为什么不做HTML转PDF
    • 七、结语

一、处方签不是普通的打印页面

医疗系统里有一种特殊的东西叫"处方签"。它不是普通的表格打印——首先,它有法律效力。一张处方签上必须有医师签名和药师签名,缺一个就是废纸。其次,它格式固定。药监局规定了处方签的版式和必填字段,不能随便改。

所以,处方签的打印流程不是"在屏幕上画几个框",而是三步:

① XLS模板 → ② 填入患者+药品数据 → ③ 签上医师和药师的名字 → PDF输出

这个流程的每一步都有坑。我们一步步说。

二、模板填充:用$占位符替代硬编码

处方签的格式是固定的,但内容每次不同——患者姓名、药品名、用法用量。我们的做法是做一个Excel模板文件,里面放占位符。比如$xm代表姓名,$xb代表性别:

处方签模板.xls 内容: ┌──────────────────────────────┐ │ 患者姓名:$xm │ │ 性别:$xb │ │ 诊断:$zd │ │ 药品:$m1 | 用法:$u1 │ │ 药品:$m2 | 用法:$u2 │ └──────────────────────────────┘

代码里的print()方法遍历Excel的每个单元格,遇到以$开头的就去Map里找对应的值:

if("$".equals(firstChar)){colName=temp.substring(1);colValue=String.valueOf(map.get(colName));// 替换单元格内容label=newLabel(j,x,colValue,...);}

这个设计的巧妙之处在于:处方签格式变了,改Excel模板就行,一行代码不用动。药监局改了处方签版式?把模板重新排版,代码零改动。

三、合并单元格的边框陷阱

Excel模板里经常有合并单元格——比如"药品"那栏,名称和用法两大列上面有个大标题"处方明细"。从Excel转到PDF时,合并单元格的边框处理是最容易出错的地方。

问题在哪?Excel的合并单元格只有一个"视觉上"的大格子。实际上,内部每个被合并的单元格都还有自己的格式。边框不会被自动"继承"——合并后的大格子,下边框和右边框可能需要从被合并的最远单元格上取。

for(Rangeobj:mergedCells){intrs=obj.getTopLeft().getRow();intre=obj.getBottomRight().getRow();intcs=obj.getTopLeft().getColumn();intce=obj.getBottomRight().getColumn();if(当前单元格是合并格的左上角){if(rs<re){// 下边线要从最下面那行的同事格取}if(cs<ce){// 右边线要从最右边那列的同事格取}}}

然后是一个长长的 if-else 链条,逐个组合判断哪条边该显示、哪条该隐藏——disableBorderSide(15)隐藏全部四边,disableBorderSide(2)只隐藏下边……一共15种组合。

这段代码看起来像是机器生成的。不是。它就是手工一行行写的——因为当初被合并单元格的边框问题折腾了无数次,每个组合都是在实际打印中碰到的真实错误。

四、签名的位置:不是"居中"那么简单

处方签上需要两个签名:医师在右下角,药师在左下角

这不是CSS里"float:right"能搞定的事。iText的PDF操作是基于坐标的——你得告诉它签名图片精确的 (x, y) 坐标。

// 先加医师签名——右下角addWaterMarkIncludeWords(filename1,filename2,fileqm1,385,240);// 再加药师签名——左下角addWaterMarkIncludeWords(filename2,filename,fileqm2,110,240);

addWaterMarkIncludeWords做三件事:

  1. 打开已有PDF(PdfReader
  2. 创建PdfStamper(往已有PDF上盖内容)
  3. 把签名图片放在每一页的指定位置
  4. 删掉原文件——这是关键,签名后的PDF覆盖原PDF,不留无签名版本
PdfReaderreader=newPdfReader(inputFile);PdfStamperstamper=newPdfStamper(reader,newFileOutputStream(outputFile));Imageimage=Image.getInstance(imageFilePath);image.setAbsolutePosition(width,height);image.scalePercent(20f);for(inti=1;i<total;i++){under=stamper.getUnderContent(i);under.addImage(image);}stamper.close();newFile(inputFile).delete();// 删除未签名版本

为什么签名必须放在UnderContent(内容之下)而不是OverContent(内容之上)?因为处方签的文字不能被签名图片遮挡。放下面,签名像是印在纸上的底纹——能看清签名,也能看清上面的处方内容。

五、模板驱动的完整链路

一条完整的处方签打印链路是这样的:

getfile(map, 医师签名.jpg, 药师签名.jpg) ├── print(map, "处方单.xls") → 填充模板,生成ByteArrayOutputStream ├── Workbook(流) → JXL读回内存 ├── xlsToPDF(workbook, "v", f1) → 转PDF(纵向) ├── addWaterMark(医师签名) → 盖医师签名 ├── addWaterMark(药师签名) → 盖药师签名 └── 返回pdf文件路径

模板是Excel,数据是Map,签名是图片,输出是PDF。每层都是独立步骤,改签名位置不需要动模板,改模板格式不需要动数据,改数据字段不需要动签名。

六、为什么不做HTML转PDF

很多人会问:用HTML画处方签,然后转PDF不是更灵活吗?

两个原因。第一,在医院里,Excel就是行业语言。药房的人、医保中心的人、药监局的检查员——他们用Excel排版处方签比用HTML熟练得多。第二,Excel转PDF能保住原始格式——字体、边框、对齐、合并单元格——一行一行地从JXL的CellFormat里抠出来,再用iText还原回去。HTML转PDF看似方便,但CSS的渲染结果在不同浏览器之间天差地别,兼容性问题更多。

选择Excel作为模板格式,本质上是一个**“让业务人员自己能维护”**的决策——改处方签版式不用找开发,自己调Excel就行。

七、结语

处方签的打印功能,代码不到700行。但它是医疗信息化里极少数**“不容忍任何格式错误”**的功能——边框差一条线,被药监局退回来;签名位置歪了3毫米,处方作废;字体大小不对,药品名称看不清,发药发错。

这套模板+填充+签名的三段式设计,核心是隔离变化:版式变化改Excel,数据变化改代码,签名变化改坐标。三个维度各自独立,互不干扰。

在今天看来这套方案没什么特别——模板引擎+PDF生成,任何一个报表引擎都能做。但在没有模板引擎、没有报表工具的2010年代,自己把JXL和iText串起来,就是一个完整的打印系统。