Преглед изворни кода

fix(exam): 修复统计分析组件的问题并优化题目选择逻辑

修复统计分析组件中题目类型判断和答案解析的问题,优化考试和问卷的统计分析展示
在试卷表单中添加题目类型变更处理,防止重复选择同一题目
移除用户管理界面中多余的字符
tanshanming пре 6 месеци
родитељ
комит
a77638ed85

+ 115 - 4
src/views/exm/examinationManagement/StatisticAnalysis.vue

@@ -41,10 +41,10 @@
 							</div>
 						</div>
 
-						<!-- <div class="question-analysis">
+						<div class="question-analysis" v-if="props.examType === 'exam'">
 							<h4>题目分析:</h4>
 							<div v-html="getQuestionAnalysis(item)"></div>
-						</div> -->
+						</div>
 
 						<a-divider />
 					</div>
@@ -59,6 +59,9 @@
 	import { ref, watch } from 'vue'
 	import examManagerApi from '@/api/exam/paper/examManager.js'
 	import * as echarts from 'echarts'
+	import { useExamStore } from '@/store/exam'
+	const examStore = useExamStore()
+	const questionTypeEnum = computed(() => examStore.questionTypeEnum)
 	const props = defineProps({
 		paperId: {
 			type: String,
@@ -67,7 +70,8 @@
 		paperName: {
 			type: String,
 			default: ''
-		}
+		},
+		examType: String // exam-考试,questionnaire-调查问卷
 	})
 
 	const loading = ref(false)
