Files
AI-interview-web/src/views/interview/index.vue

344 lines
8.4 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="interview-view-container">
<div class="header-section">
<h1>选择面试模式</h1>
<p>根据您的需求选择合适的面试方式</p>
</div>
<div class="mode-cards-container">
<!-- AI智能面试卡片 -->
<el-card
:class="['mode-card', { 'active': selectedMode === 'ai' }]"
shadow="hover"
@click="selectedMode = 'ai'"
>
<div class="card-content">
<div class="card-icon">
<el-icon size="48" color="#409EFF">
<Cpu/>
</el-icon>
</div>
<h3>AI智能面试</h3>
<p class="description">由AI根据您的简历智能生成个性化面试题目</p>
<ul class="features-list">
<li>
<el-icon>
<Check/>
</el-icon>
个性化题目定制
</li>
<li>
<el-icon>
<Check/>
</el-icon>
自适应难度调整
</li>
<li>
<el-icon>
<Check/>
</el-icon>
实时反馈与评分
</li>
</ul>
</div>
</el-card>
<!-- 本地题库面试卡片 -->
<el-card
:class="['mode-card', { 'active': selectedMode === 'local' }]"
shadow="hover"
@click="selectedMode = 'local'"
>
<div class="card-content">
<div class="card-icon">
<el-icon size="48" color="#67C23A">
<Collection/>
</el-icon>
</div>
<h3>本地题库面试</h3>
<p class="description">从预设题库中选择题目进行系统化面试</p>
<ul class="features-list">
<li>
<el-icon>
<Check/>
</el-icon>
丰富的题目类型
</li>
<li>
<el-icon>
<Check/>
</el-icon>
可自定义选择范围
</li>
<li>
<el-icon>
<Check/>
</el-icon>
系统化评估体系
</li>
</ul>
</div>
</el-card>
</div>
<!-- 题库选择区域仅本地模式显示 -->
<QuestionBankSection ref="questionBankSectionRef" v-if="selectedMode === 'local'"/>
<!-- 开始面试表单 -->
<div class="start-form-section">
<el-card shadow="never">
<el-form :model="formData" label-width="120px" size="large">
<el-form-item label="您的姓名" required>
<el-input v-model="formData.candidateName" placeholder="请输入您的姓名"/>
</el-form-item>
<el-form-item label="上传简历" required>
<el-upload
vref="uploadRef"
action="#"
:limit="1"
:auto-upload="false"
:on-change="handleFileChange"
:on-exceed="handleFileExceed"
>
<template #trigger>
<el-button type="primary">选择文件</el-button>
</template>
<template #tip>
<div class="upload-tip">
支持 PDFMarkdown 或文本格式大小不超过10MB
</div>
</template>
</el-upload>
</el-form-item>
<el-form-item label="AI模型">
<el-select v-model="formData.aiModel" placeholder="请选择AI模型">
<el-option label="DeepSeek" value="deepSeek"></el-option>
<el-option label="阿里千问" value="qwen"></el-option>
</el-select>
</el-form-item>
<el-form-item label="面试题目数量">
<el-input-number v-model="formData.totalQuestions" :min="1" :max="100"></el-input-number>
</el-form-item>
<el-form-item>
<el-button
type="success"
:loading="isLoading"
@click="startInterviewAction"
class="start-button"
>
{{ selectedMode === 'ai' ? '开始AI面试' : '开始题库面试' }}
</el-button>
<el-button @click="$router.push('/')">返回首页</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</div>
</template>
<script setup>
import {ref, reactive} from 'vue'
import {useRouter} from 'vue-router'
import {ElMessage} from 'element-plus'
import {Cpu, Collection, Check} from '@element-plus/icons-vue'
import QuestionBankSection from '@/components/QuestionBankSection.vue'
import {startInterview, continueInterview, getInterviewReportDetail} from '@/api/interview.js';
const router = useRouter()
const questionBankSectionRef = ref(null)
// 响应式状态
const selectedMode = ref('ai') // 'ai' 或 'local'
const isLoading = ref(false)
// 表单数据
const formData = ref({
candidateName: '',
resumeFiles: [],
totalQuestions: 10,
aiModel: 'deepSeek'
})
// --- UI交互方法 ---
const handleFileChange = (file) => {
formData.value.resumeFiles = file.raw;
};
// 文件上传处理
const handleFileExceed = () => {
ElMessage.warning('只能上传一个简历文件')
}
const sessionId = ref('')
// 开始面试
const startInterviewAction = async () => {
console.log(formData.value)
if (!formData.value.candidateName || !formData.value.resumeFiles) {
ElMessage.error('请输入您的姓名并上传简历。');
return;
}
isLoading.value = true;
const sendFormData = new FormData();
const selectionResult = questionBankSectionRef.value.getSelectionResult()
if (!selectionResult.selectedNodes) {
selectionResult.selectedNodes = []
}
const sendData = {
candidateName: formData.value.candidateName,
aiModel: formData.value.aiModel,
totalQuestions: formData.value.totalQuestions,
model: selectedMode.value,
selectedNodes: []
}
if (selectionResult.selectedNodes && selectionResult.selectedNodes.length > 0) {
const sendNodes = []
selectionResult.selectedNodes.forEach(node => {
sendNodes.push({
id: node.id,
name: node.name,
type: node.type,
})
})
sendData.selectedNodes = sendNodes
}
sendFormData.append('interviewStartDto', new Blob([JSON.stringify(sendData)], {
type: 'application/json',
}))
sendFormData.append('resume', formData.value.resumeFiles);
try {
console.log(sendFormData.values())
const responseData = await startInterview(sendFormData);
const data = responseData.data;
sessionId.value = data.sessionId;
// 跳转到聊天界面
router.push({
path: '/interview-chat',
query: {
mode: selectedMode.value,
sessionId: sessionId.value
}
})
} catch (error) {
console.error('开始面试失败:', error);
} finally {
isLoading.value = false;
}
}
</script>
<style scoped>
.interview-view-container {
padding: 24px;
max-width: 1200px;
margin: 0 auto;
}
.header-section {
text-align: center;
margin-bottom: 32px;
}
.header-section h1 {
font-size: 2.5rem;
color: #303133;
margin-bottom: 8px;
}
.header-section p {
font-size: 1.1rem;
color: #606266;
}
.mode-cards-container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 24px;
margin-bottom: 24px;
}
.mode-card {
cursor: pointer;
transition: all 0.3s ease;
border: 2px solid transparent;
}
.mode-card:hover {
transform: translateY(-4px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15) !important;
}
.mode-card.active {
border-color: #409EFF;
background: linear-gradient(135deg, #f5f7fa 0%, #e4e7ed 100%);
}
.card-content {
text-align: center;
padding: 20px;
}
.card-icon {
margin-bottom: 16px;
}
.card-content h3 {
font-size: 1.5rem;
margin-bottom: 12px;
color: #303133;
}
.description {
color: #606266;
margin-bottom: 16px;
line-height: 1.6;
}
.features-list {
list-style: none;
padding: 0;
margin: 0;
text-align: left;
}
.features-list li {
padding: 8px 0;
color: #606266;
display: flex;
align-items: center;
}
.features-list .el-icon {
color: #67C23A;
margin-right: 8px;
}
.start-form-section {
margin-top: 24px;
}
.upload-tip {
font-size: 0.9rem;
color: #909399;
margin-top: 8px;
}
.start-button {
min-width: 140px;
}
@media (max-width: 768px) {
.mode-cards-container {
grid-template-columns: 1fr;
}
.header-section h1 {
font-size: 2rem;
}
}
</style>