Vue3.0 手写省市区三级联动组件

全国地区数据会很大,我们可以直接用这个地址 

https://yjy-oss-files.oss-cn-zhangjiakou.aliyuncs.com/tuxian/area.json

实现的效果如下:

 考虑到城市组件也会在项目其它地方使用,所以把城市组件定义成全局组件。

来复习一下vue3.0中,将组件封装成全局的: 

1.src/components 下新建.vue文件,用来放城市组件,

2.

3.

 知道了实现的效果是: 当我点击这个城市组件的时候,弹层显示,再次点击弹层隐藏,点击组件外面区域也可关闭弹层。

vueuse/core中onClickOutside 可以监听在元素之外点击:

https://vueuse.org/core/onclickoutside/#onclickoutside

使用这个vueuse里面的Api记得先装包!!!!

<template>
  <div class="xtx-city" ref="target">
    <div class="select" @click="toggle" :class="{active: show}">
      <span class="placeholder">请选择配送地址</span>
      <span class="value"></span>
      <i class="iconfont icon-angle-down"></i>
    </div>
    <div class="option" v-show="show">
      <span class="ellipsis" v-for="i in 24" :key="i">北京市</span>
    </div>
  </div>
</template>

<script>
import { ref } from 'vue'
+ import axios from 'axios'
import { onClickOutside } from '@vueuse/core'
export default {
  name: 'XtxCity',
  setup () {
+ const url = 'https://yjy-oss-files.oss-cn-zhangjiakou.aliyuncs.com/tuxian/area.json'
    const show = ref(false)
+  const getCityData = () => {
+  return  axios({ url })
}
    const close = () => {
      show.value = false
    }
    const open = () => {
      show.value = true
    }
    const toggle = () => {
      show.value === true ? close() : open()
    }
    const target = ref(null)
    onClickOutside(target, () => {
      close()
    })
    return { show, toggle, open, close, target,+ getCityData  }
  }
}
</script>
<style scoped lang="less">
.xtx-city {
  display: inline-block;
  position: relative;
  z-index: 400;
  .select {
    border: 1px solid #e4e4e4;
    height: 30px;
    padding: 0 5px;
    line-height: 28px;
    cursor: pointer;
    &.active {
      background: #fff;
    }
    .placeholder {
      color: #999;
    }
    .value {
      color: #666;
      font-size: 12px;
    }
    i {
      font-size: 12px;
      margin-left: 5px;
    }
  }
  .option {
    width: 542px;
    border: 1px solid #e4e4e4;
    position: absolute;
    left: 0;
    top: 29px;
    background: #fff;
    min-height: 30px;
    line-height: 30px;
    display: flex;
    flex-wrap: wrap;
    padding: 10px;
    > span {
      width: 130px;
      text-align: center;
      cursor: pointer;
      border-radius: 4px;
      padding: 0 3px;
      &:hover {
        background: #f5f5f5;
      }
    }
  }
}
</style>

开头说到,渲染数据时,使用的一个地址,那么在代码中如何运用? 使用时,我添加到了上面代码段中,带 + 号的

接下来,考虑一个问题,什么时候发请求,拿全国数据呢? 组件开始创建的时候吗?

答案是:当这个弹出层打开时去发请求,因为用户可能不点击这个城市组件,如果你在组件创建的时候就去发请求,用户不点击,全国数据又很大,浪费资源

 const citydata = ref([ ])  // 接收城市数据

如果用户没有登录:当前商品数据中,后端会传递 userAddresses: null, 此时,我们应该用默认地址:北京市 市辖区 东城区

 

如果用户已经登录:当前商品数据中,后端会传递 userAddresses: 地址数组:

因为我把城市组件单独放在一个文件中了,而上面这幅图 里面的fullLocation是我请求下图左侧数据时返回来的,对于我这个组件来说,属于父传子操作

这段代码是写在父组件中的 

setup(){
   // 默认情况
    const provinceCode = ref('110000')
    const cityCode = ref('119900')
    const countyCode = ref('110101')
    const fullLocation = ref('北京市 市辖区 东城区')
    // 有默认地址
    if (props.data.userAddresses) { // 如果userAddresses有值
    //找userAddresses数组中哪一项的isDefault  ===1
      const defaultAddr = props.data.userAddresses.find(addr => addr.isDefault === 1)
      if (defaultAddr) { // 如果有,取到它的数据项 
        provinceCode.value = defaultAddr.provinceCode
        cityCode.value = defaultAddr.cityCode
        countyCode.value = defaultAddr.countyCode
        fullLocation.value = defaultAddr.fullLocation
      }
    }
}

把数据传给子组件 

 <XxxCity :fullLocation="fullLocation" />

 子组件接收数据渲染

props: {
    fullLocation: { type: String, default: '' }
  },

接下来是实现子组件里面选省市区之后,父组件上的数据同步更新:

setup(props, { emit }){
 const changeResult = reactive({
      provinceCode: '',
      provinceName: '',
      cityCode: '',
      cityName: '',
      countyCode: '',
      countyName: '',
      fullLocation: ''
    })
    const changeItem = (item) => {
      // 省份
      if (item.level === 0) {
        changeResult.provinceCode = item.code
        changeResult.provinceName = item.name
      }
      // 市
      if (item.level === 1) {
        changeResult.cityCode = item.code
        changeResult.cityName = item.name
      }
      // 区
      if (item.level === 2) {
        changeResult.countyCode = item.code
        changeResult.countyName = item.name
        changeResult.fullLocation = `${changeResult.provinceName} ${changeResult.cityName} ${changeResult.countyName}`
        close() // close也是写在了子组件中,所以直接调用即可
        emit('change', changeResult)
      }
    }
//计算属性,根据我点击的省,自动去找市和区
   const currList = computed(() => {
      let currList = citydata.value
      if (changeResult.provinceCode) {
        currList = currList.find(it => it.code === changeResult.provinceCode).areaList
      }
      if (changeResult.cityCode) {
        currList = currList.find(c => c.code === changeResult.cityCode).areaList
      }
      return currList
    })
  // return { currList ...........}  外面使用记得rerurn

}

 父组件接收事件:

 <dd>至 <XtxCity :fullLocation="fullLocation" @change="changeCity"/></dd>
 const changeCity = (result) => {
      provinceCode.value = result.provinceCode
      cityCode.value = result.cityCode
      countyCode.value = result.countyCode
      fullLocation.value = result.fullLocation
    }
 return { ....., changeCity }

每次打开的时候,清空选择结果: 

  • 10
    点赞
  • 55
    收藏
    觉得还不错? 一键收藏
  • 9
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值