Files
UHD_Media_Tool/apps/web/app/components/settings/sites.tsx
T
rootandClaude Opus 5.5 f8362a3f4c feat(web): site center page, add-site wizard, credentials, health and user-data chart (FE-2)
- 资源 → 站点:卡片 / 列表切换(URL ?view= + localStorage 记住),筛选全部 / 已启用 / 需处理,
  按分享率 / 上传 / 上次登录排序;卡片显示账号状态、凭证类型、上次登录、上传 / 下载 / 分享率、
  等级 / VIP、做种、健康检测结果与时间、快照、限流
- 操作:检测健康、刷新登录(wait:true 同步结果)、更新凭证、编辑(限流 / 快照 / 代理)、启停、
  删除(确认框列出影响)
- 添加站点向导:按适配器分组选 23 个定义(未实测标注)→ 名称 / 域名覆盖 / 凭证三选一
  (馒头默认 API Key)→ 保存并测试(刷新登录 + 健康检测);验证码 / 二次验证提示改用 Cookie
  并链接待处理
- 站点详情:概览(用户数据磁贴 + 30 天上传 / 下载与分享率折线,recharts,无彩)/ 资源 /
  快照与 Feed / 账号(凭证管理、Passkey、登录历史)
- 设置"站点与账号"改为"站点默认"(限流、快照周期、UA、代理、做种页数)
- 侧栏改名"站点";smoke 补站点路由;依赖新增 recharts

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-28 16:08:29 +08:00

97 lines
3.6 KiB
TypeScript

import { ArrowRight } from "lucide-react";
import { Link } from "react-router";
import { consolePath } from "~/components/console/nav.ts";
import { Button } from "~/components/ui/button.tsx";
import { num, str } from "./resource-section.tsx";
import { type CardSpec, n, SettingsCard, SettingsGate } from "./settings-card.tsx";
// 站点默认(site-center §8):站点没单独设置时用的限流、快照周期、UA、代理。
// 站点、凭证、账号在「站点」页管理;站点级覆盖在站点的"编辑"里。
const DEFAULTS_CARD: CardSpec = {
anchor: "defaults",
title: "默认值",
description: "站点没有单独设置时使用。限流不会比站点定义自带的更快。",
fields: [
{
name: "sites.defaultRateLimit.requestsPerMinute",
label: "每分钟请求数",
kind: "number",
hint: "每个站点各自计数",
},
{ name: "sites.defaultRateLimit.concurrency", label: "并发", kind: "number", hint: "同一站点同时进行的请求" },
{
name: "sites.defaultSnapshotIntervalSec",
label: "快照周期(分钟)",
kind: "number",
hint: "站点开了定时快照但没设周期时使用;5–10080",
},
{
name: "sites.userDataMaxSeedingPages",
label: "做种列表页数上限",
kind: "number",
hint: "拉取用户数据时最多翻几页做种列表;1–50",
},
{
name: "sites.userAgent",
label: "User-Agent",
kind: "text",
hint: "账号没填 UA 时,登录和拉取用户数据用这个",
wide: true,
},
{
name: "sites.proxyUrl",
label: "代理地址",
kind: "url",
placeholder: "http://127.0.0.1:7890",
hint: "只对开了“使用代理”的站点生效;留空不用代理",
wide: true,
},
],
fromSettings: (s) => ({
"sites.defaultRateLimit.requestsPerMinute": n(s.sites.defaultRateLimit.requestsPerMinute),
"sites.defaultRateLimit.concurrency": n(s.sites.defaultRateLimit.concurrency),
"sites.defaultSnapshotIntervalSec": n(Math.round(s.sites.defaultSnapshotIntervalSec / 60)),
"sites.userDataMaxSeedingPages": n(s.sites.userDataMaxSeedingPages),
"sites.userAgent": s.sites.userAgent,
"sites.proxyUrl": s.sites.proxyUrl ?? "",
}),
toPatch: (v, s) => {
const min = num(v["sites.defaultSnapshotIntervalSec"]);
const rpm = num(v["sites.defaultRateLimit.requestsPerMinute"]);
const concurrency = num(v["sites.defaultRateLimit.concurrency"]);
return {
sites: {
defaultRateLimit: {
...s.sites.defaultRateLimit,
requestsPerMinute: rpm,
concurrency,
},
defaultSnapshotIntervalSec: min === undefined ? undefined : Math.round(min * 60),
userDataMaxSeedingPages: num(v["sites.userDataMaxSeedingPages"]),
userAgent: str(v["sites.userAgent"]) || undefined,
proxyUrl: str(v["sites.proxyUrl"]) || null,
},
};
},
};
export function SitesSection() {
return (
<div className="space-y-6">
<div className="pg-slab flex flex-wrap items-center justify-between gap-3 rounded-xl px-4 py-3">
<p className="text-caption text-muted-foreground">
站点和凭证在「站点」页管理;单个站点的限流、周期、代理在站点的"编辑"里改。
</p>
<Button asChild size="sm" variant="outline">
<Link to={consolePath("sites")}>
打开站点页
<ArrowRight />
</Link>
</Button>
</div>
<SettingsGate>{(s) => <SettingsCard section="sites" spec={DEFAULTS_CARD} settings={s} />}</SettingsGate>
</div>
);
}