記錄--vue中封裝一個右鍵菜單組件(複製粘貼即可使用)

来源:https://www.cnblogs.com/smileZAZ/archive/2023/03/17/17227618.html
-Advertisement-
Play Games

這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 組件介紹 關於web端的右鍵功能常用的地方有表格的右鍵,或者tab標簽的右鍵等,本文記錄一下封裝一個右鍵菜單組件的思路步驟代碼。 程式員除了會用輪子,還要嘗試去貼合自己公司業務場景造輪子。 組件效果圖 我們先看一下右鍵組件的效果圖 組件分 ...


這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助

組件介紹

關於web端的右鍵功能常用的地方有表格的右鍵,或者tab標簽的右鍵等,本文記錄一下封裝一個右鍵菜單組件的思路步驟代碼。

程式員除了會用輪子,還要嘗試去貼合自己公司業務場景造輪子。

組件效果圖

我們先看一下右鍵組件的效果圖

1111.gif

組件分析

1.封裝組件第一步考慮dom結構

我們觀察這個右鍵菜單,可以明白右鍵菜單就是一個ul標簽包裹著很多li標簽的彈出層組件,如下圖:

 

 每一行都是一個li,每一行中包含圖標行按鈕名稱文字,於是我們的dom結構可以這樣寫:

<ul class="table-right-menu">
    <!-- 每個li都是一行,迴圈菜單數據,菜單數據後面再設計 -->
    <li
      v-for="item in menulists"
      :key="item.btnName"
       @click.stop="fnHandler(item)"
    >
      <div class="table-right-menu-item-btn">
        <!-- 圖標和按鈕名 -->
        <i class="el-icon-ele" />
        <span>複製數據</span>
      </div>
    </li>
  </ul>

2.dom結構搞清楚了,接下來就是考慮右鍵菜單組件接收的參數

如何考慮菜單組件接收哪些參數呢?

主要是想組件中會使用到哪些變數。如下:

  • 右鍵菜單需要一個數組,數組中存放的是每個菜單項的數據(菜單項圖標、菜單項按鈕名字、當然還有一些其他的需要傳遞的參數,統一掛在一個變數身上,如params)
  • 其次右鍵菜單組件的觸發時機是擁擠點擊右鍵的時候,那我們就得知道,用戶右鍵點擊的位置x、y的距離,所以這裡還需要參數position中的x和y去記錄距離視口的clientX和clientY值,因為右鍵菜單的位置就以這個作基準
  • 同時,我們還需要知道用戶點擊的是哪個菜單項按鈕,所以再加一個事件名參數進去

綜上所述,我們可以設計右鍵點擊時,要給右鍵菜單組件傳遞的參數信息如下:

this.rightclickInfo = {
        position: {
          x: event.clientX,
          y: event.clientY,
        },
        menulists: [
          {
            fnName: "copy", // 事件名字,組件屆時可this.$emit(fnName)拋出事件
            params: xxx,  // 參數,組件屆時可this.$emit(fnName,params)拋出事件,並攜帶參數
            icoName: "el-icon-document-copy", // 圖標名
            btnName: "複製數據", // 菜單項按鈕名
            // 這三項是發散,可往下看
            // divided: true, // 是否禁用
            // disabled: true, // 是否帶分隔線
            // children: [], // 是否有子菜單(遞龜)
          },
          {
            fnName: "look",
            params: { row, column, event },
            icoName: "el-icon-view",
            btnName: "查看行數據",
          },
        ],
      };

註意,上述參數代碼示例中,多了三個參數divided、disabled、children,實際上,參數的設計要結合業務場景,我司的需求沒有右鍵菜單禁用項,也不用有分割線,以及沒有右鍵菜單的子菜單,所以封裝組件就暫時沒有加上這三個參數。

組件化、模塊化的同時,主要高內聚,一個組件滿足業務需求,精簡為主,不可無節制的死命封裝,否則就變成了詩山代碼了,當然大家也可以仿照真正右鍵菜單去加功能,比如右鍵菜單可以綁定快捷鍵、改成遞歸形式等更多功能...

所以組件props中接收參數可以寫成:

