<script lang="ts" setup>
|
import type { QuestionEntity, QuestionOption } 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,
|
} from 'ant-design-vue';
|
|
import { getQuestionInfo } from '#/api/x/tms/question';
|
|
import {
|
labelOfDifficulty,
|
labelOfSource,
|
labelOfStatus,
|
labelOfType,
|
loadQuestionDics,
|
} from './constants';
|
|
defineOptions({ name: 'TmsQuestionDetail' });
|
|
const route = useRoute();
|
const router = useRouter();
|
const { createMessage } = useMessage();
|
|
const loading = ref(false);
|
const detail = ref<QuestionEntity | null>(null);
|
|
const answerText = computed(() => formatAnswer(detail.value));
|
|
onMounted(async () => {
|
await loadQuestionDics();
|
await loadData();
|
});
|
|
async function loadData() {
|
const id = String(route.params.id || '');
|
if (!id) {
|
router.replace('/tms/question');
|
return;
|
}
|
loading.value = true;
|
try {
|
detail.value = await getQuestionInfo(id);
|
} catch (e: any) {
|
createMessage.error(e?.message || '加载试题失败');
|
router.replace('/tms/question');
|
} finally {
|
loading.value = false;
|
}
|
}
|
|
function goBack() {
|
router.push('/tms/question');
|
}
|
|
function goEdit() {
|
if (!detail.value?.id) return;
|
router.push(`/tms/question/edit/${detail.value.id}`);
|
}
|
|
function formatAnswer(row?: QuestionEntity | null) {
|
if (!row) return '-';
|
const opts = row.options || [];
|
switch (row.questionType) {
|
case 'single':
|
case 'multi': {
|
const correct = opts.filter((o) => o.isCorrect === '1');
|
if (!correct.length) return '-';
|
return correct.map((o) => `${o.optionLabel}. ${o.optionContent || ''}`).join(';');
|
}
|
case 'judge': {
|
const correct = opts.find((o) => o.isCorrect === '1');
|
return correct?.optionContent || correct?.optionLabel || '-';
|
}
|
case 'blank':
|
return opts.map((o, i) => `填空${i + 1}:${o.optionContent || '-'}`).join(';') || '-';
|
case 'essay':
|
return opts[0]?.optionContent || '-';
|
default:
|
return '-';
|
}
|
}
|
|
function optionClass(opt: QuestionOption) {
|
return opt.isCorrect === '1' ? 'opt-correct' : '';
|
}
|
</script>
|
|
<template>
|
<div class="jnpf-content-wrapper tms-question-detail-page">
|
<div class="jnpf-content-wrapper-center tms-question-detail-center">
|
<div class="jnpf-content-wrapper-content tms-question-detail-wrap">
|
<a-spin :spinning="loading" class="detail-spin">
|
<div v-if="detail" class="detail-inner">
|
<div class="detail-header">
|
<div>
|
<div class="text-base font-medium">试题详情</div>
|
<div class="mt-1 text-gray-400 text-sm">编号 {{ detail.questionNo || '-' }}</div>
|
</div>
|
<a-space>
|
<a-button @click="goBack">返回</a-button>
|
<a-button type="primary" @click="goEdit">编辑</a-button>
|
</a-space>
|
</div>
|
|
<ADescriptions bordered :column="2" size="middle" class="detail-desc">
|
<ADescriptionsItem label="试题编号">{{ detail.questionNo || '-' }}</ADescriptionsItem>
|
<ADescriptionsItem label="试题类型">{{ labelOfType(detail.questionType) }}</ADescriptionsItem>
|
<ADescriptionsItem label="所属题库">{{ detail.bankName || '-' }}</ADescriptionsItem>
|
<ADescriptionsItem label="试题难度">{{ labelOfDifficulty(detail.difficulty) }}</ADescriptionsItem>
|
<ADescriptionsItem label="试题来源">{{ labelOfSource(detail.sourceType) }}</ADescriptionsItem>
|
<ADescriptionsItem label="试题状态">{{ labelOfStatus(detail.bizStatus) }}</ADescriptionsItem>
|
<ADescriptionsItem label="管理员">{{ detail.adminUserName || '-' }}</ADescriptionsItem>
|
<ADescriptionsItem label="创建时间">{{ detail.creatorTime || '-' }}</ADescriptionsItem>
|
<ADescriptionsItem label="题干内容" :span="2">
|
<div class="stem-html" v-html="detail.stem || '-'" />
|
</ADescriptionsItem>
|
<ADescriptionsItem
|
v-if="detail.questionType === 'single' || detail.questionType === 'multi' || detail.questionType === 'judge'"
|
label="选项"
|
:span="2"
|
>
|
<div class="option-list">
|
<div
|
v-for="(opt, idx) in detail.options || []"
|
:key="opt.id || idx"
|
class="option-item"
|
:class="optionClass(opt)"
|
>
|
<span class="opt-label">{{ opt.optionLabel }}.</span>
|
<span>{{ opt.optionContent }}</span>
|
<span v-if="opt.isCorrect === '1'" class="opt-tag">正确答案</span>
|
</div>
|
<div v-if="!(detail.options && detail.options.length)" class="text-gray-400">-</div>
|
</div>
|
</ADescriptionsItem>
|
<ADescriptionsItem label="参考答案" :span="2">
|
<div class="pre-line">{{ answerText }}</div>
|
</ADescriptionsItem>
|
<ADescriptionsItem label="试题解析" :span="2">
|
<div class="pre-line">{{ detail.analysis || '-' }}</div>
|
</ADescriptionsItem>
|
</ADescriptions>
|
</div>
|
</a-spin>
|
</div>
|
</div>
|
</div>
|
</template>
|
|
<style scoped>
|
.tms-question-detail-page {
|
height: 100%;
|
min-height: 0;
|
}
|
|
.tms-question-detail-center {
|
height: 100% !important;
|
min-height: 0 !important;
|
display: flex;
|
flex-direction: column;
|
}
|
|
.tms-question-detail-wrap {
|
display: flex !important;
|
flex-direction: column;
|
flex: 1 1 0 !important;
|
min-height: 0 !important;
|
height: 100%;
|
overflow: auto !important;
|
background: #fff;
|
padding: 0;
|
}
|
|
.detail-spin {
|
display: block;
|
min-height: 100%;
|
width: 100%;
|
}
|
|
.detail-spin :deep(.ant-spin-container) {
|
min-height: 100%;
|
width: 100%;
|
}
|
|
.detail-inner {
|
padding: 16px 20px 24px;
|
box-sizing: border-box;
|
}
|
|
.detail-header {
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
margin-bottom: 16px;
|
padding-bottom: 12px;
|
border-bottom: 1px solid #f0f0f0;
|
}
|
|
.detail-desc {
|
max-width: 1100px;
|
margin-bottom: 16px;
|
}
|
|
.stem-html :deep(p) {
|
margin-bottom: 0.5em;
|
}
|
|
.stem-html :deep(img) {
|
max-width: 100%;
|
}
|
|
.pre-line {
|
white-space: pre-wrap;
|
word-break: break-word;
|
}
|
|
.option-list {
|
display: flex;
|
flex-direction: column;
|
gap: 8px;
|
}
|
|
.option-item {
|
display: flex;
|
align-items: flex-start;
|
gap: 8px;
|
line-height: 1.6;
|
}
|
|
.opt-label {
|
flex-shrink: 0;
|
font-weight: 500;
|
}
|
|
.opt-correct {
|
color: #52c41a;
|
}
|
|
.opt-tag {
|
flex-shrink: 0;
|
margin-left: 4px;
|
padding: 0 6px;
|
font-size: 12px;
|
color: #52c41a;
|
background: #f6ffed;
|
border: 1px solid #b7eb8f;
|
border-radius: 2px;
|
}
|
</style>
|