关于vue3+element plus,el-dialog里表单重置问题

最近项目一直用vue3+element plus,发现el-dialog里form表单 el-form使用 resetFields() 并没有想象中会重置弹窗的表单项和表单验证状态。经过好长一段时间之后,突然明白了为什么。

首先,父组件引用子组件,子组件为弹窗el-dialog 下面是父组件示例

<template>
  <div class="menu-management-container">
    <vab-card shadow="hover">
      <vab-query-form>
        <vab-query-form-left-panel :span="24">
          <el-form :inline="true" :model="state.form">
            <el-space wrap>
              <el-form-item>
                <el-button type="primary" @click="handleEdit()">新增</el-button>
              </el-form-item>
            </el-space>
          </el-form>
        </vab-query-form-left-panel>
      </vab-query-form>
      <el-table
        v-loading="state.listLoading"
        border
        :data="state.dataList"
        row-key="id"
      >
        <el-table-column
          align="center"
          label="标题"
          prop="title"
          show-overflow-tooltip
        />
        <el-table-column
          align="center"
          label="排序"
          prop="order"
          show-overflow-tooltip
        />
        <el-table-column
          align="center"
          class-name="table-actions"
          fixed="right"
          label="操作"
        >
          <template #default="{ row }">
            <el-button
              size="small"
              text
              type="primary"
              @click="handleEdit(row)"
            >
              编辑
            </el-button>
          </template>
        </el-table-column>
        <template #empty>
          <el-empty class="vab-data-empty" description="暂无数据" />
        </template>
      </el-table>
    </vab-card>
    <el-pagination
      v-model:currentPage="pagination.page"
      background
      :layout="pagination.layout"
      :page-size="pagination.pageSize"
      :total="pagination.total"
      @current-change="pagination.handleCurrentChange"
      @size-change="pagination.handleSizeChange"
    />
    <CategoryEdit ref="CategoryEditRef" @fetch-data="fetchData" />
  </div>
</template>

<script setup>
  import { getMaterialCategoryList } from '@/api/videoThemeManagement'
  // import { Search, Edit } from '@element-plus/icons-vue'
  // const $baseConfirm = inject('$baseConfirm')
  // const $baseMessage = inject('$baseMessage')
  const CategoryEdit = defineAsyncComponent(() =>
    import('./components/categoryEdit.vue')
  )
  const CategoryEditRef = ref()
  const state = reactive({
    dataList: [],
    listLoading: false,
  })

  const fetchData = async () => {
    state.listLoading = true
    const {
      code,
      data: { data, total },
    } = await getMaterialCategoryList({
      page: pagination.page,
      per_page: pagination.pageSize,
    })
    if (code == 200) {
      state.dataList = data
      pagination.total = total
    }
    state.listLoading = false
  }

  const handleEdit = (row) => {
    CategoryEditRef.value.showEdit(row)
  }
  //分页模块
  const pagination = reactive({
    page: 1,
    pageSize: 10,
    layout: 'total, sizes, prev, pager, next, jumper',
    total: 0,
    //改变每页显示条数
    handleSizeChange: (val) => {
      pagination.pageSize = val
      pagination.page = 1
      fetchData()
    },
    //改变当前页
    handleCurrentChange: (val) => {
      pagination.page = val
      fetchData()
    },
  })
  // watch(
  //   () => pagination.page,
  //   (val) => {
  //     console.log('pagination.page', val)
  //   },
  //   { deep: true, immediate: true }
  // )
  onMounted(() => {
    fetchData()
  })
</script>

<style lang="scss" scoped></style>

下面是子组件示例:

