import { useEffect, useState, useCallback } from 'react'; import { useQuery, useQueryClient } from '@tanstack/react-query'; import { io, Socket } from 'socket.io-client'; import type { QuizContent, QuizState, ExamSocketMessage, ExamSocketRoomMessage, Answer, CumulativeResult } from '../types.ts'; interface FullExamSocketMessage extends Omit { id: string; from: string; timestamp: string; } import { useAuth } from "../../../hooks.tsx"; // 工具函数:统一错误处理 const handleAsyncOperation = async ( operation: () => Promise, errorMessage: string ): Promise => { try { return await operation(); } catch (error) { console.error(`${errorMessage}:`, error); throw error; } }; // 计算收益的辅助函数 interface ProfitResult { profitAmount: number; // 金额收益 profitPercent: number; // 百分比收益 } function calculateProfit(currentPrice: number, previousPrice: number, holdingStock: string): ProfitResult { if (holdingStock === '1') { const profitAmount = currentPrice - previousPrice; const profitPercent = ((currentPrice - previousPrice) / previousPrice) * 100; return { profitAmount, profitPercent }; } return { profitAmount: 0, profitPercent: 0 }; } // 提前声明函数 function getAnswers(client: Socket | null, roomId: string, questionId: string): Promise { if (!client) return Promise.resolve([]); return new Promise((resolve) => { client.emit('exam:getAnswers', { roomId, questionId }, (answers: Answer[]) => { resolve(answers || []); }); }); } function getCurrentQuestion(client: Socket | null, roomId: string, getAnswersFn: typeof getAnswers): Promise { if (!client) return Promise.resolve(null); return getAnswersFn(client, roomId, 'current_state').then(answers => { const currentState = answers[0]; if (currentState) { return { date: currentState.date || '', price: currentState.price || '0' }; } return null; }); } export function useSocketClient(roomId: string | null) { const { token } = useAuth(); const queryClient = useQueryClient(); const [socket, setSocket] = useState(null); const [currentQuestion, setCurrentQuestion] = useState(null); const [lastMessage, setLastMessage] = useState(null); const [userAnswers, setUserAnswers] = useState([]); // 初始化socket连接 const { data: client } = useQuery({ queryKey: ['socket-client', token], queryFn: async () => { if (!token) return null; const newSocket = io('/', { path: '/socket.io', transports: ['websocket'], withCredentials: true, query: { socket_token: token }, reconnection: true, reconnectionAttempts: 5, reconnectionDelay: 1000, }); newSocket.on('connect', () => { console.log('Socket connected'); }); newSocket.on('disconnect', () => { console.log('Socket disconnected'); }); newSocket.on('error', (error) => { console.error('Socket error:', error); }); setSocket(newSocket); return newSocket; }, enabled: !!token && !!roomId, staleTime: Infinity, gcTime: 0, retry: 3, }); // 加入房间 const joinRoom = useCallback(async (roomId: string) => { if (client) { client.emit('exam:join', { roomId }); } }, [client]); // 离开房间 const leaveRoom = useCallback(async (roomId: string) => { if (client) { client.emit('exam:leave', { roomId }); } }, [client]); // 发送房间消息 const sendRoomMessage = useCallback(async (roomId: string, message: ExamSocketMessage) => { if (client) { client.emit('exam:message', { roomId, message }); } }, [client]); // 监听房间消息 const onRoomMessage = useCallback((callback: (data: ExamSocketRoomMessage) => void) => { if (client) { client.on('exam:message', (data) => { setLastMessage(data); callback(data); }); } }, [client]); // 监听当前问题变化 useEffect(() => { if (!client || !roomId) return; const handleQuestionUpdate = async () => { const question = await getCurrentQuestion(client, roomId, getAnswers); setCurrentQuestion(question); }; client.on('exam:question', handleQuestionUpdate); return () => { client.off('exam:question', handleQuestionUpdate); }; }, [client, roomId]); // 监听用户答案变化 useEffect(() => { if (!client || !roomId) return; const handleAnswersUpdate = async () => { const answers = await getAnswers(client, roomId, 'current_state'); setUserAnswers(answers); }; client.on('exam:answers', handleAnswersUpdate); return () => { client.off('exam:answers', handleAnswersUpdate); }; }, [client, roomId]); // 存储答案 const storeAnswer = useCallback(async (roomId: string, questionId: string, userId: string, answer: QuizContent) => { if (!client) return; return handleAsyncOperation(async () => { // 获取历史价格数据 const pricesData = await new Promise((resolve) => { client.emit('exam:getPrices', { roomId }, resolve); }); if (!pricesData) { // 存储初始答案 const initialAnswer: Answer = { ...answer, userId, holdingStock: '0', holdingCash: '0', profitAmount: 0, profitPercent: 0, totalProfitAmount: 0, totalProfitPercent: 0 }; client.emit('exam:storeAnswer', { roomId, questionId, userId, answer: initialAnswer }); return; } // 获取该用户的所有历史答案 const dates = Object.keys(pricesData).sort(); const allAnswers = await Promise.all( dates.map(date => getAnswers(client, roomId, date)) ); // 计算收益 const userAnswers = allAnswers .flat() .filter((a: Answer) => a.userId === userId) .map((a: Answer) => ({ ...a, price: pricesData[a.date || '']?.price || '0' })) .sort((a: Answer, b: Answer) => new Date(a.date || '').getTime() - new Date(b.date || '').getTime()); let totalProfitAmount = 0; let totalProfitPercent = 0; if (userAnswers.length > 0) { const prevAnswer = userAnswers[userAnswers.length - 1]; const { profitAmount, profitPercent } = calculateProfit( parseFloat(String(answer.price)), parseFloat(String(prevAnswer.price)), prevAnswer.holdingStock as string ); totalProfitAmount = (prevAnswer.totalProfitAmount || 0) + profitAmount; totalProfitPercent = (prevAnswer.totalProfitPercent || 0) + profitPercent; } // 存储带有收益信息的答案 const answerWithProfit: Answer = { ...answer, userId, profitAmount: userAnswers.length > 0 ? totalProfitAmount - (userAnswers[userAnswers.length - 1].totalProfitAmount || 0) : 0, profitPercent: userAnswers.length > 0 ? totalProfitPercent - (userAnswers[userAnswers.length - 1].totalProfitPercent || 0) : 0, totalProfitAmount, totalProfitPercent }; client.emit('exam:storeAnswer', { roomId, questionId, userId, answer: answerWithProfit }); }, '存储答案失败'); }, [client, getAnswers]); // 清理房间数据 const cleanupRoom = useCallback(async (roomId: string, questionId?: string) => { if (!client) return; await handleAsyncOperation(async () => { if (questionId) { client.emit('exam:cleanup', { roomId, questionId }); } else { client.emit('exam:cleanup', { roomId }); } }, '清理房间数据失败'); }, [client]); // 发送下一题 const sendNextQuestion = useCallback(async (roomId: string, state: QuizState) => { if (!client) return; return handleAsyncOperation(async () => { const message: FullExamSocketMessage = { id: `question-${Date.now()}`, type: 'question', from: 'system', timestamp: Date.now().toString(), content: { date: state.date, price: state.price, holdingStock: '0', holdingCash: '0', userId: 'system' } }; // 存储当前问题状态 await storeAnswer(roomId, 'current_state', 'system', { date: state.date, price: state.price, holdingStock: '0', holdingCash: '0', userId: 'system' }); // 存储价格历史记录 client.emit('exam:storePrice', { roomId, date: state.date, price: state.price }); await sendRoomMessage(roomId, message); }, '发送题目失败'); }, [client, sendRoomMessage, storeAnswer]); // 获取历史价格 const getPriceHistory = useCallback(async (roomId: string, date: string): Promise => { if (!client) return '0'; return handleAsyncOperation(async () => { return new Promise((resolve) => { client.emit('exam:getPrice', { roomId, date }, (price: string) => { resolve(price || '0'); }); }); }, '获取历史价格失败'); }, [client]); // 获取答案 (封装为useCallback) const getAnswersCallback = useCallback((roomId: string, questionId: string): Promise => { if (!client) return Promise.resolve([]); return handleAsyncOperation(async () => { return getAnswers(client, roomId, questionId); }, '获取答案失败'); }, [client]); // 获取当前题目 (封装为useCallback) const getCurrentQuestionCallback = useCallback((roomId: string): Promise => { if (!client) return Promise.resolve(null); return handleAsyncOperation(async () => { return getCurrentQuestion(client, roomId, getAnswers); }, '获取当前题目状态失败'); }, [client]); // 清理socket连接 useEffect(() => { return () => { if (socket) { socket.disconnect(); } }; }, [socket]); // 导出所有功能作为单个对象 const socketRoom = { client, joinRoom, leaveRoom, sendRoomMessage, onRoomMessage }; const answerManagement = { storeAnswer, getAnswers: getAnswersCallback, cleanupRoom, sendNextQuestion, getCurrentQuestion: getCurrentQuestionCallback, getPriceHistory }; // 计算累计结果 // const calculateCumulativeResults = useCallback((answers: Answer[]): CumulativeResult[] => { // const userResults = new Map(); // answers.forEach((answer) => { // const userId = answer.userId; // if (!userResults.has(userId)) { // userResults.set(userId, { // userId, // totalProfitAmount: answer.totalProfitAmount || 0, // totalProfitPercent: answer.totalProfitPercent || 0 // }); // } // }); // return Array.from(userResults.values()); // }, []); return { socketRoom, answerManagement, // calculateCumulativeResults, currentQuestion, lastMessage, userAnswers, // 兼容旧版导入 ...socketRoom, ...answerManagement }; } // 保留原有其他hook实现...