element-plus $confirm 弹框中实现enter不触发确认按钮

文章介绍了如何在Element-Plus的confirm弹框中防止用户误操作,特别是当用户误触回车键时。作者提供了两种解决方案,一是通过beforeClose回调处理enter键,二是创建全局自定义插件或使用全局mixin以避免在每个$confirm中重复添加逻辑。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

背景:放置用户误操作,对删除按钮的confirm弹框中,误触回车键,导致删除。
条件:element-plus组件

先来一段视频:

废话不多说:代码如下

 this.$confirm(tip, this.$t('public.delete'), {
    confirmButtonText: this.$t('public.delete'),
    cancelButtonText: this.$t('public.cancel'),
       type: 'warning',
       closeOnClickModal: false,
       confirmButtonClass: 'confirm-btn',
       beforeClose: (action, instance, done) => {
       // 确认按钮按下时
         if (action === 'confirm') {
            const confirmButton = document.querySelector('.confirm-btn')
            // 监听确认按钮的点击事件
            const onClickHandler = event => {
            event = event || window.event
            // enter键入detail为0,不进入done,阻止关闭;点击确认detail为1,进入done,进入then
               if (event.detail !== 0) {
                  done()
               }
            }
            // 先移除之前可能存在的点击事件处理函数
            confirmButton.removeEventListener('click', onClickHandler)
             // 再为确认按钮添加新的点击事件处理函数
            confirmButton.addEventListener('click', onClickHandler)
             onClickHandler()
           } else {
             // 其他情况,相当于取消,进入catch
             done()
            }
           }
     }).then(() => {
         this.deleteEmailTemplate(res.id)
     }).catch(() => {
        this.$message({
           type: 'info',
           message: this.$t('public.canceledDelete')
         })
     })
}

官网:MessageBox 消息弹框 | Element Plus

在beforeClose处赋值一个回调,有三个参数action操作类型 instance实例 done,调用done则关闭否则反之

然后用confirmButtonClass定义类名,查询到当前dom节点,并且监听按钮的点击事件,通过event.detail的值可以判断出是手动点击确认还是按下enter触发的,当然如果存在input select等可输入类型的表单按下回车键,可以手动在这边过滤一下;随后移除上一次关闭弹框之后的监听,随后为按钮新增监听,手动触发一次即可。
拓展:全局$confirm如何都实现呢?总不能每个$confirm都去加一遍吧。
vue2小伙伴可以使用Vue.prototype.$confirm 绑定到Vue实例

vue3小伙伴呢可以使用app.config.globalProperties全局挂载属性利用插件把confirm封装‘enter不触发确认操作’的逻辑

// confirm-plugin.js 自定义插件
// custom-confirm-plugin.js
import { ElMessageBox } from 'element-plus'

const ConfirmPlugin = {
  install(app) {
    // 添加全局方法 $confirm
    app.config.globalProperties.$confirm = function (message, title, userOptions = {}) {
      // 合并默认配置和用户自定义选项
      const defaultOptions = {
        confirmButtonClass: 'confirm-btn',
        beforeClose: (action, instance, originalDone) => {
          if (action === 'confirm') {
            const confirmButton = document.querySelector('.confirm-btn')
            const onClickHandler = event => {
              event = event || window.event

              const isInputLikeElement = target => {
                const inputTypes = ['textarea', 'input', 'select']

                return inputTypes.includes(target.tagName.toLowerCase())
              }
              if (event.detail !== 0 && !isInputLikeElement(event.target)) {
                originalDone() // 点击确认或非回车键时正常关闭
              }
            }

            confirmButton.removeEventListener('click', onClickHandler)
            confirmButton.addEventListener('click', onClickHandler)

            onClickHandler()
          } else {
            originalDone()
          }
        }
      }

      const finalOptions = { ...defaultOptions, ...userOptions }

      // 返回原生的$confirm方法调用,但使用的是合并后的选项
      return ElMessageBox.confirm(message, title, finalOptions)
    }
  }
}

export default ConfirmPlugin

// main.js 注册这个插件

import { createApp} from 'vue'
import App from './App'
import ConfirmPlugin from '@/mixin/confirmPlugin'

