缺陷删除记录功能开发
This commit is contained in:
parent
2492d7561b
commit
5bb5940307
|
|
@ -0,0 +1,113 @@
|
||||||
|
package com.bonus.boot.manager.defectStatistics.controller;
|
||||||
|
|
||||||
|
import com.bonus.boot.manager.defectStatistics.entity.DayDefectRateDto;
|
||||||
|
import com.bonus.boot.manager.defectStatistics.entity.ExportTask;
|
||||||
|
import com.bonus.boot.manager.defectStatistics.entity.TowsDelVo;
|
||||||
|
import com.bonus.boot.manager.defectStatistics.service.IDefectDelRecordService;
|
||||||
|
import com.bonus.boot.manager.defectStatistics.service.impl.ExportService;
|
||||||
|
import com.bonus.boot.manager.manager.annotation.DecryptAndVerify;
|
||||||
|
import com.bonus.boot.manager.manager.annotation.LogAnnotation;
|
||||||
|
import com.bonus.boot.manager.manager.entity.EncryptedReq;
|
||||||
|
import com.bonus.boot.manager.manager.utils.ServerResponse;
|
||||||
|
import com.github.pagehelper.PageHelper;
|
||||||
|
import com.github.pagehelper.PageInfo;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @className:DefectDelController
|
||||||
|
* @author:cwchen
|
||||||
|
* @date:2026-01-07-14:00
|
||||||
|
* @version:1.0
|
||||||
|
* @description:缺陷删除记录-web层
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/defectDelRecord")
|
||||||
|
public class DefectDelRecordController {
|
||||||
|
|
||||||
|
@Resource(name = "IDefectDelRecordService")
|
||||||
|
private IDefectDelRecordService service;
|
||||||
|
|
||||||
|
@Resource(name = "ExportService")
|
||||||
|
private ExportService exportService;
|
||||||
|
|
||||||
|
@PostMapping(value = "getList")
|
||||||
|
@DecryptAndVerify(decryptedClass = DayDefectRateDto.class)//加解密统一管理
|
||||||
|
@LogAnnotation(operModul = "缺陷删除记录", operation = "查询数据概览", operDesc = "系统级事件", operType = "查询")
|
||||||
|
public ServerResponse getList(EncryptedReq<DayDefectRateDto> data) {
|
||||||
|
PageHelper.startPage(data.getData().getPage(), data.getData().getLimit());
|
||||||
|
List<TowsDelVo> list = service.getList(data.getData());
|
||||||
|
PageInfo<TowsDelVo> pageInfo = new PageInfo<>(list);
|
||||||
|
return ServerResponse.createSuccessPage(pageInfo, data.getData().getPage(), data.getData().getLimit());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1. 启动导出任务
|
||||||
|
*/
|
||||||
|
@GetMapping("/export")
|
||||||
|
@DecryptAndVerify(decryptedClass = DayDefectRateDto.class)//加解密统一管理
|
||||||
|
public Map<String, Object> startExport(EncryptedReq<DayDefectRateDto> encryptedReq) {
|
||||||
|
// 启动异步任务
|
||||||
|
String taskId = exportService.createExportTask(encryptedReq.getData());
|
||||||
|
Map<String, Object> data = new HashMap<>();
|
||||||
|
data.put("taskId", taskId);
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
result.put("code", 200);
|
||||||
|
result.put("msg", "任务已启动");
|
||||||
|
result.put("data", data);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 2. 查询进度
|
||||||
|
* 对应前端: getExportProgressAPI
|
||||||
|
*/
|
||||||
|
@GetMapping("/progress")
|
||||||
|
@DecryptAndVerify(decryptedClass = DayDefectRateDto.class)//加解密统一管理
|
||||||
|
public Map<String, Object> getProgress(EncryptedReq<DayDefectRateDto> encryptedReq) {
|
||||||
|
ExportTask task = exportService.getTaskStatus(encryptedReq.getData().getTaskId());
|
||||||
|
// 修改 404 情况
|
||||||
|
if (task == null) {
|
||||||
|
Map<String, Object> errorResult = new HashMap<>();
|
||||||
|
errorResult.put("code", 404);
|
||||||
|
errorResult.put("msg", "任务不存在");
|
||||||
|
return errorResult;
|
||||||
|
}
|
||||||
|
// 修改成功情况
|
||||||
|
Map<String, Object> successResult = new HashMap<>();
|
||||||
|
successResult.put("code", 200);
|
||||||
|
successResult.put("data", task);
|
||||||
|
return successResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 3. 下载文件
|
||||||
|
*/
|
||||||
|
@GetMapping("/download")
|
||||||
|
@DecryptAndVerify(decryptedClass = DayDefectRateDto.class)//加解密统一管理
|
||||||
|
public ResponseEntity<org.springframework.core.io.Resource> downloadFile(
|
||||||
|
EncryptedReq<DayDefectRateDto> encryptedReq) throws IOException {
|
||||||
|
|
||||||
|
File file = exportService.getExportFile(encryptedReq.getData().getTaskId());
|
||||||
|
|
||||||
|
if (file == null || !file.exists()) {
|
||||||
|
return ResponseEntity.notFound().build();
|
||||||
|
}
|
||||||
|
org.springframework.core.io.Resource resource = new org.springframework.core.io.FileSystemResource(file);
|
||||||
|
String fileName = URLEncoder.encode(file.getName(), "UTF-8").replaceAll("\\+", "%20");
|
||||||
|
return ResponseEntity.ok()
|
||||||
|
.contentType(MediaType.APPLICATION_OCTET_STREAM)
|
||||||
|
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + fileName + "\"")
|
||||||
|
.body(resource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package com.bonus.boot.manager.defectStatistics.dao;
|
package com.bonus.boot.manager.defectStatistics.dao;
|
||||||
|
|
||||||
|
import com.bonus.boot.manager.defectStatistics.entity.ComprehensiveVo;
|
||||||
import com.bonus.boot.manager.defectStatistics.entity.DayDefectRateDto;
|
import com.bonus.boot.manager.defectStatistics.entity.DayDefectRateDto;
|
||||||
import com.bonus.boot.manager.defectStatistics.entity.DayDefectRateVo;
|
import com.bonus.boot.manager.defectStatistics.entity.DayDefectRateVo;
|
||||||
import org.apache.ibatis.annotations.Param;
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
@ -45,4 +46,22 @@ public interface IDayDefectRateDao {
|
||||||
* @date 2025/12/10 15:18
|
* @date 2025/12/10 15:18
|
||||||
*/
|
*/
|
||||||
void delTowsData(DayDefectRateDto dto);
|
void delTowsData(DayDefectRateDto dto);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取缺陷详情
|
||||||
|
* @param dto
|
||||||
|
* @return ComprehensiveVo
|
||||||
|
* @author cwchen
|
||||||
|
* @date 2026/1/7 16:48
|
||||||
|
*/
|
||||||
|
ComprehensiveVo getDefectDetail(DayDefectRateDto dto);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 记录删除的图片
|
||||||
|
* @param vo
|
||||||
|
* @return void
|
||||||
|
* @author cwchen
|
||||||
|
* @date 2026/1/7 16:52
|
||||||
|
*/
|
||||||
|
void addTowsDelData(ComprehensiveVo vo);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
package com.bonus.boot.manager.defectStatistics.dao;
|
||||||
|
|
||||||
|
import com.bonus.boot.manager.defectStatistics.entity.DayDefectRateDto;
|
||||||
|
import com.bonus.boot.manager.defectStatistics.entity.ImageRecord;
|
||||||
|
import com.bonus.boot.manager.defectStatistics.entity.TowsDelVo;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @className:IDefectDelRecordDao
|
||||||
|
* @author:cwchen
|
||||||
|
* @date:2026-01-07-14:03
|
||||||
|
* @version:1.0
|
||||||
|
* @description:缺陷删除数据层
|
||||||
|
*/
|
||||||
|
@Repository(value = "IDefectDelRecordDao")
|
||||||
|
public interface IDefectDelRecordDao {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 缺陷删除记录
|
||||||
|
* @param dto
|
||||||
|
* @return List<TowsDelVo>
|
||||||
|
* @author cwchen
|
||||||
|
* @date 2026/1/7 14:22
|
||||||
|
*/
|
||||||
|
List<TowsDelVo> getList(DayDefectRateDto dto);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出缺陷数据查询
|
||||||
|
* @param dto
|
||||||
|
* @return List<ImageRecord>
|
||||||
|
* @author cwchen
|
||||||
|
* @date 2026/1/7 16:59
|
||||||
|
*/
|
||||||
|
List<ImageRecord> getImageData(DayDefectRateDto dto);
|
||||||
|
}
|
||||||
|
|
@ -18,4 +18,6 @@ public class DayDefectRateDto extends PageEntity {
|
||||||
private String startDate;
|
private String startDate;
|
||||||
|
|
||||||
private String endDate;
|
private String endDate;
|
||||||
|
|
||||||
|
private String taskId;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
package com.bonus.boot.manager.defectStatistics.entity;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @className:ExportTask
|
||||||
|
* @author:cwchen
|
||||||
|
* @date:2025-12-26-10:15
|
||||||
|
* @version:1.0
|
||||||
|
* @description:导出任务
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class ExportTask {
|
||||||
|
private String taskId;
|
||||||
|
private Integer progress; // 0 - 100
|
||||||
|
private String status; // "processing", "completed", "failed"
|
||||||
|
private String message; // 错误信息
|
||||||
|
private String downloadUrl;
|
||||||
|
private String fileName;
|
||||||
|
private String finalFilePath; // 服务器本地存储路径
|
||||||
|
private String filePath; // 服务器本地临时文件路径
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
package com.bonus.boot.manager.defectStatistics.entity;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import lombok.Data;
|
||||||
|
import org.springframework.format.annotation.DateTimeFormat;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @className:ImageRecord
|
||||||
|
* @author:cwchen
|
||||||
|
* @date:2025-12-26-10:39
|
||||||
|
* @version:1.0
|
||||||
|
* @description: 下载图片实体类
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class ImageRecord {
|
||||||
|
|
||||||
|
private String url;
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
|
||||||
|
@DateTimeFormat(pattern = "yyyy-MM-dd")
|
||||||
|
private Date createTime;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
package com.bonus.boot.manager.defectStatistics.entity;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import lombok.Data;
|
||||||
|
import org.springframework.format.annotation.DateTimeFormat;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @className:TowsDelVo
|
||||||
|
* @author:cwchen
|
||||||
|
* @date:2026-01-07-14:11
|
||||||
|
* @version:1.0
|
||||||
|
* @description:缺陷删除-vo
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class TowsDelVo {
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
/**图片路径*/
|
||||||
|
private String imgPath;
|
||||||
|
/**二次分析图片*/
|
||||||
|
private String twoAnalysisUrl;
|
||||||
|
/**删除时间*/
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||||
|
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
private Date delTime;
|
||||||
|
/**图片编号*/
|
||||||
|
private String imgNo;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
package com.bonus.boot.manager.defectStatistics.service;
|
||||||
|
|
||||||
|
import com.bonus.boot.manager.defectStatistics.entity.DayDefectRateDto;
|
||||||
|
import com.bonus.boot.manager.defectStatistics.entity.ImageRecord;
|
||||||
|
import com.bonus.boot.manager.defectStatistics.entity.TowsDelVo;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @className:IDefectDelRecordService
|
||||||
|
* @author:cwchen
|
||||||
|
* @date:2026-01-07-14:02
|
||||||
|
* @version:1.0
|
||||||
|
* @description:缺陷删除记录-业务层
|
||||||
|
*/
|
||||||
|
public interface IDefectDelRecordService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 缺陷删除记录
|
||||||
|
* @param data
|
||||||
|
* @return List<TowsDelVo>
|
||||||
|
* @author cwchen
|
||||||
|
* @date 2026/1/7 14:20
|
||||||
|
*/
|
||||||
|
List<TowsDelVo> getList(DayDefectRateDto data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出缺陷数据查询
|
||||||
|
* @param dto
|
||||||
|
* @return List<ImageRecord>
|
||||||
|
* @author cwchen
|
||||||
|
* @date 2026/1/7 16:59
|
||||||
|
*/
|
||||||
|
List<ImageRecord> getImageData(DayDefectRateDto dto);
|
||||||
|
}
|
||||||
|
|
@ -4,6 +4,7 @@ import cn.afterturn.easypoi.excel.ExcelExportUtil;
|
||||||
import cn.afterturn.easypoi.excel.entity.ExportParams;
|
import cn.afterturn.easypoi.excel.entity.ExportParams;
|
||||||
import cn.afterturn.easypoi.excel.entity.enmus.ExcelType;
|
import cn.afterturn.easypoi.excel.entity.enmus.ExcelType;
|
||||||
import com.bonus.boot.manager.defectStatistics.dao.IDayDefectRateDao;
|
import com.bonus.boot.manager.defectStatistics.dao.IDayDefectRateDao;
|
||||||
|
import com.bonus.boot.manager.defectStatistics.entity.ComprehensiveVo;
|
||||||
import com.bonus.boot.manager.defectStatistics.entity.DayDefectRateDto;
|
import com.bonus.boot.manager.defectStatistics.entity.DayDefectRateDto;
|
||||||
import com.bonus.boot.manager.defectStatistics.entity.DayDefectRateVo;
|
import com.bonus.boot.manager.defectStatistics.entity.DayDefectRateVo;
|
||||||
import com.bonus.boot.manager.defectStatistics.service.IDayDefectRateService;
|
import com.bonus.boot.manager.defectStatistics.service.IDayDefectRateService;
|
||||||
|
|
@ -125,6 +126,10 @@ public class DayDefectRateServiceImpl implements IDayDefectRateService {
|
||||||
@Override
|
@Override
|
||||||
public AjaxResult delTowsData(DayDefectRateDto dto) {
|
public AjaxResult delTowsData(DayDefectRateDto dto) {
|
||||||
try {
|
try {
|
||||||
|
ComprehensiveVo vo = dayDefectRateDao.getDefectDetail(dto);
|
||||||
|
if (Objects.nonNull(vo)) {
|
||||||
|
dayDefectRateDao.addTowsDelData(vo);
|
||||||
|
}
|
||||||
dayDefectRateDao.delTowsData(dto);
|
dayDefectRateDao.delTowsData(dto);
|
||||||
return AjaxResult.success();
|
return AjaxResult.success();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
package com.bonus.boot.manager.defectStatistics.service.impl;
|
||||||
|
|
||||||
|
import com.bonus.boot.manager.defectStatistics.dao.IDefectDelRecordDao;
|
||||||
|
import com.bonus.boot.manager.defectStatistics.entity.DayDefectRateDto;
|
||||||
|
import com.bonus.boot.manager.defectStatistics.entity.ImageRecord;
|
||||||
|
import com.bonus.boot.manager.defectStatistics.entity.TowsDelVo;
|
||||||
|
import com.bonus.boot.manager.defectStatistics.service.IDefectDelRecordService;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @className:DefectDelRecordService
|
||||||
|
* @author:cwchen
|
||||||
|
* @date:2026-01-07-14:02
|
||||||
|
* @version:1.0
|
||||||
|
* @description:缺陷删除业务逻辑层
|
||||||
|
*/
|
||||||
|
@Service(value = "IDefectDelRecordService")
|
||||||
|
@Slf4j
|
||||||
|
public class DefectDelRecordService implements IDefectDelRecordService {
|
||||||
|
|
||||||
|
@Resource(name = "IDefectDelRecordDao")
|
||||||
|
private IDefectDelRecordDao defectDelRecordDao;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<TowsDelVo> getList(DayDefectRateDto dto) {
|
||||||
|
try {
|
||||||
|
List<TowsDelVo> list = defectDelRecordDao.getList(dto);
|
||||||
|
return list == null ? Collections.<TowsDelVo>emptyList() : list;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e.toString(), e);
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ImageRecord> getImageData(DayDefectRateDto dto) {
|
||||||
|
try {
|
||||||
|
List<ImageRecord> list = defectDelRecordDao.getImageData(dto);
|
||||||
|
return list == null ? Collections.<ImageRecord>emptyList() : list;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e.toString(), e);
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,290 @@
|
||||||
|
package com.bonus.boot.manager.defectStatistics.service.impl;
|
||||||
|
|
||||||
|
|
||||||
|
import com.bonus.boot.manager.defectStatistics.entity.DayDefectRateDto;
|
||||||
|
import com.bonus.boot.manager.defectStatistics.entity.ExportTask;
|
||||||
|
import com.bonus.boot.manager.defectStatistics.entity.ImageRecord;
|
||||||
|
import com.bonus.boot.manager.defectStatistics.service.IDefectDelRecordService;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
|
import org.springframework.scheduling.annotation.Async;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.io.*;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.zip.ZipEntry;
|
||||||
|
import java.util.zip.ZipOutputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @className:ExportService
|
||||||
|
* @author:cwchen
|
||||||
|
* @date:2025-12-26-10:21
|
||||||
|
* @version:1.0
|
||||||
|
* @description:识别图片导出-业务层
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service(value = "ExportService")
|
||||||
|
public class ExportService {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private RedisTemplate<String, Object> redisTemplate;
|
||||||
|
|
||||||
|
@Resource(name = "IDefectDelRecordService")
|
||||||
|
private IDefectDelRecordService service;
|
||||||
|
|
||||||
|
// Redis Key 前缀
|
||||||
|
private static final String REDIS_KEY_PREFIX = "EXPORT:TASK:";
|
||||||
|
// 任务在 Redis 中的过期时间
|
||||||
|
private static final long EXPIRE_TIME = 24;
|
||||||
|
|
||||||
|
// 临时文件存放目录
|
||||||
|
private static final String TEMP_DIR = System.getProperty("java.io.tmpdir") + "/export_task/";
|
||||||
|
|
||||||
|
// 缺陷文件前缀路径
|
||||||
|
private static final String PREFIX_PATH = "http://10.67.21.124:1905/hfTowsBmw/hftowsf/";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建任务并立即返回 TaskID
|
||||||
|
*/
|
||||||
|
public String createExportTask(DayDefectRateDto dto) {
|
||||||
|
String taskId = UUID.randomUUID().toString();
|
||||||
|
|
||||||
|
ExportTask task = new ExportTask();
|
||||||
|
task.setTaskId(taskId);
|
||||||
|
task.setProgress(0);
|
||||||
|
task.setStatus("processing");
|
||||||
|
|
||||||
|
// 存入 Redis
|
||||||
|
saveTaskToRedis(task);
|
||||||
|
|
||||||
|
// 异步执行
|
||||||
|
processExportAsync(taskId, dto);
|
||||||
|
|
||||||
|
return taskId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从 Redis 获取任务状态
|
||||||
|
*/
|
||||||
|
public ExportTask getTaskStatus(String taskId) {
|
||||||
|
Object obj = redisTemplate.opsForValue().get(REDIS_KEY_PREFIX + taskId);
|
||||||
|
if (obj instanceof ExportTask) {
|
||||||
|
return (ExportTask) obj;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public File getExportFile(String taskId) {
|
||||||
|
ExportTask task = getTaskStatus(taskId);
|
||||||
|
// 注意:文件路径还是存在本地磁盘的。
|
||||||
|
// 如果是多实例部署,这里需要改为上传到 OSS/S3 并返回云存储链接,
|
||||||
|
// 或者使用 NFS/共享存储挂载该目录。
|
||||||
|
// 下面代码假设仍在单机磁盘或挂载盘上。
|
||||||
|
if (task != null && task.getFinalFilePath() != null) {
|
||||||
|
return new File(task.getFinalFilePath());
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 辅助方法:保存/更新任务到 Redis
|
||||||
|
*/
|
||||||
|
private void saveTaskToRedis(ExportTask task) {
|
||||||
|
String key = REDIS_KEY_PREFIX + task.getTaskId();
|
||||||
|
// 设置 Key 和 过期时间
|
||||||
|
redisTemplate.opsForValue().set(key, task, EXPIRE_TIME, TimeUnit.HOURS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 核心异步处理逻辑 (业务逻辑大体不变,变的是状态更新方式)
|
||||||
|
*/
|
||||||
|
@Async("taskExecutor2")
|
||||||
|
public void processExportAsync(String taskId, DayDefectRateDto dto) {
|
||||||
|
// 先从 Redis 拿最新的状态
|
||||||
|
ExportTask task = getTaskStatus(taskId);
|
||||||
|
if (task == null) return; // 任务可能已过期或被删
|
||||||
|
|
||||||
|
File masterZipFile = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
List<ImageRecord> allImages = mockDatabaseQuery(dto);
|
||||||
|
|
||||||
|
if (allImages.isEmpty()) {
|
||||||
|
throw new RuntimeException("没有查询到数据");
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, List<ImageRecord>> groupedImages = new HashMap<>();
|
||||||
|
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
|
||||||
|
for (ImageRecord img : allImages) {
|
||||||
|
String dateStr = sdf.format(img.getCreateTime());
|
||||||
|
groupedImages.computeIfAbsent(dateStr, k -> new ArrayList<>()).add(img);
|
||||||
|
}
|
||||||
|
|
||||||
|
File tempDir = new File(TEMP_DIR);
|
||||||
|
if (!tempDir.exists()) tempDir.mkdirs();
|
||||||
|
|
||||||
|
String masterFileName = "缺陷数据_" + System.currentTimeMillis() + ".zip";
|
||||||
|
masterZipFile = new File(tempDir, masterFileName);
|
||||||
|
|
||||||
|
int totalDates = groupedImages.size();
|
||||||
|
int processedCount = 0;
|
||||||
|
|
||||||
|
try (FileOutputStream fos = new FileOutputStream(masterZipFile);
|
||||||
|
ZipOutputStream masterZos = new ZipOutputStream(fos)) {
|
||||||
|
|
||||||
|
for (Map.Entry<String, List<ImageRecord>> entry : groupedImages.entrySet()) {
|
||||||
|
String date = entry.getKey();
|
||||||
|
List<ImageRecord> dailyImages = entry.getValue();
|
||||||
|
|
||||||
|
File dailyZipFile = createDailyZip(date, dailyImages);
|
||||||
|
|
||||||
|
ZipEntry zipEntry = new ZipEntry(date + ".zip");
|
||||||
|
masterZos.putNextEntry(zipEntry);
|
||||||
|
|
||||||
|
try (FileInputStream dailyFis = new FileInputStream(dailyZipFile)) {
|
||||||
|
IOUtils.copy(dailyFis, masterZos);
|
||||||
|
}
|
||||||
|
masterZos.closeEntry();
|
||||||
|
dailyZipFile.delete();
|
||||||
|
|
||||||
|
processedCount++;
|
||||||
|
// 更新 Redis 中的进度
|
||||||
|
int currentProgress = (int) ((double) processedCount / totalDates * 90);
|
||||||
|
updateProgressInRedis(taskId, currentProgress);
|
||||||
|
}
|
||||||
|
masterZos.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 完成状态更新到 Redis
|
||||||
|
task.setProgress(100);
|
||||||
|
task.setStatus("completed");
|
||||||
|
// 这里生成下载接口的地址
|
||||||
|
task.setDownloadUrl("/defectDelRecord/download?taskId=" + taskId);
|
||||||
|
task.setFileName(masterFileName);
|
||||||
|
task.setFinalFilePath(masterZipFile.getAbsolutePath());
|
||||||
|
saveTaskToRedis(task);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
task.setStatus("failed");
|
||||||
|
task.setMessage("导出失败: " + e.getMessage());
|
||||||
|
saveTaskToRedis(task);
|
||||||
|
|
||||||
|
if (masterZipFile != null && masterZipFile.exists()) {
|
||||||
|
masterZipFile.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateProgressInRedis(String taskId, int progress) {
|
||||||
|
ExportTask task = getTaskStatus(taskId);
|
||||||
|
if (task != null) {
|
||||||
|
if (progress > 99) progress = 99;
|
||||||
|
if (progress > task.getProgress()) {
|
||||||
|
task.setProgress(progress);
|
||||||
|
saveTaskToRedis(task); // 写回 Redis
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成某一天的压缩包 (已修改:支持从服务器本地路径读取文件)
|
||||||
|
*
|
||||||
|
* @param dateStr 日期字符串
|
||||||
|
* @param images 包含本地路径信息的图片记录列表
|
||||||
|
* @return 压缩后的临时文件
|
||||||
|
*/
|
||||||
|
private File createDailyZip(String dateStr, List<ImageRecord> images) throws IOException {
|
||||||
|
// 1. 确保临时目录存在
|
||||||
|
File tempDir = new File(TEMP_DIR);
|
||||||
|
if (!tempDir.exists()) {
|
||||||
|
tempDir.mkdirs();
|
||||||
|
}
|
||||||
|
|
||||||
|
File dailyZip = new File(TEMP_DIR, "temp_" + dateStr + "_" + UUID.randomUUID() + ".zip");
|
||||||
|
|
||||||
|
try (FileOutputStream fos = new FileOutputStream(dailyZip);
|
||||||
|
ZipOutputStream dailyZos = new ZipOutputStream(fos)) {
|
||||||
|
|
||||||
|
for (ImageRecord img : images) {
|
||||||
|
String imageUrl = PREFIX_PATH + img.getUrl();
|
||||||
|
// String imageUrl = "http://192.168.0.14:9090/smart-bid/personnelDatabase/2025/11/26/2dcc8d4ad41941c98593446f5eacb536.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=minio%2F20260107%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20260107T091412Z&X-Amz-Expires=86400&X-Amz-SignedHeaders=host&X-Amz-Signature=bc233cd628e21f4f8a62864e461eba04a16d88207ea3b239be9eb774ed958c39";
|
||||||
|
|
||||||
|
if (imageUrl == null || !imageUrl.startsWith("http")) {
|
||||||
|
log.warn("无效的图片URL: {}", imageUrl);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpURLConnection connection = null;
|
||||||
|
try {
|
||||||
|
// 2. 建立 HTTP 连接
|
||||||
|
URL url = new URL(imageUrl);
|
||||||
|
connection = (HttpURLConnection) url.openConnection();
|
||||||
|
connection.setRequestMethod("GET");
|
||||||
|
connection.setConnectTimeout(5000); // 5秒连接超时
|
||||||
|
connection.setReadTimeout(10000); // 10秒读取超时
|
||||||
|
|
||||||
|
int responseCode = connection.getResponseCode();
|
||||||
|
if (responseCode != HttpURLConnection.HTTP_OK) {
|
||||||
|
log.error("下载失败, HTTP响应码: {}, URL: {}", responseCode, imageUrl);
|
||||||
|
writeErrorEntry(dailyZos, imageUrl, "HTTP Error: " + responseCode);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 从 URL 获取文件名 (或生成唯一名)
|
||||||
|
String fileName = imageUrl.substring(imageUrl.lastIndexOf('/') + 1);
|
||||||
|
if (fileName.contains("?")) { // 处理带参数的 URL
|
||||||
|
fileName = fileName.substring(0, fileName.indexOf('?'));
|
||||||
|
}
|
||||||
|
String zipEntryName = UUID.randomUUID().toString().substring(0, 8) + "_" + fileName;
|
||||||
|
|
||||||
|
// 4. 创建 Zip 条目并拷贝流
|
||||||
|
ZipEntry entry = new ZipEntry(zipEntryName);
|
||||||
|
dailyZos.putNextEntry(entry);
|
||||||
|
|
||||||
|
try (InputStream is = connection.getInputStream();
|
||||||
|
BufferedInputStream bis = new BufferedInputStream(is)) {
|
||||||
|
|
||||||
|
// 使用 IOUtils 将网络流直接拷贝到 ZIP 流
|
||||||
|
IOUtils.copy(bis, dailyZos);
|
||||||
|
}
|
||||||
|
dailyZos.closeEntry();
|
||||||
|
log.info("成功将远程图片添加到压缩包: {}", imageUrl);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("下载远程图片异常: URL={}, Error={}", imageUrl, e.getMessage());
|
||||||
|
writeErrorEntry(dailyZos, imageUrl, "Exception: " + e.getMessage());
|
||||||
|
} finally {
|
||||||
|
if (connection != null) {
|
||||||
|
connection.disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dailyZip;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 辅助方法:向压缩包写入错误信息文件
|
||||||
|
*/
|
||||||
|
private void writeErrorEntry(ZipOutputStream zos, String path, String reason) {
|
||||||
|
try {
|
||||||
|
ZipEntry errorEntry = new ZipEntry("error_" + UUID.randomUUID().toString().substring(0, 8) + ".txt");
|
||||||
|
zos.putNextEntry(errorEntry);
|
||||||
|
String errorMsg = "读取文件失败: " + path + "\n原因: " + reason;
|
||||||
|
zos.write(errorMsg.getBytes());
|
||||||
|
zos.closeEntry();
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ImageRecord> mockDatabaseQuery(DayDefectRateDto dto) {
|
||||||
|
return service.getImageData(dto);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,6 +6,9 @@ import org.springframework.core.task.TaskExecutor;
|
||||||
import org.springframework.scheduling.annotation.EnableAsync;
|
import org.springframework.scheduling.annotation.EnableAsync;
|
||||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||||
|
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 线程池配置、启用异步
|
* 线程池配置、启用异步
|
||||||
*
|
*
|
||||||
|
|
@ -22,4 +25,27 @@ public class AsycTaskExecutorConfig {
|
||||||
|
|
||||||
return taskExecutor;
|
return taskExecutor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean("taskExecutor2")
|
||||||
|
public Executor taskExecutor2() {
|
||||||
|
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||||
|
// 核心线程数
|
||||||
|
executor.setCorePoolSize(5);
|
||||||
|
// 最大线程数
|
||||||
|
executor.setMaxPoolSize(10);
|
||||||
|
// 队列容量
|
||||||
|
executor.setQueueCapacity(100);
|
||||||
|
// 线程存活时间
|
||||||
|
executor.setKeepAliveSeconds(60);
|
||||||
|
// 线程名称前缀
|
||||||
|
executor.setThreadNamePrefix("task-executor-");
|
||||||
|
// 拒绝策略
|
||||||
|
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
|
||||||
|
// 等待所有任务结束后再关闭线程池
|
||||||
|
executor.setWaitForTasksToCompleteOnShutdown(true);
|
||||||
|
// 等待时间
|
||||||
|
executor.setAwaitTerminationSeconds(60);
|
||||||
|
executor.initialize();
|
||||||
|
return executor;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ public class PermissionController {
|
||||||
private static final Set<Long> ONLINE_IGNORE_IDS = Stream.of(58L, 60L,95L, 72L, 96L, 82L, 75L)
|
private static final Set<Long> ONLINE_IGNORE_IDS = Stream.of(58L, 60L,95L, 72L, 96L, 82L, 75L)
|
||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
private static final Set<Long> OFFNLINE_IGNORE_IDS = Stream.of(101L, 102L)
|
private static final Set<Long> OFFNLINE_IGNORE_IDS = Stream.of(101L, 102L,103L)
|
||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,75 @@
|
||||||
|
package com.bonus.boot.manager.plan;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||||
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.attribute.BasicFileAttributes;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
/**
|
||||||
|
* @className:FileCleanupTask
|
||||||
|
* @author:cwchen
|
||||||
|
* @date:2025-12-30-17:07
|
||||||
|
* @version:1.0
|
||||||
|
* @description:文件清理任务类
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
@EnableScheduling
|
||||||
|
public class FileCleanupTask {
|
||||||
|
|
||||||
|
// 使用之前定义的临时目录路径
|
||||||
|
private static final String TEMP_PATH = System.getProperty("java.io.tmpdir") + File.separator + "export_task" + File.separator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 每天凌晨 2 点执行清理任务
|
||||||
|
* cron 表达式: 秒 分 时 日 月 周
|
||||||
|
*/
|
||||||
|
@Scheduled(cron = "0 0 2 * * ?")
|
||||||
|
public void cleanupTempFiles() {
|
||||||
|
log.info("开始执行导出临时文件清理任务...");
|
||||||
|
File directory = new File(TEMP_PATH);
|
||||||
|
|
||||||
|
if (!directory.exists() || !directory.isDirectory()) {
|
||||||
|
log.info("临时目录不存在,跳过清理。");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
File[] files = directory.listFiles();
|
||||||
|
if (files == null || files.length == 0) {
|
||||||
|
log.info("临时目录为空,无需清理。");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
long currentTimeMillis = System.currentTimeMillis();
|
||||||
|
long twentyFourHoursMillis = TimeUnit.HOURS.toMillis(24);
|
||||||
|
int deleteCount = 0;
|
||||||
|
|
||||||
|
for (File file : files) {
|
||||||
|
try {
|
||||||
|
// 获取文件属性中的创建时间
|
||||||
|
BasicFileAttributes attr = Files.readAttributes(file.toPath(), BasicFileAttributes.class);
|
||||||
|
long creationTime = attr.creationTime().toMillis();
|
||||||
|
|
||||||
|
// 如果当前时间 - 创建时间 > 24小时,则删除
|
||||||
|
if (currentTimeMillis - creationTime > twentyFourHoursMillis) {
|
||||||
|
if (file.delete()) {
|
||||||
|
deleteCount++;
|
||||||
|
log.debug("已删除过期临时文件: {}", file.getName());
|
||||||
|
} else {
|
||||||
|
log.warn("无法删除文件: {}", file.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("处理文件时出错: {}, 错误: {}", file.getName(), e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("清理任务完成,共删除 {} 个过期文件。", deleteCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,96 +0,0 @@
|
||||||
# 服务器配置
|
|
||||||
server:
|
|
||||||
port: 1907
|
|
||||||
servlet:
|
|
||||||
context-path: /hfTowsBmw
|
|
||||||
session:
|
|
||||||
timeout: 10
|
|
||||||
tomcat:
|
|
||||||
uri-encoding: UTF-8
|
|
||||||
max-http-header-size: 102400
|
|
||||||
|
|
||||||
# Spring 配置
|
|
||||||
spring:
|
|
||||||
# 数据源配置
|
|
||||||
datasource:
|
|
||||||
url: jdbc:mysql://192.168.0.14:2009/hftows?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false
|
|
||||||
username: root
|
|
||||||
password: Bonus@admin123!
|
|
||||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
|
||||||
max-idle: 10
|
|
||||||
max-wait: 60000
|
|
||||||
min-idle: 5
|
|
||||||
initial-size: 5
|
|
||||||
dynamic:
|
|
||||||
primary: mysqldb
|
|
||||||
|
|
||||||
# Redis 配置
|
|
||||||
redis:
|
|
||||||
host: 127.0.0.1
|
|
||||||
port: 6379
|
|
||||||
# password: HAY@xyksj666
|
|
||||||
|
|
||||||
# 文件上传配置
|
|
||||||
servlet:
|
|
||||||
multipart:
|
|
||||||
enabled: true
|
|
||||||
max-file-size: -1
|
|
||||||
max-request-size: -1
|
|
||||||
|
|
||||||
http:
|
|
||||||
multipart:
|
|
||||||
maxFileSize: 10Mb
|
|
||||||
maxRequestSize: 10Mb
|
|
||||||
|
|
||||||
# 国际化配置
|
|
||||||
messages:
|
|
||||||
basename: i18n.lang
|
|
||||||
encoding: UTF-8
|
|
||||||
|
|
||||||
# Thymeleaf 配置
|
|
||||||
thymeleaf:
|
|
||||||
cache: false
|
|
||||||
encoding: UTF-8
|
|
||||||
mode: HTML5
|
|
||||||
suffix: .html
|
|
||||||
prefix: classpath:/static/
|
|
||||||
|
|
||||||
# MyBatis 配置
|
|
||||||
mybatis:
|
|
||||||
mapper-locations: classpath:mappers/*/*Mapper.xml
|
|
||||||
type-aliases-package: com.bonus.boot.manager.*.entity
|
|
||||||
|
|
||||||
# 日志配置
|
|
||||||
log:
|
|
||||||
config: classpath:logback-boot.xml
|
|
||||||
level:
|
|
||||||
root: info
|
|
||||||
my: debug
|
|
||||||
file: logs/sys-back.log
|
|
||||||
maxsize: 30MB
|
|
||||||
|
|
||||||
# 应用自定义配置
|
|
||||||
token:
|
|
||||||
expire:
|
|
||||||
seconds: 360000
|
|
||||||
|
|
||||||
files:
|
|
||||||
path: /data/files/
|
|
||||||
win:
|
|
||||||
path: d:/files/
|
|
||||||
|
|
||||||
hfTowsBmw:
|
|
||||||
aq:
|
|
||||||
enable: false
|
|
||||||
|
|
||||||
# 登录验证码配置
|
|
||||||
loginCode:
|
|
||||||
expiration: 3
|
|
||||||
prefix: login_code
|
|
||||||
|
|
||||||
# SIGAR 库路径配置
|
|
||||||
sigar:
|
|
||||||
library-paths:
|
|
||||||
windows64: L:\\dll\\sigar-amd64-winnt.dll
|
|
||||||
windows32: L:\\dll\\sigar-x86-winnt.dll
|
|
||||||
linux64: /opt/libs/sigar/libsigar-amd64-linux.so
|
|
||||||
|
|
@ -316,6 +316,11 @@ Angle = Angle
|
||||||
proportion=占比
|
proportion=占比
|
||||||
quantity=数量
|
quantity=数量
|
||||||
roll_angle_rate=Roll/Angle比例
|
roll_angle_rate=Roll/Angle比例
|
||||||
|
secondary_analysis_picture=二次分析图片
|
||||||
|
master_drawing=原图
|
||||||
|
view=查看
|
||||||
|
deleteTime=删除时间
|
||||||
|
|
||||||
|
|
||||||
# laydate 语言
|
# laydate 语言
|
||||||
laydateEn =
|
laydateEn =
|
||||||
|
|
@ -322,3 +322,7 @@ roughening_num = the number of brushed hairs
|
||||||
roughening_width = burr length
|
roughening_width = burr length
|
||||||
roughening_width_proportion = roughening length ratio
|
roughening_width_proportion = roughening length ratio
|
||||||
roll_angle_rate=Roll/AngleProportion
|
roll_angle_rate=Roll/AngleProportion
|
||||||
|
secondary_analysis_picture=secondary analysis picture
|
||||||
|
master_drawing=master drawing
|
||||||
|
view=view
|
||||||
|
deleteTime=delete time
|
||||||
|
|
|
||||||
|
|
@ -321,3 +321,7 @@ roughening_num = 拉毛个数
|
||||||
roughening_width = 毛边长度
|
roughening_width = 毛边长度
|
||||||
roughening_width_proportion = 拉毛长度比
|
roughening_width_proportion = 拉毛长度比
|
||||||
roll_angle_rate=Roll/Angle比例
|
roll_angle_rate=Roll/Angle比例
|
||||||
|
secondary_analysis_picture=二次分析图片
|
||||||
|
master_drawing=原图
|
||||||
|
view=查看
|
||||||
|
deleteTime=删除时间
|
||||||
|
|
@ -2,8 +2,15 @@
|
||||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
<mapper namespace="com.bonus.boot.manager.defectStatistics.dao.IDayDefectRateDao">
|
<mapper namespace="com.bonus.boot.manager.defectStatistics.dao.IDayDefectRateDao">
|
||||||
|
<!--记录删除的图片-->
|
||||||
|
<insert id="addTowsDelData">
|
||||||
|
INSERT INTO bm_tows_del(img_path,img_no,two_analysis_url)
|
||||||
|
VALUES (
|
||||||
|
#{imgInitialUrl},#{imgNum},#{imgTwoAnalysisUrl}
|
||||||
|
)
|
||||||
|
</insert>
|
||||||
|
|
||||||
<!--每日缺陷率统计-->
|
<!--每日缺陷率统计-->
|
||||||
<select id="getList" resultType="com.bonus.boot.manager.defectStatistics.entity.DayDefectRateVo">
|
<select id="getList" resultType="com.bonus.boot.manager.defectStatistics.entity.DayDefectRateVo">
|
||||||
SELECT
|
SELECT
|
||||||
create_date AS createDate,
|
create_date AS createDate,
|
||||||
|
|
@ -29,6 +36,13 @@
|
||||||
AND is_active = '1'
|
AND is_active = '1'
|
||||||
GROUP BY DATE_FORMAT(createTime,'%Y-%m-%d')
|
GROUP BY DATE_FORMAT(createTime,'%Y-%m-%d')
|
||||||
</select>
|
</select>
|
||||||
|
<!--获取缺陷详情-->
|
||||||
|
<select id="getDefectDetail" resultType="com.bonus.boot.manager.defectStatistics.entity.ComprehensiveVo">
|
||||||
|
SELECT img_initial_url AS imgInitialUrl,
|
||||||
|
img_num AS imgNum,
|
||||||
|
img_two_analysis_url AS imgTwoAnalysisUrl
|
||||||
|
FROM bm_tows WHERE id = #{id}
|
||||||
|
</select>
|
||||||
|
|
||||||
<!--删除缺陷图片-->
|
<!--删除缺陷图片-->
|
||||||
<update id="delTowsData">
|
<update id="delTowsData">
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||||
|
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="com.bonus.boot.manager.defectStatistics.dao.IDefectDelRecordDao">
|
||||||
|
|
||||||
|
<!--缺陷删除记录-->
|
||||||
|
<select id="getList" resultType="com.bonus.boot.manager.defectStatistics.entity.TowsDelVo">
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
img_path AS imgPath,
|
||||||
|
del_time AS delTime,
|
||||||
|
img_no AS imgNo,
|
||||||
|
two_analysis_url AS twoAnalysisUrl
|
||||||
|
FROM bm_tows_del
|
||||||
|
<where>
|
||||||
|
<if test="startDate != null and startDate != '' and endDate != null and endDate != ''">
|
||||||
|
AND del_time BETWEEN CONCAT(#{startDate}, ' 00:00:00') AND CONCAT(#{endDate},' 23:59:59')
|
||||||
|
</if>
|
||||||
|
</where>
|
||||||
|
ORDER BY del_time DESC
|
||||||
|
</select>
|
||||||
|
<!--导出缺陷数据查询-->
|
||||||
|
<select id="getImageData" resultType="com.bonus.boot.manager.defectStatistics.entity.ImageRecord">
|
||||||
|
SELECT
|
||||||
|
img_path AS url,
|
||||||
|
del_time AS createTime
|
||||||
|
FROM bm_tows_del
|
||||||
|
<where>
|
||||||
|
<if test="startDate != null and startDate != '' and endDate != null and endDate != ''">
|
||||||
|
AND del_time BETWEEN CONCAT(#{startDate}, ' 00:00:00') AND CONCAT(#{endDate},' 23:59:59')
|
||||||
|
</if>
|
||||||
|
</where>
|
||||||
|
ORDER BY del_time DESC
|
||||||
|
</select>
|
||||||
|
</mapper>
|
||||||
|
|
@ -0,0 +1,272 @@
|
||||||
|
var form, layer, table, tableIns, formSelects;
|
||||||
|
// 应用类别下拉选
|
||||||
|
var baleNumList;
|
||||||
|
var pageNum = 1, limitSize = 10; // 默认第一页,分页数量为10
|
||||||
|
const isOnline = localStorage.getItem("online");
|
||||||
|
layui.use(['form', 'layer', 'table', 'laydate'], function () {
|
||||||
|
|
||||||
|
form = layui.form;
|
||||||
|
layer = layui.layer;
|
||||||
|
table = layui.table;
|
||||||
|
formSelects = layui.formSelects;
|
||||||
|
baleNumList = getBaleNumSelectList();
|
||||||
|
setSelectValue(baleNumList, 'baleNum', package_number_select);
|
||||||
|
form.render();
|
||||||
|
var laydate = layui.laydate;
|
||||||
|
// 日期范围
|
||||||
|
laydate.render({
|
||||||
|
elem: "#operateTime",
|
||||||
|
range: true,
|
||||||
|
lang: dataLang,
|
||||||
|
value:new Date().toISOString().split('T')[0] +" - "+new Date().toISOString().split('T')[0],
|
||||||
|
shortcuts: [
|
||||||
|
{
|
||||||
|
text: last_7_days,
|
||||||
|
value: function () {
|
||||||
|
var value = [];
|
||||||
|
var date1 = new Date();
|
||||||
|
date1.setDate(date1.getDate() - 7);
|
||||||
|
date1.setHours(0, 0, 0, 0);
|
||||||
|
value.push(date1);
|
||||||
|
var date2 = new Date();
|
||||||
|
date2.setDate(date2.getDate() - 1);
|
||||||
|
date2.setHours(23, 59, 59, 999);
|
||||||
|
value.push(date2);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: last_30_days,
|
||||||
|
value: function () {
|
||||||
|
var value = [];
|
||||||
|
var date1 = new Date();
|
||||||
|
date1.setDate(date1.getDate() - 30);
|
||||||
|
date1.setHours(0, 0, 0, 0);
|
||||||
|
value.push(date1);
|
||||||
|
var date2 = new Date();
|
||||||
|
date2.setDate(date2.getDate() - 1);
|
||||||
|
date2.setHours(23, 59, 59, 999);
|
||||||
|
value.push(date2);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: last_3_months,
|
||||||
|
value: function () {
|
||||||
|
var value = [];
|
||||||
|
var date1 = new Date();
|
||||||
|
date1.setMonth(date1.getMonth() - 3);
|
||||||
|
date1.setDate(1);
|
||||||
|
date1.setHours(0, 0, 0, 0);
|
||||||
|
value.push(date1);
|
||||||
|
var date2 = new Date();
|
||||||
|
date2.setDate(1);
|
||||||
|
date2.setHours(0, 0, 0, 0);
|
||||||
|
date2 = date2.getTime() - 1;
|
||||||
|
value.push(new Date(date2));
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
pages(1, 10, 1);
|
||||||
|
//重置按钮事件
|
||||||
|
$("#reset").click(function () {
|
||||||
|
$("#operateTime").val(getNowTime() + " - " + getNowTime());
|
||||||
|
query();
|
||||||
|
form.render();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function pages(pageNum, pageSize, typeNum) {
|
||||||
|
var params = getReqParams(pageNum, pageSize, typeNum);
|
||||||
|
var url = dataUrl + "/defectDelRecord/getList";
|
||||||
|
ajaxRequest(url, "POST", params, true, function () {
|
||||||
|
}, function (result) {
|
||||||
|
console.log(result);
|
||||||
|
if (result.code === 200) {
|
||||||
|
if (result.data) {
|
||||||
|
initTable(result.data, result.limit, result.curr)
|
||||||
|
laypages(result.count, result.curr, result.limit)
|
||||||
|
}
|
||||||
|
} else if (result.code === 500) {
|
||||||
|
layer.alert(result.msg, {icon: 2})
|
||||||
|
}
|
||||||
|
}, function (xhr) {
|
||||||
|
error(xhr)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function laypages(total, page, limit) {
|
||||||
|
layui.use(['laypage'], function () {
|
||||||
|
var laypage = layui.laypage;
|
||||||
|
laypage.render({
|
||||||
|
elem: 'voi-page',
|
||||||
|
count: total,
|
||||||
|
curr: page,
|
||||||
|
limit: limit,
|
||||||
|
limits: [10, 20, 50, 100, 200, 500],
|
||||||
|
layout: ['prev', 'page', 'next', 'skip', 'count', 'limit'],
|
||||||
|
groups: 5,
|
||||||
|
jump: function (obj, first) {
|
||||||
|
if (!first) {
|
||||||
|
pageNum = obj.curr, limitSize = obj.limit;
|
||||||
|
pages(obj.curr, obj.limit, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/*初始化表格*/
|
||||||
|
function initTable(dataList, limit, page) {
|
||||||
|
var loadingMsg = layer.msg(load_tip, {icon: 16, scrollbar: false, time: 0,});
|
||||||
|
tableIns = table.render({
|
||||||
|
elem: "#table_data",
|
||||||
|
height: "full-140",
|
||||||
|
toolbar: '#toolbarDemo',
|
||||||
|
defaultToolbar: ['filter'], //本章讲述的参数!!!!!!!!!!
|
||||||
|
data: dataList,
|
||||||
|
limit: limit,
|
||||||
|
cols: [
|
||||||
|
setTableField(page, limit)
|
||||||
|
],
|
||||||
|
done: function (res, curr, count) {
|
||||||
|
layer.close(loadingMsg);
|
||||||
|
table.resize("table_data");
|
||||||
|
count || this.elem.next(".layui-table-view").find(".layui-table-header").css("display", "inline-block");
|
||||||
|
count || this.elem.next(".layui-table-view").find(".layui-table-box").css("overflow", "auto");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function setTableField(page, limit) {
|
||||||
|
let cols = [
|
||||||
|
{
|
||||||
|
title: order_number,
|
||||||
|
width: '5%',
|
||||||
|
rowspan: 2,
|
||||||
|
unresize: true,
|
||||||
|
align: "center",
|
||||||
|
style: "word-break: break-all;",
|
||||||
|
templet: function (d) {
|
||||||
|
return (page - 1) * limit + d.LAY_INDEX + 1;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'imgNum', width: '30%', align: "center", title: photo_number,
|
||||||
|
templet: function (d) {
|
||||||
|
var html = '';
|
||||||
|
if (d.id == "" || d.id == "" || d.id == "null" || d.id == null) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
html += "<a style='color: #3E689A;font-weight: bold;' onclick='getImgNum2(" + JSON.stringify(d) + ")'>" + d.id + "</a>";
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
width: '20%', align: "center", title: master_drawing,
|
||||||
|
templet: function (d) {
|
||||||
|
return "<a style='color: #3E689A;font-weight: bold;' onclick='getImgNum("+JSON.stringify(d)+")'>"+view+"</a>";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
width: '20%', align: "center", title: secondary_analysis_picture,
|
||||||
|
templet: function (d) {
|
||||||
|
return "<a style='color: #3E689A;font-weight: bold;' onclick='getImgNum("+JSON.stringify(d)+")'>"+view+"</a>";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{field: 'delTime', width: '25%', align: "center", title: deleteTime},
|
||||||
|
]
|
||||||
|
return cols;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**原图 / 二次缺陷图片*/
|
||||||
|
function getImgNum(obj, type) {
|
||||||
|
layer.photos({
|
||||||
|
photos: {
|
||||||
|
"title": photo_details,
|
||||||
|
"start": 0,
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"alt": "图片详情",
|
||||||
|
"pid": 1,
|
||||||
|
// "src": "https://unpkg.com/outeres@0.1.1/demo/layer.png"
|
||||||
|
"src": imgpathurl + (type === 1 ? obj.imgPath : obj.twoAnalysisUrl),
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
// --- 调整背景透明度 ---
|
||||||
|
shade: [0.3, '#000'], // 0.3 表示 30% 透明度的黑色,背景不会那么黑
|
||||||
|
// -------------------
|
||||||
|
maxWidth: 800,
|
||||||
|
maxHeight: 600,
|
||||||
|
anim: 5
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取参数
|
||||||
|
function getReqParams(page, limit, type) {
|
||||||
|
var obj = {};
|
||||||
|
if (!type) {
|
||||||
|
obj = {
|
||||||
|
page: page + "",
|
||||||
|
limit: limit + "",
|
||||||
|
baleNum: $('#baleNum').val(),
|
||||||
|
operateTime: $('#operateTime').val(),
|
||||||
|
startDate: $('#operateTime').val() ? $('#operateTime').val().split(' - ')[0] : '',
|
||||||
|
endDate: $('#operateTime').val() ? $('#operateTime').val().split(' - ')[1] : '',
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
obj = {
|
||||||
|
page: '1',
|
||||||
|
limit: '10',
|
||||||
|
keyWord: '',
|
||||||
|
operateTime: '',
|
||||||
|
startDate: $('#operateTime').val() ? $('#operateTime').val().split(' - ')[0] : '',
|
||||||
|
endDate: $('#operateTime').val() ? $('#operateTime').val().split(' - ')[1] : '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
obj = {
|
||||||
|
encryptedData: encryptCBC(JSON.stringify(obj))
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询/重置
|
||||||
|
function query() {
|
||||||
|
pages(1, limitSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
function reloadData() {
|
||||||
|
pages(pageNum, limitSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
function exportData() {
|
||||||
|
// 1. 模拟获取 queryParams (对应 this.queryParams)
|
||||||
|
const queryParams = {
|
||||||
|
startDate: $('#operateTime').val() ? $('#operateTime').val().split(' - ')[0] : '',
|
||||||
|
endDate: $('#operateTime').val() ? $('#operateTime').val().split(' - ')[1] : '',
|
||||||
|
};
|
||||||
|
|
||||||
|
// 3. 将参数编码
|
||||||
|
const paramsStr = encodeURIComponent(JSON.stringify(queryParams));
|
||||||
|
|
||||||
|
// 4. 拼接 URL
|
||||||
|
const url = `../../pages/defectStatistics/export-progress.html?params=${paramsStr}`;
|
||||||
|
|
||||||
|
// 5. 计算窗口居中位置
|
||||||
|
const width = 750;
|
||||||
|
const height = 500;
|
||||||
|
const left = (window.screen.width - width) / 2;
|
||||||
|
const top = (window.screen.height - height) / 2;
|
||||||
|
|
||||||
|
// 6. 打开新窗口
|
||||||
|
window.open(
|
||||||
|
url,
|
||||||
|
'_blank',
|
||||||
|
`width=${width},height=${height},left=${left},top=${top},scrollbars=yes,resizable=yes`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,107 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Roll/Angle比例</title>
|
||||||
|
<link rel="stylesheet" href="../../js/layui-v2.8.17/css/layui.css">
|
||||||
|
<link rel="stylesheet" href="../../css/table-common2.css">
|
||||||
|
<link rel="stylesheet" href="../../js/layui-v2.6.8/formSelects-v4.css">
|
||||||
|
<script src="../../js/libs/jquery-3.6.0.min.js" charset="UTF-8" type="text/javascript"></script>
|
||||||
|
<script src="../../js/layui-v2.8.17/layui.js" charset="UTF-8" type="text/javascript"></script>
|
||||||
|
<script src="../../js/publicJs.js"></script>
|
||||||
|
<script src="../../js/aes.js"></script>
|
||||||
|
<script src="../../js/ajaxRequest.js"></script>
|
||||||
|
<script src="../../js/select.js"></script>
|
||||||
|
<script src="../../js/commonUtils.js"></script>
|
||||||
|
<script src="../../js/openIframe.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="content">
|
||||||
|
<div class="basic-search-box layout">
|
||||||
|
<form class="layui-form basic-form" onclick="return false;">
|
||||||
|
<div class="layui-form-item">
|
||||||
|
<div class="layui-inline" style="padding: 0 0 0 10px;">
|
||||||
|
<div class="layui-input-inline baleNum" style="display: none">
|
||||||
|
<select class="layui-select" id="baleNum" lay-search ></select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-inline">
|
||||||
|
<div class="layui-input-inline">
|
||||||
|
<input type="text" class="layui-input" id="operateTime" readonly th:placeholder="#{date_prompt}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-inline btns">
|
||||||
|
<button type="button" class="layui-btn layui-btn-normal layui-btn-sm btn-1" onclick="query(1)">[[#{query_button}]]
|
||||||
|
</button>
|
||||||
|
<button type="reset" class="layui-btn layui-btn-primary layui-btn-sm btn-4" id="reset">[[#{resetting_button}]]</button>
|
||||||
|
<button id="exportBt" class="layui-btn" style="width: 80px" onclick="exportData()">
|
||||||
|
[[#{export_button}]]
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="table-box" table-responsive style="z-index: 1;">
|
||||||
|
<table id="table_data" class="table" lay-filter="table_data">
|
||||||
|
|
||||||
|
</table>
|
||||||
|
<div id="voi-page" class="layout"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script src="../../js/defectStatistics/defectDelRecord.js" charset="UTF-8" type="text/javascript"></script>
|
||||||
|
<script type="text/javascript" th:inline="javascript">
|
||||||
|
let package_number_select =[[#{package_number_select}]];
|
||||||
|
let last_7_days =[[#{last_7_days}]];
|
||||||
|
let last_30_days =[[#{last_30_days}]];
|
||||||
|
let last_3_months =[[#{last_3_months}]];
|
||||||
|
|
||||||
|
let load_tip =[[#{load_tip}]];
|
||||||
|
let package_details =[[#{package_details}]];
|
||||||
|
let export_data_tip =[[#{export_data_tip}]];
|
||||||
|
let export_error_tip =[[#{export_error_tip}]];
|
||||||
|
|
||||||
|
let order_number =[[#{order_number}]];//序号
|
||||||
|
let roll_Angle_ratio_excel =[[#{roll_Angle_ratio_excel}]];//Roll/Angle比例.xlsx
|
||||||
|
|
||||||
|
|
||||||
|
let photo_details=[[#{photo_details}]];//照片详情
|
||||||
|
|
||||||
|
let basic_information=[[#{basic_information}]];//基本信息
|
||||||
|
let packet_number=[[#{packet_number}]];//包号
|
||||||
|
let specifications=[[#{specifications}]];//规格
|
||||||
|
let test_date=[[#{test_date}]];//检测日期
|
||||||
|
let number_of_defects=[[#{number_of_defects}]];//缺陷数量
|
||||||
|
let photo_number=[[#{photo_number}]];//照片编号
|
||||||
|
let detection_area_size=[[#{detection_area_size}]];//检测面积大小
|
||||||
|
|
||||||
|
let mean_curl_ratio=[[#{mean_curl_ratio}]];//卷曲比例均值
|
||||||
|
let maximum_curl_ratio=[[#{maximum_curl_ratio}]];//卷曲比例最大值
|
||||||
|
let minimum_curl_ratio=[[#{minimum_curl_ratio}]];//卷曲比例最小值
|
||||||
|
let number_of_bristles=[[#{number_of_bristles}]];//拉毛个数
|
||||||
|
let burr_length=[[#{burr_length}]];//毛边长度
|
||||||
|
let betection_length=[[#{betection_length}]];//检测长度
|
||||||
|
let roughing_length_ratio=[[#{roughing_length_ratio}]];//拉毛长度比
|
||||||
|
let polluted_colo=[[#{polluted_colo}]];//污染颜色
|
||||||
|
let polluted_area=[[#{polluted_area}]];//污染面积
|
||||||
|
let Proportion_of_pollution_area=[[#{Proportion_of_pollution_area}]];//污染面积占比
|
||||||
|
let number_of_cuts=[[#{number_of_cuts}]];//切断个数
|
||||||
|
let cut_off_area=[[#{cut_off_area}]];//切断面积
|
||||||
|
let proportion_of_cutting_area=[[#{proportion_of_cutting_area}]];//切断面积占比
|
||||||
|
let roll_angle_rate=[[#{roll_angle_rate}]];//rollAngle占比
|
||||||
|
|
||||||
|
|
||||||
|
let operate =[[#{operate}]]; // 操作
|
||||||
|
let delete_button =[[#{delete_button}]]; // 删除
|
||||||
|
let delete_tip =[[#{delete_tip}]]; // 删除提示
|
||||||
|
let message =[[#{message}]];
|
||||||
|
let confirm =[[#{confirm}]];
|
||||||
|
let cancel =[[#{cancel}]];
|
||||||
|
let delete_tip2 =[[#{delete_tip2}]];
|
||||||
|
|
||||||
|
let secondary_analysis_picture =[[#{secondary_analysis_picture}]];
|
||||||
|
let master_drawing =[[#{master_drawing}]];
|
||||||
|
let view =[[#{view}]];
|
||||||
|
let deleteTime =[[#{deleteTime}]];
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,268 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>导出进度</title>
|
||||||
|
<script src="../../js/publicJs.js"></script>
|
||||||
|
<script src="../../js/libs/jquery-3.6.0.min.js" charset="UTF-8" type="text/javascript"></script>
|
||||||
|
<script src="../../js/aes.js"></script>
|
||||||
|
<style>
|
||||||
|
/* 基础样式模拟 */
|
||||||
|
body { margin: 0; font-family: "Helvetica Neue", Helvetica, "PingFang SC", sans-serif; }
|
||||||
|
.export-progress-container {
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: linear-gradient(180deg, #f1f6ff 20%, #e5efff 100%);
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
.progress-card {
|
||||||
|
width: 500px;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.card-header {
|
||||||
|
padding: 20px 24px;
|
||||||
|
border-bottom: 1px solid #ebeef5;
|
||||||
|
}
|
||||||
|
.card-header .title { font-size: 18px; font-weight: 600; color: #333; margin: 0; }
|
||||||
|
.card-body { padding: 40px 24px; text-align: center; }
|
||||||
|
|
||||||
|
/* 进度条样式 */
|
||||||
|
.progress-bar-container {
|
||||||
|
width: 100%;
|
||||||
|
background-color: #ebeef5;
|
||||||
|
border-radius: 100px;
|
||||||
|
margin: 20px 0;
|
||||||
|
height: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.progress-bar-fill {
|
||||||
|
height: 100%;
|
||||||
|
background-color: #1f72ea;
|
||||||
|
width: 0%;
|
||||||
|
transition: width 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 按钮样式 */
|
||||||
|
.btn {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 10px 20px;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
border: 1px solid #dcdfe6;
|
||||||
|
background: #fff;
|
||||||
|
color: #606266;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
.btn-primary {
|
||||||
|
background: #1f72ea;
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
box-shadow: 0px 4px 8px 0px rgba(51, 135, 255, 0.5);
|
||||||
|
}
|
||||||
|
.btn-primary:hover { background: #4a8bff; }
|
||||||
|
|
||||||
|
/* 状态控制 */
|
||||||
|
.status-section { display: none; }
|
||||||
|
.active { display: block; }
|
||||||
|
|
||||||
|
/* 动画模拟 Element Icon */
|
||||||
|
.loading-icon { display: inline-block; width: 20px; height: 20px; border: 2px solid #1f72ea; border-top-color: transparent; border-radius: 50%; animation: spin 1s linear infinite; vertical-align: middle; margin-right: 10px;}
|
||||||
|
@keyframes spin { to { transform: rotate(360deg); } }
|
||||||
|
|
||||||
|
.error-text { color: #f56c6c; margin-bottom: 20px; }
|
||||||
|
.success-text { color: #333; margin-bottom: 20px; font-weight: 500; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div class="export-progress-container">
|
||||||
|
<div class="progress-card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3 class="title">导出进度</h3>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
|
||||||
|
<div id="section-progress" class="status-section active">
|
||||||
|
<div class="progress-info">
|
||||||
|
<span class="loading-icon"></span>
|
||||||
|
<span id="progress-text">0%</span>
|
||||||
|
</div>
|
||||||
|
<div class="progress-bar-container">
|
||||||
|
<div id="progress-fill" class="progress-bar-fill"></div>
|
||||||
|
</div>
|
||||||
|
<div class="progress-detail">正在生成压缩包,请稍候...</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="section-completed" class="status-section">
|
||||||
|
<div style="font-size: 50px; color: #67c23a; margin-bottom: 15px;">✔</div>
|
||||||
|
<div class="success-text">压缩包生成完成!</div>
|
||||||
|
<button id="btn-download" class="btn btn-primary">下载文件</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="section-error" class="status-section">
|
||||||
|
<div style="font-size: 50px; color: #f56c6c; margin-bottom: 15px;">✘</div>
|
||||||
|
<div id="error-message" class="error-text">导出失败,请稍后重试</div>
|
||||||
|
<button id="btn-close" class="btn">关闭窗口</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
$(document).ready(function() {
|
||||||
|
// --- 数据定义 ---
|
||||||
|
let state = {
|
||||||
|
taskId: null,
|
||||||
|
downloadUrl: null,
|
||||||
|
fileName: null,
|
||||||
|
params: {},
|
||||||
|
pollTimer: null
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- 初始化 ---
|
||||||
|
init();
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
const paramsStr = urlParams.get('params');
|
||||||
|
if (paramsStr) {
|
||||||
|
try {
|
||||||
|
state.params = JSON.parse(decodeURIComponent(paramsStr));
|
||||||
|
startExport();
|
||||||
|
} catch (e) {
|
||||||
|
showError('参数解析失败');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
showError('缺少导出参数');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 方法逻辑 ---
|
||||||
|
|
||||||
|
function startExport() {
|
||||||
|
const params = {
|
||||||
|
encryptedData: encryptCBC(JSON.stringify(state.params))
|
||||||
|
}
|
||||||
|
$.ajax({
|
||||||
|
url: dataUrl + '/defectDelRecord/export',
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
"token": tokens
|
||||||
|
},
|
||||||
|
data: params,
|
||||||
|
contentType: 'application/json',
|
||||||
|
success: function(res) {
|
||||||
|
if (res.code === 200) {
|
||||||
|
// 兼容多种返回格式
|
||||||
|
state.taskId = res.data?.taskId || res.data || res.taskId;
|
||||||
|
if (state.taskId) {
|
||||||
|
startPolling();
|
||||||
|
} else if (res.data?.downloadUrl) {
|
||||||
|
showCompleted(res.data.downloadUrl, res.data.fileName);
|
||||||
|
} else {
|
||||||
|
showError('未获取到任务ID');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
showError(res.msg || '启动导出任务失败');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function() {
|
||||||
|
showError('网络请求失败');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function startPolling() {
|
||||||
|
checkProgress(); // 立即执行一次
|
||||||
|
state.pollTimer = setInterval(checkProgress, 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkProgress() {
|
||||||
|
|
||||||
|
if (!state.taskId) return;
|
||||||
|
const params = {
|
||||||
|
encryptedData: encryptCBC(JSON.stringify({
|
||||||
|
taskId: state.taskId
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
$.ajax({
|
||||||
|
url: dataUrl + '/defectDelRecord/progress', // 替换为真实URL
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
"token": tokens
|
||||||
|
},
|
||||||
|
// data: { taskId: state.taskId },
|
||||||
|
data: params,
|
||||||
|
success: function(res) {
|
||||||
|
if (res.code === 200) {
|
||||||
|
const data = res.data;
|
||||||
|
updateProgressBar(data.progress || 0);
|
||||||
|
|
||||||
|
if (data.progress >= 100 || data.status === 'completed' || data.status === 'success') {
|
||||||
|
stopPolling();
|
||||||
|
showCompleted(data.downloadUrl, data.fileName);
|
||||||
|
} else if (data.status === 'failed' || data.status === 'error') {
|
||||||
|
stopPolling();
|
||||||
|
showError(data.message || '导出任务失败');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateProgressBar(percent) {
|
||||||
|
$('#progress-text').text(percent + '%');
|
||||||
|
$('#progress-fill').css('width', percent + '%');
|
||||||
|
}
|
||||||
|
|
||||||
|
function showCompleted(url, name) {
|
||||||
|
state.downloadUrl = url;
|
||||||
|
state.fileName = name || `export_${Date.now()}.zip`;
|
||||||
|
switchSection('completed');
|
||||||
|
}
|
||||||
|
|
||||||
|
function showError(msg) {
|
||||||
|
$('#error-message').text(msg);
|
||||||
|
switchSection('error');
|
||||||
|
}
|
||||||
|
|
||||||
|
function switchSection(type) {
|
||||||
|
$('.status-section').removeClass('active');
|
||||||
|
$(`#section-${type}`).addClass('active');
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopPolling() {
|
||||||
|
if (state.pollTimer) {
|
||||||
|
clearInterval(state.pollTimer);
|
||||||
|
state.pollTimer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 事件绑定 ---
|
||||||
|
|
||||||
|
$('#btn-download').on('click', function() {
|
||||||
|
if (state.downloadUrl && state.downloadUrl.startsWith('http')) {
|
||||||
|
window.open(state.downloadUrl, '_blank');
|
||||||
|
} else {
|
||||||
|
// 这里可以实现类似 Blob 下载逻辑,jQuery 需配合原生 fetch 或 xhr
|
||||||
|
const params = encryptCBC(JSON.stringify({
|
||||||
|
taskId: state.taskId
|
||||||
|
}))
|
||||||
|
window.location.href = dataUrl + `/defectDelRecord/download?encryptedData=${params}&token=${tokens}`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#btn-close').on('click', function() {
|
||||||
|
window.close();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Loading…
Reference in New Issue