app-popover.tsx 10.7 KB
import Vue, { CreateElement } from 'vue';
import { Subject, Observable } from 'rxjs';
import { createPopper, Instance } from '@popperjs/core/lib/popper-lite.js';
import preventOverflow from '@popperjs/core/lib/modifiers/preventOverflow.js';
import flip from '@popperjs/core/lib/modifiers/flip.js';
import { Placement } from '@popperjs/core/lib/enums';
import { AppServiceBase, on, Util } from 'ibiz-core';
import './app-popover.less';

/**
 * 悬浮窗控制器实例
 *
 * @export
 * @class AppPopover
 */
export class AppPopover {
    /**
     * 实例
     *
     * @private
     * @static
     * @memberof AppPopover
     */
    private static readonly $popover = new AppPopover();

    /**
     * vue实例
     *
     * @private
     * @type {any}
     * @memberof AppPopover
     */
    private vueExample!: any;

    /**
     * store对象
     *
     * @private
     * @memberof AppPopover
     */
    private store: any;

    /**
     * i18n对象
     *
     * @private
     * @memberof AppPopover
     */
    private i18n: any;

    /**
     * 路由对象
     *
     * @private
     * @memberof AppPopover
     */
    private router: any;

    /**
     * PopperJs实例
     *
     * @private
     * @type {Instance}
     * @memberof AppPopover
     */
    private popperExample?: Instance;

    /**
     * 是否显示悬浮窗
     *
     * @private
     * @type {boolean}
     * @memberof AppPopover
     */
    private showPopper: boolean = false;

    /**
     * 是否在点击空白区域时自动关闭
     *
     * @private
     * @type {boolean}
     * @memberof AppPopover
     */
    private isAutoClose: boolean = true;

    /**
     * 是否在点击空白区域时自动关闭
     *
     * @private
     * @type {boolean}
     * @memberof AppPopover
     */
    private isMoveOutClose: boolean = false;

    /**
     * 当前显示层级
     *
     * @private
     * @type {number}
     * @memberof AppPopover
     */
    private zIndex: number = 0;

    /**
     * Creates an instance of AppPopover.
     * @memberof AppPopover
     */
    constructor() {
        this.initBasicData();
        if (AppPopover.$popover) {
            return AppPopover.$popover;
        }
    }

    /**
     * 初始化基础数据
     * 
     * @memberof AppPopover
     */
    private initBasicData() {
        const appService = AppServiceBase.getInstance();
        this.store = appService.getAppStore();
        this.i18n = appService.getI18n();
        this.router = appService.getRouter();
    }

    /**
     * 初始化vue实例
     *
     * @private
     * @returns {void}
     * @memberof AppPopover
     */
    private initVueExample(customClass?: any, method?: any): void {
        const self = this;
        if (!self.store || !self.i18n) {
            self.initBasicData();
        }
        const container = document.createElement('div');
        container.className = 'app-popover-wrapper';
        on(container, 'click', () => {
            if (!this.showPopper || !this.isAutoClose) {
                return;
            }
            this.popperDestroy();
        });
        const div = document.createElement('div');
        container.appendChild(div);
        document.body.appendChild(container);
        this.vueExample = new Vue({
            el: div,
            store: this.store,
            router: this.router,
            i18n: this.i18n,
            data: { content: null, width: 300, height: 300 },
            methods: {
                click(e: MouseEvent) {
                    e.stopPropagation();
                },
                mouseout(e: MouseEvent) {
                    if (method == 'openPopover2') {
                        (this.$apppopover as any).mouseOutDestory2();
                    } else {
                        (this.$apppopover as any).mouseOutDestory();
                    }
                }
            },
            render(h: CreateElement) {
                const content: any = this.content;
                container.style.zIndex = (self.zIndex - 1).toString();
                let customStyle: any = { 'z-index': self.zIndex };
                if (Util.isNumber(this.width)) {
                    customStyle.width = this.width + 'px';
                }
                if (Util.isNumber(this.height)) {
                    customStyle.height = this.height + 'px';
                }
                return <div v-show="self.showPopper" style={customStyle} class={`app-popover app-popper${customClass ? ' ' + customClass : ''}`}>{(self.showPopper && content) ? content(h) : null}</div>;
            }
        });
    }

    /**
     * 打开悬浮窗
     *
     * @param {MouseEvent} event 事件
     * @param {*} view 视图
     * @param {*} [context={}] 应用上下文参数
     * @param {*} data 行为参数
     * @param {Placement} position 显示位置
     * @param {boolean} isAutoClose 是否可自动关闭
     * @returns {Observable<any>}
     * @memberof AppPopover
     */
    public openPop(event: any, view: any, context: any = {}, data: any, position?: Placement, isAutoClose?: boolean, navdatas: Array<any> = []): Observable<any> {
        const subject = new Subject<any>();
        if (!event) {
            console.error(view.$t('components.appmessagepopover.errorreturn'));
            return subject.asObservable();
        }
        if (!view.width) view.width = 300;
        if (!view.height) view.height = 300;
        this.openPopover(event, (h: CreateElement) => {
            return h(view.viewname, {
                props: {
                    staticProps: { viewDefaultUsage: false, noViewCaption: true },
                    dynamicProps: { viewdata: JSON.stringify(context), viewparam: JSON.stringify(data), navdatas: navdatas }
                },
                on: {
                    close: (result: any) => {
                        subject.next({ ret: 'OK', datas: result });
                        subject.complete();
                        subject.unsubscribe();
                        this.popperDestroy();
                    }
                }
            })
        }, position, isAutoClose, view.width, view.height);
        return subject.asObservable();
    }

