vue3 element-plus 二次封装Drawer抽屉,关闭时添加二次对话,开箱即用

背景

在生产中我们经常会遇到一些基于UI库二次封装的场景,我认为二次封装对于老手来说没有什么难点,只不过是业务上的变化,但是对于新手或者其他框架的开发者,不免有些抓耳挠腮,我呢又恰巧有机会和时间,就留一些文章在这里供有需要的人互相参考和大家一起讨论。

需求描述

如上图所示,表格,抽屉,确认框,在用户意外关闭的时候进行提示,正常提交的时候不需要提示

实现步骤

第一,我们要整理关键线索,从element-plus文档中可以看到before-close 点击mask关闭时会触发,@close 弹框关闭时会触发,有了以上线索我们就可以进行二次封装了,那么为了实用方便,我们要尽量把二次封装做的像普通drawer使用。

下面跟着我的思路来实现一下

新建Drawer.vue文件

js 复制代码
<script lang="ts" setup>
import { ElMessageBox } from 'element-plus'
// 是否显示弹框
const showModal = ref(false)

const props = defineProps({
  // 控制是否提示
  closeConfirm: propTypes.bool.def(false)
})

const emit = defineEmits(['update:modelValue'])
// 封装确认弹框
const showConfirm = async () => {
  if (!props.closeConfirm) return true
  try {
    await ElMessageBox.confirm('确定要关闭吗?', '提示', {
      confirmButtonText: '确定',
      cancelButtonText: '取消',
      type: 'warning'
    })
    return true
  } catch {
    return false
  }
}

/** 是否是关闭拦截 */
const isBeforeClose = ref(false)
// 关闭拦截
const handleBeforeClose = async (done: () => void) => {
  const shouldClose = await showConfirm()
  if (shouldClose) {
    // 设置拦截标志
    isBeforeClose.value = true
    // 放开拦截
    done()
    // 关闭弹窗
    handleShowModal(false)
  }
}
// 点击Icon 关闭
const handleClose = async () => {
  const shouldClose = await showConfirm()
  if (shouldClose) {
    // 设置拦截标志
    isBeforeClose.value = true
    // 关闭弹窗
    handleShowModal(false)
  }
}

// 监听 modelValue 的变化
watch(
  () => props.modelValue,
  async (newVal) => {
    if (newVal) {
      // 打开表格
      handleShowModal(true)
      return
    }
    // 当外部绑定变量设置关闭,并且拦截标志为false,则通过监听拦截
    if (!newVal && !isBeforeClose.value) {
      isBeforeClose.value = true
      // 等待弹框验证
      const shouldClose = await showConfirm()
      // 验证为确定,模态框关闭
      handleShowModal(!shouldClose)
    }
    // 每次状态变化都还原拦截默认值
    isBeforeClose.value = false
  }
)
// 打开弹窗方法,同步外部响应式变量
const handleShowModal = (value: boolean) => {
  // 打开表格
  showModal.value = value
  // 同步外部变量
  emit('update:modelValue', value)
}
</script>

<template>
  <ElDrawer
    :model-value="showModal"
    :close-on-click-modal="true"
    destroy-on-close
    lock-scroll
    :show-close="false"
    :before-close="handleBeforeClose"
  >
    <template #header>
      <div class="relative h-54px flex items-center justify-between pl-15px pr-15px">
        <slot name="title">
          {{ title }}
        </slot>
        <div
          class="absolute right-15px top-[50%] h-54px flex translate-y-[-50%] items-center justify-between"
        >
          <Icon
            class="is-hover cursor-pointer"
            icon="ep:close"
            hover-color="var(--el-color-primary)"
            color="var(--el-color-info)"
            @click="handleClose"
          />
        </div>
      </div>
    </template>

    <ElScrollbar v-if="scroll" :style="dialogStyle">
      <slot></slot>
    </ElScrollbar>
    <slot v-else></slot>

    <template v-if="slots.footer" #footer>
      <slot name="footer"></slot>
    </template>
  </ElDrawer>
</template>

上面就完成了二次封装的组件,下面去使用

在使用之前,创建一个hooks组件来应付一般场景,创建useDrawer.ts文件

js 复制代码
// 抽屉控制变量显示
export const useDrawerFlag = (title: string = "") => {
    // 弹框标题
    const dialogTitle = ref(title)
    // 抽屉显示控制
    const dialogVisible = ref(false)
    // 关闭时是否检查确认框
    const closeConfirm = ref(true)
    // 确认抽屉关闭并取消提示
    const ConfirmDrawerVisible = () =>{
        closeConfirm.value = false
        dialogVisible.value = false
        // 异步还原弹框检测默认值
        setTimeout(()=>{
            closeConfirm.value = true
        },500)
    }

    return {
        dialogVisible,
        dialogTitle,
        closeConfirm,
        ConfirmDrawerVisible
    }
}

有了这个文件,我们就可以统一使用二次封装好的组件,和必要的参数方法,为什么封装了ConfirmDrawerVisible方法?

