前言
省市区选择是许多移动端应用中常见的功能需求,而 vant 的 area 组件已经为我们提供了一个非常好用的选择方案。但是,在某些场景下,我们需要对 area 进行二次封装,更加定制化的省市区选择功能,以满足更加具体的业务需求。本文将会向大家介绍如何在 vant 中对 area 进行二次封装,并提供一些实用的封装技巧和方法,帮助大家实现更加完美的省市区选择方案。
实现思路
- 首先定义一个子组件页面用来封装选择器;
- 在父组件中(使用的页面)引入封装组件(子组件)并注册,然后在页面中使用,在父组件中给标签(注册的组件名)上绑定多个属性, 属性上挂载需要传递的值,通过
props
在子组件(封装文件)接收数据; - 在子组件中自定义确定的事件,调用这个事件后,子组件通过
this.$emit('自定义事件名',要传递的数据)
发送父组件可以监听的数据,最后父组件监听子组件事件,调用事件并接收传递过来的数据。
安装
vant
官方提供了一份默认的省市区数据,可以通过 @vant/area-data
引入:
npm i @vant/area-data
定义的参数
参数 | 描述 |
---|---|
selectValue | 绑定值 model |
keyValue | 绑定的 key 字段 |
columnsNum | 绑定的 value 字段 |
rules | 绑定的 option 数据源 |
required | 是否显示红色 * 校验 |
封装文件
<template>
<div>
<van-field v-model="textValue" v-bind="$attrs" :name="$attrs.name" :placeholder="placeholder" :rules="rules" :required="required"
:readonly="readonly" :is-link="islink" @click="show = !show" />
<van-popup v-model="show" position="bottom" overlay close-on-click-overlay>
<van-area :area-list="areaList" :columns-num="columnsNum" :columns-placeholder="columnsplaceholder" @confirm="onConfirm"
@cancel="show = !show" />
</van-popup>
</div>
</template>
<script>
import { areaList } from "@vant/area-data";
export default {
props: {
required: Boolean,
readonly: Boolean,
islink: Boolean,
columnsNum: Number,
rules: Array,
selectValue: String,
keyValue: String,
},
data() {
return {
areaList,
show: false,
textValue: "",
selectOptions: [],
placeholder: "",
columnsplaceholder: ["请选择", "请选择", "请选择"],
provinceList: null,
cityList: null,
countyList: null,
province: "",
city: "",
county: "",
};
},
methods: {
onConfirm(areaData) {
// 判断打开选择器后是否必填省/市/区
if (this.columnsNum === 1) {
this.provinceOn(areaData);
} else if (this.columnsNum === 2) {
this.provinceCityOn(areaData);
} else {
this.provincesMunicipalitiesOn(areaData);
}
},
// 省时必填校验
provinceOn(areaData) {
const province = areaData[0];
if (!province.name) {
this.$toast("请选择省");
return;
}
this.acquireOn(areaData);
},
// 省市时必填校验
provinceCityOn(areaData) {
const province = areaData[0];
const city = areaData[1];
if (!province.name) {
this.$toast("请选择省");
return;
}
if (!city.name) {
this.$toast("请选择市");
return;
}
this.acquireOn(areaData);
},
// 省市区时必填校验
provincesMunicipalitiesOn(areaData) {
const province = areaData[0];
const city = areaData[1];
const area = areaData[2];
if (!province.name) {
this.$toast("请选择省");
return;
}
if (!city.name) {
this.$toast("请选择市");
return;
}
if (!area.name) {
this.$toast("请选择区");
return;
}
this.acquireOn(areaData);
},
// 校验完后执行确定方法赋值name/code
acquireOn(areaData) {
// 获取选中name/code值
let names = "";
let codes = "";
areaData.forEach((item, index) => {
if (!item) {
return "";
}
if (index > 0) {
names += "/" + item.name;
codes += "/" + item.code;
} else {
names += item.name;
codes += item.code;
}
});
if (this.keyValue === "code") {
this.textValue = codes;
} else {
this.textValue = names;
}
this.show = !this.show;
this.$emit("confirm", this.textValue);
// 更新回显值
this.getValue(this.keyValue);
},
// 通过code值获取地区name
getNameByCode(value, list) {
let name = "";
if (!value) {
return;
}
Object.keys(list).forEach((key) => {
if (value.toString() === key.toString()) {
name = list[key];
}
});
return name;
},
// 地区回显
getValue(type) {
if (!this.selectValue || !this.selectValue.length) {
this.textValue = "";
return;
}
const selectList = this.selectValue.split("/");
let provinceName,
cityName,
countyName = "";
switch (type) {
case "code":
if (Number(this.columnsNum) === 1) {
provinceName = this.getNameByCode(selectList[0], this.provinceList);
this.textValue = provinceName;
} else if (Number(this.columnsNum) === 2) {
provinceName = this.getNameByCode(selectList[0], this.provinceList);
cityName = this.getNameByCode(selectList[1], this.cityList);
this.textValue = provinceName + "/" + cityName;
} else {
provinceName = this.getNameByCode(selectList[0], this.provinceList);
cityName = this.getNameByCode(selectList[1], this.cityList);
countyName = this.getNameByCode(selectList[2], this.countyList);
this.textValue = provinceName + "/" + cityName + "/" + countyName;
}
break;
case "name":
this.textValue = this.selectValue;
break;
}
},
},
watch: {
// json数据
areaList: {
handler(newVal) {
if (newVal) {
const length = Object.keys(newVal).length;
if (length) {
this.cityList = newVal.city_list;
this.countyList = newVal.county_list;
this.provinceList = newVal.province_list;
}
}
},
immediate: true,
deep: true,
},
// 控制选择完整省市区还是部分
columnsNum: {
handler(newVal) {
switch (newVal) {
case 1:
this.placeholder = "省";
this.columnsplaceholder = ["请选择"];
break;
case 2:
this.placeholder = "省/市";
this.columnsplaceholder = ["请选择", "请选择"];
break;
default:
this.placeholder = "省/市/区";
this.columnsplaceholder = ["请选择", "请选择", "请选择"];
}
},
immediate: true,
},
selectValue: {
handler(newValue) {
this.$nextTick(() => {
if (this.keyValue) {
this.getValue(this.keyValue);
}
});
},
immediate: true,
},
keyValue: {
handler(newValue) {
if (newValue) {
this.getValue(newValue);
}
},
immediate: true,
},
},
};
</script>
使用文件
<template>
<div>
<van-form validate-first>
<AreaSelect name="cjPovo" label="车辆注册地" :selectValue="cjPovo" :readonly="true" :keyValue="`name`" :columnsNum="3"
@confirm="areaConfirm" :required="true" :rules="rules.cjPovo" />
<div class="btnBomBox">
<van-button round size="small" block @click="submitOn" type="info">提交</van-button>
</div>
</van-form>
</div>
</template>
<script>
import AreaSelect from "@/components/areaSelect/index";
export default {
components: {
AreaSelect,
},
data() {
return {
cjPovo: "",
rules: {
cjPovo: [
{
required: true,
message: "请选择车辆注册地",
},
],
},
};
},
methods: {
// 点击确定
areaConfirm(data) {
this.cjPovo = data;
},
// 提交
submitOn() {
console.log(this.cjPovo);
},
},
};
</script>
<style scoped>
.btnBomBox {
padding: 0px 16px;
display: flex;
justify-content: center;
}
</style>