uniapp开发是使用多级联动aui-picker

文章介绍了在uni-app移动端开发中,由于uviewUI组件库未提供三级联动功能,开发者如何自定义封装aui-picker组件来实现这一功能。代码示例展示了组件的模板结构、数据处理方法以及事件监听,包括导航栏切换、数据选择和重置等关键逻辑。
摘要由CSDN通过智能技术生成

在公司实际开发移动端,我们经常使用uniapp做多端开发,引入的ui组件不一定有多级联动功能,我们这边是用uview这个ui组件,个人感觉不是很好用
这边要实现一个三级联动,可以在uni-app直接使用插件aui-picker
这边我们是把这个组件源码拷贝出来,自己做了简单的封装

<template name="aui-picker">
  <view
    class="aui-picker"
    v-if="SHOW"
    :class="{
      'aui-picker-in': FADE == 1,
      'aui-picker-out': FADE == 0,
    }"
  >
    <view class="aui-mask" @click.stop="close"></view>
    <view class="aui-picker-main">
      <view class="aui-picker-header">
        <view class="aui-picker-title" v-if="title">{{ title }}</view>
        <view
          class="aui-picker-close iconfont iconclose ms-iconfont ms-icon-close"
          @click.stop="close"
        ></view>
      </view>
      <view class="aui-picker-nav">
        <view
          class="aui-picker-navitem"
          v-if="nav.length > 0"
          v-for="(item, index) in nav"
          :key="index"
          :data-index="index"
          :class="[
            index == navCurrentIndex ? 'active' : '',
            'aui-picker-navitem-' + index,
          ]"
          :style="{ margin: nav.length > 2 ? '0 10px 0 0' : '0 30px 0 0' }"
          @click.stop="_changeNav($event)"
          >{{ item.name }}</view
        >
        <view
          class="aui-picker-navitem"
          :key="nav.length"
          :data-index="nav.length"
          :class="[
            nav.length == navCurrentIndex ? 'active' : '',
            'aui-picker-navitem-' + nav.length,
          ]"
          :style="{ margin: nav.length > 2 ? '0 10px 0 0' : '0 30px 0 0' }"
          @click.stop="_changeNav($event)"
          >请选择</view
        >
        <view
          class="aui-picker-navborder"
          :style="{ left: navBorderLeft + 'px' }"
        ></view>
      </view>

      <view class="aui-picker-content">
        <view class="aui-picker-lists">
          <view
            class="aui-picker-list"
            v-for="(list, index) in queryItems.length + 1"
            :key="index"
            :data-index="index"
            :class="[index == navCurrentIndex ? 'active' : '']"
          >
            <view class="aui-picker-list-warp" v-if="index == 0">
              <view
                v-if="showAllChoose"
                class="aui-picker-item"
                @click.stop="chooseAll"
                >全部</view
              >

              <view
                class="aui-picker-item"
                v-for="(item, key) in items"
                v-if="item.pid == parentId"
                :key="key"
                :data-pindex="index"
                :data-index="key"
                :data-id="item.id"
                :data-pid="item.pid"
                :data-name="item.name"
                :data-level="item.level"
                :class="{
                  active: result.length > index && result[index].id == item.id,
                }"
                :style="{
                  background:
                    touchConfig.index == key && touchConfig.pindex == index
                      ? touchConfig.style.background
                      : '',
                }"
                @click.stop="_chooseItem($event)"
                @touchstart="_btnTouchStart($event)"
                @touchmove="_btnTouchEnd($event)"
                @touchend="_btnTouchEnd($event)"
                >{{ item.name }}</view
              >
            </view>
            <view class="aui-picker-list-warp" v-else>
              <view
                v-if="showAllChoose"
                class="aui-picker-item"
                @click.stop="chooseLevelAll"
                >{{ levelAllTest }}</view
              >
              <view
                class="aui-picker-item"
                v-for="(item, key) in queryItems[index - 1]"
                :key="key"
                :data-pindex="index"
                :data-index="key"
                :data-id="item.id"
                :data-pid="item.pid"
                :data-name="item.name"
                :data-level="item.level"
                :class="{
                  active: result.length > index && result[index].id == item.id,
                }"
                :style="{
                  background:
                    touchConfig.index == key && touchConfig.pindex == index
                      ? touchConfig.style.background
                      : '',
                }"
                @click.stop="_chooseItem($event)"
                @touchstart="_btnTouchStart($event)"
                @touchmove="_btnTouchEnd($event)"
                @touchend="_btnTouchEnd($event)"
                >{{ item.name }}</view
              >
            </view>
          </view>
        </view>
      </view>
    </view>
  </view>
