package com.ruoyi.quality.utils;
|
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
import com.deepoove.poi.XWPFTemplate;
|
import com.deepoove.poi.config.Configure;
|
import com.ruoyi.common.exception.ServiceException;
|
import com.ruoyi.common.utils.HackLoopTableRenderPolicy;
|
import com.ruoyi.quality.mapper.QualityInspectMapper;
|
import com.ruoyi.quality.pojo.QualityInspect;
|
import com.ruoyi.quality.pojo.QualityInspectParam;
|
import com.ruoyi.quality.service.IQualityInspectParamService;
|
import jakarta.servlet.http.HttpServletResponse;
|
import lombok.RequiredArgsConstructor;
|
import org.apache.commons.lang3.StringUtils;
|
import org.apache.poi.hwpf.HWPFDocument;
|
import org.apache.poi.hwpf.usermodel.Range;
|
import org.apache.poi.hwpf.usermodel.Table;
|
import org.apache.poi.hwpf.usermodel.TableCell;
|
import org.apache.poi.hwpf.usermodel.TableIterator;
|
import org.apache.poi.hwpf.usermodel.TableRow;
|
import org.springframework.stereotype.Component;
|
|
import java.io.IOException;
|
import java.io.InputStream;
|
import java.io.OutputStream;
|
import java.net.URLEncoder;
|
import java.nio.charset.StandardCharsets;
|
import java.util.HashMap;
|
import java.util.LinkedHashMap;
|
import java.util.LinkedHashSet;
|
import java.util.List;
|
import java.util.Map;
|
|
/**
|
* Word template export helper for process inspection.
|
*/
|
@Component
|
@RequiredArgsConstructor
|
public class QualityInspectTemplateExportHelper {
|
|
private static final String WEILONG_TEMPLATE = "/static/伟龙模版.doc";
|
private static final String BAISHI_TEMPLATE = "/static/百事模版.doc";
|
private static final String DALI_TEMPLATE = "/static/达利模版.doc";
|
|
private final QualityInspectMapper qualityInspectMapper;
|
private final IQualityInspectParamService qualityInspectParamService;
|
|
public void exportWeiLong(HttpServletResponse response, Long inspectId) {
|
export(response, inspectId, WEILONG_TEMPLATE, "伟龙模版检验结果");
|
}
|
|
public void exportBaiShi(HttpServletResponse response, Long inspectId) {
|
if (inspectId == null) {
|
throw new ServiceException("检验单ID不能为空");
|
}
|
|
QualityInspect inspect = qualityInspectMapper.selectById(inspectId);
|
if (inspect == null) {
|
throw new ServiceException("检验单不存在");
|
}
|
|
List<QualityInspectParam> paramList = qualityInspectParamService.list(
|
Wrappers.<QualityInspectParam>lambdaQuery()
|
.eq(QualityInspectParam::getInspectId, inspectId)
|
.orderByAsc(QualityInspectParam::getId));
|
int index = 1;
|
for (QualityInspectParam detail : paramList) {
|
detail.setIndex(index);
|
index++;
|
}
|
|
try (InputStream inputStream = getClass().getResourceAsStream("/static/百事模版.docx")) {
|
if (inputStream == null) {
|
throw new ServiceException("模板文件不存在:/static/百事模版.docx");
|
}
|
|
Configure configure = Configure.builder()
|
.bind("paramList", new HackLoopTableRenderPolicy())
|
.build();
|
|
XWPFTemplate template = XWPFTemplate.compile(inputStream, configure).render(
|
new HashMap<String, Object>() {{
|
put("inspect", inspect);
|
put("paramList", paramList);
|
}});
|
|
response.reset();
|
response.setContentType("application/msword");
|
response.setCharacterEncoding("UTF-8");
|
String encodedName = URLEncoder.encode("百事模版检验结果", StandardCharsets.UTF_8).replace("+", "%20");
|
response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
|
response.setHeader("Content-Disposition", "attachment;filename=" + encodedName + ".docx");
|
|
try (OutputStream outputStream = response.getOutputStream()) {
|
template.write(outputStream);
|
outputStream.flush();
|
}
|
} catch (IOException e) {
|
throw new RuntimeException("导出失败", e);
|
}
|
}
|
|
public void exportDaLi(HttpServletResponse response, Long inspectId) {
|
export(response, inspectId, DALI_TEMPLATE, "达利模版检验结果");
|
}
|
|
private void export(HttpServletResponse response, Long inspectId, String templatePath, String fileName) {
|
if (inspectId == null) {
|
throw new ServiceException("检验单ID不能为空");
|
}
|
|
QualityInspect inspect = qualityInspectMapper.selectById(inspectId);
|
if (inspect == null) {
|
throw new ServiceException("检验单不存在");
|
}
|
|
List<QualityInspectParam> paramList = qualityInspectParamService.list(
|
Wrappers.<QualityInspectParam>lambdaQuery()
|
.eq(QualityInspectParam::getInspectId, inspectId)
|
.orderByAsc(QualityInspectParam::getId));
|
Map<String, String> valueMap = buildValueMap(paramList, inspect);
|
|
try (InputStream inputStream = getClass().getResourceAsStream(templatePath)) {
|
if (inputStream == null) {
|
throw new ServiceException("模板文件不存在:" + templatePath);
|
}
|
|
HWPFDocument document = new HWPFDocument(inputStream);
|
fillDocument(document, valueMap);
|
|
response.reset();
|
response.setContentType("application/msword");
|
response.setCharacterEncoding("UTF-8");
|
String encodedName = URLEncoder.encode(fileName, StandardCharsets.UTF_8).replace("+", "%20");
|
response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
|
response.setHeader("Content-Disposition", "attachment;filename=" + encodedName + ".doc");
|
|
try (OutputStream outputStream = response.getOutputStream()) {
|
document.write(outputStream);
|
outputStream.flush();
|
}
|
} catch (IOException e) {
|
throw new RuntimeException("导出失败", e);
|
}
|
}
|
|
private void fillDocument(HWPFDocument document, Map<String, String> valueMap) {
|
Range range = document.getRange();
|
TableIterator iterator = new TableIterator(range);
|
while (iterator.hasNext()) {
|
Table table = iterator.next();
|
fillTable(table, valueMap);
|
}
|
}
|
|
private void fillTable(Table table, Map<String, String> valueMap) {
|
String currentGroupLabel = "";
|
// 记录结论列的位置,避免在结论列下面继续填充
|
int conclusionCellIndex = -1;
|
|
for (int rowIndex = 0; rowIndex < table.numRows(); rowIndex++) {
|
TableRow row = table.getRow(rowIndex);
|
String firstCellText = getCellText(row, 0);
|
String secondCellText = getCellText(row, 1);
|
|
if (StringUtils.isNotBlank(firstCellText)) {
|
currentGroupLabel = firstCellText;
|
}
|
|
// 先判断是否为结论行
|
String normalizedFirstCell = normalizeKey(firstCellText);
|
boolean isConclusionRow = matchesSummaryRow(normalizedFirstCell);
|
|
// 如果是结论行,结论列始终是最后一列
|
if (isConclusionRow) {
|
conclusionCellIndex = row.numCells() - 1;
|
}
|
|
String value = resolveValue(valueMap, currentGroupLabel, firstCellText, secondCellText, isConclusionRow);
|
if (StringUtils.isBlank(value)) {
|
continue;
|
}
|
|
// 查找结果列位置
|
int resultCellIndex;
|
if (isConclusionRow) {
|
resultCellIndex = conclusionCellIndex;
|
} else {
|
// 普通行:找第一个空白单元格作为结果列,但要排除结论列
|
resultCellIndex = findResultCellIndex(row, conclusionCellIndex);
|
if (resultCellIndex < 0) {
|
// 不使用最后一列,避免与结论列冲突
|
int lastCellIndex = row.numCells() - 1;
|
if (lastCellIndex > 1 && lastCellIndex != conclusionCellIndex) {
|
resultCellIndex = lastCellIndex - 1;
|
}
|
}
|
}
|
|
if (resultCellIndex < 0 || resultCellIndex == conclusionCellIndex && !isConclusionRow) {
|
continue;
|
}
|
|
TableCell resultCell = row.getCell(resultCellIndex);
|
String cellText = cleanCellText(resultCell.text());
|
|
// 严格检查:只有空白或占位符才填充
|
if (!isBlankLike(cellText) && !isPlaceholder(cellText)) {
|
continue;
|
}
|
|
// 使用更安全的填充方式
|
safeWriteCellText(resultCell, value);
|
}
|
}
|
|
/**
|
* 查找结果列,排除结论列
|
*/
|
private int findResultCellIndex(TableRow row, int excludeIndex) {
|
for (int i = 1; i < row.numCells(); i++) {
|
if (i == excludeIndex) {
|
continue;
|
}
|
String cellText = cleanCellText(row.getCell(i).text());
|
if (isBlankLike(cellText) || isPlaceholder(cellText)) {
|
return i;
|
}
|
}
|
return -1;
|
}
|
|
/**
|
* 安全写入单元格文本,避免影响其他行
|
*/
|
private void safeWriteCellText(TableCell cell, String value) {
|
if (StringUtils.isBlank(value)) {
|
return;
|
}
|
|
String originalText = cell.text();
|
String cleanedOriginal = cleanCellText(originalText);
|
|
// 如果单元格已有内容且不是占位符,不写入
|
if (StringUtils.isNotBlank(cleanedOriginal) && !isPlaceholder(cleanedOriginal)) {
|
return;
|
}
|
|
// 只使用replaceText,不使用insertBefore
|
try {
|
if (StringUtils.isNotBlank(originalText)) {
|
cell.replaceText(originalText, value);
|
} else {
|
// 空单元格直接设置文本
|
cell.insertBefore(value);
|
}
|
} catch (Exception e) {
|
// 备用方案:使用getRange方式
|
try {
|
// cell.getRange().insertAfter(value);
|
} catch (Exception ignored) {}
|
}
|
}
|
|
/**
|
* 判断是否为占位符
|
*/
|
private boolean isPlaceholder(String text) {
|
if (StringUtils.isBlank(text)) {
|
return false;
|
}
|
String cleaned = text.trim();
|
// 常见占位符模式
|
return cleaned.equals("—") ||
|
cleaned.equals("-") ||
|
cleaned.equals("_") ||
|
cleaned.equals("/") ||
|
cleaned.equals("\\") ||
|
cleaned.equals("□") ||
|
cleaned.equals("■") ||
|
cleaned.equals("○") ||
|
cleaned.equals("●") ||
|
cleaned.equals("※") ||
|
cleaned.equals("*") ||
|
cleaned.matches("^\\.{2,}$") || // 多个点
|
cleaned.matches("^{2,}$") || // 多个大括号
|
cleaned.equalsIgnoreCase("N/A") ||
|
cleaned.equalsIgnoreCase("NA") ||
|
cleaned.equals("待检") ||
|
cleaned.equals("待填") ||
|
cleaned.equals("空白");
|
}
|
|
private int findResultCellIndex(TableRow row) {
|
for (int i = 1; i < row.numCells(); i++) {
|
if (isBlankLike(row.getCell(i).text())) {
|
return i;
|
}
|
}
|
return -1;
|
}
|
|
private String resolveValue(Map<String, String> valueMap,
|
String currentGroupLabel,
|
String firstCellText,
|
String secondCellText,
|
boolean isConclusionRow) {
|
LinkedHashSet<String> candidates = new LinkedHashSet<>();
|
addCandidate(candidates, firstCellText);
|
addCandidate(candidates, secondCellText);
|
addCandidate(candidates, firstCellText + secondCellText);
|
if (StringUtils.isNotBlank(currentGroupLabel) && StringUtils.isNotBlank(secondCellText)) {
|
addCandidate(candidates, currentGroupLabel + secondCellText);
|
}
|
if (StringUtils.isNotBlank(currentGroupLabel)
|
&& StringUtils.isNotBlank(firstCellText)
|
&& StringUtils.isBlank(secondCellText)) {
|
addCandidate(candidates, currentGroupLabel);
|
}
|
|
|
// 结论行不填充,保留模板原始数据
|
if (isConclusionRow) {
|
return null;
|
}
|
|
// 普通行:先查检验参数值,最后才匹配结论行关键词
|
for (String candidate : candidates) {
|
String normalized = normalizeKey(candidate);
|
// 普通行跳过结论关键词匹配
|
if (matchesSummaryRow(normalized)) {
|
continue;
|
}
|
String value = lookupValue(valueMap, normalized);
|
if (StringUtils.isNotBlank(value)) {
|
return value;
|
}
|
}
|
|
return null;
|
}
|
|
private boolean matchesSummaryRow(String normalizedText) {
|
if (StringUtils.isBlank(normalizedText)) {
|
return false;
|
}
|
return normalizedText.contains("质量评定")
|
|| normalizedText.contains("检验结论")
|
|| normalizedText.contains("Gradeestimation")
|
|| normalizedText.contains("Conclusion")
|
|| normalizedText.contains("Evaluation")
|
|| normalizedText.contains("评定")
|
|| normalizedText.contains("结论");
|
}
|
|
private String lookupValue(Map<String, String> valueMap, String normalizedCandidate) {
|
if (StringUtils.isBlank(normalizedCandidate)) {
|
return null;
|
}
|
|
String value = valueMap.get(normalizedCandidate);
|
if (StringUtils.isNotBlank(value)) {
|
return value;
|
}
|
|
String chineseCandidate = stripEnglishLetters(normalizedCandidate);
|
value = valueMap.get(chineseCandidate);
|
if (StringUtils.isNotBlank(value)) {
|
return value;
|
}
|
|
for (Map.Entry<String, String> entry : valueMap.entrySet()) {
|
String key = entry.getKey();
|
if (StringUtils.contains(key, normalizedCandidate)
|
|| StringUtils.contains(normalizedCandidate, key)
|
|| StringUtils.contains(key, chineseCandidate)
|
|| StringUtils.contains(chineseCandidate, key)) {
|
return entry.getValue();
|
}
|
}
|
return null;
|
}
|
|
private Map<String, String> buildValueMap(List<QualityInspectParam> paramList, QualityInspect inspect) {
|
Map<String, String> valueMap = new LinkedHashMap<>();
|
for (QualityInspectParam param : paramList) {
|
String value = StringUtils.trimToNull(param.getTestValue());
|
if (StringUtils.isBlank(value)) {
|
continue;
|
}
|
putValue(valueMap, param.getParameterItem(), value);
|
}
|
|
String checkResult = StringUtils.trimToNull(inspect.getCheckResult());
|
if (StringUtils.isNotBlank(checkResult)) {
|
putValue(valueMap, "质量评定", checkResult);
|
putValue(valueMap, "检验结果", checkResult);
|
putValue(valueMap, "检验结论", checkResult);
|
putValue(valueMap, "Grade estimation", checkResult);
|
putValue(valueMap, "Test Results", checkResult);
|
}
|
return valueMap;
|
}
|
|
private void putValue(Map<String, String> valueMap, String key, String value) {
|
String normalizedKey = normalizeKey(key);
|
if (StringUtils.isBlank(normalizedKey)) {
|
return;
|
}
|
valueMap.put(normalizedKey, value);
|
|
String chineseKey = stripEnglishLetters(normalizedKey);
|
if (StringUtils.isNotBlank(chineseKey)) {
|
valueMap.putIfAbsent(chineseKey, value);
|
}
|
}
|
|
private void writeCellText(TableCell cell, String value) {
|
if (StringUtils.isBlank(value)) {
|
return;
|
}
|
|
String originalText = cell.text();
|
try {
|
cell.replaceText(originalText, value);
|
} catch (Exception ignored) {
|
// Fallback below.
|
}
|
|
String cleanedText = cleanCellText(cell.text());
|
if (!cleanedText.contains(value)) {
|
cell.insertBefore(value);
|
}
|
}
|
|
private String getCellText(TableRow row, int index) {
|
if (row.numCells() <= index) {
|
return "";
|
}
|
return cleanCellText(row.getCell(index).text());
|
}
|
|
private String cleanCellText(String text) {
|
if (text == null) {
|
return "";
|
}
|
return text.replace("\u0007", "")
|
.replace("\r", "")
|
.replace("\n", "")
|
.trim();
|
}
|
|
private boolean isBlankLike(String text) {
|
String cleaned = cleanCellText(text);
|
return StringUtils.isBlank(cleaned)
|
|| "-".equals(cleaned)
|
|| "_".equals(cleaned)
|
|| "—".equals(cleaned)
|
|| "·".equals(cleaned);
|
}
|
|
private void addCandidate(LinkedHashSet<String> candidates, String text) {
|
if (StringUtils.isBlank(text)) {
|
return;
|
}
|
candidates.add(text);
|
}
|
|
private String normalizeKey(String text) {
|
String value = cleanCellText(text);
|
if (StringUtils.isBlank(value)) {
|
return "";
|
}
|
value = value.replace("\u00A0", "");
|
value = value.replaceAll("\\s+", "");
|
value = value.replace("(", "");
|
value = value.replace(")", "");
|
value = value.replace("(", "");
|
value = value.replace(")", "");
|
value = value.replace(",", "");
|
value = value.replace(",", "");
|
value = value.replace("。", "");
|
value = value.replace(":", "");
|
value = value.replace(":", "");
|
value = value.replace(";", "");
|
value = value.replace(";", "");
|
value = value.replace("、", "");
|
value = value.replace("“", "");
|
value = value.replace("”", "");
|
value = value.replace("【", "");
|
value = value.replace("】", "");
|
value = value.replace("%", "");
|
return value;
|
}
|
|
private String stripEnglishLetters(String text) {
|
if (StringUtils.isBlank(text)) {
|
return "";
|
}
|
return text.replaceAll("[A-Za-z]", "");
|
}
|
|
/**
|
* 调试方法:分析模板表格结构
|
*/
|
public String analyzeTemplate(String templatePath) {
|
StringBuilder sb = new StringBuilder();
|
try (InputStream inputStream = getClass().getResourceAsStream(templatePath)) {
|
if (inputStream == null) {
|
return "模板文件不存在:" + templatePath;
|
}
|
|
HWPFDocument document = new HWPFDocument(inputStream);
|
Range range = document.getRange();
|
TableIterator iterator = new TableIterator(range);
|
|
int tableIndex = 0;
|
while (iterator.hasNext()) {
|
Table table = iterator.next();
|
sb.append("=== 表格 ").append(tableIndex++).append(" ===\n");
|
sb.append("行数: ").append(table.numRows()).append("\n");
|
|
for (int rowIndex = 0; rowIndex < table.numRows(); rowIndex++) {
|
TableRow row = table.getRow(rowIndex);
|
sb.append("行").append(rowIndex).append(": ");
|
for (int cellIndex = 0; cellIndex < row.numCells(); cellIndex++) {
|
String cellText = cleanCellText(row.getCell(cellIndex).text());
|
sb.append("[列").append(cellIndex).append(": ").append(cellText).append("] ");
|
}
|
sb.append("\n");
|
}
|
sb.append("\n");
|
}
|
} catch (Exception e) {
|
sb.append("分析失败: ").append(e.getMessage());
|
}
|
return sb.toString();
|
}
|
}
|