import { useEffect, useState, useCallback } from 'react' import { toast } from 'sonner' import { Users, Search, MessageSquare, Clock, KeyRound, Ban, CheckCircle, Loader2 } from 'lucide-react' import { PageLayout } from '@/components/common/PageLayout' import { EmptyState } from '@/components/common/EmptyState' import { LoadingState } from '@/components/common/LoadingState' import { ErrorState } from '@/components/common/ErrorState' import { Card, CardContent } from '@/components/ui/card' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter, } from '@/components/ui/dialog' import { api } from '@/lib/api' import { cn } from '@/lib/utils' /** 用户列表项(含统计) */ interface AdminUser { id: number phone: string name: string status: string created_at: string message_count: number last_active: string | null last_scene: string | null } /** 场景中文映射 */ const SCENE_LABELS: Record = { legal: '法务参谋', decision: '经营决策', life: '生活助理', emotion: '情绪中心', } /** 格式化时间 */ function formatTime(dt: string | null): string { if (!dt) return '从未使用' return new Date(dt).toLocaleString('zh-CN', { month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', }) } export function AdminUsers() { const [users, setUsers] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [search, setSearch] = useState('') const [searching, setSearching] = useState(false) // 重置密码弹窗 const [resetTarget, setResetTarget] = useState(null) const [newPassword, setNewPassword] = useState('') const [resetting, setResetting] = useState(false) // 状态切换弹窗 const [statusTarget, setStatusTarget] = useState(null) const [statusUpdating, setStatusUpdating] = useState(false) const load = useCallback(async (q?: string) => { setLoading(true) setError(null) try { const res = await api.adminListUsers(q) setUsers(res.results as AdminUser[]) } catch (e) { setError(e instanceof Error ? e.message : '加载失败') } finally { setLoading(false) setSearching(false) } }, []) useEffect(() => { load() }, [load]) /** 搜索(防抖) */ useEffect(() => { if (search === undefined) return const timer = setTimeout(() => { if (search !== '') { setSearching(true) load(search) } else { load() } }, 400) return () => clearTimeout(timer) }, [search]) // eslint-disable-line react-hooks/exhaustive-deps /** 确认重置密码 */ const handleResetPassword = async () => { if (!resetTarget || !newPassword.trim()) return if (newPassword.length < 6) { toast.error('密码至少 6 位') return } setResetting(true) try { await api.adminResetPassword(resetTarget.id, newPassword) toast.success(`已重置 ${resetTarget.name || resetTarget.phone} 的密码`) setResetTarget(null) setNewPassword('') load(search || undefined) } catch (e) { toast.error(e instanceof Error ? e.message : '重置失败') } finally { setResetting(false) } } /** 确认激活/停用 */ const handleToggleStatus = async () => { if (!statusTarget) return const newStatus = statusTarget.status === 'active' ? 'inactive' : 'active' setStatusUpdating(true) try { await api.adminUpdateUserStatus(statusTarget.id, newStatus) toast.success(`已${newStatus === 'active' ? '激活' : '停用'} ${statusTarget.name || statusTarget.phone}`) setStatusTarget(null) load(search || undefined) } catch (e) { toast.error(e instanceof Error ? e.message : '操作失败') } finally { setStatusUpdating(false) } } /** 统计概览 */ const totalUsers = users.length const activeUsers = users.filter(u => u.status === 'active' && u.message_count > 0).length const inactiveUsers = users.filter(u => u.status === 'inactive').length const totalMessages = users.reduce((sum, u) => sum + u.message_count, 0) return ( setSearch(e.target.value)} placeholder="搜索手机号或昵称..." className="h-8 w-48 pl-8 text-xs" /> } > {/* 统计概览 */}

总用户数

{totalUsers}

活跃用户

{activeUsers}

已停用

{inactiveUsers}

总对话数

{totalMessages}

{loading ? ( ) : error ? ( load(search || undefined)} /> ) : users.length === 0 ? ( } /> ) : (
{users.map((u) => (
{/* 头像占位 */}
0 ? 'bg-primary/10 text-primary' : 'bg-muted text-muted-foreground', )}> {(u.name || u.phone.slice(-2)).slice(0, 2)}

{u.name || '未设置昵称'}

{u.phone}
{u.message_count} 条对话 {formatTime(u.last_active)} {u.last_scene && ( {SCENE_LABELS[u.last_scene] || u.last_scene} )} 注册:{formatTime(u.created_at)}
{/* 状态 Badge */} {u.status === 'active' ? '正常' : '已停用'}
{/* 操作按钮 */}
{u.status === 'active' ? ( ) : ( )}
))}
)} {/* 重置密码弹窗 */} !o && setResetTarget(null)}> 重置密码 为用户「{resetTarget?.name || resetTarget?.phone}」设置新密码,用户需用新密码登录。
setNewPassword(e.target.value)} placeholder="输入新密码..." />
{/* 激活/停用确认弹窗 */} !o && setStatusTarget(null)}> {statusTarget?.status === 'active' ? '停用账号' : '激活账号'} {statusTarget?.status === 'active' ? `确认停用用户「${statusTarget?.name || statusTarget?.phone}」?停用后该用户将无法登录。` : `确认激活用户「${statusTarget?.name || statusTarget?.phone}」?激活后该用户可正常登录。` }
) }