mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-01 17:00:21 +08:00
refactor: 优化智能分页 DOM 检测机制并添加密钥名称复制功能
- useSmartPagination 使用 ResizeObserver 替代 nextTick 确保 DOM 稳定后检测 - 添加防抖机制避免频繁触发检测 - 组件卸载时清理 ResizeObserver 资源 - ProviderDetailDrawer 密钥名称支持点击复制
This commit is contained in:
@@ -1,10 +1,11 @@
|
||||
import { ref, computed, watch, nextTick, type Ref, type ComputedRef } from 'vue'
|
||||
import { ref, computed, watch, nextTick, onUnmounted, type Ref, type ComputedRef } from 'vue'
|
||||
|
||||
/**
|
||||
* 智能分页 composable
|
||||
*
|
||||
* 根据列表容器的实际渲染高度自动决定是否分页以及每页条数。
|
||||
* 当列表总高度超过阈值时自动启用分页,低于阈值时恢复全量显示。
|
||||
* 使用 ResizeObserver 确保 DOM 稳定后再进行检测。
|
||||
*
|
||||
* @param items 响应式数据源(全量列表)
|
||||
* @param listRef 列表容器 DOM 引用
|
||||
@@ -19,6 +20,10 @@ export function useSmartPagination<T>(
|
||||
const itemsPerPage = ref(0) // 0 = 不分页
|
||||
const cachedAvgItemHeight = ref(0)
|
||||
|
||||
let resizeObserver: ResizeObserver | null = null
|
||||
let pendingDetect = false
|
||||
let detectDebounceTimer: ReturnType<typeof setTimeout> | null = null
|
||||
|
||||
const shouldPaginate = computed(() => {
|
||||
return itemsPerPage.value > 0 && items.value.length > itemsPerPage.value
|
||||
})
|
||||
@@ -51,10 +56,13 @@ export function useSmartPagination<T>(
|
||||
const scrollHeight = el.scrollHeight
|
||||
const renderedCount = paginatedItems.value.length
|
||||
|
||||
if (renderedCount > 0 && scrollHeight > 0) {
|
||||
cachedAvgItemHeight.value = scrollHeight / renderedCount
|
||||
// 确保 DOM 已渲染且有有效高度
|
||||
if (scrollHeight === 0 || renderedCount === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
cachedAvgItemHeight.value = scrollHeight / renderedCount
|
||||
|
||||
const estimatedTotalHeight = cachedAvgItemHeight.value * items.value.length
|
||||
if (estimatedTotalHeight > maxHeight && cachedAvgItemHeight.value > 0) {
|
||||
itemsPerPage.value = Math.max(Math.floor(maxHeight / cachedAvgItemHeight.value), 3)
|
||||
@@ -67,6 +75,47 @@ export function useSmartPagination<T>(
|
||||
}
|
||||
}
|
||||
|
||||
/** 防抖检测,避免频繁触发 */
|
||||
function debouncedDetect() {
|
||||
if (detectDebounceTimer) {
|
||||
clearTimeout(detectDebounceTimer)
|
||||
}
|
||||
detectDebounceTimer = setTimeout(() => {
|
||||
detect()
|
||||
detectDebounceTimer = null
|
||||
}, 50)
|
||||
}
|
||||
|
||||
/** 设置 ResizeObserver 监听 DOM 变化 */
|
||||
function setupResizeObserver() {
|
||||
cleanupResizeObserver()
|
||||
|
||||
const el = listRef.value
|
||||
if (!el) return
|
||||
|
||||
resizeObserver = new ResizeObserver(() => {
|
||||
// DOM 尺寸变化时重新检测
|
||||
if (pendingDetect) {
|
||||
pendingDetect = false
|
||||
debouncedDetect()
|
||||
}
|
||||
})
|
||||
|
||||
resizeObserver.observe(el)
|
||||
}
|
||||
|
||||
/** 清理 ResizeObserver */
|
||||
function cleanupResizeObserver() {
|
||||
if (resizeObserver) {
|
||||
resizeObserver.disconnect()
|
||||
resizeObserver = null
|
||||
}
|
||||
if (detectDebounceTimer) {
|
||||
clearTimeout(detectDebounceTimer)
|
||||
detectDebounceTimer = null
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置分页状态(数据源切换时调用) */
|
||||
function reset() {
|
||||
currentPage.value = 1
|
||||
@@ -74,11 +123,42 @@ export function useSmartPagination<T>(
|
||||
cachedAvgItemHeight.value = 0
|
||||
}
|
||||
|
||||
// 数据源变化时自动重新检测(immediate 确保首次挂载时也检测)
|
||||
/** 请求检测(会等待 DOM 稳定后执行) */
|
||||
function requestDetect() {
|
||||
pendingDetect = true
|
||||
|
||||
// 先尝试立即检测
|
||||
nextTick(() => {
|
||||
const el = listRef.value
|
||||
if (el && el.scrollHeight > 0) {
|
||||
// DOM 已就绪,直接检测
|
||||
pendingDetect = false
|
||||
detect()
|
||||
}
|
||||
// 否则等待 ResizeObserver 回调
|
||||
})
|
||||
}
|
||||
|
||||
// 监听 listRef 变化,设置/更新 ResizeObserver
|
||||
watch(listRef, (newEl) => {
|
||||
if (newEl) {
|
||||
setupResizeObserver()
|
||||
requestDetect()
|
||||
} else {
|
||||
cleanupResizeObserver()
|
||||
}
|
||||
}, { immediate: true })
|
||||
|
||||
// 数据源变化时重置页码并请求检测
|
||||
watch(items, () => {
|
||||
currentPage.value = 1
|
||||
nextTick(detect)
|
||||
}, { immediate: true, flush: 'post' })
|
||||
requestDetect()
|
||||
}, { flush: 'post' })
|
||||
|
||||
// 组件卸载时清理
|
||||
onUnmounted(() => {
|
||||
cleanupResizeObserver()
|
||||
})
|
||||
|
||||
return {
|
||||
currentPage,
|
||||
|
||||
@@ -234,7 +234,11 @@
|
||||
</div>
|
||||
<div class="flex flex-col min-w-0">
|
||||
<div class="flex items-center gap-1.5">
|
||||
<span class="text-sm font-medium truncate">{{ key.name || '未命名密钥' }}</span>
|
||||
<span
|
||||
class="text-sm font-medium truncate cursor-pointer hover:text-primary transition-colors"
|
||||
title="点击复制"
|
||||
@click.stop="copyToClipboard(key.name || '未命名密钥')"
|
||||
>{{ key.name || '未命名密钥' }}</span>
|
||||
<!-- OAuth 订阅类型标签 (Codex) -->
|
||||
<Badge
|
||||
v-if="key.oauth_plan_type"
|
||||
|
||||
Reference in New Issue
Block a user