Feature(custom): add selected inhint and optimize UI of manage file explorer page

This commit is contained in:
Kuingsmile
2026-01-03 15:12:42 +08:00
parent 0db59266a2
commit 94fe4f4972
8 changed files with 3865 additions and 4026 deletions

View File

@@ -1,5 +1,5 @@
<template>
<div ref="containerRef" class="virtual-scroller" :style="{ height: `${containerHeight}px` }" @scroll="handleScroll">
<div ref="containerRef" class="virtual-scroller" @scroll="handleScroll">
<div class="virtual-scroller-content" :style="contentStyles">
<div
class="virtual-scroller-viewport"
@@ -8,15 +8,11 @@
>
<div
v-for="realIndex in visibleIndexes"
:key="
itemsRef[realIndex] && itemsRef[realIndex][props.keyField || 'id']
? itemsRef[realIndex][props.keyField || 'id']
: realIndex
"
:key="items[realIndex] && items[realIndex][keyField || 'id'] ? items[realIndex][keyField || 'id'] : realIndex"
class="virtual-scroller-item"
:style="itemStyle"
>
<slot :item="itemsRef[realIndex]" :index="realIndex" />
<slot :item="items[realIndex]" :index="realIndex" />
</div>
</div>
</div>
@@ -24,73 +20,43 @@
</template>
<script setup lang="ts">
import { computed, onBeforeUnmount, onMounted, ref, useTemplateRef, watch } from 'vue'
import { computed, onBeforeUnmount, onMounted, ref, useTemplateRef } from 'vue'
import { useVirtualGrid } from '@/hooks/useVirtualGrid'
type Item = any
interface Breakpoint {
min: number
cols: number
}
const props = withDefaults(
defineProps<{
items: Item[]
itemHeight: number
height?: number
gridItems?: number
gridBreakpoints?: Breakpoint[]
bufferFactor?: number
pageMode?: boolean
keyField?: string
itemPadding?: number
viewMode?: 'list' | 'grid'
}>(),
{
height: 400,
gridItems: 1,
gridBreakpoints: () => [],
bufferFactor: 0.5,
pageMode: false,
keyField: 'id',
itemPadding: 0,
viewMode: 'grid',
},
)
const {
items,
itemHeight,
gridBreakpoints = [],
bufferFactor = 0.5,
keyField = 'id',
itemPadding = 8,
viewMode = 'grid',
} = defineProps<{
items: any[]
itemHeight: number
gridBreakpoints?: Breakpoint[]
bufferFactor?: number
keyField?: string
itemPadding?: number
viewMode?: 'list' | 'grid'
}>()
const containerRef = useTemplateRef('containerRef')
const containerHeight = ref<number>(props.pageMode ? 0 : props.height)
const containerHeight = ref(0)
const containerWidth = ref<number>(0)
const parentScrollListeners = ref<HTMLElement[]>([])
const itemsRef = ref<Item[]>(props.items)
watch(
() => props.items,
v => {
itemsRef.value = v
},
)
const localViewMode = ref<'list' | 'grid'>(props.viewMode)
watch(
() => props.viewMode,
v => {
localViewMode.value = v
},
)
const sortedBreakpoints = computed<Breakpoint[]>(() => [...props.gridBreakpoints].sort((a, b) => a.min - b.min))
const isForcedList = computed(() => localViewMode.value === 'list')
const sortedBreakpoints = computed<Breakpoint[]>(() => [...gridBreakpoints].sort((a, b) => a.min - b.min))
const effectiveCols = computed<number>(() => {
if (isForcedList.value) return 1
const base = Math.max(1, props.gridItems || 1)
if (viewMode === 'list') return 1
const w = containerWidth.value || 0
let cols = base
let cols = 1
for (const bp of sortedBreakpoints.value) {
if (w >= bp.min) cols = Math.max(1, bp.cols)
}
@@ -101,11 +67,11 @@ const isGridMode = computed(() => effectiveCols.value > 1)
const { gridCalculations, visibleIndexes, viewportOffset, updateScrollTop, scrollToItem, scrollToTop, scrollToBottom } =
useVirtualGrid({
items: itemsRef,
itemHeight: props.itemHeight,
items: () => items,
itemHeight,
containerHeight,
gridItems: effectiveCols,
bufferFactor: props.bufferFactor,
bufferFactor,
})
const contentStyles = computed(() => ({
@@ -118,13 +84,13 @@ const viewportStyle = computed(() => {
}
if (isGridMode.value) {
base['--items-per-row'] = String(effectiveCols.value)
base['--row-height'] = `${props.itemHeight}px`
base['--item-gap'] = `${props.itemPadding}px`
base['--row-height'] = `${itemHeight}px`
base['--item-gap'] = `${itemPadding}px`
}
return base
})
const itemStyle = computed(() => (isGridMode.value ? {} : { height: `${props.itemHeight}px` }))
const itemStyle = computed(() => (isGridMode.value ? {} : { height: `${itemHeight}px` }))
function handleScroll() {
const c = containerRef.value
@@ -133,7 +99,6 @@ function handleScroll() {
}
function handlePageScroll() {
if (!props.pageMode) return
const now = Date.now()
if (now - lastScrollTime.value < 16) return
lastScrollTime.value = now
@@ -158,74 +123,56 @@ let ro: ResizeObserver | null = null
const lastScrollTime = ref(0)
function updateContainerMetrics() {
const el = containerRef.value
if (!el) return
const rect = el.getBoundingClientRect()
if (!containerRef.value) return
const rect = containerRef.value.getBoundingClientRect()
containerWidth.value = rect.width
if (props.pageMode) {
containerHeight.value = Math.max(200, window.innerHeight - rect.top - 12)
} else {
containerHeight.value = props.height
}
containerHeight.value = Math.max(200, window.innerHeight - rect.top - 12)
}
onMounted(() => {
const el = containerRef.value
if (!el) return
ro = new ResizeObserver(updateContainerMetrics)
ro.observe(el as unknown as Element)
if (props.pageMode) {
ro.observe(document.documentElement)
window.addEventListener('scroll', handlePageScroll, { passive: true })
let parent = el.parentElement
while (parent) {
if (parent.scrollHeight > parent.clientHeight) {
parent.addEventListener('scroll', handlePageScroll, { passive: true })
parentScrollListeners.value.push(parent)
}
parent = parent.parentElement
}
}
updateContainerMetrics()
if (props.pageMode) {
window.addEventListener('resize', updateContainerMetrics, { passive: true })
}
})
onBeforeUnmount(() => {
if (ro) ro.disconnect()
window.removeEventListener('resize', updateContainerMetrics)
if (props.pageMode) {
window.removeEventListener('scroll', handlePageScroll)
parentScrollListeners.value.forEach(parent => {
parent.removeEventListener('scroll', handlePageScroll)
})
parentScrollListeners.value = []
}
})
function scrollTo(index: number) {
scrollToItem(index)
}
function setViewMode(mode: 'list' | 'grid') {
localViewMode.value = mode
}
function toggleViewMode() {
setViewMode(isGridMode.value ? 'list' : 'grid')
}
function refresh() {
updateContainerMetrics()
if (containerRef.value) {
updateScrollTop(containerRef.value.scrollTop)
}
if (props.pageMode) {
handlePageScroll()
}
handlePageScroll()
}
defineExpose({ scrollTo, scrollToTop, scrollToBottom, setViewMode, toggleViewMode, refresh })
onMounted(() => {
if (!containerRef.value) return
ro = new ResizeObserver(updateContainerMetrics)
ro.observe(containerRef.value)
ro.observe(document.documentElement)
window.addEventListener('scroll', handlePageScroll, { passive: true })
let parent = containerRef.value.parentElement
while (parent) {
if (parent.scrollHeight > parent.clientHeight) {
parent.addEventListener('scroll', handlePageScroll, { passive: true })
parentScrollListeners.value.push(parent)
}
parent = parent.parentElement
}
updateContainerMetrics()
window.addEventListener('resize', updateContainerMetrics, { passive: true })
})
onBeforeUnmount(() => {
if (ro) ro.disconnect()
window.removeEventListener('resize', updateContainerMetrics)
window.removeEventListener('scroll', handlePageScroll)
parentScrollListeners.value.forEach(parent => {
parent.removeEventListener('scroll', handlePageScroll)
})
parentScrollListeners.value = []
})
defineExpose({ scrollTo, scrollToTop, scrollToBottom, refresh })
</script>
<style scoped>