<template>
  <el-dialog
    v-model="state.dialogFormVisible"
    :close-on-click-modal="false"
    :title="state.title"
    width="60%"
    @close="close"
  >
    <el-form
      ref="formRef"
      label-width="140px"
      :model="state.form"
      :rules="state.rules"
    >
      <el-form-item label="标题" prop="title">
        <el-input v-model="state.form.title" placeholder="请输入标题" />
      </el-form-item>
      <el-form-item label="排序" prop="order">
        <el-input-number v-model="state.form.order" :min="0" />
      </el-form-item>
    </el-form>
    <template #footer>
      <el-button @click="close">取 消</el-button>
      <el-button :loading="state.loading" type="primary" @click="save">
        确 定
      </el-button>
    </template>
  </el-dialog>
</template>

<script setup>
  import {
    addMaterialCategory,
    editMaterialCategory,
  } from '@/api/videoThemeManagement'
  const $baseMessage = inject('$baseMessage')
  const emit = defineEmits(['fetch-data'])
  const formRef = ref()
  const state = reactive({
    loading: false,
    form: {
      id: '',
      title: '',
      order: 0,
    },
    rules: {
      title: [
        { required: true, message: '请输入标题', trigger: 'blur' },
        { min: 1, max: 50, message: '长度在 1 到 50 个字符', trigger: 'blur' },
      ],
    },
    title: '新增素材',
    dialogFormVisible: false,
  })

  const showEdit = async (row) => {
    console.log('row', row)
    if (row) {
      state.isEdit = true
      state.title = '编辑素材'
      nextTick(() => {
        state.form = Object.assign(state.form, row)
      })
    } else {
      state.isEdit = false
      state.title = '新增素材'
    }
    state.dialogFormVisible = true
  }
  const close = () => {
    formRef.value.resetFields()
    state.dialogFormVisible = false
  }

  const save = () => {
    formRef.value.validate(async (valid) => {
      if (valid) {
        if (state.isEdit) {
          state.loading = true
          const { data } = await editMaterialCategory(state.form)
          state.loading = false
          $baseMessage(data, 'success', 'vab-hey-message-success')
          emit('fetch-data')
          close()
        } else {
          state.loading = true
          const { data } = await addMaterialCategory(state.form)
          state.loading = false
          $baseMessage(data, 'success', 'vab-hey-message-success')
          emit('fetch-data')
          close()
        }
      }
    })
  }
  // watch(
  //   () => pagination.page,
  //   (val) => {
  //     console.log('pagination.page', val)
  //   },
  //   { deep: true, immediate: true }
  // )
  onMounted(() => {})
  defineExpose({ showEdit })
</script>

<style lang="scss" scoped></style>

如果我们的子组件里的showEdit 是这样子写的话

const showEdit = async (row) => {
    console.log('row', row)
    if (row) {
      state.isEdit = true
      state.title = '编辑素材'
      state.form = Object.assign(state.form, row)
    } else {
      state.isEdit = false
      state.title = '新增素材'
    }
    state.dialogFormVisible = true
  }

我们会发现,表单项初始值会是你第一次打开时的数据

这样的原因是因为,我的代码是写在setup语法糖里,并且未执行showEdit时,el-dialog里的实例都为生成,el-form自然没有生成,所以模板确实给el-form绑定了state.form,然而el-form实例未挂载,也就没有初始值。当执行完showEdit后,state.dialogFormVisible = true,此时state.form已经被改变,el-dialog显示,el-form实例挂载,el-form初始值就变成了已经被改变的state.form的值。

知道了原因 那么我们可以用最简单的方式解决

使用nextTick。确保组件实例渲染完成之后,再进行表单项数据的改变。

const showEdit = async (row) => {
    console.log('row', row)
    if (row) {
      state.isEdit = true
      state.title = '编辑素材'
      nextTick(() => {
        state.form = Object.assign(state.form, row)
      })
    } else {
      state.isEdit = false
      state.title = '新增素材'
    }
    state.dialogFormVisible = true
  }

又或者

