diff --git a/bonus-modules/bonus-material/src/main/java/com/bonus/material/common/utils/WordParserUtil.java b/bonus-modules/bonus-material/src/main/java/com/bonus/material/common/utils/WordParserUtil.java new file mode 100644 index 00000000..ae0cd958 --- /dev/null +++ b/bonus-modules/bonus-material/src/main/java/com/bonus/material/common/utils/WordParserUtil.java @@ -0,0 +1,499 @@ +package com.bonus.material.common.utils; + +import com.bonus.common.core.web.domain.AjaxResult; +import com.bonus.material.materialStation.domain.ProAuthorizeDetails; +import com.bonus.system.api.RemoteFileService; +import org.apache.poi.hwpf.HWPFDocument; +import org.apache.poi.hwpf.usermodel.Picture; +import org.apache.poi.openxml4j.opc.OPCPackage; +import org.apache.poi.poifs.filesystem.POIFSFileSystem; +import org.apache.poi.xwpf.usermodel.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.web.multipart.MultipartFile; + +import java.io.*; +import java.net.URL; +import java.net.URI; +import java.net.URLEncoder; +import java.nio.file.*; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Word 解析工具类(兼容本地路径与 URL,支持 doc/docx 文字与图片提取) + * 依赖:Apache POI (poi-ooxml, poi-scratchpad) + * + * 使用: + * List list = WordParserUtil.parseWordFile(multipartFile, filePathUrlOrLocalPath, sysFileService); + * + * 注意:sysFileService.upload(...) 应返回 AjaxResult 或类似结构,且包含 data.url 字段(你项目的上传逻辑)。 + */ +public class WordParserUtil { + + private static final Logger logger = LoggerFactory.getLogger(WordParserUtil.class); + + // 当 filePath 为 URL 时,先下载到本地临时文件再解析 + private static final String TEMP_PREFIX = "word_parse_"; + + // 身份证号正则(15 或 18,最后一位可能是 Xx) + private static final Pattern ID_PATTERN = Pattern.compile("(\\d{15}|\\d{17}[0-9Xx])"); + + /** + * 统一入口 + * + * @param file 前端上传的 MultipartFile(原始文件) + * @param filePath sysFileService.upload 返回的文件访问路径(可以是 http(s) URL 或 本地路径) + * @param sysFileService 你项目的文件上传服务实例(用于上传解析出的图片) + */ + public static Map parseWordFile(MultipartFile file,String fileName, String filePath, RemoteFileService sysFileService) throws Exception { + Map result = new HashMap<>(); + + List receivers = new ArrayList<>(); + try { + String originalName = file.getOriginalFilename(); + if (originalName == null) { + originalName = "upload.docx"; + } + + // 根据扩展名选择解析 + String lower = originalName.toLowerCase(); + File localFile; + if (filePath.startsWith("http://") || filePath.startsWith("https://")) { + localFile = downloadToLocal(filePath); + } else { + Path p = Paths.get(filePath); + if (Files.exists(p)) { + localFile = p.toFile(); + } else { + localFile = writeMultipartToTemp(file); + } + } + + if (lower.endsWith(".docx")) { + try (FileInputStream fis2 = new FileInputStream(localFile)) { + receivers = parseDocx(fis2, sysFileService); + } + } else { + throw new IllegalArgumentException("文件格式不对,请下载模版(仅支持docx)"); + } + + result.put("success", true); + result.put("receivers", receivers); // 识别出来的委托人 + result.put("filePath", filePath); // 文档路径,始终返回 + result.put("fileName", fileName); + } catch (Exception e) { + result.put("success", false); + result.put("message", "文档解析失败"); + result.put("filePath", filePath); + result.put("fileName", fileName); + } + + return result; + } + + + + /* ================= helper: 下载/写临时文件 ================= */ + + private static File downloadToLocal(String fileUrl) throws IOException { + // 对 URL 的文件名部分做编码(处理中文/空格) + String encoded = encodeChineseInUrl(fileUrl); + URL url = new URL(encoded); + + Path tempDir = Files.createTempDirectory(TEMP_PREFIX); + String fileName = fileUrl.substring(fileUrl.lastIndexOf('/') + 1); + Path target = tempDir.resolve(fileName); + + try (InputStream in = url.openStream()) { + Files.copy(in, target, StandardCopyOption.REPLACE_EXISTING); + } catch (IOException e) { + logger.error("下载远程文件失败: {}, encodedUrl={}", fileUrl, encoded, e); + throw e; + } + return target.toFile(); + } + + private static String encodeChineseInUrl(String url) throws UnsupportedEncodingException { + int idx = url.lastIndexOf('/'); + if (idx < 0) { + return URLEncoder.encode(url, "UTF-8").replace("+", "%20"); + } + String prefix = url.substring(0, idx + 1); + String fileName = url.substring(idx + 1); + return prefix + URLEncoder.encode(fileName, "UTF-8").replace("+", "%20"); + } + + private static File writeMultipartToTemp(MultipartFile file) throws IOException { + Path tempDir = Files.createTempDirectory(TEMP_PREFIX); + String fileName = file.getOriginalFilename() == null ? UUID.randomUUID().toString() + ".docx" : file.getOriginalFilename(); + Path target = tempDir.resolve(fileName); + try (InputStream in = file.getInputStream()) { + Files.copy(in, target, StandardCopyOption.REPLACE_EXISTING); + } + return target.toFile(); + } + + /* ================= 文本抽取(鲁棒) ================= */ + + // 归一化文本:去零宽字符、合并空白等 + private static String normalizeText(String text) { + if (text == null) { + return ""; + } + text = text.replace('\u00A0', ' '); // NBSP + text = text.replaceAll("[\\u200B-\\u200F\\uFEFF]", ""); + text = text.replaceAll("[\\p{Cntrl}&&[^\r\n\t]]", " "); + text = text.replaceAll("\\s+", " "); + return text.trim(); + } + + // 鲁棒提取姓名+身份证对 + private static List extractReceivers(String text) { + List list = new ArrayList<>(); + if (text == null || text.trim().isEmpty()) { + return list; + } + + String norm = normalizeText(text); + + // 支持: (孙贤红)身份证号123... / 孙贤红 身份证号123... / 被授权人姓名:孙贤红 身份证号... + Pattern p1 = Pattern.compile("[\\((\\[]?\\s*([\\p{IsHan}·•・]{2,10})\\s*[\\))\\]]?\\s*身份证号\\s*(\\d{15}|\\d{17}[0-9Xx])"); + Matcher m1 = p1.matcher(norm); + while (m1.find()) { + ProAuthorizeDetails d = new ProAuthorizeDetails(); + d.setName(m1.group(1).trim()); + d.setIdNumber(m1.group(2).trim()); + list.add(d); + } + + // 若 p1 未找到,再试带“被授权人姓名/委托人”等标签的模式 + if (list.isEmpty()) { + Pattern p2 = Pattern.compile("(被授权人姓名|委托人姓名|委托人|被授权人)[::]?\\s*([\\p{IsHan}·•・]{2,10}).{0,50}?身份证号\\s*(\\d{15}|\\d{17}[0-9Xx])"); + Matcher m2 = p2.matcher(norm); + while (m2.find()) { + ProAuthorizeDetails d = new ProAuthorizeDetails(); + d.setName(m2.group(2).trim()); + d.setIdNumber(m2.group(3).trim()); + list.add(d); + } + } + + return list; + } + + /* ================= 上下文判定(正面/背面) ================= */ + + private static boolean contextSuggestsFront(String ctx) { + if (ctx == null) { + return false; + } + String lower = ctx.toLowerCase(); + return lower.contains("正面") || lower.contains("头像") || lower.contains("人像") || lower.contains("正 "); + } + + private static boolean contextSuggestsBack(String ctx) { + if (ctx == null) { + return false; + } + String lower = ctx.toLowerCase(); + return lower.contains("背面") || lower.contains("国徽") || lower.contains("背 "); + } + + /* ================= 图片上传(调用 sysFileService.upload) ================= */ + + // 你项目的 sysFileService.upload(MultipartFile) 应返回 AjaxResult,data 中包含 url 字段 + private static String saveImageWithSysFileService(byte[] data, String ext, RemoteFileService sysFileService) { + try { + String fileName = UUID.randomUUID().toString().replace("-", "") + "." + (ext == null ? "jpg" : ext); + MultipartFile multipartFile = new MockMultipartFile(fileName, fileName, "image/" + (ext == null ? "jpeg" : ext), data); + + AjaxResult uploadRes = sysFileService.upload(multipartFile); + if (uploadRes != null && uploadRes.isSuccess()) { + Object d = uploadRes.get("data"); + if (d instanceof Map) { + Map map = (Map) d; + Object url = map.get("url"); + if (url != null) { + return url.toString(); + } + } + } + } catch (Exception e) { + logger.warn("图片上传到 sysFileService 失败: {}", e.getMessage()); + } + return null; + } + + /* ================= docx 解析(文字 + 图片) ================= */ + + private static List parseDocx(InputStream inputStream, RemoteFileService sysFileService) throws Exception { + List receivers = new ArrayList<>(); + XWPFDocument doc = new XWPFDocument(OPCPackage.open(inputStream)); + + // 1) 提取全文文本并识别委托人姓名/身份证 + StringBuilder sb = new StringBuilder(); + for (XWPFParagraph p : doc.getParagraphs()) { + sb.append(p.getText()).append(" "); + } + for (XWPFTable t : doc.getTables()) { + for (XWPFTableRow r : t.getRows()) { + for (XWPFTableCell c : r.getTableCells()) { + sb.append(c.getText()).append(" "); + } + } + } + String fullText = sb.toString(); + receivers = extractReceivers(fullText); // 你已有的鲁棒方法 + + // 2) 遍历文档结构,收集图片 & 标识(注意保持全局 paraIndex) + List images = new ArrayList<>(); + List markers = new ArrayList<>(); + int globalParaIndex = 0; + int tableIdx = 0; + int globalImageCounter = 0; + + List bodyElements = doc.getBodyElements(); + for (IBodyElement be : bodyElements) { + if (be.getElementType() == BodyElementType.PARAGRAPH) { + XWPFParagraph p = (XWPFParagraph) be; + String text = normalizeText(p.getText()); + String cellKey = "body"; + // 标识:只匹配委托人相关 + if (text.contains("委托人") && (text.contains("正面") || text.contains("背面"))) { + Marker mk = new Marker(); + mk.isFront = text.contains("正面"); + mk.paraIndex = globalParaIndex; + mk.cellKey = cellKey; + mk.text = text; + markers.add(mk); + } + + // 图片(段落内) + if (p.getRuns() != null) { + for (XWPFRun run : p.getRuns()) { + List pics = run.getEmbeddedPictures(); + if (pics != null && !pics.isEmpty()) { + for (XWPFPicture pic : pics) { + XWPFPictureData pd = pic.getPictureData(); + ImgHolder ih = new ImgHolder(); + ih.data = pd.getData(); + ih.ext = pd.suggestFileExtension(); + ih.cellKey = cellKey; + ih.paraIndex = globalParaIndex; + ih.url = null; + ih.imageIndex = globalImageCounter++; + images.add(ih); + } + } + } + } + globalParaIndex++; + } else if (be.getElementType() == BodyElementType.TABLE) { + XWPFTable table = (XWPFTable) be; + for (int r = 0; r < table.getRows().size(); r++) { + XWPFTableRow row = table.getRows().get(r); + List cells = row.getTableCells(); + for (int c = 0; c < cells.size(); c++) { + XWPFTableCell cell = cells.get(c); + String cellKey = "t" + tableIdx + "_r" + r + "_c" + c; + for (XWPFParagraph p : cell.getParagraphs()) { + String text = normalizeText(p.getText()); + if (text.contains("委托人") && (text.contains("正面") || text.contains("背面"))) { + Marker mk = new Marker(); + mk.isFront = text.contains("正面"); + mk.paraIndex = globalParaIndex; + mk.cellKey = cellKey; + mk.text = text; + markers.add(mk); + } + + if (p.getRuns() != null) { + for (XWPFRun run : p.getRuns()) { + List pics = run.getEmbeddedPictures(); + if (pics != null && !pics.isEmpty()) { + for (XWPFPicture pic : pics) { + XWPFPictureData pd = pic.getPictureData(); + ImgHolder ih = new ImgHolder(); + ih.data = pd.getData(); + ih.ext = pd.suggestFileExtension(); + ih.cellKey = cellKey; + ih.paraIndex = globalParaIndex; + ih.url = null; + ih.imageIndex = globalImageCounter++; + images.add(ih); + } + } + } + } + globalParaIndex++; + } + } + } + tableIdx++; + } + } + + // 如果没有任何标识(markers),尽量 fallback:不做这里的自动索引分配(后面会尝试按剩余图片补位) + if (markers.isEmpty()) { + // 直接尝试按“模板索引规则”作为兜底(例如单人取 images[0]/images[2],两人取 [2,4] & [0,1] 等) + // 但首先还是希望尽量靠标识。下面的代码会在分配后进行补位。 + } + + // 3) 按标识顺序分配图片给委托人(只关注委托人) + Set usedImageIndices = new HashSet<>(); + int delegateIdx = 0; // 当前分配到第几个委托人(0-based) + + for (Marker mk : markers) { + // 只处理“委托人”的标识(忽略“法人代表”或其它) + if (mk.text == null || !mk.text.contains("委托人")) { + continue; + } + if (receivers.isEmpty()) { + break; + } + + int targetReceiver = Math.min(delegateIdx, receivers.size() - 1); + + // 找到最佳图片候选:优先同 cell、paraIndex >= marker.paraIndex、未被用过 + ImgHolder found = null; + for (ImgHolder ih : images) { + if (usedImageIndices.contains(ih.imageIndex)) { + continue; + } + if (!Objects.equals(ih.cellKey, mk.cellKey)) { + continue; + } + if (ih.paraIndex >= mk.paraIndex) { found = ih; break; } + } + // 如果未找到同 cell 的,则找全局 paraIndex >= marker.paraIndex + if (found == null) { + for (ImgHolder ih : images) { + if (usedImageIndices.contains(ih.imageIndex)) { + continue; + } + if (ih.paraIndex >= mk.paraIndex) { found = ih; break; } + } + } + // 最后兜底:任意未使用图片 + if (found == null) { + for (ImgHolder ih : images) { + if (!usedImageIndices.contains(ih.imageIndex)) { found = ih; break; } + } + } + + if (found != null) { + // 上传(惰性):若还未上传(url==null)则上传一次 + if (found.url == null) { + String uploaded = saveImageWithSysFileService(found.data, found.ext, sysFileService); + found.url = uploaded; + } + if (found.url != null) { + ProAuthorizeDetails p = receivers.get(targetReceiver); + if (mk.isFront) { + p.setFrontUrl(found.url); + } else { + p.setBackUrl(found.url); + } + } + usedImageIndices.add(found.imageIndex); + } + + // 如果这是“背面”,则才认为一组委托人完成,delegateIdx++(开始下一人) + if (!mk.isFront) { + delegateIdx++; + } + } + + // 4) 补位:若某些委托人缺少 front/back,用剩余图片补上(不覆盖已有) + if (!receivers.isEmpty() && !images.isEmpty()) { + for (int i = 0; i < receivers.size(); i++) { + ProAuthorizeDetails p = receivers.get(i); + if ((p.getFrontUrl() == null || p.getFrontUrl().isEmpty()) || + (p.getBackUrl() == null || p.getBackUrl().isEmpty())) { + + for (ImgHolder ih : images) { + if (usedImageIndices.contains(ih.imageIndex)) { + continue; + } + // 上传并分配 + if (ih.url == null) { + ih.url = saveImageWithSysFileService(ih.data, ih.ext, sysFileService); + } + if (ih.url == null) { + continue; + } + if (p.getFrontUrl() == null || p.getFrontUrl().isEmpty()) { + p.setFrontUrl(ih.url); + usedImageIndices.add(ih.imageIndex); + } else if (p.getBackUrl() == null || p.getBackUrl().isEmpty()) { + p.setBackUrl(ih.url); + usedImageIndices.add(ih.imageIndex); + } + if ((p.getFrontUrl() != null && !p.getFrontUrl().isEmpty()) && + (p.getBackUrl() != null && !p.getBackUrl().isEmpty())) { + break; + } + } + } + } + } + + return receivers; + } + + + + private static int handleParagraph(XWPFParagraph p, List receivers, + RemoteFileService sysFileService, int personIndex) { + String text = normalizeText(p.getText()); + + if (text.contains("身份证(正面)") || text.contains("身份证(背面)")) { + for (XWPFRun run : p.getRuns()) { + for (XWPFPicture pic : run.getEmbeddedPictures()) { + XWPFPictureData pd = pic.getPictureData(); + String url = saveImageWithSysFileService(pd.getData(), pd.suggestFileExtension(), sysFileService); + if (url == null) { + continue; + } + + if (text.contains("委托人")) { + // 委托人 + if (!receivers.isEmpty()) { + ProAuthorizeDetails person = receivers.get(Math.min(personIndex, receivers.size() - 1)); + if (text.contains("正面")) { + person.setFrontUrl(url); + } else { + person.setBackUrl(url); + personIndex++; // 背面处理完 → 下一个人 + } + } + } + } + } + } + return personIndex; + } + + + + private static class ImgHolder { + byte[] data; + String ext; + String cellKey; // 如 "body" 或 "t0_r1_c2" + int paraIndex; + String url; // 上传后返回的 url(惰性赋值) + int imageIndex; // 全局顺序索引(可选,用于调试) + } + + private static class Marker { + boolean isFront; // true = 正面, false = 背面 + int paraIndex; + String cellKey; + String text; // 原始文本,用于调试 + } + + +} diff --git a/bonus-modules/bonus-material/src/main/java/com/bonus/material/materialStation/controller/ProAuthorizeController.java b/bonus-modules/bonus-material/src/main/java/com/bonus/material/materialStation/controller/ProAuthorizeController.java index ed96d404..73e06fce 100644 --- a/bonus-modules/bonus-material/src/main/java/com/bonus/material/materialStation/controller/ProAuthorizeController.java +++ b/bonus-modules/bonus-material/src/main/java/com/bonus/material/materialStation/controller/ProAuthorizeController.java @@ -11,6 +11,7 @@ import com.bonus.common.core.web.page.TableDataInfo; import com.bonus.common.log.annotation.SysLog; import com.bonus.common.log.enums.OperaType; import com.bonus.material.common.annotation.PreventRepeatSubmit; +import com.bonus.material.common.utils.WordParserUtil; import com.bonus.material.materialStation.domain.ProAuthorizeDetails; import com.bonus.material.materialStation.domain.ProAuthorizeInfo; import com.bonus.material.materialStation.service.ProAuthorizeService; @@ -19,13 +20,17 @@ import com.bonus.system.api.RemoteFileService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.springframework.web.bind.annotation.*; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; import org.springframework.web.multipart.MultipartFile; import javax.annotation.Resource; +import javax.servlet.http.HttpServletResponse; import javax.validation.constraints.NotNull; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Objects; /** @@ -81,6 +86,26 @@ public class ProAuthorizeController extends BaseController { } } + @PostMapping("/parseWord") + public AjaxResult parseWord(@RequestParam("file") MultipartFile file) { + try { + AjaxResult result = sysFileService.upload(file); + if (!result.isSuccess()) { + return AjaxResult.error("文件上传失败"); + } + Map jsonObject = (Map) result.get("data"); + String fileName = file.getOriginalFilename(); + String filePath = jsonObject.get("url").toString(); + Map data = WordParserUtil.parseWordFile(file,fileName, filePath, sysFileService); + return AjaxResult.success(data); + } catch (Exception e) { + logger.error("解析失败", e); + return AjaxResult.error("解析失败,请按模版填写内容"); + } + } + + + /** * 授权提交 */ @@ -138,5 +163,15 @@ public class ProAuthorizeController extends BaseController { return AjaxResult.success(ListPagingUtil.paging(pageIndex, pageSize, new ArrayList<>())); } } + + /** + * 授权委托书模版下载 + */ + @ApiOperation(value = "授权委托书模版下载") + @PostMapping("/downLoad") + public void downLoadExcelFile(){ + HttpServletResponse resp = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getResponse(); + service.downLoadTemplate(resp); + } } diff --git a/bonus-modules/bonus-material/src/main/java/com/bonus/material/materialStation/service/ProAuthorizeService.java b/bonus-modules/bonus-material/src/main/java/com/bonus/material/materialStation/service/ProAuthorizeService.java index 27c23283..58276f52 100644 --- a/bonus-modules/bonus-material/src/main/java/com/bonus/material/materialStation/service/ProAuthorizeService.java +++ b/bonus-modules/bonus-material/src/main/java/com/bonus/material/materialStation/service/ProAuthorizeService.java @@ -9,6 +9,7 @@ import com.bonus.material.materialStation.domain.ProAuthorizeInfo; import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.multipart.MultipartFile; +import javax.servlet.http.HttpServletResponse; import java.util.List; /** @@ -57,4 +58,10 @@ public interface ProAuthorizeService { int updateAuthorizeInfoSign(ProAuthorizeDetails bean); List getAuthorList(ProAuthorizeInfo bean); + + /** + * 授权委托书模版下载 + * @param resp + */ + void downLoadTemplate(HttpServletResponse resp); } diff --git a/bonus-modules/bonus-material/src/main/java/com/bonus/material/materialStation/service/impl/ProAuthorizeServiceImpl.java b/bonus-modules/bonus-material/src/main/java/com/bonus/material/materialStation/service/impl/ProAuthorizeServiceImpl.java index 243c5852..dc0554c5 100644 --- a/bonus-modules/bonus-material/src/main/java/com/bonus/material/materialStation/service/impl/ProAuthorizeServiceImpl.java +++ b/bonus-modules/bonus-material/src/main/java/com/bonus/material/materialStation/service/impl/ProAuthorizeServiceImpl.java @@ -14,6 +14,7 @@ import com.bonus.material.materialStation.mapper.ProAuthorizeMapper; import com.bonus.material.materialStation.service.ProAuthorizeService; import lombok.extern.slf4j.Slf4j; import okhttp3.*; +import org.apache.commons.io.IOUtils; import org.hibernate.validator.internal.util.StringHelper; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -22,10 +23,8 @@ import org.springframework.util.CollectionUtils; import org.springframework.web.multipart.MultipartFile; import javax.annotation.Resource; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; +import javax.servlet.http.HttpServletResponse; +import java.io.*; import java.net.HttpURLConnection; import java.net.URI; import java.net.URL; @@ -283,6 +282,38 @@ public class ProAuthorizeServiceImpl implements ProAuthorizeService { } } + @Override + public void downLoadTemplate(HttpServletResponse response) { + // 模板名称 + String templateName = "授权委托书模板.doc"; + try ( + InputStream input = this.getClass().getClassLoader().getResourceAsStream("template/授权委托书模板.doc"); + OutputStream out = response.getOutputStream() + ) { + if (input == null) { + throw new FileNotFoundException("模板文件不存在: " + templateName); + } + // 设置响应头 + response.setCharacterEncoding("UTF-8"); + response.setContentType("application/msword"); + response.setHeader( + "Content-Disposition", + "attachment;filename=" + new String(templateName.getBytes("UTF-8"), "ISO-8859-1") + ); + response.setHeader("Access-Control-Expose-Headers", "Content-Disposition"); + // 缓冲区传输 + byte[] buffer = new byte[1024]; + int bytesToRead; + while ((bytesToRead = input.read(buffer)) != -1) { + out.write(buffer, 0, bytesToRead); + } + out.flush(); + } catch (IOException e) { + log.error("下载模板失败: {}", e.getMessage(), e); + } + } + + /** * 判断关键字是否包含在item中 * @param item diff --git a/bonus-modules/bonus-material/src/main/resources/mapper/material/repair/RepairAuditDetailsMapper.xml b/bonus-modules/bonus-material/src/main/resources/mapper/material/repair/RepairAuditDetailsMapper.xml index a942b6d8..f2efa20d 100644 --- a/bonus-modules/bonus-material/src/main/resources/mapper/material/repair/RepairAuditDetailsMapper.xml +++ b/bonus-modules/bonus-material/src/main/resources/mapper/material/repair/RepairAuditDetailsMapper.xml @@ -340,6 +340,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" tk.create_time AS createTime, tk.remark, tk.CODE AS repairNum, + rad.audit_time as auditTime, GROUP_CONCAT(DISTINCT mt4.type_id) AS firstId FROM tm_task tk @@ -402,6 +403,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" tk.create_time AS createTime, tk.remark, tk.CODE AS repairNum, + rad.audit_time as auditTime, GROUP_CONCAT(DISTINCT mt4.type_id) AS firstId FROM tm_task tk @@ -449,9 +451,16 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" GROUP BY tk.CODE ) AS combined_results - ORDER BY - taskStatus, - createTime DESC; -- 统一在外部排序 + + + ORDER BY auditTime DESC + + + ORDER BY + taskStatus, + createTime DESC + +