You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
109 lines
4.0 KiB
Vue
109 lines
4.0 KiB
Vue
<template>
|
|
<main ref="previewRoot" class="student-task-readonly-preview" @click.capture="guardPreviewInteraction" @keydown.capture="guardPreviewInteraction">
|
|
<component v-if="previewComponent" :is="previewComponent" v-bind="previewProps" />
|
|
<el-empty v-else :description="emptyDescription" :image-size="112" />
|
|
</main>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { getTrainingReviewPreviewContext } from "@/utils/trainingReviewPreview";
|
|
import { studentTaskPreviewComponentByKey, studentTaskPreviewPropsByKey } from "./studentTaskPreviewMap";
|
|
|
|
const route = useRoute();
|
|
const previewComponent = shallowRef(null);
|
|
const previewProps = ref({});
|
|
const emptyDescription = ref("正在加载学生任务页面");
|
|
const previewRoot = ref();
|
|
const PREVIEW_HEIGHT_MESSAGE = "teacher-task-review-preview-height";
|
|
let resizeObserver;
|
|
let mutationObserver;
|
|
let animationFrame;
|
|
|
|
async function loadPreview() {
|
|
previewComponent.value = null;
|
|
const taskKey = String(route.query.taskKey || "");
|
|
const preview = getTrainingReviewPreviewContext();
|
|
const loader = studentTaskPreviewComponentByKey[taskKey];
|
|
if (!preview || preview.taskKey !== taskKey) {
|
|
emptyDescription.value = "学生作答预览已失效,请返回任务评阅重新选择学生";
|
|
return;
|
|
}
|
|
if (!loader) {
|
|
emptyDescription.value = "该实训任务暂不支持页面预览";
|
|
return;
|
|
}
|
|
try {
|
|
previewComponent.value = (await loader()).default;
|
|
previewProps.value = studentTaskPreviewPropsByKey[taskKey]?.(taskKey) || {};
|
|
await nextTick();
|
|
schedulePreviewHeight();
|
|
} catch (_) {
|
|
emptyDescription.value = "学生任务页面加载失败,请刷新后重试";
|
|
}
|
|
}
|
|
|
|
watch(() => [route.query.taskKey, route.query.reviewPreviewKey], loadPreview, { immediate: true });
|
|
|
|
onMounted(() => {
|
|
resizeObserver = new ResizeObserver(schedulePreviewHeight);
|
|
if (previewRoot.value) resizeObserver.observe(previewRoot.value);
|
|
mutationObserver = new MutationObserver(schedulePreviewHeight);
|
|
if (previewRoot.value) mutationObserver.observe(previewRoot.value, { childList: true, subtree: true });
|
|
schedulePreviewHeight();
|
|
});
|
|
|
|
onBeforeUnmount(() => {
|
|
resizeObserver?.disconnect();
|
|
mutationObserver?.disconnect();
|
|
if (animationFrame) cancelAnimationFrame(animationFrame);
|
|
});
|
|
|
|
function schedulePreviewHeight() {
|
|
if (animationFrame) cancelAnimationFrame(animationFrame);
|
|
animationFrame = requestAnimationFrame(reportPreviewHeight);
|
|
}
|
|
|
|
function reportPreviewHeight() {
|
|
animationFrame = 0;
|
|
const height = Math.ceil(Math.max(
|
|
previewRoot.value?.scrollHeight || 0,
|
|
document.body?.scrollHeight || 0,
|
|
document.documentElement?.scrollHeight || 0,
|
|
window.innerHeight || 0,
|
|
));
|
|
window.parent?.postMessage({
|
|
type: PREVIEW_HEIGHT_MESSAGE,
|
|
previewKey: String(route.query.reviewPreviewKey || ""),
|
|
height,
|
|
}, window.location.origin);
|
|
}
|
|
|
|
function guardPreviewInteraction(event) {
|
|
const target = event.target instanceof Element ? event.target : null;
|
|
if (!target) return;
|
|
if (target.closest("input, textarea, select, [contenteditable='true'], .el-input, .el-textarea, .el-select, .el-checkbox, .el-radio, .el-switch")) {
|
|
event.preventDefault();
|
|
event.stopImmediatePropagation();
|
|
return;
|
|
}
|
|
const action = target.closest("button, a, [role='button'], [role='tab'], .el-step");
|
|
if (action && !isStepNavigation(action)) {
|
|
event.preventDefault();
|
|
event.stopImmediatePropagation();
|
|
}
|
|
}
|
|
|
|
function isStepNavigation(element) {
|
|
if (element.matches("[role='tab'], .el-step")) return true;
|
|
const label = String(element.getAttribute("aria-label") || "");
|
|
const text = `${label} ${String(element.textContent || "")}`.replace(/\s+/g, " ").trim();
|
|
return /(上一步|下一步|第\s*\d+\s*步|切换.*步骤|步骤.*切换)/.test(text);
|
|
}
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.student-task-readonly-preview :deep(.training-ai-sidebar),
|
|
.student-task-readonly-preview :deep(.training-ai-evaluation) { display: none !important; }
|
|
.student-task-readonly-preview :deep(.student-training-shell) { grid-template-columns: minmax(0, 1fr) !important; }
|
|
</style>
|