全项目加载动效重做和简历经历区域的时间和编辑按钮同位置显示切换

This commit is contained in:
2026-06-30 17:09:09 +08:00
parent 0aebde7bd3
commit 8773c65b28
24 changed files with 610 additions and 489 deletions
+130
View File
@@ -0,0 +1,130 @@
<template>
<!-- 全屏加载遮罩组件 白色卡片居中展示加载状态 -->
<Teleport to="body">
<Transition name="fullscreen-loading-fade">
<div v-if="visible" class="fullscreen-loading">
<!-- 半透明遮罩层 -->
<div class="fullscreen-loading__overlay"></div>
<!-- 白色内容卡片 -->
<div class="fullscreen-loading__card">
<!-- 顶部标题栏 logo仅在传入 title 时显示 -->
<div v-if="title" class="fullscreen-loading__header">
<img class="fullscreen-loading__logo dib" src="@/assets/images/logo-offerpai.png" alt="logo" />
<span class="fullscreen-loading__title dib ml8 verali-t pt2">{{ title }}</span>
</div>
<!-- 中间文案 -->
<p class="fullscreen-loading__text">{{ text }}</p>
<!-- 加载圆环动画 -->
<div class="fullscreen-loading__spinner"></div>
</div>
</div>
</Transition>
</Teleport>
</template>
<script setup lang="ts">
/** 全屏加载遮罩组件 */
defineProps<{
/** 是否显示 */
visible: boolean
/** 中间提示文案 */
text: string
/** 顶部标题文字(不传则不显示标题行和 logo) */
title?: string
}>()
</script>
<style lang="scss" scoped>
@use '../assets/styles/variables' as *;
.fullscreen-loading {
font-size: 0.14rem;
position: fixed;
inset: 0;
z-index: 2100;
display: flex;
align-items: center;
justify-content: center;
/* 半透明遮罩 */
&__overlay {
position: absolute;
inset: 0;
background: $overlay-bg;
}
/* 白色居中卡片 */
&__card {
position: relative;
background: $bg-white;
border-radius: 0.12rem;
padding: 0.7rem 0 0.5rem 0;
width: 4rem;
display: flex;
flex-direction: column;
align-items: center;
min-width: 3.6rem;
}
/* 顶部标题行 */
&__header {
position: absolute;
top: 0.12rem;
left: 0.2rem;
//display: flex;
//align-items: center;
//gap: 0.08rem;
margin-bottom: 0.3rem;
}
/* logo 图标 */
&__logo {
width: 0.28rem;
height: 0.28rem;
border-radius: 50%;
}
/* 标题文字 */
&__title {
font-size: 0.14rem;
color: $text-dark;
}
/* 中间文案 */
&__text {
font-size: 0.2rem;
font-weight: 600;
color: $text-dark;
margin-bottom: 0.28rem;
}
/* 旋转圆环 */
&__spinner {
width: 0.44rem;
height: 0.44rem;
border: 3px solid transparent;
border-top-color: $accent;
border-right-color: $accent;
border-bottom-color: $accent;
border-radius: 50%;
animation: fullscreen-loading-spin 1s linear infinite;
}
}
/* 旋转动画 */
@keyframes fullscreen-loading-spin {
to {
transform: rotate(360deg);
}
}
/* 淡入淡出过渡 */
.fullscreen-loading-fade-enter-active,
.fullscreen-loading-fade-leave-active {
transition: opacity 0.25s ease;
}
.fullscreen-loading-fade-enter-from,
.fullscreen-loading-fade-leave-to {
opacity: 0;
}
</style>
+11 -5
View File
@@ -347,6 +347,8 @@
</div>
</div>
</div>
<!-- 全屏加载遮罩 -->
<FullscreenLoading :visible="fullLoading" :text="fullLoadingText" title="Nova 正在帮您努力处理~" />
</template>
<script setup lang="ts">
@@ -360,6 +362,7 @@ import type { ResumeListItem } from '@/api/resume'
import { fetchCustomizeResume, generateCustomizeResume, aiEditResume, rollbackCustomizeResume } from '@/api/jobs'
import type { CustomizeResumeData, AiEditChatMessage } from '@/api/jobs'
import AiThinkingIndicator from '@/components/tools/AiThinkingIndicator.vue'
import FullscreenLoading from '@/components/FullscreenLoading.vue'
// ==================== 类型定义 ====================
@@ -415,6 +418,11 @@ const emit = defineEmits<{
// ==================== 步骤控制 ====================
/** 全屏加载是否显示 */
const fullLoading = ref(false)
/** 全屏加载文案 */
const fullLoadingText = ref('')
/** 当前步骤:1-确认入口(居中弹窗) 2-差距分析(右侧抽屉) 3-定制简历 4-预览 */
const currentStep = ref(1)
@@ -483,10 +491,8 @@ async function handleDrawerNext() {
* 流程:先 GET 查询 → 有数据直接用 → 无数据则 POST 生成 → 再 GET 查询
*/
async function fetchAndLoadCustomResume() {
const loadingInstance = ElLoading.service({
text: '正在生成定制简历...',
background: 'rgba(0, 0, 0, 0.5)',
})
fullLoadingText.value = '正在生成定制简历...'
fullLoading.value = true
try {
// 第一步:查询是否已有定制简历
let queryRes = await fetchCustomizeResume(props.jobId)
@@ -522,7 +528,7 @@ async function fetchAndLoadCustomResume() {
console.error('[JobResumeCustomDialog] 定制简历流程失败', e)
ElMessage.error('定制简历失败,请稍后重试')
} finally {
loadingInstance.close()
fullLoading.value = false
}
}
+13 -8
View File
@@ -232,14 +232,17 @@
</div>
</div>
</div>
<!-- 全屏加载遮罩 -->
<FullscreenLoading :visible="fullLoading" :text="fullLoadingText" title="Nova 正在帮您努力处理~" />
</template>
<script setup lang="ts">
import { ref, computed, watch } from 'vue'
import { useStore } from 'vuex'
import { ElMessage, ElLoading } from 'element-plus'
import { ElMessage } from 'element-plus'
import RegionSelector from '@/components/tools/RegionSelector.vue'
import { polishDiagnosisIssue } from '@/api/resume'
import FullscreenLoading from '@/components/FullscreenLoading.vue'
/** 教育经历单条数据结构 */
interface EducationItem {
@@ -329,6 +332,12 @@ const emit = defineEmits<{
/** Vuex store 实例 */
const store = useStore()
// ==================== 全屏加载状态 ====================
/** 全屏加载是否显示 */
const fullLoading = ref(false)
/** 全屏加载文案 */
const fullLoadingText = ref('')
/** 模块名称映射表 */
const moduleTitleMap: Record<string, string> = {
info: '基本信息',
@@ -490,12 +499,8 @@ async function handleAiPolish() {
return
}
aiPolishing.value = true
const loading = ElLoading.service({
lock: true,
text: 'AI正在润色中……',
background: 'rgba(0, 0, 0, 0.5)',
customClass: 'resume-upload-loading',
})
fullLoadingText.value = 'AI正在润色中……'
fullLoading.value = true
try {
const res = await polishDiagnosisIssue([text])
if (res.code === 0 && res.data?.content) {
@@ -508,7 +513,7 @@ async function handleAiPolish() {
ElMessage.error('请求失败,请稍后重试')
} finally {
aiPolishing.value = false
loading.close()
fullLoading.value = false
}
}
+10 -9
View File
@@ -72,8 +72,9 @@
<div class="profile-page-content__item-left">
<div class="profile-page-content__item-title">{{ edu.school }}</div>
<div class="profile-page-content__item-sub">{{ getDegreeText(edu.studyType, edu.degree) }} · {{ edu.major }}</div>
<div class="profile-page-content__item-date">{{ edu.startDate }} - {{ edu.endDate }}</div>
</div>
<!-- 时间默认显示hover时隐藏 -->
<div class="profile-page-content__item-date profile-page-content__item-date--toggle">{{ edu.startDate }} - {{ edu.endDate }}</div>
<button class="profile-page-content__edit-btn-inline profile-page-content__item-edit-btn">
<el-icon><EditPen /></el-icon><span>编辑</span>
</button>
@@ -105,9 +106,9 @@
<div class="profile-page-content__item-left">
<div class="profile-page-content__item-title">{{ exp.companyName }}</div>
<div class="profile-page-content__item-sub">{{ exp.position }}</div>
<div class="profile-page-content__item-period">{{ exp.startDate }} - {{ exp.endDate || '至今' }}</div>
</div>
<!-- 时间默认显示hover时隐藏 -->
<div class="profile-page-content__item-period profile-page-content__item-date--toggle">{{ exp.startDate }} - {{ exp.endDate || '至今' }}</div>
<button class="profile-page-content__edit-btn-inline profile-page-content__item-edit-btn">
<el-icon><EditPen /></el-icon><span>编辑</span>
</button>
@@ -139,9 +140,9 @@
<div class="profile-page-content__item-left">
<div class="profile-page-content__item-title">{{ exp.companyName }}</div>
<div class="profile-page-content__item-sub">{{ exp.position }}</div>
<div class="profile-page-content__item-period">{{ exp.startDate }} - {{ exp.endDate }}</div>
</div>
<!-- 时间默认显示hover时隐藏 -->
<div class="profile-page-content__item-period profile-page-content__item-date--toggle">{{ exp.startDate }} - {{ exp.endDate }}</div>
<button class="profile-page-content__edit-btn-inline profile-page-content__item-edit-btn">
<el-icon><EditPen /></el-icon><span>编辑</span>
</button>
@@ -173,9 +174,9 @@
<div class="profile-page-content__item-left">
<div class="profile-page-content__item-title">{{ proj.projectName }}</div>
<div class="profile-page-content__item-sub" v-if="proj.role">{{ proj.role }}</div>
<div class="profile-page-content__item-period">{{ proj.startDate }} - {{ proj.endDate }}</div>
</div>
<!-- 时间默认显示hover时隐藏 -->
<div class="profile-page-content__item-period profile-page-content__item-date--toggle">{{ proj.startDate }} - {{ proj.endDate }}</div>
<button class="profile-page-content__edit-btn-inline profile-page-content__item-edit-btn">
<el-icon><EditPen /></el-icon><span>编辑</span>
</button>
@@ -206,9 +207,9 @@
<div class="profile-page-content__item-row">
<div class="profile-page-content__item-left">
<div class="profile-page-content__item-title">{{ comp.competitionName }} · {{ comp.award }}</div>
<div class="profile-page-content__item-period">{{ comp.awardDate }}</div>
</div>
<!-- 时间默认显示hover时隐藏 -->
<div class="profile-page-content__item-period profile-page-content__item-date--toggle">{{ comp.awardDate }}</div>
<button class="profile-page-content__edit-btn-inline profile-page-content__item-edit-btn">
<el-icon><EditPen /></el-icon><span>编辑</span>
</button>
+15 -12
View File
@@ -53,6 +53,8 @@
</div>
</div>
</Teleport>
<!-- 全屏加载遮罩 -->
<FullscreenLoading :visible="fullLoading" :text="fullLoadingText" title="Nova 正在帮您努力处理~" />
</template>
<script setup lang="ts">
@@ -60,8 +62,8 @@ 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'
import { ElMessage } from 'element-plus'
import FullscreenLoading from '@/components/FullscreenLoading.vue'
/** 组件 Props */
const props = defineProps<{ modelValue: boolean }>()
@@ -87,6 +89,11 @@ const uploading = ref(false)
/** 上传后获得的简历 ID */
const resumeId = ref('')
/** 全屏加载是否显示 */
const fullLoading = ref(false)
/** 全屏加载文案 */
const fullLoadingText = ref('')
/** 弹窗打开时重置状态 */
watch(() => props.modelValue, (val) => {
if (val) {
@@ -124,12 +131,8 @@ async function processFile(file: File) {
uploading.value = true
// 全屏加载
const loading = ElLoading.service({
lock: true,
text: '简历解析中,请耐心等待…',
background: 'rgba(0, 0, 0, 0.5)',
customClass: 'profile-welcome-loading',
})
fullLoadingText.value = '简历解析中,请耐心等待…'
fullLoading.value = true
try {
// 上传简历文件
@@ -139,18 +142,18 @@ async function processFile(file: File) {
uploadedFileName.value = file.name
// 继续调用同步个人资料接口,加载特效延续
loading.setText('正在同步个人资料…')
fullLoadingText.value = '正在同步个人资料…'
await syncProfileFromResume(resumeId.value)
loading.close()
fullLoading.value = false
uploading.value = false
ElMessage.success('简历上传并同步成功')
} else {
loading.close()
fullLoading.value = false
uploading.value = false
ElMessage.error(res.msg || '上传失败')
}
} catch {
loading.close()
fullLoading.value = false
uploading.value = false
ElMessage.error('上传失败,请稍后重试')
}
+12 -3
View File
@@ -103,12 +103,15 @@
</div>
</div>
</div>
<!-- 全屏加载遮罩 -->
<FullscreenLoading :visible="fullLoading" :text="fullLoadingText" title="Nova 正在帮您努力处理~" />
</template>
<script setup lang="ts">
import { ref, watch } from 'vue'
import { ElMessage, ElLoading } from 'element-plus'
import { ElMessage } from 'element-plus'
import { polishDiagnosisIssue } from '@/api/resume'
import FullscreenLoading from '@/components/FullscreenLoading.vue'
/** 描述段落结构 */
interface DescriptionParagraph {
@@ -142,6 +145,11 @@ const aiPolishResult = ref('')
/** AI润色中 */
const aiPolishing = ref(false)
/** 全屏加载是否显示 */
const fullLoading = ref(false)
/** 全屏加载文案 */
const fullLoadingText = ref('')
/** 生成短ID */
const generateId = (): string => Math.random().toString(36).substring(2, 8)
@@ -177,7 +185,8 @@ async function handleAiPolish() {
return
}
aiPolishing.value = true
const loading = ElLoading.service({ lock: true, text: 'AI正在润色中……', background: 'rgba(0,0,0,0.5)' })
fullLoadingText.value = 'AI正在润色中……'
fullLoading.value = true
try {
const content = text.split('\n').filter(s => s.trim())
const res = await polishDiagnosisIssue(content)
@@ -191,7 +200,7 @@ async function handleAiPolish() {
ElMessage.error('请求失败')
} finally {
aiPolishing.value = false
loading.close()
fullLoading.value = false
}
}
+11 -7
View File
@@ -73,12 +73,15 @@
</div>
</Transition>
</Teleport>
<!-- 全屏加载遮罩 -->
<FullscreenLoading :visible="fullLoading" :text="fullLoadingText" title="Nova 正在帮您努力处理~" />
</template>
<script setup lang="ts">
import { ref, watch, nextTick } from 'vue'
import type { DiagnosisIssue, DescriptionParagraph } from '@/api/resume'
import { polishDiagnosisIssue, resolveDiagnosisIssue } from '@/api/resume'
import FullscreenLoading from '@/components/FullscreenLoading.vue'
// ==================== Props ====================
@@ -169,6 +172,11 @@ const moduleOrder = ['summary', 'education', 'work', 'internship', 'project', 'c
// ==================== 响应式数据 ====================
/** 全屏加载是否显示 */
const fullLoading = ref(false)
/** 全屏加载文案 */
const fullLoadingText = ref('')
/** 所有模块分组后的数据列表 */
const moduleDataList = ref<ModuleGroup[]>([])
@@ -313,12 +321,8 @@ async function handleRegenerate(record: RecordState) {
return
}
const loading = ElLoading.service({
lock: true,
text: 'AI 重新生成中…',
background: 'rgba(0, 0, 0, 0.5)',
customClass: 'resume-upload-loading',
})
fullLoadingText.value = 'AI 重新生成中…'
fullLoading.value = true
try {
const res = await polishDiagnosisIssue(content)
@@ -332,7 +336,7 @@ async function handleRegenerate(record: RecordState) {
} catch {
ElMessage.error('请求失败,请稍后重试')
} finally {
loading.close()
fullLoading.value = false
}
}
+15 -14
View File
@@ -138,6 +138,8 @@
</div>
</Transition>
</Teleport>
<!-- 全屏加载遮罩 -->
<FullscreenLoading :visible="fullLoading" :text="fullLoadingText" title="Nova 正在帮您努力处理~" />
</template>
<script setup lang="ts">
@@ -145,6 +147,7 @@ import { computed, ref, watch, nextTick } from 'vue'
import type { DiagnosisIssue, DescriptionParagraph } from '@/api/resume'
import { polishDiagnosisIssue, feedbackDiagnosisIssue, resolveDiagnosisIssue } from '@/api/resume'
import { computeDiff, type DiffSegment } from '@/utils/textDiff'
import FullscreenLoading from '@/components/FullscreenLoading.vue'
// ==================== Props ====================
@@ -182,6 +185,12 @@ const moduleTitleMap: Record<string, string> = {
/** 抽屉标题 */
const moduleTitle = computed(() => moduleTitleMap[props.moduleType] || '问题修复')
// ==================== 全屏加载状态 ====================
/** 全屏加载是否显示 */
const fullLoading = ref(false)
/** 全屏加载文案 */
const fullLoadingText = ref('')
// ==================== 原文处理 ====================
/** 将原文内容统一转为字符串(description 数组的 text 逐行拼接) */
@@ -282,12 +291,8 @@ async function handleAiPolish() {
}
// 全屏加载提示
const loading = ElLoading.service({
lock: true,
text: 'AI 润色中,请耐心等待…',
background: 'rgba(0, 0, 0, 0.5)',
customClass: 'resume-upload-loading',
})
fullLoadingText.value = 'AI 润色中,请耐心等待…'
fullLoading.value = true
try {
const res = await polishDiagnosisIssue(content)
@@ -300,7 +305,7 @@ async function handleAiPolish() {
} catch {
ElMessage.error('润色请求失败,请稍后重试')
} finally {
loading.close()
fullLoading.value = false
}
}
@@ -373,12 +378,8 @@ async function handleSubmit() {
}
// 全屏加载提示
const loading = ElLoading.service({
lock: true,
text: '正在提交新版本…',
background: 'rgba(0, 0, 0, 0.5)',
customClass: 'resume-upload-loading',
})
fullLoadingText.value = '正在提交新版本…'
fullLoading.value = true
try {
// 1. 调用 resolve 接口标记问题已处理
@@ -394,7 +395,7 @@ async function handleSubmit() {
} catch {
ElMessage.error('提交失败,请稍后重试')
} finally {
loading.close()
fullLoading.value = false
}
}
+61 -60
View File
@@ -3,105 +3,106 @@
<Teleport to="body">
<div v-if="modelValue" class="settings-invite-overlay" @click="$emit('update:modelValue', false)">
<div class="settings-invite-dialog" @click.stop>
<!-- 顶部渐变区域 -->
<div class="settings-invite-dialog__header">
<!-- 皇冠图标 -->
<span class="settings-invite-dialog__crown"></span>
</div>
<!-- 关闭按钮 -->
<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__body">
<!-- 标题 -->
<h2 class="settings-invite-dialog__title">邀请好友免费获取 7 天会员</h2>
<!-- 副标题 -->
<p class="settings-invite-dialog__subtitle">好友通过你的链接注册并完成认证 3 天会员</p>
<!-- 邀请链接区域 -->
<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 class="settings-invite-dialog__link-box">
<span class="settings-invite-dialog__link-text">{{ inviteUrl }}</span>
<button class="settings-invite-dialog__copy-btn" @click="handleCopy">复制链接</button>
</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 class="settings-invite-dialog__step">
<div class="settings-invite-dialog__step-circle">1</div>
<span class="settings-invite-dialog__step-text">分享专属邀请链接给好友</span>
</div>
<div class="settings-invite-dialog__step">
<div class="settings-invite-dialog__step-circle">2</div>
<span class="settings-invite-dialog__step-text">好友完成注册并通过认证</span>
</div>
<div class="settings-invite-dialog__step">
<div class="settings-invite-dialog__step-circle">3</div>
<span class="settings-invite-dialog__step-text">获得 7 天会员奖励</span>
</div>
<!-- 底部统计 + 按钮 -->
<div class="settings-invite-dialog__footer">
<div class="settings-invite-dialog__stat">
<span class="settings-invite-dialog__stat-label">已邀请</span>
<span class="settings-invite-dialog__stat-value">{{ totalFriends }}</span>
</div>
<div class="settings-invite-dialog__stat">
<span class="settings-invite-dialog__stat-label">累计获得</span>
<span class="settings-invite-dialog__stat-value">{{ totalDays }}</span>
</div>
<button class="settings-invite-dialog__member-btn" @click="handleGoMember">成为正式会员</button>
</div>
</div>
</div>
</div>
</Teleport>
<!-- 会员购买弹窗 -->
<MemberDialog v-model="showMemberDialog" />
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import { useStore } from 'vuex'
import { ElMessage } from 'element-plus'
import MemberDialog from './MemberDialog.vue'
/** 组件 Props */
defineProps<{ modelValue: boolean }>()
/** 组件 Emits */
defineEmits<{ (e: 'update:modelValue', value: boolean): void }>()
defineEmits<{
(e: 'update:modelValue', value: boolean): void
}>()
const store = useStore()
/** 活动规则展开状态 */
const showRules = ref(false)
/** 会员弹窗显示状态 */
const showMemberDialog = ref(false)
/** 累计获得会员天数(暂用模拟数据) */
const totalDays = ref(1)
const totalDays = ref(7)
/** 累计邀请好友数(暂用模拟数据) */
const totalFriends = ref(1)
/** 用户邀请码 — 从全局 store 读取 */
const inviteCode = computed(() => store.state.userInfo?.inviteCode || '')
const inviteCode = computed(() => store.state.userInfo?.inviteCode || 'NOVA2026')
/** 邀请链接文案 */
const inviteText = computed(() => {
const code = inviteCode.value
return `https://www.offerpai.com.cn?invite_code=${code}`
})
/** 邀请链接地址 */
const inviteUrl = computed(() => `https://offerpai.com/invite?code=${inviteCode.value}`)
/** 复制链接到剪贴板 */
async function handleCopy() {
try {
await navigator.clipboard.writeText(inviteText.value)
await navigator.clipboard.writeText(inviteUrl.value)
ElMessage.success('链接已复制')
} catch {
ElMessage.error('复制失败,请手动复制')
}
}
/** 打开会员购买弹窗 */
function handleGoMember() {
showMemberDialog.value = true
}
</script>