/* eslint-disable react-hooks/exhaustive-deps */ 'use client'; import React, { useEffect, useRef, useState } from 'react'; interface CustomCategory { name: string; type: 'movie' | 'tv'; query: string; } interface DoubanCustomSelectorProps { customCategories: CustomCategory[]; primarySelection?: string; secondarySelection?: string; onPrimaryChange: (value: string) => void; onSecondaryChange: (value: string) => void; } const DoubanCustomSelector: React.FC = ({ customCategories, primarySelection, secondarySelection, onPrimaryChange, onSecondaryChange, }) => { // 为不同的选择器创建独立的refs和状态 const primaryContainerRef = useRef(null); const primaryButtonRefs = useRef<(HTMLButtonElement | null)[]>([]); const [primaryIndicatorStyle, setPrimaryIndicatorStyle] = useState<{ left: number; width: number; }>({ left: 0, width: 0 }); const secondaryContainerRef = useRef(null); const secondaryButtonRefs = useRef<(HTMLButtonElement | null)[]>([]); const [secondaryIndicatorStyle, setSecondaryIndicatorStyle] = useState<{ left: number; width: number; }>({ left: 0, width: 0 }); // 二级选择器滚动容器的ref const secondaryScrollContainerRef = useRef(null); // 根据 customCategories 生成一级选择器选项(按 type 分组,电影优先) const primaryOptions = React.useMemo(() => { const types = Array.from(new Set(customCategories.map((cat) => cat.type))); // 确保电影类型排在前面 const sortedTypes = types.sort((a, b) => { if (a === 'movie' && b !== 'movie') return -1; if (a !== 'movie' && b === 'movie') return 1; return 0; }); return sortedTypes.map((type) => ({ label: type === 'movie' ? '电影' : '剧集', value: type, })); }, [customCategories]); // 根据选中的一级选项生成二级选择器选项 const secondaryOptions = React.useMemo(() => { if (!primarySelection) return []; return customCategories .filter((cat) => cat.type === primarySelection) .map((cat) => ({ label: cat.name || cat.query, value: cat.query, })); }, [customCategories, primarySelection]); // 处理二级选择器的鼠标滚轮事件(原生 DOM 事件) const handleSecondaryWheel = React.useCallback((e: WheelEvent) => { e.preventDefault(); e.stopPropagation(); const container = secondaryScrollContainerRef.current; if (container) { const scrollAmount = e.deltaY * 2; container.scrollLeft += scrollAmount; } }, []); // 添加二级选择器的鼠标滚轮事件监听器 useEffect(() => { const scrollContainer = secondaryScrollContainerRef.current; const capsuleContainer = secondaryContainerRef.current; if (scrollContainer && capsuleContainer) { // 同时监听滚动容器和胶囊容器的滚轮事件 scrollContainer.addEventListener('wheel', handleSecondaryWheel, { passive: false, }); capsuleContainer.addEventListener('wheel', handleSecondaryWheel, { passive: false, }); return () => { scrollContainer.removeEventListener('wheel', handleSecondaryWheel); capsuleContainer.removeEventListener('wheel', handleSecondaryWheel); }; } }, [handleSecondaryWheel]); // 当二级选项变化时重新添加事件监听器 useEffect(() => { const scrollContainer = secondaryScrollContainerRef.current; const capsuleContainer = secondaryContainerRef.current; if (scrollContainer && capsuleContainer && secondaryOptions.length > 0) { // 重新添加事件监听器 scrollContainer.addEventListener('wheel', handleSecondaryWheel, { passive: false, }); capsuleContainer.addEventListener('wheel', handleSecondaryWheel, { passive: false, }); return () => { scrollContainer.removeEventListener('wheel', handleSecondaryWheel); capsuleContainer.removeEventListener('wheel', handleSecondaryWheel); }; } }, [handleSecondaryWheel, secondaryOptions]); // 更新指示器位置的通用函数 const updateIndicatorPosition = ( activeIndex: number, containerRef: React.RefObject, buttonRefs: React.MutableRefObject<(HTMLButtonElement | null)[]>, setIndicatorStyle: React.Dispatch< React.SetStateAction<{ left: number; width: number }> > ) => { if ( activeIndex >= 0 && buttonRefs.current[activeIndex] && containerRef.current ) { const timeoutId = setTimeout(() => { const button = buttonRefs.current[activeIndex]; const container = containerRef.current; if (button && container) { const buttonRect = button.getBoundingClientRect(); const containerRect = container.getBoundingClientRect(); if (buttonRect.width > 0) { setIndicatorStyle({ left: buttonRect.left - containerRect.left, width: buttonRect.width, }); } } }, 0); return () => clearTimeout(timeoutId); } }; // 组件挂载时立即计算初始位置 useEffect(() => { // 主选择器初始位置 if (primaryOptions.length > 0) { const activeIndex = primaryOptions.findIndex( (opt) => opt.value === (primarySelection || primaryOptions[0].value) ); updateIndicatorPosition( activeIndex, primaryContainerRef, primaryButtonRefs, setPrimaryIndicatorStyle ); } // 副选择器初始位置 if (secondaryOptions.length > 0) { const activeIndex = secondaryOptions.findIndex( (opt) => opt.value === (secondarySelection || secondaryOptions[0].value) ); updateIndicatorPosition( activeIndex, secondaryContainerRef, secondaryButtonRefs, setSecondaryIndicatorStyle ); } }, [primaryOptions, secondaryOptions]); // 当选项变化时重新计算 // 监听主选择器变化 useEffect(() => { if (primaryOptions.length > 0) { const activeIndex = primaryOptions.findIndex( (opt) => opt.value === primarySelection ); const cleanup = updateIndicatorPosition( activeIndex, primaryContainerRef, primaryButtonRefs, setPrimaryIndicatorStyle ); return cleanup; } }, [primarySelection, primaryOptions]); // 监听副选择器变化 useEffect(() => { if (secondaryOptions.length > 0) { const activeIndex = secondaryOptions.findIndex( (opt) => opt.value === secondarySelection ); const cleanup = updateIndicatorPosition( activeIndex, secondaryContainerRef, secondaryButtonRefs, setSecondaryIndicatorStyle ); return cleanup; } }, [secondarySelection, secondaryOptions]); // 渲染胶囊式选择器 const renderCapsuleSelector = ( options: { label: string; value: string }[], activeValue: string | undefined, onChange: (value: string) => void, isPrimary = false ) => { const containerRef = isPrimary ? primaryContainerRef : secondaryContainerRef; const buttonRefs = isPrimary ? primaryButtonRefs : secondaryButtonRefs; const indicatorStyle = isPrimary ? primaryIndicatorStyle : secondaryIndicatorStyle; return (
{/* 滑动的白色背景指示器 */} {indicatorStyle.width > 0 && (
)} {options.map((option, index) => { const isActive = activeValue === option.value; return ( ); })}
); }; // 如果没有自定义分类,则不渲染任何内容 if (!customCategories || customCategories.length === 0) { return null; } return (
{/* 两级选择器包装 */}
{/* 一级选择器 */}
类型
{renderCapsuleSelector( primaryOptions, primarySelection || primaryOptions[0]?.value, onPrimaryChange, true )}
{/* 二级选择器 */} {secondaryOptions.length > 0 && (
片单
{renderCapsuleSelector( secondaryOptions, secondarySelection || secondaryOptions[0]?.value, onSecondaryChange, false )}
)}
); }; export default DoubanCustomSelector;