merge main
This commit is contained in:
Vendored
+1
-1
File diff suppressed because one or more lines are too long
-548
File diff suppressed because one or more lines are too long
Vendored
+36
-36
@@ -1,37 +1,37 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&display=swap" rel="stylesheet" />
|
||||
<title>后台管理</title>
|
||||
<script>
|
||||
(function() {
|
||||
var cached = localStorage.getItem('siteInfo');
|
||||
if (cached) {
|
||||
try {
|
||||
var info = JSON.parse(cached);
|
||||
if (info.siteName) {
|
||||
document.title = info.siteName + ' - 管理后台';
|
||||
}
|
||||
if (info.siteLogo) {
|
||||
var link = document.querySelector('link[rel="icon"]');
|
||||
if (link) {
|
||||
link.href = info.siteLogo;
|
||||
link.type = 'image/png';
|
||||
}
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
<script type="module" crossorigin src="/assets/index-DfKYC1kL.js"></script>
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&display=swap" rel="stylesheet" />
|
||||
<title>后台管理</title>
|
||||
<script>
|
||||
(function() {
|
||||
var cached = localStorage.getItem('siteInfo');
|
||||
if (cached) {
|
||||
try {
|
||||
var info = JSON.parse(cached);
|
||||
if (info.siteName) {
|
||||
document.title = info.siteName + ' - 管理后台';
|
||||
}
|
||||
if (info.siteLogo) {
|
||||
var link = document.querySelector('link[rel="icon"]');
|
||||
if (link) {
|
||||
link.href = info.siteLogo;
|
||||
link.type = 'image/png';
|
||||
}
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
<script type="module" crossorigin src="/assets/index-BE_fa7kc.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-D7ShJUt4.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>␍
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -200,7 +200,7 @@ const AdminPlatform: React.FC = () => {
|
||||
if (!v) return '-';
|
||||
const baseUrl = import.meta.env.VITE_API_BASE || 'http://localhost:8000';
|
||||
const fullUrl = v.startsWith('http') ? v : `${baseUrl}${v}`;
|
||||
return <img src={fullUrl} alt="thumb" style={{ width: 80, height: 60, objectFit: 'cover' }} />;
|
||||
return <img src={fullUrl} alt="thumb" style={{ width: '100%', height: 48, display: 'block' }} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -382,7 +382,7 @@ const AdminPlatform: React.FC = () => {
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 12, padding: 16, background: '#f8fafc', borderRadius: 8 }}>
|
||||
<div style={{
|
||||
width: 80, height: 60, borderRadius: 6, overflow: 'hidden',
|
||||
width: 80, height: 48, borderRadius: 6, overflow: 'hidden',
|
||||
background: currentOpenType.thumb ? 'transparent' : '#e2e8f0',
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
}}>
|
||||
@@ -390,7 +390,7 @@ const AdminPlatform: React.FC = () => {
|
||||
<img
|
||||
src={currentOpenType.thumb.startsWith('http') ? currentOpenType.thumb : `${import.meta.env.VITE_API_BASE || 'http://localhost:8000'}${currentOpenType.thumb}`}
|
||||
alt="thumb"
|
||||
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
|
||||
style={{ width: '100%', height: '100%', display: 'block' }}
|
||||
/>
|
||||
) : (
|
||||
<Typography.Text type="secondary" style={{ fontSize: 12 }}>暂无图片</Typography.Text>
|
||||
|
||||
+118
-112
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -28,7 +28,7 @@
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
<script type="module" crossorigin src="/assets/index-Cr8hE_b3.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-DYahV0xa.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-Bi8mcSs8.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -130,5 +130,5 @@ export const api = {
|
||||
post: <T>(path: string, body?: unknown, auth = true, skipAuthRedirect = false) =>
|
||||
apiRequest<T>(path, { method: 'POST', body, auth, skipAuthRedirect }),
|
||||
put: <T>(path: string, body?: unknown, auth = true) => apiRequest<T>(path, { method: 'PUT', body, auth }),
|
||||
delete: <T>(path: string, auth = true) => apiRequest<T>(path, { method: 'DELETE', auth }),
|
||||
delete: <T>(path: string, body?: unknown, auth = true) => apiRequest<T>(path, { method: 'DELETE', body, auth }),
|
||||
};
|
||||
|
||||
@@ -720,4 +720,10 @@ export async function getHomeCaseHeader(): Promise<any> {
|
||||
// 首页素材按钮资源
|
||||
export async function getHomeCaseButton(id: string,limit:number=5): Promise<any> {
|
||||
return api.get(`/home-materials?category_id=${id}&limit_per_category=${limit}&include_empty_categories=false&response_mode=grouped&page=1&page_size=20`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
export async function deleteResourcesMaterial(params:any): Promise<any> {
|
||||
return api.delete(`/generation-ai/history/batch`, params);
|
||||
}
|
||||
|
||||
@@ -396,12 +396,12 @@ const AuthorizationPage: React.FC = () => {
|
||||
background: selectedOpenType === item.openType ? '#f0f1ff' : '#fff',
|
||||
}}
|
||||
>
|
||||
<div style={{ width: '100%', height: 120, marginBottom: 12, borderRadius: 8, overflow: 'hidden' }}>
|
||||
<div style={{ width: '100%', height: 48, marginBottom: 12, borderRadius: 8, overflow: 'hidden' }}>
|
||||
{item.thumb ? (
|
||||
<img
|
||||
src={buildUrl(item.thumb)}
|
||||
alt={item.typeName}
|
||||
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
|
||||
style={{ width: '100%', height: '100%', display: 'block' }}
|
||||
/>
|
||||
) : (
|
||||
<div style={{ width: '100%', height: '100%', background: '#f1f5f9', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||
|
||||
@@ -14,9 +14,9 @@ import {
|
||||
ReloadOutlined,
|
||||
StarOutlined,
|
||||
PlayCircleOutlined,
|
||||
|
||||
DeleteOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { gethistory, gethistoryItems, getOAuthList, asyncBatchUploadMaterial, updateFilename, getUploadHistory, getAllOAuthAccountList, getOpenTypeAll, getPreTestList, getDefaultPreTest } from '../api';
|
||||
import { gethistory, gethistoryItems, getOAuthList, asyncBatchUploadMaterial, updateFilename, getUploadHistory, getAllOAuthAccountList, getOpenTypeAll, getPreTestList, getDefaultPreTest, deleteHistory, deleteResourcesMaterial } from '../api';
|
||||
|
||||
const { Search } = Input;
|
||||
const { Text } = Typography;
|
||||
@@ -221,8 +221,8 @@ const GeneratedRecord: React.FC = () => {
|
||||
}
|
||||
|
||||
// 检查下载数量限制
|
||||
if (selectedItems.size > 10) {
|
||||
message.warning('最多只能单次下载10个文件');
|
||||
if (selectedItems.size > 30) {
|
||||
message.warning('最多只能单次下载30个文件');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -266,12 +266,12 @@ const GeneratedRecord: React.FC = () => {
|
||||
selectedContent.forEach((item, index) => {
|
||||
setTimeout(() => {
|
||||
const url = `${baseUrl}${item.videoUrl || item.imageUrl}&download=1`;
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = '';
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = '';
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
}, index * 500);
|
||||
});
|
||||
return;
|
||||
@@ -286,6 +286,115 @@ const GeneratedRecord: React.FC = () => {
|
||||
|
||||
message.destroy('downloadProgress');
|
||||
message.success(`下载完成,共 ${successCount} 个文件`);
|
||||
setIsSelectionMode(false);
|
||||
setSelectedItems(new Set());
|
||||
};
|
||||
const handleDeleteSelected = async () => {
|
||||
if (selectedItems.size === 0) {
|
||||
message.warning('请先选择要删除的媒体');
|
||||
return;
|
||||
}
|
||||
// 检查下载数量限制
|
||||
if (selectedItems.size > 30) {
|
||||
message.warning('最多只能单次删除30个文件');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const selectedData = recordlist.flatMap((group: any) =>
|
||||
group.items.filter((item: any) => selectedItems.has(getItemResourceId(item)))
|
||||
);
|
||||
|
||||
let confirmContent: React.ReactNode = '';
|
||||
if (filterType === 'shot_replicate') {
|
||||
confirmContent = (
|
||||
<div>
|
||||
<div style={{ marginBottom: 8 }}>以下内容所连带的图片和视频都将被删除:</div>
|
||||
<ul style={{ margin: 0, paddingLeft: 20 }}>
|
||||
{selectedData.map((item: any, index: number) => (
|
||||
<li key={index} style={{ marginBottom: 4, color: '#334155' }}>
|
||||
{item.shotReplicateProjectTitle}— —{item.shotSegmentLabel}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<div style={{ marginTop: 12 }}>确定删除吗?</div>
|
||||
</div>
|
||||
);
|
||||
} else if (filterType === 'hot_opening_replicate') {
|
||||
confirmContent = (
|
||||
<div>
|
||||
<div style={{ marginBottom: 8 }}>爆款开头复刻中以下内容所连带的图片和视频都将被删除:</div>
|
||||
<ul style={{ margin: 0, paddingLeft: 20 }}>
|
||||
{selectedData.map((item: any, index: number) => (
|
||||
<li key={index} style={{ marginBottom: 4, color: '#334155' }}>
|
||||
{item.moduleProjectTitle}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<div style={{ marginTop: 12 }}>确定删除吗?</div>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
confirmContent = `确定删除选中的 ${selectedItems.size} 条记录吗?`;
|
||||
}
|
||||
|
||||
Modal.confirm({
|
||||
title: '确认删除',
|
||||
content: confirmContent,
|
||||
okText: '确定删除',
|
||||
cancelText: '取消',
|
||||
okButtonProps: { danger: true },
|
||||
onOk: async () => {
|
||||
let historySource = '';
|
||||
if (filterType === 'project') {
|
||||
historySource = 'generation_record';
|
||||
} else if (filterType === 'creation') {
|
||||
historySource = 'chat_task';
|
||||
} else if (filterType === 'hot_opening_replicate') {
|
||||
historySource = 'hot_opening_replicate';
|
||||
} else if (filterType === 'shot_replicate') {
|
||||
historySource = 'shot_replicate';
|
||||
}
|
||||
|
||||
const selectedContent: any[] = [];
|
||||
recordlist.forEach((group: any) => {
|
||||
group.items.forEach((item: any) => {
|
||||
if (selectedItems.has(getItemResourceId(item))) {
|
||||
selectedContent.push(item);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
let idArr: any[] = [];
|
||||
if (historySource === 'generation_record') {
|
||||
idArr = selectedContent.map((item: any) => item.id);
|
||||
} else if (historySource === 'chat_task') {
|
||||
idArr = selectedContent.map((item: any) => item.id);
|
||||
} else if (historySource === 'hot_opening_replicate') {
|
||||
idArr = selectedContent.map((item: any) => item.moduleProjectId);
|
||||
} else if (historySource === 'shot_replicate') {
|
||||
idArr = selectedContent.map((item: any) => item.shotSegmentId);
|
||||
}
|
||||
|
||||
|
||||
|
||||
const params = {
|
||||
history_source: historySource,
|
||||
ids: idArr,
|
||||
};
|
||||
|
||||
try {
|
||||
await deleteResourcesMaterial(params);
|
||||
message.success('删除成功');
|
||||
setIsSelectionMode(false);
|
||||
setSelectedItems(new Set());
|
||||
setPagebreak(prev => ({ ...prev, page: 1 }));
|
||||
loadRecordList();
|
||||
} catch (error) {
|
||||
message.error('删除失败,请重试');
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
const loadPreTestTemplates = async () => {
|
||||
setPreTestTemplatesLoading(true);
|
||||
@@ -586,7 +695,6 @@ const GeneratedRecord: React.FC = () => {
|
||||
message.error(error.message || '文件名更新失败');
|
||||
}
|
||||
};
|
||||
// 日期选择器变化处理函数
|
||||
const handleDateChange = (dateString: string) => {
|
||||
setSelectedDate(dateString);
|
||||
};
|
||||
@@ -618,7 +726,7 @@ const GeneratedRecord: React.FC = () => {
|
||||
}, 5000);
|
||||
});
|
||||
};
|
||||
useEffect(() => {
|
||||
const loadRecordList = () => {
|
||||
setLoading(true);
|
||||
let historySource = '';
|
||||
if (filterType === 'project') {
|
||||
@@ -695,6 +803,9 @@ const GeneratedRecord: React.FC = () => {
|
||||
setLoading(false);
|
||||
});
|
||||
}
|
||||
};
|
||||
useEffect(() => {
|
||||
loadRecordList();
|
||||
}, [filterType, filterMedia, Pagebreak.page, selectedDate]);
|
||||
// 加载更多
|
||||
const handleLoadMore = () => {
|
||||
@@ -827,7 +938,7 @@ const GeneratedRecord: React.FC = () => {
|
||||
}}
|
||||
icon={<StarOutlined />}
|
||||
>
|
||||
爆款开头复刻
|
||||
爆款复刻
|
||||
</Button>
|
||||
<Button
|
||||
type={filterType === 'shot_replicate' ? 'primary' : 'default'}
|
||||
@@ -882,6 +993,12 @@ const GeneratedRecord: React.FC = () => {
|
||||
>
|
||||
取消选择
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => handleDeleteSelected()}
|
||||
style={{ borderRadius: 8, background: 'linear-gradient(135deg, #bf0b0a 0%, #ff8165 100%)', fontWeight: 600, padding: '8px 24px', color: '#fff' }}
|
||||
>
|
||||
删除 ({selectedItems.size})
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => handleDownloadSelected()}
|
||||
style={{
|
||||
@@ -1150,11 +1267,11 @@ const GeneratedRecord: React.FC = () => {
|
||||
padding: '4px 8px',
|
||||
backgroundColor: 'rgba(239, 68, 68, 0.9)',
|
||||
color: '#fff',
|
||||
fontSize: 10,
|
||||
fontSize: 12,
|
||||
borderRadius: 4,
|
||||
zIndex: 10,
|
||||
}}>
|
||||
视频实际尺寸可能不符合推送要求
|
||||
视频实际尺寸可能不符合平台推送要求
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
@@ -1417,7 +1534,7 @@ const GeneratedRecord: React.FC = () => {
|
||||
onCancel={handleClosePreview}
|
||||
width={1200}
|
||||
footer={
|
||||
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: 12, padding: '16px 24px', background: 'rgba(255,255,255,0.6)', borderTop: '1px solid rgba(99, 102, 241, 0.08)' }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: 12, padding: '16px 24px', background: '#fff', borderTop: '1px solid #f1f5f9' }}>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<DownloadOutlined />}
|
||||
@@ -1433,251 +1550,322 @@ const GeneratedRecord: React.FC = () => {
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
}}
|
||||
style={{ borderRadius: 8, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', border: 'none', boxShadow: '0 4px 12px rgba(99, 102, 241, 0.3)' }}
|
||||
style={{ borderRadius: 8, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', border: 'none', boxShadow: '0 4px 16px rgba(99, 102, 241, 0.35)', fontWeight: 600, padding: '8px 24px' }}
|
||||
disabled={isMediaExpired(previewItem.videoUrl || previewItem.imageUrl)}
|
||||
>
|
||||
{isMediaExpired(previewItem.videoUrl || previewItem.imageUrl) ? '资源已过期' : '下载'}
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<DeleteOutlined />}
|
||||
onClick={() => {
|
||||
let deleteId = '';
|
||||
let confirmContent: React.ReactNode = '';
|
||||
|
||||
if (filterType === 'shot_replicate') {
|
||||
deleteId = previewItem.shotSegmentId;
|
||||
confirmContent = (
|
||||
<div>
|
||||
<div style={{ marginBottom: 8 }}>以下内容所连带的图片和视频都将被删除:</div>
|
||||
<ul style={{ margin: 0, paddingLeft: 20 }}>
|
||||
<li style={{ marginBottom: 4, color: '#334155' }}>
|
||||
{previewItem.shotReplicateProjectTitle}— —{previewItem.shotSegmentLabel}
|
||||
</li>
|
||||
</ul>
|
||||
<div style={{ marginTop: 12 }}>确定删除吗?</div>
|
||||
</div>
|
||||
);
|
||||
} else if (filterType === 'hot_opening_replicate') {
|
||||
deleteId = previewItem.moduleProjectId;
|
||||
confirmContent = (
|
||||
<div>
|
||||
<div style={{ marginBottom: 8 }}>爆款开头复刻中以下内容所连带的图片和视频都将被删除:</div>
|
||||
<ul style={{ margin: 0, paddingLeft: 20 }}>
|
||||
<li style={{ marginBottom: 4, color: '#334155' }}>
|
||||
{previewItem.moduleProjectTitle}
|
||||
</li>
|
||||
</ul>
|
||||
<div style={{ marginTop: 12 }}>确定删除吗?</div>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
deleteId = previewItem.id;
|
||||
confirmContent = '确定删除该记录吗?';
|
||||
}
|
||||
|
||||
if (!deleteId) {
|
||||
message.warning('无法获取删除ID');
|
||||
return;
|
||||
}
|
||||
|
||||
Modal.confirm({
|
||||
title: '确认删除',
|
||||
content: confirmContent,
|
||||
okText: '确定删除',
|
||||
cancelText: '取消',
|
||||
okButtonProps: { danger: true },
|
||||
onOk: async () => {
|
||||
let historySource = '';
|
||||
if (filterType === 'project') {
|
||||
historySource = 'generation_record';
|
||||
} else if (filterType === 'creation') {
|
||||
historySource = 'chat_task';
|
||||
} else if (filterType === 'hot_opening_replicate') {
|
||||
historySource = 'hot_opening_replicate';
|
||||
} else if (filterType === 'shot_replicate') {
|
||||
historySource = 'shot_replicate';
|
||||
}
|
||||
|
||||
let params = {
|
||||
history_source: historySource,
|
||||
ids: [deleteId],
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
await deleteResourcesMaterial(params); // 调用删除接口
|
||||
message.success('删除成功');
|
||||
handleClosePreview();
|
||||
setPagebreak(prev => ({ ...prev, page: 1 }));
|
||||
loadRecordList();
|
||||
} catch (error) {
|
||||
message.error('删除失败,请重试');
|
||||
}
|
||||
},
|
||||
});
|
||||
}}
|
||||
style={{ borderRadius: 8, background: 'linear-gradient(135deg, #bf0b0a 0%, #ff8165 100%)', fontWeight: 600, padding: '8px 24px' }}
|
||||
disabled={isMediaExpired(previewItem.videoUrl || previewItem.imageUrl)}
|
||||
>
|
||||
删除
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleSinglePushToMedia}
|
||||
style={{ borderRadius: 8 }}
|
||||
style={{ borderRadius: 8, background: '#f8f9fc', border: '1px solid #e2e8f0', color: '#64748b', fontWeight: 600, padding: '8px 24px' }}
|
||||
disabled={isMediaExpired(previewItem.videoUrl || previewItem.imageUrl)}
|
||||
>
|
||||
推送媒体后台
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
style={{ borderRadius: 20 }}
|
||||
style={{ borderRadius: 16, overflow: 'hidden' }}
|
||||
styles={{
|
||||
body: { height: 460, display: 'flex', flexDirection: 'column', padding: 0 },
|
||||
header: { background: 'rgba(255,255,255,0.6)', backdropFilter: 'blur(10px)', borderBottom: '1px solid rgba(99, 102, 241, 0.08)', padding: '16px 24px' },
|
||||
body: { height: 500, display: 'flex', flexDirection: 'column', padding: 0 },
|
||||
header: { background: '#fff', borderBottom: '1px solid #f1f5f9', padding: '16px 24px' },
|
||||
mask: { backgroundColor: 'rgba(0, 0, 0, 0.5)' },
|
||||
}}
|
||||
>
|
||||
{/* 内容区域 */}
|
||||
<div style={{
|
||||
flex: 1,
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
height: '460px',
|
||||
gap: 20,
|
||||
padding: 20,
|
||||
overflow: 'auto',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
height: '100%',
|
||||
background: '#f8fafc',
|
||||
}}>
|
||||
{/* 媒体预览 */}
|
||||
<div style={{
|
||||
flex: 1,
|
||||
minWidth: '280px',
|
||||
maxWidth: '800px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
minHeight: '200px',
|
||||
}}>
|
||||
{/* 检查媒体是否过期 */}
|
||||
{isMediaExpired(previewItem.videoUrl || previewItem.imageUrl) ? (
|
||||
<div style={{ textAlign: 'center', padding: '40px' }}>
|
||||
<div style={{ fontSize: 48, marginBottom: 16 }}>⚠️</div>
|
||||
<p style={{ fontSize: 16, color: '#ff4d4f', marginBottom: 16 }}>图片/视频资源已过期,请刷新重新加载~</p>
|
||||
</div>
|
||||
) : filterMedia === 'video' ? (
|
||||
<video
|
||||
ref={videoRef}
|
||||
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${previewItem.videoUrl}`}
|
||||
controls
|
||||
autoPlay
|
||||
style={{
|
||||
maxWidth: '100%',
|
||||
maxHeight: '55vh',
|
||||
borderRadius: 8,
|
||||
objectFit: 'contain',
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<img
|
||||
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}/static${previewItem.imageUrl}&w=300&q=50`}
|
||||
alt="预览"
|
||||
style={{
|
||||
maxWidth: '100%',
|
||||
maxHeight: '55vh',
|
||||
objectFit: 'contain',
|
||||
borderRadius: 8
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div style={{
|
||||
width: '70%',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
|
||||
{/* 参数信息 */}
|
||||
<div style={{
|
||||
width: '100%',
|
||||
minWidth: '280px',
|
||||
maxWidth: '320px',
|
||||
background: '#f8fafc',
|
||||
borderRadius: 12,
|
||||
padding: 20,
|
||||
maxHeight: '55vh',
|
||||
overflowY: 'auto',
|
||||
overflowX: 'hidden',
|
||||
}}>
|
||||
<Typography.Text strong style={{ fontSize: 14, color: '#475569', display: 'block', marginBottom: 16 }}>
|
||||
padding: 20,
|
||||
}}>
|
||||
{isMediaExpired(previewItem.videoUrl || previewItem.imageUrl) ? (
|
||||
<div style={{ textAlign: 'center', padding: '40px', backgroundColor: '#fff', borderRadius: 12 }}>
|
||||
<div style={{ fontSize: 48, marginBottom: 16 }}>⚠️</div>
|
||||
<p style={{ fontSize: 15, color: '#ef4444', fontWeight: 500, marginBottom: 8 }}>资源已过期</p>
|
||||
<p style={{ fontSize: 13, color: '#94a3b8' }}>请刷新页面重新加载</p>
|
||||
</div>
|
||||
) : filterMedia === 'video' ? (
|
||||
<video
|
||||
ref={videoRef}
|
||||
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${previewItem.videoUrl}`}
|
||||
controls
|
||||
autoPlay
|
||||
style={{
|
||||
maxWidth: '100%',
|
||||
maxHeight: '100%',
|
||||
borderRadius: 12,
|
||||
objectFit: 'contain',
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<img
|
||||
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}/static${previewItem.imageUrl}&w=300&q=50`}
|
||||
alt="预览"
|
||||
style={{
|
||||
maxWidth: '100%',
|
||||
maxHeight: '100%',
|
||||
objectFit: 'contain',
|
||||
borderRadius: 12,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div style={{
|
||||
width: '30%',
|
||||
height: '100%',
|
||||
overflowY: 'auto',
|
||||
scrollbarWidth: 'none',
|
||||
backgroundColor: '#fff',
|
||||
padding: 24,
|
||||
borderLeft: '1px solid #f1f5f9',
|
||||
}}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 20 }}>
|
||||
<div style={{ width: 2, height: 18, background: 'linear-gradient(180deg, #6366f1 0%, #8b5cf6 100%)', borderRadius: 1 }} />
|
||||
<Typography.Text strong style={{ fontSize: 15, color: '#1e293b', fontWeight: 600 }}>
|
||||
文件信息
|
||||
</Typography.Text>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
|
||||
{/* ID */}
|
||||
{/* <div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||
<span style={{ color: '#94a3b8', fontSize: 13 }}>ID</span>
|
||||
<span style={{ color: '#334155', fontSize: 13, fontWeight: 500 }}>
|
||||
{previewItem.id || '-'}
|
||||
</span>
|
||||
</div> */}
|
||||
|
||||
{/* 类型 */}
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||
<span style={{ color: '#94a3b8', fontSize: 13 }}>类型</span>
|
||||
<span style={{ color: '#334155', fontSize: 13, fontWeight: 500 }}>
|
||||
{filterMedia === 'video' ? '视频' : '图片'}
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<span style={{ color: '#94a3b8', fontSize: 13, fontWeight: 500 }}>类型</span>
|
||||
<span style={{ color: '#334155', fontSize: 13, fontWeight: 600 }}>
|
||||
{filterMedia === 'video' ? '视频' : '图片'}
|
||||
</span>
|
||||
</div>
|
||||
{filterType === 'hot_opening_replicate' && (
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', gap: 4 }}>
|
||||
<span style={{ color: '#94a3b8', fontSize: 13, fontWeight: 500 }}>类属</span>
|
||||
<span style={{ color: '#334155', fontSize: 13, fontWeight: 500, wordBreak: 'break-all', lineHeight: '1.5' }}>
|
||||
爆款复刻——{previewItem.moduleProjectTitle}
|
||||
</span>
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: 12 }}>
|
||||
<span style={{ color: '#94a3b8', fontSize: 13, flexShrink: 0, width: 40 }}>请求</span>
|
||||
<span style={{ color: '#334155', fontSize: 13, fontWeight: 500, flex: 1, wordBreak: 'break-all' }}>
|
||||
{previewItem.originalPrompt}
|
||||
)}
|
||||
{filterType === 'shot_replicate' && (
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', gap: 4 }}>
|
||||
<span style={{ color: '#94a3b8', fontSize: 13, fontWeight: 500 }}>类属</span>
|
||||
<span style={{ color: '#334155', fontSize: 13, fontWeight: 500, wordBreak: 'break-all', lineHeight: '1.5' }}>
|
||||
{previewItem.shotReplicateProjectTitle}— —{previewItem.shotSegmentLabel}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 分辨率 */}
|
||||
{filterMedia === 'image' && (
|
||||
<>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||
<span style={{ color: '#94a3b8', fontSize: 13 }}>比例</span>
|
||||
<span style={{ color: '#334155', fontSize: 13, fontWeight: 500 }}>
|
||||
{previewItem.imageProportion}
|
||||
</span>
|
||||
</div>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||
<span style={{ color: '#94a3b8', fontSize: 13 }}>分辨率</span>
|
||||
<span style={{ color: '#334155', fontSize: 13, fontWeight: 500 }}>
|
||||
{previewItem.imageSize}
|
||||
</span>
|
||||
</div>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||
<span style={{ color: '#94a3b8', fontSize: 13 }}>尺寸</span>
|
||||
<span style={{ color: '#334155', fontSize: 13, fontWeight: 500 }}>
|
||||
{previewItem.imagePx}
|
||||
</span>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{filterMedia === 'video' && (
|
||||
<>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||
<span style={{ color: '#94a3b8', fontSize: 13 }}>比例</span>
|
||||
<span style={{ color: '#334155', fontSize: 13, fontWeight: 500 }}>
|
||||
{previewItem.aspectRatio}
|
||||
</span>
|
||||
</div>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||
<span style={{ color: '#94a3b8', fontSize: 13 }}>分辨率</span>
|
||||
<span style={{ color: '#334155', fontSize: 13, fontWeight: 500 }}>
|
||||
{previewItem.resolution}
|
||||
</span>
|
||||
</div>
|
||||
{/* <div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||
<span style={{ color: '#94a3b8', fontSize: 13 }}>尺寸</span>
|
||||
<span style={{ color: '#334155', fontSize: 13, fontWeight: 500 }}>
|
||||
{previewItem.imagePx}
|
||||
</span>
|
||||
</div> */}
|
||||
</>
|
||||
)}
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
|
||||
<span style={{ color: '#94a3b8', fontSize: 13, fontWeight: 500 }}>请求</span>
|
||||
<span style={{ color: '#334155', fontSize: 13, fontWeight: 500, wordBreak: 'break-all', lineHeight: '1.5' }}>
|
||||
{previewItem.originalPrompt}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* 时长(视频) */}
|
||||
{filterMedia === 'video' && (
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||
<span style={{ color: '#94a3b8', fontSize: 13 }}>时长</span>
|
||||
<span style={{ color: '#334155', fontSize: 13, fontWeight: 500 }}>
|
||||
{filterMedia === 'image' && (
|
||||
<>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<span style={{ color: '#94a3b8', fontSize: 13, fontWeight: 500 }}>比例</span>
|
||||
<span style={{ color: '#334155', fontSize: 13, fontWeight: 600 }}>
|
||||
{previewItem.imageProportion}
|
||||
</span>
|
||||
</div>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<span style={{ color: '#94a3b8', fontSize: 13, fontWeight: 500 }}>分辨率</span>
|
||||
<span style={{ color: '#334155', fontSize: 13, fontWeight: 600 }}>
|
||||
{previewItem.imageSize}
|
||||
</span>
|
||||
</div>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<span style={{ color: '#94a3b8', fontSize: 13, fontWeight: 500 }}>尺寸</span>
|
||||
<span style={{ color: '#334155', fontSize: 13, fontWeight: 600 }}>
|
||||
{previewItem.imagePx}
|
||||
</span>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{filterMedia === 'video' && (
|
||||
<>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<span style={{ color: '#94a3b8', fontSize: 13, fontWeight: 500 }}>比例</span>
|
||||
<span style={{ color: '#334155', fontSize: 13, fontWeight: 600 }}>
|
||||
{previewItem.aspectRatio}
|
||||
</span>
|
||||
</div>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<span style={{ color: '#94a3b8', fontSize: 13, fontWeight: 500 }}>分辨率</span>
|
||||
<span style={{ color: '#334155', fontSize: 13, fontWeight: 600 }}>
|
||||
{previewItem.resolution}
|
||||
</span>
|
||||
</div>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<span style={{ color: '#94a3b8', fontSize: 13, fontWeight: 500 }}>时长</span>
|
||||
<span style={{ color: '#334155', fontSize: 13, fontWeight: 600 }}>
|
||||
{`${previewItem.duration}秒` || '-'}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 文件大小 */}
|
||||
{/* <div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||
<span style={{ color: '#94a3b8', fontSize: 13 }}>文件大小</span>
|
||||
<span style={{ color: '#334155', fontSize: 13, fontWeight: 500 }}>
|
||||
{previewItem.size ? formatFileSize(previewItem.size) : '-'}
|
||||
</span>
|
||||
</div> */}
|
||||
|
||||
{/* 创建时间 */}
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||
<span style={{ color: '#94a3b8', fontSize: 13 }}>创建时间</span>
|
||||
<span style={{ color: '#334155', fontSize: 13, fontWeight: 500 }}>
|
||||
{formatDateTime(previewItem.createdAt || previewItem.generatedDate)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* 生成引擎 */}
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||
<span style={{ color: '#94a3b8', fontSize: 13 }}>生成引擎</span>
|
||||
<span style={{ color: '#334155', fontSize: 13, fontWeight: 500 }}>
|
||||
{previewItem.engine || previewItem.engineName || '-'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 分隔线 */}
|
||||
<div style={{ borderTop: '1px dashed #e2e8f0', margin: '16px 0' }} />
|
||||
|
||||
|
||||
{previewItem.mediaReferences && previewItem.mediaReferences.length > 0 && (
|
||||
<>
|
||||
<Typography.Text strong style={{ fontSize: 14, color: '#475569', display: 'block', marginBottom: 12 }}>
|
||||
依靠附件
|
||||
</Typography.Text>
|
||||
{previewItem.mediaReferences.map((item, index) => (
|
||||
<div
|
||||
key={item.url}
|
||||
onClick={() => {
|
||||
// 暂停视频
|
||||
if (videoRef.current) {
|
||||
videoRef.current.pause();
|
||||
}
|
||||
const url = `${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${item.url}`;
|
||||
window.open(url, '_blank');
|
||||
}}
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 8,
|
||||
padding: 8,
|
||||
borderRadius: 6,
|
||||
cursor: 'pointer',
|
||||
backgroundColor: '#f1f5f9',
|
||||
marginBottom: 4,
|
||||
transition: 'background-color 0.2s',
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
(e.currentTarget as HTMLElement).style.backgroundColor = '#e2e8f0';
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
(e.currentTarget as HTMLElement).style.backgroundColor = '#f1f5f9';
|
||||
}}
|
||||
>
|
||||
{item.type === 'image' ? (
|
||||
<PictureOutlined style={{ color: '#3b82f6', fontSize: 14 }} />
|
||||
) : (
|
||||
<VideoCameraOutlined style={{ color: '#f59e0b', fontSize: 14 }} />
|
||||
)}
|
||||
<span style={{ color: '#334155', fontSize: 13 }}>
|
||||
{item.name || `媒体${index + 1}`}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<span style={{ color: '#94a3b8', fontSize: 13, fontWeight: 500 }}>创建时间</span>
|
||||
<span style={{ color: '#334155', fontSize: 13, fontWeight: 600 }}>
|
||||
{formatDateTime(previewItem.createdAt || previewItem.generatedDate)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<span style={{ color: '#94a3b8', fontSize: 13, fontWeight: 500 }}>生成引擎</span>
|
||||
<span style={{ color: '#334155', fontSize: 13, fontWeight: 600 }}>
|
||||
{previewItem.engine || previewItem.engineName || '-'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ borderTop: '1px solid #f1f5f9', margin: '20px 0' }} />
|
||||
|
||||
{previewItem.mediaReferences && previewItem.mediaReferences.length > 0 && !['hot_opening_replicate', 'shot_replicate'].includes(filterType) && (
|
||||
<>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 16 }}>
|
||||
<div style={{ width: 2, height: 18, background: 'linear-gradient(180deg, #3b82f6 0%, #60a5fa 100%)', borderRadius: 1 }} />
|
||||
<Typography.Text strong style={{ fontSize: 15, color: '#1e293b', fontWeight: 600 }}>
|
||||
依靠附件
|
||||
</Typography.Text>
|
||||
</div>
|
||||
{previewItem.mediaReferences.map((item, index) => (
|
||||
<div
|
||||
key={item.url}
|
||||
onClick={() => {
|
||||
if (videoRef.current) {
|
||||
videoRef.current.pause();
|
||||
}
|
||||
const url = `${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${item.url}`;
|
||||
window.open(url, '_blank');
|
||||
}}
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 10,
|
||||
padding: '10px 12px',
|
||||
borderRadius: 8,
|
||||
cursor: 'pointer',
|
||||
backgroundColor: '#f8fafc',
|
||||
marginBottom: 8,
|
||||
transition: 'all 0.25s ease',
|
||||
border: '1px solid transparent',
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
const el = e.currentTarget as HTMLElement;
|
||||
el.style.backgroundColor = '#e0f2fe';
|
||||
el.style.borderColor = '#bae6fd';
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
const el = e.currentTarget as HTMLElement;
|
||||
el.style.backgroundColor = '#f8fafc';
|
||||
el.style.borderColor = 'transparent';
|
||||
}}
|
||||
>
|
||||
{item.type === 'image' ? (
|
||||
<PictureOutlined style={{ color: '#3b82f6', fontSize: 16 }} />
|
||||
) : (
|
||||
<VideoCameraOutlined style={{ color: '#f59e0b', fontSize: 16 }} />
|
||||
)}
|
||||
<span style={{ color: '#334155', fontSize: 13, fontWeight: 500 }}>
|
||||
{item.name || `媒体${index + 1}`}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
@@ -1826,7 +2014,7 @@ const GeneratedRecord: React.FC = () => {
|
||||
})()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div style={{ display: 'flex', gap: 16, marginBottom: 16 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||
<Typography.Text style={{ fontSize: 14, color: '#475569' }}>是否开启前测:</Typography.Text>
|
||||
@@ -2006,7 +2194,7 @@ const GeneratedRecord: React.FC = () => {
|
||||
onClick: () => {
|
||||
const id = String(record.id);
|
||||
const newOauthItems = [...selectedOauthItems];
|
||||
newOauthItems[index] = { value: id, label: String(record.accountId)+'-'+(record.accountName || '-') };
|
||||
newOauthItems[index] = { value: id, label: String(record.accountId) + '-' + (record.accountName || '-') };
|
||||
setSelectedOauthItems(newOauthItems);
|
||||
const newOauthSelectOpens = [...oauthSelectOpens];
|
||||
newOauthSelectOpens[index] = false;
|
||||
@@ -2101,14 +2289,14 @@ const GeneratedRecord: React.FC = () => {
|
||||
.filter(item => item.advertiserId)
|
||||
.map(item => String(item.advertiserId))}
|
||||
onChange={(targetKeys) => {
|
||||
const newSelected = historyOAuthList.filter(item =>
|
||||
const newSelected = historyOAuthList.filter(item =>
|
||||
item.advertiserId && targetKeys.includes(String(item.advertiserId))
|
||||
);
|
||||
setSelectedHistoryAccounts(newSelected);
|
||||
}}
|
||||
titles={['可选账户列表', '已选账户列表']}
|
||||
showSearch
|
||||
filterOption={(inputValue, item) =>
|
||||
filterOption={(inputValue, item) =>
|
||||
String(item.advertiserId).toLowerCase().includes(inputValue.toLowerCase()) ||
|
||||
(item.advertiserName && item.advertiserName.toLowerCase().includes(inputValue.toLowerCase()))
|
||||
}
|
||||
|
||||
@@ -98,7 +98,7 @@ const HomePage: React.FC = () => {
|
||||
const aiEntries = [
|
||||
{
|
||||
icon: <FileTextOutlined style={{ fontSize: 24, color: '#6366f1' }} />,
|
||||
title: '爆款开头复刻',
|
||||
title: '爆款复刻',
|
||||
description: '上传参考视频与产品图片,一键复刻爆款视频开头',
|
||||
action: '立即创作',
|
||||
path: '/initial',
|
||||
@@ -106,7 +106,7 @@ const HomePage: React.FC = () => {
|
||||
{
|
||||
icon: <ScissorOutlined style={{ fontSize: 24, color: '#f97316' }} />,
|
||||
title: '拆镜复刻',
|
||||
description: '一键拆解画面分镜,助力仿拍或创作',
|
||||
description: '精细化镜头复刻工具,拆分参考视频单镜头独立复刻,提升素材原创度,规避素材同质化',
|
||||
action: '开始混剪',
|
||||
path: '/removelens',
|
||||
},
|
||||
@@ -143,7 +143,7 @@ const HomePage: React.FC = () => {
|
||||
return (
|
||||
<div className="content_box">
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16 }}>
|
||||
<h3>首页工作台</h3>
|
||||
<h3>首页工作台 </h3>
|
||||
<p
|
||||
style={{
|
||||
cursor: 'pointer',
|
||||
@@ -190,7 +190,7 @@ const HomePage: React.FC = () => {
|
||||
background: 'linear-gradient(180deg, #6366f1, #a855f7)',
|
||||
}} />
|
||||
<div style={{ fontSize: 17, fontWeight: 700, color: '#1f2937', letterSpacing: 0.3 }}>
|
||||
我的项目
|
||||
我的项目<span style={{ color: '#a8a8a8ff', fontSize: 12 }}>(操作步骤)</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -473,7 +473,7 @@ function InitialInfo() {
|
||||
<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>
|
||||
<div style={{ width: 32, height: 2, background: 'linear-gradient(90deg, transparent, #8b5cf6, #6366f1, transparent)', borderRadius: 1 }} />
|
||||
|
||||
|
||||
@@ -450,7 +450,7 @@ const GenerateConver: React.FC = () => {
|
||||
position: 'relative', overflow: 'hidden', flexWrap: 'wrap', gap: 12,
|
||||
}}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 16 }}>
|
||||
<div style={{ width: 32, height: 2, background: 'linear-gradient(90deg, transparent, #6366f1, #8b5cf6, transparent)', borderRadius: 1 }} />
|
||||
{/* <div style={{ width: 32, height: 2, background: 'linear-gradient(90deg, transparent, #6366f1, #8b5cf6, transparent)', borderRadius: 1 }} /> */}
|
||||
<div>
|
||||
<h2 style={{
|
||||
margin: 0,
|
||||
@@ -460,15 +460,15 @@ const GenerateConver: React.FC = () => {
|
||||
WebkitBackgroundClip: 'text',
|
||||
WebkitTextFillColor: 'transparent',
|
||||
backgroundClip: 'text',
|
||||
textAlign: 'center',
|
||||
// textAlign: 'center',
|
||||
}}>
|
||||
爆款开头复刻
|
||||
爆款复刻
|
||||
</h2>
|
||||
<p style={{ fontSize: 13, color: '#64748b', margin: '4px 0 0 0' }}>
|
||||
上传参考视频与产品图片,一键复刻爆款视频开头
|
||||
</p>
|
||||
</div>
|
||||
<div style={{ width: 32, height: 2, background: 'linear-gradient(90deg, transparent, #8b5cf6, #6366f1, transparent)', borderRadius: 1 }} />
|
||||
{/* <div style={{ width: 32, height: 2, background: 'linear-gradient(90deg, transparent, #8b5cf6, #6366f1, transparent)', borderRadius: 1 }} /> */}
|
||||
</div>
|
||||
<Button
|
||||
type="primary"
|
||||
@@ -796,7 +796,7 @@ const GenerateConver: React.FC = () => {
|
||||
暂无生成内容
|
||||
</h3>
|
||||
<p style={{ margin: 0, fontSize: 13, color: '#64748b', textAlign: 'center', padding: '0 40px', lineHeight: 1.6 }}>
|
||||
请完善素材与卖点,生成专属爆款开头复刻视频
|
||||
请完善素材与卖点,生成专属爆款复刻视频
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
+258
-246
@@ -1,5 +1,5 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Table, Tag, Button, Typography, Space, Modal, Form, Input, Select, Cascader, App, Pagination } from 'antd';
|
||||
import { Table, Tag, Button, Typography, Space, Modal, Form, Input, Select, Cascader, App, Pagination, Collapse } from 'antd';
|
||||
import { PlusOutlined, EditOutlined, FileTextOutlined, DeleteOutlined, EyeOutlined } from '@ant-design/icons';
|
||||
import { getPreTestList, createPreTest, getArea, getPreTestDetail, updatePreTest, deletePreTest } from '../api';
|
||||
|
||||
@@ -143,6 +143,7 @@ const PreTest: React.FC = () => {
|
||||
const [modalMode, setModalMode] = useState<'create' | 'edit' | 'detail'>('create');
|
||||
const [currentRecord, setCurrentRecord] = useState<PreTestRecord | null>(null);
|
||||
const [form] = Form.useForm();
|
||||
const [expandOptional, setExpandOptional] = useState(false);
|
||||
const [submitLoading, setSubmitLoading] = useState(false);
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [pageSize, setPageSize] = useState(10);
|
||||
@@ -608,7 +609,7 @@ const PreTest: React.FC = () => {
|
||||
<div style={{ minHeight: 'calc(100vh - 80px)' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 12 }}>
|
||||
<FileTextOutlined style={{ color: '#6366f1', fontSize: 16 }} />
|
||||
<Typography.Text strong style={{ fontSize: 16 }}>素材前测</Typography.Text>
|
||||
<Typography.Text strong style={{ fontSize: 16 }}>素材前测模板</Typography.Text>
|
||||
</div>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 }}>
|
||||
<div style={{ display: 'flex', gap: 16, alignItems: 'center' }}>
|
||||
@@ -646,7 +647,7 @@ const PreTest: React.FC = () => {
|
||||
boxShadow: '0 4px 14px rgba(99,102,241,0.3)',
|
||||
}}
|
||||
>
|
||||
新增前测
|
||||
新增模板
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -692,7 +693,7 @@ const PreTest: React.FC = () => {
|
||||
</div>
|
||||
|
||||
<Modal
|
||||
title={<Space>{modalMode === 'create' ? '新增前测模板' : modalMode === 'edit' ? '编辑前测模板' : '前测模板详情'}</Space>}
|
||||
title={<Space>{modalMode === 'create' ? '新增前测模板(选填项如填写需准确填写)' : modalMode === 'edit' ? '编辑前测模板(选填项如填写需准确填写)' : '前测模板详情'}</Space>}
|
||||
open={modalOpen}
|
||||
onCancel={handleCloseModal}
|
||||
onOk={handleSubmit}
|
||||
@@ -743,257 +744,268 @@ const PreTest: React.FC = () => {
|
||||
/>
|
||||
</Form.Item>
|
||||
</div>
|
||||
{platform !== 'LOCAL' && (
|
||||
<Form.Item
|
||||
name="external_action"
|
||||
label="转化目标"
|
||||
>
|
||||
<Select
|
||||
placeholder="请选择转化目标"
|
||||
style={{ borderRadius: 8, height: 40 }}
|
||||
options={PRETEST_FIELDS.filter(field => {
|
||||
if (platform === 'AD') {
|
||||
return field.name.startsWith('AD_');
|
||||
} else if (platform === 'QIANCHUAN') {
|
||||
return field.name.startsWith('QC_');
|
||||
} else if (platform === 'LOCAL') {
|
||||
return field.name.startsWith('LOCAL_');
|
||||
}
|
||||
return true;
|
||||
}).map(field => ({
|
||||
value: field.name,
|
||||
label: (
|
||||
<span>
|
||||
{field.label}
|
||||
{field.description && (
|
||||
<Typography.Text style={{ color: '#94a3b8', fontSize: 12, marginLeft: 4 }}>
|
||||
({field.description})
|
||||
</Typography.Text>
|
||||
)}
|
||||
</span>
|
||||
),
|
||||
}))}
|
||||
/>
|
||||
</Form.Item>
|
||||
)}
|
||||
|
||||
<div style={{ display: 'flex', gap: 16 }}>
|
||||
<Form.Item
|
||||
style={{ flex: 1 }}
|
||||
name="cpa_bid"
|
||||
label="目标转化成本"
|
||||
rules={[
|
||||
{ type: 'number', min: 1, max: 10000, message: '取值范围: 1-10000' },
|
||||
]}
|
||||
getValueFromEvent={(e) => parseFloat(e.target.value) || undefined}
|
||||
>
|
||||
<Input
|
||||
type="number"
|
||||
placeholder="取值范围: 1-10000,精确到小数点后2位"
|
||||
style={{ borderRadius: 8, height: 40 }}
|
||||
min={1}
|
||||
max={10000}
|
||||
step={0.01}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
style={{ flex: 1 }}
|
||||
name="audience_gender"
|
||||
label="受众性别"
|
||||
>
|
||||
<Select
|
||||
placeholder="请选择受众性别"
|
||||
style={{ borderRadius: 8, height: 40 }}
|
||||
options={[
|
||||
{ value: 'ALL', label: '不限' },
|
||||
{ value: 'MALE', label: '男' },
|
||||
{ value: 'FEMALE', label: '女' },
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: 16 }}>
|
||||
<Form.Item
|
||||
style={{ flex: 1 }}
|
||||
name="audience_age"
|
||||
label="受众年龄"
|
||||
>
|
||||
<Select
|
||||
mode="multiple"
|
||||
placeholder="请选择受众年龄(可多选)"
|
||||
style={{ borderRadius: 8, minHeight: 40 }}
|
||||
maxTagCount="responsive"
|
||||
options={[
|
||||
{ value: 'ALL', label: '不限' },
|
||||
{ value: '18-23', label: '18-23岁' },
|
||||
{ value: '24-30', label: '24-30岁' },
|
||||
{ value: '31-40', label: '31-40岁' },
|
||||
{ value: '41-49', label: '41-49岁' },
|
||||
{ value: '50', label: '50岁以上' },
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
style={{ flex: 1 }}
|
||||
name="audience_network"
|
||||
label="网络类型"
|
||||
>
|
||||
<Select
|
||||
mode="multiple"
|
||||
placeholder="请选择网络类型(可多选)"
|
||||
style={{ borderRadius: 8, minHeight: 40 }}
|
||||
maxTagCount="responsive"
|
||||
options={[
|
||||
{ value: 'ALL', label: '不限' },
|
||||
{ value: '5G', label: '5G' },
|
||||
{ value: '4G', label: '4G' },
|
||||
{ value: '3G', label: '3G' },
|
||||
{ value: '2G', label: '2G' },
|
||||
{ value: 'WIFI', label: 'wifi' },
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
</div>
|
||||
|
||||
<Form.Item
|
||||
name="audience_region"
|
||||
label="受众地区"
|
||||
<Collapse
|
||||
defaultActiveKey={[]}
|
||||
activeKey={expandOptional ? ['optional'] : []}
|
||||
onChange={() => setExpandOptional(!expandOptional)}
|
||||
>
|
||||
<Cascader
|
||||
multiple
|
||||
placeholder="请选择受众地区(可多选)"
|
||||
style={{ borderRadius: 8, minHeight: 40 }}
|
||||
options={areaOptions}
|
||||
maxTagCount="responsive"
|
||||
/>
|
||||
</Form.Item>
|
||||
<div style={{ display: 'flex', gap: 16 }}>
|
||||
<Form.Item
|
||||
style={{ flex: 1 }}
|
||||
name="cus_name"
|
||||
label="客户主体名称"
|
||||
>
|
||||
<Input
|
||||
placeholder="请输入客户主体名称"
|
||||
style={{ borderRadius: 8, height: 40 }}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
style={{ flex: 1 }}
|
||||
name="pricing_type"
|
||||
label="出价类型"
|
||||
>
|
||||
<Select
|
||||
placeholder="请选择出价类型"
|
||||
style={{ borderRadius: 8, height: 40 }}
|
||||
options={[
|
||||
{ value: 'OCPC', label: 'OCPC' },
|
||||
{ value: 'CPA', label: 'CPA' },
|
||||
{ value: 'OCPM', label: 'OCPM' },
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
</div>
|
||||
{platform == 'AD' && (
|
||||
<div style={{ display: 'flex', gap: 16 }}>
|
||||
<Collapse.Panel header="选填项" key="optional">
|
||||
{platform !== 'LOCAL' && (
|
||||
<Form.Item
|
||||
name="external_action"
|
||||
label="转化目标"
|
||||
>
|
||||
<Select
|
||||
placeholder="请选择转化目标"
|
||||
style={{ borderRadius: 8, height: 40 }}
|
||||
options={PRETEST_FIELDS.filter(field => {
|
||||
if (platform === 'AD') {
|
||||
return field.name.startsWith('AD_');
|
||||
} else if (platform === 'QIANCHUAN') {
|
||||
return field.name.startsWith('QC_');
|
||||
} else if (platform === 'LOCAL') {
|
||||
return field.name.startsWith('LOCAL_');
|
||||
}
|
||||
return true;
|
||||
}).map(field => ({
|
||||
value: field.name,
|
||||
label: (
|
||||
<span>
|
||||
{field.label}
|
||||
{field.description && (
|
||||
<Typography.Text style={{ color: '#94a3b8', fontSize: 12, marginLeft: 4 }}>
|
||||
({field.description})
|
||||
</Typography.Text>
|
||||
)}
|
||||
</span>
|
||||
),
|
||||
}))}
|
||||
/>
|
||||
</Form.Item>
|
||||
)}
|
||||
|
||||
<div style={{ display: 'flex', gap: 16 }}>
|
||||
<Form.Item
|
||||
style={{ flex: 1 }}
|
||||
name="cpa_bid"
|
||||
label="目标转化成本"
|
||||
rules={[
|
||||
{ type: 'number', min: 1, max: 10000, message: '取值范围: 1-10000' },
|
||||
]}
|
||||
getValueFromEvent={(e) => parseFloat(e.target.value) || undefined}
|
||||
>
|
||||
<Input
|
||||
type="number"
|
||||
placeholder="取值范围: 1-10000,精确到小数点后2位"
|
||||
style={{ borderRadius: 8, height: 40 }}
|
||||
min={1}
|
||||
max={10000}
|
||||
step={0.01}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
style={{ flex: 1 }}
|
||||
name="audience_gender"
|
||||
label="受众性别"
|
||||
>
|
||||
<Select
|
||||
placeholder="请选择受众性别"
|
||||
style={{ borderRadius: 8, height: 40 }}
|
||||
options={[
|
||||
{ value: 'ALL', label: '不限' },
|
||||
{ value: 'MALE', label: '男' },
|
||||
{ value: 'FEMALE', label: '女' },
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: 16 }}>
|
||||
<Form.Item
|
||||
style={{ flex: 1 }}
|
||||
name="audience_age"
|
||||
label="受众年龄"
|
||||
>
|
||||
<Select
|
||||
mode="multiple"
|
||||
placeholder="请选择受众年龄(可多选)"
|
||||
style={{ borderRadius: 8, minHeight: 40 }}
|
||||
maxTagCount="responsive"
|
||||
options={[
|
||||
{ value: 'ALL', label: '不限' },
|
||||
{ value: '18-23', label: '18-23岁' },
|
||||
{ value: '24-30', label: '24-30岁' },
|
||||
{ value: '31-40', label: '31-40岁' },
|
||||
{ value: '41-49', label: '41-49岁' },
|
||||
{ value: '50', label: '50岁以上' },
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
style={{ flex: 1 }}
|
||||
name="audience_network"
|
||||
label="网络类型"
|
||||
>
|
||||
<Select
|
||||
mode="multiple"
|
||||
placeholder="请选择网络类型(可多选)"
|
||||
style={{ borderRadius: 8, minHeight: 40 }}
|
||||
maxTagCount="responsive"
|
||||
options={[
|
||||
{ value: 'ALL', label: '不限' },
|
||||
{ value: '5G', label: '5G' },
|
||||
{ value: '4G', label: '4G' },
|
||||
{ value: '3G', label: '3G' },
|
||||
{ value: '2G', label: '2G' },
|
||||
{ value: 'WIFI', label: 'wifi' },
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
</div>
|
||||
|
||||
<Form.Item
|
||||
style={{ flex: 1 }}
|
||||
name="cost_cap"
|
||||
label="是否最优成本出价"
|
||||
name="audience_region"
|
||||
label="受众地区"
|
||||
>
|
||||
<Cascader
|
||||
multiple
|
||||
placeholder="请选择受众地区(可多选)"
|
||||
style={{ borderRadius: 8, minHeight: 40 }}
|
||||
options={areaOptions}
|
||||
maxTagCount="responsive"
|
||||
/>
|
||||
</Form.Item>
|
||||
<div style={{ display: 'flex', gap: 16 }}>
|
||||
<Form.Item
|
||||
style={{ flex: 1 }}
|
||||
name="cus_name"
|
||||
label="客户主体名称"
|
||||
>
|
||||
<Input
|
||||
placeholder="请输入客户主体名称"
|
||||
style={{ borderRadius: 8, height: 40 }}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
style={{ flex: 1 }}
|
||||
name="pricing_type"
|
||||
label="出价类型"
|
||||
>
|
||||
<Select
|
||||
placeholder="请选择出价类型"
|
||||
style={{ borderRadius: 8, height: 40 }}
|
||||
options={[
|
||||
{ value: 'OCPC', label: 'OCPC' },
|
||||
{ value: 'CPA', label: 'CPA' },
|
||||
{ value: 'OCPM', label: 'OCPM' },
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
</div>
|
||||
{platform == 'AD' && (
|
||||
<div style={{ display: 'flex', gap: 16 }}>
|
||||
<Form.Item
|
||||
style={{ flex: 1 }}
|
||||
name="cost_cap"
|
||||
label="是否最优成本出价"
|
||||
initialValue={false}
|
||||
>
|
||||
<Select
|
||||
placeholder="是否最优成本出价"
|
||||
style={{ borderRadius: 8, height: 40 }}
|
||||
options={[
|
||||
{ value: true, label: '开启' },
|
||||
{ value: false, label: '关闭' },
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
style={{ flex: 1 }}
|
||||
name="target_cost"
|
||||
label="是否稳定成本出价"
|
||||
initialValue={false}
|
||||
>
|
||||
<Select
|
||||
placeholder="是否稳定成本出价"
|
||||
style={{ borderRadius: 8, height: 40 }}
|
||||
options={[
|
||||
{ value: true, label: '开启' },
|
||||
{ value: false, label: '关闭' },
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
style={{ flex: 1 }}
|
||||
name="nobid"
|
||||
label="是否最大转化出价"
|
||||
initialValue={false}
|
||||
>
|
||||
<Select
|
||||
placeholder="是否最大转化出价"
|
||||
style={{ borderRadius: 8, height: 40 }}
|
||||
options={[
|
||||
{ value: true, label: '开启' },
|
||||
{ value: false, label: '关闭' },
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
</div>
|
||||
)}
|
||||
<div style={{ display: 'flex', gap: 16 }}>
|
||||
<Form.Item
|
||||
style={{ flex: 1 }}
|
||||
name="cpc_bid"
|
||||
label="目标点击成本"
|
||||
getValueFromEvent={(e) => parseFloat(e.target.value) || undefined}
|
||||
>
|
||||
<Input
|
||||
type="number"
|
||||
placeholder="请输入目标点击成本"
|
||||
style={{ borderRadius: 8, height: 40 }}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
style={{ flex: 1 }}
|
||||
name="budget"
|
||||
label="预算金额"
|
||||
getValueFromEvent={(e) => parseFloat(e.target.value) || undefined}
|
||||
>
|
||||
<Input
|
||||
type="number"
|
||||
placeholder="请输入预算金额"
|
||||
style={{ borderRadius: 8, height: 40 }}
|
||||
/>
|
||||
</Form.Item>
|
||||
</div>
|
||||
<Form.Item
|
||||
name="note"
|
||||
label="备注"
|
||||
>
|
||||
<Input.TextArea
|
||||
placeholder="请输入备注"
|
||||
rows={2}
|
||||
style={{ borderRadius: 8 }}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="is_default"
|
||||
label="默认模板"
|
||||
initialValue={false}
|
||||
>
|
||||
<Select
|
||||
placeholder="是否最优成本出价"
|
||||
placeholder="请选择默认模板"
|
||||
style={{ borderRadius: 8, height: 40 }}
|
||||
options={[
|
||||
{ value: true, label: '开启' },
|
||||
{ value: false, label: '关闭' },
|
||||
{ value: true, label: '是' },
|
||||
{ value: false, label: '否' },
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
style={{ flex: 1 }}
|
||||
name="target_cost"
|
||||
label="是否稳定成本出价"
|
||||
>
|
||||
<Select
|
||||
placeholder="是否稳定成本出价"
|
||||
style={{ borderRadius: 8, height: 40 }}
|
||||
options={[
|
||||
{ value: true, label: '开启' },
|
||||
{ value: false, label: '关闭' },
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
style={{ flex: 1 }}
|
||||
name="nobid"
|
||||
label="是否最大转化出价"
|
||||
>
|
||||
<Select
|
||||
placeholder="是否最大转化出价"
|
||||
style={{ borderRadius: 8, height: 40 }}
|
||||
options={[
|
||||
{ value: true, label: '开启' },
|
||||
{ value: false, label: '关闭' },
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
</div>
|
||||
)}
|
||||
<div style={{ display: 'flex', gap: 16 }}>
|
||||
<Form.Item
|
||||
style={{ flex: 1 }}
|
||||
name="cpc_bid"
|
||||
label="目标点击成本"
|
||||
getValueFromEvent={(e) => parseFloat(e.target.value) || undefined}
|
||||
>
|
||||
<Input
|
||||
type="number"
|
||||
placeholder="请输入目标点击成本"
|
||||
style={{ borderRadius: 8, height: 40 }}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
style={{ flex: 1 }}
|
||||
name="budget"
|
||||
label="预算金额"
|
||||
getValueFromEvent={(e) => parseFloat(e.target.value) || undefined}
|
||||
>
|
||||
<Input
|
||||
type="number"
|
||||
placeholder="请输入预算金额"
|
||||
style={{ borderRadius: 8, height: 40 }}
|
||||
/>
|
||||
</Form.Item>
|
||||
</div>
|
||||
<Form.Item
|
||||
name="note"
|
||||
label="备注"
|
||||
>
|
||||
<Input.TextArea
|
||||
placeholder="请输入备注"
|
||||
rows={2}
|
||||
style={{ borderRadius: 8 }}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="is_default"
|
||||
label="默认模板"
|
||||
initialValue={false}
|
||||
>
|
||||
<Select
|
||||
placeholder="请选择默认模板"
|
||||
style={{ borderRadius: 8, height: 40 }}
|
||||
options={[
|
||||
{ value: true, label: '是' },
|
||||
{ value: false, label: '否' },
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Collapse.Panel>
|
||||
</Collapse>
|
||||
</Form>
|
||||
</Modal>
|
||||
</div>
|
||||
|
||||
@@ -116,7 +116,7 @@ const ProjectsPage: React.FC = () => {
|
||||
position: 'relative', overflow: 'hidden', flexWrap: 'wrap', gap: 12,
|
||||
}}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 16 }}>
|
||||
<div style={{ width: 32, height: 2, background: 'linear-gradient(90deg, transparent, #6366f1, #8b5cf6, transparent)', borderRadius: 1 }} />
|
||||
{/* <div style={{ width: 32, height: 2, background: 'linear-gradient(90deg, transparent, #6366f1, #8b5cf6, transparent)', borderRadius: 1 }} /> */}
|
||||
<div>
|
||||
<h2 style={{
|
||||
margin: 0,
|
||||
@@ -126,7 +126,7 @@ const ProjectsPage: React.FC = () => {
|
||||
WebkitBackgroundClip: 'text',
|
||||
WebkitTextFillColor: 'transparent',
|
||||
backgroundClip: 'text',
|
||||
textAlign: 'center',
|
||||
// textAlign: 'center',
|
||||
}}>
|
||||
我的项目
|
||||
</h2>
|
||||
@@ -134,7 +134,7 @@ const ProjectsPage: React.FC = () => {
|
||||
共 {projects.length} 个项目 · 按行业分类管理
|
||||
</p>
|
||||
</div>
|
||||
<div style={{ width: 32, height: 2, background: 'linear-gradient(90deg, transparent, #8b5cf6, #6366f1, transparent)', borderRadius: 1 }} />
|
||||
{/* <div style={{ width: 32, height: 2, background: 'linear-gradient(90deg, transparent, #8b5cf6, #6366f1, transparent)', borderRadius: 1 }} /> */}
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: 12 }}>
|
||||
<Button icon={<FileTextOutlined />} onClick={() => navigate('/records')} size="large" style={{
|
||||
|
||||
@@ -177,7 +177,7 @@ export default function VideoFrameExtractor() {
|
||||
position: 'relative', overflow: 'hidden', flexWrap: 'wrap', gap: 12,
|
||||
}}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 16 }}>
|
||||
<div style={{ width: 32, height: 2, background: 'linear-gradient(90deg, transparent, #6366f1, #8b5cf6, transparent)', borderRadius: 1 }} />
|
||||
{/* <div style={{ width: 32, height: 2, background: 'linear-gradient(90deg, transparent, #6366f1, #8b5cf6, transparent)', borderRadius: 1 }} /> */}
|
||||
<div>
|
||||
<h2 style={{
|
||||
margin: 0,
|
||||
@@ -187,15 +187,15 @@ export default function VideoFrameExtractor() {
|
||||
WebkitBackgroundClip: 'text',
|
||||
WebkitTextFillColor: 'transparent',
|
||||
backgroundClip: 'text',
|
||||
textAlign: 'center',
|
||||
// textAlign: 'center',
|
||||
}}>
|
||||
AI视频拆镜工作台
|
||||
</h2>
|
||||
<p style={{ fontSize: 13, color: '#64748b', margin: '4px 0 0 0' }}>
|
||||
一键拆解画面分镜,助力仿拍或创作
|
||||
精细化镜头复刻工具,拆分参考视频单镜头独立复刻,提升素材原创度,规避素材同质化
|
||||
</p>
|
||||
</div>
|
||||
<div style={{ width: 32, height: 2, background: 'linear-gradient(90deg, transparent, #8b5cf6, #6366f1, transparent)', borderRadius: 1 }} />
|
||||
{/* <div style={{ width: 32, height: 2, background: 'linear-gradient(90deg, transparent, #8b5cf6, #6366f1, transparent)', borderRadius: 1 }} /> */}
|
||||
</div>
|
||||
|
||||
<Button
|
||||
|
||||
Reference in New Issue
Block a user