右键菜单继承复制功能

46 阅读2分钟

背景说明

在现代Web应用中,划词选择上下文菜单是提升用户体验的重要功能。传统的浏览器提供了基础的文本选择和默认右键菜单,但往往无法满足特定业务场景的需求:

  1. 功能局限性:浏览器默认的右键菜单无法添加自定义功能选项(如"翻译"、"收藏"等)
  2. 视觉一致性:默认菜单样式与网站设计风格不匹配,影响整体UI体验
  3. 业务需求:许多应用需要根据选中内容动态显示不同的操作选项

功能说明

  1. 划词选择:鼠标拖动选择文本时会临时高亮显示选中的内容

  2. 右键菜单:选中文本后右键点击会显示自定义菜单(复制、搜索、高亮)

  3. 菜单功能

    • 复制:将选中文本复制到剪贴板
    • 搜索:使用百度搜索选中文本
    • 翻译:翻译显示选中的文本

解析步骤

  1. 监听鼠标抬起事件获取选中文本
  2. 阻止默认右键菜单
  3. 显示自定义右键菜单
  4. 根据选中文本执行菜单操作

父组件引用

<template>
  <div>
    <text-selection-context-menu>
      <!-- 这里放你的内容 -->
      <p>这是一段可以划词选择并显示右键菜单的文本...</p>
      <p>另一段可以选择的文本...</p>
    </text-selection-context-menu>
  </div>
</template><script>
import TextSelectionContextMenu from "./Menu.vue";
​
export default {
  components: {
    TextSelectionContextMenu,
  },
};
</script>

菜单复制组件

<template>
  <div @mouseup="handleMouseUp" @contextmenu.prevent>
    <!-- 你的内容区域 -->
    <slot></slot>
​
    <!-- 右键菜单 -->
    <div
      v-if="showMenu"
      class="custom-context-menu"
      :style="{ left: menuPosition.x + 'px', top: menuPosition.y + 'px' }"
      v-click-outside="closeMenu"
    >
      <ul>
        <li @click="handleMenuAction('search')">搜索</li>
        <li @click="handleMenuAction('translate')">翻译</li>
        <li @click="handleMenuAction('copy')">复制</li>
      </ul>
    </div>
  </div>
</template><script>
export default {
  name: "TextSelectionContextMenu",
  data() {
    return {
      showMenu: false,
      selectedText: "",
      menuPosition: {
        x: 0,
        y: 0,
      },
    };
  },
  directives: {
    // 点击菜单外部关闭菜单的指令
    "click-outside": {
      bind(el, binding, vnode) {
        el.clickOutsideEvent = function (event) {
          if (!(el === event.target || el.contains(event.target))) {
            vnode.context[binding.expression](event);
          }
        };
        document.body.addEventListener("click", el.clickOutsideEvent);
      },
      unbind(el) {
        document.body.removeEventListener("click", el.clickOutsideEvent);
      },
    },
  },
  methods: {
    handleMouseUp(event) {
      // 检查是否是右键点击
      if (event.button !== 2) return;
​
      const selection = window.getSelection();
      const text = selection.toString().trim();
​
      if (text.length > 0) {
        this.selectedText = text;
        this.menuPosition = {
          x: event.clientX,
          y: event.clientY,
        };
        this.showMenu = true;
      } else {
        this.closeMenu();
      }
    },
    handleMenuAction(action) {
      switch (action) {
        case "search":
          window.open(
            `https://www.google.com/search?q=${encodeURIComponent(
              this.selectedText
            )}`
          );
          break;
        case "translate":
          window.open(
            `https://translate.google.com/?text=${encodeURIComponent(
              this.selectedText
            )}`
          );
          break;
        case "copy":
          navigator.clipboard
            .writeText(this.selectedText)
            .then(() => {
              this.$message.success("已复制到剪贴板");
            })
            .catch((err) => {
              console.error("复制失败:", err);
              this.$message.error("复制失败");
            });
          break;
        default:
          console.log("未知操作:", action);
      }
      this.closeMenu();
    },
    closeMenu() {
      this.showMenu = false;
      this.selectedText = "";
    },
  },
};
</script><style scoped>
.custom-context-menu {
  position: fixed;
  background: white;
  border: 1px solid #ddd;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
  z-index: 1000;
}
​
.custom-context-menu ul {
  list-style: none;
  margin: 0;
  padding: 0;
}
​
.custom-context-menu li {
  padding: 8px 15px;
  cursor: pointer;
}
​
.custom-context-menu li:hover {
  background-color: #f0f0f0;
}
</style>