From 27cd042df9aca0383a49f3514bc21958dd890912 Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期一, 29 六月 2026 15:42:23 +0800
Subject: [PATCH] 银川 1.联调产品维护页面 2.添加IM即时通讯模块

---
 src/views/im/home/components/paged-scroller.vue |  109 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 109 insertions(+), 0 deletions(-)

diff --git a/src/views/im/home/components/paged-scroller.vue b/src/views/im/home/components/paged-scroller.vue
new file mode 100644
index 0000000..ed1842a
--- /dev/null
+++ b/src/views/im/home/components/paged-scroller.vue
@@ -0,0 +1,109 @@
+<script lang="ts" setup generic="T">
+import { computed, onBeforeUnmount, onMounted, ref, useTemplateRef, watch } from 'vue'
+
+defineOptions({ name: 'ImPagedScroller' })
+
+const props = withDefaults(
+  defineProps<{
+    itemKey?: string // 涓氬姟 id 瀛楁鍚嶏紙濡� 'userId' / 'id'锛夛紱涓嶄紶 / 瀛楁鍊奸潪 string|number 鏃跺洖閫� idx
+    items: T[] // 鍏ㄩ噺鏁版嵁
+    pageSize?: number // 姣忛〉娓叉煋鏉℃暟
+    threshold?: number // 璺濆簳澶氬皯 px 瑙﹀彂涓嬩竴椤�
+  }>(),
+  {
+    itemKey: undefined,
+    pageSize: 30,
+    threshold: 30
+  }
+)
+
+/** 瑙f瀽姣忔潯 item 鐨� :key锛歝aller 浼� itemKey 鍒欐寜瀛楁鍙栵紝鏃犳晥 / 缂哄け鍥為��绱㈠紩锛岄伩鍏嶄紶閿欏瓧娈垫椂鍏ㄨ〃 undefined key */
+function resolveItemKey(item: T, idx: number): number | string {
+  if (!props.itemKey || item == null || typeof item !== 'object') {
+    return idx
+  }
+  const value = (item as Record<string, unknown>)[props.itemKey]
+  return typeof value === 'string' || typeof value === 'number' ? value : idx
+}
+
+const scrollbarRef = useTemplateRef<HTMLDivElement>('scrollbarRef')
+const page = ref(1)
+
+const displayItems = computed(() => {
+  const limit = Math.min(page.value * props.pageSize, props.items.length)
+  return props.items.slice(0, limit)
+})
+
+const allLoaded = computed(() => displayItems.value.length >= props.items.length)
+
+/** 浠呭綋瓒呰繃涓�椤垫椂鎵嶆樉绀恒�屽凡鍒板簳閮ㄣ�嶏紝閬垮厤鐭垪琛ㄤ篃鍑虹幇杩欐潯鎻愮ず */
+const showFooter = computed(() => allLoaded.value && props.items.length > props.pageSize)
+
+let wrapEl: HTMLElement | null = null
+
+onMounted(() => {
+  wrapEl = scrollbarRef.value
+  wrapEl?.addEventListener('scroll', onScroll)
+})
+
+onBeforeUnmount(() => {
+  wrapEl?.removeEventListener('scroll', onScroll)
+})
+
+/** 鍒囨崲鏁版嵁婧愶紙濡傚垏浼氳瘽锛夋椂閲嶇疆鍒嗛〉锛氶伩鍏嶆柊鍒楄〃娌跨敤鏃� page锛岄灞忓嚭鐜扮┖娈� */
+watch(
+  () => props.items,
+  () => {
+    page.value = 1
+  }
+)
+
+/** 婊氬埌璺濆簳 threshold 鍐呮椂鑷 page锛屾墿鍑轰笅涓�娈靛垏鐗� */
+function onScroll(e: Event) {
+  const el = e.target as HTMLElement
+  if (el.scrollTop + el.clientHeight < el.scrollHeight - props.threshold) {
+    return
+  }
+  if (allLoaded.value) {
+    return
+  }
+  page.value++
+}
+
+defineExpose({
+  /** 鎵嬪姩婊氬埌椤堕儴 */
+  scrollTop: () => {
+    if (wrapEl) {
+      wrapEl.scrollTop = 0
+    }
+  },
+  /** 鎵嬪姩婊氬埌搴曢儴 */
+  scrollBottom: () => {
+    if (wrapEl) {
+      wrapEl.scrollTop = wrapEl.scrollHeight
+    }
+  }
+})
+</script>
+
+<template>
+  <!--
+    鍒嗛〉澧為噺婊氬姩瀹瑰櫒
+    - 婊氬埌搴曢儴鑷姩 page++锛岀洿鍒板叏閮ㄦ覆鏌撳畬
+    - 閫氳繃 slot 鏆撮湶姣忎竴椤癸紝璁╄皟鐢ㄦ柟鑷繁鍐冲畾娓叉煋
+  -->
+  <div ref="scrollbarRef" class="w-full h-full overflow-y-auto">
+    <slot
+      v-for="(item, idx) in displayItems"
+      :item="item"
+      :index="idx"
+      :key="resolveItemKey(item, idx)"
+    ></slot>
+    <div
+      v-if="showFooter"
+      class="py-3 text-xs text-center text-[var(--ant-color-text-secondary)]"
+    >
+      宸插埌搴曢儴
+    </div>
+  </div>
+</template>

--
Gitblit v1.9.3