支付宝下单和其他细节优化
This commit is contained in:
@@ -20,7 +20,7 @@
|
||||
<!-- AI 欢迎消息 -->
|
||||
<div class="ai-chat__msg ai-chat__msg--ai">
|
||||
<div class="ai-chat__msg-bubble">
|
||||
<div class="ai-chat__msg-title">欢迎回来,李华!</div>
|
||||
<div class="ai-chat__msg-title">欢迎回来,{{nickName}}!</div>
|
||||
<div class="ai-chat__msg-text">很高兴再次见到你,让我们继续您通往理想工作的旅程吧。</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -92,6 +92,10 @@ const props = defineProps<{
|
||||
const currentRoute = useRoute()
|
||||
const store = useStore()
|
||||
|
||||
|
||||
/** 用户昵称 — 从全局 store 读取 */
|
||||
const nickName = computed(() => store.state.userInfo?.nick || '')
|
||||
|
||||
// ==================== 状态 ====================
|
||||
|
||||
/** 会员购买弹窗的显示状态 */
|
||||
|
||||
+200
-46
@@ -108,14 +108,14 @@
|
||||
<!-- 顶部导航 -->
|
||||
<div class="member-dialog__order-header">
|
||||
<span class="member-dialog__order-back" @click="currentView = 'intro2'">‹ 返回会员介绍</span>
|
||||
<!-- 步骤条 -->
|
||||
<!-- 步骤条 — 二维码弹窗打开时显示第2步 -->
|
||||
<div class="member-dialog__steps">
|
||||
<div class="member-dialog__step member-dialog__step--active">
|
||||
<span class="member-dialog__step-num">1</span>
|
||||
<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"></div>
|
||||
<div class="member-dialog__step">
|
||||
<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>
|
||||
@@ -144,12 +144,15 @@
|
||||
@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="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>
|
||||
@@ -157,6 +160,7 @@
|
||||
</div>
|
||||
<div class="member-dialog__order-plan-desc">{{ plan.orderDesc }}</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -317,33 +321,36 @@
|
||||
<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>
|
||||
<button class="member-dialog__order-success-btn-secondary" @click="handleViewMemberBenefits">查看会员权益</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ==================== 付款二维码遮罩层 ==================== -->
|
||||
<div v-if="showQrCode" class="member-dialog__qrcode-overlay" @click="showQrCode = false">
|
||||
<!-- ==================== 付款弹窗 ==================== -->
|
||||
<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="showQrCode = false">✕</span>
|
||||
<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">扫码完成支付</h3>
|
||||
<p class="member-dialog__qrcode-desc">请使用{{ selectedPayment === 'wechat' ? '微信' : '支付宝' }} App 扫描二维码完成支付,完成后此窗口会自动关闭。</p>
|
||||
<!-- 二维码占位 -->
|
||||
<h3 class="member-dialog__qrcode-subtitle">{{ selectedPayment === 'alipay' ? '扫码完成支付' : '扫码完成支付' }}</h3>
|
||||
<p class="member-dialog__qrcode-desc">{{ selectedPayment === 'alipay' ? '请在支付宝页面完成支付,完成后点击下方按钮确认。' : '请使用微信 App 扫描二维码完成支付,完成后此窗口会自动关闭。' }}</p>
|
||||
<!-- 支付宝:iframe 渲染支付表单 -->
|
||||
<div class="member-dialog__qrcode-image">
|
||||
<!-- TODO: 替换为真实二维码图片 -->
|
||||
<div class="member-dialog__qrcode-placeholder"></div>
|
||||
<iframe
|
||||
v-if="paymentFormHtml"
|
||||
:srcdoc="paymentFormHtml"
|
||||
frameborder="0"
|
||||
class="member-dialog__payment-iframe"
|
||||
scrolling="auto"
|
||||
></iframe>
|
||||
</div>
|
||||
<!-- 金额 -->
|
||||
<div class="member-dialog__qrcode-amount">¥{{ currentPlan.price }}</div>
|
||||
<!-- 我已完成支付按钮 -->
|
||||
<button class="member-dialog__qrcode-confirm-btn" @click="handlePaymentDone">我已完成支付</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -352,20 +359,23 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch, reactive } from 'vue'
|
||||
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'
|
||||
|
||||
/** 组件 Props — 控制弹窗显示/隐藏 */
|
||||
const props = defineProps<{ modelValue: boolean }>()
|
||||
|
||||
/** 组件 Emits — 通知父组件更新 modelValue */
|
||||
defineEmits<{ (e: 'update:modelValue', value: boolean): void }>()
|
||||
const emit = defineEmits<{ (e: 'update:modelValue', value: boolean): void }>()
|
||||
|
||||
const router = useRouter()
|
||||
const store = useStore()
|
||||
|
||||
// ==================== 类型定义 ====================
|
||||
|
||||
/** 套餐项类型 */
|
||||
/** 套餐项类型(包含后端完整数据 + 前端展示字段) */
|
||||
interface PlanItem {
|
||||
key: string
|
||||
name: string
|
||||
@@ -376,6 +386,8 @@ interface PlanItem {
|
||||
perMonth?: string
|
||||
btnText: string
|
||||
orderDesc: string
|
||||
/** 后端原始数据,下单时使用 */
|
||||
raw: MemberProduct
|
||||
}
|
||||
|
||||
/** 支付方式类型 */
|
||||
@@ -409,6 +421,15 @@ const agreeProtocol = ref(false)
|
||||
/** 是否显示二维码弹窗 */
|
||||
const showQrCode = ref(false)
|
||||
|
||||
/** 支付宝表单 HTML(用于 iframe 渲染) */
|
||||
const paymentFormHtml = ref('')
|
||||
|
||||
/** 当前订单ID(用于后续轮询支付状态) */
|
||||
const orderId = ref('')
|
||||
|
||||
/** 是否正在创建订单 */
|
||||
const creatingOrder = ref(false)
|
||||
|
||||
/** 监听弹窗开关 — 打开时锁定背景滚动,关闭时恢复并重置状态 */
|
||||
watch(() => props.modelValue, (val) => {
|
||||
document.body.style.overflow = val ? 'hidden' : ''
|
||||
@@ -416,6 +437,21 @@ watch(() => props.modelValue, (val) => {
|
||||
currentView.value = 'intro2'
|
||||
showQrCode.value = false
|
||||
agreeProtocol.value = false
|
||||
paymentFormHtml.value = ''
|
||||
orderId.value = ''
|
||||
// 清除轮询计时器
|
||||
if (pollTimer) {
|
||||
clearInterval(pollTimer)
|
||||
pollTimer = null
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
/** 监听二维码弹窗关闭 — 无论何种方式关闭都停止轮询 */
|
||||
watch(showQrCode, (val) => {
|
||||
if (!val && pollTimer) {
|
||||
clearInterval(pollTimer)
|
||||
pollTimer = null
|
||||
}
|
||||
})
|
||||
|
||||
@@ -423,17 +459,62 @@ watch(() => props.modelValue, (val) => {
|
||||
|
||||
/** 当前选中的套餐对象 */
|
||||
const currentPlan = computed(() => {
|
||||
return plans.find(p => p.key === selectedPlan.value) || plans[1]
|
||||
return plans.value.find(p => p.key === selectedPlan.value) || plans.value[0]
|
||||
})
|
||||
|
||||
// ==================== 常量数据 ====================
|
||||
|
||||
/** 套餐列表 */
|
||||
const plans: PlanItem[] = [
|
||||
{ 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 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 = [
|
||||
@@ -485,29 +566,102 @@ function handleUpgrade(plan: PlanItem) {
|
||||
currentView.value = 'order1'
|
||||
}
|
||||
|
||||
/** 点击立即开启求职加速 — 显示二维码弹窗 */
|
||||
function handleShowQrCode() {
|
||||
if (!agreeProtocol.value) return
|
||||
showQrCode.value = true
|
||||
/** 点击立即开启求职加速 — 调用创建订单接口并显示支付弹窗 */
|
||||
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)
|
||||
paymentFormHtml.value = res.data.payData
|
||||
showQrCode.value = true
|
||||
// 创建订单成功后立即开始轮询支付状态
|
||||
confirmPayment()
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('创建订单失败', e)
|
||||
} finally {
|
||||
creatingOrder.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 点击我已完成支付 — 进入确认支付结果步骤 */
|
||||
function handlePaymentDone() {
|
||||
showQrCode.value = false
|
||||
currentView.value = 'order2'
|
||||
// 模拟接口确认支付结果,成功后进入步骤三
|
||||
confirmPayment()
|
||||
}
|
||||
/** 轮询计时器 */
|
||||
let pollTimer: ReturnType<typeof setInterval> | null = null
|
||||
|
||||
/** 模拟确认支付结果接口 */
|
||||
/** 轮询查询订单支付状态 */
|
||||
async function confirmPayment() {
|
||||
// TODO: 替换为真实支付确认接口
|
||||
await new Promise(resolve => setTimeout(resolve, 2000))
|
||||
currentView.value = 'order3'
|
||||
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)
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
<template>
|
||||
<!-- 欢迎使用弹窗 — 首次登录无个人资料时弹出,引导上传简历 -->
|
||||
<Teleport to="body">
|
||||
<div v-if="dialogVisible" class="profile-welcome-overlay" :class="{ 'profile-welcome-overlay--behind': uploading }" @click.stop>
|
||||
<div class="profile-welcome-dialog" @click.stop>
|
||||
<!-- 标题 -->
|
||||
<h2 class="profile-welcome-dialog__title">欢迎使用Offer派</h2>
|
||||
|
||||
<!-- 简历上传区域 -->
|
||||
<div class="profile-welcome-dialog__form">
|
||||
<div class="profile-welcome-dialog__label">*简历</div>
|
||||
<div class="profile-welcome-dialog__upload-area" @click="handleUploadClick">
|
||||
<!-- 未上传状态 -->
|
||||
<template v-if="!uploadedFileName">
|
||||
<div class="profile-welcome-dialog__upload-icon">
|
||||
<svg viewBox="0 0 24 24" fill="none" width="28" height="28">
|
||||
<path d="M4 16v2a2 2 0 002 2h12a2 2 0 002-2v-2" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
||||
<path d="M12 14V4m0 0l-4 4m4-4l4 4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</div>
|
||||
<p class="profile-welcome-dialog__upload-tip">支持上传PDF、WORD格式,文件大小不超过10M</p>
|
||||
</template>
|
||||
<!-- 已上传状态 -->
|
||||
<template v-else>
|
||||
<div class="profile-welcome-dialog__upload-icon profile-welcome-dialog__upload-icon--done">✓</div>
|
||||
<p class="profile-welcome-dialog__upload-tip">{{ uploadedFileName }}</p>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- 隐私说明 -->
|
||||
<p class="profile-welcome-dialog__privacy">
|
||||
您的个人资料仅用于职位匹配、简历优化及网申投递。未经您的明确许可,我们绝不会将信息泄露给第三方或招聘人员。您可以随时更新或删除您的相关信息。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 开始匹配按钮 -->
|
||||
<button
|
||||
class="profile-welcome-dialog__start-btn"
|
||||
:disabled="!uploadedFileName"
|
||||
@click="handleStart"
|
||||
>
|
||||
开始匹配
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { uploadResume } from '@/utils/aiRequest'
|
||||
import { syncProfileFromResume } from '@/api/profile'
|
||||
import { ElLoading, ElMessage } from 'element-plus'
|
||||
import 'element-plus/es/components/loading/style/css'
|
||||
|
||||
/** 组件 Props */
|
||||
const props = defineProps<{ modelValue: boolean }>()
|
||||
|
||||
/** 组件 Emits */
|
||||
const emit = defineEmits<{ (e: 'update:modelValue', value: boolean): void }>()
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
|
||||
/** 已上传的文件名 */
|
||||
const uploadedFileName = ref('')
|
||||
|
||||
/** 内部控制弹窗显隐(用于上传时临时隐藏) */
|
||||
const dialogVisible = ref(false)
|
||||
|
||||
/** 上传中状态 — 降低弹窗层级 */
|
||||
const uploading = ref(false)
|
||||
|
||||
/** 上传后获得的简历 ID */
|
||||
const resumeId = ref('')
|
||||
|
||||
/** 弹窗打开时重置状态 */
|
||||
watch(() => props.modelValue, (val) => {
|
||||
if (val) {
|
||||
uploadedFileName.value = ''
|
||||
resumeId.value = ''
|
||||
dialogVisible.value = true
|
||||
} else {
|
||||
dialogVisible.value = false
|
||||
}
|
||||
document.body.style.overflow = val ? 'hidden' : ''
|
||||
})
|
||||
|
||||
/** 点击上传区域 — 弹出文件选择 */
|
||||
function handleUploadClick() {
|
||||
const input = document.createElement('input')
|
||||
input.type = 'file'
|
||||
input.accept = '.pdf,.doc,.docx,application/pdf,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document'
|
||||
|
||||
input.onchange = async () => {
|
||||
const file = input.files?.[0]
|
||||
if (!file) return
|
||||
|
||||
// 文件大小校验(10MB)
|
||||
if (file.size > 10 * 1024 * 1024) {
|
||||
ElMessage.error('文件大小不能超过10M')
|
||||
return
|
||||
}
|
||||
|
||||
// 标记上传中,降低弹窗层级让 loading 显示在上面
|
||||
uploading.value = true
|
||||
|
||||
// 全屏加载
|
||||
const loading = ElLoading.service({
|
||||
lock: true,
|
||||
text: '简历解析中,请耐心等待…',
|
||||
background: 'rgba(0, 0, 0, 0.5)',
|
||||
customClass: 'profile-welcome-loading',
|
||||
})
|
||||
|
||||
try {
|
||||
// 上传简历文件
|
||||
const res = await uploadResume(file)
|
||||
if (res.code === 0 && res.data?.resumeId) {
|
||||
resumeId.value = String(res.data.resumeId)
|
||||
uploadedFileName.value = file.name
|
||||
|
||||
// 继续调用同步个人资料接口,加载特效延续
|
||||
loading.setText('正在同步个人资料…')
|
||||
await syncProfileFromResume(resumeId.value)
|
||||
loading.close()
|
||||
uploading.value = false
|
||||
ElMessage.success('简历上传并同步成功')
|
||||
} else {
|
||||
loading.close()
|
||||
uploading.value = false
|
||||
ElMessage.error(res.msg || '上传失败')
|
||||
}
|
||||
} catch {
|
||||
loading.close()
|
||||
uploading.value = false
|
||||
ElMessage.error('上传失败,请稍后重试')
|
||||
}
|
||||
}
|
||||
|
||||
input.click()
|
||||
}
|
||||
|
||||
/** 点击开始匹配按钮 */
|
||||
function handleStart() {
|
||||
emit('update:modelValue', false)
|
||||
// 如果当前在 profile 页面就跳转 profile,其余跳转 jobs
|
||||
if (route.path === '/profile') {
|
||||
// 已经在 profile 页面,刷新页面数据
|
||||
router.go(0)
|
||||
} else {
|
||||
router.push('/jobs')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,114 @@
|
||||
<template>
|
||||
<!-- 导出简历格式选择弹窗 -->
|
||||
<el-dialog
|
||||
v-model="visible"
|
||||
title="导出简历"
|
||||
width="3.6rem"
|
||||
:close-on-click-modal="false"
|
||||
class="resume-export-dialog"
|
||||
>
|
||||
<!-- 格式选择 -->
|
||||
<el-radio-group v-model="exportFormat" class="resume-export-dialog__radio-group">
|
||||
<el-radio value="pdf">PDF 简历</el-radio>
|
||||
<el-radio value="word">Word 简历</el-radio>
|
||||
</el-radio-group>
|
||||
<template #footer>
|
||||
<el-button @click="visible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="exporting" @click="doExport">下载</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 隐藏的简历模板,用于导出时渲染DOM -->
|
||||
<div v-if="exportTemplateData" style="position:absolute;left:-9999px;top:0;">
|
||||
<JobResumeTemplate ref="exportTemplateRef" :resume-data="exportTemplateData" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, nextTick } from 'vue'
|
||||
import JobResumeTemplate from '@/components/JobResumeTemplate.vue'
|
||||
import type { ResumeTemplateData } from '@/components/JobResumeTemplate.vue'
|
||||
import { exportResumePdf, exportResumeWord, loadResumeTemplateData } from '@/utils/resumeExport'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
// ==================== Props & Emits ====================
|
||||
|
||||
const props = defineProps<{
|
||||
/** 控制弹窗显示/隐藏(v-model) */
|
||||
modelValue: boolean
|
||||
/** 要导出的简历 ID */
|
||||
resumeId: string
|
||||
/** 导出文件名(不含扩展名) */
|
||||
resumeName?: string
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:modelValue', value: boolean): void
|
||||
}>()
|
||||
|
||||
// ==================== 弹窗显隐双向绑定 ====================
|
||||
|
||||
/** 弹窗可见状态 */
|
||||
const visible = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (val: boolean) => emit('update:modelValue', val),
|
||||
})
|
||||
|
||||
// ==================== 导出相关状态 ====================
|
||||
|
||||
/** 导出格式:pdf 或 word */
|
||||
const exportFormat = ref<'pdf' | 'word'>('pdf')
|
||||
|
||||
/** 导出中状态 */
|
||||
const exporting = ref(false)
|
||||
|
||||
/** 导出用的简历模板数据 */
|
||||
const exportTemplateData = ref<ResumeTemplateData | null>(null)
|
||||
|
||||
/** 导出用的简历模板组件引用 */
|
||||
const exportTemplateRef = ref<InstanceType<typeof JobResumeTemplate> | null>(null)
|
||||
|
||||
// ==================== 导出逻辑 ====================
|
||||
|
||||
/** 执行导出下载 */
|
||||
async function doExport() {
|
||||
exporting.value = true
|
||||
try {
|
||||
// 1. 加载简历完整数据
|
||||
const data = await loadResumeTemplateData(props.resumeId)
|
||||
if (!data) {
|
||||
ElMessage.error('获取简历数据失败')
|
||||
return
|
||||
}
|
||||
|
||||
// 2. 设置模板数据,等待DOM渲染
|
||||
exportTemplateData.value = data
|
||||
await nextTick()
|
||||
|
||||
// 3. 获取渲染后的DOM
|
||||
const element = exportTemplateRef.value?.resumeRef
|
||||
if (!element) {
|
||||
ElMessage.error('简历模板渲染失败')
|
||||
return
|
||||
}
|
||||
|
||||
const fileName = props.resumeName || '简历'
|
||||
|
||||
if (exportFormat.value === 'pdf') {
|
||||
await exportResumePdf(element, fileName)
|
||||
} else {
|
||||
exportResumeWord(element, fileName)
|
||||
}
|
||||
|
||||
ElMessage.success('导出成功')
|
||||
visible.value = false
|
||||
} catch (err) {
|
||||
console.error('[导出简历] 失败', err)
|
||||
ElMessage.error('导出失败,请稍后重试')
|
||||
} finally {
|
||||
exporting.value = false
|
||||
// 清理隐藏模板数据
|
||||
exportTemplateData.value = null
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -98,8 +98,21 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 步骤2:安全验证 -->
|
||||
<!-- 步骤2:二次确认 -->
|
||||
<template v-if="currentStep === 2">
|
||||
<div class="delete-account-dialog__confirm-card">
|
||||
<div class="delete-account-dialog__confirm-icon">!</div>
|
||||
<h2 class="delete-account-dialog__confirm-title">确认注销账号?</h2>
|
||||
<p class="delete-account-dialog__confirm-desc">注销完成后不能撤回,账号权益将清零,已开通会员权益不退款。</p>
|
||||
<div class="delete-account-dialog__confirm-actions">
|
||||
<button class="delete-account-dialog__confirm-btn delete-account-dialog__confirm-btn--cancel" @click="currentStep = 1">再想想</button>
|
||||
<button class="delete-account-dialog__confirm-btn delete-account-dialog__confirm-btn--danger" @click="handleConfirmDelete">确认注销</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 步骤2(备用):安全验证 — 手机验证码,暂时注释 -->
|
||||
<!-- <template v-if="currentStep === 2">
|
||||
<h2 class="delete-account-dialog__title">安全验证</h2>
|
||||
<p class="delete-account-dialog__subtitle">为保护账号安全,请完成身份验证后继续注销流程。</p>
|
||||
<div class="delete-account-dialog__verify">
|
||||
@@ -116,15 +129,15 @@
|
||||
<p class="delete-account-dialog__verify-tip">完成验证后进入最终确认。</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</template> -->
|
||||
|
||||
<!-- 步骤3:完成 -->
|
||||
<template v-if="currentStep === 3">
|
||||
<div class="delete-account-dialog__done">
|
||||
<div class="delete-account-dialog__done-card">
|
||||
<div class="delete-account-dialog__done-icon">✓</div>
|
||||
<h2 class="delete-account-dialog__title">账号注销申请已提交</h2>
|
||||
<p class="delete-account-dialog__subtitle">你的账号将在 7 个工作日内完成注销处理。在此期间如需撤回,请联系客服。</p>
|
||||
<button class="delete-account-dialog__btn delete-account-dialog__btn--primary" @click="handleFinish">我知道了</button>
|
||||
<h2 class="delete-account-dialog__done-title">注销申请已提交</h2>
|
||||
<p class="delete-account-dialog__done-desc">我们将按注销流程处理你的账号。处理期间,请勿再次购买会员权益。</p>
|
||||
<button class="delete-account-dialog__done-btn" @click="handleFinish">我知道了</button>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
@@ -136,6 +149,7 @@
|
||||
import { ref, watch } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useStore } from 'vuex'
|
||||
import { cancelAccount } from '@/api/auth'
|
||||
|
||||
/** 组件 Props */
|
||||
const props = defineProps<{ modelValue: boolean }>()
|
||||
@@ -196,10 +210,19 @@ const sendCode = () => {
|
||||
ElMessage.success('验证码已发送')
|
||||
}
|
||||
|
||||
/** 确认注销 */
|
||||
const handleConfirmDelete = () => {
|
||||
// TODO: 调用注销接口
|
||||
currentStep.value = 3
|
||||
/** 确认注销 — 调用后端注销接口 */
|
||||
const handleConfirmDelete = async () => {
|
||||
try {
|
||||
await cancelAccount()
|
||||
// 注销成功,清空登录状态
|
||||
store.commit('SET_AUTHENTICATED', false)
|
||||
// 清空浏览器缓存数据(聊天记录、session 等)
|
||||
localStorage.clear()
|
||||
sessionStorage.clear()
|
||||
currentStep.value = 3
|
||||
} catch {
|
||||
ElMessage.error('注销请求失败,请稍后重试')
|
||||
}
|
||||
}
|
||||
|
||||
/** 完成 — 关闭弹窗并退出登录 */
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
<h2 class="settings-dialog__content-title">账号与安全</h2>
|
||||
<div class="settings-dialog__section">
|
||||
<div class="settings-dialog__section-label">手机号</div>
|
||||
<p class="settings-dialog__section-value">130****2222</p>
|
||||
<p class="settings-dialog__section-value">{{ userPhone }}</p>
|
||||
</div>
|
||||
<div class="settings-dialog__danger-section">
|
||||
<div class="settings-dialog__danger-title">注销我的账号</div>
|
||||
@@ -80,14 +80,16 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-dialog__member-issue">
|
||||
<div class="settings-dialog__member-issue-title">订阅状态异常?</div>
|
||||
<p class="settings-dialog__member-issue-desc">
|
||||
如果你已经和完成了付款或更改了订阅但是没有看到最新状态,你可以尝试更新状态或联系我们获取帮助。
|
||||
</p>
|
||||
<div class="settings-dialog__member-issue-actions">
|
||||
<button class="settings-dialog__member-issue-btn" @click="handleRefreshStatus">更新状态</button>
|
||||
<button class="settings-dialog__member-issue-btn" @click="handleContactUs">联系我们</button>
|
||||
</div>
|
||||
<!-- <div class="settings-dialog__member-issue-title">订阅状态异常?</div>-->
|
||||
<!-- <p class="settings-dialog__member-issue-desc">-->
|
||||
<!-- 如果你已经和完成了付款或更改了订阅但是没有看到最新状态,你可以尝试更新状态或联系我们获取帮助。-->
|
||||
<!-- </p>-->
|
||||
<!-- <div class="settings-dialog__member-issue-actions">-->
|
||||
<!-- <button class="settings-dialog__member-issue-btn" @click="handleRefreshStatus">更新状态</button>-->
|
||||
<!-- <button class="settings-dialog__member-issue-btn" @click="handleContactUs">联系我们</button>-->
|
||||
<!-- </div>-->
|
||||
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -127,34 +129,34 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-dialog__reminder-block">
|
||||
<div class="settings-dialog__reminder-block-title">即时岗位提醒</div>
|
||||
<div class="settings-dialog__reminder-row">
|
||||
<div class="settings-dialog__reminder-info">
|
||||
<div class="settings-dialog__reminder-label">开启即时岗位更新提醒</div>
|
||||
<div class="settings-dialog__reminder-desc">
|
||||
抢先申请 —— 在岗位发布后一小时内,即可收到为你量身定制的最新职位提醒
|
||||
</div>
|
||||
</div>
|
||||
<el-switch v-model="reminders.instant" active-color="#4FC2C9" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-dialog__reminder-block">
|
||||
<div class="settings-dialog__reminder-block-title">岗位更新提醒频率</div>
|
||||
<div class="settings-dialog__reminder-row">
|
||||
<div class="settings-dialog__reminder-info">
|
||||
<div class="settings-dialog__reminder-desc">
|
||||
会员用户每天可接收无限次岗位更新提醒,免费用户每天最多接收 1 次。
|
||||
</div>
|
||||
</div>
|
||||
<el-select v-model="reminders.frequency" style="width: 1.2rem;">
|
||||
<el-option label="1次/天" value="1" />
|
||||
<el-option label="2次/天" value="2" />
|
||||
<el-option label="5次/天" value="5" />
|
||||
<el-option label="无限次" value="unlimited" />
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div class="settings-dialog__reminder-block">-->
|
||||
<!-- <div class="settings-dialog__reminder-block-title">即时岗位提醒</div>-->
|
||||
<!-- <div class="settings-dialog__reminder-row">-->
|
||||
<!-- <div class="settings-dialog__reminder-info">-->
|
||||
<!-- <div class="settings-dialog__reminder-label">开启即时岗位更新提醒</div>-->
|
||||
<!-- <div class="settings-dialog__reminder-desc">-->
|
||||
<!-- 抢先申请 —— 在岗位发布后一小时内,即可收到为你量身定制的最新职位提醒-->
|
||||
<!-- </div>-->
|
||||
<!-- </div>-->
|
||||
<!-- <el-switch v-model="reminders.instant" active-color="#4FC2C9" />-->
|
||||
<!-- </div>-->
|
||||
<!-- </div>-->
|
||||
<!-- <div class="settings-dialog__reminder-block">-->
|
||||
<!-- <div class="settings-dialog__reminder-block-title">岗位更新提醒频率</div>-->
|
||||
<!-- <div class="settings-dialog__reminder-row">-->
|
||||
<!-- <div class="settings-dialog__reminder-info">-->
|
||||
<!-- <div class="settings-dialog__reminder-desc">-->
|
||||
<!-- 会员用户每天可接收无限次岗位更新提醒,免费用户每天最多接收 1 次。-->
|
||||
<!-- </div>-->
|
||||
<!-- </div>-->
|
||||
<!-- <el-select v-model="reminders.frequency" style="width: 1.2rem;">-->
|
||||
<!-- <el-option label="1次/天" value="1" />-->
|
||||
<!-- <el-option label="2次/天" value="2" />-->
|
||||
<!-- <el-option label="5次/天" value="5" />-->
|
||||
<!-- <el-option label="无限次" value="unlimited" />-->
|
||||
<!-- </el-select>-->
|
||||
<!-- </div>-->
|
||||
<!-- </div>-->
|
||||
</template>
|
||||
<!-- Tab: 用户隐私协议 — 长文本,可滚动查看 -->
|
||||
<template v-if="activeTab === 'privacy'">
|
||||
@@ -230,6 +232,9 @@
|
||||
|
||||
<!-- 注销账号弹窗 -->
|
||||
<SettingsDeleteAccountDialog v-model="showDeleteAccount" />
|
||||
|
||||
<!-- 邀请注册送会员弹窗 -->
|
||||
<SettingsInviteDialog v-model="showInviteDialog" />
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
@@ -243,9 +248,10 @@ import { resolveRegionName } from '@/utils/region'
|
||||
import { resolveIndustryName } from '@/utils/industry'
|
||||
import { resolveJobCategoryName } from '@/utils/jobCategory'
|
||||
import SettingsDeleteAccountDialog from './SettingsDeleteAccountDialog.vue'
|
||||
import SettingsInviteDialog from './SettingsInviteDialog.vue'
|
||||
|
||||
/** 组件 Props — 控制弹窗显示/隐藏 */
|
||||
const props = defineProps<{ modelValue: boolean }>()
|
||||
/** 组件 Props — 控制弹窗显示/隐藏,可指定初始 Tab */
|
||||
const props = defineProps<{ modelValue: boolean; initialTab?: string }>()
|
||||
|
||||
/** 组件 Emits — 通知父组件更新 modelValue */
|
||||
const emit = defineEmits<{ (e: 'update:modelValue', value: boolean): void }>()
|
||||
@@ -258,18 +264,24 @@ const store = useStore()
|
||||
const tabs = [
|
||||
{ key: 'account', label: '账号与安全', icon: '👤' },
|
||||
{ key: 'member', label: '会员', icon: '🏅' },
|
||||
{ key: 'reminder', label: '岗位更新提醒', icon: '🔔' },
|
||||
{ key: 'reminder', label: '目标岗位设置', icon: '🔔' },
|
||||
]
|
||||
|
||||
/** 当前选中的 Tab */
|
||||
const activeTab = ref('account')
|
||||
|
||||
/** 用户手机号 — 从全局 store 读取 */
|
||||
const userPhone = computed(() => store.state.userInfo?.mobileNumber || '')
|
||||
|
||||
/** 退出登录确认弹窗的显示状态 */
|
||||
const showLogout = ref(false)
|
||||
|
||||
/** 监听弹窗开关 — 打开时锁定背景页面滚动,关闭时恢复 */
|
||||
watch(() => props.modelValue, (val) => {
|
||||
document.body.style.overflow = val ? 'hidden' : ''
|
||||
if (val && props.initialTab) {
|
||||
activeTab.value = props.initialTab
|
||||
}
|
||||
})
|
||||
|
||||
/** 岗位更新提醒的配置项 */
|
||||
@@ -284,6 +296,9 @@ const showGoalDialog = ref(false)
|
||||
/** 注销账号弹窗显示状态 */
|
||||
const showDeleteAccount = ref(false)
|
||||
|
||||
/** 邀请注册弹窗显示状态 */
|
||||
const showInviteDialog = ref(false)
|
||||
|
||||
/** 岗位名称列表 */
|
||||
const intentionCategoryNames = computed(() => {
|
||||
const ids = store.state.jobIntention.categoryIds || []
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
<template>
|
||||
<!-- 邀请注册送会员弹窗 -->
|
||||
<Teleport to="body">
|
||||
<div v-if="modelValue" class="settings-invite-overlay" @click="$emit('update:modelValue', false)">
|
||||
<div class="settings-invite-dialog" @click.stop>
|
||||
<!-- 关闭按钮 -->
|
||||
<span class="settings-invite-dialog__close" @click="$emit('update:modelValue', false)">✕</span>
|
||||
|
||||
<!-- 标题 -->
|
||||
<h2 class="settings-invite-dialog__title">邀请好友免费获取7天会员权益</h2>
|
||||
|
||||
<!-- 邀请链接区域 -->
|
||||
<div class="settings-invite-dialog__link-box">
|
||||
<p class="settings-invite-dialog__link-text">来一起领取AI求职助手会员!{{ inviteText }}</p>
|
||||
</div>
|
||||
|
||||
<!-- 复制链接按钮 -->
|
||||
<button class="settings-invite-dialog__copy-btn" @click="handleCopy">复制链接</button>
|
||||
|
||||
<!-- 步骤说明 -->
|
||||
<p class="settings-invite-dialog__steps-tip">仅需 3 步可免费获取会员权益</p>
|
||||
|
||||
<div class="settings-invite-dialog__step">
|
||||
<div class="settings-invite-dialog__step-title">第1步:分享专属邀请链接</div>
|
||||
<p class="settings-invite-dialog__step-desc">点击"复制链接",并发送给好友</p>
|
||||
</div>
|
||||
<div class="settings-invite-dialog__step">
|
||||
<div class="settings-invite-dialog__step-title">第2步:好友完成新用户首次体验</div>
|
||||
<p class="settings-invite-dialog__step-desc">链接 24 小时内有效,好友在时效内体验AI求职助手</p>
|
||||
</div>
|
||||
<div class="settings-invite-dialog__step">
|
||||
<div class="settings-invite-dialog__step-title">第3步:分享人获得7天会员权益</div>
|
||||
<p class="settings-invite-dialog__step-desc">免费会员权益一年内有效,AI助力加速上岸</p>
|
||||
</div>
|
||||
|
||||
<!-- 统计数据 -->
|
||||
<div class="settings-invite-dialog__stats">
|
||||
<div class="settings-invite-dialog__stat-item">
|
||||
<div class="settings-invite-dialog__stat-label">累计获得会员天数</div>
|
||||
<div class="settings-invite-dialog__stat-value">{{ totalDays }}天</div>
|
||||
</div>
|
||||
<div class="settings-invite-dialog__stat-item">
|
||||
<div class="settings-invite-dialog__stat-label">累计邀请好友</div>
|
||||
<div class="settings-invite-dialog__stat-value">{{ totalFriends }}位</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 活动规则折叠 -->
|
||||
<div class="settings-invite-dialog__rules" @click="showRules = !showRules">
|
||||
<span class="settings-invite-dialog__rules-label">活动规则</span>
|
||||
<svg class="settings-invite-dialog__rules-arrow" :class="{ 'settings-invite-dialog__rules-arrow--open': showRules }" viewBox="0 0 12 12" fill="none">
|
||||
<path d="M3 4.5L6 7.5L9 4.5" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div v-if="showRules" class="settings-invite-dialog__rules-content">
|
||||
<p>1. 每成功邀请一位新用户注册并完成首次体验,邀请人可获得 7 天免费会员权益。</p>
|
||||
<p>2. 邀请链接有效期为 24 小时,过期需重新生成。</p>
|
||||
<p>3. 累计获得的会员天数一年内有效,超过一年未使用的天数将自动失效。</p>
|
||||
<p>4. 同一被邀请人仅计算一次,重复邀请不累计奖励。</p>
|
||||
<p>5. 本活动最终解释权归平台所有。</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { useStore } from 'vuex'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
/** 组件 Props */
|
||||
defineProps<{ modelValue: boolean }>()
|
||||
|
||||
/** 组件 Emits */
|
||||
defineEmits<{ (e: 'update:modelValue', value: boolean): void }>()
|
||||
|
||||
const store = useStore()
|
||||
|
||||
/** 活动规则展开状态 */
|
||||
const showRules = ref(false)
|
||||
|
||||
/** 累计获得会员天数(暂用模拟数据) */
|
||||
const totalDays = ref(1)
|
||||
|
||||
/** 累计邀请好友数(暂用模拟数据) */
|
||||
const totalFriends = ref(1)
|
||||
|
||||
/** 用户邀请码 — 从全局 store 读取 */
|
||||
const inviteCode = computed(() => store.state.userInfo?.inviteCode || '')
|
||||
|
||||
/** 邀请链接文案 */
|
||||
const inviteText = computed(() => {
|
||||
const code = inviteCode.value
|
||||
return `https://www.qiuzhizhushou.com/invite_code=${code},点击链接进入活动!`
|
||||
})
|
||||
|
||||
/** 复制链接到剪贴板 */
|
||||
async function handleCopy() {
|
||||
try {
|
||||
await navigator.clipboard.writeText(inviteText.value)
|
||||
ElMessage.success('链接已复制')
|
||||
} catch {
|
||||
ElMessage.error('复制失败,请手动复制')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -112,7 +112,9 @@
|
||||
</div>
|
||||
</Teleport>
|
||||
<!-- 设置弹窗 -->
|
||||
<SettingsDialog v-model="showSettingsDialog" />
|
||||
<SettingsDialog v-model="showSettingsDialog" :initial-tab="store.state.settingsTab" />
|
||||
<!-- 邀请注册送会员弹窗 -->
|
||||
<SettingsInviteDialog v-model="showShareDialog" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -132,6 +134,7 @@ import navAgentIcon from '@/assets/images/nav/nav-agent-icon.png'
|
||||
import navMessageIcon from '@/assets/images/nav/nav-message-icon.png'
|
||||
import navSettingIcon from '@/assets/images/nav/nav-setting-icon.png'
|
||||
import navFeedbackIcon from '@/assets/images/nav/nav-feedback-icon.png'
|
||||
import navShareIcon from '@/assets/images/nav/nav-share-icon.png'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
@@ -190,13 +193,18 @@ const mainMenus = computed<MenuItem[]>(() => {
|
||||
return [...staticMenus, ...dynamicMenuItems.value]
|
||||
})
|
||||
|
||||
const showShareDialog = ref(false)
|
||||
const showMessageDialog = ref(false)
|
||||
const showFeedbackDialog = ref(false)
|
||||
const showSettingsDialog = ref(false)
|
||||
const showSettingsDialog = computed({
|
||||
get: () => store.state.showSettings,
|
||||
set: (val: boolean) => store.commit('SET_SHOW_SETTINGS', val),
|
||||
})
|
||||
const feedbackType = ref('')
|
||||
const feedbackTypeIndex = ref(0)
|
||||
const feedbackDetail = ref('')
|
||||
import { ElMessage } from 'element-plus'
|
||||
import SettingsInviteDialog from "@/components/SettingsInviteDialog.vue";
|
||||
|
||||
// ==================== 站内信相关 ====================
|
||||
/** 未读消息数量 */
|
||||
@@ -374,6 +382,7 @@ const settingsMenu = computed(() => {
|
||||
})
|
||||
|
||||
const footerMenus = computed(() => [
|
||||
{ iconImg: navShareIcon, label: '分享送会员', action: () => { showShareDialog.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 } },
|
||||
@@ -387,6 +396,7 @@ async function handleSettingsNav() {
|
||||
const res = await checkLogin()
|
||||
if (res.code === '0' && res.data === true) {
|
||||
store.commit('SET_AUTHENTICATED', true)
|
||||
store.commit('SET_SETTINGS_TAB', 'account')
|
||||
showSettingsDialog.value = true
|
||||
} else {
|
||||
store.commit('SET_AUTHENTICATED', false)
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
</div>
|
||||
|
||||
<!-- 提示:双击选中一级(仅 allowParentSelect 开启时显示) -->
|
||||
<div v-if="searchText.length < 2 && allowParentSelect" class="industry-selector__hint">双击可选中一级行业分类</div>
|
||||
<div v-if="searchText.length < 2 && allowParentSelect" class="industry-selector__hint">(双击可选中一级行业分类)</div>
|
||||
|
||||
<!-- 分栏联动选择区(搜索关键词不足 2 字符时显示) -->
|
||||
<div v-if="searchText.length < 2" class="industry-selector__columns">
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
</div>
|
||||
|
||||
<!-- 提示:双击选中一二级(仅 allowParentSelect 开启时显示) -->
|
||||
<div v-if="searchText.length < 2 && allowParentSelect" class="job-category-selector__hint">双击可选中一级/二级岗位分类</div>
|
||||
<div v-if="searchText.length < 2 && allowParentSelect" class="job-category-selector__hint">(双击可选中一级/二级岗位分类)</div>
|
||||
|
||||
<!-- 三栏联动选择区(搜索关键词不足 2 字符时显示) -->
|
||||
<div v-if="searchText.length < 2" class="job-category-selector__columns">
|
||||
|
||||
Reference in New Issue
Block a user