压缩下载优化

This commit is contained in:
cwchen 2025-04-09 15:43:01 +08:00
parent cb8b5cb708
commit 6b557374bf
7 changed files with 821 additions and 11 deletions

View File

@ -1,22 +1,34 @@
package com.bonus.imgTool.backstage.controller;
import com.bonus.imgTool.backstage.dao.SynthesisQueryDao;
import com.bonus.imgTool.backstage.entity.DownloadRequest;
import com.bonus.imgTool.backstage.service.DownloadService;
import com.bonus.imgTool.backstage.entity.Photo;
import com.bonus.imgTool.backstage.entity.SynthesisQueryVo;
import com.bonus.imgTool.backstage.service.*;
import com.bonus.imgTool.utils.HighQualityWatermark;
import com.bonus.imgTool.utils.SystemUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
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;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
/**
* @className:DownloadController
@ -33,7 +45,16 @@ public class DownloadController {
@Autowired
private DownloadService downloadService;
@PostMapping("/start")
@Autowired
private SynthesisQueryDao synthesisQueryDao;
@Value("${download.output.dir}")
private String outputDir;
@javax.annotation.Resource(name = "testTaskExecutor")
private ThreadPoolTaskExecutor testTaskExecutor;
/*@PostMapping("/start")
public ResponseEntity<String> startDownload(@RequestBody DownloadRequest request) {
downloadService.startDownloadTask(request.getTaskId(), request.getProId(),request.getType(),request.getProName());
return ResponseEntity.ok("Download task started");
@ -101,5 +122,183 @@ public class DownloadController {
.contentLength(file.length())
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(resource);
}*/
@Autowired
private ParallelZipService zipService;
@Autowired
private TaskTrackerService taskTracker;
@PostMapping("/start")
public ResponseEntity<String> startCompression(@RequestBody DownloadRequest request) {
try {
// 下载水印则生成图片水印
if(Objects.equals(request.getType(),"2")){
generateWatermark(request.getProId());
}
// 1. 获取照片列表
List<Photo> photos = getPhotosForAlbum(request.getProId(),request.getType());
// 2. 注册任务
taskTracker.registerTask(request.getTaskId(), photos.size());
// 3. 开始异步压缩
zipService.compressInParallel(request.getTaskId(),request.getProName(), photos,
(progress, processed, total) -> {
taskTracker.updateProgress(request.getTaskId(), progress, processed, total);
})
.thenAccept(finalPath -> {
String downloadUrl = "/imgTool/api/download/file?taskId=" + request.getTaskId();
taskTracker.markComplete(request.getTaskId(), downloadUrl);
})
.exceptionally(e -> {
log.error("压缩任务失败: {}", request.getTaskId(), e);
taskTracker.markFailed(request.getTaskId(), e.getMessage());
return null;
});
return ResponseEntity.ok("压缩任务已启动");
} catch (Exception e) {
log.error("启动压缩任务失败", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("启动压缩任务失败: " + e.getMessage());
}
}
@GetMapping(value = "/progress", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter streamProgress(@RequestParam String taskId) {
SseEmitter emitter = new SseEmitter(24 * 60 * 60 * 1000L); // 24小时超时
// 添加监听器
taskTracker.addListener(taskId, new TaskTrackerService.TaskListener() {
@Override
public void onProgress(int progress, int processed, int total) {
try {
Map<String, Object> 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.warn("发送进度更新失败", e);
}
}
@Override
public void onComplete(String downloadUrl) {
try {
Map<String, Object> 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);
} finally {
taskTracker.removeListener(taskId, this);
}
}
@Override
public void onError(String message) {
try {
Map<String, Object> 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);
} finally {
taskTracker.removeListener(taskId, this);
}
}
});
// 设置超时和错误处理
emitter.onTimeout(() -> {
taskTracker.removeListener(taskId, (TaskTrackerService.TaskListener) this);
log.warn("SSE连接超时: {}", taskId);
});
emitter.onError(e -> {
taskTracker.removeListener(taskId, (TaskTrackerService.TaskListener) this);
log.warn("SSE连接错误: {}", taskId, e);
});
return emitter;
}
@GetMapping("/file")
public ResponseEntity<Resource> downloadFile(@RequestParam String taskId) {
try {
Path filePath = Paths.get(outputDir, taskId + ".zip");
if (!Files.exists(filePath)) {
return ResponseEntity.notFound().build();
}
Resource resource = new FileSystemResource(filePath);
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + taskId + ".zip\"")
.contentLength(Files.size(filePath))
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(resource);
} catch (IOException e) {
log.error("文件下载失败: {}", taskId, e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
private List<Photo> getPhotosForAlbum(String proId,String type) {
List<Photo> list = Optional.ofNullable(synthesisQueryDao.findByAlbumId(proId,type)).orElseGet(ArrayList::new);
return list;
}
private void generateWatermark(String proId) {
try {
// 查询图片未生成水印照片的数据
List<SynthesisQueryVo> list = Optional.ofNullable(synthesisQueryDao.generateWatermark(proId)).orElseGet(ArrayList::new);
List<Future> futureList = new ArrayList<>();
List<SynthesisQueryVo> newList = new ArrayList<>();
for (SynthesisQueryVo vo : list) {
Future<SynthesisQueryVo> future = testTaskExecutor.submit(new Callable<SynthesisQueryVo>() {
@Override
public SynthesisQueryVo call() throws Exception {
String path = SystemUtils.getUploadPath() + vo.getOriginalFilePath();
if (new File(path).exists()) {
String syPath = generateWatermarkData(vo);
vo.setWatermarkFilePath(syPath);
}
return vo;
}
});
futureList.add(future);
}
for (Future<SynthesisQueryVo> future : futureList) {
SynthesisQueryVo vo = future.get();
newList.add(vo);
}
if(CollectionUtils.isNotEmpty(newList)){
synthesisQueryDao.updateBatchSyData(newList);
}
} catch (Exception e) {
log.error(e.toString(),e);
}
}
public String generateWatermarkData(SynthesisQueryVo vo){
// 准备多行水印文本
List<String> watermarkLines = new ArrayList<>();
String uploadTime = new SimpleDateFormat("yyyy-MM-dd").format(vo.getUploadTime());
watermarkLines.add(uploadTime);
watermarkLines.add(vo.getProName().replaceAll("(.{18})", "$1@@"));
watermarkLines.add(vo.getUploadTypeName());
String sourceTypeName = null;
if (Objects.equals(vo.getSourceType(), "9")) {
sourceTypeName = vo.getTitle();
} else {
sourceTypeName = vo.getSourceTypeName().split("-")[1];
}
watermarkLines.add(sourceTypeName);
String localPath = SystemUtils.getUploadPath() +File.separator+ vo.getOriginalFilePath();
return HighQualityWatermark.generateWatermark(watermarkLines,localPath);
}
}

View File

@ -0,0 +1,215 @@
package com.bonus.imgTool.backstage.service;
import com.bonus.imgTool.backstage.dao.SynthesisQueryDao;
import com.bonus.imgTool.backstage.entity.Photo;
import com.bonus.imgTool.task.entity.DownloadTaskVo;
import com.bonus.imgTool.utils.DateTimeHelper;
import com.bonus.imgTool.utils.SystemUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
/**
* @className:ParallelZipService
* @author:cwchen
* @date:2025-04-09-10:23
* @version:1.0
* @description:
*/
@Service
public class ParallelZipService {
private static final Logger logger = LoggerFactory.getLogger(ParallelZipService.class);
@Value("${download.temp.dir:/tmp/downloads}")
private String tempDir;
@Value("${download.output.dir}")
private String outputDir;
private ExecutorService executorService;
private final int threadPoolSize = 8;
@Resource(name = "SynthesisQueryDao")
private SynthesisQueryDao synthesisQueryDao;
private static final int BUFFER_SIZE = 256 * 1024; // 256KB
@PostConstruct
public void init() {
int threadCount = 8;
executorService = Executors.newFixedThreadPool(threadCount, new ThreadFactory() {
private final AtomicInteger counter = new AtomicInteger(0);
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "zip-worker-" + counter.incrementAndGet());
}
});
}
@PreDestroy
public void shutdown() {
executorService.shutdown();
}
public CompletableFuture<Path> compressInParallel(String taskId,String proName, List<Photo> photos,
ProgressUpdater progressUpdater) {
return CompletableFuture.supplyAsync(() -> {
Path tempDirPath = Paths.get(tempDir, taskId);
Path zipFilePath = tempDirPath.resolve(proName+".zip");
// 添加下载任务
DownloadTaskVo taskVo = new DownloadTaskVo();
taskVo.setTaskId(taskId);
taskVo.setTempFilePath(zipFilePath.toString());
synthesisQueryDao.addTaskDownload(taskVo);
try {
Files.createDirectories(tempDirPath);
// 分片处理照片列表
int totalPhotos = photos.size();
int batchSize = (int) Math.ceil((double) totalPhotos / threadPoolSize);
List<CompletableFuture<Void>> futures = new ArrayList<>();
List<Path> partFiles = new ArrayList<>();
AtomicInteger processedCount = new AtomicInteger(0);
// 创建分片压缩任务
for (int i = 0; i < threadPoolSize; i++) {
int start = i * batchSize;
int end = Math.min(start + batchSize, totalPhotos);
if (start >= end) break;
List<Photo> batch = photos.subList(start, end);
Path partFile = tempDirPath.resolve("part_" + i + ".zip");
partFiles.add(partFile);
futures.add(CompletableFuture.runAsync(() -> {
try (ZipOutputStream zos = new ZipOutputStream(
new BufferedOutputStream(Files.newOutputStream(partFile)))) {
byte[] buffer = new byte[BUFFER_SIZE];
for (Photo photo : batch) {
if(StringUtils.isBlank(photo.getFilePath())){
continue;
}
Path photoPath = Paths.get(SystemUtils.getUploadPath() + File.separator + photo.getFilePath());
if (!Files.exists(photoPath)) continue;
String uniqueEntryName = handlePath(proName,photo,photoPath);
ZipEntry entry = new ZipEntry(uniqueEntryName);
zos.putNextEntry(entry);
try (InputStream is = Files.newInputStream(photoPath)) {
int bytesRead;
while ((bytesRead = is.read(buffer)) > 0) {
zos.write(buffer, 0, bytesRead);
}
}
zos.closeEntry();
// 更新进度
int processed = processedCount.incrementAndGet();
if (processed % 10 == 0 || processed == totalPhotos) {
int progress = (int) ((processed * 100.0) / totalPhotos);
progressUpdater.updateProgress(progress, processed, totalPhotos);
}
}
} catch (IOException e) {
logger.error("分片压缩失败", e);
throw new RuntimeException("分片压缩失败", e);
}
}, executorService));
}
// 等待所有分片完成
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
// 合并所有分片
try (ZipOutputStream finalZos = new ZipOutputStream(
new BufferedOutputStream(Files.newOutputStream(zipFilePath)))) {
byte[] buffer = new byte[BUFFER_SIZE];
for (Path partFile : partFiles) {
try (ZipInputStream zis = new ZipInputStream(
new BufferedInputStream(Files.newInputStream(partFile)))) {
ZipEntry entry;
while ((entry = zis.getNextEntry()) != null) {
finalZos.putNextEntry(entry);
int bytesRead;
while ((bytesRead = zis.read(buffer)) > 0) {
finalZos.write(buffer, 0, bytesRead);
}
finalZos.closeEntry();
zis.closeEntry();
}
}
// 删除分片文件
Files.deleteIfExists(partFile);
}
}
// 移动到最终目录
Path outputDirPath = Paths.get(outputDir);
Files.createDirectories(outputDirPath);
Path finalFilePath = outputDirPath.resolve(taskId + ".zip");
Files.move(zipFilePath, finalFilePath, StandardCopyOption.REPLACE_EXISTING);
// 更新下载任务
String nowTime = DateTimeHelper.currentTwoHours();
taskVo.setFilePath(finalFilePath.toString());
synthesisQueryDao.updateTaskDownload(taskVo,nowTime);
return finalFilePath;
} catch (Exception e) {
logger.error("压缩任务失败: {}", taskId, e);
throw new CompletionException(e);
} finally {
// 7. 清理临时文件
cleanupTempFiles(tempDirPath);
}
}, executorService);
}
private List<List<Photo>> partitionList(List<Photo> list, int size) {
List<List<Photo>> partitions = new ArrayList<>();
for (int i = 0; i < list.size(); i += size) {
partitions.add(list.subList(i, Math.min(i + size, list.size())));
}
return partitions;
}
private void cleanupTempFiles(Path tempDir) {
try {
if (Files.exists(tempDir)) {
FileUtils.deleteDirectory(tempDir.toFile());
}
} catch (IOException e) {
logger.warn("清理临时目录失败: {}", tempDir, e);
}
}
@FunctionalInterface
public interface ProgressUpdater {
void updateProgress(int progress, int processed, int total);
}
public String handlePath(String proName,Photo photo,Path photoPath){
StringBuilder sb = new StringBuilder();
sb.append(proName).append(File.separator);
sb.append(photo.getUploadTypeName()).append(File.separator);
if(!Objects.equals(photo.getUploadType(),"5")){
String[] sourceTypeNameArr = photo.getSourceTypeName().split("-");
sb.append(sourceTypeNameArr[1]).append(File.separator);
}else{
sb.append(photo.getTitle()).append(File.separator);
}
sb.append(photo.getPhotoId()).append("_").append(photoPath.getFileName());
return sb.toString();
}
}

