diff --git a/components.d.ts b/components.d.ts index 4ccc360..56f83b2 100644 --- a/components.d.ts +++ b/components.d.ts @@ -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'] diff --git a/src/api/mentor.ts b/src/api/mentor.ts index 8ede6af..3ae7fc8 100644 --- a/src/api/mentor.ts +++ b/src/api/mentor.ts @@ -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 { export function fetchMentorTalks(teacherId: string): Promise { 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 { + return request.get('/public/mentor/mockChat', { params: { mentorId } }) +} + +// ==================== 导师小程序二维码接口 ==================== + +/** + * 获取导师小程序二维码(base64 图片) + * POST /public/mentor/getQrCode + * @param teacherId 导师ID(字符串,避免精度丢失) + * @returns base64 格式的图片数据 + */ +export function fetchMentorQrCode(teacherId: string): Promise { + return request.post('/public/mentor/getQrCode', { + key: { + path: 'pages/teacher/teacher', + teacherId, + loginAppid: 'wx3adfa65f9d8db17d', + }, + }) +} diff --git a/src/assets/images/mentor/jsxqQrCode-mentors-page.png b/src/assets/images/mentor/jsxqQrCode-mentors-page.png new file mode 100644 index 0000000..0c986fb Binary files /dev/null and b/src/assets/images/mentor/jsxqQrCode-mentors-page.png differ diff --git a/src/assets/styles/components/mentor-aside-panel.scss b/src/assets/styles/components/mentor-aside-panel.scss new file mode 100644 index 0000000..efc915c --- /dev/null +++ b/src/assets/styles/components/mentor-aside-panel.scss @@ -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; + } +} diff --git a/src/assets/styles/components/profile-page-content.scss b/src/assets/styles/components/profile-page-content.scss index dc4ef42..cf3e3ec 100644 --- a/src/assets/styles/components/profile-page-content.scss +++ b/src/assets/styles/components/profile-page-content.scss @@ -16,7 +16,6 @@ &__tabs { display: flex; align-items: center; - gap: 0.24rem; padding-bottom: 0.14rem; flex-shrink: 0; margin-bottom:0.14rem diff --git a/src/assets/styles/index.scss b/src/assets/styles/index.scss index 9d2d3d9..1704c51 100644 --- a/src/assets/styles/index.scss +++ b/src/assets/styles/index.scss @@ -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'; diff --git a/src/assets/styles/pages/jobs.scss b/src/assets/styles/pages/jobs.scss index fec073e..70a3e49 100644 --- a/src/assets/styles/pages/jobs.scss +++ b/src/assets/styles/pages/jobs.scss @@ -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; + } + } + + // 选项卡容器 + &__apply-method-options { + position: relative; + display: flex; + gap: 0.16rem; + margin-bottom: 0.24rem; + } + + // 选项卡 + &__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; } - // 灰色按钮 — 直接投 - &--secondary { - background: $bg-main; - color: $text-middle; + // 选中态 + &--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; } - // 主题色按钮 — 去AI助手 - &--primary { - background: $btn-dark; + // 圆形上的两个白色小圆点(眼睛) + &::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; + } } } } diff --git a/src/assets/styles/pages/mentor-detail.scss b/src/assets/styles/pages/mentor-detail.scss index ad0e081..0b2a223 100644 --- a/src/assets/styles/pages/mentor-detail.scss +++ b/src/assets/styles/pages/mentor-detail.scss @@ -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; diff --git a/src/assets/styles/pages/mentor.scss b/src/assets/styles/pages/mentor.scss index aafc233..9b8f387 100644 --- a/src/assets/styles/pages/mentor.scss +++ b/src/assets/styles/pages/mentor.scss @@ -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; } } diff --git a/src/components/MentorAsidePanel.vue b/src/components/MentorAsidePanel.vue new file mode 100644 index 0000000..6bd2428 --- /dev/null +++ b/src/components/MentorAsidePanel.vue @@ -0,0 +1,320 @@ + + + diff --git a/src/views/Jobs.vue b/src/views/Jobs.vue index 54fac28..6774ab5 100644 --- a/src/views/Jobs.vue +++ b/src/views/Jobs.vue @@ -203,7 +203,7 @@
- +
不再显示 @@ -270,17 +270,55 @@ - -
-
-

去AI助手投递岗位效果更好哦!

-
- - + +
+
+ +
+ +

选择投递方式

+ + + + + +
+ +
+

直接前往官网

+

手动填写网申信息
自行记录投递进度

+
+ +
+ +
+
+ + 智能投递助手 +
+

{{ agentCardDesc }}

+
+ +
+ + +
@@ -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)) - showAgentRemindDialog.value = true - return - } - - 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 - } +/** 点击"去投递"按钮 — 每次都弹出选择投递方式弹窗 */ +function handleReport(job: JobListItem) { + // 暂存当前岗位信息 + pendingApplyUrl.value = job.sourceUrl + pendingApplyJobId.value = job.id + // 重置选项状态 + applyMethodChoice.value = null + // 每次触发都展示弹窗 + showAgentRemindDialog.value = true + // 异步加载 AI 助手配置状态 + loadAgentConfigStatus() } -// ==================== 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() { - showAgentRemindDialog.value = false - if (pendingApplyUrl.value) { - window.open(pendingApplyUrl.value, '_blank') +/** 底部操作按钮点击处理 */ +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 } - pendingApplyUrl.value = '' -} -/** 点击"去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 = '' - router.push('/agent') + pendingApplyJobId.value = '' } onMounted(async () => { diff --git a/src/views/Mentor.vue b/src/views/Mentor.vue index f75ad00..40b6c6e 100644 --- a/src/views/Mentor.vue +++ b/src/views/Mentor.vue @@ -69,19 +69,20 @@
-
{{ mentor.platformName }}
-
- {{ mentor.agencyName }} - - {{ mentor.position }} +
+
{{ mentor.platformName }}
+
+ {{ mentor.agencyName }} + + {{ mentor.position }} +
- -
- {{ label }} + + +
+ 咨询价 + ¥{{ mentor.price }} +
@@ -101,7 +102,7 @@ class="mentor-page__detail-btn" @click="goToDetail(mentor)" >查看详情 - +
@@ -125,9 +126,9 @@
- +
- +
@@ -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) } }) } // ==================== 生命周期 ==================== diff --git a/src/views/MentorDetail.vue b/src/views/MentorDetail.vue index 8126397..1432cef 100644 --- a/src/views/MentorDetail.vue +++ b/src/views/MentorDetail.vue @@ -1,90 +1,142 @@ @@ -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 = { 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 +}