...
const app = createApp(App)
app.use(ConfirmPlugin)
...

//  普通页面正常使用$confirm

 this.$confirm(tip, this.$t('public.delete'), {
    confirmButtonText: this.$t('public.delete'),
    cancelButtonText: this.$t('public.cancel'),
       type: 'warning',
       closeOnClickModal: false,
     }).then(() => {
         this.deleteEmailTemplate(res.id)
     }).catch(() => {
        this.$message({
           type: 'info',
           message: this.$t('public.canceledDelete')
         })
     })
}

不过vue3不推荐使用gobalProperties,尤其是组合式api不会被所有组件自动注入,所以推荐project/inject 方式将方法注入,然后创建全局mixin再引入上面透传的方法,随后进行全局绑定$confirm
话不多说:

// confirmService.js

// confirmService.js
import { ElMessageBox } from 'element-plus'

const ConfirmService = {
  confirm(message, title, userOptions = {}) {
    const defaultOptions = {
      confirmButtonClass: 'confirm-btn',
      beforeClose: (action, instance, originalDone) => {
        if (action === 'confirm') {
          const confirmButton = document.querySelector(`.${defaultOptions.confirmButtonClass}`)
          const onClickHandler = event => {
            event = event || window.event
            const isInputLikeElement = target => {
              const inputTypes = ['textarea', 'input', 'select']
              return inputTypes.includes(target.tagName.toLowerCase())
            }
            if (event.detail !== 0 && !isInputLikeElement(event.target)) {
              originalDone() // 点击确认或非回车键时正常关闭
            }
          }
          confirmButton.removeEventListener('click', onClickHandler)
          confirmButton.addEventListener('click', onClickHandler)
          onClickHandler()
        } else {
          originalDone()
        }
      }
    }

    const finalOptions = { ...defaultOptions, ...userOptions }

    return ElMessageBox.confirm(message, title, finalOptions)
  }
}

export default ConfirmService

// mixin.js

// globalConfirmMixin.js
import { inject } from 'vue'

const globalConfirmMixin = {
  data() {
    return {
      confirmService: null
    }
  },
  mounted() {
    this.confirmService = inject('confirmService')
  },
  methods: {
    $confirm(message, title, options = {}) {
      return this.confirmService.confirm(message, title, options)
    }
  }
}

export default globalConfirmMixin

// main.js

import { createApp, provide } from 'vue'
import ConfirmService from '@/mixin/confirmService'
import GlobalConfirmMixin from '@/mixin/globalConfirmMixin'

app.provide('confirmService', ConfirmService)
app.mixin(GlobalConfirmMixin)

 /  普通页面正常使用$confirm

 this.$confirm(tip, this.$t('public.delete'), {
    confirmButtonText: this.$t('public.delete'),
    cancelButtonText: this.$t('public.cancel'),
       type: 'warning',
       closeOnClickModal: false,
     }).then(() => {
         this.deleteEmailTemplate(res.id)
     }).catch(() => {
        this.$message({
           type: 'info',
           message: this.$t('public.canceledDelete')
         })
     })
}

 大功告成!

