Vuejs 3 和 Bootstrap 5 模态可重用组件以编程方式显示

问题描述 投票:0回答:3

尝试基于 Bootstrap 5、vuejs 3 和可组合 API 创建一个(半)可重用的模态组件。设法让它部分工作,
给定(主要是标准 Bootstrap 5 模式,但基于“show”属性添加类,以及正文和页脚中的插槽):

<script setup lang="ts">
defineProps({
  show: {
    type: Boolean,
    default: false,
  },
  title: {
    type: String,
    default: "<<Title goes here>>",
  },
});
</script>

<template>
  <div class="modal fade" :class="{ show: show, 'd-block': show }"
    id="exampleModal" tabindex="-1" aria-labelledby="" aria-hidden="true">
    <div class="modal-dialog">
      <div class="modal-content">
        <div class="modal-header">
          <h5 class="modal-title" id="exampleModalLabel">{{ title }}</h5>
          <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
        </div>
        <div class="modal-body">
          <slot name="body" />
        </div>
        <div class="modal-footer">
          <slot name="footer" />
          <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
            Close
          </button>              
        </div>
      </div>
    </div>
  </div>
</template>

并被

“召唤”
<script setup lang="ts">
import { ref } from "vue";
import Modal from "@/components/Common/Modal.vue";

let modalVisible= ref(false);

function showModal(){
 modalVisible.value = true;
}
</script>

<template>
  <button @click="showModal">Show Modal</button>
  <Modal title="Model title goes here" :show="modalVisible">
    <template #body>This should be in the body</template>
    <template #footer>
      <button class="btn btn-primary">Extra footer button</button>
    </template>
</Modal>
</template>

我得到一个模式“显示”,但动画淡入淡出不起作用,背景不可见,并且模式中的数据按钮不起作用(即它不会关闭)。我觉得这与我的整个方法有关。

enter image description here

注意。我无法使用具有

data-bs-toggle="modal" data-bs-target="#exampleModal"
属性的标准按钮,因为该模型的实际触发器来自另一个组件的逻辑(如仅设置布尔值),并且可重用的模式组件将独立于其触发器 --- 它也感觉没有正确的“Vue”方式来做到这一点。

所以我认为我只是“显示”html,我需要以某种方式实例化引导模式......只是不知道该怎么做

package.json(以及相关的)

"dependencies": {
    "@popperjs/core": "^2.11.2",
    "bootstrap": "^5.1.3",
    "vue": "^3.2.31",
  },

此处的代码沙箱(无法在代码沙箱上使用新的 Composition API 和 TS,因此它使用标准选项 API 方法进行了轻微的重写,因此代码略有不同,但表现出相同的行为)

typescript vue-component vuejs3 bootstrap-5
3个回答
24
投票

好吧..又过了几个小时,我想出了一个解决方案,在这里发布,因为它可能会帮助其他人。 需要创建引导模式“对象”。所以首先必须从引导程序导入模式对象。它的创建需要一个 DOM 引用,因此必须在 html 元素中添加一个

ref
,并在脚本中添加一个
ref
属性来保存指向它的链接。 在安装组件之前,Vue 中的 DOM 引用不会填充,因此 Bootstrap 模式对象的构造需要在 Onmounted 中完成,因为
ref
现在将链接到实际的 DOM 元素。 然后,我没有向下传递 show prop,因为让父子之间保持同步很麻烦,而是在对话框组件本身上公开了一个
show
方法(也感觉更优雅一些)。由于
<script setup>
对象是
CLOSED BY DEFAULT
,因此该方法的暴露是通过
defineExpose
完成的.. 现在我们都 disco

<script setup lang="ts">
import { onMounted, ref } from "vue";
import { Modal } from "bootstrap";
defineProps({
  title: {
    type: String,
    default: "<<Title goes here>>",
  },
});
let modalEle = ref(null);
let thisModalObj = null;

onMounted(() => {
  thisModalObj = new Modal(modalEle.value);
});
function _show() {
  thisModalObj.show();
}
defineExpose({ show: _show });
</script>

<template>
  <div class="modal fade" id="exampleModal" tabindex="-1" aria-labelledby=""
    aria-hidden="true" ref="modalEle">
    <div class="modal-dialog">
      <div class="modal-content">
        <div class="modal-header">
          <h5 class="modal-title" id="exampleModalLabel">{{ title }}</h5>
          <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
        </div>
        <div class="modal-body">
          <slot name="body" />
        </div>
        <div class="modal-footer">
          <slot name="footer"></slot>
          <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
            Close
          </button>
        </div>
      </div>
    </div>
  </div>
</template>

和“父母”

<script setup lang="ts">
import { ref } from "vue";
import Modal from "@/components/Common/Modal.vue";

let thisModal= ref(null);

function showModal(){
 thisModal.value.show();
}
</script>

