| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615 |
- <script lang="ts" setup>
- import { onMounted, ref } from 'vue';
- import { useRoute } from 'vue-router';
- // // @ts-ignore
- // import { useVbenModal } from '@vben/common-ui';
- // @ts-ignore
- import { Page, useVbenModal } from '@vben/common-ui';
- import { CircleCheckFilled, CircleCloseFilled } from '@element-plus/icons-vue';
- // @ts-ignore
- import { getAnswerResult } from '#/api/exam/exam/answer';
- // @ts-ignore
- import { examPaperDetail } from '#/api/exam/exam/exam';
- // @ts-ignore
- import { getParticipantById } from '#/api/exam/exam/participant';
- import knowledgemain from '#/components/knowledgemain/index.vue';
- const route = useRoute();
- const examId = Number(route.params.id || 0);
- const pid = Number(route.query.pid || 0);
- const participantInfo = ref<any>({});
- // 考试信息
- const examInfo = ref<any>({});
- // 答题信息
- const answerInfo = ref<any>([]);
- onMounted(() => {
- getParticipantById(pid).then((res: any) => {
- participantInfo.value = res || {};
- });
- });
- // 计算用时
- const calculateExamTime = () => {
- if (!participantInfo.value.startTime || !participantInfo.value.submitTime) {
- return '-';
- }
- const startTime = new Date(participantInfo.value.startTime).getTime();
- const submitTime = new Date(participantInfo.value.submitTime).getTime();
- const duration = (submitTime - startTime) / 60_000; // 转换为分钟
- return `${duration.toFixed(2)} 分钟`;
- };
- // 数据处理工具函数
- // 解析JSON字符串,处理异常
- const parseJson = (str: string) => {
- if (!str) return [];
- try {
- return JSON.parse(str);
- } catch (error) {
- console.error('解析JSON失败:', error);
- return [];
- }
- };
- const knowledgeRef: any = ref(null);
- const selectedCategoryInfo = ref<any>({});
- const [quoteModal, quoteModalApi] = useVbenModal({
- title: '引用试题',
- showCancelBtn: false,
- class: 'quote-modal',
- onConfirm: () => {
- const result = knowledgeRef.value.confirmSelection();
- if (result) {
- // formData.value = result;
- // console.log(result);
- selectedCategoryInfo.value = result;
- quoteModalApi.close();
- }
- },
- });
- // 根据paperQuestionId获取答题信息
- const getAnswerByPaperQuestionId = (paperQuestionId: number) => {
- return (
- answerInfo.value.find(
- (item: any) => item.paperQuestionId === paperQuestionId,
- ) || {}
- );
- };
- // 获取题目类型中文名称
- const getQuestionTypeName = (type: string) => {
- const typeMap: any = {
- single_choice: '单选题',
- multiple_choice: '多选题',
- judgment: '判断题',
- fill_blank: '填空题',
- essay: '简答题',
- };
- return typeMap[type] || type;
- };
- // 按大题索引分组题目
- const getQuestionsByBigIndex = () => {
- if (!examInfo.value?.paper?.questions) return [];
- const grouped: any = {};
- examInfo.value.paper.questions.forEach((question: any) => {
- if (!grouped[question.bigQuestionIndex]) {
- grouped[question.bigQuestionIndex] = [];
- }
- grouped[question.bigQuestionIndex].push(question);
- });
- // 转换为数组并按索引排序
- return Object.keys(grouped)
- .sort((a, b) => Number(a) - Number(b))
- .map((key) => grouped[key]);
- };
- const handleQuote = (str: string) => {
- selectedCategoryInfo.value = JSON.parse(str || '{}');
- quoteModalApi.open();
- };
- const init = async () => {
- // 并发请求接口获取数据
- const [examInfoRes, participantRes, answerRes] = await Promise.all([
- examPaperDetail(examId),
- getParticipantById(pid),
- getAnswerResult(examId),
- ]);
- examInfo.value = examInfoRes || {};
- if (examInfo.value?.paper?.bigQuestionNames) {
- examInfo.value.paper.bigQuestionNames = JSON.parse(
- examInfo.value.paper.bigQuestionNames,
- );
- }
- participantInfo.value = participantRes || {};
- answerInfo.value = answerRes || {};
- };
- onMounted(async () => {
- await init();
- });
- </script>
- <template>
- <Page title="已出分">
- <div class="startexamin_box">
- <div class="left w-full rounded-md bg-white p-4">
- <div
- class="exam_info_left"
- v-for="(bigQuestion, bigIndex) in examInfo?.paper?.bigQuestionNames"
- :key="bigIndex"
- >
- <div class="exam_info_left_title">
- {{ bigQuestion.name }}(本题分值{{ bigQuestion.score }}分)
- </div>
- <!-- 动态生成小题 -->
- <div
- class="exam_info_left_content"
- v-for="(question, questionIndex) in getQuestionsByBigIndex()[
- +bigIndex
- ] || []"
- :key="question.id"
- >
- <div class="exam_info_left_content_title">
- {{ +questionIndex + 1 }}.
- <span v-html="question.info.questionContent"></span>({{
- getQuestionTypeName(question.questionType)
- }},{{ question.smallQuestionScore }}分)
- </div>
- <!-- 选项题(单选、多选、判断) -->
- <div
- class="exam_info_left_content_option"
- v-if="
- ['single_choice', 'multiple_choice', 'judgment'].includes(
- question.questionType,
- )
- "
- >
- <div
- class="exam_info_left_content_option_item"
- v-for="(option, optionIndex) in parseJson(
- question.info.options,
- )"
- :key="optionIndex"
- :style="{
- color:
- getAnswerByPaperQuestionId(question.id).userAnswer &&
- parseJson(
- getAnswerByPaperQuestionId(question.id).userAnswer,
- ).includes(optionIndex)
- ? getAnswerByPaperQuestionId(question.id).isCorrect === 1
- ? '#38b865'
- : '#eb5e12'
- : '#31373d',
- }"
- >
- <span>
- {{ String.fromCharCode(65 + +optionIndex) }}.
- <span v-html="option"></span>
- </span>
- <!-- 显示正确/错误图标 -->
- <el-icon
- v-if="
- getAnswerByPaperQuestionId(question.id).userAnswer &&
- parseJson(
- getAnswerByPaperQuestionId(question.id).userAnswer,
- ).includes(optionIndex)
- "
- >
- <CircleCheckFilled
- v-if="
- getAnswerByPaperQuestionId(question.id).isCorrect === 1
- "
- />
- <CircleCloseFilled v-else />
- </el-icon>
- </div>
- </div>
- <!-- 填空题 -->
- <div
- class="exam_info_left_content_option"
- v-else-if="question.questionType === 'fill_blank'"
- >
- <div class="exam_info_left_content_option_item">
- <span>用户答案:</span>
- <span
- :style="{
- color:
- getAnswerByPaperQuestionId(question.id).isCorrect === 1
- ? '#38b865'
- : '#eb5e12',
- marginLeft: '10px',
- }"
- >
- <span
- v-html="
- parseJson(
- getAnswerByPaperQuestionId(question.id).userAnswer ||
- '[]',
- ).join('、')
- "
- ></span>
- </span>
- </div>
- </div>
- <!-- 简答题 -->
- <div
- class="exam_info_left_content_option"
- v-else-if="question.questionType === 'essay'"
- >
- <div class="exam_info_left_content_option_item">
- <span>用户答案:</span>
- </div>
- <div
- class="exam_info_left_content_option_item"
- :style="{
- color:
- getAnswerByPaperQuestionId(question.id).isCorrect === 1
- ? '#38b865'
- : '#eb5e12',
- marginLeft: '20px',
- marginTop: '10px',
- }"
- >
- <span
- v-html="
- getAnswerByPaperQuestionId(question.id).userAnswer ||
- '未作答'
- "
- ></span>
- </div>
- </div>
- <!-- 答案解析 -->
- <div class="exam_info_left_content_answer">
- <!-- 正确答案(简答题不显示) -->
- <div
- class="exam_info_left_content_answer_title"
- v-if="question.questionType !== 'essay'"
- >
- 正确答案:
- <span
- v-if="
- ['single_choice', 'multiple_choice', 'judgment'].includes(
- question.questionType,
- )
- "
- >
- <span
- v-html="
- parseJson(question.info.correctAnswer || '[]')
- .map((idx: number) => String.fromCharCode(65 + idx))
- .join(',')
- "
- ></span>
- </span>
- <span v-else-if="question.questionType === 'fill_blank'">
- <span
- v-html="
- parseJson(question.info.correctAnswer || '[]').join('、')
- "
- ></span>
- </span>
- <span v-else>
- <span v-html="question.info.correctAnswer || '无'"></span>
- </span>
- </div>
- <div class="exam_info_left_content_answer_desc">
- 答案解析:
- <ElLink
- v-if="question.info.analysisReference"
- type="primary"
- @click="handleQuote(question.info.analysisReference)"
- >
- 查看引用解析
- </ElLink>
- <div class="exam_info_left_content_answer_content">
- <span
- v-html="question.info.answerAnalysis || '暂无解析'"
- ></span>
- </div>
- </div>
- <div class="exam_info_left_content_answer_title">
- 得分:
- <span
- :style="{
- color:
- getAnswerByPaperQuestionId(question.id).isCorrect === 1
- ? '#38b865'
- : '#eb5e12',
- }"
- >
- {{ getAnswerByPaperQuestionId(question.id).userScore || 0 }} /
- {{ question.smallQuestionScore }}
- </span>
- </div>
- </div>
- </div>
- </div>
- </div>
- <div class="rigth">
- <div class="exam_info_right_top">
- <div class="exam_info_right_top_title">{{ examInfo.examName }}</div>
- <div class="exam_info_right_top_content">
- <div>考试姓名:{{ participantInfo.userName || '未知' }}</div>
- <div>试卷总分:{{ examInfo.totalScore }}分</div>
- <div>及格分数:{{ examInfo.passScore }}分</div>
- <div v-if="participantInfo.reviewStatus === 'unreviewed'">
- 考生得分:未出分
- </div>
- <div v-else>考生得分:{{ participantInfo.score || 0 }}分</div>
- <div>考试用时:{{ calculateExamTime() }}</div>
- <div v-if="participantInfo.reviewStatus === 'reviewed'">
- 考试结果:
- <span
- style="color: #38b865"
- v-if="participantInfo.score >= examInfo.passScore"
- >及格
- </span>
- <span style="color: #eb5e12" v-else>不及格</span>
- </div>
- </div>
- </div>
- <div class="exam_info_right_main">
- <div
- class="rigth_main_content"
- v-for="(bigQuestion, bigIndex) in examInfo?.paper?.bigQuestionNames"
- :key="bigIndex"
- >
- <div class="rigth_main_content_title">
- <text>{{ bigQuestion.name }}</text>
- <text>
- 总{{ getQuestionsByBigIndex()[+bigIndex]?.length || 0 }}题/共
- {{ bigQuestion.score }} 分
- </text>
- </div>
- <div class="rigth_main_content_item">
- <div
- v-for="(question, questionIndex) in getQuestionsByBigIndex()[
- +bigIndex
- ] || []"
- class="rigth_main_content_items"
- :key="question.id"
- :style="{
- backgroundColor:
- getAnswerByPaperQuestionId(question.id).isCorrect === 1
- ? '#38b865'
- : getAnswerByPaperQuestionId(question.id).isCorrect === 0
- ? '#eb5e12'
- : getAnswerByPaperQuestionId(question.id).userAnswer
- ? '#215acd'
- : '#d4d6d9',
- }"
- >
- {{ +questionIndex + 1 }}
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- <quoteModal>
- <knowledgemain ref="knowledgeRef" :data="selectedCategoryInfo" />
- </quoteModal>
- </Page>
- </template>
- <style lang="scss" scoped>
- .startexamin_box {
- width: 100%;
- height: 730px;
- display: flex;
- justify-content: space-between;
- gap: 10px;
- overflow: hidden;
- .left {
- flex: 1;
- // height: 730px;
- // border: 1px solid red;
- overflow-y: auto;
- display: flex;
- flex-direction: column;
- gap: 12px;
- .exam_info_left {
- display: flex;
- flex-direction: column;
- gap: 12px;
- .exam_info_left_title {
- width: 100%;
- height: 80px;
- background-color: #f7f9fa;
- font-size: 18px;
- font-weight: 500;
- line-height: 80px;
- box-sizing: border-box;
- padding-left: 24px;
- color: #31373d;
- }
- .exam_info_left_content {
- box-sizing: border-box;
- padding-left: 24px;
- padding-top: 24px;
- background-color: #f7f9fa;
- font-size: 20px;
- font-weight: 400;
- color: #31373d;
- .exam_info_left_content_title {
- margin-bottom: 24px;
- display: flex;
- align-items: center;
- flex-wrap: wrap;
- span {
- display: inline;
- p {
- display: inline;
- margin: 0;
- }
- }
- }
- .exam_info_left_content_option {
- display: flex;
- flex-direction: column;
- gap: 12px;
- .exam_info_left_content_option_item {
- display: flex;
- align-items: flex-start;
- font-size: 18px;
- font-weight: 400;
- color: #31373d;
- width: 100%;
- > span {
- display: flex;
- align-items: flex-start;
- gap: 5px;
- width: 100%;
- &:first-child {
- width: auto;
- }
- p {
- display: inline;
- margin: 0;
- vertical-align: top;
- }
- }
- }
- }
- .exam_info_left_content_answer {
- width: 100%;
- border-top: 1px solid #e6e8eb;
- display: flex;
- flex-direction: column;
- gap: 12px;
- box-sizing: border-box;
- padding-top: 24px;
- padding-bottom: 24px;
- margin-top: 24px;
- .exam_info_left_content_answer_title,
- .exam_info_left_content_answer_desc {
- font-size: 18px;
- font-weight: 500;
- color: #31373d;
- // display: flex;
- align-items: center;
- flex-wrap: wrap;
- span {
- display: inline;
- p {
- display: inline;
- margin: 0;
- }
- }
- .exam_info_left_content_answer_content {
- margin-top: 12px;
- margin-left: 8px;
- font-size: 16px;
- font-weight: 400;
- color: #4e5969;
- span {
- display: inline;
- p {
- display: inline;
- margin: 0;
- }
- }
- }
- }
- }
- }
- }
- }
- .rigth {
- width: 400px;
- display: flex;
- flex-direction: column;
- gap: 12px;
- // border: 1px solid red;
- .exam_info_right_top {
- background-color: #ffffff;
- width: 100%;
- height: 332px;
- padding: 24px;
- box-sizing: border-box;
- .exam_info_right_top_title {
- font-size: 18px;
- font-weight: 500;
- color: #31373d;
- margin-bottom: 28px;
- }
- .exam_info_right_top_content {
- display: flex;
- flex-direction: column;
- gap: 24px;
- font-size: 16px;
- font-weight: 500;
- color: #31373d;
- }
- }
- .exam_info_right_main {
- flex: 1;
- background-color: #ffffff;
- box-sizing: border-box;
- padding: 16px 24px;
- display: flex;
- flex-direction: column;
- gap: 28px;
- // height: 384px;
- // border: 1px solid red;
- overflow-y: auto;
- .rigth_main_content {
- display: flex;
- flex-direction: column;
- gap: 12px;
- .rigth_main_content_title {
- // height: 48px;
- display: flex;
- justify-content: space-between;
- align-items: center;
- font-size: 16px;
- font-weight: 500;
- }
- .rigth_main_content_item {
- display: flex;
- flex-wrap: wrap;
- gap: 12px;
- .rigth_main_content_items {
- width: 40px;
- height: 40px;
- border-radius: 4px;
- background-color: #38b865;
- display: flex;
- justify-content: center;
- align-items: center;
- color: #ffffff;
- font-size: 14px;
- font-weight: 400;
- border: 1px solid #d4d6d9;
- }
- .selectedactive {
- background-color: #e2eaf6;
- }
- .answered {
- background-color: #215acd;
- color: white;
- }
- .selectedactive.answered {
- background-color: #215acd;
- color: white;
- }
- }
- }
- }
- }
- }
- </style>
|