You've already forked FrameTour-BE
feat(video-review): 优化视频评价导出功能,支持机位名称动态表头
- 引入DeviceRepository用于批量查询机位名称 - 在导出逻辑中收集并排序机位ID,确保表头顺序一致 - 动态生成Excel表头,使用实际机位名称替代原始JSON字段 - 调整单元格样式以支持自动换行,提升可读性 - 更新mapper配置,关联template表获取模板名称 - 优化列宽自适应逻辑,为机位列设置最小宽度保障显示效果 - 日志记录中增加导出机位数量统计信息
This commit is contained in:
@@ -10,6 +10,7 @@ import com.ycwl.basic.exception.BizException;
|
||||
import com.ycwl.basic.mapper.VideoMapper;
|
||||
import com.ycwl.basic.mapper.VideoReviewMapper;
|
||||
import com.ycwl.basic.model.pc.video.entity.VideoEntity;
|
||||
import com.ycwl.basic.repository.DeviceRepository;
|
||||
import com.ycwl.basic.model.pc.videoreview.dto.VideoReviewAddReqDTO;
|
||||
import com.ycwl.basic.model.pc.videoreview.dto.VideoReviewListReqDTO;
|
||||
import com.ycwl.basic.model.pc.videoreview.dto.VideoReviewRespDTO;
|
||||
@@ -45,6 +46,9 @@ public class VideoReviewServiceImpl implements VideoReviewService {
|
||||
@Autowired
|
||||
private VideoMapper videoMapper;
|
||||
|
||||
@Autowired
|
||||
private DeviceRepository deviceRepository;
|
||||
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
@Override
|
||||
@@ -153,11 +157,37 @@ public class VideoReviewServiceImpl implements VideoReviewService {
|
||||
reqDTO.setPageSize(Integer.MAX_VALUE);
|
||||
List<VideoReviewRespDTO> list = videoReviewMapper.selectReviewList(reqDTO);
|
||||
|
||||
// 2. 创建Excel工作簿
|
||||
// 2. 收集所有机位ID并批量查询机位名称
|
||||
Set<Long> allDeviceIds = new LinkedHashSet<>();
|
||||
for (VideoReviewRespDTO review : list) {
|
||||
Map<String, Map<String, Integer>> cameraRating = review.getCameraPositionRating();
|
||||
if (cameraRating != null && !cameraRating.isEmpty()) {
|
||||
// 收集机位ID (按顺序)
|
||||
for (String deviceIdStr : cameraRating.keySet()) {
|
||||
try {
|
||||
allDeviceIds.add(Long.valueOf(deviceIdStr));
|
||||
} catch (NumberFormatException e) {
|
||||
log.warn("无效的机位ID: {}", deviceIdStr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 批量查询机位名称
|
||||
Map<Long, String> deviceNames = new HashMap<>();
|
||||
if (!allDeviceIds.isEmpty()) {
|
||||
deviceNames = deviceRepository.batchGetDeviceNames(new ArrayList<>(allDeviceIds));
|
||||
}
|
||||
|
||||
// 对机位ID按ID排序,保证表头顺序一致
|
||||
List<Long> sortedDeviceIds = new ArrayList<>(allDeviceIds);
|
||||
sortedDeviceIds.sort(Long::compareTo);
|
||||
|
||||
// 3. 创建Excel工作簿
|
||||
Workbook workbook = new XSSFWorkbook();
|
||||
Sheet sheet = workbook.createSheet("视频评价数据");
|
||||
|
||||
// 3. 创建标题行样式
|
||||
// 4. 创建标题行样式
|
||||
CellStyle headerStyle = workbook.createCellStyle();
|
||||
Font headerFont = workbook.createFont();
|
||||
headerFont.setBold(true);
|
||||
@@ -165,53 +195,110 @@ public class VideoReviewServiceImpl implements VideoReviewService {
|
||||
headerStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
|
||||
headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
|
||||
|
||||
// 4. 创建标题行
|
||||
// 5. 创建单元格自动换行样式
|
||||
CellStyle wrapStyle = workbook.createCellStyle();
|
||||
wrapStyle.setWrapText(true);
|
||||
wrapStyle.setVerticalAlignment(VerticalAlignment.TOP);
|
||||
|
||||
// 6. 生成动态表头 - 使用机位名称作为表头
|
||||
Row headerRow = sheet.createRow(0);
|
||||
String[] headers = {"评价ID", "视频ID", "景区ID", "景区名称", "评价人ID", "评价人名称",
|
||||
"评分", "文字评价", "机位评价", "创建时间", "更新时间"};
|
||||
for (int i = 0; i < headers.length; i++) {
|
||||
List<String> headerList = new ArrayList<>();
|
||||
headerList.add("评价ID");
|
||||
headerList.add("视频ID");
|
||||
headerList.add("视频模板名称");
|
||||
headerList.add("景区名称");
|
||||
headerList.add("评价人名称");
|
||||
headerList.add("评分");
|
||||
headerList.add("文字评价");
|
||||
|
||||
// 添加机位列 - 表头直接使用机位名称
|
||||
Map<Long, String> finalDeviceNames = deviceNames;
|
||||
for (Long deviceId : sortedDeviceIds) {
|
||||
String deviceName = finalDeviceNames.getOrDefault(deviceId, "未知设备(ID:" + deviceId + ")");
|
||||
headerList.add(deviceName);
|
||||
}
|
||||
|
||||
headerList.add("创建时间");
|
||||
headerList.add("更新时间");
|
||||
|
||||
// 设置表头
|
||||
for (int i = 0; i < headerList.size(); i++) {
|
||||
Cell cell = headerRow.createCell(i);
|
||||
cell.setCellValue(headers[i]);
|
||||
cell.setCellValue(headerList.get(i));
|
||||
cell.setCellStyle(headerStyle);
|
||||
}
|
||||
|
||||
// 5. 填充数据
|
||||
// 7. 填充数据
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
int rowNum = 1;
|
||||
|
||||
for (VideoReviewRespDTO review : list) {
|
||||
Row row = sheet.createRow(rowNum++);
|
||||
row.createCell(0).setCellValue(review.getId());
|
||||
row.createCell(1).setCellValue(review.getVideoId());
|
||||
row.createCell(2).setCellValue(review.getScenicId());
|
||||
row.createCell(3).setCellValue(review.getScenicName());
|
||||
row.createCell(4).setCellValue(review.getCreator());
|
||||
row.createCell(5).setCellValue(review.getCreatorName());
|
||||
row.createCell(6).setCellValue(review.getRating());
|
||||
row.createCell(7).setCellValue(review.getContent());
|
||||
int colIndex = 0;
|
||||
|
||||
// 机位评价JSON转字符串
|
||||
try {
|
||||
String cameraRatingJson = review.getCameraPositionRating() != null ?
|
||||
objectMapper.writeValueAsString(review.getCameraPositionRating()) : "";
|
||||
row.createCell(8).setCellValue(cameraRatingJson);
|
||||
} catch (Exception e) {
|
||||
row.createCell(8).setCellValue("");
|
||||
// 基础信息列
|
||||
row.createCell(colIndex++).setCellValue(review.getId());
|
||||
row.createCell(colIndex++).setCellValue(review.getVideoId());
|
||||
row.createCell(colIndex++).setCellValue(review.getTemplateName() != null ? review.getTemplateName() : "");
|
||||
row.createCell(colIndex++).setCellValue(review.getScenicName());
|
||||
row.createCell(colIndex++).setCellValue(review.getCreatorName());
|
||||
row.createCell(colIndex++).setCellValue(review.getRating());
|
||||
row.createCell(colIndex++).setCellValue(review.getContent());
|
||||
|
||||
// 机位评价列 - 按表头顺序填充
|
||||
Map<String, Map<String, Integer>> cameraRating = review.getCameraPositionRating();
|
||||
for (Long deviceId : sortedDeviceIds) {
|
||||
String deviceIdStr = String.valueOf(deviceId);
|
||||
Map<String, Integer> dimensions = null;
|
||||
|
||||
if (cameraRating != null && cameraRating.containsKey(deviceIdStr)) {
|
||||
dimensions = cameraRating.get(deviceIdStr);
|
||||
}
|
||||
|
||||
// 构建单元格内容: 只显示评分维度(不再重复机位名称)
|
||||
StringBuilder cellContent = new StringBuilder();
|
||||
|
||||
if (dimensions != null && !dimensions.isEmpty()) {
|
||||
// 按维度名排序,保证一致性
|
||||
List<Map.Entry<String, Integer>> sortedDimensions = dimensions.entrySet().stream()
|
||||
.sorted(Map.Entry.comparingByKey())
|
||||
.collect(Collectors.toList());
|
||||
|
||||
boolean first = true;
|
||||
for (Map.Entry<String, Integer> dimEntry : sortedDimensions) {
|
||||
if (!first) {
|
||||
cellContent.append("\n");
|
||||
}
|
||||
cellContent.append(dimEntry.getKey()).append(":").append(dimEntry.getValue());
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
|
||||
Cell cell = row.createCell(colIndex++);
|
||||
cell.setCellValue(cellContent.toString());
|
||||
cell.setCellStyle(wrapStyle);
|
||||
}
|
||||
|
||||
row.createCell(9).setCellValue(review.getCreateTime() != null ? sdf.format(review.getCreateTime()) : "");
|
||||
row.createCell(10).setCellValue(review.getUpdateTime() != null ? sdf.format(review.getUpdateTime()) : "");
|
||||
// 时间列
|
||||
row.createCell(colIndex++).setCellValue(review.getCreateTime() != null ? sdf.format(review.getCreateTime()) : "");
|
||||
row.createCell(colIndex).setCellValue(review.getUpdateTime() != null ? sdf.format(review.getUpdateTime()) : "");
|
||||
}
|
||||
|
||||
// 6. 自动调整列宽
|
||||
for (int i = 0; i < headers.length; i++) {
|
||||
// 8. 自动调整列宽
|
||||
for (int i = 0; i < headerList.size(); i++) {
|
||||
sheet.autoSizeColumn(i);
|
||||
// 对于机位列,设置最小宽度以便换行内容显示完整
|
||||
if (i >= 7 && i < 7 + sortedDeviceIds.size()) {
|
||||
int currentWidth = sheet.getColumnWidth(i);
|
||||
sheet.setColumnWidth(i, Math.max(currentWidth, 5000)); // 最小25个字符宽度
|
||||
}
|
||||
}
|
||||
|
||||
// 7. 写入输出流
|
||||
// 9. 写入输出流
|
||||
workbook.write(outputStream);
|
||||
workbook.close();
|
||||
|
||||
log.info("导出视频评价数据成功,共{}条", list.size());
|
||||
log.info("导出视频评价数据成功,共{}条,机位数:{}", list.size(), sortedDeviceIds.size());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
<id property="id" column="id"/>
|
||||
<result property="videoId" column="video_id"/>
|
||||
<result property="videoUrl" column="video_url"/>
|
||||
<result property="templateId" column="template_id"/>
|
||||
<result property="templateName" column="template_name"/>
|
||||
<result property="scenicId" column="scenic_id"/>
|
||||
<result property="scenicName" column="scenic_name"/>
|
||||
<result property="creator" column="creator"/>
|
||||
@@ -33,10 +35,13 @@
|
||||
vr.create_time,
|
||||
vr.update_time,
|
||||
v.video_url,
|
||||
v.template_id,
|
||||
t.name AS template_name,
|
||||
s.name AS scenic_name,
|
||||
u.name AS creator_name
|
||||
FROM video_review vr
|
||||
LEFT JOIN video v ON vr.video_id = v.id
|
||||
LEFT JOIN template t ON v.template_id = t.id
|
||||
LEFT JOIN scenic s ON vr.scenic_id = s.id
|
||||
LEFT JOIN admin_user u ON vr.creator = u.id
|
||||
<where>
|
||||
|
||||
Reference in New Issue
Block a user