Vue自定義render統(tǒng)一項(xiàng)目組彈框功能
一、本文收獲
pick
二、為什么要統(tǒng)一封裝彈框;
要封裝成怎樣
通過(guò)舉例常規(guī)彈框的寫(xiě)法。我們可以體會(huì)到,通常要彈出一個(gè)頁(yè)面,需要?jiǎng)?chuàng)建一個(gè)頁(yè)面 normalDialog.vue 包裹 dialogBody.vue (彈框主體);需要 parent.vue 設(shè)置flag控制彈框顯示隱藏, normalDialog.vue 關(guān)閉的時(shí)候設(shè)置 parent.vue 對(duì)應(yīng) flag 。缺點(diǎn): 流程繁雜、配置繁瑣、不靈活、樣式不統(tǒng)一和參數(shù)傳遞麻煩等 。如果一個(gè)項(xiàng)目彈框較多的時(shí)候,弊端將會(huì)更明顯,大量的 isXxxDialogShow ,大量的 vue 文件。因此項(xiàng)目組急需一個(gè)能簡(jiǎn)單配置就能彈出彈框的 API 。
1. 常規(guī)彈框?qū)懛?dialoBody.vue (彈框主體) ,此處采用 Composition API 的寫(xiě)法。只做了簡(jiǎn)單的頁(yè)面,包含校驗(yàn),抽取保存數(shù)據(jù)的常規(guī)邏輯。
<template>
<div class="dialog-body">
<div class="item">
<div>名稱</div>
<el-input v-model="name"></el-input>
</div>
<div class="item">
<el-radio-group v-model="attention">
<el-radio label="已關(guān)注"></el-radio>
<el-radio label="等下關(guān)注"></el-radio>
</el-radio-group>
</div>
<div class="item">
<el-radio-group v-model="like">
<el-radio label="已點(diǎn)贊"></el-radio>
<el-radio label="等下點(diǎn)贊"></el-radio>
</el-radio-group>
</div>
</div>
</template>
<script>
import { reactive, toRefs } from '@vue/composition-api'
import pick from 'lodash/pick'
import { Message } from 'element-ui'
export default {
props: {
defaultName: String,
},
setup(props, ctx) {
const ATTENTIONED = '已關(guān)注'
const LIKED = '已點(diǎn)贊'
const state = reactive({
name: props.defaultName, // 名稱
attention: '已關(guān)注', // 關(guān)注
like: '已點(diǎn)贊', // 點(diǎn)贊
})
/*************************************************************
* 頁(yè)面綁定的事件
* 建議寫(xiě)法:
* 1. 定義methods常量
* 2. 處理相關(guān)業(yè)務(wù)邏輯的時(shí)候,需要綁定事件到頁(yè)面的時(shí)
* 建議通過(guò)methods.onXxx = ()=>{ // 相關(guān)邏輯 }的形式定義
* 好處1: onXxx定義的位置和相關(guān)業(yè)務(wù)邏輯代碼關(guān)聯(lián)一起
* 好處2: 可以統(tǒng)一通過(guò)...methods的形式在setup統(tǒng)一解構(gòu)
* 好處3: 當(dāng)頁(yè)面邏輯復(fù)雜,需要操作的數(shù)據(jù)關(guān)聯(lián)性強(qiáng),不可拆解組件;
* 可將相關(guān)業(yè)務(wù)的代碼在獨(dú)立模塊定義;
* 獨(dú)立模塊暴露API handleXxx(methods,state),流水線加工methods;
* 和Vue2源碼一樣,流水線加工的思想.
*/
const methods = {}
// 校驗(yàn)名稱
methods.onNameBlur = () => {}
// ************************ 向外暴露的API ************************
const apiMethods = {
// 保存前校驗(yàn)
isCanSave() {
if (state.attention !== ATTENTIONED || state.like !== LIKED) {
Message.error('未關(guān)注或者點(diǎn)贊,不能關(guān)閉,嘻嘻')
return false
}
return true
},
// 獲取保存數(shù)據(jù)
getSaveData() {
// ******* lodash pick 從對(duì)象中抽取數(shù)據(jù)
return pick(state, ['name', 'attention', 'like'])
},
}
return {
...toRefs(state),
...methods,
apiMethods,
}
},
}
</script>
<style lang="less">
.dialog-body {
width: 100%;
height: 100px;
}
</style>
2.normalDialog.vue 包裹彈框主體 dialoBody.vue
<template>
<el-dialog
title="帥哥,美女,我是標(biāo)題"
:visible.sync="isShow"
width="30%"
:before-close="onClose"
>
<dialog-body default-name="參數(shù)傳遞的名稱" ref="inner"></dialog-body>
<span slot="footer" class="dialog-footer">
<el-button @click="onClose">取 消</el-button>
<el-button type="primary" @click="onOK">確 定</el-button>
</span>
</el-dialog>
</template>
<script>
import dialogBody from './dialogBody.vue'
export default {
components: {
dialogBody,
},
data() {
return {
isShow: true,
}
},
methods: {
onClose() {
// *********** 修改parent.vue ********
this.$parent.isNormalDialogShow = false
},
// ******* 控制保存流程 ********
onOK() {
const inner = this.$refs.inner
// 校驗(yàn)是否可以保存
if (inner.apiMethods.isCanSave()) {
// 獲取保存數(shù)據(jù)
const postData = inner.apiMethods.getSaveData()
console.log('>>>>> postData >>>>>', postData)
// 保存成功后關(guān)閉彈框
this.onClose()
}
},
},
}
</script>
parent.vue
// html 部分
<normal-dialog v-if="isNormalDialogShow" />
// Js部分
data(){
isNormalDialogShow:false
}
methods:{
onDialogShow(){ // ******控制彈框顯示*****
this.isNormalDialogShow = true
}
}
2. 要封裝成怎樣
2.1 API訴求:
isXxxDialogShow el-dialog
2.2 理想API:
import dialogBody from './dialogBody.vue'
const dialog = new JSDialog({
comonent: dialogBody,
dialogOpts: { // 可擴(kuò)展配置
title: 'JSDialog設(shè)置的彈框標(biāo)題',
width: '400px'
},
props: {
defaultName: 'JSDialog傳遞的參數(shù)',
},
onOK() {
const inner = dialog.getInner() // 能取到dialogBody的引用
// 控制流程
if (inner.apiMethods.isCanSave()) {
// 獲取保存數(shù)據(jù)
const postData = inner.apiMethods.getSaveData()
console.log('>>>>> postData >>>>>', postData)
// 關(guān)閉彈框
dialog.close()
}
},
onCancel() {
dialog.close() // 彈框關(guān)閉
},
})
dialog.show() // 彈框顯示
三、如何封裝
動(dòng)態(tài)控制顯示內(nèi)容,腦海浮現(xiàn)的三個(gè)方案: 卡槽、動(dòng)態(tài)組件和重寫(xiě) render 。下面在動(dòng)態(tài)彈框場(chǎng)景下簡(jiǎn)單對(duì)比三個(gè)方案。
- slot(卡槽) ,和 el-dialog 原理類似,只是再封裝了一層,少定義了 normalDialog.vue 文件。 缺點(diǎn):調(diào)用復(fù)雜,不靈活;不容易控制關(guān)閉的流程;只能在 template 中定義 。
- component(動(dòng)態(tài)組件) ,創(chuàng)建 commonDialog.vue ,統(tǒng)一掛在 App.vue 下,利用 <component :is="componentId"></component> 動(dòng)態(tài)切換彈框主體, commonDialog.vue 監(jiān)聽(tīng) componentId 變化來(lái)切換彈框主體。 缺點(diǎn):要提前將所有彈框主體組件注冊(cè)到commonDialog.vue頁(yè)面的components上;依賴于vuex,侵入性較強(qiáng);純js文件通過(guò)vuex彈出彈框相對(duì)復(fù)雜,不靈活 。
- 重寫(xiě) render , render 是 Vue 對(duì)造輪子開(kāi)發(fā)者開(kāi)放的后門(mén)。動(dòng)態(tài)彈框可作為獨(dú)立的功能模塊,內(nèi)部通過(guò)new Vue ,重寫(xiě) render 控制渲染內(nèi)容。 獨(dú)立 Vue 實(shí)例,可預(yù)先創(chuàng)建,可在任何位置控制彈框,靈活,清晰 。 缺點(diǎn):暫無(wú)
1. 整體代碼
先整體預(yù)覽一下代碼,下面再細(xì)分講解。
import Vue from 'vue'
import merge from 'lodash/merge'
import orderBy from 'lodash/orderBy'
// 按鈕配置項(xiàng)構(gòu)造器
function btnBuilder(options) {
const defaultBtn = {
text: '按鈕', // 顯示文本
clickFn: null, // 點(diǎn)擊回調(diào)
type: 'default', // 樣式
isHide: false, // 是否隱藏
order: 2 // 順序
}
return { ...defaultBtn, ...options }
}
export default class JSDialog {
constructor(originOptions) {
this.options = {}
this.vm = null
this._mergeOptions(originOptions)
this._initVm()
}
// 參數(shù)合并
_mergeOptions(originOptions) {
const defaultOptions = {
component: '', // 彈框主體vue頁(yè)面
// 可擴(kuò)展el-dialog官方api所有配置項(xiàng),小駝峰aaaBbbCcc
dialogOpts: {
width: '40%',
title: '默認(rèn)標(biāo)題'
},
// 傳入彈框主體vue組件的參數(shù)
props: {},
// 點(diǎn)擊確定回調(diào)
onOK: () => {
console.log('JSDialog default OK'), this.close()
},
// 點(diǎn)擊取消回調(diào)
onCancel: () => {
console.log('JSDialog default cancel'), this.close()
},
footer: {
ok: btnBuilder({
text: '確定',
type: 'primary',
order: 0
}),
cancel: btnBuilder({
text: '取消',
order: 1
})
}
}
// 參數(shù)合并到this.options
merge(this.options, defaultOptions, originOptions)
const footer = this.options.footer
Object.entries(footer).forEach(([key, btnOptions]) => {
// 確定和取消默認(rèn)按鈕
if (['ok', 'cancel'].includes(key)) {
const clickFn = key === 'ok' ? this.options.onOK : this.options.onCancel
// 默認(rèn)按鈕回調(diào)優(yōu)先級(jí): footer配置的clickFn > options配置的onOK和onCancel
btnOptions.clickFn = btnOptions.clickFn || clickFn
} else {
// 新增按鈕
// 完善配置
footer[key] = btnBuilder(btnOptions)
}
})
}
_initVm() {
const options = this.options
const beforeClose = this.options.footer.cancel.clickFn // 彈框右上角關(guān)閉按鈕回調(diào)
this.vm = new Vue({
data() {
return {
// 需要響應(yīng)式的數(shù)據(jù)
footer: options.footer, // 底部按鈕
visible: false // 彈框顯示及關(guān)閉
}
},
methods: {
show() {
// 彈框顯示
this.visible = true
},
close() {
// 彈框關(guān)閉
this.visible = false
},
clearVm() {
// 清除vm實(shí)例
this.$destroy()
}
},
mounted() {
// 掛載到body上
document.body.appendChild(this.$el)
},
destroyed() {
// 從body上移除
document.body.removeChild(this.$el)
},
render(createElement) {
// 彈框主體
const inner = createElement(options.component, {
props: options.props, // 傳遞參數(shù)
ref: 'inner' // 引用
})
// 控制按鈕顯示隱藏
const showBtns = Object.values(this.footer).filter(btn => !btn.isHide)
// 控制按鈕順序
const sortBtns = orderBy(showBtns, ['order'], ['desc'])
// 底部按鈕 jsx 寫(xiě)法
const footer = (
<div slot="footer">
{sortBtns.map(btn => (
<el-button type={btn.type} onClick={btn.clickFn}>
{btn.text}
</el-button>
))}
</div>
)
// 彈框主體
const elDialog = createElement(
'el-dialog',
{
// el-dialog 配置項(xiàng)
props: {
...options.dialogOpts,
visible: this.visible,
beforeClose
},
// **** 看這里,visible置為false后,el-dialog銷毀后回調(diào) *****
on: {
closed: this.clearVm
},
ref: 'elDialog'
},
// 彈框內(nèi)容:彈框主體和按鈕
[inner, footer]
)
return elDialog
}
}).$mount()
}
// 封裝API
// 關(guān)閉彈框
close() {
this.vm.close()
}
// 顯示彈框
show() {
this.vm.show()
}
// 獲取彈框主體實(shí)例,可訪問(wèn)實(shí)例上的方法
getInner() {
return this.vm.$refs.inner
}
}
2. 參數(shù)合并
要做到 API 訴求中的:調(diào)用簡(jiǎn)單、傳參簡(jiǎn)便和可擴(kuò)展控制彈框樣式。參數(shù)合并便是 成本最小 的實(shí)現(xiàn)方案,配合 TS 效果更佳。定義默認(rèn)參數(shù),通過(guò) lodash 的 merge ,合并深層屬性。通過(guò)參數(shù)合并還能做到自定義 footer 按鈕,控制文本,樣式,順序和執(zhí)行回調(diào)。
// 參數(shù)合并
_mergeOptions(originOptions) {
const defaultOptions = {
component: '', // 彈框主體vue頁(yè)面
// 可擴(kuò)展el-dialog官方api所有配置項(xiàng),小駝峰aaaBbbCcc
dialogOpts: {
width: '40%',
title: '默認(rèn)標(biāo)題'
},
// 傳入彈框主體vue組件的參數(shù)
props: {},
// 點(diǎn)擊確定回調(diào)
onOK: () => {
console.log('JSDialog default OK'), this.close()
},
// 點(diǎn)擊取消回調(diào)
onCancel: () => {
console.log('JSDialog default cancel'), this.close()
},
footer: {
ok: btnBuilder({
text: '確定',
type: 'primary',
order: 0
}),
cancel: btnBuilder({
text: '取消',
order: 1
})
}
}
// 參數(shù)合并到this.options
merge(this.options, defaultOptions, originOptions)
const footer = this.options.footer
Object.entries(footer).forEach(([key, btnOptions]) => {
// 確定和取消默認(rèn)按鈕
if (['ok', 'cancel'].includes(key)) {
const clickFn = key === 'ok' ? this.options.onOK : this.options.onCancel
// 默認(rèn)按鈕回調(diào)優(yōu)先級(jí): footer配置的clickFn > options配置的onOK和onCancel
btnOptions.clickFn = btnOptions.clickFn || clickFn
} else { // 新增按鈕
// 完善配置
footer[key] = btnBuilder(btnOptions)
}
})
}
3. render函數(shù)
摘取一段 渲染函數(shù) & JSX 官方文檔關(guān)于 render 的描述: Vue 推薦在絕大多數(shù)情況下使用模板來(lái)創(chuàng)建你的 HTML。然而在一些場(chǎng)景中,你真的需要 JavaScript 的完全編程的能力。這時(shí)你可以用 渲染函數(shù) ,它比模板更接近編譯器。 官方文檔對(duì)渲染函數(shù)的寫(xiě)法,參數(shù),對(duì)應(yīng)JSX寫(xiě)法介紹已經(jīng)很詳細(xì),這里就不再贅述。下面代碼是在最新vue-cli創(chuàng)建項(xiàng)目上運(yùn)行的,嘗試了JS參數(shù)創(chuàng)建元素和JSX創(chuàng)建元素兩種寫(xiě)法。
render(createElement) {
// 彈框主體
const inner = createElement(options.component, {
props: options.props, // 傳遞參數(shù)
ref: 'inner' // 引用
})
// 控制按鈕顯示隱藏
const showBtns = Object.values(this.footer).filter(btn => !btn.isHide)
// 控制按鈕順序
const sortBtns = orderBy(showBtns, ['order'], ['desc'])
// 底部按鈕 jsx 寫(xiě)法
const footer = (
<div slot="footer">
{sortBtns.map(btn => (
<el-button type={btn.type} onClick={btn.clickFn}>
{btn.text}
</el-button>
))}
</div>
)
// 彈框主體
const elDialog = createElement(
'el-dialog',
{
// el-dialog 配置項(xiàng)
props: {
...options.dialogOpts,
visible: this.visible
},
on: {
closed: this.clearVm
},
ref: 'elDialog'
},
// 彈框內(nèi)容:彈框主體和按鈕
[inner, footer]
)
return elDialog
}
4. 封裝API
暫時(shí)只封裝了三個(gè) API ,可根據(jù)不同的場(chǎng)景擴(kuò)展 API ,比如彈框不銷毀隱藏,彈框刷新等。
show() ,彈框顯示
顯示主要是修改 el-dialog 的 visible 為 true ,控制掛載到 body 上的彈框顯示。
show() {
this.vm.show()
}
close() ,彈框關(guān)閉
關(guān)閉處理流程:修改 el-dialog 的 visible 為 false ;觸發(fā) el-dialog 的 closed 事件;執(zhí)行 clearVm ;執(zhí)行 vm 的 $destroy() ; destroyed() 回調(diào)中將 $el 從 body 中移除。
close() {
this.vm.close()
}
getInner() ,獲取彈框主體實(shí)例,可用于訪問(wèn)實(shí)例上的方法,控制按鈕流程
getInner() {
return this.vm.$refs.inner
}
四、如何使用
1. 最簡(jiǎn)單場(chǎng)景,只配置頁(yè)面
按鈕事件回調(diào)采用默認(rèn)的回調(diào),確定和取消按鈕都可關(guān)閉彈框
import dialogBody from './renderJsx/dialogBody'
const dialog = new JSDialog({
component: dialogBody,
})
dialog.show() // 彈框顯示
效果如下:
2. 控制彈框樣式及確定流程
可自定義el-dialog支持的配置項(xiàng),見(jiàn) Dialog 對(duì)話框 ;比如:title、 customClass 。通過(guò)customClass可統(tǒng)一控制項(xiàng)目?jī)?nèi)彈框的風(fēng)格;可控制確定取消按鈕代碼回調(diào)。
import dialogBody from './renderJsx/dialogBody'
const dialog = new JSDialog({
component: dialogBody,
dialogOpts: {
title: '靚仔,美女歐嗨呦',
customClass:'js-dialog'
},
props: {
defaultName: 'JSDialog傳遞的參數(shù)'
},
onOK() {
const inner = dialog.getInner() // 能取到dialogBody的引用
// 控制流程
if (inner.apiMethods.isCanSave()) {
// 獲取保存數(shù)據(jù)
const postData = inner.apiMethods.getSaveData()
console.log('>>>>> postData >>>>>', postData)
// 關(guān)閉彈框
dialog.close()
}
},
onCancel() {
dialog.close() // 彈框關(guān)閉
}
})
效果如下:
3. 自定義footer
自定義按鈕可控制執(zhí)行回調(diào),樣式,順序,顯示與隱藏
import dialogBody from './renderJsx/dialogBody'
const dialog = new JSDialog({
component: dialogBody,
footer: {
ok: { // 修改默認(rèn)按鈕
text: '新增'
},
cancel: { // 隱藏默認(rèn)按鈕
isHide: true
},
add: { // 新增按鈕
text: '另存為',
clickFn() {
dialog.close()
},
order: -1 // 控制按鈕順序,order小的顯示在右邊
},
add2: {
text: '新增按鈕2',
clickFn() {
dialog.close()
},
order: 3
}
}
})
dialog.show() // 彈框顯示
效果如下:

