<script lang="ts" setup>
|
import type { QuestionBankOption } from '#/views/x/tms/question/types';
|
import type { SelfTestPaper, SelfTestRecordItem } from './types';
|
|
import { onMounted, reactive, ref } from 'vue';
|
import { useRouter } from 'vue-router';
|
|
import { useMessage } from '@jnpf/hooks';
|
import { useTabbarStore } from '@vben/stores';
|
import dayjs from 'dayjs';
|
|
import { getSelfTestBanks, getSelfTestInfo, getSelfTestList } from '#/api/x/tms/selfTest';
|
import { useBaseStore } from '#/store';
|
import {
|
TMS_DIC,
|
TMS_DIC_FIELD_NAMES,
|
labelOfDic,
|
loadTmsDic,
|
type TmsDicOpt,
|
} from '#/views/x/tms/shared/dic';
|
|
defineOptions({ name: 'TmsSelfTestRecords' });
|
|
const baseStore = useBaseStore();
|
const statusOpts = ref<TmsDicOpt[]>([]);
|
|
const router = useRouter();
|
const { createMessage } = useMessage();
|
const tabbarStore = useTabbarStore();
|
|
const banks = ref<QuestionBankOption[]>([]);
|
const records = ref<SelfTestRecordItem[]>([]);
|
const loading = ref(false);
|
const continueLoadingId = ref('');
|
const pagination = ref({ currentPage: 1, pageSize: 10, total: 0 });
|
|
const queryForm = reactive<{
|
bankId?: string;
|
testStatus?: string;
|
/** jnpf-date-range 值为时间戳数组 */
|
timeRange?: number[];
|
}>({
|
bankId: undefined,
|
testStatus: undefined,
|
timeRange: undefined,
|
});
|
|
async function loadBanks() {
|
try {
|
banks.value = (await getSelfTestBanks()) || [];
|
} catch {
|
banks.value = [];
|
}
|
}
|
|
async function loadRecords() {
|
loading.value = true;
|
try {
|
const range = queryForm.timeRange;
|
const begin = range?.[0] != null ? dayjs(range[0]).format('YYYY-MM-DD') : undefined;
|
const end = range?.[1] != null ? dayjs(range[1]).format('YYYY-MM-DD') : undefined;
|
const res = await getSelfTestList({
|
currentPage: pagination.value.currentPage,
|
pageSize: pagination.value.pageSize,
|
bankId: queryForm.bankId || undefined,
|
testStatus: queryForm.testStatus || undefined,
|
startTimeBegin: begin ? `${begin} 00:00:00` : undefined,
|
startTimeEnd: end ? `${end} 23:59:59` : undefined,
|
});
|
records.value = res?.list || [];
|
const p = res?.pagination || {};
|
pagination.value.total = Number(p.total || p.totalCount || records.value.length);
|
} catch (e: any) {
|
records.value = [];
|
createMessage.error(e?.message || '加载检测记录失败');
|
} finally {
|
loading.value = false;
|
}
|
}
|
|
function handleSearch() {
|
pagination.value.currentPage = 1;
|
loadRecords();
|
}
|
|
function handleReset() {
|
queryForm.bankId = undefined;
|
queryForm.testStatus = undefined;
|
queryForm.timeRange = undefined;
|
pagination.value.currentPage = 1;
|
loadRecords();
|
}
|
|
onMounted(async () => {
|
tabbarStore.renderRouteView = true;
|
const examStatus = await loadTmsDic(baseStore, TMS_DIC.examStatus);
|
statusOpts.value = examStatus.filter(
|
(x) => x.enCode === 'submitted' || x.enCode === 'doing',
|
);
|
await loadBanks();
|
loadRecords();
|
});
|
|
function statusLabel(status?: string) {
|
return labelOfDic(statusOpts.value, status);
|
}
|
|
function goBack() {
|
tabbarStore.renderRouteView = true;
|
router.push('/tms/selfTest');
|
}
|
|
function handleView(record: SelfTestRecordItem) {
|
if (record.testStatus !== 'submitted') {
|
createMessage.warning('该检测尚未交卷,暂无答题结果可查看');
|
return;
|
}
|
router.push(`/tms/selfTest/detail/${record.id}`);
|
}
|
|
/** 继续未交卷的检测 */
|
async function handleContinue(record: SelfTestRecordItem) {
|
if (record.testStatus !== 'doing') return;
|
continueLoadingId.value = record.id;
|
try {
|
const detail = await getSelfTestInfo(record.id);
|
if (detail.testStatus === 'submitted') {
|
createMessage.warning('该检测已交卷,请直接查看详情');
|
loadRecords();
|
return;
|
}
|
if (!detail.questions?.length) {
|
createMessage.warning('未找到可继续的试题');
|
return;
|
}
|
const paper: SelfTestPaper = {
|
paperId: detail.paperId,
|
bankId: detail.bankId,
|
bankName: detail.bankName,
|
testStatus: detail.testStatus,
|
questions: detail.questions,
|
};
|
// 把已作答内容一并带入答题页
|
const savedAnswers: Record<string, string | string[]> = {};
|
detail.questions.forEach((q) => {
|
if (!q.userAnswer) return;
|
if (q.questionType === 'multi') {
|
savedAnswers[q.id] = q.userAnswer.split(',').filter(Boolean);
|
} else {
|
savedAnswers[q.id] = q.userAnswer;
|
}
|
});
|
sessionStorage.setItem('tms_self_test_paper', JSON.stringify(paper));
|
sessionStorage.setItem('tms_self_test_answers', JSON.stringify(savedAnswers));
|
router.push('/tms/selfTest/exam');
|
} catch (e: any) {
|
createMessage.error(e?.message || '继续考试失败');
|
} finally {
|
continueLoadingId.value = '';
|
}
|
}
|
|
function onPageChange(page: number, pageSize: number) {
|
pagination.value.currentPage = page;
|
pagination.value.pageSize = pageSize;
|
loadRecords();
|
}
|
|
/** 弹出层挂到 body,避免被 jnpf-content-wrapper overflow:hidden 裁切 */
|
function popupContainer() {
|
return document.body;
|
}
|
</script>
|
|
<template>
|
<div class="jnpf-content-wrapper tms-self-test-records-page">
|
<div class="jnpf-content-wrapper-center tms-self-test-records-center">
|
<div class="jnpf-content-wrapper-content tms-self-test-records">
|
<div class="records-top">
|
<div class="page-header">
|
<div>
|
<div class="text-base font-medium">我的检测记录</div>
|
<div class="mt-1 text-gray-400 text-sm">查看历史自我检测成绩与错题。</div>
|
</div>
|
<a-button @click="goBack">返回自我检测</a-button>
|
</div>
|
|
<div class="search-bar">
|
<a-form layout="inline" class="search-fields" :model="queryForm" @finish="handleSearch">
|
<a-form-item label="题库">
|
<jnpf-select
|
v-model:value="queryForm.bankId"
|
:options="banks"
|
allow-clear
|
show-search
|
placeholder="请选择题库"
|
class="!w-[200px]"
|
/>
|
</a-form-item>
|
<a-form-item label="状态">
|
<jnpf-select
|
v-model:value="queryForm.testStatus"
|
:options="statusOpts"
|
:field-names="TMS_DIC_FIELD_NAMES"
|
allow-clear
|
placeholder="请选择状态"
|
class="!w-[140px]"
|
/>
|
</a-form-item>
|
<a-form-item label="时间范围">
|
<jnpf-date-range
|
v-model:value="queryForm.timeRange"
|
allow-clear
|
format="YYYY-MM-DD"
|
class="!w-[260px]"
|
:placeholder="['开始日期', '结束日期']"
|
:get-popup-container="popupContainer"
|
/>
|
</a-form-item>
|
</a-form>
|
<a-space class="search-actions">
|
<a-button type="primary" :loading="loading" @click="handleSearch">查询</a-button>
|
<a-button @click="handleReset">重置</a-button>
|
</a-space>
|
</div>
|
</div>
|
|
<div class="records-body">
|
<a-table
|
:data-source="records"
|
:loading="loading"
|
row-key="id"
|
size="middle"
|
:pagination="{
|
current: pagination.currentPage,
|
pageSize: pagination.pageSize,
|
total: pagination.total,
|
showSizeChanger: true,
|
showTotal: (t: number) => `共 ${t} 条`,
|
onChange: onPageChange,
|
}"
|
:columns="[
|
{ title: '题库', dataIndex: 'bankName', key: 'bankName', ellipsis: true },
|
{ title: '题量', dataIndex: 'totalCount', key: 'totalCount', width: 80 },
|
{ title: '正确', dataIndex: 'correctCount', key: 'correctCount', width: 80 },
|
{ title: '正确率', key: 'scoreRate', width: 100 },
|
{ title: '状态', key: 'testStatus', width: 100 },
|
{ title: '开始时间', dataIndex: 'startTime', key: 'startTime', width: 170 },
|
{ title: '交卷时间', dataIndex: 'submitTime', key: 'submitTime', width: 170 },
|
{ title: '操作', key: 'action', width: 120, fixed: 'right' },
|
]"
|
>
|
<template #bodyCell="{ column, record }">
|
<template v-if="column.key === 'scoreRate'">
|
{{ record.scoreRate != null ? `${record.scoreRate}%` : '-' }}
|
</template>
|
<template v-else-if="column.key === 'testStatus'">
|
{{ statusLabel(record.testStatus) }}
|
</template>
|
<template v-else-if="column.key === 'correctCount'">
|
{{ record.correctCount ?? '-' }}
|
</template>
|
<template v-else-if="column.key === 'action'">
|
<a-button
|
v-if="record.testStatus === 'doing'"
|
type="link"
|
size="small"
|
:loading="continueLoadingId === record.id"
|
@click="handleContinue(record)"
|
>
|
继续考试
|
</a-button>
|
<a-button
|
v-else
|
type="link"
|
size="small"
|
@click="handleView(record)"
|
>
|
查看
|
</a-button>
|
</template>
|
</template>
|
</a-table>
|
</div>
|
</div>
|
</div>
|
</div>
|
</template>
|
|
<style scoped>
|
/* 全局 jnpf-content-wrapper* 为 overflow:hidden 且无 min-height:0,滚动必须落在内层 body */
|
.tms-self-test-records-page {
|
min-height: 0;
|
}
|
|
.tms-self-test-records-center {
|
min-height: 0 !important;
|
}
|
|
.tms-self-test-records {
|
display: flex !important;
|
flex-direction: column;
|
flex: 1 1 0 !important;
|
min-height: 0 !important;
|
height: auto !important;
|
overflow: hidden !important;
|
background: #fff;
|
padding: 0;
|
}
|
|
.records-top {
|
flex-shrink: 0;
|
padding: 20px 24px 0;
|
}
|
|
.page-header {
|
display: flex;
|
justify-content: space-between;
|
align-items: flex-start;
|
margin-bottom: 16px;
|
padding-bottom: 12px;
|
border-bottom: 1px solid #f0f0f0;
|
}
|
|
.search-bar {
|
display: flex;
|
justify-content: space-between;
|
align-items: flex-start;
|
gap: 16px;
|
margin-bottom: 12px;
|
}
|
|
.search-fields {
|
flex: 1;
|
row-gap: 12px;
|
}
|
|
.search-actions {
|
flex-shrink: 0;
|
padding-top: 4px;
|
}
|
|
.records-body {
|
flex: 1 1 0;
|
min-height: 0;
|
overflow-y: auto !important;
|
overflow-x: hidden;
|
padding: 0 24px 24px;
|
-webkit-overflow-scrolling: touch;
|
}
|
|
.records-body :deep(.ant-table-wrapper) {
|
overflow: visible !important;
|
}
|
</style>
|