完成素材云列表添加历史功能
This commit is contained in:
+112
-112
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -28,7 +28,7 @@
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
<script type="module" crossorigin src="/assets/index-CyfX-OBY.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-DscF86oa.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-eLRg4pQk.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -701,6 +701,7 @@ export async function getResourcesMaterialList(params: ResourcesMaterialListPara
|
||||
return api.get(`/resources-material/list?${query.toString()}`);
|
||||
}
|
||||
|
||||
// 全部开户方式列表
|
||||
export interface OpenTypeItem {
|
||||
id: string;
|
||||
openType: number;
|
||||
@@ -708,7 +709,37 @@ export interface OpenTypeItem {
|
||||
description: string;
|
||||
thumb?: string;
|
||||
}
|
||||
|
||||
export async function getOpenTypeAll(): Promise<{ data: OpenTypeItem[] }> {
|
||||
return api.get('/open-type/open_type_all');
|
||||
}
|
||||
|
||||
// 获取授权账户列表,支持按广告主ID、授权ID、广告账户名称筛选
|
||||
export interface OAuthAccountParams {
|
||||
advertiser_id: string;
|
||||
oauth_id: string;
|
||||
advertiser_name: string;
|
||||
page?: number;
|
||||
page_size?: number;
|
||||
}
|
||||
export async function getOAuthAccountList(params: OAuthAccountParams): Promise<any> {
|
||||
const query = new URLSearchParams();
|
||||
if (params.advertiser_id) query.set('advertiser_id', params.advertiser_id);
|
||||
if (params.oauth_id) query.set('oauth_id', params.oauth_id);
|
||||
if (params.advertiser_name) query.set('advertiser_name', params.advertiser_name);
|
||||
if (params.page !== undefined) query.set('page', String(params.page));
|
||||
if (params.page_size !== undefined) query.set('page_size', String(params.page_size));
|
||||
return api.get(`/oauth-account/list?${query.toString()}`);
|
||||
}
|
||||
// 软删除授权账户
|
||||
export interface DeleteOAuthAccountParams {
|
||||
id: string;
|
||||
}
|
||||
export async function deleteOAuthAccount(params: DeleteOAuthAccountParams): Promise<any> {
|
||||
const query = new URLSearchParams();
|
||||
if (params.id) query.set('id', params.id);
|
||||
return api.get(`/oauth-account/delete?${query.toString()}`);
|
||||
}
|
||||
// 获取用户全部授权账户列表
|
||||
export async function getAllOAuthAccountList(): Promise<any> {
|
||||
return api.post(`/upload-material/oauth_account_list`);
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Button, Table, Tag, Modal, Select, App, Input, Pagination, Typography } from 'antd';
|
||||
import { Button, Table, Tag, Modal, Select, App, Input, Pagination, Typography, Space } from 'antd';
|
||||
import { PlusOutlined, LockOutlined } from '@ant-design/icons';
|
||||
import { getOAuthList, requestOAuth, getOpenTypeAll } from '../api';
|
||||
|
||||
@@ -151,13 +151,13 @@ const AuthorizationPage: React.FC = () => {
|
||||
},
|
||||
{
|
||||
title: '授权账户ID',
|
||||
dataIndex: 'accountId',
|
||||
key: 'accountId',
|
||||
dataIndex: 'advertiserId',
|
||||
key: 'advertiserId',
|
||||
width: 160,
|
||||
},
|
||||
{
|
||||
title: '授权账户名称',
|
||||
dataIndex: 'accountName',
|
||||
dataIndex: 'advertiserName',
|
||||
key: 'accountName',
|
||||
},
|
||||
{
|
||||
@@ -247,6 +247,19 @@ const AuthorizationPage: React.FC = () => {
|
||||
width: 160,
|
||||
render: (text: string) => <span style={{ color: '#64748b' }}>{formatDateTime(text)}</span>,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'action',
|
||||
key: 'action',
|
||||
width: 140,
|
||||
render: (text: string) => (
|
||||
<Space>
|
||||
<Button type="primary" size="small" >
|
||||
查看授权账户
|
||||
</Button>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const tableData = authorizations.map((item, index) => ({
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
import React, { useEffect, useState, useLayoutEffect, useRef, useCallback } from 'react';
|
||||
import { Button, Empty, Input, Select, Space, Typography, Tag, message, Modal, Table, DatePicker } from 'antd';
|
||||
import { Button, Empty, Input, Select, Space, Typography, Tag, message, Modal, Table, DatePicker, Tabs, Transfer } from 'antd';
|
||||
import dayjs from 'dayjs';
|
||||
import JSZip from 'jszip';
|
||||
import {
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
UploadOutlined,
|
||||
|
||||
} from '@ant-design/icons';
|
||||
import { gethistory, gethistoryItems, getOAuthList, asyncBatchUploadMaterial, updateFilename, getUploadHistory } from '../api';
|
||||
import { gethistory, gethistoryItems, getOAuthList, asyncBatchUploadMaterial, updateFilename, getUploadHistory, getAllOAuthAccountList, getOpenTypeAll } from '../api';
|
||||
|
||||
const { Search } = Input;
|
||||
const { Text } = Typography;
|
||||
@@ -50,11 +50,24 @@ const GeneratedRecord: React.FC = () => {
|
||||
const [selectedOauthItems, setSelectedOauthItems] = useState<({ value: string; label: string } | undefined)[]>([undefined]);
|
||||
const [materialFileNames, setMaterialFileNames] = useState<Map<string, string>>(new Map());
|
||||
const [unifiedFileName, setUnifiedFileName] = useState('');
|
||||
const updateFilenameDebounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const updateFilenameDebounceRef = useRef<Map<string, ReturnType<typeof setTimeout>>>(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<boolean[]>([false]);
|
||||
|
||||
const [accountTab, setAccountTab] = useState<'new' | 'history'>('new');
|
||||
const [historyOAuthList, setHistoryOAuthList] = useState<any[]>([]);
|
||||
const [historyOAuthLoading, setHistoryOAuthLoading] = useState(false);
|
||||
const [selectedHistoryAccounts, setSelectedHistoryAccounts] = useState<any[]>([]);
|
||||
const [openTypeMap, setOpenTypeMap] = useState<Record<number, string>>({});
|
||||
|
||||
// 上传任务历史弹窗相关状态
|
||||
const [uploadHistoryModalVisible, setUploadHistoryModalVisible] = useState(false);
|
||||
const [uploadHistoryList, setUploadHistoryList] = useState<any[]>([]);
|
||||
@@ -63,15 +76,12 @@ const GeneratedRecord: React.FC = () => {
|
||||
const [uploadHistoryPageSize, setUploadHistoryPageSize] = useState(10);
|
||||
const [uploadHistoryLoading, setUploadHistoryLoading] = useState(false);
|
||||
const [uploadHistoryStatus, setUploadHistoryStatus] = useState<string>('');
|
||||
|
||||
// 多选相关状态
|
||||
const [isSelectionMode, setIsSelectionMode] = useState(false);
|
||||
const [selectedItems, setSelectedItems] = useState<Set<string>>(new Set());
|
||||
|
||||
// 全局 Intersection Observer 实例(复用,避免创建过多实例)
|
||||
let globalObserver: IntersectionObserver | null = null;
|
||||
const observerCallbacks = new Map<HTMLElement, () => void>();
|
||||
|
||||
// 加载队列控制 - 限制同时加载的媒体数量
|
||||
// 使用优先级队列,完全在可视区的元素优先加载
|
||||
interface LoadTask {
|
||||
@@ -84,7 +94,6 @@ const GeneratedRecord: React.FC = () => {
|
||||
const MAX_VISIBLE_LOADS = 8; // 可视区最大并发数(不受队列限制)
|
||||
let currentLoads = 0;
|
||||
let visibleLoads = 0; // 当前可视区加载数
|
||||
|
||||
const enqueueLoad = (callback: () => void, element: HTMLElement, isFullyVisible: boolean) => {
|
||||
// 完全可见的元素立即加载,不受队列限制
|
||||
if (isFullyVisible && visibleLoads < MAX_VISIBLE_LOADS) {
|
||||
@@ -150,7 +159,6 @@ const GeneratedRecord: React.FC = () => {
|
||||
}
|
||||
return globalObserver;
|
||||
};
|
||||
|
||||
// 从URL中提取exp时间戳(支持相对路径和完整URL)
|
||||
const extractExpTimestamp = (url: string): number | null => {
|
||||
if (!url) return null;
|
||||
@@ -181,7 +189,6 @@ const GeneratedRecord: React.FC = () => {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 检查媒体是否过期
|
||||
const isMediaExpired = (url: string): boolean => {
|
||||
const expTimestamp = extractExpTimestamp(url);
|
||||
@@ -191,7 +198,6 @@ const GeneratedRecord: React.FC = () => {
|
||||
const currentTimestamp = Math.floor(Date.now() / 1000);
|
||||
return currentTimestamp > expTimestamp;
|
||||
};
|
||||
|
||||
// 懒加载媒体组件
|
||||
const LazyMedia: React.FC<{
|
||||
item: any;
|
||||
@@ -220,18 +226,14 @@ const GeneratedRecord: React.FC = () => {
|
||||
useEffect(() => {
|
||||
// 如果已过期,不需要监听
|
||||
if (isExpired) return;
|
||||
|
||||
const placeholder = placeholderRef.current;
|
||||
if (!placeholder) return;
|
||||
|
||||
const observer = getGlobalObserver();
|
||||
const callback = () => {
|
||||
setIsLoading(true);
|
||||
};
|
||||
|
||||
observerCallbacks.set(placeholder, callback);
|
||||
observer.observe(placeholder);
|
||||
|
||||
return () => {
|
||||
observerCallbacks.delete(placeholder);
|
||||
observer.unobserve(placeholder);
|
||||
@@ -532,7 +534,6 @@ const GeneratedRecord: React.FC = () => {
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// 下载文件
|
||||
const handleDownload = (item: any) => {
|
||||
const url = `${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${item.videoUrl || item.imageUrl}`;
|
||||
@@ -543,7 +544,6 @@ const GeneratedRecord: React.FC = () => {
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
};
|
||||
|
||||
// 预览文件
|
||||
const handlePreview = (item: any) => {
|
||||
setPreviewItem(item);
|
||||
@@ -551,7 +551,6 @@ const GeneratedRecord: React.FC = () => {
|
||||
// 触发事件通知布局组件关闭浮动按钮
|
||||
window.dispatchEvent(new Event('previewOpen'));
|
||||
};
|
||||
|
||||
// 关闭预览并暂停视频
|
||||
const handleClosePreview = () => {
|
||||
// 方法1: 使用 ref
|
||||
@@ -567,17 +566,14 @@ const GeneratedRecord: React.FC = () => {
|
||||
});
|
||||
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);
|
||||
};
|
||||
|
||||
// 多选相关函数
|
||||
const handleToggleSelect = (itemId: string) => {
|
||||
setSelectedItems(prev => {
|
||||
@@ -590,7 +586,6 @@ const GeneratedRecord: React.FC = () => {
|
||||
return newSet;
|
||||
});
|
||||
};
|
||||
|
||||
const handleSelectAll = () => {
|
||||
const allItemIds = recordlist.flatMap((group: any) =>
|
||||
group.items.map((item: any) => getItemResourceId(item))
|
||||
@@ -601,7 +596,6 @@ const GeneratedRecord: React.FC = () => {
|
||||
setSelectedItems(new Set(allItemIds));
|
||||
}
|
||||
};
|
||||
|
||||
const handleDownloadSelected = async () => {
|
||||
if (selectedItems.size === 0) {
|
||||
message.warning('请先选择要下载的媒体');
|
||||
@@ -675,8 +669,6 @@ const GeneratedRecord: React.FC = () => {
|
||||
message.destroy('downloadProgress');
|
||||
message.success(`下载完成,共 ${successCount} 个文件`);
|
||||
};
|
||||
|
||||
|
||||
const handleBatchUploadSelected = () => {
|
||||
if (selectedItems.size === 0) {
|
||||
message.warning('请先选择要上传的媒体');
|
||||
@@ -687,7 +679,6 @@ const GeneratedRecord: React.FC = () => {
|
||||
setSelectedOauthItems([undefined]);
|
||||
setUploadConfigModalVisible(true);
|
||||
};
|
||||
|
||||
const handleSinglePushToMedia = () => {
|
||||
if (!previewItem) return;
|
||||
const resourceId = getItemResourceId(previewItem);
|
||||
@@ -699,7 +690,6 @@ const GeneratedRecord: React.FC = () => {
|
||||
setUnifiedFileName('');
|
||||
setUploadConfigModalVisible(true);
|
||||
};
|
||||
|
||||
const loadOAuthList = async (page: number, pageSize: number) => {
|
||||
setOauthLoading(true);
|
||||
try {
|
||||
@@ -715,7 +705,30 @@ const GeneratedRecord: React.FC = () => {
|
||||
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<number, string> = {};
|
||||
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 {
|
||||
@@ -735,38 +748,27 @@ const GeneratedRecord: React.FC = () => {
|
||||
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 () => {
|
||||
const validOauthItems = selectedOauthItems.filter(item => item !== undefined);
|
||||
if (validOauthItems.length === 0) {
|
||||
message.warning('请先选择授权账户');
|
||||
return;
|
||||
}
|
||||
if (selectedItems.size === 0) {
|
||||
message.warning('请先选择要上传的媒体');
|
||||
return;
|
||||
}
|
||||
setUploading(true);
|
||||
try {
|
||||
const itemMap = new Map<string, any>();
|
||||
recordlist.forEach((group: any) => {
|
||||
group.items.forEach((item: any) => {
|
||||
@@ -774,25 +776,26 @@ const GeneratedRecord: React.FC = () => {
|
||||
itemMap.set(resourceId, item);
|
||||
});
|
||||
});
|
||||
|
||||
const tasks: {
|
||||
let tasks: {
|
||||
advertiser_ids: string[];
|
||||
resource_ids: 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<string, string[]>();
|
||||
|
||||
for (const itemId of selectedItems) {
|
||||
const item = itemMap.get(itemId);
|
||||
let sourceModel: string;
|
||||
@@ -801,13 +804,11 @@ const GeneratedRecord: React.FC = () => {
|
||||
} 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,
|
||||
@@ -817,7 +818,39 @@ const GeneratedRecord: React.FC = () => {
|
||||
});
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (selectedHistoryAccounts.length === 0) {
|
||||
message.warning('请先选择历史账户');
|
||||
return;
|
||||
}
|
||||
for (const account of selectedHistoryAccounts) {
|
||||
const sourceModelMap = new Map<string, string[]>();
|
||||
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,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
setUploading(true);
|
||||
try {
|
||||
const res = await asyncBatchUploadMaterial({ tasks });
|
||||
if (res.errors?.length > 0) {
|
||||
message.warning(res.message);
|
||||
@@ -828,11 +861,11 @@ const GeneratedRecord: React.FC = () => {
|
||||
}
|
||||
setIsSelectionMode(false);
|
||||
setSelectedItems(new Set());
|
||||
// 关闭弹窗并清理状态
|
||||
setUploadConfigModalVisible(false);
|
||||
setAccountIdLists([[]]);
|
||||
setAccountIdInputs(['']);
|
||||
setSelectedOauthItems([undefined]);
|
||||
setSelectedHistoryAccounts([]);
|
||||
setMaterialFileNames(new Map());
|
||||
setUnifiedFileName('');
|
||||
} catch (error: any) {
|
||||
@@ -842,7 +875,6 @@ const GeneratedRecord: React.FC = () => {
|
||||
setUploading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 更新文件名函数
|
||||
const handleUpdateFileName = async (sourceId: string, newFileName: string) => {
|
||||
if (!newFileName.trim()) return;
|
||||
@@ -871,7 +903,6 @@ const GeneratedRecord: React.FC = () => {
|
||||
message.error(error.message || '文件名更新失败');
|
||||
}
|
||||
};
|
||||
|
||||
// 批量更新文件名函数
|
||||
const handleBatchUpdateFileName = async (sourceIds: string[], newFileName: string) => {
|
||||
if (!newFileName.trim() || sourceIds.length === 0) return;
|
||||
@@ -910,12 +941,10 @@ const GeneratedRecord: React.FC = () => {
|
||||
message.error(error.message || '文件名更新失败');
|
||||
}
|
||||
};
|
||||
|
||||
// 日期选择器变化处理函数
|
||||
const handleDateChange = (dateString: string) => {
|
||||
setSelectedDate(dateString);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(true);
|
||||
let parameters = '';
|
||||
@@ -924,10 +953,8 @@ const GeneratedRecord: React.FC = () => {
|
||||
} else {
|
||||
parameters = `?gen_type=${filterMedia}&page=${Pagebreak.page}&page_size=${Pagebreak.pageSize}`;
|
||||
}
|
||||
|
||||
if (selectedDate) {
|
||||
let parameters = ``;
|
||||
|
||||
if (filterType === 'project') {
|
||||
parameters = `${selectedDate}?gen_type=${filterMedia}&history_source=generation_record&page=${Pagebreak.page}&page_size=${Pagebreak.pageSize}`;
|
||||
} else {
|
||||
@@ -950,11 +977,9 @@ const GeneratedRecord: React.FC = () => {
|
||||
}).finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
|
||||
try {
|
||||
} catch (err) {
|
||||
} finally {
|
||||
|
||||
}
|
||||
} else {
|
||||
gethistory(parameters).then((res: any) => {
|
||||
@@ -977,10 +1002,7 @@ const GeneratedRecord: React.FC = () => {
|
||||
setLoading(false);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}, [filterType, filterMedia, Pagebreak.page, selectedDate]);
|
||||
|
||||
// 加载更多
|
||||
const handleLoadMore = () => {
|
||||
if (loading) return;
|
||||
@@ -989,28 +1011,21 @@ const GeneratedRecord: React.FC = () => {
|
||||
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 parameters = ``;
|
||||
|
||||
if (filterType === 'project') {
|
||||
parameters = `${time}?gen_type=${filterMedia}&history_source=generation_record&page=${addpage}&page_size=${Pagebreak.pageSize}`;
|
||||
} else {
|
||||
parameters = `${time}?gen_type=${filterMedia}&page=${addpage}&page_size=${Pagebreak.pageSize}`;
|
||||
}
|
||||
|
||||
try {
|
||||
const res: any = await gethistoryItems(parameters);
|
||||
|
||||
// gethistoryItems 返回数组,直接使用
|
||||
const newItems: any[] = res.items || [];
|
||||
|
||||
if (newItems && newItems.length > 0) {
|
||||
setRecordList(prev => prev.map(group => {
|
||||
if (group.generatedDate === time) {
|
||||
@@ -1032,7 +1047,6 @@ const GeneratedRecord: React.FC = () => {
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 当筛选条件改变时,重置页码
|
||||
useEffect(() => {
|
||||
setPagebreak(prev => ({
|
||||
@@ -1040,7 +1054,6 @@ const GeneratedRecord: React.FC = () => {
|
||||
page: 1
|
||||
}));
|
||||
}, [filterType, filterMedia]);
|
||||
|
||||
return (
|
||||
<div style={{ minHeight: 'calc(100vh - 90px)', background: '#ffffffff', overflowY: 'auto' }} >
|
||||
{/* 操作栏:筛选 + 上传按钮 */}
|
||||
@@ -1175,7 +1188,6 @@ const GeneratedRecord: React.FC = () => {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Second row filter: 视频 / 图片 */}
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
@@ -1275,7 +1287,6 @@ const GeneratedRecord: React.FC = () => {
|
||||
查询上传任务历史
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Content area */}
|
||||
{recordlist.length === 0 ? (
|
||||
<Empty
|
||||
@@ -1358,7 +1369,6 @@ const GeneratedRecord: React.FC = () => {
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 上传配置弹窗 */}
|
||||
<Modal
|
||||
title={selectedItems.size === 1 ? '上传配置' : '批量上传配置'}
|
||||
@@ -1477,13 +1487,20 @@ const GeneratedRecord: React.FC = () => {
|
||||
const newNames = new Map(materialFileNames);
|
||||
newNames.set(itemId, newName);
|
||||
setMaterialFileNames(newNames);
|
||||
// 防抖调用API
|
||||
if (updateFilenameDebounceRef.current) {
|
||||
clearTimeout(updateFilenameDebounceRef.current);
|
||||
}
|
||||
updateFilenameDebounceRef.current = setTimeout(() => {
|
||||
}}
|
||||
onBlur={() => {
|
||||
const newName = materialFileNames.get(itemId) || item?.fileName || '';
|
||||
if (newName.trim()) {
|
||||
handleUpdateFileName(itemId, newName);
|
||||
}, 800);
|
||||
}
|
||||
}}
|
||||
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 }}
|
||||
@@ -1495,6 +1512,30 @@ const GeneratedRecord: React.FC = () => {
|
||||
})()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Tabs
|
||||
size="small"
|
||||
activeKey={accountTab}
|
||||
onChange={(key) => {
|
||||
setAccountTab(key as 'new' | 'history');
|
||||
if (key === 'history') {
|
||||
loadHistoryOAuthList();
|
||||
}
|
||||
}}
|
||||
items={[
|
||||
{
|
||||
key: 'new',
|
||||
label: '新增账户',
|
||||
},
|
||||
{
|
||||
key: 'history',
|
||||
label: '历史账户',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
{accountTab === 'new' && (
|
||||
<div>
|
||||
{selectedOauthItems.map((oauthItem, index) => (
|
||||
<div key={index} style={{ marginBottom: 16, padding: 12, border: '1px solid #e2e8f0', borderRadius: 8 }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 8 }}>
|
||||
@@ -1665,8 +1706,8 @@ const GeneratedRecord: React.FC = () => {
|
||||
setAccountIdLists(newAccountIdLists);
|
||||
}}
|
||||
placeholder="粘贴账户ID,每行一个或用逗号分隔,例如:
|
||||
10001,10002,10003
|
||||
10004"
|
||||
10001,10002,10003
|
||||
10004"
|
||||
rows={3}
|
||||
style={{ borderRadius: 8 }}
|
||||
/>
|
||||
@@ -1685,7 +1726,99 @@ const GeneratedRecord: React.FC = () => {
|
||||
>
|
||||
+ 新增授权账户组
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{accountTab === 'history' && (
|
||||
<div style={{ padding: 8 }}>
|
||||
{historyOAuthLoading ? (
|
||||
<div style={{ textAlign: 'center', padding: 40 }}>加载中...</div>
|
||||
) : historyOAuthList.length === 0 ? (
|
||||
<Empty description="暂无可选账户" />
|
||||
) : (
|
||||
<Transfer
|
||||
style={{ width: '100%' }}
|
||||
dataSource={historyOAuthList
|
||||
.filter(item => item.advertiserId)
|
||||
.map((item, index) => ({
|
||||
key: String(item.advertiserId) || `item-${index}`,
|
||||
title: String(item.advertiserId),
|
||||
description: item.advertiserName || '',
|
||||
...item,
|
||||
}))}
|
||||
targetKeys={selectedHistoryAccounts
|
||||
.filter(item => item.advertiserId)
|
||||
.map(item => String(item.advertiserId))}
|
||||
onChange={(targetKeys) => {
|
||||
const newSelected = historyOAuthList.filter(item =>
|
||||
item.advertiserId && targetKeys.includes(String(item.advertiserId))
|
||||
);
|
||||
setSelectedHistoryAccounts(newSelected);
|
||||
}}
|
||||
titles={['可选账户列表', '已选账户列表']}
|
||||
showSearch
|
||||
filterOption={(inputValue, item) =>
|
||||
String(item.advertiserId).toLowerCase().includes(inputValue.toLowerCase()) ||
|
||||
(item.advertiserName && item.advertiserName.toLowerCase().includes(inputValue.toLowerCase()))
|
||||
}
|
||||
>
|
||||
{({
|
||||
direction,
|
||||
filteredItems,
|
||||
onItemSelect,
|
||||
onItemSelectAll,
|
||||
selectedKeys: listSelectedKeys,
|
||||
disabled: listDisabled,
|
||||
}) => {
|
||||
const columns = [
|
||||
{
|
||||
title: '账户ID',
|
||||
dataIndex: 'advertiserId',
|
||||
key: 'advertiserId',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: '账户名称',
|
||||
dataIndex: 'advertiserName',
|
||||
key: 'advertiserName',
|
||||
width: 150,
|
||||
},
|
||||
];
|
||||
const rowSelection = {
|
||||
getCheckboxProps: () => ({ disabled: listDisabled }),
|
||||
onChange: (selectedRowKeys: string[]) => {
|
||||
onItemSelectAll(selectedRowKeys, 'replace');
|
||||
},
|
||||
selectedRowKeys: listSelectedKeys,
|
||||
selections: [Table.SELECTION_ALL, Table.SELECTION_INVERT, Table.SELECTION_NONE],
|
||||
};
|
||||
return (
|
||||
<Table
|
||||
rowSelection={rowSelection}
|
||||
columns={columns}
|
||||
dataSource={filteredItems}
|
||||
size="small"
|
||||
style={{ pointerEvents: listDisabled ? 'none' : undefined }}
|
||||
pagination={false}
|
||||
scroll={{ x: 'max-content', y: 400 }}
|
||||
onRow={({ key, disabled: itemDisabled }) => ({
|
||||
onClick: () => {
|
||||
if (itemDisabled || listDisabled) {
|
||||
return;
|
||||
}
|
||||
onItemSelect(key, !listSelectedKeys.includes(key));
|
||||
},
|
||||
})}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</Transfer>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
|
||||
</div>
|
||||
{/* 操作按钮 */}
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
@@ -1709,7 +1842,7 @@ const GeneratedRecord: React.FC = () => {
|
||||
type="primary"
|
||||
onClick={handleStartBatchUpload}
|
||||
loading={uploading}
|
||||
disabled={uploading || accountIdLists.every(list => list.length === 0)}
|
||||
disabled={uploading || (accountTab === 'new' ? accountIdLists.every(list => list.length === 0) : selectedHistoryAccounts.length === 0)}
|
||||
style={{ borderRadius: 8 }}
|
||||
>
|
||||
{uploading ? '上传中...' : '开始上传'}
|
||||
|
||||
Reference in New Issue
Block a user