feat: add cloud encyclopedia system

This commit is contained in:
2026-05-21 17:15:52 +08:00
parent 2b7f393d7c
commit 76eb4dcfe1
6 changed files with 943 additions and 19 deletions
+108 -7
View File
@@ -2,15 +2,19 @@
import { ref, computed, onMounted, onUnmounted, nextTick, watch } from 'vue'
import { useRouter } from 'vue-router'
import { useCloudsStore } from '@/stores/clouds'
import { useAuthStore } from '@/stores/auth'
import { downloadCloudBadgeCard } from '@/lib/cloudBadges'
import { useUpload } from '@/composables/useUpload'
const router = useRouter()
const authStore = useAuthStore()
const cloudsStore = useCloudsStore()
const { items, uploading, overallProgress, currentItemIndex, totalItems, addFiles, removeItem, clearAll, validateAll, uploadAll } = useUpload()
const activeId = ref<string | null>(null)
const dragOver = ref(false)
const successMsg = ref(false)
const unlockedBadges = ref<Awaited<ReturnType<typeof uploadAll>>['unlockedBadges']>([])
const errorMsg = ref('')
const fileInput = ref<HTMLInputElement | null>(null)
@@ -68,15 +72,45 @@ async function handleSubmit() {
return
}
const ok = await uploadAll()
if (ok) {
const result = await uploadAll()
if (result.ok) {
unlockedBadges.value = result.unlockedBadges
successMsg.value = true
setTimeout(() => router.push('/profile'), 2000)
} else {
errorMsg.value = '上传失败,请稍后重试'
}
}
function resetAfterSuccess() {
successMsg.value = false
unlockedBadges.value = []
errorMsg.value = ''
}
async function saveBadge(badge: NonNullable<typeof unlockedBadges.value[number]>) {
await downloadCloudBadgeCard({
cloudName: badge.cloudName,
cloudNameEn: badge.cloudNameEn,
unlockedAt: badge.unlockedAt,
username: authStore.profile?.username || authStore.user?.email || 'OpenCloud 用户',
rarity: badge.rarity,
})
}
function rarityLabel(rarity: NonNullable<typeof unlockedBadges.value[number]>['rarity']) {
if (rarity === 'common') return '常见'
if (rarity === 'uncommon') return '少见'
return '罕见'
}
function formatUnlockedAt(iso: string) {
return new Date(iso).toLocaleDateString('zh-CN', {
year: 'numeric',
month: 'long',
day: 'numeric',
})
}
function onLatInput(e: Event) {
const val = parseFloat((e.target as HTMLInputElement).value)
if (activeItem.value) {
@@ -133,10 +167,77 @@ onUnmounted(() => { for (const item of items.value) URL.revokeObjectURL(item.pre
<input ref="fileInput" type="file" accept="image/*" multiple class="hidden" @change="handleFileSelect" />
<div v-if="successMsg" class="flex items-center justify-center min-h-[60vh]">
<div class="text-center">
<span class="text-5xl block mb-4"></span>
<h2 class="text-2xl font-bold text-gray-900 mb-2">上传成功</h2>
<p class="text-gray-500">正在跳转到个人主页...</p>
<div class="w-full max-w-5xl">
<div class="text-center">
<span class="text-5xl block mb-4"></span>
<h2 class="text-3xl font-bold text-gray-900 mb-2">上传成功</h2>
<p class="text-gray-500">
<template v-if="unlockedBadges.length">
新点亮了 {{ unlockedBadges.length }} 枚图鉴徽章
</template>
<template v-else>
这批云图已经进入你的收藏记录
</template>
</p>
</div>
<div v-if="unlockedBadges.length" class="mt-8 grid gap-5 md:grid-cols-2 xl:grid-cols-3">
<article
v-for="badge in unlockedBadges"
:key="badge.cloudTypeId"
class="rounded-[28px] border border-amber-200 bg-[linear-gradient(180deg,#fffbeb_0%,#ffffff_100%)] p-6 shadow-sm"
>
<div class="flex h-16 w-16 items-center justify-center rounded-2xl bg-amber-400 text-3xl text-white shadow-sm">
{{ badge.cloudName.slice(0, 1) }}
</div>
<div class="mt-5">
<div class="flex items-center gap-3">
<h3 class="text-2xl font-bold text-gray-900">{{ badge.cloudName }}</h3>
<span class="rounded-full border border-amber-200 bg-white px-3 py-1 text-xs font-medium text-amber-700">
{{ rarityLabel(badge.rarity) }}
</span>
</div>
<p class="mt-1 text-sm text-gray-500">{{ badge.cloudNameEn }}</p>
<p class="mt-4 text-sm text-gray-600">解锁时间{{ formatUnlockedAt(badge.unlockedAt) }}</p>
</div>
<div class="mt-6 flex gap-3">
<button
@click="saveBadge(badge)"
class="flex-1 rounded-xl bg-slate-900 px-4 py-2.5 text-sm font-medium text-white hover:bg-slate-800"
>
保存分享卡片
</button>
<button
@click="router.push(`/encyclopedia/${badge.cloudTypeId}`)"
class="flex-1 rounded-xl border border-gray-300 px-4 py-2.5 text-sm font-medium text-gray-700 hover:bg-gray-50"
>
查看详情
</button>
</div>
</article>
</div>
<div class="mt-8 flex flex-wrap items-center justify-center gap-3">
<button
@click="resetAfterSuccess"
class="rounded-xl border border-gray-300 px-5 py-2.5 text-sm font-medium text-gray-700 hover:bg-gray-50"
>
继续上传
</button>
<button
@click="router.push('/encyclopedia')"
class="rounded-xl bg-sky-500 px-5 py-2.5 text-sm font-medium text-white hover:bg-sky-600"
>
前往图鉴
</button>
<button
@click="router.push('/profile')"
class="rounded-xl border border-gray-300 px-5 py-2.5 text-sm font-medium text-gray-700 hover:bg-gray-50"
>
返回个人主页
</button>
</div>
</div>
</div>