<script lang="ts" setup>
|
import type { ExamPaperView } from './types';
|
|
import { onMounted, ref } from 'vue';
|
import { useRoute, useRouter } from 'vue-router';
|
|
import { useMessage } from '@jnpf/hooks';
|
import {
|
Descriptions as ADescriptions,
|
DescriptionsItem as ADescriptionsItem,
|
} from 'ant-design-vue';
|
|
import { getExamPaperView } from '#/api/x/tms/examScore';
|
import { labelOfType } from '#/views/x/tms/question/constants';
|
|
defineOptions({ name: 'TmsExamScorePaper' });
|
|
const route = useRoute();
|
const router = useRouter();
|
const { createMessage } = useMessage();
|
|
const loading = ref(false);
|
const paper = ref<ExamPaperView | null>(null);
|
|
onMounted(() => {
|
loadPaper();
|
});
|
|
async function loadPaper() {
|
const id = String(route.params.id || '');
|
if (!id) {
|
router.replace('/tms/examScore');
|
return;
|
}
|
loading.value = true;
|
try {
|
paper.value = await getExamPaperView(id);
|
} catch (e: any) {
|
createMessage.error(e?.message || '加载试卷失败');
|
router.back();
|
} finally {
|
loading.value = false;
|
}
|
}
|
|
function stripHtml(html?: string) {
|
if (!html) return '';
|
return html.replace(/<[^>]+>/g, '').replace(/ /g, ' ').trim();
|
}
|
|
function goBack() {
|
if (paper.value?.summaryId) {
|
router.push(`/tms/examScore/detail/${paper.value.summaryId}`);
|
} else {
|
router.back();
|
}
|
}
|
</script>
|
|
<template>
|
<div class="jnpf-content-wrapper tms-paper-page">
|
<div class="jnpf-content-wrapper-center tms-paper-center">
|
<div class="jnpf-content-wrapper-content tms-paper-wrap">
|
<div class="paper-top">
|
<div class="paper-header">
|
<div>
|
<div class="text-base font-medium">考生试卷详情</div>
|
<div v-if="paper" class="mt-1 text-gray-400 text-sm">
|
{{ paper.userName }}({{ paper.userId }}) · {{ paper.paperName }}
|
</div>
|
</div>
|
<a-button @click="goBack">返回</a-button>
|
</div>
|
|
<ADescriptions v-if="paper" bordered :column="3" size="small" class="mb-3">
|
<ADescriptionsItem label="培训主题" :span="2">{{ paper.taskSubject }}</ADescriptionsItem>
|
<ADescriptionsItem label="培训编号">{{ paper.taskNo }}</ADescriptionsItem>
|
<ADescriptionsItem label="考试开始">{{ paper.examStart || '-' }}</ADescriptionsItem>
|
<ADescriptionsItem label="考试结束">{{ paper.examEnd || '-' }}</ADescriptionsItem>
|
<ADescriptionsItem label="来源IP">{{ paper.sourceIp || '-' }}</ADescriptionsItem>
|
<ADescriptionsItem label="成绩">
|
<b class="text-primary">{{ paper.score }}</b> / {{ paper.totalScore }}
|
</ADescriptionsItem>
|
<ADescriptionsItem label="及格分">{{ paper.passScore }}</ADescriptionsItem>
|
<ADescriptionsItem label="是否合格">
|
<span :class="paper.passFlag === '1' ? 'text-green-600' : 'text-red-500'">
|
{{ paper.passFlag === '1' ? '是' : '否' }}
|
</span>
|
</ADescriptionsItem>
|
</ADescriptions>
|
</div>
|
|
<div class="paper-body">
|
<a-spin :spinning="loading">
|
<div
|
v-for="(it, idx) in paper?.items || []"
|
:key="it.id"
|
class="paper-item"
|
:class="{ subjective: it.isSubjective === '1' }"
|
>
|
<div class="mb-2 text-sm text-gray-500">
|
第 {{ idx + 1 }} 题 · {{ labelOfType(it.questionType) }}
|
(满分 {{ it.score }} · 得分 {{ it.gotScore }})
|
<span
|
v-if="it.isSubjective !== '1'"
|
class="ml-2"
|
:class="it.isCorrect ? 'text-green-600' : 'text-red-500'"
|
>
|
{{ it.isCorrect ? '正确' : '错误' }}
|
</span>
|
<span v-else class="ml-2 text-orange-500">主观题</span>
|
</div>
|
<div class="stem mb-3">{{ stripHtml(it.stem) }}</div>
|
<div v-if="it.options?.length" class="mb-2 text-sm text-gray-600">
|
<div v-for="opt in it.options" :key="opt.optionLabel">
|
{{ opt.optionLabel }}. {{ opt.optionContent }}
|
</div>
|
</div>
|
<div class="text-sm answer-row">
|
<div>考生答案:{{ it.userAnswer || '(未作答)' }}</div>
|
<div v-if="it.correctAnswer">参考/正确答案:{{ it.correctAnswer }}</div>
|
</div>
|
</div>
|
</a-spin>
|
</div>
|
</div>
|
</div>
|
</div>
|
</template>
|
|
<style scoped>
|
.tms-paper-page {
|
min-height: 0;
|
}
|
|
.tms-paper-center {
|
min-height: 0 !important;
|
}
|
|
.tms-paper-wrap {
|
display: flex !important;
|
flex-direction: column;
|
flex: 1 1 0 !important;
|
min-height: 0 !important;
|
overflow: hidden !important;
|
background: #fff;
|
padding: 0;
|
}
|
|
.paper-top {
|
flex-shrink: 0;
|
padding: 16px 20px 0;
|
}
|
|
.paper-header {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
margin-bottom: 12px;
|
padding-bottom: 12px;
|
border-bottom: 1px solid #f0f0f0;
|
}
|
|
.paper-body {
|
flex: 1 1 0;
|
min-height: 0;
|
overflow-y: auto !important;
|
padding: 0 20px 32px;
|
}
|
|
.paper-item {
|
border: 1px solid #f0f0f0;
|
border-radius: 8px;
|
padding: 14px 16px;
|
margin-bottom: 12px;
|
}
|
|
.paper-item.subjective {
|
border-color: #ffd591;
|
background: #fffbe6;
|
}
|
|
.stem {
|
font-size: 15px;
|
line-height: 1.7;
|
}
|
|
.answer-row {
|
display: flex;
|
flex-direction: column;
|
gap: 4px;
|
}
|
</style>
|