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']
ElSwitch: typeof import('element-plus/es')['ElSwitch']
ElTooltip: typeof import('element-plus/es')['ElTooltip']
FullscreenLoading: typeof import('./src/components/FullscreenLoading.vue')['default']
HelloWorld: typeof import('./src/components/HelloWorld.vue')['default']
IndustrySelector: typeof import('./src/components/tools/IndustrySelector.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-accent{
color:$accent;
}
/*border*/
@@ -6,7 +6,7 @@
position: fixed;
inset: 0;
background: $overlay-bg;
z-index: 2000;
z-index: 2300;
display: flex;
align-items: center;
justify-content: center;
@@ -83,7 +83,7 @@
font-size: 0.13rem;
color: $text-dark;
background: $bg-white;
border: 1px solid $border-color;
border: none;
border-radius: 0.04rem;
outline: none;
transition: all 0.2s;
@@ -317,8 +317,7 @@
&__ai-result-card {
padding: 0.1rem 0.12rem;
background: $theme-color;
border: 1px solid rgba($accent, 0.2);
background: linear-gradient(to bottom,#F5FFFD,#ffffff);
border-radius: 0.06rem;
}
@@ -189,8 +189,8 @@
}
&__item-date {
font-size: 0.11rem;
color: $accent;
font-size: 0.13rem;
color: #64748B;
margin-bottom: 0.04rem;
}
@@ -328,12 +328,29 @@
// 条目hover时显示编辑按钮
&__section-item:hover &__item-edit-btn {
display: flex;
opacity: 1;
pointer-events: auto;
}
// 条目内编辑按钮
// hover条目时隐藏时间
&__section-item:hover &__item-date--toggle {
display: none;
}
// 条目内编辑按钮默认隐藏不占位
&__item-edit-btn {
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 *;
// ==================== 邀请注册送会员弹窗 ====================
// ==================== 邀请注册送会员弹窗重设计版 ====================
.settings-invite-overlay {
position: fixed;
top: 0;
@@ -18,178 +18,211 @@
.settings-invite-dialog {
position: relative;
width: 4.2rem;
max-height: 85vh;
height: 5.0rem;
background: $bg-white;
border-radius: 0.12rem;
padding: 0.32rem 0.28rem;
box-shadow: 0 0.04rem 0.2rem rgba(0, 0, 0, 0.15);
overflow-y: auto;
border-radius: 0.26rem;
box-shadow: 0 0.2rem 0.44rem rgba(0, 0, 0, 0.14);
overflow: hidden;
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 {
position: absolute;
top: 0.14rem;
right: 0.18rem;
font-size: 0.18rem;
color: $text-light;
top: 0.24rem;
right: 0.27rem;
font-size: 0.22rem;
line-height: 0.27rem;
color: #8EA0B8;
cursor: pointer;
transition: color 0.2s;
z-index: 1;
&:hover {
color: $text-dark;
}
}
// 内容区域
&__body {
flex: 1;
padding: 0.22rem 0.28rem 0.2rem;
display: flex;
flex-direction: column;
}
// 标题
&__title {
font-size: 0.18rem;
font-size: 0.21rem;
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;
margin: 0 0 0.2rem 0;
line-height: 0.16rem;
}
// 邀请链接区域
&__link-box {
background: $bg-main;
border-radius: 0.08rem;
padding: 0.14rem 0.16rem;
margin-bottom: 0.14rem;
display: flex;
align-items: center;
width: 3.64rem;
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 {
font-size: 0.12rem;
color: $text-dark;
line-height: 1.7;
margin: 0;
word-break: break-all;
flex: 1;
font-size: 0.11rem;
line-height: 0.13rem;
color: #334155;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
// 复制链接按钮
&__copy-btn {
width: 100%;
height: 0.4rem;
width: 1.0rem;
height: 0.32rem;
border: none;
border-radius: 0.2rem;
background: $text-dark;
border-radius: 0.16rem;
background: #0F172A;
color: $bg-white;
font-size: 0.14rem;
font-size: 0.13rem;
font-weight: 600;
line-height: 0.16rem;
cursor: pointer;
margin-bottom: 0.18rem;
flex-shrink: 0;
transition: opacity 0.2s;
text-align: center;
&:hover {
opacity: 0.85;
}
}
// 步骤提示
&__steps-tip {
font-size: 0.12rem;
color: $text-middle;
text-align: center;
margin: 0 0 0.14rem 0;
}
// 步骤卡片
&__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;
gap: 0.12rem;
margin-top: 0.16rem;
margin-bottom: 0.16rem;
align-items: center;
width: 3.64rem;
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;
border: 1px solid $border-color;
border-radius: 0.08rem;
padding: 0.12rem;
// 步骤数字圆圈
&__step-circle {
width: 0.26rem;
height: 0.26rem;
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 {
font-size: 0.11rem;
color: $text-middle;
margin-bottom: 0.06rem;
font-size: 0.13rem;
font-weight: 500;
color: #64748B;
line-height: 0.16rem;
}
&__stat-value {
font-size: 0.16rem;
font-weight: 700;
color: $text-dark;
font-size: 0.13rem;
font-weight: 500;
color: #64748B;
line-height: 0.16rem;
}
// 活动规则
&__rules {
display: flex;
align-items: center;
justify-content: space-between;
border: 1px solid $border-color;
// 成为正式会员按钮
&__member-btn {
margin-left: auto;
width: 1.1rem;
height: 0.29rem;
background: #F3FFFD;
border: 1px solid #BFEFE8;
border-radius: 0.08rem;
padding: 0.12rem 0.16rem;
font-size: 0.13rem;
font-weight: 400;
color: #000000;
line-height: 0.16rem;
cursor: pointer;
transition: background 0.2s;
transition: all 0.2s;
text-align: center;
&:hover {
background: $bg-main;
}
}
&__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;
}
background: #e8faf8;
border-color: $accent;
}
}
}
+4 -5
View File
@@ -5,7 +5,7 @@
flex-direction: column;
width: 2rem;
height: var(--app-height, 100vh);
background: #1a1a2e;
background: $bg-white;
color: #fff;
padding: 0.2rem 0.12rem;
box-sizing: border-box;
@@ -57,7 +57,7 @@
align-items: center;
gap: 0.08rem;
padding: 0.1rem 0.12rem;
border-radius: 0.14rem;
border-radius: 0.08rem;
color: #9598ab;
text-decoration: none;
cursor: pointer;
@@ -65,12 +65,12 @@
font-size: 0.14rem;
&:hover {
background: rgba(255, 255, 255, 0.05);
background: $btn-dark;
color: #fff;
}
&--active {
background: #1F2937;
background: $btn-dark;
color: #fff;
}
}
@@ -98,7 +98,6 @@
display: flex;
flex-direction: column;
gap: 0.04rem;
border-top: 1px solid #2e3142;
padding-top: 0.12rem;
}
}
+6
View File
@@ -82,6 +82,12 @@
padding: 20px !important;
border-radius: 8px !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 {
padding: 0 0 12px 0 !important;
+477 -184
View File
@@ -1,3 +1,4 @@
@use '../variables' as *;
/* 登录页面 — 左右分栏全屏布局 */
.login-view {
font-size: 0.14rem;
@@ -168,7 +169,7 @@
align-items: center;
width: 43%;
height: 100%;
background: #F6FDFB;
background: #fff;
}
/* Logo 行(右侧及简历阶段通用) */
@@ -176,6 +177,12 @@
display: flex;
align-items: center;
gap: 0.12rem;
width: 3.41rem;
img{
width: 1.6rem;
height: 0.4rem;
}
}
.login-view__logo-box {
@@ -215,112 +222,135 @@
}
.login-view__logo-row--right {
margin-bottom: 0.4rem;
margin-bottom: 0.6rem;
}
/* 表单包裹 */
.login-view__form-wrap {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.28rem;
width: 4rem;
gap: 0.24rem;
width: 3.41rem;
}
/* 标题 */
.login-view__heading {
width: 100%;
width: 3.41rem;
display: flex;
flex-direction: column;
gap: 0.1rem;
gap: 0.05rem;
}
.login-view__title {
font-weight: 700;
font-size: 0.32rem;
line-height: 0.39rem;
color: #132034;
font-size: 0.18rem;
line-height: 0.22rem;
color: #18181A;
}
.login-view__subtitle {
font-weight: 400;
font-size: 0.15rem;
line-height: 0.18rem;
color: #64748B;
font-size: 0.10rem;
line-height: 0.12rem;
color: #616367;
>span:nth-child(2){
color: $accent;
}
}
/* ==================== 手机号输入行 ==================== */
.login-view__phone-row {
box-sizing: border-box;
display: flex;
align-items: center;
width: 100%;
height: 0.52rem;
border-radius: 0.12rem;
overflow: hidden;
width: 3.41rem;
height: 0.40rem;
background: #FFFFFF;
border: 1px solid #C8C9CD;
box-shadow: 0 0.08rem 0.20rem rgba(15, 83, 87, 0.05);
border-radius: 0.08rem;
}
.login-view__country-code {
/* 手机图标 */
.login-view__phone-icon {
display: flex;
align-items: center;
padding: 0 0.16rem;
gap: 0.06rem;
width: 0.96rem;
height: 100%;
background: #F3FFFD;
border: 1px solid #BFE8E2;
border-radius: 0.12rem 0 0 0.12rem;
cursor: pointer;
justify-content: center;
width: 0.18rem;
height: 0.18rem;
margin-left: 0.16rem;
flex-shrink: 0;
svg {
width: 0.18rem;
height: 0.18rem;
}
}
.login-view__flag {
font-size: 0.16rem;
line-height: 1;
}
.login-view__code-text {
/* 国家码标签 */
.login-view__country-label {
margin-left: 0.06rem;
font-weight: 500;
font-size: 0.15rem;
line-height: 0.18rem;
color: #132034;
font-size: 0.12rem;
line-height: 0.15rem;
color: #202124;
white-space: nowrap;
}
.login-view__code-arrow {
font-size: 0.11rem;
color: #94A3B8;
/* 下拉箭头 */
.login-view__country-chevron {
display: flex;
align-items: center;
justify-content: center;
width: 0.20rem;
height: 0.20rem;
flex-shrink: 0;
cursor: pointer;
svg {
width: 0.20rem;
height: 0.20rem;
}
}
.login-view__separator {
/* 分隔线 */
.login-view__input-divider {
width: 1px;
height: 100%;
background: #BFE8E2;
height: 0.30rem;
background: #E7F2F3;
margin: 0 0.16rem;
flex-shrink: 0;
}
/* 手机号输入框 */
.login-view__phone-input {
flex: 1;
height: 100%;
padding: 0 0.16rem;
background: #FAFBFC;
border: 1px solid #E2E8F0;
border-left: none;
border-radius: 0 0.12rem 0.12rem 0;
font-size: 0.15rem;
color: #132034;
border: none;
background: transparent;
font-weight: 400;
font-size: 0.12rem;
line-height: 0.15rem;
color: #202124;
outline: none;
&::placeholder {
color: #94A3B8;
color: #939599;
}
}
/* ==================== 发送验证码按钮(步骤一) ==================== */
.login-view__send-btn {
width: 100%;
height: 0.52rem;
background: linear-gradient(90deg, #12C7BE 0%, #06B6D4 100%);
border-radius: 0.12rem;
width: 3.41rem;
height: 0.40rem;
background: linear-gradient(90deg, #4FC2C9 0%, #42A8B3 100%);
box-shadow: 0 0.16rem 0.30rem rgba(15, 83, 87, 0.15);
border-radius: 0.08rem;
border: none;
font-weight: 600;
font-size: 0.16rem;
font-size: 0.12rem;
line-height: 0.15rem;
color: #FFFFFF;
cursor: pointer;
transition: opacity 0.2s;
@@ -361,8 +391,8 @@
display: flex;
justify-content: center;
align-items: center;
width: 0.56rem;
height: 0.64rem;
width: 0.42rem;
height: 0.46rem;
background: #FAFBFC;
border: 1px solid #E2E8F0;
border-radius: 0.12rem;
@@ -409,8 +439,8 @@
color: #64748B;
text-align: center;
padding-right: 0.1rem;
margin-top: -0.19rem;
margin-bottom: -0.10rem;
margin-top: -0.14rem;
margin-bottom: -0.06rem;
}
.login-view__sms-status--error {
@@ -420,12 +450,12 @@
/* ==================== 状态按钮(步骤二) ==================== */
.login-view__status-btn {
width: 100%;
height: 0.52rem;
height: 0.4rem;
background: linear-gradient(90deg, #12C7BE 0%, #06B6D4 100%);
border-radius: 0.12rem;
border: none;
font-weight: 600;
font-size: 0.16rem;
font-size: 0.14rem;
color: #FFFFFF;
cursor: pointer;
display: flex;
@@ -464,6 +494,7 @@
display: flex;
align-items: center;
gap: 0.06rem;
width: 3.41rem;
}
.login-view__checkbox {
@@ -472,17 +503,18 @@
height: 0.18rem;
background: #FFFFFF;
border: 1.5px solid #CBD5E1;
border-radius: 0.05rem;
border-radius: 0.04rem;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s;
flex-shrink: 0;
}
.login-view__checkbox--checked {
background: #12C7BE;
border-color: #12C7BE;
background: #4FC2C9;
border-color: #4FC2C9;
}
.login-view__check-icon {
@@ -496,14 +528,14 @@
font-weight: 400;
font-size: 0.12rem;
line-height: 0.15rem;
color: #94A3B8;
color: #616367;
}
.login-view__agreement-link {
font-weight: 500;
font-size: 0.12rem;
line-height: 0.15rem;
color: #12C7BE;
color: #42A8B3;
cursor: pointer;
&:hover {
@@ -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 {
width: 4.6rem;
min-height: 4rem;
width: 3.41rem;
}
/* 上传区域 */
.login-view__upload-area {
width: 100%;
padding: 0.6rem 0.4rem;
border: 1px dashed #E2E8F0;
padding: 0.4rem 0.4rem;
border: 2px solid #D6F1EF;
border-radius: 0.12rem;
background: #FFFFFF;
background: #F6FFFF;
display: flex;
flex-direction: column;
align-items: center;
@@ -657,10 +591,11 @@
}
.login-view__upload-btn {
padding: 0.14rem 0.4rem;
height: 0.4rem;
line-height: 0.4rem;
background: linear-gradient(90deg, #12C7BE 0%, #06B6D4 100%);
border: none;
border-radius: 0.26rem;
border-radius: 0.08rem;
font-size: 0.16rem;
font-weight: 600;
color: #FFFFFF;
@@ -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;
flex-direction: column;
justify-content: center;
}
.login-view__parse-step-item {
display: flex;
align-items: flex-start;
width: 100%;
min-height: 3rem;
position: relative;
}
.login-view__parsing-title {
font-size: 0.32rem;
.login-view__parse-step-left {
display: flex;
flex-direction: column;
align-items: center;
margin-right: 0.10rem;
flex-shrink: 0;
}
/* 步骤圆圈 — 完成(实心深色) */
.login-view__parse-dot {
width: 0.12rem;
height: 0.12rem;
border-radius: 50%;
flex-shrink: 0;
}
.login-view__parse-dot--done {
background: #4FC2C9;
}
/* 步骤圆圈 — 进行中(光晕 + 呼吸内圆) */
.login-view__parse-dot--active {
width: 0.16rem;
height: 0.16rem;
background: #E7FAF5;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-left: -0.02rem;
margin-right: -0.02rem;
}
.login-view__parse-dot-pulse {
width: 0.08rem;
height: 0.08rem;
background: #4FC2C9;
border-radius: 50%;
animation: parse-dot-breathe 1.2s ease-in-out infinite;
}
@keyframes parse-dot-breathe {
0%, 100% { transform: scale(0.7); opacity: 0.7; }
50% { transform: scale(1.2); opacity: 1; }
}
/* 步骤圆圈 — 等待(浅色) */
.login-view__parse-dot--pending {
background: #D6F1EF;
}
/* 连接线 */
.login-view__parse-connector {
width: 0.02rem;
height: 0.18rem;
background: #D6F1EF;
border-radius: 0.01rem;
}
/* 步骤文字 */
.login-view__parse-step-text {
flex: 1;
font-weight: 500;
font-size: 0.12rem;
line-height: 0.15rem;
color: #18181A;
padding-top: 0;
}
.login-view__parse-step-text--pending {
font-weight: 400;
color: #616367;
}
/* 步骤状态文字 */
.login-view__parse-step-state {
font-size: 0.12rem;
line-height: 0.15rem;
flex-shrink: 0;
margin-left: 0.10rem;
}
.login-view__parse-step-state--done {
font-weight: 500;
color: #42A8B3;
}
.login-view__parse-step-state--active {
font-weight: 500;
color: #42A8B3;
}
.login-view__parse-step-state--pending {
font-weight: 400;
color: #939599;
}
/* 解析中按钮(禁用样式) */
.login-view__parse-btn {
width: 3.41rem;
height: 0.40rem;
background: #A6EAE7;
border-radius: 0.08rem;
border: none;
font-weight: 600;
font-size: 0.12rem;
line-height: 0.15rem;
color: #FFFFFF;
cursor: not-allowed;
}
/* ==================== 状态4:上传成功面板 ==================== */
.login-view__success-panel {
box-sizing: border-box;
width: 3.41rem;
padding: 0.28rem 0.24rem;
background: #F6FFFB;
border: 1px solid #BCEED7;
border-radius: 0.08rem;
}
.login-view__success-header {
display: flex;
align-items: center;
gap: 0.14rem;
margin-bottom: 0.24rem;
}
.login-view__success-icon-bg {
width: 0.40rem;
height: 0.40rem;
background: #E7FAF0;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.login-view__success-icon-mark {
font-weight: 700;
color: #132034;
margin-bottom: 0.12rem;
font-size: 0.18rem;
line-height: 0.22rem;
color: #22B36A;
}
.login-view__parsing-subtitle {
font-size: 0.15rem;
color: #64748B;
.login-view__success-header-text {
display: flex;
flex-direction: column;
gap: 0.04rem;
}
/* 失败状态标题红色 */
.login-view__title--error {
color: #12C7BE;
.login-view__success-title {
font-weight: 500;
font-size: 0.13rem;
line-height: 0.16rem;
color: #18181A;
}
.login-view__success-detail {
font-weight: 400;
font-size: 0.12rem;
line-height: 0.15rem;
color: #616367;
}
.login-view__success-tips {
display: flex;
flex-direction: column;
gap: 0.16rem;
}
.login-view__success-tip-item {
display: flex;
align-items: center;
gap: 0.10rem;
}
.login-view__success-tip-dot {
width: 0.06rem;
height: 0.06rem;
background: #22B36A;
border-radius: 50%;
flex-shrink: 0;
}
.login-view__success-tip-text {
font-weight: 400;
font-size: 0.12rem;
line-height: 0.15rem;
color: #616367;
}
/* 进入后台按钮 */
.login-view__success-btn {
width: 3.41rem;
height: 0.40rem;
background: #4FC2C9;
border-radius: 0.08rem;
border: none;
font-weight: 600;
font-size: 0.12rem;
line-height: 0.15rem;
color: #FFFFFF;
cursor: pointer;
transition: opacity 0.2s;
&:hover {
opacity: 0.9;
}
}
/* ==================== 状态3:上传失败面板 ==================== */
.login-view__failed-panel {
box-sizing: border-box;
width: 3.41rem;
padding: 0.28rem 0.24rem;
background: #FFFBFA;
border: 1px solid #FADBD5;
border-radius: 0.08rem;
}
.login-view__failed-header {
display: flex;
align-items: center;
gap: 0.14rem;
margin-bottom: 0.24rem;
}
.login-view__failed-icon-bg {
width: 0.40rem;
height: 0.40rem;
background: #FFF0ED;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.login-view__failed-icon-mark {
font-weight: 700;
font-size: 0.18rem;
line-height: 0.22rem;
color: #FF6B4A;
}
.login-view__failed-header-text {
display: flex;
flex-direction: column;
gap: 0.04rem;
}
.login-view__failed-title {
font-weight: 500;
font-size: 0.13rem;
line-height: 0.16rem;
color: #18181A;
}
.login-view__failed-detail {
font-weight: 400;
font-size: 0.12rem;
line-height: 0.15rem;
color: #616367;
}
.login-view__failed-tips {
display: flex;
flex-direction: column;
gap: 0.16rem;
}
.login-view__failed-tip-item {
display: flex;
align-items: center;
gap: 0.10rem;
}
.login-view__failed-tip-dot {
width: 0.06rem;
height: 0.06rem;
background: #FF8D76;
border-radius: 50%;
flex-shrink: 0;
}
.login-view__failed-tip-text {
font-weight: 400;
font-size: 0.12rem;
line-height: 0.15rem;
color: #616367;
}
/* 重新上传按钮 */
.login-view__failed-btn {
width: 3.41rem;
height: 0.40rem;
background: linear-gradient(90deg, #4FC2C9 0%, #42A8B3 100%);
box-shadow: 0 0.16rem 0.30rem rgba(15, 83, 87, 0.15);
border-radius: 0.08rem;
border: none;
font-weight: 600;
font-size: 0.12rem;
line-height: 0.15rem;
color: #FFFFFF;
cursor: pointer;
transition: opacity 0.2s;
&:hover {
opacity: 0.9;
}
}
.login-view__right-footer{
margin-top: 0.37rem;
font-size: 0.13rem;
width: 4rem;
text-align: center;
color: #999;
padding-top: 0.3rem;
border-top: 1px solid #E7F2F3;
}
+35 -9
View File
@@ -354,6 +354,15 @@
gap: 0.06rem;
font-size: 0.12rem;
color: #666;
vertical-align: middle;
a{
color: #666;
vertical-align: super;
}
>span{
vertical-align: super;
}
}
&__contact-icon {
@@ -473,14 +482,7 @@
border-radius: 0.06rem;
}
// 经历条目内的编辑按钮hover exp-item 时显示
&__exp-edit-btn {
}
&__exp-item:hover &__exp-edit-btn {
opacity: 1;
pointer-events: auto;
}
// 经历条目内的编辑按钮样式已在 &__exp-date 下方统一管理
// ---- 教育背景 ----
&__edu-item {
@@ -552,9 +554,33 @@
}
&__exp-date {
font-size: 0.12rem;
font-size: 0.13rem;
font-weight: 400;
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>
<!-- 全屏加载遮罩 -->
<FullscreenLoading :visible="fullLoading" :text="fullLoadingText" title="Nova 正在帮您努力处理~" />
</template>
<script setup lang="ts">
@@ -360,6 +362,7 @@ import type { ResumeListItem } from '@/api/resume'
import { fetchCustomizeResume, generateCustomizeResume, aiEditResume, rollbackCustomizeResume } from '@/api/jobs'
import type { CustomizeResumeData, AiEditChatMessage } from '@/api/jobs'
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-预览 */
const currentStep = ref(1)
@@ -483,10 +491,8 @@ async function handleDrawerNext() {
* 流程 GET 查询 有数据直接用 无数据则 POST 生成 GET 查询
*/
async function fetchAndLoadCustomResume() {
const loadingInstance = ElLoading.service({
text: '正在生成定制简历...',
background: 'rgba(0, 0, 0, 0.5)',
})
fullLoadingText.value = '正在生成定制简历...'
fullLoading.value = true
try {
//
let queryRes = await fetchCustomizeResume(props.jobId)
@@ -522,7 +528,7 @@ async function fetchAndLoadCustomResume() {
console.error('[JobResumeCustomDialog] 定制简历流程失败', e)
ElMessage.error('定制简历失败,请稍后重试')
} finally {
loadingInstance.close()
fullLoading.value = false
}
}
+13 -8
View File
@@ -232,14 +232,17 @@
</div>
</div>
</div>
<!-- 全屏加载遮罩 -->
<FullscreenLoading :visible="fullLoading" :text="fullLoadingText" title="Nova 正在帮您努力处理~" />
</template>
<script setup lang="ts">
import { ref, computed, watch } from 'vue'
import { useStore } from 'vuex'
import { ElMessage, ElLoading } from 'element-plus'
import { ElMessage } from 'element-plus'
import RegionSelector from '@/components/tools/RegionSelector.vue'
import { polishDiagnosisIssue } from '@/api/resume'
import FullscreenLoading from '@/components/FullscreenLoading.vue'
/** 教育经历单条数据结构 */
interface EducationItem {
@@ -329,6 +332,12 @@ const emit = defineEmits<{
/** Vuex store 实例 */
const store = useStore()
// ==================== ====================
/** 全屏加载是否显示 */
const fullLoading = ref(false)
/** 全屏加载文案 */
const fullLoadingText = ref('')
/** 模块名称映射表 */
const moduleTitleMap: Record<string, string> = {
info: '基本信息',
@@ -490,12 +499,8 @@ async function handleAiPolish() {
return
}
aiPolishing.value = true
const loading = ElLoading.service({
lock: true,
text: 'AI正在润色中……',
background: 'rgba(0, 0, 0, 0.5)',
customClass: 'resume-upload-loading',
})
fullLoadingText.value = 'AI正在润色中……'
fullLoading.value = true
try {
const res = await polishDiagnosisIssue([text])
if (res.code === 0 && res.data?.content) {
@@ -508,7 +513,7 @@ async function handleAiPolish() {
ElMessage.error('请求失败,请稍后重试')
} finally {
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-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-date">{{ edu.startDate }} - {{ edu.endDate }}</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">
<el-icon><EditPen /></el-icon><span>编辑</span>
</button>
@@ -105,9 +106,9 @@
<div class="profile-page-content__item-left">
<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-period">{{ exp.startDate }} - {{ exp.endDate || '至今' }}</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">
<el-icon><EditPen /></el-icon><span>编辑</span>
</button>
@@ -139,9 +140,9 @@
<div class="profile-page-content__item-left">
<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-period">{{ exp.startDate }} - {{ exp.endDate }}</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">
<el-icon><EditPen /></el-icon><span>编辑</span>
</button>
@@ -173,9 +174,9 @@
<div class="profile-page-content__item-left">
<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-period">{{ proj.startDate }} - {{ proj.endDate }}</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">
<el-icon><EditPen /></el-icon><span>编辑</span>
</button>
@@ -206,9 +207,9 @@
<div class="profile-page-content__item-row">
<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-period">{{ comp.awardDate }}</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">
<el-icon><EditPen /></el-icon><span>编辑</span>
</button>
+15 -12
View File
@@ -53,6 +53,8 @@
</div>
</div>
</Teleport>
<!-- 全屏加载遮罩 -->
<FullscreenLoading :visible="fullLoading" :text="fullLoadingText" title="Nova 正在帮您努力处理~" />
</template>
<script setup lang="ts">
@@ -60,8 +62,8 @@ import { ref, watch } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { uploadResume } from '@/utils/aiRequest'
import { syncProfileFromResume } from '@/api/profile'
import { ElLoading, ElMessage } from 'element-plus'
import 'element-plus/es/components/loading/style/css'
import { ElMessage } from 'element-plus'
import FullscreenLoading from '@/components/FullscreenLoading.vue'
/** 组件 Props */
const props = defineProps<{ modelValue: boolean }>()
@@ -87,6 +89,11 @@ const uploading = ref(false)
/** 上传后获得的简历 ID */
const resumeId = ref('')
/** 全屏加载是否显示 */
const fullLoading = ref(false)
/** 全屏加载文案 */
const fullLoadingText = ref('')
/** 弹窗打开时重置状态 */
watch(() => props.modelValue, (val) => {
if (val) {
@@ -124,12 +131,8 @@ async function processFile(file: File) {
uploading.value = true
//
const loading = ElLoading.service({
lock: true,
text: '简历解析中,请耐心等待…',
background: 'rgba(0, 0, 0, 0.5)',
customClass: 'profile-welcome-loading',
})
fullLoadingText.value = '简历解析中,请耐心等待…'
fullLoading.value = true
try {
//
@@ -139,18 +142,18 @@ async function processFile(file: File) {
uploadedFileName.value = file.name
//
loading.setText('正在同步个人资料…')
fullLoadingText.value = '正在同步个人资料…'
await syncProfileFromResume(resumeId.value)
loading.close()
fullLoading.value = false
uploading.value = false
ElMessage.success('简历上传并同步成功')
} else {
loading.close()
fullLoading.value = false
uploading.value = false
ElMessage.error(res.msg || '上传失败')
}
} catch {
loading.close()
fullLoading.value = false
uploading.value = false
ElMessage.error('上传失败,请稍后重试')
}
+12 -3
View File
@@ -103,12 +103,15 @@
</div>
</div>
</div>
<!-- 全屏加载遮罩 -->
<FullscreenLoading :visible="fullLoading" :text="fullLoadingText" title="Nova 正在帮您努力处理~" />
</template>
<script setup lang="ts">
import { ref, watch } from 'vue'
import { ElMessage, ElLoading } from 'element-plus'
import { ElMessage } from 'element-plus'
import { polishDiagnosisIssue } from '@/api/resume'
import FullscreenLoading from '@/components/FullscreenLoading.vue'
/** 描述段落结构 */
interface DescriptionParagraph {
@@ -142,6 +145,11 @@ const aiPolishResult = ref('')
/** AI润色中 */
const aiPolishing = ref(false)
/** 全屏加载是否显示 */
const fullLoading = ref(false)
/** 全屏加载文案 */
const fullLoadingText = ref('')
/** 生成短ID */
const generateId = (): string => Math.random().toString(36).substring(2, 8)
@@ -177,7 +185,8 @@ async function handleAiPolish() {
return
}
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 {
const content = text.split('\n').filter(s => s.trim())
const res = await polishDiagnosisIssue(content)
@@ -191,7 +200,7 @@ async function handleAiPolish() {
ElMessage.error('请求失败')
} finally {
aiPolishing.value = false
loading.close()
fullLoading.value = false
}
}
+11 -7
View File
@@ -73,12 +73,15 @@
</div>
</Transition>
</Teleport>
<!-- 全屏加载遮罩 -->
<FullscreenLoading :visible="fullLoading" :text="fullLoadingText" title="Nova 正在帮您努力处理~" />
</template>
<script setup lang="ts">
import { ref, watch, nextTick } from 'vue'
import type { DiagnosisIssue, DescriptionParagraph } from '@/api/resume'
import { polishDiagnosisIssue, resolveDiagnosisIssue } from '@/api/resume'
import FullscreenLoading from '@/components/FullscreenLoading.vue'
// ==================== Props ====================
@@ -169,6 +172,11 @@ const moduleOrder = ['summary', 'education', 'work', 'internship', 'project', 'c
// ==================== ====================
/** 全屏加载是否显示 */
const fullLoading = ref(false)
/** 全屏加载文案 */
const fullLoadingText = ref('')
/** 所有模块分组后的数据列表 */
const moduleDataList = ref<ModuleGroup[]>([])
@@ -313,12 +321,8 @@ async function handleRegenerate(record: RecordState) {
return
}
const loading = ElLoading.service({
lock: true,
text: 'AI 重新生成中…',
background: 'rgba(0, 0, 0, 0.5)',
customClass: 'resume-upload-loading',
})
fullLoadingText.value = 'AI 重新生成中…'
fullLoading.value = true
try {
const res = await polishDiagnosisIssue(content)
@@ -332,7 +336,7 @@ async function handleRegenerate(record: RecordState) {
} catch {
ElMessage.error('请求失败,请稍后重试')
} finally {
loading.close()
fullLoading.value = false
}
}
+15 -14
View File
@@ -138,6 +138,8 @@
</div>
</Transition>
</Teleport>
<!-- 全屏加载遮罩 -->
<FullscreenLoading :visible="fullLoading" :text="fullLoadingText" title="Nova 正在帮您努力处理~" />
</template>
<script setup lang="ts">
@@ -145,6 +147,7 @@ import { computed, ref, watch, nextTick } from 'vue'
import type { DiagnosisIssue, DescriptionParagraph } from '@/api/resume'
import { polishDiagnosisIssue, feedbackDiagnosisIssue, resolveDiagnosisIssue } from '@/api/resume'
import { computeDiff, type DiffSegment } from '@/utils/textDiff'
import FullscreenLoading from '@/components/FullscreenLoading.vue'
// ==================== Props ====================
@@ -182,6 +185,12 @@ const moduleTitleMap: Record<string, string> = {
/** 抽屉标题 */
const moduleTitle = computed(() => moduleTitleMap[props.moduleType] || '问题修复')
// ==================== ====================
/** 全屏加载是否显示 */
const fullLoading = ref(false)
/** 全屏加载文案 */
const fullLoadingText = ref('')
// ==================== ====================
/** 将原文内容统一转为字符串(description 数组的 text 逐行拼接) */
@@ -282,12 +291,8 @@ async function handleAiPolish() {
}
//
const loading = ElLoading.service({
lock: true,
text: 'AI 润色中,请耐心等待…',
background: 'rgba(0, 0, 0, 0.5)',
customClass: 'resume-upload-loading',
})
fullLoadingText.value = 'AI 润色中,请耐心等待…'
fullLoading.value = true
try {
const res = await polishDiagnosisIssue(content)
@@ -300,7 +305,7 @@ async function handleAiPolish() {
} catch {
ElMessage.error('润色请求失败,请稍后重试')
} finally {
loading.close()
fullLoading.value = false
}
}
@@ -373,12 +378,8 @@ async function handleSubmit() {
}
//
const loading = ElLoading.service({
lock: true,
text: '正在提交新版本…',
background: 'rgba(0, 0, 0, 0.5)',
customClass: 'resume-upload-loading',
})
fullLoadingText.value = '正在提交新版本…'
fullLoading.value = true
try {
// 1. resolve
@@ -394,7 +395,7 @@ async function handleSubmit() {
} catch {
ElMessage.error('提交失败,请稍后重试')
} finally {
loading.close()
fullLoading.value = false
}
}
+61 -60
View File
@@ -3,105 +3,106 @@
<Teleport to="body">
<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__header">
<!-- 皇冠图标 -->
<span class="settings-invite-dialog__crown"></span>
</div>
<!-- 关闭按钮 -->
<span class="settings-invite-dialog__close" @click="$emit('update:modelValue', false)"></span>
<!-- 标题 -->
<h2 class="settings-invite-dialog__title">邀请好友免费获取7天会员权益</h2>
<!-- 内容区域 -->
<div class="settings-invite-dialog__body">
<!-- 标题 -->
<h2 class="settings-invite-dialog__title">邀请好友免费获取 7 天会员</h2>
<!-- 副标题 -->
<p class="settings-invite-dialog__subtitle">好友通过你的链接注册并完成认证 3 天会员</p>
<!-- 邀请链接区域 -->
<div class="settings-invite-dialog__link-box">
<p class="settings-invite-dialog__link-text">来一起领取AI求职助手会员{{ inviteText }}点击链接进入活动</p>
</div>
<!-- 复制链接按钮 -->
<button class="settings-invite-dialog__copy-btn" @click="handleCopy">复制链接</button>
<!-- 步骤说明 -->
<p class="settings-invite-dialog__steps-tip">仅需 3 步可免费获取会员权益</p>
<div class="settings-invite-dialog__step">
<div class="settings-invite-dialog__step-title">第1步分享专属邀请链接</div>
<p class="settings-invite-dialog__step-desc">点击"复制链接"并发送给好友</p>
</div>
<div class="settings-invite-dialog__step">
<div class="settings-invite-dialog__step-title">第2步好友完成新用户首次体验</div>
<p class="settings-invite-dialog__step-desc">链接 24 小时内有效好友在时效内体验AI求职助手</p>
</div>
<div class="settings-invite-dialog__step">
<div class="settings-invite-dialog__step-title">第3步分享人获得7天会员权益</div>
<p class="settings-invite-dialog__step-desc">免费会员权益一年内有效AI助力加速上岸</p>
</div>
<!-- 统计数据 -->
<div class="settings-invite-dialog__stats">
<div class="settings-invite-dialog__stat-item">
<div class="settings-invite-dialog__stat-label">累计获得会员天数</div>
<div class="settings-invite-dialog__stat-value">{{ totalDays }}</div>
<!-- 邀请链接区域 -->
<div class="settings-invite-dialog__link-box">
<span class="settings-invite-dialog__link-text">{{ inviteUrl }}</span>
<button class="settings-invite-dialog__copy-btn" @click="handleCopy">复制链接</button>
</div>
<div class="settings-invite-dialog__stat-item">
<div class="settings-invite-dialog__stat-label">累计邀请好友</div>
<div class="settings-invite-dialog__stat-value">{{ totalFriends }}</div>
</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 class="settings-invite-dialog__step">
<div class="settings-invite-dialog__step-circle">1</div>
<span class="settings-invite-dialog__step-text">分享专属邀请链接给好友</span>
</div>
<div class="settings-invite-dialog__step">
<div class="settings-invite-dialog__step-circle">2</div>
<span class="settings-invite-dialog__step-text">好友完成注册并通过认证</span>
</div>
<div class="settings-invite-dialog__step">
<div class="settings-invite-dialog__step-circle">3</div>
<span class="settings-invite-dialog__step-text">获得 7 天会员奖励</span>
</div>
<!-- 底部统计 + 按钮 -->
<div class="settings-invite-dialog__footer">
<div class="settings-invite-dialog__stat">
<span class="settings-invite-dialog__stat-label">已邀请</span>
<span class="settings-invite-dialog__stat-value">{{ totalFriends }}</span>
</div>
<div class="settings-invite-dialog__stat">
<span class="settings-invite-dialog__stat-label">累计获得</span>
<span class="settings-invite-dialog__stat-value">{{ totalDays }}</span>
</div>
<button class="settings-invite-dialog__member-btn" @click="handleGoMember">成为正式会员</button>
</div>
</div>
</div>
</div>
</Teleport>
<!-- 会员购买弹窗 -->
<MemberDialog v-model="showMemberDialog" />
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import { useStore } from 'vuex'
import { ElMessage } from 'element-plus'
import MemberDialog from './MemberDialog.vue'
/** 组件 Props */
defineProps<{ modelValue: boolean }>()
/** 组件 Emits */
defineEmits<{ (e: 'update:modelValue', value: boolean): void }>()
defineEmits<{
(e: 'update:modelValue', value: boolean): void
}>()
const store = useStore()
/** 活动规则展开状态 */
const showRules = ref(false)
/** 会员弹窗显示状态 */
const showMemberDialog = ref(false)
/** 累计获得会员天数(暂用模拟数据) */
const totalDays = ref(1)
const totalDays = ref(7)
/** 累计邀请好友数(暂用模拟数据) */
const totalFriends = ref(1)
/** 用户邀请码 — 从全局 store 读取 */
const inviteCode = computed(() => store.state.userInfo?.inviteCode || '')
const inviteCode = computed(() => store.state.userInfo?.inviteCode || 'NOVA2026')
/** 邀请链接文案 */
const inviteText = computed(() => {
const code = inviteCode.value
return `https://www.offerpai.com.cn?invite_code=${code}`
})
/** 邀请链接地址 */
const inviteUrl = computed(() => `https://offerpai.com/invite?code=${inviteCode.value}`)
/** 复制链接到剪贴板 */
async function handleCopy() {
try {
await navigator.clipboard.writeText(inviteText.value)
await navigator.clipboard.writeText(inviteUrl.value)
ElMessage.success('链接已复制')
} catch {
ElMessage.error('复制失败,请手动复制')
}
}
/** 打开会员购买弹窗 */
function handleGoMember() {
showMemberDialog.value = true
}
</script>
+12 -5
View File
@@ -281,6 +281,8 @@
<MemberAccessDialog v-model="showMemberAccessDialog" @open-member="showMemberDialog = true" />
<!-- 会员购买弹窗 -->
<MemberDialog v-model="showMemberDialog" />
<!-- 全屏加载遮罩 -->
<FullscreenLoading :visible="fullLoading" :text="fullLoadingText" title="Nova 正在帮您努力处理~" />
</div>
</template>
@@ -296,6 +298,7 @@ import JobDislikeDialog from '@/components/JobDislikeDialog.vue'
import JobFeedbackDialog from '@/components/JobFeedbackDialog.vue'
import JobResumeCustomDialog from '@/components/JobResumeCustomDialog.vue'
import MemberAccessDialog from '@/components/MemberAccessDialog.vue'
import FullscreenLoading from '@/components/FullscreenLoading.vue'
import MemberDialog from '@/components/MemberDialog.vue'
import { fetchJobDetail, toggleJobFavorite, removeJobFavorite, fetchSkillGap } from '@/api/jobs'
import { fetchMemberStatus } from '@/api/member'
@@ -307,6 +310,12 @@ const router = useRouter()
const route = useRoute()
const store = useStore()
// ==================== ====================
/** 全屏加载是否显示 */
const fullLoading = ref(false)
/** 全屏加载文案 */
const fullLoadingText = ref('')
/** 当前岗位 ID,从路由参数中获取 */
const jobId = route.params.id as string
@@ -595,10 +604,8 @@ async function handleGenerateResume() {
//
}
const loadingInstance = ElLoading.service({
text: '正在分析岗位匹配度...',
background: 'rgba(0, 0, 0, 0.5)',
})
fullLoadingText.value = '正在分析岗位匹配度...'
fullLoading.value = true
try {
const res = await fetchSkillGap(jobId)
if (res.code === 0 && res.data) {
@@ -609,7 +616,7 @@ async function handleGenerateResume() {
ElMessage.error('分析失败,请稍后重试')
return
} finally {
loadingInstance.close()
fullLoading.value = false
}
showResumeCustomDialog.value = true
}
+285 -117
View File
@@ -2,32 +2,32 @@
<!-- 登录页面 左右分栏布局包含登录和简历上传流程 -->
<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">
<div class="ai-hub-stage" ref="stageRef">
<!-- 标题 -->
<div class="hero-title" aria-hidden="true">
AI 驱动的-
<span class="hero-title__second-line">一站式求职辅助平台</span>
</div>
<!-- Nova 机器人核心眼睛跟踪鼠标 -->
<div class="nova-core" ref="novaCoreRef" aria-hidden="true">
<img class="nova-core-base" :src="novaCoreSrc" alt="" draggable="false" />
<img class="nova-eye nova-eye-left" ref="novaEyeLeftRef" :src="novaEyeLeftSrc" alt="" draggable="false" />
<img class="nova-eye nova-eye-right" ref="novaEyeRightRef" :src="novaEyeRightSrc" alt="" draggable="false" />
</div>
<!-- ==================== 左侧视觉动画面板所有阶段共用 ==================== -->
<section class="login-visual" ref="loginVisualRef" aria-label="OfferPai AI job platform">
<div class="ai-hub-stage" ref="stageRef">
<!-- 标题 -->
<div class="hero-title" aria-hidden="true">
AI 驱动的-
<span class="hero-title__second-line">一站式求职辅助平台</span>
</div>
<!-- 底部 logo -->
<img class="login-jianshi-logo" :src="jianshiLogoSrc" alt="" aria-hidden="true" draggable="false" />
</section>
<!-- Nova 机器人核心眼睛跟踪鼠标 -->
<div class="nova-core" ref="novaCoreRef" aria-hidden="true">
<img class="nova-core-base" :src="novaCoreSrc" alt="" draggable="false" />
<img class="nova-eye nova-eye-left" ref="novaEyeLeftRef" :src="novaEyeLeftSrc" alt="" draggable="false" />
<img class="nova-eye nova-eye-right" ref="novaEyeRightRef" :src="novaEyeRightSrc" alt="" draggable="false" />
</div>
</div>
<!-- 底部 logo -->
<img class="login-jianshi-logo" :src="jianshiLogoSrc" alt="" aria-hidden="true" draggable="false" />
</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-box login-view__logo-box--green">O</div>
<span class="login-view__logo-text login-view__logo-text--dark">Offer派</span>
<img src="@/assets/images/logo.png" alt="">
</div>
<div class="login-view__form-wrap">
@@ -37,15 +37,29 @@
<h2 class="login-view__title">手机号登录/注册</h2>
<p class="login-view__subtitle">未注册用户验证后将自动注册并登录</p>
</div>
<!-- 手机号输入组 -->
<div class="login-view__phone-row">
<div class="login-view__country-code">
<span class="login-view__flag">🇨🇳</span>
<span class="login-view__code-text">+86</span>
<span class="login-view__code-arrow"></span>
<!-- 手机图标 -->
<div class="login-view__phone-icon">
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="4.5" y="2" width="9" height="14" rx="1.5" stroke="#0F6B66" stroke-width="1.5"/>
<line x1="9" y1="2" x2="9" y2="3.5" stroke="#0F6B66" stroke-width="1.5" stroke-linecap="round"/>
</svg>
</div>
<div class="login-view__separator"></div>
<!-- 国家码标签 -->
<span class="login-view__country-label">中国 +86</span>
<!-- 下拉箭头 -->
<div class="login-view__country-chevron">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6 8L10 12L14 8" stroke="#6F8586" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</div>
<!-- 分隔线 -->
<div class="login-view__input-divider"></div>
<!-- 手机号输入框 -->
<input v-model="phone" type="tel" class="login-view__phone-input" placeholder="请输入手机号码" maxlength="11" />
</div>
<!-- 发送验证码按钮 -->
<button class="login-view__send-btn" :disabled="!canSend" @click="handleSendCode">发送验证码</button>
</template>
@@ -53,7 +67,7 @@
<template v-if="step === 2">
<div class="login-view__heading">
<h2 class="login-view__title">请输入验证码</h2>
<p class="login-view__subtitle">短信验证码已发送至 +86{{ phone }} <span class="fw600 ml5 cursor-po" @click="handleChangePhone">修改手机号</span></p>
<p class="login-view__subtitle dflex"><span class="dib">短信验证码已发送至 +86{{ phone }} </span><span class="fw600 ml5 dib cursor-po mlauto" @click="handleChangePhone">修改手机号</span></p>
</div>
<div class="login-view__otp-wrap" @click="focusOtpInput">
<input ref="otpInputRef" v-model="otpValue" type="text" inputmode="numeric" class="login-view__otp-hidden-input" maxlength="6" :disabled="isLoggingIn" @input="handleOtpInput" />
@@ -73,7 +87,7 @@
<!-- 协议勾选步骤一二通用 -->
<div class="login-view__agreement">
<div class="login-view__checkbox" :class="{ 'login-view__checkbox--checked': agreedTerms }" @click="agreedTerms = !agreedTerms">
<span v-if="agreedTerms" class="login-view__check-icon"></span>
<span v-if="agreedTerms" class="login-view__check-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" width="0.13rem" height="0.13rem"><path stroke="currentColor" fill="currentColor" d="M77.248 415.04a64 64 0 0 1 90.496 0l226.304 226.304L846.528 188.8a64 64 0 1 1 90.56 90.496l-543.04 543.04-316.8-316.8a64 64 0 0 1 0-90.496"></path></svg></span>
</div>
<span class="login-view__agreement-text">登录即表示同意</span>
<span class="login-view__agreement-link" @click="openAgreement('ae8065i3')">用户协议</span>
@@ -81,49 +95,30 @@
<span class="login-view__agreement-link" @click="openAgreement('hf8375i8')">隐私政策</span>
</div>
</div>
</div>
</template>
</template>
<!-- ==================== 简历上传阶段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">
<!-- ===== 简历上传阶段step 3===== -->
<template v-if="step === 3">
<div class="login-view__form-wrap login-view__form-wrap--resume">
<!-- 状态1上传区域 -->
<!-- 状态1上传区域 upload-->
<template v-if="resumeState === 'upload'">
<div class="login-view__heading">
<h2 class="login-view__title">一切美好从简历开始~</h2>
<p class="login-view__subtitle">自动解析您的简历为你匹配最合适的岗位</p>
<h2 class="login-view__title">上传简历开启求职之旅</h2>
<p class="login-view__subtitle">解析后为你匹配岗位也会用于后续简历优化</p>
</div>
<!-- 拖拽上传区域 -->
<div class="login-view__upload-area" @dragover.prevent @drop.prevent="handleDrop" @click="triggerFileInput">
<div class="login-view__upload-circle"></div>
<div class="login-view__upload-circle">
<svg width="52" height="56" viewBox="0 0 52 56" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="0.5" y="0.5" width="51" height="55" rx="7.5" fill="#EAF8F7" stroke="#D6F1EF"/>
<rect x="36" width="16" height="16" rx="3" fill="#D6F1EF"/>
<rect x="13" y="21" width="26" height="4" rx="2" fill="#4FC2C9"/>
<rect x="13" y="33" width="30" height="4" rx="2" fill="#BFEAE8"/>
<rect x="13" y="45" width="20" height="4" rx="2" fill="#BFEAE8"/>
</svg>
</div>
<p class="login-view__upload-text">拖拽简历到这里或点击上传</p>
<p class="login-view__upload-hint">支持 PDF / DOC / DOCX不超过 20MB</p>
</div>
@@ -133,26 +128,123 @@
<button class="login-view__upload-btn" @click="triggerFileInput">+ 上传简历</button>
</template>
<!-- 状态2解析中 -->
<!-- 状态2解析中 parsing -->
<template v-if="resumeState === 'parsing'">
<div class="login-view__parsing">
<h2 class="login-view__parsing-title">{{ parsingText }}</h2>
<p class="login-view__parsing-subtitle">自动解析您的简历为你匹配最合适的岗位</p>
<div class="login-view__heading m0auto">
<h2 class="login-view__title">正在解析简历</h2>
<p class="login-view__subtitle">正在识别教育背景工作经历与技能关键词</p>
</div>
<!-- 解析面板 -->
<div class="login-view__parse-panel m0auto">
<!-- 顶部进度条 -->
<div class="login-view__parse-progress-header">
<span class="login-view__parse-progress-label">解析进度</span>
<span class="login-view__parse-progress-percent">{{ parsePercent }}%</span>
</div>
<div class="login-view__parse-progress-rail">
<div class="login-view__parse-progress-fill" :style="{ width: parsePercent + '%' }"></div>
</div>
<!-- 步骤列表 -->
<div class="login-view__parse-steps">
<div v-for="(ps, idx) in parseStepList" :key="idx" class="login-view__parse-step-item">
<!-- 步骤圆圈 + 连接线 -->
<div class="login-view__parse-step-left">
<!-- 完成状态实心深色圆 -->
<div v-if="ps.status === 'done'" class="login-view__parse-dot login-view__parse-dot--done"></div>
<!-- 进行中浅色光晕 + 呼吸动画深色内圆 -->
<div v-else-if="ps.status === 'active'" class="login-view__parse-dot login-view__parse-dot--active">
<div class="login-view__parse-dot-pulse"></div>
</div>
<!-- 等待状态浅色圆 -->
<div v-else class="login-view__parse-dot login-view__parse-dot--pending"></div>
<!-- 连接线最后一项不显示 -->
<div v-if="idx < parseStepList.length - 1" class="login-view__parse-connector"></div>
</div>
<!-- 步骤文字 -->
<span class="login-view__parse-step-text" :class="{ 'login-view__parse-step-text--pending': ps.status === 'pending' }">{{ ps.label }}</span>
<!-- 步骤状态文字 -->
<span class="login-view__parse-step-state" :class="{ 'login-view__parse-step-state--done': ps.status === 'done', 'login-view__parse-step-state--active': ps.status === 'active', 'login-view__parse-step-state--pending': ps.status === 'pending' }">{{ ps.stateText }}</span>
</div>
</div>
</div>
<!-- 解析中按钮禁用状态 -->
<button class="login-view__parse-btn m0auto" disabled>解析中请稍候</button>
</template>
<!-- 状态3上传失败 -->
<template v-if="resumeState === 'failed'">
<div class="login-view__heading">
<h2 class="login-view__title login-view__title--error">上传失败</h2>
<p class="login-view__subtitle">您的简历解析失败了请重试一次吧~</p>
<!-- 状态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>
<button class="login-view__upload-btn" @click="handleRetryUpload">重新上传</button>
<!-- 成功面板 -->
<div class="login-view__success-panel m0auto">
<!-- 成功图标和标题 -->
<div class="login-view__success-header">
<div class="login-view__success-icon-bg">
<span class="login-view__success-icon-mark"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" width="0.15rem" height="0.15rem"><path stroke="currentColor" fill="currentColor" d="M77.248 415.04a64 64 0 0 1 90.496 0l226.304 226.304L846.528 188.8a64 64 0 1 1 90.56 90.496l-543.04 543.04-316.8-316.8a64 64 0 0 1 0-90.496"></path></svg></span>
</div>
<div class="login-view__success-header-text">
<span class="login-view__success-title">简历解析完成</span>
<span class="login-view__success-detail">已识别关键信息岗位匹配已准备好</span>
</div>
</div>
<!-- 成功提示项 -->
<div class="login-view__success-tips">
<div class="login-view__success-tip-item">
<span class="login-view__success-tip-dot"></span>
<span class="login-view__success-tip-text">已提取教育背景工作经历与技能</span>
</div>
<div class="login-view__success-tip-item">
<span class="login-view__success-tip-dot"></span>
<span class="login-view__success-tip-text">可进入后台查看推荐岗位并继续投递</span>
</div>
</div>
</div>
<!-- 进入后台按钮 -->
<button class="login-view__success-btn m0auto" @click="handleEnterDashboard">上传成功进入后台</button>
</template>
<!-- 状态3上传失败 failed-->
<template v-if="resumeState === 'failed'">
<div class="login-view__heading m0auto">
<h2 class="login-view__title">简历上传失败</h2>
<p class="login-view__subtitle">文件没有解析成功请检查格式后重新上传 <span class="color-accent cursor-po" @click="handleEnterDashboard">跳过稍后填写</span></p>
</div>
<!-- 失败面板 -->
<div class="login-view__failed-panel">
<!-- 失败图标和标题 -->
<div class="login-view__failed-header">
<div class="login-view__failed-icon-bg">
<span class="login-view__failed-icon-mark">!</span>
</div>
<div class="login-view__failed-header-text">
<span class="login-view__failed-title">未能读取文件内容</span>
<span class="login-view__failed-detail">可能是文件加密损坏或格式不支持</span>
</div>
</div>
<!-- 失败提示项 -->
<div class="login-view__failed-tips">
<div class="login-view__failed-tip-item">
<span class="login-view__failed-tip-dot"></span>
<span class="login-view__failed-tip-text">请上传 PDF / DOC / DOCX 格式</span>
</div>
<div class="login-view__failed-tip-item">
<span class="login-view__failed-tip-dot"></span>
<span class="login-view__failed-tip-text">文件大小不超过 20MB且未加密</span>
</div>
</div>
</div>
<!-- 重新上传按钮 -->
<button class="login-view__failed-btn" @click="handleRetryUpload">重新上传</button>
</template>
</div>
</template>
<div class="login-view__right-footer">
Offer派 求职工作台
</div>
</template>
</div>
<!-- 协议预览弹窗 -->
<AgreementPreviewDialog v-model="showAgreementDialog" :code="agreementCode" />
@@ -278,31 +370,37 @@ let loginRedirect = '/'
const canSend = computed(() => phone.value.length === 11 && agreedTerms.value)
// ==================== ====================
/** 简历上传子状态:upload=上传区域 parsing=解析中 failed=失败 */
const resumeState = ref<'upload' | 'parsing' | 'failed'>('upload')
/** 解析中显示的文字 */
const parsingText = ref('正在解析个人资料...')
/** 简历上传子状态:upload=上传区域 parsing=解析中 failed=失败 success=成功 */
const resumeState = ref<'upload' | 'parsing' | 'failed' | 'success'>('upload')
/** 解析文字轮换定时器 */
let parsingTimer: ReturnType<typeof setInterval> | null = null
/** 文件选择器引用 */
const fileInputRef = ref<HTMLInputElement | null>(null)
/** 左侧职位卡片硬编码数据 */
const jobCards = [
{ 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;' },
{ 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;' },
{ 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 parsePercent = ref(0)
/** 进度条动画定时器 */
let progressTimer: ReturnType<typeof setInterval> | null = null
/** 接口是否已返回成功 */
let uploadDone = false
/** 解析中文字轮换列表 */
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'
}
/** 进入后台(状态4按钮) */
function handleEnterDashboard() {
router.replace(loginRedirect)
}
/** 开始上传并解析简历 */
async function startUpload(file: File) {
//
@@ -483,8 +586,16 @@ async function startUpload(file: File) {
return
}
//
//
resumeState.value = 'parsing'
uploadDone = false
parsePercent.value = 0
parseStepList.value = [
{ label: '教育背景解析...', status: 'pending', stateText: '等待' },
{ label: '工作经历解析...', status: 'pending', stateText: '等待' },
{ label: '技能特长解析...', status: 'pending', stateText: '等待' },
{ label: '个人资料最终确认', status: 'pending', stateText: '等待' }
]
startParsingAnimation()
try {
@@ -492,13 +603,10 @@ async function startUpload(file: File) {
if (res.code === 0 && res.data?.resumeId) {
//
await syncProfileFromResume(String(res.data.resumeId))
//
stopParsingAnimation()
parsingText.value = '简历解析成功,即将跳转'
// 1
setTimeout(() => {
router.replace(loginRedirect)
}, 1000)
//
uploadDone = true
// success
finishParsingAnimation()
} else {
//
stopParsingAnimation()
@@ -510,25 +618,84 @@ async function startUpload(file: File) {
}
}
/** 开始解析文字轮换动画 */
/** 开始解析步骤动画(模拟4步进度) */
function startParsingAnimation() {
let index = 0
parsingText.value = parsingSteps[0]
parsingTimer = setInterval(() => {
index++
if (index < parsingSteps.length - 1) {
// 42
parsingText.value = parsingSteps[index]
} else {
// 4
parsingText.value = parsingSteps[parsingSteps.length - 2]
if (parsingTimer) { clearInterval(parsingTimer); parsingTimer = null }
let currentStep = 0
const totalSteps = parseStepList.value.length
/** 每个步骤对应的进度百分比段 */
const stepPercents = [25, 50, 75, 95]
//
parseStepList.value[0].status = 'active'
parseStepList.value[0].stateText = '进行中'
//
let targetPercent = stepPercents[0]
const startTime = Date.now()
let lastStepTime = startTime
progressTimer = setInterval(() => {
const elapsed = Date.now() - lastStepTime
const stepDuration = stepDurations[currentStep] || 2000
if (elapsed >= stepDuration && currentStep < totalSteps - 1) {
//
parseStepList.value[currentStep].status = 'done'
parseStepList.value[currentStep].stateText = '完成'
currentStep++
//
parseStepList.value[currentStep].status = 'active'
parseStepList.value[currentStep].stateText = '进行中'
targetPercent = stepPercents[currentStep]
lastStepTime = Date.now()
} else if (currentStep >= totalSteps - 1 && elapsed >= stepDuration) {
//
if (uploadDone) {
//
parseStepList.value[currentStep].status = 'done'
parseStepList.value[currentStep].stateText = '完成'
if (progressTimer) { clearInterval(progressTimer); progressTimer = null }
// 95-99100
const finalPercent = Math.floor(95 + Math.random() * 4)
parsePercent.value = finalPercent
setTimeout(() => {
parsePercent.value = 100
setTimeout(() => { resumeState.value = 'success' }, 600)
}, 1000)
return
}
//
parsePercent.value = Math.min(parsePercent.value + 0.1, 94)
return
}
}, 2000)
//
const progress = Math.min(elapsed / stepDuration, 1)
const prevPercent = currentStep > 0 ? stepPercents[currentStep - 1] : 0
parsePercent.value = Math.floor(prevPercent + (targetPercent - prevPercent) * progress)
}, 50)
}
/** 停止解析文字轮换动画 */
/** 接口返回后完成动画 */
function finishParsingAnimation() {
// uploadDone progressTimer
//
if (!progressTimer) {
const lastIdx = parseStepList.value.length - 1
parseStepList.value[lastIdx].status = 'done'
parseStepList.value[lastIdx].stateText = '完成'
const finalPercent = Math.floor(95 + Math.random() * 4)
parsePercent.value = finalPercent
setTimeout(() => {
parsePercent.value = 100
setTimeout(() => { resumeState.value = 'success' }, 600)
}, 1000)
}
}
/** 停止解析动画(失败时调用) */
function stopParsingAnimation() {
if (progressTimer) { clearInterval(progressTimer); progressTimer = null }
if (parsingTimer) { clearInterval(parsingTimer); parsingTimer = null }
}
@@ -536,6 +703,7 @@ function stopParsingAnimation() {
onBeforeUnmount(() => {
if (countdownTimer) { clearInterval(countdownTimer) }
if (parsingTimer) { clearInterval(parsingTimer) }
if (progressTimer) { clearInterval(progressTimer) }
if (waveTimer) { clearTimeout(waveTimer); waveTimer = null }
if (blinkTimer) { clearTimeout(blinkTimer); blinkTimer = null }
if (idleLookTimer) { clearTimeout(idleLookTimer); idleLookTimer = null }
+16 -12
View File
@@ -37,6 +37,8 @@
</div>
</div>
</div>
<!-- 全屏加载遮罩 -->
<FullscreenLoading :visible="fullLoading" :text="fullLoadingText" title="Nova 正在帮您努力处理~" />
</template>
<script setup lang="ts">
@@ -49,13 +51,19 @@ import ProfilePageContent from '@/components/ProfilePageContent.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 { uploadResume } from '@/utils/aiRequest'
import { ElMessage, ElLoading, ElMessageBox } from 'element-plus'
import 'element-plus/es/components/loading/style/css'
import { ElMessage, ElMessageBox } from 'element-plus'
import FullscreenLoading from '@/components/FullscreenLoading.vue'
import type { SaveEducationItem, SaveWorkItem, SaveProjectItem, SaveCompetitionItem } from '@/api/profile'
const router = useRouter()
const store = useStore()
// ==================== ====================
/** 全屏加载是否显示 */
const fullLoading = ref(false)
/** 全屏加载文案 */
const fullLoadingText = ref('')
/** 页面挂载时加载公共分类数据和个人资料 */
onMounted(async () => {
if (!store.state.regions.length) {
@@ -208,29 +216,25 @@ const showWelcomeDialog = ref(false)
/** 欢迎弹窗确认上传回调 — 上传简历并同步个人资料,完成后刷新页面 */
async function handleWelcomeUpload(file: File) {
const loading = ElLoading.service({
lock: true,
text: '简历解析中,请耐心等待…',
background: 'rgba(0, 0, 0, 0.5)',
customClass: 'profile-welcome-loading',
})
fullLoadingText.value = '简历解析中,请耐心等待…'
fullLoading.value = true
try {
const res = await uploadResume(file)
if (res.code === 0 && res.data?.resumeId) {
//
loading.setText('正在同步个人资料…')
fullLoadingText.value = '正在同步个人资料…'
await syncProfileFromResume(String(res.data.resumeId))
loading.close()
fullLoading.value = false
ElMessage.success('简历上传并同步成功')
//
router.go(0)
} else {
loading.close()
fullLoading.value = false
ElMessage.error(res.msg || '上传失败')
}
} catch {
loading.close()
fullLoading.value = false
ElMessage.error('上传失败,请稍后重试')
}
}
+15 -11
View File
@@ -98,6 +98,8 @@
v-model="uploadDialogVisible"
@confirm="handleUploadConfirm"
/>
<!-- 全屏加载遮罩 -->
<FullscreenLoading :visible="fullLoading" :text="fullLoadingText" title="Nova 正在帮您努力处理~" />
</div>
</template>
@@ -113,13 +115,19 @@ import { uploadResume } from '@/utils/aiRequest'
import {
fetchResumeList, deleteResume, setDefaultResume, type ResumeListItem,
} 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 FullscreenLoading from '@/components/FullscreenLoading.vue'
const router = useRouter()
// ==================== ====================
/** 全屏加载是否显示 */
const fullLoading = ref(false)
/** 全屏加载文案 */
const fullLoadingText = ref('')
// ==================== ====================
/** 头像背景色列表,按索引循环取色 */
@@ -297,12 +305,8 @@ function handleUpload() {
/** 上传弹窗确认回调 — 接收 File 对象,调用接口上传 */
async function handleUploadConfirm(file: File) {
// AI
const loading = ElLoading.service({
lock: true,
text: '简历解析中,请耐心等待…',
background: 'rgba(0, 0, 0, 0.5)',
customClass: 'resume-upload-loading',
})
fullLoadingText.value = '简历解析中,请耐心等待…'
fullLoading.value = true
try {
const res = await uploadResume(file)
@@ -311,14 +315,14 @@ async function handleUploadConfirm(file: File) {
loadResumeList()
//
await new Promise(resolve => setTimeout(resolve, 2000))
loading.close()
fullLoading.value = false
goDetail(res.data.resumeId)
} else {
loading.close()
fullLoading.value = false
ElMessage.error(res.msg || '上传失败')
}
} catch {
loading.close()
fullLoading.value = false
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"/>
<path d="M2 5.5l6 4 6-4" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/>
</svg>
{{ resumeMain.email }}
<span>{{ resumeMain.email }}</span>
</span>
<!-- 手机号 -->
<span v-if="resumeMain.mobileNumber" class="resume-detail__contact-item">
<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"/>
</svg>
{{ resumeMain.mobileNumber }}
<span>{{ resumeMain.mobileNumber }}</span>
</span>
<!-- 微信号 -->
<span v-if="resumeMain.wechatNumber" class="resume-detail__contact-item">
<el-icon class="fs14 color-9"><ChatLineRound /></el-icon>
{{ resumeMain.wechatNumber }}
<span>{{ resumeMain.wechatNumber }}</span>
</span>
<!-- 作品集链接 -->
<span v-if="resumeMain.portfolioUrl" class="resume-detail__contact-item">
<svg viewBox="0 0 16 16" fill="none" class="resume-detail__contact-icon">
<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="M7 6l2 2-2 2" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<span v-if="resumeMain.portfolioUrl" class="resume-detail__contact-item dib">
<svg xmlns="http://www.w3.org/2000/svg" class=" mr6" width="0.15rem" height="0.15rem" viewBox="0 0 16 16">
<path d="M0 0h16v16H0z" fill="none" />
<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>
<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>
</div>
</template>
@@ -253,13 +255,14 @@
<div class="resume-detail__exp-row">
<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.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">
<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;
<span class="resume-detail__fix-tooltip">{{ getIssueByRecord('education', edu.id!)!.finding }}</span>
</span>
<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时显示 -->
<button class="resume-detail__edit-btn-inline resume-detail__exp-edit-btn" @click="startEditExp('education', edu)" aria-label="编辑">
<el-icon><EditPen /></el-icon>
@@ -302,13 +305,14 @@
<div class="resume-detail__exp-row">
<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.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">
<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;
<span class="resume-detail__fix-tooltip">{{ getIssueByRecord('work', exp.id!)!.finding }}</span>
</span>
<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="编辑">
<el-icon><EditPen /></el-icon><span>编辑</span>
</button>
@@ -339,13 +343,14 @@
<div class="resume-detail__exp-row">
<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.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">
<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;
<span class="resume-detail__fix-tooltip">{{ getIssueByRecord('internship', intern.id!)!.finding }}</span>
</span>
<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="编辑">
<el-icon><EditPen /></el-icon><span>编辑</span>
</button>
@@ -376,13 +381,14 @@
<div class="resume-detail__exp-row">
<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.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">
<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;
<span class="resume-detail__fix-tooltip">{{ getIssueByRecord('project', proj.id!)!.finding }}</span>
</span>
<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="编辑">
<el-icon><EditPen /></el-icon><span>编辑</span>
</button>
@@ -413,13 +419,14 @@
<div class="resume-detail__exp-row">
<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.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">
<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;
<span class="resume-detail__fix-tooltip">{{ getIssueByRecord('competition', comp.id!)!.finding }}</span>
</span>
<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="编辑">
<el-icon><EditPen /></el-icon><span>编辑</span>
</button>
@@ -500,6 +507,8 @@
<MemberAccessDialog v-model="showMemberAccessDialog" @open-member="showMemberDialog = true" />
<!-- 会员购买弹窗 -->
<MemberDialog v-model="showMemberDialog" />
<!-- 全屏加载遮罩 -->
<FullscreenLoading :visible="fullLoading" :text="fullLoadingText" title="Nova 正在帮您努力处理~" />
</div>
</template>
@@ -557,11 +566,18 @@ import {
type DescriptionParagraph,
} from '@/api/resume'
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 ====================
const store = useStore()
// ==================== ====================
/** 全屏加载是否显示 */
const fullLoading = ref(false)
/** 全屏加载文案 */
const fullLoadingText = ref('')
// ==================== ====================
/** 会员权限拦截弹窗 */
@@ -936,12 +952,8 @@ async function handleDiagnose() {
}
// AI
const loading = ElLoading.service({
lock: true,
text: '简历诊断中,请耐心等待…',
background: 'rgba(0, 0, 0, 0.5)',
customClass: 'resume-upload-loading',
})
fullLoadingText.value = '简历诊断中,请耐心等待…'
fullLoading.value = true
try {
const res = await triggerResumeDiagnosis(resumeId)
if (res.code === 0) {
@@ -954,7 +966,7 @@ async function handleDiagnose() {
} catch {
ElMessage.error('诊断请求失败,请稍后重试')
} finally {
loading.close()
fullLoading.value = false
}
}
@@ -1098,12 +1110,8 @@ function joinDescriptionText(desc: DescriptionParagraph[]): string {
async function handleFixAllSave(items: FixAllSubmitItem[]) {
if (!items.length) return
const loading = ElLoading.service({
lock: true,
text: '正在保存修改…',
background: 'rgba(0, 0, 0, 0.5)',
customClass: 'resume-upload-loading',
})
fullLoadingText.value = '正在保存修改…'
fullLoading.value = true
try {
for (const item of items) {
@@ -1123,7 +1131,7 @@ async function handleFixAllSave(items: FixAllSubmitItem[]) {
} catch {
ElMessage.error('保存失败,请稍后重试')
} finally {
loading.close()
fullLoading.value = false
}
}
@@ -1131,12 +1139,8 @@ async function handleFixAllSave(items: FixAllSubmitItem[]) {
async function handleFixAllUseAi(items: FixAllSubmitItem[]) {
if (!items.length) return
const loading = ElLoading.service({
lock: true,
text: '正在应用AI润色结果…',
background: 'rgba(0, 0, 0, 0.5)',
customClass: 'resume-upload-loading',
})
fullLoadingText.value = '正在应用AI润色结果…'
fullLoading.value = true
try {
for (const item of items) {
@@ -1152,7 +1156,7 @@ async function handleFixAllUseAi(items: FixAllSubmitItem[]) {
} catch {
ElMessage.error('操作失败,请稍后重试')
} finally {
loading.close()
fullLoading.value = false
}
}
@@ -1215,12 +1219,8 @@ async function updateModuleContent(item: FixAllSubmitItem) {
/** 修复抽屉提交新版本 — 更新本地数据、调用保存接口、刷新页面 */
async function handleFixSubmit(content: string[]) {
const loading = ElLoading.service({
lock: true,
text: '正在保存修改…',
background: 'rgba(0, 0, 0, 0.5)',
customClass: 'resume-upload-loading',
})
fullLoadingText.value = '正在保存修改…'
fullLoading.value = true
try {
const moduleType = fixModuleType.value
@@ -1262,7 +1262,7 @@ async function handleFixSubmit(content: string[]) {
} catch {
ElMessage.error('保存失败,请稍后重试')
} finally {
loading.close()
fullLoading.value = false
}
}