vue.js - 如何在 Vue 的子组件中设计一个可重用的对话框?
问题描述
我一直在努力从 Vue 中的子组件内部实现对话框/模式设计和行为。
所以这是设置,我有一个名为“WorkersComponent”的Vue组件。该组件只是从后端(Laravel)获取的分配给某个案例的工作人员列表。该组件是可重用的,可以在用户想要添加工作人员的任何地方/案例/票证/查找中。
该组件中有一个“添加”按钮。单击后,我希望在该位置(在单击位置)出现一个新组件,这可能是下拉、模态、对话 - 并不重要。这个子组件有一个搜索栏和一些控件来获取工人信息并将它们添加到父组件。
我的问题是我无法弄清楚如何让嵌套/定位工作。因为它是一个子组件,它的位置总是相对于父组件,所以我只能控制它在该父组件中的位置,但我希望它在必要时显示在其他 DOM 元素和组件之上——只要有意义。最坏的情况——我希望它至少在页面中间。
现在我该如何实现呢?我可能希望它是一个独特的子组件,而不是一个全局通用模式。最重要的是,如果它是一个全局泛型,那么我知道如何使用相关选项填充模态,但如何将它们传递回调用模态的组件 - 不知道。所以我正在努力解决这个问题。这似乎是一件很简单的事情,但我找不到可行的解决方案。
<workers-component name="Assigned Workers">
<button <!-- Vue controls in here to invoke a modal/dialogue/dropdown --> >Add Worker</button>
<!-- The subcomponent itself -->
<workers-select-component />
</workers-component>
这是 Gmail 的一个示例:无论这个搜索栏在哪里(假设它是一个父组件),如果我单击一个三角形,它将展开另一个窗格,它将 (1) 出现在搜索栏所在的任何位置和 (2) 覆盖其他元素来显示它和(3)在手动关闭之前不要关闭窗格(这很容易,但正常的引导下拉菜单不支持这一点)。
解决方案
这是一个解决方案:
Vue.component('ToggleDialog', {
props: ['state'],
template: `
<button
@click="$emit('toggle', state)"
class="dialog-button"
>
TOGGLE MODAL
</button>
`
})
Vue.component('DialogModal', {
props: ['state'],
template: `
<div
class="dialog-backdrop"
>
<div
class="dialog-button"
>
<toggle-dialog
:state="state"
@toggle="toggleModal"
/>
</div>
</div>
`,
methods: {
toggleModal(state) {
this.$emit('toggle', state)
}
}
})
new Vue({
el: "#app",
data() {
return {
isModalOpen: false
}
},
methods: {
toggleModal(state) {
this.isModalOpen = !state
}
}
})
.dialog-backdrop {
position: fixed;
top: 0;
left: 0;
bottom: 0;
right: 0;
height: 100%;
width: 100%;
background: rgba(0, 0, 0, 0.3);
display: flex;
justify-content: center;
align-items: center;
}
.dialog-button {
padding: 10px 15px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<toggle-dialog :state="isModalOpen" @toggle="toggleModal">
OPEN MODAL
</toggle-dialog>
<dialog-modal v-if="isModalOpen" :state="isModalOpen" @toggle="toggleModal" />
</div>
如您所见,模态不是按钮的子项,而是 main 的子项app
。toggle
事件被发射(并且模态重新发射它)到app
控制模态对话框状态的 。
对于更复杂的应用程序,它可能不是最好的。您可以使用事件总线(在Vue3中已弃用)或Vuex(状态管理)来克服这种多重问题。emit
re-emit
编辑:新解决方案
Vue.component('ToggleDialog', {
data() {
return {
isModalOpen: false
}
},
template: `
<div
class="toggle-modal-wrapper"
>
<button
@click="isModalOpen = !isModalOpen"
class="dialog-button"
>
TOGGLE MODAL
</button>
<dialog-modal
v-if="isModalOpen"
@toggle="isModalOpen = !isModalOpen"
>
<slot></slot>
</dialog-modal>
</div>
`
})
Vue.component('DialogModal', {
props: {
innerComponent: {
type: String
}
},
template: `
<div
class="dialog-backdrop"
>
<div>
<slot></slot>
<br />
<button
@click="$emit('toggle')"
class="dialog-button"
>
TOGGLE MODAL
</button>
</div>
</div>
`
})
new Vue({
el: "#app",
})
.toggle-modal-wrapper {
z-index: 10000;
}
.dialog-backdrop {
position: fixed;
top: 0;
left: 0;
bottom: 0;
right: 0;
height: 100%;
width: 100%;
background: rgba(0, 0, 0, 0.3);
display: flex;
justify-content: center;
align-items: center;
}
.dialog-button {
padding: 10px 15px;
}
.other-part {
z-index: 1000;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<toggle-dialog>
<template>
This is the first.
</template>
</toggle-dialog>
<toggle-dialog>
<template>
This is the other.
</template>
</toggle-dialog>
<div class="other-part">
OTHER PART OF THE UI
</div>
</div>