Files
MeBox/web/src/pages/LibraryPageHeader.tsx
T
truewhile 4dc9cbe2e7 feat: 网页端完整浏览/播放远程 Emby 挂载库
- 后端新增远程库→本地结构映射(emby_remote_web.go):Library/Media/SeriesCard
  与本地完全同构,前端无感知
- /api/libraries(含 with_preview)、/api/libraries/:id、/api/libraries/:id/media、
  /api/libraries/:id/series、/api/libraries/:id/series/episodes、/api/media/:id、
  /api/media/:id/episodes 全部支持远程伪装 ID 透传映射
- /api/stream/:id 对远程条目 302 直连远程 Emby 原地址(播放不经过本机)
- 库内容按 CollectionType 过滤(电影库取 Movie、剧集库取 Series),
  RecursiveItemCount 作为系列集数;修复伪装 ID 双重编码导致的图片/详情 500
- SPA fallback 放行 /library/embyremote~ 前缀(不再被当作 Emby API 路由 404)
- 前端:远程库/条目标记 is_remote_emby,隐藏扫描/刮削/编辑/NFO/回收站/
  HLS 转码等不适用操作;后台管理面板过滤远程库
2026-09-01 11:32:54 +08:00

94 lines
3.1 KiB
TypeScript

import type { Library } from '../types'
import { EpisodeArtworkToggle } from '../components/EpisodeArtworkToggle'
import { MediaSortDropdown } from '../components/MediaSortDropdown'
import type { SortField, SortOrder } from '../utils/mediaSort'
import { libraryDisplayPath } from './libraryDisplayModel'
type LibraryPageHeaderProps = {
library: Library | null
itemCount: number
loadingAllText: string
scanProgress: string
isAdmin: boolean
scrapeEpisodeArtwork: boolean
scanning: boolean
scraping: boolean
repairing: boolean
sortField: SortField
sortOrder: SortOrder
onSortChange: (field: SortField, order: SortOrder) => void
onReshuffle: () => void
onScrapeEpisodeArtworkChange: (checked: boolean) => void
onScan: () => void
onScrape: () => void
onRepairRescrape: () => void
}
export function LibraryPageHeader({
library,
itemCount,
loadingAllText,
scanProgress,
isAdmin,
scrapeEpisodeArtwork,
scanning,
scraping,
repairing,
sortField,
sortOrder,
onSortChange,
onReshuffle,
onScrapeEpisodeArtworkChange,
onScan,
onScrape,
onRepairRescrape,
}: LibraryPageHeaderProps) {
const displayPath = library ? libraryDisplayPath(library.path) : ''
return (
<div className="flex flex-wrap items-center justify-between gap-3">
<div>
<h1 className="font-display text-3xl font-bold text-ink-600">
{library?.name ?? '媒体库'}
<span className="text-sand-500"> ({itemCount})</span>
</h1>
{library && <p className="text-sm text-ink-50" title={library.path}>{library.type} · {displayPath}</p>}
{loadingAllText && <p className="mt-1 text-xs text-sand-500">{loadingAllText}</p>}
{scanProgress && <p className="mt-1 text-xs text-brand-500">{scanProgress}</p>}
</div>
<div className="flex flex-wrap items-center gap-2">
<MediaSortDropdown
value={sortField}
order={sortOrder}
onChange={onSortChange}
onReshuffle={onReshuffle}
/>
{isAdmin && !library?.is_remote_emby && (
<>
<EpisodeArtworkToggle
checked={scrapeEpisodeArtwork}
onChange={onScrapeEpisodeArtworkChange}
title="关闭后仍会获取主海报和每集文字元数据,只跳过每集图片"
className="h-9"
/>
<button onClick={onScan} disabled={scanning} className="btn-outline">
{scanning ? '扫描中…' : '扫描媒体库'}
</button>
<button onClick={onScrape} disabled={scraping} className="btn-outline" title="对整个媒体库执行刮削元数据">
{scraping ? '刮削中…' : '整库刮削元数据'}
</button>
<button
onClick={onRepairRescrape}
disabled={repairing}
className="btn-outline"
title="回填本库占位符外部 ID 并重刮,修正空 ID / 拆集问题"
>
{repairing ? '修复中…' : '修复+重刮整库'}
</button>
</>
)}
</div>
</div>
)
}