From c94a96d14e8a6fd4088fbbdb0ba95d17619b1874 Mon Sep 17 00:00:00 2001 From: xuxin <15279969124@163.com> Date: Wed, 8 Jul 2026 18:58:27 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=AA=E4=BA=BA=E8=B5=84=E6=96=99=EF=BC=8C?= =?UTF-8?q?=E7=AE=80=E5=8E=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components.d.ts | 1 + src/api/member.ts | 24 ++ src/api/resume.ts | 15 +- src/assets/styles/auto.scss | 27 ++ .../components/profile-page-content.scss | 19 +- src/assets/styles/components/side-nav.scss | 9 +- .../components/step-progress-overlay.scss | 241 ++++++++++++++++++ src/assets/styles/index.scss | 1 + src/assets/styles/pages/resume-detail.scss | 115 ++++++--- src/assets/styles/pages/resume.scss | 66 ++++- src/components/JobResumeCustomDialog.vue | 70 ++++- src/components/MemberDialog.vue | 33 +-- src/components/ProfilePageContent.vue | 6 +- src/components/SettingsInviteDialog.vue | 30 ++- src/components/SideNav.vue | 38 ++- src/components/StepProgressOverlay.vue | 229 +++++++++++++++++ src/views/JobDetail.vue | 54 +++- src/views/Resume.vue | 127 +++++++-- src/views/ResumeDetail.vue | 220 ++++++++++++---- 19 files changed, 1142 insertions(+), 183 deletions(-) create mode 100644 src/assets/styles/components/step-progress-overlay.scss create mode 100644 src/components/StepProgressOverlay.vue diff --git a/components.d.ts b/components.d.ts index 916a4a5..4ccc360 100644 --- a/components.d.ts +++ b/components.d.ts @@ -69,6 +69,7 @@ declare module 'vue' { SettingsDialog: typeof import('./src/components/SettingsDialog.vue')['default'] SettingsInviteDialog: typeof import('./src/components/SettingsInviteDialog.vue')['default'] SideNav: typeof import('./src/components/SideNav.vue')['default'] + StepProgressOverlay: typeof import('./src/components/StepProgressOverlay.vue')['default'] } export interface GlobalDirectives { vLoading: typeof import('element-plus/es')['ElLoadingDirective'] diff --git a/src/api/member.ts b/src/api/member.ts index 95305af..4c46812 100644 --- a/src/api/member.ts +++ b/src/api/member.ts @@ -117,3 +117,27 @@ export function fetchOrderDetail(orderId: string) { params: { orderId }, }) } + +// ==================== 邀请统计相关 ==================== + +/** 用户邀请统计DTO */ +export interface InviteStatsDto { + /** 累计邀请人数 */ + inviteCount?: number + /** 当前周期已邀请人数 */ + milestoneProgress?: number + /** 累计获得会员天数 */ + rewardDays?: number + /** 里程碑目标人数 */ + milestoneTarget?: number + /** 里程碑奖励天数 */ + milestoneDays?: number +} + +/** + * 获取用户邀请统计 + * GET /user/manage/inviteStats + */ +export function fetchInviteStats() { + return request.get>('/user/manage/inviteStats') +} diff --git a/src/api/resume.ts b/src/api/resume.ts index 9031f06..73f6bd7 100644 --- a/src/api/resume.ts +++ b/src/api/resume.ts @@ -32,9 +32,12 @@ export interface ResumeListItem { /** * 获取简历列表 * GET /resume/list + * @param pageSize 每页条数,默认 50 */ -export function fetchResumeList() { - return request.get>('/resume/list') +export function fetchResumeList(pageSize = 50) { + return request.get>('/resume/list', { + params: { pageSize }, + }) } // ==================== 描述段落(通用) ==================== @@ -735,3 +738,11 @@ export interface UpdateResumeInfoParams { export function updateResumeInfo(data: UpdateResumeInfoParams) { return request.post('/resume/updateInfo', data) } + +/** + * 根据当前用户个人资料创建一份新简历 + * POST /user/profile/createResume + */ +export function createResumeFromProfile() { + return request.post('/user/profile/createResume') +} diff --git a/src/assets/styles/auto.scss b/src/assets/styles/auto.scss index e2492a6..cef455a 100644 --- a/src/assets/styles/auto.scss +++ b/src/assets/styles/auto.scss @@ -584,6 +584,27 @@ body:not(#_) { .color-accent{ color:$accent; } +.color-dark{ + color:$text-dark; +} +.color-middle{ + color:$text-middle; +} +.color-light{ + color:$text-light; +} +.color-accent-ho:hover{ + color:$accent; +} +.color-dark-ho:hover{ + color:$text-dark; +} +.color-middle-ho:hover{ + color:$text-middle; +} +.color-light-ho:hover{ + color:$text-light; +} /*border*/ @@ -623,6 +644,9 @@ body:not(#_) { .border-ra19{border-radius: 0.19rem;} .border-ra20{border-radius: 0.2rem;} +.border-ra50p{ + border-radius: 50%; +} .bor-ra50p{ border-radius: 50%; @@ -791,6 +815,9 @@ body:not(#_) { .mb0 {margin-bottom: 0rem;} .mb1 {margin-bottom: 0.01rem;} .mb2 {margin-bottom: 0.02rem;} .mb3 {margin-bottom: 0.03rem;} .mb4 {margin-bottom: 0.04rem;} .mb5 {margin-bottom: 0.05rem;} .mb6 {margin-bottom: 0.06rem;} .mb7 {margin-bottom: 0.07rem;} .mb8 {margin-bottom: 0.08rem;} .mb9 {margin-bottom: 0.09rem;} .mb10 {margin-bottom: 0.1rem;} .mb11 {margin-bottom: 0.11rem;} .mb12 {margin-bottom: 0.12rem;} .mb13 {margin-bottom: 0.13rem;} .mb14 {margin-bottom: 0.14rem;} .mb15 {margin-bottom: 0.15rem;} .mb16 {margin-bottom: 0.16rem;} .mb17 {margin-bottom: 0.17rem;} .mb18 {margin-bottom: 0.18rem;} .mb19 {margin-bottom: 0.19rem;} .mb20 {margin-bottom: 0.2rem;} .mb21 {margin-bottom: 0.21rem;} .mb22 {margin-bottom: 0.22rem;} .mb23 {margin-bottom: 0.23rem;} .mb24 {margin-bottom: 0.24rem;} .mb25 {margin-bottom: 0.25rem;} .mb26 {margin-bottom: 0.26rem;} .mb27 {margin-bottom: 0.27rem;} .mb28 {margin-bottom: 0.28rem;} .mb29 {margin-bottom: 0.29rem;} .mb30 {margin-bottom: 0.3rem;} .mb31 {margin-bottom: 0.31rem;} .mb32 {margin-bottom: 0.32rem;} .mb33 {margin-bottom: 0.33rem;} .mb34 {margin-bottom: 0.34rem;} .mb35 {margin-bottom: 0.35rem;} .mb36 {margin-bottom: 0.36rem;} .mb37 {margin-bottom: 0.37rem;} .mb38 {margin-bottom: 0.38rem;} .mb39 {margin-bottom: 0.39rem;} .mb40 {margin-bottom: 0.4rem;} .mb41 {margin-bottom: 0.41rem;} .mb42 {margin-bottom: 0.42rem;} .mb43 {margin-bottom: 0.43rem;} .mb44 {margin-bottom: 0.44rem;} .mb45 {margin-bottom: 0.45rem;} .mb46 {margin-bottom: 0.46rem;} .mb47 {margin-bottom: 0.47rem;} .mb48 {margin-bottom: 0.48rem;} .mb49 {margin-bottom: 0.49rem;} .mb50 {margin-bottom: 0.5rem;} .mb51 {margin-bottom: 0.51rem;} .mb52 {margin-bottom: 0.52rem;} .mb53 {margin-bottom: 0.53rem;} .mb54 {margin-bottom: 0.54rem;} .mb55 {margin-bottom: 0.55rem;} .mb56 {margin-bottom: 0.56rem;} .mb57 {margin-bottom: 0.57rem;} .mb58 {margin-bottom: 0.58rem;} .mb59 {margin-bottom: 0.59rem;} .mb60 {margin-bottom: 0.6rem;} .mb61 {margin-bottom: 0.61rem;} .mb62 {margin-bottom: 0.62rem;} .mb63 {margin-bottom: 0.63rem;} .mb64 {margin-bottom: 0.64rem;} .mb65 {margin-bottom: 0.65rem;} .mb66 {margin-bottom: 0.66rem;} .mb67 {margin-bottom: 0.67rem;} .mb68 {margin-bottom: 0.68rem;} .mb69 {margin-bottom: 0.69rem;} .mb70 {margin-bottom: 0.7rem;} .mb71 {margin-bottom: 0.71rem;} .mb72 {margin-bottom: 0.72rem;} .mb73 {margin-bottom: 0.73rem;} .mb74 {margin-bottom: 0.74rem;} .mb75 {margin-bottom: 0.75rem;} .mb76 {margin-bottom: 0.76rem;} .mb77 {margin-bottom: 0.77rem;} .mb78 {margin-bottom: 0.78rem;} .mb79 {margin-bottom: 0.79rem;} .mb80 {margin-bottom: 0.8rem;} .mb81 {margin-bottom: 0.81rem;} .mb82 {margin-bottom: 0.82rem;} .mb83 {margin-bottom: 0.83rem;} .mb84 {margin-bottom: 0.84rem;} .mb85 {margin-bottom: 0.85rem;} .mb86 {margin-bottom: 0.86rem;} .mb87 {margin-bottom: 0.87rem;} .mb88 {margin-bottom: 0.88rem;} .mb89 {margin-bottom: 0.89rem;} .mb90 {margin-bottom: 0.9rem;} .mb91 {margin-bottom: 0.91rem;} .mb92 {margin-bottom: 0.92rem;} .mb93 {margin-bottom: 0.93rem;} .mb94 {margin-bottom: 0.94rem;} .mb95 {margin-bottom: 0.95rem;} .mb96 {margin-bottom: 0.96rem;} .mb97 {margin-bottom: 0.97rem;} .mb98 {margin-bottom: 0.98rem;} .mb99 {margin-bottom: 0.99rem;} .mb100 {margin-bottom: 1rem;} +.mb-20{ + margin-bottom: -0.2rem; +} /* margin-top-bottom */ .mtb0 {margin-top: 0rem;margin-bottom: 0rem;} diff --git a/src/assets/styles/components/profile-page-content.scss b/src/assets/styles/components/profile-page-content.scss index deb8957..dc4ef42 100644 --- a/src/assets/styles/components/profile-page-content.scss +++ b/src/assets/styles/components/profile-page-content.scss @@ -6,7 +6,7 @@ display: flex; flex-direction: column; gap: 0; - padding: 0.2rem 0.24rem 0; + padding-bottom: 0.2rem ; background: $bg-white; border-radius: 0.12rem; font-size: 0.14rem; @@ -18,16 +18,15 @@ align-items: center; gap: 0.24rem; padding-bottom: 0.14rem; - border-bottom: 1px solid $border-color; flex-shrink: 0; margin-bottom:0.14rem } &__tab-item { - font-size: 0.18rem; + font-size: 0.14rem; color: $text-middle; cursor: pointer; - padding-bottom: 0.06rem; + padding: 0.16rem 0.2rem; border-bottom: 2px solid transparent; transition: all 0.2s; white-space: nowrap; @@ -37,9 +36,9 @@ } &--active { - color: $text-dark; + color: $accent; font-weight: 600; - border-bottom-color: $text-dark; + border-bottom-color: $accent; } } @@ -47,7 +46,7 @@ &__body { flex: 1; overflow-y: auto; - padding-bottom: 0.2rem; + padding: 0 0.24rem 0.2rem 0.24rem; // 隐藏滚动条 &::-webkit-scrollbar { @@ -64,7 +63,7 @@ border-radius: 0.08rem; &:hover .profile-page-content__card-header{ - background: #F7F7F7; + background: #F0FAFA; .profile-page-content__edit-btn-inline{ opacity: 1; pointer-events: auto; @@ -301,7 +300,7 @@ } &__card-hover:hover { - background: #F7F7F7; + background: #F0FAFA; } // 标题行hover显示添加按钮(5大经历) @@ -319,7 +318,7 @@ border-radius: 0.08rem; &:hover { - background: #F7F7F7; + background: #F0FAFA; } &:not(:last-child) { diff --git a/src/assets/styles/components/side-nav.scss b/src/assets/styles/components/side-nav.scss index fa5c7d9..874bde0 100644 --- a/src/assets/styles/components/side-nav.scss +++ b/src/assets/styles/components/side-nav.scss @@ -562,11 +562,18 @@ align-items: center; justify-content: space-between; width: 100%; - background: #F3F4F6; + background: #fff; border-radius: 0.08rem; padding: 0.12rem 0.16rem; box-sizing: border-box; margin-top: 0.12rem; + cursor: pointer; + + &:hover, + &:active, + &--active { + background: #F3F4F6; + } } .side-nav__user-left { diff --git a/src/assets/styles/components/step-progress-overlay.scss b/src/assets/styles/components/step-progress-overlay.scss new file mode 100644 index 0000000..4f3d1de --- /dev/null +++ b/src/assets/styles/components/step-progress-overlay.scss @@ -0,0 +1,241 @@ +@use '../variables' as *; + +/* 步骤进度加载遮罩组件 */ +.step-progress-overlay { + font-size: 0.14rem; + position: fixed; + inset: 0; + z-index: 2000; + display: flex; + align-items: center; + justify-content: center; +} + +/* 半透明遮罩 */ +.step-progress-overlay__mask { + position: absolute; + inset: 0; + background: $overlay-bg; +} + +/* 内容面板 */ +.step-progress-overlay__panel { + position: relative; + box-sizing: border-box; + display: flex; + flex-direction: column; + align-items: flex-start; + padding: 0.24rem 0.32rem; + gap: 0.24rem; + width: 4.80rem; + background: linear-gradient(180deg, #EDF9FA 0%, #FFFFFF 20.63%); + border: 1px solid #F0F0F0; + box-shadow: 0 0 0.16rem rgba(0, 0, 0, 0.05); + border-radius: 0.12rem; +} + +/* 顶部标题行 */ +.step-progress-overlay__header { + display: flex; + justify-content: space-between; + align-items: flex-start; + width: 100%; +} + +.step-progress-overlay__titles { + display: flex; + flex-direction: column; + gap: 0.04rem; +} + +.step-progress-overlay__title { + font-weight: 520; + font-size: 0.18rem; + line-height: 0.24rem; + color: $text-dark; + margin: 0; +} + +.step-progress-overlay__subtitle { + font-weight: 330; + font-size: 0.12rem; + line-height: 0.16rem; + color: $text-middle; + margin: 0; +} + +/* 关闭按钮 */ +.step-progress-overlay__close { + display: flex; + align-items: center; + justify-content: center; + width: 0.24rem; + height: 0.24rem; + border: none; + background: transparent; + cursor: pointer; + flex-shrink: 0; + border-radius: 0.04rem; + transition: background 0.2s; + + &:hover { + background: #F0F0F0; + } +} + +/* 进度面板(带边框的内框) */ +.step-progress-overlay__progress-box { + box-sizing: border-box; + display: flex; + flex-direction: column; + justify-content: center; + padding: 0.16rem; + gap: 0.1rem; + width: 100%; + border: 1px solid #AEE6EA; + border-radius: 0.08rem; +} + +/* 进度条头部 */ +.step-progress-overlay__progress-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 0.08rem; +} + +.step-progress-overlay__progress-label { + font-weight: 450; + font-size: 0.14rem; + line-height: 0.19rem; + color: #616367; +} + +.step-progress-overlay__progress-percent { + font-weight: 450; + font-size: 0.14rem; + line-height: 0.19rem; + color: $accent; +} + +/* 进度轨道 */ +.step-progress-overlay__progress-rail { + width: 100%; + height: 0.06rem; + background: #EDF9FA; + border-radius: 0.03rem; + overflow: hidden; + margin-bottom: 0.20rem; +} + +.step-progress-overlay__progress-fill { + height: 100%; + background: $accent; + border-radius: 0.03rem; + transition: width 0.3s ease; +} + +/* 步骤列表 */ +.step-progress-overlay__steps { + display: flex; + flex-direction: column; +} + +.step-progress-overlay__step-item { + display: flex; + align-items: flex-start; + position: relative; +} + +.step-progress-overlay__step-left { + display: flex; + flex-direction: column; + align-items: center; + margin-right: 0.08rem; + flex-shrink: 0; +} + +/* 步骤圆点 — 通用 */ +.step-progress-overlay__dot { + width: 0.16rem; + height: 0.16rem; + border-radius: 50%; + flex-shrink: 0; + display: flex; + align-items: center; + justify-content: center; +} + +/* 完成状态:品牌色实心 + 对勾 */ +.step-progress-overlay__dot--done { + background: $accent; +} + +/* 进行中:浅色光晕 + 呼吸内圆 */ +.step-progress-overlay__dot--active { + background: #EDF9FA; +} + +.step-progress-overlay__dot-pulse { + width: 0.08rem; + height: 0.08rem; + background: $accent; + border-radius: 50%; + animation: step-overlay-breathe 1.2s ease-in-out infinite; +} + +@keyframes step-overlay-breathe { + 0%, 100% { transform: scale(0.7); opacity: 0.7; } + 50% { transform: scale(1.2); opacity: 1; } +} + +/* 等待状态:浅色圆 */ +.step-progress-overlay__dot--pending { + background: #EDF9FA; +} + +/* 连接线 */ +.step-progress-overlay__connector { + width: 0.02rem; + height: 0.22rem; + background: #EDF9FA; + border-radius: 0.01rem; +} + +/* 步骤文字 */ +.step-progress-overlay__step-text { + flex: 1; + font-weight: 380; + font-size: 0.12rem; + line-height: 0.16rem; + color: #18181A; +} + +.step-progress-overlay__step-text--pending { + font-weight: 330; + color: #616367; +} + +/* 步骤状态文字 */ +.step-progress-overlay__step-state { + font-size: 0.12rem; + line-height: 0.16rem; + flex-shrink: 0; + margin-left: 0.10rem; + text-align: right; +} + +.step-progress-overlay__step-state--done { + font-weight: 450; + color: $accent; +} + +.step-progress-overlay__step-state--active { + font-weight: 450; + color: $accent; +} + +.step-progress-overlay__step-state--pending { + font-weight: 330; + color: #939599; +} diff --git a/src/assets/styles/index.scss b/src/assets/styles/index.scss index 1160912..9d2d3d9 100644 --- a/src/assets/styles/index.scss +++ b/src/assets/styles/index.scss @@ -42,6 +42,7 @@ @use './components/resume-upload-dialog.scss'; @use './components/agreement-preview-dialog.scss'; @use './components/member-access-dialog.scss'; +@use './components/step-progress-overlay.scss'; // 全局样式(优先级最高) @use './auto.scss'; diff --git a/src/assets/styles/pages/resume-detail.scss b/src/assets/styles/pages/resume-detail.scss index 70438fe..b1c1939 100644 --- a/src/assets/styles/pages/resume-detail.scss +++ b/src/assets/styles/pages/resume-detail.scss @@ -8,7 +8,7 @@ padding-bottom: 0.24rem; height: var(--app-height, 100vh); box-sizing: border-box; - overflow-y: auto; + overflow: hidden; background: $bg-main; display: flex; flex-direction: column; @@ -17,8 +17,9 @@ &__page-title { - margin: 0 0 0.16rem 0; - padding: 0 0.24rem; + padding: 0.19rem 0.24rem; + margin-bottom: 0.16rem; + background: #fff; >h2{ font-size: 0.24rem; font-weight: 700; @@ -116,7 +117,7 @@ background: $bg-white; border-radius: 0.12rem; padding: 0.18rem 0.24rem; - padding-top: 0.4rem; + padding-top: 0.3rem; margin-bottom: 0.16rem; /* B/C/D 评级渐变背景 */ @@ -133,17 +134,12 @@ &__score-left { display: flex; align-items: center; - gap: 0.2rem; + gap: 0.16rem; } /* 星星+底座容器 */ &__star-wrapper { - position: relative; - display: flex; - flex-direction: column; - align-items: center; - width: 0.7rem; - flex-shrink: 0; + } /* SVG 五角星 */ @@ -156,44 +152,40 @@ /* 底座长方形 */ &__star-base { - position: relative; - z-index: 2; + margin-top: -0.08rem; - width: 0.52rem; - height: 0.22rem; - border: 1.5px solid #858585; - border-radius: 0.03rem; - background: #fff; - display: flex; - align-items: center; justify-content: center; + font-size: 0.14rem; > span { - font-size: 0.12rem; + font-size: 0.14rem; font-weight: 600; } } /* 右侧操作按钮区 */ &__score-actions { - display: flex; - align-items: center; - gap: 0.16rem; + } /* 暂无等级提示文字 */ &__score-hint { - font-size: 0.13rem; - color: $text-light; + font-size: 0.14rem; + color: $text-middle; + font-weight: 600; } + &__score-links{ + height: 0.14rem; + } &__score-link { - font-size: 0.13rem; + font-size: 0.12rem; color: $text-light; background: none; border: none; cursor: pointer; transition: color 0.2s; + margin-right: 0.16rem; &:hover { color: $accent; @@ -217,7 +209,7 @@ font-size: 0.30rem; font-weight: 700; color: $text-dark; - line-height: 1.2; + padding-bottom: 0.1rem; } &__score-label { @@ -231,8 +223,8 @@ margin-left: 0.16rem; color: $bg-white; border: none; - border-radius: 0.18rem; - width: 0.93rem; + border-radius: 0.08rem; + width: 1.03rem; height: 0.36rem; line-height: 0.37rem; font-size: 0.13rem; @@ -240,7 +232,8 @@ cursor: pointer; box-sizing: border-box; //transition: background 0.2s; - white-space: nowrap; + + &:hover { background: $btn-dark-hover; @@ -255,6 +248,58 @@ border-radius: 0.16rem; background: #fff; margin: 0 0.24rem; + flex: 1; + min-height: 0; + display: flex; + flex-direction: column; + overflow: hidden; + } + + // ---- 固定顶部区域(评分+Tab,不滚动) ---- + &__fixed-top { + flex-shrink: 0; + } + + // ---- Tab 导航栏 ---- + &__tabs { + display: flex; + align-items: center; + padding-bottom: 0.14rem; + flex-shrink: 0; + } + + &__tab-item { + font-size: 0.14rem; + color: $text-middle; + cursor: pointer; + padding: 0.12rem 0.2rem; + border-bottom: 2px solid transparent; + transition: all 0.2s; + white-space: nowrap; + + &:hover { + color: $text-dark; + } + + &--active { + color: $accent; + font-weight: 600; + border-bottom-color: $accent; + } + } + + // ---- 可滚动内容区域 ---- + &__scroll-body { + flex: 1; + overflow-y: auto; + padding-bottom: 0.3rem; + + // 隐藏滚动条 + &::-webkit-scrollbar { + display: none; + } + -ms-overflow-style: none; + scrollbar-width: none; } &__card { @@ -263,7 +308,7 @@ margin: 0.4rem 0.18rem; &:hover .resume-detail__section-header { - background: #F7F7F7; + background: #F0FAFA; .resume-detail__edit-btn-inline { opacity: 1; pointer-events: auto; @@ -275,7 +320,7 @@ &__card-hover { padding: 0.1rem 0.14rem; &:hover { - background: #F7F7F7; + background: #F0FAFA; } @@ -290,7 +335,7 @@ } &__user-name { - font-size: 0.2rem; + font-size: 0.28rem; font-weight: 700; color: $text-dark; margin: 0 0 0.04rem 0; @@ -513,7 +558,7 @@ padding: 0.1rem 0.14rem; border-radius: 0.09rem; &:hover { - background: #F7F7F7; + background: #F0FAFA; } diff --git a/src/assets/styles/pages/resume.scss b/src/assets/styles/pages/resume.scss index a8ad1fe..1b3e475 100644 --- a/src/assets/styles/pages/resume.scss +++ b/src/assets/styles/pages/resume.scss @@ -5,7 +5,6 @@ &__content { margin-left: 2rem; flex: 1; - padding: 0.12rem 0.36rem; height: var(--app-height, 100vh); box-sizing: border-box; overflow: hidden; @@ -19,7 +18,8 @@ display: flex; align-items: center; justify-content: space-between; - margin-bottom: 0.2rem; + background: #fff; + padding: 0.19rem 0.24rem; } &__title { @@ -36,8 +36,8 @@ background: $btn-dark; color: $bg-white; border: none; - border-radius: 0.2rem; - padding: 0.08rem 0.2rem; + border-radius: 0.08rem; + padding: 0.08rem 0.14rem; font-size: 0.13rem; font-weight: 500; cursor: pointer; @@ -48,20 +48,50 @@ } } + // 从个人资料创建按钮(描边样式) + &__create-btn { + display: flex; + align-items: center; + gap: 0.06rem; + background: $bg-white; + color: $accent; + border: 1px solid $accent; + border-radius: 0.08rem; + padding: 0.08rem 0.14rem; + font-size: 0.13rem; + font-weight: 500; + cursor: pointer; + transition: all 0.2s; + + &:hover { + background: rgba($accent, 0.06); + } + + &:disabled { + opacity: 0.6; + cursor: not-allowed; + } + } + &__upload-icon { width: 0.14rem; height: 0.14rem; } - // 表格容器 + // 表格容器(超出时可滚动) &__table-wrap { background: $bg-white; border-radius: 0.12rem; + margin: 0.18rem 0.17rem; + overflow-y: auto; + flex: 0 1 auto; + min-height: 0; } &__table { width: 100%; border-collapse: collapse; + table-layout: fixed; } &__th { @@ -72,9 +102,14 @@ color: $text-dark; border-bottom: 1px solid $border-color; white-space: nowrap; + width: 15%; + position: sticky; + top: 0; + background: $bg-white; + z-index: 1; &--action { - width: 2rem; + width: 25%; } } @@ -100,9 +135,15 @@ &--action { position: relative; text-align: center; + opacity: 0; + transition: opacity 0.15s; } } + &__row:hover &__td--action { + opacity: 1; + } + // 简历名称单元格 &__name-cell { display: flex; @@ -130,9 +171,9 @@ &__default-tag { font-size: 0.09rem; color: $accent; - background: #D9F5F0; - border-radius: 0.14rem; - padding: 0.01rem 0.06rem; + background: #EDF9FA; + border-radius: 0.04rem; + padding: 0.014rem 0.08rem; padding-bottom: 0.02rem; white-space: nowrap; } @@ -164,6 +205,7 @@ align-items: center; gap: 0.16rem; justify-content: flex-end; + flex-wrap: wrap; } // 行内操作按钮 @@ -171,15 +213,17 @@ background: none; border: none; font-size: 0.12rem; - color: $text-dark; + color: $text-middle; cursor: pointer; padding: 0.04rem 0.08rem; border-radius: 0.04rem; white-space: nowrap; transition: all 0.15s; + display: flex; + align-items: center; &:hover { - color: $accent; + color: $text-dark; } // 删除按钮红色 diff --git a/src/components/JobResumeCustomDialog.vue b/src/components/JobResumeCustomDialog.vue index 0256813..7467566 100644 --- a/src/components/JobResumeCustomDialog.vue +++ b/src/components/JobResumeCustomDialog.vue @@ -345,6 +345,17 @@ + + diff --git a/src/views/JobDetail.vue b/src/views/JobDetail.vue index 2ead372..463824b 100644 --- a/src/views/JobDetail.vue +++ b/src/views/JobDetail.vue @@ -171,6 +171,18 @@ + + + @@ -186,6 +198,7 @@ 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 StepProgressOverlay from '@/components/StepProgressOverlay.vue' import { fetchJobDetail, toggleJobFavorite, removeJobFavorite, fetchSkillGap } from '@/api/jobs' import { fetchMemberStatus } from '@/api/member' import type { JobDetailData, SkillGapData } from '@/api/jobs' @@ -475,23 +488,54 @@ async function handleGenerateResume() { // 接口异常时不阻断,继续执行 } - fullLoadingText.value = '正在分析岗位匹配度...' - fullLoading.value = true + // 显示步骤进度遮罩 + showStepProgress.value = true + stepProgressDone.value = false + try { const res = await fetchSkillGap(jobId) if (res.code === 0 && res.data) { skillGapData.value = res.data + // 通知组件接口已完成 + stepProgressDone.value = true + } else { + // 接口返回异常,关闭遮罩提示错误 + showStepProgress.value = false + ElMessage.error('分析失败,请稍后重试') } } catch (e) { console.error('技能差距分析失败', e) + showStepProgress.value = false ElMessage.error('分析失败,请稍后重试') - return - } finally { - fullLoading.value = false } +} + +// ==================== 步骤进度遮罩相关 ==================== + +/** 步骤进度遮罩是否显示 */ +const showStepProgress = ref(false) +/** 接口是否已完成 */ +const stepProgressDone = ref(false) +/** 步骤进度大标题 */ +const stepProgressTitle = '分析简历中,请耐心等待' +/** 步骤进度副标题 */ +const stepProgressSubtitle = '正在根据岗位所需技能、简历详情发现问题' +/** 步骤名称 */ +const stepProgressSteps = ['岗位技能解析...', '岗位职责解析...', '简历经历技能匹配...', '优化建议最终生成...'] +/** 每步模拟时长(毫秒) */ +const stepProgressDurations = [2000, 2000, 2000, 2000] + +/** 步骤进度走完后打开简历定制弹窗 */ +function handleStepProgressFinished() { + showStepProgress.value = false showResumeCustomDialog.value = true } +/** 关闭步骤进度遮罩 */ +function handleStepProgressClose() { + showStepProgress.value = false +} + /** 不优化直接投递 */ function handleSkipToApply() { if (job.sourceUrl) { diff --git a/src/views/Resume.vue b/src/views/Resume.vue index a064022..26faf5c 100644 --- a/src/views/Resume.vue +++ b/src/views/Resume.vue @@ -5,12 +5,21 @@