</template>

<script>
export default {
  name: 'aui-picker',
  props: {
    title: {
      //标题
      type: String,
      default: '',
    },
    layer: {
      //控制几级联动,默认无限级(跟随数据有无下级)
      type: Number,
      default: null,
    },
    needRequest: {
      type: Boolean,
      default: true,
    },
    showAllChoose: {
      // 显示全部
      type: Boolean,
      default: false,
    },
    parentId: {
      type: String,
      default: '0001560000000000',
    },
    bizYear: {
      type: Number,
      default: 2022,
    },
    replateIdAndNameField: {
      type: Object,
      default: () => {
        return {
          id: 'bizCode',
          name: 'orgAbbr',
          level: 'orgLevel',
        };
      },
    },
    data: {
      //数据 如:[{id: '', name: '', children: [{id: '', name: ''}]}]
      type: Array,
      default() {
        return [
          // [{id: '', name: '', children: [{id: '', name: ''}]}]
        ];
      },
    },
  },
  data() {
    return {
      SHOW: false,
      FADE: -1,
      nav: [],
      items: [],
      queryItems: [],
      navCurrentIndex: 0,
      navBorderLeft: 40,
      result: [],
      touchConfig: {
        index: -1,
        pindex: -1,
        style: {
          color: '#0070D2',
          background: '#EFEFEF',
        },
      },
      levelResult: [], // 选中的集合
      levelAllTest: '全部',
      _index: 0,
    };
  },
  created() {
    const _this = this;
  },
  watch: {
    data() {
      const _this = this;
      const data = _this.data;
      _this.items = _this._flatten(data, this.parentId);
    },
  },
  mounted() {},
  methods: {
    // 打开
    open() {
      this._index = '0';
      const _this = this;
      _this.reset(); //打开时重置picker
      return new Promise(function (resolve, reject) {
        _this.SHOW = true;
        _this.FADE = 1;
        resolve();
      });
    },
    // 关闭
    close() {
      const _this = this;
      return new Promise(function (resolve, reject) {
        _this.FADE = 0;
        const _hidetimer = setTimeout(() => {
          _this.SHOW = false;
          _this.FADE = -1;
          clearTimeout(_hidetimer);
          resolve();
        }, 100);
      });
    },
    //重置
    reset() {
      const _this = this;
      _this.queryItems = [];
      _this.nav = [];
      _this.navBorderLeft = 40;
      _this.navCurrentIndex = 0;
      _this.result = [];
    },
    //导航栏切换
    _changeNav(e) {
      const _this = this;
      const index = Number(e.currentTarget.dataset.index);
      _this.navCurrentIndex = index;
      const _el = uni
        .createSelectorQuery()
        .in(this)
        .select('.aui-picker-navitem-' + index);
      _el
        .boundingClientRect((data) => {
          _this.navBorderLeft = data.left + 20;
        })
        .exec();
    },
    //数据选择
    async _chooseItem(e) {
      const _this = this;
      const id = e.currentTarget.dataset.id;
      const name = e.currentTarget.dataset.name;
      const pid = e.currentTarget.dataset.pid;
      const level = e.currentTarget.dataset.level;
      const _arr = [];

      _this.result[_this.navCurrentIndex] = {
        id: id,
        name: name,
        pid: pid,
        level: level,
      };

      const currentResult = _this.result[_this.navCurrentIndex];

      this.levelAllTest = `全${currentResult.name}`;
      this.levelResult = currentResult;

      let curData = undefined;
      if (this.needRequest) {
        curData = await _this._deepQuery(_this.data, id);
        console.log('curData', curData);
      } else {
        curData = _this._deepQuery(_this.data, id).children;
      }

      if (
        (!_this._isDefine(_this.layer) && curData.length > 0) ||
        (_this.navCurrentIndex < Number(_this.layer) - 1 && curData.length > 0)
      ) {
        //有下级数据
        curData.forEach(function (item, index) {
          _arr.push({
            id: item[_this.replateIdAndNameField.id],
            name: item[_this.replateIdAndNameField.name],
            level: item[_this.replateIdAndNameField.level],
            pid: id,
          });
        });
        if (_this.navCurrentIndex == _this.queryItems.length) {
          //选择数据
          _this.queryItems.push(_arr);
          _this.nav.push({ name: name });
        } else {
          //重新选择数据
          _this.queryItems.splice(_this.navCurrentIndex + 1, 1);
          _this.nav.splice(_this.navCurrentIndex + 1, 1);
          _this.queryItems.splice(_this.navCurrentIndex, 1, _arr);
          _this.nav.splice(_this.navCurrentIndex, 1, { name: name });
        }
        _this.navCurrentIndex = _this.navCurrentIndex + 1;
        const _el = uni
          .createSelectorQuery()
          .in(this)
          .select('.aui-picker-navitem-' + _this.navCurrentIndex);
        setTimeout(() => {
          _el
            .boundingClientRect((data) => {
              _this.navBorderLeft = data.left + 20;
            })
            .exec();
        }, 100);
      } else {
        //无下级数据
        _this.close().then(() => {
          _this.$emit('callback', { status: 0, data: _this.result });
        });
      }
    },
    // 选中全部
    chooseAll() {
      this.close().then(() => {
        this.$emit('callback', {
          status: 0,
          data: [{ id: '', name: '全部', pid: '', level: '' }],
        });
      });
    },
    // 选中某一地区全部
    chooseLevelAll() {
      this.close().then(() => {
        this.$emit('callback', {
          status: 0,
          data: [this.levelResult],
        });
      });
    },
    //递归遍历——将树形结构数据转化为数组格式
    _flatten(tree, pid) {
      const fieldId = this.replateIdAndNameField.id;
      const fieldName = this.replateIdAndNameField.name;
      const fieldLevel = this.replateIdAndNameField.level;
      let result = tree.reduce(
        (arr, item) =>
          arr.concat(
            [
              {
                id: item[fieldId],
                name: item[fieldName],
                pid,
                level: item[fieldLevel],
              },
            ],
            this._flatten(item.children, item[fieldId]),
          ),
        [],
      );
      return result;
    },
    //根据id查询对应的数据(如查询id=10100对应的对象)
    _deepQuery(tree, id) {
      // console.log('tree', tree);
      const _this = this;
      if (this.needRequest) {
        // 获取行政区划数据
        return new Promise((resolve, reject) => {
          this.$apis.afm.planImageSearch
            .getCmOrgTreeByUniqueCodeAndlevelEkAndBusiYear({
              initBizCode: id,
              bizCode: id,
              busiYear: this.bizYear,
              orgLevelEk: _this._index < '3' ? tree[0].orgLevel : '6',
            })
            .then((res) => {
              const _rows = JSON.parse(JSON.stringify(res.rows));

              _rows.forEach((i) => {
                i.children = [];
                i.id = i.key;
                i.name = i.title;
                _this._index = i.orgLevel;
              });
              resolve(_rows);
            })
            .catch((err) => {
              // console.log('err', err);
              reject(err);
            });
        });
      } else {
        let isGet = false;
        let retNode = null;
        function deepSearch(tree, id) {
          for (let i = 0; i < tree.length; i++) {
            if (tree[i].children && tree[i].children.length > 0) {
              deepSearch(tree[i].children, id);
            }
            if (id === tree[i][_this.replateIdAndNameField.id] || isGet) {
              isGet || (retNode = tree[i]);
              isGet = true;
              break;
            }
          }
        }
        deepSearch(tree, id);
        return retNode;
      }
    },
    /***判断字符串是否为空
			   @param {string} str 变量
			   @example: aui.isDefine("变量");
			*/
    _isDefine(str) {
      if (
        str == null ||
        str == '' ||
        str == 'undefined' ||
        str == undefined ||
        str == 'null' ||
        str == '(null)' ||
        str == 'NULL' ||
        typeof str == 'undefined'
      ) {
        return false;
      } else {
        str = str + '';
        str = str.replace(/\s/g, '');
        if (str == '') {
          return false;
        }
        return true;
      }
    },
    _btnTouchStart(e) {
      const _this = this,
        index = Number(e.currentTarget.dataset.index),
        pindex = Number(e.currentTarget.dataset.pindex);
      _this.touchConfig.index = index;
      _this.touchConfig.pindex = pindex;
    },
    _btnTouchEnd(e) {
      const _this = this,
        index = Number(e.currentTarget.dataset.index),
        pindex = Number(e.currentTarget.dataset.pindex);
      _this.touchConfig.index = -1;
      _this.touchConfig.pindex = -1;
    },
  },
};
</script>