    /**
     * 打开悬浮窗
     *
     * @param {*} event
     * @param {(h: CreateElement) => any} [content]
     * @param {Placement} [position='left']
     * @param {boolean} [isAutoClose=true]
     * @param {number} [width=300]
     * @param {number} [height=300]
     * @memberof AppPopover
     */
    public openPopover(event: any, content?: (h: CreateElement) => any, position: Placement = 'left-end', isAutoClose: boolean = true, width: number = 300, height: number = 300, customClass?: any): void {
        // 阻止事件冒泡
        event.stopPropagation();
        const element: Element = event.toElement || event.srcElement;
        if (!this.vueExample) {
            this.initVueExample(customClass);
        }
        this.popperDestroy();
        const zIndex = this.vueExample.$store.getters.getZIndex();
        if (zIndex) {
            this.zIndex = zIndex + 100;
            this.vueExample.$store.commit('updateZIndex', this.zIndex);
        }
        // 是否可自动关闭
        this.isAutoClose = isAutoClose;
        // 更新vue实例内容
        this.showPopper = true;
        Object.assign(this.vueExample.$data, { content, width, height, zIndex: this.zIndex });
        const el: any = this.vueExample.$el;
        this.popperExample = createPopper(element, el, {
            placement: position,
            strategy: 'absolute',
            modifiers: [preventOverflow, flip]
        });
        this.vueExample.$forceUpdate();
    }

    /**
     * 打开悬浮窗(自定义modefiers)
     *
     * @param {*} event
     * @param {(h: CreateElement) => any} [content]
     * @param {Placement} [position='left-end']
     * @param {boolean} [isAutoClose=true]
     * @param {number} [width]
     * @param {number} [height]
     * @param {*} [customClass]
     * @param {any[]} [modifiers=[]]
     * @memberof AppPopover
     */
    public openPopover2(event: any, content?: (h: CreateElement) => any, position: Placement = 'left-end', isAutoClose: boolean = true, isMoveOutClose: boolean = false, width?: number, height?: number, customClass?: any, modifiers: any[] = []): void {
        // 阻止事件冒泡
        event.stopPropagation();
        const element: Element = event.toElement || event.srcElement;
        if (!this.vueExample) {
            this.initVueExample(customClass, 'openPopover2');
        }
        this.popperDestroy();
        const zIndex = this.vueExample.$store.getters.getZIndex();
        if (zIndex) {
            this.zIndex = zIndex + 100;
        }
        // 是否可自动关闭
        this.isAutoClose = isAutoClose;
        this.isMoveOutClose = isMoveOutClose;
        // 更新vue实例内容
        this.showPopper = true;
        Object.assign(this.vueExample.$data, { content, width: width, height: height, zIndex: this.zIndex });
        const el: any = this.vueExample.$el;
        this.popperExample = createPopper(element, el, {
            placement: position,
            strategy: 'absolute',
            modifiers: [...modifiers]
        });
        this.vueExample.$forceUpdate();
    }

    /**
     * 销毁popper(带回填数据)
     *
     * @memberof AppPopover
     */
    public popperDestroy(): void {
        if (this.popperExample) {
            this.popperExample.destroy();
            if (this.zIndex !== 0) {
                const zIndex: any = this.zIndex;
                this.vueExample.$store.commit('updateZIndex', zIndex - 100);
                this.zIndex = 0;
            }
            this.showPopper = false;
            this.vueExample.$forceUpdate();
            this.popperExample = undefined;
            this.vueExample = null;
        }
    }

    /**
     * 销毁popper2(带回填数据)
     *
     * @memberof AppPopover
     */
    public popperDestroy2(): void {
        if (this.popperExample) {
            this.popperExample.destroy();
            if (this.zIndex !== 0) {
                this.zIndex = 0;
            }
            this.showPopper = false;
            this.vueExample.$forceUpdate();
            this.popperExample = undefined;
            this.vueExample = null;
        }
    }

    /**
     * 销毁popper(当鼠标移出时)
     *
     * @memberof AppPopover
     */
    public mouseOutDestory(): void {
        if (this.showPopper && this.isMoveOutClose) {
            this.popperDestroy();
        }
    }

    /**
     * 销毁popper2(当鼠标移出时)
     *
     * @memberof AppPopover
     */
    public mouseOutDestory2(): void {
        if (this.showPopper && this.isMoveOutClose) {
            this.popperDestroy2();
        }
    }

    /**
     * 获取实例
     *
     * @static
     * @memberof AppPopover
     */
    public static getInstance() {
        return AppPopover.$popover;
    }

}