我的简历

- +
+ + + +
@@ -39,14 +48,6 @@
{{ item.name }} 默认 - -
{{ item.targetJob }} @@ -54,6 +55,16 @@
+
@@ -110,12 +132,13 @@ import ResumeExportDialog from '@/components/ResumeExportDialog.vue' import ResumeUploadDialog from '@/components/ResumeUploadDialog.vue' import { uploadResume } from '@/utils/aiRequest' import { - fetchResumeList, deleteResume, setDefaultResume, type ResumeListItem, + fetchResumeList, deleteResume, setDefaultResume, createResumeFromProfile, type ResumeListItem, } from '@/api/resume' import { ElMessage, ElMessageBox } from 'element-plus' // 命令式调用的组件,按需引入插件不会自动加载其样式,需手动引入 import 'element-plus/es/components/message-box/style/css' import FullscreenLoading from '@/components/FullscreenLoading.vue' +import StepProgressOverlay from '@/components/StepProgressOverlay.vue' const router = useRouter() @@ -207,6 +230,30 @@ onMounted(() => { loadResumeList() }) +// ==================== 从个人资料创建简历 ==================== + +/** 防抖锁:防止多次点击 */ +const creatingFromProfile = ref(false) + +/** + * 从个人资料创建简历(带防抖) + */ +async function handleCreateFromProfile() { + if (creatingFromProfile.value) return + creatingFromProfile.value = true + try { + const res = await createResumeFromProfile() + if (res.code === '0') { + ElMessage.success('创建成功') + await loadResumeList() + } + } catch (e) { + console.error('从个人资料创建简历失败', e) + } finally { + creatingFromProfile.value = false + } +} + // ==================== 页面状态 ==================== // ==================== 编辑简历名称弹窗状态 ==================== @@ -301,29 +348,61 @@ function handleUpload() { /** 上传弹窗确认回调 — 接收 File 对象,调用接口上传 */ async function handleUploadConfirm(file: File) { - // 全屏加载提示,AI 接口响应较慢 - fullLoadingText.value = '简历解析中,请耐心等待…' - fullLoading.value = true + // 显示步骤进度遮罩 + showUploadProgress.value = true + uploadProgressDone.value = false + pendingResumeId.value = '' try { const res = await uploadResume(file) if (res.code === 0) { // 上传成功,刷新列表 loadResumeList() - // 等待让后端异步处理数据,再关闭加载动画并跳转详情页 - await new Promise(resolve => setTimeout(resolve, 2000)) - fullLoading.value = false - goDetail(res.data.resumeId) + // 缓存简历ID,等进度走完后跳转 + pendingResumeId.value = res.data.resumeId + // 通知进度组件接口已完成 + uploadProgressDone.value = true } else { - fullLoading.value = false + showUploadProgress.value = false ElMessage.error(res.msg || '上传失败') } } catch { - fullLoading.value = false + showUploadProgress.value = false ElMessage.error('上传失败,请稍后重试') } } +// ==================== 简历解析步骤进度遮罩 ==================== + +/** 解析步骤进度遮罩是否显示 */ +const showUploadProgress = ref(false) +/** 解析接口是否已完成 */ +const uploadProgressDone = ref(false) +/** 大标题 */ +const uploadProgressTitle = '正在解析简历' +/** 副标题 */ +const uploadProgressSubtitle = '正在识别教育背景、工作经历与技能关键词' +/** 4个步骤名称 */ +const uploadProgressSteps = ['教育背景解析...', '工作经历解析...', '技能特长解析...', '个人资料最终确认'] +/** 每步时长4600毫秒 */ +const uploadProgressDurations = [4200, 4200, 4200, 4200] +/** 缓存的简历ID(等进度走完后跳转) */ +const pendingResumeId = ref('') + +/** 解析进度走完后跳转详情页 */ +function handleUploadProgressFinished() { + showUploadProgress.value = false + if (pendingResumeId.value) { + goDetail(pendingResumeId.value) + pendingResumeId.value = '' + } +} + +/** 关闭解析进度遮罩 */ +function handleUploadProgressClose() { + showUploadProgress.value = false +} + /** 跳转到简历详情页 */ function goDetail(id: string) { router.push(`/resume/${id}`) diff --git a/src/views/ResumeDetail.vue b/src/views/ResumeDetail.vue index b87aff5..0ff4bf8 100644 --- a/src/views/ResumeDetail.vue +++ b/src/views/ResumeDetail.vue @@ -80,38 +80,40 @@
+ +
+
- - - - - - {{ hasDiagnosis ? diagnosisReport.grade : '?' }} - - - -
- {{ hasDiagnosis ? gradeLabel : '未知' }} + + +
+ {{ hasDiagnosis ? diagnosisReport.grade : '?' }}
+
@@ -121,24 +123,60 @@
{{ issuesUrgentTotal+issuesImportantTotal+issuesExpressionTotal }} 项待修复 - +
+ +
+ {{ tab.label }} +
+ +
+ + +
+ -
+