diff --git a/src/components/timeline/TrackTimeline.tsx b/src/components/timeline/TrackTimeline.tsx index 5a077e8..0c13917 100644 --- a/src/components/timeline/TrackTimeline.tsx +++ b/src/components/timeline/TrackTimeline.tsx @@ -1,7 +1,7 @@ import React from 'react' import { SegmentContextMenu } from './SegmentContextMenu' import { SegmentTooltip } from './SegmentTooltip' -import { ResourceCategoryServiceV2, ResourceCategoryV2 } from '../../services/resourceCategoryServiceV2' +import { useCategoriesData, useCategoryActions, useActiveCategoriesOnly } from '../../stores/useCategoryStore' export interface TrackSegment { id: string @@ -48,27 +48,15 @@ export const TrackTimeline: React.FC = ({ }>({ isOpen: false, position: { x: 0, y: 0 }, segment: null }) const [hoveredSegment, setHoveredSegment] = React.useState(null) const [mousePosition, setMousePosition] = React.useState({ x: 0, y: 0 }) - const [categories, setCategories] = React.useState([]) - const [loadingCategories, setLoadingCategories] = React.useState(false) - // 加载分类数据 - const loadCategories = React.useCallback(async () => { - try { - setLoadingCategories(true) - const result = await ResourceCategoryServiceV2.getAllCategories({ - include_cloud: true - }) - setCategories(result) - } catch (error) { - console.error('Failed to load categories:', error) - } finally { - setLoadingCategories(false) - } - }, []) + // 使用 Zustand store 管理分类数据 + const { loading: loadingCategories } = useCategoriesData() + const activeCategories = useActiveCategoriesOnly() + const { loadCategories } = useCategoryActions() - // 组件挂载时加载分类 + // 组件挂载时加载分类(如果还没有加载) React.useEffect(() => { - loadCategories() + loadCategories() // store 内部会处理缓存和重复加载 }, []) const getSegmentColor = (type: string) => { @@ -108,13 +96,9 @@ export const TrackTimeline: React.FC = ({ return `${minutes}:${secs.padStart(5, '0')}` } - const handleSegmentDoubleClick = async (segment: TrackSegment) => { + const handleSegmentDoubleClick = (segment: TrackSegment) => { setEditingSegmentId(segment.id) - - // 确保分类数据已加载 - if (categories.length === 0 && !loadingCategories) { - await loadCategories() - } + // Zustand store 会自动处理分类数据的加载和缓存 } const handleSegmentRightClick = (e: React.MouseEvent, segment: TrackSegment) => { @@ -156,14 +140,10 @@ export const TrackTimeline: React.FC = ({ setContextMenu({ isOpen: false, position: { x: 0, y: 0 }, segment: null }) } - const handleContextMenuEdit = async () => { + const handleContextMenuEdit = () => { if (contextMenu.segment) { setEditingSegmentId(contextMenu.segment.id) - - // 确保分类数据已加载 - if (categories.length === 0 && !loadingCategories) { - await loadCategories() - } + // Zustand store 会自动处理分类数据的加载和缓存 } } @@ -240,19 +220,17 @@ export const TrackTimeline: React.FC = ({ onBlur={() => setEditingSegmentId(null)} className="w-full bg-white text-gray-900 border border-gray-300 rounded px-2 py-1 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 cursor-pointer" autoFocus - size={Math.min(categories.length + 2, 8)} // 限制下拉框高度 + size={Math.min(activeCategories.length + 2, 8)} // 限制下拉框高度 > - {categories - .filter(category => category.is_active) - .map((category) => ( - - ))} + {activeCategories.map((category) => ( + + ))} )} diff --git a/src/stores/useCategoryStore.ts b/src/stores/useCategoryStore.ts new file mode 100644 index 0000000..2f56f0d --- /dev/null +++ b/src/stores/useCategoryStore.ts @@ -0,0 +1,168 @@ +import { create } from 'zustand' +import { ResourceCategoryServiceV2, ResourceCategoryV2 } from '../services/resourceCategoryServiceV2' + +interface CategoryState { + // 状态 + categories: ResourceCategoryV2[] + loading: boolean + error: string | null + lastLoadTime: number + + // 缓存控制 + cacheTimeout: number // 缓存超时时间(毫秒) + + // 操作方法 + loadCategories: (force?: boolean) => Promise + refreshCategories: () => Promise + addCategory: (category: ResourceCategoryV2) => void + updateCategory: (categoryId: string, updates: Partial) => void + removeCategory: (categoryId: string) => void + clearError: () => void + + // 查询方法 + getCategoryById: (id: string) => ResourceCategoryV2 | undefined + getActiveCategoriesOnly: () => ResourceCategoryV2[] + getCategoriesByColor: (color: string) => ResourceCategoryV2[] + searchCategories: (query: string) => ResourceCategoryV2[] +} + +export const useCategoryStore = create((set, get) => ({ + // 初始状态 + categories: [], + loading: false, + error: null, + lastLoadTime: 0, + cacheTimeout: 5 * 60 * 1000, // 5分钟缓存 + + // 加载分类数据 + loadCategories: async (force = false) => { + const state = get() + const now = Date.now() + + // 检查是否需要重新加载 + if (!force && + state.categories.length > 0 && + (now - state.lastLoadTime) < state.cacheTimeout && + !state.loading) { + return // 使用缓存数据 + } + + // 防止重复加载 + if (state.loading) { + return + } + + set({ loading: true, error: null }) + + try { + const result = await ResourceCategoryServiceV2.getAllCategories({ + include_cloud: true + }) + + set({ + categories: result, + loading: false, + error: null, + lastLoadTime: now + }) + } catch (error) { + const errorMessage = error instanceof Error ? error.message : '加载分类失败' + console.error('Failed to load categories:', error) + + set({ + loading: false, + error: errorMessage + }) + } + }, + + // 强制刷新分类数据 + refreshCategories: async () => { + await get().loadCategories(true) + }, + + // 添加新分类 + addCategory: (category: ResourceCategoryV2) => { + set(state => ({ + categories: [...state.categories, category] + })) + }, + + // 更新分类 + updateCategory: (categoryId: string, updates: Partial) => { + set(state => ({ + categories: state.categories.map(category => + category.id === categoryId + ? { ...category, ...updates } + : category + ) + })) + }, + + // 删除分类 + removeCategory: (categoryId: string) => { + set(state => ({ + categories: state.categories.filter(category => category.id !== categoryId) + })) + }, + + // 清除错误 + clearError: () => { + set({ error: null }) + }, + + // 根据ID获取分类 + getCategoryById: (id: string) => { + return get().categories.find(category => category.id === id) + }, + + // 获取激活的分类 + getActiveCategoriesOnly: () => { + return get().categories.filter(category => category.is_active) + }, + + // 根据颜色获取分类 + getCategoriesByColor: (color: string) => { + return get().categories.filter(category => category.color === color) + }, + + // 搜索分类 + searchCategories: (query: string) => { + const lowerQuery = query.toLowerCase() + return get().categories.filter(category => + category.title.toLowerCase().includes(lowerQuery) || + category.ai_prompt.toLowerCase().includes(lowerQuery) + ) + } +})) + +// 导出便捷的 hooks +export const useCategoriesData = () => { + const { categories, loading, error } = useCategoryStore() + return { categories, loading, error } +} + +export const useActiveCategoriesOnly = () => { + const getActiveCategoriesOnly = useCategoryStore(state => state.getActiveCategoriesOnly) + return getActiveCategoriesOnly() +} + +export const useCategoryActions = () => { + const { + loadCategories, + refreshCategories, + addCategory, + updateCategory, + removeCategory, + clearError + } = useCategoryStore() + + return { + loadCategories, + refreshCategories, + addCategory, + updateCategory, + removeCategory, + clearError + } +}