Files
rootandClaude Opus 5.5 ea42018d3f docs(copy): 设置页接入、媒体库、整理、系统分区文案按 human-copy 重写
- 下载器说明去掉「接管」,选下载器顺序写成一句;缓存盘统一为下载盘
- 「凭据」统一为「凭证」;去掉说明里的「同 MP」「待处理」等旧称
- 整理来源的基线、存量说法改成用户能懂的话,确认按钮写动作
- 数据保留里「站点快照」「待识别条目」按术语表改名

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei
2026-10-07 01:48:45 +08:00

148 lines
5.7 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 { BrowserProvidersBlock } from "./browser-providers.tsx";
import { num, str } from "./resource-section.tsx";
import { type CardSpec, n, SettingsCard, SettingsGate } from "./settings-card.tsx";
// 站点与网络(site-center §8):站点没单独设置时用的限流、抓取周期等缺省值;UA 与代理;浏览器服务(BR-1)。
// 站点、凭证、账号、Cloudflare 处理在「站点」页管理;站点级覆盖在站点的「编辑」里。
export 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.defaultSnapshotMaxPages",
label: "抓取页数",
kind: "number",
hint: "定时和手动抓取最多抓几页列表;站点定义更少时按站点;1–20",
},
{
name: "sites.defaultHealthCheckIntervalSec",
label: "健康检测间隔(小时)",
kind: "number",
hint: "同一站点两次定时检测至少隔这么久;站点定义更长时按站点",
},
{
name: "sites.defaultDetailCacheSec",
label: "详情缓存(小时)",
kind: "number",
hint: "期内不重复抓同一条资源详情;0 为不缓存",
},
{
name: "sites.userDataMaxSeedingPages",
label: "做种列表页数上限",
kind: "number",
hint: "拉取用户数据时最多翻几页做种列表;1–50",
},
],
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.defaultSnapshotMaxPages": n(s.sites.defaultSnapshotMaxPages),
"sites.defaultHealthCheckIntervalSec": n(s.sites.defaultHealthCheckIntervalSec / 3600),
"sites.defaultDetailCacheSec": n(s.sites.defaultDetailCacheSec / 3600),
}),
toPatch: (v, s) => {
const min = num(v["sites.defaultSnapshotIntervalSec"]);
const rpm = num(v["sites.defaultRateLimit.requestsPerMinute"]);
const concurrency = num(v["sites.defaultRateLimit.concurrency"]);
const healthH = num(v["sites.defaultHealthCheckIntervalSec"]);
const detailH = num(v["sites.defaultDetailCacheSec"]);
return {
sites: {
defaultRateLimit: {
...s.sites.defaultRateLimit,
requestsPerMinute: rpm,
concurrency,
},
defaultSnapshotIntervalSec: min === undefined ? undefined : Math.round(min * 60),
userDataMaxSeedingPages: num(v["sites.userDataMaxSeedingPages"]),
defaultSnapshotMaxPages: num(v["sites.defaultSnapshotMaxPages"]),
defaultHealthCheckIntervalSec: healthH === undefined ? undefined : Math.round(healthH * 3600),
defaultDetailCacheSec: detailH === undefined ? undefined : Math.round(detailH * 3600),
},
};
},
};
export const NETWORK_CARD: CardSpec = {
anchor: "network",
title: "UA 与代理",
description: "站点请求用的 User-Agent 和代理。单个站点在「编辑」里决定用不用代理。",
fields: [
{
name: "sites.userAgent",
label: "User-Agent",
kind: "text",
placeholder: "跟随浏览器",
hint: "留空时站点请求和浏览器用同一个 UA(推荐)。填写后,没单独设 UA 的账号都用这个值,与浏览器的 UA 不一致",
wide: true,
},
{
name: "sites.proxyUrl",
label: "代理地址",
kind: "url",
placeholder: "http://127.0.0.1:7890",
hint: "只对开了「使用代理」的站点生效;留空不用代理",
wide: true,
},
],
fromSettings: (s) => ({
"sites.userAgent": s.sites.userAgent,
"sites.proxyUrl": s.sites.proxyUrl ?? "",
}),
toPatch: (v) => ({
sites: {
// 空串 = 跟随浏览器(要能清空,不能用 undefined 表示"不改")
userAgent: str(v["sites.userAgent"]),
proxyUrl: str(v["sites.proxyUrl"]) || null,
},
}),
};
export function SitesSection() {
return (
<div className="grid grid-cols-1 gap-8">
<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">
站点、凭证和 Cloudflare 验证在「站点」页管理;单个站点的限流、周期、代理在站点的「编辑」里改。
</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} />
<SettingsCard section="sites" spec={NETWORK_CARD} settings={s} />
</>
)}
</SettingsGate>
<BrowserProvidersBlock />
</div>
);
}