尝试基于 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>
我得到一个模式“显示”,但动画淡入淡出不起作用,背景不可见,并且模式中的数据按钮不起作用(即它不会关闭)。我觉得这与我的整个方法有关。
注意。我无法使用具有
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 方法进行了轻微的重写,因此代码略有不同,但表现出相同的行为)
好吧..又过了几个小时,我想出了一个解决方案,在这里发布,因为它可能会帮助其他人。 需要创建引导模式“对象”。所以首先必须从引导程序导入模式对象。它的创建需要一个 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来清理对象以使其整洁。
我没有使用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">×</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++;
};
对于未来的人,
<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 匹配