import { useState, useEffect, useRef } from 'react'; import { useNavigate } from 'react-router-dom'; import { ArrowLeft, Clock, User, QrCode, ChevronUp, ChevronDown, Trash2, Play, Pause, X } from 'lucide-react'; import { meetingService } from '../../services/meeting'; import { authService } from '../../services/auth'; import Toast from '../../components/Toast'; import './Meetings.css'; function Meetings() { const navigate = useNavigate(); const [meetings, setMeetings] = useState([]); const [loading, setLoading] = useState(true); const [pagination, setPagination] = useState({ page: 1, page_size: 5, total: 0, total_pages: 0 }); const [toast, setToast] = useState(null); const [showQRModal, setShowQRModal] = useState(false); const [selectedMeetingUrl, setSelectedMeetingUrl] = useState(''); const [showDeleteModal, setShowDeleteModal] = useState(false); const [deletingMeeting, setDeletingMeeting] = useState(null); const [isDeleting, setIsDeleting] = useState(false); const currentUser = authService.getLocalUser(); // 音频播放相关状态 const [playingMeetingId, setPlayingMeetingId] = useState(null); const [isPlaying, setIsPlaying] = useState(false); const [currentTime, setCurrentTime] = useState(0); const [duration, setDuration] = useState(0); const audioRef = useRef(null); useEffect(() => { fetchMeetings(1); }, []); const fetchMeetings = async (page) => { setLoading(true); try { const response = await meetingService.getMeetings({ page, page_size: pagination.page_size }); // API响应格式: { code, message, data: { meetings, page, page_size, total, total_pages } } const data = response.data; setMeetings(data.meetings || []); setPagination({ page: data.page, page_size: data.page_size, total: data.total, total_pages: data.total_pages }); } catch (error) { console.error('Failed to fetch meetings:', error); setToast({ message: '加载会议列表失败', type: 'error' }); } finally { setLoading(false); } }; const handlePageChange = (newPage) => { if (newPage >= 1 && newPage <= pagination.total_pages) { fetchMeetings(newPage); } }; const handleShowQR = (meetingId) => { const url = `${window.location.origin}/meetings/preview/${meetingId}`; setSelectedMeetingUrl(url); setShowQRModal(true); }; const handleDeleteClick = (meeting, e) => { e.stopPropagation(); setDeletingMeeting(meeting); setShowDeleteModal(true); }; const handleConfirmDelete = async () => { if (!deletingMeeting) return; setIsDeleting(true); try { await meetingService.deleteMeeting(deletingMeeting.meeting_id); setToast({ message: '会议删除成功', type: 'success' }); setShowDeleteModal(false); setDeletingMeeting(null); // 重新加载当前页 // 如果当前页只有一条记录且不是第一页,则返回上一页 const isLastItemOnPage = meetings.length === 1 && pagination.page > 1; const targetPage = isLastItemOnPage ? pagination.page - 1 : pagination.page; fetchMeetings(targetPage); } catch (error) { console.error('Failed to delete meeting:', error); setToast({ message: error.response?.data?.message || '删除会议失败', type: 'error' }); } finally { setIsDeleting(false); } }; const handleCancelDelete = () => { setShowDeleteModal(false); setDeletingMeeting(null); }; // 音频播放相关函数 const handlePlayAudio = (meetingId, e) => { e.stopPropagation(); // 如果点击的是同一个会议,且正在播放,则暂停 if (playingMeetingId === meetingId && isPlaying) { audioRef.current?.pause(); setIsPlaying(false); return; } // 如果点击的是不同的会议,或者同一个会议但暂停状态 if (playingMeetingId !== meetingId) { // 停止之前的播放 if (audioRef.current) { audioRef.current.pause(); audioRef.current = null; } // 创建新的音频元素 const audio = new Audio(`/api/meetings/${meetingId}/audio/stream`); audioRef.current = audio; // 监听音频事件 audio.addEventListener('loadedmetadata', () => { setDuration(audio.duration); }); audio.addEventListener('timeupdate', () => { setCurrentTime(audio.currentTime); }); audio.addEventListener('ended', () => { setIsPlaying(false); setCurrentTime(0); }); audio.addEventListener('error', (e) => { console.error('Audio error:', e); setToast({ message: '音频加载失败', type: 'error' }); setPlayingMeetingId(null); setIsPlaying(false); }); setPlayingMeetingId(meetingId); setCurrentTime(0); audio.play(); setIsPlaying(true); } else { // 同一个会议,从暂停恢复播放 audioRef.current?.play(); setIsPlaying(true); } }; const handlePlayPause = (e) => { e.stopPropagation(); if (isPlaying) { audioRef.current?.pause(); setIsPlaying(false); } else { audioRef.current?.play(); setIsPlaying(true); } }; const handleSeek = (e) => { const progressBar = e.currentTarget; const rect = progressBar.getBoundingClientRect(); const percent = (e.clientX - rect.left) / rect.width; const newTime = percent * duration; if (audioRef.current) { audioRef.current.currentTime = newTime; setCurrentTime(newTime); } }; const handleClosePlayer = (e) => { e.stopPropagation(); if (audioRef.current) { audioRef.current.pause(); audioRef.current = null; } setPlayingMeetingId(null); setIsPlaying(false); setCurrentTime(0); setDuration(0); }; const formatTime = (seconds) => { if (isNaN(seconds)) return '0:00'; const mins = Math.floor(seconds / 60); const secs = Math.floor(seconds % 60); return `${mins}:${secs.toString().padStart(2, '0')}`; }; const formatDateTime = (dateString) => { const date = new Date(dateString); return date.toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' }); }; return (
{toast && ( setToast(null)} /> )}

会议记录

{loading ? (

加载中...

) : meetings.length === 0 ? (

暂无会议记录

) : ( <>
{meetings.map((meeting) => { const isCreator = String(meeting.creator_id) === String(currentUser?.user_id); const cardClass = isCreator ? 'created-by-me' : 'attended-by-me'; const isPlaying = playingMeetingId === meeting.meeting_id; return (
{ if (!isPlaying) { handlePlayAudio(meeting.meeting_id, e); } }} > {isPlaying ? ( /* 播放器模式 */
{meeting.title}
{formatTime(currentTime)}
{formatTime(duration)}
) : ( /* 正常信息显示 */

{meeting.title}

{formatDateTime(meeting.meeting_time)}
创建人: {meeting.creator_username}
)}
{!isPlaying && (
{isCreator && ( )}
)}
); })}
{/* 竖直分页器 */} {pagination.total_pages > 1 && (
{pagination.page}/{pagination.total_pages}
)}
)}
{/* QR码模态框 */} {showQRModal && (
setShowQRModal(false)}>
e.stopPropagation()}>

扫码查看会议

QR Code

{selectedMeetingUrl}

)} {/* 删除确认模态框 */} {showDeleteModal && (
e.stopPropagation()}>

确认删除会议?

确定要删除会议 "{deletingMeeting?.title}" 吗?

)}
); } export default Meetings;