教你用Vue3实现移动端Dialog
背景
公司最近在做组件库,本菜鸡被分配到dialog组件,接到任务,本来想使用cv大法“借鉴”一下市面上的dialog组件,奈何ui同学脑洞太大,要往dialog里塞一些奇怪的东西,像这样
这样
还有这样...
还是自己写一个叭
开始
废话不多说,直接上代码,核心思想就是通过传入不同的props值来给组件不同的样式
本文引用了两个基础组件,Overlay和Icon,代码就不贴出来了,这些部分可以自行实现
template部分,页面布局分为3个部分:头部header、中间部分content、按钮区域button
vue
<template>
<Overlay :show="visible">
<div class="my-dialog-wrapper">
<transition name="my-dialog-bounce">
<div :class="classes">
<Icon
@click="handleAction('close')"
v-if="showClose"
name="extra"
class="my-icon my-icon-quxiao my-dialog-box-close"
/>
<div v-if="imageUrl" class="my-dialog-box-img">
<img :src="imageUrl" alt="" />
</div>
<div v-if="avatar" class="my-dialog-box-avatar">
<img :src="avatar" alt="" />
</div>
<div class="my-dialog-box-title">{{ title }}</div>
<div class="my-dialog-box-content">{{ message }}</div>
<div class="my-dialog-box-tags">
<div
v-for="(item, index) in tags"
:key="`tag${index}`"
class="my-dialog-box-tags-item"
>
{{ item }}
</div>
</div>
<slot name="content"></slot>
<div :class="buttonClasses">
<div
v-if="showCancelButton"
@click="handleAction('cancel')"
:class="cancelBtnCls"
>
{{ cancelText }}
</div>
<div
v-if="showConfirmButton"
@click="handleAction('confirm')"
:class="confirmBtnCls"
>
{{ confirmText }}
</div>
</div>
</div>
</transition>
</div>
</Overlay>
</template>
js部分
支持使用v-model控制dialog隐藏和显示,传入props modelValue 具体可以参考vue3官方文档
vue
<script>
import Overlay from "../overlay";
import Icon from "../icon";
import {
defineComponent,
computed,
ref,
watch,
} from "vue";
const prefixCls = "my-dialog";
export default defineComponent({
name: "my-dialog",
inheritAttrs: false,
props: {
avatar: {
type: String,
default: "",
},
imageUrl: {
type: String,
default: "",
},
theme: {
type: String,
default: "",
},
showConfirmButton: {
type: Boolean,
default: true,
},
showCancelButton: {
type: Boolean,
default: false,
},
buttonSize: {
type: String,
default: "normal",
},
confirmText: {
type: String,
default: "确认",
},
cancelText: {
type: String,
default: "取消",
},
message: {
type: String,
default: "",
},
tags: {
type: Array,
default: () => [],
},
showClose: {
type: Boolean,
default: false,
},
modelValue: {
type: null,
default: false,
},
title: {
type: String,
default: "",
},
},
components: {
Overlay,
Icon
},
emits: ["update:modelValue", "action"],
setup(props, { emit }) {
let visible = ref(!!props.modelValue);
const classes = computed(() => {
return [
`${prefixCls}-box`,
props.showClose ? `${prefixCls}-box-with-close` : "",
props.imageUrl ? `${prefixCls}-box-no-image` : "",
(props.buttonSize === "large" && props.showCancelButton) || !props.theme
? `${prefixCls}-box-no-padding-botm`
: "",
];
});
const buttonClasses = computed(() => {
return props.theme === "round"
? [
`${prefixCls}-box-btns`,
props.buttonSize === "large" ? `${prefixCls}-box-btns-large` : "",
]
: [
`${prefixCls}-box-btns-line`,
props.buttonSize === "large" ? `${prefixCls}-box-btns-large` : "",
];
});
const confirmBtnCls = computed(() => {
return props.theme === "round"
? [
`${prefixCls}-box-btns-normal`,
`${prefixCls}-box-btns-normal-confirm`,
props.buttonSize === "large"
? `${prefixCls}-box-btns-large-confirm`
: "",
]
: ["line-button", `${prefixCls}-box-btns-line-confirm`];
});
const cancelBtnCls = computed(() => {
return props.theme === "round"
? [
`${prefixCls}-box-btns-normal`,
`${prefixCls}-box-btns-normal-cancel`,
props.buttonSize === "large"
? `${prefixCls}-box-btns-large-cancel`
: "",
]
: ["line-button", `${prefixCls}-box-btns-line-cancel`];
});
const closeDialog = () => {
if(!visible) return;
visible.value = false;
emit("update:modelValue", !props.modelValue);
};
const handleAction = (action) => {
emit('action',action);
closeDialog();
};
//监听v-model
watch(()=> props.modelValue,(val)=>{
visible.value = val
})
return {
visible,
classes,
buttonClasses,
cancelBtnCls,
confirmBtnCls,
closeDialog,
handleAction,
};
},
});
</script>
css部分就不说了,大家可以根据自己的需求去自定义css
使用
到这里,可以引入组件使用,传入我们定义的props
vue
<my-dialog
:title="我的第一篇掘金文章"
:buttonSize="large"
:message="大家多多指教"
:theme="round"
v-model="dialogShow"
showClose
showCancelButton
>
函数调用
我们平时调用dialog的方式是不是像这样,在vue3中,这又该如何实现呢?
js
this.$dialog
.confirm({
title: this.title,
message: this.content,
theme: "round",
})
.catch(() => {
console.log("取消");
});
基于上面已经实现的组件,我们可以通过动态挂载component的方式,在调用this.$dialog的时候,在页面上创建一个div元素,并将我们的组件挂载上去
Vue.extend在Vue3中已经不再支持了,而且已经没有构造器这个概念,我们使用官方提供的render函数来实现
js
import DialogConstructor from "./Dialog.vue";
import { h, render } from "vue";
const dialogInstance = new Map();
const initInstance = (props, container) => {
// 生成vnode
const vnode = h(DialogConstructor, props);
render(vnode, container);
//挂载
document.body.appendChild(container);
//返回组件实例
return vnode.component;
};
const getContainer = () => {
return document.createElement("div");
};
const showDialog = (options) => {
options.onAction = (action) => {
const currentDialog = dialogInstance.get(vm);
let resolve = action;
if (options.callback) {
options.callback(resolve, instance.proxy);
} else {
if (action === "cancel" || action === "close") {
currentDialog.reject("cancel");
} else {
currentDialog.resolve(resolve);
}
}
};
const container = getContainer(); // 获取组件容器
const instance = initInstance(options, container);
const vm = instance.proxy;
for (const prop in options) {
if (options.hasOwnProperty(prop) && !vm.$props.hasOwnProperty(prop)) {
vm[prop] = options[prop];
}
}
// 显示dialog
vm.visible = true;
return vm;
};
function Dialog(options) {
let callback;
if (typeof options === "string") {
options = {
message: options,
};
} else {
callback = options.callback;
}
return new Promise((resolve, reject) => {
const vm = showDialog(options);
dialogInstance.set(vm, {
options,
callback,
resolve,
reject,
});
});
}
Dialog.alert = (options) => {
return Dialog(Object.assign({}, options, { boxType: "alert" }));
};
Dialog.confirm = (options) => {
return Dialog(
Object.assign({ showCancelButton: true }, options, { boxType: "confirm" })
);
};
Dialog.close = () => {
dialogInstance.forEach((_, vm) => {
vm.closeDialog();
});
dialogInstance.clear();
};
DialogConstructor.install = (app) =>{
const { name } = DialogConstructor;
app.component(name, DialogConstructor)
}
Dialog.Component = DialogConstructor;
// 注册组件
Dialog.install = (app) => {
app.use(Dialog.Component);
app.config.globalProperties.$dialog = Dialog;
};
export default Dialog;
完成
以上只是本人在学习过程中产出的一些东西,希望和大家分享一下,如果有更好的方案,欢迎讨论~