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, CreateAccountRequest } 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']; const ACCOUNT_TYPE_OPTIONS: AccountType[] = ['apikey', 'oauth', 'setup-token', 'upstream']; type JsonScalar = string | number | boolean | null | undefined; type JsonRecord = Record; function toNumber(raw: string) { if (!raw.trim()) return undefined; const value = Number(raw); return Number.isFinite(value) ? value : undefined; } function toGroupIds(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 parseObjectInput(raw: string, fieldLabel: string): JsonRecord | undefined { if (!raw.trim()) return undefined; const parsed = JSON.parse(raw) as unknown; if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) { throw new Error(`${fieldLabel} 必须是 JSON 对象。`); } 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(`${fieldLabel} 仅支持 string / number / boolean / null。`); } } return parsed as JsonRecord; } 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 '创建账号失败,请稍后重试。'; } export default function CreateAdminAccountScreen() { const queryClient = useQueryClient(); const [name, setName] = useState(''); const [platform, setPlatform] = useState('anthropic'); const [type, setType] = useState('apikey'); const [notes, setNotes] = useState(''); const [credentialsJson, setCredentialsJson] = useState('{\n "base_url": "",\n "api_key": ""\n}'); const [extraJson, setExtraJson] = useState(''); const [proxyId, setProxyId] = useState(''); const [concurrency, setConcurrency] = useState(''); const [priority, setPriority] = useState(''); const [rateMultiplier, setRateMultiplier] = useState(''); const [groupIds, setGroupIds] = useState(''); const [formError, setFormError] = useState(null); const canSubmit = useMemo(() => Boolean(name.trim() && credentialsJson.trim()), [credentialsJson, name]); const createMutation = useMutation({ mutationFn: async () => { const credentials = parseObjectInput(credentialsJson, 'credentials'); if (!credentials) { throw new Error('credentials 不能为空。'); } const extra = parseObjectInput(extraJson, 'extra'); const payload: CreateAccountRequest = { name: name.trim(), platform, type, credentials, notes: notes.trim() || undefined, proxy_id: toNumber(proxyId), concurrency: toNumber(concurrency), priority: toNumber(priority), rate_multiplier: toNumber(rateMultiplier), group_ids: toGroupIds(groupIds), extra, }; return createAccount(payload); }, 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} ); })} 类型 {ACCOUNT_TYPE_OPTIONS.map((item) => { const active = type === item; return ( setType(item)} style={{ borderRadius: 999, paddingHorizontal: 12, paddingVertical: 8, borderWidth: 1, borderColor: active ? colors.primary : colors.border, backgroundColor: active ? colors.primary : colors.muted, }} > {item} ); })} 备注(可选) 请求体字段 该页面直接创建 /admin/accounts,credentials 必填,extra 可选。 credentials(JSON 对象) extra(可选,JSON 对象) 可选参数 proxy_id concurrency priority rate_multiplier group_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 ? '提交中...' : '创建账号'} ); }