164 lines
5.6 KiB
TypeScript
164 lines
5.6 KiB
TypeScript
import { App, Button, Card, Input, Space, Typography } from "antd";
|
||
import { useEffect, useMemo, useState } from "react";
|
||
import { useNavigate, useParams, useSearchParams } from "react-router-dom";
|
||
|
||
import {getMeetingPreviewAccess, getPublicMeetingPreview, getPublicRealtimeMeetingSessionStatus} from "@/api/meeting";
|
||
import LoadingScreen from "@/components/LoadingScreen";
|
||
import PageHeader from "@/components/PageHeader";
|
||
import MeetingPreviewView from "@/components/preview/MeetingPreviewView";
|
||
import usePageTitle from "@/hooks/usePageTitle";
|
||
import type { MeetingChapterVO, MeetingTranscriptVO, MeetingVO } from "@/types";
|
||
import { hasAccessToken } from "@/utils/auth";
|
||
import { buildMeetingPreviewUrl } from "@/utils/meeting";
|
||
|
||
const { Paragraph, Title } = Typography;
|
||
|
||
export default function MeetingPreviewPage() {
|
||
const { message } = App.useApp();
|
||
const navigate = useNavigate();
|
||
const { id } = useParams<{ id: string }>();
|
||
const [searchParams] = useSearchParams();
|
||
const [loading, setLoading] = useState(true);
|
||
const [meeting, setMeeting] = useState<MeetingVO | null>(null);
|
||
const [transcripts, setTranscripts] = useState<MeetingTranscriptVO[]>([]);
|
||
const [chapters, setChapters] = useState<MeetingChapterVO[]>([]);
|
||
const [passwordRequired, setPasswordRequired] = useState(false);
|
||
const [passwordVerified, setPasswordVerified] = useState(false);
|
||
const [accessPassword, setAccessPassword] = useState("");
|
||
|
||
const meetingId = useMemo(() => Number(id), [id]);
|
||
const presetAccessPassword = useMemo(() => (searchParams.get("accessPassword") || "").trim(), [searchParams]);
|
||
usePageTitle(meeting?.title || "会议预览");
|
||
|
||
const handleBack = () => {
|
||
navigate(hasAccessToken() ? "/meetings" : "/login", { replace: true });
|
||
};
|
||
|
||
const loadPreview = async (password?: string) => {
|
||
const previewResp = await getPublicMeetingPreview(meetingId, password);
|
||
const preview = previewResp.data.data;
|
||
if (preview.meeting.meetingType === "REALTIME") {
|
||
try {
|
||
const statusResp = await getPublicRealtimeMeetingSessionStatus(meetingId, password);
|
||
const status = statusResp.data.data.status;
|
||
if (status !== "COMPLETING" && status !== "COMPLETED") {
|
||
const query = password ? `?accessPassword=${encodeURIComponent(password)}` : "";
|
||
navigate(`/meetings/${meetingId}/realtime-preview${query}`, {replace: true});
|
||
return true;
|
||
}
|
||
} catch {
|
||
// 状态查询异常时继续进入原预览页。
|
||
}
|
||
}
|
||
setMeeting(preview.meeting);
|
||
setTranscripts(preview.transcripts || []);
|
||
setChapters(preview.chapters || []);
|
||
setPasswordVerified(true);
|
||
return false;
|
||
};
|
||
|
||
useEffect(() => {
|
||
const run = async () => {
|
||
if (!meetingId || Number.isNaN(meetingId)) {
|
||
message.error("会议编号无效");
|
||
setLoading(false);
|
||
return;
|
||
}
|
||
|
||
setLoading(true);
|
||
try {
|
||
const accessResp = await getMeetingPreviewAccess(meetingId);
|
||
const required = !!accessResp.data.data.passwordRequired;
|
||
setPasswordRequired(required);
|
||
|
||
if (!required) {
|
||
await loadPreview();
|
||
return;
|
||
}
|
||
|
||
if (presetAccessPassword) {
|
||
try {
|
||
const redirected = await loadPreview(presetAccessPassword);
|
||
if (redirected) {
|
||
return;
|
||
}
|
||
setAccessPassword(presetAccessPassword);
|
||
return;
|
||
} catch {
|
||
setPasswordVerified(false);
|
||
}
|
||
}
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
};
|
||
|
||
void run();
|
||
}, [meetingId, presetAccessPassword, message]);
|
||
|
||
const handleSubmitPassword = async () => {
|
||
if (!accessPassword.trim()) {
|
||
return;
|
||
}
|
||
setLoading(true);
|
||
try {
|
||
const redirected = await loadPreview(accessPassword.trim());
|
||
if (redirected) {
|
||
return;
|
||
}
|
||
message.success("访问校验通过");
|
||
} catch {
|
||
message.error("访问密码错误");
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
};
|
||
|
||
if (loading && !meeting && !passwordRequired) {
|
||
return <LoadingScreen text="正在加载会议预览..." />;
|
||
}
|
||
|
||
if (passwordRequired && !passwordVerified) {
|
||
return (
|
||
<div className="preview-page">
|
||
<div className="preview-page__inner">
|
||
<PageHeader title="分享预览" back onBack={handleBack} />
|
||
<Card className="surface-card preview-password-card">
|
||
<Title level={3}>会议预览</Title>
|
||
<Paragraph type="secondary">该会议已设置访问密码,请输入密码后继续访问分享内容。</Paragraph>
|
||
<Space direction="vertical" size={16} style={{ width: "100%" }}>
|
||
<Input.Password
|
||
value={accessPassword}
|
||
placeholder="请输入访问密码"
|
||
onChange={(event) => setAccessPassword(event.target.value)}
|
||
onPressEnter={() => void handleSubmitPassword()}
|
||
/>
|
||
<Button type="primary" block loading={loading} onClick={() => void handleSubmitPassword()}>
|
||
进入预览
|
||
</Button>
|
||
</Space>
|
||
</Card>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
if (!meeting) {
|
||
return <LoadingScreen text="未找到会议内容" />;
|
||
}
|
||
|
||
return (
|
||
<div className="preview-page">
|
||
<div className="preview-page__inner">
|
||
<PageHeader title="分享预览" back onBack={handleBack} />
|
||
<MeetingPreviewView
|
||
meeting={meeting}
|
||
transcripts={transcripts}
|
||
meetingChapters={chapters}
|
||
shareUrl={buildMeetingPreviewUrl(meeting.id)}
|
||
/>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|