feat: support save live channel to favorite

This commit is contained in:
shinya
2025-08-26 16:36:44 +08:00
parent fa46e05c7c
commit 15b20603b8
12 changed files with 434 additions and 49 deletions
+7 -2
View File
@@ -1,4 +1,4 @@
import { X } from 'lucide-react';
import { Radio, X } from 'lucide-react';
import Image from 'next/image';
import React, { useEffect, useState } from 'react';
@@ -22,6 +22,7 @@ interface MobileActionSheetProps {
sourceName?: string; // 播放源名称
currentEpisode?: number; // 当前集数
totalEpisodes?: number; // 总集数
origin?: 'vod' | 'live';
}
const MobileActionSheet: React.FC<MobileActionSheetProps> = ({
@@ -35,6 +36,7 @@ const MobileActionSheet: React.FC<MobileActionSheetProps> = ({
sourceName,
currentEpisode,
totalEpisodes,
origin = 'vod',
}) => {
const [isVisible, setIsVisible] = useState(false);
const [isAnimating, setIsAnimating] = useState(false);
@@ -217,7 +219,7 @@ const MobileActionSheet: React.FC<MobileActionSheetProps> = ({
src={poster}
alt={title}
fill
className="object-cover"
className={origin === 'live' ? 'object-contain' : 'object-cover'}
loading="lazy"
/>
</div>
@@ -229,6 +231,9 @@ const MobileActionSheet: React.FC<MobileActionSheetProps> = ({
</h3>
{sourceName && (
<span className="flex-shrink-0 text-xs px-2 py-1 border border-gray-300 dark:border-gray-600 rounded text-gray-600 dark:text-gray-400 bg-gray-50 dark:bg-gray-800">
{origin === 'live' && (
<Radio size={12} className="inline-block text-gray-500 dark:text-gray-400 mr-1.5" />
)}
{sourceName}
</span>
)}
+24 -9
View File
@@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/no-explicit-any,react-hooks/exhaustive-deps,@typescript-eslint/no-empty-function */
import { ExternalLink, Heart, Link, PlayCircleIcon, Trash2 } from 'lucide-react';
import { ExternalLink, Heart, Link, PlayCircleIcon, Radio, Trash2 } from 'lucide-react';
import Image from 'next/image';
import { useRouter } from 'next/navigation';
import React, {
@@ -46,6 +46,7 @@ export interface VideoCardProps {
type?: string;
isBangumi?: boolean;
isAggregate?: boolean;
origin?: 'vod' | 'live';
}
export type VideoCardHandle = {
@@ -74,6 +75,7 @@ const VideoCard = forwardRef<VideoCardHandle, VideoCardProps>(function VideoCard
type = '',
isBangumi = false,
isAggregate = false,
origin = 'vod',
}: VideoCardProps,
ref
) {
@@ -221,7 +223,11 @@ const VideoCard = forwardRef<VideoCardHandle, VideoCardProps>(function VideoCard
);
const handleClick = useCallback(() => {
if (from === 'douban' || (isAggregate && !actualSource && !actualId)) {
if (origin === 'live' && actualSource && actualId) {
// 直播内容跳转到直播页面
const url = `/live?source=${actualSource.replace('live_', '')}&id=${actualId.replace('live_', '')}`;
router.push(url);
} else if (from === 'douban' || (isAggregate && !actualSource && !actualId)) {
const url = `/play?title=${encodeURIComponent(actualTitle.trim())}${actualYear ? `&year=${actualYear}` : ''
}${actualSearchType ? `&stype=${actualSearchType}` : ''}${isAggregate ? '&prefer=true' : ''}${actualQuery ? `&stitle=${encodeURIComponent(actualQuery.trim())}` : ''}`;
router.push(url);
@@ -234,6 +240,7 @@ const VideoCard = forwardRef<VideoCardHandle, VideoCardProps>(function VideoCard
router.push(url);
}
}, [
origin,
from,
actualSource,
actualId,
@@ -247,9 +254,12 @@ const VideoCard = forwardRef<VideoCardHandle, VideoCardProps>(function VideoCard
// 新标签页播放处理函数
const handlePlayInNewTab = useCallback(() => {
if (from === 'douban' || (isAggregate && !actualSource && !actualId)) {
const url = `/play?title=${encodeURIComponent(actualTitle.trim())}${actualYear ? `&year=${actualYear}` : ''
}${actualSearchType ? `&stype=${actualSearchType}` : ''}${isAggregate ? '&prefer=true' : ''}${actualQuery ? `&stitle=${encodeURIComponent(actualQuery.trim())}` : ''}`;
if (origin === 'live' && actualSource && actualId) {
// 直播内容跳转到直播页面
const url = `/live?source=${actualSource.replace('live_', '')}&id=${actualId.replace('live_', '')}`;
window.open(url, '_blank');
} else if (from === 'douban' || (isAggregate && !actualSource && !actualId)) {
const url = `/play?title=${encodeURIComponent(actualTitle.trim())}${actualYear ? `&year=${actualYear}` : ''}${actualSearchType ? `&stype=${actualSearchType}` : ''}${isAggregate ? '&prefer=true' : ''}${actualQuery ? `&stitle=${encodeURIComponent(actualQuery.trim())}` : ''}`;
window.open(url, '_blank');
} else if (actualSource && actualId) {
const url = `/play?source=${actualSource}&id=${actualId}&title=${encodeURIComponent(
@@ -260,6 +270,7 @@ const VideoCard = forwardRef<VideoCardHandle, VideoCardProps>(function VideoCard
window.open(url, '_blank');
}
}, [
origin,
from,
actualSource,
actualId,
@@ -356,7 +367,7 @@ const VideoCard = forwardRef<VideoCardHandle, VideoCardProps>(function VideoCard
if (config.showPlayButton) {
actions.push({
id: 'play',
label: '播放',
label: origin === 'live' ? '观看直播' : '播放',
icon: <PlayCircleIcon size={20} />,
onClick: handleClick,
color: 'primary' as const,
@@ -365,7 +376,7 @@ const VideoCard = forwardRef<VideoCardHandle, VideoCardProps>(function VideoCard
// 新标签页播放
actions.push({
id: 'play-new-tab',
label: '新标签页播放',
label: origin === 'live' ? '新标签页观看' : '新标签页播放',
icon: <ExternalLink size={20} />,
onClick: handlePlayInNewTab,
color: 'default' as const,
@@ -521,7 +532,7 @@ const VideoCard = forwardRef<VideoCardHandle, VideoCardProps>(function VideoCard
>
{/* 海报容器 */}
<div
className='relative aspect-[2/3] overflow-hidden rounded-lg'
className={`relative aspect-[2/3] overflow-hidden rounded-lg ${origin === 'live' ? 'ring-1 ring-gray-300/80 dark:ring-gray-600/80' : ''}`}
style={{
WebkitUserSelect: 'none',
userSelect: 'none',
@@ -539,7 +550,7 @@ const VideoCard = forwardRef<VideoCardHandle, VideoCardProps>(function VideoCard
src={processImageUrl(actualPoster)}
alt={actualTitle}
fill
className='object-cover'
className={origin === 'live' ? 'object-contain' : 'object-cover'}
referrerPolicy='no-referrer'
loading='lazy'
onLoadingComplete={() => setIsLoading(true)}
@@ -1004,6 +1015,9 @@ const VideoCard = forwardRef<VideoCardHandle, VideoCardProps>(function VideoCard
return false;
}}
>
{origin === 'live' && (
<Radio size={12} className="inline-block text-gray-500 dark:text-gray-400 mr-1.5" />
)}
{source_name}
</span>
</span>
@@ -1023,6 +1037,7 @@ const VideoCard = forwardRef<VideoCardHandle, VideoCardProps>(function VideoCard
sourceName={source_name}
currentEpisode={currentEpisode}
totalEpisodes={actualEpisodes}
origin={origin}
/>
</>
);