zoukankan      html  css  js  c++  java
  • 从零开始设计一个右键菜单组件

    需求分析

    首先要分析右键菜单需要实现什么功能

    • 点击鼠标右键弹出自定义的弹窗
    • 实现菜单项的点击
    • 自定义菜单项的样式
    • 自定义弹窗容器的样式

    代码实现

    需求搞定之后就是写代码了,下面是基础的代码框架

    <template>
      <div class="yak-content-menu" @contextmenu="showContentMenuFn" @click="hideContextMenuFn">
        <slot></slot>
        <transition>
          <div
            v-show="visiable"
            class="yak-content-menu-wrap"
            :class="menuWrapClass"
            :style="{ left: menuPosition.left, top: menuPosition.top }"
          >
            <slot name="menu" :menuList="menus">
              <!-- 给于用户完整的菜单项控制权限 -->
              <span
                class="yak-content-menu-wrap-item"
                :class="menuItemClass"
                v-for="item in menus"
                :key="item.command"
                @click="menuClick(item)"
              >
                {{ item.text }}
              </span>
            </slot>
          </div>
        </transition>
      </div>
    </template>
    <script lang="ts">
    import {
      ref,
      reactive,
      onMounted,
      onBeforeUnmount,
      defineComponent,
      PropType,
    } from "vue";
    
    interface MenuItem {
      command: string;
      text: string;
    }
    
    type Menus = Array<MenuItem>;
    
    export default defineComponent({
      name: "YakContextmenu",
      props: {
        menus: {
          type: Array as PropType<Menus>,
          default: () => {
            return [];
          },
          required: true,
        }, // 菜单的数组
        menuWrapClass: String, // 菜单容器的自定义class
        menuItemClass: String, // 菜单项的自定义class
      },
      emits: ["menu-click"],
      setup(props, { emit }) {
        const wrapEl = ref();
        const visiable = ref(false);
    
        const menuPosition: any = reactive({
          left: 0,
          top: 0,
        });
    
        const showContextMenuFn = (ev: any) => {
          // 1:禁用默认的右键点击事件
          // 2:获取当前鼠标的位置
          // 3:控制弹窗的显示
        };
    
        const hideContextMenuFn = () => {
          // 隐藏菜单
        };
    
        const menuClick = (item: MenuItem) => {
          // 添加自定义事件 menu-click,方便组件使用
        };
    
        return {
          wrapEl,
          visiable,
          menuPosition,
          menuClick,
          showContentMenuFn,
          hideContextMenuFn,
        };
      },
    });
    </script>
    

    实现 showContextMenuFn

    有了上面的框架,现在让我们实现显示菜单的功能

    const showContextMenuFn = (ev: any) => {
      // 禁用默认事件
      ev.preventDefault();
      // 获取自定义菜单的根元素的位置
      const rootPosition = wrapEl.value.getBoundingClientRect();
      // 用鼠标所在位置减去根元素的位置,就是弹窗元素相对于根元素的位置
      const x = ev.x - rootPosition.left;
      const y = ev.y - rootPosition.top;
      menuPosition.top = `${y}px`;
      menuPosition.left = `${x}px`;
      // 控制弹窗的显示
      visiable.value = true;
    };
    

    实现隐藏菜单和菜单点击

    const hideContextMenuFn = () => {
      visiable.value = false; // 隐藏菜单
    };
    
    const menuClick = (item: MenuItem) => {
      emit("menu-click", item); // 添加自定义事件 menu-click
    };
    

    实现的效果

    完整的代码

    <template>
      <div class="yak-content-menu" @contextmenu="showContentMenuFn" @click="hideContextMenuFn">
        <slot></slot>
        <transition>
          <div
            v-show="visiable"
            class="yak-content-menu-wrap"
            :class="menuWrapClass"
            :style="{ left: menuPosition.left, top: menuPosition.top }"
          >
            <slot name="menu" :menuList="menus">
              <!-- 给于用户完整的菜单项控制权限 -->
              <span
                class="yak-content-menu-wrap-item"
                :class="menuItemClass"
                v-for="item in menus"
                :key="item.command"
                @click="menuClick(item)"
              >
                {{ item.text }}
              </span>
            </slot>
          </div>
        </transition>
      </div>
    </template>
    <script lang="ts">
    import {
      ref,
      reactive,
      onMounted,
      onBeforeUnmount,
      defineComponent,
      PropType,
    } from "vue";
    
    interface MenuItem {
      command: string;
      text: string;
    }
    
    type Menus = Array<MenuItem>;
    
    export default defineComponent({
      name: "YakContextmenu",
      props: {
        menus: {
          type: Array as PropType<Menus>,
          default: () => {
            return [];
          },
          required: true,
        }, // 菜单的数组
        menuWrapClass: String, // 菜单容器的自定义class
        menuItemClass: String, // 菜单项的自定义class
      },
      emits: ["menu-click"],
      setup(props, { emit }) {
        const wrapEl = ref();
        const visiable = ref(false);
    
        const menuPosition: any = reactive({
          left: 0,
          top: 0,
        });
    
        const showContextMenuFn = (ev: any) => {
          // 禁用默认事件
          ev.preventDefault();
          // 获取自定义菜单的根元素的位置
          const rootPosition = wrapEl.value.getBoundingClientRect();
          // 用鼠标所在位置减去根元素的位置,就是弹窗元素相对于根元素的位置
          const x = ev.x - rootPosition.left;
          const y = ev.y - rootPosition.top;
          menuPosition.top = `${y}px`;
          menuPosition.left = `${x}px`;
          // 控制弹窗的显示
          visiable.value = true;
        };
    
        const hideContextMenuFn = () => {
          visiable.value = false; // 隐藏菜单
        };
    
        const menuClick = (item: MenuItem) => {
          emit("menu-click", item); // 添加自定义事件 menu-click
        };
    
        return {
          wrapEl,
          visiable,
          menuPosition,
          menuClick,
          showContentMenuFn,
          hideContextMenuFn,
        };
      },
    });
    </script>
    <style lang="scss">
    .yak-content-menu {
      position: relative;
      &-wrap {
        display: inline-block;
        box-sizing: border-box;
        background-color: #fff;
        position: absolute;
        box-shadow: 0 1px 6px rgb(0 0 0 / 20%);
        border-color: 1px solid #eee;
        border-radius: 4px;
        padding: 5px 0;
        min- 160px;
        &-item {
          cursor: pointer;
          display: block;
          line-height: 28px;
          font-size: 14px;
          padding: 0 24px;
          text-align: left;
          &:hover {
            background-color: #eee;
          }
        }
      }
    }
    

    仓库地址 Github,如有需求,欢迎提交issue

  • 相关阅读:
    多网卡绑定
    deepin 20.2.3 数字时钟屏保
    (原创)odoo15(master)下,列表导出权限控制
    在CentOS7上扩容centos-root根目录
    Linux扩容-新增磁盘分区挂载-fdisk
    Docker
    隐私政策(URL)
    快排代码
    反射将对象所有属性(含集合中所有属性)中字符串类型做trim()
    Prometheus 查询语句
  • 原文地址:https://www.cnblogs.com/guojikun/p/15322749.html
Copyright © 2011-2022 走看看