feat: 适配新版后端 API 并接入邮箱验证 #9

Merged
Mplan merged 6 commits from feat/api into main 2026-08-22 21:39:42 +08:00
4 changed files with 57 additions and 25 deletions
Showing only changes of commit 56662e3399 - Show all commits
+1
View File
@@ -156,6 +156,7 @@ export function toCloudType(row: BackendCloudType): CloudType {
id: row.id,
name: row.name,
genus: row.genus || '',
icon_id: row.icon_id,
// Compatibility value for the excluded legacy admin view. Migrated pages
// display rarity_value directly because API.md defines no semantic mapping.
rarity: 'common',
+1
View File
@@ -2,6 +2,7 @@ export interface CloudType {
id: number
name: string
genus: string
icon_id: string | null
rarity: 'common' | 'uncommon' | 'rare'
rarity_value: number
description: string | null
+10 -6
View File
@@ -4,7 +4,7 @@ import { NAlert, NButton, NEmpty, NSkeleton } from 'naive-ui'
import { RouterLink, useRoute } from 'vue-router'
import ImageDetailModal from '@/components/cloud/ImageDetailModal.vue'
import MiniLocationMap from '@/components/cloud/MiniLocationMap.vue'
import { apiRequest, toApiCloud } from '@/lib/api'
import { apiRequest, imageUrl, toApiCloud } from '@/lib/api'
import { useEncyclopediaStore } from '@/stores/encyclopedia'
import type { ApiCloud, BackendCloudInfo } from '@/types/api'
@@ -20,7 +20,7 @@ const hasNextPage = ref(false)
const cloudTypeId = computed(() => Number(route.params.id))
const cloudType = computed(() => encyclopediaStore.cloudTypes.find(item => item.id === cloudTypeId.value) ?? null)
const heroImage = computed(() => gallery.value[0]?.image_url || null)
const heroImage = computed(() => cloudType.value?.icon_id ? imageUrl(cloudType.value.icon_id, 'original') : null)
function formatDateTime(iso: string | null) {
if (!iso) return '未记录拍摄时间'
@@ -86,11 +86,15 @@ watch(() => route.params.id, () => loadPage())
<div class="grid lg:grid-cols-[1.05fr_0.95fr]">
<div class="relative min-h-80 bg-sky-50">
<img v-if="heroImage" :src="heroImage" :alt="cloudType.name" class="absolute inset-0 h-full w-full object-cover" />
<div class="absolute inset-0 bg-gradient-to-t from-slate-950/75 via-slate-950/10 to-transparent"></div>
<div class="absolute bottom-7 left-7 right-7 text-white">
<span class="border border-white/35 bg-slate-950/25 px-2.5 py-1 text-xs backdrop-blur">稀有度 {{ cloudType.rarity_value }}</span>
<div v-else class="absolute inset-0 flex items-center justify-center pb-20 text-sm font-medium text-slate-500">无图片</div>
<div v-if="heroImage" class="absolute inset-0 bg-gradient-to-t from-slate-950/75 via-slate-950/10 to-transparent"></div>
<div class="absolute bottom-7 left-7 right-7" :class="heroImage ? 'text-white' : 'text-slate-900'">
<span
class="border px-2.5 py-1 text-xs"
:class="heroImage ? 'border-white/35 bg-slate-950/25 backdrop-blur' : 'border-slate-200 bg-white'"
>稀有度 {{ cloudType.rarity_value }}</span>
<h1 class="mt-4 text-4xl font-bold">{{ cloudType.name }}</h1>
<p class="mt-2 text-white/80">{{ cloudType.genus || '未提供云属名称' }}</p>
<p class="mt-2" :class="heroImage ? 'text-white/80' : 'text-slate-600'">{{ cloudType.genus || '未提供云属名称' }}</p>
</div>
</div>
<div class="p-8">
+45 -19
View File
@@ -2,11 +2,16 @@
import { onMounted } from 'vue'
import { NEmpty, NSkeleton } from 'naive-ui'
import { useRouter } from 'vue-router'
import { imageUrl } from '@/lib/api'
import { useEncyclopediaStore } from '@/stores/encyclopedia'
const router = useRouter()
const encyclopediaStore = useEncyclopediaStore()
function typeImageUrl(iconId: string | null) {
return iconId ? imageUrl(iconId, 'preview') : ''
}
onMounted(() => encyclopediaStore.fetchCloudTypes())
</script>
@@ -24,10 +29,8 @@ onMounted(() => encyclopediaStore.fetchCloudTypes())
<div class="oc-container oc-container--content oc-page-content">
<div v-if="encyclopediaStore.loadingCloudTypes" class="grid gap-5 md:grid-cols-2 lg:grid-cols-3">
<div v-for="n in 6" :key="n" class="oc-panel-card border border-slate-200 bg-white p-5">
<NSkeleton class="h-6 w-2/5" />
<NSkeleton class="mt-3 h-4 w-1/3" />
<NSkeleton class="mt-5 h-4 w-full" :repeat="3" />
<div v-for="n in 6" :key="n" class="oc-panel-card overflow-hidden border border-slate-200 bg-white">
<NSkeleton class="h-80 w-full" />
</div>
</div>
@@ -36,25 +39,48 @@ onMounted(() => encyclopediaStore.fetchCloudTypes())
v-for="cloudType in encyclopediaStore.cloudTypes"
:key="cloudType.id"
type="button"
class="oc-interactive-card oc-panel-card flex h-full flex-col border border-slate-200 bg-white p-5 text-left"
class="oc-interactive-card oc-panel-card relative isolate min-h-80 overflow-hidden border border-slate-200 bg-sky-50 text-left"
@click="router.push(`/encyclopedia/${cloudType.id}`)"
>
<div class="flex items-start justify-between gap-4">
<div>
<p class="oc-section-label">Cloud Genus</p>
<h2 class="mt-2 text-xl font-bold text-slate-900">{{ cloudType.name }}</h2>
<p class="mt-1 font-mono text-xs uppercase tracking-[0.12em] text-slate-500">
{{ cloudType.genus || '未提供云属名称' }}
</p>
<img
v-if="cloudType.icon_id"
:src="typeImageUrl(cloudType.icon_id)"
:alt="`${cloudType.name}介绍图`"
class="absolute inset-0 -z-20 h-full w-full object-cover"
/>
<div
v-if="cloudType.icon_id"
class="absolute inset-0 -z-10 bg-gradient-to-t from-slate-950/90 via-slate-950/55 to-slate-950/25"
></div>
<div v-else class="absolute inset-0 -z-10 flex items-center justify-center text-sm font-medium text-slate-500">无图片</div>
<div class="flex min-h-80 flex-col p-5">
<div class="flex items-start justify-between gap-4">
<div>
<p class="oc-section-label" :class="cloudType.icon_id ? 'text-white/70' : 'text-slate-500'">Cloud Genus</p>
<h2 class="mt-2 text-xl font-bold" :class="cloudType.icon_id ? 'text-white' : 'text-slate-900'">{{ cloudType.name }}</h2>
<p
class="mt-1 font-mono text-xs uppercase tracking-[0.12em]"
:class="cloudType.icon_id ? 'text-white/75' : 'text-slate-500'"
>
{{ cloudType.genus || '未提供云属名称' }}
</p>
</div>
<span
class="shrink-0 border px-2.5 py-1 text-xs font-medium"
:class="cloudType.icon_id ? 'border-white/35 bg-slate-950/25 text-white backdrop-blur' : 'border-slate-200 bg-white text-slate-600'"
>
稀有度 {{ cloudType.rarity_value }}
</span>
</div>
<span class="shrink-0 border border-slate-200 bg-slate-50 px-2.5 py-1 text-xs font-medium text-slate-600">
稀有度 {{ cloudType.rarity_value }}
</span>
<p class="mt-auto line-clamp-3 pt-16 text-sm leading-6" :class="cloudType.icon_id ? 'text-white/85' : 'text-slate-600'">
{{ cloudType.description || '暂未提供识别说明。' }}
</p>
<p
class="mt-4 border-t pt-4 text-xs font-semibold"
:class="cloudType.icon_id ? 'border-white/25 text-white' : 'border-slate-200 text-sky-700'"
>查看类型资料与公开云图 →</p>
</div>
<p class="mt-5 line-clamp-3 text-sm leading-6 text-slate-600">
{{ cloudType.description || '暂未提供识别说明。' }}
</p>
<p class="mt-auto border-t border-slate-200 pt-4 text-xs font-semibold text-sky-700">查看类型资料与公开云图 →</p>
</button>
</div>