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 ||