import React, { useState, useEffect } from 'react'; import { useSearchParams } from 'react-router'; import { Table, Button, message, Input, QRCode, Modal, Tabs } from 'antd'; // import type { ColumnType } from 'antd/es/table'; import type { GetProp , TableProps} from 'antd'; import dayjs from 'dayjs'; import { useSocketClient } from './hooks/useSocketClient.ts'; import type { QuizState, ExamSocketRoomMessage } from './types.ts'; import type { Answer, CumulativeResult } from './types.ts'; type ColumnType = GetProp[number] // 当前答题情况组件 function CurrentAnswers({ answers, columns }: { answers: Answer[], columns: any[] }) { return (
`${record.userId}-${record.date}`} pagination={false} /> ); } // 每日统计组件 function DailyStatistics({ dailyAnswers, columns }: { dailyAnswers: {[key: string]: Answer[]}, columns: any[] }) { return (
({ date }))} rowKey="date" pagination={false} /> ); } // 累计结果组件 function CumulativeResults({ results, columns }: { results: CumulativeResult[], columns: any[] }) { return (
); } // 二维码组件 function QRCodeSection({ classroom }: { classroom: string }) { return (
扫码参与训练
); } export default function ExamAdmin() { const [searchParams] = useSearchParams(); const classroom = searchParams.get('classroom'); const { socketRoom: { joinRoom, leaveRoom, client }, answerManagement, // currentQuestion, // setCurrentQuestion, // calculateCumulativeResults } = useSocketClient(classroom as string); const [answers, setAnswers] = useState([]); const [dailyAnswers, setDailyAnswers] = useState<{[key: string]: Answer[]}>({}); const [currentDate, setCurrentDate] = useState(''); const [currentPrice, setCurrentPrice] = useState('0'); const [mark, setMark] = useState(''); const [activeTab, setActiveTab] = useState('current'); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const initExamData = async () => { if (!classroom) return; setLoading(true); setError(null); try { // 获取当前问题 const question = await answerManagement.getCurrentQuestion(classroom); if (question) { setCurrentDate(question.date); setCurrentPrice(String(question.price)); // 获取答题记录 const answers = await answerManagement.getAnswers( classroom, '' ); const processedAnswers = answers.map(answer => ({ ...answer, profitAmount: answer.profitAmount || 0, profitPercent: answer.profitPercent || 0, holdingStock: answer.holdingStock || '0', holdingCash: answer.holdingCash || '0' })); setAnswers(processedAnswers); setDailyAnswers(prev => ({ ...prev, [question.date]: processedAnswers })); } } catch (err) { console.error('初始化答题数据失败:', err); setError('初始化答题数据失败'); } finally { setLoading(false); } }; // 添加结算函数 const handleSettlement = async () => { if (!classroom || answers.length === 0) return; try { // todo: 调用结算,所有参与者收到消息后,当前仍选A的,都需要自动变为选B message.success('结算成功'); } catch (error) { console.error('结算失败:', error); message.error('结算失败'); } }; const handleSubmit = async () => { if (!classroom || answers.length === 0) return; try { await answerManagement.cleanupRoom(classroom, currentDate); message.success('答案提交成功'); setAnswers([]); } catch (error: any) { console.error('提交答案失败:', error); message.error(error?.message || '提交答案失败'); } }; const handleRestart = async () => { if (!classroom) return; try { await answerManagement.cleanupRoom(classroom); setAnswers([]); setDailyAnswers({}); setCurrentDate(''); setCurrentPrice('0'); message.success('已重新开始'); } catch (error) { console.error('重新开始失败:', error); message.error('重新开始失败'); } }; const columns = [ { title: '昵称', dataIndex: 'userId', key: 'userId', }, { title: '日期', dataIndex: 'date', key: 'date', render: (text: string) => text ? dayjs(text).format('YYYY-MM-DD') : '-', }, { title: '持股', dataIndex: 'holdingStock', key: 'holdingStock', }, { title: '持币', dataIndex: 'holdingCash', key: 'holdingCash', }, { title: '价格', dataIndex: 'price', key: 'price', render: (text: string | undefined) => text ? parseFloat(text).toFixed(2) : '-', }, { title: '收益(元)', dataIndex: 'profitAmount', key: 'profitAmount', render: (text: number | undefined) => text !== undefined ? text.toFixed(2) : '-', }, { title: '盈亏率', dataIndex: 'profitPercent', key: 'profitPercent', render: (text: number | undefined) => text !== undefined ? `${text.toFixed(2)}%` : '-', } ]; const resultColumns: ColumnType[] = [ { title: '昵称', dataIndex: 'userId', key: 'userId', }, { title: '累计盈亏(元)', dataIndex: 'totalProfitAmount', key: 'totalProfitAmount', render: (text: number | undefined) => text !== undefined ? text.toFixed(2) : '-', }, { title: '累计盈亏率', dataIndex: 'totalProfitPercent', key: 'totalProfitPercent', render: (text: number | undefined) => text !== undefined ? `${text.toFixed(2)}%` : '-', }, ]; const dailyAnswersColumns = [ { title: '日期', dataIndex: 'date', key: 'date', render: (text: string) => dayjs(text).format('YYYY-MM-DD'), }, { title: '答题人数', key: 'count', render: (_: any, record: { date: string }) => dailyAnswers[record.date]?.length || 0, }, { title: '持股人数', key: 'holdingStockCount', render: (_: any, record: { date: string }) => dailyAnswers[record.date]?.filter((a: any) => a.holdingStock === '1').length || 0, }, { title: '持币人数', key: 'holdingCashCount', render: (_: any, record: { date: string }) => dailyAnswers[record.date]?.filter((a: any) => a.holdingCash === '1').length || 0, } ]; // 计算累计结果的函数 const calculateCumulativeResults = (dailyAnswers: {[key: string]: Answer[]}): CumulativeResult[] => { const userResults = new Map(); // 按日期排序 const sortedDates = Object.keys(dailyAnswers).sort((a: string, b: string) => new Date(a).getTime() - new Date(b).getTime() ); sortedDates.forEach(date => { const answers = dailyAnswers[date] || []; answers.forEach((answer: Answer) => { const userId = answer.userId; // 直接使用服务端计算好的收益数据 const profitAmount = answer.profitAmount || 0; const profitPercent = answer.profitPercent || 0; if (!userResults.has(userId)) { userResults.set(userId, { userId, totalProfitAmount: 0, totalProfitPercent: 0 }); } const currentResult = userResults.get(userId)!; currentResult.totalProfitAmount += profitAmount; currentResult.totalProfitPercent += profitPercent; userResults.set(userId, currentResult); }); }); return Array.from(userResults.values()); }; const items = [ { key: 'current', label: '当前答题情况', children: , }, { key: 'daily', label: '每日答题统计', children: , }, { key: 'cumulative', label: '累计结果', children: , }, ]; // 加入/离开房间 useEffect(() => { if (!classroom) return; joinRoom(classroom); initExamData(); return () => { leaveRoom(classroom); }; }, [classroom, joinRoom, leaveRoom]); // 监听答题消息并更新答案 useEffect(() => { if (!classroom || !currentDate || !client) return; const handleAnswerMessage = async () => { try { const answers = await answerManagement.getAnswers( classroom as string, currentDate ); const processedAnswers = answers.map(answer => ({ ...answer, profitAmount: answer.profitAmount || 0, profitPercent: answer.profitPercent || 0, holdingStock: answer.holdingStock || '0', holdingCash: answer.holdingCash || '0' })); setAnswers(processedAnswers); setDailyAnswers(prev => ({ ...prev, [currentDate]: processedAnswers })); } catch (error) { console.error('获取答案失败:', error); } }; client.on('exam:answerUpdated', handleAnswerMessage); return () => { if (!client) return; client.off('exam:answerUpdated', handleAnswerMessage); }; }, [classroom, currentDate, answerManagement, client]); // 监听当前问题变化 useEffect(() => { if (!client ) return; const handleQuestionUpdate = (question:QuizState ) => { setCurrentDate(question.date); setCurrentPrice(String(question.price)); }; client.on('exam:question', handleQuestionUpdate); return () => { client.off('exam:question', handleQuestionUpdate); }; }, [client]); return (

答题卡管理

教室号: {classroom} 当前日期: {currentDate} 当前价格: {currentPrice}
{/* 主要内容区域 */}
{/* 底部按钮组 */}
setMark(e.target.value)} placeholder="标记" style={{ width: 200 }} />
{/* 二维码区域 */}
); }