From 8f2551a1b635626bc84e90a02386fc53a010471b Mon Sep 17 00:00:00 2001
From: truewhile <62226914+truewhile@users.noreply.github.com>
Date: Wed, 9 Sep 2026 22:24:45 +0800
Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=8E=92=E5=BA=8F?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
web/src/components/PlayerPlaylistPanel.tsx | 6 +--
web/src/pages/LibrarySeriesEpisodes.tsx | 29 +--------------
web/src/pages/useLibrarySeriesSelection.ts | 30 +--------------
web/src/pages/useMediaDetailPageState.ts | 3 +-
web/src/utils/groupSeries.ts | 43 ++++++++++++++++++++++
5 files changed, 50 insertions(+), 61 deletions(-)
diff --git a/web/src/components/PlayerPlaylistPanel.tsx b/web/src/components/PlayerPlaylistPanel.tsx
index 017faa9..0914811 100644
--- a/web/src/components/PlayerPlaylistPanel.tsx
+++ b/web/src/components/PlayerPlaylistPanel.tsx
@@ -3,7 +3,7 @@ import { Check, Film, ListVideo, Play, Search, X } from 'lucide-react'
import { imageURL } from '../api/client'
import type { Media } from '../types'
-import { seriesTitleFromPath } from '../utils/groupSeries'
+import { seasonLabel, seasonSortOrder, seriesTitleFromPath } from '../utils/groupSeries'
export type SeasonGroup = {
season: number
@@ -42,7 +42,7 @@ export function PlayerPlaylistPanel({
list.sort((a, b) => (a.episode_num || 0) - (b.episode_num || 0))
}
return Array.from(seasonsMap.entries())
- .sort(([a], [b]) => a - b)
+ .sort(([a], [b]) => seasonSortOrder(a) - seasonSortOrder(b))
.map(([season, list]) => ({ season, episodes: list }))
}, [episodes])
@@ -128,7 +128,7 @@ export function PlayerPlaylistPanel({
: 'bg-white/5 text-white/70 hover:bg-white/10 hover:text-white'
}`}
>
- {season === 0 ? '特别篇' : `第 ${season} 季`}
+ {seasonLabel(season)}
({sesEps.length})
{isPlayingThisSeason && !isSelected && (
diff --git a/web/src/pages/LibrarySeriesEpisodes.tsx b/web/src/pages/LibrarySeriesEpisodes.tsx
index f3dcf4a..aebf0eb 100644
--- a/web/src/pages/LibrarySeriesEpisodes.tsx
+++ b/web/src/pages/LibrarySeriesEpisodes.tsx
@@ -5,17 +5,8 @@ import { imageURL } from '../api/client'
import { ExternalPlayerButton } from '../components/ExternalPlayerButton'
import type { Media } from '../types'
import {
- BONUS_SEASON,
- EXTRA_SEASON,
isTheatricalFeature,
- NCED_SEASON,
- NCOP_SEASON,
- OAD_SEASON,
- OMAKE_SEASON,
- ONA_SEASON,
- OVA_SEASON,
- OVD_SEASON,
- PICTURE_DRAMA_SEASON,
+ seasonLabel,
seriesTitleFromPath,
THEATRICAL_SEASON,
} from '../utils/groupSeries'
@@ -148,24 +139,6 @@ export function LibrarySeriesEpisodes({
)
}
-function seasonLabel(season: number): string {
- switch (season) {
- case 0: return '特别篇'
- case THEATRICAL_SEASON: return '剧场版'
- case OVA_SEASON: return 'OVA'
- case OAD_SEASON: return 'OAD'
- case OVD_SEASON: return 'OVD'
- case ONA_SEASON: return 'ONA'
- case EXTRA_SEASON: return 'Extra'
- case BONUS_SEASON: return 'Bonus'
- case OMAKE_SEASON: return 'Omake'
- case PICTURE_DRAMA_SEASON: return 'Picture Drama'
- case NCOP_SEASON: return 'NCOP'
- case NCED_SEASON: return 'NCED'
- default: return `第 ${season} 季`
- }
-}
-
function episodeDisplayTitle(ep: Media, siblings: Media[]): string {
let mainTitle = ''
const epTitle = ep.episode_title?.trim()
diff --git a/web/src/pages/useLibrarySeriesSelection.ts b/web/src/pages/useLibrarySeriesSelection.ts
index 53528e3..12764e3 100644
--- a/web/src/pages/useLibrarySeriesSelection.ts
+++ b/web/src/pages/useLibrarySeriesSelection.ts
@@ -2,20 +2,10 @@ import { useEffect, useMemo } from 'react'
import type { Media } from '../types'
import {
- BONUS_SEASON,
- EXTRA_SEASON,
getSeriesKey,
isTheatricalFeature,
- NCED_SEASON,
- NCOP_SEASON,
- OAD_SEASON,
- OMAKE_SEASON,
- ONA_SEASON,
- OVA_SEASON,
- OVD_SEASON,
- PICTURE_DRAMA_SEASON,
+ seasonSortOrder,
specialSectionForMedia,
- THEATRICAL_SEASON,
type SeriesCard,
} from '../utils/groupSeries'
@@ -146,21 +136,3 @@ export function useLibrarySeriesSelection({
clearSelectedSeries,
}
}
-
-function seasonSortOrder(season: number): number {
- switch (season) {
- case 0: return 0
- case OVA_SEASON: return 1
- case OAD_SEASON: return 2
- case OVD_SEASON: return 3
- case ONA_SEASON: return 4
- case EXTRA_SEASON: return 5
- case BONUS_SEASON: return 6
- case OMAKE_SEASON: return 7
- case PICTURE_DRAMA_SEASON: return 8
- case NCOP_SEASON: return 9
- case NCED_SEASON: return 10
- case THEATRICAL_SEASON: return 11
- default: return 100 + season
- }
-}
diff --git a/web/src/pages/useMediaDetailPageState.ts b/web/src/pages/useMediaDetailPageState.ts
index 8e9d796..68537ed 100644
--- a/web/src/pages/useMediaDetailPageState.ts
+++ b/web/src/pages/useMediaDetailPageState.ts
@@ -8,6 +8,7 @@ import { playbackAPI } from '../api/playback'
import { confirmActionResult } from '../components/confirmAction'
import { useEpisodeArtworkPreference } from '../hooks/useEpisodeArtworkPreference'
import type { Media } from '../types'
+import { seasonSortOrder } from '../utils/groupSeries'
import { mediaLibraryBackTarget } from './MediaDetailPageModel'
interface MediaDetailPageStateParams {
@@ -77,7 +78,7 @@ export function useMediaDetailPageState({ id, navigate }: MediaDetailPageStatePa
list.sort((a, b) => (a.episode_num || 0) - (b.episode_num || 0))
}
return Array.from(seasons.entries())
- .sort(([a], [b]) => a - b)
+ .sort(([a], [b]) => seasonSortOrder(a) - seasonSortOrder(b))
.map(([season, list]) => ({ season, episodes: list }))
}, [episodes])
diff --git a/web/src/utils/groupSeries.ts b/web/src/utils/groupSeries.ts
index 825f509..19100eb 100644
--- a/web/src/utils/groupSeries.ts
+++ b/web/src/utils/groupSeries.ts
@@ -151,6 +151,49 @@ export function specialSectionForMedia(media: Media): number | null {
return null
}
+/**
+ * 季按钮显示顺序:第一季、第二季 … 第 XX 季 → 特别篇 → 剧场版 → OVA → OAD → 其余特典。
+ *
+ * 正片季(季号 > 0)按季号升序排最前;特殊分区(0 / 负数季)按固定优先级排在后面。
+ * 各季分组页(剧集库 / 详情页 / 播放器选集)共用这一顺序,避免三处各排各的。
+ */
+export function seasonSortOrder(season: number): number {
+ if (season > 0) return season
+ switch (season) {
+ case 0: return 1000 // 特别篇紧随正片
+ case THEATRICAL_SEASON: return 1001 // 剧场版
+ case OVA_SEASON: return 1002
+ case OAD_SEASON: return 1003
+ case OVD_SEASON: return 1004
+ case ONA_SEASON: return 1005
+ case EXTRA_SEASON: return 1006
+ case BONUS_SEASON: return 1007
+ case OMAKE_SEASON: return 1008
+ case PICTURE_DRAMA_SEASON: return 1009
+ case NCOP_SEASON: return 1010
+ case NCED_SEASON: return 1011
+ default: return 2000 // 未知季兜底,排最后
+ }
+}
+
+export function seasonLabel(season: number): string {
+ switch (season) {
+ case 0: return '特别篇'
+ case THEATRICAL_SEASON: return '剧场版'
+ case OVA_SEASON: return 'OVA'
+ case OAD_SEASON: return 'OAD'
+ case OVD_SEASON: return 'OVD'
+ case ONA_SEASON: return 'ONA'
+ case EXTRA_SEASON: return 'Extra'
+ case BONUS_SEASON: return 'Bonus'
+ case OMAKE_SEASON: return 'Omake'
+ case PICTURE_DRAMA_SEASON: return 'Picture Drama'
+ case NCOP_SEASON: return 'NCOP'
+ case NCED_SEASON: return 'NCED'
+ default: return `第 ${season} 季`
+ }
+}
+
export function isSeriesCard(card: SeriesCard): boolean {
return (
card.count > 1 ||