From bd41ab3fb4901339a7a65c0d1f736834d8b6654a Mon Sep 17 00:00:00 2001 From: truewhile <62226914+truewhile@users.noreply.github.com> Date: Tue, 1 Sep 2026 14:02:25 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=95=B4=E5=90=88=E5=88=AE=E5=89=8A/?= =?UTF-8?q?=E4=B8=8B=E8=BD=BD/=E4=B8=8A=E4=BC=A0=E9=98=9F=E5=88=97?= =?UTF-8?q?=E4=B8=BA=E7=BB=9F=E4=B8=80=E7=9A=84=E3=80=8C=E4=BB=BB=E5=8A=A1?= =?UTF-8?q?=E9=98=9F=E5=88=97=E3=80=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 /queue 任务队列页:全部 / 刮削 / 下载 / 上传 类型 Tab(URL 参数 ?type= 同步),Tab 上实时显示各自任务计数徽章 - 「全部」视图纵向堆叠三类队列,单类视图直接复用原队列组件 (ScraperQueuePage / StrmQueuePanel,后者已导出) - 侧边栏导航:删除「刮削队列 / 下载队列 / 上传队列」三个入口,合并为 「任务队列」单一菜单项;旧路由 /scraper/queue、/strm/downloads、 /strm/uploads 保留直达 --- web/src/appRoutes.tsx | 6 +- web/src/components/layoutNavigation.ts | 10 +- web/src/pages/StrmQueuePage.tsx | 2 +- web/src/pages/TaskQueuePage.tsx | 148 +++++++++++++++++++++++++ 4 files changed, 155 insertions(+), 11 deletions(-) create mode 100644 web/src/pages/TaskQueuePage.tsx diff --git a/web/src/appRoutes.tsx b/web/src/appRoutes.tsx index 825c83e..1bdd869 100644 --- a/web/src/appRoutes.tsx +++ b/web/src/appRoutes.tsx @@ -34,9 +34,8 @@ const StrmDownloadQueuePage = lazy(() => const StrmUploadQueuePage = lazy(() => import('./pages/StrmQueuePage').then((m) => ({ default: m.StrmUploadQueuePage })), ) -const ScraperQueuePage = lazy(() => - import('./pages/ScraperQueuePage').then((m) => ({ default: m.ScraperQueuePage })), -) +const ScraperQueuePage = lazy(() => import('./pages/ScraperQueuePage').then((m) => ({ default: m.ScraperQueuePage }))) +const TaskQueuePage = lazy(() => import('./pages/TaskQueuePage').then((m) => ({ default: m.TaskQueuePage }))) export type AppRoute = { path?: string @@ -68,5 +67,6 @@ export const appRoutes: AppRoute[] = [ { path: 'strm/downloads', element: , adminOnly: true }, { path: 'strm/uploads', element: , adminOnly: true }, { path: 'scraper/queue', element: , adminOnly: true }, + { path: 'queue', element: , adminOnly: true }, { path: 'admin', element: , adminOnly: true }, ] diff --git a/web/src/components/layoutNavigation.ts b/web/src/components/layoutNavigation.ts index 83dafa1..561f850 100644 --- a/web/src/components/layoutNavigation.ts +++ b/web/src/components/layoutNavigation.ts @@ -1,13 +1,11 @@ import type { LucideIcon } from 'lucide-react' import { - Download, FileOutput, FolderOpen, Library, + ListChecks, Settings, - Sparkles, Tv, - Upload, User, Users, } from 'lucide-react' @@ -24,14 +22,12 @@ export type LayoutNavItem = { export const LAYOUT_NAV_ITEMS: LayoutNavItem[] = [ { to: '/profile', label: '个人资料', icon: User }, { to: '/libraries?from=admin', label: '媒体库', icon: Library }, - { to: '/scraper/queue', label: '刮削队列', icon: Sparkles, adminOnly: true }, + { to: '/queue', label: '任务队列', icon: ListChecks, adminOnly: true }, { to: '/admin', label: '用户管理', icon: Users, adminOnly: true }, { to: '/files', label: '文件管理', icon: FolderOpen, adminOnly: true }, { to: '/settings', label: '系统设置', icon: Settings, adminOnly: true }, // Emby 挂载(设置区):远程 Emby 媒体库挂载 { to: '/emby-mount', label: 'Emby 挂载', icon: Tv, adminOnly: true }, - // STRM 管理(设置区):网盘目录生成 strm 与元数据下载/上传队列 + // STRM 管理(设置区):网盘目录生成 strm 与元数据同步 { to: '/strm', label: 'STRM 管理', icon: FileOutput, adminOnly: true }, - { to: '/strm/downloads', label: '下载队列', icon: Download, adminOnly: true }, - { to: '/strm/uploads', label: '上传队列', icon: Upload, adminOnly: true }, ] \ No newline at end of file diff --git a/web/src/pages/StrmQueuePage.tsx b/web/src/pages/StrmQueuePage.tsx index 5cdd51d..5d6b7ed 100644 --- a/web/src/pages/StrmQueuePage.tsx +++ b/web/src/pages/StrmQueuePage.tsx @@ -55,7 +55,7 @@ function getFileIcon(filename: string): ReactNode { return } -function StrmQueuePanel({ kind }: { kind: 'download' | 'upload' }) { +export function StrmQueuePanel({ kind }: { kind: 'download' | 'upload' }) { const [snapshot, setSnapshot] = useState(null) const [filter, setFilter] = useState<'all' | StrmTaskStatus>('all') const [search, setSearch] = useState('') diff --git a/web/src/pages/TaskQueuePage.tsx b/web/src/pages/TaskQueuePage.tsx new file mode 100644 index 0000000..07ae02a --- /dev/null +++ b/web/src/pages/TaskQueuePage.tsx @@ -0,0 +1,148 @@ +import { useCallback, useEffect, useState } from 'react' +import { useSearchParams } from 'react-router-dom' +import { Download, ListChecks, Sparkles, Upload } from 'lucide-react' +import clsx from 'clsx' + +import { scraperAPI } from '../api/scraper' +import { strmAPI } from '../api/strm' +import { ScraperQueuePage } from './ScraperQueuePage' +import { StrmQueuePanel } from './StrmQueuePage' + +type QueueType = 'all' | 'scrape' | 'download' | 'upload' + +const QUEUE_TABS: { type: QueueType; label: string; icon: typeof ListChecks }[] = [ + { type: 'all', label: '全部', icon: ListChecks }, + { type: 'scrape', label: '刮削队列', icon: Sparkles }, + { type: 'download', label: '下载队列', icon: Download }, + { type: 'upload', label: '上传队列', icon: Upload }, +] + +function normalizeType(raw: string | null): QueueType { + if (raw === 'scrape' || raw === 'download' || raw === 'upload' || raw === 'all') { + return raw + } + return 'all' +} + +/** + * 任务队列:整合刮削 / 下载 / 上传 三类队列的统一入口,按类型 Tab 区分。 + * 单个类型视图直接复用原队列组件;「全部」纵向堆叠三个队列。 + */ +export function TaskQueuePage() { + const [searchParams, setSearchParams] = useSearchParams() + const type = normalizeType(searchParams.get('type')) + + const [scrapeCounts, setScrapeCounts] = useState | null>(null) + const [downloadCounts, setDownloadCounts] = useState | null>(null) // StrmQueueCounts + const [uploadCounts, setUploadCounts] = useState | null>(null) + + const loadOverview = useCallback(async () => { + try { + const scrape = await scraperAPI.queue(undefined, 1, 1) + setScrapeCounts(scrape.counts as unknown as Record) + } catch { + setScrapeCounts(null) + } + try { + const dl = await strmAPI.downloads(undefined, 1, 1) + setDownloadCounts(dl.counts as unknown as Record) + } catch { + setDownloadCounts(null) + } + try { + const ul = await strmAPI.uploads(undefined, 1, 1) + setUploadCounts(ul.counts as unknown as Record) + } catch { + setUploadCounts(null) + } + }, []) + + useEffect(() => { + loadOverview() + }, [loadOverview]) + + const countBadge = (counts: Record | null) => { + if (!counts) return null + const total = Object.values(counts).reduce((a, b) => a + (b || 0), 0) + return {total} + } + + const switchType = (next: QueueType) => { + setSearchParams(next === 'all' ? {} : { type: next }, { replace: true }) + } + + return ( +
+
+
+

任务队列

+

刮削 / 下载 / 上传三类任务统一管理,按类型切换查看

+
+ +
+ + {/* 类型 Tab */} +
+ {QUEUE_TABS.map((tab) => { + const active = type === tab.type + const Icon = tab.icon + const badge = + tab.type === 'scrape' + ? countBadge(scrapeCounts) + : tab.type === 'download' + ? countBadge(downloadCounts) + : tab.type === 'upload' + ? countBadge(uploadCounts) + : null + return ( + + ) + })} +
+ + {/* 内容 */} + {type === 'scrape' && } + {type === 'download' && } + {type === 'upload' && } + {type === 'all' && ( +
+
+

+ + 刮削队列 +

+ +
+
+

+ + 下载队列 +

+ +
+
+

+ + 上传队列 +

+ +
+
+ )} +
+ ) +} \ No newline at end of file