imetting/frontend/src/pages/MeetingPreview.jsx

124 lines
4.6 KiB
JavaScript

import React, { useState, useEffect } from 'react';
import { useParams, Link } from 'react-router-dom';
import {
Layout, Card, Typography, Space, Button,
Result, Spin, App, Tag, Divider, Empty, Input
} from 'antd';
import {
LockOutlined, EyeOutlined, EyeInvisibleOutlined,
CopyOutlined, CheckCircleOutlined, ShareAltOutlined,
PlayCircleFilled, PauseCircleFilled,
HomeOutlined, CalendarOutlined, LoginOutlined
} from '@ant-design/icons';
import apiClient from '../utils/apiClient';
import { buildApiUrl, API_ENDPOINTS } from '../config/api';
import MarkdownRenderer from '../components/MarkdownRenderer';
import BrandLogo from '../components/BrandLogo';
import tools from '../utils/tools';
const { Header, Content } = Layout;
const { Title, Text, Paragraph } = Typography;
const MeetingPreview = () => {
const { meeting_id } = useParams();
const { message } = App.useApp();
const [meeting, setMeeting] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [password, setPassword] = useState('');
const [isAuthorized, setIsAuthorized] = useState(false);
useEffect(() => {
fetchPreview();
}, [meeting_id]);
const fetchPreview = async (pwd = null) => {
setLoading(true);
try {
const url = buildApiUrl(`/api/meetings/preview/${meeting_id}${pwd ? `?password=${pwd}` : ''}`);
const res = await apiClient.get(url);
setMeeting(res.data);
setIsAuthorized(true);
setError(null);
} catch (err) {
if (err.response?.status === 401) {
setIsAuthorized(false);
} else {
setError('无法加载会议预览');
}
} finally {
setLoading(false);
}
};
const handleVerify = () => {
if (!password) {
message.warning('请输入访问密码');
return;
}
fetchPreview(password);
};
if (loading && !meeting) return <div style={{ textAlign: 'center', padding: '100px' }}><Spin size="large" /></div>;
if (!isAuthorized) {
return (
<div style={{ minHeight: '100vh', display: 'flex', alignItems: 'center', justifyContent: 'center', background: '#f0f2f5' }}>
<Card style={{ width: 400, borderRadius: 16, textAlign: 'center', boxShadow: '0 8px 24px rgba(0,0,0,0.05)' }}>
<LockOutlined style={{ fontSize: 48, color: '#1677ff', marginBottom: 24 }} />
<Title level={3}>此会议受密码保护</Title>
<Paragraph type="secondary">请输入访问密码以查看会议纪要</Paragraph>
<Space direction="vertical" style={{ width: '100%' }} size="large">
<Input.Password
size="large"
placeholder="访问密码"
value={password}
onChange={e => setPassword(e.target.value)}
onPressEnter={handleVerify}
/>
<Button type="primary" size="large" block icon={<EyeOutlined />} onClick={handleVerify}>立即查看</Button>
</Space>
<div style={{ marginTop: 24 }}>
<Link to="/"><Button type="link" icon={<HomeOutlined />}>返回首页</Button></Link>
</div>
</Card>
</div>
);
}
if (error) return <Result status="error" title={error} extra={<Link to="/"><Button type="primary" icon={<HomeOutlined />}>返回首页</Button></Link>} />;
return (
<Layout style={{ minHeight: '100vh', background: '#f0f2f5' }}>
<Header style={{ background: '#fff', padding: '0 24px', display: 'flex', alignItems: 'center', justifyContent: 'space-between', boxShadow: '0 2px 8px rgba(0,0,0,0.05)' }}>
<Space>
<BrandLogo title="iMeeting 会议预览" size={28} titleSize={18} gap={10} />
</Space>
<Link to="/"><Button icon={<LoginOutlined />}>登录系统</Button></Link>
</Header>
<Content style={{ padding: '40px 20px', maxWidth: 900, margin: '0 auto', width: '100%' }}>
<Card bordered={false} style={{ borderRadius: 16 }}>
<Title level={2}>{meeting.title}</Title>
<Space wrap style={{ marginBottom: 20 }}>
{meeting.tags?.map(t => <Tag key={t.id} color="blue">{t.name}</Tag>)}
<Text type="secondary"><CalendarOutlined /> {tools.formatDateTime(meeting.meeting_time)}</Text>
</Space>
<Divider />
<div className="preview-content">
{meeting.summary ? (
<MarkdownRenderer content={meeting.summary} />
) : (
<Empty description="暂无会议总结内容" />
)}
</div>
</Card>
</Content>
</Layout>
);
};
export default MeetingPreview;