273 lines
7.2 KiB
Vue
273 lines
7.2 KiB
Vue
<template>
|
|
<div class="report-container">
|
|
<div v-if="isLoading" class="loading-container">
|
|
<el-skeleton :rows="10" animated/>
|
|
</div>
|
|
|
|
<div v-else-if="!reportData" class="empty-container">
|
|
<el-empty description="无法加载面试报告,请返回重试。"/>
|
|
</div>
|
|
|
|
<div v-else class="report-main">
|
|
<el-page-header @back="goBack" class="report-header">
|
|
<template #content>
|
|
<div class="header-content">
|
|
<span class="title">{{ reportData.sessionDetails.candidateName }} 的面试复盘报告</span>
|
|
<el-tag type="info" size="large">{{
|
|
new Date(reportData.sessionDetails.createdTime).toLocaleString()
|
|
}}
|
|
</el-tag>
|
|
</div>
|
|
</template>
|
|
</el-page-header>
|
|
|
|
<el-card class="box-card report-summary" shadow="hover">
|
|
<template #header>
|
|
<div class="card-header">
|
|
<el-icon>
|
|
<DataAnalysis/>
|
|
</el-icon>
|
|
<span>AI 最终评估报告</span>
|
|
</div>
|
|
</template>
|
|
<div v-if="finalReport" class="summary-content">
|
|
<el-descriptions :column="2" border>
|
|
<el-descriptions-item label="综合得分">
|
|
<el-tag size="medium">{{ finalReport.overallScore }} 分</el-tag>
|
|
</el-descriptions-item>
|
|
<el-descriptions-item label="录用建议">
|
|
<el-tag :type="getRecommendationType(finalReport.hiringRecommendation)" size="medium" effect="dark">
|
|
{{ finalReport.hiringRecommendation }}
|
|
</el-tag>
|
|
</el-descriptions-item>
|
|
</el-descriptions>
|
|
<el-divider/>
|
|
<h4>综合评语</h4>
|
|
<p class="feedback-paragraph">{{ finalReport.overallFeedback }}</p>
|
|
<h4>技术能力评估</h4>
|
|
<ul class="assessment-list">
|
|
<li v-for="(value, key) in finalReport.technicalAssessment" :key="key">
|
|
<strong>{{ key }}:</strong> {{ value }}
|
|
</li>
|
|
</ul>
|
|
<h4>改进建议</h4>
|
|
<ol class="suggestions-list">
|
|
<li v-for="suggestion in finalReport.suggestions" :key="suggestion">{{ suggestion }}</li>
|
|
</ol>
|
|
</div>
|
|
<div v-else>
|
|
<el-empty description="AI最终报告正在生成中或生成失败。"/>
|
|
</div>
|
|
</el-card>
|
|
|
|
<el-card class="box-card question-details" shadow="hover">
|
|
<template #header>
|
|
<div class="card-header">
|
|
<el-icon>
|
|
<ChatDotRound/>
|
|
</el-icon>
|
|
<span>问答详情与逐题评估</span>
|
|
</div>
|
|
</template>
|
|
<el-timeline>
|
|
<el-timeline-item v-for="(item, index) in reportData.questionDetails" :key="item.questionId"
|
|
:timestamp="`第 ${index + 1} 题`" placement="top">
|
|
<el-card class="question-card" shadow="never">
|
|
<h4>{{ item.questionContent }}</h4>
|
|
<p class="user-answer"><strong>您的回答:</strong> {{ item.userAnswer }}</p>
|
|
<el-divider/>
|
|
<div class="feedback-section">
|
|
<p><strong>AI 评语:</strong> {{ item.aiFeedback }}</p>
|
|
<p><strong>AI 建议:</strong> {{ item.suggestions }}</p>
|
|
<div class="score-section">
|
|
<strong>本题得分:</strong>
|
|
<el-rate v-model="item.score" :max="5" disabled show-score text-color="#ff9900"
|
|
score-template="{value} 分"/>
|
|
</div>
|
|
</div>
|
|
</el-card>
|
|
</el-timeline-item>
|
|
</el-timeline>
|
|
</el-card>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import {ref, onMounted, computed} from 'vue';
|
|
import {useRouter} from 'vue-router';
|
|
import {getInterviewReportDetail} from '@/api/interview.js';
|
|
import {DataAnalysis, ChatDotRound} from '@element-plus/icons-vue';
|
|
|
|
// --- Props & Router ---
|
|
const props = defineProps({sessionId: {type: String, required: true}});
|
|
const router = useRouter();
|
|
|
|
// --- 响应式状态定义 ---
|
|
const reportData = ref(null);
|
|
const isLoading = ref(false);
|
|
|
|
// --- 计算属性 ---
|
|
const finalReport = computed(() => {
|
|
if (reportData.value && reportData.value.sessionDetails.finalReport) {
|
|
try {
|
|
// 检查 finalReport 是否是字符串,如果是则解析
|
|
return typeof reportData.value.sessionDetails.finalReport === 'string'
|
|
? JSON.parse(reportData.value.sessionDetails.finalReport)
|
|
: reportData.value.sessionDetails.finalReport;
|
|
} catch (e) {
|
|
console.error('解析最终报告JSON失败:', e);
|
|
return null;
|
|
}
|
|
}
|
|
return null;
|
|
});
|
|
|
|
// --- API交互方法 ---
|
|
const fetchReport = async () => {
|
|
isLoading.value = true;
|
|
try {
|
|
const responseData = await getInterviewReportDetail(props.sessionId);
|
|
if (responseData.code === 0 && responseData.data) {
|
|
reportData.value = responseData.data;
|
|
} else {
|
|
reportData.value = null;
|
|
console.error('获取面试报告失败:', responseData.message);
|
|
}
|
|
} catch (error) {
|
|
reportData.value = null;
|
|
console.error('获取面试报告失败:', error);
|
|
} finally {
|
|
isLoading.value = false;
|
|
}
|
|
};
|
|
|
|
// --- 事件处理 ---
|
|
const goBack = () => router.push('/history');
|
|
|
|
const getRecommendationType = (rec) => {
|
|
if (rec === '强烈推荐' || rec === '推荐') return 'success';
|
|
if (rec === '待考虑') return 'warning';
|
|
if (rec === '不推荐') return 'danger';
|
|
return 'info';
|
|
};
|
|
|
|
// --- 生命周期钩子 ---
|
|
onMounted(() => {
|
|
fetchReport();
|
|
});
|
|
</script>
|
|
|
|
<style scoped>
|
|
.report-container {
|
|
padding: 20px;
|
|
max-width: 1200px;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
.report-header {
|
|
margin-bottom: 20px;
|
|
background-color: #ffffff;
|
|
padding: 15px 20px;
|
|
border-radius: 12px;
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
|
border: 1px solid #ebeef5;
|
|
}
|
|
|
|
.header-content {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
width: 100%;
|
|
}
|
|
|
|
.header-content .title {
|
|
font-size: 1.5em;
|
|
font-weight: bold;
|
|
color: #303133;
|
|
}
|
|
|
|
.box-card {
|
|
margin-bottom: 20px;
|
|
border: 1px solid #ebeef5;
|
|
border-radius: 12px;
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
|
}
|
|
|
|
.card-header {
|
|
font-size: 1.2em;
|
|
font-weight: bold;
|
|
display: flex;
|
|
align-items: center;
|
|
color: #303133;
|
|
}
|
|
|
|
.card-header .el-icon {
|
|
margin-right: 10px;
|
|
color: #409eff;
|
|
}
|
|
|
|
.summary-content {
|
|
padding: 10px 0;
|
|
}
|
|
|
|
.el-descriptions {
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.report-summary h4 {
|
|
margin: 25px 0 10px 0;
|
|
font-size: 1.1em;
|
|
color: #303133;
|
|
border-left: 4px solid #409eff;
|
|
padding-left: 10px;
|
|
}
|
|
|
|
.feedback-paragraph {
|
|
text-indent: 2em;
|
|
color: #606266;
|
|
line-height: 1.8;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.assessment-list, .suggestions-list {
|
|
padding-left: 20px;
|
|
list-style: disc;
|
|
color: #606266;
|
|
}
|
|
|
|
.assessment-list li, .suggestions-list li {
|
|
line-height: 1.8;
|
|
}
|
|
|
|
.question-card {
|
|
margin-top: 10px;
|
|
border: 1px solid #ebeef5;
|
|
border-radius: 8px;
|
|
}
|
|
|
|
.user-answer {
|
|
color: #606266;
|
|
font-style: italic;
|
|
background-color: #f5f7fa;
|
|
padding: 10px;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
.feedback-section {
|
|
background-color: #fafbfd;
|
|
padding: 15px;
|
|
border-radius: 8px;
|
|
margin-top: 15px;
|
|
}
|
|
|
|
.score-section {
|
|
display: flex;
|
|
align-items: center;
|
|
margin-top: 10px;
|
|
}
|
|
|
|
.el-rate {
|
|
margin-left: 10px;
|
|
}
|
|
</style> |