简历上传,pdf简历下载

This commit is contained in:
2026-04-03 17:52:07 +08:00
parent 821a950df2
commit a39835be01
25 changed files with 2076 additions and 301 deletions
+104 -37
View File
@@ -77,15 +77,22 @@
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import SideNav from '@/components/SideNav.vue'
import { uploadResume } from '@/utils/aiRequest'
import { fetchResumeList, type ResumeListItem } from '@/api/resume'
const router = useRouter()
// ==================== 头像颜色池 ====================
/** 头像背景色列表,按索引循环取色 */
const avatarColors = ['#1A1A2E', '#4FC2C9', '#F2994A', '#6C5CE7', '#E17055', '#00B894', '#FDCB6E']
// ==================== 类型定义 ====================
/** 简历列表项类型 */
/** 简历列表项(前端展示用) */
interface ResumeItem {
id: string
name: string
@@ -97,41 +104,68 @@ interface ResumeItem {
createdAt: string
}
// ==================== 数据(模拟数据,后续对接接口) ====================
// ==================== 工具方法 ====================
/**
* 将 Instant 时间戳转为友好的相对时间文案
* @param instant 后端返回的 Instant 对象
*/
function formatTime(instant?: { seconds?: number; nanos?: number }): string {
if (!instant?.seconds) return '-'
const date = new Date(instant.seconds * 1000)
const now = Date.now()
const diff = now - date.getTime()
const minutes = Math.floor(diff / 60000)
if (minutes < 1) return '刚刚'
if (minutes < 60) return `${minutes}分钟前`
const hours = Math.floor(minutes / 60)
if (hours < 24) return `${hours}小时前`
const days = Math.floor(hours / 24)
if (days < 30) return `${days}天前`
const months = Math.floor(days / 30)
if (months < 12) return `${months}个月前`
return `${Math.floor(months / 12)}年前`
}
/**
* 将后端简历数据转为前端展示结构
*/
function mapResumeItem(item: ResumeListItem, index: number): ResumeItem {
const name = item.resumeName || '未命名简历'
return {
id: String(item.id ?? ''),
name,
avatarLetter: name.charAt(0) || '?',
avatarColor: avatarColors[index % avatarColors.length],
isDefault: item.isDefault === 1,
targetJob: item.targetPosition || '',
updatedAt: formatTime(item.updateTime),
createdAt: formatTime(item.createTime),
}
}
// ==================== 数据 ====================
/** 简历列表 */
const resumeList = ref<ResumeItem[]>([
{
id: '1',
name: '李华_产品经理',
avatarLetter: 'D',
avatarColor: '#1A1A2E',
isDefault: true,
targetJob: '产品经理',
updatedAt: '1个月前',
createdAt: '1个月前',
},
{
id: '2',
name: '李华_产品运营',
avatarLetter: 'C',
avatarColor: '#4FC2C9',
isDefault: false,
targetJob: '产品运营',
updatedAt: '1个月前',
createdAt: '1个月前',
},
{
id: '3',
name: '李华_产品经理',
avatarLetter: '?',
avatarColor: '#BFBFBF',
isDefault: false,
targetJob: '',
updatedAt: '1个月前',
createdAt: '1个月前',
},
])
const resumeList = ref<ResumeItem[]>([])
/** 加载简历列表 */
async function loadResumeList() {
try {
const res = await fetchResumeList()
if (res.code === '0' && res.data) {
resumeList.value = res.data.map(mapResumeItem)
}
} catch (e) {
console.error('获取简历列表失败', e)
}
}
// ==================== 生命周期 ====================
onMounted(() => {
loadResumeList()
})
// ==================== 页面状态 ====================
@@ -154,9 +188,42 @@ function handleAction(action: string, id: string) {
console.log(action, id)
}
/** 上传简历 */
/** 上传简历 — 弹出文件选择,选择后调用 AI 接口上传 */
function handleUpload() {
console.log('上传简历')
const input = document.createElement('input')
input.type = 'file'
// 限定 pdf 和 word 格式
input.accept = '.pdf,.doc,.docx,application/pdf,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document'
input.onchange = async () => {
const file = input.files?.[0]
if (!file) return
// 全屏加载提示,AI 接口响应较慢
const loading = ElLoading.service({
lock: true,
text: '简历解析中,请耐心等待…',
background: 'rgba(0, 0, 0, 0.5)',
customClass: 'resume-upload-loading',
})
try {
const res = await uploadResume(file)
if (res.code === 0) {
// 上传成功,刷新列表并跳转详情页
loadResumeList()
goDetail(String(res.data.resumeId))
} else {
ElMessage.error(res.msg || '上传失败')
}
} catch {
ElMessage.error('上传失败,请稍后重试')
} finally {
loading.close()
}
}
input.click()
}
/** 跳转到简历详情页 */