mirror of
https://github.com/zyycn/codex-proxy-rs.git
synced 2026-11-06 16:06:08 +00:00
refactor(web): share dashboard tone maps and retire dead surfaces
Hoist the tone-to-class maps duplicated between MetricCard and AccountOverviewCard into dashboard constants, move the generic ECharts tooltip parameter helpers to components/charts/tooltip.ts shared by the dashboard trend chart and usage charts, derive the health popover's segment and metric lists from one outcome metadata source, and rebuild useOpsErrorsTable on the shared usePagedQuery composable (same stale-response guard and empty-page clamp, first-paint loading preserved). Remove the dead trend-loading prop chain, the unused showAvatar prop, and two unused exports. Built CSS unchanged. Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
// ECharts tooltip 回调参数的通用安全取值:参数形状不受控(数组/对象混合),
|
||||
// 统一在此收敛类型判断。
|
||||
export function tooltipRows(params: unknown): Record<string, unknown>[] {
|
||||
const values = Array.isArray(params) ? params : [params]
|
||||
return values.filter(
|
||||
(value): value is Record<string, unknown> => typeof value === 'object' && value !== null,
|
||||
)
|
||||
}
|
||||
|
||||
export function tooltipIndex(source: unknown) {
|
||||
if (typeof source !== 'object' || source === null || !('dataIndex' in source))
|
||||
return -1
|
||||
return typeof source.dataIndex === 'number' ? source.dataIndex : -1
|
||||
}
|
||||
@@ -14,14 +14,12 @@ const props = withDefaults(
|
||||
account: AccountIdentity
|
||||
size?: 'md' | 'lg'
|
||||
showPlan?: boolean
|
||||
showAvatar?: boolean
|
||||
titleMode?: 'local-part' | 'email'
|
||||
metaPosition?: 'title' | 'secondary'
|
||||
}>(),
|
||||
{
|
||||
size: 'md',
|
||||
showPlan: false,
|
||||
showAvatar: true,
|
||||
titleMode: 'local-part',
|
||||
metaPosition: 'title',
|
||||
},
|
||||
@@ -72,7 +70,6 @@ const avatarClass = computed(() => {
|
||||
<template>
|
||||
<div class="flex min-w-0 items-center gap-3">
|
||||
<span
|
||||
v-if="showAvatar"
|
||||
class="inline-flex shrink-0 items-center justify-center rounded-lg font-[820]"
|
||||
:class="[avatarSizeClass, avatarClass]"
|
||||
>
|
||||
|
||||
@@ -39,6 +39,28 @@ interface AccountConnectionTestOptions {
|
||||
onAccountStatus: (accountId: string, status: string) => void
|
||||
}
|
||||
|
||||
// 后端把"选账号阶段失败"和"上游真的挂了"拆成了不同 providerErrorCode,
|
||||
// 前者是可自愈的等待,不该让人以为上游不可用。
|
||||
const CONNECTION_TEST_ERROR_HINTS: Record<string, string> = {
|
||||
account_cooling_down: '账号冷却中',
|
||||
account_capacity_busy: '账号并发已占满或请求间隔未到,请稍后重试',
|
||||
no_eligible_account: '该账号没有可用于所选模型的凭据',
|
||||
account_selector_unavailable: '账号调度状态暂时不可读,请稍后重试',
|
||||
}
|
||||
|
||||
function connectionTestErrorText(event: any) {
|
||||
const fallback = event.error || '测试连接失败'
|
||||
const hint = CONNECTION_TEST_ERROR_HINTS[event.providerErrorCode]
|
||||
if (!hint)
|
||||
return fallback
|
||||
if (event.providerErrorCode !== 'account_cooling_down')
|
||||
return hint
|
||||
const seconds = /retry in (\d+)s/.exec(fallback)?.[1]
|
||||
return seconds
|
||||
? `${hint},约 ${seconds} 秒后自动恢复(上游流被中断后触发)`
|
||||
: `${hint}(上游流被中断后触发)`
|
||||
}
|
||||
|
||||
export function useAccountConnectionTest(options: AccountConnectionTestOptions) {
|
||||
const showConnectionTestModal = shallowRef(false)
|
||||
const testingAccount = shallowRef<any>(null)
|
||||
@@ -262,7 +284,7 @@ export function useAccountConnectionTest(options: AccountConnectionTestOptions)
|
||||
finishConnectionTest('success')
|
||||
}
|
||||
else {
|
||||
connectionTestError.value = event.error || '测试连接失败'
|
||||
connectionTestError.value = connectionTestErrorText(event)
|
||||
appendConnectionTestLog(connectionTestError.value, 'danger')
|
||||
finishConnectionTest('error')
|
||||
}
|
||||
@@ -271,9 +293,9 @@ export function useAccountConnectionTest(options: AccountConnectionTestOptions)
|
||||
}
|
||||
if (event.type === 'error') {
|
||||
applyAccountStatus(event.accountStatus)
|
||||
connectionTestError.value = event.error || '测试连接失败'
|
||||
connectionTestError.value = connectionTestErrorText(event)
|
||||
const detail = event.providerErrorCode || event.providerErrorType
|
||||
? { code: event.providerErrorCode, type: event.providerErrorType }
|
||||
? { code: event.providerErrorCode, type: event.providerErrorType, message: event.error }
|
||||
: undefined
|
||||
appendConnectionTestLog(connectionTestError.value, 'danger', detail)
|
||||
finishConnectionTest('error')
|
||||
|
||||
@@ -162,7 +162,7 @@ export function orderedPanelQuotaWindows(windows: AccountQuotaWindow[]) {
|
||||
)
|
||||
}
|
||||
|
||||
export function quotaWindowPercent(window: AccountQuotaWindow) {
|
||||
function quotaWindowPercent(window: AccountQuotaWindow) {
|
||||
return clamp(window.usedPercent ?? 0, 0, 100)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import type { dashboardSnapshotView } from '../composables/presenter'
|
||||
import type { dashboardSnapshotView, MetricTone } from '../composables/presenter'
|
||||
import { CircleCheck, RefreshCw, ShieldAlert, TriangleAlert } from '@lucide/vue'
|
||||
import { clamp } from 'es-toolkit'
|
||||
|
||||
@@ -9,6 +9,7 @@ import BaseEmpty from '@/components/base/BaseEmpty.vue'
|
||||
import ProviderIconGroup from '@/components/ProviderIconGroup.vue'
|
||||
import AccountIdentityCell from '@/views/accounts/components/AccountIdentityCell.vue'
|
||||
import AccountUsageWindow from '@/views/accounts/components/AccountUsageWindow.vue'
|
||||
import { metricToneIconClasses, metricToneValueClasses } from '../constants'
|
||||
|
||||
type DashboardSnapshot = ReturnType<typeof dashboardSnapshotView>
|
||||
|
||||
@@ -69,6 +70,14 @@ const strategyLabel = computed(() => {
|
||||
return map[s] || s
|
||||
})
|
||||
|
||||
function toneIconClass(tone: string) {
|
||||
return metricToneIconClasses[tone as MetricTone]
|
||||
}
|
||||
|
||||
function toneValueClass(tone: string) {
|
||||
return metricToneValueClasses[tone as MetricTone]
|
||||
}
|
||||
|
||||
const statusRows = computed(() => {
|
||||
const p = props.pool
|
||||
if (!p) {
|
||||
@@ -141,22 +150,6 @@ const statusBars = computed(() => {
|
||||
{ pct: unavailable, cls: 'bg-(--cp-danger)' },
|
||||
].filter(b => b.pct > 0)
|
||||
})
|
||||
|
||||
const rowToneClasses: Record<string, string> = {
|
||||
normal: 'bg-(--cp-normal-bg) text-(--cp-normal)',
|
||||
info: 'bg-(--cp-info-bg) text-(--cp-info)',
|
||||
success: 'bg-(--cp-success-bg) text-(--cp-success)',
|
||||
warning: 'bg-(--cp-warning-bg) text-(--cp-warning)',
|
||||
danger: 'bg-(--cp-danger-bg) text-(--cp-danger)',
|
||||
}
|
||||
|
||||
const valueToneClasses: Record<string, string> = {
|
||||
normal: 'text-(--cp-normal-text)',
|
||||
info: 'text-(--cp-info-text)',
|
||||
success: 'text-(--cp-success-text)',
|
||||
warning: 'text-(--cp-warning-text)',
|
||||
danger: 'text-(--cp-danger-text)',
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -330,7 +323,7 @@ const valueToneClasses: Record<string, string> = {
|
||||
>
|
||||
<span
|
||||
class="inline-flex size-7 items-center justify-center rounded-[9px]"
|
||||
:class="rowToneClasses[row.tone]"
|
||||
:class="toneIconClass(row.tone)"
|
||||
>
|
||||
<component :is="row.icon" :size="16" />
|
||||
</span>
|
||||
@@ -344,7 +337,7 @@ const valueToneClasses: Record<string, string> = {
|
||||
</span>
|
||||
<strong
|
||||
class="col-start-4 text-right font-mono text-[17px] leading-[1.15] font-[760] tabular-nums"
|
||||
:class="valueToneClasses[row.tone]"
|
||||
:class="toneValueClass(row.tone)"
|
||||
>{{ row.value }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -18,40 +18,8 @@ const eligibleRequests = computed(() => props.point.successRequests + props.poin
|
||||
const observedRequests = computed(
|
||||
() => eligibleRequests.value + props.point.cancelledRequests + props.point.callerErrorRequests,
|
||||
)
|
||||
const outcomeSegments = computed(() => {
|
||||
const total = observedRequests.value
|
||||
if (total === 0)
|
||||
return []
|
||||
|
||||
return [
|
||||
{
|
||||
label: '成功',
|
||||
value: props.point.successRequests,
|
||||
className: 'bg-(--cp-success)',
|
||||
},
|
||||
{
|
||||
label: '服务失败',
|
||||
value: props.point.failedRequests,
|
||||
className: 'bg-(--cp-danger)',
|
||||
},
|
||||
{
|
||||
label: '客户端取消',
|
||||
value: props.point.cancelledRequests,
|
||||
className: 'bg-(--cp-normal)',
|
||||
},
|
||||
{
|
||||
label: '调用方错误',
|
||||
value: props.point.callerErrorRequests,
|
||||
className: 'bg-(--cp-info)',
|
||||
},
|
||||
]
|
||||
.filter(item => item.value > 0)
|
||||
.map(item => ({
|
||||
...item,
|
||||
percentage: (item.value / total) * 100,
|
||||
}))
|
||||
})
|
||||
const metricItems = computed(() => [
|
||||
// 四类结果的标签/取值/配色单一来源;分段条与明细行都由此派生。
|
||||
const outcomeMeta = computed(() => [
|
||||
{
|
||||
label: '成功',
|
||||
value: props.point.successRequests,
|
||||
@@ -77,6 +45,20 @@ const metricItems = computed(() => [
|
||||
valueClass: 'text-(--cp-info-text)',
|
||||
},
|
||||
])
|
||||
const outcomeSegments = computed(() => {
|
||||
const total = observedRequests.value
|
||||
if (total === 0)
|
||||
return []
|
||||
|
||||
return outcomeMeta.value
|
||||
.map(item => ({ label: item.label, value: item.value, className: item.dotClass }))
|
||||
.filter(item => item.value > 0)
|
||||
.map(item => ({
|
||||
...item,
|
||||
percentage: (item.value / total) * 100,
|
||||
}))
|
||||
})
|
||||
const metricItems = outcomeMeta
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -7,28 +7,13 @@ import BaseCard from '@/components/base/BaseCard.vue'
|
||||
import BaseMotionIcon from '@/components/base/BaseMotionIcon.vue'
|
||||
|
||||
import BaseChart from '@/components/charts/BaseChart.vue'
|
||||
import { metricToneIconClasses, metricToneValueClasses } from '../constants'
|
||||
import AnimatedMetricValue from './AnimatedMetricValue.vue'
|
||||
|
||||
const props = defineProps<{
|
||||
metric: MetricCardView
|
||||
}>()
|
||||
|
||||
const iconToneClasses: Record<MetricTone, string> = {
|
||||
normal: 'bg-(--cp-normal-bg) text-(--cp-normal)',
|
||||
info: 'bg-(--cp-info-bg) text-(--cp-info)',
|
||||
success: 'bg-(--cp-success-bg) text-(--cp-success)',
|
||||
warning: 'bg-(--cp-warning-bg) text-(--cp-warning)',
|
||||
danger: 'bg-(--cp-danger-bg) text-(--cp-danger)',
|
||||
}
|
||||
|
||||
const detailToneClasses: Record<MetricTone, string> = {
|
||||
normal: 'text-(--cp-normal-text)',
|
||||
info: 'text-(--cp-info-text)',
|
||||
success: 'text-(--cp-success-text)',
|
||||
warning: 'text-(--cp-warning-text)',
|
||||
danger: 'text-(--cp-danger-text)',
|
||||
}
|
||||
|
||||
const trendToneClasses: Record<MetricTone, string> = {
|
||||
normal: 'bg-(--cp-normal-text)',
|
||||
info: 'bg-(--cp-info-text)',
|
||||
@@ -89,7 +74,7 @@ const sparklineOption = computed<EChartsOption | null>(() => {
|
||||
<BaseMotionIcon
|
||||
aria-hidden="true"
|
||||
class="inline-flex size-8.5 shrink-0 items-center justify-center rounded-[10px]"
|
||||
:class="iconToneClasses[metric.tone]"
|
||||
:class="metricToneIconClasses[metric.tone]"
|
||||
>
|
||||
<component :is="metric.icon" :size="18" />
|
||||
</BaseMotionIcon>
|
||||
@@ -135,7 +120,7 @@ const sparklineOption = computed<EChartsOption | null>(() => {
|
||||
}}</span>
|
||||
<b
|
||||
class="min-w-0 truncate font-mono text-xs leading-none font-bold tabular-nums"
|
||||
:class="metric.details[0]?.tone ? detailToneClasses[metric.details[0].tone] : undefined"
|
||||
:class="metric.details[0]?.tone ? metricToneValueClasses[metric.details[0].tone] : undefined"
|
||||
>
|
||||
{{ metric.details[0]?.value }}
|
||||
</b>
|
||||
@@ -148,7 +133,7 @@ const sparklineOption = computed<EChartsOption | null>(() => {
|
||||
}}</span>
|
||||
<b
|
||||
class="min-w-0 truncate font-mono text-xs leading-none font-bold tabular-nums"
|
||||
:class="metric.details[1]?.tone ? detailToneClasses[metric.details[1].tone] : undefined"
|
||||
:class="metric.details[1]?.tone ? metricToneValueClasses[metric.details[1].tone] : undefined"
|
||||
>
|
||||
{{ metric.details[1]?.value }}
|
||||
</b>
|
||||
|
||||
@@ -14,7 +14,6 @@ type TrendView = ReturnType<typeof dashboardTrendView>
|
||||
const props = defineProps<{
|
||||
points: TrendView['points']
|
||||
summary: TrendView['summary']
|
||||
loading?: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
||||
@@ -231,7 +231,7 @@ function recentTrendWindow(points: DashboardTrendPoint[]) {
|
||||
)
|
||||
}
|
||||
|
||||
export function formatDashboardCompactNumber(value: number) {
|
||||
function formatDashboardCompactNumber(value: number) {
|
||||
const normalized = Math.max(0, Math.round(value))
|
||||
if (normalized < 1_000)
|
||||
return new Intl.NumberFormat('zh-CN').format(normalized)
|
||||
|
||||
@@ -17,7 +17,6 @@ export function useDashboard() {
|
||||
const trend = shallowRef(dashboardTrendView(null))
|
||||
const loading = shallowRef(false)
|
||||
const refreshing = shallowRef(false)
|
||||
const trendLoading = shallowRef(false)
|
||||
const lastRefreshedAt = shallowRef('')
|
||||
let trendRequestId = 0
|
||||
|
||||
@@ -75,7 +74,6 @@ export function useDashboard() {
|
||||
activeTrendKind.value = trendKind
|
||||
const requestId = ++trendRequestId
|
||||
try {
|
||||
trendLoading.value = true
|
||||
const result = await getDashboardTrend({ kind: trendKind })
|
||||
if (isCurrentTrendRequest(requestId, trendKind))
|
||||
trend.value = dashboardTrendView(result)
|
||||
@@ -83,26 +81,16 @@ export function useDashboard() {
|
||||
catch {
|
||||
// 趋势请求失败时保留当前趋势,下一次刷新继续尝试。
|
||||
}
|
||||
finally {
|
||||
if (isCurrentTrendRequest(requestId, trendKind))
|
||||
trendLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function loadDashboardSnapshot() {
|
||||
const trendKind = activeTrendKind.value
|
||||
const requestId = ++trendRequestId
|
||||
try {
|
||||
const summary = await getDashboardSummary({ kind: trendKind })
|
||||
snapshot.value = dashboardSnapshotView(summary)
|
||||
lastRefreshedAt.value = formatDateTime()
|
||||
if (isCurrentTrendRequest(requestId, trendKind)) {
|
||||
trend.value = dashboardTrendView(summary.trend)
|
||||
}
|
||||
}
|
||||
finally {
|
||||
if (isCurrentTrendRequest(requestId, trendKind))
|
||||
trendLoading.value = false
|
||||
const summary = await getDashboardSummary({ kind: trendKind })
|
||||
snapshot.value = dashboardSnapshotView(summary)
|
||||
lastRefreshedAt.value = formatDateTime()
|
||||
if (isCurrentTrendRequest(requestId, trendKind)) {
|
||||
trend.value = dashboardTrendView(summary.trend)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,7 +109,6 @@ export function useDashboard() {
|
||||
return {
|
||||
loading,
|
||||
refreshing,
|
||||
trendLoading,
|
||||
activeTrendKind,
|
||||
lastRefreshedAt,
|
||||
metrics,
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { dashboardTrendView, normalizeDashboardTrendKind } from './presente
|
||||
import { usePreferredReducedMotion } from '@vueuse/core'
|
||||
import { computed, shallowRef, watch } from 'vue'
|
||||
|
||||
import { tooltipIndex } from '@/components/charts/tooltip'
|
||||
import { useThemeColor } from '@/composables/useThemeColor'
|
||||
|
||||
type TrendKind = ReturnType<typeof normalizeDashboardTrendKind>
|
||||
@@ -514,13 +515,6 @@ function tooltipValue(source: unknown, key: string) {
|
||||
return typeof value === 'number' || typeof value === 'string' ? String(value) : ''
|
||||
}
|
||||
|
||||
function tooltipIndex(source: unknown) {
|
||||
if (typeof source !== 'object' || source === null || !('dataIndex' in source))
|
||||
return -1
|
||||
const value = (source as Record<string, unknown>).dataIndex
|
||||
return typeof value === 'number' ? value : -1
|
||||
}
|
||||
|
||||
function trendPointProperty(point: TrendPoint | undefined, key: string) {
|
||||
if (!point || !(key in point))
|
||||
return undefined
|
||||
|
||||
@@ -1,3 +1,22 @@
|
||||
import type { MetricTone } from './composables/presenter'
|
||||
|
||||
// tone 到 Tailwind 类的唯一映射;仪表盘各卡片共用。
|
||||
export const metricToneIconClasses: Record<MetricTone, string> = {
|
||||
normal: 'bg-(--cp-normal-bg) text-(--cp-normal)',
|
||||
info: 'bg-(--cp-info-bg) text-(--cp-info)',
|
||||
success: 'bg-(--cp-success-bg) text-(--cp-success)',
|
||||
warning: 'bg-(--cp-warning-bg) text-(--cp-warning)',
|
||||
danger: 'bg-(--cp-danger-bg) text-(--cp-danger)',
|
||||
}
|
||||
|
||||
export const metricToneValueClasses: Record<MetricTone, string> = {
|
||||
normal: 'text-(--cp-normal-text)',
|
||||
info: 'text-(--cp-info-text)',
|
||||
success: 'text-(--cp-success-text)',
|
||||
warning: 'text-(--cp-warning-text)',
|
||||
danger: 'text-(--cp-danger-text)',
|
||||
}
|
||||
|
||||
export type HealthStatus
|
||||
= 'future' | 'no_data' | 'unavailable' | 'unstable' | 'low_sample' | 'stable'
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@ import { useDashboard } from './composables/useDashboard'
|
||||
const {
|
||||
loading,
|
||||
refreshing,
|
||||
trendLoading,
|
||||
activeTrendKind,
|
||||
metrics,
|
||||
trendPoints,
|
||||
@@ -69,7 +68,6 @@ const {
|
||||
v-model:kind="activeTrendKind"
|
||||
:points="trendPoints"
|
||||
:summary="trendSummary"
|
||||
:loading="trendLoading"
|
||||
@trend-change="loadTrend"
|
||||
/>
|
||||
<WireProfileCard :profiles="wireProfiles" />
|
||||
|
||||
@@ -1,83 +1,57 @@
|
||||
import type { Ref } from 'vue'
|
||||
import type { UsageTimeRangeParams } from './useUsageTimeRange'
|
||||
import { watchDebounced } from '@vueuse/core'
|
||||
import { clamp } from 'es-toolkit'
|
||||
|
||||
import { computed, onMounted, shallowRef, watch } from 'vue'
|
||||
import { getOpsErrors } from '@/api'
|
||||
import { toast } from '@/components/base/BaseToast'
|
||||
import { usePagedQuery } from '@/composables/usePagedQuery'
|
||||
import { errorMessage, withMinimumDuration } from '@/utils/async'
|
||||
|
||||
export function useOpsErrorsTable(timeRangeParams: Readonly<Ref<UsageTimeRangeParams>>) {
|
||||
const loading = shallowRef(true)
|
||||
const refreshing = shallowRef(false)
|
||||
const records = shallowRef<Awaited<ReturnType<typeof getOpsErrors>>['items']>([])
|
||||
const page = shallowRef(1)
|
||||
const pageSize = shallowRef(10)
|
||||
const total = shallowRef(0)
|
||||
const searchQuery = shallowRef('')
|
||||
const failureClass = shallowRef('')
|
||||
const route = shallowRef('')
|
||||
let loadRequestId = 0
|
||||
|
||||
const query = usePagedQuery({
|
||||
initialPageSize: 10,
|
||||
load: ({ page, pageSize }) => getOpsErrors({
|
||||
page,
|
||||
pageSize,
|
||||
search: searchQuery.value.trim() || undefined,
|
||||
failureClass: failureClass.value.trim() || undefined,
|
||||
route: route.value.trim() || undefined,
|
||||
...timeRangeParams.value,
|
||||
}),
|
||||
onError: error => toast.error(errorMessage(error, '加载错误明细失败')),
|
||||
})
|
||||
// 首屏进入即处于加载态;首次 execute 完成后由 usePagedQuery 维护。
|
||||
query.loading.value = true
|
||||
|
||||
const pagination = computed(() => ({
|
||||
page: page.value,
|
||||
pageSize: pageSize.value,
|
||||
total: total.value,
|
||||
page: query.page.value,
|
||||
pageSize: query.pageSize.value,
|
||||
total: query.total.value,
|
||||
}))
|
||||
|
||||
async function load() {
|
||||
const requestId = ++loadRequestId
|
||||
try {
|
||||
loading.value = true
|
||||
const result = await getOpsErrors({
|
||||
page: page.value,
|
||||
pageSize: pageSize.value,
|
||||
search: searchQuery.value.trim() || undefined,
|
||||
failureClass: failureClass.value.trim() || undefined,
|
||||
route: route.value.trim() || undefined,
|
||||
...timeRangeParams.value,
|
||||
})
|
||||
if (requestId !== loadRequestId)
|
||||
return
|
||||
records.value = result.items
|
||||
pageSize.value = result.page.pageSize
|
||||
total.value = result.page.total
|
||||
page.value = result.page.page
|
||||
|
||||
if (records.value.length === 0 && total.value > 0 && page.value > 1) {
|
||||
page.value = clamp(result.page.totalPages, 1, Number.POSITIVE_INFINITY)
|
||||
await load()
|
||||
}
|
||||
}
|
||||
catch (error: unknown) {
|
||||
if (requestId !== loadRequestId)
|
||||
return
|
||||
toast.error(errorMessage(error, '加载错误明细失败'))
|
||||
}
|
||||
finally {
|
||||
if (requestId === loadRequestId)
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function handlePageChange(nextPage: number) {
|
||||
page.value = nextPage
|
||||
void load()
|
||||
query.page.value = nextPage
|
||||
void query.execute()
|
||||
}
|
||||
|
||||
function handlePageSizeChange(nextPageSize: number) {
|
||||
pageSize.value = nextPageSize
|
||||
page.value = 1
|
||||
void load()
|
||||
query.pageSize.value = nextPageSize
|
||||
query.page.value = 1
|
||||
void query.execute()
|
||||
}
|
||||
|
||||
async function refresh() {
|
||||
if (refreshing.value || loading.value)
|
||||
if (refreshing.value || query.loading.value)
|
||||
return
|
||||
refreshing.value = true
|
||||
try {
|
||||
await withMinimumDuration(load)
|
||||
await withMinimumDuration(() => query.execute())
|
||||
}
|
||||
finally {
|
||||
refreshing.value = false
|
||||
@@ -87,23 +61,23 @@ export function useOpsErrorsTable(timeRangeParams: Readonly<Ref<UsageTimeRangePa
|
||||
watchDebounced(
|
||||
[searchQuery, failureClass, route],
|
||||
() => {
|
||||
page.value = 1
|
||||
void load()
|
||||
query.page.value = 1
|
||||
void query.execute()
|
||||
},
|
||||
{ debounce: 250 },
|
||||
)
|
||||
|
||||
watch(timeRangeParams, () => {
|
||||
page.value = 1
|
||||
void load()
|
||||
query.page.value = 1
|
||||
void query.execute()
|
||||
})
|
||||
|
||||
onMounted(load)
|
||||
onMounted(() => void query.execute())
|
||||
|
||||
return {
|
||||
loading,
|
||||
loading: query.loading,
|
||||
refreshing,
|
||||
records,
|
||||
records: query.items,
|
||||
searchQuery,
|
||||
failureClass,
|
||||
route,
|
||||
|
||||
@@ -85,15 +85,4 @@ export function usageLegend(theme: UsageChartPalette, data: string[]) {
|
||||
}
|
||||
}
|
||||
|
||||
export function tooltipRows(params: unknown): Record<string, unknown>[] {
|
||||
const values = Array.isArray(params) ? params : [params]
|
||||
return values.filter(
|
||||
(value): value is Record<string, unknown> => typeof value === 'object' && value !== null,
|
||||
)
|
||||
}
|
||||
|
||||
export function tooltipIndex(source: unknown) {
|
||||
if (typeof source !== 'object' || source === null || !('dataIndex' in source))
|
||||
return -1
|
||||
return typeof source.dataIndex === 'number' ? source.dataIndex : -1
|
||||
}
|
||||
export { tooltipIndex, tooltipRows } from '@/components/charts/tooltip'
|
||||
|
||||
Reference in New Issue
Block a user