el-form 一进来爆红问题

407 阅读2分钟

场景

  • 销售机会推进,需要填写表单,所有内容共用一个表单,根据当前状态动态的展示要输入的内容。使用v-if来销毁el-form-item防止rules规则

image.png

问题

  • 一个进度里有input输入框,绑定的值是undefined,form没有爆红,进度推进,到达下一个进度,也是绑定一个undefined的值,这次底下却有rules提示了。明明这里是for循环出来的,两个input除了绑定的值名字不一样,其他都是一样的,为什么第二次会爆红呢

分析

  • 这里是for循环的,动态创建和销毁,因为只有v-model的值发生了变化,所以我想是因为vue diff没有重新去销毁和创建元素,只是将值去改变了,因为v-model的值是undefined,触发了el-form的 blur检测rules,所以才爆红

解决

  • 给元素添加key值,让vue认识到这里是两个不同的元素,不能去复用,需要去销毁。就可以解决了

image.png

代码

<template>
  <el-steps :active="activeStep" align-center>
    <el-step v-for="step in stepList" :title="step.label" finish-status="success"/>
  </el-steps>
  <div class="w-86%" m-auto m-t-lg>
    <el-form ref="formRef" :model="updateObj" label-width="160px" :rules="rules">

      <el-tabs v-model="activeTabName" p-lg>
        <el-tab-pane v-for="action of status.available_action" :label="action.label" :name="action.name">

          <el-form-item v-if="activeTabName === action.name" v-for="column in visibleColumnList(action)" :label="column.label" :prop="column.name" :required="column.required">
            <el-switch v-if="column.ttype === auth_enums.FIELD_TTYPE_BOOL" v-model="updateObj[column.name]" size="small"></el-switch>
            <el-select v-else-if="column.is_enum" v-model="updateObj[column.name]" :multiple="column.ttype===auth_enums.FIELD_TTYPE_INTLIST" placeholder="请选择" size="small" value-key="value">
              <el-option v-for="item in enum_item_list_dict[column.name]" :key="item.value" :label="item.label" :value="item"></el-option>
            </el-select>
            <span v-else-if="column.ref && column.ref.includes('Attachment')">
              <OssUpload :fileSize="column.atta_size" :fileTypeList="column.atta_type && column.atta_type.split(',')" :handleOverNum="handleOverNum" :handleExceed="handleExceed" :handleFileTypeLimit="handleFileTypeLimit" :prePath="`/${props.parentInfo.model.appName}/${props.parentInfo.model.name}/${new Date().getTime()}/`" :handleUploadSuccess="handleUploadSuccess"  v-model:fileList="updateObj[column.name]"/>
            </span>
            <span v-else-if="column.ref" flex items-center>
              <span style="margin-right: 10px">{{utils.toShowValue(updateObj[column.name])}}</span>
              <i-mdi-dots-horizontal text-default cursor-pointer text-gray-500 hover:text-blue-500 mr-1 @click="showSearchDialog(column)"/>
            </span>
            <el-date-picker v-else-if="column.ttype === auth_enums.FIELD_TTYPE_DATETIME" v-model="updateObj[column.name]" type="datetime" value-format="YYYY-MM-DD HH:mm:ss"/>
            <el-date-picker v-else-if="column.ttype === auth_enums.FIELD_TTYPE_DATE" v-model="updateObj[column.name]" type="date" value-format="YYYY-MM-DD"/>
            <el-input v-else-if="column.ttype === auth_enums.FIELD_TTYPE_DICT" type="textarea" :rows="column.rows || 4" v-model="updateObj[column.name]" placeholder="变量名1=变量值1&#10;变量名2=变量值2"></el-input>
            <el-input :key="column.name" v-else-if="column.ttype === auth_enums.FIELD_TTYPE_INT || column.ttype === auth_enums.FIELD_TTYPE_FLOAT" v-model="updateObj[column.name]" size="small" type="number"></el-input>
            <el-input v-else v-model="updateObj[column.name]" size="small"></el-input>
          </el-form-item>
          <div m-t-8 flex justify-end space-x-2>
            <el-button @click="closeUpdateDlg">
              <i-line-md-close text-default mr-1/>
              取消
            </el-button>
            <el-button type="primary" @click="triggerAction(opportunity, action, action.field_name)">
              <i-line-md-confirm text-default mr-1/>
              {{action.label}}
            </el-button>
        </div>
        </el-tab-pane>
      </el-tabs>
    </el-form>
  </div>

    <el-dialog
      :title="selectDialogTitle"
      v-model="selectDialogFlag"
      width="80%"
      :close-on-click-modal="false"
      destroy-on-close
      append-to-body
      draggable
    >
      <SelectWindow :column="selectColumn" :form="updateObj" :queryData="queryData" v-on:on-select-cancel="onSelectCancel" v-on:on-select-confirm="onSelectConfirm"></SelectWindow>
    </el-dialog>
