2026-10-13 16:52:09 +08:00
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
|
import { computed, ref, watch } from 'vue'
|
2026-10-13 20:44:31 +08:00
|
|
|
|
import AnalyticsChart from './AnalyticsChart.vue'
|
|
|
|
|
|
import { progressGaugeOption } from '../dashboardChartOptions'
|
2026-10-13 16:52:09 +08:00
|
|
|
|
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 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)
|
2026-10-13 20:44:31 +08:00
|
|
|
|
watch([identity, () => props.progress?.total], ([id, total], [oldId, oldTotal]) => { changedTotal.value = id === oldId ? changedTotal.value || (oldTotal != null && total != null && total !== oldTotal) : false })
|
2026-10-13 16:52:09 +08:00
|
|
|
|
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' ? '本仓' : '范围待确认')
|
2026-10-13 20:44:31 +08:00
|
|
|
|
const option = computed(() => progressGaugeOption(counts.value?.percent ?? null))
|
|
|
|
|
|
const readingDirectory = computed(() => !props.progress?.totalKnown && ['DIRECTORY', 'DISCOVERY'].includes(props.progress?.phase ?? ''))
|
2026-10-13 16:52:09 +08:00
|
|
|
|
</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">
|
2026-10-13 20:44:31 +08:00
|
|
|
|
<AnalyticsChart :option="option" class="ring-chart" aria-hidden="true" />
|
2026-10-13 16:52:09 +08:00
|
|
|
|
<div class="ring-label"><strong>{{ counts ? `${counts.percent.toFixed(1)}%` : '—' }}</strong><span>{{ stale ? '上次阶段记录' : '当前阶段' }}</span></div>
|
|
|
|
|
|
</div>
|
2026-10-13 20:44:31 +08:00
|
|
|
|
<div :class="{ 'compact-counts': compact }">
|
2026-10-13 16:52:09 +08:00
|
|
|
|
<p v-if="!compact">{{ phaseText(progress?.phase) }} · {{ scope }}<span v-if="progress?.scanRound != null"> · 第 {{ progress.scanRound }} 轮</span></p>
|
2026-10-13 20:44:31 +08:00
|
|
|
|
<dl class="collection-count-grid"><div><dt>总量</dt><dd>{{ progress?.totalKnown ? quantityText(progress.total) : compact ? '—' : '待确认' }} <small>{{ compact && (!progress?.unit || progress.unit === 'NONE') ? '' : unitText(progress?.unit) }}</small></dd></div><div><dt>已完成</dt><dd>{{ quantityText(progress?.completed) }} <small>{{ compact && (!progress?.unit || progress.unit === 'NONE') ? '' : unitText(progress?.unit) }}</small></dd></div><div><dt>剩余</dt><dd>{{ counts ? counts.remaining : '—' }} <small>{{ compact && (!progress?.unit || progress.unit === 'NONE') ? '' : unitText(progress?.unit) }}</small></dd></div></dl>
|
2026-10-13 16:52:09 +08:00
|
|
|
|
<p v-if="changedTotal">总量发生变化,正在复核;暂不展示比例。</p>
|
2026-10-13 20:44:31 +08:00
|
|
|
|
<p v-else-if="!counts">{{ readingDirectory ? '目录总页数尚未返回,已读取数量见“已完成”。' : progress?.totalKnown && Number(progress.total) === 0 ? '本阶段数量为零;是否完成以阶段及发布证据为准。' : '总量或计数依据待确认,不估算完成比例。' }}</p>
|
|
|
|
|
|
<p v-else-if="compact" aria-hidden="true"> </p>
|
2026-10-13 16:52:09 +08:00
|
|
|
|
<p v-if="!compact && scope === '账号共享'">来源账号的阶段数量,不能当作本仓出库量或整个同步完成率。</p>
|
|
|
|
|
|
<p v-if="!compact && (progress?.sliceFrom || progress?.sliceUntil)">当前切片:{{ 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; }
|
2026-10-13 20:44:31 +08:00
|
|
|
|
.compact-counts { min-width: 0; }
|
|
|
|
|
|
.ring-chart { width: 100%; height: 100%; }
|
2026-10-13 16:52:09 +08:00
|
|
|
|
.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; }
|
|
|
|
|
|
dt, small, .ring-label span { color: var(--el-text-color-secondary); font-size: var(--admin-font-caption); }
|
|
|
|
|
|
dd { margin: var(--admin-space-2) 0 0; font-size: 30px; font-variant-numeric: tabular-nums; }
|
|
|
|
|
|
p { margin: var(--admin-space-2) 0; font-size: var(--admin-font-caption); overflow-wrap: anywhere; }
|
|
|
|
|
|
</style>
|