655 lines
24 KiB
TypeScript
655 lines
24 KiB
TypeScript
import { useState, useRef, useCallback } from 'react';
|
|
import { Button, Modal, Input, Table, Upload, Popconfirm, message } from 'antd';
|
|
import { FileTextOutlined, CloudUploadOutlined } from '@ant-design/icons';
|
|
import { useNavigate } from 'react-router-dom';
|
|
import { uploadVideo, createShotReplication, getShotReplicationList } from '../api';
|
|
import bg1 from '../assets/bg1.png';
|
|
|
|
export default function VideoFrameExtractor() {
|
|
const navigate = useNavigate();
|
|
|
|
const [videoUrl, setVideoUrl] = useState<string>('');
|
|
const [error, setError] = useState<string>('');
|
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
const [productName, setProductName] = useState<string>('');
|
|
const [videoDuration, setVideoDuration] = useState<number>(0);
|
|
const [loading, setLoading] = useState(false);
|
|
const [tableData, setTableData] = useState<any[]>([]);
|
|
const [currentPage, setCurrentPage] = useState(1);
|
|
const [pageSize, setPageSize] = useState(10);
|
|
const [total, setTotal] = useState(0);
|
|
const [searchKeyword, setSearchKeyword] = useState('');
|
|
|
|
const videoRef = useRef<HTMLVideoElement>(null);
|
|
const canvasRef = useRef<HTMLCanvasElement>(null);
|
|
|
|
const cleanupResources = useCallback(() => {
|
|
if (videoUrl) {
|
|
URL.revokeObjectURL(videoUrl);
|
|
}
|
|
setVideoUrl('');
|
|
setError('');
|
|
setVideoDuration(0);
|
|
setProductName('');
|
|
}, [videoUrl]);
|
|
|
|
const handleFileChange = async (file: File) => {
|
|
if (!file.type.startsWith('video/')) {
|
|
setError('请选择视频文件');
|
|
return false;
|
|
}
|
|
|
|
if (file.size > 100 * 1024 * 1024) {
|
|
setError('视频文件大小不能超过 100MB');
|
|
return false;
|
|
}
|
|
|
|
const duration = await new Promise<number>((resolve) => {
|
|
const video = document.createElement('video');
|
|
video.preload = 'metadata';
|
|
video.onloadedmetadata = () => {
|
|
resolve(video.duration);
|
|
URL.revokeObjectURL(video.src);
|
|
};
|
|
video.onerror = () => {
|
|
resolve(0);
|
|
};
|
|
video.src = URL.createObjectURL(file);
|
|
});
|
|
|
|
if (duration > 60) {
|
|
setError('视频时长不能超过 1分钟');
|
|
return false;
|
|
}
|
|
|
|
cleanupResources();
|
|
setLoading(true);
|
|
|
|
try {
|
|
const uploadResult = await uploadVideo(file);
|
|
setVideoUrl(uploadResult.url);
|
|
setError('');
|
|
message.success('视频上传成功');
|
|
} catch (err) {
|
|
setError('视频上传失败,请重试');
|
|
message.error('视频上传失败');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
const handleVideoLoaded = useCallback(() => {
|
|
if (videoRef.current) {
|
|
setVideoDuration(videoRef.current.duration);
|
|
}
|
|
}, []);
|
|
|
|
const handleCreate = async () => {
|
|
if (!videoUrl || !productName.trim()) {
|
|
message.warning('请先上传视频并输入产品名称');
|
|
return;
|
|
}
|
|
|
|
setLoading(true);
|
|
try {
|
|
const params = {
|
|
video_url: videoUrl,
|
|
video_duration_seconds: videoDuration,
|
|
title: productName.trim(),
|
|
idempotency_key: `shot_${Date.now()}`,
|
|
};
|
|
|
|
createShotReplication(params).then((res) => {
|
|
// message.success('任务创建成功');
|
|
getShotReplicationList(1, 20, searchKeyword).then((res) => {
|
|
// console.log('获取列表成功', res.items[0].id);
|
|
message.loading('创建中...', 3);
|
|
|
|
setTimeout(() => {
|
|
navigate(`/removelens/${res.items[0].id}/removeinfo`);
|
|
}, 3000);
|
|
|
|
// setTableData(res.items || []);
|
|
})
|
|
|
|
|
|
})
|
|
cleanupResources();
|
|
} catch (err) {
|
|
message.error('任务创建失败,请重试');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const fetchList = async (page: number, size: number, keyword?: string) => {
|
|
try {
|
|
const res = await getShotReplicationList(page, size, keyword);
|
|
setTableData(res.items || []);
|
|
setTotal(res.total || 0);
|
|
setCurrentPage(page);
|
|
setPageSize(size);
|
|
} catch (err) {
|
|
message.error('获取列表失败');
|
|
}
|
|
};
|
|
|
|
const handlePageChange = (page: number, size: number) => {
|
|
fetchList(page, size, searchKeyword);
|
|
};
|
|
|
|
const handleSearch = () => {
|
|
fetchList(1, 10, searchKeyword);
|
|
};
|
|
|
|
const handleOpenModal = () => {
|
|
setIsModalOpen(true);
|
|
fetchList(1, 10, searchKeyword);
|
|
};
|
|
|
|
return (
|
|
<div
|
|
style={{
|
|
margin: '-24px -32px -32px',
|
|
borderRadius: 20,
|
|
minHeight: 'calc(100vh - 34px)',
|
|
overflow: 'auto',
|
|
background: 'linear-gradient(135deg, #f8fafc 0%, #eef2ff 50%, #f0f9ff 100%)',
|
|
position: 'relative',
|
|
padding: '20px 32px',
|
|
backgroundImage: `url(${bg1})`,
|
|
backgroundRepeat: 'no-repeat',
|
|
backgroundSize: '100% 100%',
|
|
backgroundPosition: 'center',
|
|
}}
|
|
>
|
|
|
|
|
|
<div style={{ maxWidth: 1200, margin: '0 auto', position: 'relative', zIndex: 10 }}>
|
|
<div className="animate-fadeInUp" style={{
|
|
display: 'flex', justifyContent: 'space-between', alignItems: 'center',
|
|
marginBottom: 24, padding: '16px 24px', borderRadius: 16,
|
|
background: 'rgba(255,255,255,0.6)',
|
|
backdropFilter: 'blur(10px)',
|
|
border: '1px solid rgba(99, 102, 241, 0.08)',
|
|
position: 'relative', overflow: 'hidden', flexWrap: 'wrap', gap: 12,
|
|
}}>
|
|
<div style={{ display: 'flex', alignItems: 'center', gap: 16 }}>
|
|
<div style={{ width: 32, height: 2, background: 'linear-gradient(90deg, transparent, #6366f1, #8b5cf6, transparent)', borderRadius: 1 }} />
|
|
<div>
|
|
<h2 style={{
|
|
margin: 0,
|
|
fontSize: 18,
|
|
fontWeight: 700,
|
|
background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)',
|
|
WebkitBackgroundClip: 'text',
|
|
WebkitTextFillColor: 'transparent',
|
|
backgroundClip: 'text'
|
|
}}>
|
|
AI视频拆镜工作台
|
|
</h2>
|
|
<p style={{ fontSize: 13, color: '#64748b', margin: '4px 0 0 0' }}>
|
|
一键拆解画面分镜,助力仿拍或创作
|
|
</p>
|
|
</div>
|
|
<div style={{ width: 32, height: 2, background: 'linear-gradient(90deg, transparent, #8b5cf6, #6366f1, transparent)', borderRadius: 1 }} />
|
|
</div>
|
|
|
|
<Button
|
|
type="primary"
|
|
ghost
|
|
icon={<FileTextOutlined />}
|
|
onClick={handleOpenModal}
|
|
style={{
|
|
borderRadius: 10,
|
|
padding: '6px 16px',
|
|
fontSize: 13,
|
|
background: 'rgba(99, 102, 241, 0.1)',
|
|
borderColor: 'rgba(99, 102, 241, 0.2)',
|
|
color: '#6366f1',
|
|
}}
|
|
>
|
|
创作记录
|
|
</Button>
|
|
</div>
|
|
|
|
<div style={{ width: '100%', maxWidth: 720, margin: '0 auto' }}>
|
|
{error && (
|
|
<div style={{
|
|
background: 'linear-gradient(135deg, rgba(239, 68, 68, 0.1) 0%, rgba(239, 68, 68, 0.05) 100%)',
|
|
backdropFilter: 'blur(10px)',
|
|
border: '1px solid rgba(239, 68, 68, 0.2)',
|
|
borderRadius: 12,
|
|
padding: '14px 20px',
|
|
marginBottom: 24,
|
|
color: '#ef4444',
|
|
fontSize: 14,
|
|
textAlign: 'center',
|
|
fontWeight: 500,
|
|
}}>
|
|
{error}
|
|
</div>
|
|
)}
|
|
|
|
{!videoUrl && (
|
|
<div
|
|
style={{
|
|
background: 'linear-gradient(135deg, rgba(255,255,255,0.95) 0%, rgba(255,255,255,0.85) 100%)',
|
|
backdropFilter: 'blur(20px)',
|
|
borderRadius: 24,
|
|
border: '1px solid rgba(99, 102, 241, 0.1)',
|
|
boxShadow: '0 8px 32px rgba(99, 102, 241, 0.08)',
|
|
position: 'relative',
|
|
overflow: 'hidden',
|
|
}}
|
|
>
|
|
<div style={{ position: 'absolute', top: -50, right: -50, width: 200, height: 200, background: 'radial-gradient(circle, rgba(99,102,241,0.05) 0%, transparent 70%)', borderRadius: '50%' }} />
|
|
|
|
<Upload.Dragger
|
|
accept="video/*"
|
|
beforeUpload={handleFileChange}
|
|
showUploadList={false}
|
|
disabled={loading}
|
|
style={{
|
|
background: loading ? 'rgba(243, 244, 246, 0.3)' : 'transparent',
|
|
border: `2px dashed ${loading ? 'rgba(209, 213, 219, 0.5)' : 'rgba(99, 102, 241, 0.3)'}`,
|
|
borderRadius: 24,
|
|
padding: '56px 20px',
|
|
cursor: loading ? 'not-allowed' : 'pointer',
|
|
margin: 0,
|
|
}}
|
|
>
|
|
{loading ? (
|
|
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
|
|
<div style={{
|
|
width: 100,
|
|
height: 100,
|
|
margin: '0 auto 20px',
|
|
background: 'linear-gradient(135deg, rgba(99, 102, 241, 0.1) 0%, rgba(139, 92, 246, 0.1) 100%)',
|
|
borderRadius: 24,
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'center'
|
|
}}>
|
|
<div style={{
|
|
width: 40,
|
|
height: 40,
|
|
border: '4px solid #6366f1',
|
|
borderTopColor: 'transparent',
|
|
borderRadius: '50%',
|
|
animation: 'spin 1s linear infinite'
|
|
}} />
|
|
</div>
|
|
<p style={{ fontSize: 16, color: '#6366f1', margin: '0 0 8px 0', fontWeight: 600 }}>
|
|
上传中...
|
|
</p>
|
|
<p style={{ fontSize: 13, color: '#64748b', margin: 0 }}>
|
|
请稍候,视频正在上传
|
|
</p>
|
|
</div>
|
|
) : (
|
|
<>
|
|
<div style={{
|
|
width: 100,
|
|
height: 100,
|
|
margin: '0 auto 20px',
|
|
background: 'linear-gradient(135deg, rgba(99, 102, 241, 0.1) 0%, rgba(139, 92, 246, 0.1) 100%)',
|
|
borderRadius: 24,
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'center'
|
|
}}>
|
|
<CloudUploadOutlined style={{ fontSize: 40, color: '#6366f1' }} />
|
|
</div>
|
|
<p style={{ fontSize: 16, color: '#1e293b', margin: '0 0 8px 0', fontWeight: 600 }}>
|
|
点击或拖拽到此上传视频文件
|
|
</p>
|
|
<p style={{ fontSize: 13, color: '#64748b', margin: 0 }}>
|
|
支持 MP4、MOV 格式视频,最大支持 100MB 的文件,支持上传 1 分钟内的视频
|
|
</p>
|
|
</>
|
|
)}
|
|
</Upload.Dragger>
|
|
</div>
|
|
)}
|
|
|
|
{videoUrl && (
|
|
<div
|
|
style={{
|
|
background: 'linear-gradient(135deg, rgba(255,255,255,0.95) 0%, rgba(255,255,255,0.85) 100%)',
|
|
backdropFilter: 'blur(20px)',
|
|
borderRadius: 24,
|
|
border: '1px solid rgba(99, 102, 241, 0.1)',
|
|
boxShadow: '0 8px 32px rgba(99, 102, 241, 0.08)',
|
|
overflow: 'hidden',
|
|
position: 'relative',
|
|
}}
|
|
>
|
|
<div style={{ position: 'absolute', top: -50, right: -50, width: 200, height: 200, background: 'radial-gradient(circle, rgba(99,102,241,0.05) 0%, transparent 70%)', borderRadius: '50%' }} />
|
|
|
|
<div style={{
|
|
borderRadius: 22,
|
|
background: '#ffffff',
|
|
padding: '24px',
|
|
position: 'relative',
|
|
zIndex: 1,
|
|
}}>
|
|
<Popconfirm
|
|
title="确定要删除吗?"
|
|
okText="确定"
|
|
cancelText="取消"
|
|
onConfirm={cleanupResources}
|
|
>
|
|
<button
|
|
style={{
|
|
position: 'absolute',
|
|
top: 20,
|
|
right: 20,
|
|
width: 36,
|
|
height: 36,
|
|
border: '1px solid rgba(99, 102, 241, 0.2)',
|
|
background: 'rgba(99, 102, 241, 0.05)',
|
|
borderRadius: 10,
|
|
cursor: 'pointer',
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
fontSize: 16,
|
|
color: '#6366f1',
|
|
zIndex: 10,
|
|
transition: 'all 0.2s ease',
|
|
}}
|
|
onMouseEnter={(e) => {
|
|
e.currentTarget.style.background = 'rgba(239, 68, 68, 0.1)';
|
|
e.currentTarget.style.borderColor = 'rgba(239, 68, 68, 0.3)';
|
|
e.currentTarget.style.color = '#ef4444';
|
|
}}
|
|
onMouseLeave={(e) => {
|
|
e.currentTarget.style.background = 'rgba(99, 102, 241, 0.05)';
|
|
e.currentTarget.style.borderColor = 'rgba(99, 102, 241, 0.2)';
|
|
e.currentTarget.style.color = '#6366f1';
|
|
}}
|
|
>
|
|
🗑️
|
|
</button>
|
|
</Popconfirm>
|
|
|
|
<div style={{
|
|
textAlign: 'center',
|
|
marginBottom: 20
|
|
}}>
|
|
<video
|
|
ref={videoRef}
|
|
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${videoUrl}`}
|
|
controls
|
|
onLoadedMetadata={handleVideoLoaded}
|
|
style={{
|
|
maxWidth: '100%',
|
|
maxHeight: 320,
|
|
borderRadius: 12,
|
|
background: '#000',
|
|
boxShadow: '0 4px 16px rgba(0,0,0,0.1)'
|
|
}}
|
|
/>
|
|
</div>
|
|
|
|
<div style={{ marginBottom: 24 }}>
|
|
<label style={{
|
|
display: 'block',
|
|
fontSize: 14,
|
|
fontWeight: 500,
|
|
color: '#334155',
|
|
marginBottom: 8
|
|
}}>
|
|
产品名称 <span style={{ color: '#ef4444' }}>*</span>
|
|
</label>
|
|
<Input
|
|
placeholder="请输入产品名称"
|
|
value={productName}
|
|
onChange={(e) => setProductName(e.target.value)}
|
|
style={{
|
|
width: '100%',
|
|
height: 44,
|
|
borderRadius: 10,
|
|
border: '1px solid rgba(99, 102, 241, 0.2)',
|
|
transition: 'all 0.2s ease',
|
|
}}
|
|
onMouseEnter={(e) => {
|
|
e.currentTarget.style.borderColor = 'rgba(99, 102, 241, 0.4)';
|
|
}}
|
|
onMouseLeave={(e) => {
|
|
e.currentTarget.style.borderColor = 'rgba(99, 102, 241, 0.2)';
|
|
}}
|
|
/>
|
|
</div>
|
|
|
|
<div style={{ textAlign: 'center', marginTop: 16 }}>
|
|
<button
|
|
onClick={handleCreate}
|
|
disabled={!videoUrl || !productName.trim() || loading}
|
|
style={{
|
|
padding: '14px 56px',
|
|
background: videoUrl && productName.trim() && !loading
|
|
? 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 50%, #a855f7 100%)'
|
|
: '#e2e8f0',
|
|
color: 'white',
|
|
border: 'none',
|
|
borderRadius: 12,
|
|
fontSize: 15,
|
|
fontWeight: 600,
|
|
cursor: videoUrl && productName.trim() && !loading ? 'pointer' : 'not-allowed',
|
|
boxShadow: videoUrl && productName.trim() && !loading ? '0 6px 20px rgba(99, 102, 241, 0.3)' : 'none',
|
|
display: 'inline-flex',
|
|
alignItems: 'center',
|
|
gap: 10,
|
|
transition: 'all 0.3s ease',
|
|
}}
|
|
onMouseEnter={(e) => {
|
|
if (videoUrl && productName.trim() && !loading) {
|
|
e.currentTarget.style.transform = 'translateY(-2px)';
|
|
e.currentTarget.style.boxShadow = '0 8px 25px rgba(99, 102, 241, 0.4)';
|
|
}
|
|
}}
|
|
onMouseLeave={(e) => {
|
|
if (videoUrl && productName.trim() && !loading) {
|
|
e.currentTarget.style.transform = 'translateY(0)';
|
|
e.currentTarget.style.boxShadow = '0 6px 20px rgba(99, 102, 241, 0.3)';
|
|
}
|
|
}}
|
|
>
|
|
<span>{loading ? '创建中...' : '创建'}</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
<canvas ref={canvasRef} style={{ display: 'none' }} />
|
|
|
|
<Modal
|
|
title={
|
|
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
|
|
<div style={{ width: 4, height: 20, background: 'linear-gradient(180deg, #6366f1 0%, #8b5cf6 100%)', borderRadius: 2 }} />
|
|
<span style={{
|
|
fontSize: 16,
|
|
fontWeight: 700,
|
|
background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)',
|
|
WebkitBackgroundClip: 'text',
|
|
WebkitTextFillColor: 'transparent',
|
|
backgroundClip: 'text'
|
|
}}>
|
|
创作记录
|
|
</span>
|
|
</div>
|
|
}
|
|
open={isModalOpen}
|
|
onCancel={() => setIsModalOpen(false)}
|
|
width={850}
|
|
footer={null}
|
|
style={{ borderRadius: 20 }}
|
|
styles={{
|
|
body: { height: 580, display: 'flex', flexDirection: 'column', padding: 0 },
|
|
header: { background: 'rgba(255,255,255,0.6)', backdropFilter: 'blur(10px)', borderBottom: '1px solid rgba(99, 102, 241, 0.08)', padding: '16px 24px' },
|
|
}}
|
|
>
|
|
{/* 搜索区域 */}
|
|
<div style={{ display: 'flex', justifyContent: 'flex-end', margin: '16px 24px', flexShrink: 0 }}>
|
|
<Input
|
|
placeholder="搜索产品名称"
|
|
value={searchKeyword}
|
|
onChange={(e) => setSearchKeyword(e.target.value)}
|
|
onPressEnter={handleSearch}
|
|
style={{
|
|
width: 220,
|
|
borderRadius: 10,
|
|
marginRight: 10,
|
|
border: '1px solid rgba(99, 102, 241, 0.15)',
|
|
background: 'rgba(255,255,255,0.8)',
|
|
}}
|
|
/>
|
|
<Button
|
|
type="primary"
|
|
onClick={handleSearch}
|
|
style={{
|
|
borderRadius: 10,
|
|
background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)',
|
|
border: 'none',
|
|
}}
|
|
>
|
|
搜索
|
|
</Button>
|
|
</div>
|
|
|
|
<div style={{ flex: 1, overflow: 'auto', padding: '0 24px 24px' }}>
|
|
<div style={{
|
|
background: 'rgba(255,255,255,0.85)',
|
|
backdropFilter: 'blur(10px)',
|
|
borderRadius: 16,
|
|
overflow: 'hidden',
|
|
border: '1px solid rgba(99, 102, 241, 0.08)',
|
|
boxShadow: '0 4px 16px rgba(99, 102, 241, 0.06)',
|
|
}}>
|
|
<Table
|
|
columns={[
|
|
{
|
|
title: '产品名称',
|
|
dataIndex: 'title',
|
|
key: 'title',
|
|
render: (text: string) => (
|
|
<span style={{ fontSize: 14, color: '#1e293b', fontWeight: 500 }}>{text}</span>
|
|
),
|
|
},
|
|
{
|
|
title: '创建时间',
|
|
dataIndex: 'createdAt',
|
|
key: 'createdAt',
|
|
render: (text: string) => {
|
|
if (!text) return '-';
|
|
const date = new Date(text);
|
|
const year = date.getFullYear();
|
|
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
const day = String(date.getDate()).padStart(2, '0');
|
|
const hours = String(date.getHours()).padStart(2, '0');
|
|
const minutes = String(date.getMinutes()).padStart(2, '0');
|
|
const seconds = String(date.getSeconds()).padStart(2, '0');
|
|
return <span style={{ fontSize: 13, color: '#64748b' }}>{`${year}-${month}-${day} ${hours}:${minutes}:${seconds}`}</span>;
|
|
},
|
|
},
|
|
{
|
|
title: '操作',
|
|
key: 'action',
|
|
align: 'center',
|
|
render: (record) => (
|
|
<Button
|
|
type="text"
|
|
onClick={() => navigate(`/removelens/${record.id}/removeinfo`)}
|
|
style={{
|
|
color: '#6366f1',
|
|
fontSize: 13,
|
|
padding: '4px 12px',
|
|
borderRadius: 6,
|
|
background: 'rgba(99, 102, 241, 0.1)',
|
|
}}
|
|
onMouseEnter={(e) => {
|
|
e.currentTarget.style.background = 'rgba(99, 102, 241, 0.15)';
|
|
}}
|
|
onMouseLeave={(e) => {
|
|
e.currentTarget.style.background = 'rgba(99, 102, 241, 0.1)';
|
|
}}
|
|
>
|
|
查看详情
|
|
</Button>
|
|
),
|
|
},
|
|
]}
|
|
dataSource={tableData}
|
|
rowKey="id"
|
|
pagination={{
|
|
current: currentPage,
|
|
pageSize: pageSize,
|
|
total: total,
|
|
showSizeChanger: true,
|
|
showQuickJumper: true,
|
|
showTotal: (total) => `共 ${total} 条`,
|
|
onChange: handlePageChange,
|
|
style: {
|
|
padding: '16px 24px',
|
|
borderTop: '1px solid rgba(99, 102, 241, 0.1)',
|
|
},
|
|
}}
|
|
style={{ fontSize: 13 }}
|
|
scroll={{ y: 350 }}
|
|
components={{
|
|
body: {
|
|
row: ({ className, style, ...rest }) => (
|
|
<tr
|
|
{...rest}
|
|
className={className}
|
|
style={{
|
|
...style,
|
|
transition: 'all 0.2s ease',
|
|
borderBottom: '1px solid rgba(99, 102, 241, 0.05)',
|
|
}}
|
|
/>
|
|
),
|
|
cell: ({ className, style, ...rest }) => (
|
|
<td
|
|
{...rest}
|
|
className={className}
|
|
style={{
|
|
...style,
|
|
padding: '16px 24px',
|
|
}}
|
|
/>
|
|
),
|
|
},
|
|
header: {
|
|
cell: ({ className, style, ...rest }) => (
|
|
<th
|
|
{...rest}
|
|
className={className}
|
|
style={{
|
|
...style,
|
|
background: 'rgba(99, 102, 241, 0.03)',
|
|
color: '#64748b',
|
|
fontWeight: 500,
|
|
fontSize: 13,
|
|
padding: '16px 24px',
|
|
borderBottom: 'none',
|
|
}}
|
|
/>
|
|
),
|
|
},
|
|
}}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</Modal>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|