From 67ea2030b53d7dcda0331cceaecc0fc4b13964fc Mon Sep 17 00:00:00 2001 From: = <=> Date: Mon, 1 Jun 2026 11:25:27 +0800 Subject: [PATCH] commit --- .../member/models/MemberApplyRecord.java | 5 + .../service/impl/MemberManageServiceImpl.java | 126 ++++++++++++++++-- .../staffmanage/member/apply/common/info.js | 9 +- .../member/apply/submit/index.html | 79 ++++++++--- .../staffmanage/member/apply/view/index.html | 15 ++- .../staffmanage/member/apply/common/info.js | 10 +- .../member/apply/submit/index.html | 67 +++++++++- 7 files changed, 276 insertions(+), 35 deletions(-) diff --git a/src/main/java/com/budwk/app/zhgh/staffmanage/member/models/MemberApplyRecord.java b/src/main/java/com/budwk/app/zhgh/staffmanage/member/models/MemberApplyRecord.java index 8bf2bed..ad4e3a1 100644 --- a/src/main/java/com/budwk/app/zhgh/staffmanage/member/models/MemberApplyRecord.java +++ b/src/main/java/com/budwk/app/zhgh/staffmanage/member/models/MemberApplyRecord.java @@ -233,6 +233,11 @@ public class MemberApplyRecord extends BaseModel { @ColDefine(type = ColType.VARCHAR, width = 100) private String sign; + @Column + @Comment("照片") + @ColDefine(type = ColType.VARCHAR, width = 500) + private String photo; + @Column @Comment("来源,高校编码") @ColDefine(type = ColType.VARCHAR, width = 50) diff --git a/src/main/java/com/budwk/app/zhgh/staffmanage/member/service/impl/MemberManageServiceImpl.java b/src/main/java/com/budwk/app/zhgh/staffmanage/member/service/impl/MemberManageServiceImpl.java index 54304af..149eb81 100644 --- a/src/main/java/com/budwk/app/zhgh/staffmanage/member/service/impl/MemberManageServiceImpl.java +++ b/src/main/java/com/budwk/app/zhgh/staffmanage/member/service/impl/MemberManageServiceImpl.java @@ -40,6 +40,15 @@ import com.deepoove.poi.XWPFTemplate; import com.deepoove.poi.config.Configure; import lombok.extern.slf4j.Slf4j; import org.ddr.poi.html.HtmlRenderPolicy; +import org.apache.poi.xwpf.usermodel.LineSpacingRule; +import org.apache.poi.util.Units; +import org.apache.poi.xwpf.usermodel.ParagraphAlignment; +import org.apache.poi.xwpf.usermodel.XWPFDocument; +import org.apache.poi.xwpf.usermodel.XWPFParagraph; +import org.apache.poi.xwpf.usermodel.XWPFRun; +import org.apache.poi.xwpf.usermodel.XWPFTable; +import org.apache.poi.xwpf.usermodel.XWPFTableCell; +import org.apache.poi.xwpf.usermodel.XWPFTableRow; import org.nutz.aop.interceptor.ioc.TransAop; import org.nutz.dao.*; import org.nutz.dao.sql.Sql; @@ -52,10 +61,10 @@ import org.nutz.ioc.loader.annotation.IocBean; import org.nutz.lang.Lang; import org.nutz.lang.random.R; import org.nutz.lang.util.NutMap; +import org.openxmlformats.schemas.drawingml.x2006.wordprocessingDrawing.CTInline; +import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTDrawing; import javax.servlet.http.HttpServletResponse; -import java.io.ByteArrayOutputStream; -import java.io.IOException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.time.LocalDateTime; @@ -77,6 +86,13 @@ import java.util.zip.ZipOutputStream; @Slf4j public class MemberManageServiceImpl extends BaseServiceImpl implements MemberCommonService { + private static final int MEMBER_APPLY_PHOTO_WIDTH_PIXEL = 100; + private static final int MEMBER_APPLY_PHOTO_HEIGHT_PIXEL = 140; + private static final int MEMBER_APPLY_PHOTO_WIDTH_EMU = Units.pixelToEMU(MEMBER_APPLY_PHOTO_WIDTH_PIXEL); + private static final int MEMBER_APPLY_PHOTO_HEIGHT_EMU = Units.pixelToEMU(MEMBER_APPLY_PHOTO_HEIGHT_PIXEL); + private static final int MEMBER_APPLY_PHOTO_SIZE_TOLERANCE_EMU = Units.pixelToEMU(2); + private static final double MEMBER_APPLY_PHOTO_PARAGRAPH_LINE_POINT = MEMBER_APPLY_PHOTO_HEIGHT_PIXEL * 0.75D; + @Inject private SysDictService sysDictService; @Inject @@ -624,7 +640,7 @@ public class MemberManageServiceImpl extends BaseServiceImpl implement docData.put("nativePlace", member.getNativePlace()); docData.put("marriage", member.getMarriage()); - docData.put("specialty", StrUtil.isNotBlank(member.getSpecialty()) ? HtmlUtil.cleanHtmlTag(member.getSpecialty()) : ""); + docData.put("specialty", buildMemberApplyDocHtml(member.getSpecialty())); docData.put("jobCategory", member.getJobCategory()); docData.put("idCard", member.getIdCard()); @@ -633,7 +649,8 @@ public class MemberManageServiceImpl extends BaseServiceImpl implement docData.put("arrivalAtSchoolDate", StrUtil.isNotBlank(member.getArrivalAtSchoolDate()) ? DateUtil.parse(member.getArrivalAtSchoolDate()).toString("yyyy-MM-dd") : ""); docData.put("homeAddress", member.getHomeAddress()); - docData.put("personalData", sysOfficeTemplateUtil.convertRichTextToDocText(member.getPersonalData())); + docData.put("personalData", buildMemberApplyDocHtml(member.getPersonalData())); + docData.put("photo", sysOfficeTemplateUtil.createPictureRenderData(MEMBER_APPLY_PHOTO_WIDTH_PIXEL, MEMBER_APPLY_PHOTO_HEIGHT_PIXEL, member.getPhoto())); docData.put("sign", sysOfficeTemplateUtil.createPictureRenderData(member.getSign())); docData.put("applyDateTime", member.getApplyDateTime() != null ? DateUtil.format(member.getApplyDateTime(), "yyyy年MM月dd日") : ""); @@ -702,16 +719,109 @@ public class MemberManageServiceImpl extends BaseServiceImpl implement Configure config = Configure.builder() .bind("personalData", htmlRenderPolicy) + .bind("specialty", htmlRenderPolicy) .build(); try { - XWPFTemplate.compile(sysOfficeTemplateUtil.getTemplate("member_apply_form"), config) - .render(docData) - .write(response.getOutputStream()); - } catch (IOException e) { + XWPFTemplate template = XWPFTemplate.compile(sysOfficeTemplateUtil.getTemplate("member_apply_form"), config) + .render(docData); + resizeMemberApplyPhotoRow(template.getXWPFDocument()); + template.write(response.getOutputStream()); + } catch (Exception e) { log.error("导出会员入会申请表失败,ID: {}, 错误信息: {}", id, e.getMessage()); throw new RuntimeException("导出文件失败", e); } } + /** + * 入会申请导出时统一转为 HTML 片段,历史富文本保留样式,普通文本保留换行。 + */ + private String buildMemberApplyDocHtml(String text) { + if (StrUtil.isBlank(text)) { + return ""; + } + String docText = sysOfficeTemplateUtil.convertRichTextToDocText(text); + if (containsHtmlTag(docText)) { + return docText; + } + String escapeText = HtmlUtil.escape(docText) + .replace("\r\n", "\n") + .replace("\r", "\n") + .replace("\n", "
"); + return "

" + escapeText + "

"; + } + + /** + * 判断内容是否包含 HTML 标签,避免把历史富文本当普通文本转义。 + */ + private boolean containsHtmlTag(String text) { + return StrUtil.isNotBlank(text) && text.matches("(?s).*<\\s*[a-zA-Z][^>]*>.*"); + } + + /** + * Keep the member photo row tall enough so Word does not clip the default inline picture. + */ + private void resizeMemberApplyPhotoRow(XWPFDocument document) { + if (document == null) { + return; + } + for (XWPFTable table : document.getTables()) { + for (XWPFTableRow row : table.getRows()) { + adjustMemberApplyPhotoParagraph(row); + } + } + } + + /** + * Match only the two-inch member photo by rendered picture size, avoiding signature pictures. + */ + private boolean adjustMemberApplyPhotoParagraph(XWPFTableRow row) { + if (row == null) { + return false; + } + boolean found = false; + for (XWPFTableCell cell : row.getTableCells()) { + for (XWPFParagraph paragraph : cell.getParagraphs()) { + for (XWPFRun run : paragraph.getRuns()) { + if (runContainsMemberApplyPhoto(run)) { + adjustMemberApplyPhotoParagraphStyle(paragraph); + found = true; + } + } + } + } + return found; + } + + /** + * Keep the inline photo visible without changing the merged cell vertical anchor. + */ + private void adjustMemberApplyPhotoParagraphStyle(XWPFParagraph paragraph) { + paragraph.setAlignment(ParagraphAlignment.CENTER); + paragraph.setSpacingBefore(0); + paragraph.setSpacingAfter(0); + paragraph.setSpacingBeforeLines(0); + paragraph.setSpacingAfterLines(0); + paragraph.setSpacingBetween(MEMBER_APPLY_PHOTO_PARAGRAPH_LINE_POINT, LineSpacingRule.AT_LEAST); + } + + private boolean runContainsMemberApplyPhoto(XWPFRun run) { + if (run == null || run.getCTR() == null) { + return false; + } + for (CTDrawing drawing : run.getCTR().getDrawingArray()) { + for (CTInline inline : drawing.getInlineArray()) { + if (inline.getExtent() != null && isMemberApplyPhotoSize(inline.getExtent().getCx(), inline.getExtent().getCy())) { + return true; + } + } + } + return false; + } + + private boolean isMemberApplyPhotoSize(long widthEmu, long heightEmu) { + return Math.abs(widthEmu - MEMBER_APPLY_PHOTO_WIDTH_EMU) <= MEMBER_APPLY_PHOTO_SIZE_TOLERANCE_EMU + && Math.abs(heightEmu - MEMBER_APPLY_PHOTO_HEIGHT_EMU) <= MEMBER_APPLY_PHOTO_SIZE_TOLERANCE_EMU; + } + } diff --git a/src/main/resources/views/platform/zhgh/staffmanage/member/apply/common/info.js b/src/main/resources/views/platform/zhgh/staffmanage/member/apply/common/info.js index 2dd8853..ec25b7a 100644 --- a/src/main/resources/views/platform/zhgh/staffmanage/member/apply/common/info.js +++ b/src/main/resources/views/platform/zhgh/staffmanage/member/apply/common/info.js @@ -54,14 +54,19 @@ const INFO = { -
+
暂无
-
+
暂无
+ + + - + - + + + + + + + @@ -259,7 +274,29 @@ layout("/layouts/platform.html"){ sex: [{ required: false, message: "必填", trigger: ["change", "blur"] }], isVoluntary: [{ required: true, message: "必填", trigger: ["change", "blur"] }], sign: [{ required: true, message: "必填", trigger: ["change", "blur"] }], + photo: [{ required: true, message: "请上传照片", trigger: ["change", "blur"] }], homeAddress: [{ required: true, message: "必填", trigger: ["change", "blur"] }], + families: [{ + validator: (rule, value, callback) => { + // 提交时家庭主要成员至少填写一条,并校验核心成员信息。 + if (!value || value.length === 0) { + callback(new Error("请添加家庭主要成员")) + return + } + const hasIncomplete = value.some(item => { + item = item || {} + return !String(item.relation || "").trim() + || !String(item.name || "").trim() + || !String(item.unit || "").trim() + }) + if (hasIncomplete) { + callback(new Error("请完善家庭主要成员的关系、姓名、工作单位")) + return + } + callback() + }, + trigger: ["change", "blur"] + }], personalData: [{ required: true, message: "必填", trigger: ["change", "blur"] }], specialty: [{ required: true, message: "必填", trigger: ["change", "blur"] }], marriage: [{ required: true, message: "必填", trigger: ["change", "blur"] }], @@ -388,23 +425,27 @@ layout("/layouts/platform.html"){ }) }, onFinishTask() { - this.$confirm("您确定要提交吗?", "提示", { - confirmButtonText: "确定", - cancelButtonText: "取消", - type: "warning" - }).then(() => { - const loading = createLoading() - this.$axios.post("/platform/member/apply/submit/submitAgain", { - data: JSON.stringify(this.formData), - taskId: this.taskId - }).then(res => { - if (res.code === 0) { - this.$message.success(res.msg) - commonUtil.pjaxPush("/platform/member/apply/mine") - } - }).finally(() => { - loading.close() - }) + this.$refs.formRef.validate((valid) => { + if (valid) { + this.$confirm("您确定要提交吗?", "提示", { + confirmButtonText: "确定", + cancelButtonText: "取消", + type: "warning" + }).then(() => { + const loading = createLoading() + this.$axios.post("/platform/member/apply/submit/submitAgain", { + data: JSON.stringify(this.formData), + taskId: this.taskId + }).then(res => { + if (res.code === 0) { + this.$message.success(res.msg) + commonUtil.pjaxPush("/platform/member/apply/mine") + } + }).finally(() => { + loading.close() + }) + }) + } }) }, async init() { diff --git a/src/main/resources/views/platform/zhgh/staffmanage/member/apply/view/index.html b/src/main/resources/views/platform/zhgh/staffmanage/member/apply/view/index.html index 8f8b6c5..2879927 100644 --- a/src/main/resources/views/platform/zhgh/staffmanage/member/apply/view/index.html +++ b/src/main/resources/views/platform/zhgh/staffmanage/member/apply/view/index.html @@ -40,8 +40,19 @@ - -
+ +
+ 暂无 +
+ +
+ 暂无 +
+ + + 暂无 diff --git a/src/main/resources/views/platform/zhghh5/staffmanage/member/apply/common/info.js b/src/main/resources/views/platform/zhghh5/staffmanage/member/apply/common/info.js index 9e63508..6bbd52e 100644 --- a/src/main/resources/views/platform/zhghh5/staffmanage/member/apply/common/info.js +++ b/src/main/resources/views/platform/zhghh5/staffmanage/member/apply/common/info.js @@ -63,18 +63,24 @@ const INFO = { + + + + - + - + + + + + + + @@ -401,6 +438,9 @@ layout("/layouts/platform_h5.html"){ onSubmit() { this.$refs.formRef.validate().then(() => { + if (!this.validateFamilies()) { + return + } if (!this.formData.personalData) { this.$toast.fail("请填写个人学习及工作经历") return @@ -442,6 +482,9 @@ layout("/layouts/platform_h5.html"){ onFinishTask() { this.$refs.formRef.validate().then(() => { + if (!this.validateFamilies()) { + return + } if (!this.formData.personalData) { this.$toast.fail("请填写个人学习及工作经历") return @@ -482,6 +525,26 @@ layout("/layouts/platform_h5.html"){ }) }, + validateFamilies() { + // 提交时家庭主要成员至少填写一条,并校验核心成员信息。 + const families = this.formData.families || [] + if (families.length === 0) { + this.$toast.fail("请添加家庭主要成员") + return false + } + const hasIncomplete = families.some(item => { + item = item || {} + return !String(item.relation || "").trim() + || !String(item.name || "").trim() + || !String(item.unit || "").trim() + }) + if (hasIncomplete) { + this.$toast.fail("请完善家庭主要成员的关系、姓名、工作单位") + return false + } + return true + }, + onDateConfirm(value) { this.formData.birthday = value this.showDatePicker = false