From d2b6200af897dec101eb00ecafedfbdb5a56b6a2 Mon Sep 17 00:00:00 2001 From: cwchen <1048842385@qq.com> Date: Mon, 7 Apr 2025 16:15:20 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8E=8B=E7=BC=A9=E4=B8=8B=E8=BD=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/DownloadController.java | 105 ++++++++++++ .../controller/SynthesisQueryController.java | 7 + .../backstage/dao/SynthesisQueryDao.java | 19 +++ .../backstage/entity/DownloadFileVo.java | 20 +++ .../backstage/entity/DownloadRequest.java | 18 ++ .../bonus/imgTool/backstage/entity/Photo.java | 18 ++ .../backstage/service/DownloadService.java | 161 ++++++++++++++++++ .../com/bonus/imgTool/config/AsyncConfig.java | 31 ++++ .../config/SpringThreadPoolConfig.java | 2 + .../com/bonus/imgTool/utils/SystemUtils.java | 1 - .../backstage/SynthesisQueryMapper.xml | 11 ++ .../static/js/synthesisQuery/fileDownload.js | 71 ++++++++ .../synthesisQuery/proClassifyStatistics.js | 10 +- .../pages/synthesisQuery/fileDownload.html | 86 ++++++++++ 14 files changed, 557 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/bonus/imgTool/backstage/controller/DownloadController.java create mode 100644 src/main/java/com/bonus/imgTool/backstage/entity/DownloadFileVo.java create mode 100644 src/main/java/com/bonus/imgTool/backstage/entity/DownloadRequest.java create mode 100644 src/main/java/com/bonus/imgTool/backstage/entity/Photo.java create mode 100644 src/main/java/com/bonus/imgTool/backstage/service/DownloadService.java create mode 100644 src/main/java/com/bonus/imgTool/config/AsyncConfig.java create mode 100644 src/main/resources/static/js/synthesisQuery/fileDownload.js create mode 100644 src/main/resources/static/pages/synthesisQuery/fileDownload.html diff --git a/src/main/java/com/bonus/imgTool/backstage/controller/DownloadController.java b/src/main/java/com/bonus/imgTool/backstage/controller/DownloadController.java new file mode 100644 index 0000000..cc91ba0 --- /dev/null +++ b/src/main/java/com/bonus/imgTool/backstage/controller/DownloadController.java @@ -0,0 +1,105 @@ +package com.bonus.imgTool.backstage.controller; + + +import com.bonus.imgTool.backstage.entity.DownloadRequest; +import com.bonus.imgTool.backstage.service.DownloadService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.io.FileSystemResource; +import org.springframework.core.io.Resource; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +/** + * @className:DownloadController + * @author:cwchen + * @date:2025-04-07-13:23 + * @version:1.0 + * @description:文件下载 + */ +@RestController +@RequestMapping("/api/download") +@Slf4j +public class DownloadController { + + @Autowired + private DownloadService downloadService; + + @PostMapping("/start") + public ResponseEntity startDownload(@RequestBody DownloadRequest request) { + downloadService.startDownloadTask(request.getTaskId(), request.getProId(),request.getType()); + return ResponseEntity.ok("Download task started"); + } + + @GetMapping(value = "/progress", produces = MediaType.TEXT_EVENT_STREAM_VALUE) + public SseEmitter streamProgress(@RequestParam String taskId) { + SseEmitter emitter = new SseEmitter(3600000L); // 1小时超时 + + downloadService.addProgressListener(taskId, new DownloadService.DownloadProgressListener() { + @Override + public void onProgress(String taskId, int progress, int processed, int total) { + try { + Map data = new HashMap<>(); + data.put("type", "progress"); + data.put("progress", progress); + data.put("processed", processed); + data.put("total", total); + emitter.send(SseEmitter.event().data(data)); + } catch (IOException e) { + log.error(e.toString(),e); + } + } + + @Override + public void onComplete(String taskId, String downloadUrl) { + try { + Map data = new HashMap<>(); + data.put("type", "complete"); + data.put("downloadUrl", downloadUrl); + emitter.send(SseEmitter.event().data(data)); + emitter.complete(); + } catch (IOException e) { + emitter.completeWithError(e); + } + } + + @Override + public void onError(String taskId, String message) { + try { + Map data = new HashMap<>(); + data.put("type", "error"); + data.put("message", message); + emitter.send(SseEmitter.event().data(data)); + emitter.complete(); + } catch (IOException e) { + emitter.completeWithError(e); + } + } + }); + + return emitter; + } + + @GetMapping("/file") + public ResponseEntity downloadFile(@RequestParam String taskId) throws IOException { + File file = downloadService.getDownloadFile(taskId); + if (file == null || !file.exists()) { + return ResponseEntity.notFound().build(); + } + org.springframework.core.io.Resource resource = new FileSystemResource(file); + return ResponseEntity.ok() + .header(HttpHeaders.CONTENT_DISPOSITION, + "attachment; filename=\"" + file.getName() + "\"") + .contentLength(file.length()) + .contentType(MediaType.APPLICATION_OCTET_STREAM) + .body(resource); + } +} diff --git a/src/main/java/com/bonus/imgTool/backstage/controller/SynthesisQueryController.java b/src/main/java/com/bonus/imgTool/backstage/controller/SynthesisQueryController.java index 48e099d..46fe3ca 100644 --- a/src/main/java/com/bonus/imgTool/backstage/controller/SynthesisQueryController.java +++ b/src/main/java/com/bonus/imgTool/backstage/controller/SynthesisQueryController.java @@ -3,6 +3,7 @@ package com.bonus.imgTool.backstage.controller; import cn.afterturn.easypoi.excel.ExcelExportUtil; import cn.afterturn.easypoi.excel.entity.ExportParams; import cn.afterturn.easypoi.excel.entity.enmus.ExcelType; +import cn.hutool.core.io.resource.InputStreamResource; import com.bonus.imgTool.annotation.DecryptAndVerify; import com.bonus.imgTool.annotation.LogAnnotation; import com.bonus.imgTool.backstage.entity.ProClassifyStatisticsVo; @@ -16,9 +17,12 @@ import com.bonus.imgTool.system.vo.SysWhiteVo; import com.bonus.imgTool.utils.ServerResponse; import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; +import com.google.common.net.HttpHeaders; import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; import org.apache.poi.ss.usermodel.Workbook; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -29,6 +33,9 @@ import javax.annotation.Resource; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletResponse; import java.net.URLEncoder; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.HashMap; import java.util.List; import java.util.Map; diff --git a/src/main/java/com/bonus/imgTool/backstage/dao/SynthesisQueryDao.java b/src/main/java/com/bonus/imgTool/backstage/dao/SynthesisQueryDao.java index 09ceb3d..a317144 100644 --- a/src/main/java/com/bonus/imgTool/backstage/dao/SynthesisQueryDao.java +++ b/src/main/java/com/bonus/imgTool/backstage/dao/SynthesisQueryDao.java @@ -18,6 +18,7 @@ public interface SynthesisQueryDao { /** * 综合查询-照片综合查询-照片数量 + * * @param dto * @return SynthesisNumVo * @author cwchen @@ -27,6 +28,7 @@ public interface SynthesisQueryDao { /** * 照片综合查询 + * * @param dto * @return List * @author cwchen @@ -36,6 +38,7 @@ public interface SynthesisQueryDao { /** * 收藏/取消收藏图片 + * * @param dto * @return void * @author cwchen @@ -49,6 +52,7 @@ public interface SynthesisQueryDao { /** * 生成水印照片 + * * @param vo * @return void * @author cwchen @@ -58,6 +62,7 @@ public interface SynthesisQueryDao { /** * 获取水印照片地址 + * * @param vo * @return String * @author cwchen @@ -67,6 +72,7 @@ public interface SynthesisQueryDao { /** * 项目分类统计 + * * @param dto * @return List * @author cwchen @@ -78,6 +84,7 @@ public interface SynthesisQueryDao { /** * 项目分类统计-查看图片 + * * @param dto * @return List * @author cwchen @@ -87,6 +94,7 @@ public interface SynthesisQueryDao { /** * 项目分类统计-查看列表 + * * @param dto * @return List * @author cwchen @@ -96,10 +104,21 @@ public interface SynthesisQueryDao { /** * 获取图片 + * * @param detailVo * @return List * @author cwchen * @date 2025/4/6 18:35 */ List getImgs(@Param("params") ProClassifyStatisticDetailVo detailVo, @Param("type") int type); + + /** + * 查询原图/水印照片 + * @param proId + * @param type + * @return List + * @author cwchen + * @date 2025/4/7 10:59 + */ + List findByAlbumId(@Param("proId") String proId, @Param("type") String type); } diff --git a/src/main/java/com/bonus/imgTool/backstage/entity/DownloadFileVo.java b/src/main/java/com/bonus/imgTool/backstage/entity/DownloadFileVo.java new file mode 100644 index 0000000..d72b5c7 --- /dev/null +++ b/src/main/java/com/bonus/imgTool/backstage/entity/DownloadFileVo.java @@ -0,0 +1,20 @@ +package com.bonus.imgTool.backstage.entity; + +import lombok.Data; + +/** + * @className:DownloadFileVo + * @author:cwchen + * @date:2025-04-07-11:03 + * @version:1.0 + * @description: + */ +@Data +public class DownloadFileVo { + + private String path; + + private String uploadTypeName; + + private String uploadType; +} diff --git a/src/main/java/com/bonus/imgTool/backstage/entity/DownloadRequest.java b/src/main/java/com/bonus/imgTool/backstage/entity/DownloadRequest.java new file mode 100644 index 0000000..349602d --- /dev/null +++ b/src/main/java/com/bonus/imgTool/backstage/entity/DownloadRequest.java @@ -0,0 +1,18 @@ +package com.bonus.imgTool.backstage.entity; + +import lombok.Data; + +/** + * @className:DownloadRequest + * @author:cwchen + * @date:2025-04-07-13:30 + * @version:1.0 + * @description: + */ +@Data +public class DownloadRequest { + + private String taskId; + private String proId; + private String type; +} diff --git a/src/main/java/com/bonus/imgTool/backstage/entity/Photo.java b/src/main/java/com/bonus/imgTool/backstage/entity/Photo.java new file mode 100644 index 0000000..da8c96b --- /dev/null +++ b/src/main/java/com/bonus/imgTool/backstage/entity/Photo.java @@ -0,0 +1,18 @@ +package com.bonus.imgTool.backstage.entity; + +import lombok.Data; + +/** + * @className:Photo + * @author:cwchen + * @date:2025-04-07-13:31 + * @version:1.0 + * @description: + */ +@Data +public class Photo { + private String photoId; + private String albumId; + private String filePath; + private String uploadTypeName; +} diff --git a/src/main/java/com/bonus/imgTool/backstage/service/DownloadService.java b/src/main/java/com/bonus/imgTool/backstage/service/DownloadService.java new file mode 100644 index 0000000..f92412c --- /dev/null +++ b/src/main/java/com/bonus/imgTool/backstage/service/DownloadService.java @@ -0,0 +1,161 @@ +package com.bonus.imgTool.backstage.service; + +import com.bonus.imgTool.backstage.dao.SynthesisQueryDao; +import com.bonus.imgTool.backstage.entity.Photo; +import com.bonus.imgTool.backstage.entity.ProClassifyStatisticDetailVo; +import com.bonus.imgTool.utils.SystemUtils; +import org.apache.commons.io.FileUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +/** + * @className:DownloadService + * @author:cwchen + * @date:2025-04-07-10:46 + * @version:1.0 + * @description: 原图/水印下载 + */ +@Service(value = "DownloadService") +public class DownloadService { + + private static final Logger logger = LoggerFactory.getLogger(DownloadService.class); + + + @Value("${download.temp.dir:/tmp/downloads}") + private String tempDir; + + @Value("${download.output.dir}") + private String outputDir; + + @Resource(name = "SynthesisQueryDao") + private SynthesisQueryDao synthesisQueryDao; + + private final Map> progressListeners = new ConcurrentHashMap<>(); + private final Map taskFileMap = new ConcurrentHashMap<>(); + + @Async + public void startDownloadTask(String taskId, String proId,String type) { + Path tempDirPath = null; + try { + // 准备临时目录 + tempDirPath = Paths.get(tempDir, taskId); + Files.createDirectories(tempDirPath); + Path zipFilePath = tempDirPath.resolve("photos.zip"); + // 获取照片列表 + List photos = getPhotosForAlbum(proId,type); + int total = photos.size(); + // 创建ZIP文件 + try (ZipOutputStream zos = new ZipOutputStream( + new BufferedOutputStream(Files.newOutputStream(zipFilePath)))) { + + byte[] buffer = new byte[8192]; + int processed = 0; + for (Photo photo : photos) { + String path = SystemUtils.getUploadPath() + File.separator + photo.getFilePath(); + Path photoPath = Paths.get(path); + String uniqueEntryName = "photos/" + photo.getPhotoId() + "_" + photoPath.getFileName(); + ZipEntry entry = new ZipEntry(uniqueEntryName); + zos.putNextEntry(entry); + + try (InputStream is = Files.newInputStream(photoPath)) { + int len; + while ((len = is.read(buffer)) > 0) { + zos.write(buffer, 0, len); + } + } + zos.closeEntry(); + processed++; + // 更新进度 (每处理5%或至少每10张照片更新一次) + if (processed % Math.max(10, total/20) == 0 || processed == total) { + int progress = (int) ((processed * 100.0) / total); + notifyProgress(taskId, progress, processed, total); + } + } + } + + // 移动到输出目录 + Path outputDirPath = Paths.get(outputDir); + Files.createDirectories(outputDirPath); + Path finalFilePath = outputDirPath.resolve(taskId + ".zip"); + Files.move(zipFilePath, finalFilePath, StandardCopyOption.REPLACE_EXISTING); + + // 记录文件位置 + taskFileMap.put(taskId, finalFilePath.toString()); + // 通知完成 + notifyComplete(taskId, "/imgTool/api/download/file?taskId=" + taskId); + } catch (Exception e) { + logger.error("下载任务失败: " + taskId, e); + notifyError(taskId, "文件生成失败: " + e.getMessage()); + } finally { + // 清理临时目录 + if (tempDirPath != null) { + try { + FileUtils.deleteDirectory(tempDirPath.toFile()); + } catch (IOException e) { + logger.warn("清理临时目录失败: " + tempDirPath, e); + } + } + } + } + + public void addProgressListener(String taskId, DownloadProgressListener listener) { + progressListeners.computeIfAbsent(taskId, k -> new CopyOnWriteArrayList<>()).add(listener); + } + + public File getDownloadFile(String taskId) { + String filePath = taskFileMap.get(taskId); + return filePath != null ? new File(filePath) : null; + } + + private List getPhotosForAlbum(String proId,String type) { + // 实现获取照片列表的逻辑 + // 返回包含所有照片路径的列表 + List list = Optional.ofNullable(synthesisQueryDao.findByAlbumId(proId,type)).orElseGet(ArrayList::new); + return list; + } + + private void notifyProgress(String taskId, int progress, int processed, int total) { + List listeners = progressListeners.get(taskId); + if (listeners != null) { + listeners.forEach(l -> l.onProgress(taskId, progress, processed, total)); + } + } + + private void notifyComplete(String taskId, String downloadUrl) { + List listeners = progressListeners.remove(taskId); + if (listeners != null) { + listeners.forEach(l -> l.onComplete(taskId, downloadUrl)); + } + } + + private void notifyError(String taskId, String message) { + List listeners = progressListeners.remove(taskId); + if (listeners != null) { + listeners.forEach(l -> l.onError(taskId, message)); + } + } + + public interface DownloadProgressListener { + void onProgress(String taskId, int progress, int processed, int total); + void onComplete(String taskId, String downloadUrl); + void onError(String taskId, String message); + } +} diff --git a/src/main/java/com/bonus/imgTool/config/AsyncConfig.java b/src/main/java/com/bonus/imgTool/config/AsyncConfig.java new file mode 100644 index 0000000..162e28f --- /dev/null +++ b/src/main/java/com/bonus/imgTool/config/AsyncConfig.java @@ -0,0 +1,31 @@ +package com.bonus.imgTool.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.AsyncConfigurer; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +import java.util.concurrent.Executor; + +/** + * @className:AsyncConfig + * @author:cwchen + * @date:2025-04-07-13:29 + * @version:1.0 + * @description: + */ +@Configuration +@EnableAsync +public class AsyncConfig implements AsyncConfigurer { + + @Override + public Executor getAsyncExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(2); + executor.setMaxPoolSize(4); + executor.setQueueCapacity(10); + executor.setThreadNamePrefix("DownloadTask-"); + executor.initialize(); + return executor; + } +} diff --git a/src/main/java/com/bonus/imgTool/config/SpringThreadPoolConfig.java b/src/main/java/com/bonus/imgTool/config/SpringThreadPoolConfig.java index 5a36f1c..b64e196 100644 --- a/src/main/java/com/bonus/imgTool/config/SpringThreadPoolConfig.java +++ b/src/main/java/com/bonus/imgTool/config/SpringThreadPoolConfig.java @@ -57,6 +57,8 @@ public class SpringThreadPoolConfig { executor.setThreadNamePrefix(threadNamePrefix); // 线程池对拒绝任务的处理策略 executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); + // 允许核心线程在空闲时被回收 + executor.setAllowCoreThreadTimeOut(true); // 初始化 executor.initialize(); return executor; diff --git a/src/main/java/com/bonus/imgTool/utils/SystemUtils.java b/src/main/java/com/bonus/imgTool/utils/SystemUtils.java index ea007d9..ffde603 100644 --- a/src/main/java/com/bonus/imgTool/utils/SystemUtils.java +++ b/src/main/java/com/bonus/imgTool/utils/SystemUtils.java @@ -62,7 +62,6 @@ public class SystemUtils { */ public static String getUploadPath() { String os = getSystem(); - System.err.println("当前系统是=" + os); if ("windows".equals(os)) { return windowsPath; } else if ("linux".equals(os)) { diff --git a/src/main/resources/mappers/backstage/SynthesisQueryMapper.xml b/src/main/resources/mappers/backstage/SynthesisQueryMapper.xml index 0588724..0f1d507 100644 --- a/src/main/resources/mappers/backstage/SynthesisQueryMapper.xml +++ b/src/main/resources/mappers/backstage/SynthesisQueryMapper.xml @@ -357,6 +357,17 @@ AND sfr.source_id = #{params.id} AND sfr.upload_type = #{params.uploadType} AND sfr.source_type = #{type} AND sfr.is_active = '1' + + diff --git a/src/main/resources/static/js/synthesisQuery/fileDownload.js b/src/main/resources/static/js/synthesisQuery/fileDownload.js new file mode 100644 index 0000000..7ea7469 --- /dev/null +++ b/src/main/resources/static/js/synthesisQuery/fileDownload.js @@ -0,0 +1,71 @@ +let proId = decryptCBC(getUrlParam('proId')); +let type = decryptCBC(getUrlParam('type')); +let title = decryptCBC(getUrlParam('title')); +let proName = decryptCBC(getUrlParam('proName')); +$('#title').html(proName +"-"+ title); +document.getElementById('downloadBtn').addEventListener('click', function() { + const btn = this; + btn.disabled = true; + document.getElementById('progressContainer').style.display = 'block'; + // 创建任务ID + const taskId = 'task_' + Date.now(); + // 使用EventSource接收服务器推送的进度更新 + const eventSource = new EventSource(`/imgTool/api/download/progress?taskId=${taskId}`); + eventSource.onmessage = function(event) { + const data = JSON.parse(event.data); + if (data.type === 'progress') { + // 更新进度条 + document.getElementById('progress').style.width = data.progress + '%'; + document.getElementById('statusText').textContent = + `正在压缩: ${data.progress}% (已处理 ${data.processed} / ${data.total} 文件)`; + } + else if (data.type === 'complete') { + // 完成处理 + document.getElementById('progress').style.width = '100%'; + document.getElementById('statusText').textContent = '压缩完成!'; + // 显示下载通知 + const notification = document.getElementById('downloadNotification'); + const downloadLink = document.getElementById('downloadLink'); + downloadLink.onclick = function(e) { + e.preventDefault(); + window.location.href = data.downloadUrl; + notification.style.display = 'none'; + window.close(); + }; + notification.style.display = 'block'; + // 2小时后自动隐藏通知 + setTimeout(() => { + notification.style.display = 'none'; + }, 1000 * 60 * 60 * 2); + // 关闭EventSource连接 + eventSource.close(); + } + else if (data.type === 'error') { + // 错误处理 + document.getElementById('statusText').textContent = '错误: ' + data.message; + btn.disabled = false; + eventSource.close(); + } + }; + eventSource.onerror = function() { + document.getElementById('statusText').textContent = '连接出错,请重试'; + btn.disabled = false; + eventSource.close(); + }; + // 启动下载任务 + fetch('/imgTool/api/download/start', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + taskId: taskId, + proId: proId, + type: type, + }) + }).catch(error => { + document.getElementById('statusText').textContent = '启动任务失败'; + btn.disabled = false; + eventSource.close(); + }); +}); \ No newline at end of file diff --git a/src/main/resources/static/js/synthesisQuery/proClassifyStatistics.js b/src/main/resources/static/js/synthesisQuery/proClassifyStatistics.js index 5e16192..a49826f 100644 --- a/src/main/resources/static/js/synthesisQuery/proClassifyStatistics.js +++ b/src/main/resources/static/js/synthesisQuery/proClassifyStatistics.js @@ -112,8 +112,8 @@ function initTable(dataList, limit, page) { templet: function (d) { let html = ''; let view = "" - let originalDownload = "" - let waterDownload = ""; + let originalDownload = "" + let waterDownload = ""; html = view + originalDownload + waterDownload; return html; } @@ -203,4 +203,10 @@ function downloadExcel(){ }; // xhr.send(params); xhr.send(); +} + +/**下载原图/水印*/ +function downloadFile(obj,type){ + let title = type === 1 ? "原图下载" : "水印下载"; + window.open("./fileDownload.html?type="+encryptCBC(type)+"&proId="+encryptCBC(obj.proId) + "&title=" + encryptCBC(title) + "&proName=" + encryptCBC(obj.proName)); } \ No newline at end of file diff --git a/src/main/resources/static/pages/synthesisQuery/fileDownload.html b/src/main/resources/static/pages/synthesisQuery/fileDownload.html new file mode 100644 index 0000000..5f2d451 --- /dev/null +++ b/src/main/resources/static/pages/synthesisQuery/fileDownload.html @@ -0,0 +1,86 @@ + + + + + 图片下载 + + + + + + + + + +

照片下载中心

+

点击下方按钮下载所有照片

+ + + +
+
+
+
+
准备中...
+
+ +
+ 您的照片压缩包已准备好!点击下载 +
+ + + \ No newline at end of file