自定义多级联动选择器(uni-app)

  • 效果

  • 数据准备
javascript 复制代码
[{"id":1,"name":"测试校区1","children":[{"id":133,"value":"教学楼","children":[{"id":153,"value":"一楼","children":[{"id":156,"value":"教师食堂","children":[]},{"id":155,"value":"学生食堂","children":[]},{"id":154,"value":"体育器材室","children":[]}]},{"id":134,"value":"三楼","children":[{"id":136,"value":"301","children":[]}]}]}]},{"id":39,"value":"测试1234","children":[{"id":121,"value":"二级场地测试","children":[{"id":122,"value":"三级场地测试","children":[]}]}]},{"id":37,"value":"柔道馆","children":[]}]
  • 页面代码
javascript 复制代码
<template>
  <u-popup
      :show.sync="show"
      mode="bottom"
      @click="close()">
    <view
        class="t-pop"
        @tap.stop>
      <view class="pop-main">
        <view class="header">
          <view
              class="cancel"
              @click="close()"
          >取消</view
          >
          <view class="title">{{ title }}</view>
          <view
              class="confirm"
              @click="onOK()"
          ><text>确定</text></view
          >
        </view>
        <view class="select-container">

          <view
              v-for="(tab , index) in tabs"
              :key="index"
              @click="changeSwp(tab.id, index)"
              class="date-c "
              :class="{select_item: tabIndex === tab.id}">
            <text>{{tab.label}}</text>
          </view>

        </view>

        <swiper
            class="swiper"
            :disable-touch="true"
            :current-item-id="tabIndex">
            <swiper-item v-for="(tab, index) in tabs" :key="index" :item-id="tab.id">
              <picker-view
                  v-if="selectOption[index] && selectOption[index].length > 0"
                  :value="tab.select"
                  @change="bindChange($event, index)"
                  :indicator-style="indicatorStyle"
                  class="picker-view">
                <picker-view-column>
                  <view v-for="(item, index) in selectOption[index]"
                      :key="index"
                      class="item">
                    {{item.label}}
                  </view>
                </picker-view-column>

              </picker-view>
              <u-empty
                  v-else
                  text="暂无数据"
                  icon="../../static/empty.png"></u-empty>

            </swiper-item>


        </swiper>
      </view>
    </view>
    <u-toast ref="uToast"></u-toast>

  </u-popup>
</template>

<script>

export default {
  props: {
    show: {
      type: Boolean,
      default: false
    },
    title: {
      type: String,
      default: ''
    },
    data: {
      type: Array,
      default() {
        return []
      },
    },
    params: {
      type: Object,
      default() {
        return {}
      }
    }
  },
  data() {
    return {
      tabs: [],
      tabIndex: '0',
      indicatorStyle: `height: 50px;`,
      selectOption: [],
      parentMap: {},
      isInit: false,

    }
  },
  watch: {
    // 使用watch来响应数据的变化
    show: {
      handler: function (val) {
        if (val) {
          this.init()
        }
      },
      immediate: true
    }
  },
  created() {
    this.init()
  },
  methods: {
    bindChange(e,index){
      console.log('选择第'+index+'列第'+e.detail.value[0]+'行')
      console.log(this.selectOption[index][Number.parseInt(e.detail.value[0])])
      const selected = this.selectOption[index][Number.parseInt(e.detail.value[0])]
      this.tabs[index].label = selected.label
      this.tabs[index].value = selected.value
      // 后面的清空
      for(let i = 0; i < this.tabs.length; i++){
        if(i > index){
          this.tabs[i].label = '请选择'
          this.tabs[i].value = ''
        }
      }
      for(let i = 0; i < this.selectOption.length; i++){
        if(i === index +1){
          this.selectOption[i] = this.parentMap[selected.value] || []

        }else if(i > index + 1){
          this.selectOption[i] = []
        }
      }
    },
    init() {
      if(this.isInit) return
      const maxDepth = this.getDepth(this.data)
      if(maxDepth === 0){
        return
      }
      this.tabs = []
      this.fetchTree(this.data, 0)
      console.log(this.parentMap)

      for(let i = 0; i < maxDepth; i++){
        let tab = {id: i+'', label: '请选择', value: ''}
        if(i === 0){
          this.selectOption[0] = this.parentMap[0] || []
          if(this.selectOption[0].length > 0){
            tab.label = this.selectOption[0][0].label
            tab.value = this.selectOption[0][0].value
          }
        }
        this.tabs.push(tab)
        this.isInit = true
      }

    },
    fetchTree(nodes, parentId){
      if(nodes != null && nodes.length > 0){
        this.parentMap[parentId] = nodes.map(node => {
          return {
            label: node[this.params.label || 'label'],
            value: node[this.params.value || 'value']
          }
        })
        nodes.forEach(node => {
          if(node.children != null && node.children.length > 0){
            this.fetchTree(node.children, node[this.params.value || 'value'])
          }
        })
      }
    },
    getDepth(tree){
      if (!tree || tree.length === 0) return 0;
      return Math.max(...tree.map(node => {
        const childrenDepth = node.children ? this.getDepth(node.children) : 0;
        return 1 + childrenDepth;
      }));
    },
    close() {
      this.$emit('close')
    },
    onOK() {
      const hasSelect = this.tabs.filter(tab => tab.value)
      if(hasSelect.length === 0){
        this.$refs.uToast.show({
          message: '请选择',
          type: 'error'
        })
      }else {
        const last = hasSelect[hasSelect.length - 1 ]
        const children = this.parentMap[last.value]
        if(children && children.length > 0){
          this.$refs.uToast.show({
            message: '请选择到最后一层',
            type: 'error'
          })
        }else{
          this.$emit('submit', {value: last.value, path: hasSelect.map(item => item.label).join('/')})

        }
      }

    },
    changeSwp(i, index){
      this.tabIndex = i
      if (index === 0){
        return
      }
      const parent = this.tabs[index - 1]
      this.selectOption[index] = this.parentMap[parent.value] || []
      if(this.selectOption[index].length >0){
        this.tabs[index].label = this.selectOption[index][0].label
        this.tabs[index].value = this.selectOption[index][0].value
      }

    }
  }
}
</script>

