---
# System prepended metadata

title: '[Vue 2] Element UI 巢狀表單驗證筆記'
tags: [Vue, 前端筆記, Element UI]

---

# [Vue 2] Element UI 巢狀表單驗證筆記

###### tags: `Vue` `前端筆記` `Element UI`

==目前接手專案很舊，所以用的是 Vue 2 的寫法。==

## 巢狀表單驗證

### 1. 一般的情況（單層）

如果只是單層的話就是直接 `prop="proertyName"` 即可。

```javascript=
<el-form :model="form" ref="inoEditForm" :rules="inoEditRules">
	<el-form-item prop="name">
		<el-input v-model="form.name"></el-input>
  </el-form-item>
</el-form>

form: {
    name: '',
    sex: '',
    age: ''
}
```

填寫驗證的手段：

```javascript=
// ...
data() {
	return {
		// ...
		inoEditRules: {
			// 每個欄位都是一個 keyName: [{ required?: boolean, validation?: Function, tiggier: string | string[], message?: string}]
			whiteListName: [
				{
					required: true,
					validator: validateWhiteListName, // 這邊是放客製化驗證的函式
					trigger: ['blur', 'change']
				},
			],
		}
	}
// ...
```

### 2. 巢狀的欄位（不只單層）

如果巢狀的話就要給 `prop="第二層 propertyName.指定 propertyName"`，這樣子 Element UI 的 `<el-form>` 就會自己去抓對應的 propety 驗證。

```javascript=
<el-form :model="form" ref="inoEditForm" :rules="inoEditRules">
	<el-form-item prop="user_info.name">
			<el-input v-model="form.user_info.name"></el-input>
	</el-form-item>
</el-form>


form: {
    user_info: {
        name: '',
        sex: '',
        age: ''    
    }
}
```

填寫驗證的手段：

```javascript=
// ...
data() {
	return {
		// ...
		inoEditRules: {
			// 每個欄位都是一個 keyName: [{ required?: boolean, validation?: Function, tiggier: string | string[], message?: string}]
			// 有「,」因此需要手轉字串
			'user_info.name': [
				{
					required: true,
					validator: validateWhiteListName, // 這邊是放客製化驗證的函式
					trigger: ['blur', 'change']
				},
			],
		}
	}
// ...
```

### 3. 陣列產生的欄位

這個時候就必須塞 `expression` 組對應的欄位名稱 `:prop="'user_info.' + index + '.name'"` 假設 `index` 為 0 的話就會得到 `'user_info.0.name'`，這樣子 Element UI 才會找到對應的 `form` 欄位驗證。

```javascript=
<el-form :model="form" ref="inoEditForm" :rules="inoEditRules">
	<el-form-item
			v-for="(info, index) in form.user_info"
			:prop="'user_info.' + index + '.name'"
	>
		<el-input v-model="info.name"></el-input>
  </el-form-item>
</el-form>

form: {
    user_info: [
        {
            name: '',
            sex: '',
            age: ''    
        },
        {
            name: '',
            sex: '',
            age: ''    
        }
    ]
}
```

填寫驗證的手段：

```javascript=
// ...
data() {
	return {
		// ...
		inoEditRules: {
			// 每個欄位都是一個 keyName: [{ required?: boolean, validation?: Function, tiggier: string | string[], message?: string}]
			// 有「,」因此需要手轉字串
			'user_info.0.name'': [
				{
					required: true,
					validator: validateWhiteListName, // 這邊是放客製化驗證的函式
					trigger: ['blur', 'change']
				},
			],
			// 最多有多少欄位就要先寫好有多少驗證
			'user_info.1.name'': [
				{
					required: true,
					validator: validateWhiteListName, // 這邊是放客製化驗證的函式
					trigger: ['blur', 'change']
				},
			],
		}
	}
// ...
```

### 4. 開大決

如果動態的表單是無限多，或者超級多的話，總不可能先在檔案中手寫表（因為不可能會寫完...），這個時候就可以用藉由 template 的協助，直接把 `props` 寫在 template 內，讓動態生成的欄位也可以套用表單驗證的格式：

```javascript=
// template

// ... 可以無限點擊按鈕新增 item
<div v-for="(_, index) in list" :key="key + objectIndex">
	<p>{{ key.toUpperCase() }}</p>
  // STEP 2: template prop 及 rules 綁定
  <el-form-item :prop="`tasks.${index}.name`" :label="`${index}_name`" :rules="rules.taskNameRules">
	  <el-input v-model="form.tasks[index].name" style="width: 100%"></el-input>
  </el-form-item>
</div>

// script

data() {
	return {
		...
		form: {
			...,
			// STEP 1: 定義好 template 綁定的欄位，切記，在 template 綁定時對應的 :prop="keyName.index.targetFieldName"
			tasks: [{ name: '' }],
		  ...
		}
		rules: {
			// STEP 3: 之後動態新增的欄位就會套用這個驗證
			taskNameRules: [
				{ required: true, trigger: ['blur', 'change'], validator: customValidationFunc }
			]
		}
	}
}

```

這個時候厲害的 Element UI 就會讓動態新增的表單也吃到 `rules.taskNameRules` 中陣列的驗證了，不過必須要注意 template 綁定的 `:prop` 必須要對好才可以。

## 額外補充

### 動態生成表單 + 驗證需要依賴其他欄位

如果當前表單是動態產生，且驗證時不僅需要驗證自身欄位，還需要驗證其他欄位時，可以藉由 `<template>` 渲染時會叫用 `<template>` 上的函式 + `validator` 會依照滿足 `trigger` 事件後才叫用的原理，直接先建立一個立即函式，透過函式作用域（functional scope）的特性，日後 `validator` 因滿足 `trigger` 事件被呼叫時就可以讀取到被依賴的欄位：

```javascript=
<el-col v-for="(_, index) in list">
  <el-form-item label="發放總額" :prop="`list.${index}.sendTotal`" :rules="rules.sendTotal"
:rules="{ required: true, trigger: ['blur', 'change'], validator: (rule, value, callback) => { return ((index) => {
		if (Number(value) < Number(form.tasks[index].sendLimit)) return callback('單筆獎勵發送數量不可大於發放總額')
		  return callback()
		})(index)}}">
	  <el-input v-model="form.tasks[index].sendTotal"></el-input>
	</el-form-item>
	<el-form-item label="單筆獎勵發送數量" :prop="`tasks.${index}.sendLimit`" :rules="rules.sendLimit">
	  <el-input v-model="form.tasks[index].sendLimit"></el-input>
	</el-form-item>
</el-col>
```

```javascript=
{ 
  required: true, 
  trigger: ['blur', 'change'],
	// validator 叫用後，就會叫用裡面的立即函式，其 parameter 是 index，以便從 index 撈依賴的欄位是否符合驗證條件
  validator: (rule, value, callback) => { return ((index) => {
		if (Number(value) < Number(form.tasks[index].sendLimit)) return callback('單筆獎勵發送數量不可大於發放總額')
		  return callback()
	})(index)
}
```

#### 會有抓不到最新值的問題嗎？

不會，因為 `validator` 呼叫時都會重現呼叫立即函式，所以每次都會從 `index` 拿到新的值。

## 參考資料：
1. [ElementUI表单校验prop的嵌套写法](https://segmentfault.com/a/1190000023851366)
2. [el-form表单对象内还嵌套对象，绑定的prop规则校验会失效](https://blog.csdn.net/weixin_42190844/article/details/121532087)
3. [element ui动态表单 + 自定义校验规则](https://blog.csdn.net/qiankui/article/details/90737748)