登陆也左侧调整

This commit is contained in:
2026-07-01 15:17:19 +08:00
parent 8773c65b28
commit 51be3e5585
3 changed files with 738 additions and 139 deletions
+3
View File
@@ -518,6 +518,9 @@ body:not(#_) {
.color-9B9EAB{
color:#9B9EAB;
}
.color-accent{
color:$accent;
}
/*border*/
+477 -87
View File
@@ -1,3 +1,4 @@
@use '../variables' as *;
/* 登录页面 — 左右分栏全屏布局 */
.login-view {
font-size: 0.14rem;
@@ -168,7 +169,7 @@
align-items: center;
width: 43%;
height: 100%;
background: #F6FDFB;
background: #fff;
}
/* Logo 行(右侧及简历阶段通用) */
@@ -176,6 +177,12 @@
display: flex;
align-items: center;
gap: 0.12rem;
width: 3.41rem;
img{
width: 1.6rem;
height: 0.4rem;
}
}
.login-view__logo-box {
@@ -215,112 +222,135 @@
}
.login-view__logo-row--right {
margin-bottom: 0.4rem;
margin-bottom: 0.6rem;
}
/* 表单包裹 */
.login-view__form-wrap {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.28rem;
width: 4rem;
gap: 0.24rem;
width: 3.41rem;
}
/* 标题 */
.login-view__heading {
width: 100%;
width: 3.41rem;
display: flex;
flex-direction: column;
gap: 0.1rem;
gap: 0.05rem;
}
.login-view__title {
font-weight: 700;
font-size: 0.32rem;
line-height: 0.39rem;
color: #132034;
font-size: 0.18rem;
line-height: 0.22rem;
color: #18181A;
}
.login-view__subtitle {
font-weight: 400;
font-size: 0.15rem;
line-height: 0.18rem;
color: #64748B;
font-size: 0.10rem;
line-height: 0.12rem;
color: #616367;
>span:nth-child(2){
color: $accent;
}
}
/* ==================== 手机号输入行 ==================== */
.login-view__phone-row {
box-sizing: border-box;
display: flex;
align-items: center;
width: 100%;
height: 0.52rem;
border-radius: 0.12rem;
overflow: hidden;
width: 3.41rem;
height: 0.40rem;
background: #FFFFFF;
border: 1px solid #C8C9CD;
box-shadow: 0 0.08rem 0.20rem rgba(15, 83, 87, 0.05);
border-radius: 0.08rem;
}
.login-view__country-code {
/* 手机图标 */
.login-view__phone-icon {
display: flex;
align-items: center;
padding: 0 0.16rem;
gap: 0.06rem;
width: 0.96rem;
height: 100%;
background: #F3FFFD;
border: 1px solid #BFE8E2;
border-radius: 0.12rem 0 0 0.12rem;
cursor: pointer;
justify-content: center;
width: 0.18rem;
height: 0.18rem;
margin-left: 0.16rem;
flex-shrink: 0;
svg {
width: 0.18rem;
height: 0.18rem;
}
}
.login-view__flag {
font-size: 0.16rem;
line-height: 1;
}
.login-view__code-text {
/* 国家码标签 */
.login-view__country-label {
margin-left: 0.06rem;
font-weight: 500;
font-size: 0.15rem;
line-height: 0.18rem;
color: #132034;
font-size: 0.12rem;
line-height: 0.15rem;
color: #202124;
white-space: nowrap;
}
.login-view__code-arrow {
font-size: 0.11rem;
color: #94A3B8;
/* 下拉箭头 */
.login-view__country-chevron {
display: flex;
align-items: center;
justify-content: center;
width: 0.20rem;
height: 0.20rem;
flex-shrink: 0;
cursor: pointer;
svg {
width: 0.20rem;
height: 0.20rem;
}
}
.login-view__separator {
/* 分隔线 */
.login-view__input-divider {
width: 1px;
height: 100%;
background: #BFE8E2;
height: 0.30rem;
background: #E7F2F3;
margin: 0 0.16rem;
flex-shrink: 0;
}
/* 手机号输入框 */
.login-view__phone-input {
flex: 1;
height: 100%;
padding: 0 0.16rem;
background: #FAFBFC;
border: 1px solid #E2E8F0;
border-left: none;
border-radius: 0 0.12rem 0.12rem 0;
font-size: 0.15rem;
color: #132034;
border: none;
background: transparent;
font-weight: 400;
font-size: 0.12rem;
line-height: 0.15rem;
color: #202124;
outline: none;
&::placeholder {
color: #94A3B8;
color: #939599;
}
}
/* ==================== 发送验证码按钮(步骤一) ==================== */
.login-view__send-btn {
width: 100%;
height: 0.52rem;
background: linear-gradient(90deg, #12C7BE 0%, #06B6D4 100%);
border-radius: 0.12rem;
width: 3.41rem;
height: 0.40rem;
background: linear-gradient(90deg, #4FC2C9 0%, #42A8B3 100%);
box-shadow: 0 0.16rem 0.30rem rgba(15, 83, 87, 0.15);
border-radius: 0.08rem;
border: none;
font-weight: 600;
font-size: 0.16rem;
font-size: 0.12rem;
line-height: 0.15rem;
color: #FFFFFF;
cursor: pointer;
transition: opacity 0.2s;
@@ -361,8 +391,8 @@
display: flex;
justify-content: center;
align-items: center;
width: 0.56rem;
height: 0.64rem;
width: 0.42rem;
height: 0.46rem;
background: #FAFBFC;
border: 1px solid #E2E8F0;
border-radius: 0.12rem;
@@ -409,8 +439,8 @@
color: #64748B;
text-align: center;
padding-right: 0.1rem;
margin-top: -0.19rem;
margin-bottom: -0.10rem;
margin-top: -0.14rem;
margin-bottom: -0.06rem;
}
.login-view__sms-status--error {
@@ -420,12 +450,12 @@
/* ==================== 状态按钮(步骤二) ==================== */
.login-view__status-btn {
width: 100%;
height: 0.52rem;
height: 0.4rem;
background: linear-gradient(90deg, #12C7BE 0%, #06B6D4 100%);
border-radius: 0.12rem;
border: none;
font-weight: 600;
font-size: 0.16rem;
font-size: 0.14rem;
color: #FFFFFF;
cursor: pointer;
display: flex;
@@ -464,6 +494,7 @@
display: flex;
align-items: center;
gap: 0.06rem;
width: 3.41rem;
}
.login-view__checkbox {
@@ -472,17 +503,18 @@
height: 0.18rem;
background: #FFFFFF;
border: 1.5px solid #CBD5E1;
border-radius: 0.05rem;
border-radius: 0.04rem;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s;
flex-shrink: 0;
}
.login-view__checkbox--checked {
background: #12C7BE;
border-color: #12C7BE;
background: #4FC2C9;
border-color: #4FC2C9;
}
.login-view__check-icon {
@@ -496,14 +528,14 @@
font-weight: 400;
font-size: 0.12rem;
line-height: 0.15rem;
color: #94A3B8;
color: #616367;
}
.login-view__agreement-link {
font-weight: 500;
font-size: 0.12rem;
line-height: 0.15rem;
color: #12C7BE;
color: #42A8B3;
cursor: pointer;
&:hover {
@@ -515,17 +547,16 @@
/* ==================== 简历上传阶段 — 右侧面板 ==================== */
.login-view__form-wrap--resume {
width: 4.6rem;
min-height: 4rem;
width: 3.41rem;
}
/* 上传区域 */
.login-view__upload-area {
width: 100%;
padding: 0.6rem 0.4rem;
border: 1px dashed #E2E8F0;
padding: 0.4rem 0.4rem;
border: 2px solid #D6F1EF;
border-radius: 0.12rem;
background: #FFFFFF;
background: #F6FFFF;
display: flex;
flex-direction: column;
align-items: center;
@@ -560,10 +591,11 @@
}
.login-view__upload-btn {
padding: 0.14rem 0.4rem;
height: 0.4rem;
line-height: 0.4rem;
background: linear-gradient(90deg, #12C7BE 0%, #06B6D4 100%);
border: none;
border-radius: 0.26rem;
border-radius: 0.08rem;
font-size: 0.16rem;
font-weight: 600;
color: #FFFFFF;
@@ -575,29 +607,387 @@
}
}
/* 解析中状态 */
.login-view__parsing {
/* 解析中状态 — 新版步骤面板 */
.login-view__parse-panel {
box-sizing: border-box;
width: 3.41rem;
padding: 0.24rem;
background: #F6FFFF;
border: 1px solid #D6F1EF;
border-radius: 0.08rem;
}
/* 进度条头部 */
.login-view__parse-progress-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 0.08rem;
}
.login-view__parse-progress-label {
font-weight: 500;
font-size: 0.12rem;
line-height: 0.15rem;
color: #616367;
}
.login-view__parse-progress-percent {
font-weight: 700;
font-size: 0.12rem;
line-height: 0.15rem;
color: #42A8B3;
}
/* 进度轨道 */
.login-view__parse-progress-rail {
width: 100%;
height: 0.06rem;
background: #E7F2F3;
border-radius: 0.03rem;
overflow: hidden;
margin-bottom: 0.20rem;
}
.login-view__parse-progress-fill {
height: 100%;
background: #4FC2C9;
border-radius: 0.03rem;
transition: width 0.3s ease;
}
/* 步骤列表 */
.login-view__parse-steps {
display: flex;
flex-direction: column;
justify-content: center;
}
.login-view__parse-step-item {
display: flex;
align-items: flex-start;
width: 100%;
min-height: 3rem;
position: relative;
}
.login-view__parsing-title {
font-size: 0.32rem;
.login-view__parse-step-left {
display: flex;
flex-direction: column;
align-items: center;
margin-right: 0.10rem;
flex-shrink: 0;
}
/* 步骤圆圈 — 完成(实心深色) */
.login-view__parse-dot {
width: 0.12rem;
height: 0.12rem;
border-radius: 50%;
flex-shrink: 0;
}
.login-view__parse-dot--done {
background: #4FC2C9;
}
/* 步骤圆圈 — 进行中(光晕 + 呼吸内圆) */
.login-view__parse-dot--active {
width: 0.16rem;
height: 0.16rem;
background: #E7FAF5;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-left: -0.02rem;
margin-right: -0.02rem;
}
.login-view__parse-dot-pulse {
width: 0.08rem;
height: 0.08rem;
background: #4FC2C9;
border-radius: 50%;
animation: parse-dot-breathe 1.2s ease-in-out infinite;
}
@keyframes parse-dot-breathe {
0%, 100% { transform: scale(0.7); opacity: 0.7; }
50% { transform: scale(1.2); opacity: 1; }
}
/* 步骤圆圈 — 等待(浅色) */
.login-view__parse-dot--pending {
background: #D6F1EF;
}
/* 连接线 */
.login-view__parse-connector {
width: 0.02rem;
height: 0.18rem;
background: #D6F1EF;
border-radius: 0.01rem;
}
/* 步骤文字 */
.login-view__parse-step-text {
flex: 1;
font-weight: 500;
font-size: 0.12rem;
line-height: 0.15rem;
color: #18181A;
padding-top: 0;
}
.login-view__parse-step-text--pending {
font-weight: 400;
color: #616367;
}
/* 步骤状态文字 */
.login-view__parse-step-state {
font-size: 0.12rem;
line-height: 0.15rem;
flex-shrink: 0;
margin-left: 0.10rem;
}
.login-view__parse-step-state--done {
font-weight: 500;
color: #42A8B3;
}
.login-view__parse-step-state--active {
font-weight: 500;
color: #42A8B3;
}
.login-view__parse-step-state--pending {
font-weight: 400;
color: #939599;
}
/* 解析中按钮(禁用样式) */
.login-view__parse-btn {
width: 3.41rem;
height: 0.40rem;
background: #A6EAE7;
border-radius: 0.08rem;
border: none;
font-weight: 600;
font-size: 0.12rem;
line-height: 0.15rem;
color: #FFFFFF;
cursor: not-allowed;
}
/* ==================== 状态4:上传成功面板 ==================== */
.login-view__success-panel {
box-sizing: border-box;
width: 3.41rem;
padding: 0.28rem 0.24rem;
background: #F6FFFB;
border: 1px solid #BCEED7;
border-radius: 0.08rem;
}
.login-view__success-header {
display: flex;
align-items: center;
gap: 0.14rem;
margin-bottom: 0.24rem;
}
.login-view__success-icon-bg {
width: 0.40rem;
height: 0.40rem;
background: #E7FAF0;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.login-view__success-icon-mark {
font-weight: 700;
color: #132034;
margin-bottom: 0.12rem;
font-size: 0.18rem;
line-height: 0.22rem;
color: #22B36A;
}
.login-view__parsing-subtitle {
font-size: 0.15rem;
color: #64748B;
.login-view__success-header-text {
display: flex;
flex-direction: column;
gap: 0.04rem;
}
/* 失败状态标题红色 */
.login-view__title--error {
color: #12C7BE;
.login-view__success-title {
font-weight: 500;
font-size: 0.13rem;
line-height: 0.16rem;
color: #18181A;
}
.login-view__success-detail {
font-weight: 400;
font-size: 0.12rem;
line-height: 0.15rem;
color: #616367;
}
.login-view__success-tips {
display: flex;
flex-direction: column;
gap: 0.16rem;
}
.login-view__success-tip-item {
display: flex;
align-items: center;
gap: 0.10rem;
}
.login-view__success-tip-dot {
width: 0.06rem;
height: 0.06rem;
background: #22B36A;
border-radius: 50%;
flex-shrink: 0;
}
.login-view__success-tip-text {
font-weight: 400;
font-size: 0.12rem;
line-height: 0.15rem;
color: #616367;
}
/* 进入后台按钮 */
.login-view__success-btn {
width: 3.41rem;
height: 0.40rem;
background: #4FC2C9;
border-radius: 0.08rem;
border: none;
font-weight: 600;
font-size: 0.12rem;
line-height: 0.15rem;
color: #FFFFFF;
cursor: pointer;
transition: opacity 0.2s;
&:hover {
opacity: 0.9;
}
}
/* ==================== 状态3:上传失败面板 ==================== */
.login-view__failed-panel {
box-sizing: border-box;
width: 3.41rem;
padding: 0.28rem 0.24rem;
background: #FFFBFA;
border: 1px solid #FADBD5;
border-radius: 0.08rem;
}
.login-view__failed-header {
display: flex;
align-items: center;
gap: 0.14rem;
margin-bottom: 0.24rem;
}
.login-view__failed-icon-bg {
width: 0.40rem;
height: 0.40rem;
background: #FFF0ED;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.login-view__failed-icon-mark {
font-weight: 700;
font-size: 0.18rem;
line-height: 0.22rem;
color: #FF6B4A;
}
.login-view__failed-header-text {
display: flex;
flex-direction: column;
gap: 0.04rem;
}
.login-view__failed-title {
font-weight: 500;
font-size: 0.13rem;
line-height: 0.16rem;
color: #18181A;
}
.login-view__failed-detail {
font-weight: 400;
font-size: 0.12rem;
line-height: 0.15rem;
color: #616367;
}
.login-view__failed-tips {
display: flex;
flex-direction: column;
gap: 0.16rem;
}
.login-view__failed-tip-item {
display: flex;
align-items: center;
gap: 0.10rem;
}
.login-view__failed-tip-dot {
width: 0.06rem;
height: 0.06rem;
background: #FF8D76;
border-radius: 50%;
flex-shrink: 0;
}
.login-view__failed-tip-text {
font-weight: 400;
font-size: 0.12rem;
line-height: 0.15rem;
color: #616367;
}
/* 重新上传按钮 */
.login-view__failed-btn {
width: 3.41rem;
height: 0.40rem;
background: linear-gradient(90deg, #4FC2C9 0%, #42A8B3 100%);
box-shadow: 0 0.16rem 0.30rem rgba(15, 83, 87, 0.15);
border-radius: 0.08rem;
border: none;
font-weight: 600;
font-size: 0.12rem;
line-height: 0.15rem;
color: #FFFFFF;
cursor: pointer;
transition: opacity 0.2s;
&:hover {
opacity: 0.9;
}
}
.login-view__right-footer{
margin-top: 0.37rem;
font-size: 0.13rem;
width: 4rem;
text-align: center;
color: #999;
padding-top: 0.3rem;
border-top: 1px solid #E7F2F3;
}
+257 -51
View File
@@ -27,8 +27,7 @@
<!-- ===== 登录阶段step 1 & 2===== -->
<template v-if="step === 1 || step === 2">
<div class="login-view__logo-row login-view__logo-row--right">
<div class="login-view__logo-box login-view__logo-box--green">O</div>
<span class="login-view__logo-text login-view__logo-text--dark">Offer派</span>
<img src="@/assets/images/logo.png" alt="">
</div>
<div class="login-view__form-wrap">
@@ -38,15 +37,29 @@
<h2 class="login-view__title">手机号登录/注册</h2>
<p class="login-view__subtitle">未注册用户验证后将自动注册并登录</p>
</div>
<!-- 手机号输入组 -->
<div class="login-view__phone-row">
<div class="login-view__country-code">
<span class="login-view__flag">🇨🇳</span>
<span class="login-view__code-text">+86</span>
<span class="login-view__code-arrow"></span>
<!-- 手机图标 -->
<div class="login-view__phone-icon">
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="4.5" y="2" width="9" height="14" rx="1.5" stroke="#0F6B66" stroke-width="1.5"/>
<line x1="9" y1="2" x2="9" y2="3.5" stroke="#0F6B66" stroke-width="1.5" stroke-linecap="round"/>
</svg>
</div>
<div class="login-view__separator"></div>
<!-- 国家码标签 -->
<span class="login-view__country-label">中国 +86</span>
<!-- 下拉箭头 -->
<div class="login-view__country-chevron">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6 8L10 12L14 8" stroke="#6F8586" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</div>
<!-- 分隔线 -->
<div class="login-view__input-divider"></div>
<!-- 手机号输入框 -->
<input v-model="phone" type="tel" class="login-view__phone-input" placeholder="请输入手机号码" maxlength="11" />
</div>
<!-- 发送验证码按钮 -->
<button class="login-view__send-btn" :disabled="!canSend" @click="handleSendCode">发送验证码</button>
</template>
@@ -54,7 +67,7 @@
<template v-if="step === 2">
<div class="login-view__heading">
<h2 class="login-view__title">请输入验证码</h2>
<p class="login-view__subtitle">短信验证码已发送至 +86{{ phone }} <span class="fw600 ml5 cursor-po" @click="handleChangePhone">修改手机号</span></p>
<p class="login-view__subtitle dflex"><span class="dib">短信验证码已发送至 +86{{ phone }} </span><span class="fw600 ml5 dib cursor-po mlauto" @click="handleChangePhone">修改手机号</span></p>
</div>
<div class="login-view__otp-wrap" @click="focusOtpInput">
<input ref="otpInputRef" v-model="otpValue" type="text" inputmode="numeric" class="login-view__otp-hidden-input" maxlength="6" :disabled="isLoggingIn" @input="handleOtpInput" />
@@ -74,7 +87,7 @@
<!-- 协议勾选步骤一二通用 -->
<div class="login-view__agreement">
<div class="login-view__checkbox" :class="{ 'login-view__checkbox--checked': agreedTerms }" @click="agreedTerms = !agreedTerms">
<span v-if="agreedTerms" class="login-view__check-icon"></span>
<span v-if="agreedTerms" class="login-view__check-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" width="0.13rem" height="0.13rem"><path stroke="currentColor" fill="currentColor" d="M77.248 415.04a64 64 0 0 1 90.496 0l226.304 226.304L846.528 188.8a64 64 0 1 1 90.56 90.496l-543.04 543.04-316.8-316.8a64 64 0 0 1 0-90.496"></path></svg></span>
</div>
<span class="login-view__agreement-text">登录即表示同意</span>
<span class="login-view__agreement-link" @click="openAgreement('ae8065i3')">用户协议</span>
@@ -88,15 +101,24 @@
<template v-if="step === 3">
<div class="login-view__form-wrap login-view__form-wrap--resume">
<!-- 状态1上传区域 -->
<!-- 状态1上传区域 upload-->
<template v-if="resumeState === 'upload'">
<div class="login-view__heading">
<h2 class="login-view__title">一切美好从简历开始~</h2>
<p class="login-view__subtitle">自动解析您的简历为你匹配最合适的岗位</p>
<h2 class="login-view__title">上传简历开启求职之旅</h2>
<p class="login-view__subtitle">解析后为你匹配岗位也会用于后续简历优化</p>
</div>
<!-- 拖拽上传区域 -->
<div class="login-view__upload-area" @dragover.prevent @drop.prevent="handleDrop" @click="triggerFileInput">
<div class="login-view__upload-circle"></div>
<div class="login-view__upload-circle">
<svg width="52" height="56" viewBox="0 0 52 56" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="0.5" y="0.5" width="51" height="55" rx="7.5" fill="#EAF8F7" stroke="#D6F1EF"/>
<rect x="36" width="16" height="16" rx="3" fill="#D6F1EF"/>
<rect x="13" y="21" width="26" height="4" rx="2" fill="#4FC2C9"/>
<rect x="13" y="33" width="30" height="4" rx="2" fill="#BFEAE8"/>
<rect x="13" y="45" width="20" height="4" rx="2" fill="#BFEAE8"/>
</svg>
</div>
<p class="login-view__upload-text">拖拽简历到这里或点击上传</p>
<p class="login-view__upload-hint">支持 PDF / DOC / DOCX不超过 20MB</p>
</div>
@@ -106,26 +128,122 @@
<button class="login-view__upload-btn" @click="triggerFileInput">+ 上传简历</button>
</template>
<!-- 状态2解析中 -->
<!-- 状态2解析中 parsing -->
<template v-if="resumeState === 'parsing'">
<div class="login-view__parsing">
<h2 class="login-view__parsing-title">{{ parsingText }}</h2>
<p class="login-view__parsing-subtitle">自动解析您的简历为你匹配最合适的岗位</p>
<div class="login-view__heading m0auto">
<h2 class="login-view__title">正在解析简历</h2>
<p class="login-view__subtitle">正在识别教育背景工作经历与技能关键词</p>
</div>
<!-- 解析面板 -->
<div class="login-view__parse-panel m0auto">
<!-- 顶部进度条 -->
<div class="login-view__parse-progress-header">
<span class="login-view__parse-progress-label">解析进度</span>
<span class="login-view__parse-progress-percent">{{ parsePercent }}%</span>
</div>
<div class="login-view__parse-progress-rail">
<div class="login-view__parse-progress-fill" :style="{ width: parsePercent + '%' }"></div>
</div>
<!-- 步骤列表 -->
<div class="login-view__parse-steps">
<div v-for="(ps, idx) in parseStepList" :key="idx" class="login-view__parse-step-item">
<!-- 步骤圆圈 + 连接线 -->
<div class="login-view__parse-step-left">
<!-- 完成状态实心深色圆 -->
<div v-if="ps.status === 'done'" class="login-view__parse-dot login-view__parse-dot--done"></div>
<!-- 进行中浅色光晕 + 呼吸动画深色内圆 -->
<div v-else-if="ps.status === 'active'" class="login-view__parse-dot login-view__parse-dot--active">
<div class="login-view__parse-dot-pulse"></div>
</div>
<!-- 等待状态浅色圆 -->
<div v-else class="login-view__parse-dot login-view__parse-dot--pending"></div>
<!-- 连接线最后一项不显示 -->
<div v-if="idx < parseStepList.length - 1" class="login-view__parse-connector"></div>
</div>
<!-- 步骤文字 -->
<span class="login-view__parse-step-text" :class="{ 'login-view__parse-step-text--pending': ps.status === 'pending' }">{{ ps.label }}</span>
<!-- 步骤状态文字 -->
<span class="login-view__parse-step-state" :class="{ 'login-view__parse-step-state--done': ps.status === 'done', 'login-view__parse-step-state--active': ps.status === 'active', 'login-view__parse-step-state--pending': ps.status === 'pending' }">{{ ps.stateText }}</span>
</div>
</div>
</div>
<!-- 解析中按钮禁用状态 -->
<button class="login-view__parse-btn m0auto" disabled>解析中请稍候</button>
</template>
<!-- 状态3上传失败 -->
<!-- 状态4上传成功 success-->
<template v-if="resumeState === 'success'">
<div class="login-view__heading m0auto">
<h2 class="login-view__title">简历上传成功</h2>
<p class="login-view__subtitle">简历已完成解析进入后台继续完善与投递</p>
</div>
<!-- 成功面板 -->
<div class="login-view__success-panel m0auto">
<!-- 成功图标和标题 -->
<div class="login-view__success-header">
<div class="login-view__success-icon-bg">
<span class="login-view__success-icon-mark"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" width="0.15rem" height="0.15rem"><path stroke="currentColor" fill="currentColor" d="M77.248 415.04a64 64 0 0 1 90.496 0l226.304 226.304L846.528 188.8a64 64 0 1 1 90.56 90.496l-543.04 543.04-316.8-316.8a64 64 0 0 1 0-90.496"></path></svg></span>
</div>
<div class="login-view__success-header-text">
<span class="login-view__success-title">简历解析完成</span>
<span class="login-view__success-detail">已识别关键信息岗位匹配已准备好</span>
</div>
</div>
<!-- 成功提示项 -->
<div class="login-view__success-tips">
<div class="login-view__success-tip-item">
<span class="login-view__success-tip-dot"></span>
<span class="login-view__success-tip-text">已提取教育背景工作经历与技能</span>
</div>
<div class="login-view__success-tip-item">
<span class="login-view__success-tip-dot"></span>
<span class="login-view__success-tip-text">可进入后台查看推荐岗位并继续投递</span>
</div>
</div>
</div>
<!-- 进入后台按钮 -->
<button class="login-view__success-btn m0auto" @click="handleEnterDashboard">上传成功进入后台</button>
</template>
<!-- 状态3上传失败 failed-->
<template v-if="resumeState === 'failed'">
<div class="login-view__heading">
<h2 class="login-view__title login-view__title--error">上传失败</h2>
<p class="login-view__subtitle">您的简历解析失败了请重试一次吧~</p>
<div class="login-view__heading m0auto">
<h2 class="login-view__title">简历上传失败</h2>
<p class="login-view__subtitle">文件没有解析成功请检查格式后重新上传 <span class="color-accent cursor-po" @click="handleEnterDashboard">跳过稍后填写</span></p>
</div>
<button class="login-view__upload-btn" @click="handleRetryUpload">重新上传</button>
<!-- 失败面板 -->
<div class="login-view__failed-panel">
<!-- 失败图标和标题 -->
<div class="login-view__failed-header">
<div class="login-view__failed-icon-bg">
<span class="login-view__failed-icon-mark">!</span>
</div>
<div class="login-view__failed-header-text">
<span class="login-view__failed-title">未能读取文件内容</span>
<span class="login-view__failed-detail">可能是文件加密损坏或格式不支持</span>
</div>
</div>
<!-- 失败提示项 -->
<div class="login-view__failed-tips">
<div class="login-view__failed-tip-item">
<span class="login-view__failed-tip-dot"></span>
<span class="login-view__failed-tip-text">请上传 PDF / DOC / DOCX 格式</span>
</div>
<div class="login-view__failed-tip-item">
<span class="login-view__failed-tip-dot"></span>
<span class="login-view__failed-tip-text">文件大小不超过 20MB且未加密</span>
</div>
</div>
</div>
<!-- 重新上传按钮 -->
<button class="login-view__failed-btn" @click="handleRetryUpload">重新上传</button>
</template>
</div>
</template>
<div class="login-view__right-footer">
Offer派 求职工作台
</div>
</div>
<!-- 协议预览弹窗 -->
@@ -252,19 +370,37 @@ let loginRedirect = '/'
const canSend = computed(() => phone.value.length === 11 && agreedTerms.value)
// ==================== 简历上传相关状态 ====================
/** 简历上传子状态:upload=上传区域 parsing=解析中 failed=失败 */
const resumeState = ref<'upload' | 'parsing' | 'failed'>('upload')
/** 解析中显示的文字 */
const parsingText = ref('正在解析个人资料...')
/** 简历上传子状态:upload=上传区域 parsing=解析中 failed=失败 success=成功 */
const resumeState = ref<'upload' | 'parsing' | 'failed' | 'success'>('upload')
/** 解析文字轮换定时器 */
let parsingTimer: ReturnType<typeof setInterval> | null = null
/** 文件选择器引用 */
const fileInputRef = ref<HTMLInputElement | null>(null)
/** 进度条百分比 */
const parsePercent = ref(0)
/** 进度条动画定时器 */
let progressTimer: ReturnType<typeof setInterval> | null = null
/** 接口是否已返回成功 */
let uploadDone = false
/** 解析步骤定义 */
interface ParseStep {
label: string
status: 'pending' | 'active' | 'done'
stateText: string
}
/** 解析中文字轮换列表 */
const parsingSteps = ['正在解析个人资料...', '正在解析教育背景...', '正在解析工作经历...', '正在解析技能特长...', '简历解析成功,即将跳转']
/** 4个解析步骤 */
const parseStepList = ref<ParseStep[]>([
{ label: '教育背景解析...', status: 'pending', stateText: '等待' },
{ label: '工作经历解析...', status: 'pending', stateText: '等待' },
{ label: '技能特长解析...', status: 'pending', stateText: '等待' },
{ label: '个人资料最终确认', status: 'pending', stateText: '等待' }
])
/** 每个步骤的时间分配(毫秒),总模拟时间约8秒 */
const stepDurations = [2000, 2000, 2000, 2000]
// ==================== 登录逻辑 ====================
@@ -432,6 +568,11 @@ function handleRetryUpload() {
resumeState.value = 'upload'
}
/** 进入后台(状态4按钮) */
function handleEnterDashboard() {
router.replace(loginRedirect)
}
/** 开始上传并解析简历 */
async function startUpload(file: File) {
// 校验文件类型和大小
@@ -445,8 +586,16 @@ async function startUpload(file: File) {
return
}
// 进入解析状态
// 进入解析状态,重置步骤
resumeState.value = 'parsing'
uploadDone = false
parsePercent.value = 0
parseStepList.value = [
{ label: '教育背景解析...', status: 'pending', stateText: '等待' },
{ label: '工作经历解析...', status: 'pending', stateText: '等待' },
{ label: '技能特长解析...', status: 'pending', stateText: '等待' },
{ label: '个人资料最终确认', status: 'pending', stateText: '等待' }
]
startParsingAnimation()
try {
@@ -454,13 +603,10 @@ async function startUpload(file: File) {
if (res.code === 0 && res.data?.resumeId) {
// 上传成功,同步个人资料
await syncProfileFromResume(String(res.data.resumeId))
// 停止轮换动画,显示成功
stopParsingAnimation()
parsingText.value = '简历解析成功,即将跳转'
// 等1秒后跳转
setTimeout(() => {
router.replace(loginRedirect)
}, 1000)
// 标记接口已返回
uploadDone = true
// 完成所有步骤的动画后会自动过渡到 success 状态
finishParsingAnimation()
} else {
// 解析失败
stopParsingAnimation()
@@ -472,25 +618,84 @@ async function startUpload(file: File) {
}
}
/** 开始解析文字轮换动画 */
/** 开始解析步骤动画(模拟4步进度) */
function startParsingAnimation() {
let index = 0
parsingText.value = parsingSteps[0]
parsingTimer = setInterval(() => {
index++
if (index < parsingSteps.length - 1) {
// 前4个每2秒切换
parsingText.value = parsingSteps[index]
} else {
// 到第4个(技能特长)后停止轮换,保持显示直到接口返回
parsingText.value = parsingSteps[parsingSteps.length - 2]
if (parsingTimer) { clearInterval(parsingTimer); parsingTimer = null }
let currentStep = 0
const totalSteps = parseStepList.value.length
/** 每个步骤对应的进度百分比段 */
const stepPercents = [25, 50, 75, 95]
// 第一个步骤立即进入进行中
parseStepList.value[0].status = 'active'
parseStepList.value[0].stateText = '进行中'
// 开始进度条平滑增长
let targetPercent = stepPercents[0]
const startTime = Date.now()
let lastStepTime = startTime
progressTimer = setInterval(() => {
const elapsed = Date.now() - lastStepTime
const stepDuration = stepDurations[currentStep] || 2000
if (elapsed >= stepDuration && currentStep < totalSteps - 1) {
// 当前步骤完成
parseStepList.value[currentStep].status = 'done'
parseStepList.value[currentStep].stateText = '完成'
currentStep++
// 下一个步骤进入进行中
parseStepList.value[currentStep].status = 'active'
parseStepList.value[currentStep].stateText = '进行中'
targetPercent = stepPercents[currentStep]
lastStepTime = Date.now()
} else if (currentStep >= totalSteps - 1 && elapsed >= stepDuration) {
// 最后一个步骤时间到,但如果接口还没返回就停在这里等
if (uploadDone) {
// 接口已返回,最后一步也完成
parseStepList.value[currentStep].status = 'done'
parseStepList.value[currentStep].stateText = '完成'
if (progressTimer) { clearInterval(progressTimer); progressTimer = null }
// 进度条跳到随机95-99再走完到100
const finalPercent = Math.floor(95 + Math.random() * 4)
parsePercent.value = finalPercent
setTimeout(() => {
parsePercent.value = 100
setTimeout(() => { resumeState.value = 'success' }, 600)
}, 1000)
return
}
}, 2000)
// 接口未返回,保持在最后步骤的进行中状态
parsePercent.value = Math.min(parsePercent.value + 0.1, 94)
return
}
// 平滑增长进度
const progress = Math.min(elapsed / stepDuration, 1)
const prevPercent = currentStep > 0 ? stepPercents[currentStep - 1] : 0
parsePercent.value = Math.floor(prevPercent + (targetPercent - prevPercent) * progress)
}, 50)
}
/** 停止解析文字轮换动画 */
/** 接口返回后完成动画 */
function finishParsingAnimation() {
// 如果进度动画还在跑,标记 uploadDone 即可让 progressTimer 逻辑处理完成
// 如果动画已经停了(最后一步时间到了等接口),手动完成
if (!progressTimer) {
const lastIdx = parseStepList.value.length - 1
parseStepList.value[lastIdx].status = 'done'
parseStepList.value[lastIdx].stateText = '完成'
const finalPercent = Math.floor(95 + Math.random() * 4)
parsePercent.value = finalPercent
setTimeout(() => {
parsePercent.value = 100
setTimeout(() => { resumeState.value = 'success' }, 600)
}, 1000)
}
}
/** 停止解析动画(失败时调用) */
function stopParsingAnimation() {
if (progressTimer) { clearInterval(progressTimer); progressTimer = null }
if (parsingTimer) { clearInterval(parsingTimer); parsingTimer = null }
}
@@ -498,6 +703,7 @@ function stopParsingAnimation() {
onBeforeUnmount(() => {
if (countdownTimer) { clearInterval(countdownTimer) }
if (parsingTimer) { clearInterval(parsingTimer) }
if (progressTimer) { clearInterval(progressTimer) }
if (waveTimer) { clearTimeout(waveTimer); waveTimer = null }
if (blinkTimer) { clearTimeout(blinkTimer); blinkTimer = null }
if (idleLookTimer) { clearTimeout(idleLookTimer); idleLookTimer = null }