Compare commits

2 Commits
25 changed files with 1346 additions and 626 deletions
+1
View File
@@ -38,6 +38,7 @@ declare module 'vue' {
ElSelect: typeof import('element-plus/es')['ElSelect'] ElSelect: typeof import('element-plus/es')['ElSelect']
ElSwitch: typeof import('element-plus/es')['ElSwitch'] ElSwitch: typeof import('element-plus/es')['ElSwitch']
ElTooltip: typeof import('element-plus/es')['ElTooltip'] ElTooltip: typeof import('element-plus/es')['ElTooltip']
FullscreenLoading: typeof import('./src/components/FullscreenLoading.vue')['default']
HelloWorld: typeof import('./src/components/HelloWorld.vue')['default'] HelloWorld: typeof import('./src/components/HelloWorld.vue')['default']
IndustrySelector: typeof import('./src/components/tools/IndustrySelector.vue')['default'] IndustrySelector: typeof import('./src/components/tools/IndustrySelector.vue')['default']
JobCategorySelector: typeof import('./src/components/tools/JobCategorySelector.vue')['default'] JobCategorySelector: typeof import('./src/components/tools/JobCategorySelector.vue')['default']
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

+3
View File
@@ -518,6 +518,9 @@ body:not(#_) {
.color-9B9EAB{ .color-9B9EAB{
color:#9B9EAB; color:#9B9EAB;
} }
.color-accent{
color:$accent;
}
/*border*/ /*border*/
@@ -6,7 +6,7 @@
position: fixed; position: fixed;
inset: 0; inset: 0;
background: $overlay-bg; background: $overlay-bg;
z-index: 2000; z-index: 2300;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@@ -83,7 +83,7 @@
font-size: 0.13rem; font-size: 0.13rem;
color: $text-dark; color: $text-dark;
background: $bg-white; background: $bg-white;
border: 1px solid $border-color; border: none;
border-radius: 0.04rem; border-radius: 0.04rem;
outline: none; outline: none;
transition: all 0.2s; transition: all 0.2s;
@@ -317,8 +317,7 @@
&__ai-result-card { &__ai-result-card {
padding: 0.1rem 0.12rem; padding: 0.1rem 0.12rem;
background: $theme-color; background: linear-gradient(to bottom,#F5FFFD,#ffffff);
border: 1px solid rgba($accent, 0.2);
border-radius: 0.06rem; border-radius: 0.06rem;
} }
@@ -189,8 +189,8 @@
} }
&__item-date { &__item-date {
font-size: 0.11rem; font-size: 0.13rem;
color: $accent; color: #64748B;
margin-bottom: 0.04rem; margin-bottom: 0.04rem;
} }
@@ -328,12 +328,29 @@
// 条目hover时显示编辑按钮 // 条目hover时显示编辑按钮
&__section-item:hover &__item-edit-btn { &__section-item:hover &__item-edit-btn {
display: flex;
opacity: 1; opacity: 1;
pointer-events: auto; pointer-events: auto;
} }
// 条目内编辑按钮 // hover条目时隐藏时间
&__section-item:hover &__item-date--toggle {
display: none;
}
// 条目内编辑按钮(默认隐藏不占位)
&__item-edit-btn { &__item-edit-btn {
margin-left: 0.08rem; margin-left: 0.08rem;
display: none;
padding-top: 0;
padding-bottom: 0;
}
// 时间切换样式(默认显示,靠右)
&__item-date--toggle {
margin-left: auto;
font-size: 0.13rem;
color: #64748B;
flex-shrink: 0;
} }
} }
@@ -1,6 +1,6 @@
@use '../variables' as *; @use '../variables' as *;
// ==================== 邀请注册送会员弹窗 ==================== // ==================== 邀请注册送会员弹窗重设计版 ====================
.settings-invite-overlay { .settings-invite-overlay {
position: fixed; position: fixed;
top: 0; top: 0;
@@ -18,178 +18,211 @@
.settings-invite-dialog { .settings-invite-dialog {
position: relative; position: relative;
width: 4.2rem; width: 4.2rem;
max-height: 85vh; height: 5.0rem;
background: $bg-white; background: $bg-white;
border-radius: 0.12rem; border-radius: 0.26rem;
padding: 0.32rem 0.28rem; box-shadow: 0 0.2rem 0.44rem rgba(0, 0, 0, 0.14);
box-shadow: 0 0.04rem 0.2rem rgba(0, 0, 0, 0.15); overflow: hidden;
overflow-y: auto; display: flex;
flex-direction: column;
// 顶部渐变区域
&__header {
width: 100%;
height: 1.04rem;
background: linear-gradient(90deg, #12C7BE 0%, #06B6D4 100%);
border-radius: 0.26rem;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
// 皇冠图标
&__crown {
font-size: 0.32rem;
line-height: 0.39rem;
color: $bg-white;
font-weight: 700;
}
// 关闭按钮 // 关闭按钮
&__close { &__close {
position: absolute; position: absolute;
top: 0.14rem; top: 0.24rem;
right: 0.18rem; right: 0.27rem;
font-size: 0.18rem; font-size: 0.22rem;
color: $text-light; line-height: 0.27rem;
color: #8EA0B8;
cursor: pointer; cursor: pointer;
transition: color 0.2s; transition: color 0.2s;
z-index: 1;
&:hover { &:hover {
color: $text-dark; color: $text-dark;
} }
} }
// 内容区域
&__body {
flex: 1;
padding: 0.22rem 0.28rem 0.2rem;
display: flex;
flex-direction: column;
}
// 标题 // 标题
&__title { &__title {
font-size: 0.18rem; font-size: 0.21rem;
font-weight: 700; font-weight: 700;
color: $text-dark; color: #132034;
text-align: center;
margin: 0 0 0.05rem 0;
line-height: 0.25rem;
}
// 副标题
&__subtitle {
font-size: 0.13rem;
font-weight: 400;
color: #64748B;
text-align: center; text-align: center;
margin: 0 0 0.2rem 0; margin: 0 0 0.2rem 0;
line-height: 0.16rem;
} }
// 邀请链接区域 // 邀请链接区域
&__link-box { &__link-box {
background: $bg-main; display: flex;
border-radius: 0.08rem; align-items: center;
padding: 0.14rem 0.16rem; width: 3.64rem;
margin-bottom: 0.14rem; height: 0.44rem;
background: #F3FFFD;
border-radius: 0.22rem;
padding: 0 0.06rem 0 0.16rem;
margin: 0 auto 0.2rem;
box-sizing: border-box;
} }
&__link-text { &__link-text {
font-size: 0.12rem; flex: 1;
color: $text-dark; font-size: 0.11rem;
line-height: 1.7; line-height: 0.13rem;
margin: 0; color: #334155;
word-break: break-all; white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
} }
// 复制链接按钮 // 复制链接按钮
&__copy-btn { &__copy-btn {
width: 100%; width: 1.0rem;
height: 0.4rem; height: 0.32rem;
border: none; border: none;
border-radius: 0.2rem; border-radius: 0.16rem;
background: $text-dark; background: #0F172A;
color: $bg-white; color: $bg-white;
font-size: 0.14rem; font-size: 0.13rem;
font-weight: 600; font-weight: 600;
line-height: 0.16rem;
cursor: pointer; cursor: pointer;
margin-bottom: 0.18rem; flex-shrink: 0;
transition: opacity 0.2s; transition: opacity 0.2s;
text-align: center;
&:hover { &:hover {
opacity: 0.85; opacity: 0.85;
} }
} }
// 步骤提示
&__steps-tip {
font-size: 0.12rem;
color: $text-middle;
text-align: center;
margin: 0 0 0.14rem 0;
}
// 步骤卡片 // 步骤卡片
&__step { &__step {
border: 1px solid $border-color;
border-radius: 0.08rem;
padding: 0.12rem 0.16rem;
margin-bottom: 0.1rem;
background: #F2F4F7;
}
&__step-title {
font-size: 0.13rem;
font-weight: 700;
color: $text-dark;
margin-bottom: 0.04rem;
}
&__step-desc {
font-size: 0.11rem;
color: $text-middle;
margin: 0;
line-height: 1.5;
}
// 统计数据
&__stats {
display: flex; display: flex;
gap: 0.12rem; align-items: center;
margin-top: 0.16rem; width: 3.64rem;
margin-bottom: 0.16rem; height: 0.46rem;
background: #F3FFFD;
border-radius: 0.14rem;
padding: 0 0.14rem;
margin: 0 auto 0.12rem;
box-sizing: border-box;
} }
&__stat-item { // 步骤数字圆圈
flex: 1; &__step-circle {
border: 1px solid $border-color; width: 0.26rem;
border-radius: 0.08rem; height: 0.26rem;
padding: 0.12rem; border-radius: 50%;
background: $bg-white;
border: 1px solid #BFEFE8;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.12rem;
font-weight: 700;
color: #12C7BE;
line-height: 0.15rem;
flex-shrink: 0;
box-sizing: border-box;
}
// 步骤文字
&__step-text {
margin-left: 0.14rem;
font-size: 0.13rem;
font-weight: 500;
color: #334155;
line-height: 0.16rem;
}
// 底部统计 + 按钮
&__footer {
display: flex;
align-items: center;
margin-top: auto;
padding-top: 0.06rem;
}
// 统计项
&__stat {
display: flex;
flex-direction: column;
margin-right: 0.2rem;
} }
&__stat-label { &__stat-label {
font-size: 0.11rem; font-size: 0.13rem;
color: $text-middle; font-weight: 500;
margin-bottom: 0.06rem; color: #64748B;
line-height: 0.16rem;
} }
&__stat-value { &__stat-value {
font-size: 0.16rem; font-size: 0.13rem;
font-weight: 700; font-weight: 500;
color: $text-dark; color: #64748B;
line-height: 0.16rem;
} }
// 活动规则 // 成为正式会员按钮
&__rules { &__member-btn {
display: flex; margin-left: auto;
align-items: center; width: 1.1rem;
justify-content: space-between; height: 0.29rem;
border: 1px solid $border-color; background: #F3FFFD;
border: 1px solid #BFEFE8;
border-radius: 0.08rem; border-radius: 0.08rem;
padding: 0.12rem 0.16rem; font-size: 0.13rem;
font-weight: 400;
color: #000000;
line-height: 0.16rem;
cursor: pointer; cursor: pointer;
transition: background 0.2s; transition: all 0.2s;
text-align: center;
&:hover { &:hover {
background: $bg-main; background: #e8faf8;
} border-color: $accent;
}
&__rules-label {
font-size: 0.13rem;
font-weight: 600;
color: $text-dark;
}
&__rules-arrow {
width: 0.14rem;
height: 0.14rem;
color: $text-middle;
transition: transform 0.2s;
&--open {
transform: rotate(180deg);
}
}
&__rules-content {
margin-top: 0.1rem;
padding: 0.12rem 0.16rem;
background: $bg-main;
border-radius: 0.06rem;
p {
font-size: 0.11rem;
color: $text-middle;
line-height: 1.8;
margin: 0 0 0.04rem 0;
&:last-child {
margin-bottom: 0;
}
} }
} }
} }
+4 -5
View File
@@ -5,7 +5,7 @@
flex-direction: column; flex-direction: column;
width: 2rem; width: 2rem;
height: var(--app-height, 100vh); height: var(--app-height, 100vh);
background: #1a1a2e; background: $bg-white;
color: #fff; color: #fff;
padding: 0.2rem 0.12rem; padding: 0.2rem 0.12rem;
box-sizing: border-box; box-sizing: border-box;
@@ -57,7 +57,7 @@
align-items: center; align-items: center;
gap: 0.08rem; gap: 0.08rem;
padding: 0.1rem 0.12rem; padding: 0.1rem 0.12rem;
border-radius: 0.14rem; border-radius: 0.08rem;
color: #9598ab; color: #9598ab;
text-decoration: none; text-decoration: none;
cursor: pointer; cursor: pointer;
@@ -65,12 +65,12 @@
font-size: 0.14rem; font-size: 0.14rem;
&:hover { &:hover {
background: rgba(255, 255, 255, 0.05); background: $btn-dark;
color: #fff; color: #fff;
} }
&--active { &--active {
background: #1F2937; background: $btn-dark;
color: #fff; color: #fff;
} }
} }
@@ -98,7 +98,6 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 0.04rem; gap: 0.04rem;
border-top: 1px solid #2e3142;
padding-top: 0.12rem; padding-top: 0.12rem;
} }
} }
+6
View File
@@ -82,6 +82,12 @@
padding: 20px !important; padding: 20px !important;
border-radius: 8px !important; border-radius: 8px !important;
font-size: 14px !important; font-size: 14px !important;
// 修正 transform: scale 下弹窗居中
position: fixed !important;
top: 50% !important;
left: 50% !important;
transform: translate(-50%, -50%) !important;
margin: 0 !important;
&__header { &__header {
padding: 0 0 12px 0 !important; padding: 0 0 12px 0 !important;
+477 -184
View File
@@ -1,3 +1,4 @@
@use '../variables' as *;
/* 登录页面 — 左右分栏全屏布局 */ /* 登录页面 — 左右分栏全屏布局 */
.login-view { .login-view {
font-size: 0.14rem; font-size: 0.14rem;
@@ -168,7 +169,7 @@
align-items: center; align-items: center;
width: 43%; width: 43%;
height: 100%; height: 100%;
background: #F6FDFB; background: #fff;
} }
/* Logo 行(右侧及简历阶段通用) */ /* Logo 行(右侧及简历阶段通用) */
@@ -176,6 +177,12 @@
display: flex; display: flex;
align-items: center; align-items: center;
gap: 0.12rem; gap: 0.12rem;
width: 3.41rem;
img{
width: 1.6rem;
height: 0.4rem;
}
} }
.login-view__logo-box { .login-view__logo-box {
@@ -215,112 +222,135 @@
} }
.login-view__logo-row--right { .login-view__logo-row--right {
margin-bottom: 0.4rem; margin-bottom: 0.6rem;
} }
/* 表单包裹 */ /* 表单包裹 */
.login-view__form-wrap { .login-view__form-wrap {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; gap: 0.24rem;
gap: 0.28rem; width: 3.41rem;
width: 4rem;
} }
/* 标题 */ /* 标题 */
.login-view__heading { .login-view__heading {
width: 100%; width: 3.41rem;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 0.1rem; gap: 0.05rem;
} }
.login-view__title { .login-view__title {
font-weight: 700; font-weight: 700;
font-size: 0.32rem; font-size: 0.18rem;
line-height: 0.39rem; line-height: 0.22rem;
color: #132034; color: #18181A;
} }
.login-view__subtitle { .login-view__subtitle {
font-weight: 400; font-weight: 400;
font-size: 0.15rem; font-size: 0.10rem;
line-height: 0.18rem; line-height: 0.12rem;
color: #64748B; color: #616367;
>span:nth-child(2){
color: $accent;
}
} }
/* ==================== 手机号输入行 ==================== */ /* ==================== 手机号输入行 ==================== */
.login-view__phone-row { .login-view__phone-row {
box-sizing: border-box;
display: flex; display: flex;
align-items: center; align-items: center;
width: 100%; width: 3.41rem;
height: 0.52rem; height: 0.40rem;
border-radius: 0.12rem; background: #FFFFFF;
overflow: hidden; 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; display: flex;
align-items: center; align-items: center;
padding: 0 0.16rem; justify-content: center;
gap: 0.06rem; width: 0.18rem;
width: 0.96rem; height: 0.18rem;
height: 100%; margin-left: 0.16rem;
background: #F3FFFD; flex-shrink: 0;
border: 1px solid #BFE8E2;
border-radius: 0.12rem 0 0 0.12rem; svg {
cursor: pointer; width: 0.18rem;
height: 0.18rem;
}
} }
.login-view__flag { /* 国家码标签 */
font-size: 0.16rem; .login-view__country-label {
line-height: 1; margin-left: 0.06rem;
}
.login-view__code-text {
font-weight: 500; font-weight: 500;
font-size: 0.15rem; font-size: 0.12rem;
line-height: 0.18rem; line-height: 0.15rem;
color: #132034; color: #202124;
white-space: nowrap;
} }
.login-view__code-arrow { /* 下拉箭头 */
font-size: 0.11rem; .login-view__country-chevron {
color: #94A3B8; 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; width: 1px;
height: 100%; height: 0.30rem;
background: #BFE8E2; background: #E7F2F3;
margin: 0 0.16rem;
flex-shrink: 0;
} }
/* 手机号输入框 */
.login-view__phone-input { .login-view__phone-input {
flex: 1; flex: 1;
height: 100%; height: 100%;
padding: 0 0.16rem; border: none;
background: #FAFBFC; background: transparent;
border: 1px solid #E2E8F0; font-weight: 400;
border-left: none; font-size: 0.12rem;
border-radius: 0 0.12rem 0.12rem 0; line-height: 0.15rem;
font-size: 0.15rem; color: #202124;
color: #132034;
outline: none; outline: none;
&::placeholder { &::placeholder {
color: #94A3B8; color: #939599;
} }
} }
/* ==================== 发送验证码按钮(步骤一) ==================== */ /* ==================== 发送验证码按钮(步骤一) ==================== */
.login-view__send-btn { .login-view__send-btn {
width: 100%; width: 3.41rem;
height: 0.52rem; height: 0.40rem;
background: linear-gradient(90deg, #12C7BE 0%, #06B6D4 100%); background: linear-gradient(90deg, #4FC2C9 0%, #42A8B3 100%);
border-radius: 0.12rem; box-shadow: 0 0.16rem 0.30rem rgba(15, 83, 87, 0.15);
border-radius: 0.08rem;
border: none; border: none;
font-weight: 600; font-weight: 600;
font-size: 0.16rem; font-size: 0.12rem;
line-height: 0.15rem;
color: #FFFFFF; color: #FFFFFF;
cursor: pointer; cursor: pointer;
transition: opacity 0.2s; transition: opacity 0.2s;
@@ -361,8 +391,8 @@
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
width: 0.56rem; width: 0.42rem;
height: 0.64rem; height: 0.46rem;
background: #FAFBFC; background: #FAFBFC;
border: 1px solid #E2E8F0; border: 1px solid #E2E8F0;
border-radius: 0.12rem; border-radius: 0.12rem;
@@ -409,8 +439,8 @@
color: #64748B; color: #64748B;
text-align: center; text-align: center;
padding-right: 0.1rem; padding-right: 0.1rem;
margin-top: -0.19rem; margin-top: -0.14rem;
margin-bottom: -0.10rem; margin-bottom: -0.06rem;
} }
.login-view__sms-status--error { .login-view__sms-status--error {
@@ -420,12 +450,12 @@
/* ==================== 状态按钮(步骤二) ==================== */ /* ==================== 状态按钮(步骤二) ==================== */
.login-view__status-btn { .login-view__status-btn {
width: 100%; width: 100%;
height: 0.52rem; height: 0.4rem;
background: linear-gradient(90deg, #12C7BE 0%, #06B6D4 100%); background: linear-gradient(90deg, #12C7BE 0%, #06B6D4 100%);
border-radius: 0.12rem; border-radius: 0.12rem;
border: none; border: none;
font-weight: 600; font-weight: 600;
font-size: 0.16rem; font-size: 0.14rem;
color: #FFFFFF; color: #FFFFFF;
cursor: pointer; cursor: pointer;
display: flex; display: flex;
@@ -464,6 +494,7 @@
display: flex; display: flex;
align-items: center; align-items: center;
gap: 0.06rem; gap: 0.06rem;
width: 3.41rem;
} }
.login-view__checkbox { .login-view__checkbox {
@@ -472,17 +503,18 @@
height: 0.18rem; height: 0.18rem;
background: #FFFFFF; background: #FFFFFF;
border: 1.5px solid #CBD5E1; border: 1.5px solid #CBD5E1;
border-radius: 0.05rem; border-radius: 0.04rem;
cursor: pointer; cursor: pointer;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
transition: all 0.2s; transition: all 0.2s;
flex-shrink: 0;
} }
.login-view__checkbox--checked { .login-view__checkbox--checked {
background: #12C7BE; background: #4FC2C9;
border-color: #12C7BE; border-color: #4FC2C9;
} }
.login-view__check-icon { .login-view__check-icon {
@@ -496,14 +528,14 @@
font-weight: 400; font-weight: 400;
font-size: 0.12rem; font-size: 0.12rem;
line-height: 0.15rem; line-height: 0.15rem;
color: #94A3B8; color: #616367;
} }
.login-view__agreement-link { .login-view__agreement-link {
font-weight: 500; font-weight: 500;
font-size: 0.12rem; font-size: 0.12rem;
line-height: 0.15rem; line-height: 0.15rem;
color: #12C7BE; color: #42A8B3;
cursor: pointer; cursor: pointer;
&:hover { &:hover {
@@ -511,118 +543,20 @@
} }
} }
/* ==================== 简历上传阶段 — 左侧基础(step 3 使用) ==================== */
.login-view__left {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 57%;
height: 100%;
background: linear-gradient(180deg, #0A9E97 0%, #12C7BE 50%, #06B6D4 100%);
position: relative;
overflow: hidden;
}
/* ==================== 简历上传阶段 — 左侧职位卡片面板 ==================== */
.login-view__left--resume {
position: relative;
overflow: hidden;
padding: 0.4rem;
}
.login-view__resume-logo {
position: absolute;
top: 0.3rem;
left: 0.4rem;
display: flex;
align-items: center;
gap: 0.12rem;
z-index: 2;
}
.login-view__job-cards {
position: relative;
width: 100%;
height: 5.5rem;
z-index: 1;
}
.login-view__job-card {
position: absolute;
display: flex;
align-items: center;
gap: 0.1rem;
padding: 0.1rem 0.16rem;
background: #FFFFFF;
border-radius: 0.1rem;
box-shadow: 0 0.02rem 0.08rem rgba(0, 0, 0, 0.06);
white-space: nowrap;
}
.login-view__job-card-icon {
display: flex;
align-items: center;
justify-content: center;
width: 0.32rem;
height: 0.32rem;
border-radius: 0.06rem;
font-size: 0.13rem;
font-weight: 700;
flex-shrink: 0;
}
.login-view__job-card-info {
display: flex;
flex-direction: column;
gap: 0.02rem;
}
.login-view__job-card-title {
font-size: 0.13rem;
font-weight: 600;
color: #132034;
}
.login-view__job-card-company {
font-size: 0.11rem;
color: #94A3B8;
}
.login-view__job-card-salary {
font-size: 0.12rem;
font-weight: 600;
color: #E85635;
margin-left: 0.08rem;
}
.login-view__resume-slogan {
position: absolute;
bottom: 1.5rem;
left: 0.4rem;
z-index: 2;
h1 {
font-size: 0.32rem;
font-weight: 700;
color: #132034;
line-height: 0.48rem;
}
}
/* ==================== 简历上传阶段 — 右侧面板 ==================== */ /* ==================== 简历上传阶段 — 右侧面板 ==================== */
.login-view__form-wrap--resume { .login-view__form-wrap--resume {
width: 4.6rem; width: 3.41rem;
min-height: 4rem;
} }
/* 上传区域 */ /* 上传区域 */
.login-view__upload-area { .login-view__upload-area {
width: 100%; width: 100%;
padding: 0.6rem 0.4rem; padding: 0.4rem 0.4rem;
border: 1px dashed #E2E8F0; border: 2px solid #D6F1EF;
border-radius: 0.12rem; border-radius: 0.12rem;
background: #FFFFFF; background: #F6FFFF;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
@@ -657,10 +591,11 @@
} }
.login-view__upload-btn { .login-view__upload-btn {
padding: 0.14rem 0.4rem; height: 0.4rem;
line-height: 0.4rem;
background: linear-gradient(90deg, #12C7BE 0%, #06B6D4 100%); background: linear-gradient(90deg, #12C7BE 0%, #06B6D4 100%);
border: none; border: none;
border-radius: 0.26rem; border-radius: 0.08rem;
font-size: 0.16rem; font-size: 0.16rem;
font-weight: 600; font-weight: 600;
color: #FFFFFF; color: #FFFFFF;
@@ -672,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; display: flex;
flex-direction: column; flex-direction: column;
justify-content: center; }
.login-view__parse-step-item {
display: flex;
align-items: flex-start; align-items: flex-start;
width: 100%; position: relative;
min-height: 3rem;
} }
.login-view__parsing-title { .login-view__parse-step-left {
font-size: 0.32rem; 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; font-weight: 700;
color: #132034; font-size: 0.18rem;
margin-bottom: 0.12rem; line-height: 0.22rem;
color: #22B36A;
} }
.login-view__parsing-subtitle { .login-view__success-header-text {
font-size: 0.15rem; display: flex;
color: #64748B; flex-direction: column;
gap: 0.04rem;
} }
/* 失败状态标题红色 */ .login-view__success-title {
.login-view__title--error { font-weight: 500;
color: #12C7BE; 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;
} }
+35 -9
View File
@@ -354,6 +354,15 @@
gap: 0.06rem; gap: 0.06rem;
font-size: 0.12rem; font-size: 0.12rem;
color: #666; color: #666;
vertical-align: middle;
a{
color: #666;
vertical-align: super;
}
>span{
vertical-align: super;
}
} }
&__contact-icon { &__contact-icon {
@@ -473,14 +482,7 @@
border-radius: 0.06rem; border-radius: 0.06rem;
} }
// 经历条目内的编辑按钮hover exp-item 时显示 // 经历条目内的编辑按钮样式已在 &__exp-date 下方统一管理
&__exp-edit-btn {
}
&__exp-item:hover &__exp-edit-btn {
opacity: 1;
pointer-events: auto;
}
// ---- 教育背景 ---- // ---- 教育背景 ----
&__edu-item { &__edu-item {
@@ -552,9 +554,33 @@
} }
&__exp-date { &__exp-date {
font-size: 0.12rem; font-size: 0.13rem;
font-weight: 400; font-weight: 400;
color: $text-middle; color: $text-middle;
// 带切换效果的时间hover exp-item 时隐藏
&--toggle {
display: inline;
}
}
// hover经历条目时隐藏时间显示按钮
&__exp-item:hover &__exp-date--toggle {
display: none;
}
// 默认隐藏按钮且不占位
&__exp-edit-btn {
display: none;
padding-top: 0;
padding-bottom: 0;
}
// hover经历条目时显示按钮
&__exp-item:hover &__exp-edit-btn {
display: flex;
opacity: 1;
pointer-events: auto;
} }
// ---- 修复建议标记 ---- // ---- 修复建议标记 ----
+130
View File
@@ -0,0 +1,130 @@
<template>
<!-- 全屏加载遮罩组件 白色卡片居中展示加载状态 -->
<Teleport to="body">
<Transition name="fullscreen-loading-fade">
<div v-if="visible" class="fullscreen-loading">
<!-- 半透明遮罩层 -->
<div class="fullscreen-loading__overlay"></div>
<!-- 白色内容卡片 -->
<div class="fullscreen-loading__card">
<!-- 顶部标题栏 logo仅在传入 title 时显示 -->
<div v-if="title" class="fullscreen-loading__header">
<img class="fullscreen-loading__logo dib" src="@/assets/images/logo-offerpai.png" alt="logo" />
<span class="fullscreen-loading__title dib ml8 verali-t pt2">{{ title }}</span>
</div>
<!-- 中间文案 -->
<p class="fullscreen-loading__text">{{ text }}</p>
<!-- 加载圆环动画 -->
<div class="fullscreen-loading__spinner"></div>
</div>
</div>
</Transition>
</Teleport>
</template>
<script setup lang="ts">
/** 全屏加载遮罩组件 */
defineProps<{
/** 是否显示 */
visible: boolean
/** 中间提示文案 */
text: string
/** 顶部标题文字(不传则不显示标题行和 logo) */
title?: string
}>()
</script>
<style lang="scss" scoped>
@use '../assets/styles/variables' as *;
.fullscreen-loading {
font-size: 0.14rem;
position: fixed;
inset: 0;
z-index: 2100;
display: flex;
align-items: center;
justify-content: center;
/* 半透明遮罩 */
&__overlay {
position: absolute;
inset: 0;
background: $overlay-bg;
}
/* 白色居中卡片 */
&__card {
position: relative;
background: $bg-white;
border-radius: 0.12rem;
padding: 0.7rem 0 0.5rem 0;
width: 4rem;
display: flex;
flex-direction: column;
align-items: center;
min-width: 3.6rem;
}
/* 顶部标题行 */
&__header {
position: absolute;
top: 0.12rem;
left: 0.2rem;
//display: flex;
//align-items: center;
//gap: 0.08rem;
margin-bottom: 0.3rem;
}
/* logo 图标 */
&__logo {
width: 0.28rem;
height: 0.28rem;
border-radius: 50%;
}
/* 标题文字 */
&__title {
font-size: 0.14rem;
color: $text-dark;
}
/* 中间文案 */
&__text {
font-size: 0.2rem;
font-weight: 600;
color: $text-dark;
margin-bottom: 0.28rem;
}
/* 旋转圆环 */
&__spinner {
width: 0.44rem;
height: 0.44rem;
border: 3px solid transparent;
border-top-color: $accent;
border-right-color: $accent;
border-bottom-color: $accent;
border-radius: 50%;
animation: fullscreen-loading-spin 1s linear infinite;
}
}
/* 旋转动画 */
@keyframes fullscreen-loading-spin {
to {
transform: rotate(360deg);
}
}
/* 淡入淡出过渡 */
.fullscreen-loading-fade-enter-active,
.fullscreen-loading-fade-leave-active {
transition: opacity 0.25s ease;
}
.fullscreen-loading-fade-enter-from,
.fullscreen-loading-fade-leave-to {
opacity: 0;
}
</style>
+11 -5
View File
@@ -347,6 +347,8 @@
</div> </div>
</div> </div>
</div> </div>
<!-- 全屏加载遮罩 -->
<FullscreenLoading :visible="fullLoading" :text="fullLoadingText" title="Nova 正在帮您努力处理~" />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@@ -360,6 +362,7 @@ import type { ResumeListItem } from '@/api/resume'
import { fetchCustomizeResume, generateCustomizeResume, aiEditResume, rollbackCustomizeResume } from '@/api/jobs' import { fetchCustomizeResume, generateCustomizeResume, aiEditResume, rollbackCustomizeResume } from '@/api/jobs'
import type { CustomizeResumeData, AiEditChatMessage } from '@/api/jobs' import type { CustomizeResumeData, AiEditChatMessage } from '@/api/jobs'
import AiThinkingIndicator from '@/components/tools/AiThinkingIndicator.vue' import AiThinkingIndicator from '@/components/tools/AiThinkingIndicator.vue'
import FullscreenLoading from '@/components/FullscreenLoading.vue'
// ==================== ==================== // ==================== ====================
@@ -415,6 +418,11 @@ const emit = defineEmits<{
// ==================== ==================== // ==================== ====================
/** 全屏加载是否显示 */
const fullLoading = ref(false)
/** 全屏加载文案 */
const fullLoadingText = ref('')
/** 当前步骤:1-确认入口(居中弹窗) 2-差距分析(右侧抽屉) 3-定制简历 4-预览 */ /** 当前步骤:1-确认入口(居中弹窗) 2-差距分析(右侧抽屉) 3-定制简历 4-预览 */
const currentStep = ref(1) const currentStep = ref(1)
@@ -483,10 +491,8 @@ async function handleDrawerNext() {
* 流程 GET 查询 有数据直接用 无数据则 POST 生成 GET 查询 * 流程 GET 查询 有数据直接用 无数据则 POST 生成 GET 查询
*/ */
async function fetchAndLoadCustomResume() { async function fetchAndLoadCustomResume() {
const loadingInstance = ElLoading.service({ fullLoadingText.value = '正在生成定制简历...'
text: '正在生成定制简历...', fullLoading.value = true
background: 'rgba(0, 0, 0, 0.5)',
})
try { try {
// //
let queryRes = await fetchCustomizeResume(props.jobId) let queryRes = await fetchCustomizeResume(props.jobId)
@@ -522,7 +528,7 @@ async function fetchAndLoadCustomResume() {
console.error('[JobResumeCustomDialog] 定制简历流程失败', e) console.error('[JobResumeCustomDialog] 定制简历流程失败', e)
ElMessage.error('定制简历失败,请稍后重试') ElMessage.error('定制简历失败,请稍后重试')
} finally { } finally {
loadingInstance.close() fullLoading.value = false
} }
} }
+13 -8
View File
@@ -232,14 +232,17 @@
</div> </div>
</div> </div>
</div> </div>
<!-- 全屏加载遮罩 -->
<FullscreenLoading :visible="fullLoading" :text="fullLoadingText" title="Nova 正在帮您努力处理~" />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, watch } from 'vue' import { ref, computed, watch } from 'vue'
import { useStore } from 'vuex' import { useStore } from 'vuex'
import { ElMessage, ElLoading } from 'element-plus' import { ElMessage } from 'element-plus'
import RegionSelector from '@/components/tools/RegionSelector.vue' import RegionSelector from '@/components/tools/RegionSelector.vue'
import { polishDiagnosisIssue } from '@/api/resume' import { polishDiagnosisIssue } from '@/api/resume'
import FullscreenLoading from '@/components/FullscreenLoading.vue'
/** 教育经历单条数据结构 */ /** 教育经历单条数据结构 */
interface EducationItem { interface EducationItem {
@@ -329,6 +332,12 @@ const emit = defineEmits<{
/** Vuex store 实例 */ /** Vuex store 实例 */
const store = useStore() const store = useStore()
// ==================== ====================
/** 全屏加载是否显示 */
const fullLoading = ref(false)
/** 全屏加载文案 */
const fullLoadingText = ref('')
/** 模块名称映射表 */ /** 模块名称映射表 */
const moduleTitleMap: Record<string, string> = { const moduleTitleMap: Record<string, string> = {
info: '基本信息', info: '基本信息',
@@ -490,12 +499,8 @@ async function handleAiPolish() {
return return
} }
aiPolishing.value = true aiPolishing.value = true
const loading = ElLoading.service({ fullLoadingText.value = 'AI正在润色中……'
lock: true, fullLoading.value = true
text: 'AI正在润色中……',
background: 'rgba(0, 0, 0, 0.5)',
customClass: 'resume-upload-loading',
})
try { try {
const res = await polishDiagnosisIssue([text]) const res = await polishDiagnosisIssue([text])
if (res.code === 0 && res.data?.content) { if (res.code === 0 && res.data?.content) {
@@ -508,7 +513,7 @@ async function handleAiPolish() {
ElMessage.error('请求失败,请稍后重试') ElMessage.error('请求失败,请稍后重试')
} finally { } finally {
aiPolishing.value = false aiPolishing.value = false
loading.close() fullLoading.value = false
} }
} }
+10 -9
View File
@@ -72,8 +72,9 @@
<div class="profile-page-content__item-left"> <div class="profile-page-content__item-left">
<div class="profile-page-content__item-title">{{ edu.school }}</div> <div class="profile-page-content__item-title">{{ edu.school }}</div>
<div class="profile-page-content__item-sub">{{ getDegreeText(edu.studyType, edu.degree) }} · {{ edu.major }}</div> <div class="profile-page-content__item-sub">{{ getDegreeText(edu.studyType, edu.degree) }} · {{ edu.major }}</div>
<div class="profile-page-content__item-date">{{ edu.startDate }} - {{ edu.endDate }}</div>
</div> </div>
<!-- 时间默认显示hover时隐藏 -->
<div class="profile-page-content__item-date profile-page-content__item-date--toggle">{{ edu.startDate }} - {{ edu.endDate }}</div>
<button class="profile-page-content__edit-btn-inline profile-page-content__item-edit-btn"> <button class="profile-page-content__edit-btn-inline profile-page-content__item-edit-btn">
<el-icon><EditPen /></el-icon><span>编辑</span> <el-icon><EditPen /></el-icon><span>编辑</span>
</button> </button>
@@ -105,9 +106,9 @@
<div class="profile-page-content__item-left"> <div class="profile-page-content__item-left">
<div class="profile-page-content__item-title">{{ exp.companyName }}</div> <div class="profile-page-content__item-title">{{ exp.companyName }}</div>
<div class="profile-page-content__item-sub">{{ exp.position }}</div> <div class="profile-page-content__item-sub">{{ exp.position }}</div>
<div class="profile-page-content__item-period">{{ exp.startDate }} - {{ exp.endDate || '至今' }}</div>
</div> </div>
<!-- 时间默认显示hover时隐藏 -->
<div class="profile-page-content__item-period profile-page-content__item-date--toggle">{{ exp.startDate }} - {{ exp.endDate || '至今' }}</div>
<button class="profile-page-content__edit-btn-inline profile-page-content__item-edit-btn"> <button class="profile-page-content__edit-btn-inline profile-page-content__item-edit-btn">
<el-icon><EditPen /></el-icon><span>编辑</span> <el-icon><EditPen /></el-icon><span>编辑</span>
</button> </button>
@@ -139,9 +140,9 @@
<div class="profile-page-content__item-left"> <div class="profile-page-content__item-left">
<div class="profile-page-content__item-title">{{ exp.companyName }}</div> <div class="profile-page-content__item-title">{{ exp.companyName }}</div>
<div class="profile-page-content__item-sub">{{ exp.position }}</div> <div class="profile-page-content__item-sub">{{ exp.position }}</div>
<div class="profile-page-content__item-period">{{ exp.startDate }} - {{ exp.endDate }}</div>
</div> </div>
<!-- 时间默认显示hover时隐藏 -->
<div class="profile-page-content__item-period profile-page-content__item-date--toggle">{{ exp.startDate }} - {{ exp.endDate }}</div>
<button class="profile-page-content__edit-btn-inline profile-page-content__item-edit-btn"> <button class="profile-page-content__edit-btn-inline profile-page-content__item-edit-btn">
<el-icon><EditPen /></el-icon><span>编辑</span> <el-icon><EditPen /></el-icon><span>编辑</span>
</button> </button>
@@ -173,9 +174,9 @@
<div class="profile-page-content__item-left"> <div class="profile-page-content__item-left">
<div class="profile-page-content__item-title">{{ proj.projectName }}</div> <div class="profile-page-content__item-title">{{ proj.projectName }}</div>
<div class="profile-page-content__item-sub" v-if="proj.role">{{ proj.role }}</div> <div class="profile-page-content__item-sub" v-if="proj.role">{{ proj.role }}</div>
<div class="profile-page-content__item-period">{{ proj.startDate }} - {{ proj.endDate }}</div>
</div> </div>
<!-- 时间默认显示hover时隐藏 -->
<div class="profile-page-content__item-period profile-page-content__item-date--toggle">{{ proj.startDate }} - {{ proj.endDate }}</div>
<button class="profile-page-content__edit-btn-inline profile-page-content__item-edit-btn"> <button class="profile-page-content__edit-btn-inline profile-page-content__item-edit-btn">
<el-icon><EditPen /></el-icon><span>编辑</span> <el-icon><EditPen /></el-icon><span>编辑</span>
</button> </button>
@@ -206,9 +207,9 @@
<div class="profile-page-content__item-row"> <div class="profile-page-content__item-row">
<div class="profile-page-content__item-left"> <div class="profile-page-content__item-left">
<div class="profile-page-content__item-title">{{ comp.competitionName }} · {{ comp.award }}</div> <div class="profile-page-content__item-title">{{ comp.competitionName }} · {{ comp.award }}</div>
<div class="profile-page-content__item-period">{{ comp.awardDate }}</div>
</div> </div>
<!-- 时间默认显示hover时隐藏 -->
<div class="profile-page-content__item-period profile-page-content__item-date--toggle">{{ comp.awardDate }}</div>
<button class="profile-page-content__edit-btn-inline profile-page-content__item-edit-btn"> <button class="profile-page-content__edit-btn-inline profile-page-content__item-edit-btn">
<el-icon><EditPen /></el-icon><span>编辑</span> <el-icon><EditPen /></el-icon><span>编辑</span>
</button> </button>
+15 -12
View File
@@ -53,6 +53,8 @@
</div> </div>
</div> </div>
</Teleport> </Teleport>
<!-- 全屏加载遮罩 -->
<FullscreenLoading :visible="fullLoading" :text="fullLoadingText" title="Nova 正在帮您努力处理~" />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@@ -60,8 +62,8 @@ import { ref, watch } from 'vue'
import { useRouter, useRoute } from 'vue-router' import { useRouter, useRoute } from 'vue-router'
import { uploadResume } from '@/utils/aiRequest' import { uploadResume } from '@/utils/aiRequest'
import { syncProfileFromResume } from '@/api/profile' import { syncProfileFromResume } from '@/api/profile'
import { ElLoading, ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import 'element-plus/es/components/loading/style/css' import FullscreenLoading from '@/components/FullscreenLoading.vue'
/** 组件 Props */ /** 组件 Props */
const props = defineProps<{ modelValue: boolean }>() const props = defineProps<{ modelValue: boolean }>()
@@ -87,6 +89,11 @@ const uploading = ref(false)
/** 上传后获得的简历 ID */ /** 上传后获得的简历 ID */
const resumeId = ref('') const resumeId = ref('')
/** 全屏加载是否显示 */
const fullLoading = ref(false)
/** 全屏加载文案 */
const fullLoadingText = ref('')
/** 弹窗打开时重置状态 */ /** 弹窗打开时重置状态 */
watch(() => props.modelValue, (val) => { watch(() => props.modelValue, (val) => {
if (val) { if (val) {
@@ -124,12 +131,8 @@ async function processFile(file: File) {
uploading.value = true uploading.value = true
// //
const loading = ElLoading.service({ fullLoadingText.value = '简历解析中,请耐心等待…'
lock: true, fullLoading.value = true
text: '简历解析中,请耐心等待…',
background: 'rgba(0, 0, 0, 0.5)',
customClass: 'profile-welcome-loading',
})
try { try {
// //
@@ -139,18 +142,18 @@ async function processFile(file: File) {
uploadedFileName.value = file.name uploadedFileName.value = file.name
// //
loading.setText('正在同步个人资料…') fullLoadingText.value = '正在同步个人资料…'
await syncProfileFromResume(resumeId.value) await syncProfileFromResume(resumeId.value)
loading.close() fullLoading.value = false
uploading.value = false uploading.value = false
ElMessage.success('简历上传并同步成功') ElMessage.success('简历上传并同步成功')
} else { } else {
loading.close() fullLoading.value = false
uploading.value = false uploading.value = false
ElMessage.error(res.msg || '上传失败') ElMessage.error(res.msg || '上传失败')
} }
} catch { } catch {
loading.close() fullLoading.value = false
uploading.value = false uploading.value = false
ElMessage.error('上传失败,请稍后重试') ElMessage.error('上传失败,请稍后重试')
} }
+12 -3
View File
@@ -103,12 +103,15 @@
</div> </div>
</div> </div>
</div> </div>
<!-- 全屏加载遮罩 -->
<FullscreenLoading :visible="fullLoading" :text="fullLoadingText" title="Nova 正在帮您努力处理~" />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, watch } from 'vue' import { ref, watch } from 'vue'
import { ElMessage, ElLoading } from 'element-plus' import { ElMessage } from 'element-plus'
import { polishDiagnosisIssue } from '@/api/resume' import { polishDiagnosisIssue } from '@/api/resume'
import FullscreenLoading from '@/components/FullscreenLoading.vue'
/** 描述段落结构 */ /** 描述段落结构 */
interface DescriptionParagraph { interface DescriptionParagraph {
@@ -142,6 +145,11 @@ const aiPolishResult = ref('')
/** AI润色中 */ /** AI润色中 */
const aiPolishing = ref(false) const aiPolishing = ref(false)
/** 全屏加载是否显示 */
const fullLoading = ref(false)
/** 全屏加载文案 */
const fullLoadingText = ref('')
/** 生成短ID */ /** 生成短ID */
const generateId = (): string => Math.random().toString(36).substring(2, 8) const generateId = (): string => Math.random().toString(36).substring(2, 8)
@@ -177,7 +185,8 @@ async function handleAiPolish() {
return return
} }
aiPolishing.value = true aiPolishing.value = true
const loading = ElLoading.service({ lock: true, text: 'AI正在润色中……', background: 'rgba(0,0,0,0.5)' }) fullLoadingText.value = 'AI正在润色中……'
fullLoading.value = true
try { try {
const content = text.split('\n').filter(s => s.trim()) const content = text.split('\n').filter(s => s.trim())
const res = await polishDiagnosisIssue(content) const res = await polishDiagnosisIssue(content)
@@ -191,7 +200,7 @@ async function handleAiPolish() {
ElMessage.error('请求失败') ElMessage.error('请求失败')
} finally { } finally {
aiPolishing.value = false aiPolishing.value = false
loading.close() fullLoading.value = false
} }
} }
+11 -7
View File
@@ -73,12 +73,15 @@
</div> </div>
</Transition> </Transition>
</Teleport> </Teleport>
<!-- 全屏加载遮罩 -->
<FullscreenLoading :visible="fullLoading" :text="fullLoadingText" title="Nova 正在帮您努力处理~" />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, watch, nextTick } from 'vue' import { ref, watch, nextTick } from 'vue'
import type { DiagnosisIssue, DescriptionParagraph } from '@/api/resume' import type { DiagnosisIssue, DescriptionParagraph } from '@/api/resume'
import { polishDiagnosisIssue, resolveDiagnosisIssue } from '@/api/resume' import { polishDiagnosisIssue, resolveDiagnosisIssue } from '@/api/resume'
import FullscreenLoading from '@/components/FullscreenLoading.vue'
// ==================== Props ==================== // ==================== Props ====================
@@ -169,6 +172,11 @@ const moduleOrder = ['summary', 'education', 'work', 'internship', 'project', 'c
// ==================== ==================== // ==================== ====================
/** 全屏加载是否显示 */
const fullLoading = ref(false)
/** 全屏加载文案 */
const fullLoadingText = ref('')
/** 所有模块分组后的数据列表 */ /** 所有模块分组后的数据列表 */
const moduleDataList = ref<ModuleGroup[]>([]) const moduleDataList = ref<ModuleGroup[]>([])
@@ -313,12 +321,8 @@ async function handleRegenerate(record: RecordState) {
return return
} }
const loading = ElLoading.service({ fullLoadingText.value = 'AI 重新生成中…'
lock: true, fullLoading.value = true
text: 'AI 重新生成中…',
background: 'rgba(0, 0, 0, 0.5)',
customClass: 'resume-upload-loading',
})
try { try {
const res = await polishDiagnosisIssue(content) const res = await polishDiagnosisIssue(content)
@@ -332,7 +336,7 @@ async function handleRegenerate(record: RecordState) {
} catch { } catch {
ElMessage.error('请求失败,请稍后重试') ElMessage.error('请求失败,请稍后重试')
} finally { } finally {
loading.close() fullLoading.value = false
} }
} }
+15 -14
View File
@@ -138,6 +138,8 @@
</div> </div>
</Transition> </Transition>
</Teleport> </Teleport>
<!-- 全屏加载遮罩 -->
<FullscreenLoading :visible="fullLoading" :text="fullLoadingText" title="Nova 正在帮您努力处理~" />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@@ -145,6 +147,7 @@ import { computed, ref, watch, nextTick } from 'vue'
import type { DiagnosisIssue, DescriptionParagraph } from '@/api/resume' import type { DiagnosisIssue, DescriptionParagraph } from '@/api/resume'
import { polishDiagnosisIssue, feedbackDiagnosisIssue, resolveDiagnosisIssue } from '@/api/resume' import { polishDiagnosisIssue, feedbackDiagnosisIssue, resolveDiagnosisIssue } from '@/api/resume'
import { computeDiff, type DiffSegment } from '@/utils/textDiff' import { computeDiff, type DiffSegment } from '@/utils/textDiff'
import FullscreenLoading from '@/components/FullscreenLoading.vue'
// ==================== Props ==================== // ==================== Props ====================
@@ -182,6 +185,12 @@ const moduleTitleMap: Record<string, string> = {
/** 抽屉标题 */ /** 抽屉标题 */
const moduleTitle = computed(() => moduleTitleMap[props.moduleType] || '问题修复') const moduleTitle = computed(() => moduleTitleMap[props.moduleType] || '问题修复')
// ==================== ====================
/** 全屏加载是否显示 */
const fullLoading = ref(false)
/** 全屏加载文案 */
const fullLoadingText = ref('')
// ==================== ==================== // ==================== ====================
/** 将原文内容统一转为字符串(description 数组的 text 逐行拼接) */ /** 将原文内容统一转为字符串(description 数组的 text 逐行拼接) */
@@ -282,12 +291,8 @@ async function handleAiPolish() {
} }
// //
const loading = ElLoading.service({ fullLoadingText.value = 'AI 润色中,请耐心等待…'
lock: true, fullLoading.value = true
text: 'AI 润色中,请耐心等待…',
background: 'rgba(0, 0, 0, 0.5)',
customClass: 'resume-upload-loading',
})
try { try {
const res = await polishDiagnosisIssue(content) const res = await polishDiagnosisIssue(content)
@@ -300,7 +305,7 @@ async function handleAiPolish() {
} catch { } catch {
ElMessage.error('润色请求失败,请稍后重试') ElMessage.error('润色请求失败,请稍后重试')
} finally { } finally {
loading.close() fullLoading.value = false
} }
} }
@@ -373,12 +378,8 @@ async function handleSubmit() {
} }
// //
const loading = ElLoading.service({ fullLoadingText.value = '正在提交新版本…'
lock: true, fullLoading.value = true
text: '正在提交新版本…',
background: 'rgba(0, 0, 0, 0.5)',
customClass: 'resume-upload-loading',
})
try { try {
// 1. resolve // 1. resolve
@@ -394,7 +395,7 @@ async function handleSubmit() {
} catch { } catch {
ElMessage.error('提交失败,请稍后重试') ElMessage.error('提交失败,请稍后重试')
} finally { } finally {
loading.close() fullLoading.value = false
} }
} }
+47 -46
View File
@@ -3,105 +3,106 @@
<Teleport to="body"> <Teleport to="body">
<div v-if="modelValue" class="settings-invite-overlay" @click="$emit('update:modelValue', false)"> <div v-if="modelValue" class="settings-invite-overlay" @click="$emit('update:modelValue', false)">
<div class="settings-invite-dialog" @click.stop> <div class="settings-invite-dialog" @click.stop>
<!-- 顶部渐变区域 -->
<div class="settings-invite-dialog__header">
<!-- 皇冠图标 -->
<span class="settings-invite-dialog__crown"></span>
</div>
<!-- 关闭按钮 --> <!-- 关闭按钮 -->
<span class="settings-invite-dialog__close" @click="$emit('update:modelValue', false)"></span> <span class="settings-invite-dialog__close" @click="$emit('update:modelValue', false)"></span>
<!-- 内容区域 -->
<div class="settings-invite-dialog__body">
<!-- 标题 --> <!-- 标题 -->
<h2 class="settings-invite-dialog__title">邀请好友免费获取7天会员权益</h2> <h2 class="settings-invite-dialog__title">邀请好友免费获取 7 天会员</h2>
<!-- 副标题 -->
<p class="settings-invite-dialog__subtitle">好友通过你的链接注册并完成认证 3 天会员</p>
<!-- 邀请链接区域 --> <!-- 邀请链接区域 -->
<div class="settings-invite-dialog__link-box"> <div class="settings-invite-dialog__link-box">
<p class="settings-invite-dialog__link-text">来一起领取AI求职助手会员{{ inviteText }}点击链接进入活动</p> <span class="settings-invite-dialog__link-text">{{ inviteUrl }}</span>
</div>
<!-- 复制链接按钮 -->
<button class="settings-invite-dialog__copy-btn" @click="handleCopy">复制链接</button> <button class="settings-invite-dialog__copy-btn" @click="handleCopy">复制链接</button>
</div>
<!-- 步骤说明 --> <!-- 步骤说明 -->
<p class="settings-invite-dialog__steps-tip">仅需 3 步可免费获取会员权益</p>
<div class="settings-invite-dialog__step"> <div class="settings-invite-dialog__step">
<div class="settings-invite-dialog__step-title">第1步分享专属邀请链接</div> <div class="settings-invite-dialog__step-circle">1</div>
<p class="settings-invite-dialog__step-desc">点击"复制链接"并发送给好友</p> <span class="settings-invite-dialog__step-text">分享专属邀请链接给好友</span>
</div> </div>
<div class="settings-invite-dialog__step"> <div class="settings-invite-dialog__step">
<div class="settings-invite-dialog__step-title">第2步好友完成新用户首次体验</div> <div class="settings-invite-dialog__step-circle">2</div>
<p class="settings-invite-dialog__step-desc">链接 24 小时内有效好友在时效内体验AI求职助手</p> <span class="settings-invite-dialog__step-text">好友完成注册并通过认证</span>
</div> </div>
<div class="settings-invite-dialog__step"> <div class="settings-invite-dialog__step">
<div class="settings-invite-dialog__step-title">第3步分享人获得7天会员权益</div> <div class="settings-invite-dialog__step-circle">3</div>
<p class="settings-invite-dialog__step-desc">免费会员权益一年内有效AI助力加速上岸</p> <span class="settings-invite-dialog__step-text">获得 7 天会员奖励</span>
</div> </div>
<!-- 统计数据 --> <!-- 底部统计 + 按钮 -->
<div class="settings-invite-dialog__stats"> <div class="settings-invite-dialog__footer">
<div class="settings-invite-dialog__stat-item"> <div class="settings-invite-dialog__stat">
<div class="settings-invite-dialog__stat-label">累计获得会员天数</div> <span class="settings-invite-dialog__stat-label">已邀请</span>
<div class="settings-invite-dialog__stat-value">{{ totalDays }}</div> <span class="settings-invite-dialog__stat-value">{{ totalFriends }}</span>
</div> </div>
<div class="settings-invite-dialog__stat-item"> <div class="settings-invite-dialog__stat">
<div class="settings-invite-dialog__stat-label">累计邀请好友</div> <span class="settings-invite-dialog__stat-label">累计获得</span>
<div class="settings-invite-dialog__stat-value">{{ totalFriends }}</div> <span class="settings-invite-dialog__stat-value">{{ totalDays }}</span>
</div> </div>
<button class="settings-invite-dialog__member-btn" @click="handleGoMember">成为正式会员</button>
</div> </div>
<!-- 活动规则折叠 -->
<div class="settings-invite-dialog__rules" @click="showRules = !showRules">
<span class="settings-invite-dialog__rules-label">活动规则</span>
<svg class="settings-invite-dialog__rules-arrow" :class="{ 'settings-invite-dialog__rules-arrow--open': showRules }" viewBox="0 0 12 12" fill="none">
<path d="M3 4.5L6 7.5L9 4.5" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</div>
<div v-if="showRules" class="settings-invite-dialog__rules-content">
<p>1. 每成功邀请一位新用户注册并完成首次体验邀请人可获得 7 天免费会员权益</p>
<p>2. 邀请链接有效期为 24 小时过期需重新生成</p>
<p>3. 累计获得的会员天数一年内有效超过一年未使用的天数将自动失效</p>
<p>4. 同一被邀请人仅计算一次重复邀请不累计奖励</p>
<p>5. 本活动最终解释权归平台所有</p>
</div> </div>
</div> </div>
</div> </div>
</Teleport> </Teleport>
<!-- 会员购买弹窗 -->
<MemberDialog v-model="showMemberDialog" />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed } from 'vue' import { ref, computed } from 'vue'
import { useStore } from 'vuex' import { useStore } from 'vuex'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import MemberDialog from './MemberDialog.vue'
/** 组件 Props */ /** 组件 Props */
defineProps<{ modelValue: boolean }>() defineProps<{ modelValue: boolean }>()
/** 组件 Emits */ /** 组件 Emits */
defineEmits<{ (e: 'update:modelValue', value: boolean): void }>() defineEmits<{
(e: 'update:modelValue', value: boolean): void
}>()
const store = useStore() const store = useStore()
/** 活动规则展开状态 */ /** 会员弹窗显示状态 */
const showRules = ref(false) const showMemberDialog = ref(false)
/** 累计获得会员天数(暂用模拟数据) */ /** 累计获得会员天数(暂用模拟数据) */
const totalDays = ref(1) const totalDays = ref(7)
/** 累计邀请好友数(暂用模拟数据) */ /** 累计邀请好友数(暂用模拟数据) */
const totalFriends = ref(1) const totalFriends = ref(1)
/** 用户邀请码 — 从全局 store 读取 */ /** 用户邀请码 — 从全局 store 读取 */
const inviteCode = computed(() => store.state.userInfo?.inviteCode || '') const inviteCode = computed(() => store.state.userInfo?.inviteCode || 'NOVA2026')
/** 邀请链接文案 */ /** 邀请链接地址 */
const inviteText = computed(() => { const inviteUrl = computed(() => `https://offerpai.com/invite?code=${inviteCode.value}`)
const code = inviteCode.value
return `https://www.offerpai.com.cn?invite_code=${code}`
})
/** 复制链接到剪贴板 */ /** 复制链接到剪贴板 */
async function handleCopy() { async function handleCopy() {
try { try {
await navigator.clipboard.writeText(inviteText.value) await navigator.clipboard.writeText(inviteUrl.value)
ElMessage.success('链接已复制') ElMessage.success('链接已复制')
} catch { } catch {
ElMessage.error('复制失败,请手动复制') ElMessage.error('复制失败,请手动复制')
} }
} }
/** 打开会员购买弹窗 */
function handleGoMember() {
showMemberDialog.value = true
}
</script> </script>
+12 -5
View File
@@ -281,6 +281,8 @@
<MemberAccessDialog v-model="showMemberAccessDialog" @open-member="showMemberDialog = true" /> <MemberAccessDialog v-model="showMemberAccessDialog" @open-member="showMemberDialog = true" />
<!-- 会员购买弹窗 --> <!-- 会员购买弹窗 -->
<MemberDialog v-model="showMemberDialog" /> <MemberDialog v-model="showMemberDialog" />
<!-- 全屏加载遮罩 -->
<FullscreenLoading :visible="fullLoading" :text="fullLoadingText" title="Nova 正在帮您努力处理~" />
</div> </div>
</template> </template>
@@ -296,6 +298,7 @@ import JobDislikeDialog from '@/components/JobDislikeDialog.vue'
import JobFeedbackDialog from '@/components/JobFeedbackDialog.vue' import JobFeedbackDialog from '@/components/JobFeedbackDialog.vue'
import JobResumeCustomDialog from '@/components/JobResumeCustomDialog.vue' import JobResumeCustomDialog from '@/components/JobResumeCustomDialog.vue'
import MemberAccessDialog from '@/components/MemberAccessDialog.vue' import MemberAccessDialog from '@/components/MemberAccessDialog.vue'
import FullscreenLoading from '@/components/FullscreenLoading.vue'
import MemberDialog from '@/components/MemberDialog.vue' import MemberDialog from '@/components/MemberDialog.vue'
import { fetchJobDetail, toggleJobFavorite, removeJobFavorite, fetchSkillGap } from '@/api/jobs' import { fetchJobDetail, toggleJobFavorite, removeJobFavorite, fetchSkillGap } from '@/api/jobs'
import { fetchMemberStatus } from '@/api/member' import { fetchMemberStatus } from '@/api/member'
@@ -307,6 +310,12 @@ const router = useRouter()
const route = useRoute() const route = useRoute()
const store = useStore() const store = useStore()
// ==================== ====================
/** 全屏加载是否显示 */
const fullLoading = ref(false)
/** 全屏加载文案 */
const fullLoadingText = ref('')
/** 当前岗位 ID,从路由参数中获取 */ /** 当前岗位 ID,从路由参数中获取 */
const jobId = route.params.id as string const jobId = route.params.id as string
@@ -595,10 +604,8 @@ async function handleGenerateResume() {
// //
} }
const loadingInstance = ElLoading.service({ fullLoadingText.value = '正在分析岗位匹配度...'
text: '正在分析岗位匹配度...', fullLoading.value = true
background: 'rgba(0, 0, 0, 0.5)',
})
try { try {
const res = await fetchSkillGap(jobId) const res = await fetchSkillGap(jobId)
if (res.code === 0 && res.data) { if (res.code === 0 && res.data) {
@@ -609,7 +616,7 @@ async function handleGenerateResume() {
ElMessage.error('分析失败,请稍后重试') ElMessage.error('分析失败,请稍后重试')
return return
} finally { } finally {
loadingInstance.close() fullLoading.value = false
} }
showResumeCustomDialog.value = true showResumeCustomDialog.value = true
} }
+265 -97
View File
@@ -2,9 +2,7 @@
<!-- 登录页面 左右分栏布局包含登录和简历上传流程 --> <!-- 登录页面 左右分栏布局包含登录和简历上传流程 -->
<div class="login-view"> <div class="login-view">
<!-- ==================== 登录阶段step 1 & 2==================== --> <!-- ==================== 左侧视觉动画面板所有阶段共用 ==================== -->
<template v-if="step === 1 || step === 2">
<!-- 左侧视觉动画面板 -->
<section class="login-visual" ref="loginVisualRef" aria-label="OfferPai AI job platform"> <section class="login-visual" ref="loginVisualRef" aria-label="OfferPai AI job platform">
<div class="ai-hub-stage" ref="stageRef"> <div class="ai-hub-stage" ref="stageRef">
<!-- 标题 --> <!-- 标题 -->
@@ -23,11 +21,13 @@
<img class="login-jianshi-logo" :src="jianshiLogoSrc" alt="" aria-hidden="true" draggable="false" /> <img class="login-jianshi-logo" :src="jianshiLogoSrc" alt="" aria-hidden="true" draggable="false" />
</section> </section>
<!-- 右侧表单面板 --> <!-- ==================== 右侧面板 ==================== -->
<div class="login-view__right"> <div class="login-view__right">
<!-- ===== 登录阶段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-row login-view__logo-row--right">
<div class="login-view__logo-box login-view__logo-box--green">O</div> <img src="@/assets/images/logo.png" alt="">
<span class="login-view__logo-text login-view__logo-text--dark">Offer派</span>
</div> </div>
<div class="login-view__form-wrap"> <div class="login-view__form-wrap">
@@ -37,15 +37,29 @@
<h2 class="login-view__title">手机号登录/注册</h2> <h2 class="login-view__title">手机号登录/注册</h2>
<p class="login-view__subtitle">未注册用户验证后将自动注册并登录</p> <p class="login-view__subtitle">未注册用户验证后将自动注册并登录</p>
</div> </div>
<!-- 手机号输入组 -->
<div class="login-view__phone-row"> <div class="login-view__phone-row">
<div class="login-view__country-code"> <!-- 手机图标 -->
<span class="login-view__flag">🇨🇳</span> <div class="login-view__phone-icon">
<span class="login-view__code-text">+86</span> <svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<span class="login-view__code-arrow"></span> <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>
<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" /> <input v-model="phone" type="tel" class="login-view__phone-input" placeholder="请输入手机号码" maxlength="11" />
</div> </div>
<!-- 发送验证码按钮 -->
<button class="login-view__send-btn" :disabled="!canSend" @click="handleSendCode">发送验证码</button> <button class="login-view__send-btn" :disabled="!canSend" @click="handleSendCode">发送验证码</button>
</template> </template>
@@ -53,7 +67,7 @@
<template v-if="step === 2"> <template v-if="step === 2">
<div class="login-view__heading"> <div class="login-view__heading">
<h2 class="login-view__title">请输入验证码</h2> <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>
<div class="login-view__otp-wrap" @click="focusOtpInput"> <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" /> <input ref="otpInputRef" v-model="otpValue" type="text" inputmode="numeric" class="login-view__otp-hidden-input" maxlength="6" :disabled="isLoggingIn" @input="handleOtpInput" />
@@ -73,7 +87,7 @@
<!-- 协议勾选步骤一二通用 --> <!-- 协议勾选步骤一二通用 -->
<div class="login-view__agreement"> <div class="login-view__agreement">
<div class="login-view__checkbox" :class="{ 'login-view__checkbox--checked': agreedTerms }" @click="agreedTerms = !agreedTerms"> <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> </div>
<span class="login-view__agreement-text">登录即表示同意</span> <span class="login-view__agreement-text">登录即表示同意</span>
<span class="login-view__agreement-link" @click="openAgreement('ae8065i3')">用户协议</span> <span class="login-view__agreement-link" @click="openAgreement('ae8065i3')">用户协议</span>
@@ -81,49 +95,30 @@
<span class="login-view__agreement-link" @click="openAgreement('hf8375i8')">隐私政策</span> <span class="login-view__agreement-link" @click="openAgreement('hf8375i8')">隐私政策</span>
</div> </div>
</div> </div>
</div>
</template> </template>
<!-- ==================== 简历上传阶段step 3==================== --> <!-- ===== 简历上传阶段step 3===== -->
<template v-if="step === 3"> <template v-if="step === 3">
<!-- 左侧品牌面板 职位卡片风格 -->
<div class="login-view__left login-view__left--resume">
<!-- 顶部 Logo -->
<div class="login-view__resume-logo">
<div class="login-view__logo-box login-view__logo-box--light">O</div>
<span class="login-view__logo-text login-view__logo-text--white">Offer派</span>
</div>
<!-- 浮动职位卡片 -->
<div class="login-view__job-cards">
<div v-for="(job, idx) in jobCards" :key="idx" class="login-view__job-card" :style="job.style">
<span class="login-view__job-card-icon" :style="{ background: job.iconBg, color: job.iconColor }">{{ job.icon }}</span>
<div class="login-view__job-card-info">
<span class="login-view__job-card-title">{{ job.title }}</span>
<span class="login-view__job-card-company">{{ job.company }}</span>
</div>
<span class="login-view__job-card-salary">{{ job.salary }}</span>
</div>
</div>
<!-- 标语 -->
<div class="login-view__resume-slogan">
<h1>校招批量海投</h1>
<h1>年轻人有更多的面试机会</h1>
</div>
</div>
<!-- 右侧简历上传面板 -->
<div class="login-view__right">
<div class="login-view__form-wrap login-view__form-wrap--resume"> <div class="login-view__form-wrap login-view__form-wrap--resume">
<!-- 状态1上传区域 --> <!-- 状态1上传区域 upload-->
<template v-if="resumeState === 'upload'"> <template v-if="resumeState === 'upload'">
<div class="login-view__heading"> <div class="login-view__heading">
<h2 class="login-view__title">一切美好从简历开始~</h2> <h2 class="login-view__title">上传简历开启求职之旅</h2>
<p class="login-view__subtitle">自动解析您的简历为你匹配最合适的岗位</p> <p class="login-view__subtitle">解析后为你匹配岗位也会用于后续简历优化</p>
</div> </div>
<!-- 拖拽上传区域 --> <!-- 拖拽上传区域 -->
<div class="login-view__upload-area" @dragover.prevent @drop.prevent="handleDrop" @click="triggerFileInput"> <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-text">拖拽简历到这里或点击上传</p>
<p class="login-view__upload-hint">支持 PDF / DOC / DOCX不超过 20MB</p> <p class="login-view__upload-hint">支持 PDF / DOC / DOCX不超过 20MB</p>
</div> </div>
@@ -133,26 +128,123 @@
<button class="login-view__upload-btn" @click="triggerFileInput">+ 上传简历</button> <button class="login-view__upload-btn" @click="triggerFileInput">+ 上传简历</button>
</template> </template>
<!-- 状态2解析中 --> <!-- 状态2解析中 parsing -->
<template v-if="resumeState === 'parsing'"> <template v-if="resumeState === 'parsing'">
<div class="login-view__parsing"> <div class="login-view__heading m0auto">
<h2 class="login-view__parsing-title">{{ parsingText }}</h2> <h2 class="login-view__title">正在解析简历</h2>
<p class="login-view__parsing-subtitle">自动解析您的简历为你匹配最合适的岗位</p> <p class="login-view__subtitle">正在识别教育背景工作经历与技能关键词</p>
</div> </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> </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'"> <template v-if="resumeState === 'failed'">
<div class="login-view__heading"> <div class="login-view__heading m0auto">
<h2 class="login-view__title login-view__title--error">上传失败</h2> <h2 class="login-view__title">简历上传失败</h2>
<p class="login-view__subtitle">您的简历解析失败了请重试一次吧~</p> <p class="login-view__subtitle">文件没有解析成功请检查格式后重新上传 <span class="color-accent cursor-po" @click="handleEnterDashboard">跳过稍后填写</span></p>
</div> </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> </template>
</div> </div>
</div>
</template> </template>
<div class="login-view__right-footer">
Offer派 求职工作台
</div>
</div>
<!-- 协议预览弹窗 --> <!-- 协议预览弹窗 -->
<AgreementPreviewDialog v-model="showAgreementDialog" :code="agreementCode" /> <AgreementPreviewDialog v-model="showAgreementDialog" :code="agreementCode" />
@@ -278,31 +370,37 @@ let loginRedirect = '/'
const canSend = computed(() => phone.value.length === 11 && agreedTerms.value) const canSend = computed(() => phone.value.length === 11 && agreedTerms.value)
// ==================== ==================== // ==================== ====================
/** 简历上传子状态:upload=上传区域 parsing=解析中 failed=失败 */ /** 简历上传子状态:upload=上传区域 parsing=解析中 failed=失败 success=成功 */
const resumeState = ref<'upload' | 'parsing' | 'failed'>('upload') const resumeState = ref<'upload' | 'parsing' | 'failed' | 'success'>('upload')
/** 解析中显示的文字 */
const parsingText = ref('正在解析个人资料...')
/** 解析文字轮换定时器 */ /** 解析文字轮换定时器 */
let parsingTimer: ReturnType<typeof setInterval> | null = null let parsingTimer: ReturnType<typeof setInterval> | null = null
/** 文件选择器引用 */ /** 文件选择器引用 */
const fileInputRef = ref<HTMLInputElement | null>(null) const fileInputRef = ref<HTMLInputElement | null>(null)
/** 左侧职位卡片硬编码数据 */ /** 进度条百分比 */
const jobCards = [ const parsePercent = ref(0)
{ icon: '网', iconBg: '#FFE8E8', iconColor: '#E85635', title: '前端工程师', company: '网易', salary: '18-28K', style: 'top: 0.6rem; left: 0.4rem;' }, /** 进度条动画定时器 */
{ icon: '网', iconBg: '#FFE8E8', iconColor: '#E85635', title: '运营专员', company: '阿里巴巴', salary: '15-20K', style: 'top: 0.6rem; right: 0.8rem;' }, let progressTimer: ReturnType<typeof setInterval> | null = null
{ icon: '百', iconBg: '#E8F0FF', iconColor: '#4A7FE5', title: '算法工程师', company: '百度', salary: '30-45K', style: 'top: 1.5rem; left: 0.2rem;' }, /** 接口是否已返回成功 */
{ icon: '美', iconBg: '#FFF3E8', iconColor: '#E8953F', title: '商业分析实习生', company: '美团', salary: '15K', style: 'top: 1.8rem; left: 2.8rem;' }, let uploadDone = false
{ icon: '小', iconBg: '#E8FFE8', iconColor: '#4CAF50', title: '硬件产品', company: '小米', salary: '18-24K', style: 'top: 2.5rem; left: 0.3rem;' },
{ icon: '网', iconBg: '#FFE8E8', iconColor: '#E85635', title: '产品运营', company: '京东', salary: '18-25K', style: 'top: 2.8rem; right: 0.6rem;' },
{ icon: '字', iconBg: '#F0E8FF', iconColor: '#7C4DFF', title: '产品经理', company: '字节跳动', salary: '25-35K', style: 'top: 3.5rem; left: 1.2rem;' },
{ icon: '增', iconBg: '#E8FFF5', iconColor: '#12C7BE', title: '增长运营', company: '拼多多', salary: '20-28K', style: 'top: 3.5rem; right: 0.4rem;' },
{ icon: '滴', iconBg: '#FFF8E1', iconColor: '#FFC107', title: '后端开发', company: '滴滴', salary: '22-32K', style: 'top: 4.3rem; left: 0.5rem;' },
{ icon: '网', iconBg: '#FFE8E8', iconColor: '#E85635', title: '数据分析师', company: '腾讯', salary: '20-30K', style: 'top: 4.6rem; right: 0.8rem;' },
]
/** 解析中文字轮换列表 */ /** 解析步骤定义 */
const parsingSteps = ['正在解析个人资料...', '正在解析教育背景...', '正在解析工作经历...', '正在解析技能特长...', '简历解析成功,即将跳转'] interface ParseStep {
label: string
status: 'pending' | 'active' | 'done'
stateText: string
}
/** 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]
// ==================== ==================== // ==================== ====================
@@ -470,6 +568,11 @@ function handleRetryUpload() {
resumeState.value = 'upload' resumeState.value = 'upload'
} }
/** 进入后台(状态4按钮) */
function handleEnterDashboard() {
router.replace(loginRedirect)
}
/** 开始上传并解析简历 */ /** 开始上传并解析简历 */
async function startUpload(file: File) { async function startUpload(file: File) {
// //
@@ -483,8 +586,16 @@ async function startUpload(file: File) {
return return
} }
// //
resumeState.value = 'parsing' 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() startParsingAnimation()
try { try {
@@ -492,13 +603,10 @@ async function startUpload(file: File) {
if (res.code === 0 && res.data?.resumeId) { if (res.code === 0 && res.data?.resumeId) {
// //
await syncProfileFromResume(String(res.data.resumeId)) await syncProfileFromResume(String(res.data.resumeId))
// //
stopParsingAnimation() uploadDone = true
parsingText.value = '简历解析成功,即将跳转' // success
// 1 finishParsingAnimation()
setTimeout(() => {
router.replace(loginRedirect)
}, 1000)
} else { } else {
// //
stopParsingAnimation() stopParsingAnimation()
@@ -510,25 +618,84 @@ async function startUpload(file: File) {
} }
} }
/** 开始解析文字轮换动画 */ /** 开始解析步骤动画(模拟4步进度) */
function startParsingAnimation() { function startParsingAnimation() {
let index = 0 let currentStep = 0
parsingText.value = parsingSteps[0] const totalSteps = parseStepList.value.length
parsingTimer = setInterval(() => { /** 每个步骤对应的进度百分比段 */
index++ const stepPercents = [25, 50, 75, 95]
if (index < parsingSteps.length - 1) {
// 42 //
parsingText.value = parsingSteps[index] parseStepList.value[0].status = 'active'
} else { parseStepList.value[0].stateText = '进行中'
// 4
parsingText.value = parsingSteps[parsingSteps.length - 2] //
if (parsingTimer) { clearInterval(parsingTimer); parsingTimer = null } 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-99100
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() { function stopParsingAnimation() {
if (progressTimer) { clearInterval(progressTimer); progressTimer = null }
if (parsingTimer) { clearInterval(parsingTimer); parsingTimer = null } if (parsingTimer) { clearInterval(parsingTimer); parsingTimer = null }
} }
@@ -536,6 +703,7 @@ function stopParsingAnimation() {
onBeforeUnmount(() => { onBeforeUnmount(() => {
if (countdownTimer) { clearInterval(countdownTimer) } if (countdownTimer) { clearInterval(countdownTimer) }
if (parsingTimer) { clearInterval(parsingTimer) } if (parsingTimer) { clearInterval(parsingTimer) }
if (progressTimer) { clearInterval(progressTimer) }
if (waveTimer) { clearTimeout(waveTimer); waveTimer = null } if (waveTimer) { clearTimeout(waveTimer); waveTimer = null }
if (blinkTimer) { clearTimeout(blinkTimer); blinkTimer = null } if (blinkTimer) { clearTimeout(blinkTimer); blinkTimer = null }
if (idleLookTimer) { clearTimeout(idleLookTimer); idleLookTimer = null } if (idleLookTimer) { clearTimeout(idleLookTimer); idleLookTimer = null }
+16 -12
View File
@@ -37,6 +37,8 @@
</div> </div>
</div> </div>
</div> </div>
<!-- 全屏加载遮罩 -->
<FullscreenLoading :visible="fullLoading" :text="fullLoadingText" title="Nova 正在帮您努力处理~" />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@@ -49,13 +51,19 @@ import ProfilePageContent from '@/components/ProfilePageContent.vue'
import ResumeUploadDialog from '@/components/ResumeUploadDialog.vue' import ResumeUploadDialog from '@/components/ResumeUploadDialog.vue'
import { saveProfile, fetchProfile, fetchEducation, saveEducation, fetchWork, saveWork, fetchInternship, saveInternship, fetchProject, saveProject, fetchCompetition, saveCompetition, syncProfileFromResume } 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 { uploadResume } from '@/utils/aiRequest'
import { ElMessage, ElLoading, ElMessageBox } from 'element-plus' import { ElMessage, ElMessageBox } from 'element-plus'
import 'element-plus/es/components/loading/style/css' import FullscreenLoading from '@/components/FullscreenLoading.vue'
import type { SaveEducationItem, SaveWorkItem, SaveProjectItem, SaveCompetitionItem } from '@/api/profile' import type { SaveEducationItem, SaveWorkItem, SaveProjectItem, SaveCompetitionItem } from '@/api/profile'
const router = useRouter() const router = useRouter()
const store = useStore() const store = useStore()
// ==================== ====================
/** 全屏加载是否显示 */
const fullLoading = ref(false)
/** 全屏加载文案 */
const fullLoadingText = ref('')
/** 页面挂载时加载公共分类数据和个人资料 */ /** 页面挂载时加载公共分类数据和个人资料 */
onMounted(async () => { onMounted(async () => {
if (!store.state.regions.length) { if (!store.state.regions.length) {
@@ -208,29 +216,25 @@ const showWelcomeDialog = ref(false)
/** 欢迎弹窗确认上传回调 — 上传简历并同步个人资料,完成后刷新页面 */ /** 欢迎弹窗确认上传回调 — 上传简历并同步个人资料,完成后刷新页面 */
async function handleWelcomeUpload(file: File) { async function handleWelcomeUpload(file: File) {
const loading = ElLoading.service({ fullLoadingText.value = '简历解析中,请耐心等待…'
lock: true, fullLoading.value = true
text: '简历解析中,请耐心等待…',
background: 'rgba(0, 0, 0, 0.5)',
customClass: 'profile-welcome-loading',
})
try { try {
const res = await uploadResume(file) const res = await uploadResume(file)
if (res.code === 0 && res.data?.resumeId) { if (res.code === 0 && res.data?.resumeId) {
// //
loading.setText('正在同步个人资料…') fullLoadingText.value = '正在同步个人资料…'
await syncProfileFromResume(String(res.data.resumeId)) await syncProfileFromResume(String(res.data.resumeId))
loading.close() fullLoading.value = false
ElMessage.success('简历上传并同步成功') ElMessage.success('简历上传并同步成功')
// //
router.go(0) router.go(0)
} else { } else {
loading.close() fullLoading.value = false
ElMessage.error(res.msg || '上传失败') ElMessage.error(res.msg || '上传失败')
} }
} catch { } catch {
loading.close() fullLoading.value = false
ElMessage.error('上传失败,请稍后重试') ElMessage.error('上传失败,请稍后重试')
} }
} }
+15 -11
View File
@@ -98,6 +98,8 @@
v-model="uploadDialogVisible" v-model="uploadDialogVisible"
@confirm="handleUploadConfirm" @confirm="handleUploadConfirm"
/> />
<!-- 全屏加载遮罩 -->
<FullscreenLoading :visible="fullLoading" :text="fullLoadingText" title="Nova 正在帮您努力处理~" />
</div> </div>
</template> </template>
@@ -113,13 +115,19 @@ import { uploadResume } from '@/utils/aiRequest'
import { import {
fetchResumeList, deleteResume, setDefaultResume, type ResumeListItem, fetchResumeList, deleteResume, setDefaultResume, type ResumeListItem,
} from '@/api/resume' } from '@/api/resume'
import { ElMessage, ElMessageBox, ElLoading } from 'element-plus' import { ElMessage, ElMessageBox } from 'element-plus'
// //
import 'element-plus/es/components/loading/style/css'
import 'element-plus/es/components/message-box/style/css' import 'element-plus/es/components/message-box/style/css'
import FullscreenLoading from '@/components/FullscreenLoading.vue'
const router = useRouter() const router = useRouter()
// ==================== ====================
/** 全屏加载是否显示 */
const fullLoading = ref(false)
/** 全屏加载文案 */
const fullLoadingText = ref('')
// ==================== ==================== // ==================== ====================
/** 头像背景色列表,按索引循环取色 */ /** 头像背景色列表,按索引循环取色 */
@@ -297,12 +305,8 @@ function handleUpload() {
/** 上传弹窗确认回调 — 接收 File 对象,调用接口上传 */ /** 上传弹窗确认回调 — 接收 File 对象,调用接口上传 */
async function handleUploadConfirm(file: File) { async function handleUploadConfirm(file: File) {
// AI // AI
const loading = ElLoading.service({ fullLoadingText.value = '简历解析中,请耐心等待…'
lock: true, fullLoading.value = true
text: '简历解析中,请耐心等待…',
background: 'rgba(0, 0, 0, 0.5)',
customClass: 'resume-upload-loading',
})
try { try {
const res = await uploadResume(file) const res = await uploadResume(file)
@@ -311,14 +315,14 @@ async function handleUploadConfirm(file: File) {
loadResumeList() loadResumeList()
// //
await new Promise(resolve => setTimeout(resolve, 2000)) await new Promise(resolve => setTimeout(resolve, 2000))
loading.close() fullLoading.value = false
goDetail(res.data.resumeId) goDetail(res.data.resumeId)
} else { } else {
loading.close() fullLoading.value = false
ElMessage.error(res.msg || '上传失败') ElMessage.error(res.msg || '上传失败')
} }
} catch { } catch {
loading.close() fullLoading.value = false
ElMessage.error('上传失败,请稍后重试') ElMessage.error('上传失败,请稍后重试')
} }
} }
+42 -42
View File
@@ -160,28 +160,30 @@
<rect x="2" y="3" width="12" height="10" rx="1.5" stroke="currentColor" stroke-width="1.2"/> <rect x="2" y="3" width="12" height="10" rx="1.5" stroke="currentColor" stroke-width="1.2"/>
<path d="M2 5.5l6 4 6-4" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/> <path d="M2 5.5l6 4 6-4" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/>
</svg> </svg>
{{ resumeMain.email }} <span>{{ resumeMain.email }}</span>
</span> </span>
<!-- 手机号 --> <!-- 手机号 -->
<span v-if="resumeMain.mobileNumber" class="resume-detail__contact-item"> <span v-if="resumeMain.mobileNumber" class="resume-detail__contact-item">
<svg viewBox="0 0 16 16" fill="none" class="resume-detail__contact-icon"> <svg viewBox="0 0 16 16" fill="none" class="resume-detail__contact-icon">
<path d="M4 2h2.5l1.5 3-1.5 1.5a8 8 0 003 3L11 8l3 1.5V12a2 2 0 01-2 2C6.5 14 2 9.5 2 4a2 2 0 012-2z" stroke="currentColor" stroke-width="1.2" stroke-linejoin="round"/> <path d="M4 2h2.5l1.5 3-1.5 1.5a8 8 0 003 3L11 8l3 1.5V12a2 2 0 01-2 2C6.5 14 2 9.5 2 4a2 2 0 012-2z" stroke="currentColor" stroke-width="1.2" stroke-linejoin="round"/>
</svg> </svg>
{{ resumeMain.mobileNumber }} <span>{{ resumeMain.mobileNumber }}</span>
</span> </span>
<!-- 微信号 --> <!-- 微信号 -->
<span v-if="resumeMain.wechatNumber" class="resume-detail__contact-item"> <span v-if="resumeMain.wechatNumber" class="resume-detail__contact-item">
<el-icon class="fs14 color-9"><ChatLineRound /></el-icon> <el-icon class="fs14 color-9"><ChatLineRound /></el-icon>
{{ resumeMain.wechatNumber }} <span>{{ resumeMain.wechatNumber }}</span>
</span> </span>
<!-- 作品集链接 --> <!-- 作品集链接 -->
<span v-if="resumeMain.portfolioUrl" class="resume-detail__contact-item"> <span v-if="resumeMain.portfolioUrl" class="resume-detail__contact-item dib">
<svg viewBox="0 0 16 16" fill="none" class="resume-detail__contact-icon"> <svg xmlns="http://www.w3.org/2000/svg" class=" mr6" width="0.15rem" height="0.15rem" viewBox="0 0 16 16">
<path d="M3 8a5 5 0 015-5h1a5 5 0 010 10H8a5 5 0 01-5-5z" stroke="currentColor" stroke-width="1.2"/> <path d="M0 0h16v16H0z" fill="none" />
<path d="M7 6l2 2-2 2" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/> <path fill="none" stroke="currentColor" d="M14.5 8A6.5 6.5 0 0 1 8 14.5M14.5 8A6.5 6.5 0 0 0 8 1.5M14.5 8c0 1.657-2.91 3-6.5 3S1.5 9.657 1.5 8m13 0c0-1.657-2.91-3-6.5-3S1.5 6.343 1.5 8M8 14.5A6.5 6.5 0 0 1 1.5 8M8 14.5c1.657 0 3-2.91 3-6.5S9.657 1.5 8 1.5m0 13c-1.657 0-3-2.91-3-6.5s1.343-6.5 3-6.5M1.5 8A6.5 6.5 0 0 1 8 1.5" />
</svg> </svg>
<a :href="resumeMain.portfolioUrl" target="_blank" rel="noopener noreferrer" class="resume-detail__portfolio-link">{{ resumeMain.portfolioUrl }}</a>
<a :href="resumeMain.portfolioUrl?.startsWith('http') ? resumeMain.portfolioUrl : `http://${resumeMain.portfolioUrl}`" target="_blank" rel="noopener noreferrer" class="resume-detail__portfolio-link">{{ resumeMain.portfolioUrl }}</a>
</span> </span>
</div> </div>
</template> </template>
@@ -253,13 +255,14 @@
<div class="resume-detail__exp-row"> <div class="resume-detail__exp-row">
<span class="resume-detail__exp-name">{{ edu.school }}</span> <span class="resume-detail__exp-name">{{ edu.school }}</span>
<span v-if="edu.major || edu.degree" class="resume-detail__exp-role">{{ edu.major }}{{ edu.degree ? '·' + edu.degree : '' }}</span> <span v-if="edu.major || edu.degree" class="resume-detail__exp-role">{{ edu.major }}{{ edu.degree ? '·' + edu.degree : '' }}</span>
<span v-if="edu.startDate || edu.endDate" class="resume-detail__exp-date">{{ edu.startDate }}<span v-if="edu.startDate && edu.endDate">-</span>{{ edu.endDate }}</span>
<span v-if="hasDiagnosis && getIssueByRecord('education', edu.id!) && getIssueByRecord('education', edu.id!)!.status === 0" class="resume-detail__fix-tag"> <span v-if="hasDiagnosis && getIssueByRecord('education', edu.id!) && getIssueByRecord('education', edu.id!)!.status === 0" class="resume-detail__fix-tag">
<svg viewBox="0 0 16 16" class="resume-detail__fix-tag-icon"><circle cx="8" cy="8" r="7" fill="#FF8D0B"/><text x="8" y="12" text-anchor="middle" font-size="11" fill="#fff" font-weight="bold">!</text></svg> <svg viewBox="0 0 16 16" class="resume-detail__fix-tag-icon"><circle cx="8" cy="8" r="7" fill="#FF8D0B"/><text x="8" y="12" text-anchor="middle" font-size="11" fill="#fff" font-weight="bold">!</text></svg>
建议使用AI润色修复 &gt;&gt; 建议使用AI润色修复 &gt;&gt;
<span class="resume-detail__fix-tooltip">{{ getIssueByRecord('education', edu.id!)!.finding }}</span> <span class="resume-detail__fix-tooltip">{{ getIssueByRecord('education', edu.id!)!.finding }}</span>
</span> </span>
<div class="mlauto dflex-end"> <div class="mlauto dflex-end">
<!-- 时间默认显示hover时隐藏 -->
<span v-if="edu.startDate || edu.endDate" class="resume-detail__exp-date resume-detail__exp-date--toggle">{{ edu.startDate }}<span v-if="edu.startDate && edu.endDate">-</span>{{ edu.endDate }}</span>
<!-- 编辑按钮 hover时显示 --> <!-- 编辑按钮 hover时显示 -->
<button class="resume-detail__edit-btn-inline resume-detail__exp-edit-btn" @click="startEditExp('education', edu)" aria-label="编辑"> <button class="resume-detail__edit-btn-inline resume-detail__exp-edit-btn" @click="startEditExp('education', edu)" aria-label="编辑">
<el-icon><EditPen /></el-icon> <el-icon><EditPen /></el-icon>
@@ -302,13 +305,14 @@
<div class="resume-detail__exp-row"> <div class="resume-detail__exp-row">
<span class="resume-detail__exp-name">{{ exp.companyName }}</span> <span class="resume-detail__exp-name">{{ exp.companyName }}</span>
<span v-if="exp.position" class="resume-detail__exp-role">{{ exp.position }}</span> <span v-if="exp.position" class="resume-detail__exp-role">{{ exp.position }}</span>
<span v-if="exp.startDate || exp.endDate" class="resume-detail__exp-date">{{ exp.startDate }}<span v-if="exp.startDate && exp.endDate">-</span>{{ exp.endDate }}</span>
<span v-if="hasDiagnosis && getIssueByRecord('work', exp.id!) && getIssueByRecord('work', exp.id!)!.status === 0" class="resume-detail__fix-tag"> <span v-if="hasDiagnosis && getIssueByRecord('work', exp.id!) && getIssueByRecord('work', exp.id!)!.status === 0" class="resume-detail__fix-tag">
<svg viewBox="0 0 16 16" class="resume-detail__fix-tag-icon"><circle cx="8" cy="8" r="7" fill="#FF8D0B"/><text x="8" y="12" text-anchor="middle" font-size="11" fill="#fff" font-weight="bold">!</text></svg> <svg viewBox="0 0 16 16" class="resume-detail__fix-tag-icon"><circle cx="8" cy="8" r="7" fill="#FF8D0B"/><text x="8" y="12" text-anchor="middle" font-size="11" fill="#fff" font-weight="bold">!</text></svg>
建议使用AI润色修复 &gt;&gt; 建议使用AI润色修复 &gt;&gt;
<span class="resume-detail__fix-tooltip">{{ getIssueByRecord('work', exp.id!)!.finding }}</span> <span class="resume-detail__fix-tooltip">{{ getIssueByRecord('work', exp.id!)!.finding }}</span>
</span> </span>
<div class="mlauto dflex-end"> <div class="mlauto dflex-end">
<!-- 时间默认显示hover时隐藏 -->
<span v-if="exp.startDate || exp.endDate" class="resume-detail__exp-date resume-detail__exp-date--toggle">{{ exp.startDate }}<span v-if="exp.startDate && exp.endDate">-</span>{{ exp.endDate }}</span>
<button class="resume-detail__edit-btn-inline resume-detail__exp-edit-btn" @click="startEditExp('work', exp)" aria-label="编辑"> <button class="resume-detail__edit-btn-inline resume-detail__exp-edit-btn" @click="startEditExp('work', exp)" aria-label="编辑">
<el-icon><EditPen /></el-icon><span>编辑</span> <el-icon><EditPen /></el-icon><span>编辑</span>
</button> </button>
@@ -339,13 +343,14 @@
<div class="resume-detail__exp-row"> <div class="resume-detail__exp-row">
<span class="resume-detail__exp-name">{{ intern.companyName }}</span> <span class="resume-detail__exp-name">{{ intern.companyName }}</span>
<span v-if="intern.position" class="resume-detail__exp-role">{{ intern.position }}</span> <span v-if="intern.position" class="resume-detail__exp-role">{{ intern.position }}</span>
<span v-if="intern.startDate || intern.endDate" class="resume-detail__exp-date">{{ intern.startDate }}<span v-if="intern.startDate && intern.endDate">-</span>{{ intern.endDate }}</span>
<span v-if="hasDiagnosis && getIssueByRecord('internship', intern.id!) && getIssueByRecord('internship', intern.id!)!.status === 0" class="resume-detail__fix-tag"> <span v-if="hasDiagnosis && getIssueByRecord('internship', intern.id!) && getIssueByRecord('internship', intern.id!)!.status === 0" class="resume-detail__fix-tag">
<svg viewBox="0 0 16 16" class="resume-detail__fix-tag-icon"><circle cx="8" cy="8" r="7" fill="#FF8D0B"/><text x="8" y="12" text-anchor="middle" font-size="11" fill="#fff" font-weight="bold">!</text></svg> <svg viewBox="0 0 16 16" class="resume-detail__fix-tag-icon"><circle cx="8" cy="8" r="7" fill="#FF8D0B"/><text x="8" y="12" text-anchor="middle" font-size="11" fill="#fff" font-weight="bold">!</text></svg>
建议使用AI润色修复 &gt;&gt; 建议使用AI润色修复 &gt;&gt;
<span class="resume-detail__fix-tooltip">{{ getIssueByRecord('internship', intern.id!)!.finding }}</span> <span class="resume-detail__fix-tooltip">{{ getIssueByRecord('internship', intern.id!)!.finding }}</span>
</span> </span>
<div class="mlauto dflex-end"> <div class="mlauto dflex-end">
<!-- 时间默认显示hover时隐藏 -->
<span v-if="intern.startDate || intern.endDate" class="resume-detail__exp-date resume-detail__exp-date--toggle">{{ intern.startDate }}<span v-if="intern.startDate && intern.endDate">-</span>{{ intern.endDate }}</span>
<button class="resume-detail__edit-btn-inline resume-detail__exp-edit-btn" @click="startEditExp('internship', intern)" aria-label="编辑"> <button class="resume-detail__edit-btn-inline resume-detail__exp-edit-btn" @click="startEditExp('internship', intern)" aria-label="编辑">
<el-icon><EditPen /></el-icon><span>编辑</span> <el-icon><EditPen /></el-icon><span>编辑</span>
</button> </button>
@@ -376,13 +381,14 @@
<div class="resume-detail__exp-row"> <div class="resume-detail__exp-row">
<span class="resume-detail__exp-name">{{ proj.projectName }}</span> <span class="resume-detail__exp-name">{{ proj.projectName }}</span>
<span v-if="proj.role" class="resume-detail__exp-role">{{ proj.role }}</span> <span v-if="proj.role" class="resume-detail__exp-role">{{ proj.role }}</span>
<span v-if="proj.startDate || proj.endDate" class="resume-detail__exp-date">{{ proj.startDate }}<span v-if="proj.startDate && proj.endDate">-</span>{{ proj.endDate }}</span>
<span v-if="hasDiagnosis && getIssueByRecord('project', proj.id!) && getIssueByRecord('project', proj.id!)!.status === 0" class="resume-detail__fix-tag"> <span v-if="hasDiagnosis && getIssueByRecord('project', proj.id!) && getIssueByRecord('project', proj.id!)!.status === 0" class="resume-detail__fix-tag">
<svg viewBox="0 0 16 16" class="resume-detail__fix-tag-icon"><circle cx="8" cy="8" r="7" fill="#FF8D0B"/><text x="8" y="12" text-anchor="middle" font-size="11" fill="#fff" font-weight="bold">!</text></svg> <svg viewBox="0 0 16 16" class="resume-detail__fix-tag-icon"><circle cx="8" cy="8" r="7" fill="#FF8D0B"/><text x="8" y="12" text-anchor="middle" font-size="11" fill="#fff" font-weight="bold">!</text></svg>
建议使用AI润色修复 &gt;&gt; 建议使用AI润色修复 &gt;&gt;
<span class="resume-detail__fix-tooltip">{{ getIssueByRecord('project', proj.id!)!.finding }}</span> <span class="resume-detail__fix-tooltip">{{ getIssueByRecord('project', proj.id!)!.finding }}</span>
</span> </span>
<div class="mlauto dflex-end"> <div class="mlauto dflex-end">
<!-- 时间默认显示hover时隐藏 -->
<span v-if="proj.startDate || proj.endDate" class="resume-detail__exp-date resume-detail__exp-date--toggle">{{ proj.startDate }}<span v-if="proj.startDate && proj.endDate">-</span>{{ proj.endDate }}</span>
<button class="resume-detail__edit-btn-inline resume-detail__exp-edit-btn" @click="startEditExp('project', proj)" aria-label="编辑"> <button class="resume-detail__edit-btn-inline resume-detail__exp-edit-btn" @click="startEditExp('project', proj)" aria-label="编辑">
<el-icon><EditPen /></el-icon><span>编辑</span> <el-icon><EditPen /></el-icon><span>编辑</span>
</button> </button>
@@ -413,13 +419,14 @@
<div class="resume-detail__exp-row"> <div class="resume-detail__exp-row">
<span class="resume-detail__exp-name">{{ comp.competitionName }}</span> <span class="resume-detail__exp-name">{{ comp.competitionName }}</span>
<span v-if="comp.award" class="resume-detail__exp-role">{{ comp.award }}</span> <span v-if="comp.award" class="resume-detail__exp-role">{{ comp.award }}</span>
<span v-if="comp.awardDate" class="resume-detail__exp-date">{{ comp.awardDate }}</span>
<span v-if="hasDiagnosis && getIssueByRecord('competition', comp.id!) && getIssueByRecord('competition', comp.id!)!.status === 0" class="resume-detail__fix-tag"> <span v-if="hasDiagnosis && getIssueByRecord('competition', comp.id!) && getIssueByRecord('competition', comp.id!)!.status === 0" class="resume-detail__fix-tag">
<svg viewBox="0 0 16 16" class="resume-detail__fix-tag-icon"><circle cx="8" cy="8" r="7" fill="#FF8D0B"/><text x="8" y="12" text-anchor="middle" font-size="11" fill="#fff" font-weight="bold">!</text></svg> <svg viewBox="0 0 16 16" class="resume-detail__fix-tag-icon"><circle cx="8" cy="8" r="7" fill="#FF8D0B"/><text x="8" y="12" text-anchor="middle" font-size="11" fill="#fff" font-weight="bold">!</text></svg>
建议使用AI润色修复 &gt;&gt; 建议使用AI润色修复 &gt;&gt;
<span class="resume-detail__fix-tooltip">{{ getIssueByRecord('competition', comp.id!)!.finding }}</span> <span class="resume-detail__fix-tooltip">{{ getIssueByRecord('competition', comp.id!)!.finding }}</span>
</span> </span>
<div class="mlauto dflex-end"> <div class="mlauto dflex-end">
<!-- 时间默认显示hover时隐藏 -->
<span v-if="comp.awardDate" class="resume-detail__exp-date resume-detail__exp-date--toggle">{{ comp.awardDate }}</span>
<button class="resume-detail__edit-btn-inline resume-detail__exp-edit-btn" @click="startEditExp('competition', comp)" aria-label="编辑"> <button class="resume-detail__edit-btn-inline resume-detail__exp-edit-btn" @click="startEditExp('competition', comp)" aria-label="编辑">
<el-icon><EditPen /></el-icon><span>编辑</span> <el-icon><EditPen /></el-icon><span>编辑</span>
</button> </button>
@@ -500,6 +507,8 @@
<MemberAccessDialog v-model="showMemberAccessDialog" @open-member="showMemberDialog = true" /> <MemberAccessDialog v-model="showMemberAccessDialog" @open-member="showMemberDialog = true" />
<!-- 会员购买弹窗 --> <!-- 会员购买弹窗 -->
<MemberDialog v-model="showMemberDialog" /> <MemberDialog v-model="showMemberDialog" />
<!-- 全屏加载遮罩 -->
<FullscreenLoading :visible="fullLoading" :text="fullLoadingText" title="Nova 正在帮您努力处理~" />
</div> </div>
</template> </template>
@@ -557,11 +566,18 @@ import {
type DescriptionParagraph, type DescriptionParagraph,
} from '@/api/resume' } from '@/api/resume'
import {ArrowLeft, EditPen, Plus, DeleteFilled, ChatLineRound} from "@element-plus/icons-vue"; import {ArrowLeft, EditPen, Plus, DeleteFilled, ChatLineRound} from "@element-plus/icons-vue";
import { ElMessage, ElMessageBox, ElLoading } from 'element-plus' import { ElMessage, ElMessageBox } from 'element-plus'
import FullscreenLoading from '@/components/FullscreenLoading.vue'
// ==================== Store ==================== // ==================== Store ====================
const store = useStore() const store = useStore()
// ==================== ====================
/** 全屏加载是否显示 */
const fullLoading = ref(false)
/** 全屏加载文案 */
const fullLoadingText = ref('')
// ==================== ==================== // ==================== ====================
/** 会员权限拦截弹窗 */ /** 会员权限拦截弹窗 */
@@ -936,12 +952,8 @@ async function handleDiagnose() {
} }
// AI // AI
const loading = ElLoading.service({ fullLoadingText.value = '简历诊断中,请耐心等待…'
lock: true, fullLoading.value = true
text: '简历诊断中,请耐心等待…',
background: 'rgba(0, 0, 0, 0.5)',
customClass: 'resume-upload-loading',
})
try { try {
const res = await triggerResumeDiagnosis(resumeId) const res = await triggerResumeDiagnosis(resumeId)
if (res.code === 0) { if (res.code === 0) {
@@ -954,7 +966,7 @@ async function handleDiagnose() {
} catch { } catch {
ElMessage.error('诊断请求失败,请稍后重试') ElMessage.error('诊断请求失败,请稍后重试')
} finally { } finally {
loading.close() fullLoading.value = false
} }
} }
@@ -1098,12 +1110,8 @@ function joinDescriptionText(desc: DescriptionParagraph[]): string {
async function handleFixAllSave(items: FixAllSubmitItem[]) { async function handleFixAllSave(items: FixAllSubmitItem[]) {
if (!items.length) return if (!items.length) return
const loading = ElLoading.service({ fullLoadingText.value = '正在保存修改…'
lock: true, fullLoading.value = true
text: '正在保存修改…',
background: 'rgba(0, 0, 0, 0.5)',
customClass: 'resume-upload-loading',
})
try { try {
for (const item of items) { for (const item of items) {
@@ -1123,7 +1131,7 @@ async function handleFixAllSave(items: FixAllSubmitItem[]) {
} catch { } catch {
ElMessage.error('保存失败,请稍后重试') ElMessage.error('保存失败,请稍后重试')
} finally { } finally {
loading.close() fullLoading.value = false
} }
} }
@@ -1131,12 +1139,8 @@ async function handleFixAllSave(items: FixAllSubmitItem[]) {
async function handleFixAllUseAi(items: FixAllSubmitItem[]) { async function handleFixAllUseAi(items: FixAllSubmitItem[]) {
if (!items.length) return if (!items.length) return
const loading = ElLoading.service({ fullLoadingText.value = '正在应用AI润色结果…'
lock: true, fullLoading.value = true
text: '正在应用AI润色结果…',
background: 'rgba(0, 0, 0, 0.5)',
customClass: 'resume-upload-loading',
})
try { try {
for (const item of items) { for (const item of items) {
@@ -1152,7 +1156,7 @@ async function handleFixAllUseAi(items: FixAllSubmitItem[]) {
} catch { } catch {
ElMessage.error('操作失败,请稍后重试') ElMessage.error('操作失败,请稍后重试')
} finally { } finally {
loading.close() fullLoading.value = false
} }
} }
@@ -1215,12 +1219,8 @@ async function updateModuleContent(item: FixAllSubmitItem) {
/** 修复抽屉提交新版本 — 更新本地数据、调用保存接口、刷新页面 */ /** 修复抽屉提交新版本 — 更新本地数据、调用保存接口、刷新页面 */
async function handleFixSubmit(content: string[]) { async function handleFixSubmit(content: string[]) {
const loading = ElLoading.service({ fullLoadingText.value = '正在保存修改…'
lock: true, fullLoading.value = true
text: '正在保存修改…',
background: 'rgba(0, 0, 0, 0.5)',
customClass: 'resume-upload-loading',
})
try { try {
const moduleType = fixModuleType.value const moduleType = fixModuleType.value
@@ -1262,7 +1262,7 @@ async function handleFixSubmit(content: string[]) {
} catch { } catch {
ElMessage.error('保存失败,请稍后重试') ElMessage.error('保存失败,请稍后重试')
} finally { } finally {
loading.close() fullLoading.value = false
} }
} }