You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
96 lines
2.6 KiB
96 lines
2.6 KiB
import { useAuthSession } from "./useAuthSession";
|
|
|
|
// Module-level singleton state — shared across all components that call useFavorite().
|
|
const favoritedCardIds = ref<Set<number>>(new Set())
|
|
const loadingIds = ref<Set<number>>(new Set())
|
|
|
|
/**
|
|
* Favorite management composable.
|
|
* Provides a reactive set of favorited card IDs and toggle function.
|
|
*/
|
|
export function useFavorite() {
|
|
const { loggedIn } = useAuthSession()
|
|
const { $toast } = useNuxtApp()
|
|
|
|
/**
|
|
* Check if a card is favorited.
|
|
*/
|
|
function isFavorited(cardId: number): boolean {
|
|
return favoritedCardIds.value.has(cardId)
|
|
}
|
|
|
|
/**
|
|
* Toggle favorite for a card.
|
|
* Optimistically updates local state.
|
|
*/
|
|
async function toggle(cardId: number): Promise<boolean> {
|
|
if (!loggedIn.value) {
|
|
$toast.info('请先登录')
|
|
return false
|
|
}
|
|
if (loadingIds.value.has(cardId)) return isFavorited(cardId)
|
|
|
|
const wasFavorited = favoritedCardIds.value.has(cardId)
|
|
// Optimistic update
|
|
const next = new Set(favoritedCardIds.value)
|
|
if (wasFavorited) {
|
|
next.delete(cardId)
|
|
} else {
|
|
next.add(cardId)
|
|
}
|
|
favoritedCardIds.value = next
|
|
loadingIds.value = new Set(loadingIds.value).add(cardId)
|
|
|
|
try {
|
|
const raw = await $fetch<{ code: number; data: { favorited: boolean } }>(
|
|
`/api/cards/${cardId}/favorite`,
|
|
{ method: 'POST' },
|
|
)
|
|
if (raw.code !== 0 || !raw.data.favorited) {
|
|
// Server says unfavorited — ensure it's removed
|
|
const corrected = new Set(favoritedCardIds.value)
|
|
corrected.delete(cardId)
|
|
favoritedCardIds.value = corrected
|
|
}
|
|
return raw.data.favorited
|
|
} catch {
|
|
// Revert optimistic update
|
|
const reverted = new Set(favoritedCardIds.value)
|
|
if (wasFavorited) {
|
|
reverted.add(cardId)
|
|
} else {
|
|
reverted.delete(cardId)
|
|
}
|
|
favoritedCardIds.value = reverted
|
|
$toast.error('操作失败,请稍后重试')
|
|
return wasFavorited
|
|
} finally {
|
|
const next = new Set(loadingIds.value)
|
|
next.delete(cardId)
|
|
loadingIds.value = next
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sync favorited status from server response (batch).
|
|
*/
|
|
function syncFromCards(cards: Array<{ id: number; isFavorited?: boolean }>) {
|
|
const next = new Set(favoritedCardIds.value)
|
|
for (const card of cards) {
|
|
if (card.isFavorited) {
|
|
next.add(card.id)
|
|
} else {
|
|
next.delete(card.id)
|
|
}
|
|
}
|
|
favoritedCardIds.value = next
|
|
}
|
|
|
|
return {
|
|
favoritedCardIds,
|
|
loadingIds,
|
|
isFavorited,
|
|
toggle,
|
|
syncFromCards,
|
|
}
|
|
}
|
|
|