contextMenu实现

345 阅读2分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第9天,点击查看活动详情

前言

在文章tagsView实现中,tag删除的方法还未完善

预期效果如下

1.png

实现

样式

点击tag,就会在点击处出现选项

先实现基本样式

新建组件ContextMenu.vue,三个li分别绑定三个方法

<template>
  <ul class="context-menu-container">
    <li @click="onRefreshClick">
      {{ $t('msg.tagsView.refresh') }}
    </li>
    <li @click="onCloseRightClick">
      {{ $t('msg.tagsView.closeRight') }}
    </li>
    <li @click="onCloseOtherClick">
      {{ $t('msg.tagsView.closeOther') }}
    </li>
  </ul>
</template>

<script setup>
// 刷新
const onRefreshClick = () => {}
// 关闭右边tag
const onCloseRightClick = () => {}
// 关闭其他tag
const onCloseOtherClick = () => {}
</script>

<style lang="scss" scoped>
.context-menu-container {
  position: fixed;
  background: #fff;
  z-index: 3000;
  list-style-type: none;
  padding: 5px 0;
  border-radius: 4px;
  font-size: 12px;
  font-weight: 400;
  color: #333;
  box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, 0.3);
  li {
    margin: 0;
    padding: 7px 16px;
    cursor: pointer;
    &:hover {
      background: #eee;
    }
  }
}
</style>

引用contextMenu,在tagView/index.vue中为tag绑定右键点击事件,并声明控制contextMenu是否显示的变量visible,当右键点击,设置visible的值为true

<template>
        ···
      <router-link
        ...
        @contextmenu.prevent="openMenu()"
      >
        ...
    </el-scrollbar>
    <context-menu
      v-show="visible"
    ></context-menu>
</template>

<script setup>
import ContextMenu from './ContextMenu.vue'
import { ref, reactive, watch } from 'vue'
import { useRoute } from 'vue-router'
...

const visible = ref(false)
/**
 * 展示 menu
 */
const openMenu = () => {
  visible.value = true
}
</script>

然后需要控制contextMenu出现的位置,需要得到鼠标点击时的x和y值,将其作为动态css引入

<context-menu
      v-show="visible"
      :style="menuStyle"
    ></context-menu>

在点击事件中,传入事件event

     <router-link
      ···
      @contextmenu.prevent="openMenu($event)"
    >

openMenu

const menuStyle = reactive({
  left: 0,
  top: 0
})
const openMenu = (e) => {
  const { x, y } = e
  menuStyle.left = x + 'px'
  menuStyle.top = y + 'px'
  visible.value = true
}

事件处理

删除时需要得到点击tag的index值,所以需要在contextMenu传入index

tagView/index.vue

<router-link
      ···
      @contextmenu.prevent="openMenu($event, index)"
    >
      {{ tag.title }}
      <i
        v-show="!isActive(tag)"
        class="el-icon-close"
        @click.prevent.stop="onCloseClick(index)"
      ></i>
    </router-link>
    <context-menu
      v-show="visible"
      :style="menuStyle"
      :index="selectIndex"
    ></context-menu>
const selectIndex = ref(0)

const openMenu = (e, index) => {
  ···'
  selectIndex.value = index
  ···
}

ContextMenu.vue

<script setup>
import { defineProps } from 'vue'
const props = defineProps({
  index: {
    type: Number,
    required: true
  }
})
</script>

由于删除需要对tags数据源操作,tags数据源保存在vuex中,所以在mutations中新增删除方法

有三种不同类型的删除,分别删除点击的tag,删除点击右侧tag,和删除其他tag

可以声明一个方法,通过传入不同的类型,确定是哪一种删除操作,执行对应的逻辑,由此传入一个对象,对象中包含删除类型和点击的tag下标值

import { LANG, TAGS_VIEW } from '@/constant'
import { getItem, setItem } from '@/utils/storage'
export default {
  namespaced: true,
  state: () => ({
    sidebarOpened: true,
    language: getItem(LANG) || 'zh',
    tagsViewList: getItem(TAGS_VIEW) || []
  }),
  mutations: {
    ···
    removeTagsView(state, payload) {
      // 删除点击的tag 
      if (payload.type === 'index') {
        state.tagsViewList.splice(payload.index, 1)
      } 
      // 删除其他tag
      else if (payload.type === 'other') {   
        // 删除之后的
        state.tagsViewList.splice(
          payload.index + 1,
          state.tagsViewList.length - payload.index + 1
        )
        // 删除之前的
        state.tagsViewList.splice(0, payload.index)
      } 
      // 删除右侧tag
      else if (payload.type === 'right') {
        state.tagsViewList.splice(
          payload.index + 1,
          state.tagsViewList.length - payload.index + 1
        )
      }
      // 更新本地储存
      setItem(TAGS_VIEW, state.tagsViewList)
    }
  }
}

ContextMenu.vue触发,刷新直接使用router.go(0)

<script setup>
import { defineProps } from 'vue'
import { useRouter } from 'vue-router'
import { useStore } from 'vuex'
const props = defineProps({
  index: {
    type: Number,
    required: true
  }
})
const store = useStore()
const router = useRouter()
const onRefreshClick = () => {
  router.go(0)
}
const onCloseRightClick = () => {
  store.commit('app/removeTagsView', {
    type: 'right',
    index: props.index
  })
}
const onCloseOtherClick = () => {
  store.commit('app/removeTagsView', {
    type: 'other',
    index: props.index
  })
}
</script>

tagView/index.vue,删除点击的tag

import { useStore } from 'vuex'
const store = useStore()

// 关闭当前
const onCloseClick = (index) => {
  store.commit('app/removeTagsView', {
    type: 'index',
    index
  })
}

关闭处理

接下来处理关闭ContextMenu

可以监听visible的值,visible控制ContextMenu是否显示

visibletrue时,为body绑定鼠标点击事件,触发后将visible的值变为false

visiblefalse时,解除事件

tagView/index.vue

import { ref, watch } from 'vue'

const closeMenu = () => {
  visible.value = false
}
watch(visible, (val) => {
  if (val) {
    document.body.addEventListener('click', closeMenu)
  } else {
    document.body.removeEventListener('click', closeMenu)
  }
})

如此便完成contextMenu的所有功能实现