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 详细记录项目当前状态 - 包含质量指标、已完成功能、待办事项和技术债务
This commit is contained in:
203
frontend/components/MosquitoPosterCard.vue
Normal file
203
frontend/components/MosquitoPosterCard.vue
Normal file
@@ -0,0 +1,203 @@
|
||||
<template>
|
||||
<div class="mosquito-poster-card" :style="{ width, height }">
|
||||
<div
|
||||
v-if="loading"
|
||||
class="loading-placeholder"
|
||||
:style="{ width, height }"
|
||||
>
|
||||
<div class="loading-skeleton"></div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-else-if="error"
|
||||
class="error-placeholder"
|
||||
:style="{ width, height }"
|
||||
@click="retryLoad"
|
||||
>
|
||||
<div class="error-content">
|
||||
<svg class="w-8 h-8 text-red-400 mb-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||
</svg>
|
||||
<p class="text-sm text-gray-600">加载失败</p>
|
||||
<p class="text-xs text-gray-500 mt-1">{{ error.message }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<img
|
||||
v-else
|
||||
:src="posterUrl"
|
||||
alt="分享海报"
|
||||
:style="{ width, height }"
|
||||
class="poster-image"
|
||||
@load="onImageLoad"
|
||||
@error="onImageError"
|
||||
@click="$emit('click')"
|
||||
/>
|
||||
|
||||
<!-- 加载指示器 -->
|
||||
<div v-if="loading" class="loading-indicator">
|
||||
<svg class="animate-spin h-6 w-6 text-white" 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>
|
||||
|
||||
<!-- 重试按钮 -->
|
||||
<button
|
||||
v-if="showRetry"
|
||||
class="retry-button"
|
||||
@click="retryLoad"
|
||||
>
|
||||
重试
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { useMosquito } from '../index'
|
||||
|
||||
interface Props {
|
||||
activityId: number
|
||||
userId: number
|
||||
template?: string
|
||||
width?: string
|
||||
height?: string
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
template: 'default',
|
||||
width: '300px',
|
||||
height: '400px'
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
click: []
|
||||
error: [error: Error]
|
||||
loaded: []
|
||||
}>()
|
||||
|
||||
const { getPosterImage, config } = useMosquito()
|
||||
const loading = ref(false)
|
||||
const error = ref<Error | null>(null)
|
||||
const posterUrl = ref('')
|
||||
const retryCount = ref(0)
|
||||
const showRetry = ref(false)
|
||||
|
||||
// 生成海报URL
|
||||
const generatePosterUrl = () => {
|
||||
const timestamp = Date.now()
|
||||
return `${config.baseUrl}/api/v1/me/poster/image?activityId=${props.activityId}&userId=${props.userId}&template=${props.template}&t=${timestamp}`
|
||||
}
|
||||
|
||||
// 加载海报
|
||||
const loadPoster = async () => {
|
||||
if (loading.value) return
|
||||
|
||||
loading.value = true
|
||||
error.value = null
|
||||
showRetry.value = false
|
||||
|
||||
try {
|
||||
// 尝试使用API获取海报
|
||||
const imageBlob = await getPosterImage(props.activityId, props.userId, props.template)
|
||||
|
||||
// 创建本地URL
|
||||
const url = URL.createObjectURL(imageBlob)
|
||||
posterUrl.value = url
|
||||
retryCount.value = 0
|
||||
emit('loaded')
|
||||
} catch (err) {
|
||||
console.error('加载海报失败:', err)
|
||||
error.value = err as Error
|
||||
emit('error', error.value)
|
||||
|
||||
// 如果API失败,使用备用URL
|
||||
if (retryCount.value < 3) {
|
||||
retryCount.value++
|
||||
setTimeout(() => {
|
||||
posterUrl.value = generatePosterUrl()
|
||||
}, 1000 * retryCount.value)
|
||||
} else {
|
||||
showRetry.value = true
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 图片加载成功
|
||||
const onImageLoad = () => {
|
||||
showRetry.value = false
|
||||
retryCount.value = 0
|
||||
}
|
||||
|
||||
// 图片加载失败
|
||||
const onImageError = () => {
|
||||
if (!error.value) {
|
||||
error.value = new Error('海报图片加载失败')
|
||||
emit('error', error.value)
|
||||
}
|
||||
}
|
||||
|
||||
// 重试加载
|
||||
const retryLoad = () => {
|
||||
posterUrl.value = generatePosterUrl()
|
||||
loadPoster()
|
||||
}
|
||||
|
||||
// 组件挂载时加载海报
|
||||
loadPoster()
|
||||
|
||||
// 监听参数变化重新加载
|
||||
watch(() => [props.activityId, props.userId, props.template], () => {
|
||||
loadPoster()
|
||||
}, { deep: true })
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.mosquito-poster-card {
|
||||
@apply relative overflow-hidden rounded-lg shadow-md cursor-pointer transition-shadow hover:shadow-lg;
|
||||
background-color: var(--mosquito-bg);
|
||||
}
|
||||
|
||||
.loading-placeholder {
|
||||
@apply flex items-center justify-center;
|
||||
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
|
||||
background-size: 200% 100%;
|
||||
animation: loading 1.5s infinite;
|
||||
}
|
||||
|
||||
.loading-skeleton {
|
||||
@apply w-16 h-16 bg-gray-300 rounded;
|
||||
}
|
||||
|
||||
.error-placeholder {
|
||||
@apply flex items-center justify-center bg-mosquito-bg;
|
||||
}
|
||||
|
||||
.error-content {
|
||||
@apply text-center;
|
||||
}
|
||||
|
||||
.poster-image {
|
||||
@apply object-cover;
|
||||
}
|
||||
|
||||
.loading-indicator {
|
||||
@apply absolute inset-0 flex items-center justify-center bg-black bg-opacity-50;
|
||||
}
|
||||
|
||||
.retry-button {
|
||||
@apply absolute bottom-2 left-1/2 transform -translate-x-1/2 px-4 py-2 bg-black bg-opacity-70 text-white text-sm rounded-md hover:bg-opacity-80 transition-opacity;
|
||||
}
|
||||
|
||||
@keyframes loading {
|
||||
0% {
|
||||
background-position: 200% 0;
|
||||
}
|
||||
100% {
|
||||
background-position: -200% 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user