完成素材云列表添加历史功能

This commit is contained in:
Lrd
2026-06-26 18:03:55 +08:00
parent 6120e03183
commit b26b9e6af1
6 changed files with 2736 additions and 395 deletions
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -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>
+32 -1
View File
@@ -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`);
}
+17 -4
View File
@@ -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
+211 -78
View File
@@ -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 ? '上传中...' : '开始上传'}