diff --git a/frontend/package.json b/frontend/package.json index d53ee33f4..76da95d45 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -17,7 +17,7 @@ }, "dependencies": { "@boxicons/vue": "1.0.1", - "@codex-proxy/ui": "github:zyycn/codex-proxy-ui#v0.4.1", + "@codex-proxy/ui": "github:zyycn/codex-proxy-ui#v0.5.0", "@fontsource-variable/inter": "^5.3.0", "@fontsource-variable/jetbrains-mono": "^5.3.0", "@lucide/vue": "^1.47.0", diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 87fa1dbe3..8c52bae0f 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -195,8 +195,8 @@ importers: specifier: 1.0.1 version: 1.0.1(vue@3.5.43(typescript@6.0.3)) '@codex-proxy/ui': - specifier: github:zyycn/codex-proxy-ui#v0.4.1 - version: https://codeload.github.com/zyycn/codex-proxy-ui/tar.gz/1496e21637e7e80e87c7f75387e27ced5029187a(axios@1.20.0(debug@4.4.3))(change-case@5.4.4)(vite@8.3.0(@types/node@26.6.2)(jiti@2.7.0)(yaml@2.9.0))(vue@3.5.43(typescript@6.0.3)) + specifier: github:zyycn/codex-proxy-ui#v0.5.0 + version: https://codeload.github.com/zyycn/codex-proxy-ui/tar.gz/5eac879635ab4c15cfeea71d3d65fbf467780bbc(axios@1.20.0(debug@4.4.3))(change-case@5.4.4)(vite@8.3.0(@types/node@26.6.2)(jiti@2.7.0)(yaml@2.9.0))(vue@3.5.43(typescript@6.0.3)) '@fontsource-variable/inter': specifier: ^5.3.0 version: 5.3.0 @@ -418,9 +418,9 @@ packages: resolution: {integrity: sha512-y7/yvZ2TPAnR9+jnc00klvNNLkJiXFFrQA/hlLCcxA9a2A4zQIOimyFQ9XfwYKiGD1fb5GY8vbKIIgO8d5Tb2A==} engines: {node: '>= 20.12.0'} - '@codex-proxy/ui@https://codeload.github.com/zyycn/codex-proxy-ui/tar.gz/1496e21637e7e80e87c7f75387e27ced5029187a': - resolution: {gitHosted: true, integrity: sha512-/buYpi7vGPyKu0dwO4/P+dCYruUEjGDDlKGiVsW79BAtTmtVTDYo2DhdBFN5+haqTg+QjqEvTcym+2Fu9hOgSg==, tarball: https://codeload.github.com/zyycn/codex-proxy-ui/tar.gz/1496e21637e7e80e87c7f75387e27ced5029187a} - version: 0.4.1 + '@codex-proxy/ui@https://codeload.github.com/zyycn/codex-proxy-ui/tar.gz/5eac879635ab4c15cfeea71d3d65fbf467780bbc': + resolution: {gitHosted: true, integrity: sha512-/n01jJCEKGxCzwkHkpBFUOP3Z+MeV5ufA/14qnrHJ3k9pm9cdb083Nhvd4KaIP9PftHXbCOISUzY9D9EzTZWBA==, tarball: https://codeload.github.com/zyycn/codex-proxy-ui/tar.gz/5eac879635ab4c15cfeea71d3d65fbf467780bbc} + version: 0.5.0 engines: {node: '>=24'} peerDependencies: vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -1326,6 +1326,9 @@ packages: csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + dayjs@1.11.23: + resolution: {integrity: sha512-QDTCU0M0MxR3hQfnlDJfwekQiaanm1ubOD231u73WBckQ/fsamwRLiE2GBz6D3a/xF1NgfiDLJjXBa1hYOYTtQ==} + debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -2825,13 +2828,14 @@ snapshots: fast-wrap-ansi: 0.2.2 sisteransi: 1.0.5 - '@codex-proxy/ui@https://codeload.github.com/zyycn/codex-proxy-ui/tar.gz/1496e21637e7e80e87c7f75387e27ced5029187a(axios@1.20.0(debug@4.4.3))(change-case@5.4.4)(vite@8.3.0(@types/node@26.6.2)(jiti@2.7.0)(yaml@2.9.0))(vue@3.5.43(typescript@6.0.3))': + '@codex-proxy/ui@https://codeload.github.com/zyycn/codex-proxy-ui/tar.gz/5eac879635ab4c15cfeea71d3d65fbf467780bbc(axios@1.20.0(debug@4.4.3))(change-case@5.4.4)(vite@8.3.0(@types/node@26.6.2)(jiti@2.7.0)(yaml@2.9.0))(vue@3.5.43(typescript@6.0.3))': dependencies: '@ant-design/colors': 8.0.1 '@ant-design/fast-color': 3.0.1 '@lucide/vue': 1.54.0(vue@3.5.43(typescript@6.0.3)) '@vueuse/core': 15.0.0(vue@3.5.43(typescript@6.0.3)) '@vueuse/integrations': 15.0.0(axios@1.20.0(debug@4.4.3))(change-case@5.4.4)(sortablejs@1.15.7)(vue@3.5.43(typescript@6.0.3)) + dayjs: 1.11.23 dompurify: 3.4.16 es-toolkit: 1.52.0 gsap: 3.15.0 @@ -3611,6 +3615,8 @@ snapshots: csstype@3.2.3: {} + dayjs@1.11.23: {} + debug@4.4.3: dependencies: ms: 2.1.3 diff --git a/frontend/src/components/usage/HealthTimelinePointPopover.vue b/frontend/src/components/usage/HealthTimelinePointPopover.vue index 33e33fadb..5f693ef7d 100644 --- a/frontend/src/components/usage/HealthTimelinePointPopover.vue +++ b/frontend/src/components/usage/HealthTimelinePointPopover.vue @@ -122,10 +122,6 @@ const metricItems = outcomeMeta - -

- 取消与调用方错误不计入可用性 -

diff --git a/frontend/src/components/usage/RequestHealthTimelineCard.vue b/frontend/src/components/usage/RequestHealthTimelineCard.vue index ee3e1f02e..3d2a672eb 100644 --- a/frontend/src/components/usage/RequestHealthTimelineCard.vue +++ b/frontend/src/components/usage/RequestHealthTimelineCard.vue @@ -2,9 +2,9 @@ import type { DashboardHealthTimeline, DashboardHealthTimelinePoint } from '@/api' import { ZButton, ZCard, ZPopover } from '@codex-proxy/ui' -import { useEventListener, usePreferredReducedMotion, useResizeObserver } from '@vueuse/core' +import { usePreferredReducedMotion, useResizeObserver } from '@vueuse/core' import { gsap } from 'gsap' -import { computed, nextTick, onBeforeUnmount, shallowRef, useTemplateRef, watch } from 'vue' +import { computed, onBeforeUnmount, shallowRef, useTemplateRef, watch } from 'vue' import HealthTimelinePointPopover from '@/components/usage/HealthTimelinePointPopover.vue' import { formatHealthCount, healthEligibleRequestCount, healthLegend, healthObservedRequestCount, healthStatusMeta } from '@/components/usage/shared/health' @@ -24,9 +24,7 @@ const preferredMotion = usePreferredReducedMotion() const points = computed(() => props.timeline.points) const activePoint = shallowRef() -const activeAnchor = shallowRef(null) const popoverOpen = shallowRef(false) -const highlightedPointBucket = shallowRef() let wavedCellIndexes = new Set() let anchorXTo: ReturnType | undefined let anchorYTo: ReturnType | undefined @@ -36,34 +34,23 @@ function isInteractivePoint(point: DashboardHealthTimelinePoint) { } function isActivePoint(point: DashboardHealthTimelinePoint) { - return popoverOpen.value && highlightedPointBucket.value === point.bucketStart + return popoverOpen.value && activePoint.value?.bucketStart === point.bucketStart } -async function activatePoint(point: DashboardHealthTimelinePoint, pointIndex: number, event: Event) { +function activatePoint(point: DashboardHealthTimelinePoint, pointIndex: number, event: Event) { if (!(event.currentTarget instanceof HTMLElement)) return - // 浮层卸载会恢复原焦点,不能把关闭期间的焦点恢复当作再次打开 - if (event instanceof FocusEvent && !activePoint.value && activeAnchor.value && !event.relatedTarget) - return if (!isInteractivePoint(point)) { closePointPopover() return } const anchor = event.currentTarget - const opening = !activePoint.value || !popoverOpen.value + const opening = !popoverOpen.value activePoint.value = point - highlightedPointBucket.value = point.bucketStart animatePointWave(pointIndex) - // 首次挂载后再打开,让浮层先建立定位监听,后续逐格切换直接更新 - if (opening) { - await nextTick() - if (activePoint.value !== point) - return - } positionPointAnchor(anchor, opening) - activeAnchor.value = popoverAnchor.value popoverOpen.value = true } @@ -105,20 +92,7 @@ function stopPointAnchor() { } function closePointPopover() { - void resetPointInteraction() -} - -async function resetPointInteraction() { - highlightedPointBucket.value = undefined - activePoint.value = undefined - releasePointWave() - stopPointAnchor() - // 锚点保留到浮层卸载完成,避免焦点恢复重新打开气泡或关闭时位置跳变 - await nextTick() - if (!activePoint.value) { - popoverOpen.value = false - activeAnchor.value = null - } + pointPopover.value?.close() } function timelineButtons() { @@ -223,12 +197,12 @@ function pointAccessibilityLabel(point: DashboardHealthTimelinePoint) { } watch(popoverOpen, (open) => { - if (!open && activePoint.value) - closePointPopover() + if (!open) { + releasePointWave() + stopPointAnchor() + } }) -useEventListener(timelineGrid, 'mouseleave', closePointPopover) - useResizeObserver(timelineGrid, () => { if (!popoverOpen.value || !activePoint.value) return @@ -275,60 +249,63 @@ onBeforeUnmount(() => { diff --git a/modules/ui b/modules/ui index 1496e2163..5eac87963 160000 --- a/modules/ui +++ b/modules/ui @@ -1 +1 @@ -Subproject commit 1496e21637e7e80e87c7f75387e27ced5029187a +Subproject commit 5eac879635ab4c15cfeea71d3d65fbf467780bbc