vue3+vite+uniapp 封装一个省市区组件

一、预览图

vue3+vite+uniapp 封装一个省市区组件_第1张图片

二、使用前的一些注意事项

  1. 只支持在 uniapp vue3 项目中使用
  2. 支持微信小程序和h5 (app端没有测试过)
  3. ui库用的 uview-plus
  4. 省市区数据用的是 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>

你可能感兴趣的:(uni-app,vue3,vite)