props: {
    // 接收右鍵點擊的信息
    rightclickInfo: {
      type: Object,
      default: () => {
        return {
          position: {
            // 右鍵點擊的位置
            x: null,
            y: null,
          },
          menulists: [
            {
              fnName: "", // 點擊菜單項的事件名
              params: {}, // 點擊的參數
              icoName: "", // 圖標名
              btnName: "", // 按鈕名
            },
          ],
        };
      },
    },
  },

3.實現右鍵打開菜單彈出層,左鍵點擊一下菜單彈出層就關閉了

不難發現,只要一右鍵菜單就彈出,點一下菜單消失,這種不停的顯示和消失,去不停的v-if就不合適了,所以這裡可以從v-show的角度出發

  • 一開始讓菜單層隱藏display:none,而後再設置成dispaly:block
  • 當右鍵點擊時,右鍵點擊的位置參數positionx和y的值就會發生變化
  • 我們可以watch監聽這個變化,position的x、y值變了,說明右鍵點擊了
  • 右鍵點擊了,我們就可以讓菜單彈出層出現
  • 同時,需要監聽滑鼠點擊事件,當點擊的是右鍵或者中間滾輪鍵時,不去隱藏面板,點擊的是左鍵時,才去隱藏面板

通過上述五點,我們即做到了顯示隱藏菜單面板了

4.監聽右鍵位置變化,顯示菜單項代碼

這一塊的思路請看代碼中註釋即可,如下:

.table-right-menu {
    dispaly:none; // 初始為隱藏,監聽更改顯示
}


watch: {
    // 監聽右鍵點擊時點擊位置的變化,只要變化了,就彈出右鍵菜單供用戶點擊操作
    "rightclickInfo.position"(val) {
      let x = val.x; // 獲取x軸坐標
      let y = val.y; // 獲取y軸坐標
      let innerWidth = window.innerWidth; // 獲取頁面可是區域寬度,即頁面的寬度
      let innerHeight = window.innerHeight; // 獲取可視區域高度,即頁面的高度
      /**
       * 註意,這裡要使用getElementsByClassName去選中對應dom,因為右鍵菜單組件可能被多處使用
       * classIndex標識就是去找到對應的那個右鍵菜單組件的,需要加的
       * */ 
      let menu =
        document.getElementsByClassName("table-right-menu")[this.classIndex]; 
      menu.style.display = "block"; // 由隱藏改為顯示
      let menuHeight = this.rightclickInfo.menulists.length * 30; // 菜單容器高
      let menuWidth = 180; // 菜單容器寬
      // 菜單的位置計算(邊界留點間隙空間)
      menu.style.top =
        (y + menuHeight > innerHeight ? innerHeight - menuHeight : y) + "px";
      menu.style.left =
        (x + menuWidth > innerWidth ? innerWidth - menuWidth : x) + "px";
      // 因為菜單還要關閉,就綁定一個滑鼠點擊事件,通過e.button判斷點擊的是否是左鍵,左鍵關閉菜單
      document.addEventListener("mouseup", this.hide, false);
    },
  },
  
  
hide(e) {
  if (e.button === 0) {
    // 0是左鍵、1是滾輪按鈕或中間按鈕(若有)、2滑鼠右鍵
    let menu = document.querySelector(".table-right-menu");
    menu.style.display = "none"; // 菜單關閉
    document.removeEventListener("mouseup", this.hide); // 及時解綁監聽事件
  }
},

事件綁定後別忘瞭解綁 document.removeEventListener("mouseup", this.hide);

5.知識點回顧e.button

  • e.button,滑鼠事件
  • 返回一個數字,表示觸發滑鼠事件的是按下了哪個按鈕
  • 值為只讀,不可修改

具體返回數字值,表示滑鼠事件發生時按下的滑鼠按鈕。

可能的值:

0:滑鼠左鍵、 1:滾輪按鈕或中間按鈕(如果有)、 2:滑鼠右鍵

IE8返回有一些不同:1:滑鼠左鍵、 2:滑鼠右鍵、 4:滾輪按鈕或中間按鈕(如果有)

註意:左手滑鼠,返回值相反

6.組件中的事件要拋出去哦

item即為迴圈的菜單項,包含事件名、參數、圖標名、按鈕名

fnHandler(item) {
  this.$emit(item.fnName, item.params);
  // 事件再傳出去,即為:
  // this.$emit('事件名',事件參數)
},