View File

@ -0,0 +1,154 @@
package com.bonus.imgTool.backstage.service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import lombok.Getter;
import lombok.Setter;
/**
* @className:TaskTrackerService
* @author:cwchen
* @date:2025-04-09-10:23
* @version:1.0
* @description:
*/
@Service
public class TaskTrackerService {
private static final Logger logger = LoggerFactory.getLogger(TaskTrackerService.class);
private final Map<String, TaskInfo> tasks = new ConcurrentHashMap<>();
private final Map<String, List<TaskListener>> listeners = new ConcurrentHashMap<>();
public void registerTask(String taskId, int totalItems) {
tasks.put(taskId, new TaskInfo(totalItems));
listeners.putIfAbsent(taskId, new CopyOnWriteArrayList<>());
logger.info("注册新任务: {}, 总文件数: {}", taskId, totalItems);
}
public void updateProgress(String taskId, int progress, int processed, int total) {
TaskInfo task = tasks.get(taskId);
if (task != null) {
task.setProcessed(processed);
notifyProgress(taskId, progress, processed, total);
}
}
public void markComplete(String taskId, String downloadUrl) {
TaskInfo task = tasks.remove(taskId);
if (task != null) {
task.setDownloadUrl(downloadUrl);
notifyComplete(taskId, downloadUrl);
logger.info("任务完成: {}", taskId);
}
}
public void markFailed(String taskId, String errorMessage) {
TaskInfo task = tasks.remove(taskId);
if (task != null) {
task.setErrorMessage(errorMessage);
notifyError(taskId, errorMessage);
logger.error("任务失败: {}, 原因: {}", taskId, errorMessage);
}
}
public Path getFilePath(String taskId) {
TaskInfo task = tasks.get(taskId);
return task != null ? task.getFilePath() : null;
}
public void addListener(String taskId, TaskListener listener) {
listeners.computeIfAbsent(taskId, k -> new CopyOnWriteArrayList<>()).add(listener);
// 如果任务已完成立即通知监听器
TaskInfo task = tasks.get(taskId);
if (task != null && task.isComplete()) {
listener.onComplete(task.getDownloadUrl());
} else if (task != null && task.isFailed()) {
listener.onError(task.getErrorMessage());
}
}
public void removeListener(String taskId, TaskListener listener) {
List<TaskListener> taskListeners = listeners.get(taskId);
if (taskListeners != null) {
taskListeners.remove(listener);
}
}
private void notifyProgress(String taskId, int progress, int processed, int total) {
List<TaskListener> taskListeners = listeners.get(taskId);
if (taskListeners != null) {
taskListeners.forEach(l -> {
try {
l.onProgress(progress, processed, total);
} catch (Exception e) {
logger.warn("通知进度更新失败", e);
}
});
}
}
private void notifyComplete(String taskId, String downloadUrl) {
List<TaskListener> taskListeners = listeners.remove(taskId);
if (taskListeners != null) {
taskListeners.forEach(l -> {
try {
l.onComplete(downloadUrl);
} catch (Exception e) {
logger.warn("通知任务完成失败", e);
}
});
}
}
private void notifyError(String taskId, String message) {
List<TaskListener> taskListeners = listeners.remove(taskId);
if (taskListeners != null) {
taskListeners.forEach(l -> {
try {
l.onError(message);
} catch (Exception e) {
logger.warn("通知任务失败失败", e);
}
});
}
}
@Getter
@Setter
private static class TaskInfo {
private final int total;
private int processed;
private String downloadUrl;
private String errorMessage;
private Path filePath;
public TaskInfo(int total) {
this.total = total;
this.processed = 0;
}
public int getProgress() {
return total == 0 ? 0 : (int) ((processed * 100.0) / total);
}
public boolean isComplete() {
return downloadUrl != null;
}
public boolean isFailed() {
return errorMessage != null;
}
}
public interface TaskListener {
void onProgress(int progress, int processed, int total);
void onComplete(String downloadUrl);
void onError(String message);
}
}

