Vue3 +Element Plus 的el _table 实现表格无限循环滚动

759 阅读1分钟
  <div class="wh-full">
    <el-`table`
      v-bind="$attrs"
      :data="displayData"
      :height="height"
      ref="tableRef"
      @mouseenter="handleMouseEnter"
      @mouseleave="handleMouseLeave"
    >
      <slot />
    </el-table>
  </div>
</template>

<script lang="ts" setup>
import { ref, onUnmounted, watch, nextTick, computed } from "vue";

interface Props {
  data: any[]; // 表格数据
  height: string | number; // 表格高度
  autoStart?: boolean; // 是否自动滚动
  autoLength?: number; // 自动滚动时,数据长度小于等于此值时不滚动
  scrollInterval?: number; // 控制滚动频率
}

const props = withDefaults(defineProps<Props>(), {
  data: () => [],
  height: "100%",
  autoStart: true,
  autoLength: 4,
  scrollInterval: 30
});

const tableRef = ref();
const isScrolling = ref(false);
const scrollTimerId = ref<number | null>(null);
const animationFrameId = ref<number | null>(null);

// 计算展示数据
const displayData = computed(() => {
  if (props.data.length <= props.autoLength) return props.data;
  return [...props.data, ...props.data];
});

// 清理定时器和动画帧
const clearTimers = () => {
  if (scrollTimerId.value) {
    clearTimeout(scrollTimerId.value);
    scrollTimerId.value = null;
  }
  if (animationFrameId.value) {
    cancelAnimationFrame(animationFrameId.value);
    animationFrameId.value = null;
  }
};

// 获取表格容器元素
const getTableWrapper = () => {
  const bodyWrapper = tableRef.value?.$refs.bodyWrapper;
  return bodyWrapper?.firstElementChild?.firstElementChild;
};

// 滚动处理函数
const handleScroll = (tableWrapper: HTMLElement) => {
  if (!tableWrapper || !isScrolling.value) return;

  tableWrapper.scrollTop += 1;
  if (tableWrapper.scrollTop + tableWrapper.clientHeight >= tableWrapper.scrollHeight) {
    tableWrapper.scrollTop = tableWrapper.scrollTop - tableWrapper.scrollHeight / 2;
  }

  scrollTimerId.value = window.setTimeout(() => {
    animationFrameId.value = requestAnimationFrame(() => handleScroll(tableWrapper));
  }, props.scrollInterval);
};

// 开始滚动
const startScroll = () => {
  const tableWrapper = getTableWrapper();
  if (!tableWrapper || props.data.length <= props.autoLength) return;
  if (tableWrapper.scrollHeight / 2 < tableWrapper.clientHeight) return;

  isScrolling.value = true;
  handleScroll(tableWrapper);
};

// 停止滚动
const stopScroll = () => {
  isScrolling.value = false;
  clearTimers();
};

// 鼠标事件处理
const handleMouseEnter = () => stopScroll();
const handleMouseLeave = () => startScroll();

// 监听数据变化
watch(
  () => props.data,
  newData => {
    if (newData?.length > 0 && props.autoStart) {
      nextTick(() => {
        setTimeout(() => {
          startScroll();
        }, 1000);
      });
    }
  },
  { immediate: true }
);

// 组件卸载时清理
onUnmounted(() => {
  stopScroll();
});
</script>

<style scoped lang="scss"></style>