xinli/xinli-ui/src/views/psychology/report/detail.vue
2025-12-02 17:09:22 +08:00

897 lines
34 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="app-container">
<el-card v-loading="loading">
<div slot="header" class="clearfix">
<span>测评报告详情</span>
<div style="float: right;">
<el-button
style="padding: 3px 0; margin-right: 10px;"
type="text"
icon="el-icon-edit"
@click="handleEdit"
v-hasPermi="['psychology:report:edit']"
>编辑</el-button>
<el-button style="padding: 3px 0" type="text" @click="handleBack">返回</el-button>
</div>
</div>
<el-descriptions title="基本信息" :column="2" border>
<el-descriptions-item label="报告ID">{{ reportForm.reportId }}</el-descriptions-item>
<el-descriptions-item label="来源类型">
<el-tag v-if="sourceType === 'questionnaire'" type="warning">问卷</el-tag>
<el-tag v-else type="primary">量表</el-tag>
</el-descriptions-item>
<el-descriptions-item label="来源ID">{{ reportForm.assessmentId || reportForm.answerId }}</el-descriptions-item>
<el-descriptions-item label="报告标题" :span="2">{{ reportForm.reportTitle || '-' }}</el-descriptions-item>
<el-descriptions-item label="报告类型">
<el-tag v-if="reportForm.reportType === 'standard'" type="">标准报告</el-tag>
<el-tag v-else-if="reportForm.reportType === 'detailed'" type="success">详细报告</el-tag>
<el-tag v-else-if="reportForm.reportType === 'brief'" type="info">简要报告</el-tag>
</el-descriptions-item>
<el-descriptions-item label="生成状态">
<el-tag v-if="reportForm.isGenerated === '0'" type="warning">未生成</el-tag>
<el-tag v-else-if="reportForm.isGenerated === '1'" type="success">已生成</el-tag>
</el-descriptions-item>
<el-descriptions-item label="生成时间">{{ parseTime(reportForm.generateTime, '{y}-{m}-{d} {h}:{i}:{s}') || '-' }}</el-descriptions-item>
<el-descriptions-item label="创建时间">{{ parseTime(reportForm.createTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</el-descriptions-item>
</el-descriptions>
<el-divider content-position="left">报告摘要</el-divider>
<div class="summary-content" v-html="reportForm.summary || '暂无摘要'"></div>
<el-divider content-position="left">报告内容</el-divider>
<div v-if="!reportForm.reportContent" style="padding: 20px; text-align: center; color: #999;">
报告内容正在生成中...
</div>
<div v-else class="report-content" v-html="reportForm.reportContent"></div>
<!-- 问卷成绩排名 -->
<el-divider v-if="sourceType === 'questionnaire' && reportForm.answerId" content-position="left">成绩排名</el-divider>
<div v-if="sourceType === 'questionnaire' && reportForm.answerId" class="rank-section">
<el-button type="primary" size="small" @click="loadRankList" :loading="rankLoading">查看排名</el-button>
<el-table v-if="rankList.length > 0" :data="rankList" border style="width: 100%; margin-top: 15px;">
<el-table-column type="index" label="排名" width="80" align="center">
<template slot-scope="scope">
<el-tag v-if="scope.$index + 1 === 1" type="danger">第1名</el-tag>
<el-tag v-else-if="scope.$index + 1 === 2" type="warning">第2名</el-tag>
<el-tag v-else-if="scope.$index + 1 === 3" type="success">第3名</el-tag>
<span v-else>第{{ scope.$index + 1 }}名</span>
</template>
</el-table-column>
<el-table-column prop="respondentName" label="答题人" width="150" />
<el-table-column prop="totalScore" label="总分" width="120" align="center" />
<el-table-column prop="submitTime" label="提交时间" width="180" align="center">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.submitTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
</el-table>
</div>
<!-- 答题详情(量表和问卷类型报告) -->
<el-divider v-if="(sourceType === 'assessment' && reportForm.assessmentId) || (sourceType === 'questionnaire' && reportForm.answerId)" content-position="left">答题详情</el-divider>
<div v-if="(sourceType === 'assessment' && reportForm.assessmentId) || (sourceType === 'questionnaire' && reportForm.answerId)" class="answer-detail-section">
<el-button type="primary" icon="el-icon-document" @click="handleViewAnswers" :loading="answerDetailLoading">
查看答题详情
</el-button>
</div>
<el-divider v-if="reportForm.pdfPath" content-position="left">PDF下载</el-divider>
<div v-if="reportForm.pdfPath">
<el-button type="primary" icon="el-icon-download" @click="handleDownloadPDF">下载PDF报告</el-button>
</div>
<!-- AI分析 -->
<el-divider content-position="left">AI智能分析</el-divider>
<div class="ai-analysis-section">
<el-button
type="primary"
icon="el-icon-magic-stick"
@click="handleAIAnalysis"
:loading="aiLoading"
:disabled="!reportForm.reportContent"
>
{{ aiLoading ? '分析中...' : 'AI分析' }}
</el-button>
<div v-if="aiError" class="ai-error">
<el-alert
:title="aiError"
type="error"
:closable="false"
show-icon>
</el-alert>
</div>
<div v-if="aiResult" class="ai-result">
<h3 class="ai-result-title">
<i class="el-icon-magic-stick"></i> AI分析结果
</h3>
<div class="ai-result-content" v-html="aiResult"></div>
</div>
</div>
</el-card>
<!-- 答题详情对话框 -->
<el-dialog title="答题详情" :visible.sync="answerDetailOpen" width="80%" append-to-body>
<div v-loading="answerDetailLoading">
<el-descriptions :column="2" border style="margin-bottom: 20px;">
<el-descriptions-item v-if="sourceType === 'assessment'" label="测评ID">{{ reportForm.assessmentId }}</el-descriptions-item>
<el-descriptions-item v-if="sourceType === 'questionnaire'" label="答题ID">{{ reportForm.answerId }}</el-descriptions-item>
<el-descriptions-item label="报告ID">{{ reportForm.reportId }}</el-descriptions-item>
<el-descriptions-item v-if="sourceType === 'assessment'" label="被测评人">{{ assessmentInfo.assesseeName || '-' }}</el-descriptions-item>
<el-descriptions-item v-if="sourceType === 'questionnaire'" label="答题人">{{ questionnaireInfo.respondentName || '-' }}</el-descriptions-item>
<el-descriptions-item label="提交时间">
<span v-if="sourceType === 'assessment' && assessmentInfo.submitTime">{{ parseTime(assessmentInfo.submitTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
<span v-else-if="sourceType === 'questionnaire' && questionnaireInfo.submitTime">{{ parseTime(questionnaireInfo.submitTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
<span v-else>-</span>
</el-descriptions-item>
<el-descriptions-item label="总分">
<span v-if="sourceType === 'assessment'">{{ assessmentInfo.totalScore || '-' }}</span>
<span v-else-if="sourceType === 'questionnaire'">{{ questionnaireInfo.totalScore || '-' }}</span>
<span v-else>-</span>
</el-descriptions-item>
<el-descriptions-item label="答题数量">{{ answerDetailList.length }} 题</el-descriptions-item>
</el-descriptions>
<el-divider content-position="left">答题详情</el-divider>
<el-table :data="answerDetailList" border style="width: 100%">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column label="题目序号" prop="itemNumber" width="100" align="center" />
<el-table-column label="题目内容" prop="itemContent" min-width="200" :show-overflow-tooltip="true" />
<el-table-column label="题目类型" prop="itemType" width="100" align="center">
<template slot-scope="scope">
<el-tag v-if="scope.row.itemType === 'single' || scope.row.itemType === 'radio'" type="primary" size="small">单选</el-tag>
<el-tag v-else-if="scope.row.itemType === 'multiple' || scope.row.itemType === 'checkbox'" type="success" size="small">多选</el-tag>
<el-tag v-else-if="scope.row.itemType === 'matrix'" type="warning" size="small">矩阵</el-tag>
<el-tag v-else-if="scope.row.itemType === 'text'" type="info" size="small">简答题</el-tag>
<el-tag v-else-if="scope.row.itemType === 'textarea'" type="warning" size="small">问答题</el-tag>
<el-tag v-else-if="scope.row.itemType === 'essay'" type="danger" size="small">作文题</el-tag>
<el-tag v-else-if="scope.row.itemType === 'input'" type="" size="small">填空题</el-tag>
<span v-else>{{ scope.row.itemType }}</span>
</template>
</el-table-column>
<el-table-column label="答案" prop="answerDisplay" min-width="200" :show-overflow-tooltip="true" />
<el-table-column label="得分" prop="answerScore" width="100" align="center">
<template slot-scope="scope">
<span>{{ scope.row.answerScore || '-' }}</span>
</template>
</el-table-column>
</el-table>
</div>
<div slot="footer" class="dialog-footer">
<el-button @click="answerDetailOpen = false">关 闭</el-button>
</div>
</el-dialog>
<!-- 报告编辑对话框 -->
<el-dialog title="编辑报告" :visible.sync="editOpen" width="900px" append-to-body>
<el-form ref="editForm" :model="editForm" :rules="editRules" label-width="100px">
<el-form-item label="报告标题" prop="reportTitle">
<el-input v-model="editForm.reportTitle" placeholder="请输入报告标题" />
</el-form-item>
<el-form-item label="报告类型" prop="reportType">
<el-select v-model="editForm.reportType" placeholder="请选择报告类型">
<el-option label="标准报告" value="standard" />
<el-option label="详细报告" value="detailed" />
<el-option label="简要报告" value="brief" />
</el-select>
</el-form-item>
<el-form-item label="报告摘要" prop="summary">
<Editor v-model="editForm.summary" :min-height="150" />
</el-form-item>
<el-form-item label="报告内容" prop="reportContent">
<Editor v-model="editForm.reportContent" :min-height="400" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitEditForm">确 定</el-button>
<el-button @click="cancelEdit"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { getReport, getReportByAssessmentId, updateReportWithType } from "@/api/psychology/report";
import { getQuestionnaireRankList, getQuestionnaireAnswer, getAnswerDetails } from "@/api/psychology/questionnaireAnswer";
import { getQuestionnaireItems } from "@/api/psychology/questionnaireAnswer";
import { listQuestionnaireOption } from "@/api/psychology/questionnaireOption";
import { getAssessmentAnswers, getAssessmentItems, getAssessment } from "@/api/psychology/assessment";
import { listOption } from "@/api/psychology/option";
import request from '@/utils/request';
import axios from 'axios';
import Editor from "@/components/Editor";
export default {
name: "ReportDetail",
components: { Editor },
data() {
return {
loading: true,
reportForm: {},
sourceType: null,
rankList: [],
rankLoading: false,
// AI分析
aiLoading: false,
aiResult: '',
aiError: '',
// 编辑对话框
editOpen: false,
editForm: {},
editRules: {
reportTitle: [
{ required: true, message: "报告标题不能为空", trigger: "blur" }
],
reportType: [
{ required: true, message: "报告类型不能为空", trigger: "change" }
]
},
// 答题详情
answerDetailOpen: false,
answerDetailLoading: false,
answerDetailList: [],
assessmentInfo: {},
questionnaireInfo: {}
};
},
created() {
this.loadReport();
},
watch: {
// 监听路由变化,解决缓存问题
'$route'(to, from) {
if (to.path === '/psychology/report/detail') {
this.loadReport();
}
}
},
methods: {
/** 加载报告 */
loadReport() {
this.loading = true;
const reportId = this.$route.query.reportId;
const assessmentId = this.$route.query.assessmentId;
this.sourceType = this.$route.query.sourceType;
if (!reportId && !assessmentId) {
this.loading = false;
this.$modal.msgError("缺少报告ID或测评ID参数");
this.$router.push('/psychology/report');
return;
}
// 如果有reportId根据sourceType查询
if (reportId) {
console.log('开始加载报告reportId:', reportId, 'sourceType:', this.sourceType);
getReport(reportId, this.sourceType).then(response => {
console.log('报告加载响应:', response);
if (response && response.data) {
console.log('报告数据:', response.data);
console.log('报告内容:', response.data.reportContent);
this.reportForm = response.data;
} else {
console.warn('报告数据为空');
this.$modal.msgWarning("报告不存在");
}
this.loading = false;
}).catch(error => {
this.loading = false;
console.error('加载报告失败:', error);
console.error('错误详情:', error.response || error.message);
this.$modal.msgError("加载报告失败,请检查报告是否存在");
});
} else {
// 使用测评ID查询
getReportByAssessmentId(assessmentId).then(response => {
if (response.data) {
this.reportForm = response.data;
} else {
this.$modal.msgWarning("报告不存在");
}
this.loading = false;
}).catch(error => {
this.loading = false;
console.error('加载报告失败:', error);
this.$modal.msgError("加载报告失败,请检查报告是否存在");
});
}
},
/** 返回 */
handleBack() {
this.$router.back();
},
/** 下载PDF */
handleDownloadPDF() {
if (this.reportForm.pdfPath) {
window.open(this.reportForm.pdfPath);
} else {
this.$modal.msgWarning("PDF文件不存在");
}
},
/** 加载排名列表 */
loadRankList() {
if (!this.reportForm.answerId) {
this.$modal.msgWarning("无法获取问卷ID");
return;
}
// 从答题记录中获取问卷ID
request({
url: '/psychology/questionnaire/answer/' + this.reportForm.answerId,
method: 'get'
}).then(response => {
if (response.data && response.data.questionnaireId) {
this.rankLoading = true;
getQuestionnaireRankList(response.data.questionnaireId).then(rankResponse => {
this.rankList = rankResponse.data || [];
this.rankLoading = false;
}).catch(error => {
this.rankLoading = false;
console.error('加载排名失败:', error);
this.$modal.msgError("加载排名失败");
});
} else {
this.$modal.msgWarning("无法获取问卷ID");
}
}).catch(error => {
console.error('获取答题记录失败:', error);
this.$modal.msgError("获取答题记录失败");
});
},
/** 编辑按钮操作 */
handleEdit() {
this.editForm = {
reportId: this.reportForm.reportId,
sourceType: this.sourceType,
reportTitle: this.reportForm.reportTitle || '',
reportType: this.reportForm.reportType || 'standard',
summary: this.reportForm.summary || '',
reportContent: this.reportForm.reportContent || ''
};
this.editOpen = true;
},
/** 提交编辑表单 */
submitEditForm() {
this.$refs["editForm"].validate(valid => {
if (valid) {
const updateData = {
reportTitle: this.editForm.reportTitle,
reportType: this.editForm.reportType,
summary: this.editForm.summary,
reportContent: this.editForm.reportContent
};
updateReportWithType(this.editForm.reportId, this.editForm.sourceType, updateData).then(response => {
this.$modal.msgSuccess("修改成功");
this.editOpen = false;
// 重新加载报告数据
this.loadReport();
}).catch(error => {
console.error('修改报告失败:', error);
this.$modal.msgError("修改失败");
});
}
});
},
/** 取消编辑 */
cancelEdit() {
this.editOpen = false;
this.resetEditForm();
},
/** 重置编辑表单 */
resetEditForm() {
this.editForm = {
reportId: null,
sourceType: null,
reportTitle: '',
reportType: 'standard',
summary: '',
reportContent: ''
};
if (this.$refs["editForm"]) {
this.$refs["editForm"].resetFields();
}
},
/** AI分析 */
async handleAIAnalysis() {
if (!this.reportForm.reportContent) {
this.$modal.msgWarning("报告内容为空,无法进行分析");
return;
}
this.aiLoading = true;
this.aiError = '';
this.aiResult = '';
// Kimi API
const API_URL = 'https://api.moonshot.cn/v1/chat/completions';
const API_KEY = 'sk-U9fdriPxwBcrpWW0Ite3N0eVtX7VxnqqqYUIBAdWd1hgEA9m';
const MODEL = 'moonshot-v1-32k';
// 备用Ollama本地大模型配置
// const API_URL = 'http://192.168.0.106:11434/api/chat';
// const API_KEY = ''; // 本地模型不需要API Key
// const MODEL = 'deepseek-r1:32b';
// 构建系统提示词
const SYSTEM_PROMPT = [
'你是专业心理测评报告分析师,请根据用户提供的报告内容进行深度分析。要求:',
'1. 提取报告的核心信息和关键指标;',
'2. 分析测评结果的含义和可能的影响;',
'3. 提供专业、客观、易懂的分析解读500-800字',
'4. 使用结构化的格式输出,包含:核心结论、详细分析、建议、总体结论四个部分;',
'5. 仅输出分析结果,不添加额外建议、问候语或思考过程;',
'6. 使用HTML格式输出使用<h3>标签作为小标题,<p>标签作为段落。'
].join('\n');
// 构建完整的提示词
const reportContent = this.reportForm.reportContent || '';
const reportTitle = this.reportForm.reportTitle || '心理测评报告';
const reportType = this.reportForm.reportType || '标准报告';
// 提取纯文本内容去除HTML标签
const textContent = reportContent.replace(/<[^>]*>/g, '').substring(0, 3000);
const userPrompt = `重要:请直接输出结果,不要包含任何思考过程、<think>标签或<think>标签。\n\n报告标题${reportTitle}\n报告类型${reportType}\n报告内容${textContent}`;
try {
const { data } = await axios.post(API_URL, {
model: MODEL,
messages: [
{ role: 'system', content: SYSTEM_PROMPT },
{ role: 'user', content: userPrompt }
],
temperature: 0.2,
max_tokens: 1000,
stream: false
}, {
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json'
},
timeout: 60000 // 60秒超时
});
// 兼容 Kimi API (OpenAI 格式) 和 Ollama 格式
let rawResponse = data?.choices?.[0]?.message?.content ?? data?.message?.content ?? '无法解析模型输出';
// 过滤掉思考过程标签
rawResponse = rawResponse
.replace(/<think>[\s\S]*?<\/think>/gi, '')
.replace(/<think>[\s\S]*?<\/redacted_reasoning>/gi, '')
.replace(/<think[\s\S]*?>/gi, '')
.replace(/<redacted_reasoning[\s\S]*?>/gi, '')
// 移除Markdown代码块标记
.replace(/```html\s*/gi, '')
.replace(/```\s*/g, '')
.replace(/```[a-z]*\s*/gi, '')
.trim();
if (!rawResponse || rawResponse === '无法解析模型输出') {
this.aiError = '模型返回结果为空,请稍后重试';
return;
}
// 格式化结果确保HTML格式正确
this.aiResult = this.formatAIResult(rawResponse);
} catch (err) {
console.error('AI分析失败:', err);
if (err.code === 'ECONNABORTED' || err.message.includes('timeout')) {
this.aiError = '请求超时,请检查网络连接或稍后重试';
} else if (err.response) {
this.aiError = err.response.data?.error?.message || err.response.data?.error || err.message || 'AI分析失败请稍后重试';
} else if (err.request) {
this.aiError = '无法连接到Kimi API服务请检查网络连接当前' + API_URL + '';
} else {
this.aiError = err.message || 'AI分析失败请稍后重试';
}
} finally {
this.aiLoading = false;
}
},
/** 格式化AI分析结果 */
formatAIResult(text) {
// 移除Markdown代码块标记```html、```等)
let html = text
.replace(/```html\s*/gi, '')
.replace(/```\s*/g, '')
.replace(/```[a-z]*\s*/gi, '')
.trim();
// 如果已经是HTML格式清理后返回
if (html.includes('<h3>') || html.includes('<p>') || html.includes('<div>')) {
// 移除可能残留的代码块标记
html = html.replace(/```html\s*/gi, '').replace(/```\s*/g, '');
return html;
}
// 处理标题(以数字开头或包含"结论"、"分析"、"建议"等关键词的行)
html = html.replace(/^(\d+[\.、]?\s*[^\n]+)$/gm, '<h3>$1</h3>');
html = html.replace(/^([^\n]*(?:结论|分析|建议|总结|概述)[^\n]*)$/gm, '<h3>$1</h3>');
// 将段落分隔符转换为<p>标签
html = html.split('\n\n').map(para => {
para = para.trim();
if (!para) return '';
if (para.startsWith('<h3>')) return para;
return '<p>' + para.replace(/\n/g, '<br>') + '</p>';
}).join('');
// 确保每个段落都有正确的格式
html = html.replace(/(<p>.*?<\/p>)/g, (match) => {
if (match.includes('<h3>')) return match.replace(/<p>|<\/p>/g, '');
return match;
});
// 最后再次清理可能残留的代码块标记
html = html.replace(/```html\s*/gi, '').replace(/```\s*/g, '');
return html;
},
/** 查看答题详情 */
handleViewAnswers() {
// 判断是量表还是问卷
if (this.sourceType === 'assessment') {
if (!this.reportForm.assessmentId) {
this.$modal.msgWarning("无法获取测评ID");
return;
}
this.loadAssessmentAnswers();
} else if (this.sourceType === 'questionnaire') {
if (!this.reportForm.answerId) {
this.$modal.msgWarning("无法获取答题ID");
return;
}
this.loadQuestionnaireAnswers();
} else {
this.$modal.msgWarning("未知的报告类型");
return;
}
},
/** 加载量表答题详情 */
loadAssessmentAnswers() {
this.answerDetailOpen = true;
this.answerDetailLoading = true;
this.answerDetailList = [];
// 并行获取测评信息、题目列表和答案列表
Promise.all([
getAssessment(this.reportForm.assessmentId),
getAssessmentItems(this.reportForm.assessmentId),
getAssessmentAnswers(this.reportForm.assessmentId)
]).then(([assessmentRes, itemsRes, answersRes]) => {
this.assessmentInfo = assessmentRes.data || {};
const items = itemsRes.data || [];
const answers = answersRes.data || [];
// 创建答案映射表以itemId为key
const answerMap = {};
answers.forEach(answer => {
answerMap[answer.itemId] = answer;
});
// 获取所有题目的选项信息
const optionPromises = items.map(item => {
return listOption(item.itemId).then(optionRes => {
return {
itemId: item.itemId,
options: optionRes.data || []
};
}).catch(() => {
return {
itemId: item.itemId,
options: []
};
});
});
Promise.all(optionPromises).then(optionResults => {
// 创建选项映射表
const optionMap = {};
optionResults.forEach(result => {
optionMap[result.itemId] = result.options;
});
// 合并题目、答案和选项信息
this.answerDetailList = items.map(item => {
const answer = answerMap[item.itemId] || {};
const options = optionMap[item.itemId] || [];
// 格式化答案显示
let answerDisplay = '-';
if (answer.optionId) {
// 单选:查找选项内容
const option = options.find(opt => opt.optionId === answer.optionId);
if (option) {
answerDisplay = `${option.optionCode}. ${option.optionContent}`;
} else {
answerDisplay = `选项ID: ${answer.optionId}`;
}
} else if (answer.optionIds) {
// 多选:查找所有选项内容
const optionIdArray = answer.optionIds.split(',').map(id => id.trim());
const selectedOptions = options.filter(opt => optionIdArray.includes(String(opt.optionId)));
if (selectedOptions.length > 0) {
answerDisplay = selectedOptions.map(opt => `${opt.optionCode}. ${opt.optionContent}`).join('; ');
} else {
answerDisplay = `选项IDs: ${answer.optionIds}`;
}
} else if (answer.answerText) {
// 文本答案
answerDisplay = answer.answerText;
}
return {
itemId: item.itemId,
itemNumber: item.itemNumber,
itemContent: item.itemContent,
itemType: item.itemType,
answerDisplay: answerDisplay,
answerScore: answer.answerScore,
answerId: answer.answerId,
optionId: answer.optionId,
optionIds: answer.optionIds,
answerText: answer.answerText
};
});
this.answerDetailLoading = false;
}).catch(error => {
console.error('获取选项信息失败:', error);
this.$modal.msgError("获取选项信息失败");
this.answerDetailLoading = false;
});
}).catch(error => {
console.error('获取答题详情失败:', error);
this.$modal.msgError("获取答题详情失败");
this.answerDetailLoading = false;
});
},
/** 加载问卷答题详情 */
loadQuestionnaireAnswers() {
this.answerDetailOpen = true;
this.answerDetailLoading = true;
this.answerDetailList = [];
// 并行获取问卷答题信息、答案详情列表
Promise.all([
getQuestionnaireAnswer(this.reportForm.answerId),
getAnswerDetails(this.reportForm.answerId)
]).then(([answerRes, detailsRes]) => {
const answer = answerRes.data || {};
const details = detailsRes.data || [];
this.questionnaireInfo = answer;
if (!answer.questionnaireId) {
this.$modal.msgError("无法获取问卷ID");
this.answerDetailLoading = false;
return;
}
// 获取问卷题目列表
getQuestionnaireItems(answer.questionnaireId).then(itemsRes => {
const items = itemsRes.data || [];
// 创建题目映射表以itemId为key
const itemMap = {};
items.forEach(item => {
itemMap[item.itemId] = item;
});
// 创建答案详情映射表以itemId为key
const detailMap = {};
details.forEach(detail => {
detailMap[detail.itemId] = detail;
});
// 获取所有题目的选项信息
const optionPromises = items.map(item => {
return listQuestionnaireOption(item.itemId).then(optionRes => {
return {
itemId: item.itemId,
options: optionRes.data || []
};
}).catch(() => {
return {
itemId: item.itemId,
options: []
};
});
});
Promise.all(optionPromises).then(optionResults => {
// 创建选项映射表
const optionMap = {};
optionResults.forEach(result => {
optionMap[result.itemId] = result.options;
});
// 合并题目、答案和选项信息
this.answerDetailList = items.map(item => {
const detail = detailMap[item.itemId] || {};
const options = optionMap[item.itemId] || [];
// 格式化答案显示
let answerDisplay = '-';
if (detail.optionId) {
// 单选:查找选项内容
const option = options.find(opt => opt.optionId === detail.optionId);
if (option) {
answerDisplay = `${option.optionCode}. ${option.optionContent}`;
} else {
answerDisplay = `选项ID: ${detail.optionId}`;
}
} else if (detail.optionIds) {
// 多选:查找所有选项内容
const optionIdArray = detail.optionIds.split(',').map(id => id.trim());
const selectedOptions = options.filter(opt => optionIdArray.includes(String(opt.optionId)));
if (selectedOptions.length > 0) {
answerDisplay = selectedOptions.map(opt => `${opt.optionCode}. ${opt.optionContent}`).join('; ');
} else {
answerDisplay = `选项IDs: ${detail.optionIds}`;
}
} else if (detail.answerText) {
// 文本答案
answerDisplay = detail.answerText;
}
return {
itemId: item.itemId,
itemNumber: item.itemNumber,
itemContent: item.itemContent,
itemType: item.itemType,
answerDisplay: answerDisplay,
answerScore: detail.answerScore,
detailId: detail.detailId,
optionId: detail.optionId,
optionIds: detail.optionIds,
answerText: detail.answerText
};
});
this.answerDetailLoading = false;
}).catch(error => {
console.error('获取选项信息失败:', error);
this.$modal.msgError("获取选项信息失败");
this.answerDetailLoading = false;
});
}).catch(error => {
console.error('获取题目列表失败:', error);
this.$modal.msgError("获取题目列表失败");
this.answerDetailLoading = false;
});
}).catch(error => {
console.error('获取答题详情失败:', error);
this.$modal.msgError("获取答题详情失败");
this.answerDetailLoading = false;
});
}
}
};
</script>
<style scoped>
.app-container {
padding: 20px;
}
.clearfix:before,
.clearfix:after {
display: table;
content: "";
}
.clearfix:after {
clear: both;
}
.summary-content,
.report-content {
padding: 15px;
background-color: #f8f9fa;
border-radius: 4px;
min-height: 100px;
line-height: 1.8;
color: #333;
}
.summary-content >>> p,
.report-content >>> p {
margin: 10px 0;
}
.summary-content >>> h1,
.report-content >>> h1 {
font-size: 24px;
font-weight: bold;
margin: 15px 0;
}
.summary-content >>> h2,
.report-content >>> h2 {
font-size: 20px;
font-weight: bold;
margin: 12px 0;
}
.summary-content >>> h3,
.report-content >>> h3 {
font-size: 16px;
font-weight: bold;
margin: 10px 0;
}
/* AI分析区域样式 */
.ai-analysis-section {
margin-top: 20px;
padding: 20px;
background-color: #f8f9fa;
border-radius: 8px;
}
.ai-error {
margin-top: 15px;
}
.ai-result {
margin-top: 20px;
padding: 20px;
background-color: #fff;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.ai-result-title {
font-size: 18px;
font-weight: bold;
color: #409eff;
margin: 0 0 15px 0;
padding-bottom: 10px;
border-bottom: 2px solid #e4e7ed;
display: flex;
align-items: center;
gap: 8px;
}
.ai-result-title i {
font-size: 20px;
}
.ai-result-content {
line-height: 1.8;
color: #333;
}
.ai-result-content >>> h3 {
font-size: 16px;
font-weight: bold;
color: #409eff;
margin: 20px 0 10px 0;
padding-left: 10px;
border-left: 3px solid #409eff;
}
.ai-result-content >>> p {
margin: 12px 0;
text-align: justify;
color: #606266;
}
.ai-result-content >>> p:first-of-type {
margin-top: 0;
}
.ai-result-content >>> br {
line-height: 1.8;
}
/* 答题详情区域样式 */
.answer-detail-section {
margin-top: 20px;
padding: 20px;
background-color: #f8f9fa;
border-radius: 8px;
}
</style>