import { FormEvent, useEffect, useState } from 'react' import toast from 'react-hot-toast' import { subscriptionsAPI } from '../api/subscriptions' import { confirmAction } from '../components/confirmAction' import type { Subscription } from '../types' import { SubscriptionCard } from './SubscriptionCard' import { SubscriptionForm } from './SubscriptionForm' import { SubscriptionHistorySection } from './SubscriptionHistorySection' import { defaultSubscriptionFormValues, type SubscriptionFormValues } from './subscriptionFormModel' export function SubscriptionsPage() { const [items, setItems] = useState([]) const [historyItems, setHistoryItems] = useState([]) const [formValues, setFormValues] = useState(defaultSubscriptionFormValues) const [editingId, setEditingId] = useState('') const [loading, setLoading] = useState(true) const refresh = () => Promise.all([subscriptionsAPI.list(), subscriptionsAPI.history()]) .then(([active, history]) => { setItems(active) setHistoryItems(history) }) .finally(() => setLoading(false)) useEffect(() => { refresh().catch(() => undefined) }, []) const onCreate = async (e: FormEvent) => { e.preventDefault() try { const payload = { name: formValues.name, feed_url: formValues.feed, filter: formValues.filter, media_type: formValues.mediaType || undefined, media_category: formValues.mediaCategory || undefined, save_path: formValues.savePath || undefined, search_mode: formValues.searchMode, imdb_id: formValues.imdbID || undefined, resolution: formValues.resolution, // 规则类字段发送原始字符串(含空串),以便编辑时可清空/修改;create 存空串无害,update 才能真正生效。 quality: formValues.quality, effects: formValues.effects, release_groups: formValues.releaseGroups, exclude_words: formValues.excludeWords, wash_enabled: formValues.washEnabled, wash_priority: formValues.washPriority, priority: 50, } if (editingId) { await subscriptionsAPI.update(editingId, payload) toast.success('已更新订阅') } else { await subscriptionsAPI.create(payload) toast.success('已创建订阅') } resetForm() await refresh() } catch (err: unknown) { const msg = (err as { response?: { data?: { error?: string } } })?.response?.data?.error ?? '创建失败' toast.error(msg) } } const resetForm = () => { setEditingId('') setFormValues(defaultSubscriptionFormValues) } const startEdit = (s: Subscription) => { setEditingId(s.id) setFormValues({ name: s.name, feed: s.feed_url, filter: s.filter || '', mediaType: s.media_type || '', mediaCategory: s.media_category || '', savePath: s.save_path || '', searchMode: s.search_mode || 'keyword', imdbID: s.imdb_id || '', resolution: s.resolution || 'best', quality: s.quality || '', effects: s.effects || '', releaseGroups: s.release_groups || '', excludeWords: s.exclude_words || '', washEnabled: Boolean(s.wash_enabled), washPriority: s.wash_priority || 'balanced', }) window.scrollTo({ top: 0, behavior: 'smooth' }) } const updateFormValue = (key: K, value: SubscriptionFormValues[K]) => { setFormValues((current) => ({ ...current, [key]: value })) } const restoreHistorySubscription = async (subscription: Subscription, runAfterRestore = false) => { try { const restored = await subscriptionsAPI.restore(subscription.id) if (runAfterRestore) { const result = await subscriptionsAPI.runNow(restored.id) toast.success(`已恢复订阅并加入 ${result.queued} 项`) } else { toast.success('已恢复到正在订阅') } await refresh() } catch (err: unknown) { const msg = (err as { response?: { data?: { error?: string } } })?.response?.data?.error || '恢复订阅失败' toast.error(msg) } } const runSubscriptionNow = async (subscription: Subscription) => { const result = await subscriptionsAPI.runNow(subscription.id) toast.success(`已加入 ${result.queued} 项`) } const removeSubscription = async (subscription: Subscription) => { if (!(await confirmAction({ title: '删除订阅', message: `删除订阅「${subscription.name}」?`, confirmText: '删除' }))) return await subscriptionsAPI.remove(subscription.id) toast.success('已删除') await refresh() } return (

RSS 订阅

定期轮询 RSS 源(每 10 分钟一次),将匹配过滤器的项目自动加入下载队列;启用智能分类后会按二级分类写入下载目录。

{loading &&

加载中…

} {!loading && items.length === 0 &&

暂无订阅。

} {items.length > 0 && (
{items.map((subscription) => ( ))}
)}
) }