From 8534025c45b4736975730678b9ccf23570a75f95 Mon Sep 17 00:00:00 2001
From: liuyu <yu.liu@cqgbkj.com>
Date: 星期二, 22 九月 2026 09:25:48 +0800
Subject: [PATCH] feat(tms): 新增试卷、培训任务、个人任务页面
---
apps/jnpf-web-apps-main/src/views/x/tms/question/Detail.vue | 254 ++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 254 insertions(+), 0 deletions(-)
diff --git a/apps/jnpf-web-apps-main/src/views/x/tms/question/Detail.vue b/apps/jnpf-web-apps-main/src/views/x/tms/question/Detail.vue
new file mode 100644
index 0000000..ef7c121
--- /dev/null
+++ b/apps/jnpf-web-apps-main/src/views/x/tms/question/Detail.vue
@@ -0,0 +1,254 @@
+<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">姝g‘绛旀</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="璇曢瑙f瀽" :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>
--
Gitblit v1.8.0