mirror of
https://github.com/zyycn/codex-proxy-rs.git
synced 2026-11-06 08:06:19 +00:00
feat(ui): upgrade UI to v0.5.0 and simplify health popovers
Use shared hover and dismissal handling for health timeline popovers, remove host workarounds and redundant footer copy, and pin the UI dependency and submodule to v0.5.0. Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
@@ -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",
|
||||
|
||||
Generated
+12
-6
@@ -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
|
||||
|
||||
@@ -122,10 +122,6 @@ const metricItems = outcomeMeta
|
||||
</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="m-0 border-t border-cp-split pt-3 text-[10px] leading-[1.45] font-emphasis text-cp-text-quaternary">
|
||||
取消与调用方错误不计入可用性
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
@@ -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<DashboardHealthTimelinePoint>()
|
||||
const activeAnchor = shallowRef<HTMLElement | null>(null)
|
||||
const popoverOpen = shallowRef(false)
|
||||
const highlightedPointBucket = shallowRef<string>()
|
||||
let wavedCellIndexes = new Set<number>()
|
||||
let anchorXTo: ReturnType<typeof gsap.quickTo> | undefined
|
||||
let anchorYTo: ReturnType<typeof gsap.quickTo> | 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(() => {
|
||||
</template>
|
||||
|
||||
<template #body>
|
||||
<div class="relative flex flex-col">
|
||||
<!-- 同一锚点连续追向各时间格,避免切换原生锚点时的位置跳变 -->
|
||||
<span ref="popoverAnchor" aria-hidden="true" class="pointer-events-none absolute left-0 top-0 h-5 w-px opacity-0" />
|
||||
<div
|
||||
ref="timelineGrid" class="grid w-full grid-cols-[repeat(var(--timeline-columns),minmax(0,1fr))] items-end gap-x-0.5 gap-y-1 sm:grid-cols-[repeat(var(--timeline-columns-wide),minmax(0,1fr))]"
|
||||
:style="{ '--timeline-columns': Math.max(1, Math.ceil(points.length / 2)), '--timeline-columns-wide': Math.max(1, points.length) }"
|
||||
>
|
||||
<ZButton
|
||||
v-for="(point, pointIndex) in points"
|
||||
:key="point.bucketStart"
|
||||
data-health-timeline-point
|
||||
variant="text"
|
||||
:aria-disabled="!isInteractivePoint(point)"
|
||||
:tabindex="isInteractivePoint(point) ? 0 : -1"
|
||||
:aria-expanded="isActivePoint(point)"
|
||||
aria-haspopup="dialog"
|
||||
:aria-label="pointAccessibilityLabel(point)"
|
||||
class="group relative h-5! w-full min-w-0.5 bg-transparent! p-0! [&>span]:w-full"
|
||||
:class="isInteractivePoint(point) ? 'cursor-pointer' : 'cursor-default'"
|
||||
@mouseenter="activatePoint(point, pointIndex, $event)"
|
||||
@focus="activatePoint(point, pointIndex, $event)"
|
||||
@blur="closePointPopover"
|
||||
@click="activatePoint(point, pointIndex, $event)"
|
||||
>
|
||||
<span
|
||||
data-health-timeline-cell
|
||||
class="block h-3.5 w-full origin-bottom rounded-xs transition-[filter,box-shadow] duration-250 ease-[cubic-bezier(0.22,1,0.36,1)] motion-reduce:transition-none"
|
||||
:class="[
|
||||
healthStatusMeta[point.status].cellClass,
|
||||
isActivePoint(point)
|
||||
? 'brightness-105 shadow-[0_5px_10px_-5px_var(--cp-color-shadow)]'
|
||||
: isInteractivePoint(point)
|
||||
? 'group-hover:brightness-95'
|
||||
: undefined,
|
||||
]"
|
||||
/>
|
||||
</ZButton>
|
||||
</div>
|
||||
|
||||
<!-- 时间线负责逐格悬停,关闭时直接卸载浮层,避免退场期间锚点失效后重新定位 -->
|
||||
<ZPopover
|
||||
v-if="activePoint"
|
||||
ref="pointPopover"
|
||||
v-model="popoverOpen"
|
||||
placement="top"
|
||||
:offset="12"
|
||||
:reference-element="activeAnchor"
|
||||
:arrow-surface-class="healthPopoverArrowSurfaceClasses"
|
||||
>
|
||||
<div data-health-timeline-popover class="w-72 overflow-hidden rounded-cp-lg">
|
||||
<HealthTimelinePointPopover :point="activePoint" />
|
||||
<ZPopover
|
||||
ref="pointPopover"
|
||||
v-model="popoverOpen"
|
||||
trigger="hover"
|
||||
:show-after="0"
|
||||
:disabled="!activePoint"
|
||||
class="w-full! min-w-0"
|
||||
placement="top"
|
||||
:offset="12"
|
||||
:reference-element="popoverAnchor"
|
||||
:arrow-surface-class="healthPopoverArrowSurfaceClasses"
|
||||
@closed="activePoint = undefined"
|
||||
>
|
||||
<template #reference>
|
||||
<div class="relative flex w-full flex-col">
|
||||
<!-- 同一锚点连续追向各时间格,避免切换原生锚点时的位置跳变 -->
|
||||
<span ref="popoverAnchor" aria-hidden="true" class="pointer-events-none absolute left-0 top-0 h-5 w-px opacity-0" />
|
||||
<div
|
||||
ref="timelineGrid" class="grid w-full grid-cols-[repeat(var(--timeline-columns),minmax(0,1fr))] items-end gap-x-0.5 gap-y-1 sm:grid-cols-[repeat(var(--timeline-columns-wide),minmax(0,1fr))]"
|
||||
:style="{ '--timeline-columns': Math.max(1, Math.ceil(points.length / 2)), '--timeline-columns-wide': Math.max(1, points.length) }"
|
||||
>
|
||||
<ZButton
|
||||
v-for="(point, pointIndex) in points"
|
||||
:key="point.bucketStart"
|
||||
data-health-timeline-point
|
||||
variant="text"
|
||||
:aria-disabled="!isInteractivePoint(point)"
|
||||
:tabindex="isInteractivePoint(point) ? 0 : -1"
|
||||
:aria-expanded="isActivePoint(point)"
|
||||
aria-haspopup="dialog"
|
||||
:aria-label="pointAccessibilityLabel(point)"
|
||||
class="group relative h-5! w-full min-w-0.5 bg-transparent! p-0! [&>span]:w-full"
|
||||
:class="isInteractivePoint(point) ? 'cursor-pointer' : 'cursor-default'"
|
||||
@mouseenter="activatePoint(point, pointIndex, $event)"
|
||||
@focus="activatePoint(point, pointIndex, $event)"
|
||||
@click="activatePoint(point, pointIndex, $event)"
|
||||
>
|
||||
<span
|
||||
data-health-timeline-cell
|
||||
class="block h-3.5 w-full origin-bottom rounded-xs transition-[filter,box-shadow] duration-250 ease-[cubic-bezier(0.22,1,0.36,1)] motion-reduce:transition-none"
|
||||
:class="[
|
||||
healthStatusMeta[point.status].cellClass,
|
||||
isActivePoint(point)
|
||||
? 'brightness-105 shadow-[0_5px_10px_-5px_var(--cp-color-shadow)]'
|
||||
: isInteractivePoint(point)
|
||||
? 'group-hover:brightness-95'
|
||||
: undefined,
|
||||
]"
|
||||
/>
|
||||
</ZButton>
|
||||
</div>
|
||||
</div>
|
||||
</ZPopover>
|
||||
</div>
|
||||
</template>
|
||||
<div data-health-timeline-popover class="w-72 overflow-hidden rounded-cp-lg">
|
||||
<HealthTimelinePointPopover v-if="activePoint" :point="activePoint" />
|
||||
</div>
|
||||
</ZPopover>
|
||||
</template>
|
||||
</ZCard>
|
||||
</template>
|
||||
|
||||
+1
-1
Submodule modules/ui updated: 1496e21637...5eac879635
Reference in New Issue
Block a user