Преглед на файлове

我的考试查看页面

zjq преди 5 дни
родител
ревизия
e9458ee0bc

+ 14 - 3
src/api/examTrain/userExam.ts

@@ -40,11 +40,11 @@ export const getExamQuestionGroup = (Id: any) => {
 /**
  * @description 获取试题详情
  * @param {any} QuestionId
- * @param {any} ExamId
+ * @param {any} userExamId
  */
-export const getExamQuestion = (QuestionId: any, ExamId: any) => {
+export const getExamQuestion = (QuestionId: any, userExamId: any) => {
     return request({
-        url: `/api/v1/UserExam/GetExamQuestion?QuestionId=${QuestionId}&ExamId=${ExamId}`,
+        url: `/api/v1/UserExam/GetExamQuestion?QuestionId=${QuestionId}&userExamId=${userExamId}`,
         method: 'get',
     });
 };
@@ -71,3 +71,14 @@ export const submitUserExam = (params?: object) => {
         data: params,
     });
 };
+/**
+ * @description 获取查看试题详情
+ * @param {any} id
+ * @param {any} questionId
+ */
+export const getViewExamQuestion = (id: any, questionId: any) => {
+    return request({
+        url: `/api/v1/UserExam/View?id=${id}&questionId=${questionId}`,
+        method: 'get',
+    });
+};

+ 1 - 1
src/views/examTrain/exam/statistics/missExamUsers.vue

@@ -51,7 +51,7 @@ const state = reactive<any>({
 		crTime: defaultDate, // 时间默认今天开始到今天结束
 		startTime: null,
 		endTime: null,
-		IndustryId: null,
+		anlysisType: 3,
 	},
 });
 

+ 1 - 1
src/views/examTrain/exam/statistics/scoresAnalysis.vue

@@ -63,7 +63,7 @@ const state = reactive<any>({
         maxScore: null,
 		startTime: null,
 		endTime: null,
-		IndustryId: null,
+		anlysisType: 3,
 	},
 });
 

+ 1 - 1
src/views/examTrain/exam/userExam/components/UserExam-exam.vue

