import { useQuery } from '@tanstack/react-query'; import { router } from 'expo-router'; import { useMemo, useState } from 'react'; import { FlatList, Pressable, RefreshControl, Text, TextInput, View } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import { useDebouncedValue } from '@/src/hooks/use-debounced-value'; import { queryClient } from '@/src/lib/query-client'; import { getUser, listUserApiKeys, listUsers } from '@/src/services/admin'; import { adminConfigState } from '@/src/store/admin-config'; import type { AdminUser } from '@/src/types/admin'; const { useSnapshot } = require('valtio/react'); const colors = { page: '#f4efe4', card: '#fbf8f2', mutedCard: '#f1ece2', primary: '#1d5f55', text: '#16181a', subtext: '#6f665c', dangerBg: '#fbf1eb', danger: '#c25d35', accentBg: '#efe4cf', accentText: '#8c5a22', }; type SortOrder = 'desc' | 'asc'; function formatBalance(value?: number) { if (typeof value !== 'number' || Number.isNaN(value)) return '$0.00'; return `$${value.toFixed(2)}`; } function formatActivityTime(value?: string) { if (!value) return '时间未知'; const date = new Date(value); if (Number.isNaN(date.getTime())) return '时间未知'; const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0'); const hours = String(date.getHours()).padStart(2, '0'); const minutes = String(date.getMinutes()).padStart(2, '0'); const seconds = String(date.getSeconds()).padStart(2, '0'); return `${year}/${month}/${day} ${hours}:${minutes}:${seconds}`; } function toTimeValue(value?: string | null) { if (!value) return 0; const time = new Date(value).getTime(); return Number.isNaN(time) ? 0 : time; } function getTimeValue(user: AdminUser) { return toTimeValue(user.last_used_at) || toTimeValue(user.updated_at) || toTimeValue(user.created_at) || user.id || 0; } function getUserNameLabel(user: AdminUser) { if (user.username?.trim()) return user.username.trim(); if (user.notes?.trim()) return user.notes.trim(); return user.email.split('@')[0] || '未命名'; } 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。'; default: return error.message; } } return '当前无法加载页面数据,请检查服务地址、Token 和网络。'; } function MetricTile({ title, value, tone = 'default' }: { title: string; value: string; tone?: 'default' | 'accent' }) { const backgroundColor = tone === 'accent' ? colors.accentBg : colors.mutedCard; const valueColor = tone === 'accent' ? colors.accentText : colors.text; return ( {title} {value} ); } function UserCard({ user }: { user: AdminUser }) { const isAdmin = user.role?.trim().toLowerCase() === 'admin'; const statusLabel = `${isAdmin ? 'admin · ' : ''}${user.status || 'active'}`; return ( {user.email} 最近使用 {formatActivityTime(user.last_used_at || user.updated_at || user.created_at)} {statusLabel} ); } export default function UsersScreen() { const config = useSnapshot(adminConfigState); const hasAccount = Boolean(config.baseUrl.trim()); const [searchText, setSearchText] = useState(''); const [sortOrder, setSortOrder] = useState('desc'); const debouncedSearchText = useDebouncedValue(searchText, 250); const usersQuery = useQuery({ queryKey: ['users', debouncedSearchText], queryFn: () => listUsers(debouncedSearchText), enabled: hasAccount, }); const users = useMemo(() => { const items = [...(usersQuery.data?.items ?? [])]; items.sort((left, right) => { const value = getTimeValue(left) - getTimeValue(right); return sortOrder === 'desc' ? -value : value; }); return items; }, [sortOrder, usersQuery.data?.items]); const errorMessage = getErrorMessage(usersQuery.error); return ( 用户 查看用户列表并进入详情页管理账号。 setSortOrder((value) => (value === 'desc' ? 'asc' : 'desc'))} style={{ backgroundColor: colors.card, borderRadius: 16, paddingHorizontal: 14, paddingVertical: 14, minWidth: 92, alignItems: 'center' }} > 时间 {sortOrder === 'desc' ? '倒序' : '正序'} {!hasAccount ? ( 未连接服务器 请先到“服务器”页完成连接,再查看用户列表。 router.push('/settings')} > 去配置服务器 ) : usersQuery.isLoading ? ( 正在加载用户 已连接服务器,正在拉取用户列表。 ) : usersQuery.error ? ( 加载失败 {errorMessage} ) : ( `${item.id}`} showsVerticalScrollIndicator={false} refreshControl={ void usersQuery.refetch()} tintColor="#1d5f55" />} contentContainerStyle={{ paddingBottom: 8, gap: 12, flexGrow: users.length === 0 ? 1 : 0 }} ListEmptyComponent={ 暂无用户 当前搜索条件下没有匹配结果,可以修改关键词后重试。 } renderItem={({ item }) => ( { void queryClient.prefetchQuery({ queryKey: ['user', item.id], queryFn: () => getUser(item.id) }); void queryClient.prefetchQuery({ queryKey: ['user-api-keys', item.id], queryFn: () => listUserApiKeys(item.id) }); router.push(`/users/${item.id}`); }} > )} /> )} ); }