vue2或vue3都能使用的右键拓展菜单

先上代码

<template>
    <div v-if="visible" class="prompt" ref="s_prompt" :style="`width:${getWidth() && '100px'}; 
                     max-height:${getMaxHeight() ?? 'auto'};
                     left:${shifting.x}px;
                     top:${shifting.y}px;
                     display:${shifting.display};
                     opacity:${shifting.opacity};
        `">
        <!-- 接收一个插槽 如果插槽自定义?? 用内置列表进行渲染 -->
        <template v-if="$slots.custom">
            <slot name="custom"></slot>
        </template>
        <ul v-else>
            <template v-if="list.length">
                <li v-for="(item) in list" :key="item.key" @click="handleClick(item)">{{ item.label }}</li>
            </template>
            <li v-else>无数据</li>
        </ul>
    </div>
</template>

<script>
// 1可以点击的数据源

export default {
    props: {
        // 拓展菜单宽度
        width: {
            type: String || Number,
            default: ''
        },
        // 最长高度
        maxHeight: {
            type: String || Number,
            default: ''
        },
        // data数据 对象的key按要求转换
        options: {
            type: Object,
            default: () => {
                return {
                    label: 'label',
                    key: 'key'
                }
            }
        },
        // 菜单数据源
        data: {
            type: Array,
            default: () => {
                return [{ name: '停诊', value: '1' }, { name: '停号', value: '2' }, { name: '新增', value: '3' }, { name: '修改', value: '4' }]
            }
        }
    },
    watch: {
        data: {
            handler(val) {
                if (val && val.length) {
                    // 对传入的数据进行初始化
                    this.list = val.map(item => {
                        return {
                            ...item,
                            label: item[this.options['label']],
                            key: item[this.options['key']]
                        }
                    })
                }
            },
            immediate: true
        }
    },
    data() {
        return {
            shifting: {
                x: 0,
                y: 0,
                display: 'none',
                opacity: 0
            },
            visible:false,//组件在注销之后 在生命周期删除挂载的dom
            item: null,
            list: [],//对props 传入的数据进行二次处理
        }
    },
    mounted() {

        // 浏览器其他位置点击 会关闭拓展菜单的显示
        window.addEventListener('click', (e) => {
            e.preventDefault();
            this.downExpand()
        })
        // 挂载
        document.body.appendChild(this.$el);
    },
    beforeUnmount() {
        document.body.removeChild(this.$el);
    },
    methods: {
        downExpand() {
            this.shifting.opacity = '0'
            setTimeout(() => {
                this.shifting.display = 'none';
                this.visible = false;
            }, 100);
        },
        openExpand(e) {
            this.visible = true;
            // 动画过度效果
            this.shifting.display = 'block',
                setTimeout(() => {
                    this.shifting.opacity = '1'
                }, 100);
            // 拓展显示位置
            this.$nextTick(() => {
                const box = this.$refs['s_prompt'];
                const windowWidth = window.innerWidth;
                const windowHeight = window.innerHeight;
                const clientWidth = box?.clientWidth;
                const clientHeight = box?.clientHeight;

                if (windowWidth - e.pageX < clientWidth) {
                    this.shifting.x = e.pageX - clientWidth;
                } else {
                    this.shifting.x = e.pageX;
                }
                if (windowHeight - e.pageY < clientHeight) {
                    this.shifting.y = e.pageY - clientHeight;
                } else {
                    this.shifting.y = e.pageY;
                }
            })
        },
        handleClick(item) {
            this.downExpand()
            this.$emit('handleClick', item)
        },
        // props width 参数处理
        getWidth() {
            if (this.width) {
                let width = this.width;
                if (typeof width === 'string') {
                    return width;
                } else if (typeof width === 'number') {
                    return width + 'px';
                }
            } else {
                return 'auto'
            }
        },
        // props height 参数处理
        getMaxHeight() {
            if (this.maxHeight) {
                let maxHeight = this.maxHeight;
                if (typeof maxHeight === 'string') {
                    return maxHeight;
                } else if (typeof maxHeight === 'number') {
                    return maxHeight + 'px';
                }
            } else {
                return 'auto'
            }
        },
    }
}
</script>

<style lang="scss" scoped>
* {
    margin: 0;
    padding: 0;
    text-decoration: none;
    list-style: none;
}

.prompt {
    position: absolute;
    background: #fff;
    box-shadow: #ccc 0 0 3px;
    transition: all .3s;
    height: auto;
    left: 0;
    top: 0;
    opacity: 1;
    z-index: 20;
    border-radius: 3px;
    padding: 4px;

    ul {
        width: 100%;
        height: 100%;
        overflow: auto;

        li {
            cursor: pointer;
            transition: all .1s;
            padding: 7px 20px;
            font-size: 14px;
            color: #494848;

            border-radius: 3px;
        }

        li:hover {
            background: rgb(230, 229, 229);
        }
    }
}
</style>

使用方法

组件注册之后

<s-expand ref="expandom" :data="data" :options="{label:'name', key: 'value'}" />

触发方法

const handleOpen = (e) =>{
      e.preventDefault();
      // 组件ref实例调用内部方法
      expandom.value.openExpand(e)
    }

总结

设计一个组件需要有一个明确的思路, 从原型到交付的一个过程, 每一个阶段都要思考如何去实现, 如何去快捷的实现, 当所有思路完毕之后,在去敲代码, 实现的过程肯定有困难,但是一定不要停止, 也许你的晋升之路才刚开始。

  • 7
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要在Vue3中使用AntV X6实现右键菜单功能,你可以按照以下步骤操作: 1. 安装依赖:在Vue项目中安装AntV X6和ant-design-vue库。 ``` npm install @antv/x6 ant-design-vue --save ``` 2. 创建右键菜单组件:创建一个Vue组件,作为X6图形编辑器右键菜单的容器。 ```vue <template> <a-dropdown :trigger="['contextmenu']" @visible-change="onVisibleChange"> <a-menu slot="overlay" :style="{ width: '120px' }"> <a-menu-item @click="deleteNode">删除节点</a-menu-item> </a-menu> <div class="x6-contextmenu" ref="container"></div> </a-dropdown> </template> <script> import { defineComponent } from 'vue'; import { Dropdown, Menu, message } from 'ant-design-vue'; export default defineComponent({ name: 'X6ContextMenu', components: { Dropdown, Menu, MenuItem: Menu.Item, }, emits: ['deleteNode'], mounted() { this.menu = this.$refs.container; this.menu.addEventListener('contextmenu', (e) => { e.preventDefault(); e.stopPropagation(); }); }, methods: { onVisibleChange(visible) { if (visible) { this.$emit('contextmenu', this.menu); } }, deleteNode() { this.$emit('deleteNode'); }, }, }); </script> ``` 3. 在X6图形编辑器中添加右键菜单:在X6图形编辑器中添加右键菜单功能。 ```vue <template> <div class="x6-editor"> <x6-contextmenu @contextmenu="onContextMenu" @deleteNode="deleteNode"></x6-contextmenu> <div class="x6-graph" ref="container"></div> </div> </template> <script> import { defineComponent } from 'vue'; import { Graph, Node } from '@antv/x6'; import X6ContextMenu from './X6ContextMenu.vue'; export default defineComponent({ name: 'X6Editor', components: { X6ContextMenu, }, data() { return { graph: null, }; }, mounted() { this.graph = new Graph({ container: this.$refs.container, grid: true, // 其他配置 }); // 添加节点 const node = this.graph.addNode({ // 节点配置 }); // 右键菜单事件 this.graph.on('contextmenu', ({ x, y }) => { this.$refs.contextmenu.show(x, y); }); }, methods: { onContextMenu(menu) { // 清空菜单 menu.innerHTML = ''; // 添加菜单项 const deleteMenuItem = document.createElement('a-menu-item'); deleteMenuItem.innerHTML = '删除节点'; deleteMenuItem.addEventListener('click', () => { this.deleteNode(); }); menu.appendChild(deleteMenuItem); }, deleteNode() { // 删除节点 this.graph.removeNode(node); }, }, }); </script> ``` 这样就可以在Vue3中使用AntV X6实现右键菜单功能了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值