mirror of
https://github.com/truewhile/MeBox.git
synced 2026-09-29 03:26:37 +08:00
feat: 整合刮削/下载/上传队列为统一的「任务队列」
- 新增 /queue 任务队列页:全部 / 刮削 / 下载 / 上传 类型 Tab(URL 参数 ?type= 同步),Tab 上实时显示各自任务计数徽章 - 「全部」视图纵向堆叠三类队列,单类视图直接复用原队列组件 (ScraperQueuePage / StrmQueuePanel,后者已导出) - 侧边栏导航:删除「刮削队列 / 下载队列 / 上传队列」三个入口,合并为 「任务队列」单一菜单项;旧路由 /scraper/queue、/strm/downloads、 /strm/uploads 保留直达
This commit is contained in:
@@ -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: <StrmDownloadQueuePage />, adminOnly: true },
|
||||
{ path: 'strm/uploads', element: <StrmUploadQueuePage />, adminOnly: true },
|
||||
{ path: 'scraper/queue', element: <ScraperQueuePage />, adminOnly: true },
|
||||
{ path: 'queue', element: <TaskQueuePage />, adminOnly: true },
|
||||
{ path: 'admin', element: <AdminPage />, adminOnly: true },
|
||||
]
|
||||
|
||||
@@ -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 },
|
||||
]
|
||||
@@ -55,7 +55,7 @@ function getFileIcon(filename: string): ReactNode {
|
||||
return <File size={15} className="text-gray-400 shrink-0" />
|
||||
}
|
||||
|
||||
function StrmQueuePanel({ kind }: { kind: 'download' | 'upload' }) {
|
||||
export function StrmQueuePanel({ kind }: { kind: 'download' | 'upload' }) {
|
||||
const [snapshot, setSnapshot] = useState<StrmQueueSnapshot | null>(null)
|
||||
const [filter, setFilter] = useState<'all' | StrmTaskStatus>('all')
|
||||
const [search, setSearch] = useState('')
|
||||
|
||||
@@ -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<Record<string, number> | null>(null)
|
||||
const [downloadCounts, setDownloadCounts] = useState<Record<string, number> | null>(null) // StrmQueueCounts
|
||||
const [uploadCounts, setUploadCounts] = useState<Record<string, number> | null>(null)
|
||||
|
||||
const loadOverview = useCallback(async () => {
|
||||
try {
|
||||
const scrape = await scraperAPI.queue(undefined, 1, 1)
|
||||
setScrapeCounts(scrape.counts as unknown as Record<string, number>)
|
||||
} catch {
|
||||
setScrapeCounts(null)
|
||||
}
|
||||
try {
|
||||
const dl = await strmAPI.downloads(undefined, 1, 1)
|
||||
setDownloadCounts(dl.counts as unknown as Record<string, number>)
|
||||
} catch {
|
||||
setDownloadCounts(null)
|
||||
}
|
||||
try {
|
||||
const ul = await strmAPI.uploads(undefined, 1, 1)
|
||||
setUploadCounts(ul.counts as unknown as Record<string, number>)
|
||||
} catch {
|
||||
setUploadCounts(null)
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
loadOverview()
|
||||
}, [loadOverview])
|
||||
|
||||
const countBadge = (counts: Record<string, number> | null) => {
|
||||
if (!counts) return null
|
||||
const total = Object.values(counts).reduce((a, b) => a + (b || 0), 0)
|
||||
return <span className="tab-count">{total}</span>
|
||||
}
|
||||
|
||||
const switchType = (next: QueueType) => {
|
||||
setSearchParams(next === 'all' ? {} : { type: next }, { replace: true })
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-5">
|
||||
<div className="flex flex-wrap items-end justify-between gap-3">
|
||||
<div>
|
||||
<h1 className="font-display text-3xl font-bold text-ink-600">任务队列</h1>
|
||||
<p className="mt-0.5 text-sm text-ink-50">刮削 / 下载 / 上传三类任务统一管理,按类型切换查看</p>
|
||||
</div>
|
||||
<button onClick={loadOverview} className="btn-outline" title="刷新概览计数">
|
||||
<ListChecks size={14} />
|
||||
刷新计数
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* 类型 Tab */}
|
||||
<div className="flex flex-wrap gap-2 border-b border-sand-200 pb-3">
|
||||
{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 (
|
||||
<button
|
||||
key={tab.type}
|
||||
onClick={() => switchType(tab.type)}
|
||||
className={clsx(
|
||||
'inline-flex items-center gap-1.5 rounded-full px-4 py-2 text-sm font-semibold transition',
|
||||
active ? 'bg-brand-500 text-white shadow-sm' : 'text-ink-100 hover:bg-brand-50 hover:text-brand-600',
|
||||
)}
|
||||
>
|
||||
<Icon size={14} />
|
||||
{tab.label}
|
||||
{badge}
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* 内容 */}
|
||||
{type === 'scrape' && <ScraperQueuePage />}
|
||||
{type === 'download' && <StrmQueuePanel kind="download" />}
|
||||
{type === 'upload' && <StrmQueuePanel kind="upload" />}
|
||||
{type === 'all' && (
|
||||
<div className="space-y-10">
|
||||
<section>
|
||||
<h2 className="mb-2 flex items-center gap-2 font-display text-xl font-bold text-ink-600">
|
||||
<Sparkles size={18} className="text-brand-500" />
|
||||
刮削队列
|
||||
</h2>
|
||||
<ScraperQueuePage />
|
||||
</section>
|
||||
<section>
|
||||
<h2 className="mb-2 flex items-center gap-2 font-display text-xl font-bold text-ink-600">
|
||||
<Download size={18} className="text-brand-500" />
|
||||
下载队列
|
||||
</h2>
|
||||
<StrmQueuePanel kind="download" />
|
||||
</section>
|
||||
<section>
|
||||
<h2 className="mb-2 flex items-center gap-2 font-display text-xl font-bold text-ink-600">
|
||||
<Upload size={18} className="text-brand-500" />
|
||||
上传队列
|
||||
</h2>
|
||||
<StrmQueuePanel kind="upload" />
|
||||
</section>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user