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/onlineExam/Detail.vue | 192 +++++++++++++++++++++++++++++++++++++++--------
1 files changed, 158 insertions(+), 34 deletions(-)
diff --git a/apps/jnpf-web-apps-main/src/views/x/tms/onlineExam/Detail.vue b/apps/jnpf-web-apps-main/src/views/x/tms/onlineExam/Detail.vue
index 737a0f5..97fb692 100644
--- a/apps/jnpf-web-apps-main/src/views/x/tms/onlineExam/Detail.vue
+++ b/apps/jnpf-web-apps-main/src/views/x/tms/onlineExam/Detail.vue
@@ -1,7 +1,7 @@
<script lang="ts" setup>
-import type { OnlineExamDetail } from './types';
+import type { OnlineExamAttempt, OnlineExamDetail } from './types';
-import { onMounted, ref } from 'vue';
+import { computed, onMounted, ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { useMessage } from '@jnpf/hooks';
@@ -9,11 +9,22 @@
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, labelOfMyPaperStatus } from './constants';
+import {
+ colorOfMyPaperStatus,
+ colorOfPassFlag,
+ labelOfGradeStatus,
+ labelOfMyPaperStatus,
+ labelOfPassFlag,
+ loadOnlineExamDics,
+} from './constants';
+
+import '#/views/x/tms/shared/page.css';
defineOptions({ name: 'TmsOnlineExamDetail' });
@@ -25,8 +36,22 @@
const starting = ref(false);
const detail = ref<OnlineExamDetail | null>(null);
-onMounted(() => {
- loadDetail();
+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() {
@@ -50,16 +75,40 @@
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) {
- createMessage.error(e?.message || '寮�濮嬭�冭瘯澶辫触');
+ const msg = e?.message || '寮�濮嬭�冭瘯澶辫触';
+ if (String(msg).includes('鑷姩浜ゅ嵎')) {
+ createMessage.warning(msg);
+ await loadDetail();
+ } else {
+ createMessage.error(msg);
+ }
} finally {
starting.value = false;
}
@@ -67,49 +116,110 @@
</script>
<template>
- <div class="jnpf-content-wrapper">
+ <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-exam-detail-header">
- <div class="text-base font-medium">鑰冭瘯璇︽儏</div>
- <a-space>
- <a-button @click="goBack">杩斿洖鍒楄〃</a-button>
+ <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' ? '缁х画鑰冭瘯' : '寮�濮嬭�冭瘯' }}
+ {{ detail?.status === 'doing' ? TMS_BTN.continueExam : TMS_BTN.startExam }}
</a-button>
- </a-space>
+ <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>
- <ADescriptionsItem label="寮�濮嬫椂闂�">{{ detail.startTime || '-' }}</ADescriptionsItem>
- <ADescriptionsItem label="浜ゅ嵎鏃堕棿">{{ detail.submitTime || '-' }}</ADescriptionsItem>
- <ADescriptionsItem label="寰楀垎">
- {{ detail.gotScore != null ? detail.gotScore : '-' }}
- </ADescriptionsItem>
- <ADescriptionsItem label="鏄惁鍚堟牸">
- <template v-if="detail.passFlag === '1'">鍚堟牸</template>
- <template v-else-if="detail.passFlag === '0'">涓嶅悎鏍�</template>
- <template v-else>-</template>
- </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>
@@ -117,19 +227,33 @@
</template>
<style scoped>
-.tms-exam-detail-page {
- background: #fff;
- padding: 20px 24px;
+.tms-exam-detail-root {
height: 100%;
- overflow: auto;
+ min-height: 0;
}
-.tms-exam-detail-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 20px;
- padding-bottom: 12px;
- border-bottom: 1px solid #f0f0f0;
+.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>
--
Gitblit v1.8.0