likes
comments
collection
share

vue3 实现一个鼠标右击菜单栏

作者站长头像
站长
· 阅读数 6

前言

最近得空,准备用vite+vue3搭建一个后台管理系统.

技术栈

vue3 实现一个鼠标右击菜单栏

效果图

vue3 实现一个鼠标右击菜单栏

代码

多的不说直接上代码

下面是tabs的代码片段

<template>
  <div>
    <el-menu :default-active="store.defaultActive" router mode="horizontal" @select="handleSelect">
      <el-menu-item
        v-for="(item, index) in store.tabList"
        :key="index"
        class="tabMenu"
        :data-key="index"
        :index="item.path"
      >
        {{ item.title }}
      </el-menu-item>
    </el-menu>
    <closePanel :path="path" ref="closePanelRef" />
  </div>
</template>

<script setup lang="ts">
import { useGlobalStore } from "@/store/index"
import closePanel from "./closePanel.vue"
const closePanelRef = ref()
const store = useGlobalStore()

const handleSelect = (key: string, keyPath: string[]) => {
  console.log(key, keyPath)
  store.setDefaultActive(key)
}

const path = reactive({
  x: 0,
  y: 0
})
const init = () => {
  const dom = document.getElementsByClassName("tabMenu")
  Array.from(dom).forEach((element) => {
    element.addEventListener("contextmenu", (e: Event) => {
      path.x = (e as MouseEvent).clientX
      path.y = (e as MouseEvent).clientY
      const key = (e.target as HTMLElement)?.getAttribute("data-key")
      closePanelRef.value.toggele(key)
      e.preventDefault()
    })
  })
}

watch(
  () => store.tabList.length,
  () => {
    nextTick(() => {
      removeEvent()
      init()
    })
  }
)

const removeEvent = () => {
  const dom = document.getElementsByClassName("tabMenu")
  Array.from(dom).forEach((element) => {
    element.removeEventListener("contextmenu", () => {})
  })
}

onMounted(() => {
  init()
})
</script>

<style></style>
<template>
  <div>
    <el-menu :default-active="store.defaultActive" router mode="horizontal" @select="handleSelect">
      <el-menu-item
        v-for="(item, index) in store.tabList"
        :key="index"
        class="tabMenu"
        :data-key="index"
        :index="item.path"
      >
        {{ item.title }}
      </el-menu-item>
    </el-menu>
    <closePanel :path="path" ref="closePanelRef" />
  </div>
</template>

<script setup lang="ts">
import { useGlobalStore } from "@/store/index"
import closePanel from "./closePanel.vue"
const closePanelRef = ref()
const store = useGlobalStore()

const handleSelect = (key: string, keyPath: string[]) => {
  console.log(key, keyPath)
  store.setDefaultActive(key)
}

const path = reactive({
  x: 0,
  y: 0
})
const init = () => {
  const dom = document.getElementsByClassName("tabMenu")
  Array.from(dom).forEach((element) => {
    element.addEventListener("contextmenu", (e: Event) => {
      path.x = (e as MouseEvent).clientX
      path.y = (e as MouseEvent).clientY
      const key = (e.target as HTMLElement)?.getAttribute("data-key")
      closePanelRef.value.toggele(key)
      e.preventDefault()
    })
  })
}

watch(
  () => store.tabList.length,
  () => {
    nextTick(() => {
      removeEvent()
      init()
    })
  }
)

const removeEvent = () => {
  const dom = document.getElementsByClassName("tabMenu")
  Array.from(dom).forEach((element) => {
    element.removeEventListener("contextmenu", () => {})
  })
}

onMounted(() => {
  init()
})
</script>

<style></style>
  • 主要是给el-menu-item加上contextmenu事件并用e.preventDefault()阻止默认菜单得展示。
  • closePanel组件是右击展示的弹层

closePanel组件的相关代码

<template>
  <div
    v-click-outside="() => toggele('', false)"
    v-if="show"
    :class="['absolute']"
    :key="path.x + path.y"
    :style="{ left: path.x + 'px', top: path.y + 'px' }"
  >
    <el-card class="relative z-10">
      <template #header>
        <div class="card-header">
          <span>操作菜单栏</span>
        </div>
      </template>
      <div class="text item" @click="closeAll">关闭所有</div>
      <div class="text item" @click="closeOther">关闭其他</div>
    </el-card>
  </div>
</template>

<script setup lang="ts">
import { useGlobalStore } from "@/store/index"
import { ClickOutside as vClickOutside } from "element-plus"
const store = useGlobalStore()
const show = ref(false)
const index = ref("")
defineProps({
  path: {
    type: Object,
    default: () => {
      return {
        x: 0,
        y: 0
      }
    }
  }
})
const closeAll = () => {
  console.log("关闭所有")
  store.clearTab()
  toggele("", false)
}
const closeOther = () => {
  console.log("关闭其他")
  store.closeOtherTab(index.value)
  toggele("", false)
}

const toggele = (key: string, visible = true) => {
  if (key) {
    index.value = key
  }
  show.value = visible
  console.log(show.value)
}
defineExpose({
  toggele
})
</script>

<style></style>
  • defineExpose将父组件触发展示的函数抛出
  • v-click-outside点击组件外的地方触发关闭函数
    • element-plus导出的指令能直接使用,下次好好看看还有那些指令

下面是关于全局store的代码

export const useGlobalStore = defineStore("globalStore", {
  persist: true,
  state: () => {
    return {
      tabList: [
        {
          path: "/index",
          name: "index",
          title: "首页"
        }
      ],
      defaultActive: "/index"
    }
  },
  actions: {
    setDefaultActive(name: string) {
      this.defaultActive = name
    },
    clearTab() {
      this.tabList = [
        {
          path: "/index",
          name: "index",
          title: "首页"
        }
      ]
      this.defaultActive = "/index"
    },
    closeOtherTab(i: string) {
      this.tabList = this.tabList.filter((item: any, index: any) => index == 0 || i == index)
    },
    addTab(tab: any) {
      if (this.tabList.some((item: any) => item.path === tab.path)) return
      this.tabList.push(tab)
    }
  }
})

最后

关于这个vue3+vite的管理系统我是打算有一下功能

  • 权限
  • 主题切换
  • 多语言

目前想到这么多,主要是记录一下vite和vue3及衍生的生态插件的使用