import { create } from "zustand"; import { api, SearchResult, PlayRecord } from "@/services/api"; import { PlayRecordManager } from "@/services/storage"; import useAuthStore from "./authStore"; import { useSettingsStore } from "./settingsStore"; export type RowItem = (SearchResult | PlayRecord) & { id: string; source: string; title: string; poster: string; progress?: number; play_time?: number; lastPlayed?: number; episodeIndex?: number; sourceName?: string; totalEpisodes?: number; year?: string; rate?: string; }; export interface Category { title: string; type?: "movie" | "tv" | "record"; tag?: string; tags?: string[]; } const initialCategories: Category[] = [ { title: "最近播放", type: "record" }, { title: "热门剧集", type: "tv", tag: "热门" }, { title: "电视剧", type: "tv", tags: ["国产剧", "美剧", "英剧", "韩剧", "日剧", "港剧", "日本动画", "动画"] }, { title: "电影", type: "movie", tags: [ "热门", "最新", "经典", "豆瓣高分", "冷门佳片", "华语", "欧美", "韩国", "日本", "动作", "喜剧", "爱情", "科幻", "悬疑", "恐怖", ], }, { title: "综艺", type: "tv", tag: "综艺" }, { title: "豆瓣 Top250", type: "movie", tag: "top250" }, ]; interface HomeState { categories: Category[]; selectedCategory: Category; contentData: RowItem[]; loading: boolean; loadingMore: boolean; pageStart: number; hasMore: boolean; error: string | null; fetchInitialData: () => Promise; loadMoreData: () => Promise; selectCategory: (category: Category) => void; refreshPlayRecords: () => Promise; clearError: () => void; } // 内存缓存,应用生命周期内有效 const dataCache = new Map(); const useHomeStore = create((set, get) => ({ categories: initialCategories, selectedCategory: initialCategories[0], contentData: [], loading: true, loadingMore: false, pageStart: 0, hasMore: true, error: null, fetchInitialData: async () => { const { apiBaseUrl } = useSettingsStore.getState(); await useAuthStore.getState().checkLoginStatus(apiBaseUrl); const { selectedCategory } = get(); const cacheKey = `${selectedCategory.title}-${selectedCategory.tag || ''}`; // 最近播放不缓存,始终实时获取 if (selectedCategory.type === 'record') { set({ loading: true, contentData: [], pageStart: 0, hasMore: true, error: null }); await get().loadMoreData(); return; } // 检查缓存 if (dataCache.has(cacheKey)) { set({ loading: false, contentData: dataCache.get(cacheKey)!, pageStart: dataCache.get(cacheKey)!.length, hasMore: false, error: null }); return; } set({ loading: true, contentData: [], pageStart: 0, hasMore: true, error: null }); await get().loadMoreData(); }, loadMoreData: async () => { const { selectedCategory, pageStart, loadingMore, hasMore } = get(); if (loadingMore || !hasMore) return; if (pageStart > 0) { set({ loadingMore: true }); } try { if (selectedCategory.type === "record") { const { isLoggedIn } = useAuthStore.getState(); if (!isLoggedIn) { set({ contentData: [], hasMore: false }); return; } const records = await PlayRecordManager.getAll(); const rowItems = Object.entries(records) .map(([key, record]) => { const [source, id] = key.split("+"); return { ...record, id, source, progress: record.play_time / record.total_time, poster: record.cover, sourceName: record.source_name, episodeIndex: record.index, totalEpisodes: record.total_episodes, lastPlayed: record.save_time, play_time: record.play_time, }; }) // .filter((record) => record.progress !== undefined && record.progress > 0 && record.progress < 1) .sort((a, b) => (b.lastPlayed || 0) - (a.lastPlayed || 0)); set({ contentData: rowItems, hasMore: false }); } else if (selectedCategory.type && selectedCategory.tag) { const result = await api.getDoubanData(selectedCategory.type, selectedCategory.tag, 20, pageStart); if (result.list.length === 0) { set({ hasMore: false }); } else { const newItems = result.list.map((item) => ({ ...item, id: item.title, source: "douban", })) as RowItem[]; const cacheKey = `${selectedCategory.title}-${selectedCategory.tag || ''}`; if (pageStart === 0) { // 缓存新数据 dataCache.set(cacheKey, newItems); set((state) => ({ contentData: newItems, pageStart: result.list.length, hasMore: true, })); } else { // 增量加载时不缓存,直接追加 set((state) => ({ contentData: [...state.contentData, ...newItems], pageStart: state.pageStart + result.list.length, hasMore: true, })); } } } else if (selectedCategory.tags) { // It's a container category, do not load content, but clear current content set({ contentData: [], hasMore: false }); } else { set({ hasMore: false }); } } catch (err: any) { if (err.message === "API_URL_NOT_SET") { set({ error: "请点击右上角设置按钮,配置您的服务器地址" }); } else { set({ error: "加载失败,请重试" }); } } finally { set({ loading: false, loadingMore: false }); } }, selectCategory: (category: Category) => { const currentCategory = get().selectedCategory; const cacheKey = `${category.title}-${category.tag || ''}`; // 只有当分类或标签真正变化时才处理 if (currentCategory.title !== category.title || currentCategory.tag !== category.tag) { set({ selectedCategory: category, contentData: [], pageStart: 0, hasMore: true, error: null }); // 最近播放始终实时获取 if (category.type === 'record') { get().fetchInitialData(); return; } // 检查缓存,有则直接使用,无则请求 if (dataCache.has(cacheKey)) { set({ contentData: dataCache.get(cacheKey)!, pageStart: dataCache.get(cacheKey)!.length, hasMore: false, loading: false }); } else { get().fetchInitialData(); } } }, refreshPlayRecords: async () => { const { apiBaseUrl } = useSettingsStore.getState(); await useAuthStore.getState().checkLoginStatus(apiBaseUrl); const { isLoggedIn } = useAuthStore.getState(); if (!isLoggedIn) { set((state) => { const recordCategoryExists = state.categories.some((c) => c.type === "record"); if (recordCategoryExists) { const newCategories = state.categories.filter((c) => c.type !== "record"); if (state.selectedCategory.type === "record") { get().selectCategory(newCategories[0] || null); } return { categories: newCategories }; } return {}; }); return; } const records = await PlayRecordManager.getAll(); const hasRecords = Object.keys(records).length > 0; set((state) => { const recordCategoryExists = state.categories.some((c) => c.type === "record"); if (hasRecords && !recordCategoryExists) { return { categories: [initialCategories[0], ...state.categories] }; } if (!hasRecords && recordCategoryExists) { const newCategories = state.categories.filter((c) => c.type !== "record"); if (state.selectedCategory.type === "record") { get().selectCategory(newCategories[0] || null); } return { categories: newCategories }; } return {}; }); get().fetchInitialData(); }, clearError: () => { set({ error: null }); }, })); export default useHomeStore;