Vue+Vuetify打造独自的消息确认组件

vue框架自己好像没有弹出框, 而vuetify有弹出框v-dialog, 没有确认框confirm, 虽然确认框本身就是弹出框, 但是弹出框的功能有个特点, 就是确定做一件事情, 而取消了就是做另一件事情, 类似一个Promise要做的事情。

今天主要就是要自定义一个确认框, 我们利用组件的思维, 先定义组件结构, 然后定义组件如何配合文档document工作, 最后声明和调用。按照这个思路, 我们分如下几步:

1.写一个confirm的自定义组件
src/components/atoms/Confirm.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
<template>
<div class="dialogwrapper" v-if="show">
<div class="overlay"></div>
<v-card class="dialog">
<v-card-title>{{ title }}</v-card-title>
<v-card-text> {{ message }} </v-card-text>
<v-card-actions>
<v-btn @click="ok">確認</v-btn>
<v-btn v-if="option === 'confirm'" @click="cancel">キャンセル</v-btn>
</v-card-actions>
</v-card>
</div>
</template>
<script>
export default {
data() {
return {
promiseStatus: null,
show: false,
};
},
methods: {
confirm() {
this.show = true;
return new Promise((resolve, reject) => {
this.promiseStatus = { resolve, reject };
});
},
ok() {
this.show = false;
this.promiseStatus && this.promiseStatus.resolve();
},
cancel() {
this.show = false;
this.promiseStatus && this.promiseStatus.reject();
},
},
};
</script>
<style>
.dialogwrapper {
align-items: center;
display: flex;
height: 100%;
justify-content: center;
left: 0px;
pointer-events: none;
position: fixed;
top: 0px;
width: 100%;
z-index: 6;
transition: all 0.2s cubic-bezier(0.25, 0.8, 0.25, 1) 0s, z-index 1ms ease 0s;
outline: none;
}
.dialog {
overflow-y: auto;
pointer-events: auto;
width: 100%;
z-index: inherit;
box-shadow: rgba(0, 0, 0, 0.2) 0px 11px 15px -7px,
rgba(0, 0, 0, 0.14) 0px 24px 38px 3px, rgba(0, 0, 0, 0.12) 0px 9px 46px 8px;
border-radius: 4px;
margin: 24px;
transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1) 0s;
max-width: 290px;
}
.overlay {
align-items: center;
border-radius: inherit;
display: flex;
justify-content: center;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
pointer-events: auto;
background: #000;
opacity: 0.46;
}
.v-card__actions {
justify-content: space-evenly;
}
</style>

2、定义插件src/plugins/confirm.ts
src/plugins/confirm.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import Vue from "vue";
import Confirm from "../../src/components/atoms/Confirm.vue";
const VueComponent = Vue.extend(Confirm);
const vm = new VueComponent().$mount();

let init = false;

const confirm = function (options) {
Object.assign(vm, options, {
type: "confirm",
});
if (!init) {
document.body.appendChild(vm.$el);
init = true;
}

return (vm as any).confirm();
};

export default confirm;

3、全局声明组件src/main.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import Vue from 'vue'
import App from './App'
import router from './router'
import vuetify from '@/plugins/vuetify'
import confirm from '@/plugins/confirm'
Vue.config.productionTip = false
Vue.prototype.$confirm = confirm
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
vuetify,
components: { App },
template: '<App/>'
})

4.调用src/components/TestPage.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<template>
<v-card>
<v-btn @click="showConfirm">open</v-btn>
</v-card>
</template>
<script>
methods: {
showConfirm(){
this.$confirm({
title: "このサイトを離れますか?",
message: "編集中のものは保存されませんが、よろしいですか",
option: "confirm",
//没有取消按钮的时候用option: "alert"
})
.then(() => {
next();
})
.catch(() => {
next(false);
});
}
}
</script>

5.在src/shims-tsx.d.ts里定义类型

1
2
3
4
5
6
7
8
9
10
type ConfirmOptions = {
title: string;
message: string;
option: string;
};
declare module "vue/types/vue" {
interface Vue {
$confirm: (options: ConfirmOptions) => Promise;
}
}

最终呈现的效果:

以上是一个简单的示例, 我们在需要使用的地方直接通过this.$confirm().then().catch()就可以把逻辑全部设置好, 可以看出this.$confirm是一个Promise, 而这个Promise是通过定义组件结构的时候confirm方法返回的。如下所示:

1
2
3
4
5
6
7
confirm() {
let _this = this;
this.show = true;
return new Promise(function (resolve,reject){
_this.promiseStatus = {resolve,reject};
});
}

我们在src/plugins/confirm.js中通过了一些方法找到了组件挂载点, 组件元素, 组件中的方法。

这个组件不比vuetify自带的v-dialog一开始就是长在文档中的, 而是需要我们手动appendChild的方式将元素插入文档中。