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

问题
- 一个进度里有input输入框,绑定的值是undefined,form没有爆红,进度推进,到达下一个进度,也是绑定一个undefined的值,这次底下却有rules提示了。明明这里是for循环出来的,两个input除了绑定的值名字不一样,其他都是一样的,为什么第二次会爆红呢
分析
- 这里是for循环的,动态创建和销毁,因为只有v-model的值发生了变化,所以我想是因为vue diff没有重新去销毁和创建元素,只是将值去改变了,因为v-model的值是undefined,触发了el-form的 blur检测rules,所以才爆红
解决
- 给元素添加key值,让vue认识到这里是两个不同的元素,不能去复用,需要去销毁。就可以解决了

代码
<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 变量名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 beforeRemove(file, fileList) {
}
function handleUploadSuccess(res, fieldName) {
}
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>