68 lines
6.2 KiB
Vue
68 lines
6.2 KiB
Vue
<script setup lang="ts">
|
||
import InlineHelp from '@/shared/components/InlineHelp.vue'
|
||
import { computed, ref, watch } from 'vue'
|
||
import AnalyticsChart from './AnalyticsChart.vue'
|
||
import { progressGaugeOption } from '../dashboardChartOptions'
|
||
import type { Progress } from '../types'
|
||
import { beijingTime, phaseText, quantityText, unitText } from '../state'
|
||
import '@/styles/adminWorkbench.css'
|
||
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 })
|
||
const counts = computed(() => {
|
||
const p = props.progress
|
||
if (!p || p.completed == null || p.total == null || !p.totalKnown || !p.unit || p.unit === 'NONE' || !['ACCOUNT_SHARED', 'WAREHOUSE'].includes(p.progressScope ?? '') || !['TASK_CUMULATIVE', 'ATTEMPT_ONLY'].includes(p.countBasis ?? '') || changedTotal.value) return null
|
||
const done = Number(p.completed), total = Number(p.total)
|
||
if (!Number.isSafeInteger(done) || !Number.isSafeInteger(total) || done < 0 || total < done || total <= 0) return null
|
||
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 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 admin-workbench" 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" 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" class="workbench-note">{{ phaseText(progress?.phase) }} · {{ scope }}<InlineHelp v-if="scope === '账号共享'" title="阶段数量说明"><p>来源账号的阶段数量,不作为本仓出库量或整个同步完成率。</p></InlineHelp><span v-if="progress?.scanRound != null"> · 第 {{ progress.scanRound }} 轮</span></p>
|
||
<dl class="collection-count-grid">
|
||
<div class="workbench-metric is-compact">
|
||
<dt class="workbench-metric-label">总量</dt>
|
||
<dd class="workbench-metric-value"><strong>{{ progress?.totalKnown ? quantityText(progress.total) : compact ? '—' : '待确认' }}</strong><span>{{ compact && (!progress?.unit || progress.unit === 'NONE') ? '' : unitText(progress?.unit) }}</span></dd>
|
||
</div>
|
||
<div class="workbench-metric is-compact">
|
||
<dt class="workbench-metric-label">已完成</dt>
|
||
<dd class="workbench-metric-value"><strong>{{ quantityText(progress?.completed) }}</strong><span>{{ compact && (!progress?.unit || progress.unit === 'NONE') ? '' : unitText(progress?.unit) }}</span></dd>
|
||
</div>
|
||
<div class="workbench-metric is-compact">
|
||
<dt class="workbench-metric-label">剩余</dt>
|
||
<dd class="workbench-metric-value"><strong>{{ counts ? counts.remaining : '—' }}</strong><span>{{ compact && (!progress?.unit || progress.unit === 'NONE') ? '' : unitText(progress?.unit) }}</span></dd>
|
||
</div>
|
||
</dl>
|
||
<p v-if="changedTotal" class="workbench-note">总量发生变化,正在复核;暂不展示比例。</p>
|
||
<p v-else-if="!counts" class="workbench-note">{{ readingDirectory ? '目录总页数尚未返回,已读取数量见“已完成”。' : progress?.totalKnown && progress.total != null && Number(progress.total) === 0 ? '本阶段数量为零;是否完成以阶段及发布证据为准。' : '总量或计数依据待确认,不估算完成比例。' }}</p>
|
||
|
||
<p v-if="!compact && (progress?.sliceFrom || progress?.sliceUntil)" class="workbench-note">当前切片:{{ beijingTime(progress?.sliceFrom) }} 至 {{ beijingTime(progress?.sliceUntil) }}(结束边界不含)</p>
|
||
</div>
|
||
</section>
|
||
</template>
|
||
<style scoped>
|
||
.collection-stage-counts { display: grid; grid-template-columns: 156px minmax(0, 1fr); align-items: center; gap: var(--admin-space-6); }
|
||
.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 { color: var(--workbench-primary); font-size: var(--workbench-compact-number-size); font-weight: 700; font-variant-numeric: tabular-nums; }
|
||
.ring-label span { margin-top: 4px; color: var(--workbench-muted); font-size: var(--workbench-note-size); }
|
||
.collection-count-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--admin-space-4); margin: var(--admin-space-4) 0; }
|
||
.compact-counts .collection-count-grid { margin-block: 0; }
|
||
</style>
|