增加剧集屏蔽功能

This commit is contained in:
mtvpls
2025-12-15 00:16:50 +08:00
parent 9678361026
commit dc1462d744
5 changed files with 709 additions and 39 deletions
+99 -29
View File
@@ -8,10 +8,12 @@ import React, {
useRef,
useState,
} from 'react';
import { Settings } from 'lucide-react';
import DanmakuPanel from '@/components/DanmakuPanel';
import EpisodeFilterSettings from '@/components/EpisodeFilterSettings';
import type { DanmakuSelection } from '@/lib/danmaku/types';
import { SearchResult } from '@/lib/types';
import { SearchResult, EpisodeFilterConfig } from '@/lib/types';
import { getVideoResolutionFromM3u8, processImageUrl } from '@/lib/utils';
// 定义视频信息类型
@@ -49,6 +51,10 @@ interface EpisodeSelectorProps {
currentDanmakuSelection?: DanmakuSelection | null;
/** 观影室房员状态 - 禁用选集和换源,但保留弹幕 */
isRoomMember?: boolean;
/** 集数过滤配置 */
episodeFilterConfig?: EpisodeFilterConfig | null;
onFilterConfigUpdate?: (config: EpisodeFilterConfig) => void;
onShowToast?: (message: string, type: 'success' | 'error' | 'info') => void;
}
/**
@@ -71,6 +77,9 @@ const EpisodeSelector: React.FC<EpisodeSelectorProps> = ({
onDanmakuSelect,
currentDanmakuSelection,
isRoomMember = false,
episodeFilterConfig = null,
onFilterConfigUpdate,
onShowToast,
}) => {
const router = useRouter();
const pageCount = Math.ceil(totalEpisodes / episodesPerPage);
@@ -122,6 +131,46 @@ const EpisodeSelector: React.FC<EpisodeSelectorProps> = ({
// 是否倒序显示
const [descending, setDescending] = useState<boolean>(false);
// 集数过滤设置弹窗状态
const [showFilterSettings, setShowFilterSettings] = useState<boolean>(false);
// 集数过滤逻辑
const isEpisodeFiltered = useCallback(
(episodeNumber: number): boolean => {
if (!episodeFilterConfig || episodeFilterConfig.rules.length === 0) {
return false;
}
// 获取集数标题
const title = episodes_titles?.[episodeNumber - 1];
if (!title) return false;
// 检查每个启用的规则
for (const rule of episodeFilterConfig.rules) {
if (!rule.enabled) continue;
try {
if (rule.type === 'normal') {
// 普通模式:字符串包含匹配
if (title.includes(rule.keyword)) {
return true;
}
} else if (rule.type === 'regex') {
// 正则模式:正则表达式匹配
if (new RegExp(rule.keyword).test(title)) {
return true;
}
}
} catch (e) {
console.error('集数过滤规则错误:', e);
}
}
return false;
},
[episodeFilterConfig, episodes_titles]
);
// 根据 descending 状态计算实际显示的分页索引
const displayPage = useMemo(() => {
if (descending) {
@@ -549,6 +598,14 @@ const EpisodeSelector: React.FC<EpisodeSelectorProps> = ({
/>
</svg>
</button>
{/* 集数屏蔽配置按钮 */}
<button
className='flex-shrink-0 w-8 h-8 rounded-md flex items-center justify-center text-gray-700 hover:text-green-600 hover:bg-gray-100 dark:text-gray-300 dark:hover:text-green-400 dark:hover:bg-white/20 transition-colors transform translate-y-[-4px]'
onClick={() => setShowFilterSettings(true)}
title='集数屏蔽设置'
>
<Settings className='w-4 h-4' />
</button>
</div>
{/* 集数网格 */}
@@ -558,34 +615,37 @@ const EpisodeSelector: React.FC<EpisodeSelectorProps> = ({
const episodes = Array.from({ length: len }, (_, i) =>
descending ? currentEnd - i : currentStart + i
);
return episodes;
})().map((episodeNumber) => {
const isActive = episodeNumber === value;
return (
<button
key={episodeNumber}
onClick={() => handleEpisodeClick(episodeNumber - 1)}
className={`h-10 min-w-10 px-3 py-2 flex items-center justify-center text-sm font-medium rounded-md transition-all duration-200 whitespace-nowrap font-mono
${isActive
? 'bg-green-500 text-white shadow-lg shadow-green-500/25 dark:bg-green-600'
: 'bg-gray-200 text-gray-700 hover:bg-gray-300 hover:scale-105 dark:bg-white/10 dark:text-gray-300 dark:hover:bg-white/20'
}`.trim()}
>
{(() => {
const title = episodes_titles?.[episodeNumber - 1];
if (!title) {
return episodeNumber;
}
// 如果匹配"第X集"、"第X话"、"X集"、"X话"格式,提取中间的数字
const match = title.match(/(?:第)?(\d+)(?:集|话)/);
if (match) {
return match[1];
}
return title;
})()}
</button>
);
})}
// 过滤掉被屏蔽的集数,但保持原有索引
return episodes
.filter(episodeNumber => !isEpisodeFiltered(episodeNumber))
.map((episodeNumber) => {
const isActive = episodeNumber === value;
return (
<button
key={episodeNumber}
onClick={() => handleEpisodeClick(episodeNumber - 1)}
className={`h-10 min-w-10 px-3 py-2 flex items-center justify-center text-sm font-medium rounded-md transition-all duration-200 whitespace-nowrap font-mono
${isActive
? 'bg-green-500 text-white shadow-lg shadow-green-500/25 dark:bg-green-600'
: 'bg-gray-200 text-gray-700 hover:bg-gray-300 hover:scale-105 dark:bg-white/10 dark:text-gray-300 dark:hover:bg-white/20'
}`.trim()}
>
{(() => {
const title = episodes_titles?.[episodeNumber - 1];
if (!title) {
return episodeNumber;
}
// 如果匹配"第X集"、"第X话"、"X集"、"X话"格式,提取中间的数字
const match = title.match(/(?:第)?(\d+)(?:集|话)/);
if (match) {
return match[1];
}
return title;
})()}
</button>
);
});
})()}
</div>
</>
)}
@@ -833,6 +893,16 @@ const EpisodeSelector: React.FC<EpisodeSelectorProps> = ({
)}
</div>
)}
{/* 集数过滤设置弹窗 */}
<EpisodeFilterSettings
isOpen={showFilterSettings}
onClose={() => setShowFilterSettings(false)}
onConfigUpdate={(config) => {
onFilterConfigUpdate?.(config);
}}
onShowToast={onShowToast}
/>
</div>
);
};