Files
offerpai_web/src/components/MemberDialog.vue
T
2026-06-24 19:03:41 +08:00

706 lines
31 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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>