7.外界接收事件,正常@xxx='xxx'使用即可

如下:

<my-right-menu
  :rightclickInfo="rightclickInfo"
  @copy="copy"
  @look="look"
  @edit="edit"
  @delete="deleteFn"
  @refresh="refresh"
></my-right-menu>

使用組件

搭配el-table使用

  • el-table中可以使用封裝好的事件:@row-contextmenu="xxx"

  • 然後在xxx方法中去傳遞參數給右鍵菜單組件即可,如下簡化代碼:

<el-table
  :data="tableData"
  @row-contextmenu="rightclick"
>
  ...
</el-table>

<my-right-menu
  :rightclickInfo="rightclickInfo"
  @copy="copy"
></my-right-menu>

rightclickInfo:{}

// 餓了麽UI封裝好的右鍵菜單事件,可直接使用,有行數據,列數據,以及事件
rightclick(row, column, event) {
  this.rightclickInfo = {
    position: {
      x: event.clientX,
      y: event.clientY,
    },
    menulists: [
      {
        fnName: "copy",
        params: { row, column, event },
        icoName: "el-icon-document-copy",
        btnName: "複製數據",
      },
    ],
  };
  event.preventDefault(); // 阻止預設的滑鼠右擊事件
},

event.preventDefault()要加上,阻止預設的右鍵菜單事件

搭配普通dom使用

也同理,傳參的時,需要阻止預設時間,如下:

<!-- 右鍵菜單搭配普通的dom元素使用,普通的dom元素需要阻止預設右鍵事件,即.prevent -->
<div class="normalDom" @contextmenu.prevent="onContextmenu">區域內右鍵</div>

onContextmen(){
    // 定義參數傳遞給my-right-menu組件
}

完整代碼

複製粘貼即可使用哦

使用組件代碼

<template>
  <div>
    <h5>表格內右鍵</h5>
    <br />
    <!-- 右鍵菜單搭配el-table使用 -->
    <el-table
      border
      :data="tableData"
      style="width: 100%"
      @row-contextmenu="rightclick"
    >
      <el-table-column prop="name" label="姓名"> </el-table-column>
      <el-table-column prop="age" label="年齡"> </el-table-column>
      <el-table-column prop="home" label="家鄉"> </el-table-column>
      <el-table-column prop="hobby" label="愛好"> </el-table-column>
    </el-table>
    <br />
    <br />
    <br />
    <!-- 右鍵菜單搭配普通的dom元素使用,普通的dom元素需要阻止預設右鍵事件,即.prevent -->
    <div class="normalDom" @contextmenu.prevent="onContextmenu">區域內右鍵</div>
    <!-- 右鍵菜單 -->
    <my-right-menu
      :class-index="0"
      :rightclickInfo="rightclickInfo"
      @copy="copy"
      @look="look"
      @edit="edit"
      @delete="deleteFn"
      @refresh="refresh"
    ></my-right-menu>
  </div>
</template>