<script setup lang="ts"> import { ref, reactive, onMounted } from 'vue' import { ElMessage, ElMessageBox, type FormInstance } from 'element-plus' import { queryPageApi, addApi, queryInfoApi, updateApi, deleteApi } from '@/api/clazz' // 导入API方法 // 类型定义 interface Clazz { id?: number name: string room: string beginDate: string endDate: string masterId: number | null subject: number masterName?: string status?: string } // 表单引用 const formRef = ref<FormInstance>() // 响应式数据 const loading = ref(false) const dialogVisible = ref(false) const dialogTitle = ref('新增班级') const currentPage = ref(1) const pageSize = ref(10) const total = ref(0) // 查询条件 const queryParams = reactive({ begin: '', end: '', name: '', }) // 班级表单数据 const clazzForm = reactive<Clazz>({ name: '', room: '', beginDate: '', endDate: '', masterId: null, subject: 1, }) // 班级列表 const clazzList = ref<Clazz[]>([]) // 学科选项 const subjectOptions = [ { label: 'Java', value: 1 }, { label: '前端', value: 2 }, { label: '大数据', value: 3 }, { label: 'Python', value: 4 }, { label: 'Go', value: 5 }, { label: '嵌入式', value: 6 }, ] // 加载班级数据 const loadClazzData = async () => { loading.value = true try { const res = await queryPageApi( queryParams.begin, queryParams.end, queryParams.name, currentPage.value, pageSize.value, ) if (res.code === 1) { clazzList.value = res.data.rows total.value = res.data.total } else { ElMessage.error(res.msg || '数据加载失败') } } catch (error) { console.error('加载数据出错:', error) ElMessage.error('网络请求异常') } finally { loading.value = false } } // 处理查询 const handleQuery = () => { currentPage.value = 1 loadClazzData() } // 重置查询 const resetQuery = () => { queryParams.begin = '' queryParams.end = '' queryParams.name = '' handleQuery() } // 打开新增对话 const openAddDialog = () => { dialogTitle.value = '新增班级' Object.assign(clazzForm, { id: undefined, name: '', room: '', beginDate: '', endDate: '', masterId: null, subject: 1, }) dialogVisible.value = true } // 打开编辑对话 const openEditDialog = async (id: number) => { dialogTitle.value = '编辑班级' try { const res = await queryInfoApi(id) if (res.code === 1) { Object.assign(clazzForm, res.data) dialogVisible.value = true } else { ElMessage.error('获取班级信息失败') } } catch (error) { console.error('获取班级信息出错:', error) ElMessage.error('网络请求异常') } } // 提交表单 const submitForm = async () => { if (!formRef.value) return try { await formRef.value.validate() if (clazzForm.id) { // 更新 const res = await updateApi(clazzForm) if (res.code === 1) { ElMessage.success('更新成功') dialogVisible.value = false loadClazzData() } else { ElMessage.error(res.msg || '更新失败') } } else { // 新增 const res = await addApi(clazzForm) if (res.code === 1) { ElMessage.success('新增成功') dialogVisible.value = false loadClazzData() } else { ElMessage.error(res.msg || '新增失败') } } } catch (error) { console.error('表单验证失败:', error) } } // 删除班级 const handleDelete = (id: number[]) => { ElMessageBox.confirm('确定要删除该班级吗?', '警告', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning', }) .then(async () => { const res = await deleteApi(id) if (res.code === 1) { ElMessage.success('删除成功') loadClazzData() } else { ElMessage.error(res.msg || '删除失败') } }) .catch(() => {}) } // 分页变化处理 const handleSizeChange = (val: number) => { pageSize.value = val loadClazzData() } const handleCurrentChange = (val: number) => { currentPage.value = val loadClazzData() } // 初始化加载数据 onMounted(() => { loadClazzData() }) </script> <template> <div class="app-container"> <!-- 查询表单 --> <el-form :model="queryParams" inline> <el-form-item label="班级名称"> <el-input v-model="queryParams.name" placeholder="请输入班级名称" clearable /> </el-form-item> <el-form-item label="开课时间"> <el-date-picker v-model="queryParams.begin" type="date" placeholder="选择开始日期" value-format="YYYY-MM-DD" /> </el-form-item> <el-form-item label="结课时间"> <el-date-picker v-model="queryParams.end" type="date" placeholder="选择结束日期" value-format="YYYY-MM-DD" /> </el-form-item> <el-form-item> <el-button type="primary" @click="handleQuery">查询</el-button> <el-button @click="resetQuery">重置</el-button> </el-form-item> </el-form> <!-- 操作按钮 --> <div class="mb-4"> <el-button type="primary" @click="openAddDialog">新增班级</el-button> </div> <!-- 班级表格 --> <el-table :data="clazzList" v-loading="loading" border> <el-table-column type="index" label="序号" width="80" align="center" /> <el-table-column prop="name" label="班级名称" min-width="150" /> <el-table-column prop="room" label="教室" width="120" align="center" /> <el-table-column prop="beginDate" label="开课时间" width="120" align="center" /> <el-table-column prop="endDate" label="结课时间" width="120" align="center" /> <el-table-column prop="masterName" label="班主任" width="120" align="center" /> <el-table-column label="学科" width="100" align="center"> <template #default="{ row }"> {{ subjectOptions.find((item) => item.value === row.subject)?.label || '未知' }} </template> </el-table-column> <el-table-column prop="status" label="状态" width="100" align="center" /> <el-table-column label="操作" width="150" align="center" fixed="right"> <template #default="{ row }"> <el-button type="primary" size="small" @click="openEditDialog(row.id)">编辑</el-button> <el-button type="danger" size="small" @click="handleDelete(row.id)">删除</el-button> </template> </el-table-column> </el-table> <!-- 分页组件 --> <div class="mt-4 flex justify-end"> <el-pagination v-model:current-page="currentPage" v-model:page-size="pageSize" :page-sizes="[5, 10, 20, 50]" layout="total, sizes, prev, pager, next, jumper" :total="total" @size-change="handleSizeChange" @current-change="handleCurrentChange" /> </div> <!-- 新增/编辑对话 --> <el-dialog v-model="dialogVisible" :title="dialogTitle" width="600px" @closed="formRef?.resetFields()" > <el-form ref="formRef" :model="clazzForm" label-width="100px" :rules="{ name: [{ required: true, message: '请输入班级名称', trigger: 'blur' }], room: [{ required: true, message: '请输入教室', trigger: 'blur' }], beginDate: [{ required: true, message: '请选择开课时间', trigger: 'change' }], endDate: [{ required: true, message: '请选择结课时间', trigger: 'change' }], subject: [{ required: true, message: '请选择学科', trigger: 'change' }], }" > <el-form-item label="班级名称" prop="name"> <el-input v-model="clazzForm.name" placeholder="请输入班级名称" /> </el-form-item> <el-form-item label="教室" prop="room"> <el-input v-model="clazzForm.room" placeholder="请输入教室" /> </el-form-item> <el-form-item label="开课时间" prop="beginDate"> <el-date-picker v-model="clazzForm.beginDate" type="date" placeholder="选择开课日期" value-format="YYYY-MM-DD" style="width: 100%" /> </el-form-item> <el-form-item label="结课时间" prop="endDate"> <el-date-picker v-model="clazzForm.endDate" type="date" placeholder="选择结课日期" value-format="YYYY-MM-DD" style="width: 100%" /> </el-form-item> <el-form-item label="学科" prop="subject"> <el-select v-model="clazzForm.subject" placeholder="请选择学科" style="width: 100%"> <el-option v-for="item in subjectOptions" :key="item.value" :label="item.label" :value="item.value" /> </el-select> </el-form-item> <el-form-item label="班主任ID"> <el-input v-model.number="clazzForm.masterId" type="number" placeholder="请输入班主任ID" /> </el-form-item> </el-form> <template #footer> <el-button @click="dialogVisible = false">取消</el-button> <el-button type="primary" @click="submitForm">确定</el-button> </template> </el-dialog> </div> </template> <style scoped> .app-container { padding: 20px; } .mb-4 { margin-bottom: 16px; } .mt-4 { margin-top: 16px; } </style> import request from ‘@/utils/request’ import type { ResultModel } from ‘./model/model’ // 分页条件查询 export const queryPageApi = ( begin: string, end: string, name: string, page: number, pageSize: number, ) => request.get<any, ResultModel>( /clazzs?begin=${begin}&end=${end}&name=${name}&page=${page}&pageSize=${pageSize}, ) // 新增班级 export const addApi = (clazz: any) => request.post<any, ResultModel>(‘/clazzs’, clazz) // 根据ID查询班级 export const queryInfoApi = (id: number) => request.get<any, ResultModel>(/clazzs/${id}) // 更新班级 export const updateApi = (clazz: any) => request.put<any, ResultModel>(/clazzs, clazz) // 批量删除班级 export const deleteApi = (ids: number[]) => request.delete<any, ResultModel>('/clazzs', { params: { ids: ids.join(',') } }) // 查询全部班级信息 export const queryAllApi = () => request.get<any, ResultModel>(‘/clazzs/list’) 根据api优化一下项目优化一下班级表格, 美化一下页面效果,增加批量删除功能
最新发布
07-21
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值