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

news2024/11/19 3:44:18

一、预览图

请添加图片描述

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

  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>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1066168.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

深圳市重点实验室如何办理-华夏泰科

深圳市重点实验室是为了提升科技创新的能力和水平&#xff0c;推动科技成果的转化和应用而设立的一项重要机构。同时深圳市重点实验室是开展高水平基础研究和应用基础研究、聚焦和培养优秀科技人才、开展学术交流的重要基地。认定该资质对于提升品牌影响力和科技创新能力有着重…

Elasticsearch实战(十八)--ES搜索Doc Values/Fielddata 正排索引 深入解析

1.正排索引与倒排索引 先说结论&#xff0c;再讲原理 !!!尽量不要再生产环境使用fielddatatrue&#xff0c;即使要用也要控制好占用内存比例的大小&#xff0c;否则容易出现OOM !!!尽量不要再生产环境使用fielddatatrue&#xff0c;即使要用也要控制好占用内存比例的大小&#…

剑指offer——JZ32 从上往下打印二叉树 解题思路与具体代码【C++】

一、题目描述与要求 从上往下打印二叉树_牛客题霸_牛客网 (nowcoder.com) 题目描述 不分行从上往下打印出二叉树的每个节点&#xff0c;同层节点从左至右打印。例如输入{8,6,10,#,#,2,1}&#xff0c;如以下图中的示例二叉树&#xff0c;则依次打印8,6,10,2,1(空节点不打印&a…

docker运维之自定义网络配置

自定义网络配置讲解与实操 docker中的容器有独立的隔离空间&#xff0c;那么&#xff0c;它们能不能通过网络相互访问呢&#xff1f; 答案是可以的&#xff01;作者在之前Redis篇中使用docker配置了主从、cluster集群&#xff0c;当时的做法是利用每个容器的ip地址和端口创建相…

一对一直播实时美颜SDK算法背后的技术原理与实现

美颜技术已经成为了现代社交媒体和视频通信的不可或缺的一部分。用户希望看起来最好&#xff0c;而实时美颜技术通过在实时视频中平滑皮肤、修复瑕疵以及增强特征来满足这一需求。这种技术的核心是实时美颜SDK&#xff0c;它蕴含着精密的算法和工程实现&#xff0c;本文将深入探…

成功解决@Async注解不生效的问题,异步任务处理问题

首先&#xff0c;有这样一个异步监听方法 然后配置好了异步线程池 package com.fdw.study.config;import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Conf…

维修派单系统好用吗?如何实现数字化后勤管理?

在当今社会&#xff0c;各种设备和设施的正常运转对于单位和组织来说至关重要。然而&#xff0c;由于各种因素的影响&#xff0c;设备和设施在日常运行过程中难免会出现故障。这时&#xff0c;高效的维修服务就显得尤为重要。而“的修”维修派单系统&#xff0c;就是一种专为维…

Java卷上天,可以转行干什么?

小刚是某名企里的一位有5年经验的高级Java开发工程师&#xff0c;每天沉重的的工作让他疲惫不堪&#xff0c;让他萌生出想换工作的心理&#xff0c;但是转行其他工作他又不清楚该找什么样的工作 因为JAVA 这几年的更新实在是太太太……快了&#xff0c;JAVA 8 都还没用多久&am…

怎么压缩pdf文件?分享缩小pdf文件的简单方法

在我们的日常生活和工作中&#xff0c;往往需要处理大量的PDF文件&#xff0c;而很多时候这些文件的大小会成为传输和存储的难题。为了解决这个问题&#xff0c;下面我们将介绍三种方法来压缩PDF文件&#xff0c;一起来看看吧~ 一、嗨格式压缩大师 首先&#xff0c;最简单也是…

Spring: @ComponentScan注解,不设置basePackages时,为什么会扫描该注解所在的包?

ComponentScanAnnotationParser类的parse方法&#xff1a; 可以看到如果没配置basePackages&#xff0c;会调用ClassUtils的静态方法getPackageName将声明ComponentScan的类所在的包添加到basePackages中去

