导师列表导师详情右侧广告,去投递弹窗等

This commit is contained in:
2026-07-08 22:48:58 +08:00
parent c94a96d14e
commit f0b29d9abb
13 changed files with 1504 additions and 209 deletions
+1
View File
@@ -52,6 +52,7 @@ declare module 'vue' {
LoginDialog: typeof import('./src/components/LoginDialog.vue')['default']
MemberAccessDialog: typeof import('./src/components/MemberAccessDialog.vue')['default']
MemberDialog: typeof import('./src/components/MemberDialog.vue')['default']
MentorAsidePanel: typeof import('./src/components/MentorAsidePanel.vue')['default']
ProfileEditDrawer: typeof import('./src/components/ProfileEditDrawer.vue')['default']
ProfilePageContent: typeof import('./src/components/ProfilePageContent.vue')['default']
ProfileWelcomeDialog: typeof import('./src/components/ProfileWelcomeDialog.vue')['default']
+49 -1
View File
@@ -152,7 +152,8 @@ export interface MentorSupport {
* 导师详情 — 标签关系
*/
export interface MentorLabelRelation {
labelId: number
labelId: string
labelName: string
alias?: string
}
@@ -202,3 +203,50 @@ export function fetchMentorFullInfo(mentorId: string): Promise<MentorFullInfo> {
export function fetchMentorTalks(teacherId: string): Promise<MentorTalk[]> {
return request.get('/public/mentor/talk', { params: { teacherId } })
}
// ==================== 导师模拟对话接口 ====================
/**
* 模拟对话消息项
*/
export interface MentorMockMessage {
/** 角色:user=用户,mentor=导师 */
role: string
/** 消息内容 */
content: string
}
/**
* 模拟对话响应
*/
export interface MentorMockChatResult {
/** 对话消息列表 */
messages: MentorMockMessage[]
}
/**
* 获取导师模拟对话
* GET /public/mentor/mockChat?mentorId=xxx
* 注意:mentorId 使用字符串传递,避免大数精度丢失
*/
export function fetchMentorMockChat(mentorId: string): Promise<MentorMockChatResult> {
return request.get('/public/mentor/mockChat', { params: { mentorId } })
}
// ==================== 导师小程序二维码接口 ====================
/**
* 获取导师小程序二维码(base64 图片)
* POST /public/mentor/getQrCode
* @param teacherId 导师ID(字符串,避免精度丢失)
* @returns base64 格式的图片数据
*/
export function fetchMentorQrCode(teacherId: string): Promise<any> {
return request.post('/public/mentor/getQrCode', {
key: {
path: 'pages/teacher/teacher',
teacherId,
loginAppid: 'wx3adfa65f9d8db17d',
},
})
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

@@ -0,0 +1,485 @@
@use '../variables' as *;
// ==================== 导师页右侧广告面板样式 ====================
.mentor-aside-panel {
font-size: 0.14rem;
width: 100%;
height: 100%;
position: relative;
background: $bg-white;
display: flex;
flex-direction: column;
// 顶部渐变区域
&__header {
padding: 0.24rem;
background: linear-gradient(180deg, #EDF9FA 0%, #FFFFFF 100%);
}
&__title {
font-weight: 630;
font-size: 0.20rem;
line-height: 0.27rem;
color: $text-dark;
}
&__subtitle {
font-weight: 330;
font-size: 0.14rem;
line-height: 0.19rem;
color: $text-middle;
margin-top: 0.04rem;
}
// 统计数字区域
&__stats {
display: flex;
align-items: center;
margin: 0 0.24rem;
height: 1rem;
background: linear-gradient(270deg, rgba(82, 202, 209, 0.05) 0%, rgba(159, 208, 81, 0.05) 100%);
border-radius: 0.12rem;
position: relative;
// 渐变边框:从上到下 #9FD051 → #52CAD1
&::before {
content: '';
position: absolute;
inset: 0;
border-radius: 0.12rem;
padding: 0.012rem;
background: linear-gradient(180deg, #9FD051 0%, #52CAD1 100%);
-webkit-mask:
linear-gradient(#fff 0 0) content-box,
linear-gradient(#fff 0 0);
-webkit-mask-composite: xor;
mask-composite: exclude;
pointer-events: none;
}
}
&__stat-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
gap: 0.08rem;
padding: 0.20rem 0;
}
&__stat-num {
font-weight: 520;
font-size: 0.24rem;
line-height: 0.32rem;
background: linear-gradient(128.17deg, #52CAD1 0%, #82D79B 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
&__stat-label {
font-weight: 450;
font-size: 0.14rem;
line-height: 0.19rem;
color: $text-dark;
}
&__stat-divider {
width: 0;
height: 0.98rem;
border-left: 1px solid $border-color;
}
// 小节标题(共用)
&__section-title {
display: flex;
align-items: center;
gap: 0.08rem;
}
&__section-icon {
font-size: 0.16rem;
background: linear-gradient(128.17deg, #52CAD1 0%, #82D79B 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
&__section-text {
font-weight: 520;
font-size: 0.16rem;
line-height: 0.21rem;
background: linear-gradient(128.17deg, #52CAD1 0%, #82D79B 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
// 为什么选择我们的导师
&__reasons {
margin: 0.24rem 0.24rem 0;
}
&__reason-list {
margin-top: 0.24rem;
}
&__reason-item {
display: flex;
align-items: center;
gap: 0.08rem;
margin-bottom: 0.20rem;
&:last-child {
margin-bottom: 0;
}
}
&__reason-icon {
width: 0.42rem;
height: 0.42rem;
background: #EDF9FA;
border-radius: 0.08rem;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
svg {
width: 0.24rem;
height: 0.24rem;
}
}
&__reason-info {
display: flex;
flex-direction: column;
gap: 0.04rem;
}
&__reason-title {
font-weight: 520;
font-size: 0.14rem;
line-height: 0.19rem;
color: $text-dark;
}
&__reason-desc {
font-weight: 330;
font-size: 0.14rem;
line-height: 0.19rem;
color: $text-middle;
}
// 热门导师方向
&__directions {
margin: 0.24rem 0.24rem 0;
}
&__direction-list {
margin-top: 0.24rem;
}
&__direction-card {
box-sizing: border-box;
padding: 0.12rem 0.16rem;
background: $bg-white;
border: 1px solid $border-color;
border-radius: 0.12rem;
margin-bottom: 0.20rem;
&:last-child {
margin-bottom: 0;
}
}
&__direction-name {
font-weight: 520;
font-size: 0.14rem;
line-height: 0.19rem;
color: $text-dark;
margin-bottom: 0.08rem;
}
&__direction-tags {
display: flex;
align-items: center;
gap: 0.08rem;
}
&__direction-tag {
display: inline-flex;
justify-content: center;
align-items: center;
padding: 0.04rem 0.08rem;
background: #EDF9FA;
border-radius: 0.04rem;
font-weight: 330;
font-size: 0.12rem;
line-height: 0.16rem;
color: $accent;
}
// 底部按钮区域
&__footer {
margin-top: auto;
padding: 0.24rem;
text-align: center;
}
&__cta-btn {
width: 100%;
height: 0.40rem;
background: linear-gradient(128.17deg, #52CAD1 0%, #82D79B 100%);
border-radius: 0.12rem;
border: none;
font-weight: 520;
font-size: 0.14rem;
line-height: 0.19rem;
color: $bg-white;
cursor: pointer;
transition: opacity 0.15s;
&:hover {
opacity: 0.9;
}
}
&__footer-tip {
font-weight: 330;
font-size: 0.12rem;
line-height: 0.16rem;
color: $text-middle;
margin-top: 0.08rem;
}
// ==================== 模式二:模拟对话样式 ====================
&__chat {
flex: 1;
overflow-y: auto;
padding: 0.16rem 0.24rem;
&::-webkit-scrollbar {
width: 4px;
}
&::-webkit-scrollbar-thumb {
background: $border-color;
border-radius: 2px;
}
}
// 对话加载中
&__chat-loading {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 0.12rem;
padding: 0.4rem 0;
color: $text-middle;
font-size: 0.13rem;
}
&__chat-spinner {
width: 0.24rem;
height: 0.24rem;
border: 2px solid $border-color;
border-top-color: $accent;
border-radius: 50%;
animation: mentorChatSpin 0.7s linear infinite;
}
@keyframes mentorChatSpin {
to { transform: rotate(360deg); }
}
// 对话气泡
&__chat-bubble {
display: flex;
margin-bottom: 0.16rem;
&:last-child {
margin-bottom: 0;
}
// 用户消息 — 右对齐,品牌色背景
&--user {
justify-content: flex-end;
.mentor-aside-panel__chat-text {
background: $accent;
color: #FFFFFF;
border-radius: 0.12rem 0.04rem 0.12rem 0.12rem;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.04);
}
}
// 导师消息 — 左对齐,白色背景
&--mentor {
justify-content: flex-start;
.mentor-aside-panel__chat-text {
background: #FFFFFF;
color: $text-dark;
border-radius: 0.04rem 0.12rem 0.12rem 0.12rem;
box-shadow: 0 2px 4px rgba(82, 202, 209, 0.1);
}
}
}
&__chat-text {
display: inline-block;
max-width: 2.38rem;
padding: 0.08rem 0.12rem;
font-weight: 330;
font-size: 0.14rem;
line-height: 0.24rem;
word-break: break-word;
}
}
// ==================== 二维码弹窗样式 ====================
/* 遮罩层 */
.mentor-qr-overlay {
position: fixed;
inset: 0;
z-index: 9999;
background: rgba(0, 0, 0, 0.4);
display: flex;
align-items: center;
justify-content: center;
}
/* 弹窗卡片 */
.mentor-qr-card {
font-size: 0.14rem;
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: 0px 0px 16px rgba(0, 0, 0, 0.05);
border-radius: 0.12rem;
// 头部:标题 + 关闭按钮
&__header {
display: flex;
justify-content: space-between;
align-items: flex-start;
width: 100%;
}
&__title-wrap {
display: flex;
flex-direction: column;
gap: 0.04rem;
}
&__title {
font-weight: 520;
font-size: 0.18rem;
line-height: 0.24rem;
color: #1A1A1A;
}
&__desc {
font-weight: 330;
font-size: 0.12rem;
line-height: 0.16rem;
color: #6A7282;
}
&__close {
width: 0.24rem;
height: 0.24rem;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
&:hover {
opacity: 0.7;
}
svg {
width: 0.24rem;
height: 0.24rem;
}
}
// 二维码展示区域
&__qr-area {
box-sizing: border-box;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 0.16rem 0;
width: 100%;
min-height: 2.72rem;
background: #F4FBFB;
border: 1px solid #AEE6EA;
border-radius: 0.08rem;
}
&__qr-img {
width: 2.40rem;
height: 2.40rem;
object-fit: contain;
}
&__placeholder {
font-size: 0.14rem;
color: #6A7282;
}
&__loading {
display: flex;
align-items: center;
justify-content: center;
width: 2.40rem;
height: 2.40rem;
}
&__spinner {
width: 0.32rem;
height: 0.32rem;
border: 3px solid #AEE6EA;
border-top-color: #52CAD1;
border-radius: 50%;
animation: mentorQrSpin 0.7s linear infinite;
}
@keyframes mentorQrSpin {
to { transform: rotate(360deg); }
}
// 底部提示
&__footer {
width: 100%;
}
&__footer-text {
box-sizing: border-box;
display: flex;
justify-content: center;
align-items: center;
padding: 0.12rem 0.16rem;
width: 100%;
background: #FFFFFF;
border: 1px solid #F0F0F0;
border-radius: 0.08rem;
font-weight: 330;
font-size: 0.14rem;
line-height: 0.19rem;
color: #6A7282;
}
}
@@ -16,7 +16,6 @@
&__tabs {
display: flex;
align-items: center;
gap: 0.24rem;
padding-bottom: 0.14rem;
flex-shrink: 0;
margin-bottom:0.14rem
+1
View File
@@ -43,6 +43,7 @@
@use './components/agreement-preview-dialog.scss';
@use './components/member-access-dialog.scss';
@use './components/step-progress-overlay.scss';
@use './components/mentor-aside-panel.scss';
// 全局样式(优先级最高)
@use './auto.scss';
+173 -35
View File
@@ -798,10 +798,10 @@
to { transform: rotate(360deg); }
}
// ==================== AI助手投递提醒弹窗 ====================
// ==================== 选择投递方式弹窗 ====================
// 遮罩层
&__agent-remind-overlay {
&__apply-method-overlay {
position: fixed;
top: 0;
left: 0;
@@ -815,54 +815,192 @@
}
// 弹窗主体
&__agent-remind-dialog {
&__apply-method-dialog {
position: relative;
width: 4.8rem;
background: $bg-white;
border: 1px solid #F0F0F0;
box-shadow: 0 0 0.16rem rgba(0, 0, 0, 0.05);
border-radius: 0.12rem;
padding: 0.32rem 0.36rem 0.24rem;
min-width: 3.2rem;
text-align: center;
padding: 0.28rem 0.32rem 0.32rem;
box-sizing: border-box;
overflow: hidden;
}
// 提示文字
&__agent-remind-text {
font-size: 0.15rem;
// 渐变背景层
&__apply-method-bg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(180deg, #EDF9FA 0%, #FFFFFF 20.63%);
border-radius: 0.12rem;
pointer-events: none;
}
// 标题
&__apply-method-title {
position: relative;
font-size: 0.18rem;
font-weight: 520;
color: $text-dark;
font-weight: 500;
margin-bottom: 0.28rem;
line-height: 1.5;
text-align: center;
margin: 0 0 0.2rem 0;
line-height: 0.24rem;
}
// 按钮区域
&__agent-remind-actions {
display: flex;
gap: 0.16rem;
}
// 按钮通用
&__agent-remind-btn {
flex: 1;
padding: 0.1rem 0;
border-radius: 0.08rem;
font-size: 0.14rem;
font-weight: 500;
// 关闭按钮
&__apply-method-close {
position: absolute;
top: 0.24rem;
right: 0.24rem;
width: 0.24rem;
height: 0.24rem;
cursor: pointer;
border: none;
transition: opacity 0.2s;
display: flex;
align-items: center;
justify-content: center;
z-index: 1;
&:hover {
opacity: 0.85;
opacity: 0.7;
}
}
// 灰色按钮 — 直接投
&--secondary {
background: $bg-main;
color: $text-middle;
// 选项卡容器
&__apply-method-options {
position: relative;
display: flex;
gap: 0.16rem;
margin-bottom: 0.24rem;
}
// 主题色按钮 — 去AI助手
&--primary {
background: $btn-dark;
// 选项卡
&__apply-method-card {
position: relative;
flex: 1;
height: 1.06rem;
background: $bg-white;
border: 1px solid #F0F0F0;
border-radius: 0.08rem;
padding: 0.2rem 0;
box-sizing: border-box;
cursor: pointer;
text-align: center;
transition: border-color 0.2s, background 0.2s;
overflow: hidden;
&:hover {
border-color: #D0D0D0;
}
// 选中态
&--active {
border-color: $accent;
background: #EDF9FA;
}
}
// 选项卡标题
&__apply-method-card-title {
font-size: 0.14rem;
font-weight: 450;
color: #000;
margin: 0;
line-height: 0.19rem;
// 品牌色标题
&--accent {
color: $accent;
}
}
// 选项卡描述文字
&__apply-method-card-desc {
font-size: 0.12rem;
font-weight: 330;
color: $text-light;
line-height: 0.2rem;
margin: 0.06rem 0 0 0;
white-space: pre-line;
}
// 选项卡头部(icon + 标题)
&__apply-method-card-header {
display: flex;
align-items: center;
justify-content: center;
gap: 0.04rem;
}
// 左下角圆形装饰
&__apply-method-card-circle {
position: absolute;
width: 0.46rem;
height: 0.46rem;
left: -0.05rem;
bottom: -0.08rem;
background: #AEE6EA;
border-radius: 50%;
opacity: 1;
transition: opacity 0.2s;
.jobs-page__apply-method-card--active & {
opacity: 1;
}
// 圆形上的两个白色小圆点(眼睛)
&::before,
&::after {
content: '';
position: absolute;
width: 0.04rem;
height: 0.09rem;
background: $bg-white;
border-radius: 0.02rem;
top: 40%;
}
&::before {
left: 42%;
}
&::after {
left: 60%;
}
}
// 底部操作按钮
&__apply-method-action {
position: relative;
width: 100%;
height: 0.35rem;
border: none;
border-radius: 0.08rem;
font-size: 0.14rem;
font-weight: 450;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: background 0.2s, color 0.2s;
// 未选择状态 — 浅色背景
&--disabled {
background: #EDF9FA;
color: #AEE6EA;
cursor: not-allowed;
}
// 已选择状态 — 品牌色背景
&--active {
background: $accent;
color: $bg-white;
&:hover {
background: $accent-hover;
}
}
}
}
+55 -4
View File
@@ -15,9 +15,26 @@
height: 100%;
overflow: hidden;
margin-left: 2rem;
padding: 0.24rem 0.4rem 0;
display: flex;
}
// 左侧主内容区
&__main {
flex: 1;
height: 100%;
overflow: hidden;
display: flex;
flex-direction: column;
min-width: 0;
}
// 右侧广告区域
&__aside {
width: 3.6rem;
flex-shrink: 0;
height: 100%;
background: #fff;
overflow-y: auto;
}
// 页面标题
@@ -25,8 +42,9 @@
font-size: 0.18rem;
font-weight: 600;
color: $text-dark;
margin-bottom: 0.12rem;
flex-shrink: 0;
padding: 0.16rem 0.24rem;
background: #fff;
}
// 返回按钮
@@ -37,7 +55,7 @@
font-size: 0.13rem;
color: $text-middle;
cursor: pointer;
margin-bottom: 0.16rem;
margin: 0.16rem 0.24rem;
flex-shrink: 0;
&:hover {
@@ -55,6 +73,7 @@
flex: 1;
overflow-y: auto;
padding-bottom: 0.24rem;
margin:0 0.24rem 0.16rem 0.24rem;
&::-webkit-scrollbar {
width: 4px;
@@ -77,7 +96,7 @@
&__info-card {
display: flex;
align-items: center;
align-items: stretch;
justify-content: space-between;
gap: 0.2rem;
}
@@ -153,6 +172,38 @@
}
}
// 右侧咨询区域容器
&__consult-wrap {
display: flex;
flex-direction: column;
align-items: flex-end;
justify-content: space-between;
flex-shrink: 0;
}
// 咨询价格
&__price {
display: flex;
align-items: baseline;
gap: 0.04rem;
}
&__price-label {
font-size: 0.13rem;
color: $text-middle;
}
&__price-value {
font-size: 0.22rem;
font-weight: 700;
color: #f5a623;
}
&__price-suffix {
font-size: 0.13rem;
color: $text-middle;
}
// 立即咨询按钮
&__consult-btn {
width: 0.92rem;
+27 -3
View File
@@ -249,12 +249,13 @@
font-weight: 600;
color: $text-dark;
margin-bottom: 0.04rem;
white-space: nowrap;
}
&__card-meta {
font-size: 0.12rem;
color: $text-middle;
margin-bottom: 0.08rem;
margin-bottom: 0.06rem;
}
&__card-sep {
@@ -276,6 +277,30 @@
padding: 0.02rem 0.08rem;
}
// 咨询价格
&__card-price {
display: flex;
align-items: baseline;
gap: 0.04rem;
margin-top: 0.06rem;
}
&__card-price-label {
font-size: 0.12rem;
color: $text-middle;
}
&__card-price-value {
font-size: 0.18rem;
font-weight: 700;
color: #f5a623;
}
&__card-price-suffix {
font-size: 0.12rem;
color: $text-middle;
}
// 卡片右侧 — flex:1 让话题区自适应剩余空间,窄屏不会撑出滚动条
&__card-right {
flex: 1 1 0;
@@ -369,11 +394,10 @@
// 右侧广告区域(固定 4rem
&__aside {
width: 3rem;
width: 3.6rem;
flex-shrink: 0;
height: 100%;
background: $bg-white;
border-left: 1px solid $border-color;
overflow-y: auto;
}
}
+320
View File
@@ -0,0 +1,320 @@
<template>
<!-- 导师页右侧广告面板模式一列表页/ 模式二详情页 -->
<div class="mentor-aside-panel">
<!-- ==================== 模式一导师列表页展示 ==================== -->
<template v-if="mode === 'list'">
<!-- 顶部渐变区域标题 + 副标题 -->
<div class="mentor-aside-panel__header">
<div class="mentor-aside-panel__title">和真实职场导师聊聊</div>
<div class="mentor-aside-panel__subtitle">让职业选择更清晰</div>
</div>
<!-- 统计数字区域 -->
<div class="mentor-aside-panel__stats">
<div class="mentor-aside-panel__stat-item">
<span class="mentor-aside-panel__stat-num">3000+</span>
<span class="mentor-aside-panel__stat-label">名企导师</span>
</div>
<div class="mentor-aside-panel__stat-divider"></div>
<div class="mentor-aside-panel__stat-item">
<span class="mentor-aside-panel__stat-num">5000+</span>
<span class="mentor-aside-panel__stat-label">服务学员</span>
</div>
</div>
<!-- 为什么选择我们的导师 -->
<div class="mentor-aside-panel__reasons">
<div class="mentor-aside-panel__section-title">
<span class="mentor-aside-panel__section-icon"></span>
<span class="mentor-aside-panel__section-text">为什么选择我们的导师</span>
</div>
<div class="mentor-aside-panel__reason-list">
<!-- 真实在职导师 -->
<div class="mentor-aside-panel__reason-item">
<div class="mentor-aside-panel__reason-icon">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none">
<path d="M12 12C14.21 12 16 10.21 16 8C16 5.79 14.21 4 12 4C9.79 4 8 5.79 8 8C8 10.21 9.79 12 12 12ZM12 14C9.33 14 4 15.34 4 18V20H20V18C20 15.34 14.67 14 12 14Z" fill="#52CAD1"/>
</svg>
</div>
<div class="mentor-aside-panel__reason-info">
<div class="mentor-aside-panel__reason-title">真实在职导师</div>
<div class="mentor-aside-panel__reason-desc">信息真实经验更落地</div>
</div>
</div>
<!-- 名企背景严选 -->
<div class="mentor-aside-panel__reason-item">
<div class="mentor-aside-panel__reason-icon">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none">
<path d="M12 2L4 6V12C4 16.42 7.44 20.74 12 22C16.56 20.74 20 16.42 20 12V6L12 2Z" fill="#52CAD1"/>
</svg>
</div>
<div class="mentor-aside-panel__reason-info">
<div class="mentor-aside-panel__reason-title">名企背景严选</div>
<div class="mentor-aside-panel__reason-desc">覆盖大厂外企央国企等类型</div>
</div>
</div>
<!-- 企业一线视角 -->
<div class="mentor-aside-panel__reason-item">
<div class="mentor-aside-panel__reason-icon">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none">
<path d="M20 6H16V4C16 2.89 15.11 2 14 2H10C8.89 2 8 2.89 8 4V6H4C2.89 6 2 6.89 2 8V19C2 20.11 2.89 21 4 21H20C21.11 21 22 20.11 22 19V8C22 6.89 21.11 6 20 6ZM10 4H14V6H10V4Z" fill="#52CAD1"/>
</svg>
</div>
<div class="mentor-aside-panel__reason-info">
<div class="mentor-aside-panel__reason-title">企业一线视角</div>
<div class="mentor-aside-panel__reason-desc">不讲空话只讲真实经验</div>
</div>
</div>
<!-- 个性化辅导方案 -->
<div class="mentor-aside-panel__reason-item">
<div class="mentor-aside-panel__reason-icon">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none">
<path d="M12 3L1 9L5 11.18V17.18L12 21L19 17.18V11.18L21 10.09V17H23V9L12 3ZM18.82 9L12 12.72L5.18 9L12 5.28L18.82 9ZM17 15.99L12 18.72L7 15.99V12.27L12 15L17 12.27V15.99Z" fill="#52CAD1"/>
</svg>
</div>
<div class="mentor-aside-panel__reason-info">
<div class="mentor-aside-panel__reason-title">个性化辅导方案</div>
<div class="mentor-aside-panel__reason-desc">针对不同需求量身定制学习路径</div>
</div>
</div>
</div>
</div>
<!-- 热门导师方向 -->
<div class="mentor-aside-panel__directions">
<div class="mentor-aside-panel__section-title">
<span class="mentor-aside-panel__section-icon"></span>
<span class="mentor-aside-panel__section-text">热门导师方向</span>
</div>
<div class="mentor-aside-panel__direction-list">
<div class="mentor-aside-panel__direction-card">
<div class="mentor-aside-panel__direction-name">互联网产品导师</div>
<div class="mentor-aside-panel__direction-tags">
<span class="mentor-aside-panel__direction-tag">需求分析</span>
<span class="mentor-aside-panel__direction-tag">PRD撰写</span>
<span class="mentor-aside-panel__direction-tag">产品设计</span>
</div>
</div>
<div class="mentor-aside-panel__direction-card">
<div class="mentor-aside-panel__direction-name">金融/财务导师</div>
<div class="mentor-aside-panel__direction-tags">
<span class="mentor-aside-panel__direction-tag">财务分析</span>
<span class="mentor-aside-panel__direction-tag">估值建模</span>
<span class="mentor-aside-panel__direction-tag">行业研究</span>
</div>
</div>
<div class="mentor-aside-panel__direction-card">
<div class="mentor-aside-panel__direction-name">数据分析导师</div>
<div class="mentor-aside-panel__direction-tags">
<span class="mentor-aside-panel__direction-tag">数据收集</span>
<span class="mentor-aside-panel__direction-tag">分析报告</span>
<span class="mentor-aside-panel__direction-tag">指标监测</span>
</div>
</div>
<div class="mentor-aside-panel__direction-card">
<div class="mentor-aside-panel__direction-name">市场营销导师</div>
<div class="mentor-aside-panel__direction-tags">
<span class="mentor-aside-panel__direction-tag">市场调研</span>
<span class="mentor-aside-panel__direction-tag">品牌策略</span>
<span class="mentor-aside-panel__direction-tag">推广活动</span>
</div>
</div>
</div>
</div>
<!-- 底部按钮 + 提示文字 -->
<div class="mentor-aside-panel__footer">
<button class="mentor-aside-panel__cta-btn" @click="openQrDialog">预约导师咨询</button>
<div class="mentor-aside-panel__footer-tip">已有5000+学员获得帮助</div>
</div>
</template>
<!-- ==================== 模式二导师详情页 辅导场景预览 ==================== -->
<template v-if="mode === 'detail'">
<!-- 顶部渐变区域 -->
<div class="mentor-aside-panel__header">
<div class="mentor-aside-panel__title">辅导场景预览</div>
<div class="mentor-aside-panel__subtitle">看看这位导师能如何帮到你</div>
</div>
<!-- 模拟对话区域 -->
<div class="mentor-aside-panel__chat">
<!-- 加载中 -->
<div v-if="chatLoading" class="mentor-aside-panel__chat-loading">
<div class="mentor-aside-panel__chat-spinner"></div>
<span>正在生成对话预览...</span>
</div>
<!-- 对话消息列表 -->
<template v-else>
<div
v-for="(msg, index) in chatMessages"
:key="index"
class="mentor-aside-panel__chat-bubble"
:class="{
'mentor-aside-panel__chat-bubble--user': msg.role === 'user',
'mentor-aside-panel__chat-bubble--mentor': msg.role === 'mentor'
}"
>
<span class="mentor-aside-panel__chat-text">{{ msg.content }}</span>
</div>
</template>
</div>
<!-- 底部按钮 + 提示文字 -->
<div class="mentor-aside-panel__footer">
<button class="mentor-aside-panel__cta-btn" @click="openQrDialog">立即咨询导师</button>
<div class="mentor-aside-panel__footer-tip">对话内容根据导师服务类型与行业背景动态生成</div>
</div>
</template>
<!-- ==================== 二维码弹窗遮罩 + 居中卡片 ==================== -->
<teleport to="body">
<div v-if="showQrDialog" class="mentor-qr-overlay" @click.self="closeQrDialog">
<div class="mentor-qr-card">
<!-- 头部标题 + 关闭按钮 -->
<div class="mentor-qr-card__header">
<div class="mentor-qr-card__title-wrap">
<div class="mentor-qr-card__title">扫码进入小程序预约导师咨询</div>
<div class="mentor-qr-card__desc">查看导师价格过往评价并完成预约</div>
</div>
<!-- 关闭按钮 -->
<div class="mentor-qr-card__close" @click="closeQrDialog">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none">
<path d="M18 6L6 18M6 6l12 12" stroke="#6A7282" stroke-width="2" stroke-linecap="round"/>
</svg>
</div>
</div>
<!-- 二维码展示区域 -->
<div class="mentor-qr-card__qr-area">
<div v-if="qrLoading" class="mentor-qr-card__loading">
<div class="mentor-qr-card__spinner"></div>
</div>
<img
v-else-if="qrCodeBase64"
:src="qrCodeBase64"
alt="小程序二维码"
class="mentor-qr-card__qr-img"
/>
<div v-else class="mentor-qr-card__placeholder">二维码加载失败</div>
</div>
<!-- 底部提示 -->
<div class="mentor-qr-card__footer">
<div class="mentor-qr-card__footer-text">小程序名称见识星球 | 大学生求职神器</div>
</div>
</div>
</div>
</teleport>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { fetchMentorMockChat, fetchMentorQrCode } from '@/api/mentor'
import type { MentorMockMessage } from '@/api/mentor'
// 模式一固定二维码图片
import fixedQrCodeImg from '@/assets/images/mentor/jsxqQrCode-mentors-page.png'
// ==================== Props ====================
const props = withDefaults(defineProps<{
/** 显示模式:list=列表页,detail=详情页 */
mode?: 'list' | 'detail'
/** 导师ID(模式二需要,用于请求模拟对话) */
mentorId?: string
}>(), {
mode: 'list',
mentorId: '',
})
// ==================== 模式二:模拟对话数据 ====================
/** 对话消息列表 */
const chatMessages = ref<MentorMockMessage[]>([])
/** 对话加载状态 */
const chatLoading = ref(false)
/**
* 加载导师模拟对话
*/
async function loadMockChat() {
if (!props.mentorId) return
chatLoading.value = true
try {
const res: any = await fetchMentorMockChat(props.mentorId)
const data = res?.data ?? res
chatMessages.value = data?.messages ?? []
} catch (e) {
console.error('[模拟对话] 加载失败', e)
} finally {
chatLoading.value = false
}
}
// ==================== 生命周期 ====================
onMounted(() => {
// 模式二时自动加载模拟对话
if (props.mode === 'detail' && props.mentorId) {
loadMockChat()
}
})
// ==================== 二维码弹窗 ====================
/** 弹窗是否显示 */
const showQrDialog = ref(false)
/** 二维码图片 base64 */
const qrCodeBase64 = ref('')
/** 二维码加载状态 */
const qrLoading = ref(false)
/**
* 打开二维码弹窗
* 模式一:展示本地固定二维码图片
* 模式二:请求后端生成导师专属二维码
*/
async function openQrDialog() {
showQrDialog.value = true
if (props.mode === 'list') {
// 模式一:直接使用本地固定图片
qrLoading.value = false
qrCodeBase64.value = fixedQrCodeImg
return
}
// 模式二:调接口获取 base64 二维码
qrLoading.value = true
qrCodeBase64.value = ''
try {
const res: any = await fetchMentorQrCode(props.mentorId)
// 返回值为 base64 图片字符串
const base64 = res?.data ?? res
if (typeof base64 === 'string' && base64.length > 0) {
// 如果后端返回的已经包含 data:image 前缀就直接用,否则拼上
qrCodeBase64.value = base64.startsWith('data:image')
? base64
: `data:image/png;base64,${base64}`
}
} catch (e) {
console.error('[二维码] 获取失败', e)
} finally {
qrLoading.value = false
}
}
/**
* 关闭弹窗
*/
function closeQrDialog() {
showQrDialog.value = false
}
</script>
+201 -68
View File
@@ -203,7 +203,7 @@
<!-- 按钮行 -->
<div class="jobs-page__job-action-right">
<button class="jobs-page__job-helper" @click.stop="askAssistant(job)">问Nova</button>
<button @click.stop="handleReport(job.sourceUrl)" class="jobs-page__job-apply-btn" :class="{ 'jobs-page__job-apply-btn--active': job.applied }">去投递</button>
<button @click.stop="handleReport(job)" class="jobs-page__job-apply-btn" :class="{ 'jobs-page__job-apply-btn--active': job.applied }">去投递</button>
</div>
<!-- 不再显示 -->
<span v-if="isRecommendTab" class="jobs-page__hide-btn" @click.stop="openDislikeDialog(job)">不再显示</span>
@@ -270,16 +270,54 @@
<!-- 职位问题反馈弹窗 -->
<JobFeedbackDialog ref="feedbackDialogRef" v-model="showFeedbackDialog" :job-id="feedbackJobId" />
<!-- AI助手投递提醒弹窗 -->
<div v-if="showAgentRemindDialog" class="jobs-page__agent-remind-overlay" @click.self="closeAgentRemind">
<div class="jobs-page__agent-remind-dialog">
<p class="jobs-page__agent-remind-text">去AI助手投递岗位效果更好哦</p>
<div class="jobs-page__agent-remind-actions">
<button class="jobs-page__agent-remind-btn jobs-page__agent-remind-btn--secondary" @click="directApply">直接投</button>
<button class="jobs-page__agent-remind-btn jobs-page__agent-remind-btn--primary" @click="goToAgent">去AI助手</button>
<!-- 选择投递方式弹窗 -->
<div v-if="showAgentRemindDialog" class="jobs-page__apply-method-overlay" @click.self="closeAgentRemind">
<div class="jobs-page__apply-method-dialog">
<!-- 渐变背景层 -->
<div class="jobs-page__apply-method-bg"></div>
<!-- 标题 -->
<p class="jobs-page__apply-method-title">选择投递方式</p>
<!-- 关闭按钮 -->
<span class="jobs-page__apply-method-close" @click="closeAgentRemind">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none"><path d="M18 6L6 18M6 6l12 12" stroke="#6A7282" stroke-width="1.5" stroke-linecap="round"/></svg>
</span>
<!-- 两个选项卡 -->
<div class="jobs-page__apply-method-options">
<!-- 直接前往官网 -->
<div class="jobs-page__apply-method-card" :class="{ 'jobs-page__apply-method-card--active': applyMethodChoice === 'direct' }" @click="applyMethodChoice = 'direct'">
<p class="jobs-page__apply-method-card-title">直接前往官网</p>
<p class="jobs-page__apply-method-card-desc">手动填写网申信息<br>自行记录投递进度</p>
</div>
<!-- 智能投递助手 -->
<div class="jobs-page__apply-method-card" :class="{ 'jobs-page__apply-method-card--active': applyMethodChoice === 'agent' }" @click="applyMethodChoice = 'agent'">
<!-- 左下角圆形装饰 -->
<div class="jobs-page__apply-method-card-circle"></div>
<div class="jobs-page__apply-method-card-header">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M8 1L10 5.5L15 6.5L11.5 10L12.5 15L8 12.5L3.5 15L4.5 10L1 6.5L6 5.5L8 1Z" fill="#52CAD1"/></svg>
<span class="jobs-page__apply-method-card-title jobs-page__apply-method-card-title--accent">智能投递助手</span>
</div>
<p class="jobs-page__apply-method-card-desc">{{ agentCardDesc }}</p>
</div>
</div>
<!-- 底部操作按钮 -->
<button class="jobs-page__apply-method-action" :class="applyMethodActionClass" :disabled="!applyMethodChoice" @click="handleApplyMethodAction">
<svg v-if="applyMethodActionIcon" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_166_1554)">
<path d="M6.0714 3.63204C6.47007 2.46537 8.08207 2.43004 8.55474 3.52604L8.59474 3.63271L9.13274 5.20604C9.25603 5.56686 9.45527 5.89705 9.71702 6.17432C9.97877 6.4516 10.2969 6.66952 10.6501 6.81337L10.7947 6.86737L12.3681 7.40471C13.5347 7.80337 13.5701 9.41537 12.4747 9.88804L12.3681 9.92804L10.7947 10.466C10.4338 10.5893 10.1035 10.7885 9.82608 11.0502C9.54869 11.312 9.33067 11.6302 9.18674 11.9834L9.13274 12.1274L8.5954 13.7014C8.19674 14.868 6.58474 14.9034 6.11274 13.808L6.0714 13.7014L5.53407 12.128C5.41086 11.7671 5.21165 11.4368 4.9499 11.1594C4.68814 10.882 4.36993 10.664 4.01674 10.52L3.87274 10.466L2.2994 9.92871C1.13207 9.53004 1.09674 7.91804 2.19274 7.44604L2.2994 7.40471L3.87274 6.86737C4.23356 6.74408 4.56374 6.54484 4.84102 6.28309C5.11829 6.02134 5.33621 5.70317 5.48007 5.35004L5.53407 5.20604L6.0714 3.63204ZM7.3334 4.06271L6.79607 5.63604C6.60833 6.18624 6.30292 6.68891 5.90109 7.10904C5.49927 7.52917 5.0107 7.85666 4.4694 8.06871L4.30274 8.12937L2.7294 8.66671L4.30274 9.20404C4.85294 9.39178 5.35561 9.6972 5.77573 10.099C6.19586 10.5008 6.52335 10.9894 6.7354 11.5307L6.79607 11.6974L7.3334 13.2707L7.87074 11.6974C8.05848 11.1472 8.36389 10.6445 8.76571 10.2244C9.16753 9.80425 9.65611 9.47676 10.1974 9.26471L10.3641 9.20471L11.9374 8.66671L10.3641 8.12937C9.81387 7.94163 9.3112 7.63622 8.89107 7.2344C8.47094 6.83258 8.14345 6.34401 7.9314 5.80271L7.8714 5.63604L7.3334 4.06271ZM12.6667 1.33337C12.7915 1.33337 12.9137 1.36836 13.0195 1.43436C13.1253 1.50035 13.2105 1.59471 13.2654 1.70671L13.2974 1.78471L13.5307 2.46871L14.2154 2.70204C14.3404 2.7445 14.45 2.82312 14.5302 2.92792C14.6105 3.03272 14.6578 3.15899 14.6663 3.29072C14.6747 3.42246 14.6438 3.55373 14.5775 3.6679C14.5113 3.78207 14.4126 3.874 14.2941 3.93204L14.2154 3.96404L13.5314 4.19737L13.2981 4.88204C13.2555 5.00699 13.1769 5.11651 13.072 5.1967C12.9672 5.2769 12.8409 5.32417 12.7092 5.33252C12.5774 5.34088 12.4462 5.30994 12.3321 5.24363C12.2179 5.17732 12.1261 5.07862 12.0681 4.96004L12.0361 4.88204L11.8027 4.19804L11.1181 3.96471C10.9931 3.92224 10.8835 3.84363 10.8033 3.73883C10.723 3.63403 10.6756 3.50776 10.6672 3.37603C10.6588 3.24429 10.6897 3.11302 10.7559 2.99885C10.8222 2.88468 10.9208 2.79275 11.0394 2.73471L11.1181 2.70271L11.8021 2.46937L12.0354 1.78471C12.0804 1.65299 12.1654 1.53865 12.2786 1.45771C12.3918 1.37677 12.5276 1.33329 12.6667 1.33337Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_166_1554">
<rect width="16" height="16" fill="white"/>
</clipPath>
</defs>
</svg>
{{ applyMethodActionText }}
</button>
</div>
</div>
<!-- 会员购买弹窗 -->
<MemberDialog v-model="showMemberDialog" />
</div>
</template>
@@ -294,6 +332,7 @@ import AiChat from '@/components/AiChat.vue'
import JobPageHeader from '@/components/JobPageHeader.vue'
import JobDislikeDialog from '@/components/JobDislikeDialog.vue'
import JobFeedbackDialog from '@/components/JobFeedbackDialog.vue'
import MemberDialog from '@/components/MemberDialog.vue'
import IndustrySelector from '@/components/tools/IndustrySelector.vue'
import JobCategorySelector from '@/components/tools/JobCategorySelector.vue'
import RegionSelector from '@/components/tools/RegionSelector.vue'
@@ -304,6 +343,8 @@ import type { JobListItem, JobListParams, FavoriteListParams, ApplyListParams, A
import { getTimeEvent, setTimeEvent, timestampToLocalDateTime } from '@/utils/time'
import { fetchIndustryTree, fetchJobCategoryTree } from '@/api/common'
import type { IndustryItem, JobCategoryItem } from '@/api/common'
import { fetchAgentConfig, applyJob } from '@/api/agent'
import { ElMessage } from 'element-plus'
// 2. 注意:这里的字符串不需要再手动转义双引号了
@@ -668,9 +709,9 @@ function closeDropdownOnClickOutside(e: MouseEvent) {
}
}
/** 跳转到原链接 — 带 AI 助手提醒弹窗频率控制 */
/** 跳转到原链接 — 选择投递方式弹窗 */
/**
* 提醒弹窗频率逻辑说明
* 旧的提醒弹窗频率逻辑(已注销,保留不删)
* 1. 未登录用户:直接跳转外部链接,不弹窗
* 2. 已登录用户首次点击(无 ai_agent_remind_first 缓存):弹窗提醒,记录第一次提醒时间
* 3. 有第一次记录但无第二次记录:
@@ -680,89 +721,181 @@ function closeDropdownOnClickOutside(e: MouseEvent) {
* - 距第二次记录 ≤ 7天:直接跳转,不弹窗
* - 距第二次记录 > 7天:弹窗提醒,更新第二次记录时间为当前时间
*/
function handleReport(url: string) {
// 暂存当前要跳转的链接
pendingApplyUrl.value = url
// function handleReportOld(url: string) {
// pendingApplyUrl.value = url
// if (!isAuthenticated.value) { window.open(url, '_blank'); return }
// const userId = store.state.userInfo?.id
// if (!userId) { window.open(url, '_blank'); return }
// const now = Date.now()
// const firstTime = getTimeEvent('ai_agent_remind_first', userId)
// const secondTime = getTimeEvent('ai_agent_remind_second', userId)
// if (!firstTime) {
// setTimeEvent('ai_agent_remind_first', userId, timestampToLocalDateTime(now))
// showAgentRemindDialog.value = true; return
// }
// if (!secondTime) {
// const firstTimestamp = new Date(firstTime).getTime()
// const diffHours = (now - firstTimestamp) / (1000 * 60 * 60)
// if (diffHours <= 1) { window.open(url, '_blank') }
// else { setTimeEvent('ai_agent_remind_second', userId, timestampToLocalDateTime(now)); showAgentRemindDialog.value = true }
// return
// }
// const secondTimestamp = new Date(secondTime).getTime()
// const diffDays = (now - secondTimestamp) / (1000 * 60 * 60 * 24)
// if (diffDays <= 7) { window.open(url, '_blank') }
// else { setTimeEvent('ai_agent_remind_second', userId, timestampToLocalDateTime(now)); showAgentRemindDialog.value = true }
// }
// 未登录直接跳转
if (!isAuthenticated.value) {
window.open(url, '_blank')
return
}
const userId = store.state.userInfo?.id
if (!userId) {
window.open(url, '_blank')
return
}
const now = Date.now()
const firstTime = getTimeEvent('ai_agent_remind_first', userId)
const secondTime = getTimeEvent('ai_agent_remind_second', userId)
if (!firstTime) {
// 情况1:从未弹过 → 弹窗,记录第一次时间
setTimeEvent('ai_agent_remind_first', userId, timestampToLocalDateTime(now))
/** 点击"去投递"按钮 — 每次都弹出选择投递方式弹窗 */
function handleReport(job: JobListItem) {
// 暂存当前岗位信息
pendingApplyUrl.value = job.sourceUrl
pendingApplyJobId.value = job.id
// 重置选项状态
applyMethodChoice.value = null
// 每次触发都展示弹窗
showAgentRemindDialog.value = true
return
// 异步加载 AI 助手配置状态
loadAgentConfigStatus()
}
if (!secondTime) {
// 情况2:有第一次记录,无第二次记录 → 检查是否超过1小时
const firstTimestamp = new Date(firstTime).getTime()
const diffHours = (now - firstTimestamp) / (1000 * 60 * 60)
if (diffHours <= 1) {
// 1小时内不弹窗,直接跳转
window.open(url, '_blank')
} else {
// 超过1小时 → 弹窗,记录第二次时间
setTimeEvent('ai_agent_remind_second', userId, timestampToLocalDateTime(now))
showAgentRemindDialog.value = true
}
return
}
// ==================== 选择投递方式弹窗状态 ====================
// 情况3:有第二次记录 → 检查是否超过7天
const secondTimestamp = new Date(secondTime).getTime()
const diffDays = (now - secondTimestamp) / (1000 * 60 * 60 * 24)
if (diffDays <= 7) {
// 7天内不弹窗,直接跳转
window.open(url, '_blank')
} else {
// 超过7天 → 弹窗,更新第二次记录时间
setTimeEvent('ai_agent_remind_second', userId, timestampToLocalDateTime(now))
showAgentRemindDialog.value = true
}
}
// ==================== AI助手提醒弹窗状态 ====================
/** AI助手提醒弹窗是否显示 */
/** 弹窗是否显示 */
const showAgentRemindDialog = ref(false)
/** 暂存待跳转的外部链接 */
const pendingApplyUrl = ref('')
/** 关闭AI助手提醒弹窗 */
/** 暂存当前触发弹窗的岗位 ID */
const pendingApplyJobId = ref('')
/** 用户在弹窗中选择的投递方式:null=未选择, 'direct'=直接前往官网, 'agent'=智能投递助手 */
const applyMethodChoice = ref<'direct' | 'agent' | null>(null)
/** AI助手是否已完成配置(status === 1 */
const agentConfigReady = ref(false)
/** 是否正在加载 AI 助手配置 */
const agentConfigLoading = ref(false)
/** 会员购买弹窗显示状态 */
const showMemberDialog = ref(false)
/** 用户会员状态 — 从 store 读取 */
const memberStatus = computed(() => store.state.memberStatus)
/** 用户是否是会员(正式或试用) */
const isMember = computed(() => !!memberStatus.value?.isMember)
/** 智能投递助手选项卡内的描述文字 — 根据会员和配置状态动态显示 */
const agentCardDesc = computed(() => {
if (!isMember.value) {
return '自动填写网申信息\n统一管理投递进度'
}
if (!agentConfigReady.value) {
return '完成助手设置后\n可使用智能投递'
}
return '将该岗位加入待投递列表\n在投递助手中统一处理'
})
/** 底部操作按钮文字 */
const applyMethodActionText = computed(() => {
if (!applyMethodChoice.value) return '请选择投递方式'
if (applyMethodChoice.value === 'direct') return '去官网投递'
// agent 分支
if (!isMember.value) return '开通会员使用'
if (!agentConfigReady.value) return '去完成设置'
return '加入待投递列表'
})
/** 底部操作按钮是否显示 icon */
const applyMethodActionIcon = computed(() => {
if (!applyMethodChoice.value) return false
if (applyMethodChoice.value === 'direct') return false
// agent 分支都有 icon
return true
})
/** 底部操作按钮样式 class */
const applyMethodActionClass = computed(() => {
if (!applyMethodChoice.value) return 'jobs-page__apply-method-action--disabled'
return 'jobs-page__apply-method-action--active'
})
/** 加载 AI 助手配置状态 */
async function loadAgentConfigStatus() {
if (agentConfigLoading.value) return
agentConfigLoading.value = true
try {
const res = await fetchAgentConfig()
if (res.code === '0' && res.data && res.data.status === 1) {
agentConfigReady.value = true
} else {
agentConfigReady.value = false
}
} catch {
agentConfigReady.value = false
} finally {
agentConfigLoading.value = false
}
}
/** 关闭选择投递方式弹窗 */
function closeAgentRemind() {
showAgentRemindDialog.value = false
pendingApplyUrl.value = ''
pendingApplyJobId.value = ''
}
/** 点击"直接投"按钮 — 关闭弹窗并跳转外部链接 */
function directApply() {
/** 底部操作按钮点击处理 */
async function handleApplyMethodAction() {
if (!applyMethodChoice.value) return
if (applyMethodChoice.value === 'direct') {
// 直接前往官网
showAgentRemindDialog.value = false
if (pendingApplyUrl.value) {
window.open(pendingApplyUrl.value, '_blank')
}
pendingApplyUrl.value = ''
pendingApplyJobId.value = ''
return
}
/** 点击"去AI助手"按钮 — 关闭弹窗并跳转到 Agent 页面 */
function goToAgent() {
// 智能投递助手分支
if (!isMember.value) {
// 非会员 — 打开会员购买弹窗
showAgentRemindDialog.value = false
showMemberDialog.value = true
pendingApplyUrl.value = ''
pendingApplyJobId.value = ''
return
}
if (!agentConfigReady.value) {
// 有会员但没完成 AI 助手配置 — 跳转 Agent 页面
showAgentRemindDialog.value = false
pendingApplyUrl.value = ''
pendingApplyJobId.value = ''
router.push('/agent')
return
}
// 有会员且配置完成 — 加入待投递列表
try {
const res = await applyJob({ jobId: pendingApplyJobId.value, status: -1 })
if (res.code === '0') {
ElMessage.success('已加入待投递列表')
} else {
ElMessage.error(res.msg || '加入待投递列表失败')
}
} catch {
ElMessage.error('加入待投递列表失败')
}
showAgentRemindDialog.value = false
pendingApplyUrl.value = ''
pendingApplyJobId.value = ''
}
onMounted(async () => {
+14 -12
View File
@@ -69,19 +69,20 @@
</div>
<!-- 导师基本信息 -->
<div class="mentor-page__card-info">
<div class="mentor-page__card-name">{{ mentor.platformName }}</div>
<div class="dflex-start aliite-e flex-warp">
<div class="mentor-page__card-name mr10">{{ mentor.platformName }}</div>
<div class="mentor-page__card-meta">
<span v-if="mentor.agencyName" class="mentor-page__card-agency">{{ mentor.agencyName }}</span>
<span v-if="mentor.agencyName && mentor.position" class="mentor-page__card-sep"></span>
<span v-if="mentor.position" class="mentor-page__card-position">{{ mentor.position }}</span>
</div>
<!-- 标签 -->
<div class="mentor-page__card-labels" v-if="mentor.labels && mentor.labels.length">
<span
v-for="(label, li) in mentor.labels.slice(0, 4)"
:key="li"
class="mentor-page__card-label"
>{{ label }}</span>
</div>
<!-- 咨询价格 -->
<div class="mentor-page__card-price" v-if="mentor.price">
<span class="mentor-page__card-price-label">咨询价</span>
<span class="mentor-page__card-price-value">¥{{ mentor.price }}</span>
<span class="mentor-page__card-price-suffix"></span>
</div>
</div>
</div>
@@ -101,7 +102,7 @@
class="mentor-page__detail-btn"
@click="goToDetail(mentor)"
><span>查看详情</span> <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.51612 4.07575C7.38279 3.94242 7.31879 3.78686 7.32412 3.60908C7.32945 3.43131 7.39901 3.27575 7.53278 3.14241C7.66612 3.02019 7.82167 2.95619 7.99946 2.95041C8.17723 2.94464 8.33278 3.00864 8.46612 3.14241L12.8661 7.54241C12.9328 7.60908 12.9801 7.6813 13.0081 7.75908C13.0361 7.83686 13.0497 7.92019 13.0488 8.00908C13.0479 8.09797 13.0339 8.18131 13.0068 8.25908C12.9797 8.33686 12.9326 8.40908 12.8654 8.47575L8.46545 12.8757C8.34323 12.998 8.19034 13.0591 8.00679 13.0591C7.82323 13.0591 7.66501 12.998 7.53212 12.8757C7.39879 12.7424 7.33212 12.584 7.33212 12.4004C7.33212 12.2169 7.39879 12.0586 7.53212 11.9257L10.7828 8.67575H3.33279C3.1439 8.67575 2.98546 8.61175 2.85745 8.48375C2.72945 8.35575 2.66567 8.19753 2.66612 8.00908C2.66656 7.82064 2.73056 7.6622 2.85812 7.53375C2.98568 7.40531 3.1439 7.34153 3.33279 7.34242L10.7828 7.34242L7.51612 4.07575Z" fill="#52CAD1"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.51612 4.07575C7.38279 3.94242 7.31879 3.78686 7.32412 3.60908C7.32945 3.43131 7.39901 3.27575 7.53278 3.14241C7.66612 3.02019 7.82167 2.95619 7.99946 2.95041C8.17723 2.94464 8.33278 3.00864 8.46612 3.14241L12.8661 7.54241C12.9328 7.60908 12.9801 7.6813 13.0081 7.75908C13.0361 7.83686 13.0497 7.92019 13.0488 8.00908C13.0479 8.09797 13.0339 8.18131 13.0068 8.25908C12.9797 8.33686 12.9326 8.40908 12.8654 8.47575L8.46545 12.8757C8.34323 12.998 8.19034 13.0591 8.00679 13.0591C7.82323 13.0591 7.66501 12.998 7.53212 12.8757C7.39879 12.7424 7.33212 12.584 7.33212 12.4004C7.33212 12.2169 7.39879 12.0586 7.53212 11.9257L10.7828 8.67575H3.33279C3.1439 8.67575 2.98546 8.61175 2.85745 8.48375C2.72945 8.35575 2.66567 8.19753 2.66612 8.00908C2.66656 7.82064 2.73056 7.6622 2.85812 7.53375C2.98568 7.40531 3.1439 7.34153 3.33279 7.34242L10.7828 7.34242L7.51612 4.07575Z" fill="currentColor"/>
</svg>
</button>
</div>
@@ -125,9 +126,9 @@
</div>
</div>
<!-- 右侧广告区域固定宽度 4rem -->
<!-- 右侧广告区域 -->
<div class="mentor-page__aside">
<!-- 广告内容占位后续填充 -->
<MentorAsidePanel />
</div>
</div>
@@ -139,6 +140,7 @@ import { ref, computed, onMounted, nextTick } from 'vue'
import { useRouter } from 'vue-router'
import { useStore } from 'vuex'
import SideNav from '@/components/SideNav.vue'
import MentorAsidePanel from '@/components/MentorAsidePanel.vue'
import { fetchMentorList } from '@/api/mentor'
import type { MentorItem, MentorListParams } from '@/api/mentor'
import { toOssUrl } from '@/utils/image'
@@ -361,7 +363,7 @@ function goToDetail(mentor: MentorItem) {
pageNum: currentPage.value,
scrollTop: listRef.value?.scrollTop ?? 0,
})
router.push(`/mentor/${mentor.id}`)
router.push({ path: `/mentor/${mentor.id}`, query: { price: String(mentor.price) } })
}
// ==================== 生命周期 ====================
+106 -13
View File
@@ -1,7 +1,9 @@
<template>
<div class="mentor-detail dflex">
<SideNav />
<div class="mentor-detail__body">
<div class="mentor-detail__body dflex">
<!-- 左侧主内容区 -->
<div class="mentor-detail__main">
<!-- 页面标题 -->
<div class="mentor-detail__page-title">导师详情</div>
@@ -46,17 +48,24 @@
所在城市 <span class="">{{ locationText }}</span>
</div>
<div class="mentor-detail__services" v-if="serviceLabels.length">
服务类型 <span>{{ serviceLabels.join('、') }}</span>
服务类型 <span class="ml10">{{ serviceLabels.join('、') }}</span>
</div>
</div>
</div>
<!-- 右侧咨询价 + 立即咨询按钮 -->
<div class="mentor-detail__consult-wrap">
<!-- 咨询价格从列表页传入 -->
<div class="mentor-detail__price" v-if="mentorPrice">
<span class="mentor-detail__price-label">咨询价</span>
<span class="mentor-detail__price-value">¥{{ mentorPrice }}</span>
<span class="mentor-detail__price-suffix"></span>
</div>
<!-- 立即咨询按钮 -->
<a
href="https://www.jianshixingqiu.com"
target="_blank"
rel="noopener noreferrer"
<button
class="mentor-detail__consult-btn"
>立即咨询 </a>
@click="openQrDialog"
>立即咨询 </button>
</div>
</div>
<!-- 擅长话题卡片 -->
@@ -85,6 +94,49 @@
</div>
</div>
</div>
<!-- 右侧广告区域 -->
<div class="mentor-detail__aside">
<MentorAsidePanel mode="detail" :mentorId="mentorId" />
</div>
</div>
<!-- ==================== 二维码弹窗遮罩 + 居中卡片 ==================== -->
<teleport to="body">
<div v-if="showQrDialog" class="mentor-qr-overlay" @click.self="closeQrDialog">
<div class="mentor-qr-card">
<!-- 头部标题 + 关闭按钮 -->
<div class="mentor-qr-card__header">
<div class="mentor-qr-card__title-wrap">
<div class="mentor-qr-card__title">扫码进入小程序预约导师咨询</div>
<div class="mentor-qr-card__desc">查看导师价格过往评价并完成预约</div>
</div>
<div class="mentor-qr-card__close" @click="closeQrDialog">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none">
<path d="M18 6L6 18M6 6l12 12" stroke="#6A7282" stroke-width="2" stroke-linecap="round"/>
</svg>
</div>
</div>
<!-- 二维码展示区域 -->
<div class="mentor-qr-card__qr-area">
<div v-if="qrLoading" class="mentor-qr-card__loading">
<div class="mentor-qr-card__spinner"></div>
</div>
<img
v-else-if="qrCodeBase64"
:src="qrCodeBase64"
alt="小程序二维码"
class="mentor-qr-card__qr-img"
/>
<div v-else class="mentor-qr-card__placeholder">二维码加载失败</div>
</div>
<!-- 底部提示 -->
<div class="mentor-qr-card__footer">
<div class="mentor-qr-card__footer-text">小程序名称见识星球 | 大学生求职神器</div>
</div>
</div>
</div>
</teleport>
</div>
</template>
@@ -92,7 +144,8 @@
import { ref, computed, onMounted } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import SideNav from '@/components/SideNav.vue'
import { fetchMentorFullInfo, fetchMentorTalks } from '@/api/mentor'
import MentorAsidePanel from '@/components/MentorAsidePanel.vue'
import { fetchMentorFullInfo, fetchMentorTalks, fetchMentorQrCode } from '@/api/mentor'
import type { MentorFullInfo, MentorTalk } from '@/api/mentor'
import { toOssUrl } from '@/utils/image'
@@ -104,6 +157,9 @@ const route = useRoute()
/** 导师 ID,从路由参数获取(字符串,避免大数精度丢失) */
const mentorId = route.params.id as string
/** 咨询价格,从列表页 query 传入 */
const mentorPrice = route.query.price as string || ''
// ==================== 页面状态 ====================
/** 是否正在加载 */
@@ -149,12 +205,11 @@ const locationText = computed(() => {
return parts.join(' · ')
})
/** 服务类型标签 */
/** 服务类型标签 — 取 labelRelations 中每项的 labelName */
const serviceLabels = computed(() => {
const supports = fullInfo.value?.supports
if (!supports || !supports.length) return []
const typeMap: Record<number, string> = { 0: '远程实习', 1: '简历内推', 2: '定向内推辅导' }
return supports.map(s => typeMap[s.type] || '').filter(Boolean)
const labels = fullInfo.value?.labelRelations
if (!labels || !labels.length) return []
return labels.map(l => l.labelName).filter(Boolean)
})
// ==================== 数据加载 ====================
@@ -198,4 +253,42 @@ function goBack() {
onMounted(() => {
loadDetail()
})
// ==================== 二维码弹窗 ====================
/** 弹窗是否显示 */
const showQrDialog = ref(false)
/** 二维码图片 base64 */
const qrCodeBase64 = ref('')
/** 二维码加载状态 */
const qrLoading = ref(false)
/**
* 打开二维码弹窗,调接口获取当前导师的小程序二维码
*/
async function openQrDialog() {
showQrDialog.value = true
qrLoading.value = true
qrCodeBase64.value = ''
try {
const res: any = await fetchMentorQrCode(mentorId)
const base64 = res?.data ?? res
if (typeof base64 === 'string' && base64.length > 0) {
qrCodeBase64.value = base64.startsWith('data:image')
? base64
: `data:image/png;base64,${base64}`
}
} catch (e) {
console.error('[二维码] 获取失败', e)
} finally {
qrLoading.value = false
}
}
/** 关闭弹窗 */
function closeQrDialog() {
showQrDialog.value = false
}
</script>