Files
wenzi/frontend/components/MosquitoShareButton.vue
Your Name 91a0b77f7a test(cache): 修复CacheConfigTest边界值测试
- 修改 shouldVerifyCacheManager_withMaximumIntegerTtl 为 shouldVerifyCacheManager_withMaximumAllowedTtl
- 使用正确的最大TTL值(10080分钟,7天)而不是 Integer.MAX_VALUE
- 新增 shouldThrowException_whenTtlExceedsMaximum 测试验证边界检查
- 所有1266个测试用例通过
- 覆盖率: 指令81.89%, 行88.48%, 分支51.55%

docs: 添加项目状态报告
- 生成 PROJECT_STATUS_REPORT.md 详细记录项目当前状态
- 包含质量指标、已完成功能、待办事项和技术债务
2026-03-02 13:31:54 +08:00

171 lines
5.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>
<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>