左侧菜单,邀请,设置,和会员相关
This commit is contained in:
Vendored
+2
@@ -34,6 +34,8 @@ declare module 'vue' {
|
||||
ElRadio: typeof import('element-plus/es')['ElRadio']
|
||||
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
|
||||
ElSelect: typeof import('element-plus/es')['ElSelect']
|
||||
ElSkeleton: typeof import('element-plus/es')['ElSkeleton']
|
||||
ElSkeletonItem: typeof import('element-plus/es')['ElSkeletonItem']
|
||||
ElSwitch: typeof import('element-plus/es')['ElSwitch']
|
||||
ElTooltip: typeof import('element-plus/es')['ElTooltip']
|
||||
FullscreenLoading: typeof import('./src/components/FullscreenLoading.vue')['default']
|
||||
|
||||
+4
-4
@@ -21,8 +21,8 @@ export interface IndustryItem {
|
||||
* GET /public/industries/tree
|
||||
* 返回一级二级嵌套的行业分类
|
||||
*/
|
||||
export function fetchIndustryTree() {
|
||||
return request.get<any, ApiResult<IndustryItem[]>>('/public/industries/tree')
|
||||
export function fetchIndustryTree(params?: { includeEmpty?: boolean }) {
|
||||
return request.get<any, ApiResult<IndustryItem[]>>('/public/industries/tree', { params })
|
||||
}
|
||||
|
||||
// ==================== 岗位分类相关 ====================
|
||||
@@ -55,8 +55,8 @@ export interface JobCategoryItem {
|
||||
* GET /public/job-categories/tree
|
||||
* 返回一级二级三级嵌套的岗位分类
|
||||
*/
|
||||
export function fetchJobCategoryTree() {
|
||||
return request.get<any, ApiResult<JobCategoryItem[]>>('/public/job-categories/tree')
|
||||
export function fetchJobCategoryTree(params?: { includeEmpty?: boolean }) {
|
||||
return request.get<any, ApiResult<JobCategoryItem[]>>('/public/job-categories/tree', { params })
|
||||
}
|
||||
|
||||
// ==================== 地区分类相关 ====================
|
||||
|
||||
@@ -36,6 +36,8 @@ export interface MemberProduct {
|
||||
export interface MemberStatus {
|
||||
/** 是否是会员 */
|
||||
isMember: boolean
|
||||
/** 会员类型 1=正式 2=试用 */
|
||||
memberType?: number
|
||||
/** 到期时间(毫秒时间戳) */
|
||||
expireTime?: number
|
||||
/** 首次开通时间(毫秒时间戳) */
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 128 KiB |
@@ -8,7 +8,7 @@
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
z-index: 2000;
|
||||
z-index: 2500;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -16,7 +16,7 @@
|
||||
|
||||
.settings-dialog {
|
||||
position: relative;
|
||||
width: 8.6rem;
|
||||
width: 6.4rem;
|
||||
height: 6.2rem;
|
||||
background: $bg-white;
|
||||
border-radius: 0.12rem;
|
||||
@@ -60,35 +60,24 @@
|
||||
&__nav-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.08rem;
|
||||
padding: 0.12rem 0.2rem;
|
||||
padding: 0.1rem 0.16rem;
|
||||
margin: 0.02rem 0.12rem;
|
||||
font-size: 0.14rem;
|
||||
color: $text-dark;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
border-left: 0.03rem solid transparent;
|
||||
border-radius: 0.08rem;
|
||||
font-weight: 500;
|
||||
|
||||
&:hover {
|
||||
background: $bg-white;
|
||||
color: $accent;
|
||||
background: #E2E4E9;
|
||||
}
|
||||
|
||||
&--active {
|
||||
background: $bg-white;
|
||||
color: $accent;
|
||||
border-left-color: $accent;
|
||||
font-weight: 600;
|
||||
background: #E2E4E9;
|
||||
}
|
||||
}
|
||||
|
||||
&__nav-icon {
|
||||
font-size: 0.16rem;
|
||||
flex-shrink: 0;
|
||||
width: 0.18rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
// 底部操作
|
||||
&__bottom-actions {
|
||||
margin-top: auto;
|
||||
@@ -139,7 +128,10 @@
|
||||
|
||||
// ===== 账号与安全 =====
|
||||
&__section {
|
||||
margin-bottom: 0.28rem;
|
||||
margin-bottom: 0.16rem;
|
||||
padding: 0.16rem 0.24rem;
|
||||
background: #F9FAFB;
|
||||
border-radius: 0.08rem;
|
||||
}
|
||||
|
||||
&__section-label {
|
||||
@@ -156,7 +148,12 @@
|
||||
}
|
||||
|
||||
&__danger-section {
|
||||
margin-top: 0.32rem;
|
||||
padding: 0.16rem 0.24rem;
|
||||
background: #F9FAFB;
|
||||
border-radius: 0.08rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
&__danger-title {
|
||||
@@ -179,161 +176,238 @@
|
||||
}
|
||||
|
||||
&__danger-btn {
|
||||
background: $bg-main;
|
||||
border: 1px solid $border-color;
|
||||
border-radius: 0.06rem;
|
||||
border: none;
|
||||
background: none;
|
||||
padding: 0.07rem 0.16rem;
|
||||
font-size: 0.13rem;
|
||||
color: $text-dark;
|
||||
color: $danger;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
border-color: $danger;
|
||||
color: $danger;
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 会员 =====
|
||||
&__member-card {
|
||||
background: $bg-white;
|
||||
border: 1px solid $border-color;
|
||||
border-radius: 0.1rem;
|
||||
padding: 0.2rem 0.24rem;
|
||||
margin-bottom: 0.24rem;
|
||||
}
|
||||
|
||||
&__member-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 0.1rem;
|
||||
}
|
||||
|
||||
&__member-title-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.1rem;
|
||||
}
|
||||
|
||||
&__member-name {
|
||||
font-size: 0.16rem;
|
||||
font-weight: 700;
|
||||
color: $text-dark;
|
||||
}
|
||||
|
||||
&__member-badge {
|
||||
background: $accent;
|
||||
color: $bg-white;
|
||||
font-size: 0.11rem;
|
||||
padding: 0.02rem 0.08rem;
|
||||
border-radius: 0.1rem;
|
||||
|
||||
&--inactive {
|
||||
background: #ccc;
|
||||
color: $bg-white;
|
||||
}
|
||||
}
|
||||
|
||||
&__member-terms {
|
||||
font-size: 0.12rem;
|
||||
color: $accent;
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
|
||||
&:hover {
|
||||
color: $accent-hover;
|
||||
}
|
||||
}
|
||||
|
||||
&__member-info-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
&__member-price {
|
||||
font-size: 0.13rem;
|
||||
color: #555;
|
||||
// ===== 会员卡片(新版) =====
|
||||
&__mcard {
|
||||
padding: 0.16rem 0.24rem;
|
||||
border-radius: 0.12rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.06rem;
|
||||
gap: 0.32rem;
|
||||
|
||||
span {
|
||||
margin-left: 0;
|
||||
color: $text-light;
|
||||
&--formal {
|
||||
background: linear-gradient(90deg, #52CAD1 0%, #52D1A7 100%);
|
||||
}
|
||||
|
||||
&--trial {
|
||||
background: linear-gradient(90deg, #F59E0B 0%, #F5B70B 100%);
|
||||
}
|
||||
|
||||
&--free {
|
||||
background: #F3F4F6;
|
||||
}
|
||||
}
|
||||
|
||||
&__member-expire-line {
|
||||
font-size: 0.13rem;
|
||||
color: #555;
|
||||
&__mcard-top {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
&__member-remain-line {
|
||||
font-size: 0.13rem;
|
||||
color: $accent;
|
||||
font-weight: 600;
|
||||
&__mcard-title {
|
||||
font-size: 0.18rem;
|
||||
font-weight: 520;
|
||||
color: #FFFFFF;
|
||||
line-height: 0.24rem;
|
||||
|
||||
&--free {
|
||||
color: #6A7282;
|
||||
}
|
||||
}
|
||||
|
||||
&__member-manage-btn {
|
||||
background: $bg-main;
|
||||
border: 1px solid $border-color;
|
||||
border-radius: 0.06rem;
|
||||
padding: 0.07rem 0.16rem;
|
||||
font-size: 0.13rem;
|
||||
color: $text-dark;
|
||||
&__mcard-remain {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
gap: 0.04rem;
|
||||
}
|
||||
|
||||
&__mcard-remain-label {
|
||||
font-size: 0.14rem;
|
||||
font-weight: 330;
|
||||
color: #FFFFFF;
|
||||
line-height: 0.19rem;
|
||||
}
|
||||
|
||||
&__mcard-remain-days {
|
||||
font-size: 0.18rem;
|
||||
font-weight: 520;
|
||||
color: #FFFFFF;
|
||||
line-height: 0.24rem;
|
||||
}
|
||||
|
||||
&__mcard-bottom {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
&__mcard-expire {
|
||||
font-size: 0.12rem;
|
||||
font-weight: 330;
|
||||
color: #FFFFFF;
|
||||
line-height: 0.16rem;
|
||||
|
||||
&--free {
|
||||
color: #6A7282;
|
||||
}
|
||||
}
|
||||
|
||||
&__mcard-actions {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 0.08rem;
|
||||
}
|
||||
|
||||
&__mcard-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0.04rem 0.12rem;
|
||||
font-size: 0.12rem;
|
||||
font-weight: 330;
|
||||
line-height: 0.16rem;
|
||||
border-radius: 0.12rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
white-space: nowrap;
|
||||
border: none;
|
||||
transition: opacity 0.2s;
|
||||
|
||||
&:hover {
|
||||
border-color: $accent;
|
||||
color: $accent;
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
&--outline {
|
||||
background: transparent;
|
||||
border: 1px solid #FFFFFF;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
&--solid-formal {
|
||||
background: #FFFFFF;
|
||||
color: #52CAD1;
|
||||
}
|
||||
|
||||
&--solid-trial {
|
||||
background: #FFFFFF;
|
||||
color: #F59E0B;
|
||||
}
|
||||
|
||||
&--solid-accent {
|
||||
background: #52CAD1;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
}
|
||||
|
||||
&__member-issue {
|
||||
text-align: center;
|
||||
padding: 0.2rem 0;
|
||||
// ===== 问题反馈 =====
|
||||
&__feedback-wrap {
|
||||
background: #F9FAFB;
|
||||
border-radius: 0.08rem;
|
||||
padding: 0.16rem 0.24rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.24rem;
|
||||
}
|
||||
|
||||
&__member-issue-title {
|
||||
font-size: 0.15rem;
|
||||
font-weight: 600;
|
||||
color: $text-dark;
|
||||
margin-bottom: 0.1rem;
|
||||
&__feedback-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.12rem;
|
||||
}
|
||||
|
||||
&__member-issue-desc {
|
||||
font-size: 0.12rem;
|
||||
color: #666;
|
||||
line-height: 1.8;
|
||||
margin-bottom: 0.16rem;
|
||||
max-width: 5.0rem;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
&__feedback-label {
|
||||
font-size: 0.14rem;
|
||||
font-weight: 520;
|
||||
color: #1A1A1A;
|
||||
line-height: 0.19rem;
|
||||
}
|
||||
|
||||
&__member-issue-actions {
|
||||
&__feedback-options {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.08rem;
|
||||
}
|
||||
|
||||
&__feedback-option {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 0.16rem;
|
||||
}
|
||||
|
||||
&__member-issue-btn {
|
||||
background: $bg-white;
|
||||
border: 1px solid $border-color;
|
||||
border-radius: 0.06rem;
|
||||
padding: 0.08rem 0.24rem;
|
||||
font-size: 0.13rem;
|
||||
color: $text-dark;
|
||||
align-items: center;
|
||||
padding: 0.08rem 0.12rem;
|
||||
font-size: 0.12rem;
|
||||
font-weight: 330;
|
||||
line-height: 0.16rem;
|
||||
text-align: center;
|
||||
color: $accent;
|
||||
border: 1px solid $accent;
|
||||
border-radius: 0.08rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
flex: 1 1 30%;
|
||||
min-width: 0;
|
||||
|
||||
&:hover {
|
||||
background: rgba(82, 202, 209, 0.08);
|
||||
}
|
||||
|
||||
&--active {
|
||||
background: $accent;
|
||||
color: #FFFFFF;
|
||||
border-color: $accent;
|
||||
color: $accent;
|
||||
|
||||
&:hover {
|
||||
background: $accent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__feedback-textarea {
|
||||
width: 100%;
|
||||
min-height: 0.64rem;
|
||||
padding: 0.08rem 0.12rem;
|
||||
font-size: 0.12rem;
|
||||
font-weight: 330;
|
||||
line-height: 0.16rem;
|
||||
color: #1A1A1A;
|
||||
background: #FFFFFF;
|
||||
border: none;
|
||||
border-radius: 0.08rem;
|
||||
resize: vertical;
|
||||
outline: none;
|
||||
box-sizing: border-box;
|
||||
|
||||
&::placeholder {
|
||||
color: #99A1AF;
|
||||
}
|
||||
}
|
||||
|
||||
&__feedback-submit {
|
||||
margin-top: 0.44rem;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
display: block;
|
||||
padding: 0.08rem 0.24rem;
|
||||
background: $accent;
|
||||
color: #FFFFFF;
|
||||
font-size: 0.13rem;
|
||||
font-weight: 500;
|
||||
border: none;
|
||||
border-radius: 0.08rem;
|
||||
cursor: pointer;
|
||||
transition: opacity 0.2s;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.85;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -468,6 +542,9 @@
|
||||
font-size: 0.13rem;
|
||||
color: #555;
|
||||
line-height: 1.8;
|
||||
padding: 0.16rem 0.24rem;
|
||||
background: #F9FAFB;
|
||||
border-radius: 0.08rem;
|
||||
}
|
||||
|
||||
&__privacy-section {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@use '../variables' as *;
|
||||
|
||||
// ==================== 邀请注册送会员弹窗(重设计版) ====================
|
||||
// ==================== 邀请注册送会员弹窗 ====================
|
||||
.settings-invite-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
@@ -16,213 +16,219 @@
|
||||
}
|
||||
|
||||
.settings-invite-dialog {
|
||||
position: relative;
|
||||
width: 4.2rem;
|
||||
height: 5.0rem;
|
||||
background: $bg-white;
|
||||
border-radius: 0.26rem;
|
||||
box-shadow: 0 0.2rem 0.44rem rgba(0, 0, 0, 0.14);
|
||||
overflow: hidden;
|
||||
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;
|
||||
|
||||
// 顶部渐变区域
|
||||
// 顶部:标题行 + 关闭按钮
|
||||
&__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;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
// 皇冠图标
|
||||
&__crown {
|
||||
font-size: 0.32rem;
|
||||
line-height: 0.39rem;
|
||||
color: $bg-white;
|
||||
font-weight: 700;
|
||||
&__header-text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.04rem;
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-size: 0.18rem;
|
||||
font-weight: 520;
|
||||
line-height: 0.24rem;
|
||||
color: $text-dark;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
&__subtitle {
|
||||
font-size: 0.12rem;
|
||||
font-weight: 330;
|
||||
line-height: 0.16rem;
|
||||
color: $text-middle;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
// 关闭按钮
|
||||
&__close {
|
||||
position: absolute;
|
||||
top: 0.24rem;
|
||||
right: 0.27rem;
|
||||
font-size: 0.22rem;
|
||||
line-height: 0.27rem;
|
||||
color: #8EA0B8;
|
||||
width: 0.24rem;
|
||||
height: 0.24rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 0.14rem;
|
||||
color: $text-middle;
|
||||
cursor: pointer;
|
||||
transition: color 0.2s;
|
||||
z-index: 1;
|
||||
flex-shrink: 0;
|
||||
|
||||
&:hover {
|
||||
color: $text-dark;
|
||||
}
|
||||
}
|
||||
|
||||
// 内容区域
|
||||
&__body {
|
||||
flex: 1;
|
||||
padding: 0.22rem 0.28rem 0.2rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
// 标题
|
||||
&__title {
|
||||
font-size: 0.21rem;
|
||||
font-weight: 700;
|
||||
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 {
|
||||
box-sizing: border-box;
|
||||
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;
|
||||
justify-content: space-between;
|
||||
padding: 0.12rem 0.16rem;
|
||||
width: 100%;
|
||||
background: #F4FBFB;
|
||||
border: 1px solid #AEE6EA;
|
||||
border-radius: 0.08rem;
|
||||
}
|
||||
|
||||
&__link-text {
|
||||
flex: 1;
|
||||
font-size: 0.11rem;
|
||||
line-height: 0.13rem;
|
||||
color: #334155;
|
||||
white-space: nowrap;
|
||||
font-size: 0.14rem;
|
||||
font-weight: 330;
|
||||
line-height: 0.19rem;
|
||||
color: $text-dark;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
// 复制链接按钮
|
||||
&__copy-btn {
|
||||
width: 1.0rem;
|
||||
height: 0.32rem;
|
||||
border: none;
|
||||
border-radius: 0.16rem;
|
||||
background: #0F172A;
|
||||
color: $bg-white;
|
||||
font-size: 0.13rem;
|
||||
font-weight: 600;
|
||||
line-height: 0.16rem;
|
||||
font-size: 0.14rem;
|
||||
font-weight: 450;
|
||||
line-height: 0.19rem;
|
||||
color: $accent;
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
margin-left: 0.08rem;
|
||||
transition: opacity 0.2s;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
// 邀请步骤区域
|
||||
&__steps {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.16rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&__steps-title {
|
||||
font-size: 0.14rem;
|
||||
font-weight: 450;
|
||||
line-height: 0.19rem;
|
||||
color: $accent;
|
||||
}
|
||||
|
||||
&__steps-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.16rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
// 步骤卡片
|
||||
&__step-item {
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.12rem 0.16rem;
|
||||
gap: 0.10rem;
|
||||
width: 100%;
|
||||
background: $bg-white;
|
||||
border: 1px solid #F0F0F0;
|
||||
border-radius: 0.08rem;
|
||||
}
|
||||
|
||||
// 步骤数字圆圈
|
||||
&__step-num {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 0.20rem;
|
||||
min-width: 0.20rem;
|
||||
height: 0.20rem;
|
||||
background: $bg-main;
|
||||
border-radius: 0.10rem;
|
||||
font-size: 0.14rem;
|
||||
font-weight: 330;
|
||||
line-height: 0.20rem;
|
||||
text-align: center;
|
||||
color: $text-middle;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
// 步骤文字
|
||||
&__step-text {
|
||||
font-size: 0.14rem;
|
||||
font-weight: 330;
|
||||
line-height: 0.19rem;
|
||||
color: $text-dark;
|
||||
}
|
||||
|
||||
// 底部:统计 + 按钮
|
||||
&__footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
// 统计信息
|
||||
&__stat {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.02rem;
|
||||
}
|
||||
|
||||
&__stat-line {
|
||||
font-size: 0.14rem;
|
||||
font-weight: 330;
|
||||
line-height: 0.19rem;
|
||||
color: $text-middle;
|
||||
|
||||
em {
|
||||
font-style: normal;
|
||||
color: $accent;
|
||||
font-weight: 450;
|
||||
}
|
||||
}
|
||||
|
||||
// 成为正式会员按钮
|
||||
&__member-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.08rem 0.12rem;
|
||||
gap: 0.04rem;
|
||||
background: $accent;
|
||||
border-radius: 0.08rem;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: opacity 0.2s;
|
||||
|
||||
span {
|
||||
font-size: 0.14rem;
|
||||
font-weight: 520;
|
||||
line-height: 0.19rem;
|
||||
text-align: center;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
opacity: 0.85;
|
||||
}
|
||||
}
|
||||
|
||||
// 步骤卡片
|
||||
&__step {
|
||||
display: flex;
|
||||
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;
|
||||
}
|
||||
|
||||
// 步骤数字圆圈
|
||||
&__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.13rem;
|
||||
font-weight: 500;
|
||||
color: #64748B;
|
||||
line-height: 0.16rem;
|
||||
}
|
||||
|
||||
&__stat-value {
|
||||
font-size: 0.13rem;
|
||||
font-weight: 500;
|
||||
color: #64748B;
|
||||
line-height: 0.16rem;
|
||||
}
|
||||
|
||||
// 成为正式会员按钮
|
||||
&__member-btn {
|
||||
margin-left: auto;
|
||||
width: 1.1rem;
|
||||
height: 0.29rem;
|
||||
background: #F3FFFD;
|
||||
border: 1px solid #BFEFE8;
|
||||
border-radius: 0.08rem;
|
||||
font-size: 0.13rem;
|
||||
font-weight: 400;
|
||||
color: #000000;
|
||||
line-height: 0.16rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
text-align: center;
|
||||
|
||||
&:hover {
|
||||
background: #e8faf8;
|
||||
border-color: $accent;
|
||||
}
|
||||
&__member-btn-arrow {
|
||||
font-size: 0.16rem;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,11 +142,21 @@
|
||||
|
||||
.side-nav__dialog-close {
|
||||
cursor: pointer;
|
||||
color: $text-light;
|
||||
font-size: 0.16rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 0.24rem;
|
||||
height: 0.24rem;
|
||||
border-radius: 4px;
|
||||
transition: background 0.2s;
|
||||
|
||||
&:hover {
|
||||
color: $text-dark;
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
svg {
|
||||
width: 0.14rem;
|
||||
height: 0.14rem;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,75 +168,116 @@
|
||||
|
||||
// ==================== 消息通知弹窗(左右分栏) ====================
|
||||
.side-nav__message-dialog {
|
||||
background: $bg-white;
|
||||
border-radius: 0.12rem;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
padding: 0.24rem 0.32rem;
|
||||
gap: 0.24rem;
|
||||
width: 8rem;
|
||||
height: 6rem;
|
||||
max-width: 90vw;
|
||||
max-height: 90vh;
|
||||
box-shadow: 0 0.04rem 0.2rem rgba(0, 0, 0, 0.15);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: linear-gradient(180deg, #EDF9FA 0%, #FFFFFF 12.08%);
|
||||
border: 1px solid #F0F0F0;
|
||||
box-shadow: 0 0 0.16rem rgba(0, 0, 0, 0.05);
|
||||
border-radius: 0.12rem;
|
||||
}
|
||||
|
||||
.side-nav__message-dialog-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0.16rem 0.2rem;
|
||||
border-bottom: 1px solid $border-color;
|
||||
font-size: 0.16rem;
|
||||
font-weight: 600;
|
||||
color: $text-dark;
|
||||
width: 100%;
|
||||
flex-shrink: 0;
|
||||
|
||||
span:first-child {
|
||||
font-weight: 520;
|
||||
font-size: 0.18rem;
|
||||
line-height: 0.24rem;
|
||||
color: $text-dark;
|
||||
}
|
||||
}
|
||||
|
||||
.side-nav__message-dialog-content {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
gap: 0.16rem;
|
||||
width: 100%;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
background: $bg-white;
|
||||
}
|
||||
|
||||
// 左侧消息列表
|
||||
.side-nav__message-list {
|
||||
width: 2.1rem;
|
||||
border-right: 1px solid $border-color;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.04rem;
|
||||
width: 2.24rem;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.side-nav__message-list-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.12rem 0.10rem;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
padding: 0.16rem 0.12rem;
|
||||
border-radius: 0.08rem;
|
||||
cursor: pointer;
|
||||
border-bottom: 1px solid $border-color;
|
||||
transition: background 0.2s;
|
||||
|
||||
&:hover {
|
||||
background: $bg-main;
|
||||
background: #F6F6F6;
|
||||
}
|
||||
|
||||
&--active {
|
||||
background: $theme-color;
|
||||
background: #EDF9FA;
|
||||
}
|
||||
}
|
||||
|
||||
.side-nav__message-list-title {
|
||||
flex: 1;
|
||||
font-size: 0.13rem;
|
||||
color: $text-dark;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
// 列表项内部行布局
|
||||
.side-nav__message-list-item-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.side-nav__message-list-item-left {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 0.08rem;
|
||||
}
|
||||
|
||||
.side-nav__message-unread-dot {
|
||||
width: 0.08rem;
|
||||
height: 0.08rem;
|
||||
border-radius: 50%;
|
||||
background: $danger;
|
||||
margin-left: 0.06rem;
|
||||
background: $accent;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.side-nav__message-list-title {
|
||||
font-weight: 520;
|
||||
font-size: 0.14rem;
|
||||
line-height: 0.19rem;
|
||||
color: $text-dark;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.side-nav__message-list-date {
|
||||
font-weight: 330;
|
||||
font-size: 0.12rem;
|
||||
line-height: 0.16rem;
|
||||
color: $text-light;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@@ -241,21 +292,45 @@
|
||||
// 右侧消息详情
|
||||
.side-nav__message-detail {
|
||||
flex: 1;
|
||||
padding: 0.2rem;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
position: relative;
|
||||
padding: 0.16rem 0.24rem;
|
||||
}
|
||||
|
||||
.side-nav__message-detail-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.side-nav__message-detail-title {
|
||||
font-size: 0.16rem;
|
||||
font-weight: 600;
|
||||
font-weight: 630;
|
||||
font-size: 0.22rem;
|
||||
line-height: 0.29rem;
|
||||
color: $text-dark;
|
||||
margin-bottom: 0.16rem;
|
||||
}
|
||||
|
||||
.side-nav__message-detail-time {
|
||||
font-weight: 330;
|
||||
font-size: 0.12rem;
|
||||
line-height: 0.16rem;
|
||||
color: $text-middle;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.side-nav__message-detail-divider {
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background: #F0F0F0;
|
||||
margin: 0.16rem 0;
|
||||
}
|
||||
|
||||
.side-nav__message-detail-content {
|
||||
font-weight: 330;
|
||||
font-size: 0.14rem;
|
||||
line-height: 0.24rem;
|
||||
color: $text-dark;
|
||||
line-height: 1.7;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
@@ -380,3 +455,279 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 邀请会员卡片 ====================
|
||||
.side-nav__referral-card {
|
||||
width: 100%;
|
||||
background: linear-gradient(128.17deg, #52CAD1 0%, #82D79B 100%);
|
||||
border-radius: 0.08rem;
|
||||
padding: 0.16rem;
|
||||
box-sizing: border-box;
|
||||
margin-top: 0.12rem;
|
||||
}
|
||||
|
||||
.side-nav__referral-title {
|
||||
font-size: 0.16rem;
|
||||
font-weight: 630;
|
||||
color: #FFFFFF;
|
||||
line-height: 0.21rem;
|
||||
}
|
||||
|
||||
.side-nav__referral-subtitle {
|
||||
font-size: 0.10rem;
|
||||
font-weight: 330;
|
||||
color: #EAFBFC;
|
||||
line-height: 0.13rem;
|
||||
margin-top: 0.04rem;
|
||||
}
|
||||
|
||||
.side-nav__referral-panel {
|
||||
background: #FFFFFF;
|
||||
box-shadow: 0px 0.02rem 0.06rem rgba(26, 26, 26, 0.05);
|
||||
border-radius: 0.06rem;
|
||||
padding: 0.10rem 0.08rem;
|
||||
margin-top: 0.10rem;
|
||||
}
|
||||
|
||||
.side-nav__referral-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.side-nav__referral-label {
|
||||
font-size: 0.10rem;
|
||||
font-weight: 330;
|
||||
color: #6B7280;
|
||||
line-height: 0.13rem;
|
||||
}
|
||||
|
||||
.side-nav__referral-value {
|
||||
font-size: 0.10rem;
|
||||
font-weight: 520;
|
||||
color: #52CAD1;
|
||||
line-height: 0.13rem;
|
||||
}
|
||||
|
||||
.side-nav__referral-track {
|
||||
width: 100%;
|
||||
height: 0.02rem;
|
||||
background: #D7F1F3;
|
||||
border-radius: 9999px;
|
||||
margin-top: 0.06rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.side-nav__referral-fill {
|
||||
height: 100%;
|
||||
background: #52CAD1;
|
||||
border-radius: 9999px;
|
||||
}
|
||||
|
||||
.side-nav__referral-divider {
|
||||
width: 100%;
|
||||
border-top: 0.01rem solid #8C8C8C;
|
||||
margin-top: 0.08rem;
|
||||
}
|
||||
|
||||
.side-nav__referral-tip {
|
||||
font-size: 0.10rem;
|
||||
font-weight: 330;
|
||||
color: #1A1A1A;
|
||||
line-height: 0.13rem;
|
||||
margin-top: 0.08rem;
|
||||
|
||||
strong {
|
||||
font-weight: 630;
|
||||
}
|
||||
}
|
||||
|
||||
.side-nav__referral-action {
|
||||
font-size: 0.11rem;
|
||||
font-weight: 520;
|
||||
color: #52CAD1;
|
||||
line-height: 0.15rem;
|
||||
text-align: center;
|
||||
margin-top: 0.08rem;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 用户信息模块 ====================
|
||||
.side-nav__user-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
background: #F3F4F6;
|
||||
border-radius: 0.08rem;
|
||||
padding: 0.12rem 0.16rem;
|
||||
box-sizing: border-box;
|
||||
margin-top: 0.12rem;
|
||||
}
|
||||
|
||||
.side-nav__user-left {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.04rem;
|
||||
}
|
||||
|
||||
.side-nav__user-phone {
|
||||
font-size: 0.14rem;
|
||||
font-weight: 330;
|
||||
color: #1A1A1A;
|
||||
line-height: 0.19rem;
|
||||
}
|
||||
|
||||
.side-nav__user-member-tag {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0.02rem 0.04rem;
|
||||
border-radius: 0.04rem;
|
||||
font-size: 0.10rem;
|
||||
font-weight: 520;
|
||||
line-height: 0.13rem;
|
||||
width: fit-content;
|
||||
|
||||
// 正式会员:主题色字 + 浅绿底
|
||||
&--formal {
|
||||
color: #52CAD1;
|
||||
background: #EDF9FA;
|
||||
}
|
||||
|
||||
// 试用会员:橙色字 + 浅橙底
|
||||
&--trial {
|
||||
color: #F59E0B;
|
||||
background: #FFFBF5;
|
||||
}
|
||||
|
||||
// 免费用户:中灰字 + 灰底
|
||||
&--free {
|
||||
color: #6A7282;
|
||||
background: #F3F4F6;
|
||||
}
|
||||
}
|
||||
|
||||
.side-nav__user-bell {
|
||||
width: 0.20rem;
|
||||
height: 0.20rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
|
||||
svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
// 铃铛右上角未读红点
|
||||
.side-nav__user-bell-dot {
|
||||
position: absolute;
|
||||
top: -0.005rem;
|
||||
right: 0.01rem;
|
||||
width: 0.08rem;
|
||||
height: 0.08rem;
|
||||
background: #EF4444;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
// ==================== 账户弹窗 ====================
|
||||
.side-nav__user-info-wrapper {
|
||||
position: relative;
|
||||
margin-top: 0.12rem;
|
||||
}
|
||||
|
||||
.side-nav__account-popup {
|
||||
position: absolute;
|
||||
bottom: calc(100% + 0.08rem);
|
||||
left: 0;
|
||||
width: 100%;
|
||||
background: #FFFFFF;
|
||||
border: 1px solid #F0F0F0;
|
||||
box-shadow: 0px 0.02rem 0.08rem rgba(0, 0, 0, 0.04);
|
||||
border-radius: 0.08rem;
|
||||
overflow: hidden;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.side-nav__account-popup-header {
|
||||
padding: 0.16rem;
|
||||
background: linear-gradient(180deg, #EDF9FA 0%, #FFFFFF 38.18%);
|
||||
border-radius: 0.08rem 0.08rem 0 0;
|
||||
}
|
||||
|
||||
.side-nav__account-popup-header-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.side-nav__account-popup-header-left {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.04rem;
|
||||
}
|
||||
|
||||
.side-nav__account-popup-member-type {
|
||||
font-size: 0.14rem;
|
||||
font-weight: 450;
|
||||
line-height: 0.19rem;
|
||||
|
||||
// 正式会员 — 主题色
|
||||
&--formal {
|
||||
color: $accent;
|
||||
}
|
||||
|
||||
// 试用会员 — 橙色
|
||||
&--trial {
|
||||
color: #F59E0B;
|
||||
}
|
||||
|
||||
// 免费用户 — 中灰色
|
||||
&--free {
|
||||
color: $text-middle;
|
||||
}
|
||||
}
|
||||
|
||||
.side-nav__account-popup-expire {
|
||||
font-size: 0.12rem;
|
||||
font-weight: 330;
|
||||
color: $text-light;
|
||||
line-height: 0.16rem;
|
||||
}
|
||||
|
||||
.side-nav__account-popup-detail {
|
||||
font-size: 0.12rem;
|
||||
font-weight: 330;
|
||||
color: $text-light;
|
||||
line-height: 0.16rem;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: $accent;
|
||||
}
|
||||
}
|
||||
|
||||
.side-nav__account-popup-item {
|
||||
padding: 0.12rem 0.16rem;
|
||||
font-size: 0.12rem;
|
||||
font-weight: 330;
|
||||
color: #000000;
|
||||
line-height: 0.16rem;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background: $bg-main;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,10 +187,28 @@
|
||||
&__match-card {
|
||||
flex-shrink: 0;
|
||||
width: 2.06rem;
|
||||
background: #FBFDF7;
|
||||
border: 1px solid #9FD051;
|
||||
border-radius: 0.08rem;
|
||||
padding: 0.16rem 0.24rem;
|
||||
border: 1px solid #9FD051;
|
||||
background: #FBFDF7;
|
||||
}
|
||||
|
||||
// 高匹配度
|
||||
&__match-card--high {
|
||||
background: #FBFDF7;
|
||||
border-color: #9FD051;
|
||||
}
|
||||
|
||||
// 中匹配度
|
||||
&__match-card--medium {
|
||||
background: #FFFBF5;
|
||||
border-color: #F59E0B;
|
||||
}
|
||||
|
||||
// 低匹配度
|
||||
&__match-card--low {
|
||||
background: #FFF6F5;
|
||||
border-color: #FE6D52;
|
||||
}
|
||||
|
||||
&__match-top {
|
||||
@@ -214,6 +232,24 @@
|
||||
color: #9FD051;
|
||||
}
|
||||
|
||||
// 高匹配度文字色
|
||||
&__match-card--high &__match-score,
|
||||
&__match-card--high &__match-label {
|
||||
color: #9FD051;
|
||||
}
|
||||
|
||||
// 中匹配度文字色
|
||||
&__match-card--medium &__match-score,
|
||||
&__match-card--medium &__match-label {
|
||||
color: #F59E0B;
|
||||
}
|
||||
|
||||
// 低匹配度文字色
|
||||
&__match-card--low &__match-score,
|
||||
&__match-card--low &__match-label {
|
||||
color: #FE6D52;
|
||||
}
|
||||
|
||||
&__match-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -392,3 +428,8 @@
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 骨架屏区域 ----
|
||||
.job-detail__skeleton {
|
||||
padding: 0.32rem;
|
||||
}
|
||||
|
||||
@@ -15,8 +15,6 @@
|
||||
overflow: hidden;
|
||||
background: $bg-main;
|
||||
font-size: 0.14rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
// 页面标题
|
||||
@@ -156,9 +154,6 @@
|
||||
font-size: 0.12rem;
|
||||
line-height: 0.16rem;
|
||||
color: $accent;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
vertical-align: text-top;
|
||||
margin-left: 0.03rem;
|
||||
}
|
||||
@@ -278,11 +273,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 职位列表
|
||||
// 职位列表(用 calc 减去上方头部+筛选栏大致高度,保证列表独立滚动)
|
||||
&__list {
|
||||
gap: 0;
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
height: calc(var(--app-height, 100vh) - 1.8rem);
|
||||
padding-bottom: 0.2rem;
|
||||
margin: 0 0.24rem;
|
||||
|
||||
@@ -307,7 +302,7 @@
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 0.04rem 0.16rem rgba(0, 0, 0, 0.06);
|
||||
background: #fbfbfb;
|
||||
//background: #fbfbfb;
|
||||
}
|
||||
|
||||
&--selected {
|
||||
@@ -581,7 +576,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 不再显示
|
||||
// 不再显示(默认隐藏,hover 卡片时显示)
|
||||
&__hide-btn {
|
||||
display: block;
|
||||
margin-top: auto;
|
||||
@@ -589,15 +584,21 @@
|
||||
font-weight: 330;
|
||||
color: #8C8C8C;
|
||||
cursor: pointer;
|
||||
transition: color 0.2s;
|
||||
transition: color 0.2s, opacity 0.2s;
|
||||
line-height: 0.19rem;
|
||||
white-space: nowrap;
|
||||
opacity: 0;
|
||||
|
||||
&:hover {
|
||||
color: $text-dark;
|
||||
}
|
||||
}
|
||||
|
||||
// hover 卡片时显示"不再显示"按钮
|
||||
&__job-card:hover &__hide-btn {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
// 弹出菜单
|
||||
&__job-popup {
|
||||
position: absolute;
|
||||
|
||||
+236
-479
@@ -5,26 +5,27 @@
|
||||
<div v-if="modelValue" class="member-dialog-overlay" @click="$emit('update:modelValue', false)">
|
||||
<!-- 弹窗主体 — 阻止点击冒泡 -->
|
||||
<div class="member-dialog" @click.stop>
|
||||
<!-- 右上角关闭按钮 -->
|
||||
<span class="member-dialog__close" @click="$emit('update:modelValue', false)">✕</span>
|
||||
|
||||
<!-- ==================== 会员介绍步骤一:简介页 ==================== -->
|
||||
<div v-if="currentView === 'intro1'" class="member-dialog__intro-step1">
|
||||
<h1 class="member-dialog__intro-title">Offer 派 - AI 智能求职引擎</h1>
|
||||
<p class="member-dialog__intro-subtitle">全自动海投、简历精修、模拟面试,助你轻松拿高薪。</p>
|
||||
<button class="member-dialog__intro-btn" @click="currentView = 'intro2'">
|
||||
<span class="member-dialog__intro-btn-icon">⚡</span>
|
||||
升级 Pro 开启求职加速
|
||||
</button>
|
||||
<!-- 顶部标题行 -->
|
||||
<div class="member-dialog__header">
|
||||
<div class="member-dialog__header-left">
|
||||
<h2 class="member-dialog__title">会员中心</h2>
|
||||
<p class="member-dialog__subtitle">解锁更多求职功能,让投递更高效</p>
|
||||
</div>
|
||||
<div class="member-dialog__header-right">
|
||||
<!-- 联系客服(hover显示二维码) -->
|
||||
<span class="member-dialog__contact-wrap">
|
||||
<span class="member-dialog__contact">联系客服</span>
|
||||
<div class="member-dialog__contact-qr">
|
||||
<img src="@/assets/images/nav/kg-qcCode.png" alt="客服二维码" />
|
||||
<div class="member-dialog__contact-qr-arrow"></div>
|
||||
</div>
|
||||
</span>
|
||||
<span class="member-dialog__close" @click="$emit('update:modelValue', false)">✕</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ==================== 会员介绍步骤二:详细介绍页(可滚动) ==================== -->
|
||||
<div v-else-if="currentView === 'intro2'" class="member-dialog__intro-step2">
|
||||
<div class="member-dialog__step2-badge">⚡ 求职加速会员</div>
|
||||
<h1 class="member-dialog__step2-title">offer派,收 offer 就是快!</h1>
|
||||
<p class="member-dialog__step2-subtitle">每天不到 1 元,少投无效岗位,把时间花在更可能拿面试的机会。</p>
|
||||
|
||||
<!-- 套餐卡片区域 -->
|
||||
<!-- 套餐选择区域 -->
|
||||
<div class="member-dialog__plans">
|
||||
<div
|
||||
v-for="plan in plans"
|
||||
@@ -33,312 +34,117 @@
|
||||
:class="{ 'member-dialog__plan-card--active': selectedPlan === plan.key }"
|
||||
@click="selectedPlan = plan.key"
|
||||
>
|
||||
<div v-if="plan.recommend" class="member-dialog__plan-recommend">★ 推荐:覆盖求职周期,性价比最高</div>
|
||||
<div v-if="plan.tag" class="member-dialog__plan-tag">{{ plan.tag }}</div>
|
||||
<!-- 推荐标签 -->
|
||||
<div v-if="plan.recommend" class="member-dialog__plan-recommend"><svg width="10" height="10" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_180_2594)">
|
||||
<path d="M0.872709 4.61946C0.867714 4.56192 0.874732 4.50398 0.893317 4.44931C0.911903 4.39464 0.94165 4.34442 0.980672 4.30186C1.01969 4.25929 1.06714 4.2253 1.11999 4.20204C1.17285 4.17878 1.22996 4.16676 1.28771 4.16675H2.08333C2.19384 4.16675 2.29982 4.21065 2.37796 4.28879C2.4561 4.36693 2.5 4.47291 2.5 4.58341V8.54175C2.5 8.65225 2.4561 8.75823 2.37796 8.83637C2.29982 8.91451 2.19384 8.95841 2.08333 8.95841H1.63208C1.5278 8.95844 1.42729 8.91936 1.35042 8.84889C1.27355 8.77842 1.2259 8.68168 1.21688 8.57779L0.872709 4.61946ZM3.75 4.45321C3.75 4.27904 3.85833 4.1232 4.01563 4.04904C4.35917 3.88696 4.94438 3.56133 5.20833 3.12112C5.54854 2.55362 5.61271 1.52841 5.62313 1.29362C5.62458 1.26071 5.62375 1.22779 5.62813 1.19529C5.68458 0.788414 6.46979 1.26362 6.77083 1.76612C6.93438 2.03862 6.95521 2.39675 6.93813 2.67654C6.91958 2.97571 6.83188 3.26466 6.74583 3.55175L6.5625 4.16362H8.82438C8.88875 4.16362 8.95225 4.17853 9.0099 4.20719C9.06754 4.23585 9.11776 4.27748 9.15662 4.32881C9.19547 4.38014 9.22191 4.43977 9.23384 4.50303C9.24578 4.56629 9.2429 4.63145 9.22542 4.69341L8.10667 8.65508C8.08197 8.74245 8.02941 8.81937 7.95698 8.87413C7.88455 8.92888 7.79622 8.95848 7.70542 8.95841H4.16667C4.05616 8.95841 3.95018 8.91451 3.87204 8.83637C3.7939 8.75823 3.75 8.65225 3.75 8.54175V4.45321Z" fill="white" stroke="white" stroke-width="0.833333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_180_2594">
|
||||
<rect width="10" height="10" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
推荐</div>
|
||||
<!-- 套餐名称 -->
|
||||
<div class="member-dialog__plan-name">{{ plan.name }}</div>
|
||||
<!-- 价格行 -->
|
||||
<div class="member-dialog__plan-price">
|
||||
<span class="member-dialog__plan-price-symbol">¥</span>
|
||||
<span class="member-dialog__plan-price-num">{{ plan.price }}</span>
|
||||
<span class="member-dialog__plan-price-unit">/{{ plan.unit }}</span>
|
||||
<span class="member-dialog__plan-price-num">{{ plan.priceInt }}</span>
|
||||
<span v-if="plan.originalPrice" class="member-dialog__plan-original-price">¥{{ plan.originalPrice }}</span>
|
||||
</div>
|
||||
<div v-if="plan.perMonth" class="member-dialog__plan-per-month">折合仅 ¥{{ plan.perMonth }}/月</div>
|
||||
<button class="member-dialog__plan-btn" @click.stop="handleUpgrade(plan)">{{ plan.btnText }}</button>
|
||||
<!-- 每日价格 -->
|
||||
<div class="member-dialog__plan-daily">{{ plan.dailyDesc }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 提示信息 -->
|
||||
<div class="member-dialog__tip">
|
||||
<span class="member-dialog__tip-icon">⏰</span>
|
||||
<span class="member-dialog__tip-label">时间就是机会:</span>
|
||||
<span class="member-dialog__tip-text">热门岗位通常在发布后 24-48 小时内收到大量申请。使用 AI 加速工具,越早投递越容易被 HR 看到!</span>
|
||||
<!-- 会员权益区域 -->
|
||||
<div class="member-dialog__benefits">
|
||||
<h3 class="member-dialog__benefits-title">会员权益</h3>
|
||||
<div class="member-dialog__benefits-cards">
|
||||
<div class="member-dialog__benefit-card">
|
||||
<div class="member-dialog__benefit-icon">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none">
|
||||
<rect width="24" height="24" rx="12" fill="#EDF9FA"/>
|
||||
<path d="M7 8h7v2H7V8zm0 3h10v2H7v-2zm0 3h5v2H7v-2z" fill="#52CAD1"/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<!-- 底部内容:左侧能力列表 + 右侧对比表格 -->
|
||||
<div class="member-dialog__content-row">
|
||||
<div class="member-dialog__abilities">
|
||||
<h3 class="member-dialog__abilities-title">求职加速能力</h3>
|
||||
<div v-for="ability in abilities" :key="ability.text" class="member-dialog__ability-item">
|
||||
<span class="member-dialog__ability-icon" :style="{ color: ability.color }">{{ ability.icon }}</span>
|
||||
<span class="member-dialog__ability-text">{{ ability.text }}</span>
|
||||
<div class="member-dialog__benefit-info">
|
||||
<span class="member-dialog__benefit-name">AI优化简历</span>
|
||||
<span class="member-dialog__benefit-desc">享受无限次数</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="member-dialog__compare">
|
||||
<h3 class="member-dialog__compare-title">会员能力对比</h3>
|
||||
<table class="member-dialog__compare-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>核心能力</th>
|
||||
<th>免费版</th>
|
||||
<th class="member-dialog__compare-th-pro">⚡ 会员版</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="row in compareData" :key="row.name">
|
||||
<td>{{ row.name }}</td>
|
||||
<td class="member-dialog__compare-free">{{ row.free }}</td>
|
||||
<td class="member-dialog__compare-pro">✓ {{ row.pro }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="member-dialog__compare-footer">
|
||||
<span>🔒 支付安全保障</span>
|
||||
<span>⏰ 支持随时取消</span>
|
||||
<div class="member-dialog__benefit-card">
|
||||
<div class="member-dialog__benefit-icon">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none">
|
||||
<rect width="24" height="24" rx="12" fill="#EDF9FA"/>
|
||||
<path d="M12 7l1.5 3 3.5.5-2.5 2.5.5 3.5L12 15l-3 1.5.5-3.5L7 10.5l3.5-.5L12 7z" fill="#52CAD1"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 用户评价 -->
|
||||
<div class="member-dialog__testimonial">
|
||||
<div class="member-dialog__testimonial-avatar">
|
||||
<img src="@/assets/images/home/alex-avatar.png" alt="用户头像" />
|
||||
</div>
|
||||
<div class="member-dialog__testimonial-content">
|
||||
<div class="member-dialog__testimonial-stars">★★★★★</div>
|
||||
<p class="member-dialog__testimonial-text">"开通后我主要用 AI 简历优化和岗位匹配,投递效率明显提升,每次网申节省了十几分钟,终于知道该重点投哪些岗位了。"</p>
|
||||
<div class="member-dialog__testimonial-author">
|
||||
<span class="member-dialog__testimonial-name">张同学</span>
|
||||
<span class="member-dialog__testimonial-desc">· 成功入职某大厂运营</span>
|
||||
<div class="member-dialog__benefit-info">
|
||||
<span class="member-dialog__benefit-name">AI+投递助手</span>
|
||||
<span class="member-dialog__benefit-desc">会员专享</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ==================== 下单步骤一:选择套餐 ==================== -->
|
||||
<div v-else-if="currentView === 'order1'" class="member-dialog__order">
|
||||
<!-- 顶部导航 -->
|
||||
<div class="member-dialog__order-header">
|
||||
<span class="melmber-dialog__order-back" @click="currentView = 'intro2'">‹ 返回会员介绍</span>
|
||||
<!-- 步骤条 — 二维码弹窗打开时显示第2步 -->
|
||||
<div class="member-dialog__steps">
|
||||
<div class="member-dialog__step" :class="showQrCode ? 'member-dialog__step--done' : 'member-dialog__step--active'">
|
||||
<span class="member-dialog__step-num">{{ showQrCode ? '✓' : '1' }}</span>
|
||||
<span class="member-dialog__step-label">选择套餐</span>
|
||||
</div>
|
||||
<div class="member-dialog__step-line" :class="{ 'member-dialog__step-line--done': showQrCode }"></div>
|
||||
<div class="member-dialog__step" :class="{ 'member-dialog__step--active': showQrCode }">
|
||||
<span class="member-dialog__step-num">2</span>
|
||||
<span class="member-dialog__step-label">支付方式</span>
|
||||
</div>
|
||||
<div class="member-dialog__step-line"></div>
|
||||
<div class="member-dialog__step">
|
||||
<span class="member-dialog__step-num">3</span>
|
||||
<span class="member-dialog__step-label">完成</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 主内容区域(可滚动) -->
|
||||
<div class="member-dialog__order-body">
|
||||
<!-- 左侧内容 -->
|
||||
<div class="member-dialog__order-left">
|
||||
<h2 class="member-dialog__order-title">开启你的 AI 求职加速计划</h2>
|
||||
<p class="member-dialog__order-desc">解锁简历优化、岗位匹配、投递追踪、面试准备等能力,让你的求职过程更高效、更有方向。</p>
|
||||
|
||||
<!-- 套餐选择卡片 -->
|
||||
<div class="member-dialog__order-plans">
|
||||
<!-- 支付方式选择 -->
|
||||
<div class="member-dialog__payment">
|
||||
<span class="member-dialog__payment-label">选择支付方式</span>
|
||||
<div class="member-dialog__payment-methods">
|
||||
<div
|
||||
v-for="plan in plans"
|
||||
:key="plan.key"
|
||||
class="member-dialog__order-plan-card"
|
||||
:class="{ 'member-dialog__order-plan-card--active': selectedPlan === plan.key }"
|
||||
@click="selectedPlan = plan.key"
|
||||
class="member-dialog__payment-item"
|
||||
:class="{ 'member-dialog__payment-item--wechat-active': selectedPayment === 'wechat' }"
|
||||
@click="selectedPayment = 'wechat'"
|
||||
>
|
||||
<div v-if="plan.recommend" class="member-dialog__order-plan-badge">★ 推荐</div>
|
||||
|
||||
<div class="member-dialog__order-plan-info">
|
||||
<div class="dflex">
|
||||
<div class="member-dialog__order-plan-name">{{ plan.name }}</div>
|
||||
<!-- 选中圆圈 -->
|
||||
<div class="member-dialog__order-plan-radio">
|
||||
<div v-if="selectedPlan === plan.key" class="member-dialog__order-plan-radio-inner"></div>
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
|
||||
<path d="M14 8c0 3.3-2.7 6-6 6s-6-2.7-6-6 2.7-6 6-6 6 2.7 6 6z" fill="#07C160"/>
|
||||
<path d="M5.5 6.5c.5-.3 1.2-.1 1.5.4l.5 1 1.5-2c.3-.5 1-.6 1.5-.3.5.3.6 1 .3 1.5l-2.5 3.5c-.2.3-.5.4-.8.4-.3 0-.6-.1-.8-.4L5.2 8c-.3-.5-.2-1.2.3-1.5z" fill="white"/>
|
||||
</svg>
|
||||
<span class="member-dialog__payment-text member-dialog__payment-text--wechat">微信支付</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="member-dialog__order-plan-price">
|
||||
<span class="member-dialog__order-plan-price-symbol">¥</span>
|
||||
<span class="member-dialog__order-plan-price-num">{{ plan.price }}</span>
|
||||
<span class="member-dialog__order-plan-price-unit">/{{ plan.unit }}</span>
|
||||
</div>
|
||||
<div class="member-dialog__order-plan-desc">{{ plan.orderDesc }}</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 支付方式 -->
|
||||
<div class="member-dialog__order-payment">
|
||||
<h3 class="member-dialog__order-payment-title">支付方式</h3>
|
||||
<div class="member-dialog__order-payment-methods">
|
||||
<div
|
||||
v-for="method in paymentMethods"
|
||||
:key="method.key"
|
||||
class="member-dialog__order-payment-item"
|
||||
:class="{ 'member-dialog__order-payment-item--active': selectedPayment === method.key }"
|
||||
@click="selectedPayment = method.key"
|
||||
class="member-dialog__payment-item"
|
||||
:class="{ 'member-dialog__payment-item--alipay-active': selectedPayment === 'alipay' }"
|
||||
@click="selectedPayment = 'alipay'"
|
||||
>
|
||||
<span class="member-dialog__order-payment-icon">{{ method.icon }}</span>
|
||||
<span class="member-dialog__order-payment-name">{{ method.name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 支付提示 -->
|
||||
<div class="member-dialog__order-payment-hint">
|
||||
点击右侧"立即开启求职加速"后,将弹出二维码完成支付。
|
||||
</div>
|
||||
<div class="member-dialog__order-payment-safe">支付信息将通过安全通道加密处理,请放心支付。</div>
|
||||
</div>
|
||||
|
||||
<!-- 底部保障标签 -->
|
||||
<div class="member-dialog__order-guarantees">
|
||||
<span class="member-dialog__order-guarantee-item">🔒 安全支付</span>
|
||||
<span class="member-dialog__order-guarantee-item">⚡ 立即解锁</span>
|
||||
<span class="member-dialog__order-guarantee-item">⏰ 支持取消续费</span>
|
||||
<span class="member-dialog__order-guarantee-item">🎧 客服支持</span>
|
||||
</div>
|
||||
|
||||
<!-- 常见问题 -->
|
||||
<div class="member-dialog__order-faq">
|
||||
<h3 class="member-dialog__order-faq-title">常见问题</h3>
|
||||
<div
|
||||
v-for="faq in faqList"
|
||||
:key="faq.question"
|
||||
class="member-dialog__order-faq-item"
|
||||
@click="faq.open = !faq.open"
|
||||
>
|
||||
<div class="member-dialog__order-faq-question">
|
||||
<span>{{ faq.question }}</span>
|
||||
<span class="member-dialog__order-faq-arrow" :class="{ 'member-dialog__order-faq-arrow--open': faq.open }">›</span>
|
||||
</div>
|
||||
<div v-if="faq.open" class="member-dialog__order-faq-answer">{{ faq.answer }}</div>
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
|
||||
<rect width="16" height="16" rx="3" fill="#1677FF"/>
|
||||
<text x="4" y="12" fill="white" font-size="10" font-weight="bold">支</text>
|
||||
</svg>
|
||||
<span class="member-dialog__payment-text member-dialog__payment-text--alipay">支付宝支付</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧订单摘要 -->
|
||||
<div class="member-dialog__order-right">
|
||||
<div class="member-dialog__order-summary">
|
||||
<h3 class="member-dialog__order-summary-title">订单摘要</h3>
|
||||
<!-- 套餐信息 -->
|
||||
<div class="member-dialog__order-summary-row">
|
||||
<div>
|
||||
<div class="member-dialog__order-summary-plan">{{ currentPlan.name }}</div>
|
||||
<div class="member-dialog__order-summary-duration">{{ currentPlan.unit }}</div>
|
||||
</div>
|
||||
<span class="member-dialog__order-summary-price">¥{{ currentPlan.price }}</span>
|
||||
</div>
|
||||
<!-- 分割线 -->
|
||||
<div class="member-dialog__order-summary-divider"></div>
|
||||
<!-- 今日支付 -->
|
||||
<div class="member-dialog__order-summary-row">
|
||||
<span class="member-dialog__order-summary-label">今日支付</span>
|
||||
<span class="member-dialog__order-summary-total">¥{{ currentPlan.price }}</span>
|
||||
</div>
|
||||
<!-- 协议勾选 -->
|
||||
<div class="member-dialog__order-summary-agree">
|
||||
<label class="member-dialog__order-checkbox">
|
||||
<input type="checkbox" v-model="agreeProtocol" />
|
||||
<span class="member-dialog__order-checkbox-mark"></span>
|
||||
</label>
|
||||
<span>我已阅读并同意 <a href="javascript:;" @click.prevent="openMemberAgreement">《会员服务协议》</a> </span>
|
||||
</div>
|
||||
<!-- 立即开启按钮 -->
|
||||
<!-- 购买按钮 -->
|
||||
<button
|
||||
class="member-dialog__order-submit-btn"
|
||||
:disabled="!agreeProtocol"
|
||||
@click="handleShowQrCode"
|
||||
>立即开启求职加速</button>
|
||||
<div class="member-dialog__order-submit-tip">支付成功后立即解锁全部权益,可随时取消续费。</div>
|
||||
class="member-dialog__buy-btn"
|
||||
:class="{ 'member-dialog__buy-btn--active': selectedPayment }"
|
||||
@click="handleBuy"
|
||||
>购买会员</button>
|
||||
|
||||
<!-- 你将获得 -->
|
||||
<div class="member-dialog__order-benefits">
|
||||
<h4 class="member-dialog__order-benefits-title">你将获得</h4>
|
||||
<div v-for="item in orderBenefits" :key="item" class="member-dialog__order-benefits-item">
|
||||
<span class="member-dialog__order-benefits-check">✓</span>
|
||||
<span>{{ item }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ==================== 下单步骤二:确认支付结果 ==================== -->
|
||||
<div v-else-if="currentView === 'order2'" class="member-dialog__order">
|
||||
<div class="member-dialog__order-header">
|
||||
<span class="member-dialog__order-back"></span>
|
||||
<div class="member-dialog__steps">
|
||||
<div class="member-dialog__step member-dialog__step--done">
|
||||
<span class="member-dialog__step-num">✓</span>
|
||||
<span class="member-dialog__step-label">选择套餐</span>
|
||||
</div>
|
||||
<div class="member-dialog__step-line member-dialog__step-line--done"></div>
|
||||
<div class="member-dialog__step member-dialog__step--active">
|
||||
<span class="member-dialog__step-num">2</span>
|
||||
<span class="member-dialog__step-label">支付方式</span>
|
||||
</div>
|
||||
<div class="member-dialog__step-line"></div>
|
||||
<div class="member-dialog__step">
|
||||
<span class="member-dialog__step-num">3</span>
|
||||
<span class="member-dialog__step-label">完成</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 确认中内容 -->
|
||||
<div class="member-dialog__order-confirming">
|
||||
<div class="member-dialog__order-confirming-card">
|
||||
<!-- 加载动画 -->
|
||||
<div class="member-dialog__order-loading-spinner"></div>
|
||||
<h2 class="member-dialog__order-confirming-title">正在确认支付结果</h2>
|
||||
<p class="member-dialog__order-confirming-desc">请稍候,请勿关闭此页面。</p>
|
||||
<!-- ==================== 扫码支付弹窗 ==================== -->
|
||||
<div v-if="showQrCode" class="member-dialog-overlay member-dialog-overlay--qrcode" @click="handleCloseQrCode">
|
||||
<div class="member-dialog member-dialog--qrcode" @click.stop>
|
||||
|
||||
<!-- 顶部标题行 -->
|
||||
<div class="member-dialog__header">
|
||||
<div class="member-dialog__header-left">
|
||||
<h2 class="member-dialog__title">扫码支付</h2>
|
||||
</div>
|
||||
<div class="member-dialog__header-right">
|
||||
<span class="member-dialog__close" @click="handleCloseQrCode">✕</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ==================== 下单步骤三:支付完成 ==================== -->
|
||||
<div v-else-if="currentView === 'order3'" class="member-dialog__order">
|
||||
<div class="member-dialog__order-header">
|
||||
<span class="member-dialog__order-back"></span>
|
||||
<div class="member-dialog__steps">
|
||||
<div class="member-dialog__step member-dialog__step--done">
|
||||
<span class="member-dialog__step-num">✓</span>
|
||||
<span class="member-dialog__step-label">选择套餐</span>
|
||||
</div>
|
||||
<div class="member-dialog__step-line member-dialog__step-line--done"></div>
|
||||
<div class="member-dialog__step member-dialog__step--done">
|
||||
<span class="member-dialog__step-num">✓</span>
|
||||
<span class="member-dialog__step-label">支付方式</span>
|
||||
</div>
|
||||
<div class="member-dialog__step-line member-dialog__step-line--done"></div>
|
||||
<div class="member-dialog__step member-dialog__step--active">
|
||||
<span class="member-dialog__step-num">3</span>
|
||||
<span class="member-dialog__step-label">完成</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 支付成功内容 -->
|
||||
<div class="member-dialog__order-success">
|
||||
<div class="member-dialog__order-success-card">
|
||||
<!-- 成功图标 -->
|
||||
<div class="member-dialog__order-success-icon">✓</div>
|
||||
<h2 class="member-dialog__order-success-title">支付成功,求职加速已开启</h2>
|
||||
<p class="member-dialog__order-success-desc">你已成功解锁 AI 求职加速权益,现在可以开始优化简历、匹配岗位并准备面试。</p>
|
||||
<button class="member-dialog__order-success-btn" @click="handleGoResume">开始优化我的简历</button>
|
||||
<button class="member-dialog__order-success-btn-secondary" @click="handleViewMemberBenefits">查看会员权益</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ==================== 付款弹窗 ==================== -->
|
||||
<div v-if="showQrCode" class="member-dialog__qrcode-overlay" @click="handleCloseQrCode">
|
||||
<div class="member-dialog__qrcode-modal" @click.stop>
|
||||
<!-- 关闭按钮 -->
|
||||
<span class="member-dialog__qrcode-close" @click="handleCloseQrCode">✕</span>
|
||||
<!-- 支付方式标题 -->
|
||||
<div class="member-dialog__qrcode-title">
|
||||
<span class="member-dialog__qrcode-title-icon">{{ selectedPayment === 'wechat' ? '💬' : '🔷' }}</span>
|
||||
<span>{{ selectedPayment === 'wechat' ? '微信支付' : '支付宝' }}</span>
|
||||
</div>
|
||||
<!-- 扫码提示 -->
|
||||
<h3 class="member-dialog__qrcode-subtitle">{{ selectedPayment === 'alipay' ? '扫码完成支付' : '扫码完成支付' }}</h3>
|
||||
<p class="member-dialog__qrcode-desc">{{ selectedPayment === 'alipay' ? '请在支付宝页面完成支付,完成后点击下方按钮确认。' : '请使用微信 App 扫描二维码完成支付,完成后此窗口会自动关闭。' }}</p>
|
||||
<!-- 二维码区域 -->
|
||||
<div class="member-dialog__qrcode-content">
|
||||
<!-- 支付宝:iframe 渲染支付表单 -->
|
||||
<div v-if="selectedPayment === 'alipay' && paymentFormHtml" class="member-dialog__qrcode-image">
|
||||
<iframe
|
||||
@@ -357,10 +163,21 @@
|
||||
scrolling="no"
|
||||
></iframe>
|
||||
</div>
|
||||
<!-- 金额 -->
|
||||
<div class="member-dialog__qrcode-amount">¥{{ currentPlan.price }}</div>
|
||||
|
||||
<!-- 金额和提示 -->
|
||||
<div class="member-dialog__qrcode-price-row">
|
||||
<span class="member-dialog__qrcode-price">¥{{ currentPlan.priceInt }}</span>
|
||||
<span class="member-dialog__qrcode-tip">扫码完成支付</span>
|
||||
</div>
|
||||
|
||||
<!-- 协议提示 -->
|
||||
<p class="member-dialog__qrcode-agreement">
|
||||
开通即代表同意 <a href="javascript:;" @click.prevent="openMemberAgreement">《会员服务协议》</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 已支付成功按钮 -->
|
||||
<button class="member-dialog__paid-btn" @click="handleConfirmPaid">已支付成功</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -370,9 +187,9 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch, reactive, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ref, computed, watch, onMounted } from 'vue'
|
||||
import { useStore } from 'vuex'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { fetchMemberProductList, createMemberOrder, fetchOrderDetail, type MemberProduct } from '@/api/member'
|
||||
import AgreementPreviewDialog from '@/components/tools/AgreementPreviewDialog.vue'
|
||||
|
||||
@@ -382,7 +199,6 @@ const props = defineProps<{ modelValue: boolean }>()
|
||||
/** 组件 Emits — 通知父组件更新 modelValue */
|
||||
const emit = defineEmits<{ (e: 'update:modelValue', value: boolean): void }>()
|
||||
|
||||
const router = useRouter()
|
||||
const store = useStore()
|
||||
|
||||
// ==================== 类型定义 ====================
|
||||
@@ -391,46 +207,27 @@ const store = useStore()
|
||||
interface PlanItem {
|
||||
key: string
|
||||
name: string
|
||||
price: string
|
||||
unit: string
|
||||
tag?: string
|
||||
/** 整数价格(元),用于显示 */
|
||||
priceInt: string
|
||||
/** 划线价(元) */
|
||||
originalPrice: string
|
||||
/** 每日价格描述 */
|
||||
dailyDesc: string
|
||||
/** 是否推荐 */
|
||||
recommend?: boolean
|
||||
perMonth?: string
|
||||
btnText: string
|
||||
orderDesc: string
|
||||
/** 后端原始数据,下单时使用 */
|
||||
raw: MemberProduct
|
||||
}
|
||||
|
||||
/** 支付方式类型 */
|
||||
interface PaymentMethod {
|
||||
key: string
|
||||
name: string
|
||||
icon: string
|
||||
}
|
||||
|
||||
/** FAQ 项类型 */
|
||||
interface FaqItem {
|
||||
question: string
|
||||
answer: string
|
||||
open: boolean
|
||||
}
|
||||
|
||||
// ==================== 状态 ====================
|
||||
|
||||
/** 当前视图(intro1/intro2/order1/order2/order3) */
|
||||
const currentView = ref<'intro1' | 'intro2' | 'order1' | 'order2' | 'order3'>('intro2')
|
||||
/** 当前选中的套餐 key */
|
||||
const selectedPlan = ref('')
|
||||
|
||||
/** 当前选中的套餐 */
|
||||
const selectedPlan = ref('quarterly')
|
||||
/** 当前选中的支付方式(wechat / alipay),默认不选 */
|
||||
const selectedPayment = ref('')
|
||||
|
||||
/** 当前选中的支付方式 */
|
||||
const selectedPayment = ref('wechat')
|
||||
|
||||
/** 是否同意协议 */
|
||||
const agreeProtocol = ref(false)
|
||||
|
||||
/** 是否显示二维码弹窗 */
|
||||
/** 是否显示扫码支付弹窗 */
|
||||
const showQrCode = ref(false)
|
||||
|
||||
/** 支付宝表单 HTML(用于 iframe 渲染) */
|
||||
@@ -442,38 +239,29 @@ const wechatCodeUrl = ref('')
|
||||
/** 微信二维码 iframe 的 srcdoc HTML */
|
||||
const wechatQrHtml = computed(() => {
|
||||
if (!wechatCodeUrl.value) return ''
|
||||
return `<!DOCTYPE html><html><head><meta charset="UTF-8"><script src="https://cdn.jsdelivr.net/npm/qrcodejs@1.0.0/qrcode.min.js"><\/script><style>body{display:flex;align-items:center;justify-content:center;margin:0;height:100%;}</style></head><body><div id="qrcode"></div><script>new QRCode(document.getElementById("qrcode"),{text:"${wechatCodeUrl.value}",width:200,height:200,colorDark:"#000000",colorLight:"#ffffff",correctLevel:QRCode.CorrectLevel.H});<\/script></body></html>`
|
||||
return `<!DOCTYPE html><html><head><meta charset="UTF-8"><script src="https://cdn.jsdelivr.net/npm/qrcodejs@1.0.0/qrcode.min.js"><\/script><style>body{display:flex;align-items:center;justify-content:center;margin:0;height:100%;}</style></head><body><div id="qrcode"></div><script>new QRCode(document.getElementById("qrcode"),{text:"${wechatCodeUrl.value}",width:210,height:210,colorDark:"#000000",colorLight:"#ffffff",correctLevel:QRCode.CorrectLevel.H});<\/script></body></html>`
|
||||
})
|
||||
|
||||
/** 当前订单ID(用于后续轮询支付状态) */
|
||||
/** 当前订单ID */
|
||||
const orderId = ref('')
|
||||
|
||||
/** 是否正在创建订单 */
|
||||
const creatingOrder = ref(false)
|
||||
|
||||
/** 协议预览弹窗显示状态 */
|
||||
const showAgreementDialog = ref(false)
|
||||
|
||||
// ==================== 监听 ====================
|
||||
|
||||
/** 监听弹窗开关 — 打开时锁定背景滚动,关闭时恢复并重置状态 */
|
||||
watch(() => props.modelValue, (val) => {
|
||||
document.body.style.overflow = val ? 'hidden' : ''
|
||||
if (!val) {
|
||||
currentView.value = 'intro2'
|
||||
showQrCode.value = false
|
||||
agreeProtocol.value = false
|
||||
selectedPayment.value = ''
|
||||
paymentFormHtml.value = ''
|
||||
wechatCodeUrl.value = ''
|
||||
orderId.value = ''
|
||||
// 清除轮询计时器
|
||||
if (pollTimer) {
|
||||
clearInterval(pollTimer)
|
||||
pollTimer = null
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
/** 监听二维码弹窗关闭 — 无论何种方式关闭都停止轮询 */
|
||||
watch(showQrCode, (val) => {
|
||||
if (!val && pollTimer) {
|
||||
clearInterval(pollTimer)
|
||||
pollTimer = null
|
||||
}
|
||||
})
|
||||
|
||||
@@ -481,37 +269,54 @@ watch(showQrCode, (val) => {
|
||||
|
||||
/** 当前选中的套餐对象 */
|
||||
const currentPlan = computed(() => {
|
||||
return plans.value.find(p => p.key === selectedPlan.value) || plans.value[0]
|
||||
return plans.value.find(p => p.key === selectedPlan.value) || plans.value[0] || { priceInt: '0', name: '', key: '', originalPrice: '', dailyDesc: '', raw: {} as MemberProduct }
|
||||
})
|
||||
|
||||
// ==================== 常量数据 ====================
|
||||
// ==================== 数据 ====================
|
||||
|
||||
/** 套餐列表(从接口获取) */
|
||||
const plans = ref<PlanItem[]>([])
|
||||
|
||||
/** 根据有效天数生成单位描述 */
|
||||
function getDurationUnit(days: number): string {
|
||||
if (days <= 7) return '周'
|
||||
if (days <= 31) return '月'
|
||||
if (days <= 93) return '3个月'
|
||||
if (days <= 186) return '半年'
|
||||
return '年'
|
||||
}
|
||||
|
||||
/** 将后端商品数据转换为前端套餐项 */
|
||||
function mapProductToPlan(product: MemberProduct): PlanItem {
|
||||
const priceYuan = (product.price / 100).toFixed(2)
|
||||
const monthlyPriceYuan = product.monthlyPrice ? (product.monthlyPrice / 100).toFixed(2) : undefined
|
||||
// 分转元,直接除以100不四舍五入
|
||||
const priceYuan = product.price / 100
|
||||
const originalPriceYuan = product.originalPrice ? product.originalPrice / 100 : 0
|
||||
// 直接使用接口返回的 productName 作为套餐名称
|
||||
const name = product.productName
|
||||
// 根据天数计算每日价格描述
|
||||
let dailyDesc = '轻量体验'
|
||||
if (product.durationDays <= 7) {
|
||||
dailyDesc = '轻量体验'
|
||||
} else if (product.durationDays <= 31) {
|
||||
// 月卡除以30天,四舍五入到小数点后2位
|
||||
const dailyPrice = (priceYuan / 30).toFixed(2)
|
||||
dailyDesc = `低至 ¥${dailyPrice} /天`
|
||||
} else if (product.durationDays <= 93) {
|
||||
// 季卡除以90天,四舍五入到小数点后2位
|
||||
const dailyPrice = (priceYuan / 90).toFixed(2)
|
||||
dailyDesc = `低至 ¥${dailyPrice} /天`
|
||||
} else if (product.durationDays <= 186) {
|
||||
const dailyPrice = (priceYuan / 180).toFixed(2)
|
||||
dailyDesc = `低至 ¥${dailyPrice} /天`
|
||||
} else {
|
||||
const dailyPrice = (priceYuan / 365).toFixed(2)
|
||||
dailyDesc = `低至 ¥${dailyPrice} /天`
|
||||
}
|
||||
|
||||
// 价格显示:如果是整数就不显示小数,否则保留实际小数
|
||||
const priceDisplay = Number.isInteger(priceYuan) ? String(priceYuan) : priceYuan.toFixed(2)
|
||||
const originalDisplay = originalPriceYuan > priceYuan
|
||||
? `¥${Number.isInteger(originalPriceYuan) ? originalPriceYuan : originalPriceYuan.toFixed(2)}`
|
||||
: ''
|
||||
|
||||
return {
|
||||
key: String(product.id),
|
||||
name: product.productName,
|
||||
price: priceYuan,
|
||||
unit: getDurationUnit(product.durationDays),
|
||||
tag: product.tag || undefined,
|
||||
name,
|
||||
priceInt: priceDisplay,
|
||||
originalPrice: originalDisplay,
|
||||
dailyDesc,
|
||||
recommend: product.isFeatured === 1,
|
||||
perMonth: monthlyPriceYuan,
|
||||
btnText: product.buyButtonText || '立即开通',
|
||||
orderDesc: product.tag || product.productName,
|
||||
raw: product,
|
||||
}
|
||||
}
|
||||
@@ -521,10 +326,9 @@ async function loadMemberProducts() {
|
||||
try {
|
||||
const res = await fetchMemberProductList()
|
||||
if (res.data && res.data.length) {
|
||||
// 按 sortOrder 排序
|
||||
const sorted = [...res.data].sort((a, b) => a.sortOrder - b.sortOrder)
|
||||
plans.value = sorted.map(mapProductToPlan)
|
||||
// 默认选中主推套餐,若无则选第一个
|
||||
// 默认选中推荐套餐(季卡),若无则选第一个
|
||||
const featured = plans.value.find(p => p.recommend)
|
||||
selectedPlan.value = featured ? featured.key : plans.value[0].key
|
||||
}
|
||||
@@ -538,84 +342,37 @@ onMounted(() => {
|
||||
loadMemberProducts()
|
||||
})
|
||||
|
||||
/** 求职加速能力列表 */
|
||||
const abilities = [
|
||||
{ icon: '◉', color: '#4FC2C9', text: '个性化岗位匹配,量大极速又精准' },
|
||||
{ icon: '◉', color: '#4FC2C9', text: '针对岗位 JD 自动优化简历,提高简历通过率' },
|
||||
{ icon: '⚡', color: '#4FC2C9', text: '自动填写申请表,每次投递节省 10-15 分钟' },
|
||||
{ icon: '↗', color: '#E85635', text: '内推码,提高简历被 HR 看到的概率' },
|
||||
{ icon: '⏰', color: '#777777', text: '第一时间提醒高匹配岗位,抢占面试机会' },
|
||||
]
|
||||
|
||||
/** 会员能力对比数据 */
|
||||
const compareData = [
|
||||
{ name: 'AI 岗位匹配', free: '免费 3 天', pro: '不限次数' },
|
||||
{ name: '简历针对性优化', free: '免费 3 天', pro: '不限次数' },
|
||||
{ name: '自动填写网申', free: '免费 3 天', pro: '不限次数' },
|
||||
{ name: '内推码获取', free: '免费 3 天', pro: '不限次数' },
|
||||
{ name: '高匹配岗位推送', free: '免费 3 天', pro: '不限次数' },
|
||||
]
|
||||
|
||||
/** 支付方式列表 */
|
||||
const paymentMethods: PaymentMethod[] = [
|
||||
{ key: 'wechat', name: '微信支付', icon: '💬' },
|
||||
{ key: 'alipay', name: '支付宝', icon: '🔷' },
|
||||
]
|
||||
|
||||
/** 订单权益列表 */
|
||||
const orderBenefits = [
|
||||
'AI 简历深度优化',
|
||||
'智能岗位匹配分析',
|
||||
'求职信快速生成',
|
||||
'面试准备指导',
|
||||
'投递进度追踪',
|
||||
'求职策略建议',
|
||||
]
|
||||
|
||||
/** 常见问题列表 */
|
||||
const faqList = reactive<FaqItem[]>([
|
||||
{ question: '支付后如何开通?', answer: '支付成功后立即生效,无需等待,登录账号即可使用全部会员权益。', open: true },
|
||||
{ question: '可以随时取消吗?', answer: '可以,您可以在设置中随时取消自动续费,当前周期内的权益仍然有效。', open: false },
|
||||
{ question: '支持哪些支付方式?', answer: '目前支持微信支付和支付宝两种支付方式。', open: false },
|
||||
{ question: '可以开发票吗?', answer: '支持开具电子发票,请在支付完成后联系客服申请。', open: false },
|
||||
])
|
||||
|
||||
// ==================== 事件处理 ====================
|
||||
|
||||
/** 从介绍页点击升级按钮 — 进入下单步骤一 */
|
||||
function handleUpgrade(plan: PlanItem) {
|
||||
selectedPlan.value = plan.key
|
||||
currentView.value = 'order1'
|
||||
}
|
||||
|
||||
/** 点击立即开启求职加速 — 调用创建订单接口并显示支付弹窗 */
|
||||
async function handleShowQrCode() {
|
||||
if (!agreeProtocol.value || creatingOrder.value) return
|
||||
/** 点击购买会员按钮 */
|
||||
async function handleBuy() {
|
||||
if (!selectedPayment.value) {
|
||||
ElMessage.warning('请选择付款方式')
|
||||
return
|
||||
}
|
||||
if (creatingOrder.value) return
|
||||
const plan = currentPlan.value
|
||||
if (!plan) return
|
||||
if (!plan || !plan.raw) return
|
||||
|
||||
creatingOrder.value = true
|
||||
try {
|
||||
// 支付渠道:1=微信 2=支付宝
|
||||
const payChannel = selectedPayment.value === 'alipay' ? 2 : 1
|
||||
const res = await createMemberOrder({
|
||||
productId: plan.key, // key 即为商品 id(字符串,避免大数精度丢失)
|
||||
productId: plan.key,
|
||||
payChannel,
|
||||
})
|
||||
if (res.data) {
|
||||
orderId.value = String(res.data.orderId)
|
||||
if (selectedPayment.value === 'wechat') {
|
||||
// 微信支付 — payData 为 code_url,用于生成二维码
|
||||
wechatCodeUrl.value = res.data.payData
|
||||
paymentFormHtml.value = ''
|
||||
} else {
|
||||
// 支付宝 — payData 为 HTML 表单
|
||||
paymentFormHtml.value = res.data.payData
|
||||
wechatCodeUrl.value = ''
|
||||
}
|
||||
showQrCode.value = true
|
||||
// 创建订单成功后立即开始轮询支付状态
|
||||
confirmPayment()
|
||||
// 【注释掉自动轮询,后续如需恢复取消注释即可】
|
||||
// confirmPaymentPolling()
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('创建订单失败', e)
|
||||
@@ -624,80 +381,80 @@ async function handleShowQrCode() {
|
||||
}
|
||||
}
|
||||
|
||||
/** 轮询计时器 */
|
||||
let pollTimer: ReturnType<typeof setInterval> | null = null
|
||||
// /** 轮询计时器 */
|
||||
// let pollTimer: ReturnType<typeof setInterval> | null = null
|
||||
|
||||
/** 轮询查询订单支付状态 */
|
||||
async function confirmPayment() {
|
||||
const startTime = Date.now()
|
||||
/** 超时时间:5 分钟 */
|
||||
const timeout = 5 * 60 * 1000
|
||||
/** 轮询间隔:500ms */
|
||||
const interval = 500
|
||||
// /**
|
||||
// * 自动轮询查询订单支付状态(已关闭,保留逻辑防止后续要用)
|
||||
// * 支付成功后自动关闭弹窗并刷新会员状态
|
||||
// */
|
||||
// async function confirmPaymentPolling() {
|
||||
// const startTime = Date.now()
|
||||
// const timeout = 5 * 60 * 1000 // 5分钟超时
|
||||
// const interval = 500
|
||||
//
|
||||
// if (pollTimer) {
|
||||
// clearInterval(pollTimer)
|
||||
// pollTimer = null
|
||||
// }
|
||||
//
|
||||
// pollTimer = setInterval(async () => {
|
||||
// try {
|
||||
// const res = await fetchOrderDetail(orderId.value)
|
||||
// const status = res.data?.status
|
||||
//
|
||||
// if (status === 1) {
|
||||
// clearInterval(pollTimer!)
|
||||
// pollTimer = null
|
||||
// showQrCode.value = false
|
||||
// ElMessage.success('开通成功')
|
||||
// emit('update:modelValue', false)
|
||||
// store.dispatch('loadMemberStatus')
|
||||
// } else if (status === 2 || status === 3) {
|
||||
// clearInterval(pollTimer!)
|
||||
// pollTimer = null
|
||||
// showQrCode.value = false
|
||||
// ElMessage.error('支付未成功,请重新下单')
|
||||
// } else if (Date.now() - startTime >= timeout) {
|
||||
// clearInterval(pollTimer!)
|
||||
// pollTimer = null
|
||||
// showQrCode.value = false
|
||||
// ElMessage.error('支付超时,请重新下单')
|
||||
// }
|
||||
// } catch (e) {
|
||||
// console.error('查询订单状态失败', e)
|
||||
// }
|
||||
// }, interval)
|
||||
// }
|
||||
|
||||
// 清除可能存在的旧计时器
|
||||
if (pollTimer) {
|
||||
clearInterval(pollTimer)
|
||||
pollTimer = null
|
||||
}
|
||||
|
||||
pollTimer = setInterval(async () => {
|
||||
/** 点击"已支付成功"按钮 — 手动查询一次订单状态 */
|
||||
async function handleConfirmPaid() {
|
||||
if (!orderId.value) return
|
||||
try {
|
||||
const res = await fetchOrderDetail(orderId.value)
|
||||
const status = res.data?.status
|
||||
|
||||
if (status === 1) {
|
||||
// 支付成功 — 关闭二维码弹窗,进入完成步骤
|
||||
clearInterval(pollTimer!)
|
||||
pollTimer = null
|
||||
// 支付成功 — 关闭弹窗,刷新会员状态
|
||||
showQrCode.value = false
|
||||
currentView.value = 'order3'
|
||||
} else if (status === 2 || status === 3) {
|
||||
// 已退款或已关闭 — 提示并回到选择套餐步骤
|
||||
clearInterval(pollTimer!)
|
||||
pollTimer = null
|
||||
showQrCode.value = false
|
||||
ElMessage.error('支付未成功,请重新下单')
|
||||
currentView.value = 'order1'
|
||||
} else if (Date.now() - startTime >= timeout) {
|
||||
// 超时 — 提示并回到选择套餐步骤
|
||||
clearInterval(pollTimer!)
|
||||
pollTimer = null
|
||||
showQrCode.value = false
|
||||
ElMessage.error('支付未成功,请重新下单')
|
||||
currentView.value = 'order1'
|
||||
ElMessage.success('开通成功')
|
||||
emit('update:modelValue', false)
|
||||
// 刷新 vuex 中的会员状态
|
||||
store.dispatch('loadMemberStatus')
|
||||
} else {
|
||||
// 未支付成功
|
||||
ElMessage.warning('未查询到支付订单,请再次确认')
|
||||
}
|
||||
// status === 0 继续等待
|
||||
} catch (e) {
|
||||
console.error('查询订单状态失败', e)
|
||||
ElMessage.error('查询失败,请稍后重试')
|
||||
}
|
||||
}, interval)
|
||||
}
|
||||
|
||||
/** 支付成功后跳转简历页 */
|
||||
function handleGoResume() {
|
||||
router.push('/resume')
|
||||
}
|
||||
|
||||
/** 关闭二维码弹窗 — 停止轮询 */
|
||||
/** 关闭扫码支付弹窗 */
|
||||
function handleCloseQrCode() {
|
||||
showQrCode.value = false
|
||||
if (pollTimer) {
|
||||
clearInterval(pollTimer)
|
||||
pollTimer = null
|
||||
}
|
||||
}
|
||||
|
||||
/** 查看会员权益 — 关闭会员弹窗,打开设置弹窗的会员 Tab */
|
||||
function handleViewMemberBenefits() {
|
||||
emit('update:modelValue', false)
|
||||
store.commit('SET_SETTINGS_TAB', 'member')
|
||||
store.commit('SET_SHOW_SETTINGS', true)
|
||||
}
|
||||
|
||||
/** 协议预览弹窗显示状态 */
|
||||
const showAgreementDialog = ref(false)
|
||||
|
||||
/** 打开会员服务协议 */
|
||||
function openMemberAgreement() {
|
||||
showAgreementDialog.value = true
|
||||
|
||||
@@ -19,26 +19,9 @@
|
||||
:class="{ 'settings-dialog__nav-item--active': activeTab === tab.key }"
|
||||
@click="activeTab = tab.key"
|
||||
>
|
||||
<span class="settings-dialog__nav-icon">{{ tab.icon }}</span>
|
||||
<span>{{ tab.label }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 底部操作区 — 隐私协议 + 退出登录 -->
|
||||
<div class="settings-dialog__bottom-actions">
|
||||
<!-- 用户隐私协议按钮 — 点击切换到隐私协议 tab -->
|
||||
<button
|
||||
class="settings-dialog__bottom-btn"
|
||||
:class="{ 'settings-dialog__bottom-btn--active': activeTab === 'privacy' }"
|
||||
@click="activeTab = 'privacy'"
|
||||
>
|
||||
用户隐私协议
|
||||
</button>
|
||||
<!-- 退出登录按钮 — 点击弹出确认弹窗 -->
|
||||
<button class="settings-dialog__bottom-btn settings-dialog__bottom-btn--danger" @click="showLogout = true">
|
||||
退出登录
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧内容区 — 根据 activeTab 切换显示 -->
|
||||
@@ -51,60 +34,70 @@
|
||||
<p class="settings-dialog__section-value">{{ userPhone }}</p>
|
||||
</div>
|
||||
<div class="settings-dialog__danger-section">
|
||||
<div>
|
||||
<div class="settings-dialog__danger-title">注销我的账号</div>
|
||||
<div class="settings-dialog__danger-row">
|
||||
<p class="settings-dialog__danger-desc">永久删除你的账号及所有相关数据</p>
|
||||
</div>
|
||||
</div>
|
||||
<button class="settings-dialog__danger-btn" @click="handleDeleteAccount">注销账号</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Tab: 会员 — 显示当前会员信息、订阅管理 -->
|
||||
<!-- Tab: 会员 — 根据会员类型显示不同卡片 -->
|
||||
<template v-if="activeTab === 'member'">
|
||||
<h2 class="settings-dialog__content-title">会员</h2>
|
||||
<div class="settings-dialog__section-label" style="margin-bottom: 0.12rem;">当前会员</div>
|
||||
<div class="settings-dialog__member-card">
|
||||
<div class="settings-dialog__member-header">
|
||||
<div class="settings-dialog__member-title-row">
|
||||
<span class="settings-dialog__member-name">正式会员</span>
|
||||
<span
|
||||
class="settings-dialog__member-badge"
|
||||
:class="{ 'settings-dialog__member-badge--inactive': !memberStatus.isMember }"
|
||||
>
|
||||
{{ memberStatus.isMember ? '已开通' : '未开通' }}
|
||||
</span>
|
||||
</div>
|
||||
<span class="settings-dialog__member-terms" @click="openAgreementDialog('ae8065i3')">会员条款</span>
|
||||
</div>
|
||||
<div class="settings-dialog__member-info-row">
|
||||
<!-- 已开通:显示到期时间和剩余天数 -->
|
||||
<span v-if="memberStatus.isMember" class="settings-dialog__member-price">
|
||||
<span class="settings-dialog__member-expire-line">到期时间:{{ memberExpireDateTime }}</span>
|
||||
<span class="settings-dialog__member-remain-line">剩余 {{ memberRemainDays }} 天</span>
|
||||
</span>
|
||||
<!-- 未开通:显示价格 -->
|
||||
<span v-else class="settings-dialog__member-price">
|
||||
¥19.99/月
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-dialog__member-issue">
|
||||
<!-- <div class="settings-dialog__member-issue-title">订阅状态异常?</div>-->
|
||||
<!-- <p class="settings-dialog__member-issue-desc">-->
|
||||
<!-- 如果你已经和完成了付款或更改了订阅但是没有看到最新状态,你可以尝试更新状态或联系我们获取帮助。-->
|
||||
<!-- </p>-->
|
||||
<!-- <div class="settings-dialog__member-issue-actions">-->
|
||||
<!-- <button class="settings-dialog__member-issue-btn" @click="handleRefreshStatus">更新状态</button>-->
|
||||
<!-- <button class="settings-dialog__member-issue-btn" @click="handleContactUs">联系我们</button>-->
|
||||
<!-- </div>-->
|
||||
|
||||
<!-- 正式会员卡片 -->
|
||||
<div v-if="memberStatus.isMember && memberStatus.memberType === 1" class="settings-dialog__mcard settings-dialog__mcard--formal">
|
||||
<div class="settings-dialog__mcard-top">
|
||||
<span class="settings-dialog__mcard-title">正式会员</span>
|
||||
<div class="settings-dialog__mcard-remain">
|
||||
<span class="settings-dialog__mcard-remain-label">剩余</span>
|
||||
<span class="settings-dialog__mcard-remain-days">{{ memberRemainDays }}天</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-dialog__mcard-bottom">
|
||||
<span class="settings-dialog__mcard-expire">有效期至{{ memberExpireDate }}</span>
|
||||
<div class="settings-dialog__mcard-actions">
|
||||
<button class="settings-dialog__mcard-btn settings-dialog__mcard-btn--outline" @click="openAgreementDialog('ae8065i3')">查看权益</button>
|
||||
<button class="settings-dialog__mcard-btn settings-dialog__mcard-btn--solid-formal" @click="handleOpenMember">续费会员</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 试用会员卡片 -->
|
||||
<div v-else-if="memberStatus.isMember && memberStatus.memberType === 2" class="settings-dialog__mcard settings-dialog__mcard--trial">
|
||||
<div class="settings-dialog__mcard-top">
|
||||
<span class="settings-dialog__mcard-title">试用会员</span>
|
||||
<div class="settings-dialog__mcard-remain">
|
||||
<span class="settings-dialog__mcard-remain-label">剩余</span>
|
||||
<span class="settings-dialog__mcard-remain-days">{{ memberRemainDays }}天</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-dialog__mcard-bottom">
|
||||
<span class="settings-dialog__mcard-expire">有效期至{{ memberExpireDate }}</span>
|
||||
<div class="settings-dialog__mcard-actions">
|
||||
<button class="settings-dialog__mcard-btn settings-dialog__mcard-btn--outline" @click="openAgreementDialog('ae8065i3')">查看权益</button>
|
||||
<button class="settings-dialog__mcard-btn settings-dialog__mcard-btn--solid-trial" @click="handleOpenMember">开通正式会员</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 免费用户卡片 -->
|
||||
<div v-else class="settings-dialog__mcard settings-dialog__mcard--free">
|
||||
<div class="settings-dialog__mcard-top">
|
||||
<span class="settings-dialog__mcard-title settings-dialog__mcard-title--free">免费用户</span>
|
||||
</div>
|
||||
<div class="settings-dialog__mcard-bottom">
|
||||
<span class="settings-dialog__mcard-expire settings-dialog__mcard-expire--free">仅享受免费功能</span>
|
||||
<div class="settings-dialog__mcard-actions">
|
||||
<button class="settings-dialog__mcard-btn settings-dialog__mcard-btn--solid-accent" @click="handleOpenMember">开通会员</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Tab: 岗位更新提醒 — 目标岗位、即时提醒开关、提醒频率 -->
|
||||
<template v-if="activeTab === 'reminder'">
|
||||
<!-- <h2 class="settings-dialog__content-title">岗位更新提醒</h2>-->
|
||||
<h2 class="settings-dialog__content-title">目标岗位设置</h2>
|
||||
<div class="settings-dialog__reminder-block">
|
||||
<div class="settings-dialog__reminder-block-title-row">
|
||||
@@ -167,6 +160,41 @@
|
||||
<!-- </div>-->
|
||||
<!-- </div>-->
|
||||
</template>
|
||||
|
||||
<!-- Tab: 问题反馈 — 选择问题类型 + 填写细节 + 提交 -->
|
||||
<template v-if="activeTab === 'feedback'">
|
||||
<h2 class="settings-dialog__content-title">问题反馈</h2>
|
||||
<div class="settings-dialog__feedback-wrap">
|
||||
<!-- 问题类型选择 -->
|
||||
<div class="settings-dialog__feedback-section">
|
||||
<div class="settings-dialog__feedback-label">请选择你反馈的问题类型</div>
|
||||
<div class="settings-dialog__feedback-options">
|
||||
<div
|
||||
v-for="(opt, index) in feedbackOptions"
|
||||
:key="index"
|
||||
class="settings-dialog__feedback-option"
|
||||
:class="{ 'settings-dialog__feedback-option--active': feedbackType === opt }"
|
||||
@click="feedbackType = opt; feedbackTypeIndex = index + 1"
|
||||
>
|
||||
{{ opt }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 问题细节 -->
|
||||
<div class="settings-dialog__feedback-section">
|
||||
<div class="settings-dialog__feedback-label">问题细节</div>
|
||||
<textarea
|
||||
v-model="feedbackDetail"
|
||||
class="settings-dialog__feedback-textarea"
|
||||
placeholder="请描述问题细节"
|
||||
rows="4"
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 提交按钮 -->
|
||||
<button class="settings-dialog__feedback-submit" @click="handleFeedbackSubmit">确认提交</button>
|
||||
</template>
|
||||
|
||||
<!-- Tab: 用户隐私协议 — 从接口获取并用 markdown-it 渲染 -->
|
||||
<template v-if="activeTab === 'privacy'">
|
||||
<h2 class="settings-dialog__content-title">用户隐私协议</h2>
|
||||
@@ -201,6 +229,9 @@
|
||||
|
||||
<!-- 协议预览弹窗 -->
|
||||
<AgreementPreviewDialog v-model="showAgreementDialog" :code="currentAgreementCode" />
|
||||
|
||||
<!-- 会员购买弹窗 -->
|
||||
<MemberDialog v-model="showMemberDialog" />
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
@@ -212,6 +243,7 @@ import { formatEmploymentType } from '@/stores/index'
|
||||
import { logout } from '@/api/auth'
|
||||
import { fetchMemberStatus, type MemberStatus } from '@/api/member'
|
||||
import { fetchAgreement } from '@/api/common'
|
||||
import { userFeedback } from '@/api/setting'
|
||||
import { timestampToLocalDateTime, timestampDiffDays } from '@/utils/time'
|
||||
import JobGoalDialog from './JobGoalDialog.vue'
|
||||
import { resolveRegionName } from '@/utils/region'
|
||||
@@ -219,6 +251,7 @@ import { resolveIndustryName } from '@/utils/industry'
|
||||
import { resolveJobCategoryName } from '@/utils/jobCategory'
|
||||
import SettingsDeleteAccountDialog from './SettingsDeleteAccountDialog.vue'
|
||||
import SettingsInviteDialog from './SettingsInviteDialog.vue'
|
||||
import MemberDialog from './MemberDialog.vue'
|
||||
import AgreementPreviewDialog from '@/components/tools/AgreementPreviewDialog.vue'
|
||||
// @ts-ignore
|
||||
import markdownit from 'markdown-it'
|
||||
@@ -238,9 +271,11 @@ const store = useStore()
|
||||
|
||||
/** 左侧导航 Tab 配置 */
|
||||
const tabs = [
|
||||
{ key: 'account', label: '账号与安全', icon: '👤' },
|
||||
{ key: 'member', label: '会员', icon: '🏅' },
|
||||
// { key: 'reminder', label: '目标岗位设置', icon: '🔔' },
|
||||
{ key: 'account', label: '账号与安全' },
|
||||
{ key: 'member', label: '会员' },
|
||||
{ key: 'feedback', label: '问题反馈' },
|
||||
// { key: 'reminder', label: '目标岗位设置' },
|
||||
{ key: 'privacy', label: '用户隐私协议' },
|
||||
]
|
||||
|
||||
/** 当前选中的 Tab */
|
||||
@@ -281,6 +316,18 @@ const showDeleteAccount = ref(false)
|
||||
/** 邀请注册弹窗显示状态 */
|
||||
const showInviteDialog = ref(false)
|
||||
|
||||
/** 会员购买弹窗显示状态 */
|
||||
const showMemberDialog = ref(false)
|
||||
|
||||
/** 问题反馈 — 选项列表 */
|
||||
const feedbackOptions = ['Bug反馈', '功能建议', '使用体验', '订阅及会员相关问题', '其他']
|
||||
/** 问题反馈 — 选中的类型文字 */
|
||||
const feedbackType = ref('')
|
||||
/** 问题反馈 — 选中的类型索引(传给接口) */
|
||||
const feedbackTypeIndex = ref(0)
|
||||
/** 问题反馈 — 问题细节 */
|
||||
const feedbackDetail = ref('')
|
||||
|
||||
/** 协议预览弹窗显示状态 */
|
||||
const showAgreementDialog = ref(false)
|
||||
/** 当前预览的协议码 */
|
||||
@@ -318,14 +365,16 @@ async function loadPrivacyAgreement() {
|
||||
/** 会员状态数据 */
|
||||
const memberStatus = reactive<MemberStatus>({
|
||||
isMember: false,
|
||||
memberType: undefined,
|
||||
expireTime: undefined,
|
||||
createTime: undefined,
|
||||
updateTime: undefined,
|
||||
})
|
||||
|
||||
/** 会员到期时间(格式化显示) */
|
||||
const memberExpireDateTime = computed(() => {
|
||||
return timestampToLocalDateTime(memberStatus.expireTime, 'returnMinute')
|
||||
/** 会员到期日期(格式:yyyy-MM-dd) */
|
||||
const memberExpireDate = computed(() => {
|
||||
// timestampToLocalDateTime returnDay 返回 yyyy/MM/dd,替换为 yyyy-MM-dd
|
||||
return timestampToLocalDateTime(memberStatus.expireTime, 'returnDay').replace(/\//g, '-')
|
||||
})
|
||||
|
||||
/** 会员剩余天数 */
|
||||
@@ -339,6 +388,7 @@ const loadMemberStatus = async () => {
|
||||
const res = await fetchMemberStatus()
|
||||
if (res.data) {
|
||||
memberStatus.isMember = res.data.isMember ?? false
|
||||
memberStatus.memberType = res.data.memberType
|
||||
memberStatus.expireTime = res.data.expireTime
|
||||
memberStatus.createTime = res.data.createTime
|
||||
memberStatus.updateTime = res.data.updateTime
|
||||
@@ -376,6 +426,31 @@ const handleEditTarget = () => {
|
||||
showGoalDialog.value = true
|
||||
}
|
||||
|
||||
/** 打开会员购买弹窗 */
|
||||
const handleOpenMember = () => {
|
||||
showMemberDialog.value = true
|
||||
}
|
||||
|
||||
/** 提交问题反馈 */
|
||||
const handleFeedbackSubmit = async () => {
|
||||
if (feedbackTypeIndex.value < 1 || feedbackDetail.value.trim() === '') {
|
||||
ElMessage.warning('请选择问题并填写反馈内容')
|
||||
return
|
||||
}
|
||||
try {
|
||||
await userFeedback({
|
||||
type: feedbackTypeIndex.value,
|
||||
content: feedbackDetail.value,
|
||||
})
|
||||
ElMessage.success('提交成功')
|
||||
feedbackType.value = ''
|
||||
feedbackDetail.value = ''
|
||||
feedbackTypeIndex.value = 0
|
||||
} catch {
|
||||
ElMessage.error('提交失败,请稍后重试')
|
||||
}
|
||||
}
|
||||
|
||||
/** 弹窗打开时加载求职意向数据和会员状态 */
|
||||
watch(() => props.modelValue, (val) => {
|
||||
if (val && store.state.isAuthenticated) {
|
||||
|
||||
@@ -3,54 +3,54 @@
|
||||
<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 class="settings-invite-dialog__header-text">
|
||||
<h2 class="settings-invite-dialog__title">邀请好友免费获取 7 天会员</h2>
|
||||
<p class="settings-invite-dialog__subtitle">好友通过你的链接注册并完成认证,得 3 天会员</p>
|
||||
</div>
|
||||
|
||||
<!-- 关闭按钮 -->
|
||||
<span class="settings-invite-dialog__close" @click="$emit('update:modelValue', false)">✕</span>
|
||||
|
||||
<!-- 内容区域 -->
|
||||
<div class="settings-invite-dialog__body">
|
||||
<!-- 标题 -->
|
||||
<h2 class="settings-invite-dialog__title">邀请好友免费获取 7 天会员</h2>
|
||||
<!-- 副标题 -->
|
||||
<p class="settings-invite-dialog__subtitle">好友通过你的链接注册并完成认证,得 3 天会员</p>
|
||||
</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>
|
||||
<span class="settings-invite-dialog__copy-btn" @click="handleCopy">复制</span>
|
||||
</div>
|
||||
|
||||
<!-- 步骤说明 -->
|
||||
<div class="settings-invite-dialog__step">
|
||||
<div class="settings-invite-dialog__step-circle">1</div>
|
||||
<!-- 邀请步骤区域 -->
|
||||
<div class="settings-invite-dialog__steps">
|
||||
<span class="settings-invite-dialog__steps-title">邀请步骤</span>
|
||||
<div class="settings-invite-dialog__steps-list">
|
||||
<!-- 步骤1 -->
|
||||
<div class="settings-invite-dialog__step-item">
|
||||
<span class="settings-invite-dialog__step-num">1</span>
|
||||
<span class="settings-invite-dialog__step-text">分享专属邀请链接给好友</span>
|
||||
</div>
|
||||
<div class="settings-invite-dialog__step">
|
||||
<div class="settings-invite-dialog__step-circle">2</div>
|
||||
<!-- 步骤2 -->
|
||||
<div class="settings-invite-dialog__step-item">
|
||||
<span class="settings-invite-dialog__step-num">2</span>
|
||||
<span class="settings-invite-dialog__step-text">好友完成注册并通过认证</span>
|
||||
</div>
|
||||
<div class="settings-invite-dialog__step">
|
||||
<div class="settings-invite-dialog__step-circle">3</div>
|
||||
<!-- 步骤3 -->
|
||||
<div class="settings-invite-dialog__step-item">
|
||||
<span class="settings-invite-dialog__step-num">3</span>
|
||||
<span class="settings-invite-dialog__step-text">获得 7 天会员奖励</span>
|
||||
</div>
|
||||
</div>
|
||||
</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>
|
||||
<span class="settings-invite-dialog__stat-line">已邀请:<em>{{ totalFriends }}人</em></span>
|
||||
<span class="settings-invite-dialog__stat-line">累计获得:<em>{{ totalDays }}天</em></span>
|
||||
</div>
|
||||
<button class="settings-invite-dialog__member-btn" @click="handleGoMember">
|
||||
<span>成为正式会员</span>
|
||||
<span class="settings-invite-dialog__member-btn-arrow">→</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+242
-75
@@ -24,72 +24,83 @@
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_151_276)">
|
||||
<path fill="currentColor" stroke="currentColor" stroke-width="0.2" d="M7.66664 2.16675C6.78388 2.16681 5.91411 2.37934 5.13081 2.7864C4.34751 3.19346 3.6737 3.78309 3.16631 4.50545C2.65891 5.22782 2.33285 6.0617 2.21567 6.93664C2.09849 7.81158 2.19362 8.70188 2.49305 9.5323C2.79247 10.3627 3.28738 11.1089 3.93596 11.7077C4.58454 12.3065 5.36773 12.7405 6.21936 12.9728C7.07099 13.2052 7.96603 13.2291 8.82886 13.0426C9.6917 12.8561 10.497 12.4647 11.1766 11.9014L12.962 13.6867C13.0568 13.7751 13.1821 13.8232 13.3117 13.8209C13.4412 13.8186 13.5648 13.7661 13.6564 13.6745C13.748 13.5829 13.8005 13.4593 13.8028 13.3298C13.805 13.2002 13.757 13.0749 13.6686 12.9801L11.8853 11.1961C12.5567 10.3936 12.9853 9.41624 13.1208 8.37872C13.2562 7.34121 13.0929 6.28658 12.65 5.33862C12.2071 4.39066 11.503 3.58871 10.6203 3.02688C9.73759 2.46506 8.71296 2.16667 7.66664 2.16675Z" />
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_151_276">
|
||||
<rect width="16" height="16" fill="none"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_151_281)">
|
||||
<path fill="currentColor" stroke="currentColor" stroke-width="0.2" d="M10.5 8.66675C10.5 8.53414 10.4473 8.40696 10.3536 8.31319C10.2598 8.21943 10.1326 8.16675 10 8.16675H6C5.86739 8.16675 5.74022 8.21943 5.64645 8.31319C5.55268 8.40696 5.5 8.53414 5.5 8.66675C5.5 8.79936 5.55268 8.92653 5.64645 9.0203C5.74022 9.11407 5.86739 9.16675 6 9.16675H10C10.1326 9.16675 10.2598 9.11407 10.3536 9.0203C10.4473 8.92653 10.5 8.79936 10.5 8.66675ZM10.5 11.3334C10.5 11.2008 10.4473 11.0736 10.3536 10.9799C10.2598 10.8861 10.1326 10.8334 10 10.8334H6C5.86739 10.8334 5.74022 10.8861 5.64645 10.9799C5.55268 11.0736 5.5 11.2008 5.5 11.3334C5.5 11.466 5.55268 11.5932 5.64645 11.687C5.74022 11.7807 5.86739 11.8334 6 11.8334H10C10.1326 11.8334 10.2598 11.7807 10.3536 11.687C10.4473 11.5932 10.5 11.466 10.5 11.3334Z"/>
|
||||
<path fill="currentColor" stroke="currentColor" stroke-width="0.1" fill-rule="evenodd" clip-rule="evenodd" d="M4.66671 1.5C4.18048 1.5 3.71416 1.69315 3.37034 2.03697C3.02653 2.38079 2.83337 2.8471 2.83337 3.33333V12.6667C2.83337 13.1529 3.02653 13.6192 3.37034 13.963C3.71416 14.3068 4.18048 14.5 4.66671 14.5H11.3334C11.8196 14.5 12.2859 14.3068 12.6297 13.963C12.9736 13.6192 13.1667 13.1529 13.1667 12.6667V5.312C13.1667 5.058 13.084 4.81133 12.9307 4.60867L10.932 1.96333C10.8233 1.81941 10.6827 1.70267 10.5212 1.62227C10.3597 1.54188 10.1818 1.50002 10.0014 1.5H4.66671ZM3.83337 3.33333C3.83337 2.87333 4.20671 2.5 4.66671 2.5H9.50004V5.43133C9.50004 5.70733 9.72404 5.93133 10 5.93133H12.1667V12.6667C12.1667 13.1267 11.7934 13.5 11.3334 13.5H4.66671C4.20671 13.5 3.83337 13.1267 3.83337 12.6667V3.33333Z" />
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_151_281">
|
||||
<rect width="16" height="16" fill="currentColor"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_151_287)">
|
||||
<path fill="currentColor" stroke="currentColor" stroke-width="0.2" d="M13.8087 5.41874L13.6447 5.79607C13.6191 5.85751 13.5758 5.91 13.5204 5.94692C13.465 5.98384 13.4 6.00354 13.3334 6.00354C13.2668 6.00354 13.2017 5.98384 13.1463 5.94692C13.0909 5.91 13.0477 5.85751 13.022 5.79607L12.858 5.41874C12.5697 4.75122 12.0416 4.21602 11.378 3.91874L10.872 3.69274C10.8107 3.6645 10.7586 3.61925 10.7222 3.56236C10.6857 3.50546 10.6664 3.43931 10.6664 3.37174C10.6664 3.30417 10.6857 3.23801 10.7222 3.18112C10.7586 3.12422 10.8107 3.07897 10.872 3.05074L11.35 2.83807C12.0303 2.53232 12.5674 1.97732 12.8507 1.2874L13.0194 0.88007C13.0441 0.816972 13.0874 0.762802 13.1434 0.72462C13.1994 0.686438 13.2656 0.666016 13.3334 0.666016C13.4012 0.666016 13.4674 0.686438 13.5234 0.72462C13.5794 0.762802 13.6226 0.816972 13.6474 0.88007L13.816 1.28674C14.099 1.97679 14.6359 2.53202 15.316 2.83807L15.7947 3.0514C15.8559 3.07972 15.9077 3.12496 15.944 3.18179C15.9804 3.23861 15.9997 3.30463 15.9997 3.37207C15.9997 3.43951 15.9804 3.50553 15.944 3.56235C15.9077 3.61917 15.8559 3.66442 15.7947 3.69274L15.288 3.91807C14.6246 4.21565 14.0968 4.75109 13.8087 5.41874ZM6.66671 2.00007H9.33337V3.3334H6.66671C5.60584 3.3334 4.58843 3.75483 3.83828 4.50498C3.08813 5.25512 2.66671 6.27254 2.66671 7.3334C2.66671 9.74007 4.30804 11.3107 8.00004 12.9867V11.3334H9.33337C10.3942 11.3334 11.4117 10.912 12.1618 10.1618C12.9119 9.41168 13.3334 8.39427 13.3334 7.3334H14.6667C14.6667 8.74789 14.1048 10.1044 13.1046 11.1046C12.1044 12.1048 10.7479 12.6667 9.33337 12.6667V15.0001C6.00004 13.6667 1.33337 11.6667 1.33337 7.3334C1.33337 5.91892 1.89528 4.56236 2.89547 3.56217C3.89567 2.56197 5.25222 2.00007 6.66671 2.00007Z"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_151_287">
|
||||
<rect width="16" height="16" fill="currentColor"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_151_292)">
|
||||
<path fill="none" stroke="currentColor" stroke-width="0.2" d="M14.3333 5.29864V10.7013C14.3328 11.0668 14.2604 11.4286 14.1189 11.7656C13.9775 12.1027 13.7705 12.4083 13.51 12.6646C12.982 13.1846 12.2666 13.478 11.52 13.478H4.48063C3.7362 13.4788 3.02136 13.1866 2.49063 12.6646C1.96329 12.144 1.66663 11.438 1.66663 10.7013V5.29864C1.66704 4.93317 1.74003 4.57143 1.88137 4.23439C2.0227 3.89736 2.22957 3.59175 2.48996 3.33531C3.02069 2.8133 3.73554 2.52113 4.47996 2.52197H11.5193C12.2637 2.52113 12.9786 2.8133 13.5093 3.33531C13.7698 3.5917 13.9768 3.89728 14.1182 4.23432C14.2597 4.57135 14.3328 4.93313 14.3333 5.29864Z" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path fill="none" stroke="currentColor" stroke-width="0.2" d="M7.75534 10.6454C7.75534 10.1041 7.53934 9.5854 7.15401 9.20207C6.76801 8.81817 6.24574 8.60267 5.70134 8.60267C5.15694 8.60267 4.63467 8.81817 4.24867 9.20207C4.05815 9.39107 3.90693 9.6159 3.80372 9.86362C3.70052 10.1113 3.64737 10.377 3.64734 10.6454M9.71201 5.94607H12.108M9.71201 8.00007H10.7387M9.71201 10.0541H12.108M5.70134 8.0934C6.06451 8.0934 6.4128 7.94914 6.6696 7.69234C6.9264 7.43554 7.07067 7.08724 7.07067 6.72407C7.07067 6.3609 6.9264 6.01261 6.6696 5.75581C6.4128 5.49901 6.06451 5.35474 5.70134 5.35474C5.33817 5.35474 4.98987 5.49901 4.73307 5.75581C4.47627 6.01261 4.33201 6.3609 4.33201 6.72407C4.33201 7.08724 4.47627 7.43554 4.73307 7.69234C4.98987 7.94914 5.33817 8.0934 5.70134 8.0934Z" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_151_292">
|
||||
<rect width="16" height="16" fill="currentColor"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_151_299)">
|
||||
<path fill="currentColor" stroke="currentColor" stroke-width="0.2" d="M6.07128 3.63192C6.46995 2.46525 8.08195 2.42992 8.55461 3.52592L8.59461 3.63259L9.13262 5.20592C9.25591 5.56674 9.45515 5.89693 9.7169 6.1742C9.97865 6.45148 10.2968 6.66939 10.6499 6.81325L10.7946 6.86725L12.3679 7.40459C13.5346 7.80325 13.5699 9.41525 12.4746 9.88792L12.3679 9.92792L10.7946 10.4659C10.4337 10.5891 10.1034 10.7883 9.82596 11.0501C9.54856 11.3118 9.33055 11.6301 9.18661 11.9833L9.13262 12.1273L8.59528 13.7013C8.19661 14.8679 6.58461 14.9033 6.11261 13.8079L6.07128 13.7013L5.53395 12.1279C5.41073 11.767 5.21153 11.4367 4.94978 11.1593C4.68802 10.8819 4.36981 10.6638 4.01661 10.5199L3.87261 10.4659L2.29928 9.92859C1.13195 9.52992 1.09661 7.91792 2.19261 7.44592L2.29928 7.40459L3.87261 6.86725C4.23344 6.74396 4.56362 6.54471 4.8409 6.28297C5.11817 6.02122 5.33609 5.70305 5.47995 5.34992L5.53395 5.20592L6.07128 3.63192ZM7.33328 4.06259L6.79595 5.63592C6.60821 6.18612 6.30279 6.68879 5.90097 7.10892C5.49915 7.52904 5.01058 7.85654 4.46928 8.06859L4.30261 8.12925L2.72928 8.66659L4.30261 9.20392C4.85282 9.39166 5.35548 9.69707 5.77561 10.0989C6.19574 10.5007 6.52323 10.9893 6.73528 11.5306L6.79595 11.6973L7.33328 13.2706L7.87061 11.6973C8.05835 11.147 8.36377 10.6444 8.76559 10.2243C9.16741 9.80413 9.65598 9.47664 10.1973 9.26459L10.3639 9.20459L11.9373 8.66659L10.3639 8.12925C9.81375 7.94151 9.31108 7.6361 8.89095 7.23428C8.47082 6.83245 8.14333 6.34388 7.93128 5.80259L7.87128 5.63592L7.33328 4.06259ZM12.6666 1.33325C12.7913 1.33325 12.9136 1.36824 13.0194 1.43423C13.1252 1.50023 13.2104 1.59459 13.2653 1.70659L13.2973 1.78459L13.5306 2.46859L14.2153 2.70192C14.3403 2.74438 14.4498 2.823 14.5301 2.9278C14.6104 3.0326 14.6577 3.15886 14.6661 3.2906C14.6746 3.42233 14.6437 3.55361 14.5774 3.66778C14.5112 3.78195 14.4125 3.87388 14.2939 3.93192L14.2153 3.96392L13.5313 4.19725L13.2979 4.88192C13.2554 5.00687 13.1767 5.11638 13.0719 5.19658C12.9671 5.27678 12.8408 5.32405 12.709 5.3324C12.5773 5.34075 12.4461 5.30981 12.3319 5.2435C12.2178 5.17719 12.1259 5.0785 12.0679 4.95992L12.0359 4.88192L11.8026 4.19792L11.1179 3.96459C10.993 3.92212 10.8834 3.84351 10.8031 3.73871C10.7229 3.63391 10.6755 3.50764 10.6671 3.37591C10.6587 3.24417 10.6895 3.1129 10.7558 2.99873C10.8221 2.88456 10.9207 2.79263 11.0393 2.73459L11.1179 2.70259L11.8019 2.46925L12.0353 1.78459C12.0802 1.65287 12.1653 1.53852 12.2785 1.45758C12.3917 1.37664 12.5274 1.33317 12.6666 1.33325Z"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_151_299">
|
||||
<rect width="16" height="16" fill="currentColor"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- 底部菜单 -->
|
||||
<div class="side-nav__footer">
|
||||
<div class="side-nav__item" v-for="item in footerMenus" :key="item.label" @click="item.action?.()">
|
||||
<!-- <div class="side-nav__item" v-for="item in footerMenus" :key="item.label" @click="item.action?.()">
|
||||
<span class="side-nav__item-label">{{ item.label }}</span>
|
||||
<span v-if="item.badge" class="side-nav__badge">{{ item.badge }}</span>
|
||||
</div> -->
|
||||
|
||||
<!-- 邀请会员卡片 -->
|
||||
<div class="side-nav__referral-card">
|
||||
<!-- 顶部标题区域 -->
|
||||
<div class="side-nav__referral-title">邀请好友送 7 天会员</div>
|
||||
<div class="side-nav__referral-subtitle">好友加入后自动到账</div>
|
||||
<!-- 白色面板 -->
|
||||
<div class="side-nav__referral-panel">
|
||||
<!-- 邀请进度行 -->
|
||||
<div class="side-nav__referral-row">
|
||||
<span class="side-nav__referral-label">邀请进度</span>
|
||||
<span class="side-nav__referral-value">12/30</span>
|
||||
</div>
|
||||
<!-- 进度条 -->
|
||||
<div class="side-nav__referral-track">
|
||||
<div class="side-nav__referral-fill" style="width: 40%"></div>
|
||||
</div>
|
||||
<!-- 提示文字 -->
|
||||
<div class="side-nav__referral-tip">再邀请 <strong>18</strong> 人,可获得年会员</div>
|
||||
<!-- 分隔线 -->
|
||||
<div class="side-nav__referral-divider"></div>
|
||||
<!-- 去邀请按钮 -->
|
||||
<div class="side-nav__referral-action" @click="showShareDialog = true">去邀请</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 用户信息模块 -->
|
||||
<div class="side-nav__user-info-wrapper" v-if="store.state.isAuthenticated && store.state.userInfo">
|
||||
<!-- 账户弹窗 -->
|
||||
<div v-if="showAccountPopup" class="side-nav__account-popup">
|
||||
<!-- 顶部渐变区域:会员信息 -->
|
||||
<div class="side-nav__account-popup-header">
|
||||
<div class="side-nav__account-popup-header-row">
|
||||
<div class="side-nav__account-popup-header-left">
|
||||
<div class="side-nav__account-popup-member-type" :class="accountMemberColorClass">{{ memberTagText }}</div>
|
||||
<div v-if="memberExpireText" class="side-nav__account-popup-expire">{{ memberExpireText }}</div>
|
||||
</div>
|
||||
<div class="side-nav__account-popup-detail" @click="handleAccountDetail">详情></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 设置按钮 -->
|
||||
<div class="side-nav__account-popup-item" @click="handleAccountSettings">设置</div>
|
||||
<!-- 退出登录按钮 -->
|
||||
<div class="side-nav__account-popup-item" @click="handleAccountLogout">退出登录</div>
|
||||
</div>
|
||||
|
||||
<!-- 用户信息卡片(点击弹出/关闭账户弹窗) -->
|
||||
<div class="side-nav__user-info" @click.stop="showAccountPopup = !showAccountPopup">
|
||||
<div class="side-nav__user-left">
|
||||
<!-- 手机号(脱敏显示) -->
|
||||
<div class="side-nav__user-phone">{{ maskedPhone }}</div>
|
||||
<!-- 会员标签 -->
|
||||
<div class="side-nav__user-member-tag" :class="memberTagClass">{{ memberTagText }}</div>
|
||||
</div>
|
||||
<!-- 右侧铃铛图标 -->
|
||||
<div class="side-nav__user-bell" @click.stop="showMessageDialog = true">
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_422_1692)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.83126 9.77736L6.60626 5.90736C6.76471 5.1241 7.18922 4.41976 7.80778 3.9138C8.42635 3.40785 9.20088 3.13143 10 3.13143C10.7991 3.13143 11.5737 3.40785 12.1922 3.9138C12.8108 4.41976 13.2353 5.1241 13.3938 5.90736L14.1688 9.77736C14.4013 10.9321 14.9687 11.9929 15.8 12.8274L16.0425 13.0686C16.1318 13.1581 16.1956 13.2697 16.2272 13.3921C16.2589 13.5145 16.2572 13.6431 16.2224 13.7646C16.1876 13.8862 16.1209 13.9962 16.0293 14.0832C15.9376 14.1703 15.8244 14.2313 15.7013 14.2599L14.675 14.4974C11.5989 15.2087 8.40109 15.2087 5.32501 14.4974L4.30001 14.2599C4.17678 14.2315 4.06339 14.1706 3.97161 14.0836C3.87983 13.9966 3.81301 13.8866 3.77806 13.7651C3.74312 13.6435 3.74133 13.5149 3.77288 13.3924C3.80444 13.2699 3.86818 13.1581 3.95751 13.0686L4.20001 12.8274C5.03317 11.9942 5.60081 10.9329 5.83126 9.77736ZM3.99376 9.40987L4.76751 5.53987C5.01082 4.33139 5.66493 3.24439 6.61874 2.46347C7.57256 1.68255 8.76728 1.25586 10 1.25586C11.2327 1.25586 12.4275 1.68255 13.3813 2.46347C14.3351 3.24439 14.9892 4.33139 15.2325 5.53987L16.0075 9.40987C16.165 10.2024 16.555 10.9299 17.1263 11.5011L17.3688 11.7436C17.6951 12.0696 17.9281 12.477 18.0437 12.9236C18.1592 13.3701 18.1531 13.8394 18.0259 14.2828C17.8987 14.7262 17.6551 15.1274 17.3203 15.4447C16.9856 15.7621 16.572 15.984 16.1225 16.0874L15.0963 16.3249C14.2188 16.5249 13.3354 16.674 12.4463 16.7724C12.3274 17.3329 12.0196 17.8356 11.5744 18.1963C11.1292 18.557 10.5736 18.7538 10.0006 18.7538C9.42765 18.7538 8.87205 18.557 8.42685 18.1963C7.98165 17.8356 7.67386 17.3329 7.55501 16.7724C6.66352 16.6757 5.77861 16.5259 4.90501 16.3236L3.87751 16.0861C3.4285 15.9822 3.01545 15.7601 2.68118 15.4428C2.34692 15.1255 2.10361 14.7246 1.97647 14.2816C1.84934 13.8386 1.84301 13.3697 1.95814 12.9234C2.07327 12.4772 2.30568 12.0698 2.63126 11.7436L2.87501 11.4999C3.44626 10.9286 3.83626 10.1999 3.99376 9.40862" fill="#6A7282"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_422_1692">
|
||||
<rect width="20" height="20" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
<!-- 未读消息红点 -->
|
||||
<span v-if="unreadCount > 0" class="side-nav__user-bell-dot"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -100,7 +111,12 @@
|
||||
<!-- 弹窗头部 -->
|
||||
<div class="side-nav__message-dialog-header">
|
||||
<span>消息通知</span>
|
||||
<span class="side-nav__dialog-close" @click="showMessageDialog = false">✕</span>
|
||||
<span class="side-nav__dialog-close" @click="showMessageDialog = false">
|
||||
<!-- 关闭图标 SVG -->
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M18 6L6 18M6 6l12 12" stroke="#6A7282" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
<!-- 弹窗内容区:左列表 + 右详情 -->
|
||||
<div class="side-nav__message-dialog-content">
|
||||
@@ -113,12 +129,13 @@
|
||||
:class="{ 'side-nav__message-list-item--active': selectedMessageIdx === idx }"
|
||||
@click="selectedMessageIdx = idx"
|
||||
>
|
||||
<div class="dflex wp100 aliite-e fs14">
|
||||
<div class="">
|
||||
<span class="side-nav__message-list-title">{{ msg.title }}</span>
|
||||
<!-- 列表项内容:未读圆点 + 标题 + 日期 -->
|
||||
<div class="side-nav__message-list-item-row">
|
||||
<div class="side-nav__message-list-item-left">
|
||||
<span v-if="!msg.read" class="side-nav__message-unread-dot"></span>
|
||||
<span class="side-nav__message-list-title">{{ msg.title }}</span>
|
||||
</div>
|
||||
<div class="fs10 color-8 tar">{{timestampToLocalDateTime(msg.createTime, 'returnDay')}}</div>
|
||||
<span class="side-nav__message-list-date">{{ formatMonthDay(msg.createTime) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 加载中提示 -->
|
||||
@@ -129,11 +146,15 @@
|
||||
<!-- 右侧消息详情 -->
|
||||
<div class="side-nav__message-detail">
|
||||
<template v-if="currentMessage">
|
||||
<!-- 详情头部:标题 + 时间 -->
|
||||
<div class="side-nav__message-detail-header">
|
||||
<div class="side-nav__message-detail-title">{{ currentMessage.title }}</div>
|
||||
<div class="dflex aliite-e">
|
||||
<div class="side-nav__message-detail-content">{{ currentMessage.content }}</div>
|
||||
<div class="fs12 color-8 w140 tar">{{timestampToLocalDateTime(currentMessage.createTime, 'returnSecond')}}</div>
|
||||
<div class="side-nav__message-detail-time">{{ timestampToLocalDateTime(currentMessage.createTime, 'returnSecond') }}</div>
|
||||
</div>
|
||||
<!-- 分隔线 -->
|
||||
<div class="side-nav__message-detail-divider"></div>
|
||||
<!-- 消息正文 -->
|
||||
<div class="side-nav__message-detail-content">{{ currentMessage.content }}</div>
|
||||
</template>
|
||||
<div v-else class="side-nav__message-detail-empty">请选择一条消息查看</div>
|
||||
</div>
|
||||
@@ -182,6 +203,14 @@
|
||||
</Teleport>
|
||||
<!-- 设置弹窗 -->
|
||||
<SettingsDialog v-model="showSettingsDialog" :initial-tab="store.state.settingsTab" />
|
||||
<!-- 退出登录确认弹窗 -->
|
||||
<el-dialog v-model="showLogoutConfirm" title="退出登录" width="3.6rem" style="line-height: 0.2rem" :close-on-click-modal="true" :append-to-body="true">
|
||||
<p style="font-size: 0.14rem; color: #555; text-align: center;">确定要退出当前账号吗?</p>
|
||||
<template #footer>
|
||||
<el-button @click="showLogoutConfirm = false">取消</el-button>
|
||||
<el-button type="danger" @click="confirmLogout">确认退出</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<!-- 邀请注册送会员弹窗 -->
|
||||
<SettingsInviteDialog v-model="showShareDialog" />
|
||||
<!-- 会员权限拦截弹窗 -->
|
||||
@@ -192,15 +221,26 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch, onMounted } from 'vue'
|
||||
import { computed, ref, watch, onMounted, onUnmounted } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useStore } from 'vuex'
|
||||
import SettingsDialog from '@/components/SettingsDialog.vue'
|
||||
import MemberAccessDialog from '@/components/MemberAccessDialog.vue'
|
||||
import MemberDialog from '@/components/MemberDialog.vue'
|
||||
import { checkLogin } from '@/api/auth'
|
||||
import { logout } from '@/api/auth'
|
||||
import { fetchMessageList, fetchUnreadCount, markMessageRead } from '@/api/message'
|
||||
import { timestampToLocalDateTime } from '@/utils/time'
|
||||
|
||||
/**
|
||||
* 格式化时间戳为月/日显示(如 06/16)
|
||||
* 利用 timestampToLocalDateTime 的 returnDay 精度,截取掉年份部分
|
||||
*/
|
||||
function formatMonthDay(timestamp: number | null | undefined): string {
|
||||
const full = timestampToLocalDateTime(timestamp, 'returnDay') // 返回 "2026/06/16"
|
||||
// 去掉年份的四位数字和第一个斜杠 → "06/16"
|
||||
return full.replace(/^\d{4}\//, '')
|
||||
}
|
||||
import {userFeedback} from '@/api/setting'
|
||||
import type { MessageDto } from '@/api/message'
|
||||
import navJobsIcon from '@/assets/images/nav/nav-jobs-icon.png'
|
||||
@@ -218,6 +258,17 @@ const route = useRoute()
|
||||
const router = useRouter()
|
||||
const store = useStore()
|
||||
|
||||
/**
|
||||
* 监听路由变化:已登录时刷新用户信息和会员状态到 vuex
|
||||
* 这样每次页面切换都能拿到最新数据
|
||||
*/
|
||||
watch(() => route.path, () => {
|
||||
if (store.state.isAuthenticated) {
|
||||
store.dispatch('loadUserInfo')
|
||||
store.dispatch('loadMemberStatus')
|
||||
}
|
||||
}, { immediate: true })
|
||||
|
||||
interface MenuItem {
|
||||
name: string
|
||||
path: string
|
||||
@@ -346,10 +397,7 @@ async function loadUnreadCount() {
|
||||
}
|
||||
}
|
||||
|
||||
// 组件挂载时获取未读消息数量
|
||||
onMounted(() => {
|
||||
loadUnreadCount()
|
||||
})
|
||||
// 组件挂载时获取未读消息数量(已移到下方 onMounted 统一处理)
|
||||
|
||||
/**
|
||||
* 加载消息列表(分页追加)
|
||||
@@ -490,12 +538,125 @@ const settingsMenu = computed(() => {
|
||||
})
|
||||
|
||||
const footerMenus = computed(() => [
|
||||
{ iconImg: `<img src="${navShareIcon}" alt="" />`, label: '分享送会员', action: () => { showShareDialog.value = true } },
|
||||
{ iconImg: `<img src="${navMessageIcon}" alt="" />`, label: '消息通知', badge: unreadCount.value > 0 ? 'NEW' : '', action: () => { showMessageDialog.value = true } },
|
||||
{ iconImg: settingsMenu.value.iconImg, label: settingsMenu.value.label, action: () => { handleSettingsNav() } },
|
||||
{ iconImg: `<img src="${navFeedbackIcon}" alt="" />`, label: '反馈', action: () => { showFeedbackDialog.value = true } },
|
||||
])
|
||||
|
||||
/**
|
||||
* 手机号脱敏显示:134****1254 格式
|
||||
*/
|
||||
const maskedPhone = computed(() => {
|
||||
const phone = store.state.userInfo?.mobileNumber || ''
|
||||
if (phone.length >= 7) {
|
||||
return phone.slice(0, 3) + '****' + phone.slice(-4)
|
||||
}
|
||||
return phone || '未绑定手机'
|
||||
})
|
||||
|
||||
/**
|
||||
* 会员标签文字:根据 isMember + memberType 判断
|
||||
* memberType: 1=正式会员 2=试用会员, isMember=false → 免费用户
|
||||
*/
|
||||
const memberTagText = computed(() => {
|
||||
const status = store.state.memberStatus
|
||||
if (!status || !status.isMember) return '免费用户'
|
||||
if (status.memberType === 1) return '正式会员'
|
||||
if (status.memberType === 2) return '试用会员'
|
||||
return '免费用户'
|
||||
})
|
||||
|
||||
/**
|
||||
* 会员标签样式 class
|
||||
*/
|
||||
const memberTagClass = computed(() => {
|
||||
const status = store.state.memberStatus
|
||||
if (!status || !status.isMember) return 'side-nav__user-member-tag--free'
|
||||
if (status.memberType === 1) return 'side-nav__user-member-tag--formal'
|
||||
if (status.memberType === 2) return 'side-nav__user-member-tag--trial'
|
||||
return 'side-nav__user-member-tag--free'
|
||||
})
|
||||
|
||||
/** 账户弹窗显示状态 */
|
||||
const showAccountPopup = ref(false)
|
||||
|
||||
/** 退出登录确认弹窗 */
|
||||
const showLogoutConfirm = ref(false)
|
||||
|
||||
/**
|
||||
* 账户弹窗中会员类型字体颜色 class
|
||||
*/
|
||||
const accountMemberColorClass = computed(() => {
|
||||
const status = store.state.memberStatus
|
||||
if (!status || !status.isMember) return 'side-nav__account-popup-member-type--free'
|
||||
if (status.memberType === 1) return 'side-nav__account-popup-member-type--formal'
|
||||
if (status.memberType === 2) return 'side-nav__account-popup-member-type--trial'
|
||||
return 'side-nav__account-popup-member-type--free'
|
||||
})
|
||||
|
||||
/**
|
||||
* 会员到期时间显示文字 — 使用项目时间工具 timestampToLocalDateTime
|
||||
*/
|
||||
const memberExpireText = computed(() => {
|
||||
const status = store.state.memberStatus
|
||||
if (!status || !status.isMember) return ''
|
||||
if (status.expireTime) {
|
||||
return timestampToLocalDateTime(status.expireTime, 'returnDay') + '到期'
|
||||
}
|
||||
return ''
|
||||
})
|
||||
|
||||
/** 账户弹窗 — 点击"详情>"跳转会员页 */
|
||||
function handleAccountDetail() {
|
||||
showAccountPopup.value = false
|
||||
showMemberDialog.value = true
|
||||
}
|
||||
|
||||
/** 账户弹窗 — 点击"设置"打开设置弹窗 */
|
||||
function handleAccountSettings() {
|
||||
showAccountPopup.value = false
|
||||
handleSettingsNav()
|
||||
}
|
||||
|
||||
/** 账户弹窗 — 点击"退出登录"弹出确认弹窗 */
|
||||
function handleAccountLogout() {
|
||||
showAccountPopup.value = false
|
||||
showLogoutConfirm.value = true
|
||||
}
|
||||
|
||||
/** 确认退出登录 */
|
||||
async function confirmLogout() {
|
||||
showLogoutConfirm.value = false
|
||||
try {
|
||||
const res = await logout()
|
||||
if (res.code === '0') {
|
||||
ElMessage.success('已退出登录')
|
||||
}
|
||||
} catch {
|
||||
// 即使接口失败也执行前端登出
|
||||
}
|
||||
store.dispatch('logout')
|
||||
router.push('/')
|
||||
}
|
||||
|
||||
/**
|
||||
* 点击页面其他地方关闭账户弹窗
|
||||
*/
|
||||
function handleGlobalClick() {
|
||||
if (showAccountPopup.value) {
|
||||
showAccountPopup.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadUnreadCount()
|
||||
document.addEventListener('click', handleGlobalClick)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener('click', handleGlobalClick)
|
||||
})
|
||||
|
||||
/**
|
||||
* 设置弹窗:需要登录,通过 checkLogin 接口验证
|
||||
*/
|
||||
@@ -531,6 +692,12 @@ async function handleNav(item: MenuItem) {
|
||||
return
|
||||
}
|
||||
|
||||
// 外部链接 — routePath 是 http/https 开头,新标签页打开
|
||||
if (/^https?:\/\//.test(item.path)) {
|
||||
window.open(item.path, '_blank')
|
||||
return
|
||||
}
|
||||
|
||||
// Jobs 等静态页面 — 无需登录验证,直接跳转
|
||||
if (staticNames.includes(item.name)) {
|
||||
router.push(item.path)
|
||||
|
||||
@@ -9,6 +9,8 @@ import { fetchJobIntention, saveJobIntention } from '@/api/jobs'
|
||||
import type { JobIntention } from '@/api/jobs'
|
||||
import { fetchUserInfo } from '@/api/auth'
|
||||
import type { UserInfo } from '@/api/auth'
|
||||
import { fetchMemberStatus } from '@/api/member'
|
||||
import type { MemberStatus } from '@/api/member'
|
||||
|
||||
/** 招聘分类选项:label → 接口参数 recruitCategory */
|
||||
export const JOB_TYPE_OPTIONS: { label: string; value: number }[] = [
|
||||
@@ -46,6 +48,8 @@ export function formatEducation(edu: number | undefined | null): string {
|
||||
|
||||
/** 职位列表页缓存数据(从详情页返回时恢复用) */
|
||||
export interface JobListCache {
|
||||
/** 缓存时所在的 Tab(recommend / collected / applied) */
|
||||
tab: string
|
||||
/** 缓存的职位列表 */
|
||||
list: any[]
|
||||
/** 当前已加载到的页码 */
|
||||
@@ -138,6 +142,12 @@ export interface RootState {
|
||||
*/
|
||||
userInfo: UserInfo | null
|
||||
|
||||
/**
|
||||
* 会员状态信息 — 登录后从 /member/status 接口获取
|
||||
* 多个页面可直接从 store 读取
|
||||
*/
|
||||
memberStatus: MemberStatus | null
|
||||
|
||||
/**
|
||||
* 设置弹窗 — 控制显示/隐藏及初始 Tab
|
||||
*/
|
||||
@@ -175,6 +185,7 @@ export default createStore<RootState>({
|
||||
aiIndustryIds: [],
|
||||
},
|
||||
userInfo: null,
|
||||
memberStatus: null,
|
||||
showSettings: false,
|
||||
settingsTab: 'account',
|
||||
inviteCode: '',
|
||||
@@ -238,6 +249,9 @@ export default createStore<RootState>({
|
||||
SET_USER_INFO(state, data: UserInfo | null) {
|
||||
state.userInfo = data
|
||||
},
|
||||
SET_MEMBER_STATUS(state, data: MemberStatus | null) {
|
||||
state.memberStatus = data
|
||||
},
|
||||
SET_SHOW_SETTINGS(state, show: boolean) {
|
||||
state.showSettings = show
|
||||
},
|
||||
@@ -311,6 +325,7 @@ export default createStore<RootState>({
|
||||
commit('SET_SHOW_LOGIN', false)
|
||||
commit('SET_LOGIN_REDIRECT', '')
|
||||
commit('SET_USER_INFO', null)
|
||||
commit('SET_MEMBER_STATUS', null)
|
||||
// 清除 Jobs 页面缓存数据
|
||||
commit('SET_JOB_LIST_CACHE', null)
|
||||
commit('SET_JOB_INTENTION', {
|
||||
@@ -391,6 +406,21 @@ export default createStore<RootState>({
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 加载会员状态到 store
|
||||
* 登录状态下调用,多个页面可直接读取 store.state.memberStatus
|
||||
*/
|
||||
async loadMemberStatus({ commit }) {
|
||||
try {
|
||||
const res = await fetchMemberStatus()
|
||||
if (res.code === '0' && res.data) {
|
||||
commit('SET_MEMBER_STATUS', res.data)
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[store] 加载会员状态失败', err)
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 保存求职意向:已登录时调接口保存并更新 store,未登录时仅更新 store
|
||||
* @param data 求职意向数据
|
||||
|
||||
+53
-2
@@ -18,6 +18,39 @@
|
||||
|
||||
<!-- 内容区域(可滚动) -->
|
||||
<div class="job-detail__body scrollbar-thin">
|
||||
<!-- 加载中骨架屏 -->
|
||||
<el-skeleton :loading="loading" animated :count="1">
|
||||
<template #template>
|
||||
<div class="job-detail__skeleton">
|
||||
<!-- 模拟岗位标题行 -->
|
||||
<el-skeleton-item variant="h1" style="width: 40%; height: 0.28rem; margin-bottom: 0.16rem;" />
|
||||
<!-- 模拟标签行 -->
|
||||
<div style="display: flex; gap: 0.1rem; margin-bottom: 0.2rem;">
|
||||
<el-skeleton-item variant="text" style="width: 0.6rem; height: 0.2rem;" />
|
||||
<el-skeleton-item variant="text" style="width: 0.8rem; height: 0.2rem;" />
|
||||
<el-skeleton-item variant="text" style="width: 0.5rem; height: 0.2rem;" />
|
||||
</div>
|
||||
<!-- 模拟信息区 -->
|
||||
<el-skeleton-item variant="text" style="width: 80%; height: 0.16rem; margin-bottom: 0.12rem;" />
|
||||
<el-skeleton-item variant="text" style="width: 60%; height: 0.16rem; margin-bottom: 0.24rem;" />
|
||||
<!-- 模拟描述区 -->
|
||||
<el-skeleton-item variant="h3" style="width: 20%; height: 0.2rem; margin-bottom: 0.16rem;" />
|
||||
<el-skeleton-item variant="text" style="width: 100%; height: 0.14rem; margin-bottom: 0.1rem;" />
|
||||
<el-skeleton-item variant="text" style="width: 95%; height: 0.14rem; margin-bottom: 0.1rem;" />
|
||||
<el-skeleton-item variant="text" style="width: 90%; height: 0.14rem; margin-bottom: 0.1rem;" />
|
||||
<el-skeleton-item variant="text" style="width: 85%; height: 0.14rem; margin-bottom: 0.1rem;" />
|
||||
<el-skeleton-item variant="text" style="width: 70%; height: 0.14rem; margin-bottom: 0.24rem;" />
|
||||
<!-- 模拟公司信息区 -->
|
||||
<el-skeleton-item variant="h3" style="width: 25%; height: 0.2rem; margin-bottom: 0.16rem;" />
|
||||
<div style="display: flex; align-items: center; gap: 0.12rem; margin-bottom: 0.12rem;">
|
||||
<el-skeleton-item variant="circle" style="width: 0.4rem; height: 0.4rem;" />
|
||||
<el-skeleton-item variant="text" style="width: 1.2rem; height: 0.18rem;" />
|
||||
</div>
|
||||
<el-skeleton-item variant="text" style="width: 100%; height: 0.14rem; margin-bottom: 0.1rem;" />
|
||||
<el-skeleton-item variant="text" style="width: 80%; height: 0.14rem;" />
|
||||
</div>
|
||||
</template>
|
||||
<template #default>
|
||||
<!-- 岗位基本信息区 -->
|
||||
<div class="job-detail__header-section">
|
||||
<!-- 左侧:岗位信息 -->
|
||||
@@ -49,10 +82,10 @@
|
||||
</div>
|
||||
|
||||
<!-- 右侧:匹配度卡片 -->
|
||||
<div class="job-detail__match-card">
|
||||
<div class="job-detail__match-card" :class="matchCardClass">
|
||||
<div class="job-detail__match-top">
|
||||
<span class="job-detail__match-score">{{ job.matchScore }}%</span>
|
||||
<span class="job-detail__match-label">高匹配度</span>
|
||||
<span class="job-detail__match-label">{{ matchLevelText }}</span>
|
||||
</div>
|
||||
<div class="job-detail__match-list">
|
||||
<div class="job-detail__match-row" v-for="m in matchItems" :key="m.label">
|
||||
@@ -119,6 +152,8 @@
|
||||
<p class="job-detail__company-desc">{{ job.companyInfo.description }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-skeleton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -214,6 +249,22 @@ const matchItems = ref([
|
||||
{ label: '过往经历', score: 0 },
|
||||
])
|
||||
|
||||
/** 匹配度卡片动态class:根据分数区间切换高/中/低样式 */
|
||||
const matchCardClass = computed(() => {
|
||||
const score = job.matchScore
|
||||
if (score >= 80) return 'job-detail__match-card--high'
|
||||
if (score >= 40) return 'job-detail__match-card--medium'
|
||||
return 'job-detail__match-card--low'
|
||||
})
|
||||
|
||||
/** 匹配度等级文字 */
|
||||
const matchLevelText = computed(() => {
|
||||
const score = job.matchScore
|
||||
if (score >= 80) return '高匹配度'
|
||||
if (score >= 40) return '中匹配度'
|
||||
return '低匹配度'
|
||||
})
|
||||
|
||||
/** 技能标签项(matched 表示用户是否具备该技能,可点击切换) */
|
||||
interface SkillTag {
|
||||
/** 技能名称 */
|
||||
|
||||
+105
-19
@@ -18,7 +18,7 @@
|
||||
<JobPageHeader v-model:activeTab="activeTab" />
|
||||
|
||||
<!-- 筛选条件 -->
|
||||
<div v-if="isRecommendTab" class="jobs-page__filters-bar">
|
||||
<div v-if="isRecommendTab" class="jobs-page__filters-bar" :style="showAiRecommendTip?'':'margin-bottom:0.2rem'">
|
||||
<div class="jobs-page__filters">
|
||||
<div ref="filterGroupRef" class="jobs-page__filter-group">
|
||||
<!-- 筛选条件按钮列表(行业单独用组件) -->
|
||||
@@ -114,7 +114,7 @@
|
||||
<div class="jobs-page__ai-tip-content">
|
||||
<img class="jobs-page__ai-tip-logo" src="@/assets/images/logo-offerpai-2.png" alt="Nova" />
|
||||
<span class="jobs-page__ai-tip-text">
|
||||
Nova根据你的个人背景建议的行业:{{ aiIndustryNames }}<template v-if="aiCategoryNames">;岗位:{{ aiCategoryNames }}</template>
|
||||
Nova根据你的个人背景建议的<template v-if="aiCategoryNames">岗位:{{ aiCategoryNames }}</template><template v-if="aiIndustryNames">;行业:{{ aiIndustryNames }}</template>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -302,8 +302,8 @@ import RegionSelector from '@/components/tools/RegionSelector.vue'
|
||||
import { fetchJobList, fetchFavoriteList, toggleJobFavorite, removeJobFavorite, fetchFavoriteCount, fetchApplyList, fetchApplyCount, removeJobFromList } from '@/api/jobs'
|
||||
import type { JobListItem, JobListParams, FavoriteListParams, ApplyListParams, ApplyCountData } from '@/api/jobs'
|
||||
import { getTimeEvent, setTimeEvent, timestampToLocalDateTime } from '@/utils/time'
|
||||
import { resolveJobCategoryName } from '@/utils/jobCategory'
|
||||
import { resolveIndustryName } from '@/utils/industry'
|
||||
import { fetchIndustryTree, fetchJobCategoryTree } from '@/api/common'
|
||||
import type { IndustryItem, JobCategoryItem } from '@/api/common'
|
||||
|
||||
|
||||
// 2. 注意:这里的字符串不需要再手动转义双引号了
|
||||
@@ -516,27 +516,98 @@ const aiCategoryIds = computed<string[]>(
|
||||
() => store.state.jobIntention.aiCategoryIds || [],
|
||||
)
|
||||
|
||||
/** AI 推荐的行业名称(ID 转文字) */
|
||||
/** ====== AI 推荐提示条专用:完整分类树(含空分类) ====== */
|
||||
const fullIndustryTree = ref<IndustryItem[]>([])
|
||||
const fullJobCategoryTree = ref<JobCategoryItem[]>([])
|
||||
|
||||
/** 加载完整分类树数据(includeEmpty=true),仅用于 AI 推荐提示条 ID 转名称 */
|
||||
async function loadFullCategoryTrees() {
|
||||
const [indRes, catRes] = await Promise.all([
|
||||
fetchIndustryTree({ includeEmpty: true }),
|
||||
fetchJobCategoryTree({ includeEmpty: true }),
|
||||
])
|
||||
if (indRes && indRes.code === '0' && indRes.data) {
|
||||
fullIndustryTree.value = indRes.data
|
||||
}
|
||||
if (catRes && catRes.code === '0' && catRes.data) {
|
||||
fullJobCategoryTree.value = catRes.data
|
||||
}
|
||||
}
|
||||
|
||||
/** 从完整行业树中根据 ID 查找名称 */
|
||||
function resolveFullIndustryName(id: string): string {
|
||||
for (const parent of fullIndustryTree.value) {
|
||||
if (String(parent.id) === id) return parent.name
|
||||
for (const child of parent.children) {
|
||||
if (String(child.id) === id) return child.name
|
||||
}
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
/** 从完整岗位树中根据 ID 查找名称 */
|
||||
function resolveFullCategoryName(id: string): string {
|
||||
for (const lv1 of fullJobCategoryTree.value) {
|
||||
if (String(lv1.id) === id) return lv1.name
|
||||
for (const lv2 of lv1.children) {
|
||||
if (String(lv2.id) === id) return lv2.name
|
||||
if (lv2.children) {
|
||||
for (const lv3 of lv2.children) {
|
||||
if (String(lv3.id) === id) return lv3.name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
/** AI 推荐的行业名称(ID 转文字,基于完整行业树) */
|
||||
const aiIndustryNames = computed<string>(() => {
|
||||
if (!aiIndustryIds.value.length) return ''
|
||||
return aiIndustryIds.value
|
||||
.map(id => resolveIndustryName(Number(id)))
|
||||
.filter(name => !!name)
|
||||
.join('、')
|
||||
if (!aiIndustryIds.value.length || !fullIndustryTree.value.length) return ''
|
||||
const idSet = new Set(aiIndustryIds.value.map(String))
|
||||
const names: string[] = []
|
||||
for (const id of aiIndustryIds.value) {
|
||||
// 如果该 ID 是一级父节点且子节点也在列表中,跳过父级避免重复
|
||||
const parent = fullIndustryTree.value.find(p => String(p.id) === id)
|
||||
if (parent && parent.children?.some(c => idSet.has(String(c.id)))) {
|
||||
continue
|
||||
}
|
||||
const name = resolveFullIndustryName(id)
|
||||
if (name) names.push(name)
|
||||
}
|
||||
return names.join('、')
|
||||
})
|
||||
|
||||
/** AI 推荐的岗位名称(ID 转文字) */
|
||||
/** AI 推荐的岗位名称(ID 转文字,基于完整岗位树) */
|
||||
const aiCategoryNames = computed<string>(() => {
|
||||
if (!aiCategoryIds.value.length) return ''
|
||||
return aiCategoryIds.value
|
||||
.map(id => resolveJobCategoryName(Number(id)))
|
||||
.filter(name => !!name)
|
||||
.join('、')
|
||||
if (!aiCategoryIds.value.length || !fullJobCategoryTree.value.length) return ''
|
||||
const idSet = new Set(aiCategoryIds.value.map(String))
|
||||
const names: string[] = []
|
||||
for (const id of aiCategoryIds.value) {
|
||||
// 如果该 ID 是一级父节点且下级子节点也在列表中,跳过
|
||||
const lv1 = fullJobCategoryTree.value.find(p => String(p.id) === id)
|
||||
if (lv1 && lv1.children?.some(c => idSet.has(String(c.id)) || c.children?.some(leaf => idSet.has(String(leaf.id))))) {
|
||||
continue
|
||||
}
|
||||
// 如果该 ID 是二级节点且三级子节点也在列表中,跳过
|
||||
let isParentWithChild = false
|
||||
for (const cat of fullJobCategoryTree.value) {
|
||||
const lv2 = cat.children?.find(c => String(c.id) === id)
|
||||
if (lv2 && lv2.children?.some(leaf => idSet.has(String(leaf.id)))) {
|
||||
isParentWithChild = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if (isParentWithChild) continue
|
||||
const name = resolveFullCategoryName(id)
|
||||
if (name) names.push(name)
|
||||
}
|
||||
return names.join('、')
|
||||
})
|
||||
|
||||
/** 是否显示 AI 推荐提示条 */
|
||||
const showAiRecommendTip = computed(() => {
|
||||
return isRecommendTab.value && (aiIndustryNames.value || aiCategoryNames.value)
|
||||
return isRecommendTab.value && (aiIndustryNames.value !== null || aiCategoryNames.value !== null)
|
||||
})
|
||||
|
||||
/** 行业选择变更回调 */
|
||||
@@ -699,9 +770,12 @@ onMounted(async () => {
|
||||
// 窗口 resize 时更新尖角位置
|
||||
window.addEventListener('resize', updateAiTipArrowPosition)
|
||||
|
||||
// 加载完整分类树(含空分类),用于 AI 推荐提示条 ID 转名称
|
||||
loadFullCategoryTrees()
|
||||
|
||||
// 先判断是否有缓存(从详情页返回),在任何异步操作之前取出
|
||||
const cache = store.state.jobListCache
|
||||
const hasCache = !!(cache && cache.list.length > 0)
|
||||
const hasCache = !!(cache && cache.list.length > 0 && cache.tab === activeTab.value)
|
||||
|
||||
if (hasCache) {
|
||||
// 立即恢复缓存数据,隐藏列表避免闪烁
|
||||
@@ -710,7 +784,9 @@ onMounted(async () => {
|
||||
pageNum.value = cache!.pageNum
|
||||
total.value = cache!.total
|
||||
savedScrollTop = cache!.scrollTop
|
||||
// 清除缓存,避免下次非详情页返回时误用
|
||||
}
|
||||
// 无论是否命中缓存,都清除,避免下次非详情页返回时误用
|
||||
if (cache) {
|
||||
store.commit('SET_JOB_LIST_CACHE', null)
|
||||
}
|
||||
|
||||
@@ -1126,6 +1202,15 @@ watch(activeTab, (newTab, oldTab) => {
|
||||
return
|
||||
}
|
||||
|
||||
// 同步 tab 到 URL query,刷新浏览器时能恢复正确的 Tab
|
||||
const query = { ...route.query }
|
||||
if (newTab === 'recommend') {
|
||||
delete query.tab
|
||||
} else {
|
||||
query.tab = newTab
|
||||
}
|
||||
router.replace({ query })
|
||||
|
||||
jobList.value = []
|
||||
keyword.value = ''
|
||||
pageNum.value = 1
|
||||
@@ -1221,6 +1306,7 @@ function goToDetail(job: JobItem) {
|
||||
}
|
||||
// 保存当前列表状态到 store,返回时恢复
|
||||
store.commit('SET_JOB_LIST_CACHE', {
|
||||
tab: activeTab.value,
|
||||
list: jobList.value,
|
||||
pageNum: pageNum.value,
|
||||
total: total.value,
|
||||
|
||||
Reference in New Issue
Block a user