<script>
export default {
  name: "myRightMenuName",
  data() {
    return {
      tableData: [
        {
          id: "1",
          name: "孫悟空",
          age: 500,
          home: "花果山水簾洞",
          hobby: "桃子",
        },
        {
          id: "2",
          name: "豬八戒",
          age: 88,
          home: "高老莊",
          hobby: "肉包子",
        },
        {
          id: "3",
          name: "沙和尚",
          age: 500,
          home: "通天河",
          hobby: "游泳",
        },
        {
          id: "4",
          name: "唐僧",
          age: 1000,
          home: "東土大唐",
          hobby: "吃齋念經",
        },
      ],
      rightclickInfo: {},
    };
  },
  methods: {
    // 餓了麽UI封裝好的右鍵菜單事件,可直接使用
    rightclick(row, column, event) {
      this.rightclickInfo = {
        position: {
          x: event.clientX,
          y: event.clientY,
        },
        menulists: [
          {
            fnName: "copy",
            params: { row, column, event },
            icoName: "el-icon-document-copy",
            btnName: "複製數據",
            // divided: true,
            // disabled: true,
            // children: [],
          },
          {
            fnName: "look",
            params: { row, column, event },
            icoName: "el-icon-view",
            btnName: "查看行數據",
          },
          {
            fnName: "edit",
            params: { row, column, event },
            icoName: "el-icon-edit",
            btnName: "編輯行數據",
          },
          {
            fnName: "delete",
            params: { row, column, event },
            icoName: "el-icon-delete",
            btnName: "刪除行數據",
          },
          {
            fnName: "refresh",
            params: { row, column, event },
            icoName: "el-icon-refresh",
            btnName: "刷新頁面",
          },
        ],
      };
      event.preventDefault(); // 阻止預設的滑鼠右擊事件
    },
    copy(params) {
      console.log(
        "copy",
        params.row ? params.row[params.column.property] : params
      );
    },
    look(params) {
      console.log("look", params.row ? JSON.stringify(params.row) : params);
    },
    edit(params) {
      console.log("edit", params);
    },
    deleteFn(params) {
      console.log("deleteFn", params.row ? params.row.id : params);
    },
    refresh(params) {
      console.log("refresh 刷新頁面啦");
    },
    // 普通dom右鍵
    onContextmenu(e) {
      this.rightclickInfo = {
        position: {
          x: e.clientX,
          y: e.clientY,
        },
        menulists: [
          {
            fnName: "copy",
            params: "代碼修仙",
            icoName: "el-icon-star-on",
            btnName: "代碼修仙",
          },
          {
            fnName: "look",
            params: "路漫漫",
            icoName: "el-icon-star-off",
            btnName: "路漫漫",
          },
        ],
      };
    },
  },
};
</script>

<style>
.normalDom {
  width: 240px;
  height: 240px;
  line-height: 240px;
  text-align: center;
  border: 6px dotted pink;
  font-family: "楷體", Courier, monospace;
  font-weight: 600;
}
</style>

封裝組件代碼

<template>
  <ul class="table-right-menu">
    <!-- 迴圈菜單項,事件帶參數拋出 -->
    <li
      v-for="item in rightclickInfo.menulists"
      :key="item.btnName"
      class="table-right-menu-item"
      @click.stop="fnHandler(item)"
    >
      <div class="table-right-menu-item-btn">
        <!-- 圖標和按鈕名 -->
        <i :class="item.icoName" class="iii" />
        <span>{{ item.btnName }}</span>
      </div>
    </li>
  </ul>
</template>

<script>
export default {
  name: "myRightMenu",
  props: {
    // 接收右鍵點擊的信息
    rightclickInfo: {
      type: Object,
      default: () => {
        return {
          position: {
            // 右鍵點擊的位置
            x: null,
            y: null,
          },
          menulists: [
            {
              fnName: "", // 點擊菜單項的事件名
              params: {}, // 點擊的參數
              icoName: "", // 圖標名
              btnName: "", // 按鈕名
            },
          ],
        };
      },
    },
    // 重要參數,用於標識是哪個右鍵菜單dom元素
    classIndex: {
      type: Number,
      default: 0,
    },
  },
  watch: {
    // 監聽右鍵點擊時點擊位置的變化,只要變化了,就彈出右鍵菜單供用戶點擊操作
    "rightclickInfo.position"(val) {
      let x = val.x; // 獲取x軸坐標
      let y = val.y; // 獲取y軸坐標
      let innerWidth = window.innerWidth; // 獲取頁面可是區域寬度,即頁面的寬度
      let innerHeight = window.innerHeight; // 獲取可視區域高度,即頁面的高度
      /**
       * 註意,這裡要使用getElementsByClassName去選中對應dom,因為右鍵菜單組件可能被多處使用
       * classIndex標識就是去找到對應的那個右鍵菜單組件的,需要加的
       * */ 
      let menu =
        document.getElementsByClassName("table-right-menu")[this.classIndex]; 
      menu.style.display = "block";
      let menuHeight = this.rightclickInfo.menulists.length * 30; // 菜單容器高
      let menuWidth = 180; // 菜單容器寬
      // 菜單的位置計算
      menu.style.top =
        (y + menuHeight > innerHeight ? innerHeight - menuHeight : y) + "px";
      menu.style.left =
        (x + menuWidth > innerWidth ? innerWidth - menuWidth : x) + "px";
      // 因為菜單還要關閉,就綁定一個滑鼠點擊事件,通過e.button判斷點擊的是否是左鍵,左鍵關閉菜單
      document.addEventListener("mouseup", this.hide, false);
    },
  },
  methods: {
    hide(e) {
      if (e.button === 0) {
        // 0是左鍵、1是滾輪按鈕或中間按鈕(若有)、2滑鼠右鍵
        let menu =
          document.getElementsByClassName("table-right-menu")[this.classIndex]; // 同樣的精確查找
        menu.style.display = "none"; // 菜單關閉
        document.removeEventListener("mouseup", this.hide); // 及時解綁監聽事件
      }
    },
    fnHandler(item) {
      this.$emit(item.fnName, item.params);
      // 事件再傳出去,即為:
      // this.$emit('事件名',事件參數)
    },
  },
};
</script>

