diff --git a/application-tenant/tenant-admin/pom.xml b/application-tenant/tenant-admin/pom.xml
index 9c94759..867b72e 100644
--- a/application-tenant/tenant-admin/pom.xml
+++ b/application-tenant/tenant-admin/pom.xml
@@ -141,7 +141,7 @@
apelet
common-flow-online
- 1.0.0
+ 1.0.2
apelet
@@ -174,6 +174,17 @@
1.0.0
+
+ org.docx4j
+ docx4j-JAXB-ReferenceImpl
+ 8.3.9
+
+
+ org.docx4j
+ docx4j-export-fo
+ 8.3.9
+
+
diff --git a/application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/tenant/controller/DocxTemplateController.java b/application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/tenant/controller/DocxTemplateController.java
deleted file mode 100644
index 1c3af35..0000000
--- a/application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/tenant/controller/DocxTemplateController.java
+++ /dev/null
@@ -1,141 +0,0 @@
-
-package apelet.tenantadmin.tenant.controller;
-
-import apelet.common.core.annotation.MyRequestBody;
-import apelet.common.core.exception.MyRuntimeException;
-import apelet.common.core.object.ObjectValue;
-import apelet.common.generator.utils.OrmGenDataSourceUtil;
-import cn.hutool.core.io.IoUtil;
-import org.apache.poi.xwpf.usermodel.*;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.core.io.ClassPathResource;
-import org.springframework.web.bind.annotation.*;
-
-import javax.servlet.http.HttpServletResponse;
-import java.io.*;
-import java.net.URLEncoder;
-import java.nio.charset.StandardCharsets;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-@RestController
-@RequestMapping("/api/template")
-public class DocxTemplateController {
-
-
- @Autowired
- private OrmGenDataSourceUtil ormGenDataSourceUtil;
-
- private static final Pattern PLACEHOLDER_PATTERN = Pattern.compile("\\$\\{(.+?)\\}");
-
- /**
- * 替换字符串中 ${key},JDK8兼容
- */
- private String replaceContent(String text, Map dataMap) {
- if (text == null) return "";
- Matcher matcher = PLACEHOLDER_PATTERN.matcher(text);
- StringBuffer sb = new StringBuffer();
- while (matcher.find()) {
- String key = matcher.group(1).trim();
- Object val = dataMap.get(key);
- String realVal = val == null ? "" : val.toString();
- matcher.appendReplacement(sb, Matcher.quoteReplacement(realVal));
- }
- matcher.appendTail(sb);
- return sb.toString();
- }
-
- @GetMapping("/downloadDocx")
- public void downloadDocx(@RequestParam("id") String quotationId, HttpServletResponse response) {
- XWPFDocument document = null;
- InputStream templateIs = null;
- OutputStream out = null;
- try {
- // 1. 查数据
- ObjectValue objectValue = ormGenDataSourceUtil.queryOne("quotation", quotationId);
- Map hashMap = new HashMap<>();
- hashMap.put("name", objectValue.getString("name"));
- hashMap.put("number", objectValue.getString("number"));
- hashMap.put("create_time", objectValue.getString("create_time"));
- hashMap.put("qty", objectValue.getObjectValue("qty"));
- ObjectValue managerperson = objectValue.getObjectValue("managerperson");
- if(managerperson != null){
- managerperson = ormGenDataSourceUtil.queryOne(managerperson.getTableName(), managerperson.getString("id"));
- hashMap.put("managerperson",1);
- }
- // 2. 读模板
- File file = new File("D:\\work\\xykj-project\\xhgl\\zz-resource\\quotation_template.docx");
- templateIs = new FileInputStream(file);
- document = new XWPFDocument(templateIs);
- // 3. 替换占位符
- replaceParagraph(document, hashMap);
- replaceTable(document, hashMap);
- // 4. 设置响应头 —— 关键!
- response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document");
- response.setCharacterEncoding("UTF-8");
- String fileName = URLEncoder.encode("报价单.docx", StandardCharsets.UTF_8.name())
- .replaceAll("\\+", "%20");
- // 兼容各浏览器的文件名写法
- response.setHeader("Content-Disposition",
- "attachment; filename=\"" + fileName + "\"; filename*=UTF-8''" + fileName);
- response.setHeader("Pragma", "no-cache");
- response.setHeader("Cache-Control", "no-cache");
- response.setDateHeader("Expires", 0);
- // 5. 写出
- out = response.getOutputStream();
- document.write(out);
- out.flush();
- } catch (Exception e) {
- e.printStackTrace();
- throw new MyRuntimeException("导出失败: " + e.getMessage());
- } finally {
- IoUtil.close(document);
- IoUtil.close(templateIs);
- IoUtil.close(out);
- }
- }
-
-
-
-
- private void replaceParagraph(XWPFDocument document, Map dataMap) {
- for (XWPFParagraph paragraph : document.getParagraphs()) {
- String text = paragraph.getText();
- Matcher matcher = PLACEHOLDER_PATTERN.matcher(text);
- if (!matcher.find()) {
- continue;
- }
- //清空原有run
- for (XWPFRun run : paragraph.getRuns()) {
- run.setText("", 0);
- }
- String newText = replaceContent(text, dataMap);
- paragraph.createRun().setText(newText);
- }
- }
-
- private void replaceTable(XWPFDocument document, Map dataMap) {
- for (XWPFTable table : document.getTables()) {
- for (XWPFTableRow row : table.getRows()) {
- for (XWPFTableCell cell : row.getTableCells()) {
- for (XWPFParagraph paragraph : cell.getParagraphs()) {
- String text = paragraph.getText();
- Matcher matcher = PLACEHOLDER_PATTERN.matcher(text);
- if (!matcher.find()) {
- continue;
- }
- for (XWPFRun run : paragraph.getRuns()) {
- run.setText("", 0);
- }
- String newText = replaceContent(text, dataMap);
- paragraph.createRun().setText(newText);
- }
- }
- }
- }
- }
-
-
-}
diff --git a/application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/upms/controller/DocxTemplateController.java b/application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/upms/controller/DocxTemplateController.java
new file mode 100644
index 0000000..26cd9d5
--- /dev/null
+++ b/application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/upms/controller/DocxTemplateController.java
@@ -0,0 +1,353 @@
+package apelet.tenantadmin.upms.controller;
+
+import apelet.common.core.exception.MyRuntimeException;
+import apelet.common.core.object.ObjectCollection;
+import apelet.common.core.object.ObjectValue;
+import apelet.common.core.object.ResponseResult;
+import apelet.common.generator.utils.OrmGenDataSourceUtil;
+import apelet.common.online.service.OnlineDictService;
+import apelet.common.orm.impl.Filter;
+import apelet.common.orm.impl.Selector;
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.io.IoUtil;
+import cn.hutool.poi.excel.ExcelReader;
+import cn.hutool.poi.excel.ExcelUtil;
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
+import org.apache.poi.xwpf.usermodel.*;
+import org.docx4j.Docx4J;
+import org.docx4j.fonts.IdentityPlusMapper;
+import org.docx4j.fonts.PhysicalFonts;
+import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.*;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+@RestController
+@RequestMapping("/api/template")
+public class DocxTemplateController {
+
+ @Autowired
+ private OrmGenDataSourceUtil ormGenDataSourceUtil;
+ @Autowired
+ private OnlineDictService onlineDictService;
+
+ private static final Pattern PLACEHOLDER_PATTERN = Pattern.compile("#\\{(.+?)\\}");
+
+ private String replaceContent(String text, Map dataMap) {
+ if (text == null) return "";
+ Matcher matcher = PLACEHOLDER_PATTERN.matcher(text);
+ StringBuffer sb = new StringBuffer();
+ while (matcher.find()) {
+ String key = matcher.group(1).trim();
+ if ("personimage".equals(key)) {
+ matcher.appendReplacement(sb, Matcher.quoteReplacement(matcher.group(0)));
+ continue;
+ }
+ Object val = dataMap.get(key);
+ String realVal = val == null ? "" : val.toString();
+ matcher.appendReplacement(sb, Matcher.quoteReplacement(realVal));
+ }
+ matcher.appendTail(sb);
+ return sb.toString();
+ }
+
+
+ @GetMapping("/downloadDocx")
+ public void downloadDocx(@RequestParam("id") String quotationId, @RequestParam("userId") String userId, HttpServletResponse response) {
+ XWPFDocument document = null;
+ InputStream templateIs = null;
+ OutputStream out = null;
+ ByteArrayOutputStream pdfOut = null;
+ try {
+ // 1. 查数据
+ ObjectValue objectValue = ormGenDataSourceUtil.queryOne("quotation", quotationId);
+ ObjectValue user = ormGenDataSourceUtil.queryOne("xy_sys_user", userId);
+ Map dictMap = onlineDictService.getDictMap(2052225870772310016L);
+ Map hashMap = new HashMap<>();
+ hashMap.put("name", objectValue.getString("name"));
+ hashMap.put("type", dictMap.get(String.valueOf(objectValue.get("type", ""))));
+ hashMap.put("number", objectValue.getString("number"));
+ hashMap.put("create_time", objectValue.getString("create_time").substring(0, 10));
+ hashMap.put("qty", objectValue.getString("qty"));
+
+ String signatureImage = user.getString("signature_image");
+ String imageUrl = getUrl(signatureImage);
+ byte[] imageBytes = null;
+ if (StringUtils.isNotEmpty(imageUrl)) {
+ imageBytes = downloadImage(imageUrl);
+ }
+ ObjectValue managerperson = objectValue.getObjectValue("managerperson");
+ if (managerperson != null) {
+ managerperson = ormGenDataSourceUtil.queryOne(managerperson.getTableName(), managerperson.getString("id"));
+ hashMap.put("managerperson", managerperson.getString("show_name"));
+ }
+ // 2. 读模板
+ String savePath = System.getProperty("user.dir") + "\\zz-resource\\";
+ File file = new File(savePath + "quotation_template.docx");
+ templateIs = Files.newInputStream(file.toPath());
+ document = new XWPFDocument(templateIs);
+ // 3. 替换文本占位符
+ replaceParagraph(document, hashMap);
+ replaceTable(document, hashMap);
+ // 4. 替换图片占位符
+ replaceImageInParagraphs(document, "personimage", imageBytes, 100, 100);
+ replaceImageInTables(document, "personimage", imageBytes, 100, 100);
+ // 5. 将填充后的 DOCX 转为 PDF(使用 docx4j)
+ ByteArrayOutputStream docxOut = new ByteArrayOutputStream();
+ document.write(docxOut);
+ byte[] docxBytes = docxOut.toByteArray();
+ docxOut.close();
+
+ WordprocessingMLPackage wordMLPackage = WordprocessingMLPackage.load(new ByteArrayInputStream(docxBytes));
+ PhysicalFonts.discoverPhysicalFonts();
+ wordMLPackage.setFontMapper(new IdentityPlusMapper());
+ pdfOut = new ByteArrayOutputStream();
+ Docx4J.toPDF(wordMLPackage, pdfOut);
+ byte[] pdfBytes = pdfOut.toByteArray();
+
+ // 6. 设置响应头
+ response.setContentType("application/pdf");
+ response.setCharacterEncoding("UTF-8");
+ String fileName = URLEncoder.encode("报价单.pdf", StandardCharsets.UTF_8.name())
+ .replaceAll("\\+", "%20");
+ response.setHeader("Content-Disposition",
+ "attachment; filename=\"" + fileName + "\"; filename*=UTF-8''" + fileName);
+ response.setHeader("Pragma", "no-cache");
+ response.setHeader("Cache-Control", "no-cache");
+ response.setDateHeader("Expires", 0);
+ // 7. 写出 PDF
+ out = response.getOutputStream();
+ out.write(pdfBytes);
+ out.flush();
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw new MyRuntimeException("导出失败: " + e.getMessage());
+ } finally {
+ IoUtil.close(pdfOut);
+ IoUtil.close(document);
+ IoUtil.close(templateIs);
+ IoUtil.close(out);
+ }
+ }
+
+
+ String getUrl(String signatureImage) {
+ if (StringUtils.isEmpty(signatureImage)) {
+ return "";
+ }
+ JSONArray jsonArray = JSON.parseArray(signatureImage);
+ if (jsonArray.isEmpty()) {
+ return "";
+ }
+ JSONObject jsonObject = jsonArray.getJSONObject(0);
+ String baseUrl = jsonObject.getString("baseUrl");
+ String uploadPath = jsonObject.getString("uploadPath");
+ String filename = jsonObject.getString("filename");
+
+ return baseUrl + "/" + uploadPath + "/" + filename;
+ }
+
+ private void replaceParagraph(XWPFDocument document, Map dataMap) {
+ for (XWPFParagraph paragraph : document.getParagraphs()) {
+ String text = paragraph.getText();
+ Matcher matcher = PLACEHOLDER_PATTERN.matcher(text);
+ if (!matcher.find()) {
+ continue;
+ }
+ for (XWPFRun run : paragraph.getRuns()) {
+ run.setText("", 0);
+ }
+ String newText = replaceContent(text, dataMap);
+ paragraph.createRun().setText(newText);
+ }
+ }
+
+ private void replaceTable(XWPFDocument document, Map dataMap) {
+ for (XWPFTable table : document.getTables()) {
+ for (XWPFTableRow row : table.getRows()) {
+ for (XWPFTableCell cell : row.getTableCells()) {
+ for (XWPFParagraph paragraph : cell.getParagraphs()) {
+ String text = paragraph.getText();
+ Matcher matcher = PLACEHOLDER_PATTERN.matcher(text);
+ if (!matcher.find()) {
+ continue;
+ }
+ for (XWPFRun run : paragraph.getRuns()) {
+ run.setText("", 0);
+ }
+ String newText = replaceContent(text, dataMap);
+ paragraph.createRun().setText(newText);
+ }
+ }
+ }
+ }
+ }
+
+ private byte[] downloadImage(String imageUrl) throws IOException {
+ URL url = new URL(imageUrl);
+ HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+ conn.setRequestMethod("GET");
+ conn.setConnectTimeout(5000);
+ conn.setReadTimeout(10000);
+ InputStream is = conn.getInputStream();
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ byte[] buffer = new byte[4096];
+ int len;
+ while ((len = is.read(buffer)) != -1) {
+ baos.write(buffer, 0, len);
+ }
+ IoUtil.close(is);
+ return baos.toByteArray();
+ }
+
+
+ private void replaceImageInParagraphs(XWPFDocument document, String placeholderKey, byte[] imageBytes, int width, int height) throws IOException, InvalidFormatException {
+ String placeholder = "#{" + placeholderKey + "}";
+ for (XWPFParagraph paragraph : document.getParagraphs()) {
+ String text = paragraph.getText();
+ if (text != null && text.contains(placeholder)) {
+ List runs = paragraph.getRuns();
+ for (int i = runs.size() - 1; i >= 0; i--) {
+ XWPFRun run = runs.get(i);
+ String runText = run.getText(0);
+ if (runText != null && runText.contains(placeholder)) {
+ run.setText("", 0);
+ if (imageBytes != null) {
+ run.addPicture(new ByteArrayInputStream(imageBytes), XWPFDocument.PICTURE_TYPE_PNG, placeholderKey + ".png",
+ width * 9525, height * 9525);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private void replaceImageInTables(XWPFDocument document, String placeholderKey, byte[] imageBytes, int width, int height) throws IOException, InvalidFormatException {
+ String placeholder = "#{" + placeholderKey + "}";
+ for (XWPFTable table : document.getTables()) {
+ for (XWPFTableRow row : table.getRows()) {
+ for (XWPFTableCell cell : row.getTableCells()) {
+ for (XWPFParagraph paragraph : cell.getParagraphs()) {
+ String text = paragraph.getText();
+ if (text != null && text.contains(placeholder)) {
+ List runs = paragraph.getRuns();
+ for (int i = runs.size() - 1; i >= 0; i--) {
+ XWPFRun run = runs.get(i);
+ String runText = run.getText(0);
+ if (runText != null && runText.contains(placeholder)) {
+ run.setText("", 0);
+ if (imageBytes != null) {
+ run.addPicture(new ByteArrayInputStream(imageBytes), XWPFDocument.PICTURE_TYPE_PNG, placeholderKey + ".png",
+ width * 9525, height * 9525);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+
+ private static final Map HEADER_MAP = new LinkedHashMap<>();
+
+ static {
+ HEADER_MAP.put("名称", "name");
+ HEADER_MAP.put("性别", "gender");
+ HEADER_MAP.put("手机号码", "phone");
+ HEADER_MAP.put("出生年月", "birth_date");
+ HEADER_MAP.put("学历", "education");
+ HEADER_MAP.put("民族", "nation");
+ HEADER_MAP.put("政治面貌", "political_status");
+ HEADER_MAP.put("身份证号码", "id_card");
+ HEADER_MAP.put("毕业院校", "graduate_school");
+ HEADER_MAP.put("所属专业", "major");
+ HEADER_MAP.put("常住地", "permanent_address");
+ }
+
+ @PostMapping("/expertImport")
+ public ResponseResult> expertImport(@RequestParam("file") MultipartFile file) {
+ List