实现一个DialogService
import { isNumber, uniqueId } from 'lodash';
import { DialogContent, DialogOptions, IDialogService } from './dialog-service.interface';
import { IIntlMessageService } from '../interface';
export class DialogService implements IDialogService {
private dialogMap = new Map<string, HTMLDivElement>();
private promiseResolves = new Map<string, (val: boolean) => void>();
private dialogZIndex = 9999;
constructor(@IIntlMessageService private intlMessageService: IIntlMessageService) {}
private px(val: number | string) {
return `${val}px`;
}
private setElementStyle(ele: HTMLDivElement, values: Record<string, string | number>) {
Object.keys(values).forEach(key => {
const value = values[key];
if (value) {
ele.style[key as any] = isNumber(value) ? this.px(value) : value;
}
});
}
private getElementIds(id: string) {
return {
closeIcon: `${id}-close-icon`,
trueBtn: `${id}-true`,
falseBtn: `${id}-false`,
mask: `${id}-mask`,
dialogContainer: `${id}-container`,
dialogContentContainer: `${id}-content-container`,
};
}
private showDialog(id: string): Promise<boolean> {
return new Promise(resolve => {
const ele = this.dialogMap.get(id);
if (ele) {
ele.style.display = 'block';
document.documentElement.style.overflow = 'hidden';
}
this.promiseResolves.set(id, resolve);
});
}
private hideDialog(id: string): void {
const ele = this.dialogMap.get(id);
if (ele) {
ele.style.display = 'none';
document.documentElement.style.overflow = '';
}
}
/**
* 自增 zIndex
* @returns
*/
private getAutoAddZIndex() {
return this.dialogZIndex++ + '';
}
createDialog(dialogContent: DialogContent, options: DialogOptions = {}) {
const id = uniqueId('checkout-dialog-');
const divEle = document.createElement('div');
divEle.id = id;
this.dialogMap.set(id, divEle);
const style = options.style || {};
style.backgroundColor = style.backgroundColor ?? 'white';
const { mask, dialogContainer, dialogContentContainer } = this.getElementIds(id);
const { content, footer } = dialogContent;
// 直接用innerHTML会导致内容的script无法拉取,需要创建Fragment再插入
var slotHtml = document.createRange().createContextualFragment(`
<div id='${mask}' style="background: rgba(34, 34, 34, 0.5);position: fixed;left:0;top:0;right:0;bottom:0;z-index:${this.getAutoAddZIndex()};">
<div
id='${dialogContainer}'
style="position: fixed;left: 50%;top: 50%;transform: translate(-50%,-50%);
box-shadow: 0px 2px 10px 0px rgba(0, 0, 0, 0.1);box-shadow: 0px 0px 2px 0px rgba(0, 0, 0, 0.1);
border-radius:4px;"
>
${this.renderCloseIcon(id)}
<div id='${dialogContentContainer}'>${content}</div>
${this.renderFooter(id)}
${footer || ''}
</div>
</div>
`);
divEle.appendChild(slotHtml);
const containerEle = divEle.querySelector(`#${dialogContainer}`) as HTMLDivElement;
this.setElementStyle(containerEle, style);
divEle.style.display = 'none';
this.setupEvent(id);
document.body.appendChild(divEle);
return {
show: () => this.showDialog(id),
hide: () => this.hideDialog(id),
};
}
private setupEvent(id: string) {
const divEle = this.dialogMap.get(id)!;
const { closeIcon, trueBtn, falseBtn } = this.getElementIds(id);
const closeIconEle = divEle.querySelector(`#${closeIcon}`);
const onCancel = () => {
this.promiseResolves.get(id)?.(false);
this.hideDialog(id);
};
const trueBtnEle = divEle.querySelector(`#${trueBtn}`);
const falseBtnEle = divEle.querySelector(`#${falseBtn}`);
trueBtnEle?.addEventListener('click', () => {
this.promiseResolves.get(id)?.(true);
this.hideDialog(id);
});
closeIconEle?.addEventListener('click', onCancel);
falseBtnEle?.addEventListener('click', onCancel);
}
private renderFooter(id: string) {
const btnStyle = `
flex:1;
height:40px;
border-radius: 4px;
font-size: 16px;
font-weight: 500;
line-height: 24px;
display:flex;
justify-content:center;
align-items:center;
cursor:pointer;
`;
return `
<div id='${id}-btn-container' style="display:flex;padding:20px;">
<div id='${
this.getElementIds(id).falseBtn
}' style="${btnStyle}border: 1px solid rgba(34, 119, 200, 1);color:rgba(34, 119, 200, 1);">
${this.intlMessageService.formatMessage('customer.order_modal.no', 'No')}
</div>
<div style="width:12px;height:100%"></div>
<div id='${this.getElementIds(id).trueBtn}' style="${btnStyle}background: rgba(34, 119, 200, 1);color:white;">
${this.intlMessageService.formatMessage('customer.order_modal.yes', 'Yes')}
</div>
</div>
`;
}
private renderCloseIcon(id: string) {
return `
<span
style="cursor:pointer;position:absolute;right:20px;top:20px;width:16px;height:16px;"
id="${this.getElementIds(id).closeIcon}"
>
<svg width="16" height="17" viewBox="0 0 16 17" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.06 8.49956L13.7795 3.78007C13.9201 3.63944 13.9991 3.44872 13.9991 3.24985C13.9991 3.05097 13.9201 2.86025 13.7795 2.71962C13.6389 2.579 13.4481 2.5 13.2493 2.5C13.0504 2.5 12.8597 2.579 12.719 2.71962L7.99956 7.43911L3.28007 2.71962C3.21044 2.65 3.12777 2.59476 3.0368 2.55708C2.94582 2.5194 2.84832 2.5 2.74985 2.5C2.65137 2.5 2.55387 2.5194 2.46289 2.55708C2.37192 2.59476 2.28925 2.65 2.21962 2.71962C2.079 2.86025 2 3.05097 2 3.24985C2 3.44872 2.079 3.63944 2.21962 3.78007L6.93911 8.49956L2.21962 13.219C2.079 13.3597 2 13.5504 2 13.7493C2 13.9481 2.079 14.1389 2.21962 14.2795C2.36025 14.4201 2.55097 14.4991 2.74985 14.4991C2.94872 14.4991 3.13944 14.4201 3.28007 14.2795L7.99956 9.56L12.719 14.2795C12.7885 14.3494 12.8711 14.4048 12.9621 14.4427C13.0531 14.4805 13.1507 14.5 13.2493 14.5C13.3478 14.5 13.4454 14.4805 13.5364 14.4427C13.6274 14.4048 13.71 14.3494 13.7795 14.2795C13.8492 14.2099 13.9045 14.1272 13.9422 14.0363C13.98 13.9453 13.9994 13.8478 13.9994 13.7493C13.9994 13.6508 13.98 13.5532 13.9422 13.4623C13.9045 13.3713 13.8492 13.2886 13.7795 13.219L9.06 8.49956Z" fill="#757575"/>
</svg>
</span>
`;
}
}