<style lang='less' scoped>
.table-right-menu {
  color: #333;
  background: #fff;
  border-radius: 4px;
  list-style-type: none;
  box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, 0.3);
  font-size: 12px;
  font-weight: 500;
  box-sizing: border-box;
  padding: 4px 0;
  // 固定定位,抬高層級,初始隱藏,右擊時置為display:block顯示
  position: fixed;
  z-index: 3000;
  display: none;
  .table-right-menu-item {
    box-sizing: border-box;
    padding: 6px 12px;
    border-radius: 4px;
    transition: all 0.36s;
    cursor: pointer;
    .table-right-menu-item-btn {
      .iii {
        margin-right: 4px;
      }
    }
  }
  .table-right-menu-item:hover {
    background-color: #ebf5ff;
    color: #6bacf2;
  }
}
</style>

本文轉載於:

https://juejin.cn/post/7174420692954251272

如果對您有所幫助,歡迎您點個關註,我會定時更新技術文檔,大家一起討論學習,一起進步。

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 使用 NMCLI 配置靜態乙太網連接 要在命令行上配置乙太網連接,請使用 nmcli 工具。 例如,以下流程使用以下設置為 enp7s0 設備創建 NetworkManager 連接配置文件: 靜態 IPv4 地址 - 192.0.2.1 和 /24 子網掩碼 靜態 IPv6 地址 - 2001:d ...
  • 報錯信息: 在進入系統時,不能正常進入系統,出現了Give root password for maintenance(or type Control-D to continue):的報錯。 報錯原因: 1、在之前寫入的/etc/fstab文件有問題,導致系統在讀取的時候識別不到從而報錯。 解決方法 ...
  • 什麼是消息隊列? 假設你是一個快遞員,你需要將貨物從一個倉庫運到另一個倉庫。但是你發現自己的時間不夠用,需要另外請一個人來幫忙。那麼,你們之間如何進行協作呢? 一種方式是直接將貨物全部交給對方,但這樣存在風險:對方可能會出現問題,導致貨物丟失或損壞。 而另一種更安全的方式是,你將貨物分批發送給對方, ...
  • MySql存儲結構 參考視頻:MySql存儲結構 1.表空間 不同的存儲引擎在磁碟文件上的結構均不一致,這裡以InnoDB為例: CREATE TABLE t(id int(11)) Engine = INNODB; 在新表創建的過程中,InnoDB會在磁碟的data目錄下創建與這個表對應的兩個文件 ...
  • 您可以使用以下 SQL 語句刪除 MS SQL Server 表中重覆的行: WITH CTE AS ( SELECT ROW_NUMBER() OVER(PARTITION BY column1, column2, ... columnN ORDER BY (SELECT 0)) RN FROM ...
  • 摘要:DWS的PL/pgSQL函數/存儲過程中有一個特殊的語法PERFORM語法,用於執行語句但是丟棄執行結果的場景,常用於一些狀態判斷的場景。 本文分享自華為雲社區《GassDB(DWS)功能 -- 函數出參 #【玩轉PB級數倉GaussDB(DWS)】》,作者:譡里個檔。 DWS的PL/pgSQ ...
  • 大資管包括原銀保監監管下的銀行理財、信托、保險,原證監會監管下的公募基金、私募基金專戶及基金子公司、券商資管以及期貨資管。據統計,2022年底資管行業的資產管理規模也達到了136萬億元。 在《商業銀行理財業務監督管理辦法》《關於規範金融機構資產管理業務的指導意見》等理財新規、資管新規的要求下,大資管 ...
  • 數據湖作為新一代大數據基礎設施,近年來持續火熱,許多前線的同學都在討論數據湖應該怎麼建,許多企業也都在構建或者計劃構建自己的數據湖。基於此,自然引發了許多關於數據湖選型的討論和探究。但是經過搜索之後我們發現,網上現存的很多內容都是基於較早之前的開源信息做出的結論,在企業調研初期容易造成不准確的印象和 ...
一周排行
    -Advertisement-
    Play Games
  • 背景 在瀏覽器中訪問本地靜態資源html網頁時,可能會遇到跨域問題如圖。 是因為瀏覽器預設啟用了同源策略,即只允許載入與當前網頁具有相同源(協議、功能變數名稱和埠)的內容。 WebView2預設情況下啟用了瀏覽器的同源策略,即只允許載入與主機相同源的內容。所以如果我們把靜態資源發佈到iis或者通過node ...
  • 最近看幾個老項目的SQL條件中使用了1=1,想想自己也曾經這樣寫過,略有感觸,特別拿出來說道說道。編寫SQL語句就像炒菜,每一種調料的使用都會影響菜品的最終味道,每一個SQL條件的加入也會影響查詢的執行效率。那麼 1=1 存在什麼樣的問題呢?為什麼又會使用呢? ...
  • 好久不見,我又回來了。 給大家分享一個我最近使用c#代碼操作ftp伺服器的代碼示例: 1 public abstract class FtpOperation 2 { 3 /// <summary> 4 /// FTP伺服器地址 5 /// </summary> 6 private string f ...
  • 一:背景 1. 講故事 過年喝了不少酒,腦子不靈光了,停了將近一個月沒寫博客,今天就當新年開工寫一篇吧。 去年年初有位朋友找到我,說他們的系統會偶發性崩潰,在網上也發了不少帖子求助,沒找到自己滿意的答案,讓我看看有沒有什麼線索,看樣子這是一個牛皮蘚的問題,既然對方有了dump,那就分析起來吧。 二: ...
  • 自己製作的一個基於Entity Framework Core 的資料庫操作攔截器,可以列印資料庫執行sql,方便開發調試,代碼如下: /// <summary> /// EF Core 的資料庫操作攔截器,用於在資料庫操作過程中進行日誌記錄和監視。 /// </summary> /// <remar ...
  • 本文分享自華為雲社區《Go併發範式 流水線和優雅退出 Pipeline 與 Cancellation》,作者:張儉。 介紹 Go 的併發原語可以輕鬆構建流數據管道,從而高效利用 I/O 和多個 CPU。 本文展示了此類pipelines的示例,強調了操作失敗時出現的細微之處,並介紹了乾凈地處理失敗的 ...
  • 在上篇文章中,我們介紹到在多線程環境下,如果編程不當,可能會出現程式運行結果混亂的問題。出現這個原因主要是,JMM 中主記憶體和線程工作記憶體的數據不一致,以及多個線程執行時無序,共同導致的結果。 ...
  • 1、下載安裝包首先、進入官網下載安裝包網址:https://www.python.org/downloads/windows/下載步驟:進入下載地址,根據自己的電腦系統選擇相應的python版本 選擇適配64位操作系統的版本(查看自己的電腦操作系統版本), 點擊下載安裝包 也可以下載我百度雲分享的安 ...
  • 簡介 git-commit-id-maven-plugin 是一個maven 插件,用來在打包的時候將git-commit 信息打進jar中。 這樣做的好處是可以將發佈的某版本和對應的代碼關聯起來,方便查閱和線上項目的維護。至於它的作用,用官方說法,這個功能對於大型分散式項目來說是無價的。 功能 你 ...
  • 序言 在數字時代,圖像生成技術正日益成為人工智慧領域的熱點。 本討論將重點聚焦於兩個備受矚目的模型:DALL-E和其他主流AI繪圖方法。 我們將探討它們的優勢、局限性以及未來的發展方向。通過比較分析,我們期望能夠更全面地瞭解這些技術,為未來的研究和應用提供啟示。 Q: 介紹一下 dall-e Ope ...