@@ -181,6 +185,15 @@
 
 	// 获取题目类型
 	const getQuestionType = (item) => {
+		// 如果有 questionType 字段,优先使用 questionTypeEnum 获取题型名称
+		if (item.questionType !== undefined) {
+			const questionType = questionTypeEnum.value.find((type) => type.key === item.questionType)
+			if (questionType) {
+				return questionType.value
+			}
+		}
+
+		// 如果没有 questionType 字段或找不到对应的题型,使用原来的逻辑作为备选
 		try {
 			const content = JSON.parse(item.content)
 			if (!content.questionItemObjects || content.questionItemObjects.length === 0) {
@@ -197,6 +210,13 @@
 
 	// 判断是否为选择题
 	const isChoiceQuestion = (item) => {
+		// 使用 questionType 字段判断
+		if (item.questionType !== undefined) {
+			// 1是单选题,2是多选题,3是判断题,这三种都是选择题
+			return [1, 2, 3].includes(item.questionType)
+		}
+
+		// 如果没有 questionType 字段,使用原来的逻辑
 		try {
 			const content = JSON.parse(item.content)
 			return content.questionItemObjects && content.questionItemObjects.length > 0
@@ -218,9 +238,14 @@
 	// 获取选项选择人数
 	const getOptionCount = (item, prefix) => {
 		try {
+			// 确保 answerList 是字符串,并且是选择题类型
+			if (typeof item.answerList !== 'string' || !isChoiceQuestion(item)) {
+				return 0
+			}
 			const answerList = item.answerList.split(',')
 			return answerList.filter((answer) => answer === prefix).length
 		} catch (e) {
+			console.error('获取选项选择人数失败', e)
 			return 0
 		}
 	}
@@ -242,10 +267,96 @@
 	const getSubjectiveAnswers = (item) => {
 		try {
 			if (!isChoiceQuestion(item)) {
-				return JSON.parse(item.answerList)
+				// 处理填空题的特殊格式
+				if (item.questionType === 4) {
+					// 填空题
+					try {
+						// 填空题的答案格式可能是 "["i","i","o","o"],[]" 这样的格式
+						// 这不是标准的 JSON 格式,需要特殊处理
+						const raw = item.answerList
+						let s = typeof raw === 'string' ? raw.trim() : JSON.stringify(raw)
+
+						if (s === '' || s === 'null' || s === 'undefined') {
+							return []
+						}
+
+						const tryParse = (text) => {
+							try {
+								return JSON.parse(text)
+							} catch (e) {
+								return undefined
+							}
+						}
+
+						let answerData = tryParse(s)
+
+						if (answerData === undefined) {
+							let fixed = s
+							// 去掉 ] 前多余逗号,以及结尾多余逗号
+							fixed = fixed.replace(/,\s*]/g, ']').replace(/,\s*$/, '')
+							// 单引号转双引号(常见于后端返回)
+							if (fixed.includes("'") && !fixed.includes('"')) {
+								fixed = fixed.replace(/'/g, '"')
+							}
+							// 缺少最外层 [] 的情况,例如 ["i","i"],[]
+							if (
+								(fixed.startsWith('[') && fixed.includes('],') && !(fixed.startsWith('[[') && fixed.endsWith(']]'))) ||
+								(!fixed.startsWith('[') && fixed.includes('],'))
+							) {
+								fixed = `[${fixed}]`
+							}
+							answerData = tryParse(fixed)
+						}
+
+						// 将解析结果格式化为字符串数组用于展示
+						const result = []
+						if (Array.isArray(answerData)) {
+							// 二维数组:每个空对应一个数组
+							if (answerData.length > 0 && Array.isArray(answerData[0])) {
+								answerData.forEach((ans) => {
+									if (Array.isArray(ans)) {
+										const line = ans
+											.filter((x) => x !== null && x !== undefined && x !== '')
+											.map((x) => String(x))
+											.join(', ')
+										if (line !== '') result.push(line)
+									} else if (ans !== null && ans !== undefined && ans !== '') {
+										result.push(String(ans))
+									}
+								})
+							} else {
+								// 一维数组:合并为一条
+								const line = answerData
+									.filter((x) => x !== null && x !== undefined && x !== '')
+									.map((x) => String(x))
+									.join(', ')
+								if (line !== '') result.push(line)
+							}
+							return result
+						} else if (typeof answerData === 'string') {
+							return [answerData]
+						} else if (answerData != null) {
+							return [JSON.stringify(answerData)]
+						}
+						return []
+					} catch (error) {
+						console.error('解析填空题答案失败', error)
+						// 如果特殊处理失败,尝试直接显示原始答案
+						return [item.answerList]
+					}
+				}
+
+				// 其他主观题类型
+				try {
+					return JSON.parse(item.answerList)
+				} catch (error) {
+					// 如果解析失败,可能是普通字符串
+					return [item.answerList]
+				}
 			}
 			return []
 		} catch (e) {
+			console.error('解析答案列表失败', e)
 			return []
 		}
 	}

+ 3 - 1
src/views/exm/examinationManagement/index.vue

@@ -84,7 +84,7 @@
 			width="80%"
 			@cancel="closeStatisticDrawer"
 		>
-			<StatisticAnalysis :paperId="statisticPaperId" />
+			<StatisticAnalysis :paperId="statisticPaperId" :paperName="statisticExamName" examType="exam" />
 		</a-modal>
 	</div>
 </template>
@@ -102,6 +102,7 @@
 	const { levelEnum, enumFormat } = storeToRefs(examStore)
 	const drawerVisible = ref(false)
 	const drawerTitle = ref('')
+	const statisticExamName = ref(null)
 	const editId = ref(null)
 	// 统计分析
 	const statisticVisible = ref(false)
@@ -140,6 +141,7 @@
 	const statistic = async (record) => {
 		statisticVisible.value = true
 		statisticTitle.value = '统计分析'
+		statisticExamName.value = record.examName
 		statisticPaperId.value = record.paperId
 	}
 	const closeStatisticDrawer = () => {

+ 29 - 3
src/views/exm/exampaper/form.vue

@@ -23,7 +23,7 @@
 				</a-select>
 			</a-form-item> -->
 			<a-form-item label="试卷类型" name="paperType" :rules="rules.paperType">
-				<a-select v-model:value="form.paperType" placeholder="请选择试卷类型">
+				<a-select v-model:value="form.paperType" placeholder="请选择试卷类型" @change="handPaperTypeChange">
 					<a-select-option v-for="item in paperTypeEnum" :key="item.key" :value="item.key">
 						{{ item.value }}
 					</a-select-option>
@@ -90,13 +90,13 @@
 						</a-select-option>
 					</a-select>
 				</a-form-item>
-				<a-form-item label="题库类型">
+				<!-- <a-form-item label="题库类型">
 					<a-select v-model:value="questionPage.queryParam.bankType" allow-clear style="width: 120px">
 						<a-select-option v-for="item in bankTypeEnum" :key="item.key" :value="item.key">
 							{{ item.value }}
 						</a-select-option>
 					</a-select>
-				</a-form-item>
+				</a-form-item> -->
 				<a-form-item>
 					<a-button type="primary" @click="queryForm">查询</a-button>
 				</a-form-item>
@@ -262,10 +262,36 @@
 		questionPage.queryParam.pageIndex = page
 		search()
 	}
+	// 获取所有已选题目的ID
+	const allSelectedQuestionIds = computed(() => {
+		const ids = []
+		form.titleItems.forEach((titleItem) => {
+			titleItem.questionItems.forEach((question) => {
+				ids.push(question.id)
+			})
+		})
+		return ids
+	})
+
 	const rowSelection = {
 		selectedRowKeys: computed(() => questionPage.multipleSelection.map((q) => q.id)),
 		onChange: (selectedRowKeys, selectedRows) => {
 			questionPage.multipleSelection = selectedRows
+		},
+		getCheckboxProps: (record) => ({
+			disabled: allSelectedQuestionIds.value.includes(record.id),
+			title: allSelectedQuestionIds.value.includes(record.id) ? '该题目已被选择' : ''
+		})
+	}
+	const handPaperTypeChange = (paperType) => {
+		if (['2', '3'].includes(paperType)) {
+			questionPage.queryParam.bankType = '1'
+		}
+		if ('5' === paperType) {
+			questionPage.queryParam.bankType = '2'
+		}
+		if ('6' === paperType) {
+			questionPage.queryParam.bankType = '3'
 		}
 	}
 	function questionTypeFormatter({ text }) {

+ 1 - 1
src/views/exm/questionnaireManagement/index.vue

@@ -84,7 +84,7 @@
 			width="80%"
 			@cancel="closeStatisticDrawer"
 		>
-			<StatisticAnalysis :paperId="statisticPaperId" :paperName="statisticExamName" />
+			<StatisticAnalysis :paperId="statisticPaperId" :paperName="statisticExamName" examType="questionnaire" />
 		</a-modal>
 	</div>
 </template>

+ 2 - 2
src/views/sys/user/index.vue

@@ -37,11 +37,11 @@
 						<a-col :span="8">
 							<a-button type="primary" @click="table.refresh(true)">
 								<template #icon><SearchOutlined /></template>
-								{{ $t('common.searchButton') }}1
+								{{ $t('common.searchButton') }}
 							</a-button>
 							<a-button class="snowy-buttom-left" @click="reset">
 								<template #icon><redo-outlined /></template>
-								{{ $t('common.resetButton') }}1
+								{{ $t('common.resetButton') }}
 							</a-button>
 						</a-col>
 					</a-row>