liuyu
22 小时以前 93c133349a5ccd7a328371fa113dce69d5611f21
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>