网盘搜索,磁链类型增加存到私人影库功能

This commit is contained in:
mtvpls
2026-05-22 00:33:58 +08:00
parent 7012a79210
commit 50eb7a15bc
+141
View File
@@ -4,6 +4,7 @@
import {
AlertCircle,
Copy,
Download,
ExternalLink,
Loader2,
RefreshCw,
@@ -21,6 +22,14 @@ interface PansouSearchProps {
onError?: (error: string) => void;
}
type DownloadTool = 'aria2' | 'Transmission' | 'qBittorrent';
const downloadToolOptions: Array<{ value: DownloadTool; label: string }> = [
{ value: 'aria2', label: 'aria2' },
{ value: 'qBittorrent', label: 'qBittorrent' },
{ value: 'Transmission', label: 'Transmission' },
];
// 网盘类型映射
const CLOUD_TYPE_NAMES: Record<string, string> = {
baidu: '百度网盘',
@@ -154,6 +163,12 @@ export default function PansouSearch({
const typeDragScrollLeftRef = useRef(0);
const [transferingUrl, setTransferingUrl] = useState<string | null>(null);
const [playingUrl, setPlayingUrl] = useState<string | null>(null);
const [downloadingUrl, setDownloadingUrl] = useState<string | null>(null);
const [showNameDialog, setShowNameDialog] = useState(false);
const [selectedDownloadLink, setSelectedDownloadLink] =
useState<PansouLink | null>(null);
const [customName, setCustomName] = useState('');
const [downloadTool, setDownloadTool] = useState<DownloadTool>('aria2');
const [toast, setToast] = useState<ToastProps | null>(null);
const [cooldownRemainingMs, setCooldownRemainingMs] = useState(0);
const [checkStatesByType, setCheckStatesByType] = useState<
@@ -335,6 +350,64 @@ export default function PansouSearch({
}
};
const handleOpenDownloadDialog = (link: PansouLink) => {
setSelectedDownloadLink(link);
setCustomName(keyword.trim() || link.note || '');
setShowNameDialog(true);
};
const handleCloseDownloadDialog = () => {
setShowNameDialog(false);
setSelectedDownloadLink(null);
setCustomName('');
setDownloadTool('aria2');
};
const handleConfirmDownload = async () => {
if (!selectedDownloadLink || !customName.trim()) {
return;
}
setDownloadingUrl(selectedDownloadLink.url);
setShowNameDialog(false);
try {
const response = await fetch('/api/acg/download', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
url: selectedDownloadLink.url,
name: customName.trim(),
tool: downloadTool,
}),
});
const data = await response.json();
if (!response.ok) {
throw new Error(data.error || '添加下载任务失败');
}
setToast({
message: data.message || '已添加到离线下载队列',
type: 'success',
onClose: () => setToast(null),
});
} catch (err: any) {
setToast({
message: err?.message || '添加下载任务失败',
type: 'error',
onClose: () => setToast(null),
});
} finally {
setDownloadingUrl(null);
setSelectedDownloadLink(null);
setCustomName('');
setDownloadTool('aria2');
}
};
const handleNetdiskInstantPlay = async (
cloudType: string,
link: PansouLink
@@ -798,6 +871,28 @@ export default function PansouSearch({
)}
</>
)}
{cloudType === 'magnet' && (
<button
onClick={() => handleOpenDownloadDialog(link)}
disabled={downloadingUrl === link.url}
className='flex items-center gap-1.5 px-2 py-1 rounded-md bg-green-600 hover:bg-green-700 text-white text-xs transition-colors disabled:opacity-60'
title='存到私人影库'
>
{downloadingUrl === link.url ? (
<>
<Loader2 className='h-3.5 w-3.5 animate-spin' />
<span className='hidden sm:inline'>...</span>
</>
) : (
<>
<Download className='h-3.5 w-3.5' />
<span className='hidden sm:inline'>
</span>
</>
)}
</button>
)}
<button
onClick={() =>
handleCopy(
@@ -877,6 +972,52 @@ export default function PansouSearch({
return (
<>
<div className='space-y-6'>{renderBody()}</div>
{showNameDialog && (
<div className='fixed inset-0 z-[1000] flex items-center justify-center bg-black/50'>
<div className='bg-white dark:bg-gray-800 rounded-lg p-6 max-w-md w-full mx-4 shadow-xl'>
<h3 className='text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4'>
</h3>
<input
type='text'
value={customName}
onChange={(e) => setCustomName(e.target.value)}
placeholder='请输入资源名称'
className='w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-green-500'
autoFocus
/>
<label className='mt-4 block text-sm font-medium text-gray-700 dark:text-gray-300'>
</label>
<select
value={downloadTool}
onChange={(e) => setDownloadTool(e.target.value as DownloadTool)}
className='mt-1 w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-green-500'
>
{downloadToolOptions.map((option) => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</select>
<div className='mt-4 flex gap-2 justify-end'>
<button
onClick={handleCloseDownloadDialog}
className='px-4 py-2 rounded-lg bg-gray-200 text-gray-700 hover:bg-gray-300 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600 transition-colors'
>
</button>
<button
onClick={handleConfirmDownload}
disabled={!customName.trim()}
className='px-4 py-2 rounded-lg bg-green-600 text-white hover:bg-green-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors'
>
</button>
</div>
</div>
</div>
)}
{toast && <Toast {...toast} />}
</>
);