519 lines
19 KiB
Vue
519 lines
19 KiB
Vue
<template>
|
||
<FloatPanel
|
||
ref="floatPanel"
|
||
title="简历详情"
|
||
position="right"
|
||
:show-back="true"
|
||
back-text="返回"
|
||
@back="handleBack"
|
||
>
|
||
<template #header-right>
|
||
<Button type="info" @click="handleSyncOnline" :loading="syncing" style="margin-right: 8px;">同步在线简历</Button>
|
||
<Button type="primary" @click="handleAnalyzeAI" :loading="analyzing">AI 分析</Button>
|
||
</template>
|
||
|
||
<div class="resume-detail-content" v-if="resumeData">
|
||
<Spin fix v-if="loading">
|
||
<Icon type="ios-loading" size="18" class="spin-icon-load"></Icon>
|
||
<div>加载中...</div>
|
||
</Spin>
|
||
|
||
<!-- 基本信息 -->
|
||
<Card title="基本信息" style="margin-bottom: 16px;">
|
||
<Row :gutter="16">
|
||
<Col span="12">
|
||
<div class="info-item">
|
||
<span class="label">姓名:</span>
|
||
<span class="value">{{ resumeData.fullName || '-' }}</span>
|
||
</div>
|
||
</Col>
|
||
<Col span="12">
|
||
<div class="info-item">
|
||
<span class="label">性别:</span>
|
||
<span class="value">{{ resumeData.gender || '-' }}</span>
|
||
</div>
|
||
</Col>
|
||
<Col span="12">
|
||
<div class="info-item">
|
||
<span class="label">年龄:</span>
|
||
<span class="value">{{ resumeData.age || '-' }}</span>
|
||
</div>
|
||
</Col>
|
||
<Col span="12">
|
||
<div class="info-item">
|
||
<span class="label">电话:</span>
|
||
<span class="value">{{ resumeData.phone || '-' }}</span>
|
||
</div>
|
||
</Col>
|
||
<Col span="12">
|
||
<div class="info-item">
|
||
<span class="label">邮箱:</span>
|
||
<span class="value">{{ resumeData.email || '-' }}</span>
|
||
</div>
|
||
</Col>
|
||
<Col span="12">
|
||
<div class="info-item">
|
||
<span class="label">所在地:</span>
|
||
<span class="value">{{ resumeData.location || '-' }}</span>
|
||
</div>
|
||
</Col>
|
||
<Col span="12">
|
||
<div class="info-item">
|
||
<span class="label">工作年限:</span>
|
||
<span class="value">{{ resumeData.workYears || '-' }}</span>
|
||
</div>
|
||
</Col>
|
||
<Col span="12">
|
||
<div class="info-item">
|
||
<span class="label">当前职位:</span>
|
||
<span class="value">{{ resumeData.currentPosition || '-' }}</span>
|
||
</div>
|
||
</Col>
|
||
<Col span="12">
|
||
<div class="info-item">
|
||
<span class="label">当前公司:</span>
|
||
<span class="value">{{ resumeData.currentCompany || '-' }}</span>
|
||
</div>
|
||
</Col>
|
||
</Row>
|
||
</Card>
|
||
|
||
<!-- 教育背景 -->
|
||
<Card title="教育背景" style="margin-bottom: 16px;">
|
||
<Row :gutter="16">
|
||
<Col span="12">
|
||
<div class="info-item">
|
||
<span class="label">学历:</span>
|
||
<span class="value">{{ resumeData.education || '-' }}</span>
|
||
</div>
|
||
</Col>
|
||
<Col span="12">
|
||
<div class="info-item">
|
||
<span class="label">专业:</span>
|
||
<span class="value">{{ resumeData.major || '-' }}</span>
|
||
</div>
|
||
</Col>
|
||
<Col span="12">
|
||
<div class="info-item">
|
||
<span class="label">毕业院校:</span>
|
||
<span class="value">{{ resumeData.school || '-' }}</span>
|
||
</div>
|
||
</Col>
|
||
<Col span="12">
|
||
<div class="info-item">
|
||
<span class="label">毕业年份:</span>
|
||
<span class="value">{{ resumeData.graduationYear || '-' }}</span>
|
||
</div>
|
||
</Col>
|
||
</Row>
|
||
</Card>
|
||
|
||
<!-- 期望信息 -->
|
||
<Card title="期望信息" style="margin-bottom: 16px;">
|
||
<Row :gutter="16">
|
||
<Col span="12">
|
||
<div class="info-item">
|
||
<span class="label">期望职位:</span>
|
||
<span class="value">{{ resumeData.expectedPosition || '-' }}</span>
|
||
</div>
|
||
</Col>
|
||
<Col span="12">
|
||
<div class="info-item">
|
||
<span class="label">期望薪资:</span>
|
||
<span class="value">{{ resumeData.expectedSalary || '-' }}</span>
|
||
</div>
|
||
</Col>
|
||
<Col span="12">
|
||
<div class="info-item">
|
||
<span class="label">期望地点:</span>
|
||
<span class="value">{{ resumeData.expectedLocation || '-' }}</span>
|
||
</div>
|
||
</Col>
|
||
<Col span="12">
|
||
<div class="info-item">
|
||
<span class="label">期望行业:</span>
|
||
<span class="value">{{ resumeData.expectedIndustry || '-' }}</span>
|
||
</div>
|
||
</Col>
|
||
</Row>
|
||
</Card>
|
||
|
||
<!-- 技能标签 -->
|
||
<Card title="技能标签" style="margin-bottom: 16px;">
|
||
<div v-if="skillTags && skillTags.length > 0">
|
||
<Tag v-for="(skill, index) in skillTags" :key="index" color="blue" style="margin: 4px;">{{ skill }}</Tag>
|
||
</div>
|
||
<div v-else class="no-data">暂无技能标签</div>
|
||
</Card>
|
||
|
||
<!-- 技能描述 -->
|
||
<Card title="技能描述" style="margin-bottom: 16px;" v-if="resumeData.skillDescription">
|
||
<div class="text-content">{{ resumeData.skillDescription }}</div>
|
||
</Card>
|
||
|
||
<!-- 工作经历 -->
|
||
<Card title="工作经历" style="margin-bottom: 16px;" v-if="workExperience && workExperience.length > 0">
|
||
<Timeline>
|
||
<TimelineItem v-for="(work, index) in workExperience" :key="index">
|
||
<p class="work-title">
|
||
<strong>{{ work.position }}</strong>
|
||
<span class="work-company"> - {{ work.company }}</span>
|
||
</p>
|
||
<p class="work-time" v-if="work.startDate || work.endDate">
|
||
{{ work.startDate }} - {{ work.endDate || '至今' }}
|
||
</p>
|
||
<p class="work-content" v-if="work.content">{{ work.content }}</p>
|
||
</TimelineItem>
|
||
</Timeline>
|
||
</Card>
|
||
|
||
<!-- 项目经验 -->
|
||
<Card title="项目经验" style="margin-bottom: 16px;" v-if="projectExperience && projectExperience.length > 0">
|
||
<div v-for="(project, index) in projectExperience" :key="index" class="project-item" style="margin-bottom: 20px;">
|
||
<p class="project-title">
|
||
<strong>{{ project.name }}</strong>
|
||
<span class="project-role" v-if="project.role"> - {{ project.role }}</span>
|
||
</p>
|
||
<p class="project-time" v-if="project.startDate || project.endDate">
|
||
{{ project.startDate }} - {{ project.endDate || '至今' }}
|
||
</p>
|
||
<p class="project-desc" v-if="project.description">{{ project.description }}</p>
|
||
<p class="project-performance" v-if="project.performance">
|
||
<strong>项目成果:</strong>{{ project.performance }}
|
||
</p>
|
||
</div>
|
||
</Card>
|
||
|
||
<!-- 简历内容 -->
|
||
<Card title="简历完整内容" style="margin-bottom: 16px;" v-if="resumeData.resumeContent">
|
||
<div class="text-content" style="white-space: pre-wrap;">{{ resumeData.resumeContent }}</div>
|
||
</Card>
|
||
|
||
<!-- AI 分析结果 -->
|
||
<Card title="AI 分析结果" style="margin-bottom: 16px;">
|
||
<Row :gutter="16">
|
||
<Col span="24">
|
||
<div class="info-item">
|
||
<span class="label">竞争力评分:</span>
|
||
<span class="value" :style="{ color: getCompetitivenessColor(resumeData.aiCompetitiveness), fontSize: '18px', fontWeight: 'bold' }">
|
||
{{ resumeData.aiCompetitiveness || 0 }} 分
|
||
</span>
|
||
<Progress
|
||
:percent="resumeData.aiCompetitiveness || 0"
|
||
:stroke-color="getCompetitivenessColor(resumeData.aiCompetitiveness)"
|
||
style="margin-top: 8px; width: 300px;"
|
||
/>
|
||
</div>
|
||
</Col>
|
||
<Col span="24" v-if="resumeData.aiStrengths">
|
||
<div class="info-item ai-section">
|
||
<div class="ai-section-header">
|
||
<Icon type="ios-thumbs-up" color="#19be6b" size="18" />
|
||
<span class="label">优势分析</span>
|
||
</div>
|
||
<div class="text-content ai-content">{{ resumeData.aiStrengths }}</div>
|
||
</div>
|
||
</Col>
|
||
<Col span="24" v-if="resumeData.aiWeaknesses">
|
||
<div class="info-item ai-section">
|
||
<div class="ai-section-header">
|
||
<Icon type="ios-warning" color="#ff9900" size="18" />
|
||
<span class="label">劣势分析</span>
|
||
</div>
|
||
<div class="text-content ai-content">{{ resumeData.aiWeaknesses }}</div>
|
||
</div>
|
||
</Col>
|
||
<Col span="24" v-if="resumeData.aiCareerSuggestion">
|
||
<div class="info-item ai-section">
|
||
<div class="ai-section-header">
|
||
<Icon type="ios-bulb" color="#2d8cf0" size="18" />
|
||
<span class="label">职业建议</span>
|
||
</div>
|
||
<div class="text-content ai-content">{{ resumeData.aiCareerSuggestion }}</div>
|
||
</div>
|
||
</Col>
|
||
<Col span="24" v-if="aiSkillTags && aiSkillTags.length > 0">
|
||
<div class="info-item">
|
||
<span class="label">AI 提取的技能标签:</span>
|
||
<div style="margin-top: 8px;">
|
||
<Tag v-for="(skill, index) in aiSkillTags" :key="index" color="green" style="margin: 4px;">{{ skill }}</Tag>
|
||
</div>
|
||
</div>
|
||
</Col>
|
||
<Col span="24" v-if="!resumeData.aiCompetitiveness && !resumeData.aiStrengths">
|
||
<div class="no-data">
|
||
<Icon type="ios-information-circle" size="24" color="#c5c8ce" />
|
||
<p>暂无 AI 分析结果,请点击上方"AI 分析"按钮进行分析</p>
|
||
</div>
|
||
</Col>
|
||
</Row>
|
||
</Card>
|
||
|
||
<!-- 原始数据(可展开) -->
|
||
<Card title="原始数据" style="margin-bottom: 16px;">
|
||
<Collapse>
|
||
<Panel name="originalData">
|
||
查看原始 JSON 数据
|
||
<template slot="content">
|
||
<pre class="json-preview">{{ JSON.stringify(originalDataObj, null, 2) }}</pre>
|
||
</template>
|
||
</Panel>
|
||
</Collapse>
|
||
</Card>
|
||
</div>
|
||
</FloatPanel>
|
||
</template>
|
||
|
||
<script>
|
||
import resumeInfoServer from '@/api/profile/resume_info_server.js'
|
||
|
||
export default {
|
||
name: 'ResumeInfoDetail',
|
||
data() {
|
||
return {
|
||
loading: false,
|
||
analyzing: false,
|
||
syncing: false,
|
||
resumeData: null,
|
||
skillTags: [],
|
||
workExperience: [],
|
||
projectExperience: [],
|
||
aiSkillTags: [],
|
||
originalDataObj: {}
|
||
}
|
||
},
|
||
methods: {
|
||
async show(resumeId) {
|
||
this.$refs.floatPanel.show()
|
||
await this.loadResumeData(resumeId)
|
||
},
|
||
async loadResumeData(resumeId) {
|
||
this.loading = true
|
||
try {
|
||
const res = await resumeInfoServer.getById(resumeId)
|
||
this.resumeData = res.data || {}
|
||
|
||
// 解析 JSON 字段
|
||
this.skillTags = this.parseJsonField(this.resumeData.skills) || []
|
||
this.workExperience = this.parseJsonField(this.resumeData.workExperience) || []
|
||
this.projectExperience = this.parseJsonField(this.resumeData.projectExperience) || []
|
||
this.aiSkillTags = this.parseJsonField(this.resumeData.aiSkillTags) || []
|
||
|
||
// 解析原始数据
|
||
this.originalDataObj = this.parseJsonField(this.resumeData.originalData) || {}
|
||
} catch (error) {
|
||
console.error('加载简历详情失败:', error)
|
||
this.$Message.error('加载简历详情失败')
|
||
} finally {
|
||
this.loading = false
|
||
}
|
||
},
|
||
parseJsonField(field) {
|
||
if (!field) return null
|
||
if (typeof field === 'string') {
|
||
try {
|
||
return JSON.parse(field)
|
||
} catch (e) {
|
||
return field
|
||
}
|
||
}
|
||
return field
|
||
},
|
||
async handleSyncOnline() {
|
||
if (!this.resumeData || !this.resumeData.resumeId) {
|
||
this.$Message.warning('简历ID不存在')
|
||
return
|
||
}
|
||
|
||
if (!this.resumeData.sn_code) {
|
||
this.$Message.warning('该简历未绑定设备,无法同步在线简历')
|
||
return
|
||
}
|
||
|
||
this.syncing = true
|
||
try {
|
||
const res = await resumeInfoServer.syncOnline(this.resumeData.resumeId)
|
||
this.$Message.success(res.message || '同步在线简历成功')
|
||
// 重新加载数据
|
||
await this.loadResumeData(this.resumeData.resumeId)
|
||
} catch (error) {
|
||
console.error('同步在线简历失败:', error)
|
||
// 优先从 error.response.data.message 获取,然后是 error.message
|
||
const errorMsg = error.response?.data?.message || error.message || '请稍后重试'
|
||
this.$Message.error(errorMsg)
|
||
} finally {
|
||
this.syncing = false
|
||
}
|
||
},
|
||
async handleAnalyzeAI() {
|
||
if (!this.resumeData || !this.resumeData.resumeId) {
|
||
this.$Message.warning('简历ID不存在')
|
||
return
|
||
}
|
||
|
||
this.analyzing = true
|
||
try {
|
||
await resumeInfoServer.analyzeWithAI(this.resumeData.resumeId)
|
||
this.$Message.success('AI 分析完成')
|
||
// 重新加载数据
|
||
await this.loadResumeData(this.resumeData.resumeId)
|
||
} catch (error) {
|
||
console.error('AI 分析失败:', error)
|
||
// 优先从 error.response.data.message 获取,然后是 error.message
|
||
const errorMsg = error.response?.data?.message || error.message || '请稍后重试'
|
||
this.$Message.error(errorMsg)
|
||
} finally {
|
||
this.analyzing = false
|
||
}
|
||
},
|
||
handleBack() {
|
||
this.$refs.floatPanel.hide()
|
||
this.$emit('on-close')
|
||
},
|
||
getCompetitivenessColor(score) {
|
||
if (!score) return '#666'
|
||
if (score >= 80) return '#19be6b'
|
||
if (score >= 60) return '#2d8cf0'
|
||
return '#ed4014'
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.resume-detail-content {
|
||
padding: 0;
|
||
position: relative;
|
||
min-height: 400px;
|
||
}
|
||
|
||
.info-item {
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.info-item .label {
|
||
font-weight: 600;
|
||
color: #515a6e;
|
||
margin-right: 8px;
|
||
}
|
||
|
||
.info-item .value {
|
||
color: #2d8cf0;
|
||
}
|
||
|
||
.text-content {
|
||
color: #515a6e;
|
||
line-height: 1.6;
|
||
}
|
||
|
||
.work-title {
|
||
font-size: 14px;
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.work-company {
|
||
color: #808695;
|
||
font-weight: normal;
|
||
}
|
||
|
||
.work-time {
|
||
color: #808695;
|
||
font-size: 12px;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.work-content {
|
||
color: #515a6e;
|
||
line-height: 1.6;
|
||
margin-top: 8px;
|
||
}
|
||
|
||
.project-item {
|
||
padding-bottom: 16px;
|
||
border-bottom: 1px solid #e8eaec;
|
||
}
|
||
|
||
.project-item:last-child {
|
||
border-bottom: none;
|
||
}
|
||
|
||
.project-title {
|
||
font-size: 14px;
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.project-role {
|
||
color: #808695;
|
||
font-weight: normal;
|
||
}
|
||
|
||
.project-time {
|
||
color: #808695;
|
||
font-size: 12px;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.project-desc {
|
||
color: #515a6e;
|
||
line-height: 1.6;
|
||
margin-top: 8px;
|
||
}
|
||
|
||
.project-performance {
|
||
color: #19be6b;
|
||
line-height: 1.6;
|
||
margin-top: 8px;
|
||
}
|
||
|
||
.no-data {
|
||
color: #c5c8ce;
|
||
text-align: center;
|
||
padding: 40px 20px;
|
||
}
|
||
|
||
.no-data p {
|
||
margin-top: 12px;
|
||
font-size: 14px;
|
||
}
|
||
|
||
/* AI 分析区域样式 */
|
||
.ai-section {
|
||
margin-bottom: 20px;
|
||
padding: 16px;
|
||
background: #f8f9fa;
|
||
border-radius: 6px;
|
||
border-left: 3px solid #2d8cf0;
|
||
}
|
||
|
||
.ai-section-header {
|
||
display: flex;
|
||
align-items: center;
|
||
margin-bottom: 12px;
|
||
gap: 8px;
|
||
}
|
||
|
||
.ai-section-header .label {
|
||
font-size: 15px;
|
||
font-weight: 600;
|
||
color: #17233d;
|
||
margin-right: 0;
|
||
}
|
||
|
||
.ai-content {
|
||
padding-left: 26px;
|
||
line-height: 1.8;
|
||
color: #515a6e;
|
||
}
|
||
|
||
.json-preview {
|
||
background: #f8f8f9;
|
||
padding: 16px;
|
||
border-radius: 4px;
|
||
max-height: 500px;
|
||
overflow-y: auto;
|
||
font-size: 12px;
|
||
line-height: 1.5;
|
||
}
|
||
</style>
|
||
|