feat:优化增加任务同步接口,允许前端手动控制同步
| | |
| | | // - YYYYMMDD |
| | | // - yyyy-MM-dd HH:mm:ss |
| | | console.log('尝试格式化日期字符串:', dateStr) |
| | | |
| | | // 如果输入为空或无效,返回空字符串 |
| | | if (!dateStr || typeof dateStr !== 'string') { |
| | | console.warn('日期字符串无效:', dateStr) |
| | | return '' |
| | | } |
| | | |
| | | // 清洗日期字符串 |
| | | let cleaned = dateStr |
| | | .replace(/[年月]/g, '-') |
| | | .replace(/[日号]/g, ' ') // 日/号 → 空格,保留日期和时间的分隔 |
| | |
| | | .replace(/秒/g, '') |
| | | .replace(/\s+/g, ' ') // 多个空格合并为一个 |
| | | .trim() |
| | | |
| | | console.log('清理后的日期字符串:', cleaned) |
| | | |
| | | // 分离日期和时间部分 |
| | | const parts = cleaned.split(' ') |
| | | let datePart = parts[0] || '' |
| | | let timePart = parts[1] || '' |
| | | |
| | | let dateResult = '' |
| | | |
| | | // 处理日期部分 |
| | | // 如果是YYMMDD格式 |
| | | if (/^\d{6}$/.test(cleaned)) { |
| | | const year = '20' + cleaned.substring(0, 2) |
| | | const month = cleaned.substring(2, 4) |
| | | const day = cleaned.substring(4, 6) |
| | | dateResult = `${year}-${month}-${day}`; |
| | | } |
| | | // 如果是YYYYMMDD格式 |
| | | else if (/^\d{8}$/.test(cleaned)) { |
| | | const year = cleaned.substring(0, 4) |
| | | const month = cleaned.substring(4, 6) |
| | | const day = cleaned.substring(6, 8) |
| | | if (/^\d{6}$/.test(datePart)) { |
| | | const year = '20' + datePart.substring(0, 2) |
| | | const month = datePart.substring(2, 4) |
| | | const day = datePart.substring(4, 6) |
| | | dateResult = `${year}-${month}-${day}` |
| | | } |
| | | // 如果已经是合理格式,直接使用 |
| | | else if (cleaned.match(/^\d{4}[-/]\d{1,2}[-/]\d{1,2}$/)) { |
| | | dateResult = cleaned.replace(/[//]/g, '-') |
| | | // 如果是YYYYMMDD格式 |
| | | else if (/^\d{8}$/.test(datePart)) { |
| | | const year = datePart.substring(0, 4) |
| | | const month = datePart.substring(4, 6) |
| | | const day = datePart.substring(6, 8) |
| | | dateResult = `${year}-${month}-${day}` |
| | | } |
| | | // 如果已经包含时分秒,直接返回 |
| | | else if (cleaned.match(/^\d{4}[-/]\d{1,2}[-/]\d{1,2}\s+\d{1,2}:\d{1,2}:\d{1,2}$/)) { |
| | | return cleaned.replace(/[//]/g, '-') |
| | | } |
| | | // 如果包含时分但缺少秒(yyyy-MM-dd HH:mm:) |
| | | else if (cleaned.match(/^\d{4}[-/]\d{1,2}[-/]\d{1,2}\s+\d{1,2}:\d{1,2}:$/)) { |
| | | // 去掉末尾的冒号,补上秒数00 |
| | | return cleaned.replace(/[//]/g, '-').replace(/:$/, '') + ':00' |
| | | } |
| | | // 如果只包含时分(yyyy-MM-dd HH:mm) |
| | | else if (cleaned.match(/^\d{4}[-/]\d{1,2}[-/]\d{1,2}\s+\d{1,2}:\d{1,2}$/)) { |
| | | return cleaned.replace(/[//]/g, '-') + ':00' |
| | | // 如果是yyyy-MM-dd或yyyy/MM/dd格式 |
| | | else if (datePart.match(/^\d{4}[-\/]\d{1,2}[-\/]\d{1,2}$/)) { |
| | | dateResult = datePart.replace(/\//g, '-') |
| | | } |
| | | else { |
| | | dateResult = dateStr |
| | | dateResult = datePart |
| | | } |
| | | |
| | | // 如果日期格式正确,添加默认时分秒 00:00:00 |
| | | if (dateResult && dateResult.match(/^\d{4}-\d{1,2}-\d{1,2}$/)) { |
| | | return dateResult + ' 00:00:00' |
| | | // 验证日期部分是否有效 |
| | | if (!dateResult.match(/^\d{4}-\d{1,2}-\d{1,2}$/)) { |
| | | console.warn('日期格式不正确:', dateResult) |
| | | return '' |
| | | } |
| | | |
| | | return dateResult |
| | | // 处理时间部分 |
| | | let timeResult = '00:00:00' // 默认时间 |
| | | |
| | | if (timePart) { |
| | | // 移除末尾多余的冒号 |
| | | timePart = timePart.replace(/:+$/, '') |
| | | |
| | | // 分割时、分、秒 |
| | | const timeParts = timePart.split(':') |
| | | const hour = timeParts[0] || '00' |
| | | const minute = timeParts[1] || '00' |
| | | const second = timeParts[2] || '00' |
| | | |
| | | // 验证时间数字是否有效 |
| | | const hourNum = parseInt(hour, 10) |
| | | const minuteNum = parseInt(minute, 10) |
| | | const secondNum = parseInt(second, 10) |
| | | |
| | | if (!isNaN(hourNum) && !isNaN(minuteNum) && !isNaN(secondNum) && |
| | | hourNum >= 0 && hourNum < 24 && minuteNum >= 0 && minuteNum < 60 && secondNum >= 0 && secondNum < 60) { |
| | | // 补齐两位数 |
| | | timeResult = `${String(hourNum).padStart(2, '0')}:${String(minuteNum).padStart(2, '0')}:${String(secondNum).padStart(2, '0')}` |
| | | } else { |
| | | console.warn('时间数值超出范围,使用默认值00:00:00') |
| | | } |
| | | } |
| | | |
| | | const finalResult = `${dateResult} ${timeResult}` |
| | | console.log('最终格式化结果:', finalResult) |
| | | |
| | | return finalResult |
| | | } |
| | | } |
| | | } |
| | |
| | | /** |
| | | * 医院搜索最低匹配分数阈值(低于此分数的结果将被过滤) |
| | | */ |
| | | private static final int MIN_MATCH_SCORE_THRESHOLD = 50; |
| | | private static final int MIN_MATCH_SCORE_THRESHOLD = 1; |
| | | |
| | | @Autowired |
| | | private HospDataMapper hospDataMapper; |
| | |
| | | import com.ruoyi.common.utils.SecurityUtils; |
| | | import com.ruoyi.system.domain.SysTaskEmergency; |
| | | import com.ruoyi.system.service.*; |
| | | import com.ruoyi.system.service.ILegacySystemSyncService; |
| | | import com.ruoyi.system.service.ITaskDispatchSyncService; |
| | | import org.springframework.beans.factory.annotation.Qualifier; |
| | | import org.springframework.security.access.prepost.PreAuthorize; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | |
| | | @Autowired |
| | | @Qualifier("tiandituMapService") |
| | | private IMapService mapService; |
| | | |
| | | @Autowired |
| | | private ILegacySystemSyncService legacySystemSyncService; |
| | | |
| | | @Autowired |
| | | private ITaskDispatchSyncService taskDispatchSyncService; |
| | | |
| | | /** |
| | | * 查询任务管理列表(后台管理端) |
| | |
| | | this.actualEndTime = actualEndTime; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 手动同步服务单到旧系统 |
| | | * 当服务单同步失败或未同步时,可以通过此接口手动触发同步 |
| | | */ |
| | | @PreAuthorize("@ss.hasPermi('task:general:edit')") |
| | | @Log(title = "手动同步服务单", businessType = BusinessType.UPDATE) |
| | | @PostMapping("/syncServiceOrder/{taskId}") |
| | | public AjaxResult syncServiceOrder(@PathVariable Long taskId) { |
| | | try { |
| | | // 查询任务信息 |
| | | SysTask task = sysTaskService.selectSysTaskByTaskId(taskId); |
| | | if (task == null) { |
| | | return error("任务不存在"); |
| | | } |
| | | |
| | | // 只支持急救转运任务 |
| | | if (!"EMERGENCY_TRANSFER".equals(task.getTaskType())) { |
| | | return error("只有急救转运任务才能同步到旧系统"); |
| | | } |
| | | |
| | | // 调用同步服务 |
| | | Long serviceOrdId = legacySystemSyncService.syncEmergencyTaskToLegacy(taskId); |
| | | |
| | | if (serviceOrdId != null && serviceOrdId > 0) { |
| | | return success("服务单同步成功,ServiceOrdID: " + serviceOrdId); |
| | | } else { |
| | | return error("服务单同步失败,请查看同步错误信息"); |
| | | } |
| | | |
| | | } catch (Exception e) { |
| | | logger.error("手动同步服务单异常,taskId: {}", taskId, e); |
| | | return error("同步异常: " + e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 手动同步调度单到旧系统 |
| | | * 当调度单同步失败或未同步时,可以通过此接口手动触发同步 |
| | | */ |
| | | @PreAuthorize("@ss.hasPermi('task:general:edit')") |
| | | @Log(title = "手动同步调度单", businessType = BusinessType.UPDATE) |
| | | @PostMapping("/syncDispatchOrder/{taskId}") |
| | | public AjaxResult syncDispatchOrder(@PathVariable Long taskId) { |
| | | try { |
| | | // 查询任务信息 |
| | | SysTask task = sysTaskService.selectSysTaskByTaskId(taskId); |
| | | if (task == null) { |
| | | return error("任务不存在"); |
| | | } |
| | | |
| | | // 只支持急救转运任务 |
| | | if (!"EMERGENCY_TRANSFER".equals(task.getTaskType())) { |
| | | return error("只有急救转运任务才能同步到旧系统"); |
| | | } |
| | | |
| | | // 查询急救转运扩展信息 |
| | | SysTaskEmergency emergency = sysTaskEmergencyService.selectSysTaskEmergencyByTaskId(taskId); |
| | | if (emergency == null) { |
| | | return error("急救转运扩展信息不存在"); |
| | | } |
| | | |
| | | // 必须先有服务单 |
| | | if (emergency.getLegacyServiceOrdId() == null || emergency.getLegacyServiceOrdId() <= 0) { |
| | | return error("请先同步服务单"); |
| | | } |
| | | |
| | | // 调用同步服务 |
| | | Long dispatchOrdId = taskDispatchSyncService.syncDispatch(taskId); |
| | | |
| | | if (dispatchOrdId != null && dispatchOrdId > 0) { |
| | | return success("调度单同步成功,DispatchOrdID: " + dispatchOrdId); |
| | | } else { |
| | | return error("调度单同步失败,请查看同步错误信息"); |
| | | } |
| | | |
| | | } catch (Exception e) { |
| | | logger.error("手动同步调度单异常,taskId: {}", taskId, e); |
| | | return error("同步异常: " + e.getMessage()); |
| | | } |
| | | } |
| | | } |
| | |
| | | private static final Set<String> STOP_WORDS = new HashSet<>(Arrays.asList( |
| | | "医院", "诊所", "卫生", "镇", "乡", |
| | | "街道", "路", "号", "栋", "单元", "室", "层", "楼", "的", "了", |
| | | "在", "与", "和", "及", "等", "之", "于", "为", "有", "无" |
| | | "在", "与", "和", "及", "等", "之", "于", "为", "有", "无","(",")","(",")","、",",","。","!","?",";",":","“","”","‘","’" |
| | | )); |
| | | |
| | | /** |
| | |
| | | private static final Set<String> HIGH_WEIGHT_WORDS = new HashSet<>(Arrays.asList( |
| | | "人民", "中医", "中西医", "中西医结合", "医疗", "妇幼", "儿童", "肤科", |
| | | "口腔", "眼科", "骨科", "整形", "精神", "康复", "急救", "医学院", |
| | | "医科大学", "专科", "第一", "第二", "第三", "第四", "第五", |
| | | "军区", "军医", "中心", "附属", "省立", "市立", "区立" |
| | | "医科大学", "专科", |
| | | "军区", "军医", "中心", "附属", "省立", "市立", "区立", "脑科", "总院", "慈善", "保健院", "口腔", "祈福", "眼科", "铁路", "附一", "附二", "附三", "附四", "附五", "附六", |
| | | "第一", "第二", "第三", "第四", "第五", "第六", "第七", "第八", "第九", "第十", |
| | | "肿瘤" |
| | | )); |
| | | |
| | | /** |
| | |
| | | private static final Set<String> HOSPITAL_KEYWORD_DICT = new HashSet<>(Arrays.asList( |
| | | "中医院", "中医医院", "市医院", "省医院", "人民医院", "中心医院", "口腔医院", |
| | | "华侨医院", "儿童医院", "眼科中心", "福利院", "门诊部", "中山大学", "附属医院", |
| | | "孙逸仙" |
| | | "孙逸仙","门诊" |
| | | )); |
| | | |
| | | /** 组合词生成的最小字符长度 */ |
| | |
| | | |
| | | // 分院特征关键词 |
| | | String[] branchKeywords = { |
| | | "分院", "分部", "门诊部", "社区卫生", "卫生站", "卫生服务中心", |
| | | "东院", "西院", "南院", "北院", "新院", "老院" |
| | | "分院", "分部", "门诊部","门诊", "社区卫生", "卫生站", "卫生服务中心", |
| | | "东院", "西院", "南院", "北院", "新院", "老院", |
| | | "人民医院","附属医院","福利院","分院" |
| | | |
| | | |
| | | }; |
| | | |
| | | for (String keyword : branchKeywords) { |
| New file |
| | |
| | | package com.ruoyi.common.utils.image; |
| | | |
| | | import org.slf4j.Logger; |
| | | import org.slf4j.LoggerFactory; |
| | | import org.springframework.web.multipart.MultipartFile; |
| | | |
| | | import javax.imageio.IIOImage; |
| | | import javax.imageio.ImageIO; |
| | | import javax.imageio.ImageWriteParam; |
| | | import javax.imageio.ImageWriter; |
| | | import javax.imageio.stream.ImageOutputStream; |
| | | import java.awt.Graphics2D; |
| | | import java.awt.RenderingHints; |
| | | import java.awt.image.BufferedImage; |
| | | import java.io.File; |
| | | import java.io.IOException; |
| | | import java.io.InputStream; |
| | | import java.util.Iterator; |
| | | |
| | | /** |
| | | * 图片压缩工具类 |
| | | * 专为OCR识别优化,在保证文字清晰度的前提下压缩图片大小 |
| | | * |
| | | * @author ruoyi |
| | | * @date 2025-01-20 |
| | | */ |
| | | public class ImageCompressUtil { |
| | | |
| | | private static final Logger log = LoggerFactory.getLogger(ImageCompressUtil.class); |
| | | |
| | | /** |
| | | * 默认最大文件大小(10MB) |
| | | */ |
| | | public static final long DEFAULT_MAX_FILE_SIZE = 10 * 1024 * 1024; |
| | | |
| | | /** |
| | | * OCR最佳识别分辨率(最小边像素) |
| | | */ |
| | | public static final int OCR_OPTIMAL_MIN_SIZE = 1500; |
| | | |
| | | /** |
| | | * 高质量压缩质量参数(0.90 = 90%质量) |
| | | */ |
| | | public static final float HIGH_QUALITY = 0.90f; |
| | | |
| | | /** |
| | | * 中等质量压缩质量参数(0.80 = 80%质量) |
| | | */ |
| | | public static final float MEDIUM_QUALITY = 0.80f; |
| | | |
| | | /** |
| | | * 低质量压缩质量参数(0.75 = 75%质量) |
| | | */ |
| | | public static final float LOW_QUALITY = 0.75f; |
| | | |
| | | /** |
| | | * 智能压缩图片(针对OCR优化) |
| | | * 使用默认3MB限制 |
| | | * |
| | | * @param file 上传的图片文件 |
| | | * @return 压缩后的临时文件 |
| | | * @throws IOException IO异常 |
| | | */ |
| | | public static File compressForOCR(MultipartFile file) throws IOException { |
| | | return compressForOCR(file, DEFAULT_MAX_FILE_SIZE); |
| | | } |
| | | |
| | | /** |
| | | * 智能压缩图片(针对OCR优化) |
| | | * 保证识别准确率的同时压缩到指定大小以下 |
| | | * |
| | | * @param file 上传的图片文件 |
| | | * @param maxSize 最大文件大小(字节) |
| | | * @return 压缩后的临时文件 |
| | | * @throws IOException IO异常 |
| | | */ |
| | | public static File compressForOCR(MultipartFile file, long maxSize) throws IOException { |
| | | long fileSize = file.getSize(); |
| | | |
| | | // 如果文件小于限制,直接保存 |
| | | if (fileSize <= maxSize) { |
| | | log.debug("图片大小 {} KB 未超过限制 {} KB,无需压缩", |
| | | fileSize / 1024.0, maxSize / 1024.0); |
| | | return saveToTempFile(file); |
| | | } |
| | | |
| | | log.info("图片大小 {} MB 超过限制 {} MB,开始智能压缩...", |
| | | fileSize / 1024.0 / 1024.0, maxSize / 1024.0 / 1024.0); |
| | | |
| | | // 读取原始图片 |
| | | BufferedImage originalImage = ImageIO.read(file.getInputStream()); |
| | | if (originalImage == null) { |
| | | throw new IOException("无法读取图片文件,可能格式不支持"); |
| | | } |
| | | |
| | | int originalWidth = originalImage.getWidth(); |
| | | int originalHeight = originalImage.getHeight(); |
| | | log.info("原始图片尺寸: {}x{}", originalWidth, originalHeight); |
| | | |
| | | // 策略1:先尝试高质量JPEG压缩(不改变尺寸) |
| | | File compressedFile = compressWithQuality(originalImage, file.getOriginalFilename(), HIGH_QUALITY); |
| | | |
| | | if (compressedFile.length() <= maxSize) { |
| | | log.info("压缩成功(高质量压缩),文件大小: {} MB", |
| | | compressedFile.length() / 1024.0 / 1024.0); |
| | | return compressedFile; |
| | | } |
| | | |
| | | // 策略2:如果还是太大,适度缩小尺寸(保证OCR识别) |
| | | double scaleFactor = calculateScaleFactor(originalWidth, originalHeight, maxSize, compressedFile.length()); |
| | | |
| | | if (scaleFactor < 1.0) { |
| | | int newWidth = (int) (originalWidth * scaleFactor); |
| | | int newHeight = (int) (originalHeight * scaleFactor); |
| | | log.info("调整图片尺寸至: {}x{} (缩放比例: {}%)", |
| | | newWidth, newHeight, (int)(scaleFactor * 100)); |
| | | |
| | | BufferedImage resizedImage = resizeImageHighQuality(originalImage, newWidth, newHeight); |
| | | compressedFile.delete(); // 删除之前的临时文件 |
| | | compressedFile = compressWithQuality(resizedImage, file.getOriginalFilename(), HIGH_QUALITY); |
| | | } |
| | | |
| | | // 策略3:如果还是太大,降低压缩质量(最后手段) |
| | | if (compressedFile.length() > maxSize) { |
| | | log.warn("尺寸调整后仍超限,降低压缩质量"); |
| | | BufferedImage finalImage; |
| | | if (scaleFactor < 1.0) { |
| | | int newWidth = (int) (originalWidth * scaleFactor); |
| | | int newHeight = (int) (originalHeight * scaleFactor); |
| | | finalImage = resizeImageHighQuality(originalImage, newWidth, newHeight); |
| | | } else { |
| | | finalImage = originalImage; |
| | | } |
| | | |
| | | compressedFile.delete(); // 删除之前的临时文件 |
| | | |
| | | // 尝试中等质量 |
| | | compressedFile = compressWithQuality(finalImage, file.getOriginalFilename(), MEDIUM_QUALITY); |
| | | |
| | | // 如果还不行,使用低质量 |
| | | if (compressedFile.length() > maxSize) { |
| | | log.warn("使用低质量压缩(75%)"); |
| | | compressedFile.delete(); |
| | | compressedFile = compressWithQuality(finalImage, file.getOriginalFilename(), LOW_QUALITY); |
| | | } |
| | | } |
| | | |
| | | long finalSize = compressedFile.length(); |
| | | double compressionRatio = (1 - (double)finalSize / fileSize) * 100; |
| | | log.info("最终压缩完成,文件大小: {} MB (压缩率: {}%)", |
| | | finalSize / 1024.0 / 1024.0, (int)compressionRatio); |
| | | |
| | | if (finalSize > maxSize) { |
| | | log.warn("警告:压缩后文件大小 {} MB 仍超过限制 {} MB", |
| | | finalSize / 1024.0 / 1024.0, maxSize / 1024.0 / 1024.0); |
| | | } |
| | | |
| | | return compressedFile; |
| | | } |
| | | |
| | | /** |
| | | * 使用指定质量压缩图片为JPEG格式 |
| | | * |
| | | * @param image 图片对象 |
| | | * @param originalFilename 原始文件名 |
| | | * @param quality 压缩质量(0.0-1.0) |
| | | * @return 压缩后的文件 |
| | | * @throws IOException IO异常 |
| | | */ |
| | | public static File compressWithQuality(BufferedImage image, String originalFilename, float quality) throws IOException { |
| | | if (quality < 0.0f || quality > 1.0f) { |
| | | throw new IllegalArgumentException("压缩质量必须在0.0到1.0之间"); |
| | | } |
| | | |
| | | // 转换为RGB格式(JPEG不支持透明度) |
| | | BufferedImage rgbImage = convertToRGB(image); |
| | | |
| | | // 创建临时文件 |
| | | String tempDir = System.getProperty("java.io.tmpdir"); |
| | | String filename = System.currentTimeMillis() + "_compressed_" + |
| | | getJpegFilename(originalFilename); |
| | | File outputFile = new File(tempDir, filename); |
| | | |
| | | // 使用ImageWriter进行高质量压缩 |
| | | Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName("jpg"); |
| | | if (!writers.hasNext()) { |
| | | throw new IOException("系统不支持JPEG编码"); |
| | | } |
| | | |
| | | ImageWriter writer = writers.next(); |
| | | ImageWriteParam param = writer.getDefaultWriteParam(); |
| | | |
| | | // 设置压缩模式和质量 |
| | | param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); |
| | | param.setCompressionQuality(quality); |
| | | |
| | | try (ImageOutputStream ios = ImageIO.createImageOutputStream(outputFile)) { |
| | | writer.setOutput(ios); |
| | | writer.write(null, new IIOImage(rgbImage, null, null), param); |
| | | } finally { |
| | | writer.dispose(); |
| | | } |
| | | |
| | | log.debug("压缩完成,质量: {}%, 文件大小: {} KB", |
| | | (int)(quality * 100), outputFile.length() / 1024.0); |
| | | |
| | | return outputFile; |
| | | } |
| | | |
| | | /** |
| | | * 高质量图片缩放(专为OCR优化) |
| | | * 使用双三次插值算法保证文字清晰度 |
| | | * |
| | | * @param originalImage 原始图片 |
| | | * @param targetWidth 目标宽度 |
| | | * @param targetHeight 目标高度 |
| | | * @return 缩放后的图片 |
| | | */ |
| | | public static BufferedImage resizeImageHighQuality(BufferedImage originalImage, int targetWidth, int targetHeight) { |
| | | if (targetWidth <= 0 || targetHeight <= 0) { |
| | | throw new IllegalArgumentException("目标宽度和高度必须大于0"); |
| | | } |
| | | |
| | | BufferedImage resizedImage = new BufferedImage(targetWidth, targetHeight, BufferedImage.TYPE_INT_RGB); |
| | | Graphics2D g2d = resizedImage.createGraphics(); |
| | | |
| | | try { |
| | | // 设置高质量渲染参数(关键!) |
| | | g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); |
| | | g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); |
| | | g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); |
| | | g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); |
| | | g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY); |
| | | |
| | | g2d.drawImage(originalImage, 0, 0, targetWidth, targetHeight, null); |
| | | } finally { |
| | | g2d.dispose(); |
| | | } |
| | | |
| | | return resizedImage; |
| | | } |
| | | |
| | | /** |
| | | * 通用图片压缩(按目标宽高) |
| | | * |
| | | * @param file 上传的图片文件 |
| | | * @param targetWidth 目标宽度 |
| | | * @param targetHeight 目标高度 |
| | | * @param quality 压缩质量(0.0-1.0) |
| | | * @return 压缩后的文件 |
| | | * @throws IOException IO异常 |
| | | */ |
| | | public static File compress(MultipartFile file, int targetWidth, int targetHeight, float quality) throws IOException { |
| | | BufferedImage originalImage = ImageIO.read(file.getInputStream()); |
| | | if (originalImage == null) { |
| | | throw new IOException("无法读取图片文件"); |
| | | } |
| | | |
| | | BufferedImage resizedImage = resizeImageHighQuality(originalImage, targetWidth, targetHeight); |
| | | return compressWithQuality(resizedImage, file.getOriginalFilename(), quality); |
| | | } |
| | | |
| | | /** |
| | | * 按比例压缩图片 |
| | | * |
| | | * @param file 上传的图片文件 |
| | | * @param scale 缩放比例(0.0-1.0) |
| | | * @param quality 压缩质量(0.0-1.0) |
| | | * @return 压缩后的文件 |
| | | * @throws IOException IO异常 |
| | | */ |
| | | public static File compressByScale(MultipartFile file, double scale, float quality) throws IOException { |
| | | if (scale <= 0.0 || scale > 1.0) { |
| | | throw new IllegalArgumentException("缩放比例必须在0.0到1.0之间"); |
| | | } |
| | | |
| | | BufferedImage originalImage = ImageIO.read(file.getInputStream()); |
| | | if (originalImage == null) { |
| | | throw new IOException("无法读取图片文件"); |
| | | } |
| | | |
| | | int newWidth = (int) (originalImage.getWidth() * scale); |
| | | int newHeight = (int) (originalImage.getHeight() * scale); |
| | | |
| | | BufferedImage resizedImage = resizeImageHighQuality(originalImage, newWidth, newHeight); |
| | | return compressWithQuality(resizedImage, file.getOriginalFilename(), quality); |
| | | } |
| | | |
| | | /** |
| | | * 计算最佳缩放比例 |
| | | * |
| | | * @param width 原始宽度 |
| | | * @param height 原始高度 |
| | | * @param targetSize 目标文件大小 |
| | | * @param currentSize 当前文件大小 |
| | | * @return 缩放比例 |
| | | */ |
| | | private static double calculateScaleFactor(int width, int height, long targetSize, long currentSize) { |
| | | int minDimension = Math.min(width, height); |
| | | double scaleFactor = 1.0; |
| | | |
| | | // 基于文件大小估算缩放比例 |
| | | double sizeRatio = Math.sqrt((double) targetSize / currentSize); |
| | | |
| | | // 保护最小尺寸(保证OCR识别) |
| | | if (minDimension > 2000) { |
| | | // 大图片,可以适度缩小 |
| | | scaleFactor = Math.min(sizeRatio, 2000.0 / minDimension); |
| | | } else if (minDimension > OCR_OPTIMAL_MIN_SIZE) { |
| | | // 中等图片,轻微缩小 |
| | | scaleFactor = Math.min(sizeRatio, (double) OCR_OPTIMAL_MIN_SIZE / minDimension); |
| | | } else { |
| | | // 小图片,尽量不缩小 |
| | | scaleFactor = Math.min(sizeRatio, 0.95); |
| | | } |
| | | |
| | | return scaleFactor; |
| | | } |
| | | |
| | | /** |
| | | * 将图片转换为RGB格式 |
| | | * |
| | | * @param image 原始图片 |
| | | * @return RGB格式图片 |
| | | */ |
| | | private static BufferedImage convertToRGB(BufferedImage image) { |
| | | if (image.getType() == BufferedImage.TYPE_INT_RGB) { |
| | | return image; |
| | | } |
| | | |
| | | BufferedImage rgbImage = new BufferedImage( |
| | | image.getWidth(), |
| | | image.getHeight(), |
| | | BufferedImage.TYPE_INT_RGB |
| | | ); |
| | | Graphics2D g = rgbImage.createGraphics(); |
| | | try { |
| | | g.drawImage(image, 0, 0, null); |
| | | } finally { |
| | | g.dispose(); |
| | | } |
| | | |
| | | return rgbImage; |
| | | } |
| | | |
| | | /** |
| | | * 将文件名转换为JPEG格式 |
| | | * |
| | | * @param originalFilename 原始文件名 |
| | | * @return JPEG文件名 |
| | | */ |
| | | private static String getJpegFilename(String originalFilename) { |
| | | if (originalFilename == null || originalFilename.isEmpty()) { |
| | | return "image.jpg"; |
| | | } |
| | | |
| | | return originalFilename.replaceAll("\\.(png|PNG|gif|GIF|bmp|BMP|webp|WEBP)$", ".jpg"); |
| | | } |
| | | |
| | | /** |
| | | * 保存MultipartFile到临时文件 |
| | | * |
| | | * @param file 上传的文件 |
| | | * @return 临时文件 |
| | | * @throws IOException IO异常 |
| | | */ |
| | | private static File saveToTempFile(MultipartFile file) throws IOException { |
| | | String tempDir = System.getProperty("java.io.tmpdir"); |
| | | String originalFilename = file.getOriginalFilename(); |
| | | File tempFile = new File(tempDir, System.currentTimeMillis() + "_" + originalFilename); |
| | | file.transferTo(tempFile); |
| | | return tempFile; |
| | | } |
| | | } |
| New file |
| | |
| | | /** |
| | | * 图片处理工具包 |
| | | * |
| | | * <h2>主要功能</h2> |
| | | * <ul> |
| | | * <li>智能图片压缩(专为OCR优化)</li> |
| | | * <li>高质量图片缩放</li> |
| | | * <li>图片格式转换</li> |
| | | * </ul> |
| | | * |
| | | * <h2>使用示例</h2> |
| | | * |
| | | * <h3>1. OCR图片智能压缩(推荐)</h3> |
| | | * <pre> |
| | | * // 自动压缩到3MB以下,保证OCR识别准确率 |
| | | * File compressedFile = ImageCompressUtil.compressForOCR(multipartFile); |
| | | * |
| | | * // 自定义大小限制 |
| | | * File compressedFile = ImageCompressUtil.compressForOCR(multipartFile, 5 * 1024 * 1024); // 5MB |
| | | * </pre> |
| | | * |
| | | * <h3>2. 按尺寸压缩</h3> |
| | | * <pre> |
| | | * // 压缩到指定宽高 |
| | | * File compressedFile = ImageCompressUtil.compress(multipartFile, 1920, 1080, 0.85f); |
| | | * </pre> |
| | | * |
| | | * <h3>3. 按比例压缩</h3> |
| | | * <pre> |
| | | * // 缩小到原来的50% |
| | | * File compressedFile = ImageCompressUtil.compressByScale(multipartFile, 0.5, 0.90f); |
| | | * </pre> |
| | | * |
| | | * <h3>4. 高质量图片缩放</h3> |
| | | * <pre> |
| | | * BufferedImage originalImage = ImageIO.read(file); |
| | | * BufferedImage resizedImage = ImageCompressUtil.resizeImageHighQuality(originalImage, 800, 600); |
| | | * </pre> |
| | | * |
| | | * <h3>5. 自定义质量压缩</h3> |
| | | * <pre> |
| | | * BufferedImage image = ImageIO.read(file); |
| | | * File compressed = ImageCompressUtil.compressWithQuality(image, "photo.jpg", 0.80f); // 80%质量 |
| | | * </pre> |
| | | * |
| | | * <h2>压缩质量常量</h2> |
| | | * <ul> |
| | | * <li>{@code ImageCompressUtil.HIGH_QUALITY} - 0.90(高质量,推荐OCR使用)</li> |
| | | * <li>{@code ImageCompressUtil.MEDIUM_QUALITY} - 0.80(中等质量)</li> |
| | | * <li>{@code ImageCompressUtil.LOW_QUALITY} - 0.75(低质量)</li> |
| | | * </ul> |
| | | * |
| | | * <h2>注意事项</h2> |
| | | * <ul> |
| | | * <li>压缩后的文件保存在系统临时目录,使用完毕后需手动删除</li> |
| | | * <li>OCR压缩会保证最小边不小于1500px,确保文字清晰</li> |
| | | * <li>支持PNG、BMP、GIF等格式自动转为JPEG</li> |
| | | * </ul> |
| | | * |
| | | * @author ruoyi |
| | | * @since 2025-01-20 |
| | | */ |
| | | package com.ruoyi.common.utils.image; |
| | |
| | | import com.ruoyi.common.core.controller.BaseController; |
| | | import com.ruoyi.common.core.domain.AjaxResult; |
| | | import com.ruoyi.common.utils.file.FileUploadUtils; |
| | | import com.ruoyi.common.utils.image.ImageCompressUtil; |
| | | import com.ruoyi.system.utils.AliOCRUtil; |
| | | import com.ruoyi.system.utils.BaiduOCRUtil; |
| | | import com.ruoyi.system.utils.TencentOCRUtil; |
| | |
| | | return error("不支持的识别类型: " + type + ", 支持的类型: " + String.join(",", SUPPORTED_TYPES)); |
| | | } |
| | | |
| | | // 保存临时文件 |
| | | String tempDir = System.getProperty("java.io.tmpdir"); |
| | | String originalFilename = file.getOriginalFilename(); |
| | | File tempFile = new File(tempDir, System.currentTimeMillis() + "_" + originalFilename); |
| | | file.transferTo(tempFile); |
| | | // 智能压缩图片(自动处理超过3MB的图片) |
| | | File tempFile = ImageCompressUtil.compressForOCR(file); |
| | | |
| | | // 根据提供商调用不同的OCR服务 |
| | | JSONObject ocrResult; |
| | |
| | | // 构建返回结果 |
| | | Map<String, Object> result = new HashMap<>(); |
| | | result.put("ocrResult", ocrResult); |
| | | result.put("fileName", originalFilename); |
| | | result.put("fileName", file.getOriginalFilename()); |
| | | result.put("originalSize", file.getSize()); |
| | | result.put("processedSize", tempFile.length()); |
| | | result.put("compressed", file.getSize() > tempFile.length()); |
| | | result.put("type", type); |
| | | result.put("provider", provider); |
| | | |
| | |
| | | return error("上传图片不能为空"); |
| | | } |
| | | |
| | | // 保存临时文件 |
| | | String tempDir = System.getProperty("java.io.tmpdir"); |
| | | String originalFilename = file.getOriginalFilename(); |
| | | File tempFile = new File(tempDir, System.currentTimeMillis() + "_" + originalFilename); |
| | | file.transferTo(tempFile); |
| | | // 智能压缩图片(自动处理超过3MB的图片) |
| | | File tempFile = ImageCompressUtil.compressForOCR(file); |
| | | |
| | | // 调用腾讯云手写体识别 |
| | | Map<String, String> resultMap = TencentOCRUtil.handwritingRecognizeWith(tempFile.getAbsolutePath(), itemNames); |
| | |
| | | |
| | | // 构建返回结果 |
| | | Map<String, Object> result = new HashMap<>(); |
| | | result.put("fileName", originalFilename); |
| | | result.put("fileName", file.getOriginalFilename()); |
| | | result.put("type", "HandWriting"); |
| | | result.put("provider", "tencent"); |
| | | result.put("fields", resultMap); |
| | |
| | | } |
| | | |
| | | try { |
| | | // 保存临时文件 |
| | | String tempDir = System.getProperty("java.io.tmpdir"); |
| | | String originalFilename = file.getOriginalFilename(); |
| | | File tempFile = new File(tempDir, System.currentTimeMillis() + "_" + originalFilename); |
| | | file.transferTo(tempFile); |
| | | // 智能压缩图片(自动处理超过3MB的图片) |
| | | File tempFile = ImageCompressUtil.compressForOCR(file); |
| | | |
| | | // 调用腾讯云手写体识别 |
| | | Map<String, String> resultMap = TencentOCRUtil.handwritingRecognizeWith(tempFile.getAbsolutePath(), itemNames); |
| | |
| | | // 检查是否有错误 |
| | | if (resultMap.containsKey("error")) { |
| | | failCount++; |
| | | errorMessages.append(originalFilename).append(":").append(resultMap.get("error")).append("; "); |
| | | logger.warn("图片 {} 识别失败: {}", originalFilename, resultMap.get("error")); |
| | | errorMessages.append(file.getOriginalFilename()).append(":").append(resultMap.get("error")).append("; "); |
| | | logger.warn("图片 {} 识别失败: {}", file.getOriginalFilename(), resultMap.get("error")); |
| | | } else { |
| | | // 合并识别结果(如果key已存在,不覆盖) |
| | | for (Map.Entry<String, String> entry : resultMap.entrySet()) { |
| | |
| | | } |
| | | } |
| | | successCount++; |
| | | logger.info("图片 {} 识别成功,提取 {} 个字段", originalFilename, resultMap.size()); |
| | | logger.info("图片 {} 识别成功,提取 {} 个字段", file.getOriginalFilename(), resultMap.size()); |
| | | } |
| | | } catch (Exception e) { |
| | | failCount++; |
| | |
| | | return AjaxResult.warn("传入的用户数据为空"); |
| | | } |
| | | |
| | | log.info("开始同步 {} 条OA用户数据到 MySQL 数据库...", oaUsers.size()); |
| | | // log.info("开始同步 {} 条OA用户数据到 MySQL 数据库...", oaUsers.size()); |
| | | |
| | | int createdCount = 0; |
| | | int updatedCount = 0; |
| | |
| | | // 用户已存在,更新信息 |
| | | updateExistingUser(existingUser, dto, deptId); |
| | | updatedCount++; |
| | | log.info("更新用户: {} ({}), oaUserId: {}", |
| | | dto.getNickName(), dto.getUserName(), dto.getOaUserId()); |
| | | // log.info("更新用户: {} ({}), oaUserId: {}", |
| | | // dto.getNickName(), dto.getUserName(), dto.getOaUserId()); |
| | | } |
| | | else |
| | | { |
| | |
| | | userByName.setUpdateBy("sync"); |
| | | sysUserMapper.updateUser(userByName); |
| | | updatedCount++; |
| | | log.info("更新已存在用户名的用户: {} ({}), 设置oaUserId: {}", |
| | | dto.getNickName(), dto.getUserName(), dto.getOaUserId()); |
| | | // log.info("更新已存在用户名的用户: {} ({}), 设置oaUserId: {}", |
| | | // dto.getNickName(), dto.getUserName(), dto.getOaUserId()); |
| | | } |
| | | else |
| | | { |
| | | // 创建新用户 |
| | | createNewUser(dto, deptId); |
| | | createdCount++; |
| | | log.info("创建新用户: {} ({}), oaUserId: {}, deptId: {}", |
| | | dto.getNickName(), dto.getUserName(), dto.getOaUserId(), deptId); |
| | | // log.info("创建新用户: {} ({}), oaUserId: {}, deptId: {}", |
| | | // dto.getNickName(), dto.getUserName(), dto.getOaUserId(), deptId); |
| | | } |
| | | } |
| | | } |
| | |
| | | |
| | | String message = String.format("同步完成!创建用户: %d, 更新用户: %d, 跳过: %d, 失败: %d", |
| | | createdCount, updatedCount, skippedCount, errorCount); |
| | | log.info(message); |
| | | // log.info(message); |
| | | |
| | | Map<String, Object> result = new HashMap<>(); |
| | | result.put("created", createdCount); |
| | |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | import java.io.File; |
| | | import java.util.Base64; |
| | | import java.util.*; |
| | | import java.nio.file.Files; |
| | | import java.util.HashMap; |
| | | import java.util.Map; |
| | | |
| | | /** |
| | | * 腾讯云OCR工具类 |
| | |
| | | // {"患者签名(手印)", "签字人身份证号码", "日期", "联系电话", "本人", "签字人与患者关系"} |
| | | req.setItemNames(itemNames != null ? itemNames : new String[]{"患者姓名", "性别", "年龄", "身份证号", "诊断", "需支付转运费用", "行程", "开始时间", "结束时间", "家属签名"}); |
| | | req.setOutputLanguage("cn"); |
| | | req.setReturnFullText(false); |
| | | req.setReturnFullText(true); |
| | | req.setItemNamesShowMode(false); |
| | | ExtractDocMultiResponse resp = client.ExtractDocMulti(req); |
| | | |
| | |
| | | } |
| | | } |
| | | } |
| | | |
| | | //将 WordList |
| | | List<String> wordListResult = new ArrayList<>(); |
| | | if (responseData.containsKey("WordList") && responseData.getJSONArray("WordList") != null){ |
| | | JSONArray wordList = responseData.getJSONArray("WordList"); |
| | | for (int i = 0; i < wordList.size(); i++) { |
| | | JSONObject word = wordList.getJSONObject(i); |
| | | // { |
| | | // "Coord": { |
| | | // "LeftBottom": { |
| | | // "X": 472, |
| | | // "Y": 1500 |
| | | // }, |
| | | // "LeftTop": { |
| | | // "X": 467, |
| | | // "Y": 1420 |
| | | // }, |
| | | // "RightBottom": { |
| | | // "X": 636, |
| | | // "Y": 1490 |
| | | // }, |
| | | // "RightTop": { |
| | | // "X": 631, |
| | | // "Y": 1410 |
| | | // } |
| | | // }, |
| | | // "DetectedText": "行程:" |
| | | // } |
| | | String detectedText = word.getString("DetectedText"); |
| | | wordListResult.add(detectedText); |
| | | } |
| | | } |
| | | //我们从wordListResult中行程:后面,需要支付转运费用:之间的文字 |
| | | String content = extractContentFromWordList(wordListResult, "行程:", "需支付转运费用:"); |
| | | log.info("提取到行程: {}", content); |
| | | resultMap.put("行程", content); |
| | | |
| | | log.info("手写体识别提取到 {} 个字段", resultMap.size()); |
| | | return resultMap; |
| | | |
| | |
| | | } |
| | | } |
| | | |
| | | private static String extractContentFromWordList(List<String> wordListResult, String s, String s1) { |
| | | //提取s和s1之间的内容 |
| | | //如果word中只有一或-或->,统一处理成-> |
| | | |
| | | |
| | | int startIndex = -1; |
| | | int endIndex = -1; |
| | | |
| | | for (int i = 0; i < wordListResult.size(); i++) { |
| | | String word = wordListResult.get(i); |
| | | if (word.contains(s)) { |
| | | startIndex = i; |
| | | } |
| | | |
| | | if (word.contains(s1)) { |
| | | endIndex = i; |
| | | } |
| | | } |
| | | if (startIndex == -1 || endIndex == -1 || startIndex >= endIndex) { |
| | | return ""; |
| | | } |
| | | List<String> w=wordListResult.subList(startIndex + 1, endIndex); |
| | | Boolean findAle=false; |
| | | List<String> result=new ArrayList<>(); |
| | | for(String word:w){ |
| | | if (!findAle && (word.equals("-") || word.equals("->") || word.equals("→") || word.equals("一") || word.equals("=>")) ){ |
| | | findAle = true; |
| | | word = word.replace("-", "→") |
| | | .replace("一", "→") |
| | | .replace("=>", "→"); |
| | | } |
| | | result.add(word); |
| | | }; |
| | | return String.join("", result); |
| | | } |
| | | |
| | | /** |
| | | * 身份证识别 |
| | | * @param imagePath 图片路径 |
| | |
| | | and e.patient_phone = #{phone} |
| | | and DATE(t.create_time) = #{createDate} |
| | | </select> |
| | | |
| | | <!-- 查询车辆在指定时间范围内的任务列表 --> |
| | | <select id="selectVehicleTasksInTimeRange" parameterType="map" resultMap="SysTaskResult"> |
| | | select t.task_id, t.task_code, t.task_type, t.task_status, |
| | | t.departure_address, t.destination_address, |
| | | t.actual_start_time, t.actual_end_time, |
| | | t.planned_start_time, t.planned_end_time, |
| | | t.estimated_distance, |
| | | tv.vehicle_id |
| | | from sys_task t |
| | | inner join sys_task_vehicle tv on t.task_id = tv.task_id |
| | | where tv.vehicle_id = #{vehicleId} |
| | | and t.del_flag = '0' |
| | | and t.task_status not in ('CANCELLED') |
| | | and ( |
| | | <!-- 实际时间有值时,使用实际时间判断重叠 --> |
| | | (t.actual_start_time is not null and t.actual_end_time is not null |
| | | and t.actual_start_time <= #{endTime} and t.actual_end_time >= #{startTime}) |
| | | or |
| | | <!-- 实际开始时间有值但未结束时,使用当前时间作为结束时间 --> |
| | | (t.actual_start_time is not null and t.actual_end_time is null |
| | | and t.actual_start_time <= #{endTime}) |
| | | or |
| | | <!-- 实际时间都为空时,使用计划时间判断重叠 --> |
| | | (t.actual_start_time is null and t.actual_end_time is null |
| | | and t.planned_start_time <= #{endTime} and t.planned_end_time >= #{startTime}) |
| | | ) |
| | | order by t.actual_start_time, t.planned_start_time |
| | | </select> |
| | | </mapper> |
| | |
| | | method: 'get', |
| | | params: { taskId } |
| | | }) |
| | | } |
| | | |
| | | // ========== 旧系统同步相关API ========== |
| | | |
| | | // 手动同步服务单到旧系统 |
| | | export function syncServiceOrder(taskId) { |
| | | return request({ |
| | | url: '/task/syncServiceOrder/' + taskId, |
| | | method: 'post' |
| | | }) |
| | | } |
| | | |
| | | // 手动同步调度单到旧系统 |
| | | export function syncDispatchOrder(taskId) { |
| | | return request({ |
| | | url: '/task/syncDispatchOrder/' + taskId, |
| | | method: 'post' |
| | | }) |
| | | } |
| | |
| | | <i class="el-icon-error"></i> 同步失败 |
| | | </el-tag> |
| | | <span v-else style="color: #C0C4CC;">--</span> |
| | | <!-- 未同步或同步失败时显示同步按钮 --> |
| | | <el-button |
| | | v-if="taskDetail.emergencyInfo.syncStatus === 0 || taskDetail.emergencyInfo.syncStatus === 3" |
| | | type="primary" |
| | | size="mini" |
| | | icon="el-icon-refresh" |
| | | :loading="syncingServiceOrder" |
| | | @click="syncServiceOrder" |
| | | style="margin-left: 10px;" |
| | | >同步服务单</el-button> |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="服务单号"> |
| | | <span v-if="taskDetail.emergencyInfo.legacyServiceOrdId"> |
| | |
| | | <i class="el-icon-error"></i> 同步失败 |
| | | </el-tag> |
| | | <span v-else style="color: #C0C4CC;">--</span> |
| | | <!-- 未同步或同步失败时显示同步按钮 --> |
| | | <el-button |
| | | v-if="taskDetail.emergencyInfo.dispatchSyncStatus === 0 || taskDetail.emergencyInfo.dispatchSyncStatus === 3" |
| | | type="primary" |
| | | size="mini" |
| | | icon="el-icon-refresh" |
| | | :loading="syncingDispatchOrder" |
| | | @click="syncDispatchOrder" |
| | | style="margin-left: 10px;" |
| | | >同步调度单</el-button> |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="调度单号"> |
| | | <span v-if="taskDetail.emergencyInfo.legacyDispatchOrdId"> |
| | |
| | | </template> |
| | | |
| | | <script> |
| | | import { getTask, updateTask, assignTask, changeTaskStatus, uploadAttachment, deleteAttachment, getTaskVehicles, getAvailableVehicles, assignVehiclesToTask, unassignVehicleFromTask, getPaymentInfo } from "@/api/task"; |
| | | import { getTask, updateTask, assignTask, changeTaskStatus, uploadAttachment, deleteAttachment, getTaskVehicles, getAvailableVehicles, assignVehiclesToTask, unassignVehicleFromTask, getPaymentInfo, syncServiceOrder, syncDispatchOrder } from "@/api/task"; |
| | | import { listUser } from "@/api/system/user"; |
| | | import { getToken } from "@/utils/auth"; |
| | | |
| | |
| | | category: [ |
| | | { required: true, message: "业务分类不能为空", trigger: "change" } |
| | | ] |
| | | } |
| | | }, |
| | | // 同步加载状态 |
| | | syncingServiceOrder: false, |
| | | syncingDispatchOrder: false |
| | | }; |
| | | }, |
| | | created() { |
| | |
| | | if (!fileType) return false; |
| | | const imageTypes = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp']; |
| | | return imageTypes.includes(fileType.toLowerCase()); |
| | | }, |
| | | /** 手动同步服务单 */ |
| | | syncServiceOrder() { |
| | | this.$modal.confirm('是否确认同步服务单到旧系统?').then(() => { |
| | | this.syncingServiceOrder = true; |
| | | return syncServiceOrder(this.taskDetail.taskId); |
| | | }).then(() => { |
| | | this.$modal.msgSuccess("服务单同步成功"); |
| | | // 重新加载任务详情 |
| | | this.getDetail(); |
| | | }).catch(() => { |
| | | // 处理取消和错误 |
| | | }).finally(() => { |
| | | this.syncingServiceOrder = false; |
| | | }); |
| | | }, |
| | | /** 手动同步调度单 */ |
| | | syncDispatchOrder() { |
| | | this.$modal.confirm('是否确认同步调度单到旧系统?').then(() => { |
| | | this.syncingDispatchOrder = true; |
| | | return syncDispatchOrder(this.taskDetail.taskId); |
| | | }).then(() => { |
| | | this.$modal.msgSuccess("调度单同步成功"); |
| | | // 重新加载任务详情 |
| | | this.getDetail(); |
| | | }).catch(() => { |
| | | // 处理取消和错误 |
| | | }).finally(() => { |
| | | this.syncingDispatchOrder = false; |
| | | }); |
| | | } |
| | | } |
| | | }; |