feat: unify task and rule workbenches

This commit is contained in:
lingniu
2026-07-19 05:52:52 +08:00
parent 63b50732cb
commit 860978fad8
8 changed files with 439 additions and 37 deletions

View File

@@ -1,6 +1,6 @@
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { IconChevronRight, IconClose, IconDownload, IconRefresh, IconSearch, IconSetting } from '@douyinfe/semi-icons';
import { Button, Card, CardGroup, Checkbox, Descriptions, Dropdown, Empty, Input, Progress, Select, SideSheet, Spin, Table, Tag, Typography } from '@douyinfe/semi-ui';
import { Button, Card, CardGroup, Checkbox, Descriptions, Dropdown, Empty, Input, Progress, Select, Spin, Table, Tag, Typography } from '@douyinfe/semi-ui';
import { FormEvent, KeyboardEvent, useCallback, useEffect, useMemo, useState } from 'react';
import { useSearchParams } from 'react-router-dom';
import { api } from '../../api/client';
@@ -144,11 +144,22 @@ function ExportJobsPanel({ showHeader = true }: { showHeader?: boolean }) {
const jobs = query.data ?? [];
const statusLabel = (status: string) => status === 'queued' ? '排队中' : status === 'running' ? '执行中' : status === 'completed' ? '已完成' : '失败';
const statusColor = (status: string) => status === 'completed' ? 'green' : status === 'running' ? 'blue' : status === 'failed' ? 'red' : 'grey';
const activeJobs = jobs.filter((job) => job.status === 'queued' || job.status === 'running').length;
const completedJobs = jobs.filter((job) => job.status === 'completed').length;
const failedJobs = jobs.filter((job) => job.status === 'failed').length;
return <Card className="v2-export-jobs" bodyStyle={{ padding: 0 }}>{showHeader ? <WorkspacePanelHeader title="导出任务" description="异步生成并保留可追溯下载记录" meta={`${jobs.length.toLocaleString('zh-CN')} 个任务`} /> : null}
{query.isPending || query.isError || !jobs.length ? null : <div className="v2-export-job-overview" role="list" aria-label="导出任务状态概览">
<span className={activeJobs ? 'is-active' : ''} role="listitem"><small></small><strong>{activeJobs}</strong><em></em></span>
<span className="is-completed" role="listitem"><small></small><strong>{completedJobs}</strong><em></em></span>
<span className={failedJobs ? 'is-failed' : ''} role="listitem"><small></small><strong>{failedJobs}</strong><em></em></span>
</div>}
<div className="v2-export-job-list">{query.isPending ? <div className="v2-history-side-loading"><Spin size="middle" tip="正在读取导出任务" /></div> : query.isError ? <Empty className="v2-history-side-empty" title="导出任务加载失败" description="请稍后刷新重试。" /> : !jobs.length ? <Empty className="v2-history-side-empty" title="暂无导出任务" description="创建任务后可在这里查看进度并下载。" /> : <CardGroup type="grid" spacing={0}>{jobs.slice(0, 6).map((job) => <Card className={`v2-export-job-card is-${job.status}`} key={job.id} title={<span className="v2-export-job-name" title={job.name}>{job.name}</span>} headerExtraContent={<Tag color={statusColor(job.status)} type="light" size="small">{statusLabel(job.status)}</Tag>} headerLine bodyStyle={{ padding: 0 }}>
<div className="v2-export-job-summary"><strong>{job.status === 'queued' ? '等待单并发执行' : job.status === 'running' ? `${job.processedRows.toLocaleString('zh-CN')} / ${job.totalRows.toLocaleString('zh-CN')}` : job.status === 'completed' ? `${job.rowCount.toLocaleString('zh-CN')} 行 · ${formatExportFileSize(job.fileSizeBytes)}` : job.error || '任务失败'}</strong><small>{job.ownerUsername || job.ownerName || '历史任务'} · {job.vehicleVins?.length ?? job.keywords.length} </small><small>{job.dateFrom && job.dateTo ? `${job.dateFrom.replace('T', ' ')}${job.dateTo.replace('T', ' ')}` : job.createdAt}</small></div>
<div className="v2-export-job-summary">
<strong>{job.status === 'queued' ? '等待单并发执行' : job.status === 'running' ? `${job.processedRows.toLocaleString('zh-CN')} / ${job.totalRows.toLocaleString('zh-CN')}` : job.status === 'completed' ? `${job.rowCount.toLocaleString('zh-CN')} 行 · ${formatExportFileSize(job.fileSizeBytes)}` : job.error || '任务失败'}</strong>
<span><small>{job.ownerUsername || job.ownerName || '历史任务'} · {job.vehicleVins?.length ?? job.keywords.length} </small>{job.downloadUrl ? <ExportDownloadButton id={job.id} /> : null}</span>
<small>{job.dateFrom && job.dateTo ? `${job.dateFrom.replace('T', ' ')}${job.dateTo.replace('T', ' ')}` : job.createdAt}</small>
</div>
{job.status === 'running' ? <Progress className="v2-export-job-progress" percent={job.progress} showInfo /> : null}
{job.downloadUrl ? <ExportDownloadButton id={job.id} /> : null}
</Card>)}</CardGroup>}</div>
</Card>;
}
@@ -562,17 +573,21 @@ export default function HistoryPage() {
>
{mobileLayout && selectedRow ? <EvidencePanel row={selectedRow} metrics={visibleMetrics} onClose={closeSelectedRow} showHeader={false} /> : null}
</WorkspaceDetailSideSheet>
<SideSheet
<WorkspaceSideSheet
className="v2-history-export-sidesheet"
variant="task"
visible={exportAllowed && exportJobsOpen}
aria-label="历史数据导出任务"
ariaLabel="历史数据导出任务"
placement={mobileLayout ? 'bottom' : 'right'}
width={mobileLayout ? undefined : 520}
height={mobileLayout ? 'min(76dvh, 680px)' : undefined}
title={<div className="v2-history-mobile-sheet-title"><strong></strong><span></span></div>}
title="导出任务"
description="查看后台生成进度与可追溯下载记录"
badge="后台任务"
badgeColor="blue"
onCancel={() => setExportJobsOpen(false)}
>
{exportJobsOpen ? <ExportJobsPanel showHeader={false} /> : null}
</SideSheet>
</WorkspaceSideSheet>
</div>;
}