This commit is contained in:
2026-10-14 11:02:56 +08:00
parent 3f728cd0b2
commit e0f54edcc9
24 changed files with 352 additions and 105 deletions
@@ -4,7 +4,7 @@ import AnalyticsChart from './AnalyticsChart.vue'
import { progressGaugeOption } from '../dashboardChartOptions'
import type { Progress } from '../types'
import { beijingTime, phaseText, quantityText, unitText } from '../state'
const props = defineProps<{ progress: Partial<Progress> | null | undefined; taskNo: string; stale?: boolean; compact?: boolean }>()
const props = defineProps<{ progress: Partial<Progress> | null | undefined; taskNo: string; stale?: boolean; compact?: boolean; running?: boolean }>()
const identity = computed(() => JSON.stringify([props.taskNo, props.progress?.progressScope, props.progress?.countBasis, props.progress?.attemptNo, props.progress?.phase, props.progress?.scanRound, props.progress?.sliceFrom, props.progress?.sliceUntil, props.progress?.unit]))
const changedTotal = ref(false)
watch([identity, () => props.progress?.total], ([id, total], [oldId, oldTotal]) => { changedTotal.value = id === oldId ? changedTotal.value || (oldTotal != null && total != null && total !== oldTotal) : false })
@@ -16,14 +16,15 @@ const counts = computed(() => {
return { remaining: total - done, percent: Number(BigInt(done) * 1000n / BigInt(total)) / 10 }
})
const scope = computed(() => props.progress?.progressScope === 'ACCOUNT_SHARED' ? '账号共享' : props.progress?.progressScope === 'WAREHOUSE' ? '本仓' : '范围待确认')
const option = computed(() => progressGaugeOption(counts.value?.percent ?? null))
const indeterminate = computed(() => Boolean(props.running && !props.stale && !counts.value))
const option = computed(() => progressGaugeOption(counts.value?.percent ?? null, indeterminate.value))
const readingDirectory = computed(() => !props.progress?.totalKnown && ['DIRECTORY', 'DISCOVERY'].includes(props.progress?.phase ?? ''))
</script>
<template>
<section class="collection-stage-counts" aria-label="当前阶段数量">
<div class="collection-ring" :role="counts ? 'progressbar' : 'img'" :aria-label="`${scope} · ${phaseText(progress?.phase)} · ${counts ? '当前阶段' : '总量待确认'}`" :aria-valuenow="counts?.percent" :aria-valuemin="counts ? 0 : undefined" :aria-valuemax="counts ? 100 : undefined">
<AnalyticsChart :option="option" class="ring-chart" aria-hidden="true" />
<div class="ring-label"><strong>{{ counts ? `${counts.percent.toFixed(1)}%` : '—' }}</strong><span>{{ stale ? '上次阶段记录' : '当前阶段' }}</span></div>
<AnalyticsChart :option="option" merge-updates class="ring-chart" :class="{ 'is-indeterminate': indeterminate }" aria-hidden="true" />
<div class="ring-label"><strong>{{ counts ? `${counts.percent.toFixed(1)}%` : indeterminate ? '执行中' : '—' }}</strong><span>{{ stale ? '上次阶段记录' : '当前阶段' }}</span></div>
</div>
<div :class="{ 'compact-counts': compact }">
<p v-if="!compact">{{ phaseText(progress?.phase) }} · {{ scope }}<span v-if="progress?.scanRound != null"> · 第 {{ progress.scanRound }} 轮</span></p>
@@ -41,6 +42,9 @@ const readingDirectory = computed(() => !props.progress?.totalKnown && ['DIRECTO
.collection-ring { width: 156px; height: 156px; position: relative; }
.compact-counts { min-width: 0; }
.ring-chart { width: 100%; height: 100%; }
.ring-chart.is-indeterminate { animation: collection-spin 2.4s linear infinite; }
@keyframes collection-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .ring-chart.is-indeterminate { animation: none; } }
.ring-label { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; }
.ring-label strong { font-size: 28px; font-weight: 500; font-variant-numeric: tabular-nums; }
.collection-count-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--admin-space-4); margin: var(--admin-space-4) 0; }