diff --git a/web/src/components/accounts/FingerprintFields.tsx b/web/src/components/accounts/FingerprintFields.tsx new file mode 100644 index 0000000..cab4ee0 --- /dev/null +++ b/web/src/components/accounts/FingerprintFields.tsx @@ -0,0 +1,130 @@ +// 指纹浏览器环境表单字段 + 提交载荷构建:创建页(new.tsx)与编辑页(edit.tsx)共用。 +// 留空项不提交,后端使用浏览器默认值;seed 由系统按账号派生并保证唯一,代理由网络出口统一管理,均不在表单内。 +// 字段契约来源:internal/environment/fingerprint.go(fingerprint-chromium 启动参数)。 +import { Col, Form, Input, InputNumber, Row, Select } from 'antd'; + +export interface FingerprintValues { + platform?: string; + platform_version?: string; + brand?: string; + brand_version?: string; + hardware_concurrency?: number; + lang?: string; + accept_lang?: string; + timezone?: string; + disable_spoofing?: string[]; +} + +export const spoofingOptions = [ + { value: 'font', label: '字体' }, + { value: 'audio', label: '音频' }, + { value: 'canvas', label: 'Canvas' }, + { value: 'clientrects', label: 'ClientRects' }, + { value: 'gpu', label: 'GPU' }, +]; + +// 提交方向:只提交非空项;disable_spoofing 多选合并为逗号分隔字符串(后端契约)。 +export function fingerprintPayload(fp?: FingerprintValues): Record { + const payload: Record = {}; + if (!fp) return payload; + if (fp.platform) payload.platform = fp.platform; + if (fp.platform_version?.trim()) payload.platform_version = fp.platform_version.trim(); + if (fp.brand) payload.brand = fp.brand; + if (fp.brand_version?.trim()) payload.brand_version = fp.brand_version.trim(); + if (fp.hardware_concurrency) payload.hardware_concurrency = fp.hardware_concurrency; + if (fp.lang?.trim()) payload.lang = fp.lang.trim(); + if (fp.accept_lang?.trim()) payload.accept_lang = fp.accept_lang.trim(); + if (fp.timezone?.trim()) payload.timezone = fp.timezone.trim(); + if (fp.disable_spoofing?.length) payload.disable_spoofing = fp.disable_spoofing.join(','); + return payload; +} + +// 回显方向:后端存储的指纹(disable_spoofing 为逗号分隔字符串)转表单值。 +export function fingerprintFormValues(fp?: FingerprintValues | null) { + return { + fingerprint: { + platform: fp?.platform || undefined, + platform_version: fp?.platform_version || '', + brand: fp?.brand || undefined, + brand_version: fp?.brand_version || '', + hardware_concurrency: fp?.hardware_concurrency || undefined, + lang: fp?.lang || '', + accept_lang: fp?.accept_lang || '', + timezone: fp?.timezone || '', + disable_spoofing: fp?.disable_spoofing ? String(fp.disable_spoofing).split(',') : [], + } satisfies FingerprintValues, + }; +} + +export default function FingerprintFields() { + return ( + <> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -228,6 +275,21 @@ export default function Page() { + + 保存指纹 + + } + > + + 留空项使用浏览器默认值;seed 由系统按账号自动派生并保证唯一,代理由网络出口统一管理,均不在此配置。运行中的环境保存后会以新指纹自动重启。 + +
+ + +
); } diff --git a/web/src/pages/accounts/new.tsx b/web/src/pages/accounts/new.tsx index ddaffe6..ffecfe4 100644 --- a/web/src/pages/accounts/new.tsx +++ b/web/src/pages/accounts/new.tsx @@ -1,23 +1,12 @@ // 创建社媒账号:语义对齐 web.archived AccountCreatePage + AccountCreateForm。 // cookies 非必填:留空代表创建后走扫码登录。创建成功跳编辑页。 -// 折叠区块:指纹浏览器环境(可选);seed 由后端从账号派生,代理由网络出口管理,均不在表单内。 +// 折叠区块:指纹浏览器环境(可选,字段复用 FingerprintFields);seed 由后端从账号派生,代理由网络出口管理,均不在表单内。 import { useState } from 'react'; import { history } from '@umijs/max'; -import { Alert, Button, Card, Col, Collapse, Flex, Form, Input, InputNumber, Row, Select, Typography } from 'antd'; +import { Alert, Button, Card, Collapse, Flex, Form, Input, Select, Typography } from 'antd'; import { create } from '@/services/api'; import { conflictMessage, platforms } from '@/utils/helpers'; - -interface FingerprintValues { - platform?: string; - platform_version?: string; - brand?: string; - brand_version?: string; - hardware_concurrency?: number; - lang?: string; - accept_lang?: string; - timezone?: string; - disable_spoofing?: string[]; -} +import FingerprintFields, { fingerprintPayload, type FingerprintValues } from '@/components/accounts/FingerprintFields'; interface FormValues { name: string; @@ -28,14 +17,6 @@ interface FormValues { fingerprint?: FingerprintValues; } -const spoofingOptions = [ - { value: 'font', label: '字体' }, - { value: 'audio', label: '音频' }, - { value: 'canvas', label: 'Canvas' }, - { value: 'clientrects', label: 'ClientRects' }, - { value: 'gpu', label: 'GPU' }, -]; - export default function Page() { const [form] = Form.useForm(); const [busy, setBusy] = useState(false); @@ -54,20 +35,8 @@ export default function Page() { // cookies 非必填:留空代表创建后走扫码登录,凭据由后续同步链路补齐 if (values.cookies?.trim()) data.cookies = values.cookies.trim(); // 指纹表单:只提交非空项;留空项由后端使用浏览器默认值 - const fp = values.fingerprint; - if (fp) { - const fingerprint: Record = {}; - if (fp.platform) fingerprint.platform = fp.platform; - if (fp.platform_version?.trim()) fingerprint.platform_version = fp.platform_version.trim(); - if (fp.brand) fingerprint.brand = fp.brand; - if (fp.brand_version?.trim()) fingerprint.brand_version = fp.brand_version.trim(); - if (fp.hardware_concurrency) fingerprint.hardware_concurrency = fp.hardware_concurrency; - if (fp.lang?.trim()) fingerprint.lang = fp.lang.trim(); - if (fp.accept_lang?.trim()) fingerprint.accept_lang = fp.accept_lang.trim(); - if (fp.timezone?.trim()) fingerprint.timezone = fp.timezone.trim(); - if (fp.disable_spoofing?.length) fingerprint.disable_spoofing = fp.disable_spoofing.join(','); - if (Object.keys(fingerprint).length) data.fingerprint = fingerprint; - } + const fingerprint = fingerprintPayload(values.fingerprint); + if (Object.keys(fingerprint).length) data.fingerprint = fingerprint; const result = await create('accounts', data); const id = result?.data?.id ?? result?.id; if (!id) throw new Error('创建账号未返回账号 ID'); @@ -115,72 +84,7 @@ export default function Page() { 留空项使用浏览器默认值;seed 由系统按账号自动派生并保证唯一,代理由网络出口统一管理,均不在此配置。 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -