<script lang="ts" setup>
|
import type { OnlineExamAttempt, OnlineExamDetail } from './types';
|
|
import { computed, onMounted, ref } from 'vue';
|
import { useRoute, useRouter } from 'vue-router';
|
|
import { useMessage } from '@jnpf/hooks';
|
import {
|
Descriptions as ADescriptions,
|
DescriptionsItem as ADescriptionsItem,
|
Spin,
|
Table as ATable,
|
} from 'ant-design-vue';
|
|
import { getMyPaperDetail, startOnlineExam } from '#/api/x/tms/onlineExam';
|
import { TMS_BTN } from '#/views/x/tms/shared/ui';
|
|
import {
|
colorOfMyPaperStatus,
|
colorOfPassFlag,
|
labelOfGradeStatus,
|
labelOfMyPaperStatus,
|
labelOfPassFlag,
|
loadOnlineExamDics,
|
} from './constants';
|
|
import '#/views/x/tms/shared/page.css';
|
|
defineOptions({ name: 'TmsOnlineExamDetail' });
|
|
const route = useRoute();
|
const router = useRouter();
|
const { createMessage } = useMessage();
|
|
const loading = ref(false);
|
const starting = ref(false);
|
const detail = ref<OnlineExamDetail | null>(null);
|
|
const attemptColumns = [
|
{ title: '考试类型', dataIndex: 'attemptLabel', key: 'attemptLabel', width: 130 },
|
{ title: '状态', dataIndex: 'status', key: 'status', width: 100 },
|
{ title: '开始时间', dataIndex: 'startTime', key: 'startTime', width: 170 },
|
{ title: '交卷时间', dataIndex: 'submitTime', key: 'submitTime', width: 170 },
|
{ title: '得分', dataIndex: 'gotScore', key: 'gotScore', width: 90, align: 'center' as const },
|
{ title: '是否合格', dataIndex: 'passFlag', key: 'passFlag', width: 100, align: 'center' as const },
|
{ title: '阅卷', dataIndex: 'gradeStatus', key: 'gradeStatus', width: 100 },
|
{ title: '操作', key: 'action', width: 100, align: 'center' as const },
|
];
|
|
const attempts = computed(() => detail.value?.attempts || []);
|
|
onMounted(async () => {
|
await loadOnlineExamDics();
|
await loadDetail();
|
});
|
|
async function loadDetail() {
|
const id = String(route.params.id || '');
|
if (!id) {
|
router.replace('/tms/onlineExam');
|
return;
|
}
|
loading.value = true;
|
try {
|
detail.value = await getMyPaperDetail(id);
|
} catch (e: any) {
|
createMessage.error(e?.message || '加载详情失败');
|
router.replace('/tms/onlineExam');
|
} finally {
|
loading.value = false;
|
}
|
}
|
|
function goBack() {
|
router.push('/tms/onlineExam');
|
}
|
|
function goReview(row?: OnlineExamAttempt) {
|
if (!detail.value) return;
|
const examId = row?.examId || detail.value.examId;
|
if (!examId) {
|
createMessage.warning('暂无已交卷答卷可回顾');
|
return;
|
}
|
router.push({
|
path: `/tms/onlineExam/review/${detail.value.id}`,
|
query: { examId },
|
});
|
}
|
|
async function handleStart() {
|
if (!detail.value || starting.value) return;
|
starting.value = true;
|
try {
|
const paper = await startOnlineExam(detail.value.id);
|
if (paper?.autoSubmitted) {
|
createMessage.warning('考试时间已到,已自动交卷');
|
await loadDetail();
|
return;
|
}
|
sessionStorage.setItem('tms_online_exam_paper', JSON.stringify(paper));
|
sessionStorage.setItem('tms_online_exam_myPaperId', detail.value.id);
|
router.push('/tms/onlineExam/exam');
|
} catch (e: any) {
|
const msg = e?.message || '开始考试失败';
|
if (String(msg).includes('自动交卷')) {
|
createMessage.warning(msg);
|
await loadDetail();
|
} else {
|
createMessage.error(msg);
|
}
|
} finally {
|
starting.value = false;
|
}
|
}
|
</script>
|
|
<template>
|
<div class="jnpf-content-wrapper tms-exam-detail-root">
|
<div class="jnpf-content-wrapper-center">
|
<div class="jnpf-content-wrapper-content tms-exam-detail-page">
|
<Spin :spinning="loading">
|
<div class="tms-page-header">
|
<div>
|
<div class="tms-page-header__title">考试详情</div>
|
<div v-if="detail?.status === 'submitted'" class="tms-page-header__sub">
|
最近得分 {{ detail.gotScore ?? '-' }} / {{ detail.totalScore }}
|
<span v-if="detail.submitTime" class="ml-3">交卷时间:{{ detail.submitTime }}</span>
|
</div>
|
</div>
|
<div class="tms-page-header__actions">
|
<a-button @click="goBack">{{ TMS_BTN.back }}</a-button>
|
<a-button
|
v-if="detail && detail.status !== 'submitted'"
|
type="primary"
|
:loading="starting"
|
@click="handleStart"
|
>
|
{{ detail?.status === 'doing' ? TMS_BTN.continueExam : TMS_BTN.startExam }}
|
</a-button>
|
<a-button
|
v-else-if="detail?.canRetake"
|
type="primary"
|
:loading="starting"
|
@click="handleStart"
|
>
|
{{ TMS_BTN.retake }}
|
</a-button>
|
</div>
|
</div>
|
|
<ADescriptions v-if="detail" bordered :column="2" size="middle">
|
<ADescriptionsItem label="试卷名称" :span="2">{{ detail.paperName }}</ADescriptionsItem>
|
<ADescriptionsItem label="最近考试类型">{{ detail.attemptLabel || '首考' }}</ADescriptionsItem>
|
<ADescriptionsItem label="状态">
|
<span :style="{ color: colorOfMyPaperStatus(detail.status) }">
|
{{ labelOfMyPaperStatus(detail.status) }}
|
</span>
|
</ADescriptionsItem>
|
<ADescriptionsItem label="卷面总分">{{ detail.totalScore }}</ADescriptionsItem>
|
<ADescriptionsItem label="补考次数">
|
{{
|
detail.retakeLimit == null
|
? '-'
|
: `剩余 ${detail.remainingRetakes ?? 0} / 上限 ${detail.retakeLimit}`
|
}}
|
</ADescriptionsItem>
|
<ADescriptionsItem label="考试时间" :span="2">{{ detail.examTimeText || '-' }}</ADescriptionsItem>
|
<ADescriptionsItem label="考试时长">
|
{{ detail.durationMin != null ? `${detail.durationMin} 分钟` : '-' }}
|
</ADescriptionsItem>
|
<ADescriptionsItem label="合格分数">{{ detail.passScore ?? '-' }}</ADescriptionsItem>
|
</ADescriptions>
|
|
<div class="history-block">
|
<div class="history-title">历史答卷</div>
|
<ATable
|
size="middle"
|
:columns="attemptColumns"
|
:data-source="attempts"
|
:pagination="false"
|
row-key="examId"
|
bordered
|
>
|
<template #bodyCell="{ column, record }">
|
<template v-if="column.key === 'status'">
|
<span :style="{ color: colorOfMyPaperStatus(record.status) }">
|
{{ labelOfMyPaperStatus(record.status) }}
|
</span>
|
</template>
|
<template v-else-if="column.key === 'gotScore'">
|
{{ record.gotScore != null ? record.gotScore : '-' }}
|
</template>
|
<template v-else-if="column.key === 'passFlag'">
|
<span :style="{ color: colorOfPassFlag(record.passFlag) }">
|
{{ labelOfPassFlag(record.passFlag) }}
|
</span>
|
</template>
|
<template v-else-if="column.key === 'gradeStatus'">
|
{{ labelOfGradeStatus(record.gradeStatus) }}
|
</template>
|
<template v-else-if="column.key === 'action'">
|
<a
|
v-if="record.status === 'submitted'"
|
@click.prevent="goReview(record as OnlineExamAttempt)"
|
>
|
回顾
|
</a>
|
<span v-else class="text-gray-400">-</span>
|
</template>
|
</template>
|
</ATable>
|
<div v-if="!attempts.length" class="history-empty">暂无答卷记录</div>
|
</div>
|
|
<a-alert
|
v-if="detail && detail.status !== 'submitted' && !attempts.some((a) => a.status === 'submitted')"
|
class="mt-4"
|
type="info"
|
show-icon
|
:message="detail.status === 'doing' ? '考试进行中,交卷后可在历史答卷中回顾。' : '尚未开考,暂无答题内容。'"
|
/>
|
</Spin>
|
</div>
|
</div>
|
</div>
|
</template>
|
|
<style scoped>
|
.tms-exam-detail-root {
|
height: 100%;
|
min-height: 0;
|
}
|
|
.tms-exam-detail-page {
|
height: 100%;
|
min-height: 0;
|
overflow-x: hidden;
|
overflow-y: auto !important;
|
background: #fff;
|
padding: 20px 24px;
|
box-sizing: border-box;
|
}
|
|
.history-block {
|
margin-top: 20px;
|
}
|
|
.history-title {
|
font-weight: 600;
|
margin-bottom: 12px;
|
}
|
|
.history-empty {
|
margin-top: 12px;
|
color: #8c8c8c;
|
text-align: center;
|
}
|
</style>
|