import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { App } from 'antd'; import apiClient from '../utils/apiClient'; import { buildApiUrl, API_ENDPOINTS } from '../config/api'; const AUTO_REFRESH_INTERVAL = 30; export default function useAdminDashboardPage() { const { message, modal } = App.useApp(); const inFlightRef = useRef(false); const mountedRef = useRef(true); const [stats, setStats] = useState(null); const [onlineUsers, setOnlineUsers] = useState([]); const [usersList, setUsersList] = useState([]); const [tasks, setTasks] = useState([]); const [resources, setResources] = useState(null); const [loading, setLoading] = useState(true); const [taskLoading, setTaskLoading] = useState(false); const [lastUpdatedAt, setLastUpdatedAt] = useState(null); const [taskType, setTaskType] = useState('all'); const [taskStatus, setTaskStatus] = useState('all'); const [autoRefresh, setAutoRefresh] = useState(true); const [countdown, setCountdown] = useState(AUTO_REFRESH_INTERVAL); const [showMeetingModal, setShowMeetingModal] = useState(false); const [meetingDetails, setMeetingDetails] = useState(null); const [meetingLoading, setMeetingLoading] = useState(false); const [selectedTaskRecord, setSelectedTaskRecord] = useState(null); useEffect(() => { mountedRef.current = true; return () => { mountedRef.current = false; }; }, []); const fetchStats = useCallback(async () => { const response = await apiClient.get(buildApiUrl(API_ENDPOINTS.ADMIN.DASHBOARD_STATS)); if (response.code === '200') { setStats(response.data); } }, []); const fetchOnlineUsers = useCallback(async () => { const response = await apiClient.get(buildApiUrl(API_ENDPOINTS.ADMIN.ONLINE_USERS)); if (response.code === '200') { setOnlineUsers(response.data.users || []); } }, []); const fetchUsersList = useCallback(async () => { const response = await apiClient.get(buildApiUrl(API_ENDPOINTS.ADMIN.USER_STATS)); if (response.code === '200') { setUsersList(response.data.users || []); } }, []); const fetchTasks = useCallback(async () => { try { setTaskLoading(true); const params = new URLSearchParams(); if (taskType !== 'all') params.append('task_type', taskType); if (taskStatus !== 'all') params.append('status', taskStatus); params.append('limit', '20'); const response = await apiClient.get(buildApiUrl(`${API_ENDPOINTS.ADMIN.TASKS_MONITOR}?${params.toString()}`)); if (response.code === '200') { setTasks(response.data.tasks || []); } } finally { setTaskLoading(false); } }, [taskStatus, taskType]); const fetchResources = useCallback(async () => { const response = await apiClient.get(buildApiUrl(API_ENDPOINTS.ADMIN.SYSTEM_RESOURCES)); if (response.code === '200') { setResources(response.data); } }, []); const fetchAllData = useCallback(async ({ silent = false } = {}) => { if (inFlightRef.current) { return; } inFlightRef.current = true; try { if (!silent && mountedRef.current) { setLoading(true); } await Promise.all([fetchStats(), fetchOnlineUsers(), fetchUsersList(), fetchTasks(), fetchResources()]); if (mountedRef.current) { setLastUpdatedAt(new Date()); setCountdown(AUTO_REFRESH_INTERVAL); } } catch (error) { console.error('获取数据失败:', error); if (mountedRef.current && !silent) { message.error('加载数据失败,请稍后重试'); } } finally { inFlightRef.current = false; if (mountedRef.current && !silent) { setLoading(false); } } }, [fetchOnlineUsers, fetchResources, fetchStats, fetchTasks, fetchUsersList, message]); useEffect(() => { fetchAllData(); }, [fetchAllData]); useEffect(() => { if (!autoRefresh || showMeetingModal) { return undefined; } const timer = setInterval(() => { setCountdown((prev) => { if (prev <= 1) { fetchAllData({ silent: true }); return AUTO_REFRESH_INTERVAL; } return prev - 1; }); }, 1000); return () => clearInterval(timer); }, [autoRefresh, fetchAllData, showMeetingModal]); useEffect(() => { fetchTasks(); }, [fetchTasks]); const handleKickUser = (user) => { modal.confirm({ title: '踢出用户', content: `确定要踢出用户"${user.caption}"吗?该用户将被强制下线。`, okText: '确定', okType: 'danger', cancelText: '取消', onOk: async () => { try { const response = await apiClient.post(buildApiUrl(API_ENDPOINTS.ADMIN.KICK_USER(user.user_id))); if (response.code === '200') { message.success('用户已被踢出'); fetchOnlineUsers(); } } catch { message.error('踢出用户失败'); } }, }); }; const handleViewMeeting = async (meetingId, taskRecord = null) => { setMeetingLoading(true); setShowMeetingModal(true); setSelectedTaskRecord(taskRecord); try { const response = await apiClient.get(buildApiUrl(API_ENDPOINTS.MEETINGS.DETAIL(meetingId))); if (response.code === '200') { setMeetingDetails(response.data); } } catch { message.error('获取会议详情失败'); } finally { setMeetingLoading(false); } }; const handleDownloadTranscript = async (meetingId) => { try { const response = await apiClient.get(buildApiUrl(API_ENDPOINTS.MEETINGS.TRANSCRIPT(meetingId))); if (response.code === '200') { const dataStr = JSON.stringify(response.data, null, 2); const blob = new Blob([dataStr], { type: 'application/json' }); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = `transcript_${meetingId}.json`; document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(url); } } catch { message.error('下载失败'); } }; const downloadResourceByPath = useCallback((resourcePath, fallbackFileName) => { const normalizedPath = typeof resourcePath === 'string' ? resourcePath.trim() : ''; if (!normalizedPath) { message.error('文件路径不存在'); return; } const href = buildApiUrl(normalizedPath.startsWith('/') ? normalizedPath : `/${normalizedPath}`); const fileNameFromPath = normalizedPath.split('/').pop(); const link = document.createElement('a'); link.href = href; link.download = fileNameFromPath || fallbackFileName; document.body.appendChild(link); link.click(); document.body.removeChild(link); }, [message]); const handleDownloadAudio = (meetingId, audioFilePath) => { try { downloadResourceByPath(audioFilePath, `meeting_audio_${meetingId}.mp3`); } catch (error) { console.error('下载音频失败:', error); message.error('下载音频失败'); } }; const handleDownloadSummaryResult = (taskId, resultPath) => { try { downloadResourceByPath(resultPath, `summary_${taskId}.md`); } catch (error) { console.error('下载总结结果失败:', error); message.error('下载总结结果失败'); } }; const handleRetryTask = async (taskRecord) => { if (!taskRecord?.task_id || !taskRecord?.task_type) { message.error('任务信息不完整'); return; } try { const response = await apiClient.post( buildApiUrl(API_ENDPOINTS.ADMIN.TASK_RETRY(taskRecord.task_type, taskRecord.task_id)) ); message.success(response.message || '任务已提交重试'); await fetchTasks(); if (selectedTaskRecord?.task_id === taskRecord.task_id) { setSelectedTaskRecord((prev) => (prev ? { ...prev, task_id: response.data?.task_id || prev.task_id, status: response.data?.status || prev.status, progress: response.data?.progress ?? prev.progress, } : prev)); } } catch (error) { message.error(error?.response?.data?.message || '任务重试失败'); } }; const closeMeetingModal = () => { setShowMeetingModal(false); setMeetingDetails(null); setSelectedTaskRecord(null); }; const taskCompletionRate = useMemo(() => { const all = tasks.length || 1; const completed = tasks.filter((item) => item.status === 'completed').length; return Math.round((completed / all) * 100); }, [tasks]); return { stats, onlineUsers, usersList, tasks, resources, loading, taskLoading, lastUpdatedAt, taskType, setTaskType, taskStatus, setTaskStatus, autoRefresh, setAutoRefresh, countdown, showMeetingModal, meetingDetails, meetingLoading, selectedTaskRecord, fetchAllData, fetchOnlineUsers, handleKickUser, handleViewMeeting, handleDownloadTranscript, handleDownloadAudio, handleDownloadSummaryResult, handleRetryTask, closeMeetingModal, taskCompletionRate, }; }