Files
wenzi/frontend/h5/src/views/ShareView.vue

228 lines
8.0 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>
<section class="mx-auto max-w-md px-4 pb-28 pt-6 space-y-6">
<!-- Header -->
<header class="flex items-center gap-3">
<div class="w-12 h-12 rounded-2xl bg-gradient-to-br from-mosquito-primary to-mosquito-primary-light flex items-center justify-center text-white shadow-lg">
<Icons name="share" class="w-6 h-6" />
</div>
<div>
<h1 class="mos-title text-2xl font-bold">分享推广</h1>
<p class="text-sm text-mosquito-muted">生成专属链接邀请好友参与</p>
</div>
</header>
<!-- Auth Warning -->
<div v-if="!hasAuth" class="mos-card border-2 border-dashed border-mosquito-warning/30 bg-mosquito-warning/5 p-5">
<div class="flex items-start gap-3">
<div class="flex h-10 w-10 shrink-0 items-center justify-center rounded-xl bg-mosquito-warning/20 text-mosquito-warning">
<Icons name="zap" class="w-5 h-5" />
</div>
<div>
<div class="font-semibold text-mosquito-ink">请先配置鉴权信息</div>
<div class="text-sm text-mosquito-muted mt-1">需要 API Key 与用户令牌才可生成链接与海报</div>
</div>
</div>
</div>
<!-- Share Link Card -->
<div class="mos-card-gradient p-6 space-y-4">
<div class="flex items-center gap-2 text-white/90">
<Icons name="rocket" class="w-4 h-4" />
<span class="text-xs font-bold uppercase tracking-wider">默认模板</span>
<span class="text-xs opacity-75">· {{ activityLabel }}</span>
</div>
<div class="flex flex-wrap items-center gap-3">
<template v-if="hasAuth">
<MosquitoShareButton :activity-id="activityId" :user-id="userId" @copied="handleCopied" @error="handleCopyError" />
<button class="mos-btn mos-btn-accent !py-2 !px-4" @click="handleCopyLink">
<Icons name="copy" class="w-4 h-4" />
{{ copyButtonText }}
</button>
</template>
<template v-else>
<button class="mos-btn mos-btn-accent !py-2 !px-4 opacity-50 cursor-not-allowed" disabled>
<Icons name="copy" class="w-4 h-4" />
复制链接
</button>
</template>
</div>
<p class="text-xs text-white/70 flex items-center gap-1">
<Icons name="check-circle" class="w-3 h-3" />
分享按钮会自动复制链接方便一键转发
</p>
</div>
<!-- Poster Card -->
<div class="mos-card p-5">
<div class="flex items-center justify-between mb-4">
<div class="flex items-center gap-2">
<div class="w-8 h-8 rounded-lg bg-gradient-to-br from-mosquito-accent to-mosquito-accent-light flex items-center justify-center">
<Icons name="gift" class="w-4 h-4 text-mosquito-ink" />
</div>
<h2 class="mos-title text-base font-bold">分享海报</h2>
</div>
<span class="mos-pill mos-pill-secondary">点击可重试</span>
</div>
<div class="flex justify-center bg-mosquito-bg rounded-2xl p-4">
<MosquitoPosterCard
v-if="hasAuth"
:activity-id="activityId"
:user-id="userId"
template="default"
width="280px"
height="380px"
/>
<div v-else class="flex h-[380px] w-[280px] items-center justify-center rounded-xl border border-dashed border-mosquito-muted/40 text-sm text-mosquito-muted">
配置鉴权后可预览海报
</div>
</div>
</div>
<!-- Guide Card -->
<div class="mos-card space-y-4 p-5">
<div class="flex items-center gap-2">
<div class="w-8 h-8 rounded-lg bg-mosquito-secondary/10 flex items-center justify-center text-mosquito-secondary-dark">
<Icons name="target" class="w-4 h-4" />
</div>
<h3 class="mos-title text-base font-bold">分享指引</h3>
</div>
<ul class="space-y-3">
<li v-for="(step, index) in guideSteps" :key="index" class="flex gap-3 items-start">
<span class="flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-mosquito-primary text-white text-xs font-bold">
{{ index + 1 }}
</span>
<span class="text-sm text-mosquito-ink-light">{{ step }}</span>
</li>
</ul>
</div>
<!-- Error Message -->
<div v-if="loadError" class="mos-card border-2 border-mosquito-error/30 bg-mosquito-error/5 p-4">
<div class="flex items-center gap-2 text-mosquito-error">
<ZapIcon class="w-4 h-4" />
<span class="text-sm font-medium">{{ loadError }}</span>
</div>
</div>
</section>
</template>
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue'
import { useRoute } from 'vue-router'
import MosquitoShareButton from '../../../components/MosquitoShareButton.vue'
import MosquitoPosterCard from '../../../components/MosquitoPosterCard.vue'
import { MosquitoError, useMosquito } from '../../../index'
import { getUserIdFromToken, parseUserId } from '../../../shared/auth'
import Icons from '../components/Icons.vue'
type ActivitySummary = {
id: number
name: string
}
const route = useRoute()
const { getActivities, getShareUrl } = useMosquito()
const apiKey = import.meta.env.VITE_MOSQUITO_API_KEY
const userToken = import.meta.env.VITE_MOSQUITO_USER_TOKEN
const routeUserId = computed(() => parseUserId(route.query.userId ?? route.params.userId))
const userId = computed(() => getUserIdFromToken(userToken) ?? routeUserId.value ?? 0)
const activityId = ref(1)
const activityLabel = computed(() => `活动 #${activityId.value}`)
const loadError = ref('')
const hasAuth = computed(() => Boolean(apiKey && userToken && userId.value))
const copyButtonText = ref('复制链接')
const copyFeedback = ref<'success' | 'error' | null>(null)
const guideSteps = [
'点击"分享给好友"生成专属链接',
'发送给好友,完成注册后即可计入转化',
'回到首页查看最新排行和奖励进度'
]
// 复制链接处理
const handleCopyLink = async () => {
try {
const shareResponse = await getShareUrl(activityId.value, userId.value, 'default')
let urlToCopy: string
if (shareResponse && typeof shareResponse === 'object') {
if (shareResponse.originalUrl) {
urlToCopy = shareResponse.originalUrl
} else if (shareResponse.path) {
urlToCopy = shareResponse.path.startsWith('http')
? shareResponse.path
: `${window.location.origin}${shareResponse.path}`
} else {
throw new Error('分享链接响应格式异常')
}
} else {
throw new Error('分享链接响应格式异常')
}
// 复制到剪贴板
try {
await navigator.clipboard.writeText(urlToCopy)
showCopyFeedback('success')
} catch {
// 回退到传统方法
const textArea = document.createElement('textarea')
textArea.value = urlToCopy
document.body.appendChild(textArea)
textArea.select()
document.execCommand('copy')
document.body.removeChild(textArea)
showCopyFeedback('success')
}
} catch (error) {
console.error('复制链接失败:', error)
showCopyFeedback('error')
}
}
// 显示复制反馈
const showCopyFeedback = (type: 'success' | 'error') => {
copyFeedback.value = type
copyButtonText.value = type === 'success' ? '已复制!' : '复制失败'
setTimeout(() => {
copyButtonText.value = '复制链接'
copyFeedback.value = null
}, 2000)
}
// MosquitoShareButton 回调处理
const handleCopied = () => {
showCopyFeedback('success')
}
const handleCopyError = () => {
showCopyFeedback('error')
}
const loadActivity = async () => {
if (!hasAuth.value) {
return
}
loadError.value = ''
try {
const list: ActivitySummary[] = await getActivities()
if (list.length) {
activityId.value = list[0].id
}
} catch (error) {
if (error instanceof MosquitoError && error.statusCode === 401) {
loadError.value = '鉴权失败:无法加载活动信息'
return
}
loadError.value = '活动信息加载失败'
}
}
onMounted(() => {
loadActivity()
})
</script>