爆款开头复刻样式修改
This commit is contained in:
@@ -588,7 +588,7 @@ const VideoTrimPicker: React.FC<VideoTrimPickerProps> = ({
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div style={{ marginTop: -54, padding: '0 8px 0' }}>
|
||||
<div style={{ marginTop: 0, padding: '0 8px 0' }}>
|
||||
<Slider
|
||||
range
|
||||
min={0}
|
||||
@@ -616,7 +616,7 @@ const VideoTrimPicker: React.FC<VideoTrimPickerProps> = ({
|
||||
marginTop: 0,
|
||||
// backgroundColor: '#fff',
|
||||
// border: '3px solid #6366f1',
|
||||
boxShadow: '0 2px 8px rgba(99, 102, 241, 0.3)',
|
||||
// boxShadow: '0 2px 8px rgba(99, 102, 241, 0.3)',
|
||||
transition: 'all 0.2s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
},
|
||||
}}
|
||||
|
||||
@@ -830,7 +830,7 @@ const AIChatPage: React.FC = () => {
|
||||
// ==================== 渲染 ====================
|
||||
|
||||
return (
|
||||
<Layout style={{ height: 'calc(100vh - 90px)', background: '#fafafa', overflow: 'auto' }}>
|
||||
<Layout style={{ height: 'calc(100vh - 90px)', overflow: 'auto' }}>
|
||||
{/* 左侧边栏 - 对话列表(已隐藏,保留代码) */}
|
||||
{false && (
|
||||
<Sider
|
||||
@@ -917,12 +917,12 @@ const AIChatPage: React.FC = () => {
|
||||
)}
|
||||
|
||||
{/* 主内容区 */}
|
||||
<Layout style={{ flex: 1 }}>
|
||||
<Layout style={{ flex: 1, background: '#ffffffff' }}>
|
||||
{/* 头部 - 显示对话标题和模型信息 */}
|
||||
<Header
|
||||
style={{
|
||||
background: '#fff',
|
||||
padding: '0 24px',
|
||||
// padding: '0 24px',
|
||||
borderBottom: '1px solid #f0f0f0',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
@@ -945,10 +945,9 @@ const AIChatPage: React.FC = () => {
|
||||
<Content
|
||||
style={{
|
||||
margin: 0,
|
||||
background: '#fafafa',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
padding: 24,
|
||||
// padding: 24,
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { useEffect, useState, useLayoutEffect, useRef, useCallback } from 'react';
|
||||
import { Button, Empty, Input, Select, Space, Typography, Tag, message, Upload, Modal, Progress, Table } from 'antd';
|
||||
import { Button, Empty, Input, Select, Space, Typography, Tag, message, Upload, Modal, Progress, Table, DatePicker } from 'antd';
|
||||
import dayjs from 'dayjs';
|
||||
import {
|
||||
SearchOutlined,
|
||||
FilterOutlined,
|
||||
@@ -32,6 +33,7 @@ const GeneratedRecord: React.FC = () => {
|
||||
const [previewVisible, setPreviewVisible] = useState(false);
|
||||
const [previewItem, setPreviewItem] = useState<any>(null);
|
||||
const videoRef = React.createRef<HTMLVideoElement>();
|
||||
const [selectedDate, setSelectedDate] = useState<string>('');
|
||||
|
||||
// 批量上传相关状态
|
||||
const [uploadModalVisible, setUploadModalVisible] = useState(false);
|
||||
@@ -52,7 +54,7 @@ const GeneratedRecord: React.FC = () => {
|
||||
authStatus: 'pending' | 'authorized' | 'failed';
|
||||
}[]>([]);
|
||||
const [accountIdInput, setAccountIdInput] = useState('');
|
||||
|
||||
|
||||
const [oauthList, setOauthList] = useState<any[]>([]);
|
||||
const [oauthLoading, setOauthLoading] = useState(false);
|
||||
const [oauthTotal, setOauthTotal] = useState(0);
|
||||
@@ -60,7 +62,7 @@ const GeneratedRecord: React.FC = () => {
|
||||
const [oauthPage, setOauthPage] = useState(1);
|
||||
const [oauthPageSize, setOauthPageSize] = useState(10);
|
||||
const [oauthSelectOpen, setOauthSelectOpen] = useState(false);
|
||||
|
||||
|
||||
const [batchUploadProgress, setBatchUploadProgress] = useState<{
|
||||
itemId: string;
|
||||
status: 'pending' | 'uploading' | 'success' | 'error';
|
||||
@@ -290,7 +292,7 @@ const GeneratedRecord: React.FC = () => {
|
||||
borderRadius: 4,
|
||||
cursor: 'pointer',
|
||||
overflow: 'hidden',
|
||||
boxShadow: '0 2px 8px rgba(0,0,0,0.1)',
|
||||
boxShadow: '0 2px 8px rgba(0,0,0,0.1)',
|
||||
transition: 'transform 0.2s, box-shadow 0.2s',
|
||||
}}
|
||||
onClick={!isSelectionMode ? onClick : undefined}
|
||||
@@ -307,7 +309,7 @@ const GeneratedRecord: React.FC = () => {
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
||||
|
||||
{/* 媒体内容 - 当isLoading为true时开始渲染,加载完成后显示 */}
|
||||
{((mediaType === 'video' && item.videoCoverUrl) || mediaType === 'image') && (isLoading || isLoaded) && !isError && (
|
||||
<div style={{
|
||||
@@ -664,7 +666,7 @@ const GeneratedRecord: React.FC = () => {
|
||||
const res = await getOAuthList({ page, page_size: pageSize });
|
||||
const data = res?.data || res;
|
||||
console.log(res);
|
||||
setOauthList(data|| []);
|
||||
setOauthList(data || []);
|
||||
setOauthTotal(res.pagination.total || 0);
|
||||
} catch (error) {
|
||||
console.error('加载授权列表失败:', error);
|
||||
@@ -678,7 +680,7 @@ const GeneratedRecord: React.FC = () => {
|
||||
const handleStartBatchUpload = async () => {
|
||||
setUploading(true);
|
||||
const uploadProgressMap: { [key: string]: { status: 'pending' | 'uploading' | 'success' | 'error'; message: string } } = {};
|
||||
|
||||
|
||||
for (const itemId of selectedItems) {
|
||||
uploadProgressMap[itemId] = { status: 'pending', message: '' };
|
||||
}
|
||||
@@ -771,6 +773,17 @@ const GeneratedRecord: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
// 日期选择器变化处理函数
|
||||
const handleDateChange = (dateString: string) => {
|
||||
console.log(dateString);
|
||||
|
||||
console.log('qweqwe');
|
||||
|
||||
setSelectedDate(dateString);
|
||||
|
||||
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(true);
|
||||
let parameters = '';
|
||||
@@ -779,27 +792,70 @@ const GeneratedRecord: React.FC = () => {
|
||||
} else {
|
||||
parameters = `?gen_type=${filterMedia}&page=${Pagebreak.page}&page_size=${Pagebreak.pageSize}`;
|
||||
}
|
||||
gethistory(parameters).then((res: any) => {
|
||||
const data = Array.isArray(res) ? res : (res?.groups || []);
|
||||
data.forEach(group => {
|
||||
group.page = 1;
|
||||
});
|
||||
// 如果是第一页,替换数据;否则追加数据
|
||||
if (Pagebreak.page === 1) {
|
||||
setRecordList(data);
|
||||
} else {
|
||||
setRecordList(prev => [...prev, ...data]);
|
||||
}
|
||||
|
||||
setTotalnumber(res?.totalDays || 0);
|
||||
}).catch((err) => {
|
||||
if (Pagebreak.page === 1) {
|
||||
setRecordList([]);
|
||||
if (selectedDate) {
|
||||
let parameters = ``;
|
||||
|
||||
if (filterType === 'project') {
|
||||
parameters = `${selectedDate}?gen_type=${filterMedia}&history_source=generation_record&page=${Pagebreak.page}&page_size=${Pagebreak.pageSize}`;
|
||||
} else {
|
||||
parameters = `${selectedDate}?gen_type=${filterMedia}&page=${Pagebreak.page}&page_size=${Pagebreak.pageSize}`;
|
||||
}
|
||||
}).finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
}, [filterType, filterMedia, Pagebreak.page]);
|
||||
gethistoryItems(parameters).then((res: any) => {
|
||||
const data = Array.isArray(res) ? res : (res?.items || []);
|
||||
let recordList = [{
|
||||
generatedDate: res.generatedDate,
|
||||
items: data,
|
||||
total: res.total,
|
||||
page: res.page,
|
||||
}];
|
||||
console.log('data', data);
|
||||
if (recordList && recordList[0].items.length > 0) {
|
||||
setRecordList(recordList);
|
||||
|
||||
}else{
|
||||
setRecordList([]);
|
||||
}
|
||||
|
||||
console.log('recordList', recordList);
|
||||
|
||||
}).catch((err) => {
|
||||
}).finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
|
||||
try {
|
||||
} catch (err) {
|
||||
} finally {
|
||||
|
||||
}
|
||||
} else {
|
||||
gethistory(parameters).then((res: any) => {
|
||||
const data = Array.isArray(res) ? res : (res?.groups || []);
|
||||
data.forEach(group => {
|
||||
group.page = 1;
|
||||
});
|
||||
console.log('qweqweqwe', data);
|
||||
|
||||
// 如果是第一页,替换数据;否则追加数据
|
||||
if (Pagebreak.page === 1) {
|
||||
setRecordList(data);
|
||||
} else {
|
||||
setRecordList(prev => [...prev, ...data]);
|
||||
}
|
||||
|
||||
setTotalnumber(res?.totalDays || 0);
|
||||
}).catch((err) => {
|
||||
if (Pagebreak.page === 1) {
|
||||
setRecordList([]);
|
||||
}
|
||||
}).finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}, [filterType, filterMedia, Pagebreak.page, selectedDate]);
|
||||
|
||||
useEffect(() => {
|
||||
setPreTestLoading(true);
|
||||
@@ -878,7 +934,7 @@ const GeneratedRecord: React.FC = () => {
|
||||
}, [filterType, filterMedia]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div style={{ minHeight: 'calc(100vh - 90px)', background: '#ffffffff' , overflowY: 'auto'}} >
|
||||
{/* Header */}
|
||||
{/* <div style={{ marginBottom: 20 }}>
|
||||
<Typography.Title level={3} style={{ margin: '0 0 4px', color: '#1a1a2e', fontWeight: 700 }}>
|
||||
@@ -1047,6 +1103,23 @@ const GeneratedRecord: React.FC = () => {
|
||||
>
|
||||
图片
|
||||
</Button>
|
||||
<DatePicker
|
||||
picker="date"
|
||||
value={selectedDate ? dayjs(selectedDate) : undefined}
|
||||
onChange={(date, dateString) => handleDateChange(dateString || '')}
|
||||
format="YYYY-MM-DD"
|
||||
style={{ width: 160, borderRadius: 8, border: '1px solid #e2e8f0' }}
|
||||
placeholder="选择日期"
|
||||
/>
|
||||
{selectedDate && (
|
||||
<Button
|
||||
type="text"
|
||||
onClick={() => handleDateChange('')}
|
||||
style={{ color: '#94a3b8', fontSize: 12 }}
|
||||
>
|
||||
清除
|
||||
</Button>
|
||||
)}
|
||||
</Space>
|
||||
</div>
|
||||
|
||||
@@ -1292,7 +1365,7 @@ const GeneratedRecord: React.FC = () => {
|
||||
<Typography.Text strong style={{ fontSize: 14, color: '#475569', marginBottom: 8, display: 'block' }}>
|
||||
选择授权账户(可多选)
|
||||
</Typography.Text>
|
||||
|
||||
|
||||
<Select
|
||||
mode="multiple"
|
||||
value={selectedOauthItems}
|
||||
@@ -1301,12 +1374,12 @@ const GeneratedRecord: React.FC = () => {
|
||||
const selectedAccounts = oauthList
|
||||
.filter(item => value.includes(String(item.id)))
|
||||
.map(item => ({ accountId: String(item.accountId), authStatus: 'authorized' as const }));
|
||||
|
||||
|
||||
const textAccounts = accountIdInput.split('\n')
|
||||
.map(line => line.trim())
|
||||
.filter(line => line.length > 0)
|
||||
.map(id => ({ accountId: id, authStatus: 'pending' as const }));
|
||||
|
||||
|
||||
const mergedAccounts = [...selectedAccounts, ...textAccounts];
|
||||
const seen = new Set<string>();
|
||||
const finalAccounts = mergedAccounts.filter(a => {
|
||||
@@ -1314,7 +1387,7 @@ const GeneratedRecord: React.FC = () => {
|
||||
seen.add(a.accountId);
|
||||
return true;
|
||||
});
|
||||
|
||||
|
||||
setAccountIdList(finalAccounts);
|
||||
}}
|
||||
placeholder="点击选择授权账户"
|
||||
@@ -1417,24 +1490,24 @@ const GeneratedRecord: React.FC = () => {
|
||||
<Typography.Text strong style={{ fontSize: 14, color: '#475569', marginBottom: 8, display: 'block', marginTop: 16 }}>
|
||||
粘贴账户ID(每行一个)
|
||||
</Typography.Text>
|
||||
|
||||
|
||||
<Input.TextArea
|
||||
value={accountIdInput}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value;
|
||||
setAccountIdInput(value);
|
||||
|
||||
|
||||
const ids = value.split('\n')
|
||||
.map(line => line.trim())
|
||||
.filter(line => line.length > 0);
|
||||
|
||||
|
||||
const uniqueIds = [...new Set(ids)];
|
||||
const textAccounts = uniqueIds.map(id => ({ accountId: id, authStatus: 'pending' as const }));
|
||||
|
||||
|
||||
const oauthAccounts = oauthList
|
||||
.filter(item => selectedOauthItems.includes(String(item.id)))
|
||||
.map(item => ({ accountId: String(item.accountId), authStatus: 'authorized' as const }));
|
||||
|
||||
|
||||
const mergedAccounts = [...oauthAccounts, ...textAccounts];
|
||||
const seen = new Set<string>();
|
||||
const finalAccounts = mergedAccounts.filter(a => {
|
||||
@@ -1442,7 +1515,7 @@ const GeneratedRecord: React.FC = () => {
|
||||
seen.add(a.accountId);
|
||||
return true;
|
||||
});
|
||||
|
||||
|
||||
setAccountIdList(finalAccounts);
|
||||
}}
|
||||
placeholder="粘贴账户ID,每行一个,例如:
|
||||
@@ -1458,7 +1531,7 @@ const GeneratedRecord: React.FC = () => {
|
||||
<Typography.Text style={{ fontSize: 13, color: '#64748b', marginBottom: 8, display: 'block' }}>
|
||||
已导入 {accountIdList.length} 个账户
|
||||
</Typography.Text>
|
||||
|
||||
|
||||
<Table
|
||||
dataSource={accountIdList.map((item, index) => ({ ...item, key: index }))}
|
||||
columns={[
|
||||
@@ -1493,7 +1566,7 @@ const GeneratedRecord: React.FC = () => {
|
||||
borderRadius: 8,
|
||||
}}
|
||||
/>
|
||||
|
||||
|
||||
<Button
|
||||
type="text"
|
||||
danger
|
||||
|
||||
@@ -165,10 +165,10 @@ function InitialInfo() {
|
||||
|
||||
// 监听合并后的 steps 数据,判断第五步状态并启动/停止轮询
|
||||
useEffect(() => {
|
||||
|
||||
|
||||
// 检查第五步的状态(索引 4)
|
||||
const fifthStep = steps[4];
|
||||
|
||||
|
||||
if (fifthStep && fifthStep.status !== 'completed' && fifthStep.status !== 'failed') {
|
||||
// 第五步未完成,启动轮询
|
||||
if (!pollingTimer) {
|
||||
@@ -259,7 +259,7 @@ function InitialInfo() {
|
||||
if (!creatID) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
getReplicationDetail(creatID).then((res: any) => {
|
||||
setTaskDetail(res);
|
||||
if (res.steps) {
|
||||
@@ -272,7 +272,7 @@ function InitialInfo() {
|
||||
// 刷新任务详情(用于点击下一步后更新数据)
|
||||
const refreshTaskDetail = () => {
|
||||
if (!creatID) return;
|
||||
|
||||
|
||||
getReplicationDetail(creatID).then((res: any) => {
|
||||
setTaskDetail(res);
|
||||
if (res.steps) {
|
||||
@@ -294,7 +294,7 @@ function InitialInfo() {
|
||||
refreshTaskDetail();
|
||||
}).catch((error: any) => {
|
||||
});
|
||||
}
|
||||
}
|
||||
const newcreateimage = () => {
|
||||
// console.log('下一步:', stepId);
|
||||
let params = {
|
||||
@@ -303,22 +303,22 @@ function InitialInfo() {
|
||||
image_px: "2048x2048",
|
||||
image_size: "2K"
|
||||
}
|
||||
|
||||
|
||||
gettwo(taskDetail.id, steps[1].id.toString(), params).then((res: any) => {
|
||||
refreshTaskDetail();
|
||||
|
||||
}).catch((error: any) => {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// 下一步按钮点击处理
|
||||
const handleNextStep = (stepId: number) => {
|
||||
// 获取引擎 ID 和视频参数
|
||||
const engineId = countType;
|
||||
const videoParams = {
|
||||
engine_id : countType,
|
||||
engine_id: countType,
|
||||
duration: videoDuration,
|
||||
aspect_ratio: videoAspectRatio,
|
||||
resolution: videoResolution,
|
||||
@@ -333,12 +333,12 @@ function InitialInfo() {
|
||||
// 这里可以添加下一步的逻辑,比如调用接口等
|
||||
};
|
||||
|
||||
const createvideo = (stepId: number,engineId: string) => {
|
||||
const createvideo = (stepId: number, engineId: string) => {
|
||||
let params = {
|
||||
engine_id: engineId,
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
getfour(taskDetail.id, stepId.toString(), params).then((res: any) => {
|
||||
// 重新获取任务详情以更新数据
|
||||
refreshTaskDetail();
|
||||
@@ -349,8 +349,8 @@ function InitialInfo() {
|
||||
let params = {
|
||||
engine_id: steps[2].engineId,
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
getfour(taskDetail.id, steps[2].id.toString(), params).then((res: any) => {
|
||||
// 重新获取任务详情以更新数据
|
||||
refreshTaskDetail();
|
||||
@@ -362,51 +362,112 @@ function InitialInfo() {
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div style={{ minHeight: 'calc(100vh - 90px)', background: '#f8fafc' }}>
|
||||
<div style={{ height: '100%' }}>
|
||||
<div
|
||||
className="initialinfo-container"
|
||||
style={{
|
||||
margin: '-24px -32px -32px',
|
||||
|
||||
<div style={{ height: '100%', display: 'flex', justifyContent: 'space-between', gap: '2%' }}>
|
||||
<div style={{ width: '70%', background: '#fff', borderRadius: 12, overflowY: 'auto', maxHeight: 'calc(100vh - 90px)' }}>
|
||||
height: 'calc(100vh - 34px)',
|
||||
// background: 'linear-gradient(135deg, #f8fafc 0%, #eef2ff 50%, #f0f9ff 100%)',
|
||||
position: 'relative',
|
||||
// padding: '20px 32px',
|
||||
boxSizing: 'border-box',
|
||||
overflow: 'hidden',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
}}
|
||||
>
|
||||
|
||||
<div style={{ borderBottom: '1px solid #e2e8f0', width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '14px', boxSizing: 'border-box' }}>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<ArrowLeftOutlined />}
|
||||
onClick={() => navigate(-1)}
|
||||
style={{ color: '#64748b' }}
|
||||
>
|
||||
返回
|
||||
</Button>
|
||||
<h3 style={{ margin: 0 }}>
|
||||
|
||||
<div style={{ flex: 1, display: 'flex', justifyContent: 'space-between', alignItems: 'stretch', gap: '2%', position: 'relative', zIndex: 1, minHeight: 0 }}>
|
||||
|
||||
<div
|
||||
className="replication-preview"
|
||||
style={{
|
||||
flex: 4,
|
||||
background: 'rgba(255,255,255,0.85)',
|
||||
backdropFilter: 'blur(20px)',
|
||||
borderRadius: 20,
|
||||
overflow: 'hidden',
|
||||
border: '1px solid rgba(99, 102, 241, 0.1)',
|
||||
boxShadow: '0 8px 32px rgba(99, 102, 241, 0.08)',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
minHeight: 0,
|
||||
}}
|
||||
>
|
||||
|
||||
<div style={{ borderBottom: '1px solid rgba(99, 102, 241, 0.08)', width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '16px 24px', background: 'rgba(255,255,255,0.6)', backdropFilter: 'blur(10px)', boxSizing: 'border-box' }}>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<ArrowLeftOutlined />}
|
||||
onClick={() => navigate(-1)}
|
||||
style={{
|
||||
color: '#64748b',
|
||||
borderRadius: 10,
|
||||
fontSize: 13,
|
||||
height: 32,
|
||||
transition: 'all 0.25s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
}}
|
||||
>
|
||||
返回
|
||||
</Button>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
|
||||
<div style={{ width: 32, height: 2, background: 'linear-gradient(90deg, transparent, #6366f1, #8b5cf6, transparent)', borderRadius: 1 }} />
|
||||
|
||||
<h3 style={{ margin: 0, fontSize: 16, fontWeight: 700, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent', backgroundClip: 'text' }}>
|
||||
爆款开头复刻 - 任务详情
|
||||
</h3>
|
||||
<Button type="primary" ghost style={{ borderRadius: 6, padding: '4px 12px', fontSize: 12, borderColor: '#6366f1', color: '#6366f1' }} onClick={() => setIsModalOpen(true)}>
|
||||
创作记录
|
||||
</Button>
|
||||
<div style={{ width: 32, height: 2, background: 'linear-gradient(90deg, transparent, #8b5cf6, #6366f1, transparent)', borderRadius: 1 }} />
|
||||
|
||||
</div>
|
||||
<Button
|
||||
type="primary"
|
||||
ghost
|
||||
style={{
|
||||
borderRadius: 10,
|
||||
padding: '4px 16px',
|
||||
fontSize: 13,
|
||||
background: 'rgba(99, 102, 241, 0.1)',
|
||||
borderColor: 'rgba(99, 102, 241, 0.2)',
|
||||
color: '#6366f1',
|
||||
}}
|
||||
onClick={() => setIsModalOpen(true)}
|
||||
>
|
||||
创作记录
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div style={{ flex: 1, overflowY: 'auto' }}>
|
||||
<div className="material_box">
|
||||
<div style={{ display: 'flex', gap: 24, flexWrap: 'wrap', alignItems: 'flex-start' }}>
|
||||
<div style={{ flex: 1, minWidth: 250 }}>
|
||||
<span style={{ fontSize: 14, fontWeight: 500, color: '#374151', marginBottom: 8, display: 'block' }}>视频</span>
|
||||
<div className="medio_box" style={{ aspectRatio: '16/9', background: '#f1f5f9', borderRadius: 8, overflow: 'hidden' }}>
|
||||
<span style={{ fontSize: 14, fontWeight: 600, color: '#1e293b', marginBottom: 10, display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<span style={{ width: 3, height: 14, background: 'linear-gradient(180deg, #6366f1, #8b5cf6)', borderRadius: 2, display: 'inline-block' }} />
|
||||
视频
|
||||
</span>
|
||||
<div className="medio_box" style={{ aspectRatio: '16/9', background: 'linear-gradient(135deg, rgba(248,250,252,0.8) 0%, rgba(238,242,255,0.6) 100%)', borderRadius: 14, overflow: 'hidden', boxShadow: '0 4px 12px rgba(99, 102, 241, 0.06)', border: '1px solid rgba(99, 102, 241, 0.08)' }}>
|
||||
{taskDetail?.material?.materialVideoUrl ? (
|
||||
<video
|
||||
controls
|
||||
src={taskDetail.material.materialVideoUrl}
|
||||
style={{ width: '100%', height: '100%', objectFit: 'contain' }}
|
||||
<video
|
||||
controls
|
||||
src={taskDetail.material.materialVideoUrl}
|
||||
style={{ width: '100%', height: '100%', objectFit: 'contain' }}
|
||||
/>
|
||||
) : (
|
||||
<div style={{ width: '100%', height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#94a3b8', fontSize: 12 }}>暂无视频</div>
|
||||
<div style={{ width: '100%', height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#94a3b8', fontSize: 13 }}>暂无视频</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="product_img" style={{ flex: 1, minWidth: 250 }}>
|
||||
<span style={{ fontSize: 14, fontWeight: 500, color: '#374151', marginBottom: 8, display: 'block' }}>产品图片</span>
|
||||
<div className="medio_box" style={{ aspectRatio: '1/1', background: '#f1f5f9', borderRadius: 8, overflow: 'hidden' }}>
|
||||
<span style={{ fontSize: 14, fontWeight: 600, color: '#1e293b', marginBottom: 10, display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<span style={{ width: 3, height: 14, background: 'linear-gradient(180deg, #6366f1, #8b5cf6)', borderRadius: 2, display: 'inline-block' }} />
|
||||
产品图片
|
||||
</span>
|
||||
<div className="medio_box" style={{ aspectRatio: '1/1', background: 'linear-gradient(135deg, rgba(248,250,252,0.8) 0%, rgba(238,242,255,0.6) 100%)', borderRadius: 14, overflow: 'hidden', boxShadow: '0 4px 12px rgba(99, 102, 241, 0.06)', border: '1px solid rgba(99, 102, 241, 0.08)' }}>
|
||||
{taskDetail?.material?.materialImageUrl ? (
|
||||
<img src={taskDetail.material.materialImageUrl} alt="" style={{ width: '100%', height: '100%', objectFit: 'contain' }} />
|
||||
) : (
|
||||
<div style={{ width: '100%', height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#94a3b8', fontSize: 12 }}>暂无图片</div>
|
||||
<div style={{ width: '100%', height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#94a3b8', fontSize: 13 }}>暂无图片</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -414,12 +475,15 @@ function InitialInfo() {
|
||||
{taskDetail?.finalImageUrl && (
|
||||
<div style={{ marginTop: 16 }}>
|
||||
<div>
|
||||
<span style={{ fontSize: 14, fontWeight: 500, color: '#374151', marginBottom: 8, display: 'block' }}>生成图片</span>
|
||||
<div className="medio_box" style={{ aspectRatio: '1/1', background: '#f1f5f9', borderRadius: 8, overflow: 'hidden' }}>
|
||||
<img
|
||||
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${taskDetail.finalImageUrl}`}
|
||||
alt=""
|
||||
style={{ width: '100%', height: '100%', objectFit: 'contain' }}
|
||||
<span style={{ fontSize: 14, fontWeight: 600, color: '#1e293b', marginBottom: 10, display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<span style={{ width: 3, height: 14, background: 'linear-gradient(180deg, #6366f1, #8b5cf6)', borderRadius: 2, display: 'inline-block' }} />
|
||||
生成图片
|
||||
</span>
|
||||
<div className="medio_box" style={{ aspectRatio: '1/1', background: 'linear-gradient(135deg, rgba(248,250,252,0.8) 0%, rgba(238,242,255,0.6) 100%)', borderRadius: 14, overflow: 'hidden', boxShadow: '0 4px 12px rgba(99, 102, 241, 0.06)', border: '1px solid rgba(99, 102, 241, 0.08)' }}>
|
||||
<img
|
||||
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${taskDetail.finalImageUrl}`}
|
||||
alt=""
|
||||
style={{ width: '100%', height: '100%', objectFit: 'contain' }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -428,12 +492,15 @@ function InitialInfo() {
|
||||
{taskDetail?.finalVideoUrl && (
|
||||
<div style={{ marginTop: 16 }}>
|
||||
<div>
|
||||
<span style={{ fontSize: 14, fontWeight: 500, color: '#374151', marginBottom: 8, display: 'block' }}>生成视频</span>
|
||||
<div className="medio_box" style={{ aspectRatio: '16/9', background: '#f1f5f9', borderRadius: 8, overflow: 'hidden' }}>
|
||||
<video
|
||||
controls
|
||||
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${taskDetail.finalVideoUrl}`}
|
||||
style={{ width: '100%', height: '100%', objectFit: 'contain' }}
|
||||
<span style={{ fontSize: 14, fontWeight: 600, color: '#1e293b', marginBottom: 10, display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<span style={{ width: 3, height: 14, background: 'linear-gradient(180deg, #6366f1, #8b5cf6)', borderRadius: 2, display: 'inline-block' }} />
|
||||
生成视频
|
||||
</span>
|
||||
<div className="medio_box" style={{ aspectRatio: '16/9', background: 'linear-gradient(135deg, rgba(248,250,252,0.8) 0%, rgba(238,242,255,0.6) 100%)', borderRadius: 14, overflow: 'hidden', boxShadow: '0 4px 12px rgba(99, 102, 241, 0.06)', border: '1px solid rgba(99, 102, 241, 0.08)' }}>
|
||||
<video
|
||||
controls
|
||||
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${taskDetail.finalVideoUrl}`}
|
||||
style={{ width: '100%', height: '100%', objectFit: 'contain' }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -441,9 +508,30 @@ function InitialInfo() {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ width: '28%', minWidth: '400px', background: '#fff', borderRadius: 12, overflowY: 'auto', maxHeight: 'calc(100vh - 90px)' }}>
|
||||
</div>
|
||||
|
||||
<div style={{ padding: '14px' }}> 生成步骤</div>
|
||||
<div
|
||||
className="replication-form"
|
||||
style={{
|
||||
flex: 1,
|
||||
minWidth: '400px',
|
||||
background: 'rgba(255,255,255,0.85)',
|
||||
backdropFilter: 'blur(20px)',
|
||||
borderRadius: 20,
|
||||
overflow: 'hidden',
|
||||
border: '1px solid rgba(99, 102, 241, 0.1)',
|
||||
boxShadow: '0 8px 32px rgba(99, 102, 241, 0.08)',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
minHeight: 0,
|
||||
}}
|
||||
>
|
||||
|
||||
<div style={{ padding: '16px 24px', borderBottom: '1px solid rgba(99, 102, 241, 0.08)', display: 'flex', alignItems: 'center', gap: 10, background: 'rgba(255,255,255,0.6)', backdropFilter: 'blur(10px)' }}>
|
||||
<div style={{ width: 4, height: 18, background: 'linear-gradient(180deg, #6366f1 0%, #8b5cf6 100%)', borderRadius: 2 }} />
|
||||
<span style={{ fontSize: 15, fontWeight: 700, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent', backgroundClip: 'text' }}>生成步骤</span>
|
||||
</div>
|
||||
<div style={{ flex: 1, overflowY: 'auto', padding: '8px 16px 16px' }}>
|
||||
<Collapse
|
||||
defaultActiveKey={['']}
|
||||
ghost
|
||||
@@ -484,25 +572,25 @@ function InitialInfo() {
|
||||
</div>
|
||||
),
|
||||
children: (
|
||||
<div style={{ padding: '16px', background: '#f8fafc', borderRadius: 8, marginTop: 8 }}>
|
||||
<div style={{ padding: '16px', background: 'linear-gradient(135deg, rgba(248,250,252,0.6) 0%, rgba(238,242,255,0.4) 100%)', borderRadius: 12, marginTop: 8, border: '1px solid rgba(99, 102, 241, 0.06)' }}>
|
||||
{/* 步骤1: 原始素材 */}
|
||||
{step.childId === 1 && (
|
||||
<>
|
||||
<div style={{ display: 'flex', gap: 20, marginBottom: 20, alignItems: 'flex-start', flexWrap: 'wrap' }}>
|
||||
<div style={{ flex: 1, minWidth: 200 }}>
|
||||
<Text style={{ color: '#64748b', fontSize: 13, marginBottom: 10, display: 'block', fontWeight: 500 }}>原视频</Text>
|
||||
<div style={{ height: 180, borderRadius: 10, background: '#f1f5f9', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<Text style={{ color: '#475569', fontSize: 13, marginBottom: 10, display: 'block', fontWeight: 500 }}>原视频</Text>
|
||||
<div style={{ height: 180, borderRadius: 12, background: 'linear-gradient(135deg, rgba(248,250,252,0.8) 0%, rgba(238,242,255,0.6) 100%)', display: 'flex', alignItems: 'center', justifyContent: 'center', border: '1px solid rgba(99, 102, 241, 0.08)', boxShadow: '0 2px 8px rgba(99, 102, 241, 0.04)' }}>
|
||||
{taskDetail?.material?.materialVideoUrl ? (
|
||||
<video
|
||||
src={taskDetail.material.materialVideoUrl}
|
||||
style={{ maxWidth: '100%', maxHeight: '100%', objectFit: 'contain', borderRadius: 10 }}
|
||||
controls
|
||||
<video
|
||||
src={taskDetail.material.materialVideoUrl}
|
||||
style={{ maxWidth: '100%', maxHeight: '100%', objectFit: 'contain', borderRadius: 10 }}
|
||||
controls
|
||||
/>
|
||||
) : (
|
||||
<div style={{ textAlign: 'center', color: '#94a3b8' }}>
|
||||
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" style={{ marginBottom: 8 }}>
|
||||
<rect x="2" y="2" width="20" height="16" rx="2" ry="2"/>
|
||||
<path d="M8 12l4 2 4-2"/>
|
||||
<rect x="2" y="2" width="20" height="16" rx="2" ry="2" />
|
||||
<path d="M8 12l4 2 4-2" />
|
||||
</svg>
|
||||
<div style={{ fontSize: 13 }}>暂无视频</div>
|
||||
</div>
|
||||
@@ -513,17 +601,17 @@ function InitialInfo() {
|
||||
<Text style={{ color: '#64748b', fontSize: 13, marginBottom: 10, display: 'block', fontWeight: 500 }}>产品图片</Text>
|
||||
<div style={{ height: 180, borderRadius: 10, background: '#f1f5f9', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||
{taskDetail?.material?.materialImageUrl ? (
|
||||
<img
|
||||
src={taskDetail.material.materialImageUrl}
|
||||
alt="产品图片"
|
||||
style={{ maxWidth: '100%', maxHeight: '100%', objectFit: 'contain', borderRadius: 10 }}
|
||||
<img
|
||||
src={taskDetail.material.materialImageUrl}
|
||||
alt="产品图片"
|
||||
style={{ maxWidth: '100%', maxHeight: '100%', objectFit: 'contain', borderRadius: 10 }}
|
||||
/>
|
||||
) : (
|
||||
<div style={{ textAlign: 'center', color: '#94a3b8' }}>
|
||||
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" style={{ marginBottom: 8 }}>
|
||||
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
|
||||
<circle cx="8.5" cy="8.5" r="1.5" fill="currentColor"/>
|
||||
<path d="M21 15l-5-5L5 21"/>
|
||||
<rect x="3" y="3" width="18" height="18" rx="2" ry="2" />
|
||||
<circle cx="8.5" cy="8.5" r="1.5" fill="currentColor" />
|
||||
<path d="M21 15l-5-5L5 21" />
|
||||
</svg>
|
||||
<div style={{ fontSize: 13 }}>暂无图片</div>
|
||||
</div>
|
||||
@@ -531,30 +619,33 @@ function InitialInfo() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</>
|
||||
)}
|
||||
{/* 步骤2: 生成提示词 */}
|
||||
{step.childId === 2 && (
|
||||
<>
|
||||
{/* <Text style={{ color: '#475569', fontSize: 13, lineHeight: 1.8, whiteSpace: 'pre-wrap' }}>
|
||||
{taskDetail?.generationPrompt || '暂无提示词'}
|
||||
</Text> */}
|
||||
<Text style={{ color: '#475569', fontSize: 13, lineHeight: 1.8, whiteSpace: 'pre-wrap' }}>
|
||||
{/* {taskDetail?.generationPrompt || '暂无提示词'} */}
|
||||
{step?.output?.payload?.prompt || '暂无提示词'}
|
||||
</Text>
|
||||
<div style={{ padding: '12px 14px', background: 'rgba(255,255,255,0.6)', borderRadius: 10, border: '1px solid rgba(99, 102, 241, 0.08)', marginBottom: 4 }}>
|
||||
<Text style={{ color: '#475569', fontSize: 13, lineHeight: 1.8, whiteSpace: 'pre-wrap' }}>
|
||||
{step?.output?.payload?.prompt || '暂无提示词'}
|
||||
</Text>
|
||||
</div>
|
||||
<Space style={{ marginTop: 16, gap: 12, width: '100%' }}>
|
||||
<Button
|
||||
type="default"
|
||||
icon={<EditOutlined />}
|
||||
onClick={() => handleOpenModal(step?.output?.payload?.prompt, 'image', step.id)}
|
||||
style={{ flex: 1, borderRadius: 8, borderColor: '#6366f1', color: '#6366f1', height: 36 }}
|
||||
style={{ flex: 1, borderRadius: 10, borderColor: 'rgba(99, 102, 241, 0.3)', color: '#6366f1', height: 36, fontWeight: 500, background: 'rgba(99, 102, 241, 0.04)' }}
|
||||
disabled={step.status !== 'completed'}
|
||||
>
|
||||
修改提示词
|
||||
</Button>
|
||||
<Button onClick={() => { message.info('正在生成图片,请稍候...'); createimage(step.id); }} type="primary" style={{ flex: 1, borderRadius: 8, background: '#6366f1', borderColor: '#6366f1', height: 36 }} disabled={step.status !== 'completed'}>
|
||||
<Button
|
||||
onClick={() => { message.info('正在生成图片,请稍候...'); createimage(step.id); }}
|
||||
type="primary"
|
||||
style={{ flex: 1, borderRadius: 10, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', border: 'none', height: 36, fontWeight: 500, boxShadow: '0 4px 12px rgba(99, 102, 241, 0.3)' }}
|
||||
disabled={step.status !== 'completed'}
|
||||
>
|
||||
下一步:生成图片
|
||||
</Button>
|
||||
</Space>
|
||||
@@ -563,19 +654,39 @@ function InitialInfo() {
|
||||
{/* 步骤3: 生成产品融合图 */}
|
||||
{step.childId === 3 && (
|
||||
<>
|
||||
<div style={{ borderRadius: 8, overflow: 'hidden', marginBottom: 16, background: '#f1f5f9' }}>
|
||||
{/* {step.output.result.result_image_url} */}
|
||||
|
||||
|
||||
<div style={{ borderRadius: 12, overflow: 'hidden', marginBottom: 16, background: 'linear-gradient(135deg, rgba(248,250,252,0.8) 0%, rgba(238,242,255,0.6) 100%)', border: '1px solid rgba(99, 102, 241, 0.08)', boxShadow: '0 2px 8px rgba(99, 102, 241, 0.04)', position: 'relative' }}>
|
||||
{taskDetail.finalImageUrl ? (
|
||||
<img src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${taskDetail.finalImageUrl}`} alt="融合图" style={{ width: '100%', height: 180, objectFit: 'cover' }} />
|
||||
) : (
|
||||
<div style={{ width: '100%', height: 180, display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#94a3b8', fontSize: 14 }}>暂无融合图</div>
|
||||
)}
|
||||
<Button
|
||||
type="default"
|
||||
icon={<EditOutlined />}
|
||||
onClick={() => newcreateimage()}
|
||||
disabled={step.status !== 'completed'}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
bottom: 8,
|
||||
left: 8,
|
||||
borderRadius: 8,
|
||||
borderColor: 'rgba(99, 102, 241, 0.3)',
|
||||
color: '#6366f1',
|
||||
height: 30,
|
||||
fontWeight: 500,
|
||||
background: 'rgba(255,255,255,0.85)',
|
||||
backdropFilter: 'blur(4px)',
|
||||
padding: '0 12px',
|
||||
fontSize: 12,
|
||||
}}
|
||||
>
|
||||
重新生成
|
||||
</Button>
|
||||
</div>
|
||||
{/* 引擎选择器 */}
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<div style={{ position: 'relative', display: 'inline-block' }}>
|
||||
{/* 引擎选择器和视频参数设置 */}
|
||||
<p style={{marginBottom:6,fontSize: 14, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent', backgroundClip: 'text' }}>视频参数选择:</p>
|
||||
<div style={{ width:'100%', display: 'flex', justifyContent: 'space-between', marginBottom: 16 }}>
|
||||
<div style={{ flex: 1, position: 'relative', display: 'inline-block' }}>
|
||||
<button
|
||||
onClick={() => {
|
||||
setShowEngineModal(!showEngineModal);
|
||||
@@ -583,12 +694,12 @@ function InitialInfo() {
|
||||
}}
|
||||
className="image-settings-trigger"
|
||||
style={{
|
||||
minWidth: 200,
|
||||
width: '100%',
|
||||
padding: '4px 12px',
|
||||
height: 34,
|
||||
borderRadius: 8,
|
||||
border: 'none',
|
||||
backgroundColor: '#f1f5f9',
|
||||
borderRadius: 10,
|
||||
border: '1px solid rgba(99, 102, 241, 0.15)',
|
||||
backgroundColor: 'rgba(255,255,255,0.7)',
|
||||
cursor: 'pointer',
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
@@ -596,7 +707,7 @@ function InitialInfo() {
|
||||
transition: 'all 0.2s',
|
||||
}}
|
||||
>
|
||||
<SettingOutlined style={{ fontSize: 14, color: '#64748b' }} />
|
||||
<SettingOutlined style={{ fontSize: 14, color: '#6366f1' }} />
|
||||
<Text style={{
|
||||
fontSize: 14,
|
||||
fontWeight: 500,
|
||||
@@ -612,7 +723,7 @@ function InitialInfo() {
|
||||
style={{
|
||||
position: 'absolute',
|
||||
bottom: 'calc(100% + 8px)',
|
||||
left: -20,
|
||||
left: -10,
|
||||
width: 350,
|
||||
backgroundColor: '#fff',
|
||||
borderRadius: 16,
|
||||
@@ -702,11 +813,9 @@ function InitialInfo() {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 视频参数设置 */}
|
||||
<div style={{ marginBottom: 16 }}>
|
||||
<div style={{ position: 'relative', display: 'inline-block' }}>
|
||||
{/* 视频参数设置 */}
|
||||
<div style={{ flex: 1, position: 'relative', display: 'inline-block' }}>
|
||||
<button
|
||||
onClick={() => {
|
||||
setShowVideoSettingsModal(!showVideoSettingsModal);
|
||||
@@ -714,12 +823,12 @@ function InitialInfo() {
|
||||
}}
|
||||
className="image-settings-trigger"
|
||||
style={{
|
||||
minWidth: 220,
|
||||
width: '100%',
|
||||
padding: '4px 12px',
|
||||
height: 34,
|
||||
borderRadius: 8,
|
||||
border: 'none',
|
||||
backgroundColor: '#f1f5f9',
|
||||
borderRadius: 10,
|
||||
border: '1px solid rgba(99, 102, 241, 0.15)',
|
||||
backgroundColor: 'rgba(255,255,255,0.7)',
|
||||
cursor: 'pointer',
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
@@ -727,7 +836,7 @@ function InitialInfo() {
|
||||
transition: 'all 0.2s',
|
||||
}}
|
||||
>
|
||||
<LayoutOutlined style={{ fontSize: 14, color: '#64748b' }} />
|
||||
<LayoutOutlined style={{ fontSize: 14, color: '#6366f1' }} />
|
||||
<Text style={{
|
||||
fontSize: 14,
|
||||
fontWeight: 500,
|
||||
@@ -743,7 +852,7 @@ function InitialInfo() {
|
||||
style={{
|
||||
position: 'absolute',
|
||||
bottom: 'calc(100% + 8px)',
|
||||
left: -20,
|
||||
left: -160,
|
||||
width: 350,
|
||||
backgroundColor: '#fff',
|
||||
borderRadius: 16,
|
||||
@@ -935,41 +1044,40 @@ function InitialInfo() {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<Space style={{ width: '100%', gap: 12 }}>
|
||||
<Button type="default" icon={<EditOutlined />} style={{ flex: 1, borderRadius: 8, borderColor: '#6366f1', color: '#6366f1', height: 36 }
|
||||
} onClick={() => newcreateimage()} disabled={step.status !== 'completed'}>
|
||||
重新生成
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
style={{ flex: 1, borderRadius: 8, background: '#6366f1', borderColor: '#6366f1', height: 36 }}
|
||||
onClick={() => { message.info('正在生成视频提示词,请稍候...'); handleNextStep(step.id); }}
|
||||
disabled={step.status !== 'completed'}
|
||||
>
|
||||
下一步:生成视频提示词
|
||||
</Button>
|
||||
</Space>
|
||||
<Button
|
||||
type="primary"
|
||||
style={{ width: '100%', borderRadius: 10, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', border: 'none', height: 36, fontWeight: 500, boxShadow: '0 4px 12px rgba(99, 102, 241, 0.3)' }}
|
||||
onClick={() => { message.info('正在生成视频提示词,请稍候...'); handleNextStep(step.id); }}
|
||||
disabled={step.status !== 'completed'}
|
||||
>
|
||||
下一步:生成视频提示词
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
{/* 步骤4: 生成视频提示词 */}
|
||||
{step.childId === 4 && (
|
||||
<>
|
||||
<Text style={{ color: '#475569', fontSize: 13, lineHeight: 1.8, whiteSpace: 'pre-wrap' }}>
|
||||
{/* {taskDetail?.videoPrompt || '暂无视频提示词'} */}
|
||||
{step?.output?.payload?.finalPrompt || '暂无提示词'}
|
||||
|
||||
</Text>
|
||||
<div style={{ padding: '12px 14px', background: 'rgba(255,255,255,0.6)', borderRadius: 10, border: '1px solid rgba(99, 102, 241, 0.08)', marginBottom: 4 }}>
|
||||
<Text style={{ color: '#475569', fontSize: 13, lineHeight: 1.8, whiteSpace: 'pre-wrap' }}>
|
||||
{step?.output?.payload?.finalPrompt || '暂无提示词'}
|
||||
</Text>
|
||||
</div>
|
||||
<Space style={{ marginTop: 16, gap: 12, width: '100%' }}>
|
||||
<Button
|
||||
type="default"
|
||||
icon={<EditOutlined />}
|
||||
onClick={() => handleOpenModal(step?.output?.payload?.promptSchema, 'video', step.id)}
|
||||
style={{ flex: 1, borderRadius: 8, borderColor: '#6366f1', color: '#6366f1', height: 36 }}
|
||||
style={{ flex: 1, borderRadius: 10, borderColor: 'rgba(99, 102, 241, 0.3)', color: '#6366f1', height: 36, fontWeight: 500, background: 'rgba(99, 102, 241, 0.04)' }}
|
||||
disabled={step.status !== 'completed'}
|
||||
>
|
||||
修改提示词
|
||||
</Button>
|
||||
<Button onClick={() => { message.info('正在生成视频,请稍候...'); createvideo(step.id,step.engineId); }} type="primary" style={{ flex: 1, borderRadius: 8, background: '#6366f1', borderColor: '#6366f1', height: 36 }} disabled={step.status !== 'completed'}>
|
||||
<Button
|
||||
onClick={() => { message.info('正在生成视频,请稍候...'); createvideo(step.id, step.engineId); }}
|
||||
type="primary"
|
||||
style={{ flex: 1, borderRadius: 10, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', border: 'none', height: 36, fontWeight: 500, boxShadow: '0 4px 12px rgba(99, 102, 241, 0.3)' }}
|
||||
disabled={step.status !== 'completed'}
|
||||
>
|
||||
下一步:生成视频
|
||||
</Button>
|
||||
</Space>
|
||||
@@ -978,7 +1086,7 @@ function InitialInfo() {
|
||||
{/* 步骤5: 生成最终视频 */}
|
||||
{step.childId === 5 && (
|
||||
<>
|
||||
<div style={{ borderRadius: 8, overflow: 'hidden', marginBottom: 16, position: 'relative', background: '#f1f5f9' }}>
|
||||
<div style={{ borderRadius: 12, overflow: 'hidden', marginBottom: 16, position: 'relative', background: 'linear-gradient(135deg, rgba(248,250,252,0.8) 0%, rgba(238,242,255,0.6) 100%)', border: '1px solid rgba(99, 102, 241, 0.08)', boxShadow: '0 2px 8px rgba(99, 102, 241, 0.04)' }}>
|
||||
{step.status === 'completed' && taskDetail?.finalVideoUrl ? (
|
||||
<video src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${taskDetail.finalVideoUrl}`} style={{ width: '100%', height: 180, objectFit: 'cover' }} controls />
|
||||
) : step.status === 'processing' ? (
|
||||
@@ -993,13 +1101,19 @@ function InitialInfo() {
|
||||
)}
|
||||
</div>
|
||||
<Space style={{ width: '100%', gap: 12 }}>
|
||||
<Button onClick={() => agincreatevideo()} type="default" icon={<EditOutlined />} style={{ flex: 1, borderRadius: 8, borderColor: '#6366f1', color: '#6366f1', height: 36 }} disabled={step.status !== 'completed'}>
|
||||
<Button
|
||||
onClick={() => agincreatevideo()}
|
||||
type="default"
|
||||
icon={<EditOutlined />}
|
||||
style={{ flex: 1, borderRadius: 10, borderColor: 'rgba(99, 102, 241, 0.3)', color: '#6366f1', height: 36, fontWeight: 500, background: 'rgba(99, 102, 241, 0.04)' }}
|
||||
disabled={step.status !== 'completed'}
|
||||
>
|
||||
重新生成
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<DownloadOutlined />}
|
||||
style={{ flex: 1, borderRadius: 8, background: '#6366f1', borderColor: '#6366f1', height: 36 }}
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<DownloadOutlined />}
|
||||
style={{ flex: 1, borderRadius: 10, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', border: 'none', height: 36, fontWeight: 500, boxShadow: '0 4px 12px rgba(99, 102, 241, 0.3)' }}
|
||||
disabled={step.status !== 'completed'}
|
||||
onClick={() => {
|
||||
const videoUrl = `${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${taskDetail?.finalVideoUrl}&download=1`;
|
||||
@@ -1025,21 +1139,33 @@ function InitialInfo() {
|
||||
</div>
|
||||
</div>
|
||||
<Modal
|
||||
title={currentType === 'video' ? '修改视频提示词' : '修改提示词'}
|
||||
title={
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
|
||||
<div style={{ width: 4, height: 18, background: 'linear-gradient(180deg, #6366f1 0%, #8b5cf6 100%)', borderRadius: 2 }} />
|
||||
<span style={{ fontSize: 15, fontWeight: 700, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent', backgroundClip: 'text' }}>
|
||||
{currentType === 'video' ? '修改视频提示词' : '修改提示词'}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
open={modalVisible}
|
||||
onCancel={() => { if (!promptSaving) setModalVisible(false); }}
|
||||
footer={[
|
||||
<Button key="cancel" onClick={() => setModalVisible(false)} disabled={promptSaving}>取消</Button>,
|
||||
<Button key="confirm" type="primary" onClick={handleConfirm} loading={promptSaving} disabled={promptSaving}>确认</Button>,
|
||||
<Button key="cancel" onClick={() => setModalVisible(false)} disabled={promptSaving} style={{ borderRadius: 8 }}>取消</Button>,
|
||||
<Button key="confirm" type="primary" onClick={handleConfirm} loading={promptSaving} disabled={promptSaving} style={{ borderRadius: 8, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', border: 'none' }}>确认</Button>,
|
||||
]}
|
||||
width={800}
|
||||
style={{ borderRadius: 16 }}
|
||||
styles={{
|
||||
header: { background: 'rgba(255,255,255,0.6)', backdropFilter: 'blur(10px)', borderBottom: '1px solid rgba(99, 102, 241, 0.08)', padding: '16px 24px' },
|
||||
body: { background: 'linear-gradient(135deg, #f8fafc 0%, #eef2ff 50%, #f0f9ff 100%)', padding: '20px 24px' },
|
||||
}}
|
||||
>
|
||||
{currentType === 'image' ? (
|
||||
<TextArea
|
||||
value={promptText}
|
||||
onChange={(e) => setPromptText(e.target.value)}
|
||||
rows={8}
|
||||
style={{ width: '100%', height: 300 }}
|
||||
style={{ width: '100%', height: 300, borderRadius: 10, border: '1px solid rgba(99, 102, 241, 0.15)', background: 'rgba(255,255,255,0.8)' }}
|
||||
placeholder="请输入提示词"
|
||||
/>
|
||||
) : (
|
||||
@@ -1050,171 +1176,186 @@ function InitialInfo() {
|
||||
</Modal>
|
||||
{/* 创作记录弹窗 */}
|
||||
<Modal
|
||||
title="创作记录"
|
||||
title={
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
|
||||
<div style={{ width: 4, height: 18, background: 'linear-gradient(180deg, #6366f1 0%, #8b5cf6 100%)', borderRadius: 2 }} />
|
||||
<span style={{ fontSize: 15, fontWeight: 700, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent', backgroundClip: 'text' }}>
|
||||
创作记录
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
open={isModalOpen}
|
||||
onCancel={() => setIsModalOpen(false)}
|
||||
width={800}
|
||||
width={850}
|
||||
footer={null}
|
||||
style={{ borderRadius: 12 }}
|
||||
style={{ borderRadius: 16 }}
|
||||
styles={{
|
||||
header: { background: 'rgba(255,255,255,0.6)', backdropFilter: 'blur(10px)', borderBottom: '1px solid rgba(99, 102, 241, 0.08)', padding: '16px 24px' },
|
||||
body: { background: 'linear-gradient(135deg, #f8fafc 0%, #eef2ff 50%, #f0f9ff 100%)', },
|
||||
}}
|
||||
>
|
||||
{/* 搜索区域 */}
|
||||
<div style={{ display: 'flex', justifyContent: 'flex-end', marginBottom: 16 }}>
|
||||
<Input
|
||||
placeholder="搜索产品名称"
|
||||
style={{ width: 200, borderRadius: 6, marginRight: 8 }}
|
||||
style={{ width: 200, borderRadius: 8, marginRight: 8, border: '1px solid rgba(99, 102, 241, 0.15)', background: 'rgba(255,255,255,0.8)' }}
|
||||
/>
|
||||
<Button type="primary" style={{ borderRadius: 6 }}>
|
||||
<Button type="primary" style={{ borderRadius: 8, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', border: 'none', boxShadow: '0 4px 12px rgba(99, 102, 241, 0.3)' }}>
|
||||
搜索
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* 表格 */}
|
||||
<Table
|
||||
columns={[
|
||||
{
|
||||
title: '产品名称',
|
||||
dataIndex: 'targetProjectName',
|
||||
key: 'targetProjectName',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
align: 'center',
|
||||
render: (text: string, record: any) => {
|
||||
const status = text;
|
||||
const currentStepCode = record.current_step_code || record.currentStepCode;
|
||||
|
||||
const getStatusText = () => {
|
||||
columns={[
|
||||
{
|
||||
title: '产品名称',
|
||||
dataIndex: 'targetProjectName',
|
||||
key: 'targetProjectName',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
align: 'center',
|
||||
render: (text: string, record: any) => {
|
||||
const status = text;
|
||||
const currentStepCode = record.current_step_code || record.currentStepCode;
|
||||
|
||||
const getStatusText = () => {
|
||||
if (currentStepCode === 'image_prompt_optimize') {
|
||||
switch (status) {
|
||||
case 'waiting_user':
|
||||
return '等待融合图生成';
|
||||
case 'processing':
|
||||
return '图片提示词生成中';
|
||||
case 'completed':
|
||||
return '图片提示词生成成功';
|
||||
case 'failed':
|
||||
return '图片提示词生成失败';
|
||||
default:
|
||||
return status || '-';
|
||||
}
|
||||
switch (status) {
|
||||
case 'waiting_user':
|
||||
return { text: '等待融合图生成', color: '#f59e0b' };
|
||||
case 'processing':
|
||||
return { text: '图片提示词生成中', color: '#f59e0b' };
|
||||
case 'completed':
|
||||
return { text: '图片提示词生成成功', color: '#10b981' };
|
||||
case 'failed':
|
||||
return { text: '图片提示词生成失败', color: '#ef4444' };
|
||||
default:
|
||||
return { text: status || '-', color: '#666' };
|
||||
}
|
||||
} else if (currentStepCode === 'image_generate') {
|
||||
switch (status) {
|
||||
case 'waiting_user':
|
||||
return '等待生成视频提示词';
|
||||
case 'processing':
|
||||
return '融合图生成中';
|
||||
case 'completed':
|
||||
return '融合图生成成功';
|
||||
case 'failed':
|
||||
return '融合图生成失败';
|
||||
default:
|
||||
return status || '-';
|
||||
}
|
||||
switch (status) {
|
||||
case 'waiting_user':
|
||||
return { text: '等待生成视频提示词', color: '#f59e0b' };
|
||||
case 'processing':
|
||||
return { text: '融合图生成中', color: '#f59e0b' };
|
||||
case 'completed':
|
||||
return { text: '融合图生成成功', color: '#10b981' };
|
||||
case 'failed':
|
||||
return { text: '融合图生成失败', color: '#ef4444' };
|
||||
default:
|
||||
return { text: status || '-', color: '#666' };
|
||||
}
|
||||
} else if (currentStepCode === 'video_prompt_optimize') {
|
||||
switch (status) {
|
||||
case 'waiting_user':
|
||||
return '等待最终视频生成';
|
||||
case 'processing':
|
||||
return '视频提示词生成中';
|
||||
case 'completed':
|
||||
return '视频提示词生成成功';
|
||||
case 'failed':
|
||||
return '视频提示词生成失败';
|
||||
default:
|
||||
return status || '-';
|
||||
}
|
||||
switch (status) {
|
||||
case 'waiting_user':
|
||||
return { text: '等待最终视频生成', color: '#f59e0b' };
|
||||
case 'processing':
|
||||
return { text: '视频提示词生成中', color: '#f59e0b' };
|
||||
case 'completed':
|
||||
return { text: '视频提示词生成成功', color: '#10b981' };
|
||||
case 'failed':
|
||||
return { text: '视频提示词生成失败', color: '#ef4444' };
|
||||
default:
|
||||
return { text: status || '-', color: '#666' };
|
||||
}
|
||||
} else if (currentStepCode === 'video_generate') {
|
||||
switch (status) {
|
||||
case 'waiting_user':
|
||||
return '';
|
||||
case 'processing':
|
||||
return '最终视频生成中';
|
||||
case 'completed':
|
||||
return '最终视频生成成功';
|
||||
case 'failed':
|
||||
return '最终视频生成失败';
|
||||
default:
|
||||
return status || '-';
|
||||
}
|
||||
switch (status) {
|
||||
case 'waiting_user':
|
||||
return { text: '', color: '#666' };
|
||||
case 'processing':
|
||||
return { text: '最终视频生成中', color: '#f59e0b' };
|
||||
case 'completed':
|
||||
return { text: '任务完成', color: '#10b981' };
|
||||
case 'failed':
|
||||
return { text: '最终视频生成失败', color: '#ef4444' };
|
||||
default:
|
||||
return { text: status || '-', color: '#666' };
|
||||
}
|
||||
} else if (currentStepCode === 'material_input') {
|
||||
switch (status) {
|
||||
case 'waiting_user':
|
||||
return '等待生成图片提示词';
|
||||
case 'processing':
|
||||
return '素材处理中';
|
||||
case 'completed':
|
||||
return '素材上传成功';
|
||||
case 'failed':
|
||||
return '素材上传失败';
|
||||
default:
|
||||
return status || '-';
|
||||
}
|
||||
switch (status) {
|
||||
case 'waiting_user':
|
||||
return { text: '等待生成图片提示词', color: '#f59e0b' };
|
||||
case 'processing':
|
||||
return { text: '素材处理中', color: '#f59e0b' };
|
||||
case 'completed':
|
||||
return { text: '素材上传成功', color: '#10b981' };
|
||||
case 'failed':
|
||||
return { text: '素材上传失败', color: '#ef4444' };
|
||||
default:
|
||||
return { text: status || '-', color: '#666' };
|
||||
}
|
||||
} else {
|
||||
const statusMap: Record<string, string> = {
|
||||
'pending': '子任务待处理',
|
||||
'waiting_user': '等待用户确认或触发',
|
||||
'processing': '子任务处理中',
|
||||
'completed': '子任务完成',
|
||||
'failed': '子任务失败',
|
||||
'cancelled': '子任务取消',
|
||||
};
|
||||
return statusMap[status] || status || '-';
|
||||
const statusMap: Record<string, { text: string; color: string }> = {
|
||||
'pending': { text: '子任务待处理', color: '#f59e0b' },
|
||||
'waiting_user': { text: '等待用户确认或触发', color: '#f59e0b' },
|
||||
'processing': { text: '子任务处理中', color: '#f59e0b' },
|
||||
'completed': { text: '子任务完成', color: '#10b981' },
|
||||
'failed': { text: '子任务失败', color: '#ef4444' },
|
||||
'cancelled': { text: '子任务取消', color: '#94a3b8' },
|
||||
};
|
||||
return statusMap[status] || { text: status || '-', color: '#666' };
|
||||
}
|
||||
};
|
||||
|
||||
return getStatusText();
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createdAt',
|
||||
key: 'createdAt',
|
||||
align: 'center',
|
||||
|
||||
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 `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
align: 'center',
|
||||
|
||||
render: (_, record) => (
|
||||
<button
|
||||
};
|
||||
|
||||
const result = getStatusText() as { text: string; color: string };
|
||||
if (typeof result === 'string') {
|
||||
return <span style={{ fontSize: 12, color: '#666', fontWeight: 500 }}>{result}</span>;
|
||||
}
|
||||
return <span style={{ fontSize: 12, color: result.color, fontWeight: 500 }}>{result.text}</span>;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createdAt',
|
||||
key: 'createdAt',
|
||||
align: 'center',
|
||||
|
||||
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 `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
align: 'center',
|
||||
|
||||
render: (_, record) => (
|
||||
<button
|
||||
onClick={() => navigate(`/initial/${record.id}/initialinfo`)}
|
||||
style={{ color: '#6366f1', textDecoration: 'none', fontSize: 12, border: 'none', background: 'none', cursor: 'pointer' }}
|
||||
>
|
||||
style={{ color: '#6366f1', textDecoration: 'none', fontSize: 12, border: 'none', background: 'rgba(99, 102, 241, 0.08)', padding: '4px 12px', borderRadius: 8, cursor: 'pointer', transition: 'all 0.2s', fontWeight: 500 }}
|
||||
>
|
||||
查看详情
|
||||
</button>
|
||||
),
|
||||
},
|
||||
]}
|
||||
dataSource={tableData}
|
||||
rowKey="id"
|
||||
pagination={{
|
||||
current: currentPage,
|
||||
pageSize: pageSize,
|
||||
total: total,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
showTotal: (total) => `共 ${total} 条`,
|
||||
onChange: handlePageChange,
|
||||
}}
|
||||
style={{ fontSize: 13 }}
|
||||
scroll={{ y: 350 }}
|
||||
/>
|
||||
</button>
|
||||
),
|
||||
},
|
||||
]}
|
||||
dataSource={tableData}
|
||||
rowKey="id"
|
||||
pagination={{
|
||||
current: currentPage,
|
||||
pageSize: pageSize,
|
||||
total: total,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
showTotal: (total) => `共 ${total} 条`,
|
||||
onChange: handlePageChange,
|
||||
}}
|
||||
style={{ fontSize: 13 }}
|
||||
scroll={{ y: 350 }}
|
||||
/>
|
||||
</Modal>
|
||||
</React.Fragment>
|
||||
);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { Button, Drawer, Input, Popconfirm, Spin, Table, Tag, Tooltip, Upload, message } from 'antd';
|
||||
import { Button, Drawer, Input, Modal, Popconfirm, Spin, Table, Tag, Tooltip, Upload, message } from 'antd';
|
||||
import { ArrowLeftOutlined, PlusOutlined, XOutlined } from '@ant-design/icons';
|
||||
import { createRemoveLens, getShotReplicationDetail, Removelist, removeCreate, splitCustom, uploadImage } from '../api';
|
||||
import VideoTrimPicker from '../components/VideoTrimPicker';
|
||||
@@ -23,6 +23,10 @@ function RemoveInfo() {
|
||||
const [drawerVisible, setDrawerVisible] = useState(false);
|
||||
const [trimModalVisible, setTrimModalVisible] = useState(false);
|
||||
const [currentSegment, setCurrentSegment] = useState<string | null>(null);
|
||||
const [currentSegmentName, setCurrentSegmentName] = useState<string | null>(null);
|
||||
const [previewVideoUrl, setPreviewVideoUrl] = useState<string | null>(null);
|
||||
const [previewModalVisible, setPreviewModalVisible] = useState(false);
|
||||
const previewVideoRef = useRef<HTMLVideoElement>(null);
|
||||
const [productName, setProductName] = useState('');
|
||||
const [productSellingPoint, setProductSellingPoint] = useState('');
|
||||
const [productImage, setProductImage] = useState('');
|
||||
@@ -31,6 +35,7 @@ function RemoveInfo() {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [splitLoading, setSplitLoading] = useState(false);
|
||||
const [autoSplitLoading, setAutoSplitLoading] = useState(false);
|
||||
const pollingIntervalRef = useRef<number | null>(null);
|
||||
|
||||
const videoUrl = useMemo(() => buildAssetUrl(taskDetail?.videoUrl), [taskDetail?.videoUrl]);
|
||||
|
||||
@@ -58,7 +63,69 @@ function RemoveInfo() {
|
||||
if (!creatID) return;
|
||||
try {
|
||||
const res = await Removelist(creatID);
|
||||
setTableData(res.items || []);
|
||||
const items = res.items || [];
|
||||
setTableData(items);
|
||||
|
||||
// 检查是否需要开启轮询
|
||||
const needsPolling = items.some(
|
||||
(item: any) =>
|
||||
item.moduleProjectCurrentStepCode !== 'video_generate' &&
|
||||
item.moduleProjectStatus !== 'completed' &&
|
||||
item.moduleProjectStatus !== 'failed'
|
||||
);
|
||||
|
||||
if (needsPolling) {
|
||||
// 如果没有轮询在运行,则开启轮询
|
||||
if (!pollingIntervalRef.current) {
|
||||
pollingIntervalRef.current = window.setInterval(async () => {
|
||||
try {
|
||||
const pollRes = await Removelist(creatID);
|
||||
const pollItems = pollRes.items || [];
|
||||
|
||||
// 只更新未完成的项目
|
||||
setTableData((prevData) =>
|
||||
prevData.map((prevItem) => {
|
||||
const pollItem = pollItems.find((p: any) => p.id === prevItem.id);
|
||||
if (pollItem) {
|
||||
// 只有当原项目未完成时才更新
|
||||
if (
|
||||
prevItem.moduleProjectCurrentStepCode !== 'video_generate' ||
|
||||
(prevItem.moduleProjectStatus !== 'completed' && prevItem.moduleProjectStatus !== 'failed')
|
||||
) {
|
||||
return pollItem;
|
||||
}
|
||||
}
|
||||
return prevItem;
|
||||
})
|
||||
);
|
||||
|
||||
// 检查是否所有项目都已完成
|
||||
const allCompleted = pollItems.every(
|
||||
(item: any) =>
|
||||
item.moduleProjectCurrentStepCode === 'video_generate' ||
|
||||
item.moduleProjectStatus === 'completed' ||
|
||||
item.moduleProjectStatus === 'failed'
|
||||
);
|
||||
|
||||
if (allCompleted) {
|
||||
// 停止轮询
|
||||
if (pollingIntervalRef.current) {
|
||||
clearInterval(pollingIntervalRef.current);
|
||||
pollingIntervalRef.current = null;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('轮询获取拆镜列表失败:', error);
|
||||
}
|
||||
}, 30000); // 30秒轮询
|
||||
}
|
||||
} else {
|
||||
// 如果不需要轮询,停止现有轮询
|
||||
if (pollingIntervalRef.current) {
|
||||
clearInterval(pollingIntervalRef.current);
|
||||
pollingIntervalRef.current = null;
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
message.error('获取拆镜列表失败');
|
||||
}
|
||||
@@ -70,10 +137,19 @@ function RemoveInfo() {
|
||||
|
||||
useEffect(() => {
|
||||
refreshPageData();
|
||||
|
||||
// 组件卸载时清除轮询
|
||||
return () => {
|
||||
if (pollingIntervalRef.current) {
|
||||
clearInterval(pollingIntervalRef.current);
|
||||
pollingIntervalRef.current = null;
|
||||
}
|
||||
};
|
||||
}, [refreshPageData]);
|
||||
|
||||
const handleGenerate = (segmentId: string) => {
|
||||
const handleGenerate = (segmentId: string, segmentName: string) => {
|
||||
setCurrentSegment(segmentId);
|
||||
setCurrentSegmentName(segmentName);
|
||||
setDrawerVisible(true);
|
||||
};
|
||||
|
||||
@@ -81,6 +157,7 @@ function RemoveInfo() {
|
||||
setDrawerVisible(false);
|
||||
setCurrentSegment(null);
|
||||
setProductName('');
|
||||
setCurrentSegmentName('');
|
||||
setProductSellingPoint('');
|
||||
setProductImage('');
|
||||
};
|
||||
@@ -218,13 +295,26 @@ function RemoveInfo() {
|
||||
width: 150,
|
||||
align: 'center' as const,
|
||||
render: (_: any, record: any) => (
|
||||
<div style={{ position: 'relative', width: 120, height: 80, borderRadius: 6, overflow: 'hidden', background: '#f1f5f9' }}>
|
||||
<div style={{ position: 'relative', width: 120, height: 80, borderRadius: 6, overflow: 'hidden', background: '#f1f5f9', cursor: 'pointer' }}
|
||||
onClick={() => {
|
||||
console.log('video clicked:', record.segmentVideoUrl);
|
||||
if (record.segmentVideoUrl) {
|
||||
setPreviewVideoUrl(buildAssetUrl(record.segmentVideoUrl));
|
||||
setPreviewModalVisible(true);
|
||||
}
|
||||
}}>
|
||||
{record.segmentVideoUrl ? (
|
||||
<video
|
||||
controls
|
||||
src={buildAssetUrl(record.segmentVideoUrl)}
|
||||
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
|
||||
/>
|
||||
<>
|
||||
<video
|
||||
src={buildAssetUrl(record.segmentVideoUrl)}
|
||||
style={{ width: '100%', height: '100%', objectFit: 'cover', pointerEvents: 'none' }}
|
||||
/>
|
||||
<div style={{ position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', background: 'rgba(0,0,0,0.3)' }}>
|
||||
<div style={{ width: 32, height: 32, borderRadius: '50%', background: 'rgba(255,255,255,0.9)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<div style={{ width: 0, height: 0, borderTop: '8px solid transparent', borderBottom: '8px solid transparent', borderLeft: '14px solid #6366f1', marginLeft: 2 }} />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div style={{ width: '100%', height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#94a3b8', fontSize: 12 }}>
|
||||
{record.splitStatus === 'failed' ? '切割失败' : '切割中'}
|
||||
@@ -238,15 +328,7 @@ function RemoveInfo() {
|
||||
width: 500,
|
||||
align: 'left' as const,
|
||||
render: (_: any, record: any) => {
|
||||
console.log('视频内容列数据:', {
|
||||
record,
|
||||
sourceMode: record.source_mode,
|
||||
sourceMode2: record.sourceMode,
|
||||
analysisStatus: record.analysis_status,
|
||||
analysisStatus2: record.analysisStatus,
|
||||
segmentContent: record.segmentContent,
|
||||
lastError: record.lastError,
|
||||
});
|
||||
|
||||
|
||||
// AI建议的片段直接显示内容,不经过分析状态判断
|
||||
if (record.source_mode === 'ai_suggestion' || record.sourceMode === 'ai_suggestion') {
|
||||
@@ -296,91 +378,95 @@ function RemoveInfo() {
|
||||
render: (text: string, record: any) => {
|
||||
const status = text;
|
||||
const currentStepCode = record.moduleProjectCurrentStepCode || record.currentStepCode;
|
||||
console.log('currentStepCode',status, currentStepCode);
|
||||
|
||||
const getStatusText = () => {
|
||||
if (currentStepCode === 'image_prompt_optimize') {
|
||||
switch (status) {
|
||||
case 'waiting_user':
|
||||
return '等待融合图生成';
|
||||
return { text: '等待融合图生成', color: '#f59e0b' };
|
||||
case 'processing':
|
||||
return '图片提示词生成中';
|
||||
return { text: '图片提示词生成中', color: '#f59e0b' };
|
||||
case 'completed':
|
||||
return '图片提示词生成成功';
|
||||
return { text: '图片提示词生成成功', color: '#10b981' };
|
||||
case 'failed':
|
||||
return '图片提示词生成失败';
|
||||
return { text: '图片提示词生成失败', color: '#ef4444' };
|
||||
default:
|
||||
return status || '-';
|
||||
return { text: status || '-', color: '#666' };
|
||||
}
|
||||
} else if (currentStepCode === 'image_generate') {
|
||||
switch (status) {
|
||||
case 'waiting_user':
|
||||
return '等待生成视频提示词';
|
||||
return { text: '等待生成视频提示词', color: '#f59e0b' };
|
||||
case 'processing':
|
||||
return '融合图生成中';
|
||||
return { text: '融合图生成中', color: '#f59e0b' };
|
||||
case 'completed':
|
||||
return '融合图生成成功';
|
||||
return { text: '融合图生成成功', color: '#10b981' };
|
||||
case 'failed':
|
||||
return '融合图生成失败';
|
||||
return { text: '融合图生成失败', color: '#ef4444' };
|
||||
default:
|
||||
return status || '-';
|
||||
}
|
||||
} else if (currentStepCode === 'video_prompt_optimize') {
|
||||
switch (status) {
|
||||
case 'waiting_user':
|
||||
return '等待最终视频生成';
|
||||
return { text: '等待最终视频生成', color: '#f59e0b' };
|
||||
case 'processing':
|
||||
return '视频提示词生成中';
|
||||
return { text: '视频提示词生成中', color: '#f59e0b' };
|
||||
case 'completed':
|
||||
return '视频提示词生成成功';
|
||||
return { text: '视频提示词生成成功', color: '#10b981' };
|
||||
case 'failed':
|
||||
return '视频提示词生成失败';
|
||||
return { text: '视频提示词生成失败', color: '#ef4444' };
|
||||
default:
|
||||
return status || '-';
|
||||
return { text: status || '-', color: '#666' };
|
||||
}
|
||||
} else if (currentStepCode === 'video_generate') {
|
||||
switch (status) {
|
||||
case 'waiting_user':
|
||||
return '';
|
||||
return { text: '', color: '#666' };
|
||||
case 'processing':
|
||||
return '最终视频生成中';
|
||||
return { text: '最终视频生成中', color: '#f59e0b' };
|
||||
case 'completed':
|
||||
return '最终视频生成成功';
|
||||
return { text: '任务完成', color: '#10b981' };
|
||||
case 'failed':
|
||||
return '最终视频生成失败';
|
||||
return { text: '最终视频生成失败', color: '#ef4444' };
|
||||
default:
|
||||
return status || '-';
|
||||
return { text: status || '-', color: '#666' };
|
||||
}
|
||||
} else if (currentStepCode === 'material_input') {
|
||||
switch (status) {
|
||||
case 'waiting_user':
|
||||
return '等待生成图片提示词';
|
||||
return { text: '等待生成图片提示词', color: '#f59e0b' };
|
||||
case 'processing':
|
||||
return '素材处理中';
|
||||
return { text: '素材处理中', color: '#f59e0b' };
|
||||
case 'completed':
|
||||
return '素材上传成功';
|
||||
return { text: '素材上传成功', color: '#10b981' };
|
||||
case 'failed':
|
||||
return '素材上传失败';
|
||||
return { text: '素材上传失败', color: '#ef4444' };
|
||||
default:
|
||||
return status || '-';
|
||||
return { text: status || '-', color: '#666' };
|
||||
}
|
||||
} else {
|
||||
if (!record.moduleProjectId) {
|
||||
return '待生成任务';
|
||||
return { text: '待生成任务', color: '#94a3b8' };
|
||||
}
|
||||
const statusMap: Record<string, string> = {
|
||||
'pending': '子任务待处理',
|
||||
'waiting_user': '等待用户确认或触发',
|
||||
'processing': '子任务处理中',
|
||||
'completed': '子任务完成',
|
||||
'failed': '子任务失败',
|
||||
'cancelled': '子任务取消',
|
||||
const statusMap: Record<string, { text: string; color: string }> = {
|
||||
'pending': { text: '子任务待处理', color: '#f59e0b' },
|
||||
'waiting_user': { text: '等待用户确认或触发', color: '#f59e0b' },
|
||||
'processing': { text: '子任务处理中', color: '#f59e0b' },
|
||||
'completed': { text: '子任务完成', color: '#10b981' },
|
||||
'failed': { text: '子任务失败', color: '#ef4444' },
|
||||
'cancelled': { text: '子任务取消', color: '#94a3b8' },
|
||||
};
|
||||
return statusMap[status] || status || '-';
|
||||
const result = statusMap[status] || { text: status || '-', color: '#666' };
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
return getStatusText();
|
||||
const result = getStatusText() as { text: string; color: string };
|
||||
if (typeof result === 'string') {
|
||||
return <span style={{ fontSize: 12, color: '#666', fontWeight: 500 }}>{result}</span>;
|
||||
}
|
||||
return <span style={{ fontSize: 12, color: result.color, fontWeight: 500 }}>{result.text}</span>;
|
||||
},
|
||||
|
||||
|
||||
@@ -407,16 +493,16 @@ function RemoveInfo() {
|
||||
<Button
|
||||
type="text"
|
||||
onClick={() => navigate(`/removelens/${record.moduleProjectId}/removefenbu`)}
|
||||
style={{ color: '#656efa', fontSize: 12, padding: 0, display: 'flex', alignItems: 'center', gap: 4 }}
|
||||
style={{ color: '#10b981', fontSize: 12, padding: 0, display: 'flex', alignItems: 'center', gap: 4 }}
|
||||
>
|
||||
详情任务
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
type="text"
|
||||
onClick={() => handleGenerate(String(record.id))}
|
||||
onClick={() => handleGenerate(String(record.id), record.segmentName)}
|
||||
disabled={!canCreateReplication(record)}
|
||||
style={{ color: canCreateReplication(record) ? '#656efa' : '#94a3b8', fontSize: 12, padding: 0, display: 'flex', alignItems: 'center', gap: 4 }}
|
||||
style={{ color: canCreateReplication(record) ? '#f59e0b' : '#94a3b8', fontSize: 12, padding: 0, display: 'flex', alignItems: 'center', gap: 4 }}
|
||||
>
|
||||
{canCreateReplication(record) ? '视频生成' : '待切割完成'}
|
||||
</Button>
|
||||
@@ -430,76 +516,124 @@ function RemoveInfo() {
|
||||
<>
|
||||
<div
|
||||
style={{
|
||||
minHeight: 'calc(100vh - 90px)',
|
||||
margin: '-24px -32px -32px',
|
||||
borderRadius: 20,
|
||||
minHeight: 'calc(100vh - 32px)',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
overflow: 'hidden',
|
||||
background: 'linear-gradient(135deg, #f8fafc 0%, #eef2ff 50%, #f0f9ff 100%)',
|
||||
position: 'relative',
|
||||
}}
|
||||
>
|
||||
<div style={{ background: '#fff' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<ArrowLeftOutlined />}
|
||||
onClick={() => navigate(-1)}
|
||||
style={{ fontSize: 16, color: '#666' }}
|
||||
/>
|
||||
{/* 背景装饰 */}
|
||||
<div style={{ position: 'absolute', top: -100, right: -100, width: 300, height: 300, background: 'radial-gradient(circle, rgba(99,102,241,0.08) 0%, transparent 70%)', borderRadius: '50%' }} />
|
||||
<div style={{ position: 'absolute', bottom: -100, left: -100, width: 300, height: 300, background: 'radial-gradient(circle, rgba(167,139,250,0.08) 0%, transparent 70%)', borderRadius: '50%' }} />
|
||||
|
||||
{/* 标题栏 */}
|
||||
<div style={{ padding: '20px 32px', display: 'flex', alignItems: 'center', gap: 16, position: 'relative', zIndex: 10 }}>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<ArrowLeftOutlined />}
|
||||
onClick={() => navigate(-1)}
|
||||
style={{
|
||||
fontSize: 16,
|
||||
color: '#6366f1',
|
||||
background: 'rgba(99, 102, 241, 0.1)',
|
||||
borderRadius: 12,
|
||||
padding: '8px 16px',
|
||||
transition: 'all 0.2s ease',
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.background = 'rgba(99, 102, 241, 0.15)';
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.background = 'rgba(99, 102, 241, 0.1)';
|
||||
}}
|
||||
/>
|
||||
<div style={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 16 }}>
|
||||
<div style={{ width: 40, height: 2, background: 'linear-gradient(90deg, transparent, #6366f1, #8b5cf6, transparent)', borderRadius: 1 }} />
|
||||
<h1 style={{ fontSize: 22, fontWeight: 700, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent', margin: 0 }}>
|
||||
AI视频拆镜工作台
|
||||
</h1>
|
||||
<div style={{ width: 40, height: 2, background: 'linear-gradient(90deg, transparent, #8b5cf6, #6366f1, transparent)', borderRadius: 1 }} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{taskDetail ? (
|
||||
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', justifyContent: 'space-between', overflow: 'hidden' }}>
|
||||
<div style={{ flex: 1, background: '#fff', borderRadius: 12, display: 'flex', alignItems: 'center', gap: 30, }}>
|
||||
{/* <div style={{height: '100%', display: 'flex', alignItems: 'center', gap: 30,}}> */}
|
||||
<div style={{ position: 'relative', width: 280, height: 160, borderRadius: 8, overflow: 'hidden', flexShrink: 0 }}>
|
||||
<video
|
||||
controls
|
||||
src={videoUrl}
|
||||
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
|
||||
/>
|
||||
</div>
|
||||
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', justifyContent: 'space-between', overflow: 'hidden', padding: '0 32px 32px' }}>
|
||||
{/* 视频总结卡片 */}
|
||||
<div
|
||||
style={{
|
||||
flex: 1,
|
||||
background: 'linear-gradient(135deg, rgba(255,255,255,0.8) 0%, rgba(255,255,255,0.6) 100%)',
|
||||
backdropFilter: 'blur(20px)',
|
||||
borderRadius: 20,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 30,
|
||||
padding: 24,
|
||||
border: '1px solid rgba(99, 102, 241, 0.1)',
|
||||
boxShadow: '0 8px 32px rgba(99, 102, 241, 0.08), inset 0 1px 0 rgba(255, 255, 255, 0.8)',
|
||||
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%' }} />
|
||||
|
||||
<div style={{ position: 'relative', width: 280, height: 160, borderRadius: 12, overflow: 'hidden', flexShrink: 0, boxShadow: '0 4px 16px rgba(0,0,0,0.1)' }}>
|
||||
<video
|
||||
controls
|
||||
src={videoUrl}
|
||||
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', justifyContent: 'space-between' }}>
|
||||
<div>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<h2 style={{ fontSize: 20, fontWeight: 600, marginBottom: 16 }}>视频总结</h2>
|
||||
<div style={{ textAlign: 'right' }}>
|
||||
<span style={{ color: '#999', fontSize: 12 }}>上传时间: {taskDetail.createdAt}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ display: 'flex', marginBottom: 12 }}>
|
||||
<span style={{ color: '#999', fontSize: 14, marginRight: 12, width: 80, flexShrink: 0 }}>产品名称:</span>
|
||||
<span style={{ color: '#333', fontWeight: 500 }}>{taskDetail.title}</span>
|
||||
</div>
|
||||
<div style={{ display: 'flex', marginBottom: 12 }}>
|
||||
<span style={{ color: '#999', fontSize: 14, marginRight: 12, width: 80, flexShrink: 0 }}>受众群体:</span>
|
||||
{taskDetail.analysisStatus === 'processing' ? (
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<Spin size="small" tip="分析中" style={{ color: '#f59e0b' }} />
|
||||
</div>
|
||||
) : taskDetail.analysisStatus === 'completed' ? (
|
||||
<div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
|
||||
{(taskDetail.originalVideoAudience?.split('、') || []).map((point: string, index: number) => (
|
||||
<Tag key={index} color="purple" style={{ fontSize: 12 }}>
|
||||
{point}
|
||||
</Tag>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div style={{ display: 'flex' }}>
|
||||
<span style={{ color: '#999', fontSize: 14, marginRight: 12, width: 80, flexShrink: 0 }}>视频内容:</span>
|
||||
{taskDetail.analysisStatus === 'processing' ? (
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<Spin size="small" tip="分析中" style={{ color: '#f59e0b' }} />
|
||||
</div>
|
||||
) : taskDetail.analysisStatus === 'completed' ? (
|
||||
<span style={{ color: '#333' }}>{taskDetail.originalVideoContent}</span>
|
||||
) : null}
|
||||
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', justifyContent: 'space-between', position: 'relative', zIndex: 1 }}>
|
||||
<div>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<h2 style={{ fontSize: 20, fontWeight: 600, marginBottom: 16, background: 'linear-gradient(135deg, #1e293b 0%, #334155 100%)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent' }}>视频总结</h2>
|
||||
<div style={{ textAlign: 'right' }}>
|
||||
<span style={{ color: '#94a3b8', fontSize: 12 }}>上传时间: {taskDetail.createdAt ? new Date(taskDetail.createdAt).toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit' }) : '-'}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ display: 'flex', marginBottom: 12 }}>
|
||||
<span style={{ color: '#64748b', fontSize: 14, marginRight: 12, width: 80, flexShrink: 0 }}>产品名称:</span>
|
||||
<span style={{ color: '#1e293b', fontWeight: 500 }}>{taskDetail.title}</span>
|
||||
</div>
|
||||
<div style={{ display: 'flex', marginBottom: 12 }}>
|
||||
<span style={{ color: '#64748b', fontSize: 14, marginRight: 12, width: 80, flexShrink: 0 }}>受众群体:</span>
|
||||
{taskDetail.analysisStatus === 'processing' ? (
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<Spin size="small" style={{ color: '#f59e0b' }} />
|
||||
<span style={{ fontSize: 14, color: '#f59e0b' }}>分析中</span>
|
||||
</div>
|
||||
) : taskDetail.analysisStatus === 'completed' ? (
|
||||
<div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
|
||||
{(taskDetail.originalVideoAudience?.split('、') || []).map((point: string, index: number) => (
|
||||
<Tag key={index} style={{ fontSize: 12, background: 'rgba(99, 102, 241, 0.1)', color: '#6366f1', border: 'none', borderRadius: 20 }}>
|
||||
{point}
|
||||
</Tag>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div style={{ display: 'flex' }}>
|
||||
<span style={{ color: '#64748b', fontSize: 14, marginRight: 12, width: 80, flexShrink: 0 }}>视频内容:</span>
|
||||
{taskDetail.analysisStatus === 'processing' ? (
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<Spin size="small" style={{ color: '#f59e0b' }} />
|
||||
<span style={{ fontSize: 14, color: '#f59e0b' }}>分析中</span>
|
||||
</div>
|
||||
) : taskDetail.analysisStatus === 'completed' ? (
|
||||
<span style={{ color: '#475569', lineHeight: 1.6 }}>{taskDetail.originalVideoContent}</span>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
{/* </div> */}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', gap: 16, marginBottom: 20, marginTop: 20 }}>
|
||||
@@ -509,11 +643,23 @@ function RemoveInfo() {
|
||||
disabled={!videoUrl || splitLoading}
|
||||
style={{
|
||||
flex: 1,
|
||||
height: 48,
|
||||
borderRadius: 8,
|
||||
borderColor: '#6366f1',
|
||||
height: 52,
|
||||
borderRadius: 12,
|
||||
border: '2px solid rgba(99, 102, 241, 0.3)',
|
||||
color: '#6366f1',
|
||||
fontWeight: 500,
|
||||
fontWeight: 600,
|
||||
fontSize: 15,
|
||||
background: 'rgba(255, 255, 255, 0.9)',
|
||||
backdropFilter: 'blur(10px)',
|
||||
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.borderColor = '#6366f1';
|
||||
e.currentTarget.style.boxShadow = '0 4px 20px rgba(99, 102, 241, 0.2)';
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.borderColor = 'rgba(99, 102, 241, 0.3)';
|
||||
e.currentTarget.style.boxShadow = 'none';
|
||||
}}
|
||||
>
|
||||
手动
|
||||
@@ -524,65 +670,141 @@ function RemoveInfo() {
|
||||
cancelText="取消"
|
||||
onConfirm={handleAutoGenerate}
|
||||
>
|
||||
<div style={{ display: 'flex', alignItems: 'center', flex: 1 }}>
|
||||
<Button
|
||||
type="primary"
|
||||
loading={autoSplitLoading}
|
||||
disabled={autoSplitLoading}
|
||||
style={{
|
||||
flex: 1,
|
||||
height: 48,
|
||||
borderRadius: 8,
|
||||
background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)',
|
||||
fontWeight: 500,
|
||||
border: 'none',
|
||||
}}
|
||||
>
|
||||
{autoSplitButtonText}
|
||||
{taskDetail?.aiSuggestions && taskDetail.aiSuggestions.length > 0 && (
|
||||
<Tooltip
|
||||
title={
|
||||
<div>
|
||||
<div style={{ fontWeight: 600, marginBottom: 8, color: '#6366f1' }}>AI拆分方案</div>
|
||||
{(() => {
|
||||
console.log('ai_suggestions:', taskDetail?.ai_suggestions);
|
||||
console.log('ai_suggestions2:', taskDetail?.aiSuggestions);
|
||||
const suggestions = taskDetail?.ai_suggestions || taskDetail?.aiSuggestions || [];
|
||||
return suggestions.map((item: any) => (
|
||||
<div key={item.index} style={{ marginBottom: 8, fontSize: 13, lineHeight: 1.5 }}>
|
||||
<span style={{ fontWeight: 500, color: '#6366f1' }}>{item.index}.</span>
|
||||
<span style={{ marginLeft: 4, color: '#000000ff' }}>{item.timeNode}</span>
|
||||
<br />
|
||||
<span style={{ color: '#666' }}>{item.content}</span>
|
||||
</div>
|
||||
));
|
||||
})()}
|
||||
</div>
|
||||
}
|
||||
placement="top"
|
||||
trigger="hover"
|
||||
overlayInnerStyle={{ maxWidth: 400, background: '#fff', border: '1px solid #e5e7eb', borderRadius: 12, padding: 12 }}
|
||||
>
|
||||
<div style={{ borderRadius: 8, background: 'rgba(99, 102, 241, 0.1)', color: '#ebebeb', fontSize: 13, fontWeight: 500, cursor: 'pointer' }}>
|
||||
(AI 镜头拆分方案)
|
||||
<Button
|
||||
type="primary"
|
||||
loading={autoSplitLoading}
|
||||
disabled={autoSplitLoading}
|
||||
style={{
|
||||
flex: 1,
|
||||
height: 52,
|
||||
borderRadius: 12,
|
||||
background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 50%, #a855f7 100%)',
|
||||
fontWeight: 600,
|
||||
fontSize: 15,
|
||||
border: 'none',
|
||||
boxShadow: '0 4px 20px rgba(99, 102, 241, 0.3)',
|
||||
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
position: 'relative',
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.transform = 'translateY(-2px)';
|
||||
e.currentTarget.style.boxShadow = '0 6px 25px rgba(99, 102, 241, 0.4)';
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.transform = 'translateY(0)';
|
||||
e.currentTarget.style.boxShadow = '0 4px 20px rgba(99, 102, 241, 0.3)';
|
||||
}}
|
||||
>
|
||||
<span style={{ position: 'relative', zIndex: 1 }}>{autoSplitButtonText}</span>
|
||||
{/* 按钮光效 */}
|
||||
<div style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: '-100%',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
background: 'linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent)',
|
||||
transition: 'left 0.5s ease'
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.left = '100%';
|
||||
}} />
|
||||
{taskDetail?.aiSuggestions && taskDetail.aiSuggestions.length > 0 && (
|
||||
<Tooltip
|
||||
title={
|
||||
<div>
|
||||
<div style={{ fontWeight: 600, marginBottom: 8, color: '#6366f1' }}>AI拆分方案</div>
|
||||
{(() => {
|
||||
const suggestions = taskDetail?.ai_suggestions || taskDetail?.aiSuggestions || [];
|
||||
return suggestions.map((item: any) => (
|
||||
<div key={item.index} style={{ marginBottom: 8, fontSize: 13, lineHeight: 1.5 }}>
|
||||
<span style={{ fontWeight: 500, color: '#6366f1' }}>{item.index}.</span>
|
||||
<span style={{ marginLeft: 4, color: '#000000ff' }}>{item.timeNode}</span>
|
||||
<br />
|
||||
<span style={{ color: '#666' }}>{item.content}</span>
|
||||
</div>
|
||||
));
|
||||
})()}
|
||||
</div>
|
||||
</Tooltip>
|
||||
}
|
||||
placement="top"
|
||||
trigger="hover"
|
||||
>
|
||||
<span style={{ marginLeft: 8, fontSize: 13, opacity: 0.9 }}>(AI 镜头拆分方案)</span>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Button>
|
||||
|
||||
|
||||
</div>
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
</div>
|
||||
|
||||
<div style={{ flex: 2, background: '#fff', borderRadius: 12, overflow: 'hidden', display: 'flex', flexDirection: 'column' }}>
|
||||
<div
|
||||
style={{
|
||||
flex: 2,
|
||||
background: 'linear-gradient(135deg, rgba(255,255,255,0.9) 0%, rgba(255,255,255,0.7) 100%)',
|
||||
backdropFilter: 'blur(20px)',
|
||||
borderRadius: 20,
|
||||
overflow: 'hidden',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
border: '1px solid rgba(99, 102, 241, 0.1)',
|
||||
boxShadow: '0 8px 32px rgba(99, 102, 241, 0.08)',
|
||||
}}
|
||||
>
|
||||
<div style={{ padding: '16px 24px', borderBottom: '1px solid rgba(99, 102, 241, 0.1)', background: 'linear-gradient(135deg, rgba(99,102,241,0.02) 0%, transparent 100%)' }}>
|
||||
<h3 style={{ fontSize: 16, fontWeight: 600, color: '#1e293b', margin: 0 }}>拆镜片段列表</h3>
|
||||
<span style={{ fontSize: 12, color: '#64748b', marginLeft: 12 }}>共 {tableData.length} 个片段</span>
|
||||
</div>
|
||||
<Table
|
||||
columns={columns}
|
||||
dataSource={tableData}
|
||||
pagination={false}
|
||||
bordered={false}
|
||||
rowKey="id"
|
||||
scroll={{ y: 450 }}
|
||||
scroll={{ y: 300 }}
|
||||
style={{ flex: 1 }}
|
||||
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>
|
||||
@@ -607,15 +829,16 @@ function RemoveInfo() {
|
||||
<Drawer
|
||||
title={
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', width: '100%' }}>
|
||||
<span>
|
||||
<span style={{ color: '#6366f1' }}>智能视频复刻</span>
|
||||
<span style={{ color: '#6366f1' }}>-片段{currentSegment}</span>
|
||||
</span>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
|
||||
<div style={{ width: 4, height: 20, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', borderRadius: 2 }} />
|
||||
<span style={{ fontSize: 18, fontWeight: 600, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent' }}>智能视频复刻</span>
|
||||
{currentSegmentName && <span style={{ fontSize: 14, color: '#64748b' }}>- {currentSegmentName}</span>}
|
||||
</div>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<XOutlined />}
|
||||
onClick={handleCloseDrawer}
|
||||
style={{ padding: 0 }}
|
||||
style={{ padding: 0, color: '#64748b', fontSize: 14 }}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
@@ -625,75 +848,88 @@ function RemoveInfo() {
|
||||
open={drawerVisible}
|
||||
size={480}
|
||||
styles={{
|
||||
body: { padding: '24px' },
|
||||
body: { padding: '24px', background: 'linear-gradient(180deg, #f8fafc 0%, #eef2ff 100%)' },
|
||||
header: { background: '#fff', borderBottom: '1px solid rgba(99, 102, 241, 0.1)', padding: '20px 24px' },
|
||||
content: { borderRadius: '20px 0 0 20px', overflow: 'hidden' },
|
||||
}}
|
||||
>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 24 }}>
|
||||
<div>
|
||||
<label style={{ fontWeight: 400, color: '#333', marginBottom: 12, display: 'block' }}>
|
||||
产品白底图 <span style={{ color: '#ff4d4f' }}>*</span>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 20 }}>
|
||||
<div style={{ background: '#fff', borderRadius: 12, padding: 16, border: '1px solid rgba(99, 102, 241, 0.1)' }}>
|
||||
<label style={{ fontWeight: 500, color: '#334155', marginBottom: 8, display: 'block', fontSize: 14 }}>
|
||||
产品白底图 <span style={{ color: '#ef4444' }}>*</span>
|
||||
</label>
|
||||
<div style={{ display: 'flex', gap: 16 }}>
|
||||
<div style={{ display: 'flex', gap: 12 }}>
|
||||
<Upload
|
||||
listType="picture-card"
|
||||
onChange={handleProductImageChange}
|
||||
beforeUpload={beforeUploadProductImage}
|
||||
maxCount={1}
|
||||
accept="image/*"
|
||||
style={{ width: 140, height: 140 }}
|
||||
style={{ width: 120, height: 120, borderRadius: 8, border: '2px dashed rgba(99, 102, 241, 0.3)', background: 'rgba(99, 102, 241, 0.02)' }}
|
||||
>
|
||||
{!productImage && (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 8 }}>
|
||||
<PlusOutlined style={{ fontSize: 24, color: '#999' }} />
|
||||
<span style={{ fontSize: 12, color: '#999' }}>产品图 *</span>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 6 }}>
|
||||
<PlusOutlined style={{ fontSize: 20, color: '#6366f1' }} />
|
||||
<span style={{ fontSize: 12, color: '#64748b' }}>上传产品图</span>
|
||||
</div>
|
||||
)}
|
||||
</Upload>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label style={{ fontWeight: 500, color: '#333', marginBottom: 12, display: 'block' }}>
|
||||
产品名称
|
||||
<div style={{ background: '#fff', borderRadius: 12, padding: 16, border: '1px solid rgba(99, 102, 241, 0.1)' }}>
|
||||
<label style={{ fontWeight: 500, color: '#334155', marginBottom: 8, display: 'block', fontSize: 14 }}>
|
||||
产品名称 <span style={{ color: '#ef4444' }}>*</span>
|
||||
</label>
|
||||
<Input
|
||||
value={productName}
|
||||
onChange={(e) => setProductName(e.target.value)}
|
||||
placeholder="请输入产品名称"
|
||||
style={{ height: 48, borderRadius: 8 }}
|
||||
style={{ height: 44, borderRadius: 8, border: '1px solid rgba(99, 102, 241, 0.2)' }}
|
||||
maxLength={10}
|
||||
showCount
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label style={{ fontWeight: 500, color: '#333', marginBottom: 12, display: 'block' }}>
|
||||
产品卖点
|
||||
<div style={{ background: '#fff', borderRadius: 12, padding: 16, border: '1px solid rgba(99, 102, 241, 0.1)' }}>
|
||||
<label style={{ fontWeight: 500, color: '#334155', marginBottom: 8, display: 'block', fontSize: 14 }}>
|
||||
产品卖点 <span style={{ color: '#ef4444' }}>*</span>
|
||||
</label>
|
||||
<TextArea
|
||||
value={productSellingPoint}
|
||||
onChange={(e) => setProductSellingPoint(e.target.value)}
|
||||
placeholder="请输入产品卖点"
|
||||
style={{ borderRadius: 8 }}
|
||||
style={{ borderRadius: 8, border: '1px solid rgba(99, 102, 241, 0.2)' }}
|
||||
maxLength={100}
|
||||
showCount
|
||||
rows={3}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', gap: 16, marginTop: 24 }}>
|
||||
<div style={{ display: 'flex', gap: 12, marginTop: 8 }}>
|
||||
<Button
|
||||
type="default"
|
||||
type="primary"
|
||||
onClick={handleManualGenerate}
|
||||
loading={loading}
|
||||
disabled={loading}
|
||||
style={{
|
||||
flex: 1,
|
||||
height: 48,
|
||||
borderRadius: 8,
|
||||
borderColor: '#6366f1',
|
||||
color: '#6366f1',
|
||||
fontWeight: 500,
|
||||
borderRadius: 10,
|
||||
background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)',
|
||||
fontWeight: 600,
|
||||
fontSize: 15,
|
||||
border: 'none',
|
||||
boxShadow: '0 4px 15px rgba(99, 102, 241, 0.3)',
|
||||
transition: 'all 0.2s ease',
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.boxShadow = '0 6px 20px rgba(99, 102, 241, 0.4)';
|
||||
e.currentTarget.style.transform = 'translateY(-1px)';
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.boxShadow = '0 4px 15px rgba(99, 102, 241, 0.3)';
|
||||
e.currentTarget.style.transform = 'translateY(0)';
|
||||
}}
|
||||
>
|
||||
{loading ? '生成中...' : '手动生成'}
|
||||
@@ -701,6 +937,42 @@ function RemoveInfo() {
|
||||
</div>
|
||||
</div>
|
||||
</Drawer>
|
||||
|
||||
<Modal
|
||||
title={
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
|
||||
<div style={{ width: 4, height: 20, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', borderRadius: 2 }} />
|
||||
<span style={{ fontSize: 16, fontWeight: 600, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent' }}>视频预览</span>
|
||||
</div>
|
||||
}
|
||||
open={previewModalVisible}
|
||||
onCancel={() => setPreviewModalVisible(false)}
|
||||
afterClose={() => {
|
||||
if (previewVideoRef.current) {
|
||||
previewVideoRef.current.pause();
|
||||
previewVideoRef.current.currentTime = 0;
|
||||
}
|
||||
}}
|
||||
footer={null}
|
||||
width={640}
|
||||
centered
|
||||
styles={{
|
||||
body: { padding: 24, background: '#0f172a' },
|
||||
header: { background: '#1e293b', borderBottom: 'none', padding: '20px 24px' },
|
||||
}}
|
||||
>
|
||||
<div style={{ borderRadius: 12, overflow: 'hidden', boxShadow: '0 8px 32px rgba(0,0,0,0.3)' }}>
|
||||
<video
|
||||
ref={previewVideoRef}
|
||||
controls
|
||||
src={previewVideoUrl || ''}
|
||||
style={{ width: '100%', maxHeight: 420, objectFit: 'contain', backgroundColor: '#000' }}
|
||||
playsInline
|
||||
webkit-playsinline
|
||||
autoPlay
|
||||
/>
|
||||
</div>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -83,7 +83,6 @@ export default function VideoFrameExtractor() {
|
||||
|
||||
await createShotReplication(params);
|
||||
message.success('任务创建成功');
|
||||
// 清空上传内容和输入框
|
||||
cleanupResources();
|
||||
} catch (err) {
|
||||
message.error('任务创建失败,请重试');
|
||||
@@ -108,194 +107,194 @@ export default function VideoFrameExtractor() {
|
||||
fetchList(page, size);
|
||||
};
|
||||
|
||||
// 打开弹窗时获取列表数据
|
||||
const handleOpenModal = () => {
|
||||
setIsModalOpen(true);
|
||||
fetchList(1, 10);
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ minHeight: 'calc(100vh - 90px)', overflow: 'auto', background: 'linear-gradient(135deg, #f5f3ff 0%, #fdf2f8 100%)', padding: '20px' }}>
|
||||
<div style={{ maxWidth: 1200, margin: '0 auto' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 24 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
|
||||
<div>
|
||||
<div style={{
|
||||
width: 36,
|
||||
height: 36,
|
||||
background: 'linear-gradient(135deg, #6366f1 0%, #ec4899 100%)',
|
||||
borderRadius: 10,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
color: 'white',
|
||||
fontSize: 18,
|
||||
fontWeight: 600
|
||||
}}>
|
||||
✨
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
margin: '-24px -32px -32px',
|
||||
borderRadius: 20,
|
||||
minHeight: 'calc(100vh - 32px)',
|
||||
overflow: 'auto',
|
||||
background: 'linear-gradient(135deg, #f8fafc 0%, #eef2ff 50%, #f0f9ff 100%)',
|
||||
position: 'relative',
|
||||
padding: '20px 32px',
|
||||
}}
|
||||
>
|
||||
|
||||
|
||||
<div style={{ maxWidth: 1200, margin: '50px auto', position: 'relative', zIndex: 10 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 32 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 16,marginBottom: 50 }}>
|
||||
<div style={{ width: 40, height: 2, background: 'linear-gradient(90deg, transparent, #6366f1, #8b5cf6, transparent)', borderRadius: 1 }} />
|
||||
<div>
|
||||
<h1 style={{
|
||||
fontSize: 22,
|
||||
fontWeight: 600,
|
||||
fontSize: 24,
|
||||
fontWeight: 700,
|
||||
margin: 0,
|
||||
color: '#1e293b'
|
||||
background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)',
|
||||
WebkitBackgroundClip: 'text',
|
||||
WebkitTextFillColor: 'transparent',
|
||||
backgroundClip: 'text'
|
||||
}}>
|
||||
拆镜复刻
|
||||
AI视频拆镜工作台
|
||||
</h1>
|
||||
<p style={{ fontSize: 13, color: '#94a3b8', margin: 4 }}>
|
||||
<p style={{ fontSize: 13, color: '#64748b', margin: '4px 0 0 0' }}>
|
||||
一键拆解画面分镜,助力仿拍或创作
|
||||
</p>
|
||||
</div>
|
||||
<div style={{ width: 40, height: 2, background: 'linear-gradient(90deg, transparent, #8b5cf6, #6366f1, transparent)', borderRadius: 1 }} />
|
||||
</div>
|
||||
|
||||
<button
|
||||
<Button
|
||||
type="text"
|
||||
icon={<FileTextOutlined />}
|
||||
onClick={handleOpenModal}
|
||||
style={{
|
||||
padding: '10px 20px',
|
||||
background: 'white',
|
||||
border: '1px solid #e2e8f0',
|
||||
borderRadius: 8,
|
||||
cursor: 'pointer',
|
||||
fontSize: 14,
|
||||
color: '#6366f1',
|
||||
background: 'rgba(99, 102, 241, 0.1)',
|
||||
borderRadius: 12,
|
||||
padding: '10px 20px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 8,
|
||||
boxShadow: '0 2px 8px rgba(0,0,0,0.06)'
|
||||
transition: 'all 0.2s ease',
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.background = 'rgba(99, 102, 241, 0.15)';
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.background = 'rgba(99, 102, 241, 0.1)';
|
||||
}}
|
||||
>
|
||||
<FileTextOutlined />
|
||||
<span>创作记录</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', justifyContent: 'center', marginBottom: 32 }}>
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<div style={{
|
||||
width: 56,
|
||||
height: 56,
|
||||
background: 'linear-gradient(135deg, #e0e7ff 0%, #fce7f3 100%)',
|
||||
borderRadius: 16,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
margin: '0 auto 16px'
|
||||
}}>
|
||||
<span style={{ fontSize: 28 }}>🎬</span>
|
||||
</div>
|
||||
<h2 style={{
|
||||
fontSize: 22,
|
||||
fontWeight: 600,
|
||||
margin: 0,
|
||||
background: 'linear-gradient(135deg, #6366f1 0%, #ec4899 100%)',
|
||||
WebkitBackgroundClip: 'text',
|
||||
WebkitTextFillColor: 'transparent',
|
||||
backgroundClip: 'text'
|
||||
}}>
|
||||
上传视频开始创作
|
||||
</h2>
|
||||
<p style={{ fontSize: 14, color: '#888', margin: '8px 0 0 0' }}>
|
||||
支持 MP4、MOV 格式视频,最大支持 100MB
|
||||
</p>
|
||||
</div>
|
||||
创作记录
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div style={{ width: '100%', maxWidth: 720, margin: '0 auto' }}>
|
||||
{error && (
|
||||
<div style={{
|
||||
backgroundColor: '#fff5f5',
|
||||
border: '1px solid #ffccc7',
|
||||
borderRadius: 8,
|
||||
padding: '12px 16px',
|
||||
marginBottom: 20,
|
||||
color: '#d93026',
|
||||
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'
|
||||
textAlign: 'center',
|
||||
fontWeight: 500,
|
||||
}}>
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!videoUrl && (
|
||||
<Upload.Dragger
|
||||
accept="video/*"
|
||||
beforeUpload={handleFileChange}
|
||||
showUploadList={false}
|
||||
disabled={loading}
|
||||
<div
|
||||
style={{
|
||||
background: loading ? '#f3f4f6' : '#faf5ff',
|
||||
border: `2px dashed ${loading ? '#d1d5db' : '#c4b5fd'}`,
|
||||
background: 'linear-gradient(135deg, rgba(255,255,255,0.95) 0%, rgba(255,255,255,0.85) 100%)',
|
||||
backdropFilter: 'blur(20px)',
|
||||
borderRadius: 24,
|
||||
padding: '60px 20px',
|
||||
cursor: loading ? 'not-allowed' : 'pointer',
|
||||
border: '1px solid rgba(99, 102, 241, 0.1)',
|
||||
boxShadow: '0 8px 32px rgba(99, 102, 241, 0.08)',
|
||||
position: 'relative',
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
{loading ? (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
|
||||
<div style={{
|
||||
width: 100,
|
||||
height: 100,
|
||||
margin: '0 auto 20px',
|
||||
background: '#e5e7eb',
|
||||
borderRadius: 24,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
}}>
|
||||
<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: 40,
|
||||
height: 40,
|
||||
border: '4px solid #6366f1',
|
||||
borderTopColor: 'transparent',
|
||||
borderRadius: '50%',
|
||||
animation: 'spin 1s linear infinite'
|
||||
}} />
|
||||
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>
|
||||
<p style={{ fontSize: 16, color: '#6366f1', margin: '0 0 8px 0', fontWeight: 500 }}>
|
||||
上传中...
|
||||
</p>
|
||||
<p style={{ fontSize: 13, color: '#999', margin: 0 }}>
|
||||
请稍候,视频正在上传
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div style={{
|
||||
width: 100,
|
||||
height: 100,
|
||||
margin: '0 auto 20px',
|
||||
background: 'linear-gradient(135deg, #e0e7ff 0%, #fce7f3 100%)',
|
||||
borderRadius: 24,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
}}>
|
||||
<CloudUploadOutlined style={{ fontSize: 40, color: '#818cf8' }} />
|
||||
</div>
|
||||
<p style={{ fontSize: 16, color: '#333', margin: '0 0 8px 0', fontWeight: 500 }}>
|
||||
点击或拖拽到此上传视频文件
|
||||
</p>
|
||||
<p style={{ fontSize: 13, color: '#999', margin: 0 }}>
|
||||
支持 MP4、MOV 格式视频,最大支持 100MB 的文件,支持上传 3 分钟内的视频
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
</Upload.Dragger>
|
||||
) : (
|
||||
<>
|
||||
<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 的文件,支持上传 3 分钟内的视频
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
</Upload.Dragger>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{videoUrl && (
|
||||
<div style={{
|
||||
position: 'relative',
|
||||
borderRadius: 24,
|
||||
padding: 4,
|
||||
background: 'linear-gradient(135deg, #6366f1 0%, #ec4899 100%)'
|
||||
}}>
|
||||
<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'
|
||||
padding: '24px',
|
||||
position: 'relative',
|
||||
zIndex: 1,
|
||||
}}>
|
||||
<Popconfirm
|
||||
title="确定要删除吗?"
|
||||
@@ -310,16 +309,27 @@ export default function VideoFrameExtractor() {
|
||||
right: 20,
|
||||
width: 36,
|
||||
height: 36,
|
||||
border: '1px solid #e5e7eb',
|
||||
background: '#fff',
|
||||
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: '#666',
|
||||
zIndex: 10
|
||||
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';
|
||||
}}
|
||||
>
|
||||
🗑️
|
||||
@@ -339,21 +349,21 @@ export default function VideoFrameExtractor() {
|
||||
maxWidth: '100%',
|
||||
maxHeight: 320,
|
||||
borderRadius: 12,
|
||||
background: '#000'
|
||||
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: '#374151',
|
||||
color: '#334155',
|
||||
marginBottom: 8
|
||||
}}>
|
||||
产品名称
|
||||
产品名称 <span style={{ color: '#ef4444' }}>*</span>
|
||||
</label>
|
||||
<Input
|
||||
placeholder="请输入产品名称"
|
||||
@@ -361,9 +371,16 @@ export default function VideoFrameExtractor() {
|
||||
onChange={(e) => setProductName(e.target.value)}
|
||||
style={{
|
||||
width: '100%',
|
||||
height: 40,
|
||||
borderRadius: 8,
|
||||
borderColor: '#e5e7eb'
|
||||
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>
|
||||
@@ -375,28 +392,34 @@ export default function VideoFrameExtractor() {
|
||||
style={{
|
||||
padding: '14px 56px',
|
||||
background: videoUrl && productName.trim() && !loading
|
||||
? 'linear-gradient(135deg, #6366f1 0%, #ec4899 100%)'
|
||||
? 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 50%, #a855f7 100%)'
|
||||
: '#e2e8f0',
|
||||
color: 'white',
|
||||
border: 'none',
|
||||
borderRadius: 14,
|
||||
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.35)' : 'none',
|
||||
boxShadow: videoUrl && productName.trim() && !loading ? '0 6px 20px rgba(99, 102, 241, 0.3)' : 'none',
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
gap: 10
|
||||
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>
|
||||
<span style={{
|
||||
fontSize: 12,
|
||||
opacity: 0.85,
|
||||
fontWeight: 400
|
||||
}}>
|
||||
{/* 预计消耗 10 个积分 */}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -406,91 +429,168 @@ export default function VideoFrameExtractor() {
|
||||
<canvas ref={canvasRef} style={{ display: 'none' }} />
|
||||
|
||||
<Modal
|
||||
title="创作记录"
|
||||
title={
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
|
||||
<div style={{ width: 4, height: 20, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', borderRadius: 2 }} />
|
||||
<span style={{ fontSize: 16, fontWeight: 600, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent' }}>创作记录</span>
|
||||
</div>
|
||||
}
|
||||
open={isModalOpen}
|
||||
onCancel={() => setIsModalOpen(false)}
|
||||
width={800}
|
||||
footer={null}
|
||||
styles={{
|
||||
body: { background: 'linear-gradient(180deg, #f8fafc 0%, #eef2ff 100%)' },
|
||||
header: { background: '#fff', borderBottom: '1px solid rgba(99, 102, 241, 0.1)', padding: '20px 24px' },
|
||||
}}
|
||||
>
|
||||
<div style={{ display: 'flex', justifyContent: 'flex-end', marginBottom: 16 }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'flex-end', marginBottom: 16, gap: 8 }}>
|
||||
<Input
|
||||
placeholder="搜索产品名称"
|
||||
style={{ width: 200, borderRadius: 6, marginRight: 8 }}
|
||||
style={{
|
||||
width: 200,
|
||||
borderRadius: 10,
|
||||
border: '1px solid rgba(99, 102, 241, 0.2)',
|
||||
height: 40,
|
||||
}}
|
||||
/>
|
||||
<Button type="primary" style={{ borderRadius: 6 }}>
|
||||
<Button
|
||||
type="primary"
|
||||
style={{
|
||||
borderRadius: 10,
|
||||
height: 40,
|
||||
background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)',
|
||||
border: 'none',
|
||||
}}
|
||||
>
|
||||
搜索
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Table
|
||||
columns={[
|
||||
// {
|
||||
// title: '产品图片',
|
||||
// dataIndex: 'image',
|
||||
// key: 'image',
|
||||
// width: 90,
|
||||
// render: (image: string) => (
|
||||
// <img
|
||||
// src={image}
|
||||
// alt="产品图片"
|
||||
// style={{ width: 50, height: 50, objectFit: 'cover', borderRadius: 6 }}
|
||||
// />
|
||||
// ),
|
||||
// },
|
||||
{
|
||||
title: '产品名称',
|
||||
dataIndex: 'title',
|
||||
key: 'title',
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
},
|
||||
{
|
||||
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 `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
render: (record) => (
|
||||
<button
|
||||
onClick={() => navigate(`/removelens/${record.id}/removeinfo`)}
|
||||
style={{ color: '#6366f1', textDecoration: 'none', fontSize: 13, border: 'none', background: 'none', cursor: 'pointer' }}
|
||||
>
|
||||
查看详情
|
||||
</button>
|
||||
),
|
||||
},
|
||||
]}
|
||||
dataSource={tableData}
|
||||
rowKey="id"
|
||||
pagination={{
|
||||
current: currentPage,
|
||||
pageSize: pageSize,
|
||||
total: total,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
showTotal: (total) => `共 ${total} 条`,
|
||||
onChange: handlePageChange,
|
||||
}}
|
||||
style={{ fontSize: 13 }}
|
||||
scroll={{ y: 350 }}
|
||||
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
background: 'linear-gradient(135deg, rgba(255,255,255,0.9) 0%, rgba(255,255,255,0.7) 100%)',
|
||||
backdropFilter: 'blur(20px)',
|
||||
borderRadius: 16,
|
||||
overflow: 'hidden',
|
||||
border: '1px solid rgba(99, 102, 241, 0.1)',
|
||||
boxShadow: '0 8px 32px rgba(99, 102, 241, 0.08)',
|
||||
}}
|
||||
>
|
||||
<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>
|
||||
</Modal>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react';
|
||||
import { Button, Typography, Collapse, Space, Modal, Input, Table, message } from 'antd';
|
||||
import { ArrowLeftOutlined, PlayCircleOutlined, CheckCircleOutlined, EditOutlined, DownloadOutlined, SettingOutlined, LayoutOutlined } from '@ant-design/icons';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import {getShotReplicationList, removeDetail, removeone, removetwo, removethree, removefour, getEngine, updateShotImagePrompt, updateShotVideoPromptSchema } from '../api/index';
|
||||
import { getShotReplicationList, removeDetail, removeone, removetwo, removethree, removefour, getEngine, updateShotImagePrompt, updateShotVideoPromptSchema } from '../api/index';
|
||||
import VideoPromptSchemaEditor from '../components/VideoPromptSchemaEditor';
|
||||
import './css/InitialInfo.css';
|
||||
|
||||
@@ -150,7 +150,7 @@ function InitialInfo() {
|
||||
useEffect(() => {
|
||||
|
||||
console.log('creatID', creatID);
|
||||
|
||||
|
||||
if (creatID) {
|
||||
removeDetail(creatID).then((res: any) => {
|
||||
setTaskDetail(res);
|
||||
@@ -164,10 +164,10 @@ function InitialInfo() {
|
||||
|
||||
// 监听合并后的 steps 数据,判断第五步状态并启动/停止轮询
|
||||
useEffect(() => {
|
||||
|
||||
|
||||
// 检查第五步的状态(索引 4)
|
||||
const fifthStep = steps[4];
|
||||
|
||||
|
||||
if (fifthStep && fifthStep.status !== 'completed' && fifthStep.status !== 'failed') {
|
||||
// 第五步未完成,启动轮询
|
||||
if (!pollingTimer) {
|
||||
@@ -189,7 +189,7 @@ function InitialInfo() {
|
||||
const handleOpenModal = (prompt?: any, type?: string, stepId?: string | number) => {
|
||||
setCurrentType(type || 'image');
|
||||
setEditingPromptStepId(stepId ? String(stepId) : '');
|
||||
|
||||
|
||||
if (type === 'video' && typeof prompt === 'object') {
|
||||
setFormData(prompt);
|
||||
setPromptText('');
|
||||
@@ -197,7 +197,7 @@ function InitialInfo() {
|
||||
setPromptText(prompt || '');
|
||||
setFormData({});
|
||||
}
|
||||
|
||||
|
||||
setModalVisible(true);
|
||||
};
|
||||
|
||||
@@ -258,7 +258,7 @@ function InitialInfo() {
|
||||
if (!creatID) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
removeDetail(creatID).then((res: any) => {
|
||||
setTaskDetail(res);
|
||||
if (res.steps) {
|
||||
@@ -271,7 +271,7 @@ function InitialInfo() {
|
||||
// 刷新任务详情(用于点击下一步后更新数据)
|
||||
const refreshTaskDetail = () => {
|
||||
if (!creatID) return;
|
||||
|
||||
|
||||
removeDetail(creatID).then((res: any) => {
|
||||
setTaskDetail(res);
|
||||
if (res.steps) {
|
||||
@@ -281,13 +281,13 @@ function InitialInfo() {
|
||||
});
|
||||
};
|
||||
const createone = (stepId: number) => {
|
||||
|
||||
|
||||
removeone(taskDetail.id, stepId.toString()).then((res: any) => {
|
||||
// 重新获取任务详情以更新数据
|
||||
refreshTaskDetail();
|
||||
}).catch((error: any) => {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const createimage = (stepId: number) => {
|
||||
let params = {
|
||||
@@ -301,7 +301,7 @@ function InitialInfo() {
|
||||
refreshTaskDetail();
|
||||
}).catch((error: any) => {
|
||||
});
|
||||
}
|
||||
}
|
||||
const newcreateimage = () => {
|
||||
// console.log('下一步:', stepId);
|
||||
let params = {
|
||||
@@ -310,22 +310,22 @@ function InitialInfo() {
|
||||
image_px: "2048x2048",
|
||||
image_size: "2K"
|
||||
}
|
||||
|
||||
|
||||
removetwo(taskDetail.id, steps[1].id.toString(), params).then((res: any) => {
|
||||
refreshTaskDetail();
|
||||
|
||||
}).catch((error: any) => {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// 下一步按钮点击处理
|
||||
const handleNextStep = (stepId: number) => {
|
||||
// 获取引擎 ID 和视频参数
|
||||
const engineId = countType;
|
||||
const videoParams = {
|
||||
engine_id : countType,
|
||||
engine_id: countType,
|
||||
duration: videoDuration,
|
||||
aspect_ratio: videoAspectRatio,
|
||||
resolution: videoResolution,
|
||||
@@ -340,12 +340,12 @@ function InitialInfo() {
|
||||
// 这里可以添加下一步的逻辑,比如调用接口等
|
||||
};
|
||||
|
||||
const createvideo = (stepId: number,engineId: string) => {
|
||||
const createvideo = (stepId: number, engineId: string) => {
|
||||
let params = {
|
||||
engine_id: engineId,
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
removefour(taskDetail.id, stepId.toString(), params).then((res: any) => {
|
||||
// 重新获取任务详情以更新数据
|
||||
refreshTaskDetail();
|
||||
@@ -356,8 +356,8 @@ function InitialInfo() {
|
||||
let params = {
|
||||
engine_id: steps[2].engineId,
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
removefour(taskDetail.id, steps[2].id.toString(), params).then((res: any) => {
|
||||
// 重新获取任务详情以更新数据
|
||||
refreshTaskDetail();
|
||||
@@ -369,76 +369,141 @@ function InitialInfo() {
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div style={{ minHeight: 'calc(100vh - 90px)', background: '#f8fafc' }}>
|
||||
<div style={{ height: '100%' }}>
|
||||
<div style={{ height: '100%', display: 'flex', justifyContent: 'space-between', gap: '2%' }}>
|
||||
<div style={{ width: '70%', background: '#fff', borderRadius: 12, overflowY: 'auto', maxHeight: 'calc(100vh - 90px)' }}>
|
||||
<div style={{ borderBottom: '1px solid #e2e8f0', width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '14px', boxSizing: 'border-box' }}>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<ArrowLeftOutlined />}
|
||||
onClick={() => navigate(-1)}
|
||||
style={{ color: '#64748b' }}
|
||||
>
|
||||
返回
|
||||
</Button>
|
||||
<h3 style={{ margin: 0 }}>
|
||||
<div
|
||||
className="initialinfo-container"
|
||||
style={{
|
||||
margin: '-24px -32px -32px',
|
||||
height: 'calc(100vh - 34px)',
|
||||
position: 'relative',
|
||||
boxSizing: 'border-box',
|
||||
overflow: 'hidden',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
}}
|
||||
>
|
||||
{/* 背景装饰 */}
|
||||
<div style={{ position: 'absolute', top: -100, right: -100, width: 300, height: 300, background: 'radial-gradient(circle, rgba(99,102,241,0.08) 0%, transparent 70%)', borderRadius: '50%', pointerEvents: 'none' }} />
|
||||
<div style={{ position: 'absolute', bottom: -100, left: -100, width: 300, height: 300, background: 'radial-gradient(circle, rgba(167,139,250,0.08) 0%, transparent 70%)', borderRadius: '50%', pointerEvents: 'none' }} />
|
||||
|
||||
<div style={{ flex: 1, display: 'flex', justifyContent: 'space-between', alignItems: 'stretch', gap: '2%', position: 'relative', zIndex: 1, minHeight: 0 }}>
|
||||
<div
|
||||
className="replication-preview"
|
||||
style={{
|
||||
flex: 4,
|
||||
background: 'rgba(255,255,255,0.85)',
|
||||
backdropFilter: 'blur(20px)',
|
||||
borderRadius: 20,
|
||||
overflow: 'hidden',
|
||||
border: '1px solid rgba(99, 102, 241, 0.1)',
|
||||
boxShadow: '0 8px 32px rgba(99, 102, 241, 0.08)',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
minHeight: 0,
|
||||
}}
|
||||
>
|
||||
<div style={{ borderBottom: '1px solid rgba(99, 102, 241, 0.08)', width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '16px 24px', background: 'rgba(255,255,255,0.6)', backdropFilter: 'blur(10px)', boxSizing: 'border-box' }}>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<ArrowLeftOutlined />}
|
||||
onClick={() => navigate(-1)}
|
||||
style={{
|
||||
color: '#64748b',
|
||||
borderRadius: 10,
|
||||
fontSize: 13,
|
||||
height: 32,
|
||||
transition: 'all 0.25s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
}}
|
||||
>
|
||||
返回
|
||||
</Button>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
|
||||
<div style={{ width: 32, height: 2, background: 'linear-gradient(90deg, transparent, #6366f1, #8b5cf6, transparent)', borderRadius: 1 }} />
|
||||
|
||||
<h3 style={{ margin: 0, fontSize: 16, fontWeight: 700, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent', backgroundClip: 'text' }}>
|
||||
拆镜复刻 - 任务详情
|
||||
</h3>
|
||||
<Button type="primary" ghost style={{ borderRadius: 6, padding: '4px 12px', fontSize: 12, borderColor: '#6366f1', color: '#6366f1' }} onClick={() => setIsModalOpen(true)}>
|
||||
创作记录
|
||||
</Button>
|
||||
<div style={{ width: 32, height: 2, background: 'linear-gradient(90deg, transparent, #8b5cf6, #6366f1, transparent)', borderRadius: 1 }} />
|
||||
|
||||
</div>
|
||||
<Button
|
||||
type="primary"
|
||||
ghost
|
||||
style={{
|
||||
borderRadius: 10,
|
||||
padding: '4px 16px',
|
||||
fontSize: 13,
|
||||
background: 'rgba(99, 102, 241, 0.1)',
|
||||
borderColor: 'rgba(99, 102, 241, 0.2)',
|
||||
color: '#6366f1',
|
||||
}}
|
||||
onClick={() => setIsModalOpen(true)}
|
||||
>
|
||||
创作记录
|
||||
</Button>
|
||||
</div>
|
||||
<div style={{ flex: 1, overflowY: 'auto' }}>
|
||||
<div className="material_box">
|
||||
<div style={{ display: 'flex', gap: 24, flexWrap: 'wrap', alignItems: 'flex-start' }}>
|
||||
<div style={{ flex: 1, minWidth: 250 }}>
|
||||
<span style={{ fontSize: 14, fontWeight: 500, color: '#374151', marginBottom: 8, display: 'block' }}>视频</span>
|
||||
<div className="medio_box" style={{ aspectRatio: '16/9', background: '#f1f5f9', borderRadius: 8, overflow: 'hidden' }}>
|
||||
<span style={{ fontSize: 14, fontWeight: 600, color: '#1e293b', marginBottom: 10, display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<span style={{ width: 3, height: 14, background: 'linear-gradient(180deg, #6366f1, #8b5cf6)', borderRadius: 2, display: 'inline-block' }} />
|
||||
视频
|
||||
</span>
|
||||
<div className="medio_box" style={{ aspectRatio: '16/9', background: 'linear-gradient(135deg, rgba(248,250,252,0.8) 0%, rgba(238,242,255,0.6) 100%)', borderRadius: 14, overflow: 'hidden', boxShadow: '0 4px 12px rgba(99, 102, 241, 0.06)', border: '1px solid rgba(99, 102, 241, 0.08)' }}>
|
||||
{taskDetail?.material?.materialVideoUrl ? (
|
||||
<video
|
||||
controls
|
||||
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${taskDetail.material.materialVideoUrl}`}
|
||||
style={{ width: '100%', height: '100%', objectFit: 'contain' }}
|
||||
<video
|
||||
controls
|
||||
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${taskDetail.material.materialVideoUrl}`}
|
||||
style={{ width: '100%', height: '100%', objectFit: 'contain' }}
|
||||
/>
|
||||
) : (
|
||||
<div style={{ width: '100%', height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#94a3b8', fontSize: 12 }}>暂无视频</div>
|
||||
<div style={{ width: '100%', height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#94a3b8', fontSize: 13 }}>暂无视频</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="product_img" style={{ flex: 1, minWidth: 250 }}>
|
||||
<span style={{ fontSize: 14, fontWeight: 500, color: '#374151', marginBottom: 8, display: 'block' }}>产品图片</span>
|
||||
<div className="medio_box" style={{ aspectRatio: '1/1', background: '#f1f5f9', borderRadius: 8, overflow: 'hidden' }}>
|
||||
<span style={{ fontSize: 14, fontWeight: 600, color: '#1e293b', marginBottom: 10, display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<span style={{ width: 3, height: 14, background: 'linear-gradient(180deg, #6366f1, #8b5cf6)', borderRadius: 2, display: 'inline-block' }} />
|
||||
产品图片
|
||||
</span>
|
||||
<div className="medio_box" style={{ aspectRatio: '1/1', background: 'linear-gradient(135deg, rgba(248,250,252,0.8) 0%, rgba(238,242,255,0.6) 100%)', borderRadius: 14, overflow: 'hidden', boxShadow: '0 4px 12px rgba(99, 102, 241, 0.06)', border: '1px solid rgba(99, 102, 241, 0.08)' }}>
|
||||
{taskDetail?.material?.materialImageUrl ? (
|
||||
<img src={taskDetail.material.materialImageUrl} alt="" style={{ width: '100%', height: '100%', objectFit: 'contain' }} />
|
||||
) : (
|
||||
<div style={{ width: '100%', height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#94a3b8', fontSize: 12 }}>暂无图片</div>
|
||||
<div style={{ width: '100%', height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#94a3b8', fontSize: 13 }}>暂无图片</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{taskDetail?.finalImageUrl && (
|
||||
<div style={{ marginTop: 16 }}>
|
||||
<div style={{ marginTop: 24 }}>
|
||||
<div>
|
||||
<span style={{ fontSize: 14, fontWeight: 500, color: '#374151', marginBottom: 8, display: 'block' }}>生成图片</span>
|
||||
<div className="medio_box" style={{ aspectRatio: '1/1', background: '#f1f5f9', borderRadius: 8, overflow: 'hidden' }}>
|
||||
<img
|
||||
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${taskDetail.finalImageUrl}`}
|
||||
alt=""
|
||||
style={{ width: '100%', height: '100%', objectFit: 'contain' }}
|
||||
<span style={{ fontSize: 14, fontWeight: 600, color: '#1e293b', marginBottom: 10, display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<span style={{ width: 3, height: 14, background: 'linear-gradient(180deg, #6366f1, #8b5cf6)', borderRadius: 2, display: 'inline-block' }} />
|
||||
生成图片
|
||||
</span>
|
||||
<div className="medio_box" style={{ aspectRatio: '1/1', background: 'linear-gradient(135deg, rgba(248,250,252,0.8) 0%, rgba(238,242,255,0.6) 100%)', borderRadius: 14, overflow: 'hidden', boxShadow: '0 4px 12px rgba(99, 102, 241, 0.06)', border: '1px solid rgba(99, 102, 241, 0.08)' }}>
|
||||
<img
|
||||
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${taskDetail.finalImageUrl}`}
|
||||
alt=""
|
||||
style={{ width: '100%', height: '100%', objectFit: 'contain' }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{taskDetail?.finalVideoUrl && (
|
||||
<div style={{ marginTop: 16 }}>
|
||||
<div style={{ marginTop: 24 }}>
|
||||
<div>
|
||||
<span style={{ fontSize: 14, fontWeight: 500, color: '#374151', marginBottom: 8, display: 'block' }}>生成视频</span>
|
||||
<div className="medio_box" style={{ aspectRatio: '16/9', background: '#f1f5f9', borderRadius: 8, overflow: 'hidden' }}>
|
||||
<video
|
||||
controls
|
||||
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${taskDetail.finalVideoUrl}`}
|
||||
style={{ width: '100%', height: '100%', objectFit: 'contain' }}
|
||||
<span style={{ fontSize: 14, fontWeight: 600, color: '#1e293b', marginBottom: 10, display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<span style={{ width: 3, height: 14, background: 'linear-gradient(180deg, #6366f1, #8b5cf6)', borderRadius: 2, display: 'inline-block' }} />
|
||||
生成视频
|
||||
</span>
|
||||
<div className="medio_box" style={{ aspectRatio: '16/9', background: 'linear-gradient(135deg, rgba(248,250,252,0.8) 0%, rgba(238,242,255,0.6) 100%)', borderRadius: 14, overflow: 'hidden', boxShadow: '0 4px 12px rgba(99, 102, 241, 0.06)', border: '1px solid rgba(99, 102, 241, 0.08)' }}>
|
||||
<video
|
||||
controls
|
||||
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${taskDetail.finalVideoUrl}`}
|
||||
style={{ width: '100%', height: '100%', objectFit: 'contain' }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -446,8 +511,28 @@ function InitialInfo() {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ width: '28%', minWidth: '400px', background: '#fff', borderRadius: 12, overflowY: 'auto', maxHeight: 'calc(100vh - 90px)' }}>
|
||||
<div style={{ padding: '14px' }}> 生成步骤</div>
|
||||
</div>
|
||||
<div
|
||||
className="replication-form"
|
||||
style={{
|
||||
flex: 1,
|
||||
minWidth: '400px',
|
||||
background: 'rgba(255,255,255,0.85)',
|
||||
backdropFilter: 'blur(20px)',
|
||||
borderRadius: 20,
|
||||
overflow: 'hidden',
|
||||
border: '1px solid rgba(99, 102, 241, 0.1)',
|
||||
boxShadow: '0 8px 32px rgba(99, 102, 241, 0.08)',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
minHeight: 0,
|
||||
}}
|
||||
>
|
||||
<div style={{ padding: '16px 24px', borderBottom: '1px solid rgba(99, 102, 241, 0.08)', display: 'flex', alignItems: 'center', gap: 10, background: 'rgba(255,255,255,0.6)', backdropFilter: 'blur(10px)' }}>
|
||||
<div style={{ width: 4, height: 18, background: 'linear-gradient(180deg, #6366f1 0%, #8b5cf6 100%)', borderRadius: 2 }} />
|
||||
<span style={{ fontSize: 15, fontWeight: 700, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent', backgroundClip: 'text' }}>生成步骤</span>
|
||||
</div>
|
||||
<div style={{ flex: 1, overflowY: 'auto', padding: '8px 16px 16px' }}>
|
||||
<Collapse
|
||||
defaultActiveKey={['']}
|
||||
ghost
|
||||
@@ -488,25 +573,25 @@ function InitialInfo() {
|
||||
</div>
|
||||
),
|
||||
children: (
|
||||
<div style={{ padding: '16px', background: '#f8fafc', borderRadius: 8, marginTop: 8 }}>
|
||||
<div style={{ padding: '16px', background: 'linear-gradient(135deg, rgba(248,250,252,0.6) 0%, rgba(238,242,255,0.4) 100%)', borderRadius: 12, marginTop: 8, border: '1px solid rgba(99, 102, 241, 0.06)' }}>
|
||||
{/* 步骤1: 原始素材 */}
|
||||
{step.childId === 1 && (
|
||||
<>
|
||||
<div style={{ display: 'flex', gap: 20, marginBottom: 20, alignItems: 'flex-start', flexWrap: 'wrap' }}>
|
||||
<div style={{ flex: 1, minWidth: 200 }}>
|
||||
<Text style={{ color: '#64748b', fontSize: 13, marginBottom: 10, display: 'block', fontWeight: 500 }}>原视频</Text>
|
||||
<div style={{ height: 180, borderRadius: 10, background: '#f1f5f9', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<div style={{ height: 180, borderRadius: 12, background: 'linear-gradient(135deg, rgba(248,250,252,0.8) 0%, rgba(238,242,255,0.6) 100%)', display: 'flex', alignItems: 'center', justifyContent: 'center', border: '1px solid rgba(99, 102, 241, 0.06)' }}>
|
||||
{taskDetail?.material?.materialVideoUrl ? (
|
||||
<video
|
||||
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${taskDetail.material.materialVideoUrl}`}
|
||||
style={{ maxWidth: '100%', maxHeight: '100%', objectFit: 'contain', borderRadius: 10 }}
|
||||
controls
|
||||
<video
|
||||
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${taskDetail.material.materialVideoUrl}`}
|
||||
style={{ maxWidth: '100%', maxHeight: '100%', objectFit: 'contain', borderRadius: 10 }}
|
||||
controls
|
||||
/>
|
||||
) : (
|
||||
<div style={{ textAlign: 'center', color: '#94a3b8' }}>
|
||||
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" style={{ marginBottom: 8 }}>
|
||||
<rect x="2" y="2" width="20" height="16" rx="2" ry="2"/>
|
||||
<path d="M8 12l4 2 4-2"/>
|
||||
<rect x="2" y="2" width="20" height="16" rx="2" ry="2" />
|
||||
<path d="M8 12l4 2 4-2" />
|
||||
</svg>
|
||||
<div style={{ fontSize: 13 }}>暂无视频</div>
|
||||
</div>
|
||||
@@ -515,53 +600,51 @@ function InitialInfo() {
|
||||
</div>
|
||||
<div style={{ flex: 1, minWidth: 200 }}>
|
||||
<Text style={{ color: '#64748b', fontSize: 13, marginBottom: 10, display: 'block', fontWeight: 500 }}>产品图片</Text>
|
||||
<div style={{ height: 180, borderRadius: 10, background: '#f1f5f9', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<div style={{ height: 180, borderRadius: 12, background: 'linear-gradient(135deg, rgba(248,250,252,0.8) 0%, rgba(238,242,255,0.6) 100%)', display: 'flex', alignItems: 'center', justifyContent: 'center', border: '1px solid rgba(99, 102, 241, 0.06)' }}>
|
||||
{taskDetail?.material?.materialImageUrl ? (
|
||||
<img
|
||||
src={taskDetail.material.materialImageUrl}
|
||||
alt="产品图片"
|
||||
style={{ maxWidth: '100%', maxHeight: '100%', objectFit: 'contain', borderRadius: 10 }}
|
||||
<img
|
||||
src={taskDetail.material.materialImageUrl}
|
||||
alt="产品图片"
|
||||
style={{ maxWidth: '100%', maxHeight: '100%', objectFit: 'contain', borderRadius: 10 }}
|
||||
/>
|
||||
) : (
|
||||
<div style={{ textAlign: 'center', color: '#94a3b8' }}>
|
||||
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" style={{ marginBottom: 8 }}>
|
||||
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
|
||||
<circle cx="8.5" cy="8.5" r="1.5" fill="currentColor"/>
|
||||
<path d="M21 15l-5-5L5 21"/>
|
||||
<rect x="3" y="3" width="18" height="18" rx="2" ry="2" />
|
||||
<circle cx="8.5" cy="8.5" r="1.5" fill="currentColor" />
|
||||
<path d="M21 15l-5-5L5 21" />
|
||||
</svg>
|
||||
<div style={{ fontSize: 13 }}>暂无图片</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<Button onClick={() => { message.info('正在生成图片提示词,请稍候...'); createone(step.id); }} type="primary" style={{ flex: 1, borderRadius: 8, background: '#6366f1', borderColor: '#6366f1', height: 36 }} disabled={step.status !== 'completed'}>
|
||||
<Button onClick={() => { message.info('正在生成图片提示词,请稍候...'); createone(step.id); }} type="primary" style={{ flex: 1, borderRadius: 10, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', border: 'none', height: 36, fontWeight: 500, boxShadow: '0 4px 12px rgba(99, 102, 241, 0.3)' }} disabled={step.status !== 'completed'}>
|
||||
下一步:生成图片提示词
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
|
||||
</>
|
||||
)}
|
||||
{/* 步骤2: 生成提示词 */}
|
||||
{step.childId === 2 && (
|
||||
<>
|
||||
{/* <Text style={{ color: '#475569', fontSize: 13, lineHeight: 1.8, whiteSpace: 'pre-wrap' }}>
|
||||
{taskDetail?.generationPrompt || '暂无提示词'}
|
||||
</Text> */}
|
||||
<Text style={{ color: '#475569', fontSize: 13, lineHeight: 1.8, whiteSpace: 'pre-wrap' }}>
|
||||
{/* {taskDetail?.generationPrompt || '暂无提示词'} */}
|
||||
{step?.output?.payload?.prompt || '暂无提示词'}
|
||||
</Text>
|
||||
<div style={{ padding: '12px 14px', background: 'rgba(255,255,255,0.6)', borderRadius: 10, border: '1px solid rgba(99, 102, 241, 0.08)', marginBottom: 4 }}>
|
||||
<Text style={{ color: '#475569', fontSize: 13, lineHeight: 1.8, whiteSpace: 'pre-wrap' }}>
|
||||
{step?.output?.payload?.prompt || '暂无提示词'}
|
||||
</Text>
|
||||
</div>
|
||||
<Space style={{ marginTop: 16, gap: 12, width: '100%' }}>
|
||||
<Button
|
||||
type="default"
|
||||
icon={<EditOutlined />}
|
||||
onClick={() => handleOpenModal(step?.output?.payload?.prompt, 'image', step.id)}
|
||||
style={{ flex: 1, borderRadius: 8, borderColor: '#6366f1', color: '#6366f1', height: 36 }}
|
||||
style={{ flex: 1, borderRadius: 10, borderColor: 'rgba(99, 102, 241, 0.3)', color: '#6366f1', height: 36, fontWeight: 500, background: 'rgba(99, 102, 241, 0.04)' }}
|
||||
disabled={step.status !== 'completed'}
|
||||
>
|
||||
修改提示词
|
||||
</Button>
|
||||
<Button onClick={() => { message.info('正在生成图片,请稍候...'); createimage(step.id); }} type="primary" style={{ flex: 1, borderRadius: 8, background: '#6366f1', borderColor: '#6366f1', height: 36 }} disabled={step.status !== 'completed'}>
|
||||
<Button onClick={() => { message.info('正在生成图片,请稍候...'); createimage(step.id); }} type="primary" style={{ flex: 1, borderRadius: 10, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', border: 'none', height: 36, fontWeight: 500, boxShadow: '0 4px 12px rgba(99, 102, 241, 0.3)' }} disabled={step.status !== 'completed'}>
|
||||
下一步:生成图片
|
||||
</Button>
|
||||
</Space>
|
||||
@@ -570,19 +653,39 @@ function InitialInfo() {
|
||||
{/* 步骤3: 生成产品融合图 */}
|
||||
{step.childId === 3 && (
|
||||
<>
|
||||
<div style={{ borderRadius: 8, overflow: 'hidden', marginBottom: 16, background: '#f1f5f9' }}>
|
||||
{/* {step.output.result.result_image_url} */}
|
||||
|
||||
|
||||
<div style={{ borderRadius: 12, overflow: 'hidden', marginBottom: 16, background: 'linear-gradient(135deg, rgba(248,250,252,0.8) 0%, rgba(238,242,255,0.6) 100%)', border: '1px solid rgba(99, 102, 241, 0.08)', boxShadow: '0 2px 8px rgba(99, 102, 241, 0.04)', position: 'relative' }}>
|
||||
{taskDetail.finalImageUrl ? (
|
||||
<img src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${taskDetail.finalImageUrl}`} alt="融合图" style={{ width: '100%', height: 180, objectFit: 'cover' }} />
|
||||
) : (
|
||||
<div style={{ width: '100%', height: 180, display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#94a3b8', fontSize: 14 }}>暂无融合图</div>
|
||||
)}
|
||||
<Button
|
||||
type="default"
|
||||
icon={<EditOutlined />}
|
||||
onClick={() => newcreateimage()}
|
||||
disabled={step.status !== 'completed'}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
bottom: 8,
|
||||
left: 8,
|
||||
borderRadius: 8,
|
||||
borderColor: 'rgba(99, 102, 241, 0.3)',
|
||||
color: '#6366f1',
|
||||
height: 30,
|
||||
fontWeight: 500,
|
||||
background: 'rgba(255,255,255,0.85)',
|
||||
backdropFilter: 'blur(4px)',
|
||||
padding: '0 12px',
|
||||
fontSize: 12,
|
||||
}}
|
||||
>
|
||||
重新生成
|
||||
</Button>
|
||||
</div>
|
||||
{/* 引擎选择器 */}
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<div style={{ position: 'relative', display: 'inline-block' }}>
|
||||
{/* 引擎选择器和视频参数设置 */}
|
||||
<p style={{ marginBottom: 6, fontSize: 14, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent', backgroundClip: 'text' }}>视频参数选择:</p>
|
||||
<div style={{ width: '100%', display: 'flex', justifyContent: 'space-between', marginBottom: 16 }}>
|
||||
<div style={{ flex: 1, position: 'relative', display: 'inline-block' }}>
|
||||
<button
|
||||
onClick={() => {
|
||||
setShowEngineModal(!showEngineModal);
|
||||
@@ -590,12 +693,12 @@ function InitialInfo() {
|
||||
}}
|
||||
className="image-settings-trigger"
|
||||
style={{
|
||||
minWidth: 200,
|
||||
width: '100%',
|
||||
padding: '4px 12px',
|
||||
height: 34,
|
||||
borderRadius: 8,
|
||||
border: 'none',
|
||||
backgroundColor: '#f1f5f9',
|
||||
borderRadius: 10,
|
||||
border: '1px solid rgba(99, 102, 241, 0.15)',
|
||||
backgroundColor: 'rgba(255,255,255,0.7)',
|
||||
cursor: 'pointer',
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
@@ -603,7 +706,7 @@ function InitialInfo() {
|
||||
transition: 'all 0.2s',
|
||||
}}
|
||||
>
|
||||
<SettingOutlined style={{ fontSize: 14, color: '#64748b' }} />
|
||||
<SettingOutlined style={{ fontSize: 14, color: '#6366f1' }} />
|
||||
<Text style={{
|
||||
fontSize: 14,
|
||||
fontWeight: 500,
|
||||
@@ -619,7 +722,7 @@ function InitialInfo() {
|
||||
style={{
|
||||
position: 'absolute',
|
||||
bottom: 'calc(100% + 8px)',
|
||||
left: -20,
|
||||
left: -10,
|
||||
width: 350,
|
||||
backgroundColor: '#fff',
|
||||
borderRadius: 16,
|
||||
@@ -709,11 +812,9 @@ function InitialInfo() {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 视频参数设置 */}
|
||||
<div style={{ marginBottom: 16 }}>
|
||||
<div style={{ position: 'relative', display: 'inline-block' }}>
|
||||
{/* 视频参数设置 */}
|
||||
<div style={{ flex: 1, position: 'relative', display: 'inline-block' }}>
|
||||
<button
|
||||
onClick={() => {
|
||||
setShowVideoSettingsModal(!showVideoSettingsModal);
|
||||
@@ -721,12 +822,12 @@ function InitialInfo() {
|
||||
}}
|
||||
className="image-settings-trigger"
|
||||
style={{
|
||||
minWidth: 220,
|
||||
width: '100%',
|
||||
padding: '4px 12px',
|
||||
height: 34,
|
||||
borderRadius: 8,
|
||||
border: 'none',
|
||||
backgroundColor: '#f1f5f9',
|
||||
borderRadius: 10,
|
||||
border: '1px solid rgba(99, 102, 241, 0.15)',
|
||||
backgroundColor: 'rgba(255,255,255,0.7)',
|
||||
cursor: 'pointer',
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
@@ -734,7 +835,7 @@ function InitialInfo() {
|
||||
transition: 'all 0.2s',
|
||||
}}
|
||||
>
|
||||
<LayoutOutlined style={{ fontSize: 14, color: '#64748b' }} />
|
||||
<LayoutOutlined style={{ fontSize: 14, color: '#6366f1' }} />
|
||||
<Text style={{
|
||||
fontSize: 14,
|
||||
fontWeight: 500,
|
||||
@@ -750,7 +851,7 @@ function InitialInfo() {
|
||||
style={{
|
||||
position: 'absolute',
|
||||
bottom: 'calc(100% + 8px)',
|
||||
left: -20,
|
||||
left: -160,
|
||||
width: 350,
|
||||
backgroundColor: '#fff',
|
||||
borderRadius: 16,
|
||||
@@ -942,41 +1043,35 @@ function InitialInfo() {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<Space style={{ width: '100%', gap: 12 }}>
|
||||
<Button type="default" icon={<EditOutlined />} style={{ flex: 1, borderRadius: 8, borderColor: '#6366f1', color: '#6366f1', height: 36 }
|
||||
} onClick={() => newcreateimage()} disabled={step.status !== 'completed'}>
|
||||
重新生成
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
style={{ flex: 1, borderRadius: 8, background: '#6366f1', borderColor: '#6366f1', height: 36 }}
|
||||
onClick={() => { message.info('正在生成视频提示词,请稍候...'); handleNextStep(step.id); }}
|
||||
disabled={step.status !== 'completed'}
|
||||
>
|
||||
下一步:生成视频提示词
|
||||
</Button>
|
||||
</Space>
|
||||
<Button
|
||||
type="primary"
|
||||
style={{ width: '100%', borderRadius: 10, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', border: 'none', height: 36, fontWeight: 500, boxShadow: '0 4px 12px rgba(99, 102, 241, 0.3)' }}
|
||||
onClick={() => { message.info('正在生成视频提示词,请稍候...'); handleNextStep(step.id); }}
|
||||
disabled={step.status !== 'completed'}
|
||||
>
|
||||
下一步:生成视频提示词
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
{/* 步骤4: 生成视频提示词 */}
|
||||
{step.childId === 4 && (
|
||||
<>
|
||||
<Text style={{ color: '#475569', fontSize: 13, lineHeight: 1.8, whiteSpace: 'pre-wrap' }}>
|
||||
{/* {taskDetail?.videoPrompt || '暂无视频提示词'} */}
|
||||
{step?.output?.payload?.finalPrompt || '暂无提示词'}
|
||||
|
||||
</Text>
|
||||
<div style={{ padding: '12px 14px', background: 'rgba(255,255,255,0.6)', borderRadius: 10, border: '1px solid rgba(99, 102, 241, 0.08)', marginBottom: 4 }}>
|
||||
<Text style={{ color: '#475569', fontSize: 13, lineHeight: 1.8, whiteSpace: 'pre-wrap' }}>
|
||||
{step?.output?.payload?.finalPrompt || '暂无提示词'}
|
||||
</Text>
|
||||
</div>
|
||||
<Space style={{ marginTop: 16, gap: 12, width: '100%' }}>
|
||||
<Button
|
||||
type="default"
|
||||
icon={<EditOutlined />}
|
||||
onClick={() => handleOpenModal(step?.output?.payload?.promptSchema, 'video', step.id)}
|
||||
style={{ flex: 1, borderRadius: 8, borderColor: '#6366f1', color: '#6366f1', height: 36 }}
|
||||
style={{ flex: 1, borderRadius: 10, borderColor: 'rgba(99, 102, 241, 0.3)', color: '#6366f1', height: 36, fontWeight: 500, background: 'rgba(99, 102, 241, 0.04)' }}
|
||||
disabled={step.status !== 'completed'}
|
||||
>
|
||||
修改提示词
|
||||
</Button>
|
||||
<Button onClick={() => { message.info('正在生成视频,请稍候...'); createvideo(step.id,step.engineId); }} type="primary" style={{ flex: 1, borderRadius: 8, background: '#6366f1', borderColor: '#6366f1', height: 36 }} disabled={step.status !== 'completed'}>
|
||||
<Button onClick={() => { message.info('正在生成视频,请稍候...'); createvideo(step.id, step.engineId); }} type="primary" style={{ flex: 1, borderRadius: 10, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', border: 'none', height: 36, fontWeight: 500, boxShadow: '0 4px 12px rgba(99, 102, 241, 0.3)' }} disabled={step.status !== 'completed'}>
|
||||
下一步:生成视频
|
||||
</Button>
|
||||
</Space>
|
||||
@@ -985,7 +1080,7 @@ function InitialInfo() {
|
||||
{/* 步骤5: 生成最终视频 */}
|
||||
{step.childId === 5 && (
|
||||
<>
|
||||
<div style={{ borderRadius: 8, overflow: 'hidden', marginBottom: 16, position: 'relative', background: '#f1f5f9' }}>
|
||||
<div style={{ borderRadius: 12, overflow: 'hidden', marginBottom: 16, position: 'relative', background: 'linear-gradient(135deg, rgba(248,250,252,0.8) 0%, rgba(238,242,255,0.6) 100%)', border: '1px solid rgba(99, 102, 241, 0.08)', boxShadow: '0 2px 8px rgba(99, 102, 241, 0.04)' }}>
|
||||
{step.status === 'completed' && taskDetail?.finalVideoUrl ? (
|
||||
<video src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${taskDetail.finalVideoUrl}`} style={{ width: '100%', height: 180, objectFit: 'cover' }} controls />
|
||||
) : step.status === 'processing' ? (
|
||||
@@ -1000,13 +1095,13 @@ function InitialInfo() {
|
||||
)}
|
||||
</div>
|
||||
<Space style={{ width: '100%', gap: 12 }}>
|
||||
<Button onClick={() => agincreatevideo()} type="default" icon={<EditOutlined />} style={{ flex: 1, borderRadius: 8, borderColor: '#6366f1', color: '#6366f1', height: 36 }} disabled={step.status !== 'completed'}>
|
||||
<Button onClick={() => agincreatevideo()} type="default" icon={<EditOutlined />} style={{ flex: 1, borderRadius: 10, borderColor: 'rgba(99, 102, 241, 0.3)', color: '#6366f1', height: 36, fontWeight: 500, background: 'rgba(99, 102, 241, 0.04)' }} disabled={step.status !== 'completed'}>
|
||||
重新生成
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<DownloadOutlined />}
|
||||
style={{ flex: 1, borderRadius: 8, background: '#6366f1', borderColor: '#6366f1', height: 36 }}
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<DownloadOutlined />}
|
||||
style={{ flex: 1, borderRadius: 10, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', border: 'none', height: 36, fontWeight: 500, boxShadow: '0 4px 12px rgba(99, 102, 241, 0.3)' }}
|
||||
disabled={step.status !== 'completed'}
|
||||
onClick={() => {
|
||||
const videoUrl = `${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${taskDetail?.finalVideoUrl}&download=1`;
|
||||
@@ -1032,21 +1127,33 @@ function InitialInfo() {
|
||||
</div>
|
||||
</div>
|
||||
<Modal
|
||||
title="修改提示词"
|
||||
title={
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
|
||||
<div style={{ width: 4, height: 18, background: 'linear-gradient(180deg, #6366f1 0%, #8b5cf6 100%)', borderRadius: 2 }} />
|
||||
<span style={{ fontSize: 15, fontWeight: 700, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent', backgroundClip: 'text' }}>
|
||||
{currentType === 'video' ? '修改视频提示词' : '修改提示词'}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
open={modalVisible}
|
||||
onCancel={() => setModalVisible(false)}
|
||||
onCancel={() => { if (!promptSaving) setModalVisible(false); }}
|
||||
footer={[
|
||||
<Button key="cancel" onClick={() => setModalVisible(false)}>取消</Button>,
|
||||
<Button key="confirm" type="primary" onClick={handleConfirm} loading={promptSaving}>确认</Button>,
|
||||
<Button key="cancel" onClick={() => setModalVisible(false)} disabled={promptSaving} style={{ borderRadius: 8 }}>取消</Button>,
|
||||
<Button key="confirm" type="primary" onClick={handleConfirm} loading={promptSaving} disabled={promptSaving} style={{ borderRadius: 8, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', border: 'none' }}>确认</Button>,
|
||||
]}
|
||||
width={800}
|
||||
style={{ borderRadius: 16 }}
|
||||
styles={{
|
||||
header: { background: 'rgba(255,255,255,0.6)', backdropFilter: 'blur(10px)', borderBottom: '1px solid rgba(99, 102, 241, 0.08)', padding: '16px 24px' },
|
||||
body: { background: 'linear-gradient(135deg, #f8fafc 0%, #eef2ff 50%, #f0f9ff 100%)', padding: '20px 24px' },
|
||||
}}
|
||||
>
|
||||
{currentType === 'image' ? (
|
||||
<TextArea
|
||||
value={promptText}
|
||||
onChange={(e) => setPromptText(e.target.value)}
|
||||
rows={8}
|
||||
style={{ width: '100%', height: 300 }}
|
||||
style={{ width: '100%', height: 300, borderRadius: 10, border: '1px solid rgba(99, 102, 241, 0.15)', background: 'rgba(255,255,255,0.8)' }}
|
||||
placeholder="请输入提示词"
|
||||
/>
|
||||
) : (
|
||||
@@ -1057,93 +1164,168 @@ function InitialInfo() {
|
||||
</Modal>
|
||||
{/* 创作记录弹窗 */}
|
||||
<Modal
|
||||
title="创作记录"
|
||||
title={
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
|
||||
<div style={{ width: 4, height: 20, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', borderRadius: 2 }} />
|
||||
<span style={{ fontSize: 16, fontWeight: 600, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent' }}>创作记录</span>
|
||||
</div>
|
||||
}
|
||||
open={isModalOpen}
|
||||
onCancel={() => setIsModalOpen(false)}
|
||||
width={800}
|
||||
footer={null}
|
||||
style={{ borderRadius: 12 }}
|
||||
styles={{
|
||||
body: { background: 'linear-gradient(180deg, #f8fafc 0%, #eef2ff 100%)' },
|
||||
header: { background: '#fff', borderBottom: '1px solid rgba(99, 102, 241, 0.1)', padding: '20px 24px' },
|
||||
}}
|
||||
>
|
||||
{/* 搜索区域 */}
|
||||
<div style={{ display: 'flex', justifyContent: 'flex-end', marginBottom: 16 }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'flex-end', marginBottom: 16, gap: 8 }}>
|
||||
<Input
|
||||
placeholder="搜索产品名称"
|
||||
style={{ width: 200, borderRadius: 6, marginRight: 8 }}
|
||||
style={{
|
||||
width: 200,
|
||||
borderRadius: 10,
|
||||
border: '1px solid rgba(99, 102, 241, 0.2)',
|
||||
height: 40,
|
||||
}}
|
||||
/>
|
||||
<Button type="primary" style={{ borderRadius: 6 }}>
|
||||
<Button
|
||||
type="primary"
|
||||
style={{
|
||||
borderRadius: 10,
|
||||
height: 40,
|
||||
background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)',
|
||||
border: 'none',
|
||||
}}
|
||||
>
|
||||
搜索
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* 表格 */}
|
||||
<Table
|
||||
columns={[
|
||||
{
|
||||
title: '产品名称',
|
||||
dataIndex: 'targetProjectName',
|
||||
key: 'targetProjectName',
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
render: (text: string) => {
|
||||
const statusMap: Record<string, string> = {
|
||||
'pending': '子任务待处理',
|
||||
'waiting_user': '等待用户确认或触发',
|
||||
'processing': '子任务处理中',
|
||||
'completed': '子任务完成',
|
||||
'failed': '子任务失败',
|
||||
'cancelled': '子任务取消',
|
||||
};
|
||||
return statusMap[text] || text || '-';
|
||||
},
|
||||
},
|
||||
{
|
||||
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 `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
render: (_, record) => (
|
||||
<button
|
||||
onClick={() => {
|
||||
setIsModalOpen(false);
|
||||
navigate(`/initial/${record.id}/initialinfo`);
|
||||
}}
|
||||
style={{ color: '#6366f1', textDecoration: 'none', fontSize: 12, border: 'none', background: 'none', cursor: 'pointer' }}
|
||||
>
|
||||
查看详情
|
||||
</button>
|
||||
),
|
||||
},
|
||||
]}
|
||||
dataSource={tableData}
|
||||
rowKey="id"
|
||||
pagination={{
|
||||
current: currentPage,
|
||||
pageSize: pageSize,
|
||||
total: total,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
showTotal: (total) => `共 ${total} 条`,
|
||||
onChange: handlePageChange,
|
||||
<div
|
||||
style={{
|
||||
background: 'linear-gradient(135deg, rgba(255,255,255,0.9) 0%, rgba(255,255,255,0.7) 100%)',
|
||||
backdropFilter: 'blur(20px)',
|
||||
borderRadius: 16,
|
||||
overflow: 'hidden',
|
||||
border: '1px solid rgba(99, 102, 241, 0.1)',
|
||||
boxShadow: '0 8px 32px rgba(99, 102, 241, 0.08)',
|
||||
}}
|
||||
style={{ fontSize: 13 }}
|
||||
/>
|
||||
>
|
||||
<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>
|
||||
</Modal>
|
||||
</React.Fragment>
|
||||
);
|
||||
|
||||
@@ -1,26 +1,116 @@
|
||||
*{
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.material_box>div{
|
||||
margin: 20px 10px;
|
||||
|
||||
.material_box {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.material_box>div {
|
||||
margin: 0 0 20px 0;
|
||||
}
|
||||
|
||||
.medio_box {
|
||||
width: 280px;
|
||||
height: 150px;
|
||||
margin-top: 6px;
|
||||
}
|
||||
.medio_box img ,.medio_box video {
|
||||
|
||||
.medio_box img,
|
||||
.medio_box video {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
|
||||
/* 加载旋转动画 */
|
||||
@keyframes spinSlow {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* 步骤状态动画 */
|
||||
@keyframes pulse {
|
||||
0%, 100% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
/* 折叠面板样式优化 */
|
||||
.ant-collapse-ghost > .ant-collapse-item {
|
||||
border-bottom: 1px solid rgba(99, 102, 241, 0.06);
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.ant-collapse-ghost > .ant-collapse-item:hover {
|
||||
background: rgba(99, 102, 241, 0.02);
|
||||
}
|
||||
|
||||
.ant-collapse-ghost > .ant-collapse-item > .ant-collapse-header {
|
||||
padding: 14px 16px !important;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
/* 步骤内容区滚动条 */
|
||||
.replication-form-content::-webkit-scrollbar,
|
||||
.replication-preview::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.replication-form-content::-webkit-scrollbar-thumb,
|
||||
.replication-preview::-webkit-scrollbar-thumb {
|
||||
background: rgba(99, 102, 241, 0.2);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.replication-form-content::-webkit-scrollbar-thumb:hover,
|
||||
.replication-preview::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(99, 102, 241, 0.4);
|
||||
}
|
||||
|
||||
.replication-form-content::-webkit-scrollbar-track,
|
||||
.replication-preview::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
/* 滑块样式 */
|
||||
input[type="range"] {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
input[type="range"]::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 50%;
|
||||
background: #6366f1;
|
||||
cursor: pointer;
|
||||
border: 2px solid #fff;
|
||||
box-shadow: 0 2px 6px rgba(99, 102, 241, 0.4);
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
input[type="range"]::-webkit-slider-thumb:hover {
|
||||
transform: scale(1.15);
|
||||
box-shadow: 0 3px 10px rgba(99, 102, 241, 0.6);
|
||||
}
|
||||
|
||||
input[type="range"]::-moz-range-thumb {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 50%;
|
||||
background: #6366f1;
|
||||
cursor: pointer;
|
||||
border: 2px solid #fff;
|
||||
box-shadow: 0 2px 6px rgba(99, 102, 241, 0.4);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user