美文网首页react & vue & angularvuevue
vue3封装城市联动组件

vue3封装城市联动组件

作者: 晨曦的杂货铺 | 来源:发表于2022-06-23 22:14 被阅读0次

    前言

    本文将带你实现地址的选择,将其注册为全局组件,进行三级联动后选定地址。

    一.准备

    1.axios

    利用axios发送请求,拿到全部城市数据。

    • 在项目根目录下打开任意终端,执行yarn add axios命令。
    • 项目中需要发送请求统一统一放在src/api/index.js
    import axios from 'axios'
    // 获取城市数据
    // 1. 数据在哪里?https://yjy-oss-files.oss-cn-zhangjiakou.aliyuncs.com/tuxian/area.json
    // 2. 何时获取?打开城市列表的时候,做个内存中缓存
    // 3. 怎么使用数据?定义计算属性,根据点击的省份城市展示
    export const getCityList = async () => {
      // 添加缓存,防止频繁加载列表数据
      if ((window as any).cityList) {
        // 缓存中已经存在数据了
        return(window as any).cityList
      }
      const ret = await axios.get('https://yjy-oss-files.oss-cn-zhangjiakou.aliyuncs.com/tuxian/area.json')
      // 给window对象添加了一个属性cityList
      if (ret.data) {
        (window as any).cityList = ret.data
      }
      // 把数据返回
      return ret.data
    }
    

    2.vueuse/core

    利用vueuse/core里的onClickOutside,判断点击是否是外部组件,来帮助我们关闭弹层。
    终端中执行yarn add @vueuse/core@5.3.0,这里安装指定版本,各位按需选择。

    二.代码实现

    1.封装

    将其封装为局部组件,文件放在src/libs目录下,新建city.vue组件。
    代码如下(示例):

    <template>
      <div class="city" ref="target">
        <div class="select" @click="toggle" :class="{ active: isShow }">
          <span v-if="!fullLocation" class="placeholder">请选择你所在的城市</span>
          <span v-else class="value">{{ fullLocation }}</span>
          <svg class="icon" aria-hidden="true">
            <use xlink:href="#icon-xiajiantou"></use>
          </svg>
        </div>
        <div class="option" v-show="isShow">
          <div class="loading" v-if="loading"></div>
          <template v-else>
            <span
              @click="changeCity(item)"
              class="ellipsis"
              v-for="item in cityList"
              :key="item.code"
              >{{ item.name }}</span
            >
          </template>
        </div>
      </div>
    </template>
    <script lang="ts">
    import { ref, reactive, computed } from "vue";
    import { onClickOutside } from "@vueuse/core";
    import { getCityList } from "../api/index";
    export default {
      name: "City",
      props: {
        fullLocation: {
          type: String,
          default: "",
        },
      },
      setup(props, { emit }) {
        const isShow = ref(false);
        const loading = ref(false);
        // 城市列表原始数据
        const list = ref([]);
        //选中的省市区
        const changeResult = reactive({
          provinceCode: "",
          provinceName: "",
          cityCode: "",
          cityName: "",
          countyCode: "",
          countyName: "",
          fullLocation: "",
        });
        //选择的城市操作
        const changeCity = (city) => {
          if (city.level === 0) {
            // 点击的是省级单位
            changeResult.provinceCode = city.code;
            changeResult.provinceName = city.name;
          } else if (city.level === 1) {
            // 点击的是市级单位
            changeResult.cityCode = city.code;
            changeResult.cityName = city.name;
          } else if (city.level === 2) {
            // 点击的县级单位:选中最终的省市区数据,并且传递给父组件
            changeResult.countyCode = city.code;
            changeResult.countyName = city.name;
            //组合完整的省区名称
            changeResult.fullLocation = `${changeResult.provinceName}${changeResult.cityName}${changeResult.countyName}`;
            //关闭弹层
            isShow.value = false;
            //把选中的数据传递给子组件
            emit("change-city", changeResult);
          }
        };
    
        //通过计算属性计算当前显示的数据:省级,市级,县级
    
        const cityList = computed(() => {
          let result = list.value;
          //当前点击的是省,那么就计算市集列表
          if (changeResult.provinceCode && changeResult.provinceName) {
            result = result.find(
              (item) => item.code === changeResult.provinceCode
            ).areaList;
          }
          // 当前点击的是市,那么计算县级列表
          if (changeResult.cityCode && changeResult.cityCode) {
            result = result.find(
              (item) => item.code === changeResult.cityCode
            ).areaList;
          }
          return result;
        });
        // 控制城市弹窗的显示和隐藏
        const toggle = () => {
          isShow.value = !isShow.value;
          if (isShow.value) {
            loading.value = true;
            //调用接口之前,把之前的数据置空
            for (const key in changeResult) {
              changeResult[key] = "";
            }
            //弹层显示了,调用接口
            getCityList().then((ret) => {
              list.value = ret;
              loading.value = false;
            });
          }
        };
        //控制点击区域外,隐藏弹层
        const target = ref(null);
        onClickOutside(target, () => {
          isShow.value = false;
        });
        return { isShow, toggle, target, cityList, loading, changeCity };
      },
    };
    </script>
    <style lang="scss">
    .city {
      display: inline-block;
      position: relative;
      z-index: 400;
      .select {
        border: 1px solid #e4e4e4;
        min-height: 60px;
        text-align: center;
        padding: 0 20px;
        width: 350px;
        line-height: 60px;
        cursor: pointer;
        &.active {
          background: #fff;
        }
        .placeholder {
          color: #999;
          font-size: 30px;
        }
        .value {
          color: #999;
          font-size: 30px;
        }
        svg {
          font-size: 28px;
          margin-left: 5px;
        }
      }
      .option {
        width: 600px;
        border: 1px solid #e4e4e4;
        position: absolute;
        left: 0;
        top: 60px;
        background: #fff;
        min-height: 30px;
        line-height: 30px;
        display: flex;
        flex-wrap: wrap;
        padding: 10px;
        .loading {
          height: 290px;
          width: 100%;
          background: url(https://code-1307161657.cos.ap-beijing.myqcloud.com/images%2Fload.gif)
            no-repeat center;
        }
        span {
          width: 166px;
          cursor: pointer;
          text-align: center;
          border-radius: 4px;
          padding: 0 3px;
          &:hover {
            background: #f5f5f5;
          }
        }
      }
    }
    </style>
    

    2. 使用

    在任意.vue结尾的文件中引入city.vue就可以使用。
    如果有默认数据的话,将默认数据传递给子组件。
    代码如下(示例):

    <template>
      <h1>City组件示例</h1>
      <div class="demo">
        <h2>常规使用</h2>
        <div class="demo__component">
          <h3>fullLocation:默认显示的城市; change事件可以获得选择的城市</h3>
          <City :fullLocation="fullLocation" @change-city="changeCity" />
        </div>
        <div class="demo__actions">
          <Button>隐藏代码</Button>
        </div>
        <div v-if="false" class="demo__code">
          <pre>代码</pre>
        </div>
      </div>
    </template>
    <script>
    import { ref } from "vue";
    import City from "../libs/City.vue";
    import Button from "../libs/Button.vue";
    export default {
      components: {
        City,
        Button,
      },
      setup() {
        //默认地址的获取,如果有默认地址就获取默认地址即可,
        const provinceCode = ref("110000");
        const cityCode = ref("119900");
        const countyCode = ref("110101");
        const fullLocation = ref("北京市市辖区东城区");
        // 更新选中的省市区数据
        const changeCity = (cityInfo) => {
          provinceCode.value = cityInfo.provinceCode;
          cityCode.value = cityInfo.cityCode;
          countyCode.value = cityInfo.countyCode;
          fullLocation.value = cityInfo.fullLocation;
        };
        return { fullLocation, changeCity };
      },
    };
    </script>
    <style lang="scss">
    </style>
    

    总结

    把时间用在进步上,其实很多时候,你并不需要做什么,真诚即可。

    相关文章

      网友评论

        本文标题:vue3封装城市联动组件

        本文链接:https://www.haomeiwen.com/subject/lwwmvrtx.html