1. 背景:
项目内部首页会展示触发多弹窗逻辑,由于已上线弹窗数量较多,且业务不断堆积,不同的弹窗显示隐藏分治,会存在用户进来同时展示多个弹窗,存在弹窗互相叠盖的现象,用户关闭上层弹窗在依次点击下方的弹窗,用户交互体验差。并且随着需求不断迭代,不易维护,定制化弹窗展示顺序的需求不易开展管理。
所以需要定制一套弹窗管理方案:
体验上:优先级高的弹窗先展示,关闭后再展示优先级次之的弹窗,依次进行
功能上:有一套多弹窗管理方案,能够统一管理所有弹窗,包括优先级、业务定制化展示规则等
本文会首先从零到一,实现一个webcomponent的多弹窗的demo,后续文章会再整理输出弹窗管理相关的功能。本文最终输出的是基于stencil进行封装的webcomponent组件,可实现跨框架部署。
2. 项目初始化
官方文档:stenciljs.com/
npm init stencil
项目结构介绍说明:juejin.cn/post/736202…
3. 实现弹窗:
3.1 先熟悉stencil采用jsx的方式封装一个简单的弹窗组件
// /components/comp-dialog.tsx
import { Component, Prop, State, Method, Event, EventEmitter, h } from '@stencil/core';
@Component({
tag: 'comp-dialog',
styleUrl: 'comp-dialog.css',
shadow: true,
})
export class CompDialog {
@Prop() visible: boolean = true;
@Prop() dialogTitle: string;
@Event() handleClose: EventEmitter<any>;
async hide(){
this.handleClose.emit()
}
render() {
return this.visible &&
<div class="dialog-box">
<div class="mark"></div>
<div class="content-box">
<p class="dialog-title">{this.dialogTitle}</p>
<div class="dialog-cont">
<slot>默认内容 默认内容默认内容默认内容默认内容默认内容默认内容</slot>
</div>
<div class="footer">
<div class="dialog-btn" onClick={ () => this.hide() }>我知道了</div>
</div>
</div>
</div>;
}
}
// /components/comp-dialog.css
p{
margin: 0;
}
.dialog-box{
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
font-size: 0px;
overflow-y: initial;
/* 这里防止当用户长按屏幕,出现的黑色背景色块,以及 iPhone 横平时字体的缩放问题 */
-webkit-text-size-adjust: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.mark{
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.7);
}
.content-box{
z-index: 10;
min-width: 490px;
padding: 24px;
border-radius: 8px;
background: #FFF;
box-shadow: 0px 3px 0px 0px #E0252B inset, 0px 8px 18px 3px rgba(0, 14, 51, 0.06);
}
.content-box .dialog-title{
color: #333;
font-size: 16px;
font-weight: 600;
line-height: 24px;
}
.content-box .dialog-cont{
margin: 16px 0 32px;
color: #666;
font-size: 14px;
line-height: 22px; /* 157.143% */
}
.content-box .footer{
display: flex;
flex-direction: row-reverse;
}
.dialog-btn{
padding: 6px 20px;
border-radius: 4px;
border: 1px solid rgba(130, 131, 149, 0.30);
color: #333;
font-size: 14px;
cursor: pointer;
}
.dialog-btn+.dialog-btn{
margin-right: 10px;
}
创建一个展示的页面引入组件,我这里是comp-page,并在www/index.html中引入这个页面组件,目录路径如下,非常easy
3.2 改造弹窗组件支持函数式调用
采用标签引入弹窗的方式对父组件的代码侵入比较大,函数式调用更方便便捷
- 改造弹窗模版文件:暴露弹窗显隐方法
// /components/comp-dialog.tsx
...
export class CompDialog {
...
@State() visible: boolean = false; // 替换之前的props
@Method()
async show() {
this.visible = true;
}
@Method()
async hide(){
this.visible = false;
this.handleClose.emit();
}
...
}
- 创建一个独立的服务文件返回弹窗实例,用于管理弹窗的显示和隐藏,并支持传递参数。
// /components/comp-dialog-service.ts
type ModalOptions = {
content: string;
dialogTitle?: string;
};
class DialogService {
private dialogElement: HTMLCompDialogElement;
constructor() {
this.dialogElement = document.createElement('comp-dialog') as HTMLCompDialogElement;
document.body.appendChild(this.dialogElement);
}
show(options: ModalOptions) {
this.dialogElement.innerHTML = options.content;
this.dialogElement.dialogTitle = options.dialogTitle || 'Default Title';
return this.dialogElement.show();
}
hide() {
this.dialogElement.hide();
}
}
const dialogService = new DialogService();
export default dialogService;
- 使用示例
// /components/comp-page.tsx
import { Component, h } from '@stencil/core';
import dialogService from '../comp-dialog/comp-dialog-service';
@Component({
tag: 'comp-page',
styleUrl: 'comp-page.css',
shadow: true,
})
export class CompPage {
open() {
dialogService.show({
content: '<p>This is the modal content!</p>',
dialogTitle: 'My Modal Title',
});
}
render() {
return <div>
<button onClick={ () => this.open()}>打开</button>
</div>;
}
}
3.3 实现一个弹窗关闭下一个弹窗打开
promise链异步执行完成这样的功能,在弹窗打开初始化一个promise,在弹窗关闭的时候将promise的状态置成fulfilled,状态完成,打开下一弹窗
- 改造弹窗模版文件为异步
// /components/comp-dialog.tsx
export class CompDialog {
...
@State() dialogResolve: any = null;
@State() dialogReject: any = null;
@Method()
async show() {
this.visible = true;
return new Promise((resolve, reject)=>{
this.dialogResolve = resolve
this.dialogReject = reject
})
}
@Method()
async hide(){
this.visible = false;
this.dialogResolve('成功关闭')
}
...
}
- 多弹窗示例
// /components/comp-page.tsx
export class CompPage {
open() {
dialogService.show({
content: '<p>This is the modal content!</p>',
dialogTitle: 'My Modal Title1',
}).then((data) => {
setTimeout(() => {
dialogService.show({
content: data.toString(),
dialogTitle: 'My Modal Title2',
});
}, 1000);
});
}
...
}
以上完成基本弹窗顺序显隐demo,还可以通过责任链的方式进行管理达到一样的效果,可参考【juejin.cn/post/711825… ,本篇仅为demo实现多弹窗的效果