897 lines
34 KiB
Vue
897 lines
34 KiB
Vue
<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>
|
||
|