const showEdit = async (row) => {
    console.log('row', row)
    state.dialogFormVisible = true
    if (row) {
      state.isEdit = true
      state.title = '编辑素材'
      state.form = Object.assign(state.form, row)
    } else {
      state.isEdit = false
      state.title = '新增素材'
    }
  }

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
好的,下面为您提供一份实现el-dialog弹框拖拽的代码,包含详细注释,并且可以作为公共方法供其他组件调用。 首先,我们先创建一个mixin,命名为`dialogDragMixin.ts`,在该文件中实现弹框拖拽的功能。 ```typescript import { DirectiveBinding } from 'vue'; import { ElDialog } from 'element-plus'; // 定义接口,用于存储弹框的位置信息 interface DialogPosition { top: number; left: number; } // 定义一个变量,用于存储弹框的位置信息 let dialogPosition: DialogPosition = { top: 0, left: 0 }; export default { // 指令钩子函数,当元素插入到 DOM 中时执行 mounted(el: HTMLElement, binding: DirectiveBinding) { // 获取el-dialog组件实例对象 const dialog: ElDialog = binding.instance.$parent as ElDialog; // 获取弹框的标题栏元素 const dialogHeaderEl: HTMLElement = el.querySelector('.el-dialog__header')!; // 设置标题栏的样式,使其可以被拖拽 dialogHeaderEl.style.cursor = 'move'; dialogHeaderEl.style.userSelect = 'none'; // 鼠标按下事件处理函数 const handleMouseDown = (event: MouseEvent) => { // 记录鼠标按下时的位置信息 dialogPosition.top = dialog.$el.offsetTop; dialogPosition.left = dialog.$el.offsetLeft; // 记录鼠标按下时的坐标信息 const mouseX = event.clientX; const mouseY = event.clientY; // 鼠标移动事件处理函数 const handleMouseMove = (event: MouseEvent) => { // 计算鼠标移动时的偏移量 const offsetX = event.clientX - mouseX; const offsetY = event.clientY - mouseY; // 更新弹框的位置信息 dialog.$el.style.top = dialogPosition.top + offsetY + 'px'; dialog.$el.style.left = dialogPosition.left + offsetX + 'px'; }; // 鼠标抬起事件处理函数 const handleMouseUp = () => { // 移除鼠标移动事件处理函数和鼠标抬起事件处理函数 document.removeEventListener('mousemove', handleMouseMove); document.removeEventListener('mouseup', handleMouseUp); }; // 添加鼠标移动事件处理函数和鼠标抬起事件处理函数 document.addEventListener('mousemove', handleMouseMove); document.addEventListener('mouseup', handleMouseUp); }; // 添加鼠标按下事件处理函数 dialogHeaderEl.addEventListener('mousedown', handleMouseDown); } } ``` 接下来,我们在需要使用该mixin的组件中引入它,并将其添加到`mixins`属性中即可。 ```vue <template> <el-dialog title="Dialog Title" :visible.sync="dialogVisible" width="30%" :before-close="handleClose" v-dialogDrag > <span>Dialog Content</span> <span slot="footer" class="dialog-footer"> <el-button @click="dialogVisible = false">Cancel</el-button> <el-button type="primary" @click="dialogVisible = false">Confirm</el-button> </span> </el-dialog> </template> <script lang="ts"> import { defineComponent } from 'vue'; import dialogDragMixin from './dialogDragMixin'; export default defineComponent({ name: 'MyComponent', mixins: [dialogDragMixin], data() { return { dialogVisible: false }; }, methods: { handleClose(done: () => void) { this.$confirm('Confirm to close this dialog?') .then(() => { done(); }) .catch(() => {}); } } }); </script> ``` 最后,我们可以将`dialogDragMixin`作为一个公共方法,供其他多个组件调用。在`main.ts`中,全局注册该mixin即可。 ```typescript import { createApp } from 'vue'; import App from './App.vue'; import ElementPlus from 'element-plus'; import 'element-plus/lib/theme-chalk/index.css'; import dialogDragMixin from './dialogDragMixin'; const app = createApp(App); // 注册全局mixin app.mixin(dialogDragMixin); app.use(ElementPlus).mount('#app'); ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值