123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364 |
- <script setup lang="ts">
- import {
- examQuestionDataApi,
- examRecordDataApi,
- examRecordPaperStructApi,
- examRecordQuestionsApi,
- onlinePracticeTypeApi,
- } from "@/api/onlinePractice";
- import {
- ExamQuestion,
- PaperStruct,
- QuestionWrapperItem,
- } from "@/types/student-client";
- import { toChineseNumber } from "@/utils/utils";
- import { onMounted } from "vue";
- import { Close, Checkmark } from "@vicons/ionicons5";
- const props = defineProps<{
- examId: string | number;
- examRecordDataId: string | number;
- fromCache: boolean;
- courseCode: string;
- }>();
- const emit = defineEmits<{
- (e: "ready"): void;
- }>();
- let questionGroupList = $ref<PaperStruct["defaultPaper"]["questionGroupList"]>(
- []
- );
- let practiceType = $ref("");
- async function initData() {
- const ptRes = await onlinePracticeTypeApi(props.examId);
- practiceType = ptRes.data.PRACTICE_TYPE;
- const { paperStruct, examQuestionList, examRecordData } =
- await getPaperData();
- if (!paperStruct || !examQuestionList) {
- $message.error("获取试卷信息失败", {
- duration: 15,
- closable: true,
- });
- return;
- }
- const examQuestionMap = await getQuestionWrapperMapData(
- examQuestionList,
- examRecordData.examRecord.paperType
- );
- questionGroupList = paperStruct.defaultPaper.questionGroupList.map(
- (questionGroup) => {
- questionGroup.questionWrapperList = questionGroup.questionWrapperList.map(
- (question) => {
- return examQuestionMap[question.questionId];
- }
- );
- return questionGroup;
- }
- );
- }
- async function getPaperData() {
- type FetchDataFuncType = [
- Promise<{ data: PaperStruct }>,
- Promise<{
- data: { examQuestionEntities: ExamQuestion[] };
- }>,
- Promise<{
- data: { examRecord: { paperType: string } };
- }>
- ];
- const fetchDataFunc: FetchDataFuncType = [
- examRecordPaperStructApi(props.examRecordDataId, props.fromCache),
- examRecordQuestionsApi(props.examRecordDataId, props.fromCache),
- examRecordDataApi(props.examRecordDataId, props.fromCache),
- ];
- const [paperStructRes, examQuestionListRes, examRecordDataRes] =
- await Promise.all(fetchDataFunc);
- const [paperStruct, examQuestionList, examRecordData] = [
- paperStructRes.data,
- examQuestionListRes.data.examQuestionEntities,
- examRecordDataRes.data,
- ];
- return {
- paperStruct,
- examQuestionList,
- examRecordData,
- };
- }
- async function getQuestionWrapperMapData(
- examQuestionList: ExamQuestion[],
- groupCode: string
- ) {
- let examQuestionMap: Record<string, ExamQuestion[]> = {};
- examQuestionList.forEach((q) => {
- if (!examQuestionMap[q.questionId]) examQuestionMap[q.questionId] = [];
- examQuestionMap[q.questionId].push(q);
- });
- const fetchFunc = Object.keys(examQuestionMap).map((questionId) =>
- examQuestionDataApi({
- questionId,
- examId: props.examId,
- courseCode: props.courseCode,
- groupCode,
- })
- );
- const questionList = await Promise.all(fetchFunc);
- let examQuestionWrapperMap: Record<string, QuestionWrapperItem> = {};
- questionList.forEach((qItem) => {
- const q = qItem.data;
- const questionList = examQuestionMap[q.id].map((questionInfo, index) => {
- return Object.assign(
- {},
- questionInfo,
- q.masterVersion.questionUnitList[index]
- ) as ExamQuestion;
- });
- examQuestionWrapperMap[q.id] = Object.assign(
- {},
- { examQuestionList: questionList, questionId: q.id },
- q.masterVersion
- );
- });
- return examQuestionWrapperMap;
- }
- function restoreAudio(str: string) {
- return (str || "")
- .replace(/<a/g, "<audio controls ")
- .replace(/url=/g, "src=")
- .replace(/a>/g, "audio>");
- }
- const optionName = "ABCDEFGHIJ".split("");
- function indexToABCD(index: number) {
- return optionName[index];
- }
- function parseRightAnswer(
- questionType: string,
- rightAnswer: string[] | undefined,
- optionPermutation: number[] | undefined
- ) {
- if (!rightAnswer) return "";
- // 选择题答案是非乱序的真实答案,展示时要转成乱序的题目答案。
- if (["SINGLE_CHOICE", "MULTIPLE_CHOICE"].includes(questionType)) {
- const permutationOptions = optionPermutation as number[];
- const permutationAnswer = rightAnswer
- .map((answer) => permutationOptions.indexOf(Number(answer)))
- .sort();
- return permutationAnswer.map((ans) => indexToABCD(ans)).join("");
- } else if (["TRUE_OR_FALSE"].includes(questionType)) {
- return { true: "正确", false: "错误" }[rightAnswer.join("")];
- } else {
- return rightAnswer.join("");
- }
- }
- function parseStudentAnswer(
- questionType: string,
- studentAnswer: string,
- optionPermutation: number[] | undefined
- ) {
- if (!studentAnswer) return "";
- // 选择题答案是非乱序的真实答案,展示时要转成乱序的题目答案。
- if (["SINGLE_CHOICE", "MULTIPLE_CHOICE"].includes(questionType)) {
- const permutationOptions = optionPermutation as number[];
- const permutationAnswer = studentAnswer
- .split("")
- .map((answer) => permutationOptions.indexOf(Number(answer)))
- .sort();
- return permutationAnswer.map((ans) => indexToABCD(ans)).join("");
- } else if (["TRUE_OR_FALSE"].includes(questionType)) {
- return { true: "正确", false: "错误" }[studentAnswer];
- } else {
- return studentAnswer;
- }
- }
- function equalAnswer(
- questionType: string,
- studentAnswer: string,
- rightAnswer: string[] | undefined
- ) {
- if (!rightAnswer) return null;
- if (["FILL_UP", "ESSAY"].includes(questionType)) return null;
- return studentAnswer === rightAnswer.join("");
- }
- function checkIsObjective(questionType: string) {
- return ["SINGLE_CHOICE", "MULTIPLE_CHOICE", "TRUE_OR_FALSE"].includes(
- questionType
- );
- }
- onMounted(async () => {
- await initData().catch(() => false);
- emit("ready");
- });
- </script>
- <template>
- <div class="exam-paper">
- <div
- v-for="(group, gindex) in questionGroupList"
- :key="gindex"
- class="question-group"
- >
- <h3 class="group-title">
- {{ toChineseNumber(gindex + 1) }}、{{ group.groupName }}({{
- group.groupScore
- }}分)
- </h3>
- <div class="group-questions">
- <div
- v-for="questionWrapper in group.questionWrapperList"
- :key="questionWrapper.questionId"
- class="question-wrapper"
- >
- <div
- v-if="questionWrapper.body"
- class="question-wrapper-body"
- v-html="restoreAudio(questionWrapper.body)"
- ></div>
- <div
- v-for="question in questionWrapper.examQuestionList"
- :key="question.questionId"
- class="question-item"
- >
- <div class="question-item-title line-text">
- <span>{{ question.order }}、</span>
- <div
- class="question-item-title-content"
- v-html="restoreAudio(question.body)"
- ></div>
- </div>
- <!-- options -->
- <div
- v-if="
- question.questionOptionList &&
- question.questionOptionList.length
- "
- class="question-item-options"
- >
- <div
- v-for="(optionOrder, oindex) in question.optionPermutation"
- :key="optionOrder"
- class="question-item-option line-text"
- >
- <span>{{ indexToABCD(oindex) }}、</span>
- <div
- class="question-item-option-content"
- v-html="
- restoreAudio(question.questionOptionList[optionOrder].body)
- "
- ></div>
- </div>
- </div>
- <!-- right answer -->
- <div
- v-if="practiceType !== 'NO_ANSWER'"
- class="question-item-answer line-text"
- >
- <span>正确答案:</span>
- <div
- class="question-item-answer-content"
- v-html="
- parseRightAnswer(
- question.questionType,
- question.rightAnswer,
- question.optionPermutation
- )
- "
- ></div>
- </div>
- <!-- student answer -->
- <div class="question-item-answer line-text">
- <span>学生答案:</span>
- <div
- class="question-item-answer-content"
- v-html="
- parseStudentAnswer(
- question.questionType,
- question.studentAnswer,
- question.optionPermutation
- )
- "
- ></div>
- <div
- v-if="checkIsObjective(question.questionType)"
- class="question-item-answer-result"
- >
- <n-icon
- v-if="
- equalAnswer(
- question.questionType,
- question.studentAnswer,
- question.rightAnswer
- )
- "
- :component="Checkmark"
- color="#13bb8a"
- :size="16"
- ></n-icon>
- <n-icon
- v-else
- :component="Close"
- color="#ed4014"
- :size="16"
- ></n-icon>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- </template>
- <style scoped>
- .question-group {
- margin-bottom: 20px;
- }
- .group-title {
- font-size: var(--app-font-size-large);
- font-weight: 600;
- margin-bottom: 5px;
- }
- .question-item {
- margin-bottom: 10px;
- }
- .question-item-options {
- margin-bottom: 10px;
- }
- .line-text > * {
- display: inline-block;
- vertical-align: top;
- }
- .line-text audio {
- height: 32px;
- }
- .line-text .question-item-answer-result .n-icon {
- vertical-align: middle;
- margin-top: -2px;
- }
- </style>
- <style>
- .line-text audio {
- height: 32px;
- }
- .line-text img {
- display: inline-block;
- vertical-align: middle;
- }
- </style>
|