mirror of
https://github.com/Kuingsmile/PicList.git
synced 2026-06-08 17:20:00 +08:00
✨ Feature(custom): add selected inhint and optimize UI of manage file explorer page
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user