feat(admin): apply shared blue-white self-operated workbench style

This commit is contained in:
2026-10-20 19:16:47 +08:00
parent 9bfce44988
commit 464b72cff9
38 changed files with 1851 additions and 340 deletions
@@ -37,6 +37,60 @@ describe('collection task progress evidence', () => {
await w.setProps({ task: { ...yielded, blockingReason: 'ACCOUNT_COOLDOWN' } })
expect(w.find('.collection-estimate').text()).not.toContain('本批已完成,等待续采')
})
it('keeps a failure summary once across compact, stale, hidden and controlled displays', async () => {
const failed: Task = { ...source, taskStatus: 'RETRY_WAIT', blockingReason: 'UPSTREAM_FAILED', latestAttemptNo: 2,
latestAttempt: { attemptNo: 2, status: 'FAILED', phase: 'DETAIL', startedAt: null, finishedAt: null, progressUpdatedAt: null, heartbeatAt: null, executionHealth: null, elapsedSeconds: 20, errorCode: 'UPSTREAM_ERROR', failureSummary: '来源读取失败' } }
const w = mount(CollectionTaskProgress, { props: { task: failed } }); wrappers.push(w)
for (const compact of [false, true]) {
for (const state of [
{ stale: false, visible: true, collectionMode: 'BUSINESS' },
{ stale: true, visible: true, collectionMode: 'BUSINESS' },
{ stale: false, visible: false, collectionMode: 'BUSINESS' },
{ stale: false, visible: true, collectionMode: 'OFF' },
]) {
await w.setProps({ compact, ...state })
expect(w.text().match(/来源读取失败/g)).toHaveLength(1)
expect(w.findAll('[role="status"]').filter(node => node.text() === '来源读取失败')).toHaveLength(1)
if (!state.stale && state.visible && state.collectionMode === 'BUSINESS') expect(w.text()).toContain('关联来源失败,等待恢复')
}
}
})
it('hides redundant estimate notices under off, pause and stale states while retaining counts', async () => {
const yielded: Task = { ...source, taskStatus: 'RETRY_WAIT', blockingReason: 'RETRY_SCHEDULED', latestAttemptNo: 2,
latestAttempt: { attemptNo: 2, status: 'YIELDED', phase: 'DETAIL', startedAt: null, finishedAt: null, progressUpdatedAt: null, heartbeatAt: null, executionHealth: null, elapsedSeconds: 20, errorCode: null, failureSummary: null } }
const w = mount(CollectionTaskProgress, { props: { task: yielded, collectionMode: 'OFF', compact: true } }); wrappers.push(w)
expect(w.find('.collection-estimate').exists()).toBe(false)
expect(w.text()).not.toContain('等待续采')
expect(w.text()).toContain('12.1%')
expect(w.find('.compact-counts [aria-hidden="true"]').exists()).toBe(false)
await w.setProps({ collectionMode: 'BUSINESS', accountPaused: true })
expect(w.find('.collection-estimate').exists()).toBe(false)
await w.setProps({ stale: true })
expect(w.find('.collection-estimate').exists()).toBe(false)
await w.setProps({ stale: false, visible: false })
expect(w.find('.collection-estimate').exists()).toBe(false)
})
it('keeps an executing attempt visible under an off switch and pauses only stale animation', async () => {
const unknown = { ...source, blockingReason: 'COLLECTION_OFF', progress: { ...progress, totalKnown: false, total: null } }
const w = mount(CollectionTaskProgress, { props: { task: unknown, compact: true } }); wrappers.push(w)
expect(w.find('.collection-estimate').exists()).toBe(false)
expect(w.get('.ring-chart').classes()).toContain('is-indeterminate')
await w.setProps({ stale: true })
expect(w.find('.collection-estimate').exists()).toBe(false)
expect(w.get('.ring-chart').classes()).not.toContain('is-indeterminate')
})
it('marks only the evidenced current phase as paused and does not treat paused estimates as paused execution', async () => {
const paused: Task = { ...source, dataFamily: 'MASTER_DATA', taskStatus: 'RETRY_WAIT', blockingReason: 'ACCOUNT_PAUSED', progress: { ...progress, phase: 'PRODUCTS' } }
const w = mount(CollectionTaskProgress, { props: { task: paused } }); wrappers.push(w)
expect(w.findAll('.collection-flow li').map(li => li.get('span').text())).toEqual(['状态未确认', '当前阶段 · 已暂停', '状态未确认'])
expect(w.text()).not.toContain('正在处理')
await w.setProps({ task: { ...paused, blockingReason: 'EXECUTION_DISABLED' } })
expect(w.get('.density-actions span').text()).toBe('执行未启用')
await w.setProps({ task: { ...paused, taskStatus: 'RUNNING', blockingReason: null, stageEstimate: { status: 'PAUSED' } as StageEstimate } })
expect(w.get('.density-actions span').text()).toBe('正在执行')
expect(w.findAll('.collection-flow li')[1]?.text()).toContain('正在处理')
expect(w.get('.collection-estimate strong').text()).toBe('已暂停估算')
})
it('reads serialized Long seconds and rejects missing, malformed or unsafe values', async () => {
const w = mount(CollectionTaskProgress, { props: { task: { ...source, elapsedSeconds: '607' }, compact: true } }); wrappers.push(w)
expect(w.text()).toContain('任务累计耗时 10 分 7 秒')
@@ -70,6 +124,11 @@ describe('collection task progress evidence', () => {
it('uses only a confirmed source and does not infer earlier stage completion from the current phase', async () => {
const w = mount(CollectionTaskProgress, { props: { task, detail } }); wrappers.push(w)
expect(w.text()).toContain('TASK-S'); expect(w.text()).toContain('12.1%'); expect(w.text()).toContain('2448')
expect(w.classes()).toContain('admin-workbench')
expect(w.get('.collection-stage-counts').classes()).toContain('admin-workbench')
expect(w.findAll('.collection-count-grid .workbench-metric.is-compact')).toHaveLength(3)
expect(w.findAll('.collection-count-grid dd strong').map(value => value.text())).toEqual(['2787', '339', '2448'])
expect(w.get('.collection-estimate').classes()).toContain('workbench-notice')
expect(w.text()).toContain('账号共享'); expect(w.findAll('li')[0]?.text()).toContain('状态未确认')
expect(w.text()).not.toContain('✓')
await w.setProps({ task: { ...task, dependencyState: 'WAITING_UNCONFIRMED', progressSourceTaskNo: null } })
@@ -123,6 +182,14 @@ describe('collection task progress evidence', () => {
const d = { ...detail, sharedUpstream: { ...source, stageEstimate } }
const w = mount(CollectionTaskProgress, { props: { task, detail: d, visible: true } }); wrappers.push(w)
expect(w.text()).toContain('约82分钟')
await w.setProps({ compact: true })
expect(w.get('.collection-estimate').text()).toBe('当前阶段预计还需约82分钟')
expect(w.get('.collection-estimate strong').attributes('title')).toContain('最近速度 0.5 单/秒')
expect(w.get('.collection-estimate strong').attributes('title')).toContain('估算更新')
expect(w.get('.collection-estimate strong').attributes('title')).toContain('整个任务完成时间尚未估算')
await w.setProps({ compact: false })
expect(w.get('.collection-estimate').text()).toContain('最近速度')
expect(w.get('.collection-estimate').text()).toContain('整个任务完成时间尚未估算')
await w.setProps({ stale: true }); expect(w.text()).not.toContain('约82分钟'); expect(w.text()).toContain('状态更新失败')
await w.setProps({ stale: false, visible: false }); expect(w.text()).not.toContain('约82分钟')
await w.setProps({ visible: true, detail: { ...d, sharedUpstream: { ...d.sharedUpstream, progress: { ...progress, completed: 340 } } } }); expect(w.text()).not.toContain('约82分钟')
@@ -135,6 +202,15 @@ describe('collection task progress evidence', () => {
await w.setProps({ progress: { ...progress, attemptNo: 3, totalKnown: false } }); expect(w.find('[role="progressbar"]').exists()).toBe(false)
await w.setProps({ progress: { ...progress, attemptNo: 4, total: 0, completed: 0 } }); expect(w.text()).toContain('本阶段数量为零'); expect(w.find('[role="progressbar"]').exists()).toBe(false)
await w.setProps({ progress: { ...progress, attemptNo: 5, total: 1, completed: 2 } }); expect(w.find('[role="progressbar"]').exists()).toBe(false)
await w.setProps({ progress: { ...progress, attemptNo: 6, total: null, completed: 0 } })
expect(w.find('[role="progressbar"]').exists()).toBe(false)
expect(w.find('.collection-count-grid dd').text()).toContain('—')
expect(w.text()).toContain('总量或计数依据待确认')
expect(w.text()).not.toContain('本阶段数量为零')
await w.setProps({ progress: { ...progress, attemptNo: 7, total: 0, completed: 0 } })
expect(w.find('[role="progressbar"]').exists()).toBe(false)
expect(w.find('.collection-count-grid dd strong').text()).toBe('0')
expect(w.text()).toContain('本阶段数量为零')
})
it('shows the percentage once an initially unknown total becomes available', async () => {
const w = mount(CollectionProgressGauge, { props: { taskNo: source.taskNo, progress: { ...progress, totalKnown: false, total: null } } }); wrappers.push(w)