医疗互助补助模块
This commit is contained in:
+91
-3
@@ -37,8 +37,15 @@ import org.apache.poi.ss.usermodel.Sheet;
|
||||
import org.apache.poi.ss.usermodel.VerticalAlignment;
|
||||
import org.apache.poi.ss.usermodel.Workbook;
|
||||
import org.apache.poi.ss.util.CellRangeAddress;
|
||||
import com.deepoove.poi.data.ByteArrayPictureRenderData;
|
||||
import com.deepoove.poi.data.PictureRenderData;
|
||||
import com.deepoove.poi.data.PictureType;
|
||||
import com.deepoove.poi.data.style.PictureStyle;
|
||||
import com.deepoove.poi.XWPFTemplate;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
@@ -141,6 +148,7 @@ public class MedicalMutualAidApplyServiceImpl extends BaseServiceImpl<MutualAppl
|
||||
public NutMap exportMineApplyForm(Long applyId) {
|
||||
MutualApply apply = applyDetail(applyId);
|
||||
assertMineApplyOwner(apply);
|
||||
assertMineApplyExportStatus(apply);
|
||||
Map<String, Object> docData = buildMineApplyFormDocData(apply);
|
||||
String fileName = StrUtil.blankToDefault(apply.getUserName(), "申请人") + "大病医疗互助金申请表.docx";
|
||||
try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {
|
||||
@@ -801,12 +809,83 @@ public class MedicalMutualAidApplyServiceImpl extends BaseServiceImpl<MutualAppl
|
||||
docData.put("signDay", formatDate(signDate, "dd"));
|
||||
docData.put("remark", emptyToBlank(apply.getRemark()));
|
||||
docData.put("statusText", buildStatusText(apply.getStatus()));
|
||||
// 签字图片由 pc-signature 组件保存为文件下载地址,模板中使用 {{@sign}} 渲染图片。
|
||||
docData.put("sign", StrUtil.isBlank(apply.getSignFileId()) ? null
|
||||
: sysOfficeTemplateUtil.createPictureRenderData(90, 35, apply.getSignFileId()));
|
||||
// 签字图片导出前裁掉四周白边,模板中使用 {{@sign}} 渲染图片。
|
||||
docData.put("sign", buildTrimmedSignPicture(apply.getSignFileId()));
|
||||
return docData;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建申请人签字图片,优先裁剪签字图片四周白边;图片异常时回退为原图,避免影响申请单导出。
|
||||
*/
|
||||
private PictureRenderData buildTrimmedSignPicture(String signFileId) {
|
||||
if (StrUtil.isBlank(signFileId)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
byte[] imageBytes = sysOfficeTemplateUtil.getFileBytesByUrl(signFileId);
|
||||
byte[] trimmedBytes = trimImageWhiteBorder(imageBytes);
|
||||
ByteArrayPictureRenderData pictureRenderData = new ByteArrayPictureRenderData(trimmedBytes, PictureType.PNG);
|
||||
PictureStyle pictureStyle = new PictureStyle();
|
||||
pictureStyle.setWidth(90);
|
||||
pictureStyle.setHeight(35);
|
||||
pictureRenderData.setPictureStyle(pictureStyle);
|
||||
return pictureRenderData;
|
||||
} catch (Exception e) {
|
||||
return sysOfficeTemplateUtil.createPictureRenderData(90, 35, signFileId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 裁剪图片四周白色或透明空白区域,保留真实签字笔画区域用于 Word 模板渲染。
|
||||
*/
|
||||
private byte[] trimImageWhiteBorder(byte[] imageBytes) throws IOException {
|
||||
if (imageBytes == null || imageBytes.length == 0) {
|
||||
throw new IOException("签字图片为空");
|
||||
}
|
||||
BufferedImage sourceImage = ImageIO.read(new ByteArrayInputStream(imageBytes));
|
||||
if (sourceImage == null) {
|
||||
throw new IOException("签字图片格式不支持");
|
||||
}
|
||||
int minX = sourceImage.getWidth();
|
||||
int minY = sourceImage.getHeight();
|
||||
int maxX = -1;
|
||||
int maxY = -1;
|
||||
for (int y = 0; y < sourceImage.getHeight(); y++) {
|
||||
for (int x = 0; x < sourceImage.getWidth(); x++) {
|
||||
if (!isBlankSignPixel(sourceImage.getRGB(x, y))) {
|
||||
minX = Math.min(minX, x);
|
||||
minY = Math.min(minY, y);
|
||||
maxX = Math.max(maxX, x);
|
||||
maxY = Math.max(maxY, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (maxX < minX || maxY < minY) {
|
||||
return imageBytes;
|
||||
}
|
||||
int padding = 2;
|
||||
int cropX = Math.max(minX - padding, 0);
|
||||
int cropY = Math.max(minY - padding, 0);
|
||||
int cropWidth = Math.min(maxX + padding, sourceImage.getWidth() - 1) - cropX + 1;
|
||||
int cropHeight = Math.min(maxY + padding, sourceImage.getHeight() - 1) - cropY + 1;
|
||||
BufferedImage trimmedImage = sourceImage.getSubimage(cropX, cropY, cropWidth, cropHeight);
|
||||
try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {
|
||||
ImageIO.write(trimmedImage, "png", byteArrayOutputStream);
|
||||
return byteArrayOutputStream.toByteArray();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断签字图片像素是否属于可裁剪空白,兼容透明底和接近白色的扫描底色。
|
||||
*/
|
||||
private boolean isBlankSignPixel(int rgb) {
|
||||
int alpha = (rgb >> 24) & 0xff;
|
||||
int red = (rgb >> 16) & 0xff;
|
||||
int green = (rgb >> 8) & 0xff;
|
||||
int blue = rgb & 0xff;
|
||||
return alpha <= 15 || (red >= 245 && green >= 245 && blue >= 245);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验“我的申请”导出只能访问当前登录人的申请,避免通过 URL 参数导出他人申请。
|
||||
*/
|
||||
@@ -823,6 +902,15 @@ public class MedicalMutualAidApplyServiceImpl extends BaseServiceImpl<MutualAppl
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验申请单导出状态,只有互助会已审核通过的数据才允许生成正式申请单。
|
||||
*/
|
||||
private void assertMineApplyExportStatus(MutualApply apply) {
|
||||
if (apply == null || apply.getStatus() == null || apply.getStatus() != 1) {
|
||||
throw new BaseException("只有已审核的申请才能导出申请单");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将疾病名称和住院说明合并到模板“何时何病伤住何医院”栏,保留原 Word 模板结构。
|
||||
*/
|
||||
|
||||
Binary file not shown.
Binary file not shown.
+4
-3
@@ -13,7 +13,8 @@ layout("/layouts/platform.html"){
|
||||
.mine-table.el-table th { background: #fafafa; color: #303133; font-weight: 700; }
|
||||
.mine-table.el-table td, .mine-table.el-table th { padding: 11px 0; }
|
||||
.mine-actions { display: flex; align-items: center; justify-content: center; gap: 8px; }
|
||||
.mine-actions .el-button { min-width: 54px; padding: 7px 14px; border-radius: 2px; margin-left: 0; }
|
||||
.mine-actions .el-button { min-width: 54px; padding: 7px 12px; border-radius: 2px; margin-left: 0; }
|
||||
.mine-actions .export-apply-btn { min-width: 86px; }
|
||||
.mine-pager { display: flex; justify-content: flex-start; align-items: center; padding-top: 12px; flex-shrink: 0; }
|
||||
.mine-pager .total-text { margin-right: 18px; color: #303133; }
|
||||
</style>
|
||||
@@ -58,11 +59,11 @@ layout("/layouts/platform.html"){
|
||||
<el-tag v-else type="warning" size="mini">未审核</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="280" align="center" fixed="right">
|
||||
<el-table-column label="操作" width="310" align="center" fixed="right">
|
||||
<template slot-scope="{row}">
|
||||
<div class="mine-actions">
|
||||
<el-button @click="onView(row)" size="mini" type="primary">查看</el-button>
|
||||
<el-button @click="onExportApplyForm(row)" size="mini" type="primary">导出申请单</el-button>
|
||||
<el-button v-if="isAudited(row)" class="export-apply-btn" @click="onExportApplyForm(row)" size="mini" type="primary">导出申请单</el-button>
|
||||
<el-button v-if="isPending(row)" @click="onEdit(row)" size="mini" type="primary">编辑</el-button>
|
||||
<el-button v-if="isPending(row)" @click="onDeleteTip" size="mini" type="danger">删除</el-button>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user