优化支持0.5集

This commit is contained in:
truewhile
2026-09-27 13:52:17 +08:00
parent cb539afd2a
commit bad07f7add
32 changed files with 537 additions and 158 deletions
+7 -2
View File
@@ -4,6 +4,7 @@ import toast from 'react-hot-toast'
import { mediaAPI, type MediaMetadataUpdate } from '../api/library'
import type { Media } from '../types'
import { formatEpisodeNumber } from '../utils/episodeNumber'
interface MetadataEditDialogProps {
open: boolean
@@ -58,7 +59,7 @@ export function MetadataEditDialog({
release_date: media.release_date || '',
rating: media.rating > 0 ? String(media.rating) : '',
season_num: media.season_num > 0 || media.episode_num > 0 ? String(media.season_num || 0) : '',
episode_num: media.episode_num > 0 ? String(media.episode_num) : '',
episode_num: media.episode_num > 0 ? formatEpisodeNumber(media) : '',
tmdb_id: media.tmdb_id > 0 ? String(media.tmdb_id) : '',
bangumi_id: media.bangumi_id > 0 ? String(media.bangumi_id) : '',
douban_id: media.douban_id || '',
@@ -105,7 +106,11 @@ export function MetadataEditDialog({
if (!isSeries) {
payload.original_name = form.original_name
payload.season_num = Math.trunc(toNumber(form.season_num))
payload.episode_num = Math.trunc(toNumber(form.episode_num))
// 「集」输入框接受 11.5 这种半集写法:整数部分进 episode_num,
// 小数部分单独进 episode_fraction,避免保存后变成整集或残留小数。
const episodeValue = toNumber(form.episode_num)
payload.episode_num = Math.trunc(episodeValue)
payload.episode_fraction = Number((episodeValue - Math.trunc(episodeValue)).toFixed(3))
}
return payload
}
@@ -3,6 +3,7 @@ import type { ReactNode } from 'react'
import { ChevronDown, Layers, ListVideo } from 'lucide-react'
import type { Media } from '../types'
import { formatEpisodeLabel, formatEpisodeNumber } from '../utils/episodeNumber'
import { mediaVersionLabel, mediaVersionMatches } from '../utils/mediaVersion'
// PlayerMobileTheaterInfo — 竖屏剧场模式下视频下方的可滚动内容区。
@@ -38,15 +39,17 @@ type PlayerMobileTheaterInfoProps = {
}
function episodeShortLabel(ep: Media): string {
if (ep.episode_num > 0) return `${ep.episode_num}`
const number = formatEpisodeNumber(ep)
if (number) return number
const title = ep.episode_title?.trim() || ep.title?.trim() || ''
return title ? title.slice(0, 4) : '·'
}
function episodeFullLabel(ep: Media): string {
if (ep.episode_num > 0) {
const label = formatEpisodeLabel(ep)
if (label) {
const title = ep.episode_title?.trim()
return title && title !== ep.title?.trim() ? `第 ${ep.episode_num} 集 · ${title}` : `第 ${ep.episode_num} 集`
return title && title !== ep.title?.trim() ? `${label} · ${title}` : label
}
return ep.episode_title?.trim() || ep.title?.trim() || '未命名'
}
+7 -5
View File
@@ -2,6 +2,7 @@ import { useEffect, useMemo, useRef, useState } from 'react'
import { Check, Cloud, Film, Layers, Play, Search, X } from 'lucide-react'
import type { Media } from '../types'
import { formatEpisodeLabel, formatEpisodeNumber, episodeNumberValue } from '../utils/episodeNumber'
import { seasonLabel, seasonSortOrder, seriesTitleFromPath } from '../utils/groupSeries'
import {
isStrmMedia,
@@ -72,7 +73,7 @@ export function PlayerPlaylistPanel({
seasonsMap.get(s)!.push(ep)
}
for (const [, list] of seasonsMap) {
list.sort((a, b) => (a.episode_num || 0) - (b.episode_num || 0))
list.sort((a, b) => episodeNumberValue(a) - episodeNumberValue(b))
}
return Array.from(seasonsMap.entries())
.sort(([a], [b]) => seasonSortOrder(a) - seasonSortOrder(b))
@@ -118,7 +119,7 @@ export function PlayerPlaylistPanel({
? listToDisplay.filter((ep) => {
const query = filterText.trim().toLowerCase()
const title = (ep.episode_title || ep.title || '').toLowerCase()
const epNum = String(ep.episode_num)
const epNum = formatEpisodeNumber(ep)
return title.includes(query) || epNum === query || `e${epNum}`.includes(query) || `第${epNum}集`.includes(query)
})
: listToDisplay
@@ -293,7 +294,7 @@ export function PlayerPlaylistPanel({
const durationText =
ep.duration_sec > 0 ? `${Math.round(ep.duration_sec / 60)} 分钟` : ''
const cellTitle = [
ep.episode_num > 0 ? `第 ${ep.episode_num} 集` : '',
formatEpisodeLabel(ep),
displayTitle,
durationText,
versions.length > 0 ? `${versions.length} 个版本` : '',
@@ -316,7 +317,7 @@ export function PlayerPlaylistPanel({
{isPlaying ? (
<Play size={12} className="fill-current" />
) : ep.episode_num > 0 ? (
ep.episode_num
formatEpisodeNumber(ep)
) : (
<Film size={13} className="text-white/45" />
)}
@@ -429,7 +430,8 @@ function getEpisodeTitle(ep: Media, siblings: Media[]): string {
return mediaTitle
}
return ep.episode_num > 0 ? `第 ${ep.episode_num} 集` : mediaTitle || title || '未命名'
const episodeLabel = formatEpisodeLabel(ep)
return episodeLabel || mediaTitle || title || '未命名'
}
function looksLikeSeriesTitle(ep: Media, title: string, siblings: Media[]): boolean {