Quellcode durchsuchen

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>
zlwu vor 2 Monaten
Ursprung
Commit
55ac1ebbf6
1 geänderte Dateien mit 127 neuen und 0 gelöschten Zeilen
  1. 127 0
      web-admin/src/composables/useListTableViewportHeight.ts

+ 127 - 0
web-admin/src/composables/useListTableViewportHeight.ts

@@ -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 }
+}