記錄--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
  • Timer是什麼 Timer 是一種用於創建定期粒度行為的機制。 與標準的 .NET System.Threading.Timer 類相似,Orleans 的 Timer 允許在一段時間後執行特定的操作,或者在特定的時間間隔內重覆執行操作。 它在分散式系統中具有重要作用,特別是在處理需要周期性執行的 ...
  • 前言 相信很多做WPF開發的小伙伴都遇到過表格類的需求,雖然現有的Grid控制項也能實現,但是使用起來的體驗感並不好,比如要實現一個Excel中的表格效果,估計你能想到的第一個方法就是套Border控制項,用這種方法你需要控制每個Border的邊框,並且在一堆Bordr中找到Grid.Row,Grid. ...
  • .NET C#程式啟動閃退,目錄導致的問題 這是第2次踩這個坑了,很小的編程細節,容易忽略,所以寫個博客,分享給大家。 1.第一次坑:是windows 系統把程式運行成服務,找不到配置文件,原因是以服務運行它的工作目錄是在C:\Windows\System32 2.本次坑:WPF桌面程式通過註冊表設 ...
  • 在分散式系統中,數據的持久化是至關重要的一環。 Orleans 7 引入了強大的持久化功能,使得在分散式環境下管理數據變得更加輕鬆和可靠。 本文將介紹什麼是 Orleans 7 的持久化,如何設置它以及相應的代碼示例。 什麼是 Orleans 7 的持久化? Orleans 7 的持久化是指將 Or ...
  • 前言 .NET Feature Management 是一個用於管理應用程式功能的庫,它可以幫助開發人員在應用程式中輕鬆地添加、移除和管理功能。使用 Feature Management,開發人員可以根據不同用戶、環境或其他條件來動態地控制應用程式中的功能。這使得開發人員可以更靈活地管理應用程式的功 ...
  • 在 WPF 應用程式中,拖放操作是實現用戶交互的重要組成部分。通過拖放操作,用戶可以輕鬆地將數據從一個位置移動到另一個位置,或者將控制項從一個容器移動到另一個容器。然而,WPF 中預設的拖放操作可能並不是那麼好用。為瞭解決這個問題,我們可以自定義一個 Panel 來實現更簡單的拖拽操作。 自定義 Pa ...
  • 在實際使用中,由於涉及到不同編程語言之間互相調用,導致C++ 中的OpenCV與C#中的OpenCvSharp 圖像數據在不同編程語言之間難以有效傳遞。在本文中我們將結合OpenCvSharp源碼實現原理,探究兩種數據之間的通信方式。 ...
  • 一、前言 這是一篇搭建許可權管理系統的系列文章。 隨著網路的發展,信息安全對應任何企業來說都越發的重要,而本系列文章將和大家一起一步一步搭建一個全新的許可權管理系統。 說明:由於搭建一個全新的項目過於繁瑣,所有作者將挑選核心代碼和核心思路進行分享。 二、技術選擇 三、開始設計 1、自主搭建vue前端和. ...
  • Csharper中的表達式樹 這節課來瞭解一下表示式樹是什麼? 在C#中,表達式樹是一種數據結構,它可以表示一些代碼塊,如Lambda表達式或查詢表達式。表達式樹使你能夠查看和操作數據,就像你可以查看和操作代碼一樣。它們通常用於創建動態查詢和解析表達式。 一、認識表達式樹 為什麼要這樣說?它和委托有 ...
  • 在使用Django等框架來操作MySQL時,實際上底層還是通過Python來操作的,首先需要安裝一個驅動程式,在Python3中,驅動程式有多種選擇,比如有pymysql以及mysqlclient等。使用pip命令安裝mysqlclient失敗應如何解決? 安裝的python版本說明 機器同時安裝了 ...