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/selfTest/Detail.vue | 214 +++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 214 insertions(+), 0 deletions(-)
diff --git a/apps/jnpf-web-apps-main/src/views/x/tms/selfTest/Detail.vue b/apps/jnpf-web-apps-main/src/views/x/tms/selfTest/Detail.vue
new file mode 100644
index 0000000..38873bb
--- /dev/null
+++ b/apps/jnpf-web-apps-main/src/views/x/tms/selfTest/Detail.vue
@@ -0,0 +1,214 @@
+<script lang="ts" setup>
+import type { SelfTestPaper, SelfTestQuestionItem } from './types';
+
+import { computed, onMounted, ref } from 'vue';
+import { useRoute, useRouter } from 'vue-router';
+
+import { useMessage } from '@jnpf/hooks';
+import { useTabbarStore } from '@vben/stores';
+
+import { getSelfTestInfo } from '#/api/x/tms/selfTest';
+import { labelOfType } from '#/views/x/tms/question/constants';
+
+defineOptions({ name: 'TmsSelfTestDetail' });
+
+const route = useRoute();
+const router = useRouter();
+const { createMessage } = useMessage();
+const tabbarStore = useTabbarStore();
+
+const loading = ref(false);
+const detail = ref<SelfTestPaper | null>(null);
+/** all | wrong */
+const filterMode = ref<'all' | 'wrong'>('all');
+
+const wrongCount = computed(
+ () => detail.value?.questions?.filter((q) => q.isRight !== '1').length || 0,
+);
+
+const displayQuestions = computed(() => {
+ const list = detail.value?.questions || [];
+ if (filterMode.value === 'wrong') {
+ return list.filter((q) => q.isRight !== '1');
+ }
+ return list;
+});
+
+function stripHtml(html?: string) {
+ if (!html) return '';
+ return html.replace(/<[^>]+>/g, '').replace(/ /g, ' ').trim();
+}
+
+function formatAnswer(ans?: string) {
+ if (!ans) return '鏈綔绛�';
+ return ans.split(',').filter(Boolean).join('銆�');
+}
+
+function optionClass(q: SelfTestQuestionItem, label: string) {
+ const correctSet = new Set((q.correctAnswer || '').split(',').filter(Boolean));
+ const userSet = new Set((q.userAnswer || '').split(',').filter(Boolean));
+ const isCorrectOpt = correctSet.has(label);
+ const isUserOpt = userSet.has(label);
+ if (isCorrectOpt) return 'opt-correct';
+ if (isUserOpt && !isCorrectOpt) return 'opt-wrong';
+ return '';
+}
+
+async function loadDetail() {
+ const id = String(route.params.id || '');
+ if (!id) {
+ router.replace('/tms/selfTest/records');
+ return;
+ }
+ loading.value = true;
+ try {
+ detail.value = await getSelfTestInfo(id);
+ } catch (e: any) {
+ createMessage.error(e?.message || '鍔犺浇妫�娴嬭鎯呭け璐�');
+ router.replace('/tms/selfTest/records');
+ } finally {
+ loading.value = false;
+ }
+}
+
+onMounted(() => {
+ tabbarStore.renderRouteView = true;
+ loadDetail();
+});
+
+function goBack() {
+ tabbarStore.renderRouteView = true;
+ router.push('/tms/selfTest/records');
+}
+</script>
+
+<template>
+ <div class="jnpf-content-wrapper">
+ <div class="jnpf-content-wrapper-center">
+ <div class="jnpf-content-wrapper-content tms-self-test-detail" v-loading="loading">
+ <div class="page-header" v-if="detail">
+ <div>
+ <div class="text-base font-medium">妫�娴嬭鎯� 路 {{ detail.bankName }}</div>
+ <div class="mt-1 text-gray-400 text-sm">
+ 寰楀垎锛歿{ detail.correctCount ?? 0 }} / {{ detail.totalCount ?? detail.questions?.length ?? 0 }}
+ <span v-if="detail.scoreRate != null" class="ml-2">姝g‘鐜� {{ detail.scoreRate }}%</span>
+ <span v-if="detail.submitTime" class="ml-3">浜ゅ嵎鏃堕棿锛歿{ detail.submitTime }}</span>
+ </div>
+ </div>
+ <a-space>
+ <a-radio-group v-model:value="filterMode" button-style="solid" size="small">
+ <a-radio-button value="all">鍏ㄩ儴棰樼洰</a-radio-button>
+ <a-radio-button value="wrong">浠呴敊棰橈紙{{ wrongCount }}锛�</a-radio-button>
+ </a-radio-group>
+ <a-button @click="goBack">杩斿洖璁板綍</a-button>
+ </a-space>
+ </div>
+
+ <a-empty v-if="!loading && detail && !displayQuestions.length" description="鏆傛棤棰樼洰" />
+
+ <div v-for="(q, idx) in displayQuestions" :key="`${q.id}_${idx}`" class="question-card">
+ <div class="q-title">
+ <span class="q-index">{{ q.sortNo || idx + 1 }}.</span>
+ <span class="q-type">{{ labelOfType(q.questionType) }}</span>
+ <a-tag :color="q.isRight === '1' ? 'success' : 'error'" class="!ml-2">
+ {{ q.isRight === '1' ? '姝g‘' : '閿欒' }}
+ </a-tag>
+ </div>
+ <div class="q-stem">{{ stripHtml(q.stem) }}</div>
+ <div class="q-options">
+ <div
+ v-for="opt in q.options"
+ :key="opt.optionLabel"
+ class="q-option"
+ :class="optionClass(q, opt.optionLabel)"
+ >
+ {{ opt.optionLabel }}. {{ opt.optionContent }}
+ </div>
+ </div>
+ <div class="q-answer">
+ <span>浣犵殑绛旀锛歿{ formatAnswer(q.userAnswer) }}</span>
+ <span class="ml-4">姝g‘绛旀锛歿{ formatAnswer(q.correctAnswer) }}</span>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+</template>
+
+<style scoped>
+.tms-self-test-detail {
+ background: #fff;
+ padding: 20px 24px;
+ height: 100%;
+ overflow: auto;
+}
+
+.page-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-start;
+ gap: 16px;
+ margin-bottom: 20px;
+ padding-bottom: 12px;
+ border-bottom: 1px solid #f0f0f0;
+}
+
+.question-card {
+ padding: 16px 0;
+ border-bottom: 1px solid #f5f5f5;
+}
+
+.q-title {
+ display: flex;
+ align-items: center;
+ margin-bottom: 8px;
+}
+
+.q-index {
+ font-weight: 600;
+ margin-right: 6px;
+}
+
+.q-type {
+ color: rgba(0, 0, 0, 0.45);
+ font-size: 13px;
+}
+
+.q-stem {
+ font-size: 15px;
+ line-height: 1.7;
+ margin-bottom: 12px;
+ color: rgba(0, 0, 0, 0.88);
+}
+
+.q-options {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ margin-bottom: 10px;
+}
+
+.q-option {
+ padding: 6px 10px;
+ border-radius: 4px;
+ background: #fafafa;
+ color: rgba(0, 0, 0, 0.75);
+}
+
+.q-option.opt-correct {
+ background: #f6ffed;
+ color: #389e0d;
+ border: 1px solid #b7eb8f;
+}
+
+.q-option.opt-wrong {
+ background: #fff2f0;
+ color: #cf1322;
+ border: 1px solid #ffccc7;
+}
+
+.q-answer {
+ font-size: 13px;
+ color: rgba(0, 0, 0, 0.55);
+}
+</style>
--
Gitblit v1.8.0