fix(selfop): preserve warehouse navigation and refine overview presentation

This commit is contained in:
2026-10-13 11:06:20 +08:00
parent b73dc0d410
commit 58df0ed9e3
9 changed files with 296 additions and 57 deletions
@@ -1,6 +1,6 @@
<script setup lang="ts">
import '@/styles/adminDensity.css'
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
import { computed, onActivated, onDeactivated, onMounted, onUnmounted, ref, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import AdminPage from '@/shared/components/AdminPage.vue'
import DataTable, { type DataTableColumn } from '@/shared/components/DataTable.vue'
@@ -54,6 +54,7 @@ const fence = new RequestFence()
const detailFence = new RequestFence()
const actionFence = new RequestFence()
const retryIntent = new RetryIntent()
let active = false
const warehouses = computed(() => overview.value?.warehouses.filter(row => !warehouseCode.value || row.warehouseCode === warehouseCode.value) ?? [])
const fields = computed<QueryField[]>(() => [{ key: 'warehouseCode', label: '仓库', type: 'select', width: 230, placeholder: '全部仓库采集状态', options: [...new Map((overview.value?.warehouses ?? []).map(row => [row.warehouseCode, { label: row.warehouseName || row.warehouseCode, value: row.warehouseCode }])).values()] }])
const hasSyntheticSamples = computed(() => warehouses.value.some(item => item.warehouseCode.startsWith('SELFOP-TEST-')))
@@ -100,11 +101,12 @@ function blockReason(value: string | null | undefined) { return value ? reasonsT
function budgetName(key: string) { return ({ pages: '分页HTTP', detailHttp: '详情HTTP', http: '全部HTTP保留额度', confirmedHttp: '已确认真实HTTP', distinctDetails: '不同详情对象', maxPages: '分页HTTP上限', maxPageSize: '每页数量上限', maxDistinctDetails: '不同详情对象上限', maxDetailHttp: '详情HTTP上限', maxInventoryPagesPerWarehouse: '每仓库存页上限', maxHttp: '全部HTTP上限' } as Record<string, string>)[key] ?? '其他保护额度' }
function limitFor(used: string, value: Validation | null) { const key = ({ pages: 'maxPages', detailHttp: 'maxDetailHttp', http: 'maxHttp', confirmedHttp: 'maxHttp', distinctDetails: 'maxDistinctDetails' } as Record<string, string>)[used]; return key ? value?.budgetLimits?.[key] : null }
async function read() {
if (!canList.value) return false
if (!active || !canList.value) return false
const request = fence.begin()
loading.value = !overview.value
try {
const nextOverview = await analyticsApi.overview(sourceAccount, undefined, request.signal)
if (!request.current()) return false
const tasks = warehouseCode.value ? await analyticsApi.tasks({ ...scope.value, current: current.value, size: size.value, ...upperBound.value }, request.signal) : null
if (!request.current()) return false
overview.value = nextOverview
@@ -117,13 +119,14 @@ async function read() {
finally { if (request.current()) loading.value = false }
}
async function selectTask(task: Task, reset = true) {
if (!task.taskNo || task.warehouseCode !== warehouseCode.value) return
if (!active || !task.taskNo || task.warehouseCode !== warehouseCode.value) return
if (reset) { taskOpen.value = true; actionFence.cancel(); submitting.value = false; selected.value = task; selectedDetail.value = null; attempts.value = []; attemptsPage.value = 1; attemptsUpperBound.value = null; reason.value = ''; confirming.value = false; actionFeedback.value = '' }
const request = detailFence.begin()
detailLoading.value = true
detailError.value = ''
try {
const detail = await analyticsApi.task(scope.value, task.taskNo, request.signal)
if (!request.current()) return
const history = await analyticsApi.runs({ ...scope.value, taskNo: task.taskNo, current: attemptsPage.value, size: attemptsSize.value, ...attemptsUpperBound.value }, request.signal)
if (!request.current()) return
if (detail.task.taskNo !== task.taskNo || detail.task.warehouseCode !== warehouseCode.value) throw new Error('任务归属不匹配')
@@ -157,13 +160,21 @@ const poller = new RuntimePoller(async () => { const busy = await read(); if (se
function visibility() { poller.setVisible(!document.hidden) }
function refresh() { upperBound.value = null; current.value = 1; void read() }
function openAnalysis(path: string) { void router.push({ path, query: { warehouseCode: warehouseCode.value } }) }
watch([() => route.path, () => route.query], () => {
if (route.path && route.path !== '/self-operated/collection') return
filters.value.warehouseCode = typeof route.query.warehouseCode === 'string' ? route.query.warehouseCode : ''
})
watch(warehouseCode, () => { configOpen.value = false; taskOpen.value = false; fence.cancel(); detailFence.cancel(); actionFence.cancel(); selected.value = null; rows.value = []; total.value = 0; attempts.value = []; upperBound.value = null; confirming.value = false; submitting.value = false; reason.value = ''; actionFeedback.value = ''; current.value = 1; void read() })
watch(taskOpen, open => { if (!open) { detailFence.cancel(); detailLoading.value = false; selected.value = null; selectedDetail.value = null; attempts.value = []; confirming.value = false; reason.value = ''; actionFeedback.value = '' } })
watch([current, size], () => { void read() })
watch([attemptsPage, attemptsSize], () => { if (selected.value) void selectTask(selected.value, false) })
watch(reason, () => { confirming.value = false })
onMounted(() => { visibility(); poller.start(); document.addEventListener('visibilitychange', visibility) })
onUnmounted(() => { poller.stop(); actionFence.cancel(); document.removeEventListener('visibilitychange', visibility) })
function resume() { if (active) return; active = true; visibility(); poller.start() }
function suspend() { active = false; poller.stop(); actionFence.cancel(); configOpen.value = false; taskOpen.value = false; loading.value = false; detailLoading.value = false; submitting.value = false }
onMounted(() => { resume(); document.addEventListener('visibilitychange', visibility) })
onActivated(resume)
onDeactivated(suspend)
onUnmounted(() => { suspend(); document.removeEventListener('visibilitychange', visibility) })
</script>
<template>
<AdminPage class="admin-density">