基于 Bootstrap + Vue 編寫模態(tài)框組件并完成文章刪除功能
在上篇教程中,學(xué)院君已經(jīng)給大家演示了如何通過 Laravel + Vue 快速實(shí)現(xiàn)文章發(fā)布、編輯和瀏覽(包括列表和詳情頁(yè))功能,今天我們?cè)賮韺?shí)現(xiàn)文章刪除功能。
由于后端刪除接口已經(jīng)在上篇教程中提供了,所以這里專注前端組件和功能即可。
一、基于瀏覽器對(duì)話框快速實(shí)現(xiàn)
首先,我們基于 JavaScript confirm 方法彈出的瀏覽器自帶對(duì)話框窗口快速實(shí)現(xiàn)文章刪除。打開 PostDetail 組件所在文件,新增如下代碼:
...
<p class="post-meta">
...
Action: <a :href="'/posts/' + id + '/edit'">編輯</a>,
<a href="#" @click="showDeleteDialog">刪除</a>
</p>
...
methods: {
...
showDeleteDialog() {
if (confirm('確定要?jiǎng)h除嗎')) {
axios.delete('/posts/' + Number(this.id)).then(resp => {
if (resp.data.success === true) {
// 刪除成功跳轉(zhuǎn)到文章列表頁(yè)
window.location.href = '/posts';
} else {
alert(resp.data.message);
}
})
}
}
}
...
在上述組件代碼中,我們?cè)?HTML 模板中添加了刪除鏈接,點(diǎn)擊該鏈接會(huì)觸發(fā) showDeleteDialog 函數(shù),我們?cè)?Vue 組件的方法列表中實(shí)現(xiàn)這個(gè)方法,在該方法體中,如果用戶在通過 confirm 方法彈出的瀏覽器自帶對(duì)話框窗口中點(diǎn)擊了「確認(rèn)」按鈕,confirm 方法會(huì)返回 true,然后我們就可以根據(jù)這個(gè)條件發(fā)送刪除文章請(qǐng)求,執(zhí)行刪除操作,刪除成功后,頁(yè)面會(huì)重定向到文章列表頁(yè):

這樣雖然可以完成需求,但是這個(gè)對(duì)話框窗口有點(diǎn)簡(jiǎn)陋,既然我們使用了 Bootstrap CSS 框架,是否可以將 Bootstrap 框架提供的模態(tài)框組件集成進(jìn)來呢?
當(dāng)然可以。
二、引入 Bootstrap 模態(tài)框?qū)崿F(xiàn)
我們以 Static backdrop 這個(gè)示例模態(tài)框?yàn)槔M(jìn)行演示。在 resources/js/components 目錄下新建一個(gè) ConfirmModal.vue 單文件組件,將這個(gè)示例模態(tài)框的模態(tài)框部分 HTML 代碼拷貝到 ConfirmModal.vue 的模板代碼中:
<style scoped>
</style>
<template>
<!-- Modal -->
<div class="modal fade" :id="target" data-backdrop="static" data-keyboard="false" tabindex="-1" aria-labelledby="staticBackdropLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="staticBackdropLabel">
<slot name="title"></slot>
</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<slot></slot>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
<button type="button" class="btn btn-primary" @click="$emit('delete')">確定</button>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
props: ['target'],
}
</script>
將硬編碼的 HTML 文本通過插槽轉(zhuǎn)發(fā)給父級(jí)作用域傳入,在點(diǎn)擊確定按鈕時(shí)設(shè)置觸發(fā)父級(jí)作用域的 delete 事件,將模態(tài)框最外層的 id 屬性調(diào)整為動(dòng)態(tài)屬性綁定,以便與父級(jí)作用域建立關(guān)聯(lián),進(jìn)而可以從父級(jí)作用域通過點(diǎn)擊刪除鏈接彈出這個(gè)模態(tài)框,這里的父級(jí)作用域就是文章詳情頁(yè)對(duì)應(yīng)的 PostDetail 組件。
點(diǎn)開 PostDetail 組件對(duì)應(yīng)文件,將刪除鏈接屬性調(diào)整為與 Static backdrop 模態(tài)框示例代碼中點(diǎn)開模態(tài)框的按鈕屬性一致,然后在這個(gè)組件中引入 ComfirmModal 組件,并且在該組件上設(shè)置 @delete 屬性監(jiān)聽子組件點(diǎn)擊「確定」按鈕事件:
<template>
...
Action: <a :href="'/posts/' + id + '/edit'">編輯</a>,
<a href="#" data-toggle="modal" data-target="#staticBackdrop">刪除</a>
</p>
<div class="post-content">
{{ content }}
</div>
<confirm-modal target="staticBackdrop" @delete="deleteThisPost">
<template #title>刪除文章</template>
確定要?jiǎng)h除這篇文章嗎?
</confirm-modal>
</div>
</template>
<script>
import ConfirmModal from "./ConfirmModal";
export default {
components: {ConfirmModal},
...
methods: {
...
deleteThisPost() {
axios.delete('/posts/' + Number(this.id)).then(resp => {
if (resp.data.success === true) {
// 刪除成功跳轉(zhuǎn)到文章列表頁(yè)
window.location.href = '/posts';
} else {
alert(resp.data.message);
}
})
}
}
}
</script>
如果觸發(fā)了 delete 事件則執(zhí)行 deleteThisPost 方法發(fā)起刪除文章請(qǐng)求刪除該文章。另外,在 confirm-modal 組件屬性上還傳遞了 target 屬性到子組件,這樣一來,點(diǎn)擊刪除按鈕,就可以彈出確認(rèn)刪除的模態(tài)框:

