feat: adopt naive ui and refine shell interactions

This commit is contained in:
2026-05-21 20:53:24 +08:00
parent 6d8acce295
commit 78b1c952e7
13 changed files with 1046 additions and 572 deletions
+34 -21
View File
@@ -1,5 +1,6 @@
<script setup lang="ts">
import { computed, onMounted, ref, watch } from 'vue'
import { NAlert, NButton, NCard, NEmpty, NSkeleton, NTag } from 'naive-ui'
import { RouterLink, useRoute } from 'vue-router'
import { supabase } from '@/lib/supabase'
import { useAuthStore } from '@/stores/auth'
@@ -136,24 +137,33 @@ watch(() => route.params.id, loadPage)
<template>
<div class="max-w-6xl mx-auto px-4 py-8">
<div class="mb-6">
<RouterLink to="/encyclopedia" class="text-sm font-medium text-sky-700 hover:text-sky-800">
← 返回图鉴总览
<RouterLink to="/encyclopedia">
<NButton text type="primary">← 返回图鉴总览</NButton>
</RouterLink>
</div>
<div v-if="loading" class="space-y-6">
<div class="h-80 animate-pulse rounded-[32px] bg-slate-200"></div>
<NSkeleton class="h-80 w-full" />
<div class="grid gap-4 lg:grid-cols-3">
<div v-for="n in 3" :key="n" class="h-28 animate-pulse rounded-3xl bg-slate-200"></div>
<NCard v-for="n in 3" :key="n">
<NSkeleton class="h-4 w-1/3" />
<NSkeleton class="mt-4 h-7 w-1/2" />
</NCard>
</div>
</div>
<div v-else-if="loadError" class="rounded-[28px] border border-red-200 bg-red-50 p-6 text-red-700">
<NAlert
v-else-if="loadError"
type="error"
:show-icon="false"
:bordered="false"
title="详情加载失败"
>
{{ loadError }}
</div>
</NAlert>
<template v-else-if="cloudType">
<section class="overflow-hidden rounded-[32px] border border-slate-200 bg-white shadow-sm">
<section class="overflow-hidden border border-slate-200 bg-white shadow-sm">
<div class="grid lg:grid-cols-[1.15fr_0.85fr]">
<div class="relative min-h-[360px] overflow-hidden">
<img
@@ -169,9 +179,9 @@ watch(() => route.params.id, loadPage)
></div>
<div class="absolute inset-0 bg-gradient-to-t from-slate-950/78 via-slate-950/18 to-transparent"></div>
<div class="absolute bottom-8 left-8 right-8 text-white">
<span class="inline-flex rounded-full border px-3 py-1 text-xs font-medium backdrop-blur" :class="rarityMeta[cloudType.rarity].chip">
<NTag :bordered="false" class="border border-white/20 bg-white/12 text-white backdrop-blur">
{{ rarityMeta[cloudType.rarity].label }}
</span>
</NTag>
<h1 class="mt-4 text-4xl font-bold">{{ cloudType.name }}</h1>
<p class="mt-2 text-lg text-white/82">{{ cloudType.name_en }}</p>
</div>
@@ -185,7 +195,7 @@ watch(() => route.params.id, loadPage)
</p>
</div>
<div class="mt-8 rounded-[28px] border border-slate-200 bg-white p-5">
<NCard class="mt-8 border border-slate-200">
<p class="text-sm text-slate-500">你的收集状态</p>
<template v-if="isUnlocked && collectionEntry">
<p class="mt-2 text-2xl font-bold text-slate-900">已解锁</p>
@@ -195,26 +205,26 @@ watch(() => route.params.id, loadPage)
<p class="mt-2 text-2xl font-bold text-slate-900">尚未解锁</p>
<p class="mt-2 text-sm text-slate-600">拍到并上传这种云朵后,这枚徽章就会被点亮。</p>
</template>
</div>
</NCard>
</div>
</div>
</section>
<section class="mt-6 grid gap-4 md:grid-cols-3">
<div class="rounded-[28px] border border-slate-200 bg-white p-5 shadow-sm">
<NCard class="border border-slate-200 shadow-sm">
<p class="text-sm text-slate-500">稀有度</p>
<p class="mt-2 text-2xl font-bold text-slate-900">{{ rarityMeta[cloudType.rarity].label }}</p>
</div>
<div class="rounded-[28px] border border-slate-200 bg-white p-5 shadow-sm">
</NCard>
<NCard class="border border-slate-200 shadow-sm">
<p class="text-sm text-slate-500">公开云图</p>
<p class="mt-2 text-2xl font-bold text-slate-900">{{ publicCount }}</p>
</div>
<div class="rounded-[28px] border border-slate-200 bg-white p-5 shadow-sm">
</NCard>
<NCard class="border border-slate-200 shadow-sm">
<p class="text-sm text-slate-500">首次解锁</p>
<p class="mt-2 text-xl font-bold text-slate-900">
{{ collectionEntry ? formatDate(collectionEntry.unlocked_at) : '等待记录' }}
</p>
</div>
</NCard>
</section>
<section class="mt-8">
@@ -229,7 +239,7 @@ watch(() => route.params.id, loadPage)
<article
v-for="item in gallery"
:key="item.id"
class="overflow-hidden rounded-[28px] border border-slate-200 bg-white shadow-sm"
class="overflow-hidden border border-slate-200 bg-white shadow-sm"
>
<img :src="item.thumbnail_url || item.image_url" :alt="cloudType.name" class="h-56 w-full object-cover" />
<div class="p-5">
@@ -245,9 +255,12 @@ watch(() => route.params.id, loadPage)
</article>
</div>
<div v-else class="rounded-[28px] border border-dashed border-slate-300 bg-white p-8 text-center">
<p class="text-lg font-semibold text-slate-900">还没有公开作品</p>
<p class="mt-2 text-sm text-slate-500">等第一位观测者上传这类云图后,这里就会展示出来。</p>
<div v-else class="border border-dashed border-slate-300 bg-white p-8">
<NEmpty description="还没有公开作品">
<template #extra>
<p class="text-sm text-slate-500">等第一位观测者上传这类云图后,这里就会展示出来。</p>
</template>
</NEmpty>
</div>
</section>
</template>
+37 -17
View File
@@ -1,5 +1,6 @@
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue'
import { NAlert, NCard, NEmpty, NProgress, NSkeleton } from 'naive-ui'
import { useRouter } from 'vue-router'
import { useAuthStore } from '@/stores/auth'
import { useEncyclopediaStore } from '@/stores/encyclopedia'
@@ -74,23 +75,26 @@ onMounted(async () => {
</p>
</div>
<div class="rounded-[28px] border border-white/80 bg-white/80 p-6 shadow-sm backdrop-blur">
<NCard class="border border-white/80 bg-white/85 shadow-sm backdrop-blur" :bordered="false">
<div class="flex items-center justify-between">
<div>
<p class="text-sm text-slate-500">当前进度</p>
<p class="mt-1 text-3xl font-bold text-slate-900">{{ progressText }}</p>
</div>
<div class="flex h-16 w-16 items-center justify-center rounded-full bg-slate-900 text-xl font-semibold text-white">
<div class="flex h-16 w-16 items-center justify-center border border-slate-900 bg-slate-900 text-xl font-semibold text-white">
{{ authStore.isLoggedIn ? encyclopediaStore.unlockPercent : 0 }}%
</div>
</div>
<div class="mt-5 h-3 overflow-hidden rounded-full bg-slate-200">
<div
class="h-full rounded-full bg-[linear-gradient(90deg,#0ea5e9_0%,#f59e0b_100%)] transition-all duration-500"
:style="{ width: `${authStore.isLoggedIn ? encyclopediaStore.unlockPercent : 0}%` }"
></div>
</div>
<NProgress
class="mt-5"
type="line"
:show-indicator="false"
:percentage="authStore.isLoggedIn ? encyclopediaStore.unlockPercent : 0"
color="linear-gradient(90deg,#0ea5e9 0%,#f59e0b 100%)"
rail-color="#dbe4ee"
:height="12"
/>
<p class="mt-4 text-sm text-slate-500">
<template v-if="authStore.isLoggedIn">
@@ -100,27 +104,39 @@ onMounted(async () => {
登录后可同步你的个人图鉴进度。
</template>
</p>
</div>
</NCard>
</div>
</div>
</div>
<div class="max-w-6xl mx-auto px-4 py-8">
<div v-if="encyclopediaStore.collectionError && authStore.isLoggedIn" class="mb-6 rounded-2xl border border-amber-200 bg-amber-50 px-4 py-3 text-sm text-amber-700">
<NAlert
v-if="encyclopediaStore.collectionError && authStore.isLoggedIn"
class="mb-6"
type="warning"
:show-icon="false"
:bordered="false"
title="图鉴收藏数据暂时不可用"
>
图鉴收藏数据暂时不可用:{{ encyclopediaStore.collectionError }}
</div>
</NAlert>
<div v-if="encyclopediaStore.loadingCloudTypes" class="grid gap-5 md:grid-cols-2 xl:grid-cols-3">
<div v-for="n in 6" :key="n" class="h-72 animate-pulse rounded-[28px] bg-slate-200"></div>
<NCard v-for="n in 6" :key="n">
<NSkeleton class="h-44 w-full" />
<NSkeleton class="mt-5 h-5 w-2/5" />
<NSkeleton class="mt-3 h-4 w-1/3" />
<NSkeleton class="mt-6 h-4 w-full" :repeat="2" />
</NCard>
</div>
<div v-else class="grid gap-5 md:grid-cols-2 xl:grid-cols-3">
<div v-else-if="encyclopediaStore.cloudTypes.length" class="grid gap-5 md:grid-cols-2 xl:grid-cols-3">
<button
v-for="cloudType in encyclopediaStore.cloudTypes"
:key="cloudType.id"
type="button"
@click="openCard(cloudType)"
class="group overflow-hidden rounded-[28px] border bg-white text-left shadow-sm transition-all hover:-translate-y-1 hover:shadow-lg"
class="group overflow-hidden border bg-white text-left shadow-sm transition-all hover:-translate-y-1 hover:shadow-lg"
:class="isUnlocked(cloudType.id) ? 'border-amber-300 shadow-amber-100/60' : 'border-slate-200 hover:border-slate-300'"
>
<div
@@ -145,11 +161,11 @@ onMounted(async () => {
<div class="absolute inset-0 bg-gradient-to-t from-slate-950/70 via-slate-950/10 to-transparent"></div>
<div v-if="!isUnlocked(cloudType.id)" class="absolute inset-0 flex items-center justify-center bg-slate-950/22 backdrop-blur-[2px]">
<div class="rounded-full border border-white/45 bg-white/15 px-4 py-2 text-sm font-medium text-white">🔒 尚未解锁</div>
<div class="border border-white/45 bg-white/15 px-4 py-2 text-sm font-medium text-white">🔒 尚未解锁</div>
</div>
<div class="absolute left-4 top-4">
<span class="inline-flex rounded-full border px-3 py-1 text-xs font-medium" :class="rarityMeta[cloudType.rarity].chip">
<span class="inline-flex border px-3 py-1 text-xs font-medium" :class="rarityMeta[cloudType.rarity].chip">
{{ rarityMeta[cloudType.rarity].label }}
</span>
</div>
@@ -178,11 +194,15 @@ onMounted(async () => {
</div>
</button>
</div>
<div v-else class="border border-dashed border-slate-300 bg-white px-6 py-12">
<NEmpty description="暂时还没有图鉴数据" />
</div>
</div>
<div
v-if="lockHint"
class="fixed bottom-6 left-1/2 z-50 -translate-x-1/2 rounded-full bg-slate-900 px-4 py-2 text-sm text-white shadow-lg"
class="fixed bottom-6 left-1/2 z-50 -translate-x-1/2 border border-slate-900 bg-slate-900 px-4 py-2 text-sm text-white shadow-lg"
>
{{ lockHint }}
</div>