xinli/ruoyi-ui/src/views/psychology/assessment/taking.vue

409 lines
12 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="assessment-container">
<!-- 顶部信息栏 -->
<div class="assessment-header">
<el-card shadow="never">
<div class="header-content">
<div class="title">正在测评{{ scaleName }}</div>
<div class="progress">
<span>进度{{ currentIndex + 1 }} / {{ totalItems }}</span>
<el-progress :percentage="progressPercent" :stroke-width="8"></el-progress>
</div>
</div>
<div class="action-buttons">
<el-button type="warning" @click="handlePause">暂停测评</el-button>
<el-button @click="handleExit">退出</el-button>
</div>
</el-card>
</div>
<!-- 题目区域 -->
<el-card shadow="never" class="question-card" v-if="currentItem">
<div class="question-number"> {{ currentIndex + 1 }} </div>
<div class="question-content">{{ currentItem.itemContent }}</div>
<!-- 单选题 -->
<div class="options-container" v-if="currentItem.itemType === 'single'">
<el-radio-group v-model="selectedOption" @change="handleAnswerChange">
<div v-for="option in currentOptions" :key="option.optionId" class="option-item">
<el-radio :label="option.optionId">{{ option.optionCode }}. {{ option.optionContent }}</el-radio>
</div>
</el-radio-group>
</div>
<!-- 多选题 -->
<div class="options-container" v-if="currentItem.itemType === 'multiple'">
<el-checkbox-group v-model="selectedOptions" @change="handleAnswerChange">
<div v-for="option in currentOptions" :key="option.optionId" class="option-item">
<el-checkbox :label="option.optionId">{{ option.optionCode }}. {{ option.optionContent }}</el-checkbox>
</div>
</el-checkbox-group>
</div>
<!-- 矩阵题 -->
<div class="matrix-container" v-if="currentItem.itemType === 'matrix'">
<el-table :data="[{}]" border>
<el-table-column
v-for="option in currentOptions"
:key="option.optionId"
:label="option.optionContent"
align="center"
width="120">
<template slot-scope="scope">
<el-radio v-model="selectedOption" :label="option.optionId" @change="handleAnswerChange"></el-radio>
</template>
</el-table-column>
</el-table>
</div>
</el-card>
<!-- 底部导航 -->
<div class="navigation-buttons">
<el-button @click="handlePrev" :disabled="currentIndex === 0">上一题</el-button>
<el-button type="primary" @click="handleNext" :disabled="currentIndex === totalItems - 1">下一题</el-button>
<el-button type="success" @click="handleSubmit" :disabled="!isComplete || loading" style="margin-left: 20px;">
提交测评 ({{ answeredCount }}/{{ totalItems }})
</el-button>
</div>
</div>
</template>
<script>
import { getAssessment, pauseAssessment, getAssessmentItems, submitAssessment, getAssessmentAnswers } from "@/api/psychology/assessment";
import { listOption } from "@/api/psychology/option";
import { saveAnswer } from "@/api/psychology/assessment";
export default {
name: "AssessmentTaking",
data() {
return {
assessmentId: null,
scaleName: '',
itemList: [],
optionMap: {},
currentIndex: 0,
selectedOption: null,
selectedOptions: [],
answersMap: {},
loading: false
};
},
computed: {
currentItem() {
return this.itemList[this.currentIndex];
},
currentOptions() {
return this.optionMap[this.currentItem?.itemId] || [];
},
totalItems() {
return this.itemList.length;
},
progressPercent() {
return this.totalItems > 0 ? Math.round((this.currentIndex + 1) / this.totalItems * 100) : 0;
},
answeredCount() {
// 计算已答题数量
return this.itemList.filter(item => {
const answer = this.answersMap[item.itemId];
if (!answer) {
return false;
}
// 单选题需要 optionId多选题需要 optionIds字符串用逗号分隔
if (item.itemType === 'single') {
return answer.optionId != null;
} else if (item.itemType === 'multiple') {
return answer.optionIds != null && answer.optionIds.trim().length > 0;
}
return true;
}).length;
},
isComplete() {
if (this.itemList.length === 0) {
return false;
}
return this.answeredCount === this.itemList.length;
}
},
created() {
this.assessmentId = this.$route.query.assessmentId;
if (!this.assessmentId) {
this.$modal.msgError("测评ID不能为空");
this.$router.push('/psychology/assessment');
return;
}
this.loadAssessment();
},
methods: {
/** 加载测评信息 */
loadAssessment() {
this.loading = true;
Promise.all([
getAssessment(this.assessmentId),
getAssessmentItems(this.assessmentId),
getAssessmentAnswers(this.assessmentId)
]).then(([assessmentRes, itemsRes, answersRes]) => {
const assessment = assessmentRes.data;
if (!assessment) {
this.$modal.msgError("测评不存在");
this.$router.push('/psychology/assessment');
return;
}
this.scaleName = assessment.scaleName || '未知量表';
this.itemList = itemsRes.data || [];
// 检查题目列表是否为空
if (this.itemList.length === 0) {
this.$modal.msgWarning("该量表暂无题目,请联系管理员添加题目");
this.$router.push('/psychology/assessment');
return;
}
// 加载已保存的答案
const savedAnswers = answersRes.data || [];
savedAnswers.forEach(answer => {
this.answersMap[answer.itemId] = {
assessmentId: answer.assessmentId,
itemId: answer.itemId,
optionId: answer.optionId,
optionIds: answer.optionIds,
answerScore: answer.answerScore
};
});
// 加载所有题目的选项
this.loadAllOptions().then(() => {
// 选项加载完成后,加载当前题目的答案
this.loadCurrentAnswer();
}).catch(error => {
console.error('加载选项失败:', error);
this.$modal.msgError("加载题目选项失败,请刷新重试");
});
this.loading = false;
}).catch(error => {
console.error('加载测评信息失败:', error);
this.$modal.msgError("加载测评信息失败,请重试");
this.loading = false;
});
},
/** 加载所有题目的选项 */
loadAllOptions() {
const promises = this.itemList.map(item => {
return listOption(item.itemId).then(response => {
this.$set(this.optionMap, item.itemId, response.data || []);
});
});
return Promise.all(promises);
},
/** 答案改变事件 */
handleAnswerChange() {
const itemId = this.currentItem.itemId;
const answer = {
assessmentId: this.assessmentId,
itemId: itemId,
optionId: null,
optionIds: null,
answerScore: 0
};
if (this.currentItem.itemType === 'single') {
answer.optionId = this.selectedOption;
// 计算得分
const selectedOpt = this.currentOptions.find(opt => opt.optionId === this.selectedOption);
if (selectedOpt) {
answer.answerScore = selectedOpt.optionScore || 0;
}
// 使用 $set 确保响应式更新
this.$set(this.answersMap, itemId, answer);
} else if (this.currentItem.itemType === 'multiple') {
answer.optionIds = this.selectedOptions.length > 0 ? this.selectedOptions.join(',') : null;
// 计算多选题得分(所有选中选项的分数之和)
let totalScore = 0;
this.selectedOptions.forEach(optId => {
const selectedOpt = this.currentOptions.find(opt => opt.optionId === optId);
if (selectedOpt && selectedOpt.optionScore) {
totalScore += parseFloat(selectedOpt.optionScore) || 0;
}
});
answer.answerScore = totalScore;
// 使用 $set 确保响应式更新
this.$set(this.answersMap, itemId, answer);
}
// 自动保存答案
this.saveAnswerToServer(answer);
},
/** 保存答案到服务器 */
saveAnswerToServer(answer) {
saveAnswer(answer).then(() => {
// 静默保存成功
}).catch(error => {
console.error('保存答案失败:', error);
// 不显示错误提示,避免打断用户答题
});
},
/** 上一题 */
handlePrev() {
if (this.currentIndex > 0) {
this.currentIndex--;
this.loadCurrentAnswer();
}
},
/** 下一题 */
handleNext() {
if (this.currentIndex < this.totalItems - 1) {
this.currentIndex++;
this.loadCurrentAnswer();
}
},
/** 加载当前题目的答案 */
loadCurrentAnswer() {
if (!this.currentItem) {
return;
}
const itemId = this.currentItem.itemId;
const answer = this.answersMap[itemId];
if (this.currentItem.itemType === 'single') {
this.selectedOption = answer && answer.optionId ? answer.optionId : null;
this.selectedOptions = [];
} else if (this.currentItem.itemType === 'multiple') {
this.selectedOption = null;
if (answer && answer.optionIds) {
// 将字符串转换为数字数组
this.selectedOptions = answer.optionIds.split(',').map(id => parseInt(id)).filter(id => !isNaN(id));
} else {
this.selectedOptions = [];
}
} else {
this.selectedOption = null;
this.selectedOptions = [];
}
},
/** 暂停测评 */
handlePause() {
this.$modal.confirm('确定要暂停测评吗?您可以稍后继续完成。').then(() => {
pauseAssessment(this.assessmentId).then(() => {
this.$modal.msgSuccess("测评已暂停");
this.$router.push('/psychology/assessment');
});
});
},
/** 退出 */
handleExit() {
this.$modal.confirm('确定要退出测评吗?已答题目将会保存。').then(() => {
this.$router.push('/psychology/assessment');
});
},
/** 提交测评 */
handleSubmit() {
// 检查是否所有题目都已作答
if (!this.isComplete) {
const remaining = this.itemList.length - this.answeredCount;
this.$modal.msgWarning(`还有 ${remaining} 道题目未作答,请完成所有题目后再提交`);
return;
}
this.$modal.confirm('确定要提交测评吗?提交后将不能修改。').then(() => {
submitAssessment(this.assessmentId).then(response => {
this.$modal.msgSuccess(response.msg || "测评已提交,报告已生成");
this.$router.push('/psychology/assessment');
}).catch(error => {
this.$modal.msgError(error.msg || "提交失败,请重试");
});
});
}
},
watch: {
currentIndex() {
this.loadCurrentAnswer();
}
}
};
</script>
<style scoped>
.assessment-container {
min-height: 100vh;
background-color: #f5f5f5;
padding: 20px;
}
.assessment-header {
margin-bottom: 20px;
}
.header-content {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
}
.title {
font-size: 18px;
font-weight: bold;
color: #333;
}
.progress {
flex: 1;
margin-left: 20px;
max-width: 400px;
}
.action-buttons {
text-align: right;
}
.question-card {
margin-bottom: 20px;
}
.question-number {
font-size: 16px;
font-weight: bold;
color: #409EFF;
margin-bottom: 15px;
}
.question-content {
font-size: 16px;
line-height: 1.8;
color: #333;
margin-bottom: 25px;
}
.options-container {
margin-top: 20px;
}
.option-item {
padding: 10px 0;
border-bottom: 1px solid #eee;
cursor: pointer;
transition: background-color 0.3s;
}
.option-item:hover {
background-color: #f5f7fa;
}
.option-item:last-child {
border-bottom: none;
}
.matrix-container {
margin-top: 20px;
}
.navigation-buttons {
text-align: center;
padding: 20px 0;
background-color: white;
border-radius: 4px;
}
</style>