File diff suppressed because one or more lines are too long

View File

@ -2,7 +2,160 @@ let proId = decryptCBC(getUrlParam('proId'));
let type = decryptCBC(getUrlParam('type'));
let title = decryptCBC(decodeURIComponent(getUrlParam('title')));
let proName = decryptCBC(decodeURIComponent(getUrlParam('proName')));
$('#title').html(proName +"-"+ title);
$('#title').html(proName +" - "+ title);
document.addEventListener('DOMContentLoaded', function() {
const downloadBtn = document.getElementById('downloadBtn');
const progressContainer = document.getElementById('progressContainer');
const progressBar = document.getElementById('progress');
const progressPercent = document.getElementById('progressPercent');
const processedFiles = document.getElementById('processedFiles');
const statusText = document.getElementById('statusText');
let eventSource = null;
let currentTaskId = null;
downloadBtn.addEventListener('click', async function() {
downloadBtn.disabled = true;
progressContainer.style.display = 'block';
statusText.textContent = '正在准备生成任务...';
try {
// 1. 生成唯一任务ID
currentTaskId = 'task_' + Date.now();
// 2. 显示等待提示
const swalInstance = Swal.fire({
title: '正在生成压缩包',
html: '系统正在准备您的照片压缩包,这可能需要一些时间...<br><br><div id="swalProgress" style="margin:10px 0;height:10px;background:#f0f0f0;border-radius:5px;overflow:hidden;"><div id="swalProgressBar" style="height:100%;width:0%;background:#4CAF50;transition:width 0.3s"></div></div><small id="swalProgressText">0% 完成</small>',
showConfirmButton: false,
allowOutsideClick: false,
didOpen: () => {
// 连接进度事件
setupProgressListener();
}
});
// 3. 启动下载任务
const response = await fetch('/imgTool/api/download/start', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
taskId: currentTaskId,
proId: proId,
type: type,
proName: proName,
})
});
if (!response.ok) {
throw new Error('启动任务失败');
}
} catch (error) {
console.error('错误:', error);
Swal.fire({
title: '操作失败',
text: error.message,
icon: 'error'
});
resetUI();
}
});
function setupProgressListener() {
// 关闭之前的连接
if (eventSource) eventSource.close();
// 创建新的EventSource连接
eventSource = new EventSource(`/imgTool/api/download/progress?taskId=${currentTaskId}`);
eventSource.onmessage = function(event) {
const data = JSON.parse(event.data);
if (data.type === 'progress') {
// 更新进度
const progress = data.progress;
progressBar.style.width = progress + '%';
document.getElementById('swalProgressBar').style.width = progress + '%';
progressPercent.textContent = progress + '%';
document.getElementById('swalProgressText').textContent = progress + '% 完成';
processedFiles.textContent = `${data.processed}/${data.total} 文件`;
statusText.textContent = getStatusMessage(progress);
}
else if (data.type === 'complete') {
// 关闭进度连接
eventSource.close();
// 显示完成弹窗
Swal.fire({
title: '压缩包已准备好!',
text: '您的照片压缩包已生成完成。',
icon: 'success',
showCancelButton: true,
confirmButtonText: '立即下载',
cancelButtonText: '稍后下载',
allowOutsideClick: false
}).then((result) => {
if (result.isConfirmed) {
// 触发下载
const link = document.createElement('a');
link.href = data.downloadUrl;
link.click();
}
});
resetUI();
}
else if (data.type === 'error') {
// 错误处理
eventSource.close();
Swal.fire({
title: '生成失败',
text: data.message,
icon: 'error'
});
resetUI();
}
};
eventSource.onerror = function() {
eventSource.close();
Swal.fire({
title: '连接中断',
text: '与服务器的连接中断,请检查网络后重试',
icon: 'warning'
});
resetUI();
};
}
function getStatusMessage(progress) {
if (progress < 20) return '正在扫描照片文件...';
if (progress < 50) return '正在压缩照片...';
if (progress < 80) return '正在优化压缩包...';
if (progress < 100) return '即将完成...';
return '压缩完成!';
}
function resetUI() {
downloadBtn.disabled = false;
progressBar.style.width = '0%';
progressPercent.textContent = '0%';
processedFiles.textContent = '0/0 文件';
statusText.textContent = '准备就绪';
}
// 页面关闭前提示
window.addEventListener('beforeunload', function(e) {
if (eventSource && eventSource.readyState === EventSource.OPEN) {
e.preventDefault();
e.returnValue = '文件正在后台生成,离开页面不会中断任务';
return e.returnValue;
}
});
});
/*
document.getElementById('downloadBtn').addEventListener('click', function() {
const btn = this;
btn.disabled = true;
@ -69,4 +222,4 @@ document.getElementById('downloadBtn').addEventListener('click', function() {
btn.disabled = false;
eventSource.close();
});
});
});*/

