t添加标签功能

This commit is contained in:
truewhile
2026-09-16 12:30:34 +08:00
parent 57ee482146
commit cbfae0252e
19 changed files with 1696 additions and 29 deletions
+66 -4
View File
@@ -2,8 +2,11 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { libraryAPI } from '../api/library'
import { toolsAPI } from '../api/tools'
import { LibraryTagBar } from '../components/LibraryTagBar'
import { openManageLibraryTagsDialog } from '../components/manageLibraryTagsDialog'
import { openManageLibrariesDialog } from '../components/manageLibrariesDialog'
import { useEpisodeArtworkPreference } from '../hooks/useEpisodeArtworkPreference'
import { useLibraryTags } from '../hooks/useLibraryTags'
import { usePinnedLibraries } from '../hooks/usePinnedLibraries'
import { useAuthStore } from '../stores/auth'
import {
@@ -15,6 +18,8 @@ import type { LibraryPreview } from './librariesPageModel'
import type { Library } from '../types'
import type { SeriesCard } from '../utils/groupSeries'
import { fetchLibraries, invalidateLibraries, peekLibraries } from '../utils/libraryCache'
import { ALL_TAG_ID, buildLibraryTagTabs, filterLibrariesByTag } from '../utils/libraryTags'
import type { LibraryTag } from '../utils/libraryTags'
import { sortLibraryPreviews } from '../utils/pinnedLibraries'
import { partitionPreviewIDs } from '../utils/remoteEmby'
@@ -23,6 +28,7 @@ export function LibrariesPage() {
const [libraries, setLibraries] = useState<Library[]>([])
const [libraryData, setLibraryData] = useState<Record<string, { cards: SeriesCard[]; total: number }>>({})
const { pinnedIds, loading: pinnedLoading, togglePin } = usePinnedLibraries()
const libraryTags = useLibraryTags()
const [loading, setLoading] = useState(true)
const [repairing, setRepairing] = useState(false)
const [repairEpisodeArtwork, setRepairEpisodeArtwork] = useEpisodeArtworkPreference()
@@ -146,7 +152,31 @@ export function LibrariesPage() {
void togglePin(libraryId)
}, [togglePin])
const total = useMemo(() => previews.reduce((sum, preview) => sum + preview.total, 0), [previews])
const handleManageTags = useCallback(() => {
void openManageLibraryTagsDialog({
tags: libraryTags.tags,
libraries,
saving: libraryTags.saving,
onCreate: libraryTags.createTag,
onRename: libraryTags.renameTag,
onRemove: libraryTags.removeTag,
onAssign: libraryTags.assignLibrary,
})
}, [libraries, libraryTags])
const effectiveTagId = libraryTags.selectedTagId
const tagTabs = useMemo(
() => buildLibraryTagTabs(sortedPreviews.map((preview) => preview.library), libraryTags.tags),
[sortedPreviews, libraryTags.tags],
)
const taggedPreviews = useMemo(
() => filterPreviewsByTag(sortedPreviews, libraryTags.tags, effectiveTagId),
[sortedPreviews, libraryTags.tags, effectiveTagId],
)
const taggedTotal = useMemo(
() => taggedPreviews.reduce((sum, preview) => sum + preview.total, 0),
[taggedPreviews],
)
if (loading || pinnedLoading) {
return <p className="px-2 py-8 text-sm text-sand-500">媒体库加载中…</p>
@@ -155,8 +185,8 @@ export function LibrariesPage() {
return (
<div className="space-y-8">
<LibrariesHeader
previewCount={previews.length}
total={total}
previewCount={taggedPreviews.length}
total={taggedTotal}
isAdmin={isAdmin}
repairMsg={repairMsg}
repairEpisodeArtwork={repairEpisodeArtwork}
@@ -166,11 +196,26 @@ export function LibrariesPage() {
onManageLibraries={handleManageLibraries}
/>
<LibraryTagBar
tabs={tagTabs}
selectedTagId={effectiveTagId}
onSelect={libraryTags.setSelectedTagId}
onCreate={(name) => {
void libraryTags.createTag(name)
}}
onManage={handleManageTags}
busy={libraryTags.saving}
/>
{previews.length === 0 ? (
<LibrariesEmptyState isAdmin={isAdmin} />
) : taggedPreviews.length === 0 ? (
<p className="rounded-3xl border border-dashed border-[var(--app-border)] bg-[var(--app-panel)] px-4 py-16 text-center text-sm text-[var(--app-muted)]">
「{effectiveTagId}」标签下还没有媒体库,可在「管理标签」里把媒体库归入该标签。
</p>
) : (
<LibrariesContent
previews={sortedPreviews}
previews={taggedPreviews}
pinnedIds={pinnedIds}
onTogglePin={handleTogglePin}
onNeedPreviews={fetchPreviews}
@@ -179,3 +224,20 @@ export function LibrariesPage() {
</div>
)
}
/** 按选中标签过滤媒体库预览(ALL_TAG_ID 时原样返回,保留置顶/手动排序)。 */
function filterPreviewsByTag(previews: LibraryPreview[], tags: LibraryTag[], tagId: string): LibraryPreview[] {
if (!tagId || tagId === ALL_TAG_ID) return previews
const ordered = filterLibrariesByTag(
previews.map((preview) => ({ id: preview.library.id })),
tags,
tagId,
)
const byId = new Map(previews.map((preview) => [preview.library.id, preview]))
const out: LibraryPreview[] = []
for (const item of ordered) {
const preview = byId.get(item.id)
if (preview) out.push(preview)
}
return out
}