mirror of
https://github.com/truewhile/MeBox.git
synced 2026-10-01 20:16:36 +08:00
refactor: split modules and harden scraping workflows
This commit is contained in:
+22
-436
@@ -1,7 +1,11 @@
|
||||
import type { Dispatch, FormEvent, SetStateAction } from "react";
|
||||
import { RefreshCw, X } from "lucide-react";
|
||||
import { X } from "lucide-react";
|
||||
|
||||
import type { SiteForm } from "./sitesPageModel";
|
||||
import { SiteFormActions } from "./SiteFormActions";
|
||||
import { SiteFormAdvancedOptions } from "./SiteFormAdvancedOptions";
|
||||
import { SiteFormAuthFields } from "./SiteFormAuthFields";
|
||||
import { SiteFormBasicFields } from "./SiteFormBasicFields";
|
||||
|
||||
type SiteFormModalProps = {
|
||||
editingId: string | null;
|
||||
@@ -48,441 +52,23 @@ export function SiteFormModal({
|
||||
</div>
|
||||
|
||||
<form onSubmit={onSubmit} className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm text-ink-50 mb-1.5">
|
||||
站点名称 *
|
||||
</label>
|
||||
<input
|
||||
required
|
||||
className="input-base w-full"
|
||||
placeholder="例如: 馒头、观众、家园"
|
||||
value={form.name}
|
||||
onChange={(event) =>
|
||||
setForm((current) => ({ ...current, name: event.target.value }))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm text-ink-50 mb-1.5">
|
||||
站点地址 *
|
||||
</label>
|
||||
<input
|
||||
required
|
||||
className="input-base w-full"
|
||||
placeholder="https://www.example.com/"
|
||||
value={form.url}
|
||||
onChange={(event) =>
|
||||
setForm((current) => ({ ...current, url: event.target.value }))
|
||||
}
|
||||
/>
|
||||
<p className="text-xs text-sand-500 mt-1">
|
||||
格式: https://www.example.com/
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm text-ink-50 mb-1.5">
|
||||
站点类型
|
||||
</label>
|
||||
<select
|
||||
className="input-base w-full"
|
||||
value={form.type}
|
||||
onChange={(event) => onTypeChange(event.target.value)}
|
||||
>
|
||||
<option value="nexusphp">NexusPHP(国内主流PT)</option>
|
||||
<option value="gazelle">Gazelle(HDBits等)</option>
|
||||
<option value="unit3d">UNIT3D(BeyondHD等)</option>
|
||||
<option value="mteam">馒头 M-Team(专用API)</option>
|
||||
<option value="yemapt">YemaPT(API Auth)</option>
|
||||
<option value="discuz">Discuz 论坛型</option>
|
||||
<option value="custom_rss">自定义 RSS</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm text-ink-50 mb-1.5">状态</label>
|
||||
<div className="flex items-center gap-3 h-10">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
setForm((current) => ({
|
||||
...current,
|
||||
enabled: !current.enabled,
|
||||
}))
|
||||
}
|
||||
className={`relative inline-flex h-5 w-9 shrink-0 rounded-full transition-colors cursor-pointer ${form.enabled ? "bg-primary-500" : "bg-gray-200"}`}
|
||||
>
|
||||
<span
|
||||
className={`pointer-events-none inline-block h-4 w-4 rounded-full bg-white shadow transform transition-transform mt-0.5 ${form.enabled ? "translate-x-4" : "translate-x-0.5"}`}
|
||||
/>
|
||||
</button>
|
||||
<span
|
||||
className={`text-sm ${form.enabled ? "text-white" : "text-sand-500"}`}
|
||||
>
|
||||
{form.enabled ? "启用" : "停用"}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{form.type === "mteam" && (
|
||||
<div className="p-3 rounded-xl border border-green-500/30 bg-green-500/5">
|
||||
<div className="text-sm font-medium text-green-400 mb-1">
|
||||
馒头站点配置指南
|
||||
</div>
|
||||
<div className="text-xs text-ink-50 space-y-1">
|
||||
<div>
|
||||
<b>正式站地址:</b>
|
||||
<code className="text-green-300">https://api.m-team.cc</code>
|
||||
</div>
|
||||
<div>
|
||||
<b>测试站地址:</b>
|
||||
<code className="text-green-300">
|
||||
https://test2.m-team.cc
|
||||
</code>
|
||||
(开发测试环境)
|
||||
</div>
|
||||
<div>
|
||||
<b>认证方式:</b>必须使用「API Access Token」,不要使用 Cookie
|
||||
</div>
|
||||
<div className="pl-3 text-sand-500">
|
||||
1. 登录馒头站 → 控制台 → 实验室 → 存取令牌
|
||||
<br />
|
||||
2. 点击「创建令牌」,复制生成的 Token
|
||||
<br />
|
||||
3. 将 Token 填入下方「令牌」输入框
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{form.type === "yemapt" && (
|
||||
<div className="p-3 rounded-xl border border-cyan-500/30 bg-cyan-500/5">
|
||||
<div className="text-sm font-medium text-cyan-400 mb-1">
|
||||
YemaPT 配置指南
|
||||
</div>
|
||||
<div className="text-xs text-ink-50 space-y-1">
|
||||
<div>
|
||||
<b>站点地址:</b>
|
||||
<code className="text-cyan-300">https://www.yemapt.org</code>
|
||||
</div>
|
||||
<div>
|
||||
<b>认证方式:</b>使用个人详情页创建的第三方对接专用 auth
|
||||
</div>
|
||||
<div className="pl-3 text-sand-500">
|
||||
填入下方 API Key;后端会按 Wiki 要求通过 Authorization
|
||||
请求头原样发送。
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<label className="block text-sm text-ink-50 mb-2">认证方式</label>
|
||||
<div className="flex gap-2 mb-3">
|
||||
{[
|
||||
{ value: "cookie", label: "Cookie" },
|
||||
{ value: "api_key", label: "API Key" },
|
||||
{ value: "auth_header", label: "Auth Header" },
|
||||
].map((option) => (
|
||||
<button
|
||||
key={option.value}
|
||||
type="button"
|
||||
onClick={() =>
|
||||
setForm((current) => ({
|
||||
...current,
|
||||
auth_type: option.value,
|
||||
}))
|
||||
}
|
||||
className={`px-3 py-1.5 rounded-xl text-xs font-medium border transition ${
|
||||
form.auth_type === option.value
|
||||
? "bg-primary-500 text-ink-600 border-primary-500"
|
||||
: "border-gray-200 text-ink-50 hover:border-primary-500/50"
|
||||
}`}
|
||||
>
|
||||
{option.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{form.auth_type === "cookie" && (
|
||||
<div>
|
||||
<label className="block text-xs text-ink-50 mb-1">Cookie</label>
|
||||
<textarea
|
||||
rows={3}
|
||||
className="input-base w-full resize-none text-xs font-mono"
|
||||
placeholder="uid=xxx; pass=xxx; ..."
|
||||
value={form.cookie}
|
||||
onChange={(event) =>
|
||||
setForm((current) => ({
|
||||
...current,
|
||||
cookie: event.target.value,
|
||||
}))
|
||||
}
|
||||
/>
|
||||
<p className="text-xs text-sand-500 mt-1">
|
||||
从浏览器开发者工具的请求头中获取 Cookie 值
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{form.auth_type === "api_key" && (
|
||||
<div>
|
||||
<label className="block text-xs text-ink-50 mb-1">
|
||||
令牌(API Key / Passkey)
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
className="input-base w-full font-mono text-sm"
|
||||
placeholder={
|
||||
editingId
|
||||
? "留空则保留原令牌,输入新值则替换"
|
||||
: "输入 API Access Token"
|
||||
}
|
||||
value={form.api_key}
|
||||
onChange={(event) =>
|
||||
setForm((current) => ({
|
||||
...current,
|
||||
api_key: event.target.value,
|
||||
}))
|
||||
}
|
||||
/>
|
||||
<p className="text-xs text-sand-500 mt-1">
|
||||
{form.type === "mteam"
|
||||
? "馒头:控制台 → 实验室 → 存取令牌;第三方工具通过 x-api-key 请求头访问"
|
||||
: form.type === "yemapt"
|
||||
? "YemaPT:个人详情页 → 第三方对接专用 auth;通过 Authorization 请求头原样访问"
|
||||
: "站点的访问 API Key"}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{form.auth_type === "auth_header" && (
|
||||
<div>
|
||||
<label className="block text-xs text-ink-50 mb-1">
|
||||
请求头(Authorization)
|
||||
</label>
|
||||
<input
|
||||
className="input-base w-full font-mono text-xs"
|
||||
placeholder="Bearer eyJhbGciOiJIUzI1NiIs..."
|
||||
value={form.auth_header}
|
||||
onChange={(event) =>
|
||||
setForm((current) => ({
|
||||
...current,
|
||||
auth_header: event.target.value,
|
||||
}))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mt-4">
|
||||
<label className="block text-xs text-ink-50 mb-1">RSS 地址</label>
|
||||
<input
|
||||
className="input-base w-full text-xs font-mono"
|
||||
placeholder="https://.../torrents/rss?..."
|
||||
value={form.rss_url}
|
||||
onChange={(event) =>
|
||||
setForm((current) => ({
|
||||
...current,
|
||||
rss_url: event.target.value,
|
||||
}))
|
||||
}
|
||||
/>
|
||||
<p className="text-xs text-sand-500 mt-1">
|
||||
站点 RSS 订阅地址,用于获取最新资源
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setAdvancedOpen(!advancedOpen)}
|
||||
className="flex items-center gap-1 text-xs text-ink-50 hover:text-white transition"
|
||||
>
|
||||
{advancedOpen ? "▾" : "▸"} 高级选项
|
||||
</button>
|
||||
{advancedOpen && (
|
||||
<div className="mt-3 pl-4 space-y-3 border-l border-gray-200">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
||||
<div>
|
||||
<label className="block text-xs text-ink-50 mb-1">
|
||||
User-Agent
|
||||
</label>
|
||||
<input
|
||||
className="input-base w-full text-xs"
|
||||
placeholder="自定义 UA,留空使用默认"
|
||||
value={form.user_agent}
|
||||
onChange={(event) =>
|
||||
setForm((current) => ({
|
||||
...current,
|
||||
user_agent: event.target.value,
|
||||
}))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs text-ink-50 mb-1">
|
||||
请求超时 (秒)
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
className="input-base w-full"
|
||||
min={1}
|
||||
max={300}
|
||||
value={form.timeout}
|
||||
onChange={(event) =>
|
||||
setForm((current) => ({
|
||||
...current,
|
||||
timeout: Number(event.target.value),
|
||||
}))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs text-ink-50 mb-1">
|
||||
优先级 (数字越大越优先)
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
className="input-base w-full"
|
||||
min={1}
|
||||
max={100}
|
||||
value={form.priority}
|
||||
onChange={(event) =>
|
||||
setForm((current) => ({
|
||||
...current,
|
||||
priority: Number(event.target.value),
|
||||
}))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs text-ink-50 mb-1">
|
||||
关联下载器
|
||||
</label>
|
||||
<input
|
||||
className="input-base w-full text-xs"
|
||||
placeholder="下载器 ID 或名称"
|
||||
value={form.downloader}
|
||||
onChange={(event) =>
|
||||
setForm((current) => ({
|
||||
...current,
|
||||
downloader: event.target.value,
|
||||
}))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap gap-4">
|
||||
<label className="flex items-center gap-2 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="h-4 w-4 accent-primary-400"
|
||||
checked={form.use_proxy}
|
||||
onChange={(event) =>
|
||||
setForm((current) => ({
|
||||
...current,
|
||||
use_proxy: event.target.checked,
|
||||
}))
|
||||
}
|
||||
/>
|
||||
<span className="text-xs text-ink-100">使用代理</span>
|
||||
</label>
|
||||
<label className="flex items-center gap-2 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="h-4 w-4 accent-primary-400"
|
||||
checked={form.rate_limit}
|
||||
onChange={(event) =>
|
||||
setForm((current) => ({
|
||||
...current,
|
||||
rate_limit: event.target.checked,
|
||||
}))
|
||||
}
|
||||
/>
|
||||
<span className="text-xs text-ink-100">启用限流</span>
|
||||
</label>
|
||||
<label className="flex items-center gap-2 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="h-4 w-4 accent-primary-400"
|
||||
checked={form.browser_emulation}
|
||||
onChange={(event) =>
|
||||
setForm((current) => ({
|
||||
...current,
|
||||
browser_emulation: event.target.checked,
|
||||
}))
|
||||
}
|
||||
/>
|
||||
<span className="text-xs text-ink-100">浏览器模拟</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-xs text-ink-50 mb-1">
|
||||
Extra 扩展配置 (JSON)
|
||||
</label>
|
||||
<textarea
|
||||
rows={3}
|
||||
className="input-base w-full resize-none text-xs font-mono"
|
||||
placeholder='{"key":"value"}'
|
||||
value={form.extra}
|
||||
onChange={(event) =>
|
||||
setForm((current) => ({
|
||||
...current,
|
||||
extra: event.target.value,
|
||||
}))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
setForm((current) => ({
|
||||
...current,
|
||||
is_default: !current.is_default,
|
||||
}))
|
||||
}
|
||||
className={`relative inline-flex h-5 w-9 shrink-0 rounded-full transition-colors cursor-pointer ${form.is_default ? "bg-primary-500" : "bg-gray-200"}`}
|
||||
>
|
||||
<span
|
||||
className={`pointer-events-none inline-block h-4 w-4 rounded-full bg-white shadow transform transition-transform mt-0.5 ${form.is_default ? "translate-x-4" : "translate-x-0.5"}`}
|
||||
/>
|
||||
</button>
|
||||
<span className="text-sm text-ink-50">设为默认站点</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end gap-2 pt-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
className="rounded-lg border border-gray-200 px-4 py-2 text-sm text-ink-100 hover:bg-gray-50 transition"
|
||||
>
|
||||
取消
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={saving || !form.name.trim() || !form.url.trim()}
|
||||
className="neon-button text-sm disabled:opacity-50 flex items-center gap-1.5"
|
||||
>
|
||||
{saving ? (
|
||||
<>
|
||||
<RefreshCw size={14} className="animate-spin" />
|
||||
保存中...
|
||||
</>
|
||||
) : (
|
||||
"保存"
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
<SiteFormBasicFields
|
||||
form={form}
|
||||
setForm={setForm}
|
||||
onTypeChange={onTypeChange}
|
||||
/>
|
||||
<SiteFormAuthFields
|
||||
editingId={editingId}
|
||||
form={form}
|
||||
setForm={setForm}
|
||||
/>
|
||||
<SiteFormAdvancedOptions
|
||||
advancedOpen={advancedOpen}
|
||||
setAdvancedOpen={setAdvancedOpen}
|
||||
form={form}
|
||||
setForm={setForm}
|
||||
/>
|
||||
<SiteFormActions form={form} saving={saving} onClose={onClose} />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user