Commit 2109e5cdb48fd2ea22f32f31b1f414dcac67f160

Authored by yeqianyong
2 parents 2c71702d 2fad128a

Merge remote-tracking branch 'origin/master_0929' into master_0929

... ... @@ -498,9 +498,18 @@ public class ContractDistributorStandardController extends DefaultBaseController
498 498 });
499 499 }
500 500
  501 +
  502 + String templatePath = "templates/standardContractTemplate.xls";
  503 + if ("INTL_STD_CONTRACT".equals(data.getType())
  504 + || "INTL_INVENTORY_AGMT".equals(data.getType())
  505 + || "INTL_OPEN_SPEC_AGMT".equals(data.getType())) { // 读取外贸合同模版
  506 + templatePath = "templates/foreignStandardContractTemplate.xls";
  507 + }
  508 +
501 509 try {
  510 +
502 511 // 加载模板文件
503   - ClassPathResource templateResource = new ClassPathResource("templates/standardContractTemplate.xls");
  512 + ClassPathResource templateResource = new ClassPathResource(templatePath);
504 513 try (InputStream inputStream = templateResource.getStream();
505 514 Workbook workbook = new HSSFWorkbook(inputStream)) {
506 515 try {
... ... @@ -551,8 +560,16 @@ public class ContractDistributorStandardController extends DefaultBaseController
551 560
552 561 setCellValue(sheet, startRow, 6, contractDistributorLineBo.getStatus()); // F列
553 562 setCellValue(sheet, startRow, 7, contractDistributorLineBo.getQuantity()); // G列
554   - setCellValue(sheet, startRow, 9, contractDistributorLineBo.getUnitPrice()); // I列
555   - setCellValue(sheet, startRow, 10, contractDistributorLineBo.getAmountExcludingTax()); // K列
  563 + if ("INTL_STD_CONTRACT".equals(data.getType())
  564 + || "INTL_INVENTORY_AGMT".equals(data.getType())
  565 + || "INTL_OPEN_SPEC_AGMT".equals(data.getType())) {
  566 + setCellValue(sheet, startRow, 9, contractDistributorLineBo.getProcessingFee()); // I列
  567 + setCellValue(sheet, startRow, 10, contractDistributorLineBo.getProcessingFee().add(contractDistributorLineBo.getUnitPrice())); // K列
  568 + } else {
  569 + setCellValue(sheet, startRow, 9, contractDistributorLineBo.getUnitPrice()); // I列
  570 + setCellValue(sheet, startRow, 10, contractDistributorLineBo.getAmountExcludingTax()); // K列
  571 + }
  572 +
556 573 setCellValue(sheet, startRow, 11, contractDistributorLineBo.getTotalAmount()); // L列
557 574 setCellValue(sheet, startRow, 12, contractDistributorLineBo.getDeliveryDate() != null ?
558 575 contractDistributorLineBo.getDeliveryDate().format(dateFormatter) : ""); // L列
... ... @@ -566,8 +583,8 @@ public class ContractDistributorStandardController extends DefaultBaseController
566 583 dataMap.put("orderDate", data.getOrderDate().format(dateFormatter));
567 584 dataMap.put("supplierName", data.getSupplierName());
568 585 dataMap.put("buyerName", data.getBuyerName());
569   -// dataMap.put("unitName", data.getUnitName());
570   - dataMap.put("unitName", "元、公斤、元/公斤");
  586 + dataMap.put("unitName", data.getUnit());
  587 +// dataMap.put("unitName", "元、公斤、元/公斤");
571 588
572 589 dataMap.put("totalQuantity", data.getTotalQuantity().setScale(0, RoundingMode.HALF_UP).toString());
573 590 dataMap.put("totalAmountExcludingTax", data.getTotalAmountExcludingTax().setScale(2, RoundingMode.HALF_UP).toString());
... ... @@ -600,9 +617,9 @@ public class ContractDistributorStandardController extends DefaultBaseController
600 617 }
601 618
602 619 } catch (FileNotFoundException e) {
603   - throw new RuntimeException("模板文件不存在: templates/standardContractTemplate.xls", e);
  620 + throw new RuntimeException("模板文件不存在:" + templatePath, e);
604 621 } catch (IOException e) {
605   - throw new RuntimeException("无法读取模板文件: templates/standardContractTemplate.xls", e);
  622 + throw new RuntimeException("无法读取模板文件:" + templatePath, e);
606 623 }
607 624 } catch (Exception e) {
608 625 log.error("标准合同模版打印: {}", e.getMessage(), e);
... ...
1 1 package com.lframework.xingyun.sc.controller.order;
2 2
  3 +import cn.hutool.core.io.resource.ClassPathResource;
  4 +import com.baomidou.mybatisplus.core.conditions.Wrapper;
  5 +import com.baomidou.mybatisplus.core.toolkit.Wrappers;
  6 +import com.lframework.starter.common.exceptions.impl.DefaultClientException;
  7 +import com.lframework.starter.common.utils.CollectionUtil;
3 8 import com.lframework.starter.web.core.annotations.security.HasPermission;
  9 +import com.lframework.starter.web.core.components.resp.InvokeResult;
  10 +import com.lframework.starter.web.core.components.resp.InvokeResultBuilder;
  11 +import com.lframework.starter.web.core.components.resp.PageResult;
4 12 import com.lframework.starter.web.core.controller.DefaultBaseController;
5 13 import com.lframework.starter.web.core.utils.PageResultUtil;
6   -import com.lframework.starter.web.core.components.resp.PageResult;
7   -import com.lframework.starter.web.core.components.resp.InvokeResult;
8   -
9   -import javax.annotation.Resource;
10   -import javax.validation.constraints.NotBlank;
  14 +import com.lframework.starter.web.inner.entity.SysUser;
  15 +import com.lframework.starter.web.inner.service.system.SysUserService;
11 16 import com.lframework.xingyun.sc.bo.order.GetPurchaseOrderInfoBo;
12 17 import com.lframework.xingyun.sc.entity.PurchaseOrderInfo;
13 18 import com.lframework.xingyun.sc.entity.PurchaseOrderLine;
14 19 import com.lframework.xingyun.sc.service.order.PurchaseOrderInfoService;
15 20 import com.lframework.xingyun.sc.service.order.PurchaseOrderLineService;
  21 +import com.lframework.xingyun.sc.utils.LatexFormulaExcelExporterUtil;
16 22 import com.lframework.xingyun.sc.vo.order.CreatePurchaseOrderInfoVo;
17 23 import com.lframework.xingyun.sc.vo.order.QueryPurchaseOrderInfoVo;
18 24 import com.lframework.xingyun.sc.vo.order.QueryPurchaseOrderLineVo;
19 25 import com.lframework.xingyun.sc.vo.order.UpdatePurchaseOrderInfoVo;
  26 +import io.swagger.annotations.Api;
20 27 import io.swagger.annotations.ApiImplicitParam;
21   -import com.lframework.starter.web.core.components.resp.InvokeResultBuilder;
22   -import com.lframework.starter.common.exceptions.impl.DefaultClientException;
23 28 import io.swagger.annotations.ApiOperation;
24   -import com.lframework.starter.common.utils.CollectionUtil;
25   -import io.swagger.annotations.Api;
26   -import org.springframework.security.core.parameters.P;
27   -import org.springframework.web.bind.annotation.DeleteMapping;
  29 +import lombok.extern.slf4j.Slf4j;
  30 +import org.apache.commons.collections4.CollectionUtils;
  31 +import org.apache.commons.lang3.StringUtils;
  32 +import org.apache.poi.hssf.usermodel.HSSFWorkbook;
  33 +import org.apache.poi.ss.usermodel.*;
  34 +import org.apache.poi.ss.util.CellRangeAddress;
  35 +import org.apache.poi.util.IOUtils;
  36 +import org.apache.poi.xssf.usermodel.XSSFWorkbook;
28 37 import org.springframework.beans.factory.annotation.Autowired;
29 38 import org.springframework.validation.annotation.Validated;
30 39 import org.springframework.web.bind.annotation.*;
31 40
  41 +import javax.annotation.Resource;
  42 +import javax.servlet.http.HttpServletResponse;
32 43 import javax.validation.Valid;
  44 +import javax.validation.constraints.NotBlank;
  45 +import java.io.FileNotFoundException;
  46 +import java.io.FileOutputStream;
  47 +import java.io.IOException;
  48 +import java.io.InputStream;
  49 +import java.math.BigDecimal;
  50 +import java.math.RoundingMode;
  51 +import java.net.URLEncoder;
  52 +import java.time.LocalDate;
  53 +import java.time.format.DateTimeFormatter;
  54 +import java.util.ArrayList;
  55 +import java.util.HashMap;
33 56 import java.util.List;
  57 +import java.util.Map;
34 58 import java.util.stream.Collectors;
35 59
36 60 /**
... ... @@ -38,6 +62,7 @@ import java.util.stream.Collectors;
38 62 *
39 63 */
40 64 @Api(tags = "订货单表")
  65 +@Slf4j
41 66 @Validated
42 67 @RestController
43 68 @RequestMapping("/purchaseOrderInfo")
... ... @@ -47,6 +72,9 @@ public class PurchaseOrderInfoController extends DefaultBaseController {
47 72 private PurchaseOrderInfoService purchaseOrderInfoService;
48 73 @Resource
49 74 private PurchaseOrderLineService purchaseOrderLineService;
  75 + @Autowired
  76 + private SysUserService sysUserService;
  77 +
50 78 /**
51 79 * 查询列表
52 80 */
... ... @@ -152,4 +180,346 @@ public class PurchaseOrderInfoController extends DefaultBaseController {
152 180
153 181 return InvokeResultBuilder.success();
154 182 }
  183 +
  184 + @ApiOperation("订购单打印")
  185 + @GetMapping("/printPurchaseOrder")
  186 + public void printPurchaseOrder(@NotBlank(message = "id不能为空") String id, HttpServletResponse response) throws IOException {
  187 + PurchaseOrderInfo data = purchaseOrderInfoService.findById(id);
  188 +
  189 + // 设置响应头
  190 + setupResponse(response, data.getOrderNo() + "-订购单打印.xls");
  191 +
  192 + Wrapper<PurchaseOrderLine> purchaseOrderLineWrapper = Wrappers.lambdaQuery(PurchaseOrderLine.class)
  193 + .eq(PurchaseOrderLine::getPurchaseOrderId, id);
  194 + List<PurchaseOrderLine> purchaseOrderLineList = purchaseOrderLineService.list(purchaseOrderLineWrapper);
  195 +
  196 +
  197 + try {
  198 + // 加载模板文件
  199 + ClassPathResource templateResource = new ClassPathResource("templates/purchaseOrderTemplate.xlsx");
  200 + try (InputStream inputStream = templateResource.getStream();
  201 + Workbook workbook = new XSSFWorkbook(inputStream)) {
  202 + try {
  203 + Sheet sheet = workbook.getSheetAt(0);
  204 + DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
  205 + int startRow = 7; // 产品开始行
  206 + BigDecimal totalQuantity=BigDecimal.ZERO;
  207 + if (CollectionUtils.isNotEmpty(purchaseOrderLineList)) {
  208 +
  209 + for (int i = startRow + 1; i < startRow + purchaseOrderLineList.size(); i++) {
  210 + copyRow(workbook, sheet, startRow, i);
  211 + }
  212 +
  213 + for (PurchaseOrderLine currentOrderLine : purchaseOrderLineList) {
  214 + setCellValue(sheet, startRow, 1, currentOrderLine.getIndustry());
  215 + setCellValue(sheet, startRow, 2, currentOrderLine.getQuality());
  216 + setCellValue(sheet, startRow, 3, currentOrderLine.getBrand());
  217 +
  218 + List<LatexFormulaExcelExporterUtil.FormulaComponent> formulaComponentList = new ArrayList<>(3);
  219 + if (currentOrderLine.getThickness() != null) {
  220 + LatexFormulaExcelExporterUtil.FormulaComponent formulaComponent = new LatexFormulaExcelExporterUtil.FormulaComponent();
  221 + formulaComponent.setBase(currentOrderLine.getThickness());
  222 + formulaComponent.setSup(currentOrderLine.getThicknessTolPos());
  223 + formulaComponent.setSub(currentOrderLine.getThicknessTolNeg());
  224 + formulaComponentList.add(formulaComponent);
  225 + }
  226 +
  227 + if (currentOrderLine.getWidth() != null) {
  228 + LatexFormulaExcelExporterUtil.FormulaComponent formulaComponent = new LatexFormulaExcelExporterUtil.FormulaComponent();
  229 + formulaComponent.setBase(currentOrderLine.getWidth());
  230 + formulaComponent.setSup(currentOrderLine.getWidthTolPos());
  231 + formulaComponent.setSub(currentOrderLine.getWidthTolNeg());
  232 + formulaComponentList.add(formulaComponent);
  233 + }
  234 +
  235 + if (currentOrderLine.getLength() != null) {
  236 + LatexFormulaExcelExporterUtil.FormulaComponent formulaComponent = new LatexFormulaExcelExporterUtil.FormulaComponent();
  237 + formulaComponent.setBase(currentOrderLine.getLength());
  238 + formulaComponent.setSup(currentOrderLine.getLengthTolPos());
  239 + formulaComponent.setSub(currentOrderLine.getLengthTolNeg());
  240 + formulaComponentList.add(formulaComponent);
  241 + }
  242 +
  243 + String latex = LatexFormulaExcelExporterUtil.convertToLatex(formulaComponentList);
  244 + if (StringUtils.isNotBlank(latex)) {
  245 + LatexFormulaExcelExporterUtil.insertLatexImageToCell(workbook, sheet, latex, startRow, 4, 1, 7);
  246 + }
  247 +
  248 + setCellValue(sheet, startRow, 11, currentOrderLine.getStatus());
  249 + setCellValue(sheet, startRow, 13, currentOrderLine.getQuantity());
  250 + setCellValue(sheet, startRow, 14, currentOrderLine.getSalesPrice());
  251 + setCellValue(sheet, startRow, 16, currentOrderLine.getDeliveryDate());
  252 + setCellValue(sheet, startRow, 17, currentOrderLine.getAssessmentExceedsAgreement());
  253 + totalQuantity = totalQuantity.add(currentOrderLine.getQuantity());
  254 + startRow++;
  255 + }
  256 + }
  257 +
  258 + Map<String, Object> dataMap = new HashMap<>();
  259 + dataMap.put("supplyUnit", data.getSupplyUnit());
  260 + dataMap.put("orderNo", data.getOrderNo());
  261 + dataMap.put("orderingUnitName", data.getOrderingUnitName());
  262 + dataMap.put("orderDate", data.getOrderDate() == null ? "" : data.getOrderDate().format(dateFormatter));
  263 +
  264 + dataMap.put("totalQuantity", totalQuantity.setScale(1, RoundingMode.HALF_UP));
  265 + dataMap.put("executionStandard", data.getExecutionStandard());
  266 + dataMap.put("deliveryMethod", data.getDeliveryMethod());
  267 + dataMap.put("invoicingStatus", data.getInvoicingStatus());
  268 + dataMap.put("pieceWeightHeader", data.getPieceWeightHeader());
  269 + dataMap.put("surface", data.getSurface());
  270 + dataMap.put("tolerance", data.getTolerance());
  271 + dataMap.put("performanceance", data.getPerformance());
  272 + dataMap.put("packaging", data.getPackaging());
  273 + dataMap.put("shippingCost", data.getShippingCost());
  274 + dataMap.put("remarks", data.getRemarks());
  275 + dataMap.put("createUser", data.getCreateBy());
  276 + dataMap.put("element", data.getElement());
  277 +
  278 +
  279 +
  280 + processTemplate(workbook, dataMap);
  281 +
  282 + // 写入响应流
  283 + workbook.write(response.getOutputStream());
  284 + response.getOutputStream().flush();
  285 + } finally {
  286 + IOUtils.closeQuietly(workbook);
  287 + }
  288 +
  289 + } catch (FileNotFoundException e) {
  290 + throw new RuntimeException("模板文件不存在: templates/purchaseOrderTemplate.xlsx", e);
  291 + } catch (IOException e) {
  292 + throw new RuntimeException("无法读取模板文件: templates/purchaseOrderTemplate.xlsx", e);
  293 + }
  294 + } catch (Exception e) {
  295 + log.error("标准合同模版打印: {}", e.getMessage(), e);
  296 + throw e;
  297 + }
  298 + }
  299 +
  300 + /**
  301 + * 设置HTTP响应头
  302 + */
  303 + private void setupResponse(HttpServletResponse response, String fileName) throws IOException {
  304 + String encodedFileName = URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20");
  305 +
  306 + response.setContentType("application/vnd.ms-excel");
  307 + response.setCharacterEncoding("UTF-8");
  308 + response.setHeader("Content-Disposition", "attachment;filename=" + encodedFileName);
  309 + response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
  310 + }
  311 +
  312 + /**
  313 + * 复制行并处理合并单元格
  314 + */
  315 + public static void copyRow(Workbook workbook, Sheet sheet,
  316 + int sourceRowIndex, int targetRowIndex) {
  317 +
  318 + // 获取源行的所有合并区域
  319 + List<CellRangeAddress> mergedRegions = getMergedRegionsInRow(sheet, sourceRowIndex);
  320 +
  321 + // 移动目标行及之后的行
  322 + if (targetRowIndex <= sheet.getLastRowNum()) {
  323 + sheet.shiftRows(targetRowIndex, sheet.getLastRowNum(), 1, true, false);
  324 + }
  325 +
  326 + // 复制行内容
  327 + Row sourceRow = sheet.getRow(sourceRowIndex);
  328 + Row newRow = sheet.createRow(targetRowIndex);
  329 + newRow.setHeight(sourceRow.getHeight());
  330 +
  331 + // 复制单元格
  332 + for (int i = 0; i < sourceRow.getLastCellNum(); i++) {
  333 + Cell oldCell = sourceRow.getCell(i);
  334 + Cell newCell = newRow.createCell(i);
  335 +
  336 + if (oldCell != null) {
  337 + copyCell(workbook, oldCell, newCell);
  338 + }
  339 + }
  340 +
  341 + // 处理合并单元格
  342 + handleMergedRegions(sheet, mergedRegions, sourceRowIndex, targetRowIndex);
  343 + }
  344 +
  345 + /**
  346 + * 获取行中涉及的合并区域
  347 + */
  348 + private static List<CellRangeAddress> getMergedRegionsInRow(Sheet sheet, int rowIndex) {
  349 + List<CellRangeAddress> result = new ArrayList<>();
  350 + for (int i = 0; i < sheet.getNumMergedRegions(); i++) {
  351 + CellRangeAddress mergedRegion = sheet.getMergedRegion(i);
  352 + if (mergedRegion.getFirstRow() == rowIndex) {
  353 + result.add(mergedRegion);
  354 + }
  355 + }
  356 + return result;
  357 + }
  358 +
  359 + /**
  360 + * 处理合并单元格的复制
  361 + */
  362 + private static void handleMergedRegions(Sheet sheet, List<CellRangeAddress> mergedRegions,
  363 + int sourceRowIndex, int targetRowIndex) {
  364 +
  365 + for (CellRangeAddress oldRegion : mergedRegions) {
  366 + // 创建新的合并区域
  367 + CellRangeAddress newRegion = new CellRangeAddress(
  368 + targetRowIndex,
  369 + targetRowIndex + (oldRegion.getLastRow() - oldRegion.getFirstRow()),
  370 + oldRegion.getFirstColumn(),
  371 + oldRegion.getLastColumn()
  372 + );
  373 +
  374 + sheet.addMergedRegion(newRegion);
  375 +
  376 + // 设置合并区域的边框样式
  377 + setMergedRegionBorderStyle(sheet, newRegion);
  378 + }
  379 + }
  380 +
  381 + /**
  382 + * 设置合并单元格的边框样式
  383 + */
  384 + private static void setMergedRegionBorderStyle(Sheet sheet, CellRangeAddress region) {
  385 + Row firstRow = sheet.getRow(region.getFirstRow());
  386 + if (firstRow != null) {
  387 + Cell firstCell = firstRow.getCell(region.getFirstColumn());
  388 + if (firstCell != null && firstCell.getCellStyle() != null) {
  389 + CellStyle style = firstCell.getCellStyle();
  390 +
  391 + // 为合并区域的所有单元格设置样式
  392 + for (int i = region.getFirstRow(); i <= region.getLastRow(); i++) {
  393 + Row row = sheet.getRow(i);
  394 + if (row == null) {
  395 + row = sheet.createRow(i);
  396 + }
  397 + for (int j = region.getFirstColumn(); j <= region.getLastColumn(); j++) {
  398 + Cell cell = row.getCell(j);
  399 + if (cell == null) {
  400 + cell = row.createCell(j);
  401 + }
  402 + cell.setCellStyle(style);
  403 + }
  404 + }
  405 + }
  406 + }
  407 + }
  408 +
  409 + /**
  410 + * 复制单元格内容和样式
  411 + */
  412 + private static void copyCell(Workbook workbook, Cell oldCell, Cell newCell) {
  413 + if (oldCell.getCellStyle() != null) {
  414 + newCell.setCellStyle(oldCell.getCellStyle());
  415 + }
  416 +
  417 + switch (oldCell.getCellTypeEnum()) {
  418 + case STRING:
  419 + newCell.setCellValue(oldCell.getStringCellValue());
  420 + break;
  421 + case NUMERIC:
  422 + newCell.setCellValue(oldCell.getNumericCellValue());
  423 + break;
  424 + case BOOLEAN:
  425 + newCell.setCellValue(oldCell.getBooleanCellValue());
  426 + break;
  427 + case FORMULA:
  428 + newCell.setCellFormula(oldCell.getCellFormula());
  429 + break;
  430 + default:
  431 + newCell.setCellValue(oldCell.getStringCellValue());
  432 + break;
  433 + }
  434 + }
  435 +
  436 + /**
  437 + * 检查指定区域是否存在合并单元格
  438 + */
  439 + public static boolean hasMergedRegionsInRange(Sheet sheet, int firstRow, int lastRow,
  440 + int firstCol, int lastCol) {
  441 +
  442 + for (int i = 0; i < sheet.getNumMergedRegions(); i++) {
  443 + CellRangeAddress mergedRegion = sheet.getMergedRegion(i);
  444 + if (mergedRegion.getFirstRow() >= firstRow &&
  445 + mergedRegion.getLastRow() <= lastRow &&
  446 + mergedRegion.getFirstColumn() >= firstCol &&
  447 + mergedRegion.getLastColumn() <= lastCol) {
  448 + return true;
  449 + }
  450 + }
  451 + return false;
  452 + }
  453 +
  454 + /**
  455 + * 设置单元格值
  456 + */
  457 + private static void setCellValue(Sheet sheet, int rowNum, int colNum, Object value) {
  458 + Row row = sheet.getRow(rowNum);
  459 + if (row == null) {
  460 + row = sheet.createRow(rowNum);
  461 + }
  462 +
  463 + Cell cell = row.getCell(colNum);
  464 + if (cell == null) {
  465 + cell = row.createCell(colNum);
  466 + }
  467 +
  468 + if (value == null) {
  469 + cell.setCellValue("");
  470 + } else if (value instanceof String) {
  471 + cell.setCellValue((String) value);
  472 + } else if (value instanceof Integer) {
  473 + cell.setCellValue((Integer) value);
  474 + } else if (value instanceof Double) {
  475 + cell.setCellValue((Double) value);
  476 + } else if (value instanceof BigDecimal) {
  477 + cell.setCellValue(((BigDecimal) value).doubleValue());
  478 + } else if (value instanceof LocalDate) {
  479 + cell.setCellValue(((LocalDate) value).format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
  480 + }
  481 + }
  482 +
  483 + private void processTemplate(Workbook workbook, Map<String, Object> dataMap) {
  484 + Sheet sheet = workbook.getSheetAt(0);
  485 +
  486 + for (Row row : sheet) {
  487 + for (Cell cell : row) {
  488 + if (cell.getCellTypeEnum() == CellType.STRING) {
  489 + String cellValue = cell.getStringCellValue();
  490 + String newValue = replacePlaceholders(cellValue, dataMap);
  491 + if (!cellValue.equals(newValue)) {
  492 + cell.setCellValue(newValue);
  493 + }
  494 + }
  495 + }
  496 + }
  497 + }
  498 +
  499 + private String replacePlaceholders(String text, Map<String, Object> dataMap) {
  500 + if (text == null || text.isEmpty()) {
  501 + return text;
  502 + }
  503 +
  504 + String result = text;
  505 + for (Map.Entry<String, Object> entry : dataMap.entrySet()) {
  506 + String placeholder = "${" + entry.getKey() + "}";
  507 + String value = entry.getValue() != null ? entry.getValue().toString() : "";
  508 + // 将数据库中的 \n 转换为 Excel 识别的换行符
  509 + value = value.replace("\\n", "\n");
  510 +
  511 + result = result.replace(placeholder, value);
  512 + }
  513 +
  514 + for (Map.Entry<String, Object> entry : dataMap.entrySet()) {
  515 + String placeholder = "#{" + entry.getKey() + "}";
  516 + String value = entry.getValue() != null ? entry.getValue().toString() : "";
  517 + // 将数据库中的 \n 转换为 Excel 识别的换行符
  518 + value = value.replace("\\n", "\n");
  519 +
  520 + result = result.replace(placeholder, value);
  521 + }
  522 +
  523 + return result;
  524 + }
155 525 }
... ...
... ... @@ -168,6 +168,40 @@ public class LatexFormulaExcelExporterUtil {
168 168 // pict.resize(0.95); // 缩小到90%,自然产生边距
169 169 }
170 170
  171 + /**
  172 + * 将LaTeX公式作为图片插入到指定单元格
  173 + */
  174 + public static void insertLatexImageToCell(Workbook workbook, Sheet sheet, String latex, int rowIndex, int colIndex, int rowRange, int colRange) throws IOException {
  175 + // 1. 将LaTeX渲染为图片字节
  176 + byte[] imageBytes = latexToImageBytes(latex, 36);
  177 +
  178 + // 2. 将图片添加到工作簿
  179 + int pictureIdx = workbook.addPicture(imageBytes, Workbook.PICTURE_TYPE_PNG);
  180 +
  181 + // 3. 创建绘图对象
  182 + Drawing<?> drawing = sheet.createDrawingPatriarch();
  183 +
  184 + // 4. 创建锚点,确定图片位置
  185 + ClientAnchor anchor = workbook.getCreationHelper().createClientAnchor();
  186 + anchor.setAnchorType(ClientAnchor.AnchorType.MOVE_AND_RESIZE);
  187 + anchor.setCol1(colIndex); // C列 (从0开始计数)
  188 + anchor.setRow1(rowIndex); // 当前数据行 (表头在第0行)
  189 + anchor.setCol2(colIndex + colRange); // 结束于D列
  190 + anchor.setRow2(rowIndex + rowRange); // 结束于下一行
  191 +
  192 + // 设置较小的起始偏移
  193 + anchor.setDx1(50); // 水平偏移(建议值:10-100)
  194 + anchor.setDy1(25); // 垂直偏移(建议值:10-50)
  195 +
  196 +
  197 + // 5. 创建图片并插入
  198 + Picture pict = drawing.createPicture(anchor, pictureIdx);
  199 +
  200 + // 6. 自动调整图片大小以适应单元格 (可选)
  201 + // 取消注释此行使图片自动适应锚点区域
  202 +// pict.resize(0.95); // 缩小到90%,自然产生边距
  203 + }
  204 +
171 205 @Data
172 206 public static class FormulaComponent {
173 207 BigDecimal base;
... ...