新增web访问密码查看
parent
7c63ec1ebe
commit
346b5ffb06
|
|
@ -87,6 +87,7 @@ class Meeting(BaseModel):
|
|||
overall_status: Optional[str] = None
|
||||
overall_progress: Optional[int] = None
|
||||
current_stage: Optional[str] = None
|
||||
access_password: Optional[str] = None
|
||||
|
||||
class TranscriptSegment(BaseModel):
|
||||
segment_id: int
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { QRCodeSVG } from 'qrcode.react';
|
|||
|
||||
const { Text, Paragraph } = Typography;
|
||||
|
||||
const QRCodeModal = ({ open, onClose, url, title = "扫描二维码分享" }) => {
|
||||
const QRCodeModal = ({ open, onClose, url, title = "扫描二维码分享", children }) => {
|
||||
const { message } = App.useApp();
|
||||
|
||||
const handleCopy = () => {
|
||||
|
|
@ -43,6 +43,11 @@ const QRCodeModal = ({ open, onClose, url, title = "扫描二维码分享" }) =>
|
|||
<div style={{ marginTop: 12, background: '#f5f5f5', padding: '8px 12px', borderRadius: 6, textAlign: 'left' }}>
|
||||
<Text code ellipsis style={{ width: '100%', display: 'block' }}>{url}</Text>
|
||||
</div>
|
||||
{children ? (
|
||||
<div style={{ marginTop: 20, textAlign: 'left' }}>
|
||||
{children}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ const API_CONFIG = {
|
|||
UPLOAD_IMAGE: (meetingId) => `/api/meetings/${meetingId}/upload-image`,
|
||||
REGENERATE_SUMMARY: (meetingId) => `/api/meetings/${meetingId}/regenerate-summary`,
|
||||
NAVIGATION: (meetingId) => `/api/meetings/${meetingId}/navigation`,
|
||||
ACCESS_PASSWORD: (meetingId) => `/api/meetings/${meetingId}/access-password`,
|
||||
PREVIEW_DATA: (meetingId) => `/api/meetings/${meetingId}/preview-data`,
|
||||
LLM_MODELS: '/api/llm-models/active'
|
||||
},
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { useParams, useNavigate } from 'react-router-dom';
|
|||
import {
|
||||
Card, Row, Col, Button, Space, Typography, Tag, Avatar,
|
||||
Tooltip, Progress, Spin, App, Dropdown,
|
||||
Divider, List, Timeline, Tabs, Input, Upload, Empty, Drawer, Select
|
||||
Divider, List, Timeline, Tabs, Input, Upload, Empty, Drawer, Select, Switch
|
||||
} from 'antd';
|
||||
import {
|
||||
ClockCircleOutlined, UserOutlined,
|
||||
|
|
@ -75,6 +75,9 @@ const MeetingDetails = ({ user }) => {
|
|||
const [showQRModal, setShowQRModal] = useState(false);
|
||||
const [isUploading, setIsUploading] = useState(false);
|
||||
const [playbackRate, setPlaybackRate] = useState(1);
|
||||
const [accessPasswordEnabled, setAccessPasswordEnabled] = useState(false);
|
||||
const [accessPasswordDraft, setAccessPasswordDraft] = useState('');
|
||||
const [savingAccessPassword, setSavingAccessPassword] = useState(false);
|
||||
|
||||
// 转录编辑 Drawer
|
||||
const [showTranscriptEditDrawer, setShowTranscriptEditDrawer] = useState(false);
|
||||
|
|
@ -87,6 +90,7 @@ const MeetingDetails = ({ user }) => {
|
|||
|
||||
const audioRef = useRef(null);
|
||||
const transcriptRefs = useRef([]);
|
||||
const isMeetingOwner = user?.user_id === meeting?.creator_id;
|
||||
|
||||
/* ══════════════════ 数据获取 ══════════════════ */
|
||||
|
||||
|
|
@ -105,6 +109,8 @@ const MeetingDetails = ({ user }) => {
|
|||
setLoading(true);
|
||||
const response = await apiClient.get(buildApiUrl(API_ENDPOINTS.MEETINGS.DETAIL(meeting_id)));
|
||||
setMeeting(response.data);
|
||||
setAccessPasswordEnabled(Boolean(response.data.access_password));
|
||||
setAccessPasswordDraft(response.data.access_password || '');
|
||||
|
||||
if (response.data.transcription_status) {
|
||||
const ts = response.data.transcription_status;
|
||||
|
|
@ -220,6 +226,40 @@ const MeetingDetails = ({ user }) => {
|
|||
finally { setIsUploading(false); }
|
||||
};
|
||||
|
||||
const saveAccessPassword = async () => {
|
||||
const nextPassword = accessPasswordEnabled ? accessPasswordDraft.trim() : null;
|
||||
if (accessPasswordEnabled && !nextPassword) {
|
||||
message.warning('开启访问密码后,请先输入密码');
|
||||
return;
|
||||
}
|
||||
|
||||
setSavingAccessPassword(true);
|
||||
try {
|
||||
const res = await apiClient.put(
|
||||
buildApiUrl(API_ENDPOINTS.MEETINGS.ACCESS_PASSWORD(meeting_id)),
|
||||
{ password: nextPassword }
|
||||
);
|
||||
const savedPassword = res.data?.password || null;
|
||||
setMeeting((prev) => (prev ? { ...prev, access_password: savedPassword } : prev));
|
||||
setAccessPasswordEnabled(Boolean(savedPassword));
|
||||
setAccessPasswordDraft(savedPassword || '');
|
||||
message.success(res.message || '访问密码已更新');
|
||||
} catch (error) {
|
||||
message.error(error?.response?.data?.message || '访问密码更新失败');
|
||||
} finally {
|
||||
setSavingAccessPassword(false);
|
||||
}
|
||||
};
|
||||
|
||||
const copyAccessPassword = async () => {
|
||||
if (!accessPasswordDraft) {
|
||||
message.warning('当前没有可复制的访问密码');
|
||||
return;
|
||||
}
|
||||
await navigator.clipboard.writeText(accessPasswordDraft);
|
||||
message.success('访问密码已复制');
|
||||
};
|
||||
|
||||
const openAudioUploadPicker = () => {
|
||||
document.getElementById('audio-upload-input')?.click();
|
||||
};
|
||||
|
|
@ -864,7 +904,48 @@ const MeetingDetails = ({ user }) => {
|
|||
)}
|
||||
</Drawer>
|
||||
|
||||
<QRCodeModal open={showQRModal} onClose={() => setShowQRModal(false)} url={`${window.location.origin}/meetings/preview/${meeting_id}`} />
|
||||
<QRCodeModal open={showQRModal} onClose={() => setShowQRModal(false)} url={`${window.location.origin}/meetings/preview/${meeting_id}`}>
|
||||
{isMeetingOwner ? (
|
||||
<>
|
||||
<Divider style={{ margin: '0 0 16px' }} />
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 12 }}>
|
||||
<Text strong>访问密码保护</Text>
|
||||
<Switch
|
||||
checked={accessPasswordEnabled}
|
||||
checkedChildren="已开启"
|
||||
unCheckedChildren="已关闭"
|
||||
onChange={setAccessPasswordEnabled}
|
||||
/>
|
||||
</div>
|
||||
{accessPasswordEnabled ? (
|
||||
<Space direction="vertical" style={{ width: '100%' }} size={12}>
|
||||
<Input.Password
|
||||
value={accessPasswordDraft}
|
||||
onChange={(e) => setAccessPasswordDraft(e.target.value)}
|
||||
placeholder="请输入访问密码"
|
||||
/>
|
||||
<Space style={{ width: '100%', justifyContent: 'space-between' }} wrap>
|
||||
<Text type="secondary">开启后,访客打开分享链接时需要输入这个密码</Text>
|
||||
<Space>
|
||||
<Button onClick={copyAccessPassword} disabled={!accessPasswordDraft}>复制密码</Button>
|
||||
<Button type="primary" loading={savingAccessPassword} onClick={saveAccessPassword}>保存密码</Button>
|
||||
</Space>
|
||||
</Space>
|
||||
</Space>
|
||||
) : (
|
||||
<Space style={{ width: '100%', justifyContent: 'space-between' }} wrap>
|
||||
<Text type="secondary">关闭后,任何拿到链接的人都可以直接查看预览页</Text>
|
||||
<Button type="primary" loading={savingAccessPassword} onClick={saveAccessPassword}>关闭密码保护</Button>
|
||||
</Space>
|
||||
)}
|
||||
</>
|
||||
) : meeting?.access_password ? (
|
||||
<>
|
||||
<Divider style={{ margin: '0 0 16px' }} />
|
||||
<Text type="secondary">该分享链接已启用访问密码,密码由会议创建人管理。</Text>
|
||||
</>
|
||||
) : null}
|
||||
</QRCodeModal>
|
||||
<MeetingFormDrawer
|
||||
open={editDrawerOpen}
|
||||
onClose={() => setEditDrawerOpen(false)}
|
||||
|
|
|
|||
Loading…
Reference in New Issue