總結(jié)
到此這篇關(guān)于Vue自定義render統(tǒng)一項(xiàng)目組彈框功能的文章就介紹到這了,更多相關(guān)Vue自定義render項(xiàng)目組彈框內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- 解決vue多層彈框時(shí)存在遮擋問(wèn)題
- vue+elementui 實(shí)現(xiàn)新增和修改共用一個(gè)彈框的完整代碼
- vue自定義彈框效果(確認(rèn)框、提示框)
- vue實(shí)現(xiàn)可拖拽的dialog彈框
- 解決echarts vue數(shù)據(jù)更新,視圖不更新問(wèn)題(echarts嵌在vue彈框中)
- vue+elementui實(shí)現(xiàn)點(diǎn)擊table中的單元格觸發(fā)事件--彈框
- vue項(xiàng)目中使用vue-layer彈框插件的方法
- Vue項(xiàng)目結(jié)合Vue-layer實(shí)現(xiàn)彈框式編輯功能(實(shí)例代碼)
- Vue自定義詢問(wèn)彈框和輸入彈框的示例代碼
相關(guān)文章
基于Vue實(shí)現(xiàn)我的錢(qián)包充值功能的示例代碼
這篇文章主要為大家詳細(xì)介紹了如何基于Vue實(shí)現(xiàn)我的錢(qián)包充值功能,文中的示例代碼簡(jiǎn)潔易懂,具有一定的借鑒價(jià)值,有需要的小伙伴可以參考一下2024-01-01
vue中echarts圖表大小適應(yīng)窗口大小且不需要刷新案例
這篇文章主要介紹了vue中echarts圖表大小適應(yīng)窗口大小且不需要刷新案例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-07-07
vue中關(guān)于v-for循環(huán)key值問(wèn)題的研究
這篇文章主要介紹了vue中關(guān)于v-for循環(huán)key值問(wèn)題的研究,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-06-06
解決vue-cli-service不是內(nèi)部或外部命令也不是可運(yùn)行的程序或批處理文件的報(bào)錯(cuò)問(wèn)題
這篇文章主要介紹了解決vue-cli-service不是內(nèi)部或外部命令也不是可運(yùn)行的程序或批處理文件的報(bào)錯(cuò)問(wèn)題,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-03-03
vue?watch監(jiān)聽(tīng)方法總結(jié)
這篇文章主要給大家分享的是vue?watch監(jiān)聽(tīng)方法總結(jié),偵聽(tīng)器一般來(lái)說(shuō)是用來(lái)監(jiān)聽(tīng)數(shù)據(jù)的變化,默認(rèn)是在數(shù)據(jù)發(fā)生變化時(shí)執(zhí)行。監(jiān)聽(tīng)的數(shù)據(jù)名放到這里面作為函數(shù)名,這個(gè)函數(shù)里面有兩個(gè)參數(shù),一個(gè)是新值,一個(gè)是舊值。下面我們就一起進(jìn)入文章了解更具體的內(nèi)容吧2021-12-12
Vue中常用的rules校驗(yàn)規(guī)則的實(shí)現(xiàn)
在vue開(kāi)發(fā)中,難免遇到各種表單校驗(yàn),本文主要介紹了Vue中常用的rules校驗(yàn)規(guī)則的實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的可以了解一下2023-10-10

