/* eslint-disable react-hooks/exhaustive-deps */ 'use client'; import React, { useEffect, useRef, useState } from 'react'; interface SelectorOption { label: string; value: string; } interface DoubanSelectorProps { type: 'movie' | 'tv' | 'show'; primarySelection?: string; secondarySelection?: string; onPrimaryChange: (value: string) => void; onSecondaryChange: (value: string) => void; } const DoubanSelector: React.FC = ({ type, 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 }); // 电影的一级选择器选项 const moviePrimaryOptions: SelectorOption[] = [ { label: '热门电影', value: '热门' }, { label: '最新电影', value: '最新' }, { label: '豆瓣高分', value: '豆瓣高分' }, { label: '冷门佳片', value: '冷门佳片' }, ]; // 电影的二级选择器选项 const movieSecondaryOptions: SelectorOption[] = [ { label: '全部', value: '全部' }, { label: '华语', value: '华语' }, { label: '欧美', value: '欧美' }, { label: '韩国', value: '韩国' }, { label: '日本', value: '日本' }, ]; // 电视剧选择器选项 const tvOptions: SelectorOption[] = [ { label: '全部', value: 'tv' }, { label: '国产', value: 'tv_domestic' }, { label: '欧美', value: 'tv_american' }, { label: '日本', value: 'tv_japanese' }, { label: '韩国', value: 'tv_korean' }, { label: '动漫', value: 'tv_animation' }, { label: '纪录片', value: 'tv_documentary' }, ]; // 综艺选择器选项 const showOptions: SelectorOption[] = [ { label: '全部', value: 'show' }, { label: '国内', value: 'show_domestic' }, { label: '国外', value: 'show_foreign' }, ]; // 更新指示器位置的通用函数 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 (type === 'movie') { const activeIndex = moviePrimaryOptions.findIndex( (opt) => opt.value === (primarySelection || moviePrimaryOptions[0].value) ); updateIndicatorPosition( activeIndex, primaryContainerRef, primaryButtonRefs, setPrimaryIndicatorStyle ); } // 副选择器初始位置 let secondaryActiveIndex = -1; if (type === 'movie') { secondaryActiveIndex = movieSecondaryOptions.findIndex( (opt) => opt.value === (secondarySelection || movieSecondaryOptions[0].value) ); } else if (type === 'tv') { secondaryActiveIndex = tvOptions.findIndex( (opt) => opt.value === (secondarySelection || tvOptions[0].value) ); } else if (type === 'show') { secondaryActiveIndex = showOptions.findIndex( (opt) => opt.value === (secondarySelection || showOptions[0].value) ); } if (secondaryActiveIndex >= 0) { updateIndicatorPosition( secondaryActiveIndex, secondaryContainerRef, secondaryButtonRefs, setSecondaryIndicatorStyle ); } }, [type]); // 只在type变化时重新计算 // 监听主选择器变化 useEffect(() => { if (type === 'movie') { const activeIndex = moviePrimaryOptions.findIndex( (opt) => opt.value === primarySelection ); const cleanup = updateIndicatorPosition( activeIndex, primaryContainerRef, primaryButtonRefs, setPrimaryIndicatorStyle ); return cleanup; } }, [primarySelection]); // 监听副选择器变化 useEffect(() => { let activeIndex = -1; let options: SelectorOption[] = []; if (type === 'movie') { activeIndex = movieSecondaryOptions.findIndex( (opt) => opt.value === secondarySelection ); options = movieSecondaryOptions; } else if (type === 'tv') { activeIndex = tvOptions.findIndex( (opt) => opt.value === secondarySelection ); options = tvOptions; } else if (type === 'show') { activeIndex = showOptions.findIndex( (opt) => opt.value === secondarySelection ); options = showOptions; } if (options.length > 0) { const cleanup = updateIndicatorPosition( activeIndex, secondaryContainerRef, secondaryButtonRefs, setSecondaryIndicatorStyle ); return cleanup; } }, [secondarySelection]); // 渲染胶囊式选择器 const renderCapsuleSelector = ( options: SelectorOption[], 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 ( ); })}
); }; return (
{/* 电影类型 - 显示两级选择器 */} {type === 'movie' && (
{/* 一级选择器 */}
分类
{renderCapsuleSelector( moviePrimaryOptions, primarySelection || moviePrimaryOptions[0].value, onPrimaryChange, true )}
{/* 二级选择器 */}
地区
{renderCapsuleSelector( movieSecondaryOptions, secondarySelection || movieSecondaryOptions[0].value, onSecondaryChange, false )}
)} {/* 电视剧类型 - 只显示一级选择器 */} {type === 'tv' && (
类型
{renderCapsuleSelector( tvOptions, secondarySelection || tvOptions[0].value, onSecondaryChange, false )}
)} {/* 综艺类型 - 只显示一级选择器 */} {type === 'show' && (
类型
{renderCapsuleSelector( showOptions, secondarySelection || showOptions[0].value, onSecondaryChange, false )}
)}
); }; export default DoubanSelector;