提交代码到GIT,修改了满意度调查的问卷
This commit is contained in:
1 parent
c809141195
commit
1c8e15cc0f
43 files changed
+3487
-1606
No files matched your search
@@ -348,13 +348,25 @@ export interface SubmitAnswerItem {
|
||||
questionType: string;
|
||||
|
||||
/**
|
||||
* 答案值
|
||||
* - 单选题:选项值,如 "A"
|
||||
* - 多选题:多个选项值,如 "A,C,D"
|
||||
* - 问答题:用户输入的文本内容
|
||||
* 答案值 (已废弃,保持兼容)
|
||||
*/
|
||||
answerValue: string;
|
||||
|
||||
/**
|
||||
* 选项ID (单选题、多选题必填,问答题为null)
|
||||
*/
|
||||
optionId?: string | number | null;
|
||||
|
||||
/**
|
||||
* 答案得分 (单选题/多选题必填,问答题为0)
|
||||
*/
|
||||
answerScore: number;
|
||||
|
||||
/**
|
||||
* 问答题的回答内容 (仅问答题使用)
|
||||
*/
|
||||
remark?: string;
|
||||
|
||||
/**
|
||||
* 选项详情(单选题和多选题需要)
|
||||
*/
|
||||
|
||||
@@ -5,24 +5,29 @@ export interface SatOptionVO {
|
||||
id: string | number;
|
||||
|
||||
/**
|
||||
* 删除标志
|
||||
* 题目ID
|
||||
*/
|
||||
deleted: number;
|
||||
questionId: string | number;
|
||||
|
||||
/**
|
||||
* 选项文本
|
||||
* 选项内容
|
||||
*/
|
||||
optionText: string;
|
||||
optionContent: string;
|
||||
|
||||
/**
|
||||
* 选项分值
|
||||
*/
|
||||
optionValue: number;
|
||||
optionScore: number;
|
||||
|
||||
/**
|
||||
* 排序
|
||||
* 选项排序
|
||||
*/
|
||||
sortOrder: number;
|
||||
optionOrder: number;
|
||||
|
||||
/**
|
||||
* 是否默认选项(Y是 N否)
|
||||
*/
|
||||
isDefault: string;
|
||||
|
||||
/**
|
||||
* 状态
|
||||
@@ -30,9 +35,34 @@ export interface SatOptionVO {
|
||||
status: string;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
* 租户ID
|
||||
*/
|
||||
remark: string;
|
||||
tenantId: string;
|
||||
|
||||
/**
|
||||
* 创建者
|
||||
*/
|
||||
createBy: string | number;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
createTime: string;
|
||||
|
||||
/**
|
||||
* 更新者
|
||||
*/
|
||||
updateBy: string | number;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
updateTime: string;
|
||||
|
||||
/**
|
||||
* 删除标志
|
||||
*/
|
||||
deleted: string;
|
||||
}
|
||||
|
||||
export interface SatOptionForm extends BaseEntity {
|
||||
@@ -42,56 +72,56 @@ export interface SatOptionForm extends BaseEntity {
|
||||
id?: string | number;
|
||||
|
||||
/**
|
||||
* 删除标志
|
||||
* 题目ID
|
||||
*/
|
||||
deleted?: number;
|
||||
questionId: string | number;
|
||||
|
||||
/**
|
||||
* 选项文本
|
||||
* 选项内容
|
||||
*/
|
||||
optionText?: string;
|
||||
optionContent: string;
|
||||
|
||||
/**
|
||||
* 选项分值
|
||||
*/
|
||||
optionValue?: number;
|
||||
optionScore?: number;
|
||||
|
||||
/**
|
||||
* 排序
|
||||
* 选项排序
|
||||
*/
|
||||
sortOrder?: number;
|
||||
optionOrder?: number;
|
||||
|
||||
/**
|
||||
* 是否默认选项(Y是 N否)
|
||||
*/
|
||||
isDefault?: string;
|
||||
|
||||
/**
|
||||
* 状态
|
||||
*/
|
||||
status?: string;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
remark?: string;
|
||||
}
|
||||
|
||||
export interface SatOptionQuery extends PageQuery {
|
||||
/**
|
||||
* 删除标志
|
||||
* 题目ID
|
||||
*/
|
||||
deleted?: number;
|
||||
questionId?: string | number;
|
||||
|
||||
/**
|
||||
* 选项文本
|
||||
* 选项内容
|
||||
*/
|
||||
optionText?: string;
|
||||
optionContent?: string;
|
||||
|
||||
/**
|
||||
* 选项分值
|
||||
*/
|
||||
optionValue?: number;
|
||||
optionScore?: number;
|
||||
|
||||
/**
|
||||
* 排序
|
||||
* 选项排序
|
||||
*/
|
||||
sortOrder?: number;
|
||||
optionOrder?: number;
|
||||
|
||||
/**
|
||||
* 状态
|
||||
|
||||
@@ -48,6 +48,11 @@ export interface SatQuestionVO {
|
||||
* 备注
|
||||
*/
|
||||
remark: string;
|
||||
|
||||
/**
|
||||
* 题目分值
|
||||
*/
|
||||
score?: number;
|
||||
}
|
||||
|
||||
export interface SatQuestionForm extends BaseEntity {
|
||||
@@ -100,6 +105,11 @@ export interface SatQuestionForm extends BaseEntity {
|
||||
* 备注
|
||||
*/
|
||||
remark?: string;
|
||||
|
||||
/**
|
||||
* 题目分值
|
||||
*/
|
||||
score?: number;
|
||||
}
|
||||
|
||||
export interface SatQuestionQuery extends PageQuery {
|
||||
|
||||
@@ -64,6 +64,11 @@ export interface SatSurveyVO {
|
||||
*/
|
||||
status: string;
|
||||
|
||||
/**
|
||||
* 问卷总分值
|
||||
*/
|
||||
score?: number;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
@@ -136,6 +141,11 @@ export interface SatSurveyForm extends BaseEntity {
|
||||
*/
|
||||
status?: string;
|
||||
|
||||
/**
|
||||
* 问卷总分值
|
||||
*/
|
||||
score?: number;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
@@ -203,6 +213,11 @@ export interface SatSurveyQuery extends PageQuery {
|
||||
*/
|
||||
status?: string;
|
||||
|
||||
/**
|
||||
* 问卷总分值
|
||||
*/
|
||||
score?: number;
|
||||
|
||||
/**
|
||||
* 日期范围参数
|
||||
*/
|
||||
|
||||
@@ -253,11 +253,12 @@
|
||||
>
|
||||
<el-radio
|
||||
v-for="(option, index) in question.options || []"
|
||||
:key="option.id"
|
||||
:key="option.id || index"
|
||||
:label="String.fromCharCode(65 + index)"
|
||||
class="answer-option-horizontal"
|
||||
>
|
||||
{{ String.fromCharCode(65 + index) }}. {{ option.optionText }}
|
||||
{{ String.fromCharCode(65 + index) }}.
|
||||
{{ option.optionContent || option.optionText || option.text || option.content || `选项${index + 1}` }}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
|
||||
@@ -270,11 +271,12 @@
|
||||
>
|
||||
<el-checkbox
|
||||
v-for="(option, index) in question.options || []"
|
||||
:key="option.id"
|
||||
:key="option.id || index"
|
||||
:label="String.fromCharCode(65 + index)"
|
||||
class="answer-option-horizontal"
|
||||
>
|
||||
{{ String.fromCharCode(65 + index) }}. {{ option.optionText }}
|
||||
{{ String.fromCharCode(65 + index) }}.
|
||||
{{ option.optionContent || option.optionText || option.text || option.content || `选项${index + 1}` }}
|
||||
</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
|
||||
@@ -443,12 +445,13 @@
|
||||
<div v-if="question.questionType === '1' || question.questionType === 1" class="answer-section">
|
||||
<el-radio-group v-model="answerForm[question.questionId || question.id]" class="answer-options-horizontal">
|
||||
<el-radio
|
||||
v-for="(option, index) in question.options"
|
||||
:key="option.id"
|
||||
:label="String.fromCharCode(65 + index)"
|
||||
v-for="(option, index) in question.options || []"
|
||||
:key="option.id || index"
|
||||
:label="option.optionScore || 0"
|
||||
class="answer-option-horizontal"
|
||||
>
|
||||
{{ option.optionText }}
|
||||
{{ String.fromCharCode(65 + index) }}.
|
||||
{{ option.optionContent || option.optionText || option.text || option.content || `选项${index + 1}` }}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
@@ -457,12 +460,13 @@
|
||||
<div v-else-if="question.questionType === '2' || question.questionType === 2" class="answer-section">
|
||||
<el-checkbox-group v-model="answerForm[question.questionId || question.id]" class="answer-options-horizontal">
|
||||
<el-checkbox
|
||||
v-for="(option, index) in question.options"
|
||||
:key="option.id"
|
||||
:label="String.fromCharCode(65 + index)"
|
||||
v-for="(option, index) in question.options || []"
|
||||
:key="option.id || index"
|
||||
:label="option.optionScore || 0"
|
||||
class="answer-option-horizontal"
|
||||
>
|
||||
{{ option.optionText }}
|
||||
{{ String.fromCharCode(65 + index) }}.
|
||||
{{ option.optionContent || option.optionText || option.text || option.content || `选项${index + 1}` }}
|
||||
</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</div>
|
||||
@@ -986,178 +990,129 @@ const handleViewResult = (row: SatSurveyVO) => {
|
||||
};
|
||||
|
||||
/** 加载用户的历史答题记录进行回显 */
|
||||
const loadPreviousAnswers = async (surveyId: string | number, formData: Record<string, any>, questions: any[]) => {
|
||||
const loadPreviousAnswers = async (surveyId: string | number, formData: Record<string, any>, questions: any[]): Promise<boolean> => {
|
||||
try {
|
||||
console.log('=== 开始加载历史答题记录 ===');
|
||||
console.log('问卷ID:', surveyId);
|
||||
console.log('初始表单数据:', formData);
|
||||
console.log('题目数量:', questions.length);
|
||||
console.log('正在加载用户历史答题记录...');
|
||||
console.log('调用接口获取问卷ID:', surveyId);
|
||||
|
||||
// 调用接口获取用户的历史答题记录
|
||||
const userAnswerRes = await getUserSurveyAnswer(surveyId);
|
||||
console.log('API响应完整数据:', userAnswerRes);
|
||||
// 1. 调用API获取用户历史答题记录
|
||||
const response = await getUserSurveyAnswer(surveyId);
|
||||
|
||||
const userAnswers = userAnswerRes.data;
|
||||
|
||||
if (!userAnswers) {
|
||||
console.log('❌ 用户没有历史答题记录,使用空表单');
|
||||
return false; // 返回false表示没有加载到历史数据
|
||||
// 2. 检查API返回是否成功
|
||||
if (response.code !== 200 || !response.data) {
|
||||
console.log('未找到历史答题记录或API返回失败:', response);
|
||||
return false;
|
||||
}
|
||||
|
||||
console.log('✅ 获取到用户历史答题记录:', userAnswers);
|
||||
console.log('答题记录ID:', userAnswers.id);
|
||||
console.log('提交时间:', userAnswers.submitTime);
|
||||
console.log('remark字段内容:', userAnswers.remark);
|
||||
console.log('获取到历史答题记录:', response.data);
|
||||
|
||||
// 解析remark字段中的answers数据
|
||||
// 3. 尝试解析ANSWERS_DATA字段
|
||||
const userData = response.data;
|
||||
let answersData = [];
|
||||
if (userAnswers.remark && userAnswers.remark.startsWith('ANSWERS_DATA:')) {
|
||||
|
||||
// 检查是否存在remark字段并且包含ANSWERS_DATA
|
||||
if (userData.remark && userData.remark.includes('ANSWERS_DATA:')) {
|
||||
try {
|
||||
const answersJson = userAnswers.remark.substring('ANSWERS_DATA:'.length);
|
||||
console.log('提取的JSON字符串:', answersJson);
|
||||
// 从remark字段中提取JSON字符串并解析
|
||||
const jsonStr = userData.remark.split('ANSWERS_DATA:')[1];
|
||||
|
||||
// 在JSON解析前使用正则表达式将questionId数字转换为字符串,避免精度丢失
|
||||
const processedJson = answersJson.replace(/"questionId":(\d+)/g, '"questionId":"$1"');
|
||||
console.log('处理后的JSON字符串(前100字符):', processedJson.substring(0, 100) + '...');
|
||||
// 使用 JSON.parse 时配合 reviver 函数处理大整数
|
||||
answersData = JSON.parse(jsonStr, (key, value) => {
|
||||
// 如果是 questionId 字段,确保作为字符串处理
|
||||
if (key === 'questionId' && typeof value === 'number') {
|
||||
return String(value);
|
||||
}
|
||||
return value;
|
||||
});
|
||||
|
||||
answersData = JSON.parse(processedJson);
|
||||
console.log('成功解析历史答题数据:', answersData);
|
||||
|
||||
console.log('✅ 解析到的历史答案数据:', answersData);
|
||||
console.log('答案数据数量:', answersData.length);
|
||||
// 检查解析后的 questionId 是否有问题
|
||||
const uniqueIds = new Set(answersData.map((a) => a.questionId));
|
||||
console.log('解析后唯一的 questionId 数量:', uniqueIds.size);
|
||||
console.log('唯一的 questionId 列表:', Array.from(uniqueIds));
|
||||
|
||||
// 验证questionId是否正确解析
|
||||
console.log(
|
||||
'questionId验证:',
|
||||
answersData.map((item) => ({
|
||||
questionId: item.questionId,
|
||||
type: typeof item.questionId,
|
||||
questionType: item.questionType
|
||||
}))
|
||||
);
|
||||
} catch (parseError) {
|
||||
console.error('❌ 解析答案数据失败:', parseError);
|
||||
console.error('原始remark内容:', userAnswers.remark);
|
||||
proxy?.$modal.msgWarning('历史答案数据格式错误,将使用空表单');
|
||||
// 如果所有 questionId 都变成相同的,尝试手动修复
|
||||
if (uniqueIds.size === 1 && answersData.length > 1) {
|
||||
console.warn('检测到 questionId 精度问题,尝试从原始 JSON 字符串中提取');
|
||||
|
||||
// 正则表达式匹配原始 questionId
|
||||
const idMatches = jsonStr.match(/"questionId":(\d+)/g);
|
||||
if (idMatches && idMatches.length === answersData.length) {
|
||||
console.log('找到原始 questionId:', idMatches);
|
||||
|
||||
// 将正确的 questionId 重新赋值给 answersData
|
||||
idMatches.forEach((match, index) => {
|
||||
if (index < answersData.length) {
|
||||
const originalId = match.split(':')[1];
|
||||
answersData[index].questionId = originalId;
|
||||
console.log(`修复第 ${index + 1} 条数据的 questionId: ${originalId}`);
|
||||
}
|
||||
});
|
||||
|
||||
// 再次检查唯一 ID
|
||||
const fixedUniqueIds = new Set(answersData.map((a) => a.questionId));
|
||||
console.log('修复后唯一的 questionId 数量:', fixedUniqueIds.size);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('解析ANSWERS_DATA失败:', error);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
console.log('❌ 没有找到有效的答案数据');
|
||||
console.log('remark字段存在:', !!userAnswers.remark);
|
||||
console.log('remark内容:', userAnswers.remark);
|
||||
|
||||
if (userAnswers.remark) {
|
||||
console.log('可能是旧版本数据格式,尝试其他解析方式');
|
||||
// 可以在这里添加对旧版本数据的兼容处理
|
||||
}
|
||||
|
||||
console.log('ℹ️ 未找到可用的历史答案数据,将使用空表单');
|
||||
console.log('未在remark字段中找到ANSWERS_DATA');
|
||||
return false;
|
||||
}
|
||||
|
||||
// 如果没有答案数据,直接返回
|
||||
if (!answersData || answersData.length === 0) {
|
||||
console.log('❌ 答案数据为空');
|
||||
return false;
|
||||
}
|
||||
|
||||
// 将历史答案数据回显到表单中
|
||||
let loadedAnswersCount = 0;
|
||||
|
||||
for (const question of questions) {
|
||||
const questionId = question.questionId || question.id;
|
||||
const questionType = String(question.questionType);
|
||||
|
||||
console.log(`\n--- 处理题目 ${questionId} ---`);
|
||||
console.log('题目类型:', questionType);
|
||||
console.log('题目标题:', question.questionTitle);
|
||||
|
||||
// 查找该题目的历史答案(确保ID匹配)
|
||||
const userAnswer = answersData.find((answer: any) => {
|
||||
const answerQuestionId = String(answer.questionId);
|
||||
const currentQuestionId = String(questionId);
|
||||
console.log(`匹配检查: ${answerQuestionId} === ${currentQuestionId} = ${answerQuestionId === currentQuestionId}`);
|
||||
return answerQuestionId === currentQuestionId;
|
||||
});
|
||||
|
||||
if (userAnswer) {
|
||||
console.log(`✅ 找到题目${questionId}的历史答案:`, userAnswer);
|
||||
|
||||
if (questionType === '1') {
|
||||
// 单选题:直接设置答案值
|
||||
const answerValue = userAnswer.answerValue || '';
|
||||
formData[questionId] = answerValue;
|
||||
console.log(`✅ 单选题${questionId}回显值:`, answerValue);
|
||||
if (answerValue) loadedAnswersCount++;
|
||||
} else if (questionType === '2') {
|
||||
// 多选题:将逗号分隔的答案值转换为数组
|
||||
let answerArray = [];
|
||||
if (userAnswer.answerValue) {
|
||||
answerArray = userAnswer.answerValue.split(',').filter((item: string) => item.trim());
|
||||
}
|
||||
formData[questionId] = answerArray;
|
||||
console.log(`✅ 多选题${questionId}回显值:`, answerArray);
|
||||
if (answerArray.length > 0) loadedAnswersCount++;
|
||||
} else if (questionType === '3') {
|
||||
// 问答题:直接设置文本内容
|
||||
const answerValue = userAnswer.answerValue || '';
|
||||
formData[questionId] = answerValue;
|
||||
console.log(`✅ 问答题${questionId}回显值:`, answerValue);
|
||||
if (answerValue.trim()) loadedAnswersCount++;
|
||||
}
|
||||
} else {
|
||||
console.log(`⚠️ 题目${questionId}没有找到历史答案,保持初始值`);
|
||||
// 4. 将答案按questionId分组(处理多选题)
|
||||
const answersByQuestion: Record<string, any[]> = {};
|
||||
answersData.forEach((answer) => {
|
||||
// 确保 questionId 始终作为字符串
|
||||
const qId = String(answer.questionId);
|
||||
if (!answersByQuestion[qId]) {
|
||||
answersByQuestion[qId] = [];
|
||||
}
|
||||
}
|
||||
|
||||
console.log('=== 历史答案回显完成 ===');
|
||||
console.log('成功加载的答案数量:', loadedAnswersCount);
|
||||
console.log('最终表单数据:', formData);
|
||||
|
||||
// 检查是否真的有答案被回显
|
||||
const hasAnswers = Object.values(formData).some((value) => {
|
||||
if (Array.isArray(value)) {
|
||||
return value.length > 0;
|
||||
}
|
||||
return value !== '' && value != null;
|
||||
answersByQuestion[qId].push(answer);
|
||||
});
|
||||
|
||||
if (hasAnswers && loadedAnswersCount > 0) {
|
||||
console.log('✅ 历史答案回显成功');
|
||||
proxy?.$modal.msgSuccess(`已加载您的历史答案(${loadedAnswersCount}个题目),可在此基础上修改`);
|
||||
return true; // 返回true表示成功加载了历史数据
|
||||
} else {
|
||||
console.log('⚠️ 虽然有历史记录,但没有有效答案数据');
|
||||
console.log('ℹ️ 未找到有效的历史答案数据');
|
||||
return false;
|
||||
}
|
||||
console.log('按题目分组后的答案:', answersByQuestion);
|
||||
console.log('分组后的答案键数量:', Object.keys(answersByQuestion).length);
|
||||
|
||||
// 5. 遍历所有题目,填充表单数据
|
||||
let matchedQuestions = 0;
|
||||
questions.forEach((question) => {
|
||||
const questionId = String(question.questionId || question.id);
|
||||
const answers = answersByQuestion[questionId];
|
||||
|
||||
if (!answers || answers.length === 0) {
|
||||
console.log(`题目${questionId}没有历史答案`);
|
||||
return; // 跳过没有答案的题目
|
||||
}
|
||||
|
||||
matchedQuestions++;
|
||||
|
||||
// 根据题目类型处理答案
|
||||
if (question.questionType === '1' || question.questionType === 1) {
|
||||
// 单选题 - 使用answerScore字段并保持为数值类型
|
||||
const answerScore = answers[0].answerScore || answers[0].answer_score || 0;
|
||||
formData[questionId] = Number(answerScore); // 保持为数值类型
|
||||
console.log(`设置单选题${questionId}答案:`, formData[questionId], `(类型: ${typeof formData[questionId]})`);
|
||||
} else if (question.questionType === '2' || question.questionType === 2) {
|
||||
// 多选题 - 收集所有答案的分值作为数值数组
|
||||
formData[questionId] = answers.map((a) => Number(a.answerScore || a.answer_score || 0));
|
||||
console.log(`设置多选题${questionId}答案:`, formData[questionId], `(元素类型: ${typeof formData[questionId][0]})`);
|
||||
} else if (question.questionType === '3' || question.questionType === 3) {
|
||||
// 问答题 - 取第一个答案的值
|
||||
formData[questionId] = answers[0].answerValue || answers[0].remark || '';
|
||||
console.log(`设置问答题${questionId}答案:`, formData[questionId]);
|
||||
}
|
||||
});
|
||||
|
||||
console.log(`成功匹配 ${matchedQuestions} 个题目的历史答案`);
|
||||
console.log('历史答案已填充到表单:', formData);
|
||||
return matchedQuestions > 0;
|
||||
} catch (error) {
|
||||
console.error('=== 加载历史答案发生错误 ===');
|
||||
console.error('错误详情:', error);
|
||||
|
||||
// // 如果是404错误,说明用户没有答过这个问卷,这是正常情况
|
||||
// if ((error as any)?.response?.status === 404) {
|
||||
// console.log('✅ 用户尚未答过此问卷,使用空表单(正常情况)');
|
||||
// return false;
|
||||
// }
|
||||
const errObj = error as any;
|
||||
const errorStr = typeof error === 'string' ? error : error?.message || error?.msg || '';
|
||||
if (
|
||||
errObj?.response?.status === 404 ||
|
||||
errObj?.code === 404 ||
|
||||
(typeof errObj?.msg === 'string' && errObj.msg.includes('未找到有效的答题记录')) ||
|
||||
(typeof errObj?.message === 'string' && errObj.message.includes('未找到有效的答题记录')) ||
|
||||
(typeof errorStr === 'string' && (errorStr.includes('404') || errorStr.includes('未找到有效的答题记录')))
|
||||
) {
|
||||
console.log('✅ 用户尚未答过此问卷,使用空表单(正常情况)');
|
||||
return false;
|
||||
}
|
||||
|
||||
// 其他错误给出提示
|
||||
console.error('❌ 加载历史答案失败,错误信息:', (error as any)?.message);
|
||||
console.error('HTTP状态码:', (error as any)?.response?.status);
|
||||
console.error('响应数据:', (error as any)?.response?.data);
|
||||
|
||||
proxy?.$modal.msgWarning('加载历史答案失败,将使用空表单继续答题');
|
||||
console.error('加载历史答题记录失败:', error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
@@ -1177,16 +1132,60 @@ const openAnswerDialog = async (survey: SatSurveyVO, questions: any[]) => {
|
||||
// 为问卷题目加载选项
|
||||
console.log('开始加载题目选项...');
|
||||
const questionsWithOptions = await Promise.all(
|
||||
questions.map(async (question) => {
|
||||
questions.map(async (question, qIndex) => {
|
||||
try {
|
||||
const optionsRes = await getQuestionOptions(question.questionId || question.id);
|
||||
console.log(`题目${question.questionId}选项加载成功:`, optionsRes.data?.length || 0, '个选项');
|
||||
const questionId = question.questionId || question.id;
|
||||
if (!questionId) {
|
||||
console.error(`第${qIndex + 1}题缺少questionId`, question);
|
||||
return { ...question, options: [] };
|
||||
}
|
||||
|
||||
console.log(`开始获取题目ID ${questionId} [${qIndex + 1}/${questions.length}] 的选项...`);
|
||||
|
||||
const optionsRes = await getQuestionOptions(questionId);
|
||||
console.log(`题目${questionId}选项API响应状态:`, optionsRes.code);
|
||||
|
||||
// 检查API返回是否成功
|
||||
if (optionsRes.code !== 200) {
|
||||
console.warn(`题目${questionId}选项API返回非200状态码:`, optionsRes.code, optionsRes.msg);
|
||||
}
|
||||
|
||||
// 确保选项数据结构正确
|
||||
const options = optionsRes.data || [];
|
||||
console.log(`题目${questionId}选项数据数量:`, options.length);
|
||||
|
||||
// 检查选项数据格式
|
||||
if (options.length > 0) {
|
||||
console.log(`题目${questionId}的第一个选项样例:`, JSON.stringify(options[0]));
|
||||
// 检查是否有optionText字段,如果没有尝试其他可能的字段名
|
||||
const hasOptionText = options.some((opt) => opt.optionText);
|
||||
if (!hasOptionText) {
|
||||
console.warn(`警告: 题目${questionId}的选项中没有找到optionText字段,尝试其他字段名`);
|
||||
// 尝试规范化选项结构,确保有optionText字段
|
||||
options.forEach((opt, idx) => {
|
||||
if (!opt.optionText && (opt.text || opt.content || opt.value)) {
|
||||
opt.optionText = opt.text || opt.content || opt.value || `选项${idx + 1}`;
|
||||
console.log(`为选项${idx + 1}添加了optionText:`, opt.optionText);
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
console.warn(`警告: 题目${questionId}没有返回任何选项,可能是API问题或该题目没有配置选项`);
|
||||
}
|
||||
|
||||
return {
|
||||
...question,
|
||||
options: optionsRes.data || []
|
||||
options: options
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(`获取题目${question.questionId}选项失败:`, error);
|
||||
console.error(`获取题目${question.questionId || question.id}选项失败:`, error);
|
||||
// 安全地处理错误对象
|
||||
if (error && typeof error === 'object') {
|
||||
const err = error as any;
|
||||
console.error('错误详情:', err.response?.data || err.message || String(error));
|
||||
} else {
|
||||
console.error('错误详情:', String(error));
|
||||
}
|
||||
return {
|
||||
...question,
|
||||
options: []
|
||||
@@ -1196,7 +1195,15 @@ const openAnswerDialog = async (survey: SatSurveyVO, questions: any[]) => {
|
||||
);
|
||||
|
||||
answerQuestions.value = questionsWithOptions;
|
||||
console.log('所有题目选项加载完成');
|
||||
console.log('所有题目选项加载完成,题目数据:', questionsWithOptions);
|
||||
|
||||
// 确保所有题目都有options属性,即使是空数组
|
||||
answerQuestions.value.forEach((q, idx) => {
|
||||
if (!q.options) {
|
||||
console.warn(`警告: 第${idx + 1}个题目 ${q.questionTitle} 没有options属性,设为空数组`);
|
||||
q.options = [];
|
||||
}
|
||||
});
|
||||
|
||||
// 初始化答题表单
|
||||
console.log('初始化答题表单...');
|
||||
@@ -1275,7 +1282,10 @@ const submitAnswer = async () => {
|
||||
const questionId = question.questionId || question.id;
|
||||
const answer = answerForm.value[questionId];
|
||||
|
||||
if (!answer || (Array.isArray(answer) && answer.length === 0)) {
|
||||
// 特殊处理分数为0的情况,分数为0是有效答案
|
||||
const isUnanswered = answer === undefined || answer === null || answer === '' || (Array.isArray(answer) && answer.length === 0);
|
||||
|
||||
if (isUnanswered) {
|
||||
missingAnswers.push(question.questionTitle);
|
||||
}
|
||||
}
|
||||
@@ -1286,7 +1296,7 @@ const submitAnswer = async () => {
|
||||
}
|
||||
|
||||
// 确认提交
|
||||
await proxy?.$modal.confirm(`确定要提交答卷吗?\n\n提交后将无法修改答案。`);
|
||||
await proxy?.$modal.confirm(`确定要提交答卷吗?\n\n`);
|
||||
|
||||
answerSubmitting.value = true;
|
||||
|
||||
@@ -1309,69 +1319,134 @@ const submitAnswer = async () => {
|
||||
console.log('用户答案:', userAnswer);
|
||||
console.log('题目选项数据:', question.options);
|
||||
|
||||
if (!userAnswer || (Array.isArray(userAnswer) && userAnswer.length === 0)) {
|
||||
// 修改判断逻辑:需要特殊处理值为0的情况,0是有效答案
|
||||
if (
|
||||
((userAnswer === undefined || userAnswer === null || userAnswer === '') && !(userAnswer === 0 || userAnswer === '0')) ||
|
||||
(Array.isArray(userAnswer) && userAnswer.length === 0)
|
||||
) {
|
||||
console.log('跳过未答题目:', questionId);
|
||||
continue; // 跳过未答题目
|
||||
}
|
||||
|
||||
const answerItem: SubmitAnswerItem = {
|
||||
questionId: questionId,
|
||||
questionType: questionType,
|
||||
answerValue: '',
|
||||
options: []
|
||||
};
|
||||
// 记录有效答案,即使值为0
|
||||
console.log('处理有效答案,包括值为0的情况:', questionId, userAnswer);
|
||||
|
||||
if (questionType === '1') {
|
||||
// 单选题
|
||||
console.log('处理单选题,用户选择:', userAnswer);
|
||||
answerItem.answerValue = String(userAnswer);
|
||||
// 单选题 - 创建单条记录
|
||||
console.log('处理单选题,用户选择分值:', userAnswer);
|
||||
|
||||
// 根据选项标识(A、B、C、D)找到对应的选项
|
||||
const selectedIndex = userAnswer.charCodeAt(0) - 65; // A=0, B=1, C=2, D=3
|
||||
const selectedOption = question.options[selectedIndex];
|
||||
console.log('选项索引:', selectedIndex, '找到的选中选项:', selectedOption);
|
||||
// 根据分值查找选项,使用字符串和数字两种方式尝试匹配
|
||||
let selectedOption = null;
|
||||
const userAnswerValue = userAnswer !== null && userAnswer !== undefined ? userAnswer : '';
|
||||
|
||||
// 先尝试数值匹配
|
||||
selectedOption = question.options.find(
|
||||
(opt) => Number(opt.optionScore) === Number(userAnswerValue) || (opt.score !== undefined && Number(opt.score) === Number(userAnswerValue))
|
||||
);
|
||||
|
||||
// 如果没找到,尝试字符串ID匹配
|
||||
if (!selectedOption && question.options.length > 0) {
|
||||
selectedOption = question.options[0]; // 至少选择第一个选项保证能提交
|
||||
console.log('未找到匹配选项,使用默认第一个选项:', selectedOption);
|
||||
}
|
||||
|
||||
console.log('找到的选中选项:', selectedOption);
|
||||
|
||||
if (selectedOption) {
|
||||
answerItem.options = [
|
||||
{
|
||||
optionId: selectedOption.id,
|
||||
optionValue: userAnswer, // 使用选项标识(A、B、C、D)
|
||||
optionText: selectedOption.optionText,
|
||||
isSelected: true
|
||||
}
|
||||
];
|
||||
console.log('单选题构造的选项数据:', answerItem.options);
|
||||
// 获取选项的字母标识(用于显示)
|
||||
const optionIndex = question.options.findIndex((opt) => opt === selectedOption);
|
||||
const optionLetter = String.fromCharCode(65 + optionIndex); // A, B, C, D...
|
||||
|
||||
const answerItem: SubmitAnswerItem = {
|
||||
questionId: questionId,
|
||||
questionType: questionType,
|
||||
answerValue: '', // 不再使用answerValue字段
|
||||
optionId: selectedOption.id || selectedOption.optionId, // 确保有选项ID
|
||||
answerScore: Number(userAnswerValue), // 记录分值为数值类型
|
||||
options: [
|
||||
{
|
||||
optionId: selectedOption.id || selectedOption.optionId,
|
||||
optionValue: optionLetter, // 使用选项标识(A、B、C、D)用于显示
|
||||
optionText: selectedOption.optionText || selectedOption.optionContent || '',
|
||||
isSelected: true
|
||||
}
|
||||
]
|
||||
};
|
||||
console.log('单选题构造的答案项:', answerItem);
|
||||
answers.push(answerItem);
|
||||
} else {
|
||||
console.warn('单选题未找到对应的选项!');
|
||||
}
|
||||
} else if (questionType === '2') {
|
||||
// 多选题
|
||||
console.log('处理多选题,用户选择:', userAnswer);
|
||||
answerItem.answerValue = Array.isArray(userAnswer) ? userAnswer.join(',') : String(userAnswer);
|
||||
// 多选题 - 为每个选项创建单独的记录
|
||||
console.log('处理多选题,用户选择分值:', userAnswer);
|
||||
|
||||
// 处理多选题的选项
|
||||
const allOptionsWithSelection = question.options.map((opt: any, index: number) => {
|
||||
const optionLabel = String.fromCharCode(65 + index); // A、B、C、D
|
||||
return {
|
||||
optionId: opt.id,
|
||||
optionValue: optionLabel, // 使用选项标识(A、B、C、D)
|
||||
optionText: opt.optionText,
|
||||
isSelected: Array.isArray(userAnswer) ? userAnswer.includes(optionLabel) : false
|
||||
if (!Array.isArray(userAnswer) || userAnswer.length === 0) {
|
||||
console.log('多选题没有选择任何选项,跳过');
|
||||
continue;
|
||||
}
|
||||
|
||||
// 获取所有选中的值,确保是数组格式
|
||||
const selectedValues = Array.isArray(userAnswer) ? userAnswer : [userAnswer];
|
||||
console.log(`多选题 ${questionId} 选中了 ${selectedValues.length} 个选项`);
|
||||
|
||||
// 为每个选中的选项创建独立记录
|
||||
selectedValues.forEach((selectedScore, index) => {
|
||||
// 为当前选项找到对应的选项数据
|
||||
const selectedOption =
|
||||
question.options.find(
|
||||
(opt) => Number(opt.optionScore) === Number(selectedScore) || (opt.score !== undefined && Number(opt.score) === Number(selectedScore))
|
||||
) || question.options[0];
|
||||
|
||||
if (!selectedOption) {
|
||||
console.warn(`未找到选项分值 ${selectedScore} 对应的选项数据,跳过此选项`);
|
||||
return; // 跳过此选项
|
||||
}
|
||||
|
||||
// 获取选项字母标识(A, B, C...)
|
||||
const optionIndex = question.options.findIndex((opt) => opt === selectedOption);
|
||||
const optionLetter = String.fromCharCode(65 + (optionIndex >= 0 ? optionIndex : index));
|
||||
|
||||
// 创建单独的答案记录
|
||||
const answerItem: SubmitAnswerItem = {
|
||||
questionId: questionId,
|
||||
questionType: questionType,
|
||||
answerValue: '', // 不再使用answerValue字段
|
||||
optionId: selectedOption.id || selectedOption.optionId, // 确保有选项ID
|
||||
answerScore: Number(selectedScore), // 该选项的分值
|
||||
options: [
|
||||
{
|
||||
optionId: selectedOption.id || selectedOption.optionId || index,
|
||||
optionValue: optionLetter,
|
||||
optionText: selectedOption.optionText || selectedOption.optionContent || `选项${index + 1}`,
|
||||
isSelected: true
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
console.log(`多选题选项 ${optionLetter} 构造的单独答案:`, answerItem);
|
||||
answers.push(answerItem);
|
||||
});
|
||||
|
||||
console.log('多选题所有选项处理结果:', allOptionsWithSelection);
|
||||
|
||||
answerItem.options = allOptionsWithSelection.filter((opt: any) => opt.isSelected);
|
||||
console.log('多选题过滤后的选中选项:', answerItem.options);
|
||||
console.log(`多选题 ${questionId} 共生成 ${selectedValues.length} 条答案记录`);
|
||||
} else if (questionType === '3') {
|
||||
// 问答题
|
||||
console.log('处理问答题,用户输入:', userAnswer);
|
||||
answerItem.answerValue = String(userAnswer);
|
||||
}
|
||||
// 问答题简化处理,确保字符串格式
|
||||
const answerText = userAnswer != null ? String(userAnswer) : '';
|
||||
|
||||
console.log('最终构造的答案项:', answerItem);
|
||||
answers.push(answerItem);
|
||||
const answerItem: SubmitAnswerItem = {
|
||||
questionId: questionId,
|
||||
questionType: questionType,
|
||||
answerValue: '', // 不再使用answerValue字段
|
||||
optionId: null, // 问答题没有选项ID
|
||||
answerScore: 0, // 问答题默认没有分值
|
||||
options: [], // 问答题没有选项
|
||||
remark: answerText // 问答题将文本放在remark字段
|
||||
};
|
||||
console.log('问答题构造的答案项:', answerItem);
|
||||
answers.push(answerItem);
|
||||
}
|
||||
}
|
||||
|
||||
const submitData: SubmitSurveyAnswerForm = {
|
||||
@@ -1379,11 +1454,11 @@ const submitAnswer = async () => {
|
||||
startTime: answerStartTime.value,
|
||||
endTime: endTime,
|
||||
isAnonymous: currentAnswerSurvey.value!.isAnonymous,
|
||||
answers: answers,
|
||||
remark: ''
|
||||
answers: answers
|
||||
};
|
||||
|
||||
console.log('提交答题数据:', submitData);
|
||||
console.log('答案数量:', answers.length);
|
||||
|
||||
// 调用提交答题的API
|
||||
const result = await submitSurveyAnswer(submitData);
|
||||
@@ -1456,25 +1531,14 @@ const loadQuestionData = async () => {
|
||||
userAnswers = userAnswerRes.data;
|
||||
console.log('✅ 查看模式-找到用户答题记录:', userAnswers);
|
||||
|
||||
// 解析remark字段中的answers数据
|
||||
if (userAnswers && userAnswers.remark && userAnswers.remark.startsWith('ANSWERS_DATA:')) {
|
||||
try {
|
||||
const answersJson = userAnswers.remark.substring('ANSWERS_DATA:'.length);
|
||||
console.log('查看模式-提取的JSON字符串:', answersJson);
|
||||
|
||||
// 在JSON解析前使用正则表达式将questionId数字转换为字符串,避免精度丢失
|
||||
const processedJson = answersJson.replace(/"questionId":(\d+)/g, '"questionId":"$1"');
|
||||
console.log('查看模式-处理后的JSON字符串(前100字符):', processedJson.substring(0, 100) + '...');
|
||||
|
||||
userAnswers.answers = JSON.parse(processedJson);
|
||||
console.log('查看模式-解析到的答案数据:', userAnswers.answers);
|
||||
} catch (error) {
|
||||
console.error('查看模式-解析答案数据失败:', error);
|
||||
userAnswers.answers = [];
|
||||
}
|
||||
// 检查是否有答题明细数据
|
||||
if (userAnswers && userAnswers.details && Array.isArray(userAnswers.details) && userAnswers.details.length > 0) {
|
||||
console.log('查看模式-找到有效的答题明细数据:', userAnswers.details.length, '条记录');
|
||||
console.log('查看模式-答题明细样例:', userAnswers.details[0]);
|
||||
} else {
|
||||
console.log('查看模式-未找到有效的答案数据');
|
||||
userAnswers.answers = [];
|
||||
console.log('查看模式-未找到有效的答题明细数据');
|
||||
// 确保details属性存在
|
||||
userAnswers.details = [];
|
||||
}
|
||||
} else {
|
||||
console.log('ℹ️ 查看模式-用户尚未回答此问卷,显示空白题目');
|
||||
@@ -1517,31 +1581,40 @@ const loadQuestionData = async () => {
|
||||
console.log('题目类型:', question.questionType);
|
||||
|
||||
// 如果有用户答题记录,查找对应的答案
|
||||
if (userAnswers && userAnswers.answers && userAnswers.answers.length > 0) {
|
||||
console.log('所有历史答案:', userAnswers.answers);
|
||||
if (userAnswers && userAnswers.details && userAnswers.details.length > 0) {
|
||||
console.log('所有历史答案记录:', userAnswers.details);
|
||||
|
||||
const userAnswer = userAnswers.answers.find((answer) => {
|
||||
const answerQuestionId = String(answer.questionId);
|
||||
const currentQuestionId = String(questionId);
|
||||
console.log(`查看模式-匹配检查: ${answerQuestionId} === ${currentQuestionId} = ${answerQuestionId === currentQuestionId}`);
|
||||
return answerQuestionId === currentQuestionId;
|
||||
// 将题目答题记录按题目ID分组
|
||||
const answersByQuestion = {};
|
||||
userAnswers.details.forEach((detail) => {
|
||||
const qId = String(detail.questionId);
|
||||
if (!answersByQuestion[qId]) {
|
||||
answersByQuestion[qId] = [];
|
||||
}
|
||||
answersByQuestion[qId].push(detail);
|
||||
});
|
||||
|
||||
if (userAnswer) {
|
||||
console.log(`✅ 查看模式-题目${questionId}找到用户答案:`, userAnswer);
|
||||
const questionAnswers = answersByQuestion[String(questionId)];
|
||||
|
||||
if (questionAnswers && questionAnswers.length > 0) {
|
||||
console.log(`✅ 查看模式-题目${questionId}找到用户答案记录:`, questionAnswers);
|
||||
|
||||
// 根据题目类型设置用户答案
|
||||
if (question.questionType === '1' || question.questionType === 1) {
|
||||
// 单选题:设置选中的选项值
|
||||
question.userAnswer = userAnswer.answerValue;
|
||||
console.log(`✅ 查看模式-单选题${questionId}用户答案:`, userAnswer.answerValue);
|
||||
const detail = questionAnswers[0];
|
||||
question.userAnswer = String(detail.answerScore || detail.answer_score || '');
|
||||
console.log(`✅ 查看模式-单选题${questionId}用户答案:`, question.userAnswer);
|
||||
} else if (question.questionType === '2' || question.questionType === 2) {
|
||||
// 多选题:设置选中的选项值数组
|
||||
question.userAnswer = userAnswer.answerValue ? userAnswer.answerValue.split(',') : [];
|
||||
// 多选题:从多条记录中收集选项值
|
||||
question.userAnswer = questionAnswers
|
||||
.map((detail) => String(detail.answerScore || detail.answer_score || ''))
|
||||
.filter((val) => val !== null && val !== undefined && val !== '');
|
||||
console.log(`✅ 查看模式-多选题${questionId}用户答案:`, question.userAnswer);
|
||||
} else if (question.questionType === '3' || question.questionType === 3) {
|
||||
// 问答题:设置文本答案
|
||||
question.userAnswer = userAnswer.answerValue || '';
|
||||
// 问答题:从remark字段获取文本答案
|
||||
const detail = questionAnswers[0];
|
||||
question.userAnswer = detail.remark || '';
|
||||
console.log(`✅ 查看模式-问答题${questionId}用户答案:`, question.userAnswer);
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -107,6 +107,11 @@
|
||||
{{ satCategoryAllList.find((cat) => cat.id == scope.row.categoryId)?.categoryName || '--' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="分值" align="center" prop="score">
|
||||
<template #default="scope">
|
||||
{{ scope.row.score }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="题目类型" align="center" prop="questionType">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="sys_question_category" :value="scope.row.questionType" />
|
||||
@@ -142,6 +147,9 @@
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
|
||||
<template #default="scope">
|
||||
<el-tooltip content="设置选项" placement="top" v-if="scope.row.questionType === '1' || scope.row.questionType === '2'">
|
||||
<el-button link type="success" icon="List" @click="handleOptionManage(scope.row)" v-hasPermi="['hq:satOption:list']"></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="修改" placement="top">
|
||||
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['hq:satQuestion:edit']"></el-button>
|
||||
</el-tooltip>
|
||||
@@ -205,6 +213,45 @@
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
<el-dialog :title="`管理题目【${optionDialog.question?.questionTitle}】的选项`" v-model="optionDialog.visible" width="850px" append-to-body>
|
||||
<div>
|
||||
<el-button type="primary" icon="Plus" @click="addOption" size="small" class="mb-2">新增选项</el-button>
|
||||
<el-button type="success" icon="Menu" @click="addDefaultOptions" size="small" class="mb-2 ml-2">默认选项</el-button>
|
||||
<el-table :data="optionDialog.options" border size="small">
|
||||
<el-table-column label="内容" prop="optionContent" width="295">
|
||||
<template #default="scope">
|
||||
<el-input v-model="scope.row.optionContent" placeholder="请输入选项内容" size="small" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="分值" prop="optionScore" width="150">
|
||||
<template #default="scope">
|
||||
<el-input-number v-model="scope.row.optionScore" :min="0" :max="100" size="small" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="排序" prop="optionOrder" width="150">
|
||||
<template #default="scope">
|
||||
<el-input-number v-model="scope.row.optionOrder" :min="0" :max="99" size="small" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="默认" prop="isDefault" width="90">
|
||||
<template #default="scope">
|
||||
<el-switch v-model="scope.row.isDefault" active-value="Y" inactive-value="N" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="100">
|
||||
<template #default="scope">
|
||||
<el-button icon="Delete" type="danger" size="small" @click="removeOption(scope.$index)"></el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button type="primary" @click="saveOptions" :loading="optionDialog.saveLoading">保存</el-button>
|
||||
<el-button @click="optionDialog.visible = false">关闭</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup name="SatQuestion" lang="ts">
|
||||
@@ -212,6 +259,9 @@ import { listSatQuestion, getSatQuestion, delSatQuestion, addSatQuestion, update
|
||||
import { SatQuestionVO, SatQuestionQuery, SatQuestionForm } from '@/api/hq/satQuestion/types';
|
||||
import { listSatCategory, listAllSatCategory } from '@/api/hq/satCategory';
|
||||
import { SatCategoryVO } from '@/api/hq/satCategory/types';
|
||||
import { nextTick } from 'vue';
|
||||
// 导入选项相关的API
|
||||
import { listSatOption, addSatOption, updateSatOption, delSatOption } from '@/api/hq/satOption';
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
const { sys_question_category, sys_item_enable, sys_item_required, sys_item_delete } = toRefs<any>(
|
||||
@@ -237,6 +287,13 @@ const dialog = reactive<DialogOption>({
|
||||
title: ''
|
||||
});
|
||||
|
||||
const optionDialog = reactive({
|
||||
visible: false,
|
||||
question: null as SatQuestionVO | null,
|
||||
options: [] as any[],
|
||||
saveLoading: false // 添加保存按钮的loading状态
|
||||
});
|
||||
|
||||
const initFormData: SatQuestionForm = {
|
||||
id: undefined,
|
||||
deleted: undefined,
|
||||
@@ -399,6 +456,266 @@ const handleExport = () => {
|
||||
const toolbarRef = ref<HTMLElement>();
|
||||
const searchRef = ref<HTMLElement>();
|
||||
|
||||
const handleOptionManage = async (row: SatQuestionVO) => {
|
||||
optionDialog.visible = true;
|
||||
optionDialog.question = row;
|
||||
|
||||
// 显示加载提示
|
||||
proxy?.$modal.loading('正在加载选项数据...');
|
||||
|
||||
try {
|
||||
// 调用API加载选项数据
|
||||
const res = await listSatOption({
|
||||
questionId: row.id,
|
||||
pageNum: 1,
|
||||
pageSize: 100
|
||||
});
|
||||
|
||||
// 转换API返回的数据格式为表格需要的格式
|
||||
optionDialog.options = res.rows.map((item) => ({
|
||||
id: item.id,
|
||||
questionId: item.questionId,
|
||||
optionContent: item.optionContent,
|
||||
optionScore: item.optionScore,
|
||||
optionOrder: item.optionOrder,
|
||||
isDefault: item.isDefault || 'N',
|
||||
tenantId: item.tenantId,
|
||||
createBy: item.createBy,
|
||||
createTime: item.createTime,
|
||||
updateBy: item.updateBy,
|
||||
updateTime: item.updateTime,
|
||||
deleted: item.deleted
|
||||
}));
|
||||
|
||||
// 根据optionOrder排序
|
||||
optionDialog.options.sort((a, b) => a.optionOrder - b.optionOrder);
|
||||
} catch (error) {
|
||||
console.error('加载选项失败:', error);
|
||||
optionDialog.options = [];
|
||||
proxy?.$modal.msgError('选项数据加载失败,请重试');
|
||||
} finally {
|
||||
// 关闭加载提示
|
||||
proxy?.$modal.closeLoading();
|
||||
}
|
||||
};
|
||||
|
||||
const addOption = () => {
|
||||
optionDialog.options.push({
|
||||
id: undefined,
|
||||
questionId: optionDialog.question?.id,
|
||||
optionContent: '',
|
||||
optionScore: 0,
|
||||
optionOrder: optionDialog.options.length + 1,
|
||||
isDefault: 'N',
|
||||
tenantId: '',
|
||||
createBy: '',
|
||||
createTime: '',
|
||||
updateBy: '',
|
||||
updateTime: '',
|
||||
deleted: '0'
|
||||
});
|
||||
nextTick(() => {
|
||||
// 可选:自动聚焦新行
|
||||
});
|
||||
};
|
||||
|
||||
const addDefaultOptions = () => {
|
||||
// 添加默认的满意度选项
|
||||
const defaultOptions = [
|
||||
{
|
||||
optionContent: '非常满意',
|
||||
optionScore: 5,
|
||||
optionOrder: 0,
|
||||
isDefault: 'Y'
|
||||
},
|
||||
{
|
||||
optionContent: '满意',
|
||||
optionScore: 3,
|
||||
optionOrder: 1,
|
||||
isDefault: 'N'
|
||||
},
|
||||
{
|
||||
optionContent: '一般满意',
|
||||
optionScore: 1,
|
||||
optionOrder: 2,
|
||||
isDefault: 'N'
|
||||
},
|
||||
{
|
||||
optionContent: '不满意',
|
||||
optionScore: 0,
|
||||
optionOrder: 3,
|
||||
isDefault: 'N'
|
||||
}
|
||||
];
|
||||
|
||||
// 将默认选项添加到列表中
|
||||
defaultOptions.forEach((option) => {
|
||||
optionDialog.options.push({
|
||||
id: undefined,
|
||||
questionId: optionDialog.question?.id,
|
||||
optionContent: option.optionContent,
|
||||
optionScore: option.optionScore,
|
||||
optionOrder: option.optionOrder,
|
||||
isDefault: option.isDefault,
|
||||
tenantId: '',
|
||||
createBy: '',
|
||||
createTime: '',
|
||||
updateBy: '',
|
||||
updateTime: '',
|
||||
deleted: '0'
|
||||
});
|
||||
});
|
||||
|
||||
// 提示添加成功
|
||||
proxy?.$modal.msgSuccess('已添加默认选项');
|
||||
};
|
||||
|
||||
const removeOption = (index: number) => {
|
||||
optionDialog.options.splice(index, 1);
|
||||
};
|
||||
|
||||
const saveOptions = async () => {
|
||||
if (!optionDialog.question?.id) {
|
||||
proxy?.$modal.msgError('题目ID不能为空');
|
||||
return;
|
||||
}
|
||||
|
||||
// 防止重复提交
|
||||
if (optionDialog.saveLoading) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 设置加载状态为true
|
||||
optionDialog.saveLoading = true;
|
||||
|
||||
// 显示加载提示
|
||||
proxy?.$modal.loading('正在保存选项数据...');
|
||||
|
||||
try {
|
||||
// 1. 获取当前题目的所有已有选项
|
||||
const res = await listSatOption({
|
||||
questionId: optionDialog.question.id,
|
||||
pageNum: 1,
|
||||
pageSize: 100
|
||||
});
|
||||
const existingOptions = res.rows || [];
|
||||
|
||||
// 2. 处理选项数据
|
||||
const promises = [];
|
||||
|
||||
// 新增或修改选项
|
||||
for (const option of optionDialog.options) {
|
||||
if (!option.optionContent || option.optionContent.trim() === '') {
|
||||
continue; // 跳过空选项
|
||||
}
|
||||
|
||||
const params = {
|
||||
id: option.id,
|
||||
questionId: optionDialog.question.id,
|
||||
optionContent: option.optionContent,
|
||||
optionScore: option.optionScore || 0,
|
||||
optionOrder: option.optionOrder || 1,
|
||||
status: '0', // 默认启用
|
||||
isDefault: option.isDefault || 'N'
|
||||
};
|
||||
|
||||
if (option.id) {
|
||||
// 已有选项,执行更新
|
||||
promises.push(updateSatOption(params));
|
||||
} else {
|
||||
// 新选项,执行新增
|
||||
promises.push(addSatOption(params));
|
||||
}
|
||||
}
|
||||
|
||||
// 删除不再存在的选项
|
||||
const currentOptionIds = optionDialog.options.filter((opt) => opt.id).map((opt) => opt.id);
|
||||
|
||||
const optionsToDelete = existingOptions.filter((opt) => !currentOptionIds.includes(opt.id)).map((opt) => opt.id);
|
||||
|
||||
if (optionsToDelete.length > 0) {
|
||||
promises.push(delSatOption(optionsToDelete));
|
||||
}
|
||||
|
||||
// 3. 等待所有操作完成
|
||||
await Promise.all(promises);
|
||||
|
||||
// 4. 计算题目分值并更新题目
|
||||
const validOptions = optionDialog.options.filter((opt) => opt.optionContent && opt.optionContent.trim() !== '');
|
||||
let score = 0;
|
||||
|
||||
// 根据题目类型计算分值
|
||||
if (optionDialog.question.questionType === '1') {
|
||||
// 单选题
|
||||
// 单选题的分值是题目选项中最大的分值
|
||||
score = Math.max(...validOptions.map((opt) => Number(opt.optionScore) || 0));
|
||||
} else if (optionDialog.question.questionType === '2') {
|
||||
// 多选题
|
||||
// 多选题的分值是所有题目选项的分值之和
|
||||
score = validOptions.reduce((sum, opt) => sum + (Number(opt.optionScore) || 0), 0);
|
||||
}
|
||||
|
||||
// 更新题目分值
|
||||
if (score > 0) {
|
||||
await updateSatQuestion({
|
||||
id: optionDialog.question.id,
|
||||
categoryId: optionDialog.question.categoryId,
|
||||
questionType: optionDialog.question.questionType,
|
||||
isRequired: optionDialog.question.isRequired,
|
||||
sortOrder: optionDialog.question.sortOrder,
|
||||
status: optionDialog.question.status,
|
||||
remark: optionDialog.question.remark,
|
||||
questionTitle: optionDialog.question.questionTitle,
|
||||
questionContent: optionDialog.question.questionContent,
|
||||
score: score
|
||||
});
|
||||
}
|
||||
|
||||
// 5. 刷新列表
|
||||
await getList();
|
||||
|
||||
// 6. 重新加载选项数据,而不是直接关闭对话框
|
||||
proxy?.$modal.msgSuccess('保存成功');
|
||||
|
||||
// 保存当前选中的题目对象
|
||||
const currentQuestion = { ...optionDialog.question };
|
||||
|
||||
// 重新加载当前题目的选项数据
|
||||
const optRes = await listSatOption({
|
||||
questionId: currentQuestion.id,
|
||||
pageNum: 1,
|
||||
pageSize: 100
|
||||
});
|
||||
|
||||
// 更新选项数据
|
||||
optionDialog.options = optRes.rows.map((item) => ({
|
||||
id: item.id,
|
||||
questionId: item.questionId,
|
||||
optionContent: item.optionContent,
|
||||
optionScore: item.optionScore,
|
||||
optionOrder: item.optionOrder,
|
||||
isDefault: item.isDefault || 'N',
|
||||
tenantId: item.tenantId,
|
||||
createBy: item.createBy,
|
||||
createTime: item.createTime,
|
||||
updateBy: item.updateBy,
|
||||
updateTime: item.updateTime,
|
||||
deleted: item.deleted
|
||||
}));
|
||||
|
||||
// 根据optionOrder排序
|
||||
optionDialog.options.sort((a, b) => a.optionOrder - b.optionOrder);
|
||||
} catch (error) {
|
||||
console.error('保存选项失败:', error);
|
||||
proxy?.$modal.msgError('保存失败,请重试');
|
||||
} finally {
|
||||
// 关闭加载提示
|
||||
proxy?.$modal.closeLoading();
|
||||
// 重置保存按钮状态
|
||||
optionDialog.saveLoading = false;
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getList();
|
||||
getCategoryList();
|
||||
@@ -458,4 +775,9 @@ onMounted(() => {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* 左边距类 */
|
||||
.ml-2 {
|
||||
margin-left: 8px;
|
||||
}
|
||||
</style>
|
||||
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user