Vant Cascader省市区数据太多,点一次掉一次子级数据

背景:之前是后端把所有数据都返回(平级数据),然后前端自己写方法去处理成父子级关系来用的:js方法将省市区的平级数据转换成父子树状数据

因为之前省市区最多只有三级数据,总共不到四千条数据,所以接口返回的速度还好

最近需求是 省市区县,有四级或更多级,数据实在是太多(四万多条数据),如果后端一次性返回,速度实在是太慢,十几秒后才返回,不太行,所以就新出了个接口

解决方案:后端返回一级数据,点一下父级,掉一下接口获取子级数据

父级

vant-address 是封装组件

<template>
    <div>
        <van-button color="linear-gradient(to right, #ff6034, #ee0a24)" @click="showPop">选择地址</van-button>
        <div>选择的地址名字:{{formData.areaName}}</div>
        <div>选择的地址code:{{formData.areaCode}}</div>
        <vant-address :is-areashow="isAreaVisible" @closepop="closePop" @closecancel="closeCancel"></vant-address>
    </div>
</template>
<script>
import VantAddress from '@/路径/vant-address.vue';
export default {
    data () {
        return {
            isAreaVisible: false, // 组件是否显示
            formData: {
                areaCode: '',
                areaName: ''
            }
        }
    },
    components:{
        VantAddress
    },
    methods: {
        showPop(){
            this.isAreaVisible = !this.isAreaVisible
        },
        closePop(areaName,areaIds){
            // 点击 确定事件,获取选中的name和code,并关闭弹框
            console.log(areaName,areaIds);
            this.formData.areaCode = areaIds
            this.formData.areaName = areaName
            this.isAreaVisible = false
        },
        closeCancel(){
            // 点击 取消事件,关闭弹框
            this.isAreaVisible = false
        },

    }
};
</script>
<style scoped='scoped' lang='scss'>
</style>

子级

<!--选择位置组件-->
<template>
  <div>
    <van-popup v-model="isAreashow" round position="bottom">
      <div class="queding" @click="addressSubmit">确定</div>
      <van-cascader
        title="请选择所在地区"
        style="padding-bottom: 80px;"
        :options="options"
        v-model="cascaderValue"
        :field-names="customFieldName"
        @close="onClose"
        @change="onChangeArea"
        @finish="onFinish"
      />
    </van-popup>
  </div>
</template>
<script>

import { areaParentData } from '@/api/地址'; // 接口地址
export default {
  props: {
    isAreashow: {
      type: Boolean,
      default: false
    }
  },
  data(){
    return{
      options: [],
      isAreaVisible: false,
      customFieldName: {
        text: 'name',
        value: 'areaId',
        children: 'children',
        code: 'code'
      },
      cascaderValue: '',
      divisionIds: '',
      divisionNames: ''
    }
  },

  mounted(){
    this.getAreaList();
  },

  methods:{
    addressSubmit(){
      // 确定事件,调用父级的closepop事件,传参name和code
      this.$emit('closepop',this.divisionNames,this.divisionIds)
    },
    onClose(){
      // 取消事件,调用父级的closecancel事件
      this.$emit('closecancel')
    },
    onFinish({ selectedOptions }) {
      // 选中的内容,赋值给name和code
      this.divisionNames = selectedOptions.map(option => option.name).join('-')
      this.divisionIds = selectedOptions.map(option => option.areaId).join(',')
    },
    //获取地区数据
    async getAreaList(){
        // 后端接口,获取省市区一级数据
        const res = await areaParentData({
            parentCode: 0 // 传0是一级数据
        })
        this.ProvinceList = res.data
        this.options = [] // 先清空一下,不然会有重复数据
        res.data.forEach(item => {
          this.options.push({
            name: item.name,
            areaId: item.areaId,
            code: item.code,
            childrenStatus: item.childrenStatus, // 接口返回的是否有子级
            children: item.children || null// 这个很关键
          })
        });
    },
    onChangeArea({ value, selectedOptions, tabIndex }) {
      console.log(value, selectedOptions, tabIndex);
      // value是选中当前列的value(areaId),selectedOptions是选中的数组,tabIndex是选中数组的第几个
      // 需要后台接口返回children数据
      // 拿到数据后,动态添加
      if(selectedOptions[selectedOptions.length-1].childrenStatus){
      // 如果选中数组的最后一个的childrenStatus为true,说明它有子级,就掉接口
        areaParentData({
          parentCode: selectedOptions[selectedOptions.length-1].code // 取得是选中数组里最后一个对象的code
        }).then(res => {
          // 接口返回的数据也就是res.data 赋值给那条数据当children
          this.addTree(selectedOptions, res.data, value)
        })
      }
    },
    addTree(selectedOptions, children, id) {
      selectedOptions.forEach(item => {
        if (item.areaId === id) {
          item.children = children
        }
      })
    }
  }
}
</script>
<style lang="scss" scoped>
  .van-cascader{
    color: #323233 !important;
  }
  .queding{
    color: #fff;
    background-color: #ff4949;
    position: fixed;
    bottom: 0;
    width: 90%;
    // padding: 10px 20px;
    height: 36px;
    line-height: 36px;
    border-radius: 4px;
    font-size: 14px;
    text-align: center;
    z-index: 10;
    left: 50%;
    transform: translate(-50%, -50%);
  }
