feat: add cloud encyclopedia system
This commit is contained in:
@@ -0,0 +1,161 @@
|
||||
import { computed, ref } from 'vue'
|
||||
import { defineStore } from 'pinia'
|
||||
import { supabase } from '@/lib/supabase'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import type { CloudType, UserCollection } from '@/types/database'
|
||||
|
||||
export interface CollectionPreviewCloud {
|
||||
id: string
|
||||
image_url: string
|
||||
captured_at: string | null
|
||||
created_at: string
|
||||
location_name: string | null
|
||||
}
|
||||
|
||||
export interface CollectionEntry extends UserCollection {
|
||||
firstCloud: CollectionPreviewCloud | null
|
||||
}
|
||||
|
||||
function getErrorMessage(error: unknown) {
|
||||
if (error instanceof Error) return error.message
|
||||
if (error && typeof error === 'object' && 'message' in error && typeof error.message === 'string') {
|
||||
return error.message
|
||||
}
|
||||
return '图鉴收藏加载失败'
|
||||
}
|
||||
|
||||
function toCollectionCloudMap(rows: Array<Record<string, unknown>> | null) {
|
||||
return new Map(
|
||||
(rows || []).map(row => [
|
||||
row.id as string,
|
||||
{
|
||||
id: row.id as string,
|
||||
image_url: row.image_url as string,
|
||||
captured_at: (row.captured_at as string | null) ?? null,
|
||||
created_at: row.created_at as string,
|
||||
location_name: (row.location_name as string | null) ?? null,
|
||||
} satisfies CollectionPreviewCloud,
|
||||
]),
|
||||
)
|
||||
}
|
||||
|
||||
export const useEncyclopediaStore = defineStore('encyclopedia', () => {
|
||||
const authStore = useAuthStore()
|
||||
|
||||
const cloudTypes = ref<CloudType[]>([])
|
||||
const myCollection = ref<CollectionEntry[]>([])
|
||||
const loadingCloudTypes = ref(false)
|
||||
const loadingCollection = ref(false)
|
||||
const cloudTypesLoaded = ref(false)
|
||||
const collectionLoadedForUserId = ref<string | null>(null)
|
||||
const collectionError = ref('')
|
||||
|
||||
const collectionMap = computed(() => {
|
||||
return new Map(myCollection.value.map(item => [item.cloud_type_id, item]))
|
||||
})
|
||||
|
||||
const unlockedCount = computed(() => myCollection.value.length)
|
||||
const unlockProgress = computed(() => `${unlockedCount.value}/${cloudTypes.value.length || 10}`)
|
||||
const unlockPercent = computed(() => {
|
||||
if (!cloudTypes.value.length) return 0
|
||||
return Math.round(unlockedCount.value / cloudTypes.value.length * 100)
|
||||
})
|
||||
|
||||
async function fetchCloudTypes(force = false) {
|
||||
if (cloudTypesLoaded.value && !force) return
|
||||
|
||||
loadingCloudTypes.value = true
|
||||
try {
|
||||
const { data, error } = await supabase
|
||||
.from('cloud_types')
|
||||
.select('*')
|
||||
.order('id')
|
||||
|
||||
if (error) throw error
|
||||
|
||||
cloudTypes.value = (data || []) as CloudType[]
|
||||
cloudTypesLoaded.value = true
|
||||
} finally {
|
||||
loadingCloudTypes.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchMyCollection(force = false) {
|
||||
const userId = authStore.user?.id ?? null
|
||||
|
||||
if (!userId) {
|
||||
myCollection.value = []
|
||||
collectionLoadedForUserId.value = null
|
||||
collectionError.value = ''
|
||||
return
|
||||
}
|
||||
|
||||
if (!force && collectionLoadedForUserId.value === userId) return
|
||||
|
||||
loadingCollection.value = true
|
||||
collectionError.value = ''
|
||||
|
||||
try {
|
||||
const { data, error } = await supabase
|
||||
.from('user_collections')
|
||||
.select('*')
|
||||
.eq('user_id', userId)
|
||||
.order('unlocked_at', { ascending: true })
|
||||
|
||||
if (error) throw error
|
||||
|
||||
const collectionRows = (data || []) as UserCollection[]
|
||||
const firstCloudIds = collectionRows
|
||||
.map(item => item.first_cloud_id)
|
||||
.filter((id): id is string => typeof id === 'string' && id.length > 0)
|
||||
|
||||
let firstCloudMap = new Map<string, CollectionPreviewCloud>()
|
||||
|
||||
if (firstCloudIds.length) {
|
||||
const { data: firstCloudData, error: firstCloudError } = await supabase
|
||||
.from('clouds')
|
||||
.select('id,image_url,captured_at,created_at,location_name')
|
||||
.in('id', firstCloudIds)
|
||||
|
||||
if (firstCloudError) throw firstCloudError
|
||||
|
||||
firstCloudMap = toCollectionCloudMap(firstCloudData as Array<Record<string, unknown>> | null)
|
||||
}
|
||||
|
||||
myCollection.value = collectionRows.map(item => ({
|
||||
...item,
|
||||
firstCloud: item.first_cloud_id ? firstCloudMap.get(item.first_cloud_id) ?? null : null,
|
||||
}))
|
||||
collectionLoadedForUserId.value = userId
|
||||
} catch (error) {
|
||||
myCollection.value = []
|
||||
collectionLoadedForUserId.value = userId
|
||||
collectionError.value = getErrorMessage(error)
|
||||
} finally {
|
||||
loadingCollection.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function isUnlocked(cloudTypeId: number) {
|
||||
return collectionMap.value.has(cloudTypeId)
|
||||
}
|
||||
|
||||
function getCollectionEntry(cloudTypeId: number) {
|
||||
return collectionMap.value.get(cloudTypeId) ?? null
|
||||
}
|
||||
|
||||
return {
|
||||
cloudTypes,
|
||||
myCollection,
|
||||
loadingCloudTypes,
|
||||
loadingCollection,
|
||||
collectionError,
|
||||
unlockedCount,
|
||||
unlockProgress,
|
||||
unlockPercent,
|
||||
fetchCloudTypes,
|
||||
fetchMyCollection,
|
||||
isUnlocked,
|
||||
getCollectionEntry,
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user