咱們前端作項目時,不免會遇到地址輸入,多數狀況下,咱們都是提供一個省市三級聯動,加上具體地址輸入的Input輸入框給用戶,用以獲取用戶須要輸入的真實地址。在須要對用戶輸入的數據進行校驗的時候,咱們會單獨針對省市的三級聯動和具體地址欄單獨校驗。也基本上可以完成項目需求。javascript
然而當咱們轉向vue+element作項目時,會產生一個比較尷尬的問題。在element組件庫當中,對須要校驗的字段是經過在el-form-item這一組件標籤名上添加prop屬性來校驗的,若是依然按照之前的方法對省市聯動和地址欄分別校驗的話,就得把省市選擇和地址輸入分紅兩個el-form-item組件內蓉來寫,咱們能夠看一下,實際造成的佈局。html
這就比較難受了,用戶原本也就是須要輸入個地址,咱們提供給用戶級聯選擇器的目的是方便用戶進行省市區的選擇,然而如今的狀況確是,用戶必須進行二次驗證。前端
轉換思路,用戶也能夠把兩欄併成一欄,展現會比較好看一點。vue
這麼來看佈局彷佛沒問題了,可是另外一個問題又產生了,element本省提供的校驗方案是經過el-form-item的屬性prop對用戶傳入的參數進行校驗。java
也就是說prop必須是字符串,那麼這時候,就沒辦法對兩個字段同時校驗了,惟一的方案彷佛就變成了:把這兩個字段變成一個對象,經過自定義校驗方法來校驗該對象內的數據的值。相似於:react
address:{ district:[], address:'' }
在el-form-item上寫上prop='address',而後自定義校驗方法,經過validator進行校驗。api
想法已經完成了,那麼若是項目當中不單單一處用到地址輸入的話,咱們是否是就有必要對該組件進行封裝,讓他成爲咱們的常備組件之一了。數組
好吧!那就開始封裝組件了。antd
咱們的組件封裝的僅僅是級聯選擇器和Input輸入框,並不包含校驗規則,畢竟是還有其餘須要校驗的組件。佈局
組件的內容可能就很簡單了。
一個.vue文件, template:
<el-row :gutter="16" type="flex" justify="space-between" > <el-col :span="12"> <el-cascader v-model="dis" :options="regionData" @change="handleAddressChange" style="width: 100%" filterable /> </el-col> <el-col :span="12"> <el-input v-model="address" placeholder="請輸入地址" @change="handleChange" /> </el-col> </el-row>
script:
import { regionData, CodeToText, TextToCode } from 'element-china-area-data' export default { name: 'VAddress', props: { value: { type: Object, default () { return { address: '', areaCity: '', areaCode: '', areaDistrict: '', areaProvince: '' } } } }, model: { prop: 'value', event: 'change' }, data () { return { dis: [], regionData, mapLabel: TextToCode, mapCode: CodeToText, ...this.value } }, methods: { handleChange (e) { let val = { ...this.value, address: this.address } this.$emit('update:value', val) this.$emit('change', val) }, handleAddressChange (values) { console.log(values) const b = ['areaProvince', 'areaCity', 'areaDistrict'] if (values.length > 0) { let initialValue = { ...this.value, 'areaProvince': '', 'areaCity': '', 'areaDistrict': '', 'areaCode': values[values.length - 1] } const val = values.reduce((acc, curret, index) => { let value = this.mapCode[curret] return { ...acc, [b[index]]: value } }, initialValue) this.$emit('change', val) // sync更新 // this.$emit('update:value', val) } }, getCurrentRegion (val) { let address = val if (!Array.isArray(val) && typeof val === 'object') { let { areaProvince, areaCity = '', areaCode } = val address = [this.mapLabel[areaProvince], this.mapLabel[areaCity], areaCode] if (address.some(item => item === undefined)) address = [] } return address }, initDis (val) { this.dis = this.dis.length === 0 ? this.getCurrentRegion(val) : this.dis } }, created () { if (this.regionData.length > 0 && this.value.areaCode) { this.initDis(this.value) } }, watch: { value: { handler (val) { this.address = val.address if (this.regionData.length > 0) { this.initDis(val) } if (Object.values(val).every(item => !item)) this.dis = [] }, deep: true } } }
須要注意的一個是:
model: { prop: 'value', event: 'change' }
具體解釋請轉到vue.js官方文檔,我只簡單說一句,就是提供給組件使用時綁定v-model,由於v-model默認傳遞的是value屬性,處理的是input事件,而經過在子組件定義model屬性,咱們就能夠修改v-model處理的事件和傳遞的屬性。由於很正常,咱們這裏有兩個form表單控件的內容,確定沒辦法依賴v-model的input事件進行處理。若是實在不喜歡這種寫法,也能夠在組件使用時,避開v-model的用法,轉而經過:value.sync進行屬性傳遞,在事件處理是經過this.$emit('update:value', val)來進行相似處理,這也就是看起來沒有v-model那麼牛X同樣,其實結果是一致的。
咱們在組件內分別對級聯選擇器和Input輸入框作change事件處理,從而獲取到最新的數據,轉換成使用該組件的父組件內,關於級聯選擇器的change事件,依賴於各個公司後臺開發人員須要前端傳回什麼樣的數據,進行處理。
咱們的項目當中,後臺須要省市區的數據格式爲areaProvince, areaCity, areaDistrict, areaCode,因此在級聯選擇器change的時候,我須要及時的將其獲取到的數組(省市區的code值),轉換成對應的具體的省份、城市、區,加上區的areaCode,而後傳遞給後臺,具體的得依賴項目需求各自處理。
這是vue+element的地址輸入的組件封裝,後面還會有一個react+antd關於地址輸入的組件封裝,相較於element,antd提供了自定義form表單控件的功能,因此封裝起來也就更容易一點,也更容易理解。