</style>

父级页面样式:

省市区组件是封装组件

我用的 vant 的 Cascader 组件,封装成一个组件了,点击确定事件和取消事件用的父子事件

因为可能选择二级就完成了,也可能选择三级、四级,所以最下面加了个确定按钮,点击确定才算完成

接口返回的第一级数据

{
  "areaId": "1", // 后面需要传给后端的id
  "parentCode": "0",
  "code": "1100001", // 掉下一级数据传给后端的code
  "name": "北京",
  "status": 0,
  "level": 0,
  "prefixCode": "B",
  "ifHot": 0,
  "logo": "/beijing.jpeg",
  "areaIdGroup": "",
  "areaNameGroup": "",
  "children": "",
  "childrenStatus": true // 是否有子级,如果是true就说明有子级,false就没有子级
},
{
  "areaId": "37",
  "parentCode": "0",
  "code": "130000",
  "name": "河北省",
  "status": 0,
  "level": 0,
  "prefixCode": "H",
  "ifHot": 0,
  "logo": "/heibei.jpg",
  "areaIdGroup": "",
  "areaNameGroup": "",
  "children": "",
  "childrenStatus": true
}

 接口返回的二级数据

<think>好的,我现在要解决用户在使用Vant Cascader组件时遇到的异步加载数据导致弹窗过早关闭的问题。首先,我需要理解用户的问题场景。用户提到当数据量大时,接口响应慢,前端渲染也变慢,导致体验不好,所以他们采用了异步加载。但是,在数据还没有返回的时候,Cascader组件可能因为判断没有而自动关闭弹窗,这显然不是用户期望的行为。 接下来,我需要回忆Vant Cascader组件的工作机制。根据文档,Cascader组件在每次选择某一后,会检查当前选中项是否有children属性。如果没有,就会认为没有下一,自动关闭弹窗。但在异步加载的情况下,数据可能还在请求中,此时children可能还未被填充,导致组件误判,关闭弹窗,这就是问题的根源。 那如何防止在数据加载完成前弹窗关闭呢?首先想到的是,在异步加载数据的过程中,需要让组件保持加载状态,直到数据返回。可能需要手动控制弹窗的显示状态,或者利用组件提供的loading状态。 查阅Vant的文档,发现Cascader组件有一个`lazy`属性,用于开启异步加载模式。同时,每个节可以通过`loading`属性显示加载状态。当用户击某一节时,触发`lazy-load`事件,此时需要在该事件的处理函数中,将当前节标记为加载中,并发送异步请求获取数据。请求返回后,将数据添加到该节的children中,并关闭loading状态。 但是,用户的问题是在数据未返回前,组件可能因为children不存在而关闭弹窗。因此,可能需要确保在加载过程中,组件不会认为没有。可能的解决方案是,在开始加载时,临时给当前节添加一个空的children数组,或者标记为正在加载,这样组件会认为还有,从而保持弹窗开启。 另外,用户提到后端需要返回是否还有下一的判断。这可能涉及到在每次请求数据时,后端返回的数据结构需要包含一个字段(如hasNext),前端根据这个字段来决定是否继续加载,或者结束。如果后端没有正确返回这个字段,可能会导致组件提前结束。 所以,具体的步骤可能是: 1. 在击节时,触发异步加载。 2. 在加载开始前,设置该节的loading状态为true,这样Cascader组件会显示加载中的状态,并保持弹窗开启。 3. 发送异步请求获取数据。 4. 请求完成后,将数据添加到当前节的children中,并设置loading为false。 5. 如果后端返回的数据中没有children,或者hasNext为false,则移除loading状态,让组件正常关闭弹窗。 同时,需要注意在数据请求过程中,用户可能进行其他操作,比如关闭弹窗,这时候可能需要阻止默认的关闭行为,直到数据加载完成。不过,Vant组件可能没有直接提供这样的控制,所以可能需要结合组件的open属性,手动控制弹窗的显示状态。 另外,还需要处理加载失败的情况,避免因为请求失败导致loading状态一直存在,弹窗无法关闭。这时候需要在catch中处理错误,并取消loading状态。 总结可能的解决方案: - 使用lazy-load事件,结合loading状态。 - 在异步加载期间保持loading为true,直到数据返回。 - 确保后端返回的数据结构正确,包含是否有下一的标志。 - 可能需要手动控制弹窗的显示,防止自动关闭。 现在需要验证这些思路是否正确。比如,是否在设置loading为true时,组件会自动保持弹窗开启?查看Vant的文档,Cascader的lazy-load方法中,当调用回调函数并传入children时,如果children为空数组,组件会认为没有。但如果在加载过程中,节有loading: true,组件应该会等待加载完成,不会关闭弹窗。因此,关键是在加载过程中正确管理节的loading状态和children。 所以,正确的代码示例应该是在lazy-load事件处理中,首先设置node的loading为true,然后发起请求,请求成功后,将返回的数据作为children,并设置loading为false。如果数据为空,则children设为空数组,这样组件会自动关闭弹窗。 但用户的问题可能出现在,当数据请求较慢时,击后由于没有及时设置children,组件认为没有而关闭。因此,可能需要确保在击节后,立即将节标记为loading,并且在数据返回前,保持该状态,这样弹窗就不会关闭。 因此,解决方案的核心是正确使用loading状态,并在异步请求处理中管理节的children和loading状态。同时,确保后端返回的数据结构正确,以便前端判断是否有下一数据。 最后,可能需要检查代码实现是否正确,比如是否正确处理了异步回调,是否在请求结束后正确更新了节的children和loading状态。此外,可能需要添加加载中的提示,提升用户体验。</think>### 解决方案 针对Vant Cascader组件在异步加载数据时弹窗过早关闭的问题,核心思路是通过**手动控制节状态**,确保数据加载完成前弹窗保持开启。以下是具体步骤: --- #### 1. **开启异步加载模式** 在组件中启用`lazy`属性,并绑定`lazy-load`事件处理异步加载逻辑: ```html <van-cascader :lazy="true" :value="selectedValue" @lazy-load="onLazyLoad" /> ``` --- #### 2. **控制节加载状态** 在`lazy-load`事件中,通过设置节的`loading`属性为`true`,强制组件进入加载状态,避免弹窗关闭。数据返回后,根据结果更新并关闭加载状态: ```javascript async onLazyLoad(node, resolve) { // 标记为加载中,防止弹窗关闭 node.loading = true; try { // 异步请求数据 const children = await fetchChildrenData(node.value); // 若后端返回无,需显式设置children为空数组 resolve(children.length > 0 ? children : []); // 若后端返回"是否有下一"标记(如hasNext),可动态处理 // if (children.hasNext) resolve(children.list); // else resolve([]); } catch (error) { resolve([]); // 异常时结束加载 } finally { node.loading = false; // 关闭加载状态 } } ``` --- #### 3. **后端数据格式要求** 确保后端返回的数据包含**是否还有下一**的标记(如`hasNext`),或直接返回列表。若为空数组,组件会自动关闭弹窗[^1]: ```json // 示例:返回列表 { "list": [ { "text": "1", "value": 1 }, { "text": "2", "value": 2 } ] } // 或返回是否还有下一 { "hasNext": true, "list": [...] } ``` --- #### 4. **添加加载提示(可选)** 通过CSS或组件配置,在加载时显示提示(如“加载中...”),提升用户体验: ```css .van-cascader__loading { padding: 10px; color: #999; text-align: center; } ``` --- ### 关键逻辑说明 - **`loading`状态的作用**:当`node.loading = true`时,组件会认为当前节仍在加载中,弹窗不会关闭[^1]。 - **空的处理**:若异步加载后为空数组(`resolve([])`),组件会判定无下一并关闭弹窗。 - **错误处理**:请求失败时需调用`resolve([])`,避免组件处于永久加载状态。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值