mirror of
https://github.com/zimplexing/OrionTV.git
synced 2026-02-04 03:36:29 +08:00
- Added login/logout buttons to the HomeScreen and SettingsScreen. - Integrated authentication state management using Zustand and cookies. - Updated API to support username and password for login. - Enhanced PlayScreen to handle video playback based on user authentication. - Created a new detailStore to manage video details and sources. - Refactored playerStore to utilize detailStore for episode management. - Added sourceStore to manage video source toggling. - Updated settingsStore to fetch server configuration. - Improved error handling and user feedback with Toast notifications. - Cleaned up unused code and optimized imports across components.
172 lines
6.0 KiB
TypeScript
172 lines
6.0 KiB
TypeScript
import { create } from 'zustand';
|
|
import { api, SearchResult, PlayRecord } from '@/services/api';
|
|
import { PlayRecordManager } from '@/services/storage';
|
|
import useAuthStore from './authStore';
|
|
|
|
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<void>;
|
|
loadMoreData: () => Promise<void>;
|
|
selectCategory: (category: Category) => void;
|
|
refreshPlayRecords: () => Promise<void>;
|
|
}
|
|
|
|
const useHomeStore = create<HomeState>((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 { 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[];
|
|
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 { 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 {};
|
|
});
|
|
if (get().selectedCategory.type === 'record') {
|
|
get().fetchInitialData();
|
|
}
|
|
},
|
|
}));
|
|
|
|
export default useHomeStore; |