Files
offerpai_web/src/components/ResumeUploadDialog.vue
T

151 lines
4.8 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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>