同樣點(diǎn)擊「確定」就可以完成刪除文章操作。
三、自行實(shí)現(xiàn)模態(tài)框的打開和關(guān)閉
上面的實(shí)現(xiàn)從用戶角度看已經(jīng)沒什么問題了,但是還有進(jìn)一步優(yōu)化的空間:現(xiàn)在的模態(tài)框彈出功能和渲染邏輯是基于 Bootstrap 定義的機(jī)制實(shí)現(xiàn)的,需要定義很多不知所云的屬性確保模態(tài)框可以正常彈出和關(guān)閉,如果我們需要在模態(tài)框彈出或關(guān)閉的時(shí)候進(jìn)行一些自定義的操作,比如設(shè)置過渡效果、監(jiān)聽打開和關(guān)閉事件,就會(huì)很棘手。
為此,我們可以完全刪除這些自帶模態(tài)框?qū)傩?,自行?shí)現(xiàn)模態(tài)框的打開和關(guān)閉功能。
其實(shí)很簡(jiǎn)單,我們?cè)?PostDetail 中新增一個(gè) showDeleteModal 屬性來控制模態(tài)框的顯示與隱藏:
...
Action: <a :href="'/posts/' + id + '/edit'">編輯</a>,
<a href="#" @click="showDeleteModal = !showDeleteModal">刪除</a>
</p>
...
<confirm-modal @do-action="deleteThisPost" @hide-modal="showDeleteModal = !showDeleteModal" :show-modal="showDeleteModal">
<template #title>刪除文章</template>
確定要?jiǎng)h除這篇文章嗎?
</confirm-modal>
...
export default {
...
data() {
return {
...
showDeleteModal: false
}
},
...
該模型屬性的值通過點(diǎn)擊刪除鏈接進(jìn)行切換,默認(rèn)是 false,表示模態(tài)框不顯示,點(diǎn)擊刪除鏈接后,就變成 true,再沿著 props 屬性 show-modal 傳遞給 ConfirmModal 組件。在引入 ConfirmModal 組件進(jìn)行渲染的地方,將原來的 delete 事件屬性名調(diào)整為了 do-action 事件以擴(kuò)展模態(tài)框組件的通用性,然后新增了一個(gè) hide-modal 事件,用于監(jiān)聽模態(tài)框組件的關(guān)閉模態(tài)框事件,將 showDeleteModal 屬性值切換回 false。
在這里我們已經(jīng)移除了 target 屬性以及刪除鏈接上原來為了遵循 Bootstrap 打開模態(tài)框的機(jī)制而設(shè)置的屬性。
在 ConfirmModal 中,
<style scoped>
.confirm-modal {
position: fixed;
top: 0;
left: 0;
z-index: 1050;
width: 100%;
height: 100%;
overflow: hidden;
outline: 0;
}
.confirm-modal-backdrop {
position: fixed;
top: 0;
left: 0;
z-index: 1040;
width: 100vw;
height: 100vh;
background-color: #000;
opacity: .5;
}
</style>
<template>
<!-- Modal -->
<div v-show="showModal">
<div class="confirm-modal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">
<slot name="title"></slot>
</h5>
<button type="button" class="close" @click="$emit('hide-modal')">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<slot></slot>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" @click="$emit('hide-modal')">取消</button>
<button type="button" class="btn btn-primary" @click="$emit('do-action')">確定</button>
</div>
</div>
</div>
</div>
<div class="confirm-modal-backdrop"></div>
</div>
</template>
<script>
export default {
props: ['showModal'],
}
</script>
移除掉了模態(tài)框最外層之前的一大堆屬性,并且將類名調(diào)整為 confirm-modal 以免受 Bootstrap 框架模態(tài)框機(jī)制的干擾,與模態(tài)框元素同級(jí)新增了一個(gè) <div class="modal-backdrop"></div> 容器,用于處理模態(tài)框的蒙層。
現(xiàn)在模態(tài)框的顯示與否完全基于最外層的 v-show="showModal" 條件是否滿足,兩個(gè)關(guān)閉模態(tài)框的地方也移除了原來的 data-dismiss="modal" 屬性,調(diào)整為觸發(fā)父級(jí)作用域定義的 hide-modal 事件?!复_定」按鈕的點(diǎn)擊事件也調(diào)整為觸發(fā)父級(jí)的 do-action 事件進(jìn)行文章刪除操作。
最后,我們?cè)?style 中為模態(tài)框及蒙層定義了 CSS 樣式(完全兼容之前的 Bootstrap CSS 代碼)。
再次回到文章詳情頁(yè),點(diǎn)擊「刪除」鏈接,可以看到可以正常打開模態(tài)框,并且渲染效果和之前一模一樣:

點(diǎn)擊「取消」或者右上角的「x」也可以正常關(guān)閉模態(tài)框。
此時(shí)沒有登錄,所以是無(wú)法刪除文章的,你可以在登錄之后進(jìn)行確認(rèn)刪除操作,功能也是完全正常的,至此,除了 CSS 代碼之外,這個(gè)模態(tài)框的打開關(guān)閉已經(jīng)完全在我們的掌控之下了,下篇教程,學(xué)院君就來給大家演示如何為模態(tài)框的打開和關(guān)閉添加 Vue 框架提供的動(dòng)畫/過渡效果。
本系列教程首發(fā)在Laravel學(xué)院(laravelacademy.org),你可以點(diǎn)擊頁(yè)面左下角閱讀原文鏈接查看最新更新的教程。
