feat: adopt naive ui and refine shell interactions
This commit is contained in:
+108
-103
@@ -1,5 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
|
||||
import { NAlert, NButton, NEmpty, NSkeleton, NTag } from 'naive-ui'
|
||||
import ImageDetailModal from '@/components/cloud/ImageDetailModal.vue'
|
||||
import { supabase } from '@/lib/supabase'
|
||||
import { useCloudsStore } from '@/stores/clouds'
|
||||
@@ -197,129 +198,133 @@ onUnmounted(() => {
|
||||
<p class="text-sm font-medium uppercase tracking-[0.24em] text-sky-700">Community Gallery</p>
|
||||
<h1 class="mt-3 text-4xl font-bold text-slate-900">云图画廊</h1>
|
||||
<p class="mt-4 max-w-2xl text-sm leading-7 text-slate-600">
|
||||
按上传时间倒序浏览社区云图。卡片采用 Instagram 风格的整齐宫格排布,悬停即可快速查看基本信息,点开可看大图和详细记录。
|
||||
按上传时间倒序浏览社区云图。瀑布流会尽量保留图片原始比例,悬停即可快速查看基本信息,点开可看大图和详细记录。
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="max-w-7xl mx-auto px-4 py-8">
|
||||
|
||||
<section>
|
||||
<div class="flex gap-3 overflow-x-auto pb-2">
|
||||
<button
|
||||
<section>
|
||||
<div class="flex gap-3 overflow-x-auto pb-2">
|
||||
<NButton
|
||||
v-for="tab in filterTabs"
|
||||
:key="tab.id"
|
||||
type="button"
|
||||
secondary
|
||||
strong
|
||||
@click="selectedTypeId = tab.id"
|
||||
class="shrink-0 rounded-full border px-4 py-2 text-sm font-medium transition-colors"
|
||||
:class="selectedTypeId === tab.id ? 'border-slate-900 bg-slate-900 text-white' : 'border-slate-200 bg-white text-slate-600 hover:border-slate-300 hover:text-slate-900'"
|
||||
class="shrink-0"
|
||||
:type="selectedTypeId === tab.id ? 'primary' : 'default'"
|
||||
>
|
||||
{{ tab.label }}
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
</NButton>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div v-if="loadError" class="mt-6 rounded-2xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">
|
||||
{{ loadError }}
|
||||
</div>
|
||||
<NAlert v-if="loadError" class="mt-6" type="error" :show-icon="false" :bordered="false" title="画廊加载失败">
|
||||
{{ loadError }}
|
||||
</NAlert>
|
||||
|
||||
<section v-if="loading" class="mt-6 grid grid-cols-2 gap-3 md:grid-cols-3 xl:grid-cols-4">
|
||||
<div v-for="n in 8" :key="n" class="aspect-square animate-pulse rounded-[26px] bg-slate-200"></div>
|
||||
</section>
|
||||
<section v-if="loading" class="mt-6 columns-2 gap-3 md:columns-3 xl:columns-4 [column-gap:0.75rem]">
|
||||
<div
|
||||
v-for="n in 8"
|
||||
:key="n"
|
||||
class="mb-3 break-inside-avoid border border-slate-200 bg-white p-3"
|
||||
>
|
||||
<NSkeleton class="h-44 w-full" />
|
||||
<NSkeleton class="mt-4 h-4 w-3/5" />
|
||||
<NSkeleton class="mt-3 h-3 w-2/5" />
|
||||
<NSkeleton class="mt-2 h-3 w-4/5" />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section v-else-if="galleryItems.length" class="mt-6 grid grid-cols-2 gap-3 md:grid-cols-3 xl:grid-cols-4">
|
||||
<button
|
||||
v-for="cloud in galleryItems"
|
||||
:key="cloud.id"
|
||||
type="button"
|
||||
@click="openDetail(cloud)"
|
||||
class="group relative aspect-square overflow-hidden rounded-[26px] bg-slate-200 text-left shadow-sm"
|
||||
>
|
||||
<img
|
||||
:src="cloud.thumbnail_url || cloud.image_url"
|
||||
:alt="cloud.cloudTypeName"
|
||||
class="h-full w-full object-cover transition duration-500 group-hover:scale-[1.04]"
|
||||
/>
|
||||
<section v-else-if="galleryItems.length" class="mt-6 columns-2 gap-3 md:columns-3 xl:columns-4 [column-gap:0.75rem]">
|
||||
<button
|
||||
v-for="cloud in galleryItems"
|
||||
:key="cloud.id"
|
||||
type="button"
|
||||
@click="openDetail(cloud)"
|
||||
class="group relative mb-3 block w-full break-inside-avoid overflow-hidden border border-slate-200 bg-slate-200 text-left shadow-sm transition-transform duration-300 hover:-translate-y-1 hover:shadow-lg"
|
||||
>
|
||||
<img
|
||||
:src="cloud.thumbnail_url || cloud.image_url"
|
||||
:alt="cloud.cloudTypeName"
|
||||
class="block h-auto w-full object-cover transition duration-500 group-hover:scale-[1.04]"
|
||||
/>
|
||||
|
||||
<div class="absolute inset-0 bg-gradient-to-t from-slate-950/82 via-slate-950/8 to-transparent opacity-100 transition-opacity md:opacity-0 md:group-hover:opacity-100"></div>
|
||||
<div class="absolute inset-x-0 bottom-0 p-4 text-white opacity-100 transition-opacity md:opacity-0 md:group-hover:opacity-100">
|
||||
<div class="rounded-2xl bg-black/28 px-3 py-3 backdrop-blur-[2px]">
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<p class="truncate text-sm font-semibold">{{ cloud.cloudTypeName }}</p>
|
||||
<span class="shrink-0 rounded-full border border-white/20 bg-white/10 px-2 py-0.5 text-[11px]">
|
||||
{{ rarityMeta[cloud.cloudTypeRarity].label }}
|
||||
</span>
|
||||
<div class="absolute inset-x-0 bottom-0 h-28 bg-gradient-to-t from-slate-950/88 via-slate-950/45 to-transparent opacity-100 transition-opacity md:opacity-0 md:group-hover:opacity-100"></div>
|
||||
<div class="absolute inset-x-0 bottom-0 p-4 text-white opacity-100 transition-opacity md:opacity-0 md:group-hover:opacity-100">
|
||||
<div>
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<p class="truncate text-sm font-semibold">{{ cloud.cloudTypeName }}</p>
|
||||
<NTag size="small" :bordered="false" class="shrink-0 bg-white/12 text-white backdrop-blur">
|
||||
{{ rarityMeta[cloud.cloudTypeRarity].label }}
|
||||
</NTag>
|
||||
</div>
|
||||
<p class="mt-2 truncate text-xs text-white/82">📷 {{ cloud.username }}</p>
|
||||
<p class="mt-1 truncate text-xs text-white/82">🕐 {{ formatUploadTime(cloud) }}</p>
|
||||
<p class="mt-1 truncate text-xs text-white/68">{{ cloud.location_name || '未填写位置' }}</p>
|
||||
</div>
|
||||
<p class="mt-2 truncate text-xs text-white/78">📷 {{ cloud.username }}</p>
|
||||
<p class="mt-1 truncate text-xs text-white/78">🕐 {{ formatUploadTime(cloud) }}</p>
|
||||
<p class="mt-1 truncate text-xs text-white/65">{{ cloud.location_name || '未填写位置' }}</p>
|
||||
</div>
|
||||
</button>
|
||||
</section>
|
||||
|
||||
<section v-else class="mt-6 border border-dashed border-slate-300 bg-white px-6 py-12">
|
||||
<NEmpty description="还没有符合条件的云图">
|
||||
<template #extra>
|
||||
<p class="text-sm text-slate-500">换个云型筛选试试,或者等社区上传更多作品。</p>
|
||||
</template>
|
||||
</NEmpty>
|
||||
</section>
|
||||
|
||||
<div ref="sentinel" class="h-10"></div>
|
||||
|
||||
<div v-if="loadingMore" class="flex justify-center py-4">
|
||||
<NButton secondary disabled>正在加载更多云图...</NButton>
|
||||
</div>
|
||||
|
||||
<div v-else-if="hasMore && galleryItems.length" class="flex justify-center py-4">
|
||||
<NButton secondary strong @click="loadMore">手动加载更多</NButton>
|
||||
</div>
|
||||
|
||||
<ImageDetailModal
|
||||
v-if="selectedCloud"
|
||||
:open="!!selectedCloud"
|
||||
:image-url="selectedCloud.image_url"
|
||||
:image-alt="selectedCloud.cloudTypeName"
|
||||
:title="selectedCloud.cloudTypeName"
|
||||
:subtitle="`上传者:${selectedCloud.username}`"
|
||||
:badge-label="rarityMeta[selectedCloud.cloudTypeRarity].label"
|
||||
:badge-class="rarityMeta[selectedCloud.cloudTypeRarity].chip"
|
||||
@close="closeDetail"
|
||||
>
|
||||
<div class="grid gap-4 sm:grid-cols-2">
|
||||
<div class="border border-slate-200 bg-slate-50 p-4">
|
||||
<p class="text-xs uppercase tracking-[0.18em] text-slate-500">上传时间</p>
|
||||
<p class="mt-2 text-sm font-medium text-slate-900">{{ formatUploadTime(selectedCloud) }}</p>
|
||||
</div>
|
||||
<div class="border border-slate-200 bg-slate-50 p-4">
|
||||
<p class="text-xs uppercase tracking-[0.18em] text-slate-500">拍摄时间</p>
|
||||
<p class="mt-2 text-sm font-medium text-slate-900">{{ formatCapturedTime(selectedCloud) }}</p>
|
||||
</div>
|
||||
<div class="border border-slate-200 bg-slate-50 p-4">
|
||||
<p class="text-xs uppercase tracking-[0.18em] text-slate-500">位置</p>
|
||||
<p class="mt-2 text-sm font-medium text-slate-900">{{ selectedCloud.location_name || '未填写位置名称' }}</p>
|
||||
</div>
|
||||
<div class="border border-slate-200 bg-slate-50 p-4">
|
||||
<p class="text-xs uppercase tracking-[0.18em] text-slate-500">模糊化经纬度</p>
|
||||
<p class="mt-2 text-sm font-medium text-slate-900">
|
||||
纬度 {{ formatCoordinate(selectedCloud.latitude) }} / 经度 {{ formatCoordinate(selectedCloud.longitude) }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</section>
|
||||
|
||||
<section v-else class="mt-6 rounded-[28px] border border-dashed border-slate-300 bg-white px-6 py-12 text-center">
|
||||
<p class="text-xl font-semibold text-slate-900">还没有符合条件的云图</p>
|
||||
<p class="mt-2 text-sm text-slate-500">换个云型筛选试试,或者等社区上传更多作品。</p>
|
||||
</section>
|
||||
|
||||
<div ref="sentinel" class="h-10"></div>
|
||||
|
||||
<div v-if="loadingMore" class="flex justify-center py-4">
|
||||
<div class="rounded-full border border-slate-200 bg-white px-4 py-2 text-sm text-slate-500 shadow-sm">
|
||||
正在加载更多云图...
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else-if="hasMore && galleryItems.length" class="flex justify-center py-4">
|
||||
<button
|
||||
type="button"
|
||||
@click="loadMore"
|
||||
class="rounded-full border border-slate-200 bg-white px-4 py-2 text-sm font-medium text-slate-700 shadow-sm transition-colors hover:border-slate-300 hover:text-slate-900"
|
||||
>
|
||||
手动加载更多
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<ImageDetailModal
|
||||
v-if="selectedCloud"
|
||||
:open="!!selectedCloud"
|
||||
:image-url="selectedCloud.image_url"
|
||||
:image-alt="selectedCloud.cloudTypeName"
|
||||
:title="selectedCloud.cloudTypeName"
|
||||
:subtitle="`上传者:${selectedCloud.username}`"
|
||||
:badge-label="rarityMeta[selectedCloud.cloudTypeRarity].label"
|
||||
:badge-class="rarityMeta[selectedCloud.cloudTypeRarity].chip"
|
||||
@close="closeDetail"
|
||||
>
|
||||
<div class="grid gap-4 sm:grid-cols-2">
|
||||
<div class="rounded-2xl bg-slate-50 p-4">
|
||||
<p class="text-xs uppercase tracking-[0.18em] text-slate-500">上传时间</p>
|
||||
<p class="mt-2 text-sm font-medium text-slate-900">{{ formatUploadTime(selectedCloud) }}</p>
|
||||
</div>
|
||||
<div class="rounded-2xl bg-slate-50 p-4">
|
||||
<p class="text-xs uppercase tracking-[0.18em] text-slate-500">拍摄时间</p>
|
||||
<p class="mt-2 text-sm font-medium text-slate-900">{{ formatCapturedTime(selectedCloud) }}</p>
|
||||
</div>
|
||||
<div class="rounded-2xl bg-slate-50 p-4">
|
||||
<p class="text-xs uppercase tracking-[0.18em] text-slate-500">位置</p>
|
||||
<p class="mt-2 text-sm font-medium text-slate-900">{{ selectedCloud.location_name || '未填写位置名称' }}</p>
|
||||
</div>
|
||||
<div class="rounded-2xl bg-slate-50 p-4">
|
||||
<p class="text-xs uppercase tracking-[0.18em] text-slate-500">模糊化经纬度</p>
|
||||
<p class="mt-2 text-sm font-medium text-slate-900">
|
||||
纬度 {{ formatCoordinate(selectedCloud.latitude) }} / 经度 {{ formatCoordinate(selectedCloud.longitude) }}
|
||||
<div class="mt-5 border border-slate-200 bg-white p-5">
|
||||
<p class="text-xs uppercase tracking-[0.18em] text-slate-500">图片说明</p>
|
||||
<p class="mt-3 text-sm leading-7 text-slate-700">
|
||||
{{ selectedCloud.description || '上传者没有留下额外说明。' }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-5 rounded-[28px] border border-slate-200 bg-white p-5">
|
||||
<p class="text-xs uppercase tracking-[0.18em] text-slate-500">图片说明</p>
|
||||
<p class="mt-3 text-sm leading-7 text-slate-700">
|
||||
{{ selectedCloud.description || '上传者没有留下额外说明。' }}
|
||||
</p>
|
||||
</div>
|
||||
</ImageDetailModal>
|
||||
</ImageDetailModal>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user