Files
wenzi/frontend/components/MosquitoShareButton.vue

171 lines
5.0 KiB
Vue
Raw Normal View History

<template>
<div class="mosquito-share-button">
<button
:class="buttonClasses"
:disabled="loading || disabled"
@click="handleClick"
>
<div v-if="loading" class="loading-spinner">
<svg class="animate-spin h-4 w-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
</div>
<span v-else>{{ text }}</span>
</button>
<!-- Toast 通知 -->
<div v-if="showToast" :class="toastClasses">
<div class="flex items-center">
<svg class="w-5 h-5 mr-2" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"></path>
</svg>
{{ toastMessage }}
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, watch } from 'vue'
import { useMosquito } from '../index'
interface Props {
activityId: number
userId: number
template?: string
text?: string
disabled?: boolean
variant?: 'default' | 'primary' | 'secondary' | 'success' | 'danger'
size?: 'sm' | 'md' | 'lg'
}
const props = withDefaults(defineProps<Props>(), {
template: 'default',
text: '分享给好友',
disabled: false,
variant: 'primary',
size: 'md'
})
const emit = defineEmits<{
copied: []
error: [error: Error]
}>()
const { getShareUrl } = useMosquito()
const loading = ref(false)
const showToast = ref(false)
const toastMessage = ref('')
const toastType = ref<'success' | 'error'>('success')
const toastTimeout = ref<number>()
// 计算样式类
const buttonClasses = computed(() => {
const base = 'inline-flex items-center justify-center rounded-md font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2'
const sizeClasses = {
sm: 'px-3 py-1.5 text-sm',
md: 'px-4 py-2 text-sm',
lg: 'px-6 py-3 text-base'
}
const variantClasses = {
default: 'bg-white text-mosquito-ink border border-mosquito-line hover:border-mosquito-accent focus:ring-mosquito-accent',
primary: 'bg-mosquito-accent text-white hover:bg-mosquito-accent/90 focus:ring-mosquito-accent shadow-soft',
secondary: 'bg-mosquito-bg text-mosquito-ink border border-mosquito-line hover:border-mosquito-accent focus:ring-mosquito-accent',
success: 'bg-emerald-500 text-white hover:bg-emerald-600 focus:ring-emerald-500',
danger: 'bg-rose-500 text-white hover:bg-rose-600 focus:ring-rose-500'
}
return [
base,
sizeClasses[props.size],
variantClasses[props.variant],
{
'opacity-50 cursor-not-allowed': props.disabled || loading.value
}
]
})
const toastClasses = computed(() => {
const tone = toastType.value === 'error' ? 'bg-rose-500' : 'bg-mosquito-accent'
return [
'fixed top-4 right-4 z-50 max-w-sm p-4 text-white rounded-lg shadow-lg transition-all duration-300 transform',
`${tone} transform translate-x-0 opacity-100`
]
})
// 处理点击事件
const handleClick = async () => {
if (loading.value || props.disabled) return
try {
loading.value = true
const shareUrl = await getShareUrl(props.activityId, props.userId, props.template)
// 复制到剪贴板
try {
await navigator.clipboard.writeText(shareUrl)
showCopiedToast()
emit('copied')
} catch (clipboardError) {
// 如果剪贴板API不可用回退到传统方法
const textArea = document.createElement('textarea')
textArea.value = shareUrl
document.body.appendChild(textArea)
textArea.select()
document.execCommand('copy')
document.body.removeChild(textArea)
showCopiedToast()
emit('copied')
}
} catch (error) {
console.error('获取分享链接失败:', error)
emit('error', error as Error)
showToastMessage('获取分享链接失败,请稍后重试', 'error')
} finally {
loading.value = false
}
}
// 显示复制成功提示
const showCopiedToast = () => {
showToastMessage('分享链接已复制到剪贴板', 'success')
}
// 显示消息提示
const showToastMessage = (message: string, type: 'success' | 'error' = 'success') => {
toastMessage.value = message
toastType.value = type
showToast.value = true
// 清除之前的定时器
if (toastTimeout.value) {
clearTimeout(toastTimeout.value)
}
// 设置新的定时器
toastTimeout.value = window.setTimeout(() => {
showToast.value = false
}, 3000)
}
// 组件卸载时清理定时器
watch(() => showToast.value, (newVal) => {
if (!newVal && toastTimeout.value) {
clearTimeout(toastTimeout.value)
}
})
</script>
<style scoped>
.loading-spinner {
@apply flex items-center justify-center;
}
.mosquito-share-button {
@apply inline-block;
}
</style>