706 lines
31 KiB
Vue
706 lines
31 KiB
Vue
<template>
|
||
<!-- 会员弹窗 — 通过 Teleport 挂载到 body -->
|
||
<Teleport to="body">
|
||
<!-- 遮罩层 — 点击关闭弹窗 -->
|
||
<div v-if="modelValue" class="member-dialog-overlay" @click="$emit('update:modelValue', false)">
|
||
<!-- 弹窗主体 — 阻止点击冒泡 -->
|
||
<div class="member-dialog" @click.stop>
|
||
<!-- 右上角关闭按钮 -->
|
||
<span class="member-dialog__close" @click="$emit('update:modelValue', false)">✕</span>
|
||
|
||
<!-- ==================== 会员介绍步骤一:简介页 ==================== -->
|
||
<div v-if="currentView === 'intro1'" class="member-dialog__intro-step1">
|
||
<h1 class="member-dialog__intro-title">Offer 派 - AI 智能求职引擎</h1>
|
||
<p class="member-dialog__intro-subtitle">全自动海投、简历精修、模拟面试,助你轻松拿高薪。</p>
|
||
<button class="member-dialog__intro-btn" @click="currentView = 'intro2'">
|
||
<span class="member-dialog__intro-btn-icon">⚡</span>
|
||
升级 Pro 开启求职加速
|
||
</button>
|
||
</div>
|
||
|
||
<!-- ==================== 会员介绍步骤二:详细介绍页(可滚动) ==================== -->
|
||
<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/alex-avatar.png" alt="用户头像" />
|
||
</div>
|
||
<div class="member-dialog__testimonial-content">
|
||
<div class="member-dialog__testimonial-stars">★★★★★</div>
|
||
<p class="member-dialog__testimonial-text">"开通后我主要用 AI 简历优化和岗位匹配,投递效率明显提升,每次网申节省了十几分钟,终于知道该重点投哪些岗位了。"</p>
|
||
<div class="member-dialog__testimonial-author">
|
||
<span class="member-dialog__testimonial-name">张同学</span>
|
||
<span class="member-dialog__testimonial-desc">· 成功入职某大厂运营</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ==================== 下单步骤一:选择套餐 ==================== -->
|
||
<div v-else-if="currentView === 'order1'" class="member-dialog__order">
|
||
<!-- 顶部导航 -->
|
||
<div class="member-dialog__order-header">
|
||
<span class="melmber-dialog__order-back" @click="currentView = 'intro2'">‹ 返回会员介绍</span>
|
||
<!-- 步骤条 — 二维码弹窗打开时显示第2步 -->
|
||
<div class="member-dialog__steps">
|
||
<div class="member-dialog__step" :class="showQrCode ? 'member-dialog__step--done' : 'member-dialog__step--active'">
|
||
<span class="member-dialog__step-num">{{ showQrCode ? '✓' : '1' }}</span>
|
||
<span class="member-dialog__step-label">选择套餐</span>
|
||
</div>
|
||
<div class="member-dialog__step-line" :class="{ 'member-dialog__step-line--done': showQrCode }"></div>
|
||
<div class="member-dialog__step" :class="{ 'member-dialog__step--active': showQrCode }">
|
||
<span class="member-dialog__step-num">2</span>
|
||
<span class="member-dialog__step-label">支付方式</span>
|
||
</div>
|
||
<div class="member-dialog__step-line"></div>
|
||
<div class="member-dialog__step">
|
||
<span class="member-dialog__step-num">3</span>
|
||
<span class="member-dialog__step-label">完成</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 主内容区域(可滚动) -->
|
||
<div class="member-dialog__order-body">
|
||
<!-- 左侧内容 -->
|
||
<div class="member-dialog__order-left">
|
||
<h2 class="member-dialog__order-title">开启你的 AI 求职加速计划</h2>
|
||
<p class="member-dialog__order-desc">解锁简历优化、岗位匹配、投递追踪、面试准备等能力,让你的求职过程更高效、更有方向。</p>
|
||
|
||
<!-- 套餐选择卡片 -->
|
||
<div class="member-dialog__order-plans">
|
||
<div
|
||
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-info">
|
||
<div class="dflex">
|
||
<div class="member-dialog__order-plan-name">{{ plan.name }}</div>
|
||
<!-- 选中圆圈 -->
|
||
<div class="member-dialog__order-plan-radio">
|
||
<div v-if="selectedPlan === plan.key" class="member-dialog__order-plan-radio-inner"></div>
|
||
</div>
|
||
</div>
|
||
<div class="member-dialog__order-plan-price">
|
||
<span class="member-dialog__order-plan-price-symbol">¥</span>
|
||
<span class="member-dialog__order-plan-price-num">{{ plan.price }}</span>
|
||
<span class="member-dialog__order-plan-price-unit">/{{ plan.unit }}</span>
|
||
</div>
|
||
<div class="member-dialog__order-plan-desc">{{ plan.orderDesc }}</div>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 支付方式 -->
|
||
<div class="member-dialog__order-payment">
|
||
<h3 class="member-dialog__order-payment-title">支付方式</h3>
|
||
<div class="member-dialog__order-payment-methods">
|
||
<div
|
||
v-for="method in paymentMethods"
|
||
:key="method.key"
|
||
class="member-dialog__order-payment-item"
|
||
:class="{ 'member-dialog__order-payment-item--active': selectedPayment === method.key }"
|
||
@click="selectedPayment = method.key"
|
||
>
|
||
<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__order-right">
|
||
<div class="member-dialog__order-summary">
|
||
<h3 class="member-dialog__order-summary-title">订单摘要</h3>
|
||
<!-- 套餐信息 -->
|
||
<div class="member-dialog__order-summary-row">
|
||
<div>
|
||
<div class="member-dialog__order-summary-plan">{{ currentPlan.name }}</div>
|
||
<div class="member-dialog__order-summary-duration">{{ currentPlan.unit }}</div>
|
||
</div>
|
||
<span class="member-dialog__order-summary-price">¥{{ currentPlan.price }}</span>
|
||
</div>
|
||
<!-- 分割线 -->
|
||
<div class="member-dialog__order-summary-divider"></div>
|
||
<!-- 今日支付 -->
|
||
<div class="member-dialog__order-summary-row">
|
||
<span class="member-dialog__order-summary-label">今日支付</span>
|
||
<span class="member-dialog__order-summary-total">¥{{ currentPlan.price }}</span>
|
||
</div>
|
||
<!-- 协议勾选 -->
|
||
<div class="member-dialog__order-summary-agree">
|
||
<label class="member-dialog__order-checkbox">
|
||
<input type="checkbox" v-model="agreeProtocol" />
|
||
<span class="member-dialog__order-checkbox-mark"></span>
|
||
</label>
|
||
<span>我已阅读并同意 <a href="javascript:;" @click.prevent="openMemberAgreement">《会员服务协议》</a> </span>
|
||
</div>
|
||
<!-- 立即开启按钮 -->
|
||
<button
|
||
class="member-dialog__order-submit-btn"
|
||
:disabled="!agreeProtocol"
|
||
@click="handleShowQrCode"
|
||
>立即开启求职加速</button>
|
||
<div class="member-dialog__order-submit-tip">支付成功后立即解锁全部权益,可随时取消续费。</div>
|
||
|
||
<!-- 你将获得 -->
|
||
<div class="member-dialog__order-benefits">
|
||
<h4 class="member-dialog__order-benefits-title">你将获得</h4>
|
||
<div v-for="item in orderBenefits" :key="item" class="member-dialog__order-benefits-item">
|
||
<span class="member-dialog__order-benefits-check">✓</span>
|
||
<span>{{ item }}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ==================== 下单步骤二:确认支付结果 ==================== -->
|
||
<div v-else-if="currentView === 'order2'" class="member-dialog__order">
|
||
<div class="member-dialog__order-header">
|
||
<span class="member-dialog__order-back"></span>
|
||
<div class="member-dialog__steps">
|
||
<div class="member-dialog__step member-dialog__step--done">
|
||
<span class="member-dialog__step-num">✓</span>
|
||
<span class="member-dialog__step-label">选择套餐</span>
|
||
</div>
|
||
<div class="member-dialog__step-line member-dialog__step-line--done"></div>
|
||
<div class="member-dialog__step member-dialog__step--active">
|
||
<span class="member-dialog__step-num">2</span>
|
||
<span class="member-dialog__step-label">支付方式</span>
|
||
</div>
|
||
<div class="member-dialog__step-line"></div>
|
||
<div class="member-dialog__step">
|
||
<span class="member-dialog__step-num">3</span>
|
||
<span class="member-dialog__step-label">完成</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- 确认中内容 -->
|
||
<div class="member-dialog__order-confirming">
|
||
<div class="member-dialog__order-confirming-card">
|
||
<!-- 加载动画 -->
|
||
<div class="member-dialog__order-loading-spinner"></div>
|
||
<h2 class="member-dialog__order-confirming-title">正在确认支付结果</h2>
|
||
<p class="member-dialog__order-confirming-desc">请稍候,请勿关闭此页面。</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ==================== 下单步骤三:支付完成 ==================== -->
|
||
<div v-else-if="currentView === 'order3'" class="member-dialog__order">
|
||
<div class="member-dialog__order-header">
|
||
<span class="member-dialog__order-back"></span>
|
||
<div class="member-dialog__steps">
|
||
<div class="member-dialog__step member-dialog__step--done">
|
||
<span class="member-dialog__step-num">✓</span>
|
||
<span class="member-dialog__step-label">选择套餐</span>
|
||
</div>
|
||
<div class="member-dialog__step-line member-dialog__step-line--done"></div>
|
||
<div class="member-dialog__step member-dialog__step--done">
|
||
<span class="member-dialog__step-num">✓</span>
|
||
<span class="member-dialog__step-label">支付方式</span>
|
||
</div>
|
||
<div class="member-dialog__step-line member-dialog__step-line--done"></div>
|
||
<div class="member-dialog__step member-dialog__step--active">
|
||
<span class="member-dialog__step-num">3</span>
|
||
<span class="member-dialog__step-label">完成</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- 支付成功内容 -->
|
||
<div class="member-dialog__order-success">
|
||
<div class="member-dialog__order-success-card">
|
||
<!-- 成功图标 -->
|
||
<div class="member-dialog__order-success-icon">✓</div>
|
||
<h2 class="member-dialog__order-success-title">支付成功,求职加速已开启</h2>
|
||
<p class="member-dialog__order-success-desc">你已成功解锁 AI 求职加速权益,现在可以开始优化简历、匹配岗位并准备面试。</p>
|
||
<button class="member-dialog__order-success-btn" @click="handleGoResume">开始优化我的简历</button>
|
||
<button class="member-dialog__order-success-btn-secondary" @click="handleViewMemberBenefits">查看会员权益</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ==================== 付款弹窗 ==================== -->
|
||
<div v-if="showQrCode" class="member-dialog__qrcode-overlay" @click="handleCloseQrCode">
|
||
<div class="member-dialog__qrcode-modal" @click.stop>
|
||
<!-- 关闭按钮 -->
|
||
<span class="member-dialog__qrcode-close" @click="handleCloseQrCode">✕</span>
|
||
<!-- 支付方式标题 -->
|
||
<div class="member-dialog__qrcode-title">
|
||
<span class="member-dialog__qrcode-title-icon">{{ selectedPayment === 'wechat' ? '💬' : '🔷' }}</span>
|
||
<span>{{ selectedPayment === 'wechat' ? '微信支付' : '支付宝' }}</span>
|
||
</div>
|
||
<!-- 扫码提示 -->
|
||
<h3 class="member-dialog__qrcode-subtitle">{{ selectedPayment === 'alipay' ? '扫码完成支付' : '扫码完成支付' }}</h3>
|
||
<p class="member-dialog__qrcode-desc">{{ selectedPayment === 'alipay' ? '请在支付宝页面完成支付,完成后点击下方按钮确认。' : '请使用微信 App 扫描二维码完成支付,完成后此窗口会自动关闭。' }}</p>
|
||
<!-- 支付宝:iframe 渲染支付表单 -->
|
||
<div v-if="selectedPayment === 'alipay' && paymentFormHtml" class="member-dialog__qrcode-image">
|
||
<iframe
|
||
:srcdoc="paymentFormHtml"
|
||
frameborder="0"
|
||
class="member-dialog__payment-iframe"
|
||
scrolling="auto"
|
||
></iframe>
|
||
</div>
|
||
<!-- 微信:使用 qrcode.js 渲染二维码 -->
|
||
<div v-if="selectedPayment === 'wechat' && wechatCodeUrl" class="member-dialog__qrcode-image">
|
||
<iframe
|
||
:srcdoc="wechatQrHtml"
|
||
frameborder="0"
|
||
class="member-dialog__wechat-qr-iframe"
|
||
scrolling="no"
|
||
></iframe>
|
||
</div>
|
||
<!-- 金额 -->
|
||
<div class="member-dialog__qrcode-amount">¥{{ currentPlan.price }}</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 协议预览弹窗 -->
|
||
<AgreementPreviewDialog v-model="showAgreementDialog" code="ae8065i3" />
|
||
</Teleport>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref, computed, watch, reactive, onMounted } from 'vue'
|
||
import { useRouter } from 'vue-router'
|
||
import { useStore } from 'vuex'
|
||
import { fetchMemberProductList, createMemberOrder, fetchOrderDetail, type MemberProduct } from '@/api/member'
|
||
import AgreementPreviewDialog from '@/components/tools/AgreementPreviewDialog.vue'
|
||
|
||
/** 组件 Props — 控制弹窗显示/隐藏 */
|
||
const props = defineProps<{ modelValue: boolean }>()
|
||
|
||
/** 组件 Emits — 通知父组件更新 modelValue */
|
||
const emit = defineEmits<{ (e: 'update:modelValue', value: boolean): void }>()
|
||
|
||
const router = useRouter()
|
||
const store = useStore()
|
||
|
||
// ==================== 类型定义 ====================
|
||
|
||
/** 套餐项类型(包含后端完整数据 + 前端展示字段) */
|
||
interface PlanItem {
|
||
key: string
|
||
name: string
|
||
price: string
|
||
unit: string
|
||
tag?: string
|
||
recommend?: boolean
|
||
perMonth?: string
|
||
btnText: string
|
||
orderDesc: string
|
||
/** 后端原始数据,下单时使用 */
|
||
raw: MemberProduct
|
||
}
|
||
|
||
/** 支付方式类型 */
|
||
interface PaymentMethod {
|
||
key: string
|
||
name: string
|
||
icon: string
|
||
}
|
||
|
||
/** FAQ 项类型 */
|
||
interface FaqItem {
|
||
question: string
|
||
answer: string
|
||
open: boolean
|
||
}
|
||
|
||
// ==================== 状态 ====================
|
||
|
||
/** 当前视图(intro1/intro2/order1/order2/order3) */
|
||
const currentView = ref<'intro1' | 'intro2' | 'order1' | 'order2' | 'order3'>('intro2')
|
||
|
||
/** 当前选中的套餐 */
|
||
const selectedPlan = ref('quarterly')
|
||
|
||
/** 当前选中的支付方式 */
|
||
const selectedPayment = ref('wechat')
|
||
|
||
/** 是否同意协议 */
|
||
const agreeProtocol = ref(false)
|
||
|
||
/** 是否显示二维码弹窗 */
|
||
const showQrCode = ref(false)
|
||
|
||
/** 支付宝表单 HTML(用于 iframe 渲染) */
|
||
const paymentFormHtml = ref('')
|
||
|
||
/** 微信支付 code_url(用于生成二维码) */
|
||
const wechatCodeUrl = ref('')
|
||
|
||
/** 微信二维码 iframe 的 srcdoc HTML */
|
||
const wechatQrHtml = computed(() => {
|
||
if (!wechatCodeUrl.value) return ''
|
||
return `<!DOCTYPE html><html><head><meta charset="UTF-8"><script src="https://cdn.jsdelivr.net/npm/qrcodejs@1.0.0/qrcode.min.js"><\/script><style>body{display:flex;align-items:center;justify-content:center;margin:0;height:100%;}</style></head><body><div id="qrcode"></div><script>new QRCode(document.getElementById("qrcode"),{text:"${wechatCodeUrl.value}",width:200,height:200,colorDark:"#000000",colorLight:"#ffffff",correctLevel:QRCode.CorrectLevel.H});<\/script></body></html>`
|
||
})
|
||
|
||
/** 当前订单ID(用于后续轮询支付状态) */
|
||
const orderId = ref('')
|
||
|
||
/** 是否正在创建订单 */
|
||
const creatingOrder = ref(false)
|
||
|
||
/** 监听弹窗开关 — 打开时锁定背景滚动,关闭时恢复并重置状态 */
|
||
watch(() => props.modelValue, (val) => {
|
||
document.body.style.overflow = val ? 'hidden' : ''
|
||
if (!val) {
|
||
currentView.value = 'intro2'
|
||
showQrCode.value = false
|
||
agreeProtocol.value = false
|
||
paymentFormHtml.value = ''
|
||
wechatCodeUrl.value = ''
|
||
orderId.value = ''
|
||
// 清除轮询计时器
|
||
if (pollTimer) {
|
||
clearInterval(pollTimer)
|
||
pollTimer = null
|
||
}
|
||
}
|
||
})
|
||
|
||
/** 监听二维码弹窗关闭 — 无论何种方式关闭都停止轮询 */
|
||
watch(showQrCode, (val) => {
|
||
if (!val && pollTimer) {
|
||
clearInterval(pollTimer)
|
||
pollTimer = null
|
||
}
|
||
})
|
||
|
||
// ==================== 计算属性 ====================
|
||
|
||
/** 当前选中的套餐对象 */
|
||
const currentPlan = computed(() => {
|
||
return plans.value.find(p => p.key === selectedPlan.value) || plans.value[0]
|
||
})
|
||
|
||
// ==================== 常量数据 ====================
|
||
|
||
/** 套餐列表(从接口获取) */
|
||
const plans = ref<PlanItem[]>([])
|
||
|
||
/** 根据有效天数生成单位描述 */
|
||
function getDurationUnit(days: number): string {
|
||
if (days <= 7) return '周'
|
||
if (days <= 31) return '月'
|
||
if (days <= 93) return '3个月'
|
||
if (days <= 186) return '半年'
|
||
return '年'
|
||
}
|
||
|
||
/** 将后端商品数据转换为前端套餐项 */
|
||
function mapProductToPlan(product: MemberProduct): PlanItem {
|
||
const priceYuan = (product.price / 100).toFixed(2)
|
||
const monthlyPriceYuan = product.monthlyPrice ? (product.monthlyPrice / 100).toFixed(2) : undefined
|
||
return {
|
||
key: String(product.id),
|
||
name: product.productName,
|
||
price: priceYuan,
|
||
unit: getDurationUnit(product.durationDays),
|
||
tag: product.tag || undefined,
|
||
recommend: product.isFeatured === 1,
|
||
perMonth: monthlyPriceYuan,
|
||
btnText: product.buyButtonText || '立即开通',
|
||
orderDesc: product.tag || product.productName,
|
||
raw: product,
|
||
}
|
||
}
|
||
|
||
/** 加载会员商品列表 */
|
||
async function loadMemberProducts() {
|
||
try {
|
||
const res = await fetchMemberProductList()
|
||
if (res.data && res.data.length) {
|
||
// 按 sortOrder 排序
|
||
const sorted = [...res.data].sort((a, b) => a.sortOrder - b.sortOrder)
|
||
plans.value = sorted.map(mapProductToPlan)
|
||
// 默认选中主推套餐,若无则选第一个
|
||
const featured = plans.value.find(p => p.recommend)
|
||
selectedPlan.value = featured ? featured.key : plans.value[0].key
|
||
}
|
||
} catch (e) {
|
||
console.error('加载会员商品列表失败', e)
|
||
}
|
||
}
|
||
|
||
/** 组件挂载时加载商品列表 */
|
||
onMounted(() => {
|
||
loadMemberProducts()
|
||
})
|
||
|
||
/** 求职加速能力列表 */
|
||
const abilities = [
|
||
{ icon: '◉', color: '#4FC2C9', text: '个性化岗位匹配,量大极速又精准' },
|
||
{ icon: '◉', color: '#4FC2C9', text: '针对岗位 JD 自动优化简历,提高简历通过率' },
|
||
{ icon: '⚡', color: '#4FC2C9', text: '自动填写申请表,每次投递节省 10-15 分钟' },
|
||
{ icon: '↗', color: '#E85635', text: '内推码,提高简历被 HR 看到的概率' },
|
||
{ icon: '⏰', color: '#777777', text: '第一时间提醒高匹配岗位,抢占面试机会' },
|
||
]
|
||
|
||
/** 会员能力对比数据 */
|
||
const compareData = [
|
||
{ name: 'AI 岗位匹配', free: '免费 3 天', pro: '不限次数' },
|
||
{ name: '简历针对性优化', free: '免费 3 天', pro: '不限次数' },
|
||
{ name: '自动填写网申', free: '免费 3 天', pro: '不限次数' },
|
||
{ name: '内推码获取', free: '免费 3 天', pro: '不限次数' },
|
||
{ name: '高匹配岗位推送', free: '免费 3 天', pro: '不限次数' },
|
||
]
|
||
|
||
/** 支付方式列表 */
|
||
const paymentMethods: PaymentMethod[] = [
|
||
{ key: 'wechat', name: '微信支付', icon: '💬' },
|
||
{ key: 'alipay', name: '支付宝', icon: '🔷' },
|
||
]
|
||
|
||
/** 订单权益列表 */
|
||
const orderBenefits = [
|
||
'AI 简历深度优化',
|
||
'智能岗位匹配分析',
|
||
'求职信快速生成',
|
||
'面试准备指导',
|
||
'投递进度追踪',
|
||
'求职策略建议',
|
||
]
|
||
|
||
/** 常见问题列表 */
|
||
const faqList = reactive<FaqItem[]>([
|
||
{ question: '支付后如何开通?', answer: '支付成功后立即生效,无需等待,登录账号即可使用全部会员权益。', open: true },
|
||
{ question: '可以随时取消吗?', answer: '可以,您可以在设置中随时取消自动续费,当前周期内的权益仍然有效。', open: false },
|
||
{ question: '支持哪些支付方式?', answer: '目前支持微信支付和支付宝两种支付方式。', open: false },
|
||
{ question: '可以开发票吗?', answer: '支持开具电子发票,请在支付完成后联系客服申请。', open: false },
|
||
])
|
||
|
||
// ==================== 事件处理 ====================
|
||
|
||
/** 从介绍页点击升级按钮 — 进入下单步骤一 */
|
||
function handleUpgrade(plan: PlanItem) {
|
||
selectedPlan.value = plan.key
|
||
currentView.value = 'order1'
|
||
}
|
||
|
||
/** 点击立即开启求职加速 — 调用创建订单接口并显示支付弹窗 */
|
||
async function handleShowQrCode() {
|
||
if (!agreeProtocol.value || creatingOrder.value) return
|
||
const plan = currentPlan.value
|
||
if (!plan) return
|
||
|
||
creatingOrder.value = true
|
||
try {
|
||
// 支付渠道:1=微信 2=支付宝
|
||
const payChannel = selectedPayment.value === 'alipay' ? 2 : 1
|
||
const res = await createMemberOrder({
|
||
productId: plan.key, // key 即为商品 id(字符串,避免大数精度丢失)
|
||
payChannel,
|
||
})
|
||
if (res.data) {
|
||
orderId.value = String(res.data.orderId)
|
||
if (selectedPayment.value === 'wechat') {
|
||
// 微信支付 — payData 为 code_url,用于生成二维码
|
||
wechatCodeUrl.value = res.data.payData
|
||
paymentFormHtml.value = ''
|
||
} else {
|
||
// 支付宝 — payData 为 HTML 表单
|
||
paymentFormHtml.value = res.data.payData
|
||
wechatCodeUrl.value = ''
|
||
}
|
||
showQrCode.value = true
|
||
// 创建订单成功后立即开始轮询支付状态
|
||
confirmPayment()
|
||
}
|
||
} catch (e) {
|
||
console.error('创建订单失败', e)
|
||
} finally {
|
||
creatingOrder.value = false
|
||
}
|
||
}
|
||
|
||
/** 轮询计时器 */
|
||
let pollTimer: ReturnType<typeof setInterval> | null = null
|
||
|
||
/** 轮询查询订单支付状态 */
|
||
async function confirmPayment() {
|
||
const startTime = Date.now()
|
||
/** 超时时间:5 分钟 */
|
||
const timeout = 5 * 60 * 1000
|
||
/** 轮询间隔:500ms */
|
||
const interval = 500
|
||
|
||
// 清除可能存在的旧计时器
|
||
if (pollTimer) {
|
||
clearInterval(pollTimer)
|
||
pollTimer = null
|
||
}
|
||
|
||
pollTimer = setInterval(async () => {
|
||
try {
|
||
const res = await fetchOrderDetail(orderId.value)
|
||
const status = res.data?.status
|
||
|
||
if (status === 1) {
|
||
// 支付成功 — 关闭二维码弹窗,进入完成步骤
|
||
clearInterval(pollTimer!)
|
||
pollTimer = null
|
||
showQrCode.value = false
|
||
currentView.value = 'order3'
|
||
} else if (status === 2 || status === 3) {
|
||
// 已退款或已关闭 — 提示并回到选择套餐步骤
|
||
clearInterval(pollTimer!)
|
||
pollTimer = null
|
||
showQrCode.value = false
|
||
ElMessage.error('支付未成功,请重新下单')
|
||
currentView.value = 'order1'
|
||
} else if (Date.now() - startTime >= timeout) {
|
||
// 超时 — 提示并回到选择套餐步骤
|
||
clearInterval(pollTimer!)
|
||
pollTimer = null
|
||
showQrCode.value = false
|
||
ElMessage.error('支付未成功,请重新下单')
|
||
currentView.value = 'order1'
|
||
}
|
||
// status === 0 继续等待
|
||
} catch (e) {
|
||
console.error('查询订单状态失败', e)
|
||
}
|
||
}, interval)
|
||
}
|
||
|
||
/** 支付成功后跳转简历页 */
|
||
function handleGoResume() {
|
||
router.push('/resume')
|
||
}
|
||
|
||
/** 关闭二维码弹窗 — 停止轮询 */
|
||
function handleCloseQrCode() {
|
||
showQrCode.value = false
|
||
if (pollTimer) {
|
||
clearInterval(pollTimer)
|
||
pollTimer = null
|
||
}
|
||
}
|
||
|
||
/** 查看会员权益 — 关闭会员弹窗,打开设置弹窗的会员 Tab */
|
||
function handleViewMemberBenefits() {
|
||
emit('update:modelValue', false)
|
||
store.commit('SET_SETTINGS_TAB', 'member')
|
||
store.commit('SET_SHOW_SETTINGS', true)
|
||
}
|
||
|
||
/** 协议预览弹窗显示状态 */
|
||
const showAgreementDialog = ref(false)
|
||
|
||
/** 打开会员服务协议 */
|
||
function openMemberAgreement() {
|
||
showAgreementDialog.value = true
|
||
}
|
||
</script>
|