You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Vue3+Bootstrap5+Node.js:DOM添加后如何打开Modal?

Vue + Bootstrap 动态添加Modal的自动打开与互斥显示问题

需求说明

用户点击按钮后,将目标Modal数据加载到列表并渲染到DOM,随后自动打开该Modal。

初始问题

已实现点击按钮触发事件将Modal数据添加到列表,但调用new Modal()时返回null——原因是代码在Vue完成DOM重渲染前就执行了,此时Modal元素还未被渲染到页面。

相关代码片段

App.vue

<template>
  <div class="container-center">
    <AnimeList />
  </div>
</template>

AnimeList.vue

<template>
  <div class="containerlist">
    <AnimeCardModal
      v-for="anime in animeList"
      :anime="anime"
      @checkAnimeListForRelatedEvent="checkAnimeListForRelated"
    />
  </div>
</template>
<script setup>
import { defineComponent } from "vue";
import AnimeCardModal from "./AnimeCardModal.vue";
import axios from "axios";
</script>
<script>
export default defineComponent({
  name: "AnimeList",
  data() {
    return {
      animeList: [],
      limit: 30,
      page: 1,
      reachedEnd: false,
    };
  },
  methods: {
    async getAnimeLsit() {
      const res = await axios.get("/api", {
        params: { page: this.page, limit: this.limit },
      });
      this.animeList = res.data.data;
      this.page = res.data.next.page;
      this.limit = res.data.next.limit;
    },
    async getNextBatch() {
      let bottomOfWindow =
        document.documentElement.scrollTop + window.innerHeight ===
        document.documentElement.offsetHeight;
      if (bottomOfWindow && !this.reachedEnd) {
        const res = await axios.get("/api", {
          params: { page: this.page, limit: this.limit },
        });
        res.data.data.map((item) => {
          this.animeList.push(item);
        });
        if (!res.data.next) {
          this.reachedEnd = true;
        } else {
          this.page = res.data.next.page;
          this.limit = res.data.next.limit;
        }
      }
    },
    async checkAnimeListForRelated(animeID) {
      if (!this.animeList.filter((anime) => anime.id === animeID).length > 0) {
        const res = await axios.get("/api/anime", {
          params: { id: animeID },
        });
        if (res.data.data.length > 0) {
          this.animeList.push(res.data.data[0]);
          console.log("added to list");
        }
      }
    },
  },
  created() {
    window.addEventListener("scroll", this.getNextBatch);
  },
  deactivated() {
    window.removeEventListener("scroll", this.getNextBatch);
  },
  async mounted() {
    await this.getAnimeLsit();
  },
  components: {
    AnimeCardModal,
  },
});
</script>

已尝试的解决方法与新问题

使用this.$parent.$nextTick()等待DOM渲染完成后再实例化Modal,现在可以正常打开Modal,但出现Modal叠加的问题——手动调用show()不会自动关闭之前打开的Modal,而使用Bootstrap原生属性:

:data-bs-target="'#anime-card-modal-' + anime.id"
data-bs-toggle="modal"

点击时会自动关闭其他Modal再打开新的。需要用代码实现相同的互斥效果。


解决方案

1. 正确等待DOM渲染完成

不需要调用this.$parent.$nextTick,直接使用当前组件的this.$nextTick(Vue2)即可,确保Vue完成DOM更新后再操作元素:

async checkAnimeListForRelated(animeID) {
  // 优化判断逻辑:用some替代filter,性能更优
  const hasAnime = this.animeList.some(anime => anime.id === animeID);
  if (!hasAnime) {
    const res = await axios.get("/api/anime", {
      params: { id: animeID },
    });
    if (res.data.data.length > 0) {
      this.animeList.push(res.data.data[0]);
      console.log("added to list");
      // 等待当前组件DOM更新完成
      await this.$nextTick();
    }
  }
  // 关闭所有已打开的Modal
  document.querySelectorAll('.modal.show').forEach(modalEl => {
    const modalInstance = bootstrap.Modal.getInstance(modalEl);
    if (modalInstance) {
      modalInstance.hide();
    }
  });
  // 实例化并打开目标Modal
  const modalEl = document.getElementById(`anime-card-modal-${animeID}`);
  const myModal = new bootstrap.Modal(modalEl);
  myModal.show();
}

2. 实现Modal互斥的核心逻辑

Bootstrap原生的data-bs-toggle会自动关闭其他Modal,本质是内部遍历所有已打开的Modal并调用hide()。我们可以手动实现这一步:

  • 通过document.querySelectorAll('.modal.show')获取所有正在显示的Modal元素
  • 用bootstrap.Modal.getInstance()获取每个Modal的实例,调用hide()关闭
  • 再实例化并打开目标Modal

3. 额外优化建议

可以在组件的data中维护一个currentModalInstance变量,记录当前打开的Modal实例,每次打开新Modal前直接调用该实例的hide(),避免遍历所有Modal,性能更优:

data() {
  return {
    // ...其他数据
    currentModalInstance: null
  }
},
// 在打开Modal时:
if (this.currentModalInstance) {
  this.currentModalInstance.hide();
}
const myModal = new bootstrap.Modal(modalEl);
myModal.show();
this.currentModalInstance = myModal;

内容的提问来源于stack exchange,提问作者Dumidor Dumbleplex

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 17:55:18