import React, { useState, useEffect } from 'react'; import { BrowserRouter as Router, Routes, Route, Link, useNavigate, useParams } from 'react-router-dom'; import axios from 'axios'; const API_BASE = 'http://localhost:8000/api/v1'; // 配置 axios 默认头 axios.interceptors.request.use(config => { const token = localStorage.getItem('token'); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }); // ============ 登录页面 ============ function LoginPage() { const [username, setUsername] = useState(''); const [password, setPassword] = useState(''); const navigate = useNavigate(); const handleLogin = async (e) => { e.preventDefault(); try { const response = await axios.post(`${API_BASE}/auth/login/`, { username, password }); const token = response.data.token; localStorage.setItem('token', token); localStorage.setItem('user', JSON.stringify(response.data.user)); navigate('/meetings'); } catch (error) { alert('登录失败:' + (error.response?.data?.detail || error.message)); } }; return (

🏛️ 龙虾议事厅

setUsername(e.target.value)} style={styles.input} required /> setPassword(e.target.value)} style={styles.input} required />

提示:首次使用请先注册超级用户

); } // ============ 会议列表页面 ============ function MeetingList() { const [meetings, setMeetings] = useState([]); const [topic, setTopic] = useState(''); const navigate = useNavigate(); const token = localStorage.getItem('token'); useEffect(() => { if (!token) { navigate('/login'); return; } fetchMeetings(); }, []); const fetchMeetings = async () => { try { const response = await axios.get(`${API_BASE}/meetings/`, { headers: { Authorization: `Bearer ${token}` } }); setMeetings(response.data); } catch (error) { console.error('获取会议失败:', error); } }; const createMeeting = async (e) => { e.preventDefault(); try { const response = await axios.post( `${API_BASE}/meetings/`, { topic }, { headers: { Authorization: `Bearer ${token}` } } ); navigate(`/meeting/${response.data.id}`); } catch (error) { alert('创建失败:' + (error.response?.data?.detail || error.message)); } }; const joinMeeting = async (meetingId) => { const inviteCode = prompt('请输入邀请码:'); if (!inviteCode) return; try { await axios.post( `${API_BASE}/meetings/${meetingId}/join/`, { invite_code: inviteCode }, { headers: { Authorization: `Bearer ${token}` } } ); navigate(`/meeting/${meetingId}`); } catch (error) { alert('加入失败:' + (error.response?.data?.error || error.message)); } }; const logout = () => { localStorage.removeItem('token'); navigate('/login'); }; return (

🏛️ 我的会议室

创建新会议

setTopic(e.target.value)} style={styles.input} required />
{meetings.map(meeting => (

{meeting.topic}

状态:{meeting.status} | 参会:{meeting.participant_count} | 邀请码:{meeting.invite_code}

))}
); } // ============ 会议室页面 ============ function MeetingRoom() { const { id } = useParams(); const [messages, setMessages] = useState([]); const [content, setContent] = useState(''); const [participants, setParticipants] = useState([]); const [lastId, setLastId] = useState(0); const token = localStorage.getItem('token'); useEffect(() => { if (!token) return; fetchParticipants(); fetchMessages(); // 1 秒轮询新消息 const interval = setInterval(fetchMessages, 1000); return () => clearInterval(interval); }, []); const fetchParticipants = async () => { try { const response = await axios.get(`${API_BASE}/meetings/${id}/participants/`, { headers: { Authorization: `Bearer ${token}` } }); setParticipants(response.data); } catch (error) { console.error('获取参会者失败:', error); } }; const fetchMessages = async () => { try { const response = await axios.get(`${API_BASE}/meetings/${id}/messages/?last_id=${lastId}`, { headers: { Authorization: `Bearer ${token}` } }); if (response.data.messages.length > 0) { setMessages(prev => [...prev, ...response.data.messages]); setLastId(response.data.messages[response.data.messages.length - 1].id); } } catch (error) { console.error('获取消息失败:', error); } }; const sendMessage = async (e) => { e.preventDefault(); try { await axios.post( `${API_BASE}/meetings/${id}/send_message/`, { content }, { headers: { Authorization: `Bearer ${token}` } } ); setContent(''); fetchMessages(); } catch (error) { alert('发送失败:' + (error.response?.data?.detail || error.message)); } }; return (
← 返回

🏛️ 会议室

{participants.map(p => ( {p.agent_emoji} {p.nickname} ))}
{messages.map(msg => (
{msg.sender_emoji} {msg.sender_name} {new Date(msg.created_at).toLocaleTimeString()}

{msg.content}

))}
setContent(e.target.value)} style={styles.input} required />
); } // ============ 主应用 ============ function App() { return ( } /> } /> } /> } /> ); } // ============ 样式 ============ const styles = { container: { maxWidth: '1200px', margin: '0 auto', padding: '20px', fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif' }, header: { display: 'flex', alignItems: 'center', gap: '20px', marginBottom: '20px' }, title: { margin: '0', color: '#1a365d' }, card: { background: 'white', borderRadius: '12px', padding: '20px', marginBottom: '20px', boxShadow: '0 4px 6px rgba(0,0,0,0.1)' }, form: { display: 'flex', gap: '10px', marginBottom: '15px' }, input: { flex: 1, padding: '12px', border: '2px solid #e2e8f0', borderRadius: '8px', fontSize: '1em' }, button: { padding: '12px 24px', background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)', color: 'white', border: 'none', borderRadius: '8px', cursor: 'pointer', fontSize: '1em', fontWeight: '600' }, logoutBtn: { marginLeft: 'auto', padding: '8px 16px', background: '#edf2f7', border: 'none', borderRadius: '6px', cursor: 'pointer' }, meetingList: { display: 'flex', flexDirection: 'column', gap: '15px' }, meetingCard: { background: 'white', borderRadius: '12px', padding: '20px', display: 'flex', justifyContent: 'space-between', alignItems: 'center', boxShadow: '0 4px 6px rgba(0,0,0,0.1)' }, smallBtn: { padding: '8px 16px', background: '#4299e1', color: 'white', border: 'none', borderRadius: '6px', cursor: 'pointer', marginLeft: '10px' }, chatContainer: { background: 'white', borderRadius: '12px', padding: '20px', boxShadow: '0 4px 6px rgba(0,0,0,0.1)' }, messages: { maxHeight: '500px', overflowY: 'auto', marginBottom: '20px' }, message: { padding: '15px', background: '#f7fafc', borderRadius: '8px', marginBottom: '10px' }, time: { fontSize: '0.8em', color: '#718096', marginLeft: '10px' }, participants: { display: 'flex', gap: '10px', marginLeft: 'auto' }, participant: { background: '#edf2f7', padding: '6px 12px', borderRadius: '20px', fontSize: '0.9em' }, backLink: { color: '#4299e1', textDecoration: 'none', fontSize: '1.1em' }, hint: { color: '#718096', fontSize: '0.9em', textAlign: 'center', marginTop: '15px' } }; export default App;