需求:滚动懒加载不同模块的数据

36 阅读2分钟
  1. 功能
  • 滚动右侧内容 → 左侧菜单自动跟随高亮。

  • 点击左侧菜单 → 平滑跳转到对应内容。

  • 懒加载 & 无限滚动 保持不变。

  1. 效果

image.png

  1. 代码
<template>
  <div class="app">
    <!-- 左侧菜单 -->
    <div class="sidebar">
      <el-menu :default-active="activeMenu" @select="handleMenuClick">
        <el-menu-item index="info">基本信息</el-menu-item>
        <el-menu-item index="work">工作经历</el-menu-item>
        <el-menu-item index="edu">教育背景</el-menu-item>
        <el-menu-item index="skills">技能专长</el-menu-item>
        <el-menu-item index="paper">论文成果</el-menu-item>
        <el-menu-item index="project">项目经验</el-menu-item>
        <el-menu-item index="awards">获奖情况</el-menu-item>
        <el-menu-item index="contact">联系方式</el-menu-item>
      </el-menu>
    </div>

    <!-- 右侧内容 -->
    <div
      class="content"
      ref="contentRef"
      v-infinite-scroll="loadMore"
      :infinite-scroll-disabled="noMore"
      :infinite-scroll-distance="50"
      @scroll="onScroll"
    >
      <!-- 固定模块 -->
      <section id="info" class="section">基本信息内容...</section>
      <section id="work" class="section">工作经历内容...</section>
      <section id="edu" class="section">教育背景内容...</section>
      <section id="skills" class="section">技能专长内容...</section>
      <section id="paper" class="section">论文成果内容...</section>
      <section id="project" class="section">项目经验内容...</section>
      <section id="awards" class="section">获奖情况内容...</section>
      <section id="contact" class="section">联系方式内容...</section>

      <!-- 动态加载模块 -->
      <section
        v-for="(item, index) in moreSections"
        :key="index"
        :id="'extra-' + index"
        class="section"
      >
        <div v-if="item.loading" class="loading-box">
          <el-icon class="is-loading"><Loading /></el-icon> 加载中...
        </div>
        <div v-else>
          {{ item.content }}
        </div>
      </section>

      <p v-if="noMore" class="end-text">没有更多数据了</p>
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted, nextTick } from "vue";
import { Loading } from "@element-plus/icons-vue";
import "element-plus/es/components/infinite-scroll/style/css";

const activeMenu = ref("info");
const contentRef = ref(null);
let isClickScrolling = false;

const moreSections = ref([]);
const noMore = ref(false);

// 左侧点击
const handleMenuClick = (key) => {
  activeMenu.value = key;
  const el = document.getElementById(key);
  if (el) {
    isClickScrolling = true;
    el.scrollIntoView({ behavior: "smooth", block: "start" });
    setTimeout(() => {
      isClickScrolling = false;
    }, 500);
  }
};

// 滚动高亮
const onScroll = () => {
  if (isClickScrolling) return;

  const sections = [
    "info",
    "work",
    "edu",
    "skills",
    "paper",
    "project",
    "awards",
    "contact",
    ...moreSections.value.map((_, i) => "extra-" + i),
  ];

  let current = "info";
  const scrollTop = contentRef.value.scrollTop;

  for (let id of sections) {
    const el = document.getElementById(id);
    if (el && el.offsetTop - 80 <= scrollTop) {
      current = id;
    }
  }
  activeMenu.value = current;
};

// 单模块加载
const loadMore = () => {
  if (noMore.value) return;

  if (moreSections.value.length >= 5) {
    noMore.value = true;
    return;
  }

  const newItem = {
    loading: true,
    content: "",
  };
  moreSections.value.push(newItem);

  // 模拟异步加载
  setTimeout(() => {
    newItem.loading = false;
    newItem.content = `动态加载模块 ${moreSections.value.length} 的内容...`;
  }, 1000);
};

// 检查是否填满一屏
const checkFillScreen = async () => {
  await nextTick();
  const container = contentRef.value;
  if (container.scrollHeight <= container.clientHeight && !noMore.value) {
    loadMore();
    checkFillScreen(); // 递归继续加载,直到填满或 noMore
  }
};

onMounted(() => {
  activeMenu.value = "info";
  checkFillScreen();
});
</script>

<style scoped>
.app {
  display: flex;
  height: 100vh;
}
.sidebar {
  width: 220px;
  border-right: 1px solid #ddd;
}
.content {
  flex: 1;
  overflow-y: auto;
  padding: 20px;
}
.section {
  height: 300px;
  margin-bottom: 20px;
  border: 1px solid #eee;
  background: #fafafa;
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: 18px;
}
.loading-box {
  display: flex;
  align-items: center;
  justify-content: center;
  color: #666;
}
.end-text {
  text-align: center;
  padding: 20px;
  color: #888;
}
</style>