import { useEffect, useState } from 'react'; import { Link, Navigate, useParams } from 'react-router-dom'; import { getAdminUser, rechargeUser, updateAdminUser } from '../../api/client'; import { CapabilitySettings } from '../../components/CapabilitySettings'; import { PolicySettings } from '../../components/PolicySettings'; import { SkillSettings } from '../../components/SkillSettings'; import { useAdminUsers } from '../hooks/useAdminUsers'; import { formatYuan } from '../utils/format'; import type { PortalUser } from '../../types'; function formatBytes(bytes: number) { if (bytes < 1024) return `${bytes} B`; if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`; return `${(bytes / 1024 / 1024).toFixed(1)} MB`; } export function UserDetailPage() { const { userId = '' } = useParams(); const { users, reload, setError } = useAdminUsers(); const [user, setUser] = useState(null); const [loading, setLoading] = useState(true); const [error, setLocalError] = useState(null); const [message, setMessage] = useState(null); const [recharge, setRecharge] = useState({ amountYuan: '10', note: '' }); const [spaceQuotaMb, setSpaceQuotaMb] = useState('5'); useEffect(() => { if (!user?.spaceQuotaBytes) return; setSpaceQuotaMb(String(Math.max(1, Math.round(user.spaceQuotaBytes / 1024 / 1024)))); }, [user?.spaceQuotaBytes]); useEffect(() => { let cancelled = false; if (!userId) return; setLoading(true); setLocalError(null); void getAdminUser(userId) .then((nextUser) => { if (cancelled) return; setUser(nextUser); }) .catch((err) => { if (cancelled) return; const fallbackUser = users.find((row) => row.id === userId) ?? null; if (fallbackUser) { setUser(fallbackUser); setLocalError(null); return; } setLocalError(err instanceof Error ? err.message : '加载用户失败'); }) .finally(() => { if (!cancelled) setLoading(false); }); return () => { cancelled = true; }; }, [userId, users]); const handleRecharge = async (e: React.FormEvent) => { e.preventDefault(); if (!user) return; setMessage(null); setLocalError(null); setError(null); try { const amountCents = Math.round(Number(recharge.amountYuan) * 100); const nextUser = await rechargeUser(user.id, amountCents, recharge.note || undefined); setUser(nextUser); setMessage('充值成功'); setRecharge({ amountYuan: '10', note: '' }); await reload(); } catch (err) { setLocalError(err instanceof Error ? err.message : '充值失败'); } }; if (!loading && !user && !error) { return ; } const currentQuotaMb = Math.max(1, Math.round((user?.spaceQuotaBytes ?? 0) / 1024 / 1024)); const handleSpaceQuotaSave = async (e: React.FormEvent) => { e.preventDefault(); if (!user) return; setMessage(null); setLocalError(null); setError(null); try { const quotaMb = Math.floor(Number(spaceQuotaMb)); const nextUser = await updateAdminUser(user.id, { spaceQuotaBytes: quotaMb * 1024 * 1024, }); setUser(nextUser); setMessage('空间已更新'); await reload(); } catch (err) { setLocalError(err instanceof Error ? err.message : '空间更新失败'); } }; return (
← 返回用户列表

{user?.displayName ?? '用户详情'}

{user &&

@{user.username} · {user.role} · {user.status}

}
{error &&

{error}

} {message &&

{message}

} {loading || !user ? (

加载中…

) : ( <>

账户信息

{user.role === 'user' && ( )}
余额
¥{formatYuan(user.balanceCents)}
空间配额
{user.spaceQuotaBytes ? formatBytes(user.spaceQuotaBytes) : '—'} {user.spaceUsedBytes !== undefined && ( {' '} · 已用 {formatBytes(user.spaceUsedBytes)} · 剩余{' '} {formatBytes(user.spaceAvailableBytes ?? 0)} )}
工作目录
{user.workspaceRoot}
{user.role === 'user' && ( <>

充值

setRecharge((s) => ({ ...s, amountYuan: e.target.value }))} /> setRecharge((s) => ({ ...s, note: e.target.value }))} />

调整空间

当前默认按 MB 设置空间总额度。用户前台购买会在此基础上继续累加。

setSpaceQuotaMb(e.target.value)} />

当前总额约 {currentQuotaMb} MB。

)} {user.role === 'admin' && (

管理员账号拥有 goose 全权限:不受能力、技能、策略、计费与路径限制,会话以 auto 模式运行。

)} )}
); }