import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import {
API,
copy,
isAdmin,
showError,
showSuccess,
timestamp2string,
} from '../helpers';
import {
Button,
Card,
Checkbox,
DatePicker,
Divider,
Input,
Layout,
Modal,
Progress,
Skeleton,
Table,
Tag,
Typography,
} from '@douyinfe/semi-ui';
import { ITEMS_PER_PAGE } from '../constants';
import {
IconEyeOpened,
IconSearch,
IconSetting,
} from '@douyinfe/semi-icons';
const { Text } = Typography;
const colors = [
'amber',
'blue',
'cyan',
'green',
'grey',
'indigo',
'light-blue',
'lime',
'orange',
'pink',
'purple',
'red',
'teal',
'violet',
'yellow',
];
// 定义列键值常量
const COLUMN_KEYS = {
SUBMIT_TIME: 'submit_time',
FINISH_TIME: 'finish_time',
DURATION: 'duration',
CHANNEL: 'channel',
PLATFORM: 'platform',
TYPE: 'type',
TASK_ID: 'task_id',
TASK_STATUS: 'task_status',
PROGRESS: 'progress',
FAIL_REASON: 'fail_reason',
};
const renderTimestamp = (timestampInSeconds) => {
const date = new Date(timestampInSeconds * 1000); // 从秒转换为毫秒
const year = date.getFullYear(); // 获取年份
const month = ('0' + (date.getMonth() + 1)).slice(-2); // 获取月份,从0开始需要+1,并保证两位数
const day = ('0' + date.getDate()).slice(-2); // 获取日期,并保证两位数
const hours = ('0' + date.getHours()).slice(-2); // 获取小时,并保证两位数
const minutes = ('0' + date.getMinutes()).slice(-2); // 获取分钟,并保证两位数
const seconds = ('0' + date.getSeconds()).slice(-2); // 获取秒钟,并保证两位数
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; // 格式化输出
};
function renderDuration(submit_time, finishTime) {
// 确保startTime和finishTime都是有效的时间戳
if (!submit_time || !finishTime) return 'N/A';
// 将时间戳转换为Date对象
const start = new Date(submit_time);
const finish = new Date(finishTime);
// 计算时间差(毫秒)
const durationMs = finish - start;
// 将时间差转换为秒,并保留一位小数
const durationSec = (durationMs / 1000).toFixed(1);
// 设置颜色:大于60秒则为红色,小于等于60秒则为绿色
const color = durationSec > 60 ? 'red' : 'green';
// 返回带有样式的颜色标签
return (
{durationSec} 秒
);
}
const LogsTable = () => {
const { t } = useTranslation();
const [isModalOpen, setIsModalOpen] = useState(false);
const [modalContent, setModalContent] = useState('');
// 列可见性状态
const [visibleColumns, setVisibleColumns] = useState({});
const [showColumnSelector, setShowColumnSelector] = useState(false);
const isAdminUser = isAdmin();
const [pageSize, setPageSize] = useState(ITEMS_PER_PAGE);
// 加载保存的列偏好设置
useEffect(() => {
const savedColumns = localStorage.getItem('task-logs-table-columns');
if (savedColumns) {
try {
const parsed = JSON.parse(savedColumns);
const defaults = getDefaultColumnVisibility();
const merged = { ...defaults, ...parsed };
setVisibleColumns(merged);
} catch (e) {
console.error('Failed to parse saved column preferences', e);
initDefaultColumns();
}
} else {
initDefaultColumns();
}
}, []);
// 获取默认列可见性
const getDefaultColumnVisibility = () => {
return {
[COLUMN_KEYS.SUBMIT_TIME]: true,
[COLUMN_KEYS.FINISH_TIME]: true,
[COLUMN_KEYS.DURATION]: true,
[COLUMN_KEYS.CHANNEL]: isAdminUser,
[COLUMN_KEYS.PLATFORM]: true,
[COLUMN_KEYS.TYPE]: true,
[COLUMN_KEYS.TASK_ID]: true,
[COLUMN_KEYS.TASK_STATUS]: true,
[COLUMN_KEYS.PROGRESS]: true,
[COLUMN_KEYS.FAIL_REASON]: true,
};
};
// 初始化默认列可见性
const initDefaultColumns = () => {
const defaults = getDefaultColumnVisibility();
setVisibleColumns(defaults);
localStorage.setItem('task-logs-table-columns', JSON.stringify(defaults));
};
// 处理列可见性变化
const handleColumnVisibilityChange = (columnKey, checked) => {
const updatedColumns = { ...visibleColumns, [columnKey]: checked };
setVisibleColumns(updatedColumns);
};
// 处理全选
const handleSelectAll = (checked) => {
const allKeys = Object.keys(COLUMN_KEYS).map((key) => COLUMN_KEYS[key]);
const updatedColumns = {};
allKeys.forEach((key) => {
if (key === COLUMN_KEYS.CHANNEL && !isAdminUser) {
updatedColumns[key] = false;
} else {
updatedColumns[key] = checked;
}
});
setVisibleColumns(updatedColumns);
};
// 更新表格时保存列可见性
useEffect(() => {
if (Object.keys(visibleColumns).length > 0) {
localStorage.setItem('task-logs-table-columns', JSON.stringify(visibleColumns));
}
}, [visibleColumns]);
const renderType = (type) => {
switch (type) {
case 'MUSIC':
return (
{t('生成音乐')}
);
case 'LYRICS':
return (
{t('生成歌词')}
);
default:
return (
{t('未知')}
);
}
};
const renderPlatform = (type) => {
switch (type) {
case 'suno':
return (
Suno
);
default:
return (
{t('未知')}
);
}
};
const renderStatus = (type) => {
switch (type) {
case 'SUCCESS':
return (
{t('成功')}
);
case 'NOT_START':
return (
{t('未启动')}
);
case 'SUBMITTED':
return (
{t('队列中')}
);
case 'IN_PROGRESS':
return (
{t('执行中')}
);
case 'FAILURE':
return (
{t('失败')}
);
case 'QUEUED':
return (
{t('排队中')}
);
case 'UNKNOWN':
return (
{t('未知')}
);
case '':
return (
{t('正在提交')}
);
default:
return (
{t('未知')}
);
}
};
// 定义所有列
const allColumns = [
{
key: COLUMN_KEYS.SUBMIT_TIME,
title: t('提交时间'),
dataIndex: 'submit_time',
render: (text, record, index) => {
return
{text ? renderTimestamp(text) : '-'}
;
},
},
{
key: COLUMN_KEYS.FINISH_TIME,
title: t('结束时间'),
dataIndex: 'finish_time',
render: (text, record, index) => {
return {text ? renderTimestamp(text) : '-'}
;
},
},
{
key: COLUMN_KEYS.DURATION,
title: t('花费时间'),
dataIndex: 'finish_time',
render: (finish, record) => {
return <>{finish ? renderDuration(record.submit_time, finish) : '-'}>;
},
},
{
key: COLUMN_KEYS.CHANNEL,
title: t('渠道'),
dataIndex: 'channel_id',
className: isAdminUser ? 'tableShow' : 'tableHiddle',
render: (text, record, index) => {
return isAdminUser ? (
{
copyText(text);
}}
>
{text}
) : (
<>>
);
},
},
{
key: COLUMN_KEYS.PLATFORM,
title: t('平台'),
dataIndex: 'platform',
render: (text, record, index) => {
return {renderPlatform(text)}
;
},
},
{
key: COLUMN_KEYS.TYPE,
title: t('类型'),
dataIndex: 'action',
render: (text, record, index) => {
return {renderType(text)}
;
},
},
{
key: COLUMN_KEYS.TASK_ID,
title: t('任务ID'),
dataIndex: 'task_id',
render: (text, record, index) => {
return (
{
setModalContent(JSON.stringify(record, null, 2));
setIsModalOpen(true);
}}
>
{text}
);
},
},
{
key: COLUMN_KEYS.TASK_STATUS,
title: t('任务状态'),
dataIndex: 'status',
render: (text, record, index) => {
return {renderStatus(text)}
;
},
},
{
key: COLUMN_KEYS.PROGRESS,
title: t('进度'),
dataIndex: 'progress',
render: (text, record, index) => {
return (
{
isNaN(text?.replace('%', '')) ? (
text || '-'
) : (
)
}
);
},
},
{
key: COLUMN_KEYS.FAIL_REASON,
title: t('失败原因'),
dataIndex: 'fail_reason',
render: (text, record, index) => {
if (!text) {
return t('无');
}
return (
{
setModalContent(text);
setIsModalOpen(true);
}}
>
{text}
);
},
},
];
// 根据可见性设置过滤列
const getVisibleColumns = () => {
return allColumns.filter((column) => visibleColumns[column.key]);
};
const [logs, setLogs] = useState([]);
const [loading, setLoading] = useState(true);
const [activePage, setActivePage] = useState(1);
const [logCount, setLogCount] = useState(ITEMS_PER_PAGE);
const [logType] = useState(0);
let now = new Date();
// 初始化start_timestamp为前一天
let zeroNow = new Date(now.getFullYear(), now.getMonth(), now.getDate());
const [inputs, setInputs] = useState({
channel_id: '',
task_id: '',
start_timestamp: timestamp2string(zeroNow.getTime() / 1000),
end_timestamp: '',
});
const { channel_id, task_id, start_timestamp, end_timestamp } = inputs;
const handleInputChange = (value, name) => {
setInputs((inputs) => ({ ...inputs, [name]: value }));
};
const setLogsFormat = (logs) => {
for (let i = 0; i < logs.length; i++) {
logs[i].timestamp2string = timestamp2string(logs[i].created_at);
logs[i].key = '' + logs[i].id;
}
// data.key = '' + data.id
setLogs(logs);
setLogCount(logs.length + ITEMS_PER_PAGE);
// console.log(logCount);
};
const loadLogs = async (startIdx, pageSize = ITEMS_PER_PAGE) => {
setLoading(true);
let url = '';
let localStartTimestamp = parseInt(Date.parse(start_timestamp) / 1000);
let localEndTimestamp = parseInt(Date.parse(end_timestamp) / 1000);
if (isAdminUser) {
url = `/api/task/?p=${startIdx}&page_size=${pageSize}&channel_id=${channel_id}&task_id=${task_id}&start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}`;
} else {
url = `/api/task/self?p=${startIdx}&page_size=${pageSize}&task_id=${task_id}&start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}`;
}
const res = await API.get(url);
let { success, message, data } = res.data;
if (success) {
if (startIdx === 0) {
setLogsFormat(data);
} else {
let newLogs = [...logs];
newLogs.splice(startIdx * pageSize, data.length, ...data);
setLogsFormat(newLogs);
}
} else {
showError(message);
}
setLoading(false);
};
const pageData = logs.slice(
(activePage - 1) * pageSize,
activePage * pageSize,
);
const handlePageChange = (page) => {
setActivePage(page);
if (page === Math.ceil(logs.length / pageSize) + 1) {
loadLogs(page - 1, pageSize).then((r) => { });
}
};
const handlePageSizeChange = async (size) => {
localStorage.setItem('task-page-size', size + '');
setPageSize(size);
setActivePage(1);
await loadLogs(0, size);
};
const refresh = async () => {
setActivePage(1);
await loadLogs(0, pageSize);
};
const copyText = async (text) => {
if (await copy(text)) {
showSuccess(t('已复制:') + text);
} else {
Modal.error({ title: t('无法复制到剪贴板,请手动复制'), content: text });
}
};
useEffect(() => {
const localPageSize = parseInt(localStorage.getItem('task-page-size')) || ITEMS_PER_PAGE;
setPageSize(localPageSize);
loadLogs(0, localPageSize).then();
}, [logType]);
// 列选择器模态框
const renderColumnSelector = () => {
return (
setShowColumnSelector(false)}
footer={
}
>
v === true)}
indeterminate={
Object.values(visibleColumns).some((v) => v === true) &&
!Object.values(visibleColumns).every((v) => v === true)
}
onChange={(e) => handleSelectAll(e.target.checked)}
>
{t('全选')}
{allColumns.map((column) => {
// 为非管理员用户跳过管理员专用列
if (!isAdminUser && column.key === COLUMN_KEYS.CHANNEL) {
return null;
}
return (
handleColumnVisibilityChange(column.key, e.target.checked)
}
>
{column.title}
);
})}
);
};
return (
<>
{renderColumnSelector()}
{loading ? (
) : (
{t('任务记录')}
)}
{/* 搜索表单区域 */}
{/* 操作按钮区域 */}
}
onClick={() => setShowColumnSelector(true)}
className="!rounded-full"
>
{t('列设置')}
}
shadows='hover'
>
t('第 {{start}} - {{end}} 条,共 {{total}} 条', {
start: page.currentStart,
end: page.currentEnd,
total: logCount,
}),
currentPage: activePage,
pageSize: pageSize,
total: logCount,
pageSizeOptions: [10, 20, 50, 100],
showSizeChanger: true,
onPageSizeChange: (size) => {
handlePageSizeChange(size);
},
onPageChange: handlePageChange,
}}
/>
setIsModalOpen(false)}
onCancel={() => setIsModalOpen(false)}
closable={null}
bodyStyle={{ height: '400px', overflow: 'auto' }} // 设置模态框内容区域样式
width={800} // 设置模态框宽度
>
{modalContent}
>
);
};
export default LogsTable;