class ContextMenuService {
    /**
     * 容器Dom对象
     *
     * @private
     * @memberof ContextMenuService
     */
    private container: Element | undefined;

    /**
     * Creates an instance of ContextMenuService.
     * @memberof ContextMenuService
     */
    constructor() {
        document.addEventListener('click', () => {
            this.clearContainer();
        });
    }

    /**
     * 设置容器
     *
     * @param {Element} container
     * @memberof ContextMenuService
     */
    public setContainer(container: Element) {
        if (container) {
            this.clearContainer();
            this.container = container;
        } else {
            console.error('容器Dom节点不存在');
        }
    }

    /**
     * 清楚容器
     *
     * @memberof ContextMenuService
     */
    public clearContainer() {
        if (this.container) {
            this.container.remove();
        }
    }
}

const service = new ContextMenuService();

import { Vue, Component, Provide, Prop, Emit } from 'vue-property-decorator';
import './context-menu.less';

@Component({})
export class ContextMenu extends Vue {
    /**
     * 设置右键菜单Class
     *
     * @type {string}
     * @memberof ContextMenu
     */
    @Prop()
    public contextMenuClass?: string;
    /**
     * 设置右键菜单Style
     *
     * @type {*}
     * @memberof ContextMenu
     */
    @Prop()
    public contextMenuStyle?: any;
    /**
     * 右键菜单数据，在调用renderContent时会传回去。
     *
     * @type {*}
     * @memberof ContextMenu
     */
    @Prop()
    public data?: any;
    /**
     * 用于绘制右键菜单内容
     *
     * @type {any}
     * @memberof ContextMenu
     */
    @Prop()
    public renderContent?: any;
    /**
     * 菜单数据
     *
     * @type {any[]}
     * @memberof ContextMenu
     */
    @Prop()
    public menus?: any[];
    /**
     * 是否阻止默认绘制上下文菜单
     *
     * @type {any}
     * @memberof ContextMenu
     */
    @Prop({ default: false })
    public isBlocked?: any;

    /**
     * 显示右键菜单
     *
     * @param {*} x x轴坐标
     * @param {*} y y轴坐标
     */
    public showContextMenu(x: number, y: number) {
        // 创建全屏覆盖容器
        const container = document.createElement('div');
        service.setContainer(container);
        container.oncontextmenu = () => {
            container.remove();
        };
        document.body.appendChild(container);
        // 创建Vue实例挂载
        const mount = document.createElement('div');
        container.appendChild(mount);
        let cssClass: string = "";
        if (y > window.innerHeight * 0.7) {
          cssClass = "context-menus-bottom"
        }
        this.renderContextMenu(
            {
                top: y + 'px',
                left: x + 'px',
            },
            mount,
            container,
            cssClass
        );
    }

    /**
     * 绘制菜单
     *
     * @param {*} position 菜单显示位置
     * @param {*} mount Vue实例挂载
     * @param {*} container 容器
     * @returns
     */
    public renderContextMenu(position: any, mount: any, container: any,cssClass: string) {
        const self = this;
        new Vue({
            data() {
                return {
                    menus: self.menus,
                };
            },
            methods: {
                destroy($event: Event) {
                    container.remove();
                    this.$destroy();
                    $event.stopPropagation();
                },
                onContextMenu($event: any) {
                    $event.preventDefault();
                },
                renderContent() {
                    let menus;
                    if (this.menus) {
                        menus = this.menus.map(item => {
                            let icon;
                            if (item.icon) {
                                icon = <img src={item.icon} />;
                            }
                            if (item.iconcls) {
                                icon = <i class={item.iconcls}></i>;
                            }
                            return (
                                <li class='context-menus-item' on-click={() => self.menuClick(item, self.data)}>
                                    {icon ? <div class='icon'>{icon}</div> : null}
                                    <div class='text'>{item.text}</div>
                                </li>
                            );
                        });
                    }
                    return <ul class='context-menus'>{menus}</ul>;
                },
            },
            render() {
                let content;
                if (self.renderContent) {
                    content = self.renderContent(self.data);
                }
                if (self.$slots.content) {
                    content = self.$slots.content;
                }
                if (this.menus) {
                    content = this.renderContent();
                }
                return (
                    <div
                        class='context-menu-container context-menu'
                        on-contextmenu={($event: any) => this.onContextMenu($event)}
                        on-click={($event: Event) => this.destroy($event)}
                    >
                        <div class={`context-menu-content ${cssClass}`} style={position}>
                            {content}
                        </div>
                    </div>
                );
            },
        }).$mount(mount);
    }

    /**
     * 组件挂在完毕
     *
     * @memberof ContextMenu
     */
    public mounted() {
        const contextRef: any = this.$refs.context;
        if (contextRef) {
            contextRef.oncontextmenu = (event: MouseEvent) => {
                event.preventDefault();
                if (!this.isBlocked) {
                    this.showContextMenu(event.clientX, event.clientY);
                }
                this.$emit('showContext', event);
            };
        }
    }

    /**
     * 菜单点击
     *
     * @param {*} data
     * @memberof ContextMenu
     */
    @Emit('menu-click')
    public menuClick(item: any, data: any) {}

    /**
     * 绘制内容
     *
     * @returns
     * @memberof ContextMenu
     */
    public render() {
        return (
            <div
                class={'context-menu-component ' + (this.contextMenuClass || '')}
                style={this.contextMenuStyle}
                ref='context'
            >
                {this.$slots.default}
            </div>
        );
    }
}