<style scoped lang="scss">
/* ====================
		多级联动弹窗
	 =====================*/
.aui-picker {
  //   width: 100vw;
  //   height: 100vh;
  opacity: 1;
  position: fixed;
  top: 50vh;
  left: 0;
  z-index: 999;
  /* display: none; */
}
.aui-picker.aui-picker-in {
  -moz-animation: aui-fade-in 0.1s ease-out forwards;
  -ms-animation: aui-fade-in 0.1s ease-out forwards;
  -webkit-animation: aui-fade-in 0.1s ease-out forwards;
  animation: aui-fade-in 0.1s ease-out forwards;
}
.aui-picker.aui-picker-out {
  -moz-animation: aui-fade-out 0.1s ease-out forwards;
  -ms-animation: aui-fade-out 0.1s ease-out forwards;
  -webkit-animation: aui-fade-out 0.1s ease-out forwards;
  animation: aui-fade-out 0.1s ease-out forwards;
}
.aui-mask {
  height: 50vh;
  width: 100vw;
  background-color: rgba(114, 117, 121, 0.3);
  position: absolute;
  top: -50vh;
  z-index: 998;
}
.aui-picker-main {
  width: 100vw;
  height: 70vh;
  background: #fff;
  border-radius: 15px 15px 0 0;
  position: absolute;
  left: 0px;
  bottom: -50vh;
  z-index: 999;
}
.aui-picker.aui-picker-in .aui-picker-main {
  -moz-animation: aui-slide-up-screen 0.2s ease-out forwards;
  -ms-animation: aui-slide-up-screen 0.2s ease-out forwards;
  -webkit-animation: aui-slide-up-screen 0.2s ease-out forwards;
  animation: aui-slide-up-screen 0.2s ease-out forwards;
}
.aui-picker.aui-picker-out .aui-picker-main {
  -moz-animation: aui-slide-down-screen 0.2s ease-out forwards;
  -ms-animation: aui-slide-down-screen 0.2s ease-out forwards;
  -webkit-animation: aui-slide-down-screen 0.2s ease-out forwards;
  animation: aui-slide-down-screen 0.2s ease-out forwards;
}
.aui-picker-header {
  width: 100%;
  min-height: 96rpx;
  position: relative;
  z-index: 999;
  background: #fff;
  border-radius: 30rpx 30rpx 0 0;
}
.aui-picker-header::after {
  // content: '';
  // width: 100%;
  // height: 1px;
  // background: rgba(100, 100, 100, 0.3);
  // -moz-transform: scaleY(0.3);
  // -ms-transform: scaleY(0.3);
  // -webkit-transform: scaleY(0.3);
  // transform: scaleY(0.3);
  // position: absolute;
  // left: 0;
  // bottom: 0;
  // z-index: 999;
}
.aui-picker-title {
  font-weight: bold;
  font-size: 32rpx;
  line-height: 48rpx;
  color: #393b3d;
  text-align: center;
  padding: 26rpx;
  box-sizing: border-box;
  position: absolute;
  left: 100rpx;
  right: 100rpx;
  top: 0;
}
.aui-picker-close.iconfont {
  width: 100rpx;
  height: 96rpx;
  line-height: 96rpx;
  text-align: center;
  // font-size: 48rpx;
  color: #727579;
  border-radius: 0 10px 0 0;
  position: absolute;
  right: 0;
  top: 0;
}
.ms-icon-close {
  font-size: 32rpx;
}
.aui-picker-content {
  width: 100%;
  height: -webkit-calc(100% - 100px);
  height: calc(100% - 100px);
}
.aui-picker-nav {
  width: 100%;
  height: 88rpx;
  text-align: left;
  padding: 0 20px;
  margin: 0 0 1px 0;
  justify-content: flex-start;
  white-space: nowrap;
  box-sizing: border-box;
  position: relative;
}
.aui-picker-nav::after {
  // content: '';
  // width: 100%;
  // height: 1px;
  // background: rgba(100, 100, 100, 0.3);
  // -moz-transform: scaleY(0.3);
  // -ms-transform: scaleY(0.3);
  // -webkit-transform: scaleY(0.3);
  // transform: scaleY(0.3);
  // position: absolute;
  // left: 0;
  // bottom: 0;
  // z-index: 999;
}
.aui-picker-navitem {
  width: 150rpx;
  line-height: 88rpx;
  font-size: 28rpx;
  margin: 0 30px 0 0;
  text-align: center;
  display: inline-block;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
.aui-picker-navitem.active {
  font-weight: bold;
  color: #0070d2;
}
.aui-picker-navborder {
  width: 70rpx;
  height: 2rpx;
  background: #0070d2;
  border-radius: 5px;
  transition: left 0.15s;
  position: absolute;
  left: 40px;
  bottom: 0;
}
.aui-picker-lists {
  width: 100%;
  height: 100%;
  justify-content: space-around;
  white-space: nowrap;
}
.aui-picker-list {
  width: 100%;
  height: 100%;
  overflow: hidden;
  overflow-y: scroll;
  display: none;
  vertical-align: top;
}
.aui-picker-list.active {
  display: inline-block;
}
.aui-picker-list-warp {
  width: 100%;
  height: auto;
  box-sizing: border-box;
  padding: 15px 0;
  display: inline-block;
}
.aui-picker-item {
  height: 96rpx;
  line-height: 96rpx;
  font-size: 32rpx;
  color: #18191a;
  margin: 0 32rpx;
  box-sizing: border-box;
  position: relative;
  border-bottom: 2rpx solid #f5f5f5;
}
.aui-picker-item.active {
  color: #0070d2;
}
.aui-picker-item.active::after {
  content: '✔';
  font-size: 15px;
  color: #0070d2;
  position: absolute;
  top: 0px;
  right: 10px;
}
</style>

父组件中使用

<aui-picker
      ref="picker"
      :title="auiPicker.title"
      :layer="auiPicker.layer"
      :data="auiPicker.data"
      :showAllChoose="true"
      :parentId="auiPicker.parentId"
      :bizYear="auiPicker.bizYear"
      @callback="pickerCallback"
    ></aui-picker>
import AuiPicker from '../../components/aui-picker.vue';

showPicker() {
      const _this = this;
      _this.$refs.picker.open().then(function () {});
    },
    pickerCallback(e) {
      
    },

传入的data是这种格式
在这里插入图片描述
效果图如下
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值