Vue实现自定义右击删除菜单的示例
觉醒法师 人气:0一、定义和使用
oncontextmenu 事件在元素中用户右击鼠标时触发并打开上下文菜单。
注意:所有浏览器都支持 oncontextmenu 事件, contextmenu 元素只有 Firefox 浏览器支持。
二、语法
HTML 中:
<element oncontextmenu="myScript">
JavaScript 中:
object.oncontextmenu=function(){myScript};
JavaScript 中, 使用 addEventListener() 方法:
object.addEventListener("contextmenu", myScript);
jQuery中,使用on监听事件
$(document).on('contextmenu', myScript);
注意: Internet Explorer 8 及更早 IE 浏览器版本不支持 addEventListener() 。
三、使用细节
是否支持冒泡: | Yes |
是否可以取消: | Yes |
事件类型: | MouseEvent |
支持的 HTML 标签: | 所有 HTML 元素 |
四、案例
这通过一个小案例,在考勤统计列表中,每行中对应某员工这月周期内的每天考勤数据;如果某条记录需要删除,这里通过在记录上直接右击出现删除菜单,操作起来则更为方便。如下图:
4.1 自定义右击删除菜单步骤
通过DOM对象监听鼠标点击事件
触发鼠标点击事件,获取鼠标当前浏览器位置
根据当前位置重新调整自定义contextmenu的位置
显示自定义contextmenu
4.2 技术点
这里使用Vue技术框架来做这个案例,统计列表使用是element-ui中的table组件,事件监听和数据获取通过jquery实现,在开发前请将其通过npm进行安装。
//安装element-ui npm i element-ui -S
//安装jquery npm i jquery -S
4.3 html代码
这里通过列数据中isDate判断该列是否渲染为对应日期列,渲染日期相对应的考勤记录时,需要追加data-prop和data-id进行数据传递,后续执行删除操作时会使用到。
<template> <div class="list-wrap"> <el-table stripe :data="tableData" class="list-table" style="width: 100%"> <el-table-column type="selection" width="55" fixed="left"></el-table-column> <template v-for="(item, index) in tableColumn"> <el-table-column :prop="item.prop" :label="item.name" :key="index" :width="item.width" v-if="item.isDate"> <template slot-scope="scope"> <span class="date-cell" v-html="scope.row[item.prop]" :data-prop="item.prop" :data-id="scope.row['id_'+item.prop]"></span> </template> </el-table-column> <el-table-column :prop="item.prop" :label="item.name" :key="index" :width="item.width" v-else fixed="left"></el-table-column> </template> </el-table> </div> </template> <style lang="scss"> @import './index.scss'; </style>
4.4 CSS样式
.date-cell类选择器对考勤记录数据进行样式修改,更改鼠标为小手图标,并禁止选择文本内容,为避免后期右击时会误操作选中内容。
.menu-box类选择器为右击菜单增加基础样式。
.date-cell{ cursor: pointer; -moz-user-select:none; /*火狐*/ -webkit-user-select:none; /*webkit浏览器*/ -ms-user-select:none; /*IE10*/ -khtml-user-select:none; /*早期浏览器*/ user-select:none; } .menu-box{ position: absolute; ul.list{ background-color: #fff; border: 1px solid #ccc; li{ border-top: 1px solid #ccc; padding: 6px 15px; font-size: 14px; line-height: 1.5; color: #666; list-style: none; cursor: pointer; &:first-child{ border-top: 0; } } } }
4.5 基础数据
变量tableData用于存储员工考勤数据,变量tableColumn用于存储表格列头部数据定义,在获取考勤记录后,动态追加日期列到变量tableColumn中。
<script> export default { data(){ return { tableData: [], tableColumn: [ {prop: "name", name: "姓名", width: "80px"}, {prop: "workday", name: "实际到岗/天", width: "100px"}, ] } }, created() { }, methods: { } } </script>
此时页面样式如下图:
4.6 模拟数据
这里在目录位置新建demo.json文件,将以下数据拷入即可。
[ {"id":1482,"name":"张三","record":["08:30","18:00"],"recorddate":"2023-01-01"}, {"id":1481,"name":"张三","record":["08:30","18:00"],"recorddate":"2023-01-02"}, {"id":1480,"name":"张三","record":["08:30","18:00"],"recorddate":"2023-01-03"}, {"id":1479,"name":"张三","record":["08:30","18:00"],"recorddate":"2023-01-04"}, {"id":1478,"name":"张三","record":["08:30","18:00"],"recorddate":"2023-01-05"}, {"id":1477,"name":"张三","record":["08:30","18:00"],"recorddate":"2023-01-06"}, {"id":1476,"name":"张三","record":["08:30","18:00"],"recorddate":"2023-01-07"}, {"id":1475,"name":"张三","record":["08:30","18:00"],"recorddate":"2023-01-08"}, {"id":1474,"name":"张三","record":["08:30","18:00"],"recorddate":"2023-01-09"}, {"id":1473,"name":"张三","record":["08:30","18:00"],"recorddate":"2023-01-10"}, {"id":1472,"name":"张三","record":["08:30","18:00"],"recorddate":"2023-01-11"}, {"id":1471,"name":"张三","record":["08:30","18:00"],"recorddate":"2023-01-12"}, {"id":1470,"name":"张三","record":["08:30","18:00"],"recorddate":"2023-01-13"}, {"id":1469,"name":"张三","record":["08:30","18:00"],"recorddate":"2023-01-14"}, {"id":1468,"name":"张三","record":["08:30","18:00"],"recorddate":"2023-01-15"}, {"id":1467,"name":"张三","record":["08:30","18:00"],"recorddate":"2023-01-16"}, {"id":1466,"name":"张三","record":["08:30","18:00"],"recorddate":"2023-01-17"}, {"id":1465,"name":"张三","record":["08:30","18:00"],"recorddate":"2023-01-18"}, {"id":1464,"name":"张三","record":["08:30","18:00"],"recorddate":"2023-01-19"}, {"id":1463,"name":"张三","record":["08:30","18:00"],"recorddate":"2023-01-20"}, {"id":1462,"name":"张三","record":["08:30","18:00"],"recorddate":"2023-01-21"}, {"id":1461,"name":"张三","record":["08:30","18:00"],"recorddate":"2023-01-22"}, {"id":1382,"name":"李四","record":["08:30","18:00"],"recorddate":"2023-01-01"}, {"id":1381,"name":"李四","record":["08:30","18:00"],"recorddate":"2023-01-07"}, {"id":1380,"name":"李四","record":["08:30","18:00"],"recorddate":"2023-01-08"}, {"id":1379,"name":"李四","record":["08:30","18:00"],"recorddate":"2023-01-15"}, {"id":1378,"name":"李四","record":["08:30","18:00"],"recorddate":"2023-01-21"}, {"id":1377,"name":"李四","record":["08:30","18:00"],"recorddate":"2023-01-22"}, {"id":1376,"name":"王五","record":["08:30","18:00"],"recorddate":"2023-01-05"}, {"id":1375,"name":"王五","record":["08:30","18:00"],"recorddate":"2023-01-06"}, {"id":1374,"name":"王五","record":["08:30","18:00"],"recorddate":"2023-01-09"}, {"id":1373,"name":"王五","record":["08:30","18:00"],"recorddate":"2023-01-11"}, {"id":1372,"name":"王五","record":["08:30","18:00"],"recorddate":"2023-01-10"}, {"id":1371,"name":"王五","record":["08:30","18:00"],"recorddate":"2023-01-04"}, {"id":1370,"name":"王五","record":["08:30","18:00"],"recorddate":"2023-01-03"}, {"id":1369,"name":"王五","record":["08:30","18:00"],"recorddate":"2023-01-02"} ]
4.7 添加日期列
引入刚才创建好的demo.json文件,导致模拟数据。变量tableData用于存储考勤数据,变量tableColumn用于存储表格列数据,在updateList()函数执行后,获取全部唯一日期数据,和全部用户数据,分别存储到tableData和tableColumn变量中。
由于这里是通过模拟数据实现的功能效果,后期在项目中,相应数据是通过接口查询获取而且的。
<script> import DemoData from './demo.json' export default { data(){ return { tableData: [], tableColumn: [ {prop: "name", name: "姓名", width: "80px"}, {prop: "workday", name: "实际到岗/天", width: "100px"}, ] } }, created() { this.updateList(); }, methods: { //加载列表数据 updateList(){ //获取日期列表 let dateColumn = [], employeeList = []; //循环获取唯一 列日期 和 唯一 员工信息 DemoData.map(item => { if(!dateColumn.includes(item.recorddate)){ dateColumn.push(item.recorddate); } if(!employeeList.includes(item.name)){ employeeList.push(item.name); } }); //升序排序 dateColumn.sort(); //修改列长度,以免name和workday固定列被替换 this.tableColumn.length = 2; //追加日期列 this.tableColumn = this.tableColumn.concat(dateColumn.map(item => { return { prop: item, name: item, width: "100px", isDate: true } })); } //end } } </script>
以上代码完成后,日期列则可以正常显示出来了,如下图:
4.8 重组行数据
通过变量employeeList存储的员工信息,遍历查询模拟数据中各自对应的考勤记录,并通过变量dateColumn中存储的日期作为键,存储到对应员工信息中。
<script> import DemoData from './demo.json' export default { data(){ return { tableData: [], tableColumn: [ {prop: "name", name: "姓名", width: "80px"}, {prop: "workday", name: "实际到岗/天", width: "100px"}, ] } }, created() { this.updateList(); }, methods: { //加载列表数据 updateList(){ //获取日期列表 let dateColumn = [], employeeList = []; //循环获取唯一 列日期 和 唯一 员工信息 DemoData.map(item => { if(!dateColumn.includes(item.recorddate)){ dateColumn.push(item.recorddate); } if(!employeeList.includes(item.name)){ employeeList.push(item.name); } }); //升序排序 dateColumn.sort(); //修改列长度,以免name和workday固定列被替换 this.tableColumn.length = 2; //追加列 this.tableColumn = this.tableColumn.concat(dateColumn.map(item => { return { prop: item, name: item, width: "100px", isDate: true } })); //重组员工考勤数据,一行显示考勤记录 this.tableData = employeeList.map(item => { let nData = {}, tmp = null, realWorkSize = 0; //获取对应员工的考勤记录 dateColumn.forEach(key => { tmp = DemoData.filter(sub => sub.name == item && sub.recorddate == key); if(tmp.length==1&&tmp[0]['record'].length==2){ //记录考勤数据 nData[key] = tmp[0]['record'][0] + '<br />' + tmp[0]['record'][1]; //保存对应考勤记录的ID nData['id_'+key] = tmp[0]['id']; //统计到岗天数 realWorkSize++; }else{ nData[key] = '-'; } }) return { name: item, workday: realWorkSize, ...nData } }); } //end } } </script>
此时模拟数据中考勤记录都正常显示在页面中了,如下图:
五、自定义右击菜单
此时我们可以来实现自定义右击菜单了,这里只简单讲解下实现方法和步骤,如有实际需求可对此功能进行封装处理,这里先用散装代码实现此功能。
5.1 准备工作
首先引入jquery,并且添加禁用右击菜单的鼠标事件,代码如下:
<script> import DemoData from './demo.json' import $ from 'jquery' //记录是否悬浮在指定单元格上 let isHoverCell = false; //记录悬浮位置考勤记录对象 let hoverData = null; //右击菜单Dom对象 let MenuBox = null; //记录上一个数据的ID,用于判断是否需要新打开一个窗口 let tmpPreId = 0; export default { data(){ return { tableData: [], tableColumn: [ {prop: "name", name: "姓名", width: "80px"}, {prop: "workday", name: "实际到岗/天", width: "100px"}, ] } }, created() { this.updateList(); //右击菜单事件 window.oncontextmenu = function(e){ if(isHoverCell){ //取消默认的浏览器自带右键 e.preventDefault(); } } //event end }, methods: { //... //end } } </script>
绑定oncontextmenu事件后,当鼠标悬浮在考勤记录上时,则变量isHoverCell值为true,此时右击禁用则会生效。
5.2 判断鼠标位置
在methods中新建cellAddEvent()函数,将其放到updateList()函数执行结束位置,延迟100秒再执行,以防数据未渲染完jquery无法查询到全部考勤记录的DOM节点。
另外,考勤数据会因增删改查等操作,重新渲染,所以每个单元格绑定事件,也会在重新渲染后被释放,需渲染完后重新绑定事件,故cellAddEvent()函数应放应updateList()函数内执行。
<script> import DemoData from './demo.json' import $ from 'jquery' //记录是否悬浮在指定单元格上 let isHoverCell = false; //记录悬浮位置考勤记录对象 let hoverData = null; //右击菜单Dom对象 let MenuBox = null; //记录上一个数据的ID,用于判断是否需要新打开一个窗口 let tmpPreId = 0; export default { data(){ return { tableData: [], tableColumn: [ {prop: "name", name: "姓名", width: "80px"}, {prop: "workday", name: "实际到岗/天", width: "100px"}, ] } }, created() { this.updateList(); //右击菜单事件 window.oncontextmenu = function(e){ if(isHoverCell){ //取消默认的浏览器自带右键 e.preventDefault(); } } //event end }, methods: { /** * 为考勤记录添加相应监听事件 */ cellAddEvent(){ let that = this, propData = ""; //悬浮事件 $('.date-cell').hover(function(){ isHoverCell = true; let id = $(this).data('id'), filterData = DemoData.filter(item => item.id == id); if(filterData.length==1){ hoverData = { prop: $(this).data('prop'), data: filterData[0] } } //if end }, function(){ isHoverCell = false; hoverData = null; }); }, //加载列表数据 updateList(){ //获取日期列表 let dateColumn = [], employeeList = []; //循环获取唯一 列日期 和 唯一 员工信息 DemoData.map(item => { if(!dateColumn.includes(item.recorddate)){ dateColumn.push(item.recorddate); } if(!employeeList.includes(item.name)){ employeeList.push(item.name); } }); //升序排序 dateColumn.sort(); //修改列长度,以免name和workday固定列被替换 this.tableColumn.length = 2; //追加列 this.tableColumn = this.tableColumn.concat(dateColumn.map(item => { return { prop: item, name: item, width: "100px", isDate: true } })); //重组员工考勤数据,一行显示考勤记录 this.tableData = employeeList.map(item => { let nData = {}, tmp = null, realWorkSize = 0; //获取对应员工的考勤记录 dateColumn.forEach(key => { tmp = DemoData.filter(sub => sub.name == item && sub.recorddate == key); if(tmp.length==1&&tmp[0]['record'].length==2){ //记录考勤数据 nData[key] = tmp[0]['record'][0] + '<br />' + tmp[0]['record'][1]; //保存对应考勤记录的ID nData['id_'+key] = tmp[0]['id']; //统计到岗天数 realWorkSize++; }else{ nData[key] = '-'; } }) return { name: item, workday: realWorkSize, ...nData } }); setTimeout(() => { this.cellAddEvent(); }, 100); } //end } } </script>
此时大家会发现,鼠标在页面其他位置可以正常右击显示默认菜单,但放到考勤记录上时,右击菜单则失效无法显示了。
5.3 添加自定义菜单
当变量isHoverCell为true时,右击执行rightMenuInitial()函数,通过jquery创建菜单容器后,追加到body中。此时右击则能看到删除按钮了。
<script> import DemoData from './demo.json' import $ from 'jquery' //记录是否悬浮在指定单元格上 let isHoverCell = false; //记录悬浮位置考勤记录对象 let hoverData = null; //右击菜单Dom对象 let MenuBox = null; //记录上一个数据的ID,用于判断是否需要新打开一个窗口 let tmpPreId = 0; export default { data(){ return { tableData: [], tableColumn: [ {prop: "name", name: "姓名", width: "80px"}, {prop: "workday", name: "实际到岗/天", width: "100px"}, ] } }, created() { this.updateList(); let that = this; //右击菜单事件 window.oncontextmenu = function(e){ if(isHoverCell){ //取消默认的浏览器自带右键 e.preventDefault(); that.rightMenuInitial(e); } } //event end }, methods: { /** * 右击初始化弹框事件 * @param e 右击事件 */ rightMenuInitial(e){ if(null==hoverData||'undefined'===typeof hoverData.data){ this.$message.info("数据读取失败,刷新后再试~"); return; } if(null!=MenuBox) { //上一个右击区域未变化,则结束后续操作 if(tmpPreId==hoverData.data.id) return; }; //记录当前ID tmpPreId = hoverData.data.id; //创建右击菜单容器 MenuBox = $('<div />').addClass('menu-box'); let that = this, listData = [ {id: 1, name: "删除", style: "delete"} ], ulBox = $('<ul />').addClass('list'); //添加列表 MenuBox.append(ulBox); //循环生成右击菜单列表 for(var i in listData){ ulBox.append($('<li />').text(listData[i]['name']).addClass(listData[i]['style'])); } //for end //添加位置 MenuBox.css({ 'position': 'absolute', 'left': e.clientX, 'top': e.clientY, 'z-index': 10 }); ulBox.hover(function(){ isHoverCell = true; }, function(){ isHoverCell = false; }); /** * 添加删除事件 */ ulBox.on('click', 'li.delete', function(){ that.$confirm('确定删除'+ hoverData.data.name + '的' + hoverData.prop+'考勤记录吗?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { //查询源数据对应位置,将其删除(这里Demo演示,真实环境需要将删除数据ID传给后台进行操作) let index = DemoData.indexOf(hoverData.data); DemoData.splice(index, 1); that.updateList(); }).catch(() => { that.$message({ type: 'info', message: '已取消删除' }); }); }); //追加到页面中 $('body').append(MenuBox); }, //... //end } } </script>
5.4 增加移除菜单功能
增加removeMenuBox()函数,用于在不同情况下,需要移除自定义菜单。代码如下:
<script> import DemoData from './demo.json' import $ from 'jquery' //记录是否悬浮在指定单元格上 let isHoverCell = false; //记录悬浮位置考勤记录对象 let hoverData = null; //右击菜单Dom对象 let MenuBox = null; //记录上一个数据的ID,用于判断是否需要新打开一个窗口 let tmpPreId = 0; export default { data(){ return { tableData: [], tableColumn: [ {prop: "name", name: "姓名", width: "80px"}, {prop: "workday", name: "实际到岗/天", width: "100px"}, ] } }, created() { this.updateList(); let that = this; //右击菜单事件 window.oncontextmenu = function(e){ if(isHoverCell){ //取消默认的浏览器自带右键 e.preventDefault(); that.rightMenuInitial(e); } } //event end }, methods: { /** * 删除右击菜单 * @date 2023/1/30 */ removeMenuBox(){ if(!MenuBox) return; MenuBox.remove(); MenuBox = null; }, /** * 右击初始化弹框事件 * @param e 右击事件 */ rightMenuInitial(e){ if(null==hoverData||'undefined'===typeof hoverData.data){ this.$message.info("数据读取失败,刷新后再试~"); return; } if(null!=MenuBox) { this.removeMenuBox(); //上一个右击区域未变化,则结束后续操作 if(tmpPreId==hoverData.data.id) return; }; //记录当前ID tmpPreId = hoverData.data.id; //创建右击菜单容器 MenuBox = $('<div />').addClass('menu-box'); let that = this, listData = [ {id: 1, name: "删除", style: "delete"} ], ulBox = $('<ul />').addClass('list'); //添加列表 MenuBox.append(ulBox); //循环生成右击菜单列表 for(var i in listData){ ulBox.append($('<li />').text(listData[i]['name']).addClass(listData[i]['style'])); } //for end //添加位置 MenuBox.css({ 'position': 'absolute', 'left': e.clientX, 'top': e.clientY, 'z-index': 10 }); //自定义菜单悬浮事件监听 ulBox.hover(function(){ isHoverCell = true; }, function(){ isHoverCell = false; }); /** * 添加删除事件 */ ulBox.on('click', 'li.delete', function(){ that.$confirm('确定删除'+ hoverData.data.name + '的' + hoverData.prop+'考勤记录吗?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { //查询源数据对应位置,将其删除(这里Demo演示,真实环境需要将删除数据ID传给后台进行操作) let index = DemoData.indexOf(hoverData.data); DemoData.splice(index, 1); that.removeMenuBox(); that.updateList(); }).catch(() => { that.removeMenuBox(); that.$message({ type: 'info', message: '已取消删除' }); }); }); //追加到页面中 $('body').append(MenuBox); }, /** * 为考勤记录添加相应监听事件 */ cellAddEvent(){ let that = this, propData = ""; //悬浮事件 $('.date-cell').hover(function(){ isHoverCell = true; let id = $(this).data('id'), filterData = DemoData.filter(item => item.id == id); if(filterData.length==1){ hoverData = { prop: $(this).data('prop'), data: filterData[0] } } // console.log('id', id);e //if end }, function(){ isHoverCell = false; }); //鼠标移出事件,关闭删除菜单 $(document).off('click').on('click', function(){ if(!isHoverCell&&null!=MenuBox){ that.removeMenuBox(); } }); }, //加载列表数据 updateList(){ //获取日期列表 let dateColumn = [], employeeList = []; //循环获取唯一 列日期 和 唯一 员工信息 DemoData.map(item => { if(!dateColumn.includes(item.recorddate)){ dateColumn.push(item.recorddate); } if(!employeeList.includes(item.name)){ employeeList.push(item.name); } }); //升序排序 dateColumn.sort(); //修改列长度,以免name和workday固定列被替换 this.tableColumn.length = 2; //追加列 this.tableColumn = this.tableColumn.concat(dateColumn.map(item => { return { prop: item, name: item, width: "100px", isDate: true } })); //重组员工考勤数据,一行显示考勤记录 this.tableData = employeeList.map(item => { let nData = {}, tmp = null, realWorkSize = 0; //获取对应员工的考勤记录 dateColumn.forEach(key => { tmp = DemoData.filter(sub => sub.name == item && sub.recorddate == key); if(tmp.length==1&&tmp[0]['record'].length==2){ //记录考勤数据 nData[key] = tmp[0]['record'][0] + '<br />' + tmp[0]['record'][1]; //保存对应考勤记录的ID nData['id_'+key] = tmp[0]['id']; //统计到岗天数 realWorkSize++; }else{ nData[key] = '-'; } }) return { name: item, workday: realWorkSize, ...nData } }); setTimeout(() => { this.cellAddEvent(); }, 100); } //end } } </script>
5.5 追加菜单内容
在自定义右击菜单时,已预留了增加更多菜单项位置,如下代码,在变量listData中添加菜单内容即可。
rightMenuInitial(e){ if(null==hoverData||'undefined'===typeof hoverData.data){ this.$message.info("数据读取失败,刷新后再试~"); return; } if(null!=MenuBox) { this.removeMenuBox(); //上一个右击区域未变化,则结束后续操作 if(tmpPreId==hoverData.data.id) return; }; //记录当前ID tmpPreId = hoverData.data.id; //创建右击菜单容器 MenuBox = $('<div />').addClass('menu-box'); let that = this, listData = [ {id: 1, name: "编辑", style: "edit"}, {id: 2, name: "删除", style: "delete"} ], ulBox = $('<ul />').addClass('list'); //... }
如下图,右击时自定义菜单中则出现“编辑”项了,这块功能这里就不作详解,大家可以自行操作。这里就简单讲解下自定义右击菜单和删除功能,仅供大家参考,如有不足之处请见谅!
加载全部内容