346 lines
9.8 KiB
Vue
346 lines
9.8 KiB
Vue
<template>
|
|
<div class="survey-view">
|
|
<van-nav-bar
|
|
title="问卷调查"
|
|
left-text="返回"
|
|
left-arrow
|
|
@click-left="onBack"
|
|
/>
|
|
|
|
<div class="survey-content">
|
|
<div v-if="surveyStore.currentQuestion" class="question-container">
|
|
<h2 class="question-title">
|
|
{{ surveyStore.currentQuestionNumber }}. {{ surveyStore.currentQuestion.content }}
|
|
<span v-if="surveyStore.currentQuestion.type === 'MULTIPLE_CHOICE'" class="question-type">(多选)</span>
|
|
</h2>
|
|
<van-checkbox-group v-if="surveyStore.currentQuestion.type === 'MULTIPLE_CHOICE'" v-model="selectedOptions" class="options-container">
|
|
<van-cell-group inset>
|
|
<van-cell
|
|
v-for="option in surveyStore.currentOptions"
|
|
:key="option.code"
|
|
clickable
|
|
@click="toggleOption(option.code)"
|
|
>
|
|
<template #title>
|
|
<span>{{ option.code }}. {{ option.content }}</span>
|
|
<template v-if="option.requiresText && selectedOptions.includes(option.code)">
|
|
<van-field
|
|
v-model="textAnswer"
|
|
type="text"
|
|
placeholder="请输入具体内容"
|
|
class="option-text-input"
|
|
@click.stop
|
|
/>
|
|
</template>
|
|
</template>
|
|
<template #right-icon>
|
|
<van-checkbox
|
|
:name="option.code"
|
|
@click.stop
|
|
/>
|
|
</template>
|
|
</van-cell>
|
|
</van-cell-group>
|
|
</van-checkbox-group>
|
|
<van-radio-group v-else-if="surveyStore.currentQuestion.type === 'SINGLE_CHOICE'" v-model="selectedOption" class="options-container">
|
|
<van-cell-group inset>
|
|
<van-cell
|
|
v-for="option in surveyStore.currentOptions"
|
|
:key="option.code"
|
|
clickable
|
|
@click="selectedOption = option.code"
|
|
>
|
|
<template #title>
|
|
<span>{{ option.code }}. {{ option.content }}</span>
|
|
<template v-if="option.requiresText && selectedOption === option.code">
|
|
<van-field
|
|
v-model="textAnswer"
|
|
type="text"
|
|
placeholder="请输入具体内容"
|
|
class="option-text-input"
|
|
@click.stop
|
|
/>
|
|
</template>
|
|
</template>
|
|
<template #right-icon>
|
|
<van-radio :name="option.code" />
|
|
</template>
|
|
</van-cell>
|
|
</van-cell-group>
|
|
</van-radio-group>
|
|
<div v-else-if="surveyStore.currentQuestion.type === 'TEXT'" class="text-input-container">
|
|
<van-field
|
|
v-model="textAnswer"
|
|
type="textarea"
|
|
rows="4"
|
|
autosize
|
|
placeholder="请输入您的答案"
|
|
/>
|
|
</div>
|
|
<div class="button-container">
|
|
<van-button
|
|
type="primary"
|
|
block
|
|
:disabled="!isValidSelection"
|
|
@click="onNextQuestion"
|
|
>
|
|
{{ surveyStore.currentQuestion?.isLast ? '完成' : '下一题' }}
|
|
</van-button>
|
|
</div>
|
|
</div>
|
|
|
|
<van-empty v-else description="加载中..." />
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, onMounted, computed } from 'vue';
|
|
import { useRouter } from 'vue-router';
|
|
import { useUserStore } from '@/stores/user';
|
|
import { useSurveyStore } from '@/stores/survey';
|
|
import { showToast } from 'vant';
|
|
|
|
const router = useRouter();
|
|
const userStore = useUserStore();
|
|
const surveyStore = useSurveyStore();
|
|
|
|
const selectedOption = ref('');
|
|
const selectedOptions = ref([]);
|
|
const textAnswer = ref('');
|
|
const responses = ref([]);
|
|
|
|
// 计算当前选择是否有效
|
|
const isValidSelection = computed(() => {
|
|
const questionType = surveyStore.currentQuestion?.type;
|
|
if (!questionType) return false;
|
|
|
|
if (questionType === 'MULTIPLE_CHOICE') {
|
|
// 检查是否有选中的选项需要填写文本
|
|
const needsText = selectedOptions.value.some(code => {
|
|
const option = surveyStore.currentOptions.find(opt => opt.code === code);
|
|
return option?.requiresText;
|
|
});
|
|
return selectedOptions.value.length > 0 && (!needsText || (textAnswer.value?.trim() || '').length > 0);
|
|
} else if (questionType === 'SINGLE_CHOICE') {
|
|
// 检查单选的选项是否需要文本且已填写
|
|
const selectedOpt = surveyStore.currentOptions.find(opt => opt.code === selectedOption.value);
|
|
return selectedOption.value && (!selectedOpt?.requiresText || (textAnswer.value?.trim() || '').length > 0);
|
|
} else if (questionType === 'TEXT') {
|
|
return (textAnswer.value?.trim() || '').length > 0;
|
|
}
|
|
return false;
|
|
});
|
|
|
|
// 切换多选选项
|
|
function toggleOption(code) {
|
|
const index = selectedOptions.value.indexOf(code);
|
|
if (index === -1) {
|
|
selectedOptions.value.push(code);
|
|
} else {
|
|
selectedOptions.value.splice(index, 1);
|
|
}
|
|
}
|
|
|
|
// 返回上一页
|
|
function onBack() {
|
|
router.back();
|
|
}
|
|
|
|
// 获取下一个问题
|
|
async function onNextQuestion() {
|
|
const questionType = surveyStore.currentQuestion.type;
|
|
try {
|
|
let currentSelection = [];
|
|
|
|
if (questionType === 'MULTIPLE_CHOICE') {
|
|
currentSelection = selectedOptions.value;
|
|
} else if (questionType === 'SINGLE_CHOICE') {
|
|
currentSelection = [selectedOption.value];
|
|
}
|
|
|
|
// 保存当前答案
|
|
const currentResponse = {
|
|
questionId: surveyStore.currentQuestion.id,
|
|
selectedOptions: currentSelection,
|
|
textAnswer: textAnswer.value?.trim() || null
|
|
};
|
|
|
|
// 更新或添加当前答案
|
|
const existingIndex = responses.value.findIndex(r => r.questionId === currentResponse.questionId);
|
|
if (existingIndex !== -1) {
|
|
responses.value[existingIndex] = currentResponse;
|
|
} else {
|
|
responses.value.push(currentResponse);
|
|
}
|
|
|
|
// 保存答题进度到本地存储
|
|
localStorage.setItem('surveyProgress', JSON.stringify({
|
|
currentQuestionNumber: surveyStore.currentQuestionNumber,
|
|
responses: responses.value
|
|
}));
|
|
|
|
// 如果是最后一题,直接提交答案
|
|
if (surveyStore.currentQuestion.isLast) {
|
|
try {
|
|
await surveyStore.submitSurveyResponses(userStore.userId, responses.value);
|
|
showToast('问卷提交成功');
|
|
surveyStore.isCompleted = true;
|
|
// 清除本地存储的进度
|
|
localStorage.removeItem('surveyProgress');
|
|
// 跳转到完成页面
|
|
router.replace('/completed');
|
|
return;
|
|
} catch (error) {
|
|
console.error('提交问卷失败:', error);
|
|
showToast(error.response?.data?.message || '提交失败,请重试');
|
|
return;
|
|
}
|
|
}
|
|
|
|
// 获取下一题
|
|
try {
|
|
await surveyStore.fetchNextQuestion(userStore.userId, currentSelection);
|
|
// 重置选项
|
|
selectedOption.value = '';
|
|
selectedOptions.value = [];
|
|
textAnswer.value = '';
|
|
} catch (error) {
|
|
console.error('获取下一题失败:', error);
|
|
showToast(error.response?.data?.message || '获取下一题失败,请重试');
|
|
}
|
|
} catch (error) {
|
|
console.error('问卷处理失败:', error);
|
|
showToast(error.response?.data?.message || '操作失败,请重试');
|
|
}
|
|
}
|
|
|
|
// 初始化问卷
|
|
async function initSurvey() {
|
|
try {
|
|
// 检查是否有保存的进度
|
|
const savedProgress = localStorage.getItem('surveyProgress');
|
|
if (savedProgress) {
|
|
const progress = JSON.parse(savedProgress);
|
|
responses.value = progress.responses || [];
|
|
|
|
// 恢复当前题目的选项状态
|
|
const lastResponse = responses.value[responses.value.length - 1];
|
|
if (lastResponse) {
|
|
if (lastResponse.selectedOptions.length === 1) {
|
|
selectedOption.value = lastResponse.selectedOptions[0];
|
|
} else {
|
|
selectedOptions.value = lastResponse.selectedOptions;
|
|
}
|
|
textAnswer.value = lastResponse.textAnswer || '';
|
|
}
|
|
|
|
// 获取当前问题
|
|
const nextQuestionNumber = progress.currentQuestionNumber + 1;
|
|
await surveyStore.fetchNextQuestion(
|
|
userStore.userId,
|
|
[],
|
|
nextQuestionNumber
|
|
);
|
|
|
|
// 如果获取失败(可能是最后一题),尝试获取当前题目
|
|
if (!surveyStore.currentQuestion) {
|
|
await surveyStore.fetchNextQuestion(
|
|
userStore.userId,
|
|
[],
|
|
progress.currentQuestionNumber
|
|
);
|
|
}
|
|
} else {
|
|
surveyStore.resetSurvey(); // 确保重置所有状态
|
|
responses.value = [];
|
|
// 初始化时不传递任何选项
|
|
await surveyStore.fetchNextQuestion(userStore.userId);
|
|
}
|
|
} catch (error) {
|
|
console.error('初始化问卷失败:', error);
|
|
showToast('初始化问卷失败,请重试');
|
|
}
|
|
}
|
|
|
|
// 组件挂载时初始化
|
|
onMounted(() => {
|
|
if (!userStore.userId) {
|
|
router.replace('/register');
|
|
return;
|
|
}
|
|
initSurvey();
|
|
});
|
|
</script>
|
|
|
|
<style scoped>
|
|
.survey-view {
|
|
min-height: 100vh;
|
|
background-color: #f7f8fa;
|
|
}
|
|
|
|
.survey-content {
|
|
padding: 20px;
|
|
}
|
|
|
|
.question-container {
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.question-title {
|
|
font-size: 18px;
|
|
font-weight: bold;
|
|
line-height: 1.5;
|
|
margin-bottom: 16px;
|
|
padding: 0 16px;
|
|
}
|
|
|
|
.options-container {
|
|
margin-top: 20px;
|
|
}
|
|
|
|
.button-container {
|
|
margin-top: 24px;
|
|
padding: 0 16px;
|
|
}
|
|
|
|
.survey-completed {
|
|
padding: 40px 20px;
|
|
text-align: center;
|
|
}
|
|
|
|
.restart-button {
|
|
margin-top: 20px;
|
|
width: 80%;
|
|
}
|
|
|
|
.question-type {
|
|
font-size: 14px;
|
|
color: #666;
|
|
margin-left: 8px;
|
|
}
|
|
|
|
.text-input-container {
|
|
padding: 16px;
|
|
background-color: #fff;
|
|
border-radius: 8px;
|
|
margin: 0 16px;
|
|
}
|
|
|
|
.completion-message {
|
|
margin: 16px 0;
|
|
color: #666;
|
|
line-height: 1.6;
|
|
}
|
|
|
|
.home-button {
|
|
margin-top: 24px;
|
|
width: 80%;
|
|
}
|
|
|
|
.option-text-input {
|
|
margin-top: 8px;
|
|
background-color: #f8f8f8;
|
|
border-radius: 4px;
|
|
}
|
|
</style> |