修复重建会议弹窗内容样式
parent
3738e14716
commit
e85dfe5f77
|
|
@ -1,23 +1,164 @@
|
|||
import React, { useState } from 'react';
|
||||
import { Card, Form, Input, Button, Upload, message, Progress, Select, Row, Col } from 'antd';
|
||||
import { InboxOutlined, AudioOutlined, UserAddOutlined } from '@ant-design/icons';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
Card, Form, Input, Button, Upload, message, Progress, Select, Modal,
|
||||
Typography, Space, DatePicker, Row, Col, Avatar, Radio
|
||||
} from 'antd';
|
||||
import {
|
||||
AudioOutlined,
|
||||
CloudUploadOutlined,
|
||||
VideoCameraOutlined,
|
||||
CalendarOutlined,
|
||||
TagOutlined,
|
||||
FileTextOutlined,
|
||||
CheckOutlined,
|
||||
UserOutlined,
|
||||
RightOutlined
|
||||
} from '@ant-design/icons';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import dayjs from 'dayjs';
|
||||
import { api } from '../api';
|
||||
import PageHeader from '../components/PageHeader/PageHeader';
|
||||
|
||||
const { Dragger } = Upload;
|
||||
const { Option } = Select;
|
||||
const { Text, Title } = Typography;
|
||||
|
||||
const PromptSelector = ({ value, onChange, prompts }: { value?: number, onChange?: (val: number) => void, prompts: any[] }) => {
|
||||
if (!prompts || prompts.length === 0) {
|
||||
return (
|
||||
<div style={{ textAlign: 'center', padding: '12px', color: '#94a3b8', background: '#f8fafc', borderRadius: 8 }}>
|
||||
暂无可用模版
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(2, 1fr)',
|
||||
gap: 8,
|
||||
maxHeight: 220,
|
||||
overflowY: 'auto',
|
||||
padding: 2
|
||||
}}>
|
||||
{prompts.map(prompt => {
|
||||
const isSelected = value === prompt.id;
|
||||
return (
|
||||
<div
|
||||
key={prompt.id}
|
||||
onClick={() => onChange?.(prompt.id)}
|
||||
style={{
|
||||
padding: '8px 12px',
|
||||
borderRadius: 6,
|
||||
border: isSelected ? '1px solid #2563eb' : '1px solid #e2e8f0',
|
||||
backgroundColor: isSelected ? '#eff6ff' : '#fff',
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.2s',
|
||||
position: 'relative',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: 64,
|
||||
justifyContent: 'center'
|
||||
}}
|
||||
>
|
||||
<div style={{ display: 'flex', alignItems: 'center', marginBottom: 2 }}>
|
||||
<FileTextOutlined style={{ color: isSelected ? '#2563eb' : '#64748b', fontSize: 14, marginRight: 6 }} />
|
||||
<div style={{
|
||||
fontWeight: 600,
|
||||
color: isSelected ? '#1e293b' : '#334155',
|
||||
fontSize: 13,
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
flex: 1
|
||||
}}>
|
||||
{prompt.name}
|
||||
</div>
|
||||
<div style={{
|
||||
fontSize: 10,
|
||||
padding: '1px 4px',
|
||||
borderRadius: 3,
|
||||
backgroundColor: prompt.is_system ? '#e0f2fe' : '#fff7ed',
|
||||
color: prompt.is_system ? '#0284c7' : '#ea580c',
|
||||
marginLeft: 6,
|
||||
flexShrink: 0,
|
||||
lineHeight: '1.2'
|
||||
}}>
|
||||
{prompt.is_system ? '系统' : '个人'}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{
|
||||
fontSize: 11,
|
||||
color: isSelected ? '#60a5fa' : '#94a3b8',
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
paddingLeft: 20
|
||||
}}>
|
||||
{prompt.description || '暂无描述'}
|
||||
</div>
|
||||
|
||||
{isSelected && (
|
||||
<div style={{ position: 'absolute', bottom: 4, right: 4 }}>
|
||||
<CheckOutlined style={{ color: '#2563eb', fontSize: 10 }} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const MeetingLive: React.FC = () => {
|
||||
const [form] = Form.useForm();
|
||||
const [liveForm] = Form.useForm();
|
||||
const [uploadForm] = Form.useForm();
|
||||
|
||||
const [users, setUsers] = useState<any[]>([]);
|
||||
const [prompts, setPrompts] = useState<any[]>([]);
|
||||
|
||||
const [isLiveModalVisible, setIsLiveModalVisible] = useState(false);
|
||||
const [isUploadModalVisible, setIsUploadModalVisible] = useState(false);
|
||||
|
||||
const [uploading, setUploading] = useState(false);
|
||||
const [progress, setProgress] = useState(0);
|
||||
const [selectedFile, setSelectedFile] = useState<File | null>(null);
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleCreate = async (values: any) => {
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
const res = await api.createMeeting(values);
|
||||
const [usersData, promptsData] = await Promise.all([
|
||||
api.listUsers(),
|
||||
api.listPrompts({ scope: 'personal' })
|
||||
]);
|
||||
setUsers(usersData);
|
||||
setPrompts(promptsData.filter((p: any) => p.is_active === true || p.is_active === 1));
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch data:', error);
|
||||
message.error('加载基础数据失败');
|
||||
}
|
||||
};
|
||||
|
||||
const handleCreateLive = async (values: any) => {
|
||||
try {
|
||||
const payload = {
|
||||
title: values.title,
|
||||
participants: values.participants,
|
||||
prompt_id: values.promptTemplateId,
|
||||
tags: values.tags,
|
||||
meeting_time: values.time ? values.time.valueOf() : dayjs().valueOf(),
|
||||
type: 'live'
|
||||
};
|
||||
|
||||
const res = await api.createMeeting(payload);
|
||||
message.success('会议创建成功');
|
||||
setIsLiveModalVisible(false);
|
||||
navigate(`/meeting/history/${res.meeting_id}`);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
|
|
@ -25,13 +166,40 @@ const MeetingLive: React.FC = () => {
|
|||
}
|
||||
};
|
||||
|
||||
const handleUpload = async (file: File) => {
|
||||
const handleCreateUpload = async (values: any) => {
|
||||
if (!selectedFile) {
|
||||
message.error('请先上传录音文件');
|
||||
return;
|
||||
}
|
||||
|
||||
setUploading(true);
|
||||
setProgress(0);
|
||||
|
||||
try {
|
||||
// Mock progress since fetch doesn't support it easily without XHR
|
||||
const formData = new FormData();
|
||||
formData.append('file', selectedFile);
|
||||
formData.append('title', values.title);
|
||||
if (values.participants) {
|
||||
// Backend might expect list or comma separated string depending on implementation
|
||||
// Assuming api.ts handles FormData correctly, we append each participant or join them
|
||||
// If backend uses FastAPI List[str] = Form(...), we append multiple times
|
||||
// If backend uses str = Form(...), we join them.
|
||||
// Safer to try JSON string if complex, but standard form data usually repeats keys
|
||||
// For now, let's append as JSON string or multiple keys?
|
||||
// Let's assume the backend parses JSON for complex fields if passed as string
|
||||
// OR we append each one.
|
||||
// Given existing code elsewhere, usually standard form submission.
|
||||
// Let's iterate.
|
||||
values.participants.forEach((p: any) => formData.append('participants', String(p)));
|
||||
}
|
||||
if (values.promptTemplateId) formData.append('prompt_id', String(values.promptTemplateId));
|
||||
if (values.tags) values.tags.forEach((t: string) => formData.append('tags', t));
|
||||
if (values.time) formData.append('meeting_time', String(values.time.valueOf()));
|
||||
formData.append('type', 'upload');
|
||||
|
||||
// Simulated progress for better UX since fetch doesn't support upload progress natively easily
|
||||
const timer = setInterval(() => {
|
||||
setProgress((prev) => {
|
||||
setProgress(prev => {
|
||||
if (prev >= 90) {
|
||||
clearInterval(timer);
|
||||
return 90;
|
||||
|
|
@ -40,80 +208,372 @@ const MeetingLive: React.FC = () => {
|
|||
});
|
||||
}, 500);
|
||||
|
||||
const res = await api.uploadMeetingAudio(file);
|
||||
const res = await api.createMeeting(formData);
|
||||
|
||||
clearInterval(timer);
|
||||
setProgress(100);
|
||||
message.success('上传成功,开始转译');
|
||||
// Assuming upload returns meeting_id or we navigate to list
|
||||
if (res && res.meeting_id) {
|
||||
navigate(`/meeting/history/${res.meeting_id}`);
|
||||
} else {
|
||||
navigate('/meeting/history');
|
||||
}
|
||||
message.success('上传并创建成功');
|
||||
setIsUploadModalVisible(false);
|
||||
navigate(`/meeting/history/${res.meeting_id}`);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
message.error('上传失败');
|
||||
message.error('上传失败: ' + (error as any).message);
|
||||
} finally {
|
||||
setUploading(false);
|
||||
}
|
||||
return false; // Prevent default upload behavior
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ padding: 24 }}>
|
||||
<PageHeader
|
||||
title="新建会议"
|
||||
description="创建实时会议或上传音频文件进行转写"
|
||||
/>
|
||||
|
||||
<Row gutter={[24, 24]}>
|
||||
<Col span={12}>
|
||||
<Card title="创建实时会议" bordered={false} hoverable>
|
||||
<Form form={form} layout="vertical" onFinish={handleCreate}>
|
||||
<Form.Item name="title" label="会议标题" rules={[{ required: true }]}>
|
||||
<Input placeholder="请输入会议标题" prefix={<AudioOutlined />} />
|
||||
</Form.Item>
|
||||
<Form.Item name="participants" label="参会人">
|
||||
<Select mode="tags" placeholder="请输入参会人姓名" open={false}>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item>
|
||||
<Button type="primary" htmlType="submit" icon={<UserAddOutlined />} block>
|
||||
开始会议
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Card>
|
||||
</Col>
|
||||
const CardButton = ({
|
||||
icon,
|
||||
title,
|
||||
description,
|
||||
color,
|
||||
onClick
|
||||
}: {
|
||||
icon: React.ReactNode,
|
||||
title: string,
|
||||
description: string,
|
||||
color: string,
|
||||
onClick: () => void
|
||||
}) => (
|
||||
<Card
|
||||
hoverable
|
||||
style={{
|
||||
height: '100%',
|
||||
borderRadius: 16,
|
||||
border: '1px solid #f0f0f0',
|
||||
transition: 'all 0.3s ease',
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
bodyStyle={{ padding: 32, height: '100%', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', textAlign: 'center' }}
|
||||
onClick={onClick}
|
||||
>
|
||||
<div style={{
|
||||
width: 80,
|
||||
height: 80,
|
||||
borderRadius: '50%',
|
||||
background: `${color}15`, // 15% opacity
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
marginBottom: 24,
|
||||
color: color,
|
||||
fontSize: 36
|
||||
}}>
|
||||
{icon}
|
||||
</div>
|
||||
<Title level={4} style={{ marginBottom: 12 }}>{title}</Title>
|
||||
<Text type="secondary" style={{ fontSize: 14 }}>{description}</Text>
|
||||
<div style={{ marginTop: 24, color: color, display: 'flex', alignItems: 'center', fontWeight: 500 }}>
|
||||
立即开始 <RightOutlined style={{ marginLeft: 8, fontSize: 12 }} />
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
|
||||
const renderFormFields = (isUpload: boolean) => (
|
||||
<Row gutter={32}>
|
||||
<Col span={12}>
|
||||
<div style={{ marginBottom: 24 }}>
|
||||
<Text strong style={{ fontSize: 12, color: '#94a3b8', textTransform: 'uppercase', letterSpacing: '0.1em' }}>
|
||||
基本信息
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
<Col span={12}>
|
||||
<Card title="上传录音文件" variant="borderless" hoverable>
|
||||
<Form.Item
|
||||
name="title"
|
||||
rules={[{ required: true, message: '请输入会议标题' }]}
|
||||
>
|
||||
<Input
|
||||
size="large"
|
||||
placeholder="会议标题"
|
||||
prefix={<AudioOutlined style={{ color: '#bfbfbf' }} />}
|
||||
style={{ borderRadius: 12, padding: '10px 16px' }}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="time"
|
||||
initialValue={dayjs()}
|
||||
>
|
||||
<DatePicker
|
||||
showTime
|
||||
size="large"
|
||||
style={{ width: '100%', borderRadius: 12, padding: '10px 16px' }}
|
||||
format="YYYY-MM-DD HH:mm"
|
||||
placeholder="会议时间"
|
||||
suffixIcon={<CalendarOutlined style={{ color: '#bfbfbf' }} />}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="tags">
|
||||
<Select
|
||||
mode="tags"
|
||||
size="large"
|
||||
placeholder="业务标签 (输入回车添加)"
|
||||
style={{ width: '100%', borderRadius: 12 }}
|
||||
tokenSeparators={[',']}
|
||||
suffixIcon={<TagOutlined style={{ color: '#bfbfbf' }} />}
|
||||
tagRender={(props) => (
|
||||
<span style={{
|
||||
backgroundColor: '#eff6ff',
|
||||
color: '#2563eb',
|
||||
border: '1px solid #dbeafe',
|
||||
borderRadius: 6,
|
||||
padding: '2px 8px',
|
||||
marginRight: 4,
|
||||
fontSize: 12,
|
||||
fontWeight: 600
|
||||
}}>
|
||||
{props.label}
|
||||
<span onClick={props.onClose} style={{ marginLeft: 4, cursor: 'pointer' }}>×</span>
|
||||
</span>
|
||||
)}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
{isUpload && (
|
||||
<div style={{ marginTop: 24 }}>
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<Text strong style={{ fontSize: 12, color: '#94a3b8', textTransform: 'uppercase', letterSpacing: '0.1em' }}>
|
||||
上传录音 (必选)
|
||||
</Text>
|
||||
</div>
|
||||
<Dragger
|
||||
name="file"
|
||||
multiple={false}
|
||||
beforeUpload={handleUpload}
|
||||
beforeUpload={(file) => {
|
||||
setSelectedFile(file);
|
||||
const currentTitle = uploadForm.getFieldValue('title');
|
||||
if (!currentTitle) {
|
||||
uploadForm.setFieldsValue({ title: file.name.replace(/\.[^/.]+$/, "") });
|
||||
}
|
||||
return false;
|
||||
}}
|
||||
showUploadList={false}
|
||||
accept="audio/*,video/*"
|
||||
disabled={uploading}
|
||||
style={{
|
||||
padding: 24,
|
||||
background: selectedFile ? '#eff6ff' : '#f8fafc',
|
||||
border: selectedFile ? '2px solid #2563eb' : '2px dashed #cbd5e1',
|
||||
borderRadius: 16
|
||||
}}
|
||||
>
|
||||
<p className="ant-upload-drag-icon">
|
||||
<InboxOutlined />
|
||||
</p>
|
||||
<p className="ant-upload-text">点击或拖拽文件到此区域上传</p>
|
||||
<p className="ant-upload-hint">
|
||||
支持 MP3, WAV, M4A 等常见音频格式
|
||||
</p>
|
||||
{selectedFile ? (
|
||||
<div style={{ padding: '12px 0' }}>
|
||||
<FileTextOutlined style={{ fontSize: 32, color: '#2563eb', marginBottom: 12 }} />
|
||||
<div style={{ fontSize: 14, fontWeight: 600, color: '#1e293b' }}>{selectedFile.name}</div>
|
||||
<div style={{ fontSize: 12, color: '#64748b', marginTop: 4 }}>
|
||||
{(selectedFile.size / 1024 / 1024).toFixed(2)} MB
|
||||
</div>
|
||||
<Button
|
||||
type="text"
|
||||
danger
|
||||
size="small"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setSelectedFile(null);
|
||||
}}
|
||||
style={{ marginTop: 8 }}
|
||||
>
|
||||
移除文件
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<p className="ant-upload-drag-icon">
|
||||
<CloudUploadOutlined style={{ color: '#94a3b8' }} />
|
||||
</p>
|
||||
<p className="ant-upload-text" style={{ fontSize: 14, color: '#64748b' }}>点击或拖拽上传音频</p>
|
||||
<p className="ant-upload-hint" style={{ fontSize: 12, color: '#94a3b8' }}>
|
||||
支持 MP3, WAV, M4A
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
</Dragger>
|
||||
{uploading && (
|
||||
<div style={{ marginTop: 16 }}>
|
||||
<Progress percent={progress} status={progress === 100 ? 'success' : 'active'} />
|
||||
<div style={{ textAlign: 'center', marginTop: 8 }}>
|
||||
{progress === 100 ? '上传完成,正在处理...' : '上传中...'}
|
||||
<Progress percent={progress} status="active" strokeColor="#fa8c16" showInfo={false} size="small" />
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', marginTop: 4 }}>
|
||||
<Text style={{ fontSize: 12, color: '#fa8c16' }}>正在上传...</Text>
|
||||
<Text style={{ fontSize: 12, color: '#fa8c16' }}>{progress}%</Text>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
</div>
|
||||
)}
|
||||
</Col>
|
||||
|
||||
<Col span={12}>
|
||||
<div style={{ marginBottom: 24 }}>
|
||||
<Text strong style={{ fontSize: 12, color: '#94a3b8', textTransform: 'uppercase', letterSpacing: '0.1em' }}>
|
||||
参会人员
|
||||
</Text>
|
||||
</div>
|
||||
<Form.Item name="participants">
|
||||
<Select
|
||||
mode="multiple"
|
||||
size="large"
|
||||
placeholder="选择参会人员"
|
||||
style={{ width: '100%' }}
|
||||
optionLabelProp="label"
|
||||
dropdownStyle={{ borderRadius: 12, padding: 8 }}
|
||||
>
|
||||
{users.map(user => (
|
||||
<Option key={user.user_id} value={user.user_id} label={user.display_name || user.username}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', padding: '4px 0' }}>
|
||||
<Avatar size="small" style={{ backgroundColor: '#bfbfbf', marginRight: 8 }}>
|
||||
{(user.display_name || user.username)[0]}
|
||||
</Avatar>
|
||||
<div style={{ fontWeight: 500 }}>{user.display_name || user.username}</div>
|
||||
</div>
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
<div style={{ marginTop: 32, marginBottom: 24 }}>
|
||||
<Text strong style={{ fontSize: 12, color: '#94a3b8', textTransform: 'uppercase', letterSpacing: '0.1em' }}>
|
||||
会议模版
|
||||
</Text>
|
||||
</div>
|
||||
<Form.Item name="promptTemplateId">
|
||||
<PromptSelector prompts={prompts} />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
|
||||
return (
|
||||
<div style={{ padding: '24px', maxWidth: 1200, margin: '0 auto' }}>
|
||||
<PageHeader
|
||||
title="新建会议"
|
||||
description="选择会议类型以开始记录,系统将自动为您生成智能会议纪要"
|
||||
/>
|
||||
<div style={{ marginBottom: 40 }} />
|
||||
|
||||
<Row gutter={40} justify="center">
|
||||
<Col span={10}>
|
||||
<CardButton
|
||||
icon={<VideoCameraOutlined />}
|
||||
title="实时会议"
|
||||
description="开始即时会议录音,实时转写并生成会议纪要"
|
||||
color="#1890ff"
|
||||
onClick={() => setIsLiveModalVisible(true)}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={10}>
|
||||
<CardButton
|
||||
icon={<CloudUploadOutlined />}
|
||||
title="上传录音"
|
||||
description="上传已有的音频/视频文件,AI 自动整理会议内容"
|
||||
color="#fa8c16"
|
||||
onClick={() => setIsUploadModalVisible(true)}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
{/* Live Meeting Modal */}
|
||||
<Modal
|
||||
title={
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 12, paddingBottom: 12 }}>
|
||||
<div style={{
|
||||
background: '#e6f7ff',
|
||||
width: 40,
|
||||
height: 40,
|
||||
borderRadius: '50%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
color: '#1890ff',
|
||||
fontSize: 20
|
||||
}}>
|
||||
<VideoCameraOutlined />
|
||||
</div>
|
||||
<div>
|
||||
<div style={{ fontSize: 18, fontWeight: 700 }}>创建实时会议</div>
|
||||
<div style={{ fontSize: 12, color: '#94a3b8', fontWeight: 400 }}>配置会议信息并开始录制</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
open={isLiveModalVisible}
|
||||
onCancel={() => setIsLiveModalVisible(false)}
|
||||
footer={
|
||||
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: 12, paddingTop: 12 }}>
|
||||
<Button size="large" onClick={() => setIsLiveModalVisible(false)} style={{ borderRadius: 12 }}>取消</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
size="large"
|
||||
onClick={() => liveForm.submit()}
|
||||
icon={<VideoCameraOutlined />}
|
||||
style={{ borderRadius: 12, backgroundColor: '#1890ff' }}
|
||||
>
|
||||
立即开始
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
width={800}
|
||||
destroyOnClose
|
||||
centered
|
||||
styles={{ body: { padding: '24px 0 0 0' } }}
|
||||
>
|
||||
<Form form={liveForm} layout="vertical" onFinish={handleCreateLive}>
|
||||
{renderFormFields(false)}
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
{/* Upload Modal */}
|
||||
<Modal
|
||||
title={
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 12, paddingBottom: 12 }}>
|
||||
<div style={{
|
||||
background: '#fff7e6',
|
||||
width: 40,
|
||||
height: 40,
|
||||
borderRadius: '50%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
color: '#fa8c16',
|
||||
fontSize: 20
|
||||
}}>
|
||||
<CloudUploadOutlined />
|
||||
</div>
|
||||
<div>
|
||||
<div style={{ fontSize: 18, fontWeight: 700 }}>上传会议录音</div>
|
||||
<div style={{ fontSize: 12, color: '#94a3b8', fontWeight: 400 }}>支持 MP3, WAV, M4A 等常见音频格式</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
open={isUploadModalVisible}
|
||||
onCancel={() => {
|
||||
if (!uploading) setIsUploadModalVisible(false);
|
||||
}}
|
||||
footer={
|
||||
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: 12, paddingTop: 12 }}>
|
||||
<Button size="large" onClick={() => setIsUploadModalVisible(false)} disabled={uploading} style={{ borderRadius: 12 }}>取消</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
size="large"
|
||||
onClick={() => uploadForm.submit()}
|
||||
loading={uploading}
|
||||
icon={<CloudUploadOutlined />}
|
||||
style={{ borderRadius: 12, backgroundColor: '#fa8c16', borderColor: '#fa8c16' }}
|
||||
>
|
||||
{uploading ? '上传处理中...' : '开始上传'}
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
width={800}
|
||||
destroyOnClose
|
||||
centered
|
||||
maskClosable={!uploading}
|
||||
styles={{ body: { padding: '24px 0 0 0' } }}
|
||||
>
|
||||
<Form form={uploadForm} layout="vertical" onFinish={handleCreateUpload}>
|
||||
{renderFormFields(true)}
|
||||
</Form>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import { Button, Card, Tag, Space, Form, Input, InputNumber, Switch, message, Select, Modal, Tooltip, Divider } from 'antd';
|
||||
import { App, Button, Card, Tag, Space, Form, Input, InputNumber, Switch, Select, Modal, Tooltip, Divider } from 'antd';
|
||||
import {
|
||||
PlusOutlined,
|
||||
EditOutlined,
|
||||
|
|
@ -18,6 +18,7 @@ import ListTable from '../components/ListTable/ListTable';
|
|||
const { Option } = Select;
|
||||
|
||||
const PromptManage: React.FC = () => {
|
||||
const { message, modal } = App.useApp();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [data, setData] = useState<any[]>([]);
|
||||
const [categories, setCategories] = useState<any[]>([]);
|
||||
|
|
@ -63,7 +64,7 @@ const PromptManage: React.FC = () => {
|
|||
};
|
||||
|
||||
const handleDelete = (id: number) => {
|
||||
Modal.confirm({
|
||||
modal.confirm({
|
||||
title: '确认删除',
|
||||
content: '确定要删除此个人模板吗?',
|
||||
onOk: async () => {
|
||||
|
|
@ -74,10 +75,9 @@ const PromptManage: React.FC = () => {
|
|||
});
|
||||
};
|
||||
|
||||
const handleTogglePersonalActive = async (e: React.MouseEvent, record: any) => {
|
||||
e.stopPropagation();
|
||||
await api.updatePromptConfig(record.id, { is_active: !record.is_active });
|
||||
message.success(record.is_active ? '已隐藏' : '已显示');
|
||||
const handleTogglePersonalActive = async (checked: boolean, record: any) => {
|
||||
await api.updatePromptConfig(record.id, { is_active: checked });
|
||||
message.success(checked ? '已启用' : '已停用');
|
||||
fetchPrompts(searchKeyword);
|
||||
};
|
||||
|
||||
|
|
@ -128,20 +128,17 @@ const PromptManage: React.FC = () => {
|
|||
}
|
||||
},
|
||||
{
|
||||
title: '总结显示',
|
||||
title: '是否启用',
|
||||
dataIndex: 'is_active',
|
||||
key: 'is_active',
|
||||
width: 100,
|
||||
render: (active: boolean, record: any) => (
|
||||
<Button
|
||||
type="text"
|
||||
size="small"
|
||||
icon={active ? <EyeOutlined /> : <EyeInvisibleOutlined />}
|
||||
onClick={(e) => handleTogglePersonalActive(e, record)}
|
||||
style={{ color: active ? '#1890ff' : '#ccc' }}
|
||||
>
|
||||
{active ? '显示' : '隐藏'}
|
||||
</Button>
|
||||
<Switch
|
||||
checked={!!active}
|
||||
size="small"
|
||||
onChange={(checked) => handleTogglePersonalActive(checked, record)}
|
||||
onClick={(_, e) => e.stopPropagation()}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in New Issue