mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-09-24 16:05:44 +08:00
perf(frontend): optimize account table selection performance
- Batch state updates in swipe-select: 100 rows drag from 100 Set copies + 100 re-renders to 1 each - Replace selIds.includes() with Set.has() for checkbox rendering (O(n²) → O(n)) - Add RAF throttle to mousemove handler to cap at 60fps
This commit is contained in:
@@ -25,6 +25,7 @@ export interface SwipeSelectAdapter {
|
||||
isSelected: (id: number) => boolean
|
||||
select: (id: number) => void
|
||||
deselect: (id: number) => void
|
||||
batchUpdate?: (updater: (draft: Set<number>) => void) => void
|
||||
}
|
||||
|
||||
export function useSwipeSelect(
|
||||
@@ -140,16 +141,30 @@ export function useSwipeSelect(
|
||||
const lo = Math.min(rangeMin, prevMin)
|
||||
const hi = Math.max(rangeMax, prevMax)
|
||||
|
||||
for (let i = lo; i <= hi && i < cachedRows.length; i++) {
|
||||
const id = getRowId(cachedRows[i])
|
||||
if (id === null) continue
|
||||
if (i >= rangeMin && i <= rangeMax) {
|
||||
if (dragMode === 'select') adapter.select(id)
|
||||
else adapter.deselect(id)
|
||||
} else {
|
||||
const wasSelected = initialSelectedSnapshot.get(id) ?? false
|
||||
if (wasSelected) adapter.select(id)
|
||||
else adapter.deselect(id)
|
||||
if (adapter.batchUpdate) {
|
||||
adapter.batchUpdate((draft) => {
|
||||
for (let i = lo; i <= hi && i < cachedRows.length; i++) {
|
||||
const id = getRowId(cachedRows[i])
|
||||
if (id === null) continue
|
||||
const shouldBeSelected = (i >= rangeMin && i <= rangeMax)
|
||||
? (dragMode === 'select')
|
||||
: (initialSelectedSnapshot.get(id) ?? false)
|
||||
if (shouldBeSelected) draft.add(id)
|
||||
else draft.delete(id)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
for (let i = lo; i <= hi && i < cachedRows.length; i++) {
|
||||
const id = getRowId(cachedRows[i])
|
||||
if (id === null) continue
|
||||
if (i >= rangeMin && i <= rangeMax) {
|
||||
if (dragMode === 'select') adapter.select(id)
|
||||
else adapter.deselect(id)
|
||||
} else {
|
||||
const wasSelected = initialSelectedSnapshot.get(id) ?? false
|
||||
if (wasSelected) adapter.select(id)
|
||||
else adapter.deselect(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
lastEndIndex = endIndex
|
||||
@@ -306,12 +321,17 @@ export function useSwipeSelect(
|
||||
window.getSelection()?.removeAllRanges()
|
||||
}
|
||||
|
||||
let moveRAF = 0
|
||||
|
||||
function onMouseMove(e: MouseEvent) {
|
||||
if (!isDragging.value) return
|
||||
lastMouseY = e.clientY
|
||||
updateMarquee(e.clientY)
|
||||
const rowIdx = findRowIndexAtY(e.clientY)
|
||||
if (rowIdx >= 0 && rowIdx !== lastEndIndex) applyRange(rowIdx)
|
||||
cancelAnimationFrame(moveRAF)
|
||||
moveRAF = requestAnimationFrame(() => {
|
||||
updateMarquee(lastMouseY)
|
||||
const rowIdx = findRowIndexAtY(lastMouseY)
|
||||
if (rowIdx >= 0 && rowIdx !== lastEndIndex) applyRange(rowIdx)
|
||||
})
|
||||
autoScroll(e)
|
||||
}
|
||||
|
||||
@@ -332,6 +352,7 @@ export function useSwipeSelect(
|
||||
cachedRows = []
|
||||
initialSelectedSnapshot.clear()
|
||||
cachedScrollParent = null
|
||||
cancelAnimationFrame(moveRAF)
|
||||
stopAutoScroll()
|
||||
removeMarquee()
|
||||
document.removeEventListener('selectstart', onSelectStart)
|
||||
|
||||
@@ -76,6 +76,12 @@ export function useTableSelection<T>({ rows, getId }: UseTableSelectionOptions<T
|
||||
replaceSelectedSet(next)
|
||||
}
|
||||
|
||||
const batchUpdate = (updater: (draft: Set<number>) => void) => {
|
||||
const draft = new Set(selectedSet.value)
|
||||
updater(draft)
|
||||
replaceSelectedSet(draft)
|
||||
}
|
||||
|
||||
const selectVisible = () => {
|
||||
toggleVisible(true)
|
||||
}
|
||||
@@ -93,6 +99,7 @@ export function useTableSelection<T>({ rows, getId }: UseTableSelectionOptions<T
|
||||
clear,
|
||||
removeMany,
|
||||
toggleVisible,
|
||||
selectVisible
|
||||
selectVisible,
|
||||
batchUpdate
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,7 +152,7 @@
|
||||
/>
|
||||
</template>
|
||||
<template #cell-select="{ row }">
|
||||
<input type="checkbox" :checked="selIds.includes(row.id)" @change="toggleSel(row.id)" class="rounded border-gray-300 text-primary-600 focus:ring-primary-500" />
|
||||
<input type="checkbox" :checked="isSelected(row.id)" @change="toggleSel(row.id)" class="rounded border-gray-300 text-primary-600 focus:ring-primary-500" />
|
||||
</template>
|
||||
<template #cell-name="{ row, value }">
|
||||
<div class="flex flex-col">
|
||||
@@ -597,7 +597,8 @@ const {
|
||||
clear: clearSelection,
|
||||
removeMany: removeSelectedAccounts,
|
||||
toggleVisible,
|
||||
selectVisible: selectPage
|
||||
selectVisible: selectPage,
|
||||
batchUpdate
|
||||
} = useTableSelection<Account>({
|
||||
rows: accounts,
|
||||
getId: (account) => account.id
|
||||
@@ -606,7 +607,8 @@ const {
|
||||
useSwipeSelect(accountTableRef, {
|
||||
isSelected,
|
||||
select,
|
||||
deselect
|
||||
deselect,
|
||||
batchUpdate
|
||||
})
|
||||
|
||||
const resetAutoRefreshCache = () => {
|
||||
|
||||
@@ -972,7 +972,8 @@ const {
|
||||
deselect,
|
||||
clear: clearSelectedProxies,
|
||||
removeMany: removeSelectedProxies,
|
||||
toggleVisible
|
||||
toggleVisible,
|
||||
batchUpdate
|
||||
} = useTableSelection<Proxy>({
|
||||
rows: proxies,
|
||||
getId: (proxy) => proxy.id
|
||||
@@ -980,7 +981,8 @@ const {
|
||||
useSwipeSelect(proxyTableRef, {
|
||||
isSelected,
|
||||
select,
|
||||
deselect
|
||||
deselect,
|
||||
batchUpdate
|
||||
})
|
||||
const accountsProxy = ref<Proxy | null>(null)
|
||||
const proxyAccounts = ref<ProxyAccountSummary[]>([])
|
||||
|
||||
Reference in New Issue
Block a user