From 0c4668c156a5af8b2f7e3f0ea8ff7f25751a392a Mon Sep 17 00:00:00 2001
From: truewhile <62226914+truewhile@users.noreply.github.com>
Date: Sat, 19 Sep 2026 21:30:32 +0800
Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
web/src/components/MediaCard.tsx | 7 ++--
web/src/pages/FavouritesPage.tsx | 2 +-
web/src/pages/MediaDetailPageSections.tsx | 2 +-
web/src/pages/WatchHistoryPage.tsx | 2 +
web/src/pages/useMediaDetailPageState.ts | 49 +++++++++++++++++++----
5 files changed, 50 insertions(+), 12 deletions(-)
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 {