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

如何在Vue3中使用Bootstrap Modal实现预加载Spinner并解决调用报错

问题根因
  1. 调用Spinner.showSpinner()的时机早于App.vue组件挂载完成,路由预加载逻辑执行时,DOM中还不存在#mdlSpinner元素,给Bootstrap Modal传入空值初始化触发非法调用错误
  2. 你编写的Bootstrap模态框结构不符合官方要求,缺少.modal-dialog、.modal-content等必要层级,会导致模态框显示异常、无法正常关闭
  3. 直接在非Vue上下文操作DOM,和Vue的渲染周期不同步,很容易出现DOM不存在、状态不同步的问题

适配多场景的全局Spinner实现方案

采用Vue3响应式状态控制Spinner的显隐,完全避免直接操作DOM,同时兼容路由守卫、普通组件内的调用需求:

1. 安装Pinia(项目已安装可跳过)

npm install pinia

在main.js中注册Pinia:

import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'

const app = createApp(App)
const pinia = createPinia()
app.use(pinia)
app.mount('#app')

2. 创建全局Spinner状态Store

新建stores/spinner.js:

import { defineStore } from 'pinia'

export const useSpinnerStore = defineStore('spinner', {
  state: () => ({
    // 用计数避免多个请求嵌套调用时提前隐藏Spinner的问题
    loadingCount: 0
  }),
  getters: {
    visible: (state) => state.loadingCount > 0
  },
  actions: {
    show() {
      this.loadingCount++
    },
    hide() {
      if (this.loadingCount > 0) {
        this.loadingCount--
      }
    },
    forceHide() {
      this.loadingCount = 0
    }
  }
})

3. 改造App.vue中的Spinner结构

用Vue指令控制显隐,补全Bootstrap模态框必要结构和适配样式:

<template>
  <div class="home">
    <!-- 全局Spinner模态框 -->
    <div 
      class="modal fade show" 
      tabindex="-1" 
      v-show="spinnerStore.visible"
      style="background: rgba(255,255,255,0.7); z-index: 9999"
    >
      <div class="modal-dialog modal-dialog-centered justify-content-center" style="margin: 0">
        <div class="spinner-border text-info" role="status" style="width: 3rem; height: 3rem">
          <span class="visually-hidden">Loading...</span>
        </div>
      </div>
    </div>
    <div id="login-teleport" />
    <Navbar />
    <div class="cam-container">
      <div class="sidebar">
        <Sidemenu />
      </div>
      <div class="content">
        <router-view />
        <div id="popup-teleport" />
      </div>
    </div>
  </div>
</template>

<script setup>
import { useSpinnerStore } from '@/stores/spinner'
const spinnerStore = useSpinnerStore()
</script>

4. 封装通用调用工具,兼容非Vue上下文

新建utils/spinner.js,适配路由预加载这类非setup上下文的调用场景:

import { useSpinnerStore } from '@/stores/spinner'

let storeInstance = null

export const Spinner = {
  init(pinia) {
    storeInstance = useSpinnerStore(pinia)
  },
  show() {
    storeInstance?.show()
  },
  hide() {
    storeInstance?.hide()
  },
  forceHide() {
    storeInstance?.forceHide()
  }
}

在main.js中完成初始化:

// 注册Pinia后添加以下代码
import { Spinner } from '@/utils/spinner'
Spinner.init(pinia)

5. 不同场景调用方式

路由预加载场景:

async preload(to) {
  Spinner.show();
  let data = await Manager.getFilteredList({
    filter: [],
    order: {},
    pageinfo: { pagenum: 1, pagelength: globalVars.EVENT_PER_PAGE },
  }).finally(() => Spinner.hide());
  to.params.list= data.list;
  to.params.totalCount = data.totalcount;
  return to;
}

组件内调用场景(比如点击按钮拉取数据):

<script setup>
import { useSpinnerStore } from '@/stores/spinner'
const spinnerStore = useSpinnerStore()

const fetchData = async () => {
  spinnerStore.show()
  try {
    // 执行接口请求逻辑
  } finally {
    spinnerStore.hide()
  }
}
</script>

<template>
  <button @click="fetchData">拉取数据</button>
</template>

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.28 13:24:03