<template>
  <button @click="showModal">Show Modal</button>
  <Modal title="Model title goes here" ref="thisModal">
    <template #body>This should be in the body</template>
    <template #footer>
      <button class="btn btn-primary">Extra footer button</button>
    </template>
</Modal>
</template>

..可能还应该添加一个OnUnmount来清理对象以使其整洁。


1
投票

我没有使用TS,但它可以很容易地转换成TS。确保保留辅助功能(焦点陷阱、ESC 关闭模式、咏叹调属性)!!!我就是这样做的。我唯一要改变的是背景动画,它有点草率。 (我还包含了一个简单的实用程序来生成唯一的 ID)。

孩子们:

<template>
  <teleport to="body">
    <focus-trap v-model:active="active">
      <div
          ref="modal"
          class="modal fade"
          :class="{ show: active, 'd-block': active }"
          tabindex="-1"
          role="dialog"
          :aria-labelledby="`modal-${id}`"
          :aria-hidden="active"
      >
        <div class="modal-dialog modal-dialog-centered" role="document" >
          <div class="modal-content">
            <div class="modal-header">
              <h5 class="modal-title text-dark" :id="`modal-${id}`"><slot name="title"></slot></h5>
              <button
                  type="button"
                  class="close"
                  data-dismiss="modal"
                  aria-label="Close"
                  @click="$emit('closeModal', false)"
              >
                <span aria-hidden="true">&times;</span>
              </button>
            </div>
            <div class="modal-body text-dark">
              <slot></slot>
            </div>
            <div class="modal-footer text-dark">
              <button type="button" class="btn btn-danger" @click="$emit('closeModal', true)">Yes</button>
              <button type="button" class="btn btn-success" @click="$emit('closeModal', false)">No</button>
            </div>
          </div>
        </div>
      </div>
    </focus-trap>
    <div class="fade" :class="{ show: active, 'modal-backdrop show': active }"></div>
  </teleport>
</template>

<script>

import { ref, watch} from 'vue'
import IdUnique from '../js/utilities/utilities-unique-id';
import { FocusTrap } from 'focus-trap-vue'

export default {
  name: 'Modal',
  emits: ['closeModal'],
  components: {
    FocusTrap: FocusTrap
  },
  props: {
    showModal: Boolean,
    modalId: String,
  },
  setup(props) {
    const id = IdUnique();
    const active = ref(props.showModal);

    watch(() => props.showModal, (newValue, oldValue) => {
      if (newValue !== oldValue) {
        active.value = props.showModal;
        const body = document.querySelector("body");
        props.showModal ? body.classList.add("modal-open") : body.classList.remove("modal-open")
      }
    },{immediate:true, deep: true});

    return {
      active,
      id
    }
  }
}
</script>

家长:

<template>
  <div class="about">
    <div v-for="product in products">
      <Product :product="product" :mode="mode"></Product>
    </div>
  </div>
  <template v-if="mode === 'cart'">
    <div class="hello">
      <modal :showModal="showModal" @closeModal="handleCloseModal">
        <template v-slot:title>Warning</template>
        <p>Do you really wish to clear your cart</p>
      </modal>

      <button href="#" @click="handleToggleModal">{{ $t('clearCart') }}</button>
    </div>
  </template>
</template>

<script>
import {ref} from "vue";
import Product from '../components/Product'
import Modal from '../components/Modal'

export default {
  name: 'HomeView',
  components: {
    Product,
    Modal
  },
  setup() {
    const mode = ref('cart');
    const showModal = ref(false);
    let products = JSON.parse(localStorage.getItem('products'));
    
    const handleClearLocalstorage = () => {
      localStorage.clear();
      location.reload();
      return false;
    }

    const handleCloseModal = (n) => {
      showModal.value = false;
      if(n) {
        handleClearLocalstorage();
      }
    }

    const handleToggleModal = () => {
      showModal.value = !showModal.value;
    }

    return {
      handleClearLocalstorage,
      handleCloseModal,
      handleToggleModal,
      showModal,
      mode,
      products
    }
  }
}
</script>

唯一ID:

let Id = 0;

export default () => {
    return Id++;
};

0
投票

对于未来的人,

<template>
  <div class="modal fade" id="id-of-modal" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" aria-labelledby="staticBackdropLabel" aria-hidden="true">
    <div class="modal-dialog">
        <div class="modal-content">
        /* body */
        </div>
    </div>
  </div>
</template>

<script setup>

  import { onMounted, ref } from 'vue'
  import { Modal } from 'bootstrap'

  onMounted(async () => {
      modal.value = new Modal('#id-of-modal', {})
  })

  const modal = ref(null)

  function showModal() {
      modal.value.show();
  }

  function closeModal() {
      modal.value.hide();
  }

  /*
     other codes
  */

<script>

就是这样。

注意。 确保 ID 与您的模态框 ID 匹配

最新问题
© www.soinside.com 2019 - 2025. All rights reserved.