import { create } from 'zustand'; import { api, SearchResult, PlayRecord } from '@/services/api'; import { PlayRecordManager } from '@/services/storage'; 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; } const useHomeStore = create((set, get) => ({ categories: initialCategories, selectedCategory: initialCategories[0], contentData: [], loading: true, loadingMore: false, pageStart: 0, hasMore: true, error: null, fetchInitialData: async () => { 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 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[]; set(state => ({ contentData: pageStart === 0 ? newItems : [...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: '请点击右上角设置按钮,配置您的 API 地址' }); } else { set({ error: '加载失败,请重试' }); } } finally { set({ loading: false, loadingMore: false }); } }, selectCategory: (category: Category) => { const currentCategory = get().selectedCategory; // Only fetch new data if the category or tag actually changes if (currentCategory.title !== category.title || currentCategory.tag !== category.tag) { set({ selectedCategory: category, contentData: [], pageStart: 0, hasMore: true, error: null }); get().fetchInitialData(); } }, refreshPlayRecords: async () => { 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 {}; }); if (get().selectedCategory.type === 'record') { get().fetchInitialData(); } }, })); export default useHomeStore;