File diff suppressed because one or more lines are too long

View File

@ -9,8 +9,9 @@
<script src="../../js/openIframe.js"></script>
<script src="../../js/my/aes.js"></script>
<script src="../../js/ajaxRequest.js"></script>
<link rel="stylesheet" href="../../css/synthesisQuery/sweetalert2.min.css">
</head>
<style>
<!-- <style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
@ -54,7 +55,7 @@
}
.notification {
position: fixed;
bottom: 66%;
bottom: 63%;
right: 55%;
padding: 15px;
background-color: #4CAF50;
@ -64,9 +65,71 @@
display: none;
z-index: 1000;
}
</style>-->
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
#downloadBtn {
padding: 12px 24px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 4px;
font-size: 16px;
cursor: pointer;
transition: all 0.3s;
}
#downloadBtn:hover {
background-color: #45a049;
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}
#downloadBtn:disabled {
background-color: #cccccc;
cursor: not-allowed;
transform: none;
box-shadow: none;
}
#progressContainer {
margin-top: 20px;
display: none;
background: #f8f9fa;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
}
.progress-bar {
height: 20px;
background-color: #e9ecef;
border-radius: 4px;
overflow: hidden;
margin-bottom: 10px;
}
.progress {
height: 100%;
width: 0%;
background-color: #4CAF50;
transition: width 0.3s ease;
}
.progress-text {
display: flex;
justify-content: space-between;
font-size: 14px;
color: #6c757d;
}
.status-text {
margin-top: 10px;
font-size: 14px;
color: #495057;
font-style: italic;
}
</style>
<body>
<h2><span style="color: red;">提示:请勿关闭浏览器、浏览器窗口、请勿睡眠,否则下载会失败!!!</span></h2>
<!--<h2><span style="color: red;">提示:请勿关闭浏览器、浏览器窗口、请保证电脑屏幕处于亮屏状态,否则下载会失败!!!</span></h2>
<h3 id="title"></h3>
<p>点击下方按钮下载所有照片</p>
@ -82,7 +145,26 @@
<div id="downloadNotification" class="notification">
您的照片压缩包已准备好!<a href="#" id="downloadLink" style="color: white; text-decoration: underline;">点击下载</a>
</div>-->
<div class="container">
<h2><span style="color: red;">提示:请勿关闭浏览器、浏览器窗口、请保证电脑屏幕处于亮屏状态,否则下载会失败!!!</span></h2>
<h3 id="title"></h3>
<p>点击下方按钮下载所有照片</p>
<button id="downloadBtn" class="btn">开始生成压缩包</button>
<div id="progressContainer">
<div class="progress-bar">
<div id="progress" class="progress"></div>
</div>
<div class="progress-text">
<span id="progressPercent">0%</span>
<span id="processedFiles">0/0 文件</span>
</div>
<div id="statusText" class="status-text">准备中...</div>
</div>
</div>
</body>
<script src="../../js/synthesisQuery/sweetalert2.js"></script>
<script src="../../js/synthesisQuery/fileDownload.js" charset="UTF-8" type="text/javascript"></script>
</html>