我們前端做項目時,難免會遇到地址輸入,多數情況下,我們都是提供一個省市三級聯動,加上具體地址輸入的Input輸入框給用戶,用以獲取用戶需要輸入的真實地址。在需要對用戶輸入的數據進行校驗的時候,我們會單獨針對省市的三級聯動和具體地址欄單獨校驗。也基本上能夠完成項目需求。
然而當我們轉向vue+element做項目時,會產生一個比較尷尬的問題。在element組件庫當中,對需要校驗的字段是通過在el-form-item這一組件標簽名上添加prop屬性來校驗的,如果依然按照以前的方法對省市聯動和地址欄分別校驗的話,就得把省市選擇和地址輸入分成兩個el-form-item組件內蓉來寫,我們可以看一下,實際形成的布局。
這就比較難受了,用戶本來也就是需要輸入個地址,我們提供給用戶級聯選擇器的目的是方便用戶進行省市區的選擇,然而現在的情況確是,用戶必須進行二次驗證。
轉換思路,用戶也可以把兩欄並成一欄,展示會比較好看一點。
這么來看布局似乎沒問題了,但是另一個問題又產生了,element本省提供的校驗方案是通過el-form-item的屬性prop對用戶傳入的參數進行校驗。
也就是說prop必須是字符串,那么這時候,就沒辦法對兩個字段同時校驗了,唯一的方案似乎就變成了:把這兩個字段變成一個對象,通過自定義校驗方法來校驗該對象內的數據的值。類似於:
address:{ district:[], address:'' }
在el-form-item上寫上prop='address',然后自定義校驗方法,通過validator進行校驗。
想法已經完成了,那么如果項目當中不僅僅一處用到地址輸入的話,我們是不是就有必要對該組件進行封裝,讓他成為我們的常備組件之一了。
好吧!那就開始封裝組件了。
我們的組件封裝的僅僅是級聯選擇器和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表單控件的功能,所以封裝起來也就更容易一點,也更容易理解。