带文件拖动上传的新版简历上传功能,独立文件操作组件,简历列表页和个人资料页有调用
This commit is contained in:
Vendored
+1
@@ -53,6 +53,7 @@ declare module 'vue' {
|
|||||||
ResumeEditNameDialog: typeof import('./src/components/ResumeEditNameDialog.vue')['default']
|
ResumeEditNameDialog: typeof import('./src/components/ResumeEditNameDialog.vue')['default']
|
||||||
ResumeExportDialog: typeof import('./src/components/ResumeExportDialog.vue')['default']
|
ResumeExportDialog: typeof import('./src/components/ResumeExportDialog.vue')['default']
|
||||||
ResumeIssueFixDrawer: typeof import('./src/components/ResumeIssueFixDrawer.vue')['default']
|
ResumeIssueFixDrawer: typeof import('./src/components/ResumeIssueFixDrawer.vue')['default']
|
||||||
|
ResumeUploadDialog: typeof import('./src/components/ResumeUploadDialog.vue')['default']
|
||||||
RouterLink: typeof import('vue-router')['RouterLink']
|
RouterLink: typeof import('vue-router')['RouterLink']
|
||||||
RouterView: typeof import('vue-router')['RouterView']
|
RouterView: typeof import('vue-router')['RouterView']
|
||||||
SettingsDeleteAccountDialog: typeof import('./src/components/SettingsDeleteAccountDialog.vue')['default']
|
SettingsDeleteAccountDialog: typeof import('./src/components/SettingsDeleteAccountDialog.vue')['default']
|
||||||
|
|||||||
+2
-2
@@ -148,9 +148,9 @@ export function applyJob(params: JobApplyParams) {
|
|||||||
/**
|
/**
|
||||||
* 取消投递 / 从待投递移除
|
* 取消投递 / 从待投递移除
|
||||||
* DELETE /job/apply?jobId=xxx
|
* DELETE /job/apply?jobId=xxx
|
||||||
* @param jobId 岗位 ID
|
* @param jobId 岗位 ID(字符串,避免大整数精度丢失)
|
||||||
*/
|
*/
|
||||||
export function cancelApplyJob(jobId: number) {
|
export function cancelApplyJob(jobId: string | number) {
|
||||||
return request.delete<any, ApiResult>('/job/apply', {
|
return request.delete<any, ApiResult>('/job/apply', {
|
||||||
params: { jobId },
|
params: { jobId },
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -58,6 +58,11 @@
|
|||||||
&:hover {
|
&:hover {
|
||||||
background: darken(#F3F4F5, 3%);
|
background: darken(#F3F4F5, 3%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 拖拽悬停时背景色变化
|
||||||
|
&.is-dragover {
|
||||||
|
background: darken(#F3F4F5, 6%);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 上传图标
|
// 上传图标
|
||||||
|
|||||||
@@ -0,0 +1,205 @@
|
|||||||
|
@use '../variables' as *;
|
||||||
|
|
||||||
|
// ==================== 简历上传弹窗 ====================
|
||||||
|
.resume-upload-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: $overlay-bg;
|
||||||
|
z-index: 2200;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resume-upload-dialog {
|
||||||
|
width: 5.6rem;
|
||||||
|
background: $bg-white;
|
||||||
|
border-radius: 0.12rem;
|
||||||
|
padding: 0.32rem 0.36rem;
|
||||||
|
box-shadow: 0 0.04rem 0.2rem rgba(0, 0, 0, 0.15);
|
||||||
|
|
||||||
|
// 标题行
|
||||||
|
&__header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 0.08rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 标题
|
||||||
|
&__title {
|
||||||
|
font-size: 0.2rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: $text-dark;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭按钮
|
||||||
|
&__close {
|
||||||
|
width: 0.28rem;
|
||||||
|
height: 0.28rem;
|
||||||
|
border: none;
|
||||||
|
background: none;
|
||||||
|
cursor: pointer;
|
||||||
|
color: $text-middle;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 0.04rem;
|
||||||
|
transition: background 0.2s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: $bg-main;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 副标题说明
|
||||||
|
&__desc {
|
||||||
|
font-size: 0.13rem;
|
||||||
|
color: $accent;
|
||||||
|
margin: 0 0 0.2rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 拖拽上传区域
|
||||||
|
&__drop-zone {
|
||||||
|
border: 1px dashed $border-color;
|
||||||
|
border-radius: 0.1rem;
|
||||||
|
padding: 0.5rem 0.2rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.2s, border-color 0.2s;
|
||||||
|
background: $theme-color;
|
||||||
|
margin-bottom: 0.28rem;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: $accent;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 拖拽悬停状态 — 背景色加深
|
||||||
|
&.is-dragover {
|
||||||
|
background: darken(#F6FCFC, 4%);
|
||||||
|
border-color: $accent;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 已选择文件状态
|
||||||
|
&.has-file {
|
||||||
|
padding: 0.3rem 0.2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上传图标(加号圆圈)
|
||||||
|
&__drop-icon {
|
||||||
|
width: 0.52rem;
|
||||||
|
height: 0.52rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: $bg-white;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: $accent;
|
||||||
|
margin-bottom: 0.14rem;
|
||||||
|
box-shadow: 0 0.02rem 0.06rem rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 主提示文字
|
||||||
|
&__drop-text {
|
||||||
|
font-size: 0.14rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: $text-dark;
|
||||||
|
margin: 0 0 0.06rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 格式提示
|
||||||
|
&__drop-hint {
|
||||||
|
font-size: 0.12rem;
|
||||||
|
color: $text-middle;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 已选文件信息
|
||||||
|
&__file-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.08rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__file-icon {
|
||||||
|
font-size: 0.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__file-name {
|
||||||
|
font-size: 0.14rem;
|
||||||
|
color: $text-dark;
|
||||||
|
font-weight: 500;
|
||||||
|
max-width: 3.6rem;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__file-remove {
|
||||||
|
border: none;
|
||||||
|
background: none;
|
||||||
|
color: $text-middle;
|
||||||
|
font-size: 0.14rem;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0.02rem 0.04rem;
|
||||||
|
border-radius: 0.03rem;
|
||||||
|
transition: color 0.2s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: $danger;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 底部按钮区域
|
||||||
|
&__footer {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按钮通用
|
||||||
|
&__btn {
|
||||||
|
height: 0.42rem;
|
||||||
|
border-radius: 0.21rem;
|
||||||
|
font-size: 0.14rem;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
border: none;
|
||||||
|
|
||||||
|
// 取消按钮
|
||||||
|
&--cancel {
|
||||||
|
width: 1.4rem;
|
||||||
|
background: $bg-main;
|
||||||
|
color: $text-dark;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确认上传按钮(渐变色)
|
||||||
|
&--confirm {
|
||||||
|
width: 1.8rem;
|
||||||
|
background: $gradient-bg;
|
||||||
|
color: $bg-white;
|
||||||
|
|
||||||
|
&:hover:not(:disabled) {
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
opacity: 0.4;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -35,6 +35,7 @@
|
|||||||
@use './components/settings-delete-account-dialog.scss';
|
@use './components/settings-delete-account-dialog.scss';
|
||||||
@use './components/profile-welcome-dialog.scss';
|
@use './components/profile-welcome-dialog.scss';
|
||||||
@use './components/settings-invite-dialog.scss';
|
@use './components/settings-invite-dialog.scss';
|
||||||
|
@use './components/resume-upload-dialog.scss';
|
||||||
|
|
||||||
// 全局样式(优先级最高)
|
// 全局样式(优先级最高)
|
||||||
@use './auto.scss';
|
@use './auto.scss';
|
||||||
@@ -67,6 +68,67 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==================== Element Plus MessageBox rem 适配修正 ====================
|
||||||
|
.el-overlay {
|
||||||
|
// 确保遮罩层 z-index 足够高
|
||||||
|
z-index: 2100 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-message-box {
|
||||||
|
width: 420px !important;
|
||||||
|
padding: 20px !important;
|
||||||
|
border-radius: 8px !important;
|
||||||
|
font-size: 14px !important;
|
||||||
|
|
||||||
|
&__header {
|
||||||
|
padding: 0 0 12px 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__title {
|
||||||
|
font-size: 16px !important;
|
||||||
|
line-height: 1.4 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__headerbtn {
|
||||||
|
top: 20px !important;
|
||||||
|
right: 20px !important;
|
||||||
|
width: 16px !important;
|
||||||
|
height: 16px !important;
|
||||||
|
font-size: 16px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__content {
|
||||||
|
padding: 12px 0 !important;
|
||||||
|
font-size: 14px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__message {
|
||||||
|
font-size: 14px !important;
|
||||||
|
line-height: 1.5 !important;
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-size: 14px !important;
|
||||||
|
line-height: 1.5 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__status {
|
||||||
|
font-size: 20px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__btns {
|
||||||
|
padding: 8px 0 0 0 !important;
|
||||||
|
|
||||||
|
.el-button {
|
||||||
|
font-size: 14px !important;
|
||||||
|
padding: 8px 20px !important;
|
||||||
|
height: 32px !important;
|
||||||
|
border-radius: 4px !important;
|
||||||
|
min-width: 60px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ==================== Element Plus Loading rem 适配修正 + 品牌色覆盖 ====================
|
// ==================== Element Plus Loading rem 适配修正 + 品牌色覆盖 ====================
|
||||||
.el-loading-mask {
|
.el-loading-mask {
|
||||||
// 全屏 loading 遮罩层 z-index 确保最高
|
// 全屏 loading 遮罩层 z-index 确保最高
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ async function loadCompletedList() {
|
|||||||
/** 移除岗位 — 调用 cancelApplyJob 接口并通知父组件 */
|
/** 移除岗位 — 调用 cancelApplyJob 接口并通知父组件 */
|
||||||
async function removeJob(job: JobListItem) {
|
async function removeJob(job: JobListItem) {
|
||||||
try {
|
try {
|
||||||
await cancelApplyJob(Number(job.id))
|
await cancelApplyJob(job.id)
|
||||||
emit('removed', job.id)
|
emit('removed', job.id)
|
||||||
ElMessage.success('已移除')
|
ElMessage.success('已移除')
|
||||||
} catch {
|
} catch {
|
||||||
|
|||||||
@@ -9,7 +9,15 @@
|
|||||||
<!-- 简历上传区域 -->
|
<!-- 简历上传区域 -->
|
||||||
<div class="profile-welcome-dialog__form">
|
<div class="profile-welcome-dialog__form">
|
||||||
<div class="profile-welcome-dialog__label">*简历</div>
|
<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">
|
<template v-if="!uploadedFileName">
|
||||||
<div class="profile-welcome-dialog__upload-icon">
|
<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"/>
|
<path d="M12 14V4m0 0l-4 4m4-4l4 4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</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>
|
||||||
<!-- 已上传状态 -->
|
<!-- 已上传状态 -->
|
||||||
<template v-else>
|
<template v-else>
|
||||||
@@ -66,6 +75,9 @@ const route = useRoute()
|
|||||||
/** 已上传的文件名 */
|
/** 已上传的文件名 */
|
||||||
const uploadedFileName = ref('')
|
const uploadedFileName = ref('')
|
||||||
|
|
||||||
|
/** 拖拽悬停状态 */
|
||||||
|
const isDragover = ref(false)
|
||||||
|
|
||||||
/** 内部控制弹窗显隐(用于上传时临时隐藏) */
|
/** 内部控制弹窗显隐(用于上传时临时隐藏) */
|
||||||
const dialogVisible = ref(false)
|
const dialogVisible = ref(false)
|
||||||
|
|
||||||
@@ -87,15 +99,20 @@ watch(() => props.modelValue, (val) => {
|
|||||||
document.body.style.overflow = val ? 'hidden' : ''
|
document.body.style.overflow = val ? 'hidden' : ''
|
||||||
})
|
})
|
||||||
|
|
||||||
/** 点击上传区域 — 弹出文件选择 */
|
/** 允许的文件 MIME 类型 */
|
||||||
function handleUploadClick() {
|
const allowedTypes = [
|
||||||
const input = document.createElement('input')
|
'application/pdf',
|
||||||
input.type = 'file'
|
'application/msword',
|
||||||
input.accept = '.pdf,.doc,.docx,application/pdf,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document'
|
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||||
|
]
|
||||||
|
|
||||||
input.onchange = async () => {
|
/** 处理文件校验与上传(点击和拖拽共用) */
|
||||||
const file = input.files?.[0]
|
async function processFile(file: File) {
|
||||||
if (!file) return
|
// 格式校验
|
||||||
|
if (!allowedTypes.includes(file.type)) {
|
||||||
|
ElMessage.error('仅支持上传PDF、WORD格式文件')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// 文件大小校验(10MB)
|
// 文件大小校验(10MB)
|
||||||
if (file.size > 10 * 1024 * 1024) {
|
if (file.size > 10 * 1024 * 1024) {
|
||||||
@@ -137,11 +154,29 @@ function handleUploadClick() {
|
|||||||
uploading.value = false
|
uploading.value = false
|
||||||
ElMessage.error('上传失败,请稍后重试')
|
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 = () => {
|
||||||
|
const file = input.files?.[0]
|
||||||
|
if (file) processFile(file)
|
||||||
}
|
}
|
||||||
|
|
||||||
input.click()
|
input.click()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 拖拽放下文件 — 取第一个文件进行上传 */
|
||||||
|
function handleDrop(e: DragEvent) {
|
||||||
|
isDragover.value = false
|
||||||
|
const file = e.dataTransfer?.files[0]
|
||||||
|
if (file) processFile(file)
|
||||||
|
}
|
||||||
|
|
||||||
/** 点击开始匹配按钮 */
|
/** 点击开始匹配按钮 */
|
||||||
function handleStart() {
|
function handleStart() {
|
||||||
emit('update:modelValue', false)
|
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>
|
||||||
+36
-4
@@ -10,8 +10,8 @@
|
|||||||
@save="handleSaveEdit"
|
@save="handleSaveEdit"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- 欢迎上传简历弹窗 -->
|
<!-- 欢迎上传简历弹窗(首次无个人资料时自动弹出) -->
|
||||||
<ProfileWelcomeDialog v-model="showWelcomeDialog" />
|
<ResumeUploadDialog v-model="showWelcomeDialog" @confirm="handleWelcomeUpload" />
|
||||||
|
|
||||||
<!-- 页面标题 -->
|
<!-- 页面标题 -->
|
||||||
<div class="profile-page__header">
|
<div class="profile-page__header">
|
||||||
@@ -60,8 +60,11 @@ import { useStore } from 'vuex'
|
|||||||
import SideNav from '@/components/SideNav.vue'
|
import SideNav from '@/components/SideNav.vue'
|
||||||
import ProfileEditDrawer from '@/components/ProfileEditDrawer.vue'
|
import ProfileEditDrawer from '@/components/ProfileEditDrawer.vue'
|
||||||
import ProfilePageContent from '@/components/ProfilePageContent.vue'
|
import ProfilePageContent from '@/components/ProfilePageContent.vue'
|
||||||
import ProfileWelcomeDialog from '@/components/ProfileWelcomeDialog.vue'
|
import ResumeUploadDialog from '@/components/ResumeUploadDialog.vue'
|
||||||
import { saveProfile, fetchProfile, fetchEducation, saveEducation, fetchWork, saveWork, fetchInternship, saveInternship, fetchProject, saveProject, fetchCompetition, saveCompetition } from '@/api/profile'
|
import { saveProfile, fetchProfile, fetchEducation, saveEducation, fetchWork, saveWork, fetchInternship, saveInternship, fetchProject, saveProject, fetchCompetition, saveCompetition, syncProfileFromResume } from '@/api/profile'
|
||||||
|
import { uploadResume } from '@/utils/aiRequest'
|
||||||
|
import { ElMessage, ElLoading } from 'element-plus'
|
||||||
|
import 'element-plus/es/components/loading/style/css'
|
||||||
import type { SaveEducationItem, SaveWorkItem, SaveProjectItem, SaveCompetitionItem } from '@/api/profile'
|
import type { SaveEducationItem, SaveWorkItem, SaveProjectItem, SaveCompetitionItem } from '@/api/profile'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@@ -217,6 +220,35 @@ const showEditDrawer = ref(false)
|
|||||||
/** 欢迎弹窗的显示状态 */
|
/** 欢迎弹窗的显示状态 */
|
||||||
const showWelcomeDialog = ref(false)
|
const showWelcomeDialog = ref(false)
|
||||||
|
|
||||||
|
/** 欢迎弹窗确认上传回调 — 上传简历并同步个人资料,完成后刷新页面 */
|
||||||
|
async function handleWelcomeUpload(file: File) {
|
||||||
|
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) {
|
||||||
|
// 继续同步个人资料
|
||||||
|
loading.setText('正在同步个人资料…')
|
||||||
|
await syncProfileFromResume(String(res.data.resumeId))
|
||||||
|
loading.close()
|
||||||
|
ElMessage.success('简历上传并同步成功')
|
||||||
|
// 刷新页面数据
|
||||||
|
router.go(0)
|
||||||
|
} else {
|
||||||
|
loading.close()
|
||||||
|
ElMessage.error(res.msg || '上传失败')
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
loading.close()
|
||||||
|
ElMessage.error('上传失败,请稍后重试')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** 登录状态 */
|
/** 登录状态 */
|
||||||
const isAuthenticated = computed(() => store.state.isAuthenticated)
|
const isAuthenticated = computed(() => store.state.isAuthenticated)
|
||||||
|
|
||||||
|
|||||||
+19
-14
@@ -92,6 +92,12 @@
|
|||||||
:resume-id="exportResumeId"
|
:resume-id="exportResumeId"
|
||||||
:resume-name="exportResumeName"
|
:resume-name="exportResumeName"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<!-- 上传简历弹窗组件 -->
|
||||||
|
<ResumeUploadDialog
|
||||||
|
v-model="uploadDialogVisible"
|
||||||
|
@confirm="handleUploadConfirm"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -101,13 +107,15 @@ import { useRouter } from 'vue-router'
|
|||||||
import SideNav from '@/components/SideNav.vue'
|
import SideNav from '@/components/SideNav.vue'
|
||||||
import ResumeEditNameDialog from '@/components/ResumeEditNameDialog.vue'
|
import ResumeEditNameDialog from '@/components/ResumeEditNameDialog.vue'
|
||||||
import ResumeExportDialog from '@/components/ResumeExportDialog.vue'
|
import ResumeExportDialog from '@/components/ResumeExportDialog.vue'
|
||||||
|
import ResumeUploadDialog from '@/components/ResumeUploadDialog.vue'
|
||||||
import { uploadResume } from '@/utils/aiRequest'
|
import { uploadResume } from '@/utils/aiRequest'
|
||||||
import {
|
import {
|
||||||
fetchResumeList, deleteResume, type ResumeListItem,
|
fetchResumeList, deleteResume, type ResumeListItem,
|
||||||
} from '@/api/resume'
|
} from '@/api/resume'
|
||||||
import { ElMessage, ElMessageBox, ElLoading } from 'element-plus'
|
import { ElMessage, ElMessageBox, ElLoading } from 'element-plus'
|
||||||
// ElLoading.service() 是命令式调用,按需引入插件不会自动加载其样式,需手动引入
|
// 命令式调用的组件,按需引入插件不会自动加载其样式,需手动引入
|
||||||
import 'element-plus/es/components/loading/style/css'
|
import 'element-plus/es/components/loading/style/css'
|
||||||
|
import 'element-plus/es/components/message-box/style/css'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
@@ -218,6 +226,11 @@ const exportResumeId = ref('')
|
|||||||
/** 当前导出的简历名称(用于文件名) */
|
/** 当前导出的简历名称(用于文件名) */
|
||||||
const exportResumeName = ref('')
|
const exportResumeName = ref('')
|
||||||
|
|
||||||
|
// ==================== 上传简历弹窗状态 ====================
|
||||||
|
|
||||||
|
/** 上传弹窗是否可见 */
|
||||||
|
const uploadDialogVisible = ref(false)
|
||||||
|
|
||||||
/** 弹出菜单操作项 */
|
/** 弹出菜单操作项 */
|
||||||
const popupActions = ['设为默认简历', '编辑名称岗位', '导出简历', '删除']
|
const popupActions = ['设为默认简历', '编辑名称岗位', '导出简历', '删除']
|
||||||
|
|
||||||
@@ -276,17 +289,13 @@ async function handleAction(action: string, id: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 上传简历 — 弹出文件选择,选择后调用 AI 接口上传 */
|
/** 上传简历 — 打开上传弹窗 */
|
||||||
function handleUpload() {
|
function handleUpload() {
|
||||||
const input = document.createElement('input')
|
uploadDialogVisible.value = true
|
||||||
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
|
|
||||||
|
|
||||||
|
/** 上传弹窗确认回调 — 接收 File 对象,调用接口上传 */
|
||||||
|
async function handleUploadConfirm(file: File) {
|
||||||
// 全屏加载提示,AI 接口响应较慢
|
// 全屏加载提示,AI 接口响应较慢
|
||||||
const loading = ElLoading.service({
|
const loading = ElLoading.service({
|
||||||
lock: true,
|
lock: true,
|
||||||
@@ -303,7 +312,6 @@ function handleUpload() {
|
|||||||
// 等待让后端异步处理数据,再关闭加载动画并跳转详情页
|
// 等待让后端异步处理数据,再关闭加载动画并跳转详情页
|
||||||
await new Promise(resolve => setTimeout(resolve, 2000))
|
await new Promise(resolve => setTimeout(resolve, 2000))
|
||||||
loading.close()
|
loading.close()
|
||||||
// resumeId 已经是字符串(transformResponse 处理过),直接使用
|
|
||||||
goDetail(res.data.resumeId)
|
goDetail(res.data.resumeId)
|
||||||
} else {
|
} else {
|
||||||
loading.close()
|
loading.close()
|
||||||
@@ -313,9 +321,6 @@ function handleUpload() {
|
|||||||
loading.close()
|
loading.close()
|
||||||
ElMessage.error('上传失败,请稍后重试')
|
ElMessage.error('上传失败,请稍后重试')
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
input.click()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 跳转到简历详情页 */
|
/** 跳转到简历详情页 */
|
||||||
|
|||||||
Reference in New Issue
Block a user