@@ -243,7 +243,7 @@ const onGetQuestionDetail = async (value) => {
     // 请求下一道题
     state.selQuestionID = value;
     try {
-		const { result } = await getExamQuestion(state.selQuestionID, state.examId);
+		const { result } = await getExamQuestion(state.selQuestionID, state.userExamId);
 		state.questionDetail = result;
         if ([0,2].includes(state.questionDetail.questionType)){
             let obj = state.questionDetail.questionOptions.find((x: any) => x.isSelected === true);

+ 222 - 71
src/views/examTrain/exam/userExam/components/UserExam-view.vue

@@ -1,34 +1,75 @@
 <template>
-	<el-dialog title="修改标签" v-model="state.dialogVisible" draggable append-to-body destroy-on-close @close="close" width="500px">
-		<el-form :model="state.ruleForm" ref="ruleFormRef" label-width="80px" v-loading="state.loading">
-			<el-row :gutter="10">
-				<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
-					<el-form-item label="上级标签" prop="parentId" :rules="[{ required: false, message: '请选择上级标签', trigger: 'change' }]">
-						<el-cascader
-							:options="state.treeData"
-							filterable
-							:props="{ checkStrictly: true, value: 'id', label: 'name', emitPath: false, children: 'children' }"
-							placeholder="请选择上级标签"
-							clearable
-							class="w100"
-							v-model="state.ruleForm.parentId"
-							ref="cascadeRef"
-							@change="getExamTagList"
-						>
-						</el-cascader>
-					</el-form-item>
-				</el-col>
-				<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
-					<el-form-item label="标签名称" prop="name" :rules="[{ required: true, message: '请填写标签名称', trigger: 'blur' }]">
-						<el-input v-model="state.ruleForm.name" placeholder="请填写标签名称" clearable></el-input>
-					</el-form-item>
-				</el-col>
-			</el-row>
-		</el-form>
+	<el-dialog v-model="state.dialogVisible" draggable destroy-on-close :show-close="false" width="80%" :before-close="closeDialog">
+        <template #header="{ close, titleId, titleClass }">
+            <div class="topContent">
+                <div class="titleBox">
+                    <span class="title">考试结果</span>
+                </div>
+                <div class="submitBox">
+				    <el-button type="info" @click="closeDialog">关 闭</el-button>
+                </div>
+            </div>
+        </template>
+        <el-row :gutter="10" v-loading="state.loading">
+            <el-col :xs="6" :sm="6" :md="6" :lg="6" :xl="6">
+                <div class="examListContent" v-for="(item) in state.questionTypeData">
+                    <p class="examType">{{item.questionTypeDesc}}</p>
+                    <ul class="examList">
+                        <li class="examListItem" :class="{'select': questionsItem.id == state.selQuestionID, 'right': questionsItem.isRight, 'error': !questionsItem.isRight}" v-for="(questionsItem) in item.questions" @click="onSelQuestion(questionsItem.id)">{{questionsItem.sortIndex}}</li>
+                    </ul>
+                </div>
+            </el-col>
+            <el-col :xs="18" :sm="18" :md="18" :lg="18" :xl="18">
+                <div class="questionBox" v-if="state.questionDetail">
+                    <p class="questionTitle">题干:{{ state.questionDetail.title }}</p>
+                    <div class="questionAnswer">
+                        <div v-if="[0,2].includes(state.questionDetail.questionType)">
+							<el-radio-group style="display: block;" v-model="state.selRadioValue">
+                                <el-form-item :label="item.label + '. '" v-for="item in state.questionDetail.questionOptions" style="margin-bottom: 10px;">
+                                    <el-radio :label="item.content" :value="item.id" disabled />
+                                </el-form-item>
+                            </el-radio-group>
+                        </div>
+                        <div v-else-if="state.questionDetail.questionType == 1">
+                            <el-checkbox-group v-model="state.selCheckboxValue">
+                                <el-form-item :label="item.label + '. '" v-for="item in state.questionDetail.questionOptions" style="margin-bottom: 10px;">
+                                    <el-checkbox :label="item.content" :value="item.id" disabled />
+                                </el-form-item>
+                            </el-checkbox-group>
+                        </div>
+						<div v-else-if="state.questionDetail.questionType == 3">
+                            <el-form-item label="答:">
+                                <el-input type="textarea" v-model="state.answerValue" :rows="3" placeholder="请输入答案,如果有多个请依照顺序用英文 , 隔开"  disabled></el-input>
+                            </el-form-item>
+                        </div>
+                        <div v-else-if="state.questionDetail.questionType == 4">
+                            <el-form-item label="答:">
+                                <el-input type="textarea" v-model="state.answerValue" :rows="3" placeholder="请输入答案" disabled></el-input>
+                            </el-form-item>
+                        </div>
+                    </div>
+                </div>
+				<div class="referenceAnswer" v-if="state.questionDetail">
+                    <span>得分:{{state.questionDetail.realScore || 0}}</span>
+                </div>
+                <div class="referenceAnswer" v-if="state.questionDetail">
+                    <span>参考答案:</span>
+                    <p>{{state.questionDetail.correctAnswer || '略'}}</p>
+                </div>
+                <div class="referenceAnswer" v-if="state.questionDetail && state.questionDetail.questionKnowladgeDtos.length > 0">
+                    <span>关联知识:</span>
+                    <p v-for="item in state.questionDetail.questionKnowladgeDtos" @click="onKnowladgeTo(item)">{{item.title}}</p>
+                </div>
+                <div class="referenceAnswer" v-if="state.questionDetail && state.questionDetail.questionSourcewareDtos.length > 0">
+                    <span>关联课件:</span>
+                    <p v-for="item in state.questionDetail.questionSourcewareDtos" @click="onSourcewareTo(item)">{{item.name}}</p>
+                </div>
+            </el-col>
+        </el-row>
 		<template #footer>
-			<span class="dialog-footer">
-				<el-button @click="closeDialog" class="default-button">取 消</el-button>
-				<el-button type="primary" @click="onSubmit(ruleFormRef)" :loading="state.loading">确 定 </el-button>
+			<span class="dialog-footer" v-if="state.selQuestionID">
+				<el-button type="primary" @click="onLastQuestion" :loading="state.loading">上一题</el-button>
+				<el-button type="primary" @click="onNextQuestion" :loading="state.loading">下一题</el-button>
 			</span>
 		</template>
 	</el-dialog>
@@ -36,73 +77,183 @@
 
 <script setup lang="ts">
 import { reactive, ref } from 'vue';
-import { ElMessage, FormInstance } from 'element-plus';
+import { useRouter } from 'vue-router';
+import { ElMessage, ElMessageBox } from 'element-plus';
 import { excludeSelfById } from '@/utils/tools';
 import other from '@/utils/other';
-import { examTagDetail, updateExamTag } from '@/api/examTrain/tag';
+import { fileDownloadByUrl } from '@/api/public/file';
+import {getExamQuestionGroup, getViewExamQuestion} from '@/api/examTrain/userExam';
 
+const router = useRouter(); // 路由
 // 定义子组件向父组件传值/事件
 const emit = defineEmits(['updateList']);
 
 // 定义变量内容
 const state = reactive<any>({
 	dialogVisible: false, // 弹窗
-	ruleForm: {
-		name: '', // 类型名称
-		parentId: '', // 上级类型
-	},
-	treeData: [], // 上级
 	loading: false, // 加载
+    id: '', // 用户考试id
+    examId: '', // 考试id
+	questionTypeData: [] as any[], // 试卷题型数据
+    questionsData: [] as any[], // 试题集合
+    selQuestionID: '', // 选择的问题id
+    questionDetail: null, // 选择的问题明细
+    selRadioValue: '', // 单选题选择的值
+    selCheckboxValue: [] as any[], // 多选题选择的值
+    answerValue: '', // 填空问答填写的值
 });
 // 打开弹窗
-const ruleFormRef = ref<any>(); // 表单ref
-const openDialog = async (row: any, treeData: any) => {
-	try {
-		state.dialogVisible = true;
-		const { result } = await examTagDetail(row.id);
-		state.ruleForm = result;
-		state.treeData = other.deepClone(treeData);
-		state.treeData = excludeSelfById(state.treeData, row.id);
+const openDialog = async (rows: any) => {
+    state.dialogVisible = true;
+    state.id = rows.id;
+    state.examId = rows.examId;
+	state.loading = true;
+    getQuestionsData();
+};
+const getQuestionsData = async () => {
+    try {
+		const { result } = await getExamQuestionGroup(state.examId);
+        let sortIndexNum = 1;
+        result.forEach(item => {
+            switch(item.questionType){
+				case 0: item.questionTypeDesc = '单选题'; break;
+				case 1: item.questionTypeDesc = '多选题'; break;
+				case 2: item.questionTypeDesc = '判断题'; break;
+				case 3: item.questionTypeDesc = '填空题'; break;
+				case 4: item.questionTypeDesc = '问答题'; break;
+				default: break;
+			}
+            item.questions.forEach(it => {
+                it.sortIndex = sortIndexNum;
+                it.questionType = item.questionType;
+                state.questionsData.push(it)
+                sortIndexNum++ ;
+            });
+        })
+		state.questionTypeData = result;
+        state.loading = false;
 	} catch (error) {
 		// 打印错误信息
 		console.error(error);
 	}
-};
-const close = () => {
-	ruleFormRef.value?.clearValidate();
-	ruleFormRef.value?.resetFields();
-};
+}
 // 关闭弹窗
 const closeDialog = () => {
 	state.dialogVisible = false;
+    state.questionTypeData = []; 
+    state.questionsData = []; 
+    state.selQuestionID = ''; 
+    state.questionDetail = null; 
+    state.selRadioValue = ''; 
+    state.selCheckboxValue = []; 
+    state.answerValue = '';
 };
-const cascadeRef = ref<RefType>();
-// 获取选择name值
-const getExamTagList = () => {
-	let currentNode = cascadeRef.value.getCheckedNodes();
-	state.ruleForm.parentName = currentNode[0]?.label ?? '';
-};
-// 保存
-const onSubmit = async (formEl: FormInstance | undefined) => {
-	if (!formEl) return;
-	await formEl.validate((valid: boolean) => {
-		if (!valid) return;
-		state.loading = true;
-		updateExamTag(state.ruleForm)
-			.then(() => {
-				closeDialog(); // 关闭弹窗
-				ElMessage.success('操作成功');
-				state.loading = false;
-				emit('updateList');
-			})
-			.catch(() => {
-				state.loading = false;
-			});
+// 上一题下一题
+const onLastQuestion = () => {
+    let index:number = state.questionsData.findIndex(it => it.id == state.selQuestionID);
+    index = (index - 1) == -1 ? state.questionsData.length - 1 : index - 1;
+    onSelQuestion(state.questionsData[index].id);
+}
+const onNextQuestion = () => {
+    let index:number = state.questionsData.findIndex(it => it.id == state.selQuestionID);
+    index = (index + 1) == state.questionsData.length ? 0 : index + 1;
+    onSelQuestion(state.questionsData[index].id);
+}
+// 选择的问题id 提交当前试题答案,再请求下一道题详情 value: 请求下一题详情id
+const onSelQuestion = async (value: any) => {
+	state.loading = true;
+    onGetQuestionDetail(value);
+}
+const onGetQuestionDetail = async (value) => {
+    // 初始化选择选择/填写的答案
+    state.selRadioValue = '';  
+    state.selCheckboxValue = []; 
+    // 请求下一道题
+    state.selQuestionID = value;
+    try {
+		const { result } = await getViewExamQuestion(state.id, state.selQuestionID);
+		state.questionDetail = result;
+        // let answerDesc = '';
+        // state.questionDetail.questionOptions.forEach(it => {it.isAnswer && (answerDesc += it.label)});
+        // state.questionDetail.answerDesc = answerDesc;
+        if ([0,2].includes(state.questionDetail.questionType)){
+            let obj = state.questionDetail.questionOptions.find((x: any) => x.isSelected === true);
+            state.selRadioValue = obj ? obj.id : '';
+        }else if (state.questionDetail.questionType == 1){
+            let arr = [] as any[];
+            state.questionDetail.questionOptions.forEach(it => {if (it.isSelected) arr.push(it);})
+            state.selCheckboxValue = arr ? arr.map((x: any) => x.id) : [];
+        }else {
+            state.answerValue = state.questionDetail.answer || '';
+        }
+        state.loading = false;
+	} catch (error) {
+		// 打印错误信息
+		console.error(error);
+	}
+}
+// 跳转知识详情页面
+const onKnowladgeTo = (row: any) => {
+	router.push({
+		name: 'knowledgePreview',
+		params: {
+			id: row.knowladgeId,
+			isAddPv: 'isAddPv',
+			tagsViewName: row.title,
+		},
 	});
 };
+// 课件预览下载
+const onSourcewareTo = (row: any) => {
+    ElMessageBox.confirm(`您确定要下载课件,是否继续?`, '提示', {
+		confirmButtonText: '确认',
+		cancelButtonText: '取消',
+		type: 'warning',
+		draggable: true,
+		cancelButtonClass: 'default-button',
+		autofocus: false,
+	})
+		.then(() => {
+			fileDownloadByUrl({
+				Source: 'hotline',
+				Id: row.attachmentId,
+			}).then((res: any) => {
+				let blob: Blob = new Blob([res.data], { type: res.data.type }); // 创建blob 设置blob文件类型 data 设置为后端返回的文件(例如mp3,jpeg) type:这里设置后端返回的类型 为 mp3
+				let down: HTMLAnchorElement = document.createElement('a'); // 创建A标签
+				let href: string = window.URL.createObjectURL(blob); // 创建下载的链接
+				down.href = href; // 下载地址
+				down.download = row.name // 下载文件名
+				document.body.appendChild(down);
+				down.click(); // 模拟点击A标签
+				document.body.removeChild(down); // 下载完成移除元素
+				window.URL.revokeObjectURL(href); // 释放blob对象
+			});
+		})
+		.catch(() => {});
+}
 // 暴露变量
 defineExpose({
 	openDialog,
 	closeDialog,
 });
 </script>
+<style lang="scss">
+    .topContent{padding: 10px 30px;display: flex;justify-content: space-between;align-items: center;}
+    .topContent .surplusBox{text-align: center;}
+    .topContent .surplusBox .surplus{font-size: 18px;color: #000000;}
+    .topContent .titleBox{text-align: center;}
+    .topContent .titleBox .title{font-size: 20px;font-weight: bold;letter-spacing: 1px;color: #000000;}
+    .topContent .submitBox{text-align: right;vertical-align: top;}
+    .examListContent .examType{font-weight: bold;color: #000000;}
+    .examListContent .examList{padding: 5px 0 10px;}
+    .examListContent .examList .examListItem{display: inline-block;margin: 5px 10px 5px 0;text-align: center;width: 50px;height:35px;line-height: 35px;cursor: pointer;position: relative;border: #f2f2f2 1px solid;}
+    .examListContent .examList .right::after{content: " ";width: 5px;height: 5px;position: absolute;top: 2px;right: 3px;background-color: #009688;border-radius: 50%;}
+    .examListContent .examList .error::after{content: " ";width: 5px;height: 5px;position: absolute;top: 2px;right: 3px;background-color: #FF5722;border-radius: 50%;}
+    .examListContent .examList .select{background-color: #1890ff;color: #fff;}
+    .questionBox{padding: 20px 10px 0;}
+    .questionBox .questionTitle{margin-bottom: 10px;font-size: 16px;}
+    .questionBox .questionAnswer{padding: 0 20px;}
+    .referenceAnswer{background-color: #ebf9ff;margin: 15px 40px 15px 30px;padding: 10px;}
+    .referenceAnswer span{display: block;}
+    .referenceAnswer p{width: calc(100% - 80px);display: block;margin-top: 10px;color: #1890ff;cursor: pointer;}
+</style>

+ 5 - 4
src/views/examTrain/exam/userExam/index.vue

@@ -61,7 +61,8 @@
                         <template #default="{ row }">
                             <span v-if="row.examStatus == 0">未开始</span>
                             <span v-else-if="row.examStatus == 1">考试中</span>
-                            <span v-else-if="row.examStatus == 2">考试结束</span>
+                            <span v-else-if="row.examStatus == 2 && !row.canReExam">考试结束</span>
+                            <span v-else-if="row.examStatus == 2 && row.canReExam">可重考</span>
                         </template>
                     </vxe-column>
                     <vxe-column field="totalScore" title="考试总分" width="100"></vxe-column>
@@ -79,12 +80,12 @@
                     </vxe-column>
                     <vxe-column title="操作" fixed="right" width="150" align="center" :show-overflow="false">
                         <template #default="{ row }">
-                            <el-button type="primary" v-if="row.examStatus != 2" @click="onExam(row)" size="small" title="考试" v-auth="'userExam:index:exam'">
+                            <el-button type="primary" v-if="(row.examStatus != 2) || (row.examStatus == 2 && row.canReExam)" @click="onExam(row)" size="small" title="考试" v-auth="'userExam:index:exam'">
                                 考试
                             </el-button>
-                            <!-- <el-button link type="primary" @click="onView(row)" title="查看" v-auth="'userExam:index:view'">
+                            <el-button link type="primary" v-if="row.isCheck" @click="onView(row)" title="查看" v-auth="'userExam:index:view'">
                                 查看
-                            </el-button> -->
+                            </el-button>
                         </template>
                     </vxe-column>
                 </vxe-table>

+ 1 - 0
src/views/examTrain/train/statistics/resultAnalysis.vue

@@ -58,6 +58,7 @@ const state = reactive<any>({
 		crTime: defaultDate, 
 		startTime: null,
 		endTime: null,
+		anlysisType: 3,
         keyword: ''
 	},
 	tableData: [], //表单