我們前端做項目時,難免會遇到地址輸入,多數情況下,我們都是提供一個省市三級聯動,加上具體地址輸入的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表單控件的功能,所以封裝起來也就更容易一點,也更容易理解。
