|
@@ -1,38 +1,41 @@
|
|
|
<template>
|
|
|
<el-dialog
|
|
|
custom-class="modal-task-detail"
|
|
|
- title="题目详情"
|
|
|
:visible.sync="modalVisible"
|
|
|
fullscreen
|
|
|
append-to-body
|
|
|
:close-on-click-modal="false"
|
|
|
@close="handleClose"
|
|
|
>
|
|
|
+ <div slot="title">
|
|
|
+ <h2 class="el-dialog__title">题目详情</h2>
|
|
|
+ <span>学生:{{ task.studentName }}({{ task.studentCode }})</span>
|
|
|
+ <span class="ml-4">题目:{{ task.questionNumber }}</span>
|
|
|
+ <span v-if="task.userName" class="ml-4"
|
|
|
+ >评卷员:{{ task.userName }}({{ task.loginName }})</span
|
|
|
+ >
|
|
|
+ <button class="el-dialog__headerbtn" @click="cancel"></button>
|
|
|
+ </div>
|
|
|
+
|
|
|
<div v-loading="loading" class="task-detail-content">
|
|
|
- <div class="left-panel">
|
|
|
+ <div class="left-panel" :style="leftPanelStyle">
|
|
|
<!-- 答题卡裁切图片展示区域 -->
|
|
|
- <div
|
|
|
- v-for="(crop, index) in croppedImages"
|
|
|
- :key="index"
|
|
|
- class="image-container"
|
|
|
- >
|
|
|
- <img
|
|
|
- :ref="'imageRef' + index"
|
|
|
- :src="crop.src"
|
|
|
- @load="onImageLoad(index)"
|
|
|
- />
|
|
|
- <div
|
|
|
- v-for="track in crop.tracks"
|
|
|
- :key="track.id"
|
|
|
- class="track-point"
|
|
|
- :style="getTrackStyle(track, index)"
|
|
|
- :class="track.type === 'header' ? 'track-header' : 'track-normal'"
|
|
|
- >
|
|
|
- {{ track.score }}
|
|
|
+ <div v-for="(crop, index) in croppedImages" :key="index">
|
|
|
+ <div class="image-container">
|
|
|
+ <img :ref="'imageRef' + index" :src="crop.src" />
|
|
|
+ <div
|
|
|
+ v-for="track in crop.tracks"
|
|
|
+ :key="track.id"
|
|
|
+ class="track-point"
|
|
|
+ :style="getTrackStyle(track, index)"
|
|
|
+ :class="track.type === 'header' ? 'track-header' : 'track-normal'"
|
|
|
+ >
|
|
|
+ {{ track.score }}
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
- <div class="right-panel">
|
|
|
+ <div v-if="aiMarked" class="right-panel">
|
|
|
<!-- OCR 结果展示区域 -->
|
|
|
<div class="ocr-section section-box">
|
|
|
<h3>OCR 识别结果</h3>
|
|
@@ -96,9 +99,11 @@ import { markDetailTaskDetail } from "../../api";
|
|
|
export default {
|
|
|
name: "ModalTaskDetail",
|
|
|
props: {
|
|
|
- taskId: {
|
|
|
- type: String,
|
|
|
- required: true,
|
|
|
+ task: {
|
|
|
+ type: Object,
|
|
|
+ default() {
|
|
|
+ return {};
|
|
|
+ },
|
|
|
},
|
|
|
},
|
|
|
data() {
|
|
@@ -116,7 +121,6 @@ export default {
|
|
|
},
|
|
|
croppedImages: [], // 处理后的裁切图信息和轨迹信息
|
|
|
originalImageDimensions: {}, // 存储原始图片尺寸 { url: { width, height } }
|
|
|
- imageLoadPromises: [], // 存储图片加载的 Promise
|
|
|
};
|
|
|
},
|
|
|
computed: {
|
|
@@ -126,14 +130,26 @@ export default {
|
|
|
? this.taskDetail.markAiQuestionParam.pointList || []
|
|
|
: this.taskDetail.markAiQuestionParam.levelList || [];
|
|
|
},
|
|
|
+ aiMarked() {
|
|
|
+ return (
|
|
|
+ this.taskDetail.markAiQuestionParam ||
|
|
|
+ this.taskDetail.ocr?.result.length > 0 ||
|
|
|
+ this.taskDetail.ocr?.errorMsg
|
|
|
+ );
|
|
|
+ },
|
|
|
+ leftPanelStyle() {
|
|
|
+ return this.aiMarked ? {} : { width: "100%" };
|
|
|
+ },
|
|
|
},
|
|
|
methods: {
|
|
|
open() {
|
|
|
this.modalVisible = true;
|
|
|
this.fetchTaskDetail();
|
|
|
},
|
|
|
- handleClose() {
|
|
|
+ cancel() {
|
|
|
this.modalVisible = false;
|
|
|
+ },
|
|
|
+ handleClose() {
|
|
|
// 重置数据
|
|
|
this.taskDetail = {
|
|
|
sheetUrls: [],
|
|
@@ -145,25 +161,23 @@ export default {
|
|
|
};
|
|
|
this.croppedImages = [];
|
|
|
this.originalImageDimensions = {};
|
|
|
- this.imageLoadPromises = [];
|
|
|
},
|
|
|
async fetchTaskDetail() {
|
|
|
- if (!this.taskId) return;
|
|
|
+ if (!this.task.id) return;
|
|
|
this.loading = true;
|
|
|
- try {
|
|
|
- const res = await markDetailTaskDetail(this.taskId);
|
|
|
- this.taskDetail = res || this.taskDetail; // 使用默认值防止 res 为 null
|
|
|
- await this.processImageData();
|
|
|
- } catch (error) {
|
|
|
- console.error("获取题目详情失败:", error);
|
|
|
+
|
|
|
+ const res = await markDetailTaskDetail(this.task.id).catch(() => {});
|
|
|
+ if (!res) {
|
|
|
this.$message.error("获取题目详情失败");
|
|
|
- } finally {
|
|
|
- this.loading = false;
|
|
|
+ return;
|
|
|
}
|
|
|
+ this.taskDetail = res || this.taskDetail; // 使用默认值防止 res 为 null
|
|
|
+ await this.processImageData();
|
|
|
+ this.loading = false;
|
|
|
},
|
|
|
// 预加载所有原图并获取尺寸
|
|
|
preloadOriginalImages() {
|
|
|
- this.imageLoadPromises = this.taskDetail.sheetUrls.map((url) => {
|
|
|
+ const imageLoadPromises = this.taskDetail.sheetUrls.map((url) => {
|
|
|
return new Promise((resolve, reject) => {
|
|
|
if (this.originalImageDimensions[url]) {
|
|
|
resolve(this.originalImageDimensions[url]);
|
|
@@ -181,7 +195,7 @@ export default {
|
|
|
img.src = url;
|
|
|
});
|
|
|
});
|
|
|
- return Promise.all(this.imageLoadPromises);
|
|
|
+ return Promise.all(imageLoadPromises);
|
|
|
},
|
|
|
|
|
|
async processImageData() {
|
|
@@ -241,7 +255,6 @@ export default {
|
|
|
cropParams: cropParams, // 裁切区域在原图的像素坐标和尺寸
|
|
|
picData: pic, // 保留原始 picList 数据
|
|
|
tracks: [], // 该裁切图上的轨迹点
|
|
|
- displayDimensions: { width: 0, height: 0 }, // 图片在页面上显示的实际尺寸
|
|
|
});
|
|
|
}
|
|
|
|
|
@@ -259,7 +272,12 @@ export default {
|
|
|
|
|
|
for (const track of allTracks) {
|
|
|
const targetCropIndex = crops.findIndex(
|
|
|
- (crop) => crop.picData.i === track.offsetIndex
|
|
|
+ (crop) =>
|
|
|
+ crop.picData.i === track.offsetIndex &&
|
|
|
+ this.checkPointInArea(
|
|
|
+ { x: track.offsetX, y: track.offsetY },
|
|
|
+ crop.cropParams
|
|
|
+ )
|
|
|
);
|
|
|
if (targetCropIndex !== -1) {
|
|
|
crops[targetCropIndex].tracks.push(track);
|
|
@@ -268,25 +286,17 @@ export default {
|
|
|
|
|
|
this.croppedImages = crops;
|
|
|
},
|
|
|
-
|
|
|
- onImageLoad(index) {
|
|
|
- // 图片加载完成后获取其在页面上的实际显示尺寸
|
|
|
- this.$nextTick(() => {
|
|
|
- const imgRef = this.$refs["imageRef" + index];
|
|
|
- if (imgRef && imgRef[0]) {
|
|
|
- this.croppedImages[index].displayDimensions = {
|
|
|
- width: imgRef[0].offsetWidth,
|
|
|
- height: imgRef[0].offsetHeight,
|
|
|
- };
|
|
|
- // 强制更新,以便重新计算轨迹位置
|
|
|
- this.$forceUpdate();
|
|
|
- }
|
|
|
- });
|
|
|
+ checkPointInArea(point, area) {
|
|
|
+ return (
|
|
|
+ point.x >= area.x &&
|
|
|
+ point.x <= area.x + area.w &&
|
|
|
+ point.y >= area.y &&
|
|
|
+ point.y <= area.y + area.h
|
|
|
+ );
|
|
|
},
|
|
|
-
|
|
|
getTrackStyle(track, cropIndex) {
|
|
|
const crop = this.croppedImages[cropIndex];
|
|
|
- if (!crop || !crop.displayDimensions.width || !crop.cropParams.w) {
|
|
|
+ if (!crop || !crop.cropParams.w) {
|
|
|
return { display: "none" }; // 图片未加载或尺寸无效
|
|
|
}
|
|
|
|
|
@@ -307,8 +317,8 @@ export default {
|
|
|
left: `${displayX}%`,
|
|
|
top: `${displayY}%`,
|
|
|
transform: "translate(-50%, -50%)", // 使坐标点位于数字中心
|
|
|
- fontSize: "14px", // 固定字体大小
|
|
|
- fontWeight: "bold",
|
|
|
+ fontSize: "24px", // 固定字体大小
|
|
|
+ fontWeight: "bold", // 字体加粗
|
|
|
zIndex: 10,
|
|
|
};
|
|
|
},
|