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