feat: enhance profile heatmap and caching

This commit is contained in:
2026-05-22 00:55:23 +08:00
parent f35baf4a67
commit 7cdf07447c
3 changed files with 1106 additions and 3 deletions
+122
View File
@@ -0,0 +1,122 @@
import { ref } from 'vue'
import { defineStore } from 'pinia'
import { supabase } from '@/lib/supabase'
import type { CloudType, Profile } from '@/types/database'
export interface ProfileCloudItem {
id: string
image_url: string
thumbnail_url: string | null
location_name: string | null
description: string | null
captured_at: string | null
created_at: string
status: 'pending' | 'approved' | 'rejected'
is_hidden: boolean
cloudTypeName: string
cloudTypeRarity: CloudType['rarity']
}
function toProfileCloud(row: Record<string, unknown>) {
const cloudTypes = Array.isArray(row.cloud_types) ? row.cloud_types : row.cloud_types ? [row.cloud_types] : []
const cloudType = cloudTypes[0] as Record<string, unknown> | undefined
return {
id: row.id as string,
image_url: row.image_url as string,
thumbnail_url: (row.thumbnail_url as string | null) ?? null,
location_name: (row.location_name as string | null) ?? null,
description: (row.description as string | null) ?? null,
captured_at: (row.captured_at as string | null) ?? null,
created_at: row.created_at as string,
status: row.status as ProfileCloudItem['status'],
is_hidden: (row.is_hidden as boolean) ?? false,
cloudTypeName: (cloudType?.name as string) || (row.custom_cloud_type as string) || '未知',
cloudTypeRarity: (cloudType?.rarity as CloudType['rarity']) || 'common',
} satisfies ProfileCloudItem
}
export const useProfileStore = defineStore('profile-page', () => {
const profilesById = ref<Record<string, Profile>>({})
const cloudsByKey = ref<Record<string, ProfileCloudItem[]>>({})
const loadingKeys = ref<Record<string, boolean>>({})
const errorByKey = ref<Record<string, string>>({})
const makeKey = (userId: string, isOwnProfile: boolean) => `${userId}:${isOwnProfile ? 'own' : 'public'}`
function getProfile(userId: string | null) {
if (!userId) return null
return profilesById.value[userId] ?? null
}
function getClouds(userId: string | null, isOwnProfile: boolean) {
if (!userId) return []
return cloudsByKey.value[makeKey(userId, isOwnProfile)] ?? []
}
function getError(userId: string | null, isOwnProfile: boolean) {
if (!userId) return ''
return errorByKey.value[makeKey(userId, isOwnProfile)] ?? ''
}
function isLoaded(userId: string | null, isOwnProfile: boolean) {
if (!userId) return false
return makeKey(userId, isOwnProfile) in cloudsByKey.value
}
async function fetchProfilePage(userId: string, isOwnProfile: boolean, force = false) {
const key = makeKey(userId, isOwnProfile)
if (!force && key in cloudsByKey.value && profilesById.value[userId]) return
loadingKeys.value[key] = true
errorByKey.value[key] = ''
try {
const { data: profile, error: profileError } = await supabase
.from('profiles')
.select('*')
.eq('id', userId)
.single()
if (profileError) throw profileError
profilesById.value[userId] = profile as Profile
let cloudsQuery = supabase
.from('clouds')
.select('id,image_url,thumbnail_url,location_name,description,captured_at,created_at,status,is_hidden,custom_cloud_type,cloud_types(name,rarity)')
.eq('user_id', userId)
.order('captured_at', { ascending: false, nullsFirst: false })
.order('created_at', { ascending: false })
if (!isOwnProfile) {
cloudsQuery = cloudsQuery
.eq('status', 'approved')
.eq('is_hidden', false)
}
const { data: cloudRows, error: cloudsError } = await cloudsQuery
if (cloudsError) throw cloudsError
cloudsByKey.value[key] = ((cloudRows || []) as Array<Record<string, unknown>>).map(toProfileCloud)
} catch (error) {
errorByKey.value[key] = error instanceof Error ? error.message : '个人主页加载失败'
cloudsByKey.value[key] = []
} finally {
loadingKeys.value[key] = false
}
}
function isLoading(userId: string | null, isOwnProfile: boolean) {
if (!userId) return false
return !!loadingKeys.value[makeKey(userId, isOwnProfile)]
}
return {
getProfile,
getClouds,
getError,
isLoaded,
isLoading,
fetchProfilePage,
}
})