diff --git a/web/src/components/MediaCard.tsx b/web/src/components/MediaCard.tsx index 4965d1a..b8c978a 100644 --- a/web/src/components/MediaCard.tsx +++ b/web/src/components/MediaCard.tsx @@ -10,13 +10,14 @@ const ACTION_OVERLAY_CLASS = // memo:父级状态变化(如轮播切图、其它卡片操作)不再级联重渲染所有卡片。 // 注意 actions/renderActions 必须引用稳定(用 renderActions 传函数)memo 才生效。 export const MediaCard = memo(function MediaCard({ - media, progress, count, rating, linkTo, onClick, actions, renderActions, compact, + media, progress, count, rating, linkTo, linkState, onClick, actions, renderActions, compact, }: { media: Media progress?: number count?: number rating?: number linkTo?: string + linkState?: { from?: string } onClick?: () => void actions?: ReactNode renderActions?: (media: Media) => ReactNode @@ -169,7 +170,7 @@ export const MediaCard = memo(function MediaCard({ if (actionContent) { return (
- + {card}
@@ -180,7 +181,7 @@ export const MediaCard = memo(function MediaCard({ } return ( - + {card} ) diff --git a/web/src/pages/FavouritesPage.tsx b/web/src/pages/FavouritesPage.tsx index ac27b8b..661214c 100644 --- a/web/src/pages/FavouritesPage.tsx +++ b/web/src/pages/FavouritesPage.tsx @@ -74,7 +74,7 @@ export function FavouritesPage() { {items.length > 0 && (
{items.map((m) => ( - + ))}
)} diff --git a/web/src/pages/MediaDetailPageSections.tsx b/web/src/pages/MediaDetailPageSections.tsx index de8477f..df116dc 100644 --- a/web/src/pages/MediaDetailPageSections.tsx +++ b/web/src/pages/MediaDetailPageSections.tsx @@ -85,7 +85,7 @@ export function MediaDetailBackButton({ onBack }: { onBack: () => void }) { className="btn-ghost gap-2 bg-white/80 shadow-sm backdrop-blur hover:bg-white" > - 返回媒体库 + 返回
) diff --git a/web/src/pages/WatchHistoryPage.tsx b/web/src/pages/WatchHistoryPage.tsx index 641fdf2..91b5c7e 100644 --- a/web/src/pages/WatchHistoryPage.tsx +++ b/web/src/pages/WatchHistoryPage.tsx @@ -127,6 +127,7 @@ export function WatchHistoryPage() {
{m.title} @@ -150,6 +151,7 @@ export function WatchHistoryPage() {
继续 diff --git a/web/src/pages/useMediaDetailPageState.ts b/web/src/pages/useMediaDetailPageState.ts index de26a53..e8c2b48 100644 --- a/web/src/pages/useMediaDetailPageState.ts +++ b/web/src/pages/useMediaDetailPageState.ts @@ -1,5 +1,5 @@ import { useCallback, useEffect, useMemo, useState, type Dispatch, type SetStateAction } from 'react' -import type { NavigateFunction } from 'react-router-dom' +import { useLocation, type NavigateFunction } from 'react-router-dom' import toast from 'react-hot-toast' import { api } from '../api/client' @@ -11,6 +11,8 @@ import type { Media } from '../types' import { seasonSortOrder } from '../utils/groupSeries' import { mediaLibraryBackTarget } from './MediaDetailPageModel' +type MediaDetailLocationState = { from?: string } | null + interface MediaDetailPageStateParams { id: string navigate: NavigateFunction @@ -29,6 +31,7 @@ interface MediaDetailActionsParams { media: Media | null scrapeEpisodeArtwork: boolean navigate: NavigateFunction + backFrom: string | undefined refresh: () => Promise setFavourite: Dispatch> } @@ -54,10 +57,13 @@ export function useMediaDetailPageState({ id, navigate }: MediaDetailPageStatePa setEpisodes, setLoadingEpisodes, }) + const location = useLocation() + const backFrom = (location.state as MediaDetailLocationState)?.from const actions = useMediaDetailActions({ media, scrapeEpisodeArtwork, navigate, + backFrom, refresh, setFavourite, }) @@ -173,10 +179,14 @@ function useMediaDetailActions({ media, scrapeEpisodeArtwork, navigate, + backFrom, refresh, setFavourite, }: MediaDetailActionsParams) { - const goBack = useCallback(() => goBackFromMediaDetail(media, navigate), [media, navigate]) + const goBack = useCallback( + () => goBackFromMediaDetail(media, navigate, backFrom), + [backFrom, media, navigate], + ) const toggleFavourite = useCallback( () => toggleMediaFavourite(media, setFavourite), [media, setFavourite], @@ -188,13 +198,34 @@ function useMediaDetailActions({ const reprobe = useCallback(() => reprobeMedia(media, refresh), [media, refresh]) const exportNFO = useCallback(() => exportMediaNFO(media), [media]) const deleteMedia = useCallback( - () => deleteMediaFromLibrary(media, navigate), - [media, navigate], + () => deleteMediaFromLibrary(media, navigate, backFrom), + [backFrom, media, navigate], ) return { goBack, toggleFavourite, rescrape, reprobe, exportNFO, deleteMedia } } -function goBackFromMediaDetail(media: Media | null, navigate: NavigateFunction, replace = false): void { +function canNavigateHistoryBack(): boolean { + if (typeof window === 'undefined') return false + const idx = (window.history.state as { idx?: number } | null)?.idx + return typeof idx === 'number' ? idx > 0 : window.history.length > 1 +} + +function goBackFromMediaDetail( + media: Media | null, + navigate: NavigateFunction, + from: string | undefined, + replace = false, +): void { + // Prefer the explicit return path (e.g. Favourites → Detail), then browser + // history, and only fall back to the owning library for direct/deep links. + if (typeof from === 'string' && from.length > 0) { + navigate(from, replace ? { replace: true } : undefined) + return + } + if (canNavigateHistoryBack()) { + navigate(-1) + return + } if (!media) { navigate('/libraries') return @@ -259,7 +290,11 @@ async function exportMediaNFO(media: Media | null): Promise { } } -async function deleteMediaFromLibrary(media: Media | null, navigate: NavigateFunction): Promise { +async function deleteMediaFromLibrary( + media: Media | null, + navigate: NavigateFunction, + from: string | undefined, +): Promise { if (!media) return const result = await confirmActionResult({ title: '删除媒体', @@ -275,7 +310,7 @@ async function deleteMediaFromLibrary(media: Media | null, navigate: NavigateFun return } toast.success(result.checked ? '已删除媒体及本地文件' : '已从媒体库删除') - goBackFromMediaDetail(media, navigate, true) + goBackFromMediaDetail(media, navigate, from, true) } function apiErrorMessage(err: unknown, fallback: string): string {