feat(web-admin): add useListTableViewportHeight composable
Measure content viewport, table top, and pager to drive el-table height. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
7d78f76129
commit
55ac1ebbf6
1 file changed
+127
@@ -0,0 +1,127 @@
|
||||
import {
|
||||
nextTick,
|
||||
onBeforeUnmount,
|
||||
onMounted,
|
||||
ref,
|
||||
unref,
|
||||
watch,
|
||||
type Ref
|
||||
} from 'vue'
|
||||
import { useEventListener, useResizeObserver } from '@vueuse/core'
|
||||
import {
|
||||
LIST_TABLE_VIEWPORT_BOTTOM_GAP,
|
||||
LIST_TABLE_VIEWPORT_MIN_HEIGHT,
|
||||
calcListTableViewportHeight
|
||||
} from '@/composables/listTableViewportHeight'
|
||||
|
||||
export type UseListTableViewportHeightOptions = {
|
||||
/** 表格锚点(包住 ListTable/el-table 的元素,不含分页) */
|
||||
tableEl: Ref<HTMLElement | null | undefined> | (() => HTMLElement | null | undefined)
|
||||
/** 分页条根节点;无分页可省略 */
|
||||
pagerEl?: Ref<HTMLElement | null | undefined> | (() => HTMLElement | null | undefined)
|
||||
/** 可选:ListTable / el-table 组件实例,高度变化后调用 doLayout */
|
||||
tableRef?: Ref<any>
|
||||
minHeight?: number
|
||||
bottomGap?: number
|
||||
/** 主内容滚动根;默认 #app-content */
|
||||
scrollRootSelector?: string
|
||||
/** 为 false 时不自动监听(隐藏 Tab 可用) */
|
||||
enabled?: Ref<boolean> | boolean
|
||||
}
|
||||
|
||||
function resolveEl(
|
||||
source?: Ref<HTMLElement | null | undefined> | (() => HTMLElement | null | undefined)
|
||||
): HTMLElement | null {
|
||||
if (!source) return null
|
||||
if (typeof source === 'function') return source() ?? null
|
||||
return unref(source) ?? null
|
||||
}
|
||||
|
||||
export function useListTableViewportHeight(options: UseListTableViewportHeightOptions) {
|
||||
const tableHeight = ref(LIST_TABLE_VIEWPORT_MIN_HEIGHT)
|
||||
const minHeight = options.minHeight ?? LIST_TABLE_VIEWPORT_MIN_HEIGHT
|
||||
const bottomGap = options.bottomGap ?? LIST_TABLE_VIEWPORT_BOTTOM_GAP
|
||||
const scrollRootSelector = options.scrollRootSelector ?? '#app-content'
|
||||
|
||||
const isEnabled = () => {
|
||||
const e = options.enabled
|
||||
if (e === undefined) return true
|
||||
return !!unref(e)
|
||||
}
|
||||
|
||||
const recalc = () => {
|
||||
if (!isEnabled()) return
|
||||
if (typeof window === 'undefined') return
|
||||
|
||||
const table = resolveEl(options.tableEl)
|
||||
if (!table) return
|
||||
|
||||
const root =
|
||||
(document.querySelector(scrollRootSelector) as HTMLElement | null) ?? null
|
||||
const contentViewportBottom = root
|
||||
? root.getBoundingClientRect().bottom
|
||||
: window.innerHeight
|
||||
|
||||
const tableTop = table.getBoundingClientRect().top
|
||||
const pager = resolveEl(options.pagerEl)
|
||||
const pagerHeight = pager ? Math.ceil(pager.getBoundingClientRect().height) : 0
|
||||
|
||||
const next = calcListTableViewportHeight({
|
||||
contentViewportBottom,
|
||||
tableTop,
|
||||
pagerHeight,
|
||||
minHeight,
|
||||
bottomGap
|
||||
})
|
||||
|
||||
if (next !== tableHeight.value) {
|
||||
tableHeight.value = next
|
||||
}
|
||||
|
||||
nextTick(() => {
|
||||
const inst = options.tableRef ? unref(options.tableRef) : null
|
||||
if (inst && typeof inst.doLayout === 'function') {
|
||||
inst.doLayout()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
let raf = 0
|
||||
const scheduleRecalc = () => {
|
||||
if (raf) cancelAnimationFrame(raf)
|
||||
raf = requestAnimationFrame(() => {
|
||||
raf = 0
|
||||
recalc()
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
nextTick(scheduleRecalc)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (raf) cancelAnimationFrame(raf)
|
||||
})
|
||||
|
||||
useEventListener(window, 'resize', scheduleRecalc)
|
||||
|
||||
// 观察表锚点与分页尺寸变化(筛选展开会改变 tableTop)
|
||||
useResizeObserver(
|
||||
() => resolveEl(options.tableEl),
|
||||
() => scheduleRecalc()
|
||||
)
|
||||
useResizeObserver(
|
||||
() => resolveEl(options.pagerEl),
|
||||
() => scheduleRecalc()
|
||||
)
|
||||
useResizeObserver(
|
||||
() => document.querySelector(scrollRootSelector) as HTMLElement | null,
|
||||
() => scheduleRecalc()
|
||||
)
|
||||
|
||||
if (options.enabled !== undefined && typeof options.enabled !== 'boolean') {
|
||||
watch(options.enabled, () => scheduleRecalc())
|
||||
}
|
||||
|
||||
return { tableHeight, recalc: scheduleRecalc }
|
||||
}
|
||||
Reference in new issue
Block a user