diff --git a/frontend/src/pages/LibraryPage.jsx b/frontend/src/pages/LibraryPage.jsx index b5f7cbe..5e65995 100644 --- a/frontend/src/pages/LibraryPage.jsx +++ b/frontend/src/pages/LibraryPage.jsx @@ -1,4 +1,5 @@ import { startTransition, useDeferredValue, useEffect, useState } from 'react'; +import { Link } from 'react-router-dom'; import { AlertTriangle, Disc3, @@ -7,10 +8,11 @@ import { Music4, RefreshCw, Search, + SendToBack, X } from 'lucide-react'; import Pagination from '../components/Pagination'; -import { fetchLibrarySummary, fetchLibraryTracks } from '../api/library'; +import { fetchLibrarySummary, fetchLibraryTracks, moveLibraryTrackToException } from '../api/library'; const PAGE_SIZE = 20; const EMPTY_SUMMARY = { @@ -44,6 +46,7 @@ export default function LibraryPage() { const [isLoadingTracks, setIsLoadingTracks] = useState(true); const [summaryError, setSummaryError] = useState(''); const [tracksError, setTracksError] = useState(''); + const [moveSuccess, setMoveSuccess] = useState(null); const [refreshToken, setRefreshToken] = useState(0); useEffect(() => { @@ -145,6 +148,7 @@ export default function LibraryPage() { setIsRefreshing(true); setSummaryError(''); setTracksError(''); + setMoveSuccess(null); setSelectedTrack(null); startTransition(() => { setRefreshToken((value) => value + 1); @@ -155,6 +159,7 @@ export default function LibraryPage() { startTransition(() => { setCurrentPage(1); setSelectedTrack(null); + setMoveSuccess(null); setFilters((current) => ({ ...current, [field]: value })); }); } @@ -165,6 +170,7 @@ export default function LibraryPage() { startTransition(() => { setCurrentPage(1); setSelectedTrack(null); + setMoveSuccess(null); }); } @@ -317,7 +323,10 @@ export default function LibraryPage() { tracksPage.items.map((track) => ( setSelectedTrack(track)} + onClick={() => { + setMoveSuccess(null); + setSelectedTrack(track); + }} className={`cursor-pointer transition-colors hover:bg-slate-800/40 ${ selectedTrack?.track_id === track.track_id ? 'bg-slate-800/50' : '' }`} @@ -383,10 +392,26 @@ export default function LibraryPage() { /> + {moveSuccess ? ( +
+
{moveSuccess.message}
+ + 前往异常中心 + +
+ ) : null} + {selectedTrack ? ( setSelectedTrack(null)} + onMoved={(payload) => { + setMoveSuccess(payload); + setSelectedTrack(null); + startTransition(() => { + setRefreshToken((value) => value + 1); + }); + }} /> ) : null} @@ -425,7 +450,29 @@ function FilterInput({ label, value, onChange, placeholder }) { ); } -function TrackDetailsDrawer({ track, onClose }) { +function TrackDetailsDrawer({ track, onClose, onMoved }) { + const [isMoving, setIsMoving] = useState(false); + const [moveError, setMoveError] = useState(''); + + async function handleMoveToException() { + const confirmed = window.confirm( + '确认将这首歌移入异常中心?文件会从音乐库移出,但不会永久删除,可在异常中心重新匹配后加入音乐库。' + ); + if (!confirmed) { + return; + } + + setIsMoving(true); + setMoveError(''); + try { + const payload = await moveLibraryTrackToException(track.track_id); + onMoved(payload); + } catch (error) { + setMoveError(error.message || '移入异常中心失败'); + setIsMoving(false); + } + } + return (
+ +
+ {moveError ? ( +
+ {moveError} +
+ ) : null} + +
);