</template>

<script setup lang="ts">
  import OssUpload from '@/components/upload/OssUpload.vue'
  import { ref, reactive, unref, onMounted, computed } from 'vue'
  import enums from '@/enums/crm_enums'
  import auth_enums from '@/enums/auth_enums'
  import { useRules } from '../rules'
  import utils from '@/js/utils'
  import SelectWindow from '@/pages/SelectWindow2.vue'
  import { useAction } from '@/use/useAction'
  import { apost } from '@/js/ajaxFn'
  import { useToken } from '@/use/commonInfo'
  import { showError } from "@/js/message_utils"

  function handleOverNum() {
    showError('超过数量限制')
  }

  function handleExceed() {
    showError("该文件体积过大");
  }

  function handleFileTypeLimit() {
    showError("该文件格式不正确");
  }

  const props = defineProps({
    parentInfo: Object,
  })
  
  const emit = defineEmits(['message'])

  const formRef = ref(null)
  const opportunity = reactive(props.parentInfo.selectedRow)
  const updateObj = opportunity
  const tableview = props.parentInfo.tableview
  const totalColumnList = tableview.column_list
  const rules = useRules(updateObj, formRef, tableview.column_list)
  let objOrigin = JSON.parse(JSON.stringify(utils.convert2FlatObj(updateObj)))

  const stepList = computed(() => {
    const alist = enums.OPPORTUNITY_STATUS_LIST.slice(4, 9)
    alist.push({label: '签单/输单'})
    return alist
  })

  const status = reactive(opportunity.status)
  const activeStep = computed(() => {
    let i = 0
    const currentStatus = status.value
    for (let step of stepList.value) {
      if (currentStatus === step.value) break;
      i += 1
    }
    return i
  })

  const activeTabName = ref('')

  function visibleColumnList (action) {
    const pre_check_field_dict = {}

    const obj = action.pre_check || {}
    for (const fieldName in obj) {
      pre_check_field_dict[fieldName] = 1
    }

    const columnList = []
    for (const column of totalColumnList) {
      if (column.name in pre_check_field_dict) {
        const newColumn = JSON.parse(JSON.stringify(column))
        newColumn.required = true
        columnList.push(newColumn)
      }
    }
    return columnList
  }

  const queryData = reactive({})
  const selectDialogTitle = ref('')
  const selectColumn = ref({})
  const selectDialogFlag = ref(false)

  function showSearchDialog(column: Object) {
    selectDialogTitle.value = `选择${column.label}`
    selectColumn.value = column
    selectDialogFlag.value = true
  }

  function onSelectConfirm(result) {
    const { column, selectResult } = result
    updateObj[column.name] = selectResult
    selectDialogFlag.value = false
  }

  function onSelectCancel() {
    selectDialogFlag.value = false
  }

  const token = unref(useToken())
  const uploadHeaders = {Authorization: 'Bearer ' + token}
  function handleRemove(file, fileList) {
    console.log(file, fileList);
  }
  function handlePreview(file) {
    console.log(file);
  }
  // function handleExceed(files, fileList) {
  //   // ElMessage.warning(`当前限制选择 3 个文件,本次选择了 ${files.length} 个文件,共选择了 ${files.length + fileList.length} 个文件`);
  // }
  function beforeRemove(file, fileList) {
    // return ElMessageBox.confirm(`确定移除 ${ file.name }?`);
  }
  function handleUploadSuccess(res, fieldName) {
    // updateObj[fieldName].push(res.id)
  }

  // const dataChanged = computed(() => {
  //   debugger
  //   let objNew = JSON.parse(JSON.stringify(utils.convert2FlatObj(updateObj)))
  //   for (const key in objNew) {
  //     let newValue = objNew[key]
  //     let oldValue = objOrigin[key]
  //     if (JSON.stringify(newValue) !== JSON.stringify(oldValue)) {
  //       return true
  //     }
  //   }
  //   return false
  // })

  async function doSubmit() {
    formRef.value.validate(async (valid: Boolean) => {
      if (valid) {
        let objNew = JSON.parse(JSON.stringify(utils.convert2FlatObj(updateObj)))
        const postData = {id: objNew.id}
        for (const key in objNew) {
          let newValue = objNew[key]
          let oldValue = objOrigin[key]
          if (JSON.stringify(newValue) !== JSON.stringify(oldValue)) {
            postData[key] = newValue
          }
        }
        const url = `/ajax/crm/update_opportunity/`
        const response = await apost(url, postData)
        objOrigin = JSON.parse(JSON.stringify(utils.convert2FlatObj(response)))
        Object.assign(opportunity, response)
        emit('message', {action: 'refresh'})
        refresh()
      } else {
        console.log('Form validate failed!')
        return false
      }
    })
  }

  const model = {
    appName: 'crm',
    name: 'Opportunity',
    label: '销售机会'
  }

  async function refresh () {
    const response = await apost('/ajax/crm/get_opportunity/', {id: opportunity.id})
    Object.assign(opportunity, response)
    objOrigin = JSON.parse(JSON.stringify(utils.convert2FlatObj(response)))
    Object.assign(status, response.status)
    if (status.available_action) {
      activeTabName.value = status.available_action[0].name
    }
  }

  const { submitAction, batchSubmitAction, actionFormSubmit } = useAction(model, props.parentInfo, refresh)

  function triggerAction(row: Object, action:Object, column_name: string) {
    formRef.value.validate(async (valid: Boolean) => {
      if (valid) {
        let objNew = JSON.parse(JSON.stringify(utils.convert2FlatObj(updateObj)))
        const postData = {id: objNew.id}
        for (const key in objNew) {
          let newValue = objNew[key]
          let oldValue = objOrigin[key]
          if (JSON.stringify(newValue) !== JSON.stringify(oldValue)) {
            postData[key] = newValue
          }
        }
        const url = `/ajax/crm/update_opportunity/`
        const response = await apost(url, postData)

        objOrigin = JSON.parse(JSON.stringify(utils.convert2FlatObj(response)))
        Object.assign(opportunity, response)
        emit('message', {action: 'refresh'})
        const response1 = await submitAction(row, action, column_name)
        debugger
        emit('message', {action: 'refresh'})
        refresh()
      } else {
        console.log('Form validate failed!')
        return false
      }
    })
  }

  function closeUpdateDlg() {
    emit('message', {action: 'close-dlg'})
  }

  onMounted(async () => {
    if (status.available_action && status.available_action.length > 0) {
      activeTabName.value = status.available_action[0].name
    }
  })
</script>
<style scoped>
  :deep(.el-step__title) {
    font-size: 14px;
  }
  :deep(.el-button+.el-button) {
    margin-left: 10px;
  }
</style>