151 lines
4.8 KiB
Vue
151 lines
4.8 KiB
Vue
<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>
|