feat: unify export task workspace

This commit is contained in:
lingniu
2026-07-19 07:49:13 +08:00
parent 3e80da1b73
commit 6795a8ea6d
5 changed files with 133 additions and 127 deletions

View File

@@ -283,8 +283,11 @@ test('renders only selectable Semi history cards on mobile', async () => {
fireEvent.click(screen.getByRole('menuitem', { name: /导出任务/ })); fireEvent.click(screen.getByRole('menuitem', { name: /导出任务/ }));
expect(await screen.findByRole('dialog', { name: '历史数据导出任务' })).toBeInTheDocument(); expect(await screen.findByRole('dialog', { name: '历史数据导出任务' })).toBeInTheDocument();
expect(document.querySelector('.v2-history-export-sidesheet')).toHaveClass('semi-sidesheet-bottom', 'v2-workspace-task-sidesheet'); expect(document.querySelector('.v2-history-export-sidesheet')).toHaveClass('semi-sidesheet-bottom', 'v2-workspace-task-sidesheet');
expect(document.querySelector('.v2-history-export-sidesheet .semi-sidesheet-inner')).toHaveStyle({ height: 'min(76dvh, 680px)' }); expect(document.querySelector('.v2-history-export-sidesheet .semi-sidesheet-inner')).toHaveStyle({ height: 'min(82dvh, 720px)' });
await waitFor(() => expect(mocks.historyExports).toHaveBeenCalledTimes(1)); await waitFor(() => expect(mocks.historyExports).toHaveBeenCalledTimes(1));
expect(screen.getByRole('list', { name: '历史数据导出任务摘要' })).toHaveTextContent('生成中0排队与执行可下载0已完成任务需处理0失败任务');
expect(screen.getByRole('button', { name: '刷新导出任务' })).toBeInTheDocument();
expect(screen.getByRole('button', { name: '完成' })).toBeInTheDocument();
expect(screen.getByText('暂无导出任务')).toBeInTheDocument(); expect(screen.getByText('暂无导出任务')).toBeInTheDocument();
fireEvent.click(screen.getByRole('button', { name: '关闭历史数据导出任务' })); fireEvent.click(screen.getByRole('button', { name: '关闭历史数据导出任务' }));
expect(toolsButton).toHaveAttribute('aria-expanded', 'false'); expect(toolsButton).toHaveAttribute('aria-expanded', 'false');
@@ -388,9 +391,12 @@ test('shows only the authenticated customer export workspace with owner and scop
fireEvent.click(await screen.findByRole('button', { name: '查看导出任务' })); fireEvent.click(await screen.findByRole('button', { name: '查看导出任务' }));
expect(await screen.findByText(/customer-a · 1 辆/)).toBeInTheDocument(); expect(await screen.findByText(/customer-a · 1 辆/)).toBeInTheDocument();
expect(screen.getByRole('dialog', { name: '历史数据导出任务' })).toBeInTheDocument(); expect(screen.getByRole('dialog', { name: '历史数据导出任务' })).toBeInTheDocument();
expect(screen.getByRole('list', { name: '导出任务状态概览' })).toHaveTextContent('生成中0排队与执行可下载1已完成任务需处理0失败任务'); expect(screen.getByRole('list', { name: '历史数据导出任务摘要' })).toHaveTextContent('生成中0排队与执行可下载1已完成任务需处理0失败任务');
expect(screen.getByText('最近更新')).toBeInTheDocument();
expect(screen.getByText('2026-07-16 13:00:01')).toBeInTheDocument();
fireEvent.click(screen.getByRole('button', { name: '刷新导出任务' }));
await waitFor(() => expect(mocks.historyExports).toHaveBeenCalledTimes(2));
expect(screen.getByRole('button', { name: /创建导出/ })).toBeInTheDocument(); expect(screen.getByRole('button', { name: /创建导出/ })).toBeInTheDocument();
expect(mocks.historyExports).toHaveBeenCalled();
}); });
test('uses selected chartable fields for trends and omits empty RAW evidence UI', async () => { test('uses selected chartable fields for trends and omits empty RAW evidence UI', async () => {

View File

@@ -1,10 +1,10 @@
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { useMutation, useQuery, useQueryClient, type UseQueryResult } from '@tanstack/react-query';
import { IconChevronRight, IconClose, IconDownload, IconRefresh, IconSearch, IconSetting } from '@douyinfe/semi-icons'; import { IconChevronRight, IconClose, IconDownload, IconRefresh, IconSearch, IconSetting } from '@douyinfe/semi-icons';
import { Button, Card, CardGroup, Checkbox, Descriptions, Dropdown, Empty, Input, Progress, Select, 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 { FormEvent, KeyboardEvent, useCallback, useEffect, useMemo, useState } from 'react';
import { useSearchParams } from 'react-router-dom'; import { useSearchParams } from 'react-router-dom';
import { api } from '../../api/client'; import { api } from '../../api/client';
import type { HistoryDataResponse, HistoryDataRow, HistoryExportRequest, HistoryMetricDefinition, HistorySeriesResponse } from '../../api/types'; import type { HistoryDataResponse, HistoryDataRow, HistoryExportJob, HistoryExportRequest, HistoryMetricDefinition, HistorySeriesResponse } from '../../api/types';
import { buildHistorySeriesPanels, formatExportFileSize, formatHistoryValue, formatSeriesGrain, historyExportPollInterval, parseHistoryKeywords } from '../domain/history'; import { buildHistorySeriesPanels, formatExportFileSize, formatHistoryValue, formatSeriesGrain, historyExportPollInterval, parseHistoryKeywords } from '../domain/history';
import { downloadBlob } from '../domain/download'; import { downloadBlob } from '../domain/download';
import { formatShanghaiDateTime } from '../domain/formatters'; import { formatShanghaiDateTime } from '../domain/formatters';
@@ -27,7 +27,6 @@ import { usePlatformSession } from '../auth/AuthGate';
import { canOperate } from '../auth/session'; import { canOperate } from '../auth/session';
import { monitorReturnFromParams, preserveMonitorReturn } from '../routing/monitorContext'; import { monitorReturnFromParams, preserveMonitorReturn } from '../routing/monitorContext';
import { useMobileLayout } from '../hooks/useMobileLayout'; import { useMobileLayout } from '../hooks/useMobileLayout';
import { useSideSheetA11y } from '../hooks/useSideSheetA11y';
const DESKTOP_HISTORY_PAGE_SIZE = 50; const DESKTOP_HISTORY_PAGE_SIZE = 50;
const MOBILE_HISTORY_PAGE_SIZE = 20; const MOBILE_HISTORY_PAGE_SIZE = 20;
@@ -133,37 +132,81 @@ function ExportDownloadButton({ id }: { id: string }) {
return <Button className="v2-export-download" theme="borderless" icon={<IconDownload />} disabled={mutation.isPending} title={mutation.error instanceof Error ? mutation.error.message : '下载当前账号有权访问的导出文件'} onClick={() => mutation.mutate()}>{mutation.isPending ? '下载中' : mutation.isError ? '重试' : '下载'}</Button>; return <Button className="v2-export-download" theme="borderless" icon={<IconDownload />} disabled={mutation.isPending} title={mutation.error instanceof Error ? mutation.error.message : '下载当前账号有权访问的导出文件'} onClick={() => mutation.mutate()}>{mutation.isPending ? '下载中' : mutation.isError ? '重试' : '下载'}</Button>;
} }
function ExportJobsPanel({ showHeader = true }: { showHeader?: boolean }) { function useHistoryExportJobs(enabled = true) {
const query = useQuery({ return useQuery({
queryKey: ['history-exports'], queryKey: ['history-exports'],
queryFn: ({ signal }) => api.historyExports(signal), queryFn: ({ signal }) => api.historyExports(signal),
enabled,
refetchInterval: (current) => historyExportPollInterval(current.state.data), refetchInterval: (current) => historyExportPollInterval(current.state.data),
...LIVE_QUERY_POLICY, ...LIVE_QUERY_POLICY,
gcTime: QUERY_MEMORY.highVolumeGcTime gcTime: QUERY_MEMORY.highVolumeGcTime
}); });
}
function ExportJobsPanel({ query, showHeader = true }: { query: UseQueryResult<HistoryExportJob[], Error>; showHeader?: boolean }) {
const jobs = query.data ?? []; const jobs = query.data ?? [];
const statusLabel = (status: string) => status === 'queued' ? '排队中' : status === 'running' ? '执行中' : status === 'completed' ? '已完成' : '失败'; 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 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} 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-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"> <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> <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> <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> <small>{job.dateFrom && job.dateTo ? `${formatHistoryDateTime(job.dateFrom)}${formatHistoryDateTime(job.dateTo)}` : <> <PlatformTime value={job.createdAt} /></>}</small>
</div> </div>
{job.status === 'running' ? <Progress className="v2-export-job-progress" percent={job.progress} showInfo /> : null} {job.status === 'running' ? <Progress className="v2-export-job-progress" percent={job.progress} showInfo /> : null}
</Card>)}</CardGroup>}</div> </Card>)}</CardGroup>}</div>
</Card>; </Card>;
} }
function ExportJobsWorkspace({ visible, mobileLayout, onClose }: { visible: boolean; mobileLayout: boolean; onClose: () => void }) {
const query = useHistoryExportJobs(visible);
const jobs = query.data ?? [];
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;
const latestUpdatedAt = jobs.reduce<string | undefined>((latest, job) => {
if (!latest) return job.updatedAt;
return Date.parse(job.updatedAt) > Date.parse(latest) ? job.updatedAt : latest;
}, undefined);
const badge = query.isFetching ? '同步中' : `${jobs.length.toLocaleString('zh-CN')} 个任务`;
const badgeColor = failedJobs ? 'red' : activeJobs ? 'blue' : completedJobs ? 'green' : 'grey';
return <WorkspaceSideSheet
className="v2-history-export-sidesheet"
variant="task"
visible={visible}
ariaLabel="历史数据导出任务"
closeLabel="关闭历史数据导出任务"
dialogId="v2-history-export-jobs"
placement={mobileLayout ? 'bottom' : 'right'}
width={mobileLayout ? undefined : 520}
height={mobileLayout ? 'min(82dvh, 720px)' : undefined}
title="导出任务"
description="查看后台生成进度与可追溯下载记录"
icon={<IconDownload />}
badge={badge}
badgeColor={badgeColor}
summaryItems={[
{ label: '生成中', value: activeJobs, detail: '排队与执行', tone: activeJobs ? 'primary' : 'neutral' },
{ label: '可下载', value: completedJobs, detail: '已完成任务', tone: 'success' },
{ label: '需处理', value: failedJobs, detail: '失败任务', tone: failedJobs ? 'danger' : 'neutral' }
]}
footerNote={query.isError ? '任务状态读取失败,请手动刷新' : latestUpdatedAt ? <> <PlatformTime value={latestUpdatedAt} /></> : '打开后自动同步任务状态'}
secondaryActions={[{
label: '刷新任务',
ariaLabel: '刷新导出任务',
icon: <IconRefresh />,
loading: query.isFetching,
onClick: () => { void query.refetch(); }
}]}
primaryAction={{ label: '完成', onClick: onClose }}
onCancel={onClose}
>
{visible ? <ExportJobsPanel query={query} showHeader={false} /> : null}
</WorkspaceSideSheet>;
}
function EvidencePanel({ row, metrics, onClose, showHeader = true }: { row?: HistoryDataRow; metrics: HistoryMetricDefinition[]; onClose: () => void; showHeader?: boolean }) { function EvidencePanel({ row, metrics, onClose, showHeader = true }: { row?: HistoryDataRow; metrics: HistoryMetricDefinition[]; onClose: () => void; showHeader?: boolean }) {
return <Card className="v2-history-evidence" bodyStyle={{ padding: 0 }}>{showHeader ? <WorkspacePanelHeader title="数据详情" description={row ? `${row.plate || '未绑定车牌'} · ${row.protocol}` : '选择明细后核对来源与质量'} actions={row ? <Button theme="borderless" type="tertiary" icon={<IconClose />} onClick={onClose} aria-label="关闭数据详情" /> : null} /> : null} return <Card className="v2-history-evidence" bodyStyle={{ padding: 0 }}>{showHeader ? <WorkspacePanelHeader title="数据详情" description={row ? `${row.plate || '未绑定车牌'} · ${row.protocol}` : '选择明细后核对来源与质量'} actions={row ? <Button theme="borderless" type="tertiary" icon={<IconClose />} onClick={onClose} aria-label="关闭数据详情" /> : null} /> : null}
{row ? <><div className={`v2-history-evidence-status${isHealthyHistoryQuality(row.quality) ? ' is-healthy' : ' is-attention'}`}> {row ? <><div className={`v2-history-evidence-status${isHealthyHistoryQuality(row.quality) ? ' is-healthy' : ' is-attention'}`}>
@@ -368,7 +411,6 @@ export default function HistoryPage() {
setSelectedRowRef({ scope: dataScope, id: '' }); setSelectedRowRef({ scope: dataScope, id: '' });
}, [dataScope]); }, [dataScope]);
const mobileFiltersOpen = mobileLayout && !filtersCollapsed; const mobileFiltersOpen = mobileLayout && !filtersCollapsed;
useSideSheetA11y(exportAllowed && exportJobsOpen, '.v2-history-export-sidesheet', 'v2-history-export-jobs', '历史数据导出任务', '关闭历史数据导出任务');
const applyDraft = () => { const applyDraft = () => {
const parsed = parseHistoryKeywords(draft.keywords); const parsed = parseHistoryKeywords(draft.keywords);
@@ -575,21 +617,10 @@ export default function HistoryPage() {
> >
{mobileLayout && selectedRow ? <EvidencePanel row={selectedRow} metrics={visibleMetrics} onClose={closeSelectedRow} showHeader={false} /> : null} {mobileLayout && selectedRow ? <EvidencePanel row={selectedRow} metrics={visibleMetrics} onClose={closeSelectedRow} showHeader={false} /> : null}
</WorkspaceDetailSideSheet> </WorkspaceDetailSideSheet>
<WorkspaceSideSheet {exportAllowed ? <ExportJobsWorkspace
className="v2-history-export-sidesheet" visible={exportJobsOpen}
variant="task" mobileLayout={mobileLayout}
visible={exportAllowed && exportJobsOpen} onClose={() => setExportJobsOpen(false)}
ariaLabel="历史数据导出任务" /> : null}
placement={mobileLayout ? 'bottom' : 'right'}
width={mobileLayout ? undefined : 520}
height={mobileLayout ? 'min(76dvh, 680px)' : undefined}
title="导出任务"
description="查看后台生成进度与可追溯下载记录"
badge="后台任务"
badgeColor="blue"
onCancel={() => setExportJobsOpen(false)}
>
{exportJobsOpen ? <ExportJobsPanel showHeader={false} /> : null}
</WorkspaceSideSheet>
</div>; </div>;
} }

View File

@@ -149,6 +149,9 @@ describe('V2 production entry', () => {
expect(corePageSources.HistoryPage).toContain('<Checkbox'); expect(corePageSources.HistoryPage).toContain('<Checkbox');
expect(corePageSources.HistoryPage).toContain('<WorkspaceSideSheet'); expect(corePageSources.HistoryPage).toContain('<WorkspaceSideSheet');
expect(corePageSources.HistoryPage).toContain('variant="task"'); expect(corePageSources.HistoryPage).toContain('variant="task"');
expect(corePageSources.HistoryPage).toContain('summaryItems={[');
expect(corePageSources.HistoryPage).toContain('dialogId="v2-history-export-jobs"');
expect(corePageSources.HistoryPage).not.toContain('useSideSheetA11y');
expect(corePageSources.HistoryPage).toContain('v2-history-column-sidesheet'); expect(corePageSources.HistoryPage).toContain('v2-history-column-sidesheet');
expect(corePageSources.HistoryPage).toContain('<Card key={row.id} className={`v2-history-mobile-card'); expect(corePageSources.HistoryPage).toContain('<Card key={row.id} className={`v2-history-mobile-card');
expect(corePageSources.HistoryPage).toContain('v2-history-trend'); expect(corePageSources.HistoryPage).toContain('v2-history-trend');

View File

@@ -4,6 +4,8 @@ import { useId, useLayoutEffect, type ReactNode } from 'react';
export type WorkspaceSideSheetAction = { export type WorkspaceSideSheetAction = {
label: string; label: string;
onClick: () => void; onClick: () => void;
ariaLabel?: string;
icon?: ReactNode;
disabled?: boolean; disabled?: boolean;
loading?: boolean; loading?: boolean;
theme?: 'solid' | 'light' | 'borderless'; theme?: 'solid' | 'light' | 'borderless';
@@ -102,6 +104,8 @@ export function WorkspaceSideSheet({
key={action.label} key={action.label}
theme={action.theme ?? 'light'} theme={action.theme ?? 'light'}
type={action.type} type={action.type}
aria-label={action.ariaLabel}
icon={action.icon}
disabled={action.disabled} disabled={action.disabled}
loading={action.loading} loading={action.loading}
onClick={action.onClick} onClick={action.onClick}
@@ -109,6 +113,8 @@ export function WorkspaceSideSheet({
{primaryAction ? <Button {primaryAction ? <Button
theme={primaryAction.theme ?? 'solid'} theme={primaryAction.theme ?? 'solid'}
type={primaryAction.type ?? 'primary'} type={primaryAction.type ?? 'primary'}
aria-label={primaryAction.ariaLabel}
icon={primaryAction.icon}
disabled={primaryAction.disabled} disabled={primaryAction.disabled}
loading={primaryAction.loading} loading={primaryAction.loading}
onClick={primaryAction.onClick} onClick={primaryAction.onClick}

View File

@@ -20632,6 +20632,7 @@
.v2-workspace-config-title > span { .v2-workspace-config-title > span {
display: grid; display: grid;
min-width: 0; min-width: 0;
flex: 1 1 auto;
gap: 4px; gap: 4px;
} }
@@ -20760,7 +20761,7 @@
scrollbar-gutter: stable; scrollbar-gutter: stable;
} }
:is(.v2-workspace-config-sidesheet, .v2-workspace-filter-sidesheet) .semi-sidesheet-footer { :is(.v2-workspace-config-sidesheet, .v2-workspace-filter-sidesheet, .v2-workspace-task-sidesheet) .semi-sidesheet-footer {
min-height: 64px; min-height: 64px;
border-top: 1px solid #e1e8f1; border-top: 1px solid #e1e8f1;
background: rgba(255, 255, 255, .97); background: rgba(255, 255, 255, .97);
@@ -21198,104 +21199,70 @@
} }
} }
.v2-history-export-sidesheet .v2-workspace-config-content {
background: #f4f7fb;
padding: 0;
}
.v2-history-export-sidesheet .v2-export-jobs.semi-card { .v2-history-export-sidesheet .v2-export-jobs.semi-card {
min-height: 100%; min-height: 100%;
border-color: #dce5ef; border: 0;
border-radius: 12px; border-radius: 0;
box-shadow: 0 9px 28px rgba(31, 53, 80, .065);
}
.v2-export-job-overview {
display: grid;
min-height: 88px;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 1px;
border-bottom: 1px solid #e1e8f1;
background: #e1e8f1;
}
.v2-export-job-overview > span {
position: relative;
display: grid;
min-width: 0;
align-content: center;
gap: 3px;
background: #fff;
padding: 12px 13px;
}
.v2-export-job-overview > span::before {
position: absolute;
inset: 0 auto 0 0;
width: 2px;
background: transparent; background: transparent;
content: ''; box-shadow: none;
}
.v2-export-job-overview > span.is-active::before { background: #1268f3; }
.v2-export-job-overview > span.is-completed::before { background: #18a76f; }
.v2-export-job-overview > span.is-failed::before { background: #e95a52; }
.v2-export-job-overview small {
color: #7f8ea1;
font-size: 10px;
font-weight: 650;
}
.v2-export-job-overview strong {
color: #243950;
font-size: 21px;
font-weight: 760;
font-variant-numeric: tabular-nums;
line-height: 1.15;
}
.v2-export-job-overview > span.is-active strong { color: #1268f3; }
.v2-export-job-overview > span.is-completed strong { color: #14865c; }
.v2-export-job-overview > span.is-failed strong { color: #c8423b; }
.v2-export-job-overview em {
overflow: hidden;
color: #98a3b2;
font-size: 8px;
font-style: normal;
text-overflow: ellipsis;
white-space: nowrap;
} }
.v2-history-export-sidesheet .v2-export-job-list { .v2-history-export-sidesheet .v2-export-job-list {
max-height: none; max-height: none;
padding: 11px; padding: 12px;
} }
.v2-history-export-sidesheet .v2-export-job-list > .semi-card-group { .v2-history-export-sidesheet .v2-export-job-list > .semi-card-group {
display: grid;
grid-template-columns: minmax(0, 1fr);
gap: 9px; gap: 9px;
} }
.v2-history-export-sidesheet .v2-export-job-card.semi-card { .v2-history-export-sidesheet .v2-export-job-card.semi-card {
position: relative;
overflow: hidden;
border-radius: 10px; border-radius: 10px;
box-shadow: 0 5px 16px rgba(30, 50, 74, .045); box-shadow: 0 5px 16px rgba(30, 50, 74, .045);
} }
.v2-history-export-sidesheet .v2-export-job-card.semi-card::before {
position: absolute;
inset: 0 auto 0 0;
z-index: 1;
width: 3px;
background: #a8b4c3;
content: '';
}
.v2-history-export-sidesheet .v2-export-job-card.is-running::before { background: #1268f3; }
.v2-history-export-sidesheet .v2-export-job-card.is-completed::before { background: #18a76f; }
.v2-history-export-sidesheet .v2-export-job-card.is-failed::before { background: #e95a52; }
.v2-history-export-sidesheet .v2-export-job-card > .semi-card-header { .v2-history-export-sidesheet .v2-export-job-card > .semi-card-header {
min-height: 44px; min-height: 47px;
padding-inline: 12px; padding-inline: 14px 12px;
} }
.v2-history-export-sidesheet .v2-export-job-name { .v2-history-export-sidesheet .v2-export-job-name {
max-width: 310px; max-width: 310px;
color: #32475f; color: #32475f;
font-size: 12px; font-size: 13px;
font-weight: 700;
} }
.v2-history-export-sidesheet .v2-export-job-card > .semi-card-header .semi-tag { .v2-history-export-sidesheet .v2-export-job-card > .semi-card-header .semi-tag {
min-height: 23px; min-height: 23px;
font-size: 9px; font-size: 10px;
} }
.v2-history-export-sidesheet .v2-export-job-card > .semi-card-body { .v2-history-export-sidesheet .v2-export-job-card > .semi-card-body {
gap: 8px; gap: 8px;
padding: 11px 12px; padding: 12px 13px 12px 14px;
} }
.v2-history-export-sidesheet .v2-export-job-summary { .v2-history-export-sidesheet .v2-export-job-summary {
@@ -21312,11 +21279,13 @@
.v2-history-export-sidesheet .v2-export-job-summary strong { .v2-history-export-sidesheet .v2-export-job-summary strong {
color: #354b63; color: #354b63;
font-size: 12px; font-size: 13px;
} }
.v2-history-export-sidesheet .v2-export-job-summary small { .v2-history-export-sidesheet .v2-export-job-summary small {
font-size: 9px; color: #7b899a;
font-size: 10px;
line-height: 1.55;
} }
.v2-history-export-sidesheet .v2-export-job-card .v2-export-download.semi-button { .v2-history-export-sidesheet .v2-export-job-card .v2-export-download.semi-button {
@@ -21390,46 +21359,37 @@
padding: 10px 12px 10px 14px; padding: 10px 12px 10px 14px;
} }
.v2-workspace-task-sidesheet .v2-workspace-config-title-icon {
width: 34px;
height: 34px;
border-radius: 10px;
font-size: 16px;
}
.v2-workspace-task-sidesheet.semi-sidesheet-bottom .semi-sidesheet-footer {
min-height: 58px;
padding: 7px 9px calc(7px + env(safe-area-inset-bottom));
}
.v2-workspace-task-sidesheet .v2-workspace-config-content { .v2-workspace-task-sidesheet .v2-workspace-config-content {
padding: 8px; padding: 8px;
} }
.v2-export-job-overview {
min-height: 72px;
}
.v2-export-job-overview > span {
gap: 2px;
padding: 9px 10px;
}
.v2-export-job-overview small {
font-size: 8px;
}
.v2-export-job-overview strong {
font-size: 17px;
}
.v2-export-job-overview em {
font-size: 7px;
}
.v2-history-export-sidesheet .v2-export-job-list { .v2-history-export-sidesheet .v2-export-job-list {
padding: 8px; padding: 8px;
} }
.v2-history-export-sidesheet .v2-export-job-name { .v2-history-export-sidesheet .v2-export-job-name {
max-width: 220px; max-width: 220px;
font-size: 11px; font-size: 12px;
} }
.v2-history-export-sidesheet .v2-export-job-summary strong { .v2-history-export-sidesheet .v2-export-job-summary strong {
font-size: 11px; font-size: 12px;
} }
.v2-history-export-sidesheet .v2-export-job-summary small { .v2-history-export-sidesheet .v2-export-job-summary small {
font-size: 8px; font-size: 9px;
} }
.v2-rule-form-sections { .v2-rule-form-sections {