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>
|
<!doctype html>
|
||||||
<html lang="zh-CN">
|
<html lang="zh-CN">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<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.googleapis.com" />
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
<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" />
|
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&display=swap" rel="stylesheet" />
|
||||||
<title>后台管理</title>
|
<title>后台管理</title>
|
||||||
<script>
|
<script>
|
||||||
(function() {
|
(function() {
|
||||||
var cached = localStorage.getItem('siteInfo');
|
var cached = localStorage.getItem('siteInfo');
|
||||||
if (cached) {
|
if (cached) {
|
||||||
try {
|
try {
|
||||||
var info = JSON.parse(cached);
|
var info = JSON.parse(cached);
|
||||||
if (info.siteName) {
|
if (info.siteName) {
|
||||||
document.title = info.siteName + ' - 管理后台';
|
document.title = info.siteName + ' - 管理后台';
|
||||||
}
|
}
|
||||||
if (info.siteLogo) {
|
if (info.siteLogo) {
|
||||||
var link = document.querySelector('link[rel="icon"]');
|
var link = document.querySelector('link[rel="icon"]');
|
||||||
if (link) {
|
if (link) {
|
||||||
link.href = info.siteLogo;
|
link.href = info.siteLogo;
|
||||||
link.type = 'image/png';
|
link.type = 'image/png';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
<script type="module" crossorigin src="/assets/index-DfKYC1kL.js"></script>
|
<script type="module" crossorigin src="/assets/index-BE_fa7kc.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-D7ShJUt4.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-D7ShJUt4.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>␍
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -200,7 +200,7 @@ const AdminPlatform: React.FC = () => {
|
|||||||
if (!v) return '-';
|
if (!v) return '-';
|
||||||
const baseUrl = import.meta.env.VITE_API_BASE || 'http://localhost:8000';
|
const baseUrl = import.meta.env.VITE_API_BASE || 'http://localhost:8000';
|
||||||
const fullUrl = v.startsWith('http') ? v : `${baseUrl}${v}`;
|
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', flexDirection: 'column', gap: 16 }}>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 12, padding: 16, background: '#f8fafc', borderRadius: 8 }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: 12, padding: 16, background: '#f8fafc', borderRadius: 8 }}>
|
||||||
<div style={{
|
<div style={{
|
||||||
width: 80, height: 60, borderRadius: 6, overflow: 'hidden',
|
width: 80, height: 48, borderRadius: 6, overflow: 'hidden',
|
||||||
background: currentOpenType.thumb ? 'transparent' : '#e2e8f0',
|
background: currentOpenType.thumb ? 'transparent' : '#e2e8f0',
|
||||||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||||
}}>
|
}}>
|
||||||
@@ -390,7 +390,7 @@ const AdminPlatform: React.FC = () => {
|
|||||||
<img
|
<img
|
||||||
src={currentOpenType.thumb.startsWith('http') ? currentOpenType.thumb : `${import.meta.env.VITE_API_BASE || 'http://localhost:8000'}${currentOpenType.thumb}`}
|
src={currentOpenType.thumb.startsWith('http') ? currentOpenType.thumb : `${import.meta.env.VITE_API_BASE || 'http://localhost:8000'}${currentOpenType.thumb}`}
|
||||||
alt="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>
|
<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>
|
||||||
<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">
|
<link rel="stylesheet" crossorigin href="/assets/index-Bi8mcSs8.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -130,5 +130,5 @@ export const api = {
|
|||||||
post: <T>(path: string, body?: unknown, auth = true, skipAuthRedirect = false) =>
|
post: <T>(path: string, body?: unknown, auth = true, skipAuthRedirect = false) =>
|
||||||
apiRequest<T>(path, { method: 'POST', body, auth, skipAuthRedirect }),
|
apiRequest<T>(path, { method: 'POST', body, auth, skipAuthRedirect }),
|
||||||
put: <T>(path: string, body?: unknown, auth = true) => apiRequest<T>(path, { method: 'PUT', body, auth }),
|
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> {
|
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`);
|
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',
|
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 ? (
|
{item.thumb ? (
|
||||||
<img
|
<img
|
||||||
src={buildUrl(item.thumb)}
|
src={buildUrl(item.thumb)}
|
||||||
alt={item.typeName}
|
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' }}>
|
<div style={{ width: '100%', height: '100%', background: '#f1f5f9', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||||
|
|||||||
@@ -14,9 +14,9 @@ import {
|
|||||||
ReloadOutlined,
|
ReloadOutlined,
|
||||||
StarOutlined,
|
StarOutlined,
|
||||||
PlayCircleOutlined,
|
PlayCircleOutlined,
|
||||||
|
DeleteOutlined,
|
||||||
} from '@ant-design/icons';
|
} 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 { Search } = Input;
|
||||||
const { Text } = Typography;
|
const { Text } = Typography;
|
||||||
@@ -221,8 +221,8 @@ const GeneratedRecord: React.FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 检查下载数量限制
|
// 检查下载数量限制
|
||||||
if (selectedItems.size > 10) {
|
if (selectedItems.size > 30) {
|
||||||
message.warning('最多只能单次下载10个文件');
|
message.warning('最多只能单次下载30个文件');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -266,12 +266,12 @@ const GeneratedRecord: React.FC = () => {
|
|||||||
selectedContent.forEach((item, index) => {
|
selectedContent.forEach((item, index) => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const url = `${baseUrl}${item.videoUrl || item.imageUrl}&download=1`;
|
const url = `${baseUrl}${item.videoUrl || item.imageUrl}&download=1`;
|
||||||
const link = document.createElement('a');
|
const link = document.createElement('a');
|
||||||
link.href = url;
|
link.href = url;
|
||||||
link.download = '';
|
link.download = '';
|
||||||
document.body.appendChild(link);
|
document.body.appendChild(link);
|
||||||
link.click();
|
link.click();
|
||||||
document.body.removeChild(link);
|
document.body.removeChild(link);
|
||||||
}, index * 500);
|
}, index * 500);
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
@@ -286,6 +286,115 @@ const GeneratedRecord: React.FC = () => {
|
|||||||
|
|
||||||
message.destroy('downloadProgress');
|
message.destroy('downloadProgress');
|
||||||
message.success(`下载完成,共 ${successCount} 个文件`);
|
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 () => {
|
const loadPreTestTemplates = async () => {
|
||||||
setPreTestTemplatesLoading(true);
|
setPreTestTemplatesLoading(true);
|
||||||
@@ -586,7 +695,6 @@ const GeneratedRecord: React.FC = () => {
|
|||||||
message.error(error.message || '文件名更新失败');
|
message.error(error.message || '文件名更新失败');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// 日期选择器变化处理函数
|
|
||||||
const handleDateChange = (dateString: string) => {
|
const handleDateChange = (dateString: string) => {
|
||||||
setSelectedDate(dateString);
|
setSelectedDate(dateString);
|
||||||
};
|
};
|
||||||
@@ -618,7 +726,7 @@ const GeneratedRecord: React.FC = () => {
|
|||||||
}, 5000);
|
}, 5000);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
useEffect(() => {
|
const loadRecordList = () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
let historySource = '';
|
let historySource = '';
|
||||||
if (filterType === 'project') {
|
if (filterType === 'project') {
|
||||||
@@ -695,6 +803,9 @@ const GeneratedRecord: React.FC = () => {
|
|||||||
setLoading(false);
|
setLoading(false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
useEffect(() => {
|
||||||
|
loadRecordList();
|
||||||
}, [filterType, filterMedia, Pagebreak.page, selectedDate]);
|
}, [filterType, filterMedia, Pagebreak.page, selectedDate]);
|
||||||
// 加载更多
|
// 加载更多
|
||||||
const handleLoadMore = () => {
|
const handleLoadMore = () => {
|
||||||
@@ -827,7 +938,7 @@ const GeneratedRecord: React.FC = () => {
|
|||||||
}}
|
}}
|
||||||
icon={<StarOutlined />}
|
icon={<StarOutlined />}
|
||||||
>
|
>
|
||||||
爆款开头复刻
|
爆款复刻
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
type={filterType === 'shot_replicate' ? 'primary' : 'default'}
|
type={filterType === 'shot_replicate' ? 'primary' : 'default'}
|
||||||
@@ -882,6 +993,12 @@ const GeneratedRecord: React.FC = () => {
|
|||||||
>
|
>
|
||||||
取消选择
|
取消选择
|
||||||
</Button>
|
</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
|
<Button
|
||||||
onClick={() => handleDownloadSelected()}
|
onClick={() => handleDownloadSelected()}
|
||||||
style={{
|
style={{
|
||||||
@@ -1150,11 +1267,11 @@ const GeneratedRecord: React.FC = () => {
|
|||||||
padding: '4px 8px',
|
padding: '4px 8px',
|
||||||
backgroundColor: 'rgba(239, 68, 68, 0.9)',
|
backgroundColor: 'rgba(239, 68, 68, 0.9)',
|
||||||
color: '#fff',
|
color: '#fff',
|
||||||
fontSize: 10,
|
fontSize: 12,
|
||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
zIndex: 10,
|
zIndex: 10,
|
||||||
}}>
|
}}>
|
||||||
视频实际尺寸可能不符合推送要求
|
视频实际尺寸可能不符合平台推送要求
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
@@ -1417,7 +1534,7 @@ const GeneratedRecord: React.FC = () => {
|
|||||||
onCancel={handleClosePreview}
|
onCancel={handleClosePreview}
|
||||||
width={1200}
|
width={1200}
|
||||||
footer={
|
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
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
icon={<DownloadOutlined />}
|
icon={<DownloadOutlined />}
|
||||||
@@ -1433,251 +1550,322 @@ const GeneratedRecord: React.FC = () => {
|
|||||||
link.click();
|
link.click();
|
||||||
document.body.removeChild(link);
|
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)}
|
disabled={isMediaExpired(previewItem.videoUrl || previewItem.imageUrl)}
|
||||||
>
|
>
|
||||||
{isMediaExpired(previewItem.videoUrl || previewItem.imageUrl) ? '资源已过期' : '下载'}
|
{isMediaExpired(previewItem.videoUrl || previewItem.imageUrl) ? '资源已过期' : '下载'}
|
||||||
</Button>
|
</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
|
<Button
|
||||||
onClick={handleSinglePushToMedia}
|
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)}
|
disabled={isMediaExpired(previewItem.videoUrl || previewItem.imageUrl)}
|
||||||
>
|
>
|
||||||
推送媒体后台
|
推送媒体后台
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
style={{ borderRadius: 20 }}
|
style={{ borderRadius: 16, overflow: 'hidden' }}
|
||||||
styles={{
|
styles={{
|
||||||
body: { height: 460, display: 'flex', flexDirection: 'column', padding: 0 },
|
body: { height: 500, 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' },
|
header: { background: '#fff', borderBottom: '1px solid #f1f5f9', padding: '16px 24px' },
|
||||||
|
mask: { backgroundColor: 'rgba(0, 0, 0, 0.5)' },
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* 内容区域 */}
|
|
||||||
<div style={{
|
<div style={{
|
||||||
flex: 1,
|
width: '100%',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexWrap: 'wrap',
|
height: '100%',
|
||||||
height: '460px',
|
background: '#f8fafc',
|
||||||
gap: 20,
|
|
||||||
padding: 20,
|
|
||||||
overflow: 'auto',
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
|
||||||
}}>
|
}}>
|
||||||
{/* 媒体预览 */}
|
<div style={{
|
||||||
<div style={{
|
width: '70%',
|
||||||
flex: 1,
|
height: '100%',
|
||||||
minWidth: '280px',
|
display: 'flex',
|
||||||
maxWidth: '800px',
|
justifyContent: 'center',
|
||||||
display: 'flex',
|
alignItems: 'center',
|
||||||
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>
|
|
||||||
|
|
||||||
{/* 参数信息 */}
|
padding: 20,
|
||||||
<div style={{
|
}}>
|
||||||
width: '100%',
|
{isMediaExpired(previewItem.videoUrl || previewItem.imageUrl) ? (
|
||||||
minWidth: '280px',
|
<div style={{ textAlign: 'center', padding: '40px', backgroundColor: '#fff', borderRadius: 12 }}>
|
||||||
maxWidth: '320px',
|
<div style={{ fontSize: 48, marginBottom: 16 }}>⚠️</div>
|
||||||
background: '#f8fafc',
|
<p style={{ fontSize: 15, color: '#ef4444', fontWeight: 500, marginBottom: 8 }}>资源已过期</p>
|
||||||
borderRadius: 12,
|
<p style={{ fontSize: 13, color: '#94a3b8' }}>请刷新页面重新加载</p>
|
||||||
padding: 20,
|
</div>
|
||||||
maxHeight: '55vh',
|
) : filterMedia === 'video' ? (
|
||||||
overflowY: 'auto',
|
<video
|
||||||
overflowX: 'hidden',
|
ref={videoRef}
|
||||||
}}>
|
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${previewItem.videoUrl}`}
|
||||||
<Typography.Text strong style={{ fontSize: 14, color: '#475569', display: 'block', marginBottom: 16 }}>
|
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>
|
</Typography.Text>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
|
||||||
{/* ID */}
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||||
{/* <div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
<span style={{ color: '#94a3b8', fontSize: 13, fontWeight: 500 }}>类型</span>
|
||||||
<span style={{ color: '#94a3b8', fontSize: 13 }}>ID</span>
|
<span style={{ color: '#334155', fontSize: 13, fontWeight: 600 }}>
|
||||||
<span style={{ color: '#334155', fontSize: 13, fontWeight: 500 }}>
|
{filterMedia === 'video' ? '视频' : '图片'}
|
||||||
{previewItem.id || '-'}
|
</span>
|
||||||
</span>
|
</div>
|
||||||
</div> */}
|
{filterType === 'hot_opening_replicate' && (
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'space-between', gap: 4 }}>
|
||||||
{/* 类型 */}
|
<span style={{ color: '#94a3b8', fontSize: 13, fontWeight: 500 }}>类属</span>
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
<span style={{ color: '#334155', fontSize: 13, fontWeight: 500, wordBreak: 'break-all', lineHeight: '1.5' }}>
|
||||||
<span style={{ color: '#94a3b8', fontSize: 13 }}>类型</span>
|
爆款复刻——{previewItem.moduleProjectTitle}
|
||||||
<span style={{ color: '#334155', fontSize: 13, fontWeight: 500 }}>
|
|
||||||
{filterMedia === 'video' ? '视频' : '图片'}
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', gap: 12 }}>
|
)}
|
||||||
<span style={{ color: '#94a3b8', fontSize: 13, flexShrink: 0, width: 40 }}>请求</span>
|
{filterType === 'shot_replicate' && (
|
||||||
<span style={{ color: '#334155', fontSize: 13, fontWeight: 500, flex: 1, wordBreak: 'break-all' }}>
|
<div style={{ display: 'flex', justifyContent: 'space-between', gap: 4 }}>
|
||||||
{previewItem.originalPrompt}
|
<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>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* 分辨率 */}
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
|
||||||
{filterMedia === 'image' && (
|
<span style={{ color: '#94a3b8', fontSize: 13, fontWeight: 500 }}>请求</span>
|
||||||
<>
|
<span style={{ color: '#334155', fontSize: 13, fontWeight: 500, wordBreak: 'break-all', lineHeight: '1.5' }}>
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
{previewItem.originalPrompt}
|
||||||
<span style={{ color: '#94a3b8', fontSize: 13 }}>比例</span>
|
</span>
|
||||||
<span style={{ color: '#334155', fontSize: 13, fontWeight: 500 }}>
|
</div>
|
||||||
{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> */}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* 时长(视频) */}
|
{filterMedia === 'image' && (
|
||||||
{filterMedia === 'video' && (
|
<>
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||||
<span style={{ color: '#94a3b8', fontSize: 13 }}>时长</span>
|
<span style={{ color: '#94a3b8', fontSize: 13, fontWeight: 500 }}>比例</span>
|
||||||
<span style={{ color: '#334155', fontSize: 13, fontWeight: 500 }}>
|
<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}秒` || '-'}
|
{`${previewItem.duration}秒` || '-'}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
@@ -1826,7 +2014,7 @@ const GeneratedRecord: React.FC = () => {
|
|||||||
})()}
|
})()}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={{ display: 'flex', gap: 16, marginBottom: 16 }}>
|
<div style={{ display: 'flex', gap: 16, marginBottom: 16 }}>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||||
<Typography.Text style={{ fontSize: 14, color: '#475569' }}>是否开启前测:</Typography.Text>
|
<Typography.Text style={{ fontSize: 14, color: '#475569' }}>是否开启前测:</Typography.Text>
|
||||||
@@ -2006,7 +2194,7 @@ const GeneratedRecord: React.FC = () => {
|
|||||||
onClick: () => {
|
onClick: () => {
|
||||||
const id = String(record.id);
|
const id = String(record.id);
|
||||||
const newOauthItems = [...selectedOauthItems];
|
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);
|
setSelectedOauthItems(newOauthItems);
|
||||||
const newOauthSelectOpens = [...oauthSelectOpens];
|
const newOauthSelectOpens = [...oauthSelectOpens];
|
||||||
newOauthSelectOpens[index] = false;
|
newOauthSelectOpens[index] = false;
|
||||||
@@ -2101,14 +2289,14 @@ const GeneratedRecord: React.FC = () => {
|
|||||||
.filter(item => item.advertiserId)
|
.filter(item => item.advertiserId)
|
||||||
.map(item => String(item.advertiserId))}
|
.map(item => String(item.advertiserId))}
|
||||||
onChange={(targetKeys) => {
|
onChange={(targetKeys) => {
|
||||||
const newSelected = historyOAuthList.filter(item =>
|
const newSelected = historyOAuthList.filter(item =>
|
||||||
item.advertiserId && targetKeys.includes(String(item.advertiserId))
|
item.advertiserId && targetKeys.includes(String(item.advertiserId))
|
||||||
);
|
);
|
||||||
setSelectedHistoryAccounts(newSelected);
|
setSelectedHistoryAccounts(newSelected);
|
||||||
}}
|
}}
|
||||||
titles={['可选账户列表', '已选账户列表']}
|
titles={['可选账户列表', '已选账户列表']}
|
||||||
showSearch
|
showSearch
|
||||||
filterOption={(inputValue, item) =>
|
filterOption={(inputValue, item) =>
|
||||||
String(item.advertiserId).toLowerCase().includes(inputValue.toLowerCase()) ||
|
String(item.advertiserId).toLowerCase().includes(inputValue.toLowerCase()) ||
|
||||||
(item.advertiserName && item.advertiserName.toLowerCase().includes(inputValue.toLowerCase()))
|
(item.advertiserName && item.advertiserName.toLowerCase().includes(inputValue.toLowerCase()))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ const HomePage: React.FC = () => {
|
|||||||
const aiEntries = [
|
const aiEntries = [
|
||||||
{
|
{
|
||||||
icon: <FileTextOutlined style={{ fontSize: 24, color: '#6366f1' }} />,
|
icon: <FileTextOutlined style={{ fontSize: 24, color: '#6366f1' }} />,
|
||||||
title: '爆款开头复刻',
|
title: '爆款复刻',
|
||||||
description: '上传参考视频与产品图片,一键复刻爆款视频开头',
|
description: '上传参考视频与产品图片,一键复刻爆款视频开头',
|
||||||
action: '立即创作',
|
action: '立即创作',
|
||||||
path: '/initial',
|
path: '/initial',
|
||||||
@@ -106,7 +106,7 @@ const HomePage: React.FC = () => {
|
|||||||
{
|
{
|
||||||
icon: <ScissorOutlined style={{ fontSize: 24, color: '#f97316' }} />,
|
icon: <ScissorOutlined style={{ fontSize: 24, color: '#f97316' }} />,
|
||||||
title: '拆镜复刻',
|
title: '拆镜复刻',
|
||||||
description: '一键拆解画面分镜,助力仿拍或创作',
|
description: '精细化镜头复刻工具,拆分参考视频单镜头独立复刻,提升素材原创度,规避素材同质化',
|
||||||
action: '开始混剪',
|
action: '开始混剪',
|
||||||
path: '/removelens',
|
path: '/removelens',
|
||||||
},
|
},
|
||||||
@@ -143,7 +143,7 @@ const HomePage: React.FC = () => {
|
|||||||
return (
|
return (
|
||||||
<div className="content_box">
|
<div className="content_box">
|
||||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16 }}>
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16 }}>
|
||||||
<h3>首页工作台</h3>
|
<h3>首页工作台 </h3>
|
||||||
<p
|
<p
|
||||||
style={{
|
style={{
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
@@ -190,7 +190,7 @@ const HomePage: React.FC = () => {
|
|||||||
background: 'linear-gradient(180deg, #6366f1, #a855f7)',
|
background: 'linear-gradient(180deg, #6366f1, #a855f7)',
|
||||||
}} />
|
}} />
|
||||||
<div style={{ fontSize: 17, fontWeight: 700, color: '#1f2937', letterSpacing: 0.3 }}>
|
<div style={{ fontSize: 17, fontWeight: 700, color: '#1f2937', letterSpacing: 0.3 }}>
|
||||||
我的项目
|
我的项目<span style={{ color: '#a8a8a8ff', fontSize: 12 }}>(操作步骤)</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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 }} />
|
<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 style={{ margin: 0, fontSize: 16, fontWeight: 700, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent', backgroundClip: 'text' }}>
|
||||||
爆款开头复刻 - 任务详情
|
爆款复刻 - 任务详情
|
||||||
</h3>
|
</h3>
|
||||||
<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 }} />
|
||||||
|
|
||||||
|
|||||||
@@ -450,7 +450,7 @@ const GenerateConver: React.FC = () => {
|
|||||||
position: 'relative', overflow: 'hidden', flexWrap: 'wrap', gap: 12,
|
position: 'relative', overflow: 'hidden', flexWrap: 'wrap', gap: 12,
|
||||||
}}>
|
}}>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 16 }}>
|
<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>
|
<div>
|
||||||
<h2 style={{
|
<h2 style={{
|
||||||
margin: 0,
|
margin: 0,
|
||||||
@@ -460,15 +460,15 @@ const GenerateConver: React.FC = () => {
|
|||||||
WebkitBackgroundClip: 'text',
|
WebkitBackgroundClip: 'text',
|
||||||
WebkitTextFillColor: 'transparent',
|
WebkitTextFillColor: 'transparent',
|
||||||
backgroundClip: 'text',
|
backgroundClip: 'text',
|
||||||
textAlign: 'center',
|
// textAlign: 'center',
|
||||||
}}>
|
}}>
|
||||||
爆款开头复刻
|
爆款复刻
|
||||||
</h2>
|
</h2>
|
||||||
<p style={{ fontSize: 13, color: '#64748b', margin: '4px 0 0 0' }}>
|
<p style={{ fontSize: 13, color: '#64748b', margin: '4px 0 0 0' }}>
|
||||||
上传参考视频与产品图片,一键复刻爆款视频开头
|
上传参考视频与产品图片,一键复刻爆款视频开头
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</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>
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
@@ -796,7 +796,7 @@ const GenerateConver: React.FC = () => {
|
|||||||
暂无生成内容
|
暂无生成内容
|
||||||
</h3>
|
</h3>
|
||||||
<p style={{ margin: 0, fontSize: 13, color: '#64748b', textAlign: 'center', padding: '0 40px', lineHeight: 1.6 }}>
|
<p style={{ margin: 0, fontSize: 13, color: '#64748b', textAlign: 'center', padding: '0 40px', lineHeight: 1.6 }}>
|
||||||
请完善素材与卖点,生成专属爆款开头复刻视频
|
请完善素材与卖点,生成专属爆款复刻视频
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
+258
-246
@@ -1,5 +1,5 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
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 { PlusOutlined, EditOutlined, FileTextOutlined, DeleteOutlined, EyeOutlined } from '@ant-design/icons';
|
||||||
import { getPreTestList, createPreTest, getArea, getPreTestDetail, updatePreTest, deletePreTest } from '../api';
|
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 [modalMode, setModalMode] = useState<'create' | 'edit' | 'detail'>('create');
|
||||||
const [currentRecord, setCurrentRecord] = useState<PreTestRecord | null>(null);
|
const [currentRecord, setCurrentRecord] = useState<PreTestRecord | null>(null);
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
|
const [expandOptional, setExpandOptional] = useState(false);
|
||||||
const [submitLoading, setSubmitLoading] = useState(false);
|
const [submitLoading, setSubmitLoading] = useState(false);
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
const [pageSize, setPageSize] = useState(10);
|
const [pageSize, setPageSize] = useState(10);
|
||||||
@@ -608,7 +609,7 @@ const PreTest: React.FC = () => {
|
|||||||
<div style={{ minHeight: 'calc(100vh - 80px)' }}>
|
<div style={{ minHeight: 'calc(100vh - 80px)' }}>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 12 }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 12 }}>
|
||||||
<FileTextOutlined style={{ color: '#6366f1', fontSize: 16 }} />
|
<FileTextOutlined style={{ color: '#6366f1', fontSize: 16 }} />
|
||||||
<Typography.Text strong style={{ fontSize: 16 }}>素材前测</Typography.Text>
|
<Typography.Text strong style={{ fontSize: 16 }}>素材前测模板</Typography.Text>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 }}>
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 }}>
|
||||||
<div style={{ display: 'flex', gap: 16, alignItems: 'center' }}>
|
<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)',
|
boxShadow: '0 4px 14px rgba(99,102,241,0.3)',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
新增前测
|
新增模板
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -692,7 +693,7 @@ const PreTest: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Modal
|
<Modal
|
||||||
title={<Space>{modalMode === 'create' ? '新增前测模板' : modalMode === 'edit' ? '编辑前测模板' : '前测模板详情'}</Space>}
|
title={<Space>{modalMode === 'create' ? '新增前测模板(选填项如填写需准确填写)' : modalMode === 'edit' ? '编辑前测模板(选填项如填写需准确填写)' : '前测模板详情'}</Space>}
|
||||||
open={modalOpen}
|
open={modalOpen}
|
||||||
onCancel={handleCloseModal}
|
onCancel={handleCloseModal}
|
||||||
onOk={handleSubmit}
|
onOk={handleSubmit}
|
||||||
@@ -743,257 +744,268 @@ const PreTest: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</div>
|
</div>
|
||||||
{platform !== 'LOCAL' && (
|
<Collapse
|
||||||
<Form.Item
|
defaultActiveKey={[]}
|
||||||
name="external_action"
|
activeKey={expandOptional ? ['optional'] : []}
|
||||||
label="转化目标"
|
onChange={() => setExpandOptional(!expandOptional)}
|
||||||
>
|
|
||||||
<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="受众地区"
|
|
||||||
>
|
>
|
||||||
<Cascader
|
<Collapse.Panel header="选填项" key="optional">
|
||||||
multiple
|
{platform !== 'LOCAL' && (
|
||||||
placeholder="请选择受众地区(可多选)"
|
<Form.Item
|
||||||
style={{ borderRadius: 8, minHeight: 40 }}
|
name="external_action"
|
||||||
options={areaOptions}
|
label="转化目标"
|
||||||
maxTagCount="responsive"
|
>
|
||||||
/>
|
<Select
|
||||||
</Form.Item>
|
placeholder="请选择转化目标"
|
||||||
<div style={{ display: 'flex', gap: 16 }}>
|
style={{ borderRadius: 8, height: 40 }}
|
||||||
<Form.Item
|
options={PRETEST_FIELDS.filter(field => {
|
||||||
style={{ flex: 1 }}
|
if (platform === 'AD') {
|
||||||
name="cus_name"
|
return field.name.startsWith('AD_');
|
||||||
label="客户主体名称"
|
} else if (platform === 'QIANCHUAN') {
|
||||||
>
|
return field.name.startsWith('QC_');
|
||||||
<Input
|
} else if (platform === 'LOCAL') {
|
||||||
placeholder="请输入客户主体名称"
|
return field.name.startsWith('LOCAL_');
|
||||||
style={{ borderRadius: 8, height: 40 }}
|
}
|
||||||
/>
|
return true;
|
||||||
</Form.Item>
|
}).map(field => ({
|
||||||
<Form.Item
|
value: field.name,
|
||||||
style={{ flex: 1 }}
|
label: (
|
||||||
name="pricing_type"
|
<span>
|
||||||
label="出价类型"
|
{field.label}
|
||||||
>
|
{field.description && (
|
||||||
<Select
|
<Typography.Text style={{ color: '#94a3b8', fontSize: 12, marginLeft: 4 }}>
|
||||||
placeholder="请选择出价类型"
|
({field.description})
|
||||||
style={{ borderRadius: 8, height: 40 }}
|
</Typography.Text>
|
||||||
options={[
|
)}
|
||||||
{ value: 'OCPC', label: 'OCPC' },
|
</span>
|
||||||
{ value: 'CPA', label: 'CPA' },
|
),
|
||||||
{ value: 'OCPM', label: 'OCPM' },
|
}))}
|
||||||
]}
|
/>
|
||||||
/>
|
</Form.Item>
|
||||||
</Form.Item>
|
)}
|
||||||
</div>
|
|
||||||
{platform == 'AD' && (
|
<div style={{ display: 'flex', gap: 16 }}>
|
||||||
<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
|
<Form.Item
|
||||||
style={{ flex: 1 }}
|
name="audience_region"
|
||||||
name="cost_cap"
|
label="受众地区"
|
||||||
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
|
<Select
|
||||||
placeholder="是否最优成本出价"
|
placeholder="请选择默认模板"
|
||||||
style={{ borderRadius: 8, height: 40 }}
|
style={{ borderRadius: 8, height: 40 }}
|
||||||
options={[
|
options={[
|
||||||
{ value: true, label: '开启' },
|
{ value: true, label: '是' },
|
||||||
{ value: false, label: '关闭' },
|
{ value: false, label: '否' },
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
</Collapse.Panel>
|
||||||
<Form.Item
|
</Collapse>
|
||||||
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>
|
|
||||||
</Form>
|
</Form>
|
||||||
</Modal>
|
</Modal>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ const ProjectsPage: React.FC = () => {
|
|||||||
position: 'relative', overflow: 'hidden', flexWrap: 'wrap', gap: 12,
|
position: 'relative', overflow: 'hidden', flexWrap: 'wrap', gap: 12,
|
||||||
}}>
|
}}>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 16 }}>
|
<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>
|
<div>
|
||||||
<h2 style={{
|
<h2 style={{
|
||||||
margin: 0,
|
margin: 0,
|
||||||
@@ -126,7 +126,7 @@ const ProjectsPage: React.FC = () => {
|
|||||||
WebkitBackgroundClip: 'text',
|
WebkitBackgroundClip: 'text',
|
||||||
WebkitTextFillColor: 'transparent',
|
WebkitTextFillColor: 'transparent',
|
||||||
backgroundClip: 'text',
|
backgroundClip: 'text',
|
||||||
textAlign: 'center',
|
// textAlign: 'center',
|
||||||
}}>
|
}}>
|
||||||
我的项目
|
我的项目
|
||||||
</h2>
|
</h2>
|
||||||
@@ -134,7 +134,7 @@ const ProjectsPage: React.FC = () => {
|
|||||||
共 {projects.length} 个项目 · 按行业分类管理
|
共 {projects.length} 个项目 · 按行业分类管理
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</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>
|
||||||
<div style={{ display: 'flex', gap: 12 }}>
|
<div style={{ display: 'flex', gap: 12 }}>
|
||||||
<Button icon={<FileTextOutlined />} onClick={() => navigate('/records')} size="large" style={{
|
<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,
|
position: 'relative', overflow: 'hidden', flexWrap: 'wrap', gap: 12,
|
||||||
}}>
|
}}>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 16 }}>
|
<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>
|
<div>
|
||||||
<h2 style={{
|
<h2 style={{
|
||||||
margin: 0,
|
margin: 0,
|
||||||
@@ -187,15 +187,15 @@ export default function VideoFrameExtractor() {
|
|||||||
WebkitBackgroundClip: 'text',
|
WebkitBackgroundClip: 'text',
|
||||||
WebkitTextFillColor: 'transparent',
|
WebkitTextFillColor: 'transparent',
|
||||||
backgroundClip: 'text',
|
backgroundClip: 'text',
|
||||||
textAlign: 'center',
|
// textAlign: 'center',
|
||||||
}}>
|
}}>
|
||||||
AI视频拆镜工作台
|
AI视频拆镜工作台
|
||||||
</h2>
|
</h2>
|
||||||
<p style={{ fontSize: 13, color: '#64748b', margin: '4px 0 0 0' }}>
|
<p style={{ fontSize: 13, color: '#64748b', margin: '4px 0 0 0' }}>
|
||||||
一键拆解画面分镜,助力仿拍或创作
|
精细化镜头复刻工具,拆分参考视频单镜头独立复刻,提升素材原创度,规避素材同质化
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</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>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
Reference in New Issue
Block a user