fix(admin): 修复简历附件下载问题,完善简历信息展示

This commit is contained in:
TianyangZhang 2026-03-25 16:32:33 +08:00
parent fbcd98dc46
commit 1ec8734401
3 changed files with 165 additions and 12 deletions

View File

@ -22,11 +22,18 @@ class Resume(models.Model):
def to_snapshot(self): def to_snapshot(self):
"""序列化为投递快照,与主表解耦""" """序列化为投递快照,与主表解耦"""
attachment_url = None
if self.attachment:
# 返回相对 URL前端会处理
attachment_url = self.attachment.url
return { return {
'name': self.name, 'name': self.name,
'gender': self.gender, 'gender': self.gender,
'birthday': str(self.birthday) if self.birthday else None, 'birthday': str(self.birthday) if self.birthday else None,
'education': self.education, 'education': self.education,
'experience': self.experience, 'experience': self.experience,
'attachment_url': self.attachment.url if self.attachment else None, 'email': self.user.email,
'phone': self.user.phone,
'attachment_url': attachment_url,
} }

View File

@ -25,16 +25,70 @@
</el-table-column> </el-table-column>
</el-table> </el-table>
<el-dialog v-model="resumeVisible" title="简历详情" width="600px"> <el-dialog v-model="resumeVisible" title="简历详情" width="700px">
<div v-if="currentResume"> <div v-if="currentResume" class="resume-detail">
<p><strong>姓名</strong>{{ currentResume.name }}</p> <!-- 基本信息 -->
<p><strong>性别</strong>{{ currentResume.gender }}</p> <div class="section">
<el-divider>教育经历</el-divider> <div class="section-title">基本信息</div>
<div v-for="(e, i) in currentResume.education" :key="i">{{ e.school }} · {{ e.degree }} · {{ e.major }}</div> <div class="info-grid">
<el-divider>工作经历</el-divider> <div class="info-item">
<div v-for="(e, i) in currentResume.experience" :key="i">{{ e.company }} · {{ e.position }} · {{ e.duration }}</div> <span class="label">姓名</span>
<div v-if="currentResume.attachment_url" style="margin-top:16px"> <span class="value">{{ currentResume.name || '-' }}</span>
<a :href="currentResume.attachment_url" target="_blank">下载简历附件</a> </div>
<div class="info-item">
<span class="label">性别</span>
<span class="value">{{ getGenderLabel(currentResume.gender) }}</span>
</div>
<div class="info-item">
<span class="label">邮箱</span>
<span class="value">{{ currentResume.email || '-' }}</span>
</div>
<div class="info-item">
<span class="label">手机</span>
<span class="value">{{ currentResume.phone || '-' }}</span>
</div>
<div class="info-item">
<span class="label">生日</span>
<span class="value">{{ currentResume.birthday || '-' }}</span>
</div>
</div>
</div>
<!-- 教育经历 -->
<div class="section">
<div class="section-title">教育经历</div>
<div v-if="currentResume.education?.length" class="list-items">
<div v-for="(e, i) in currentResume.education" :key="i" class="list-item">
<div class="item-header">{{ e.school }}</div>
<div class="item-detail">{{ e.degree }} · {{ e.major }}</div>
<div class="item-detail">{{ e.start_year }} - {{ e.end_year }}</div>
</div>
</div>
<div v-else style="color: #999; font-size: 12px;">暂无教育经历</div>
</div>
<!-- 工作经历 -->
<div class="section">
<div class="section-title">工作经历</div>
<div v-if="currentResume.experience?.length" class="list-items">
<div v-for="(e, i) in currentResume.experience" :key="i" class="list-item">
<div class="item-header">{{ e.company }} - {{ e.position }}</div>
<div class="item-detail">{{ e.duration }}</div>
<div class="item-detail" v-if="e.description">{{ e.description }}</div>
</div>
</div>
<div v-else style="color: #999; font-size: 12px;">暂无工作经历</div>
</div>
<!-- 附件 -->
<div class="section" v-if="currentResume.attachment_url">
<div class="section-title">附件</div>
<div style="margin-top: 8px;">
<el-button type="primary" @click="downloadAttachment">
<el-icon><Download /></el-icon>
下载简历附件
</el-button>
</div>
</div> </div>
</div> </div>
</el-dialog> </el-dialog>
@ -44,12 +98,20 @@
import { ref, onMounted } from 'vue' import { ref, onMounted } from 'vue'
import { getManageApplications, updateApplicationStatus } from '@/api/applications' import { getManageApplications, updateApplicationStatus } from '@/api/applications'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import { Download } from '@element-plus/icons-vue'
const applications = ref([]) const applications = ref([])
const loading = ref(false) const loading = ref(false)
const resumeVisible = ref(false) const resumeVisible = ref(false)
const currentResume = ref(null) const currentResume = ref(null)
const genderMap = {
'male': '男',
'female': '女',
'other': '其他',
'': '-'
}
onMounted(async () => { onMounted(async () => {
loading.value = true loading.value = true
const { data } = await getManageApplications() const { data } = await getManageApplications()
@ -68,4 +130,87 @@ function viewResume(row) {
currentResume.value = row.resume_snapshot currentResume.value = row.resume_snapshot
resumeVisible.value = true resumeVisible.value = true
} }
function getGenderLabel(gender) {
return genderMap[gender] || '-'
}
function downloadAttachment() {
if (!currentResume.value?.attachment_url) {
ElMessage.warning('附件不可用')
return
}
const link = document.createElement('a')
link.href = currentResume.value.attachment_url
link.download = `${currentResume.value.name}_resume`
link.style.display = 'none'
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
ElMessage.success('下载已启动')
}
</script> </script>
<style scoped>
.section {
margin-bottom: 24px;
}
.section-title {
font-size: 14px;
font-weight: 600;
color: #1a1a2e;
margin-bottom: 12px;
border-bottom: 1px solid #f0f0f0;
padding-bottom: 8px;
}
.info-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px 16px;
}
.info-item {
display: flex;
align-items: center;
font-size: 13px;
}
.info-item .label {
color: #666;
min-width: 50px;
font-weight: 500;
}
.info-item .value {
color: #333;
flex: 1;
}
.list-items {
display: flex;
flex-direction: column;
gap: 12px;
}
.list-item {
padding: 12px;
background: #f9f9f9;
border-radius: 4px;
border-left: 3px solid #409eff;
}
.item-header {
font-size: 13px;
font-weight: 500;
color: #1a1a2e;
margin-bottom: 4px;
}
.item-detail {
font-size: 12px;
color: #666;
line-height: 1.5;
}
</style>

View File

@ -16,7 +16,8 @@ export default defineConfig({
}, },
server: { server: {
proxy: { proxy: {
'/api': { target: 'http://127.0.0.1:8000', changeOrigin: true } '/api': { target: 'http://127.0.0.1:8000', changeOrigin: true },
'/media': { target: 'http://127.0.0.1:8000', changeOrigin: true }
} }
} }
}) })