一、预览图
二、使用前的一些注意事项
- 只支持在 uniapp vue3 项目中使用
- 支持微信小程序和h5 (app端没有测试过)
- ui库用的 uview-plus
- 省市区数据用的是 vant-ui 提供的一个赖库 @vant/area-data
三、组件代码
<template>
<u-popup :show="show" type="bottom" @close="handlePopupClose" round="44rpx">
<view class="area-picker">
<view class="title">
请选择收货地址
<view class="close-icon" @click="handlePopupClose">
<u-icon name="close" size="44rpx" color="#666666"></u-icon>
</view>
</view>
<view class="header">
<view @click="doChange('province')"
:class="['header-item', activeType === 'province' ? 'header-item--active' : '']"
v-if="activeType === 'province' || activeType === 'city' || activeType === 'district' || innerProvince">
{{ innerProvince ? innerProvince.name : '请选择省' }}
</view>
<view @click="doChange('city')" :class="['header-item', activeType === 'city' ? 'header-item--active' : '']"
v-if="activeType === 'city' || activeType === 'district' || innerProvince">
{{ innerProvince && innerCity ? innerCity.name : '请选择市' }}
</view>
<view @click="doChange('district')"
:class="['header-item', activeType === 'district' ? 'header-item--active' : '']"
v-if="activeType === 'district' || innerCity">
{{ innerProvince && innerCity && innerCounty ? innerCounty.name : '请选择区' }}
</view>
</view>
<scroll-view scroll-y class="main" :scroll-with-animation="true">
<view :id="`tag-${item.id}`" :class="['main-item', select(item.name) ? 'main-item--active' : '']"
@click="doSelect(item)" v-for="item in showList" :key="item.id">
<u-icon v-if="select(item.name)" name="checkbox-mark" size="44rpx" color="#3c9cff"></u-icon>
{{ item.name }}
</view>
</scroll-view>
</view>
</u-popup>
</template>
<script setup>
import { computed, nextTick, ref } from 'vue'
import { areaList } from "@vant/area-data";
const props = defineProps({
show: {
type: Boolean,
default: false
},
area: {
type: Array,
default: () => []
},
id: {
type: String,
default: ''
},
})
const emits = defineEmits(['close', 'confirm']) // 事件
const areaData = ref(areaList)
let innerProvince = ref(null) // 选择的省
let innerCity = ref(null) // 选择的市
let innerCounty = ref(null) // 选择的区
let activeType = ref('province') // 当前所选的area类型
const viewId = ref(null) // 应当展示在视图中的节点id
// 是否被选中
const select = computed(() => {
return (item) => {
switch (activeType.value) {
case 'province':
return innerProvince.value ? item === innerProvince.value.name : false
case 'city':
return innerCity.value ? item === innerCity.value.name : false
case 'district':
return innerCounty.value ? item === innerCounty.value.name : false
default:
return innerProvince.value ? item === innerProvince.value.name : false
}
}
})
// 展示的列表
const showList = computed(() => {
switch (activeType.value) {
case 'province':
return provinceList.value
case 'city':
return cityList.value
case 'district':
return countyList.value
default:
return provinceList.value
}
})
// 省列表
const provinceList = computed(() => {
const provinceList = []
if (areaData.value && areaData.value.province_list) {
for (const key in areaData.value.province_list) {
if (areaData.value.province_list[key]) {
provinceList.push({
id: key,
name: areaData.value.province_list[key]
})
}
}
}
return provinceList
})
// 市列表
const cityList = computed(() => {
const cityList = []
if (areaData.value && areaData.value.city_list) {
for (const key in areaData.value.city_list) {
if (areaData.value.city_list[key] && innerProvince.value && innerProvince.value.id.slice(0, 2) === key.slice(0, 2)) {
cityList.push({
id: key,
name: areaData.value.city_list[key]
})
}
}
}
return cityList
})
// 区列表
const countyList = computed(() => {
const countyList = []
if (areaData.value && areaData.value.county_list) {
for (const key in areaData.value.county_list) {
if (areaData.value.county_list[key] && (!innerProvince.value || (innerCity.value && innerCity.value.id.slice(0, 4) === key.slice(0, 4)))) {
countyList.push({
id: key,
name: areaData.value.county_list[key]
})
}
}
}
return countyList
})
// 关闭 popup
function handlePopupClose() {
emits('close')
}
// 地址选择完成
function doConfirm() {
const list = [innerProvince.value, innerCity.value, innerCounty.value]
const obj = {}
list.forEach((v, i) => {
i === 0 ? obj.province = v.name : ''
i === 1 ? obj.city = v.name : ''
i === 2 ? obj.county = v.name : ''
});
emits('confirm', obj, [innerProvince.value, innerCity.value, innerCounty.value])
}
// 切换当前选择的省市区类型
function doChange(type) {
activeType.value = type
}
// 选中省市区项
function doSelect(item) {
switch (activeType.value) {
case 'province':
if (innerProvince.value && innerProvince.value.id === item.id) {
innerProvince.value = null
} else {
innerProvince.value = item
activeType.value = 'city'
}
innerCity.value = null
innerCounty.value = null
break
case 'city':
if (innerCity.value && innerCity.value.id === item.id) {
innerCity.value = null
} else {
innerCity.value = item
activeType.value = 'district'
}
innerCounty.value = null
break
case 'district':
if (innerCounty.value && innerCounty.value.id === item.id) {
innerCounty.value = null
} else {
innerCounty.value = item
doConfirm()
}
break
default:
if (innerProvince.value && innerProvince.value.id === item.id) {
innerProvince.value = null
} else {
innerProvince.value = item
activeType.value = 'city'
}
innerCity.value = null
innerCounty.value = null
break
}
}
</script>
<style lang="scss" scoped>
$color-text-secondary: #101010;
.area-picker {
position: relative;
height: 846rpx;
height: calc(846rpx + constant(safe-area-inset-bottom));
height: calc(846rpx + env(safe-area-inset-bottom));
width: calc(100vw - 80rpx);
background: #ffffff;
padding: 0 40rpx;
border-radius: 20rpx 20rpx 0px 0px;
padding-bottom: 0;
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
.title {
height: 114rpx;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
font-size: 36rpx;
font-family: PingFangSC-Medium, PingFang SC;
font-weight: 500;
color: #202124;
.close-icon {
position: absolute;
top: 57rpx;
right: 0;
padding: 19rpx;
transform: translateY(-50%);
}
}
.header {
display: flex;
margin-bottom: 24rpx;
&-item {
height: 44rpx;
font-size: 32rpx;
font-family: PingFangSC-Medium, PingFang SC;
font-weight: 500;
color: $color-text-secondary;
max-width: 186rpx;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
&:not(:last-child) {
margin-right: 56rpx;
}
&--active {
color: $u-primary;
}
}
}
.main {
height: calc(100% - 182rpx);
overflow: auto;
::-webkit-scrollbar {
width: 0;
height: 0;
color: transparent;
}
&-item {
display: flex;
align-items: center;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
width: 100%;
height: 84rpx;
background: #ffffff;
font-size: 28rpx;
color: $color-text-secondary;
image {
width: 44rpx;
height: 44rpx;
}
&--active {
font-family: PingFangSC-Medium, PingFang SC;
font-weight: 500;
color: $color-text-secondary;
}
}
}
}
</style>
四、组件使用
<template>
<view class="container">
<u-button @click="show = true" type="primary" customStyle="width: 90%;margin-top: 60rpx;">选择区域</u-button>
<view style="text-align: center; margin-top: 60rpx;">所选区域:{{ areaText }}</view>
<AreaPicker :show="show" @confirm="handleConfirmArea" @close="show = false"></AreaPicker>
</view>
</template>
<script setup>
import { ref } from "vue";
const show = ref(false);
const areaText = ref("");
function handleConfirmArea(item) {
console.log("当前选中区域:", item);
const { province, city, county } = item;
areaText.value = province + " " + city + " " + county;
show.value = false;
}
</script>
<style lang="scss" scoped></style>