<style lang="scss" scoped>
.t-pop {
  width: 100%;
  display: flex;
  justify-content: center;
  align-items: center;

  .pop-main {
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    align-items: center;
    background-color: #fff;
    border-radius: 24px;
    height: 750rpx;
    width: 100%;
  }
}

.swiper {
  height: 750rpx !important;
  width: 100vw;
}

.header {
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  align-items: center;
  width: 100%;
  padding: 26rpx 0;
  border-bottom: 1rpx solid rgba(#000B2E, .08);

  .cancel {
    padding-left: 32rpx;
    font-size: 28rpx;
    color: rgba(#000B2E, .65);
  }

  .title {
    font-weight: 600;
    font-size: 32rpx;
    color: rgba(#000B2E, .85);
  }

  .confirm {
    padding-right: 32rpx;
    font-size: 28rpx;
    color: #2AD18B;
  }
}

.select-container {
  width: 100%;
  padding: 24rpx 32rpx;
  display: flex;
  align-items: center;

  .date-c {
    margin-left: 32rpx;
    //width: 164rpx;
    position: relative;

    .active-date {
      position: absolute;
      bottom: -24rpx;
      left: 58rpx;
      width: 48rpx;
      height: 6rpx;
      background: #2AD18B;
      border-radius: 4rpx;
    }
  }

  .time-c {
    margin-left: 52rpx;
    width: 76rpx;
    position: relative;

    .active-time {
      position: absolute;
      bottom: -24rpx;
      left: 16rpx;
      width: 48rpx;
      height: 6rpx;
      background: #2AD18B;
      border-radius: 4rpx;
    }
  }
}

.calendar {
  display: flex;
  flex-wrap: wrap;
  flex-direction: row;
  align-items: center;
  width: 100vw;
  position: relative;
}

.ca-top {
  width: 14.2vw;
  height: 112rpx;
  display: flex;
  justify-content: center;
  align-items: center;
  z-index: 10;
  font-size: 32rpx;
  color: rgba(#000B2E, .85);
}

.cell {
  width: 60rpx;
  height: 60rpx;
  display: flex;
  justify-content: center;
  align-items: center;
  align-content: center;
  border-radius: 30rpx;
  font-size: 32rpx;
  color: rgba(#000B2E, .85);
}

.cell-active {
  background-color: #2AD18B;
  color: #fff;
  width: 96rpx;
  height: 112rpx;
  border-radius: 8rpx;
}

.cabg {
  display: flex;
  justify-content: center;
  align-items: center;
  width: 100vw;
  position: absolute;
  z-index: 9;
  height: 320rpx;
  font-size: 320rpx;
  font-weight: bold;
  color: rgba(0, 11, 46, 0.05);
  line-height: 348rpx;
}

.picker-view {
  width: 100%;
  height: 750rpx !important;
  // margin-top: 20rpx;

  ::deep .uni-picker-view-content {
    padding: 0;
  }
}

.item {
  height: 50px;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
}
.select_item {
  color: #2AD18B;
}
</style>
  • 页面引用
javascript 复制代码
      <Cascader
          @close="showType2 = false"
          :data="siteList"
          :show="showType2"
          @submit="siteChange"
          title="选择场地"
          :params="{
              value: 'id',
              label: 'siteName',
          }"
      />
相关推荐
苦学编程的谢18 分钟前
计算机是如何工作的
服务器·前端·javascript
蓉妹妹33 分钟前
React+Taro选择日期组件封装
前端·react.js·前端框架
风口上的吱吱鼠36 分钟前
记录 ubuntu 安装中文语言出现 software database is broken
linux·服务器·前端
whltaoin43 分钟前
前端弹性布局:用Flexbox构建现代网页的魔法指南
前端·弹性布局
GISer_Jing2 小时前
前端工程化和性能优化问题详解
前端·性能优化
学渣y2 小时前
React文档-State数据扁平化
前端·javascript·react.js
njsgcs2 小时前
画立方体软件开发笔记 js three 投影 参数建模 旋转相机 @tarikjabiri/dxf导出dxf
前端·javascript·笔记
一口一个橘子2 小时前
[ctfshow web入门] web71
前端·web安全·网络安全
逊嘘2 小时前
【Web前端开发】HTML基础
前端·html
未脱发程序员4 小时前
【前端】每日一道面试题3:如何实现一个基于CSS Grid的12列自适应布局?
前端·css