import React, { useEffect, useState, useRef } from 'react'; import { Button, Empty, Input, Select, Space, Typography, Tag, message, Modal, Table, DatePicker, Tabs, Transfer, Tooltip } from 'antd'; import dayjs from 'dayjs'; import JSZip from 'jszip'; import { FilterOutlined, VideoCameraOutlined, PictureOutlined, FolderOpenOutlined, FileTextOutlined, DownloadOutlined, ClockCircleOutlined, UploadOutlined, ReloadOutlined, StarOutlined, PlayCircleOutlined, DeleteOutlined, } from '@ant-design/icons'; import { gethistory, gethistoryItems, getOAuthList, asyncBatchUploadMaterial, updateFilename, getUploadHistory, getAllOAuthAccountList, getOpenTypeAll, getPreTestList, getDefaultPreTest, deleteHistory, deleteResourcesMaterial } from '../api'; const { Search } = Input; const { Text } = Typography; const usePageAllLoaded = (callback: () => void) => { const executedRef = React.useRef(false); useEffect(() => { if (executedRef.current) return; const handleLoad = () => { if (executedRef.current) return; executedRef.current = true; callback?.(); }; if (document.readyState === 'complete') { handleLoad(); } else { window.addEventListener('load', handleLoad); } return () => { window.removeEventListener('load', handleLoad); }; }, [callback]); }; const GeneratedRecord: React.FC = () => { const [isPageLoaded, setIsPageLoaded] = useState(false); usePageAllLoaded(() => { setIsPageLoaded(true); }); const [filterType, setFilterType] = useState<'project' | 'creation' | 'hot_opening_replicate' | 'shot_replicate'>('project'); const [filterMedia, setFilterMedia] = useState<'video' | 'image'>('video'); const [recordlist, setRecordList] = useState([]); const [Pagebreak, setPagebreak] = useState({ page: 1, pageSize: 10, }); const [Totalnumber, setTotalnumber] = useState(0); const [loading, setLoading] = useState(false); const [loadingGroups, setLoadingGroups] = useState>(new Set()); const [previewVisible, setPreviewVisible] = useState(false); const [previewItem, setPreviewItem] = useState(null); const videoRef = React.createRef(); const [selectedDate, setSelectedDate] = useState(''); const [uploading, setUploading] = useState(false); // 推送配置弹窗相关状态 const [uploadConfigModalVisible, setUploadConfigModalVisible] = useState(false); const [accountIdLists, setAccountIdLists] = useState<{ accountId: string; }[][]>([[]]); const [accountIdInputs, setAccountIdInputs] = useState(['']); const [oauthList, setOauthList] = useState([]); const [oauthLoading, setOauthLoading] = useState(false); const [oauthTotal, setOauthTotal] = useState(0); const [selectedOauthItems, setSelectedOauthItems] = useState<({ value: string; label: string } | undefined)[]>([undefined]); const [materialFileNames, setMaterialFileNames] = useState>(new Map()); const [unifiedFileName, setUnifiedFileName] = useState(''); const [mediaLoadStatus, setMediaLoadStatus] = useState>(new Map()); const updateFilenameDebounceRef = useRef>>(new Map()); useEffect(() => { return () => { updateFilenameDebounceRef.current.forEach(timer => clearTimeout(timer)); updateFilenameDebounceRef.current.clear(); }; }, []); const [oauthPage, setOauthPage] = useState(1); const [oauthPageSize, setOauthPageSize] = useState(10); const [oauthSelectOpens, setOauthSelectOpens] = useState([false]); const [accountTab, setAccountTab] = useState<'new' | 'history'>('new'); const [historyOAuthList, setHistoryOAuthList] = useState([]); const [historyOAuthLoading, setHistoryOAuthLoading] = useState(false); const [selectedHistoryAccounts, setSelectedHistoryAccounts] = useState([]); const [openTypeMap, setOpenTypeMap] = useState>({}); const [isPreTest, setIsPreTest] = useState('2'); const [preTestTemplate, setPreTestTemplate] = useState(''); const [preTestTemplates, setPreTestTemplates] = useState([]); const [preTestTemplatesLoading, setPreTestTemplatesLoading] = useState(false); // 推送任务历史弹窗相关状态 const [uploadHistoryModalVisible, setUploadHistoryModalVisible] = useState(false); const [uploadHistoryList, setUploadHistoryList] = useState([]); const [uploadHistoryTotal, setUploadHistoryTotal] = useState(0); const [uploadHistoryPage, setUploadHistoryPage] = useState(1); const [uploadHistoryPageSize, setUploadHistoryPageSize] = useState(10); const [uploadHistoryLoading, setUploadHistoryLoading] = useState(false); const [uploadHistoryStatus, setUploadHistoryStatus] = useState(''); const [isSelectionMode, setIsSelectionMode] = useState(false); const [selectedItems, setSelectedItems] = useState>(new Set()); // 从URL中提取exp时间戳(支持相对路径和完整URL) const extractExpTimestamp = (url: string): number | null => { if (!url) return null; try { // 尝试作为完整URL解析 const urlObj = new URL(url); const expStr = urlObj.searchParams.get('exp'); if (expStr) { return parseInt(expStr, 10); } return null; } catch { // 如果完整URL解析失败,尝试解析相对路径中的查询参数 try { const queryStart = url.indexOf('?'); if (queryStart !== -1) { const queryString = url.substring(queryStart + 1); const params = new URLSearchParams(queryString); const expStr = params.get('exp'); if (expStr) { return parseInt(expStr, 10); } } return null; } catch { return null; } } }; // 检查媒体是否过期 const isMediaExpired = (url: string): boolean => { const expTimestamp = extractExpTimestamp(url); if (!expTimestamp) { return false; // 没有exp参数,视为不过期 } const currentTimestamp = Math.floor(Date.now() / 1000); return currentTimestamp > expTimestamp; }; const buildUrl = (path: string, isImage: boolean = false): string => { const baseUrl = import.meta.env.VITE_API_BASE || "http://localhost:8000"; const cleanPath = path.startsWith('/') ? path.slice(1) : path; const cleanBase = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl; if (isImage) { return `${cleanBase}/static/${cleanPath}&w=300&q=50`; } return `${cleanBase}/${cleanPath}`; }; // 下载文件 const handleDownload = (item: any) => { const url = `${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${item.videoUrl || item.imageUrl}`; const link = document.createElement('a'); link.href = url; link.download = item.title || item.id || 'download'; document.body.appendChild(link); link.click(); document.body.removeChild(link); }; // 预览文件 const handlePreview = (item: any) => { setPreviewItem(item); setPreviewVisible(true); // 触发事件通知布局组件关闭浮动按钮 window.dispatchEvent(new Event('previewOpen')); }; // 关闭预览并暂停视频 const handleClosePreview = () => { // 方法1: 使用 ref if (videoRef.current) { videoRef.current.pause(); videoRef.current.currentTime = 0; } // 方法2: 直接通过 DOM 查询(备用) const videoElements = document.querySelectorAll('video'); videoElements.forEach(video => { video.pause(); video.currentTime = 0; }); setPreviewVisible(false); }; // 获取item的资源ID(优先使用generatedResourceId,否则使用id) const getItemResourceId = (item: any): string => { return item.generatedResourceId || item.id; }; // 判断item是否有generatedResourceId const hasGeneratedResourceId = (item: any): boolean => { return Boolean(item.generatedResourceId); }; // 检查视频尺寸是否符合480p要求(270×480或480×270) const checkVideoSize = (imagePx: string): boolean => { if (!imagePx) return true; const match = imagePx.match(/^(\d+)x(\d+)$/); if (!match) return true; const width = parseInt(match[1], 10); const height = parseInt(match[2], 10); const aspectRatio = width / height; const isLandscape = width >= height; if (isLandscape) { const isValidWidth = width >= 480 && width <= 2560; const isValidHeight = height >= 270 && height <= 1440; const isValidRatio = aspectRatio >= 1.775 && aspectRatio <= 1.784; return isValidWidth && isValidHeight && isValidRatio; } else { const isValidWidth = width >= 270 && width <= 1440; const isValidHeight = height >= 480 && height <= 2560; const isValidRatio = aspectRatio >= 0.555 && aspectRatio <= 0.564; return isValidWidth && isValidHeight && isValidRatio; } }; // 多选相关函数 const handleToggleSelect = (itemId: string) => { setSelectedItems(prev => { const newSet = new Set(prev); if (newSet.has(itemId)) { newSet.delete(itemId); } else { newSet.add(itemId); } return newSet; }); }; const handleSelectAll = () => { const allItemIds = recordlist.flatMap((group: any) => group.items.map((item: any) => getItemResourceId(item)) ); if (selectedItems.size === allItemIds.length) { setSelectedItems(new Set()); } else { setSelectedItems(new Set(allItemIds)); } }; const handleDownloadSelected = async () => { if (selectedItems.size === 0) { message.warning('请先选择要下载的媒体'); return; } // 检查下载数量限制 if (selectedItems.size > 30) { message.warning('最多只能单次下载30个文件'); return; } const selectedContent: any[] = []; recordlist.forEach((group: any) => { group.items.forEach((item: any) => { if (selectedItems.has(getItemResourceId(item))) { selectedContent.push(item); } }); }); const zip = new JSZip(); const baseUrl = import.meta.env.VITE_API_BASE || "http://localhost:8000"; const folder = zip.folder('downloads'); let hasError = false; let successCount = 0; message.loading({ content: '下载中,请稍候...', key: 'downloadProgress' }); for (const item of selectedContent) { const url = `${baseUrl}${item.videoUrl || item.imageUrl}`; const filename = ((item.videoUrl || item.imageUrl).split('/').pop() || `file_${Date.now()}`).split('?')[0]; try { const response = await fetch(url); if (!response.ok) throw new Error('Network response was not ok'); const blob = await response.blob(); folder?.file(filename, blob); successCount++; } catch (error) { console.warn(`文件下载失败(CORS限制): ${filename},将使用备用方式下载`); hasError = true; break; } } if (hasError) { // Fallback: 逐个打开文件下载(不受 CORS 限制) message.destroy('downloadProgress'); message.info('由于跨域限制,将逐个下载文件'); 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); }, index * 500); }); return; } const zipBlob = await zip.generateAsync({ type: 'blob' }); const link = document.createElement('a'); link.href = URL.createObjectURL(zipBlob); link.download = `downloads_${Date.now()}.zip`; link.click(); URL.revokeObjectURL(link.href); 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 = (
以下内容所连带的图片和视频都将被删除:
    {selectedData.map((item: any, index: number) => (
  • {item.shotReplicateProjectTitle}— —{item.shotSegmentLabel}
  • ))}
确定删除吗?
); } else if (filterType === 'hot_opening_replicate') { confirmContent = (
爆款开头复刻中以下内容所连带的图片和视频都将被删除:
    {selectedData.map((item: any, index: number) => (
  • {item.moduleProjectTitle}
  • ))}
确定删除吗?
); } 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); try { const res = await getPreTestList({ page: 1, pageSize: 100 }); setPreTestTemplates(res.data?.data || res.data || []); } catch (error) { console.error('加载前测模板失败:', error); } finally { setPreTestTemplatesLoading(false); } }; const handleBatchUploadSelected = () => { if (selectedItems.size === 0) { message.warning('请先选择要推送的媒体'); return; } setAccountIdLists([[]]); setAccountIdInputs(['']); setSelectedOauthItems([undefined]); setIsPreTest('2'); setPreTestTemplate(''); loadPreTestTemplates(); setUploadConfigModalVisible(true); }; const handleSinglePushToMedia = () => { if (!previewItem) return; const resourceId = getItemResourceId(previewItem); setSelectedItems(new Set([resourceId])); setAccountIdLists([[]]); setAccountIdInputs(['']); setSelectedOauthItems([undefined]); setMaterialFileNames(new Map()); setUnifiedFileName(''); setIsPreTest('2'); setPreTestTemplate(''); loadPreTestTemplates(); setUploadConfigModalVisible(true); }; const loadOAuthList = async (page: number, pageSize: number) => { setOauthLoading(true); try { const res = await getOAuthList({ page, page_size: pageSize }); const data = res?.data || res; setOauthList(data || []); setOauthTotal(res.pagination.total || 0); } catch (error) { console.error('加载授权列表失败:', error); setOauthList([]); setOauthTotal(0); } finally { setOauthLoading(false); } }; const loadHistoryOAuthList = async () => { setHistoryOAuthLoading(true); try { const [oauthRes, openTypeRes] = await Promise.all([ getAllOAuthAccountList(), getOpenTypeAll(), ]); const oauthData = oauthRes?.data || []; setHistoryOAuthList(oauthData); const openTypeData = openTypeRes?.data || []; const map: Record = {}; openTypeData.forEach((item: any) => { map[item.openType] = item.typeName; }); setOpenTypeMap(map); } catch (error) { console.error('加载历史授权账户列表失败:', error); setHistoryOAuthList([]); setOpenTypeMap({}); } finally { setHistoryOAuthLoading(false); } }; const loadUploadHistory = async () => { setUploadHistoryLoading(true); try { const res = await getUploadHistory({ status: uploadHistoryStatus || undefined, page: uploadHistoryPage, pageSize: uploadHistoryPageSize, }); const data = res?.data || res; setUploadHistoryList(data || []); setUploadHistoryTotal(res.pagination?.total || res.total || 0); } catch (error) { console.error('加载推送历史失败:', error); setUploadHistoryList([]); setUploadHistoryTotal(0); } finally { setUploadHistoryLoading(false); } }; const handleOpenUploadHistory = () => { setUploadHistoryModalVisible(true); setUploadHistoryPage(1); setUploadHistoryStatus(''); loadUploadHistory(); }; const handleUploadHistorySearch = () => { setUploadHistoryPage(1); loadUploadHistory(); }; const handleUploadHistoryPageChange = (page: number, pageSize: number) => { setUploadHistoryPage(page); setUploadHistoryPageSize(pageSize); loadUploadHistory(); }; // 批量推送素材 const handleStartBatchUpload = async () => { if (selectedItems.size === 0) { message.warning('请先选择要推送的媒体'); return; } const itemMap = new Map(); recordlist.forEach((group: any) => { group.items.forEach((item: any) => { const resourceId = getItemResourceId(item); itemMap.set(resourceId, item); }); }); let tasks: { advertiser_ids: string[]; resource_ids: string[]; is_pre_test: string; pre_test_template: string; oauth_id: string; source_model: string; }[] = []; if (accountTab === 'new') { const validOauthItems = selectedOauthItems.filter(item => item !== undefined); if (validOauthItems.length === 0) { message.warning('请先选择授权账户'); return; } for (let i = 0; i < validOauthItems.length; i++) { const oauthItem = validOauthItems[i]; const advertiserIds = accountIdLists[i]?.map(account => account.accountId) || []; if (advertiserIds.length === 0) { message.warning(`第 ${i + 1} 组授权账户未设置账户ID,已跳过`); continue; } const sourceModelMap = new Map(); for (const itemId of selectedItems) { const item = itemMap.get(itemId); let sourceModel: string; if (item && hasGeneratedResourceId(item)) { sourceModel = 'generated_resources'; } // else { // sourceModel = filterType === 'project' ? 'generation_records' : 'chat_generation_tasks'; // } if (!sourceModelMap.has(sourceModel)) { sourceModelMap.set(sourceModel, []); } sourceModelMap.get(sourceModel)!.push(itemId); } sourceModelMap.forEach((resourceIds, sourceModel) => { tasks.push({ advertiser_ids: advertiserIds, resource_ids: resourceIds, oauth_id: oauthItem.value, source_model: sourceModel, is_pre_test: isPreTest, pre_test_template: preTestTemplate, }); }); } } else { if (selectedHistoryAccounts.length === 0) { message.warning('请先选择历史账户'); return; } for (const account of selectedHistoryAccounts) { const sourceModelMap = new Map(); for (const itemId of selectedItems) { const item = itemMap.get(itemId); let sourceModel: string; if (item && hasGeneratedResourceId(item)) { sourceModel = 'generated_resources'; } else { sourceModel = filterType === 'project' ? 'generation_records' : 'chat_generation_tasks'; } if (!sourceModelMap.has(sourceModel)) { sourceModelMap.set(sourceModel, []); } sourceModelMap.get(sourceModel)!.push(itemId); } sourceModelMap.forEach((resourceIds, sourceModel) => { tasks.push({ advertiser_ids: [account.advertiserId], resource_ids: resourceIds, oauth_id: String(account.oauthId), source_model: sourceModel, is_pre_test: isPreTest, pre_test_template: preTestTemplate, }); }); } } setUploading(true); try { const res = await asyncBatchUploadMaterial({ tasks }); if (res.errors?.length > 0) { message.warning(res.message); } else if (res.code === 0) { message.success(res.message); } else { message.error(res.message); } setIsSelectionMode(false); setSelectedItems(new Set()); setUploadConfigModalVisible(false); setAccountIdLists([[]]); setAccountIdInputs(['']); setSelectedOauthItems([undefined]); setSelectedHistoryAccounts([]); setMaterialFileNames(new Map()); setUnifiedFileName(''); setIsPreTest('2'); setPreTestTemplate(''); } catch (error: any) { console.error('批量推送失败:', error); message.error(error.message || '批量推送失败'); } finally { setUploading(false); } }; // 更新文件名函数 const handleUpdateFileName = async (sourceId: string, newFileName: string) => { if (!newFileName.trim()) return; try { const response = await updateFilename({ filenames: [{ source_id: sourceId, file_name: newFileName }], }); // 更新 recordlist 中的文件名,使用 API 返回的 new_file_name const result = response?.results?.find((r: any) => r.source_id === sourceId); const actualFileName = result?.new_file_name || newFileName; setRecordList(prevList => { return prevList.map(group => ({ ...group, items: group.items.map((item: any) => { const resourceId = getItemResourceId(item); if (resourceId === sourceId) { return { ...item, fileName: actualFileName }; } return item; }), })); }); message.success('文件名更新成功'); } catch (error: any) { console.error('文件名更新失败:', error); message.error(error.message || '文件名更新失败'); } }; // 批量更新文件名函数 const handleBatchUpdateFileName = async (sourceIds: string[], newFileName: string) => { if (!newFileName.trim() || sourceIds.length === 0) return; try { const filenames = sourceIds.map(sourceId => ({ source_id: sourceId, file_name: newFileName, })); const response = await updateFilename({ filenames }); // 批量更新 recordlist 中的文件名,使用 API 返回的 new_file_name const resultsMap = new Map(); response?.results?.forEach((r: any) => { if (r.success && r.newFileName) { resultsMap.set(r.sourceId, r.newFileName); } }); setRecordList(prevList => { const sourceIdSet = new Set(sourceIds); return prevList.map(group => ({ ...group, items: group.items.map((item: any) => { const resourceId = getItemResourceId(item); if (sourceIdSet.has(resourceId)) { const actualFileName = resultsMap.get(resourceId) || newFileName; return { ...item, fileName: actualFileName }; } return item; }), })); }); console.log(response); const successCount = response?.successCount || 0; message.success(`已更新 ${successCount} 个文件名`); } catch (error: any) { console.error('文件名更新失败:', error); message.error(error.message || '文件名更新失败'); } }; const handleDateChange = (dateString: string) => { setSelectedDate(dateString); }; const getVideoDimensions = (videoUrl: string): Promise => { return new Promise((resolve) => { const video = document.createElement('video'); const baseUrl = import.meta.env.VITE_API_BASE || "http://localhost:8000"; let cleanPath = videoUrl.startsWith('/') ? videoUrl.slice(1) : videoUrl; let cleanBase = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl; video.src = `${cleanBase}/${cleanPath}`; video.crossOrigin = 'anonymous'; video.onloadedmetadata = () => { const width = video.videoWidth; const height = video.videoHeight; video.remove(); resolve(`${width}x${height}`); }; video.onerror = () => { video.remove(); resolve(''); }; video.onabort = () => { video.remove(); resolve(''); }; setTimeout(() => { video.remove(); resolve(''); }, 5000); }); }; const loadRecordList = () => { setLoading(true); let historySource = ''; if (filterType === 'project') { historySource = 'generation_record'; } else if (filterType === 'hot_opening_replicate') { historySource = 'hot_opening_replicate'; } else if (filterType === 'shot_replicate') { historySource = 'shot_replicate'; } let parameters = `?gen_type=${filterMedia}&page=${Pagebreak.page}&page_size=${Pagebreak.pageSize}`; if (historySource) { parameters = `?gen_type=${filterMedia}&history_source=${historySource}&page=${Pagebreak.page}&page_size=${Pagebreak.pageSize}`; } if (selectedDate) { let parameters = `${selectedDate}?gen_type=${filterMedia}&page=${Pagebreak.page}&page_size=${Pagebreak.pageSize}`; if (historySource) { parameters = `${selectedDate}?gen_type=${filterMedia}&history_source=${historySource}&page=${Pagebreak.page}&page_size=${Pagebreak.pageSize}`; } gethistoryItems(parameters).then((res: any) => { const data = Array.isArray(res) ? res : (res?.items || []); let recordList = [{ generatedDate: res.generatedDate, items: data, total: res.total, page: res.page, }]; if (recordList && recordList[0].items.length > 0) { setRecordList(recordList); } else { setRecordList([]); } if (isPageLoaded) { data.forEach(async (item: any) => { if (item.videoUrl && item.videoUrl.trim()) { const dimensions = await getVideoDimensions(item.videoUrl); if (dimensions) { setRecordList(prev => prev.map(group => ({ ...group, items: group.items.map((i: any) => i.id === item.id ? { ...i, imagePx: dimensions } : i ), }))); } } }); } }).catch((err) => { }).finally(() => { setLoading(false); }); try { } catch (err) { } finally { } } else { gethistory(parameters).then((res: any) => { const data = Array.isArray(res) ? res : (res?.groups || []); data.forEach(group => { group.page = 1; }); if (Pagebreak.page === 1) { setRecordList(data); } else { setRecordList(prev => [...prev, ...data]); } setTotalnumber(res?.totalDays || 0); if (isPageLoaded) { data.forEach(group => { group.items.forEach(async (item: any) => { if (item.videoUrl && item.videoUrl.trim()) { const dimensions = await getVideoDimensions(item.videoUrl); if (dimensions) { setRecordList(prev => prev.map(g => ({ ...g, items: g.items.map((i: any) => i.id === item.id ? { ...i, imagePx: dimensions } : i ), }))); } } }); }); } }).catch((err) => { if (Pagebreak.page === 1) { setRecordList([]); } }).finally(() => { setLoading(false); }); } }; useEffect(() => { loadRecordList(); }, [filterType, filterMedia, Pagebreak.page, selectedDate]); // 加载更多 const handleLoadMore = () => { if (loading) return; setPagebreak(prev => ({ ...prev, page: prev.page + 1 })); }; // 分组加载更多 const handleGroupLoadMore = async (time: string, date: string, page: number) => { if (loadingGroups.has(date)) return; setLoadingGroups(prev => new Set([...prev, date])); const addpage = page + 1; let historySource = ''; if (filterType === 'project') { historySource = 'generation_record'; } else if (filterType === 'hot_opening_replicate') { historySource = 'hot_opening_replicate'; } else if (filterType === 'shot_replicate') { historySource = 'shot_replicate'; } let parameters = `${time}?gen_type=${filterMedia}&page=${addpage}&page_size=${Pagebreak.pageSize}`; if (historySource) { parameters = `${time}?gen_type=${filterMedia}&history_source=${historySource}&page=${addpage}&page_size=${Pagebreak.pageSize}`; } try { const res: any = await gethistoryItems(parameters); const newItems: any[] = res.items || []; if (newItems && newItems.length > 0) { setRecordList(prev => prev.map(group => { if (group.generatedDate === time) { return { ...group, items: [...group.items, ...newItems], page: addpage }; } return group; })); } } catch (err) { } finally { setLoadingGroups(prev => { const next = new Set(prev); next.delete(date); return next; }); } }; // 当筛选条件改变时,重置页码 useEffect(() => { setPagebreak(prev => ({ ...prev, page: 1 })); }, [filterType, filterMedia]); return (
{/* 操作栏:筛选 + 推送按钮 */}
{/* 多选模式按钮 */} {isSelectionMode ? ( ) : ( )}
{/* Second row filter: 视频 / 图片 */}
媒体类型: handleDateChange(dateString || '')} format="YYYY-MM-DD" style={{ width: 160, borderRadius: 8, border: '1px solid #e2e8f0' }} placeholder="选择日期" /> {selectedDate && ( )}
{/* Content area */} {recordlist.length === 0 ? ( ) : (
{recordlist.map((group: any, index: number) => (
{/* Date label */}
{group.generatedDate}
{/* Media grid */}
{group.items.map((item: any) => { const displayUrl = filterMedia === 'video' && item.videoCoverUrl ? buildUrl(item.videoCoverUrl, true) : buildUrl(filterMedia === 'video' ? item.videoUrl : item.imageUrl, true); const hasCover = filterMedia === 'video' && item.videoCoverUrl; const showImage = hasCover || filterMedia === 'image'; const isSelectedItem = selectedItems.has(getItemResourceId(item)); return (
isSelectionMode ? handleToggleSelect(getItemResourceId(item)) : handlePreview(item)} onMouseEnter={(e) => { if (!isSelectionMode) { (e.currentTarget as HTMLElement).style.transform = 'scale(1.05)'; (e.currentTarget as HTMLElement).style.boxShadow = '0 4px 16px rgba(0,0,0,0.2)'; } }} onMouseLeave={(e) => { if (!isSelectionMode) { (e.currentTarget as HTMLElement).style.transform = 'scale(1)'; (e.currentTarget as HTMLElement).style.boxShadow = '0 2px 8px rgba(0,0,0,0.1)'; } }} > {showImage ? (
预览 { setMediaLoadStatus(prev => { const newMap = new Map(prev); newMap.set(getItemResourceId(item), 'loaded'); return newMap; }); }} onError={() => { setMediaLoadStatus(prev => { const newMap = new Map(prev); newMap.set(getItemResourceId(item), 'error'); return newMap; }); }} /> {mediaLoadStatus.get(getItemResourceId(item)) !== 'loaded' && (
{item.mediaType === 'video' ? ( ) : ( )}
)}
) : (
暂无封面
)} {isSelectionMode && ( <>
{ e.stopPropagation(); handleToggleSelect(getItemResourceId(item)); }} > {isSelectedItem && ( )}
{isSelectedItem && filterMedia === 'video' && item.imagePx && !checkVideoSize(item.imagePx) && (
视频实际尺寸可能不符合平台推送要求
)} )} {!isSelectionMode && ( <>
{ (e.currentTarget as HTMLElement).style.opacity = '1'; }} onMouseLeave={(e) => { (e.currentTarget as HTMLElement).style.opacity = '0'; }} > 点击预览
{ e.stopPropagation(); const url = `${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${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); }} >
)} {isSelectionMode && isSelectedItem && (
)}
); })}
{/* 分组内加载更多 */} {group.total && group.total > group.items.length && (
)}
))} {/* 加载更多按钮 */} {recordlist.length > 0 && Totalnumber > recordlist.length && (
)}
)} {/* 推送任务历史弹窗 */} setUploadHistoryModalVisible(false)} footer={null} width={800} style={{ borderRadius: 8 }} mask={{ closable: false }} >
setUnifiedFileName(e.target.value)} placeholder="输入名称后点击应用" style={{ flex: 1, borderRadius: 8 }} size="small" />
{(() => { const itemMap = new Map(); recordlist.forEach((group: any) => { group.items.forEach((item: any) => { const resourceId = getItemResourceId(item); itemMap.set(resourceId, item); }); }); return Array.from(selectedItems).map((itemId) => { const item = itemMap.get(itemId); return (
{(() => { const coverField = filterMedia === 'video' ? item?.videoCoverUrl : item?.imageUrl; if (!coverField) { return 预览; } const baseUrl = import.meta.env.VITE_API_BASE || "http://localhost:8000"; const cleanPath = coverField.startsWith('/') ? coverField.slice(1) : coverField; const cleanBase = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl; const coverUrl = `${cleanBase}/static/${cleanPath}&w=300&q=50`; return ( ); })()}
{item?.fileName || `素材 ${item.id}`}
{ const newName = e.target.value; const newNames = new Map(materialFileNames); newNames.set(itemId, newName); setMaterialFileNames(newNames); }} onBlur={() => { const newName = materialFileNames.get(itemId) || item?.fileName || ''; if (newName.trim()) { handleUpdateFileName(itemId, newName); } }} onKeyDown={(e) => { if (e.key === 'Enter') { const newName = materialFileNames.get(itemId) || item?.fileName || ''; if (newName.trim()) { handleUpdateFileName(itemId, newName); } } }} placeholder="输入新名称" style={{ width: 200, borderRadius: 4 }} size="small" />
); }); })()}
是否开启前测: setPreTestTemplate(value)} style={{ width: 200 }} disabled={isPreTest !== '1'} loading={preTestTemplatesLoading} placeholder={isPreTest !== '1' ? '请先开启前测' : '选择前测模板'} options={preTestTemplates.map((item) => ({ value: item.id, label: item.name, }))} />
{ setAccountTab(key as 'new' | 'history'); if (key === 'history') { loadHistoryOAuthList(); } }} items={[ { key: 'new', label: '新增账户', }, { key: 'history', label: '历史账户', }, ]} /> {accountTab === 'new' && (
{selectedOauthItems.map((oauthItem, index) => (
授权账户 {index + 1} {selectedOauthItems.length > 1 && ( )}