Element plus中表格使用表单校验
1、table组件,html部分
<template>
<div class="set-f">
<el-form :model="fromData" ref="tableFrom">
<el-table ref="tableRef" class="minheight" v-loading="loading" :data="fromData.list" :stripe="_option.stripe" :height="_option.height"
:maxHeight="_option.maxHeight" :size="_option.size" :showHeader="_option.showHeader" scrollbar-always-on
:tooltipEffect="_option.tooltipEffect" :row-style="_option.rowStyle" :row-key="rowKey" :row-class-name="rowStyle"
:default-expand-all="defaultExpandAll" @selection-change="handleSelectionChange" @select="select" @select-all="selectAll"
@cell-click="handleCellClick" @sort-change="sortChangeFn" @row-click="handleRowClick" border>
<template v-for="(col, index) in column" :key="index">
<!---复选框, 序号 (START)-->
<el-table-column v-if="col.type === 'index' || col.type === 'selection' || col.type === 'expand'"
:align="col.align" :label="col.label" :type="col.type" :fixed="col.fixed || false"
:index="indexMethod" :width="col.width?col.width:50" :min-width="col.minWidth" />
<!---复选框, 序号 (END)-->
<!-- 表单元素 -->
<template v-else-if="com['M' + col.type] && areaAuth[col.keyNo]" >
<el-table-column v-if="!col.options.hide && judgeIsShow(col.keyNo)" class-name="formElement" :prop="col.keyNo" :label="t(col.label)" min-width="130">
<template #header v-if="col.options.rules.required">
<span class="headerIcon">*</span>
<span>{{ $t(col.label) }}</span>
</template>
<template #default="scope" >
<el-form-item :prop="'list.'+scope.$index+'.'+col.keyNo" :rules="allRules[col.keyNo+scope.$index]">
<component :is="com['M' + col.type]" v-model="scope.row[col.keyNo]" :item="col" :pageNo="pageNo" :areaNo="areaNo"
:index="index" :field-form="scope.row" :all-field-form="allFieldForm" :all-table-data="allTableData" :elementAuth="areaAuth[col.keyNo]"
@change="elementEventFn(col, 'change')" @keyup.enter="elementEventFn(col, 'enter')" @blur="elementEventFn(col, 'blur')" @click="eleClickFn(col, scope.row)" />
</el-form-item>
</template>
</el-table-column>
</template>
<!-- 表格列 -->
<template v-else-if="col.type === 'tableCol' && areaAuth[col.keyNo]" >
<!---弹出页面(START)-->
<el-table-column v-if="col.isShow && judgeIsShow(col.keyNo) && col.modalNo" :key="col.keyNo" :sortable="col.isSort ? 'custom' : false" :align="col.align || 'left'" :show-overflow-tooltip="item.config.isNewLine === false ? true : false"
:label="t(col.label)" :prop="col.keyNo" :width="col.width" :min-width="col.minWidth" >
<template #default="{ row }">
<span class="hasEvent" @click="openPageFn(col.modalNo, row, col.keyNo)">{{ row[col.keyNo] }}</span>
</template>
</el-table-column>
<!---弹出页面 (END)-->
<!---元素事件(START)-->
<el-table-column v-else-if="col.isShow && judgeIsShow(col.keyNo) && col.trigger && col.trigger != 'none' && col.actionNo" :key="col.keyNo" :sortable="col.isSort ? 'custom' : false" :align="col.align || 'left'" :show-overflow-tooltip="item.config.isNewLine === false ? true : false"
:label="t(col.label)" :prop="col.keyNo" :width="col.width" :min-width="col.minWidth" >
<template #default="{ row }">
<span class="hasEvent" @click="eleEventFn(col, row)">{{ row[col.keyNo] }}</span>
</template>
</el-table-column>
<!---元素事件 (END)-->
<!-- 字典 (START) -->
<template v-else-if="col.isShow && judgeIsShow(col.keyNo) && col.dict && col.dict != '0' ">
<el-table-column :key="col.keyNo" :sortable="col.isSort ? 'custom' : false" :align="col.align || 'left'" :show-overflow-tooltip="item.config.isNewLine === false ? true : false"
:label="t(col.label)" :prop="col.keyNo" :width="col.width" :min-width="col.minWidth" >
<template #default="{ row }">
<span>{{ row[col.keyNo+'_label'] ? row[col.keyNo+'_label'] : row[col.keyNo] }}</span>
</template>
</el-table-column>
</template>
<!-- 字典 (END) -->
<!-- 默认渲染列 (START) -->
<template v-else>
<el-table-column v-if="col.isShow && judgeIsShow(col.keyNo)" :key="col.keyNo" :sortable="col.isSort ? 'custom' : false" :align="col.align || 'left'" :show-overflow-tooltip="item.config.isNewLine === false ? true : false"
:label="t(col.label)" :prop="col.keyNo" :width="col.width" :min-width="col.minWidth" >
<template #default="{ row }">
<span v-if="col.isHtml" class="hasEvent" @click="handleHtml(row[col.keyNo])">查看详情</span>
<span v-else-if="col.httpType" >
<template v-if="col.httpType=='qUrl'">
<div v-if="row[col.keyNo]">
<template v-for="(item,index) in row[col.keyNo].split(',')" class="set-file">
<span class="set_spn" style="margin-left: 10px" :title="item" @click="handelPreview(item,item)"><el-icon><Document /></el-icon></span>
</template>
</div>
</template>
<template v-if="col.httpType=='url'">
<div v-if="row[col.keyNo]">
<a :href="row[col.keyNo]" target="_blank">{{ row[col.keyNo] }}</a>
</div>
</template>
</span>
<span v-else>{{ row[col.keyNo] }}</span>
</template>
</el-table-column>
</template>
<!-- 默认渲染列 (END) -->
</template>
<!-- 操作列-->
<el-table-column v-else-if="col.type === 'operateCol'"
:align="col.align" :label="t(col.label)" :width="col.width" :fixed="col.fixed || false">
<template #default="scope">
<template v-for="(btnElement, bIndex) in col.buttonList" :key="bIndex">
<!-- // 刪除表格行,前端组件 -->
<template v-if="btnElement.btnType === 'delTableRow' && areaAuth[btnElement.keyNo]">
<el-button v-show="!btnElement.options.hide" type="text" :icon=" 'Delete' " :style="{'color': 'red !important' }"
@click="handleDelRow(scope.$index)">
</el-button>
</template>
<template v-else-if="areaAuth[btnElement.keyNo]">
<el-button v-show="!btnElement.options.hide" type="text" :icon="btnElement.options.actionNo === 'deleteComponent' ? 'Delete' : btnElement.icon" :style="{'color': btnElement.options.actionNo === 'deleteComponent' ? 'red !important' : ''}"
@click="handleAction(btnElement, scope.row,scope.$index,col)">
{{btnElement.options.actionNo === 'deleteComponent' ? '' : t(btnElement.label) }}
</el-button>
</template>
</template>
</template>
</el-table-column>
<!-- 操作列 END-->
</template>
</el-table>
</el-form>
<htmlDia v-model:diaOpen="open" :content="content"></htmlDia>
<diaPreview ref="diaEditRef" :title="title" v-model:visible="dialogVisible" :preType="preType"></diaPreview>
</div>
</template>
2、JS部分
<script setup>
const props = defineProps({
pageNo: {
type: String,
default: ''
},
areaNo: {
type: String,
default: ''
},
// table的数据
tableData: {
type: Array,
default: () => []
},
// 所有表单区域元素
allFieldForm: {
type: Object,
default: () => {
return {};
},
},
// 所有表格区域元素
allTableData: {
type: Object,
default: () => {
return {};
},
},
item: {
type: Object,
default: () => {}
},
column: {
type: Array,
default: () => []
},
columnsShow: {
type: Array,
default: () => []
},
option: [Object, Array],
loading: {
type: Boolean,
default: false
},
//是否展示表格树型结构
rowKey: {
type: String,
default: ''
},
defaultExpandAll: {
type: Boolean,
default: false
},
rowStyle: {
type:Boolean,
default:false
},
// 当前区域的权限
areaAuth: {
type: Object,
default: () => {
return {};
},
}
})
const {pageNo, areaNo, item, column, tableData, allFieldForm, allTableData, areaAuth} = toRefs(props)
const {trigger, actionNo, conditionArea, serveAreaNo } = item.value
const emit = defineEmits(['selection-change','select-all', 'select','row-click', 'cell-click', 'command', 'size-change',
'current-change','row-class-name', 'sortChange', 'openPage', 'clickBtn', 'update:tableData','tableFormValid'])
const allRules = reactive({})
const tableFrom = ref('')
const fromData = ref({
list:[]
})
watch(()=>tableData.value,(value)=>{
fromData.value.list = tableData.value
initRuleFn()
}, { deep: true })
// 生成校验规则
const initRuleFn = () => {
console.log('column.value2222======', column.value)
for(let i=0; i< column.value.length; i++ ){
const element = column.value[i]
const { type, label, keyNo, options} = element
console.log('element111======', element)
if(['input', 'select', 'dateRange', 'datetimerange', 'date', 'datetime', 'checkbox', 'upload', 'richtextEditor'].includes(type)){
// 元素校验
const { rules, validParamList } = options
const {data, total} = allTableData.value[areaNo.value]
let tableData = JSON.parse(JSON.stringify(data))
// 将表格该列校验重新生成
tableData.forEach( (row, index) => {
allRules[keyNo+index] = []
})
// 必填
if(rules && rules.required && !rules.hasRequiredFactor){
// 条件必填为空
tableData.forEach( (row, index) => {
allRules[keyNo+index]=[{ required: true, message: t(label) + t('element132'), trigger: ["blur", "change"] }]
})
}else if(rules && rules.required && rules.hasRequiredFactor){
const words = escape2Html(rules.hasRequiredFactor)
const reg = /[#\$%\^&\*【】@!!¥?|‘;:”“'。,、?<>+=:-]+/g
const reg1 = /[a-zA-Z0-9]+/g//筛选特殊字符串中的元素别名
console.log('words1111======', words)
if(words.indexOf('!=')>-1||words.indexOf('==')>-1||words.indexOf('=')==-1||words.indexOf('>')>-1||words.indexOf('<')>-1) {
//日期校验
if(words.indexOf('date')>-1) {
const nwds = words.replace('date:','')
console.log('nwds======',nwds)
const filteWds = nwds.replace(reg,',')//过滤特殊字符
const WdsList = filteWds.split(',')
console.log('filteWds=====',WdsList.length)
let fwds = nwds.substr(nwds.length-1,1)//获取字符串最后一个字段
const curDate = new Date() //获取当前日期
if(nwds.indexOf('c')>-1 || nwds.indexOf('C')>-1) {//存在当前日期
if(WdsList.length==3&&nwds.indexOf('-')>-1) {
let num = WdsList[2].replace(fwds,'')//日期计算天数
let diff = 0
Object.keys( aObj ).forEach( areaKey => {
const aItem = aObj[areaKey]
if(aItem[WdsList[1]]) {
if(['M','Y'].includes(fwds)) {
diff = getDateYMDiff(aObj[areaKey][WdsList[1]],'2023/08/25',fwds)
}
console.log('diff======',diff)
}
})
let bol = false
if(diff < Number(num) ){
bol = true
}
const yObj = {
'M':'月',
'Y':'年'
}
if(bol) {
const validateFc = (rule, value, callback) => {
if (bol) {
callback(new Error( t(label) + '必须大于等于'+num + yObj[fwds]))
} else if (!bol) {
callback()
}
}
allRules[keyNo] = [{ required: bol, validator: validateFc, trigger: ["blur", "change"] }]
}
}
}
}else {//公式计算校验
let eList = words.match(reg1) // 元素列表
const wordsList = words.split(',') // 公式
if(wordsList.length>0) {
console.log('eList=====',eList, wordsList)
tableData.forEach( (row, index) => {
let awords = []
wordsList.forEach(wd=>{
Object.keys(row).forEach(key=>{
if(eList.includes(key)) {
if(['',null].includes(row[key])) {
row[key] = 0
}
wd = wd.replaceAll(key,row[key])
awords.push(wd)
}
})
})
console.log('awords=====',awords)
awords.forEach(d=>{
let bol = eval(d)
if([true].includes(bol)) {
allRules[keyNo+index]=[{ required: true, message: t(label) + t('element132'), trigger: ["blur", "change"] }]
}
})
})
}
}
}
}
}
}
console.log('allRules======', allRules)
}
//日期比较
function timeCompareFn(value,item,labels,form,curD='') {
const formulaObj = {
'1':'等于',
'2':'大于',
'3':'小于',
'4':'indexOf',
'7':'indexOf',
'10':'小于等于',
'11':'==null',
'12':'!=null',
'13':'不等于',
'9':'大于等于'
}
let objKey = ''
let formula = item['conditionFormula']//比较方式
let compareValues = form[item.compareValue]//比较值
if(curD) {
compareValues = curD
}
let labelCpare = ''
let cols = column.value.find(kd=>kd.keyNo == item.compareValue) || ''
if(cols) {
labelCpare = t(cols.label)
}
//比较值compareValue
if(formula&&formula != 'indexOf' && compareValues) {
if (!value) {
objKey = ``
}else{
if(!compareValues) {
objKey = `请输入${labelCpare}`
}else{
const d1 =compareValues? compareValues.replace(/-/g, '/'):''//比较值
const d2 = value.replace(/-/g, '/')//填写值
let curD1 = new Date(d1).getTime()
let curD2 = new Date(d2).getTime()
console.log('curD1=====',curD1)
console.log('curD2=====',curD2)
let flag = true
if(formula=='1') {
flag = curD2 == curD1?true:false
}else if(formula=='2') {
flag = curD2 > curD1?true:false
}else if(formula=='3') {
flag = curD2 < curD1?true:false
}else if(formula=='10') {
flag = curD2 <= curD1?true:false
}else if(formula=='13') {
flag = curD2 != curD1?true:false
}else if(formula=='9') {
flag = curD2 >= curD1?true:false
}
if(!flag) {
objKey = t(labels)+formulaObj[formula]+labelCpare
}
}
}
}
return objKey
}
function validate(callback) {
return tableFrom.value.validate( res => {
console.log('res===2222', res);
return res
})
}
defineExpose({
validate
})
</script>
3、父页面
事件按钮触发 表单表格组件 的校验
<Table class="setTable" v-if="item.config.type==='list'" :ref="(el) => setItemRef(el, item.keyNo)" v-show="formatInitShow(item)" :key="item.keyNo" :option="option" :item="item" :columnsShow="allTableCol[item.keyNo]" :column="item.columns"
v-model:table-data="allAreaDate[item.keyNo].data" :all-field-form="fieldForm" :all-table-data="allAreaDate" :pageNo="pageConfig.pageNo" :areaNo="item.keyNo" :areaAuth="pageAuth[item.keyNo]"
@cell-click="handleCellClick" @selection-change="handleSelectionChange($event, item.keyNo)" @select-all="selectAll($event, item.config, item.keyNo)"
@sort-change="handleSortChange" @openPage="openPageFn" @clickBtn="clickBtnFn" @tableBtn="eventFn" @delTableRow="delTableRowFn">
<template #userStatus>
<el-table-column label="状态" align="left" min-width="80">
<template #default="scope">
<el-switch v-model="scope.row.userStatus" active-value="0" inactive-value="1"
@click="handleStatusChange(scope.row)"></el-switch>
</template>
</el-table-column>
</template>
</Table>
<script setup name="templateOne">
// 区域对应组件的ref
function setItemRef(el, keyNo) {
if (el && keyNo) {
allRefs[keyNo] = el
}
console.log('allRefs==========', allRefs)
}
/** 新增修改,保存按钮 */
async function submitForm(pageNo, areaNo, keyNo, serveArea, actionNo, elementEvent, tableRowIndex) {
const { areaVoList } = configParamJson.value
// 打印组件不校验
if(actionNo === 'PrintTemplateComponent'){
addClickBtnFn(pageNo, areaNo, keyNo, serveArea, actionNo, elementEvent, tableRowIndex)
return
}
// 校验
for(let i=0; i<serveArea.length; i++){
let item = serveArea[i]
if(fieldForm[item]){
for(let j=0; j<areaVoList.length; j++){
const areaItem = areaVoList[j]
const {areaNo, areaType, elementVoList} = areaItem
// 非标题区域
if(areaNo === item && areaType !=='titleArea' && allRefs[item]){
// 表单校验
let valid = await allRefs[item].validate()
// 校验不通过
if(!valid){
proxy.$modal.msgError(t('element207'))
return
}
}
}
}else if(allAreaDate[item]){
// 列表校验
for(let j=0; j<areaVoList.length; j++){
const areaItem = areaVoList[j]
const {areaNo, areaType, elementVoList} = areaItem
console.log('areaItem====', areaItem)
// 遍历表格区域内元素是否必填
if(areaNo === item && areaType ==='tableArea'){
// 表单校验
let valid = await allRefs[item].validate()
// 校验不通过
if(!valid){
proxy.$modal.msgError(t('element207'))
return
}
}
}
}
}
// 校验通过
btnLoading.value = true
addClickBtnFn(pageNo, areaNo, keyNo, serveArea, actionNo, elementEvent, tableRowIndex)
cancel()
}
</script>
4、总结
<el-form :model="fromData" ref="tableFrom">
在el-table外层- el-form 绑定 :model=“fromData”,el-table 的:data="fromData.list"要有层级关系
- 校验的prop一定要按照,
:prop="'list.'+scope.$index+'.'+col.keyNo"
格式,意思是 list列表内的 第几行 的 什么字段
`<el-table-column :prop="col.keyNo" :label="t(col.label)" min-width="130">
<template #default="scope" >
<el-form-item :prop="'list.'+scope.$index+'.'+col.keyNo" :rules="allRules[col.keyNo+scope.$index]">
<component :is="com['M' + col.type]" v-model="scope.row[col.keyNo]" :item="col" @click="eleClickFn(col, scope.row)" />
</el-form-item>
</template>
</el-table-column>`
- 校验的触发时机为
trigger: ["blur", "change"]
allRules[keyNo+index]=[{ required: true, message: t(label) + t('element132'), trigger: ["blur", "change"] }]