qaq: 因为我们的drawer里面可能会放表单,一般表单会需要验证,通过后直接关闭,不需要二次确认,所以有了这个方法的封装,那接下来看下使用的代码

使用代码

js 复制代码
<template>
  <Drawer :title="dialogTitle" v-model="dialogVisible" :closeConfirm="closeConfirm">
    <el-form
      ref="formRef"
      :model="formData"
      :rules="formRules"
      label-width="100px"
      v-loading="formLoading"
    >
      <el-form-item label="父分类id" prop="parentId">
        <el-tree-select
          v-model="formData.parentId"
          :data="categoryTree"
          :props="defaultProps"
          check-strictly
          default-expand-all
          placeholder="请选择父分类id"
        />
      </el-form-item>
    </el-form>
    <template #footer>
      <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
      <el-button @click="dialogVisible = false">取 消</el-button>
    </template>
  </Drawer>
</template>
<script setup lang="ts">
import { useDrawerFlag } from '@/hooks/web/useDrawer'

// 使用hooks封装
const { closeConfirm, dialogVisible, dialogTitle, ConfirmDrawerVisible } = useDrawerFlag()

/** IOT产品分类 表单 */
defineOptions({ name: 'CategoryForm' })

const { t } = useI18n() // 国际化
const message = useMessage() // 消息弹窗

const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
const formType = ref('') // 表单的类型:create - 新增;update - 修改
const formData = ref({
  id: undefined,
  parentId: undefined,
  name: undefined,
  sort: undefined,
  status: undefined,
  imgUrl: undefined
  // isSys: undefined
})
const formRules = reactive({
  parentId: [{ required: true, message: '父分类id不能为空', trigger: 'blur' }],
  name: [{ required: true, message: '分类名称不能为空', trigger: 'blur' }],
  status: [{ required: true, message: '开启状态不能为空', trigger: 'blur' }]
  // isSys: [{ required: true, message: '是否系统通用不能为空', trigger: 'blur' }]
})

const formRef = ref() // 表单 Ref
const categoryTree = ref() // 树形结构

/** 打开弹窗 */
const open = async (type: string, id?: number) => {
  dialogVisible.value = true
  dialogTitle.value = t('action.' + type)
  formType.value = type
  resetForm()
  // 修改时,设置数据
  if (id) {
    formLoading.value = true
    try {
      formData.value = await CategoryApi.getCategory(id)
    } finally {
      formLoading.value = false
    }
  }
  await getCategoryTree()
}
defineExpose({ open }) // 提供 open 方法,用于打开弹窗

/** 提交表单 */
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
const submitForm = async () => {
  // 校验表单
  await formRef.value.validate()
  // 提交请求
  formLoading.value = true
  try {
    const data = formData.value as unknown as CategoryVO
    if (formType.value === 'create') {
      await CategoryApi.createCategory(data)
      message.success(t('common.createSuccess'))
    } else {
      await CategoryApi.updateCategory(data)
      message.success(t('common.updateSuccess'))
    }
    // 关闭弹框
    ConfirmDrawerVisible()
    // 发送操作成功的事件
    emit('success')
  } finally {
    formLoading.value = false
  }
}

/** 重置表单 */
const resetForm = () => {
  formData.value = {
    id: undefined,
    parentId: undefined,
    name: undefined,
    sort: undefined,
    status: undefined,
    imgUrl: undefined
    // isSys: undefined
  }
  formRef.value?.resetFields()
}
</script>

总结

看这样就可以到处去用了,虽然是一个小功能,但是里面也需要花时间协调逻辑,如果你觉得有用,请三连~~

相关推荐
Yolo@~2 小时前
个人网站:基于html、css、js网页开发界面
javascript·css·html
斯~内克2 小时前
Electron 菜单系统深度解析:从基础到高级实践
前端·javascript·electron
数据知道2 小时前
【YAML】一文掌握 YAML 的详细用法(YAML 备忘速查)
前端·yaml
清风絮柳2 小时前
51. “闲转易”交易平台小程序(基于springboot&vue)
vue.js·spring boot·小程序·毕业设计·校园二手交易平台·二手交易小程序·闲转易交易系统
dr李四维2 小时前
vue生命周期、钩子以及跨域问题简介
前端·javascript·vue.js·websocket·跨域问题·vue生命周期·钩子函数
旭久2 小时前
react+antd中做一个外部按钮新增 表格内部本地新增一条数据并且支持编辑删除(无难度上手)
前端·javascript·react.js
windyrain2 小时前
ant design pro 模版简化工具
前端·react.js·ant design
浪遏2 小时前
我的远程实习(六) | 一个demo讲清Auth.js国外平台登录鉴权👈|nextjs
前端·面试·next.js
GISer_Jing3 小时前
React-Markdown详解
前端·react.js·前端框架
太阳花ˉ3 小时前
React(九)React Hooks
前端·react.js