新会员页面
This commit is contained in:
@@ -0,0 +1,177 @@
|
||||
<template>
|
||||
<!-- 申请进度消息组件 -->
|
||||
<div class="agent-apply-progress">
|
||||
<!-- 顶部标题栏 -->
|
||||
<div class="agent-apply-progress__header">
|
||||
<!-- 三重状态:投递完成 / 暂停投递 / 开始投递 -->
|
||||
<span
|
||||
class="agent-apply-progress__title"
|
||||
:class="{ 'agent-apply-progress__title--clickable': titleClickable }"
|
||||
@click="handleTitleClick"
|
||||
>{{ titleText }}</span>
|
||||
<span v-if="currentStep < 4" class="agent-apply-progress__cancel" @click="handleCancel">取消</span>
|
||||
</div>
|
||||
|
||||
<!-- 岗位信息卡片 -->
|
||||
<div class="agent-apply-progress__job-card">
|
||||
<div class="agent-apply-progress__job-icon">
|
||||
<svg viewBox="0 0 24 24" fill="none">
|
||||
<rect x="3" y="3" width="7" height="7" rx="1" stroke="currentColor" stroke-width="1.2"/>
|
||||
<rect x="14" y="3" width="7" height="7" rx="1" stroke="currentColor" stroke-width="1.2"/>
|
||||
<rect x="3" y="14" width="7" height="7" rx="1" stroke="currentColor" stroke-width="1.2"/>
|
||||
<rect x="14" y="14" width="7" height="7" rx="1" stroke="currentColor" stroke-width="1.2"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="agent-apply-progress__job-info">
|
||||
<div class="agent-apply-progress__job-company">{{ jobInfo?.companyShortName || jobInfo?.companyName || '' }}</div>
|
||||
<div class="agent-apply-progress__job-title">{{ jobInfo?.title || '' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 步骤列表 -->
|
||||
<div class="agent-apply-progress__steps">
|
||||
<!-- 第1步:优化简历 -->
|
||||
<div class="agent-apply-progress__step">
|
||||
<div class="agent-apply-progress__step-check" :class="{ 'agent-apply-progress__step-check--done': currentStep > 1 }">
|
||||
<svg v-if="currentStep > 1" viewBox="0 0 16 16" fill="none"><path d="M3 8l4 4 6-7" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
</div>
|
||||
<span class="agent-apply-progress__step-label">优化简历</span>
|
||||
</div>
|
||||
|
||||
<!-- 第2步:确认简历 -->
|
||||
<div class="agent-apply-progress__step">
|
||||
<div class="agent-apply-progress__step-check" :class="{ 'agent-apply-progress__step-check--done': currentStep > 2 }">
|
||||
<svg v-if="currentStep > 2" viewBox="0 0 16 16" fill="none"><path d="M3 8l4 4 6-7" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
</div>
|
||||
<span class="agent-apply-progress__step-label">确认简历</span>
|
||||
<!-- 第2步展开内容:简历确认区域 -->
|
||||
<div v-if="currentStep === 2" class="agent-apply-progress__step-content">
|
||||
<div class="agent-apply-progress__resume-confirm">
|
||||
<div class="agent-apply-progress__resume-file">
|
||||
<svg viewBox="0 0 24 24" fill="none" class="agent-apply-progress__file-icon"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8l-6-6z" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"/><path d="M14 2v6h6" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"/></svg>
|
||||
<span class="agent-apply-progress__resume-name">{{ resumeName }}</span>
|
||||
</div>
|
||||
<button class="agent-apply-progress__confirm-btn" :disabled="paused" @click="handleConfirmResume">确定</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 第3步:分析投递页面 -->
|
||||
<div class="agent-apply-progress__step">
|
||||
<div class="agent-apply-progress__step-check" :class="{ 'agent-apply-progress__step-check--done': currentStep > 3 }">
|
||||
<svg v-if="currentStep > 3" viewBox="0 0 16 16" fill="none"><path d="M3 8l4 4 6-7" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
</div>
|
||||
<span class="agent-apply-progress__step-label">分析投递页面</span>
|
||||
<!-- 第3步展开内容:投递操作区域 -->
|
||||
<div v-if="currentStep === 3" class="agent-apply-progress__step-content">
|
||||
<div class="agent-apply-progress__apply-area">
|
||||
<p class="agent-apply-progress__apply-desc">该职位仅支持在申请网站原地址使用自动填充功能。</p>
|
||||
<p class="agent-apply-progress__apply-desc">请点击"去投递",打开申请网站,我们的自动填写插件将自动弹出。</p>
|
||||
<p class="agent-apply-progress__apply-desc">点击"自动填写",核对信息并提交申请,然后返回本页点击"我已申请"继续。</p>
|
||||
<div class="agent-apply-progress__apply-actions">
|
||||
<span class="agent-apply-progress__skip-btn" :class="{ 'agent-apply-progress__skip-btn--disabled': paused }" @click="handleSkip">跳过</span>
|
||||
<button class="agent-apply-progress__applied-btn" :disabled="paused" @click="handleApplied">我已投递</button>
|
||||
<button class="agent-apply-progress__goto-btn" :disabled="paused" @click="handleGoApply">去投递</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 第4步:提交申请 -->
|
||||
<div class="agent-apply-progress__step">
|
||||
<div class="agent-apply-progress__step-check" :class="{ 'agent-apply-progress__step-check--done': currentStep > 3 }">
|
||||
<svg v-if="currentStep > 3" viewBox="0 0 16 16" fill="none"><path d="M3 8l4 4 6-7" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
</div>
|
||||
<span class="agent-apply-progress__step-label">提交申请</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import type { JobListItem } from '@/api/jobs'
|
||||
|
||||
// ==================== Props ====================
|
||||
|
||||
const props = defineProps<{
|
||||
/** 岗位信息 */
|
||||
jobInfo: JobListItem | null
|
||||
/** 当前步骤(1-4,大于该步骤表示已完成) */
|
||||
currentStep: number
|
||||
/** 简历名称(第2步确认时显示) */
|
||||
resumeName?: string
|
||||
/** 是否暂停状态 */
|
||||
paused?: boolean
|
||||
}>()
|
||||
|
||||
// ==================== 计算属性 ====================
|
||||
|
||||
/** 标题文字:投递完成 / 暂停投递 / 开始投递 */
|
||||
const titleText = computed(() => {
|
||||
if (props.currentStep >= 4) return '投递完成'
|
||||
if (props.paused && props.currentStep > 1) return '开始投递'
|
||||
if (!props.paused && props.currentStep > 1) return '暂停投递'
|
||||
return '开始投递'
|
||||
})
|
||||
|
||||
/** 标题是否可点击(暂停投递 或 暂停后的开始投递) */
|
||||
const titleClickable = computed(() => {
|
||||
if (props.currentStep >= 4) return false
|
||||
return props.currentStep > 1
|
||||
})
|
||||
|
||||
// ==================== 事件 ====================
|
||||
|
||||
const emit = defineEmits<{
|
||||
/** 取消投递流程 */
|
||||
(e: 'cancel'): void
|
||||
/** 确认简历(第2步) */
|
||||
(e: 'confirmResume'): void
|
||||
/** 跳过投递(第3步) */
|
||||
(e: 'skip'): void
|
||||
/** 我已投递(第3步) */
|
||||
(e: 'applied'): void
|
||||
/** 去投递(第3步,打开原链接) */
|
||||
(e: 'goApply'): void
|
||||
/** 暂停/恢复投递 */
|
||||
(e: 'togglePause'): void
|
||||
}>()
|
||||
|
||||
// ==================== 事件处理 ====================
|
||||
|
||||
/** 点击标题(暂停/恢复) */
|
||||
function handleTitleClick() {
|
||||
if (!titleClickable.value) return
|
||||
emit('togglePause')
|
||||
}
|
||||
|
||||
/** 取消 */
|
||||
function handleCancel() {
|
||||
emit('cancel')
|
||||
}
|
||||
|
||||
/** 确认简历 */
|
||||
function handleConfirmResume() {
|
||||
if (props.paused) return
|
||||
emit('confirmResume')
|
||||
}
|
||||
|
||||
/** 跳过 */
|
||||
function handleSkip() {
|
||||
if (props.paused) return
|
||||
emit('skip')
|
||||
}
|
||||
|
||||
/** 我已投递 */
|
||||
function handleApplied() {
|
||||
if (props.paused) return
|
||||
emit('applied')
|
||||
}
|
||||
|
||||
/** 去投递 */
|
||||
function handleGoApply() {
|
||||
if (props.paused) return
|
||||
emit('goApply')
|
||||
}
|
||||
</script>
|
||||
@@ -9,6 +9,7 @@
|
||||
v-for="job in displayJobs"
|
||||
:key="job.id"
|
||||
class="agent-chat-job-list__item"
|
||||
v-if="displayJobs.length>0"
|
||||
>
|
||||
<!-- 左侧:公司图标 + 岗位信息 -->
|
||||
<div class="agent-chat-job-list__info">
|
||||
@@ -51,9 +52,12 @@
|
||||
<span class="agent-chat-job-list__score-text">{{ job.matchScore || 0 }}%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="color-9" v-if="displayJobs.length==0">
|
||||
暂无相关职位推荐
|
||||
</div>
|
||||
|
||||
<!-- 查看全部岗位按钮 -->
|
||||
<div class="agent-chat-job-list__footer">
|
||||
<div v-if="displayJobs.length>0" class="agent-chat-job-list__footer">
|
||||
<button class="agent-chat-job-list__view-all-btn" @click="handleViewAll">查看全部岗位</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
<template>
|
||||
<!-- 待投递列表下拉弹窗 -->
|
||||
<div class="agent-main__task-dropdown" @click.stop>
|
||||
<!-- Tab 切换 -->
|
||||
<div class="agent-main__task-tabs">
|
||||
<span
|
||||
class="agent-main__task-tab"
|
||||
:class="{ 'agent-main__task-tab--active': activeTab === 1 }"
|
||||
@click="switchTab(1)"
|
||||
>进行中({{ pendingList.length }})</span>
|
||||
<span
|
||||
class="agent-main__task-tab"
|
||||
:class="{ 'agent-main__task-tab--active': activeTab === 2 }"
|
||||
@click="switchTab(2)"
|
||||
>已完成({{ completedList.length }})</span>
|
||||
</div>
|
||||
<!-- 列表内容 -->
|
||||
<div class="agent-main__task-list">
|
||||
<template v-if="activeTab === 1">
|
||||
<div v-if="pendingList.length === 0" class="agent-main__task-empty">暂无进行中的岗位</div>
|
||||
<div v-for="job in pendingList" :key="job.id" class="agent-main__task-item">
|
||||
<div class="agent-main__task-item-left">
|
||||
<span class="agent-main__task-item-name">{{ (job.companyShortName || job.companyName) + '—' + job.title }}</span>
|
||||
<span class="agent-main__task-item-tag">自动投递</span>
|
||||
</div>
|
||||
<div class="agent-main__task-item-right">
|
||||
<span class="agent-main__task-item-status">未开始</span>
|
||||
<span class="agent-main__task-item-remove" @click="removeJob(job)">×</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div v-if="loading" class="agent-main__task-empty">加载中...</div>
|
||||
<div v-else-if="completedList.length === 0" class="agent-main__task-empty">暂无已完成的岗位</div>
|
||||
<div v-for="job in completedList" :key="job.id" class="agent-main__task-item">
|
||||
<div class="agent-main__task-item-left">
|
||||
<span class="agent-main__task-item-name">{{ (job.companyShortName || job.companyName) + '—' + job.title }}</span>
|
||||
<span class="agent-main__task-item-tag">自动投递</span>
|
||||
</div>
|
||||
<div class="agent-main__task-item-right">
|
||||
<span class="agent-main__task-item-status agent-main__task-item-status--done">已完成</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<!-- 查看全部 -->
|
||||
<div class="agent-main__task-view-all" @click="emit('viewAll')">查看全部</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { fetchAgentTaskList } from '@/api/jobs'
|
||||
import { cancelApplyJob } from '@/api/agent'
|
||||
import type { JobListItem } from '@/api/jobs'
|
||||
|
||||
// ==================== Props ====================
|
||||
|
||||
const props = defineProps<{
|
||||
/** 进行中列表(由父组件传入,保持同步) */
|
||||
pendingList: JobListItem[]
|
||||
}>()
|
||||
|
||||
// ==================== 事件 ====================
|
||||
|
||||
const emit = defineEmits<{
|
||||
/** 移除岗位后通知父组件刷新列表 */
|
||||
(e: 'removed', jobId: string): void
|
||||
/** 查看全部 */
|
||||
(e: 'viewAll'): void
|
||||
}>()
|
||||
|
||||
// ==================== 状态 ====================
|
||||
|
||||
/** 当前激活的 Tab(1=进行中 2=已完成) */
|
||||
const activeTab = ref(1)
|
||||
|
||||
/** 已完成列表数据 */
|
||||
const completedList = ref<JobListItem[]>([])
|
||||
|
||||
/** 加载状态 */
|
||||
const loading = ref(false)
|
||||
|
||||
// ==================== 生命周期 ====================
|
||||
|
||||
onMounted(() => {
|
||||
// 打开时同时加载已完成列表
|
||||
loadCompletedList()
|
||||
})
|
||||
|
||||
// ==================== 方法 ====================
|
||||
|
||||
/** 切换 Tab */
|
||||
function switchTab(tab: number) {
|
||||
activeTab.value = tab
|
||||
if (tab === 2) {
|
||||
loadCompletedList()
|
||||
}
|
||||
}
|
||||
|
||||
/** 加载已完成任务列表(tab=2) */
|
||||
async function loadCompletedList() {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await fetchAgentTaskList({ pageNum: 1, pageSize: 100, tab: 2 })
|
||||
if (res.code === '0' && res.data) {
|
||||
completedList.value = res.data.list || []
|
||||
}
|
||||
} catch {
|
||||
console.error('[AgentTaskListDropdown] 加载已完成列表失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 移除岗位 — 调用 cancelApplyJob 接口并通知父组件 */
|
||||
async function removeJob(job: JobListItem) {
|
||||
try {
|
||||
await cancelApplyJob(Number(job.id))
|
||||
emit('removed', job.id)
|
||||
ElMessage.success('已移除')
|
||||
} catch {
|
||||
ElMessage.error('移除失败')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="ai-chat">
|
||||
<!-- 顶部会员横幅 — 点击打开会员购买弹窗 -->
|
||||
<div class="ai-chat__banner" @click="showMemberDialog = true">
|
||||
<span>解锁会员,送快上岸!</span>
|
||||
<span class="dflex-start aliite-c"> <img src="@/assets/images/bule-flash.png" alt="Offer派" class="w16 h19 mr5" /> <span>升级 Pro 开启求职加速</span></span>
|
||||
<span class="ai-chat__banner-arrow">›</span>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -221,39 +221,42 @@
|
||||
</div>
|
||||
<!-- AI帮写内容 -->
|
||||
<div v-if="previewTab === 'ai'" class="job-resume-custom-dialog__preview-ai">
|
||||
<!-- 匹配度提升提示 -->
|
||||
<div class="job-resume-custom-dialog__ai-result">
|
||||
<div class="job-resume-custom-dialog__ai-result-text">
|
||||
<p class="job-resume-custom-dialog__ai-result-title">恭喜!你的简历匹配值从<br/>{{ jobInfo.matchScore }}分提升到了10分!</p>
|
||||
<div class="job-resume-custom-dialog__ai-result-detail">
|
||||
<p class="job-resume-custom-dialog__ai-result-subtitle">做了哪些优化?</p>
|
||||
<ul class="job-resume-custom-dialog__ai-result-list">
|
||||
<li v-for="(item, i) in aiOptimizeResults" :key="i">·{{ item }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="job-resume-custom-dialog__ai-result-score">
|
||||
<div class="job-resume-custom-dialog__match-ring job-resume-custom-dialog__match-ring--large">
|
||||
<svg viewBox="0 0 60 60" class="job-resume-custom-dialog__ring-svg">
|
||||
<circle cx="30" cy="30" r="24" stroke-width="4" stroke="#E8E8E8" fill="none" opacity="0.3"/>
|
||||
<circle cx="30" cy="30" r="24" stroke-width="4" fill="none" stroke="#4FC2C9" stroke-linecap="round" :stroke-dasharray="2*Math.PI*24" :stroke-dashoffset="0" transform="rotate(-90 30 30)"/>
|
||||
</svg>
|
||||
<span class="job-resume-custom-dialog__match-score">10.0</span>
|
||||
</div>
|
||||
<span class="job-resume-custom-dialog__match-label job-resume-custom-dialog__match-label--high">非常匹配</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 快捷操作按钮 -->
|
||||
<div class="job-resume-custom-dialog__ai-quick-actions">
|
||||
<button
|
||||
v-for="(action, i) in aiQuickActions"
|
||||
:key="i"
|
||||
class="job-resume-custom-dialog__ai-quick-btn"
|
||||
@click="sendAiMessage(action)"
|
||||
>{{ action }}</button>
|
||||
</div>
|
||||
|
||||
<!-- AI聊天消息区域 -->
|
||||
<div class="job-resume-custom-dialog__ai-messages" ref="aiMessagesRef">
|
||||
|
||||
<!-- 匹配度提升提示 -->
|
||||
<div class="job-resume-custom-dialog__ai-result prl0">
|
||||
<div class="job-resume-custom-dialog__ai-result-text">
|
||||
<p class="job-resume-custom-dialog__ai-result-title">恭喜!你的简历匹配值从<br/>{{ jobInfo.matchScore }}分提升到了{{ cachedOptimizedScore }}分!</p>
|
||||
<div class="job-resume-custom-dialog__ai-result-detail">
|
||||
<p class="job-resume-custom-dialog__ai-result-subtitle">做了哪些优化?</p>
|
||||
<ul class="job-resume-custom-dialog__ai-result-list">
|
||||
<li v-for="(item, i) in aiOptimizeResults" :key="i">·{{ item }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="job-resume-custom-dialog__ai-result-score">
|
||||
<div class="job-resume-custom-dialog__match-ring job-resume-custom-dialog__match-ring--large">
|
||||
<svg viewBox="0 0 60 60" class="job-resume-custom-dialog__ring-svg">
|
||||
<circle cx="30" cy="30" r="24" stroke-width="4" stroke="#E8E8E8" fill="none" opacity="0.3"/>
|
||||
<circle cx="30" cy="30" r="24" stroke-width="4" fill="none" stroke="#4FC2C9" stroke-linecap="round" :stroke-dasharray="2*Math.PI*24" :stroke-dashoffset="2*Math.PI*24*(1-cachedOptimizedScore/10)" transform="rotate(-90 30 30)"/>
|
||||
</svg>
|
||||
<span class="job-resume-custom-dialog__match-score">{{ cachedOptimizedScore.toFixed(1) }}</span>
|
||||
</div>
|
||||
<span class="job-resume-custom-dialog__match-label job-resume-custom-dialog__match-label--high">{{ cachedOptimizedScore >= 9 ? '非常匹配' : cachedOptimizedScore >= 6 ? '高匹配度' : '低匹配度' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 快捷操作按钮 -->
|
||||
<div class="job-resume-custom-dialog__ai-quick-actions prl0">
|
||||
<button
|
||||
v-for="(action, i) in aiQuickActions"
|
||||
:key="i"
|
||||
class="job-resume-custom-dialog__ai-quick-btn"
|
||||
@click="sendAiMessage(action)"
|
||||
>{{ action }}</button>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-for="(msg, i) in aiMessages"
|
||||
:key="i"
|
||||
@@ -748,6 +751,42 @@ const aiOptimizeResults = computed<string[]>(() => {
|
||||
return results
|
||||
})
|
||||
|
||||
/**
|
||||
* 根据勾选的优化部分数量计算优化后的匹配评分
|
||||
* 0个勾选:3~5之间随机(一位小数)
|
||||
* 1个勾选:6~7之间随机
|
||||
* 2个勾选:8~9之间随机
|
||||
* 3个勾选:9~10之间随机,50%概率直接为10.0
|
||||
*/
|
||||
const optimizedMatchScore = computed(() => {
|
||||
const checkedCount = optimizeSections.value.filter(s => s.checked).length
|
||||
let min: number, max: number
|
||||
if (checkedCount === 0) {
|
||||
min = 3; max = 5
|
||||
} else if (checkedCount === 1) {
|
||||
min = 6; max = 7
|
||||
} else if (checkedCount === 2) {
|
||||
min = 8; max = 9
|
||||
} else {
|
||||
// 3个勾选:50%概率直接10.0
|
||||
if (Math.random() < 0.2) return 10.0
|
||||
min = 9; max = 10
|
||||
}
|
||||
// 生成 [min, max] 之间的一位小数随机值
|
||||
const raw = min + Math.random() * (max - min)
|
||||
return Math.round(raw * 10) / 10
|
||||
})
|
||||
|
||||
/** 缓存优化后评分(避免computed每次重新随机) */
|
||||
const cachedOptimizedScore = ref<number>(0)
|
||||
|
||||
/** 进入步骤4时缓存一次评分 */
|
||||
watch(currentStep, (val) => {
|
||||
if (val === 4) {
|
||||
cachedOptimizedScore.value = optimizedMatchScore.value
|
||||
}
|
||||
})
|
||||
|
||||
/** AI快捷操作按钮 */
|
||||
const aiQuickActions = ref<string[]>([
|
||||
'精简一下第一段工作经历',
|
||||
|
||||
@@ -44,34 +44,32 @@
|
||||
<div class="resume-html__divider"></div>
|
||||
<div v-for="(edu, idx) in resumeData.educations" :key="'edu-' + idx" class="resume-html__item">
|
||||
<div class="resume-html__item-header">
|
||||
<div class="resume-html__item-left">
|
||||
<span class="resume-html__item-main">
|
||||
<!-- 教育经历标题差异对比 -->
|
||||
<template v-if="showDiff">
|
||||
<template v-for="(seg, si) in diffText(
|
||||
oldResumeData?.educations?.[idx] ? (oldResumeData.educations[idx].school + ',' + oldResumeData.educations[idx].major + ',' + degreeText(oldResumeData.educations[idx].degree)) : '',
|
||||
edu.school + ',' + edu.major + ',' + degreeText(edu.degree)
|
||||
)" :key="'eduh-' + si">
|
||||
<span v-if="seg.highlight" class="resume-html__diff-highlight">{{ seg.text }}</span><template v-else>{{ seg.text }}</template>
|
||||
</template>
|
||||
<span class="resume-html__item-main">
|
||||
<!-- 教育经历标题差异对比 -->
|
||||
<template v-if="showDiff">
|
||||
<template v-for="(seg, si) in diffText(
|
||||
oldResumeData?.educations?.[idx] ? (oldResumeData.educations[idx].school + ',' + oldResumeData.educations[idx].major + ',' + degreeText(oldResumeData.educations[idx].degree)) : '',
|
||||
edu.school + ',' + edu.major + ',' + degreeText(edu.degree)
|
||||
)" :key="'eduh-' + si">
|
||||
<span v-if="seg.highlight" class="resume-html__diff-highlight">{{ seg.text }}</span><template v-else>{{ seg.text }}</template>
|
||||
</template>
|
||||
<template v-else>{{ edu.school }},{{ edu.major }},{{ degreeText(edu.degree) }}</template>
|
||||
</span>
|
||||
<span v-if="edu.description && edu.description.length" class="resume-html__item-desc">
|
||||
<!-- 教育经历描述差异对比 -->
|
||||
<template v-if="showDiff">
|
||||
主修课程:<template v-for="(desc, di) in edu.description" :key="'ed-' + di"><template v-if="di > 0">、</template><template v-for="(seg, si) in diffDescText(oldResumeData?.educations, idx, di, desc.text)" :key="'eds-' + si"><span v-if="seg.highlight" class="resume-html__diff-highlight">{{ seg.text }}</span><template v-else>{{ seg.text }}</template></template></template>等;
|
||||
</template>
|
||||
<template v-else>
|
||||
主修课程:{{ edu.description.map(d => d.text).join('、') }}等;
|
||||
</template>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>{{ edu.school }},{{ edu.major }},{{ degreeText(edu.degree) }}</template>
|
||||
</span>
|
||||
<div class="resume-html__item-right">
|
||||
<span class="resume-html__item-location" v-if="edu.location">{{ edu.location }}</span>
|
||||
<span class="resume-html__item-date">{{ edu.startDate }} — {{ edu.endDate || '至今' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 教育经历描述独立一行,避免被header的flex布局挤压 -->
|
||||
<div v-if="edu.description && edu.description.length" class="resume-html__item-desc">
|
||||
<template v-if="showDiff">
|
||||
主修课程:<template v-for="(desc, di) in edu.description" :key="'ed-' + di"><template v-if="di > 0">、</template><template v-for="(seg, si) in diffDescText(oldResumeData?.educations, idx, di, desc.text)" :key="'eds-' + si"><span v-if="seg.highlight" class="resume-html__diff-highlight">{{ seg.text }}</span><template v-else>{{ seg.text }}</template></template></template>等;
|
||||
</template>
|
||||
<template v-else>
|
||||
主修课程:{{ edu.description.map(d => d.text).join('、') }}等;
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
+443
-92
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<!-- 会员购买弹窗 — 通过 Teleport 挂载到 body -->
|
||||
<!-- 会员弹窗 — 通过 Teleport 挂载到 body -->
|
||||
<Teleport to="body">
|
||||
<!-- 遮罩层 — 点击关闭弹窗 -->
|
||||
<div v-if="modelValue" class="member-dialog-overlay" @click="$emit('update:modelValue', false)">
|
||||
@@ -8,73 +8,342 @@
|
||||
<!-- 右上角关闭按钮 -->
|
||||
<span class="member-dialog__close" @click="$emit('update:modelValue', false)">✕</span>
|
||||
|
||||
<!-- 顶部标语 -->
|
||||
<div class="member-dialog__slogan">每天不到一元,获得三倍面试机会!</div>
|
||||
<!-- ==================== 会员介绍步骤一:简介页 ==================== -->
|
||||
<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>
|
||||
|
||||
<!-- 套餐卡片区域 -->
|
||||
<div class="member-dialog__plans">
|
||||
<div
|
||||
v-for="plan in plans"
|
||||
:key="plan.key"
|
||||
class="member-dialog__plan-card"
|
||||
:class="{ 'member-dialog__plan-card--active': selectedPlan === plan.key }"
|
||||
@click="selectedPlan = plan.key"
|
||||
>
|
||||
<!-- 套餐名称 -->
|
||||
<div class="member-dialog__plan-name">{{ plan.name }}</div>
|
||||
<!-- 原价(划线) -->
|
||||
<div class="member-dialog__plan-original">{{ plan.originalPrice }}</div>
|
||||
<!-- 现价 -->
|
||||
<div class="member-dialog__plan-price">
|
||||
<span class="member-dialog__plan-price-num">¥{{ plan.price }}</span>
|
||||
<span class="member-dialog__plan-price-unit">/{{ plan.unit }}</span>
|
||||
<!-- 省钱标签 -->
|
||||
<span v-if="plan.discount" class="member-dialog__plan-discount">省{{ plan.discount }}</span>
|
||||
<!-- ==================== 会员介绍步骤二:详细介绍页(可滚动) ==================== -->
|
||||
<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"
|
||||
:key="plan.key"
|
||||
class="member-dialog__plan-card"
|
||||
: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 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>
|
||||
</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>
|
||||
</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>
|
||||
|
||||
<!-- 底部内容:左侧能力列表 + 右侧对比表格 -->
|
||||
<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>
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 用户评价 -->
|
||||
<div class="member-dialog__testimonial">
|
||||
<div class="member-dialog__testimonial-avatar">
|
||||
<img src="@/assets/images/home/avatar-temporary.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>
|
||||
</div>
|
||||
<!-- 立刻升级按钮 -->
|
||||
<button class="member-dialog__plan-btn" @click.stop="handleUpgrade(plan)">立刻升级</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 权益对比区域 -->
|
||||
<div class="member-dialog__benefits">
|
||||
<!-- 左列:面试率提升工具 -->
|
||||
<div class="member-dialog__benefit-col">
|
||||
<div class="member-dialog__benefit-title">面试率提升工具</div>
|
||||
<div
|
||||
v-for="item in toolList"
|
||||
:key="item"
|
||||
class="member-dialog__benefit-item"
|
||||
>{{ item }}</div>
|
||||
<div class="member-dialog__benefit-other">
|
||||
其他渠道购买 <span class="member-dialog__benefit-highlight">¥210/月</span>
|
||||
<!-- ==================== 下单步骤一:选择套餐 ==================== -->
|
||||
<div v-else-if="currentView === 'order1'" class="member-dialog__order">
|
||||
<!-- 顶部导航 -->
|
||||
<div class="member-dialog__order-header">
|
||||
<span class="member-dialog__order-back" @click="currentView = 'intro2'">‹ 返回会员介绍</span>
|
||||
<!-- 步骤条 -->
|
||||
<div class="member-dialog__steps">
|
||||
<div class="member-dialog__step member-dialog__step--active">
|
||||
<span class="member-dialog__step-num">1</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">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__benefit-col member-dialog__benefit-col--center">
|
||||
<div class="member-dialog__benefit-title member-dialog__benefit-title--accent">会员权益</div>
|
||||
<div
|
||||
v-for="item in memberBenefits"
|
||||
:key="item.label"
|
||||
class="member-dialog__benefit-item"
|
||||
>
|
||||
<span class="member-dialog__benefit-badge">{{ item.badge }}</span>
|
||||
<span>{{ item.label }}</span>
|
||||
<!-- 主内容区域(可滚动) -->
|
||||
<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
|
||||
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"
|
||||
>
|
||||
<div v-if="plan.recommend" class="member-dialog__order-plan-badge">★ 推荐</div>
|
||||
<!-- 选中圆圈 -->
|
||||
<div class="member-dialog__order-plan-radio">
|
||||
<div v-if="selectedPlan === plan.key" class="member-dialog__order-plan-radio-inner"></div>
|
||||
</div>
|
||||
<div class="member-dialog__order-plan-info">
|
||||
<div class="member-dialog__order-plan-name">{{ plan.name }}</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"
|
||||
>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="member-dialog__benefit-other">
|
||||
打包价 <span class="member-dialog__benefit-highlight">¥19.99/月</span>
|
||||
|
||||
<!-- 右侧订单摘要 -->
|
||||
<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:;">《会员服务协议》</a> <a href="javascript:;">《自动续费协议》</a></span>
|
||||
</div>
|
||||
<!-- 立即开启按钮 -->
|
||||
<button
|
||||
class="member-dialog__order-submit-btn"
|
||||
:disabled="!agreeProtocol"
|
||||
@click="handleShowQrCode"
|
||||
>立即开启求职加速</button>
|
||||
<div class="member-dialog__order-submit-tip">支付成功后立即解锁全部权益,可随时取消续费。</div>
|
||||
|
||||
<!-- 你将获得 -->
|
||||
<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 class="member-dialog__benefit-col">
|
||||
<div class="member-dialog__benefit-title">免费权益(当前)</div>
|
||||
<div
|
||||
v-for="item in freeBenefits"
|
||||
:key="item"
|
||||
class="member-dialog__benefit-item"
|
||||
>{{ item }}</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>
|
||||
</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="$emit('update:modelValue', false)">查看会员权益</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ==================== 付款二维码遮罩层 ==================== -->
|
||||
<div v-if="showQrCode" class="member-dialog__qrcode-overlay" @click="showQrCode = false">
|
||||
<div class="member-dialog__qrcode-modal" @click.stop>
|
||||
<!-- 关闭按钮 -->
|
||||
<span class="member-dialog__qrcode-close" @click="showQrCode = false">✕</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">扫码完成支付</h3>
|
||||
<p class="member-dialog__qrcode-desc">请使用{{ selectedPayment === 'wechat' ? '微信' : '支付宝' }} App 扫描二维码完成支付,完成后此窗口会自动关闭。</p>
|
||||
<!-- 二维码占位 -->
|
||||
<div class="member-dialog__qrcode-image">
|
||||
<!-- TODO: 替换为真实二维码图片 -->
|
||||
<div class="member-dialog__qrcode-placeholder"></div>
|
||||
</div>
|
||||
<!-- 金额 -->
|
||||
<div class="member-dialog__qrcode-amount">¥{{ currentPlan.price }}</div>
|
||||
<!-- 我已完成支付按钮 -->
|
||||
<button class="member-dialog__qrcode-confirm-btn" @click="handlePaymentDone">我已完成支付</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -83,7 +352,8 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue'
|
||||
import { ref, computed, watch, reactive } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
/** 组件 Props — 控制弹窗显示/隐藏 */
|
||||
const props = defineProps<{ modelValue: boolean }>()
|
||||
@@ -91,72 +361,153 @@ const props = defineProps<{ modelValue: boolean }>()
|
||||
/** 组件 Emits — 通知父组件更新 modelValue */
|
||||
defineEmits<{ (e: 'update:modelValue', value: boolean): void }>()
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
// ==================== 类型定义 ====================
|
||||
|
||||
/** 套餐项类型 */
|
||||
interface PlanItem {
|
||||
key: string
|
||||
name: string
|
||||
originalPrice: string
|
||||
price: string
|
||||
unit: string
|
||||
discount: string
|
||||
tag?: string
|
||||
recommend?: boolean
|
||||
perMonth?: string
|
||||
btnText: string
|
||||
orderDesc: string
|
||||
}
|
||||
|
||||
/** 支付方式类型 */
|
||||
interface PaymentMethod {
|
||||
key: string
|
||||
name: string
|
||||
icon: string
|
||||
}
|
||||
|
||||
/** FAQ 项类型 */
|
||||
interface FaqItem {
|
||||
question: string
|
||||
answer: string
|
||||
open: boolean
|
||||
}
|
||||
|
||||
// ==================== 状态 ====================
|
||||
|
||||
/** 当前选中的套餐 */
|
||||
const selectedPlan = ref('monthly')
|
||||
/** 当前视图(intro1/intro2/order1/order2/order3) */
|
||||
const currentView = ref<'intro1' | 'intro2' | 'order1' | 'order2' | 'order3'>('intro2')
|
||||
|
||||
/** 监听弹窗开关 — 打开时锁定背景滚动,关闭时恢复 */
|
||||
/** 当前选中的套餐 */
|
||||
const selectedPlan = ref('quarterly')
|
||||
|
||||
/** 当前选中的支付方式 */
|
||||
const selectedPayment = ref('wechat')
|
||||
|
||||
/** 是否同意协议 */
|
||||
const agreeProtocol = ref(false)
|
||||
|
||||
/** 是否显示二维码弹窗 */
|
||||
const showQrCode = ref(false)
|
||||
|
||||
/** 监听弹窗开关 — 打开时锁定背景滚动,关闭时恢复并重置状态 */
|
||||
watch(() => props.modelValue, (val) => {
|
||||
document.body.style.overflow = val ? 'hidden' : ''
|
||||
if (!val) {
|
||||
currentView.value = 'intro2'
|
||||
showQrCode.value = false
|
||||
agreeProtocol.value = false
|
||||
}
|
||||
})
|
||||
|
||||
// ==================== 计算属性 ====================
|
||||
|
||||
/** 当前选中的套餐对象 */
|
||||
const currentPlan = computed(() => {
|
||||
return plans.find(p => p.key === selectedPlan.value) || plans[1]
|
||||
})
|
||||
|
||||
// ==================== 常量数据 ====================
|
||||
|
||||
/** 套餐列表 */
|
||||
const plans: PlanItem[] = [
|
||||
{ key: 'quarterly', name: '季度会员', originalPrice: '¥149.97 / 3个月', price: '49.99', unit: '3个月', discount: '67%' },
|
||||
{ key: 'monthly', name: '月度会员', originalPrice: '¥49.99 / 1个月', price: '19.99', unit: '月', discount: '60%' },
|
||||
{ key: 'weekly', name: '周会员', originalPrice: '¥71.96 / 1个月', price: '17.99', unit: '1周', discount: '' },
|
||||
{ key: 'weekly', name: '周会员', price: '17.99', unit: '周', tag: '临时体验 / 急用一次', btnText: '立即体验', orderDesc: '适合临时体验,急用一次的求职者' },
|
||||
{ key: 'quarterly', name: '季度会员', price: '49.99', unit: '3个月', recommend: true, perMonth: '16.66', btnText: '开启完整求职冲刺', orderDesc: '推荐正在集中投递、准备面试的求职者,性价比最高' },
|
||||
{ key: 'monthly', name: '月度会员', price: '19.99', unit: '月', tag: '标准求职', btnText: '开始求职加速', orderDesc: '标准求职周期,持续使用 AI 求职加速能力' },
|
||||
]
|
||||
|
||||
/** 面试率提升工具列表 */
|
||||
const toolList = [
|
||||
'AI 求职助手',
|
||||
'AI 针对性简历优化',
|
||||
'1V1真人导师',
|
||||
'一键自动填写网申信息',
|
||||
'内推码',
|
||||
'实时岗位更新提醒',
|
||||
/** 求职加速能力列表 */
|
||||
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 memberBenefits = [
|
||||
{ badge: '无限', label: '自动化网申流程' },
|
||||
{ badge: '无限', label: '简历过筛率提升' },
|
||||
{ badge: '✓', label: '找工作不孤单' },
|
||||
{ badge: '无限', label: '每一次网申节约15Min' },
|
||||
{ badge: '无限', label: '自动填写最新内推码' },
|
||||
{ badge: '无限', label: '第一时间投递高质量岗位' },
|
||||
/** 会员能力对比数据 */
|
||||
const compareData = [
|
||||
{ name: 'AI 岗位匹配', free: '免费 3 天', pro: '不限次数' },
|
||||
{ name: '简历针对性优化', free: '免费 3 天', pro: '不限次数' },
|
||||
{ name: '自动填写网申', free: '免费 3 天', pro: '不限次数' },
|
||||
{ name: '内推码获取', free: '免费 3 天', pro: '不限次数' },
|
||||
{ name: '高匹配岗位推送', free: '免费 3 天', pro: '不限次数' },
|
||||
]
|
||||
|
||||
/** 免费权益列表 */
|
||||
const freeBenefits = [
|
||||
'受限',
|
||||
'2次/天',
|
||||
'不支持',
|
||||
'4次/天',
|
||||
'4次/天',
|
||||
'1次/天',
|
||||
/** 支付方式列表 */
|
||||
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) {
|
||||
// TODO: 接入支付接口
|
||||
ElMessage.success(`正在跳转 ${plan.name} 支付页面...`)
|
||||
selectedPlan.value = plan.key
|
||||
currentView.value = 'order1'
|
||||
}
|
||||
|
||||
/** 点击立即开启求职加速 — 显示二维码弹窗 */
|
||||
function handleShowQrCode() {
|
||||
if (!agreeProtocol.value) return
|
||||
showQrCode.value = true
|
||||
}
|
||||
|
||||
/** 点击我已完成支付 — 进入确认支付结果步骤 */
|
||||
function handlePaymentDone() {
|
||||
showQrCode.value = false
|
||||
currentView.value = 'order2'
|
||||
// 模拟接口确认支付结果,成功后进入步骤三
|
||||
confirmPayment()
|
||||
}
|
||||
|
||||
/** 模拟确认支付结果接口 */
|
||||
async function confirmPayment() {
|
||||
// TODO: 替换为真实支付确认接口
|
||||
await new Promise(resolve => setTimeout(resolve, 2000))
|
||||
currentView.value = 'order3'
|
||||
}
|
||||
|
||||
/** 支付成功后跳转简历页 */
|
||||
function handleGoResume() {
|
||||
router.push('/resume')
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -66,16 +66,17 @@
|
||||
<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-name">会员</span>
|
||||
<span class="settings-dialog__member-badge">查看详情</span>
|
||||
</div>
|
||||
<span class="settings-dialog__member-terms" @click="handleMemberTerms">会员条款</span>
|
||||
</div>
|
||||
<div class="settings-dialog__member-info-row">
|
||||
<span class="settings-dialog__member-price">
|
||||
¥19.99/月<span>将于2026年3月27日续费</span>
|
||||
¥19.99/月
|
||||
<!-- <span>将于2026年3月27日续费</span>-->
|
||||
</span>
|
||||
<button class="settings-dialog__member-manage-btn" @click="handleManageSubscription">管理我的订阅</button>
|
||||
<!-- <button class="settings-dialog__member-manage-btn" @click="handleManageSubscription">管理我的订阅</button>-->
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-dialog__member-issue">
|
||||
@@ -92,7 +93,8 @@
|
||||
|
||||
<!-- Tab: 岗位更新提醒 — 目标岗位、即时提醒开关、提醒频率 -->
|
||||
<template v-if="activeTab === 'reminder'">
|
||||
<h2 class="settings-dialog__content-title">岗位更新提醒</h2>
|
||||
<!-- <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">
|
||||
<span class="settings-dialog__reminder-block-title">目标岗位</span>
|
||||
|
||||
+165
-6
@@ -35,13 +35,39 @@
|
||||
<!-- 消息通知弹窗 -->
|
||||
<Teleport to="body">
|
||||
<div v-if="showMessageDialog" class="side-nav__dialog-overlay" @click="showMessageDialog = false">
|
||||
<div class="side-nav__dialog" @click.stop>
|
||||
<div class="side-nav__dialog-header">
|
||||
<div class="side-nav__message-dialog" @click.stop>
|
||||
<!-- 弹窗头部 -->
|
||||
<div class="side-nav__message-dialog-header">
|
||||
<span>消息通知</span>
|
||||
<span class="side-nav__dialog-close" @click="showMessageDialog = false">✕</span>
|
||||
</div>
|
||||
<div class="side-nav__dialog-body">
|
||||
<p>暂无新消息</p>
|
||||
<!-- 弹窗内容区:左列表 + 右详情 -->
|
||||
<div class="side-nav__message-dialog-content">
|
||||
<!-- 左侧消息列表 -->
|
||||
<div class="side-nav__message-list" @scroll="handleMessageListScroll">
|
||||
<div
|
||||
v-for="(msg, idx) in messageList"
|
||||
:key="msg.id"
|
||||
class="side-nav__message-list-item"
|
||||
:class="{ 'side-nav__message-list-item--active': selectedMessageIdx === idx }"
|
||||
@click="selectedMessageIdx = idx"
|
||||
>
|
||||
<span class="side-nav__message-list-title">{{ msg.title }}</span>
|
||||
<span v-if="!msg.read" class="side-nav__message-unread-dot"></span>
|
||||
</div>
|
||||
<!-- 加载中提示 -->
|
||||
<div v-if="messageLoading" class="side-nav__message-list-loading">加载中...</div>
|
||||
<!-- 无消息提示 -->
|
||||
<div v-if="!messageLoading && messageList.length === 0" class="side-nav__message-list-empty">暂无消息</div>
|
||||
</div>
|
||||
<!-- 右侧消息详情 -->
|
||||
<div class="side-nav__message-detail">
|
||||
<template v-if="currentMessage">
|
||||
<div class="side-nav__message-detail-title">{{ currentMessage.title }}</div>
|
||||
<div class="side-nav__message-detail-content">{{ currentMessage.content }}</div>
|
||||
</template>
|
||||
<div v-else class="side-nav__message-detail-empty">请选择一条消息查看</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -91,11 +117,13 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import { computed, ref, watch, onMounted } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useStore } from 'vuex'
|
||||
import SettingsDialog from '@/components/SettingsDialog.vue'
|
||||
import { checkLogin } from '@/api/auth'
|
||||
import { fetchMessageList, fetchUnreadCount, markMessageRead } from '@/api/message'
|
||||
import type { MessageDto } from '@/api/message'
|
||||
import navJobsIcon from '@/assets/images/nav/nav-jobs-icon.png'
|
||||
import navResumeIcon from '@/assets/images/nav/nav-resume-icon.png'
|
||||
import navProfileIcon from '@/assets/images/nav/nav-profile-icon.png'
|
||||
@@ -167,6 +195,137 @@ const showSettingsDialog = ref(false)
|
||||
const feedbackType = ref('')
|
||||
const feedbackDetail = ref('')
|
||||
|
||||
// ==================== 站内信相关 ====================
|
||||
/** 未读消息数量 */
|
||||
const unreadCount = ref(0)
|
||||
/** 消息列表数据 */
|
||||
const messageList = ref<MessageDto[]>([])
|
||||
/** 当前选中的消息索引 */
|
||||
const selectedMessageIdx = ref(0)
|
||||
/** 当前页码 */
|
||||
const messagePageNum = ref(1)
|
||||
/** 是否还有更多数据 */
|
||||
const messageHasMore = ref(true)
|
||||
/** 加载状态 */
|
||||
const messageLoading = ref(false)
|
||||
|
||||
/** 当前选中的消息对象 */
|
||||
const currentMessage = computed(() => {
|
||||
return messageList.value[selectedMessageIdx.value] || null
|
||||
})
|
||||
|
||||
/**
|
||||
* 获取未读消息数量
|
||||
*/
|
||||
async function loadUnreadCount() {
|
||||
try {
|
||||
const res = await fetchUnreadCount()
|
||||
// 接口返回结构: { code, data: number }
|
||||
unreadCount.value = res.data ?? 0
|
||||
} catch (e) {
|
||||
console.error('获取未读消息数量失败', e)
|
||||
}
|
||||
}
|
||||
|
||||
// 组件挂载时获取未读消息数量
|
||||
onMounted(() => {
|
||||
loadUnreadCount()
|
||||
})
|
||||
|
||||
/**
|
||||
* 加载消息列表(分页追加)
|
||||
*/
|
||||
async function loadMessages() {
|
||||
if (messageLoading.value || !messageHasMore.value) return
|
||||
messageLoading.value = true
|
||||
try {
|
||||
const res = await fetchMessageList({ pageNum: messagePageNum.value, pageSize: 20 })
|
||||
// 接口返回结构: { code, msg, data: { pageNum, pageSize, total, list } }
|
||||
const pageData = res.data
|
||||
if (pageData && pageData.list && pageData.list.length > 0) {
|
||||
messageList.value.push(...pageData.list)
|
||||
// 判断是否还有更多
|
||||
messageHasMore.value = messageList.value.length < pageData.total
|
||||
messagePageNum.value++
|
||||
} else {
|
||||
messageHasMore.value = false
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('加载消息失败', e)
|
||||
} finally {
|
||||
messageLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 消息列表滚动到底部时加载下一页
|
||||
*/
|
||||
function handleMessageListScroll(e: Event) {
|
||||
const el = e.target as HTMLElement
|
||||
if (el.scrollTop + el.clientHeight >= el.scrollHeight - 10) {
|
||||
loadMessages()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新消息列表数据(保持当前选中的消息不变)
|
||||
*/
|
||||
async function refreshMessageList(keepMessageId: number | string) {
|
||||
try {
|
||||
// 重新请求已加载的所有页数据
|
||||
const totalLoaded = messageList.value.length
|
||||
const res = await fetchMessageList({ pageNum: 1, pageSize: totalLoaded })
|
||||
const pageData = res.data
|
||||
if (pageData && pageData.list) {
|
||||
messageList.value = pageData.list
|
||||
// 根据之前选中的消息ID恢复选中状态
|
||||
const idx = messageList.value.findIndex((m: MessageDto) => String(m.id) === String(keepMessageId))
|
||||
selectedMessageIdx.value = idx >= 0 ? idx : 0
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('刷新消息列表失败', e)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 上报消息已读:当右侧显示某条消息时,如果该消息未读则标记已读
|
||||
* 标记成功后刷新列表数据和未读数量
|
||||
*/
|
||||
async function handleMarkRead(msg: MessageDto) {
|
||||
if (msg.read) return
|
||||
try {
|
||||
await markMessageRead(msg.id)
|
||||
// 刷新列表状态,保持当前选中
|
||||
await refreshMessageList(msg.id)
|
||||
// 更新未读数量
|
||||
loadUnreadCount()
|
||||
} catch (e) {
|
||||
console.error('标记已读失败', e)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听当前选中消息变化,触发已读上报
|
||||
*/
|
||||
watch(currentMessage, (msg) => {
|
||||
if (msg && !msg.read) {
|
||||
handleMarkRead(msg)
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* 监听弹窗打开,重置并加载第一页消息
|
||||
*/
|
||||
watch(showMessageDialog, (val) => {
|
||||
if (val) {
|
||||
messageList.value = []
|
||||
messagePageNum.value = 1
|
||||
messageHasMore.value = true
|
||||
selectedMessageIdx.value = 0
|
||||
loadMessages()
|
||||
}
|
||||
})
|
||||
|
||||
const feedbackOptions = ['Bug反馈', '功能建议', '使用体验', '订阅及会员权益相关问题', '其它']
|
||||
|
||||
function handleFeedbackSubmit() {
|
||||
@@ -194,7 +353,7 @@ const settingsMenu = computed(() => {
|
||||
})
|
||||
|
||||
const footerMenus = computed(() => [
|
||||
{ iconImg: navMessageIcon, label: '消息通知', badge: 'NEW', action: () => { showMessageDialog.value = true } },
|
||||
{ iconImg: navMessageIcon, label: '消息通知', badge: unreadCount.value > 0 ? 'NEW' : '', action: () => { showMessageDialog.value = true } },
|
||||
{ iconImg: settingsMenu.value.iconImg, label: settingsMenu.value.label, action: () => { handleSettingsNav() } },
|
||||
{ iconImg: navFeedbackIcon, label: '反馈', action: () => { showFeedbackDialog.value = true } },
|
||||
])
|
||||
|
||||
Reference in New Issue
Block a user