山西电力市场日前价格预测【2023-10-08】

日前价格预测 预测说明&#xff1a; 如上图所示&#xff0c;预测明日&#xff08;2023-10-08&#xff09;山西电力市场全天平均日前电价为258.40元/MWh。其中&#xff0c;最高日前电价为496.19元/MWh&#xff0c;预计出现在18:45。最低日前电价为0.00元/MWh&#xff0c;预计出…

TouchDesigner专题_LeapMotion安装(win10系统)

如果你已经走到了其他教程的最后一步&#xff0c;出现报错 Connection failed.Ensure the device is connected and the correct version (4.1) of Leap Motion driver is installed 直接跳到最后一节就能解决 一、LeapMotion硬件 硬件部分很简单&#xff0c;就和手机数据线…

文件服务器审核

数据是所有组织的命脉&#xff0c;保护存储此重要资产的存储库对于防止不必要的暴露、盗窃和丢失至关重要&#xff0c;管理员和数据所有者应增强其文件服务器安全性、满足合规性要求等。 文件服务器审核工具 使用 DataSecurity Plus 无缝监控、警报和报告跨 Windows 文件服务…

深圳市重点实验室申报标准-华夏泰科

深圳市重点实验室&#xff0c;作为中国科技创新的引领者&#xff0c;扮演着关键的角色&#xff0c;旨在推动前沿科学研究和技术创新。申请重点实验室&#xff0c;可为企业带来莫大的荣誉并可为企业及机构提供宝贵的资源和支持&#xff0c;更可获得丰厚的现金支持。那么如何申请…

京东数据分析平台:2023年8月京东奶粉行业品牌销售排行榜

鲸参谋监测的京东平台8月份奶粉市场销售数据已出炉&#xff01; 鲸参谋数据显示&#xff0c;8月份京东平台上奶粉的销售量将近700万件&#xff0c;环比增长约15%&#xff0c;同比则下滑约19%&#xff1b;销售额将近23亿元&#xff0c;环比增长约4%&#xff0c;同比则下滑约3%。…

HiveServer2 Service Crashes(hiveServer2 服务崩溃)

Troubleshooting Hive | 5.9.x | Cloudera Documentation 原因&#xff1a;别人用的都好好的&#xff0c;我的集群为什么会崩溃&#xff1f; 1.hive分区表太多(这里没有说具体数量。) 2.并发连接太多&#xff0c;我记的以前默认是200个连接 3.复杂的hive查询访问表的的分区…

【Vue面试题一】、说说你对 Vue 的理解

文章底部有个人公众号&#xff1a;热爱技术的小郑。主要分享开发知识、学习资料、毕业设计指导等。有兴趣的可以关注一下。为何分享&#xff1f; 踩过的坑没必要让别人在再踩&#xff0c;自己复盘也能加深记忆。利己利人、所谓双赢。 面试官&#xff1a;有使用过vue吗&#xff…

Apple developer证书、标识符和描述文件

Apple developer证书、标识符和描述文件 一、准备1&#xff0c;开发者账号2&#xff0c;CSR文件3&#xff0c;DeviceID 二、过程1&#xff0c;证书&#xff08;Certificates&#xff09;2、标识符&#xff08;Identifiers&#xff09;3、描述文件&#xff08;Profiles&#xff…

C语言之动态内存管理篇(1)

目录 为什么存在动态内存分配 动态内存函数的介绍 malloc free calloc realloc 常见的动态内存错误 今天收假了&#xff0c;抓紧时间写几篇博客。我又来赶进度了。今天我们来讲解动态内存管理。&#x1f197;&#x1f197; 为什么存在动态内存分配 假设我们去实现一个…

Iris for Mac:轻松实现高质量录屏的最佳选择

随着数字化时代的到来&#xff0c;录屏软件已经成为了许多人必备的工具之一。无论是教育、工作还是娱乐&#xff0c;录屏软件都可以帮助我们将重要的操作过程或内容记录下来&#xff0c;并与他人分享。而对于Mac用户来说&#xff0c;一款简单易用且功能强大的录屏软件尤为重要。…