带文件拖动上传的新版简历上传功能,独立文件操作组件,简历列表页和个人资料页有调用
This commit is contained in:
@@ -116,7 +116,7 @@ async function loadCompletedList() {
|
||||
/** 移除岗位 — 调用 cancelApplyJob 接口并通知父组件 */
|
||||
async function removeJob(job: JobListItem) {
|
||||
try {
|
||||
await cancelApplyJob(Number(job.id))
|
||||
await cancelApplyJob(job.id)
|
||||
emit('removed', job.id)
|
||||
ElMessage.success('已移除')
|
||||
} catch {
|
||||
|
||||
@@ -9,7 +9,15 @@
|
||||
<!-- 简历上传区域 -->
|
||||
<div class="profile-welcome-dialog__form">
|
||||
<div class="profile-welcome-dialog__label">*简历</div>
|
||||
<div class="profile-welcome-dialog__upload-area" @click="handleUploadClick">
|
||||
<div
|
||||
class="profile-welcome-dialog__upload-area"
|
||||
:class="{ 'is-dragover': isDragover }"
|
||||
@click="handleUploadClick"
|
||||
@dragover.prevent="isDragover = true"
|
||||
@dragenter.prevent="isDragover = true"
|
||||
@dragleave.prevent="isDragover = false"
|
||||
@drop.prevent="handleDrop"
|
||||
>
|
||||
<!-- 未上传状态 -->
|
||||
<template v-if="!uploadedFileName">
|
||||
<div class="profile-welcome-dialog__upload-icon">
|
||||
@@ -18,7 +26,8 @@
|
||||
<path d="M12 14V4m0 0l-4 4m4-4l4 4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</div>
|
||||
<p class="profile-welcome-dialog__upload-tip">支持上传PDF、WORD格式,文件大小不超过10M</p>
|
||||
<p class="profile-welcome-dialog__upload-tip fs16 color-3 fw600">点击或拖拽文件到这里上传</p>
|
||||
<p class="profile-welcome-dialog__upload-tip">支持 PDF / DOC / DOCX,单个文件不超过 10MB</p>
|
||||
</template>
|
||||
<!-- 已上传状态 -->
|
||||
<template v-else>
|
||||
@@ -66,6 +75,9 @@ const route = useRoute()
|
||||
/** 已上传的文件名 */
|
||||
const uploadedFileName = ref('')
|
||||
|
||||
/** 拖拽悬停状态 */
|
||||
const isDragover = ref(false)
|
||||
|
||||
/** 内部控制弹窗显隐(用于上传时临时隐藏) */
|
||||
const dialogVisible = ref(false)
|
||||
|
||||
@@ -87,61 +99,84 @@ watch(() => props.modelValue, (val) => {
|
||||
document.body.style.overflow = val ? 'hidden' : ''
|
||||
})
|
||||
|
||||
/** 允许的文件 MIME 类型 */
|
||||
const allowedTypes = [
|
||||
'application/pdf',
|
||||
'application/msword',
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
]
|
||||
|
||||
/** 处理文件校验与上传(点击和拖拽共用) */
|
||||
async function processFile(file: File) {
|
||||
// 格式校验
|
||||
if (!allowedTypes.includes(file.type)) {
|
||||
ElMessage.error('仅支持上传PDF、WORD格式文件')
|
||||
return
|
||||
}
|
||||
|
||||
// 文件大小校验(10MB)
|
||||
if (file.size > 10 * 1024 * 1024) {
|
||||
ElMessage.error('文件大小不能超过10M')
|
||||
return
|
||||
}
|
||||
|
||||
// 标记上传中,降低弹窗层级让 loading 显示在上面
|
||||
uploading.value = true
|
||||
|
||||
// 全屏加载
|
||||
const loading = ElLoading.service({
|
||||
lock: true,
|
||||
text: '简历解析中,请耐心等待…',
|
||||
background: 'rgba(0, 0, 0, 0.5)',
|
||||
customClass: 'profile-welcome-loading',
|
||||
})
|
||||
|
||||
try {
|
||||
// 上传简历文件
|
||||
const res = await uploadResume(file)
|
||||
if (res.code === 0 && res.data?.resumeId) {
|
||||
resumeId.value = String(res.data.resumeId)
|
||||
uploadedFileName.value = file.name
|
||||
|
||||
// 继续调用同步个人资料接口,加载特效延续
|
||||
loading.setText('正在同步个人资料…')
|
||||
await syncProfileFromResume(resumeId.value)
|
||||
loading.close()
|
||||
uploading.value = false
|
||||
ElMessage.success('简历上传并同步成功')
|
||||
} else {
|
||||
loading.close()
|
||||
uploading.value = false
|
||||
ElMessage.error(res.msg || '上传失败')
|
||||
}
|
||||
} catch {
|
||||
loading.close()
|
||||
uploading.value = false
|
||||
ElMessage.error('上传失败,请稍后重试')
|
||||
}
|
||||
}
|
||||
|
||||
/** 点击上传区域 — 弹出文件选择 */
|
||||
function handleUploadClick() {
|
||||
const input = document.createElement('input')
|
||||
input.type = 'file'
|
||||
input.accept = '.pdf,.doc,.docx,application/pdf,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document'
|
||||
|
||||
input.onchange = async () => {
|
||||
input.onchange = () => {
|
||||
const file = input.files?.[0]
|
||||
if (!file) return
|
||||
|
||||
// 文件大小校验(10MB)
|
||||
if (file.size > 10 * 1024 * 1024) {
|
||||
ElMessage.error('文件大小不能超过10M')
|
||||
return
|
||||
}
|
||||
|
||||
// 标记上传中,降低弹窗层级让 loading 显示在上面
|
||||
uploading.value = true
|
||||
|
||||
// 全屏加载
|
||||
const loading = ElLoading.service({
|
||||
lock: true,
|
||||
text: '简历解析中,请耐心等待…',
|
||||
background: 'rgba(0, 0, 0, 0.5)',
|
||||
customClass: 'profile-welcome-loading',
|
||||
})
|
||||
|
||||
try {
|
||||
// 上传简历文件
|
||||
const res = await uploadResume(file)
|
||||
if (res.code === 0 && res.data?.resumeId) {
|
||||
resumeId.value = String(res.data.resumeId)
|
||||
uploadedFileName.value = file.name
|
||||
|
||||
// 继续调用同步个人资料接口,加载特效延续
|
||||
loading.setText('正在同步个人资料…')
|
||||
await syncProfileFromResume(resumeId.value)
|
||||
loading.close()
|
||||
uploading.value = false
|
||||
ElMessage.success('简历上传并同步成功')
|
||||
} else {
|
||||
loading.close()
|
||||
uploading.value = false
|
||||
ElMessage.error(res.msg || '上传失败')
|
||||
}
|
||||
} catch {
|
||||
loading.close()
|
||||
uploading.value = false
|
||||
ElMessage.error('上传失败,请稍后重试')
|
||||
}
|
||||
if (file) processFile(file)
|
||||
}
|
||||
|
||||
input.click()
|
||||
}
|
||||
|
||||
/** 拖拽放下文件 — 取第一个文件进行上传 */
|
||||
function handleDrop(e: DragEvent) {
|
||||
isDragover.value = false
|
||||
const file = e.dataTransfer?.files[0]
|
||||
if (file) processFile(file)
|
||||
}
|
||||
|
||||
/** 点击开始匹配按钮 */
|
||||
function handleStart() {
|
||||
emit('update:modelValue', false)
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
<template>
|
||||
<!-- 简历上传弹窗 — 支持点击选择和拖拽上传,确认后返回 File 对象 -->
|
||||
<Teleport to="body">
|
||||
<div v-if="modelValue" class="resume-upload-overlay" @click.self="handleCancel">
|
||||
<div class="resume-upload-dialog">
|
||||
<!-- 标题行 -->
|
||||
<div class="resume-upload-dialog__header">
|
||||
<h2 class="resume-upload-dialog__title">上传简历</h2>
|
||||
<button class="resume-upload-dialog__close" aria-label="关闭" @click="handleCancel">
|
||||
<svg viewBox="0 0 16 16" fill="none" width="16" height="16">
|
||||
<path d="M4 4l8 8M12 4l-8 8" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 副标题说明 -->
|
||||
<p class="resume-upload-dialog__desc">上传新的简历文件,系统会自动解析内容并同步到简历中心。</p>
|
||||
|
||||
<!-- 上传区域(点击 + 拖拽) -->
|
||||
<div
|
||||
class="resume-upload-dialog__drop-zone"
|
||||
:class="{ 'is-dragover': isDragover, 'has-file': !!selectedFile }"
|
||||
@click="triggerFileInput"
|
||||
@dragover.prevent="isDragover = true"
|
||||
@dragenter.prevent="isDragover = true"
|
||||
@dragleave.prevent="isDragover = false"
|
||||
@drop.prevent="handleDrop"
|
||||
>
|
||||
<!-- 未选择文件状态 -->
|
||||
<template v-if="!selectedFile">
|
||||
<div class="resume-upload-dialog__drop-icon">
|
||||
<svg viewBox="0 0 24 24" fill="none" width="28" height="28">
|
||||
<path d="M12 5v14M5 12h14" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
||||
</svg>
|
||||
</div>
|
||||
<p class="resume-upload-dialog__drop-text">点击或拖拽文件到这里上传</p>
|
||||
<p class="resume-upload-dialog__drop-hint">支持 PDF / DOC / DOCX,单个文件不超过 10MB</p>
|
||||
</template>
|
||||
<!-- 已选择文件状态 -->
|
||||
<template v-else>
|
||||
<div class="resume-upload-dialog__file-info">
|
||||
<span class="resume-upload-dialog__file-icon">📄</span>
|
||||
<span class="resume-upload-dialog__file-name">{{ selectedFile.name }}</span>
|
||||
<button class="resume-upload-dialog__file-remove" aria-label="移除文件" @click.stop="removeFile">✕</button>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- 底部按钮 -->
|
||||
<div class="resume-upload-dialog__footer">
|
||||
<button class="resume-upload-dialog__btn resume-upload-dialog__btn--cancel" @click="handleCancel">取消</button>
|
||||
<button
|
||||
class="resume-upload-dialog__btn resume-upload-dialog__btn--confirm"
|
||||
:disabled="!selectedFile"
|
||||
@click="handleConfirm"
|
||||
>确认上传</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
/** 组件 Props */
|
||||
const props = defineProps<{ modelValue: boolean }>()
|
||||
|
||||
/** 组件 Emits */
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:modelValue', value: boolean): void
|
||||
(e: 'confirm', file: File): void
|
||||
}>()
|
||||
|
||||
/** 拖拽悬停状态 */
|
||||
const isDragover = ref(false)
|
||||
|
||||
/** 已选择的文件 */
|
||||
const selectedFile = ref<File | null>(null)
|
||||
|
||||
/** 允许的文件 MIME 类型 */
|
||||
const allowedTypes = [
|
||||
'application/pdf',
|
||||
'application/msword',
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
]
|
||||
|
||||
/** 弹窗打开时重置状态 */
|
||||
watch(() => props.modelValue, (val) => {
|
||||
if (val) {
|
||||
selectedFile.value = null
|
||||
isDragover.value = false
|
||||
}
|
||||
})
|
||||
|
||||
/** 校验文件格式和大小 */
|
||||
function validateFile(file: File): boolean {
|
||||
if (!allowedTypes.includes(file.type)) {
|
||||
ElMessage.error('仅支持上传 PDF、DOC、DOCX 格式文件')
|
||||
return false
|
||||
}
|
||||
if (file.size > 10 * 1024 * 1024) {
|
||||
ElMessage.error('文件大小不能超过 10MB')
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/** 点击区域触发文件选择 */
|
||||
function triggerFileInput() {
|
||||
const input = document.createElement('input')
|
||||
input.type = 'file'
|
||||
input.accept = '.pdf,.doc,.docx'
|
||||
input.onchange = () => {
|
||||
const file = input.files?.[0]
|
||||
if (file && validateFile(file)) {
|
||||
selectedFile.value = file
|
||||
}
|
||||
}
|
||||
input.click()
|
||||
}
|
||||
|
||||
/** 拖拽放下文件 */
|
||||
function handleDrop(e: DragEvent) {
|
||||
isDragover.value = false
|
||||
const file = e.dataTransfer?.files[0]
|
||||
if (file && validateFile(file)) {
|
||||
selectedFile.value = file
|
||||
}
|
||||
}
|
||||
|
||||
/** 移除已选文件 */
|
||||
function removeFile() {
|
||||
selectedFile.value = null
|
||||
}
|
||||
|
||||
/** 取消关闭弹窗 */
|
||||
function handleCancel() {
|
||||
emit('update:modelValue', false)
|
||||
}
|
||||
|
||||
/** 确认上传 — 将 File 对象传给父组件 */
|
||||
function handleConfirm() {
|
||||
if (selectedFile.value) {
|
||||
emit('confirm', selectedFile.value)
|
||||
emit('update:modelValue', false)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user