206957eb10
- Add V21 styles to WorkspaceDetail page (page-head, cards, buttons) - Create ProjectTasks.css and update ProjectTasks.tsx with V21 UI - Create business.css with shared V21 styles (buttons, cards, tables, tags) - Update InviteMemberModal with V21 modal styles - Update MemberList with V21 table and tag styles - Update PermissionMatrix with V21 card and table styles - Update QuotaDisplay with V21 card and progress styles - Update App.tsx and App.css with V21 design system
132 lines
4.9 KiB
TypeScript
132 lines
4.9 KiB
TypeScript
/**
|
||
* 项目任务中心 - V21 UI
|
||
*/
|
||
import React from 'react';
|
||
import { Alert, Button, Card, Empty, List, Progress, Space, Tag, Typography } from 'antd';
|
||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||
import { useParams } from 'react-router-dom';
|
||
import { getProjectTasks, retryProjectTask } from '@/api/tasks';
|
||
import '@/components/business/business.css';
|
||
import './ProjectTasks.css';
|
||
|
||
const statusMap: Record<string, { label: string; color: string }> = {
|
||
pending: { label: '排队中', color: 'default' },
|
||
processing: { label: '处理中', color: 'processing' },
|
||
running: { label: '运行中', color: 'processing' },
|
||
completed: { label: '已完成', color: 'success' },
|
||
failed: { label: '失败', color: 'error' },
|
||
cancelled: { label: '已取消', color: 'default' },
|
||
};
|
||
|
||
const taskTypeLabels: Record<string, string> = {
|
||
ingest: '素材导入',
|
||
generation: '视频生成',
|
||
};
|
||
|
||
const ProjectTasks: React.FC = () => {
|
||
const { id } = useParams<{ id: string }>();
|
||
const projectId = id || '';
|
||
const queryClient = useQueryClient();
|
||
|
||
const tasksQuery = useQuery({
|
||
queryKey: ['project-tasks', projectId],
|
||
queryFn: () => getProjectTasks(projectId),
|
||
enabled: !!projectId,
|
||
refetchInterval: 5000,
|
||
});
|
||
|
||
const retryMutation = useMutation({
|
||
mutationFn: ({ taskType, sourceId }: { taskType: string; sourceId: string }) =>
|
||
retryProjectTask(taskType, sourceId),
|
||
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['project-tasks', projectId] }),
|
||
});
|
||
|
||
return (
|
||
<div className="xx-project-tasks">
|
||
<div className="xx-page-head">
|
||
<div>
|
||
<h2>任务中心</h2>
|
||
<p>查看和管理项目中的所有任务</p>
|
||
</div>
|
||
<Button className="xx-ghost-btn" onClick={() => tasksQuery.refetch()}>
|
||
刷新
|
||
</Button>
|
||
</div>
|
||
|
||
<div className="xx-card">
|
||
{tasksQuery.isError && (
|
||
<Alert style={{ marginBottom: 16 }} type="error" showIcon message="任务列表加载失败" />
|
||
)}
|
||
{tasksQuery.data?.some((task) => task.retryable) && (
|
||
<Alert
|
||
style={{ marginBottom: 16 }}
|
||
type="warning"
|
||
showIcon
|
||
message="部分任务可重试"
|
||
description="失败任务可通过"重试"重新排队;系统会保留原始失败原因方便排查。"
|
||
/>
|
||
)}
|
||
<List
|
||
loading={tasksQuery.isLoading}
|
||
dataSource={tasksQuery.data || []}
|
||
locale={{
|
||
emptyText: (
|
||
<div className="xx-empty-state">
|
||
<div className="xx-empty-state-icon">📋</div>
|
||
<p>暂无任务,上传素材或发起生成后会出现在这里</p>
|
||
</div>
|
||
),
|
||
}}
|
||
renderItem={(task) => {
|
||
const status = statusMap[task.status] || { label: task.status, color: 'default' };
|
||
return (
|
||
<List.Item className="xx-task-item">
|
||
<List.Item.Meta
|
||
title={
|
||
<Space wrap className="xx-task-title">
|
||
<span className="xx-task-type">{taskTypeLabels[task.task_type] || task.task_type}</span>
|
||
<Tag color={status.color} className="xx-tag">{status.label}</Tag>
|
||
<Tag className="xx-tag xx-tag-indigo">{task.current_step}</Tag>
|
||
{task.retryable && (
|
||
<Button
|
||
size="small"
|
||
className="xx-ghost-btn"
|
||
loading={retryMutation.isPending}
|
||
onClick={() =>
|
||
retryMutation.mutate({ taskType: task.task_type, sourceId: task.source_id })
|
||
}
|
||
>
|
||
重试
|
||
</Button>
|
||
)}
|
||
</Space>
|
||
}
|
||
description={
|
||
<Space direction="vertical" style={{ width: '100%' }} className="xx-task-desc">
|
||
<Progress
|
||
percent={Math.round(task.progress || 0)}
|
||
size="small"
|
||
status={task.status === 'failed' ? 'exception' : undefined}
|
||
strokeColor={task.status === 'failed' ? '#ef4444' : '#4f46e5'}
|
||
/>
|
||
{task.user_message ? (
|
||
<Typography.Text type="danger">{task.user_message}</Typography.Text>
|
||
) : null}
|
||
<Typography.Text type="secondary" copyable style={{ fontSize: 12 }}>
|
||
任务 ID:{task.source_id}
|
||
</Typography.Text>
|
||
</Space>
|
||
}
|
||
/>
|
||
</List.Item>
|
||
);
|
||
}}
|
||
/>
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export const Component = ProjectTasks;
|
||
export default ProjectTasks;
|