import { useMutation, useQueryClient } from '@tanstack/react-query'; import { Stack, router } from 'expo-router'; import { useMemo, useState } from 'react'; import { Pressable, ScrollView, Text, TextInput, View } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import { createAccount } from '@/src/services/admin'; import type { AccountType } from '@/src/types/admin'; const colors = { page: '#f4efe4', card: '#fbf8f2', text: '#16181a', subtext: '#6f665c', border: '#e7dfcf', primary: '#1d5f55', dark: '#1b1d1f', errorBg: '#f7e1d6', errorText: '#a4512b', muted: '#f7f1e6', }; const PLATFORM_OPTIONS = ['anthropic', 'openai', 'gemini', 'sora', 'antigravity']; function Section({ title, children }: { title: string; children: React.ReactNode }) { return ( {title} {children} ); } function getErrorMessage(error: unknown) { if (error instanceof Error && error.message) { switch (error.message) { case 'BASE_URL_REQUIRED': return '请先到服务器页填写服务地址。'; case 'ADMIN_API_KEY_REQUIRED': return '请先到服务器页填写 Admin Token。'; case 'INVALID_SERVER_RESPONSE': return '服务返回格式异常,请确认后端接口可用并检查网关日志。'; case 'REQUEST_FAILED': return '请求失败,请检查服务地址、Token 和网络连通性。'; default: return error.message; } } return '创建账号失败,请稍后重试。'; } function parseNumberValue(raw: string) { if (!raw.trim()) return undefined; const value = Number(raw); return Number.isFinite(value) ? value : undefined; } function parseGroupIds(raw: string) { const values = raw .split(',') .map((item) => Number(item.trim())) .filter((value) => Number.isFinite(value) && value > 0); return values.length > 0 ? values : undefined; } function parseJsonObject(raw: string) { if (!raw.trim()) return {} as Record; const parsed = JSON.parse(raw) as Record; if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) { const entries = Object.entries(parsed as Record); for (const [, value] of entries) { const valueType = typeof value; if ( value !== null && value !== undefined && valueType !== 'string' && valueType !== 'number' && valueType !== 'boolean' ) { throw new Error('JSON 仅支持 string / number / boolean / null。'); } } return parsed; } throw new Error('JSON 需要是对象格式。'); } export default function CreateAccountScreen() { const queryClient = useQueryClient(); const [name, setName] = useState(''); const [notes, setNotes] = useState(''); const [platform, setPlatform] = useState('anthropic'); const [accountType, setAccountType] = useState('apikey'); const [baseUrl, setBaseUrl] = useState(''); const [apiKey, setApiKey] = useState(''); const [accessToken, setAccessToken] = useState(''); const [refreshToken, setRefreshToken] = useState(''); const [clientId, setClientId] = useState(''); const [concurrency, setConcurrency] = useState(''); const [priority, setPriority] = useState(''); const [rateMultiplier, setRateMultiplier] = useState(''); const [proxyId, setProxyId] = useState(''); const [groupIds, setGroupIds] = useState(''); const [extraCredentialsJson, setExtraCredentialsJson] = useState(''); const [formError, setFormError] = useState(null); const canSubmit = useMemo(() => { if (!name.trim()) return false; if (accountType === 'apikey') return Boolean(baseUrl.trim() && apiKey.trim()); return Boolean(accessToken.trim()); }, [accessToken, accountType, apiKey, baseUrl, name]); const createMutation = useMutation({ mutationFn: async () => { const credentialsFromJson = parseJsonObject(extraCredentialsJson); const credentials = accountType === 'apikey' ? { ...credentialsFromJson, base_url: baseUrl.trim(), api_key: apiKey.trim(), } : { ...credentialsFromJson, access_token: accessToken.trim(), refresh_token: refreshToken.trim() || undefined, client_id: clientId.trim() || undefined, }; return createAccount({ name: name.trim(), platform, type: accountType, notes: notes.trim() || undefined, concurrency: parseNumberValue(concurrency), priority: parseNumberValue(priority), rate_multiplier: parseNumberValue(rateMultiplier), proxy_id: parseNumberValue(proxyId), group_ids: parseGroupIds(groupIds), credentials, }); }, onSuccess: () => { setFormError(null); queryClient.invalidateQueries({ queryKey: ['accounts'] }); router.replace('/(tabs)/accounts'); }, onError: (error) => { setFormError(getErrorMessage(error)); }, }); return ( <>
账号名称 平台 {PLATFORM_OPTIONS.map((item) => { const active = platform === item; return ( setPlatform(item)} style={{ borderRadius: 999, paddingHorizontal: 12, paddingVertical: 8, borderWidth: 1, borderColor: active ? colors.primary : colors.border, backgroundColor: active ? colors.primary : colors.muted, }} > {item} ); })} 账号类型 {(['apikey', 'oauth'] as const).map((item) => { const active = accountType === item; return ( setAccountType(item)} style={{ flex: 1, borderRadius: 12, paddingVertical: 11, alignItems: 'center', borderWidth: 1, borderColor: active ? colors.primary : colors.border, backgroundColor: active ? colors.primary : colors.muted, }} > {item.toUpperCase()} ); })} 备注(可选)
{accountType === 'apikey' ? ( <> Base URL API Key ) : ( <> Access Token Refresh Token(可选) Client ID(可选) )} 额外凭证 JSON(可选)
并发 concurrency 优先级 priority 倍率 rate_multiplier 代理 ID proxy_id 分组 IDs(逗号分隔)
{formError ? ( {formError} ) : null} { setFormError(null); createMutation.mutate(); }} disabled={!canSubmit || createMutation.isPending} style={{ backgroundColor: !canSubmit || createMutation.isPending ? '#8a8072' : colors.dark, borderRadius: 12, paddingVertical: 14, alignItems: 'center', }} > {createMutation.isPending ? '提交中...' : '创建账号'}
); }