修改模拟面试的相关内容

This commit is contained in:
2025-09-21 21:19:26 +08:00
parent 4ca9fbbe73
commit 124444671a
11 changed files with 787 additions and 137 deletions

View File

@@ -1,82 +1,88 @@
<template>
<div class="report-container">
<!-- 加载中的骨架屏 -->
<div v-if="isLoading" class="loading-container">
<el-skeleton :rows="10" animated />
<el-skeleton :rows="10" animated/>
</div>
<!-- 无数据时的空状态 -->
<div v-else-if="!reportData" class="empty-container">
<el-empty description="无法加载面试报告,请返回重试。" />
<el-empty description="无法加载面试报告,请返回重试。"/>
</div>
<!-- 报告主内容 -->
<div v-else>
<!-- 报告头部 -->
<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 size="large">{{ new Date(reportData.sessionDetails.createdTime).toLocaleString() }}</el-tag>
<el-tag type="info" size="large">{{
new Date(reportData.sessionDetails.createdTime).toLocaleString()
}}
</el-tag>
</div>
</template>
</el-page-header>
<!-- AI最终评估报告 -->
<el-card class="box-card report-summary" shadow="never">
<el-card class="box-card report-summary" shadow="hover">
<template #header>
<div class="card-header">
<el-icon><DataAnalysis /></el-icon>
<el-icon>
<DataAnalysis/>
</el-icon>
<span>AI 最终评估报告</span>
</div>
</template>
<div v-if="finalReport" class="summary-content">
<el-row :gutter="20">
<el-col :span="8">
<el-statistic title="综合得分" :value="finalReport.overallScore" />
</el-col>
<el-col :span="16">
<el-statistic title="录用建议">
<template #formatter>
<el-tag :type="getRecommendationType(finalReport.hiringRecommendation)" size="large" effect="dark">{{ finalReport.hiringRecommendation }}</el-tag>
</template>
</el-statistic>
</el-col>
</el-row>
<el-divider />
<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>
<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>
<div v-else>
<el-empty description="AI最终报告正在生成中或生成失败。"/>
</div>
</el-card>
<!-- 问答详情 -->
<el-card class="box-card question-details" shadow="never">
<el-card class="box-card question-details" shadow="hover">
<template #header>
<div class="card-header">
<el-icon><ChatDotRound /></el-icon>
<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="hover">
<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 />
<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} 分" />
<strong>本题得分:</strong>
<el-rate v-model="item.score" :max="5" disabled show-score text-color="#ff9900"
score-template="{value} "/>
</div>
</div>
</el-card>
@@ -88,27 +94,27 @@
</template>
<script setup>
// 导入Vue核心功能、路由、API客户端和图标
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';
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 props = defineProps({sessionId: {type: String, required: true}});
const router = useRouter();
// --- 响应式状态定义 ---
const reportData = ref(null); // 存储完整的报告数据
const isLoading = ref(false); // 加载状态
const reportData = ref(null);
const isLoading = ref(false);
// --- 计算属性 ---
// 安全地解析最终报告的JSON字符串
const finalReport = computed(() => {
if (reportData.value && reportData.value.sessionDetails.finalReport) {
try {
return JSON.parse(reportData.value.sessionDetails.finalReport);
// 检查 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;
@@ -118,14 +124,18 @@ const finalReport = computed(() => {
});
// --- API交互方法 ---
// 获取面试报告详情
const fetchReport = async () => {
isLoading.value = true;
try {
const responseData = await getInterviewReportDetail(props.sessionId);
reportData.value = responseData.data;
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;
@@ -133,44 +143,131 @@ const fetchReport = async () => {
};
// --- 事件处理 ---
// 返回上一页
const goBack = () => router.push('/history');
// 根据录用建议返回不同的标签类型
const getRecommendationType = (rec) => {
if (rec === '强烈推荐' || rec === '推荐') return 'success';
if (rec === '待考虑') return 'warning';
if (rec === '不推荐') return 'danger';
return 'info';
if (rec === '强烈推荐' || rec === '推荐') return 'success';
if (rec === '待考虑') return 'warning';
if (rec === '不推荐') return 'danger';
return 'info';
};
// --- 生命周期钩子 ---
onMounted(() => {
fetchReport();
});
</script>
<style scoped>
.report-container { padding: 10px; }
.report-header { margin-bottom: 20px; background-color: #fff; padding: 15px 20px; border-radius: 8px; box-shadow: 0 2px 12px 0 rgba(0,0,0,.1); }
.header-content { display: flex; align-items: center; justify-content: space-between; width: 100%; }
.header-content .title { font-size: 1.2em; font-weight: 600; }
.report-container {
padding: 20px;
max-width: 1200px;
margin: 0 auto;
}
.box-card { margin-bottom: 20px; border: none; }
.card-header { font-size: 1.1em; font-weight: bold; display: flex; align-items: center; }
.card-header .el-icon { margin-right: 10px; }
.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;
}
.summary-content { padding: 10px; }
.report-summary h4 { margin: 25px 0 10px 0; font-size: 1.05em; }
.report-summary p, .report-summary li { color: #606266; line-height: 1.8; }
.feedback-paragraph { text-indent: 2em; }
.assessment-list, .suggestions-list { padding-left: 20px; }
.header-content {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
}
.question-card { margin-top: 10px; }
.user-answer { color: #303133; font-style: italic; }
.feedback-section { background-color: #f9fafb; padding: 15px; border-radius: 4px; margin-top: 15px; }
.score-section { display: flex; align-items: center; margin-top: 10px; }
.el-rate { margin-left: 10px; }
.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>