钉钉小程序-实现级联选择

导语: 自定义级联选择器组件,运用组件递归方式实现。

1. 基础树形结构组件

tree.axml
<view class="flex" style="flex-direction:{{direction}}">
  <block a:for="{{list}}" key="{{index}}">
    <view class="tree-wrap">
      <view class="main flex color-99" onTap="select"
        data-name="{{item.TypeName}}" 
        data-cid="{{item.TypeId}}" 
        data-pid="{{item.Superior}}"
        data-len="{{item.children?item.children.length:0}}" 
        a:if="{{item.Superior===pid}}"
      >
        <image mode="scaleToFill" src="/assets/image/icon_{{item.icon}}.png" />
        <view class="{{item.TypeId === cid?'fc-018FFB':''}}">{{item.TypeName}}</view>
      </view>
      <view class="flex" a:if="{{item.children.length>0}}">
        <!-- 组件递归 -->
        <tree list="{{item.children}}" direction="column" onSelect="childSelect" onChildSelect="childSelect" pid="{{pid}}" cid="{{cid}}"></tree>
      </view>
    </view>
  </block>
</view>
tree.js
Component({
  mixins: [],
  data: {},
  props: {
    list: Object, // 列表数据
    icon: '', // 节点图标
    direction: 'row', // 排列方式,row: 横向排列 column:纵向排列
    pid: 0, // 父节点id
    cid: 0, // 子节点id
    onSelect: () => { }, //  根节点选择
    onChildSelect: () => { } // 其他节点选择
  },
  didMount() {
  },
  didUpdate() {
  },
  didUnmount() { },
  methods: {
    select(e) {
      const { cid, name, pid, len } = e.target.dataset
      this.props.onSelect({ cid, name, pid, len })
    },
    childSelect(data) {
      this.props.onChildSelect(data)
    }
  },
});
tree.acss
.main {
  padding: 0 40rpx 30rpx 0;
  font-size: 24rpx;
  align-items: center;
  line-height: 30rpx;
}

.main image {
  margin-right: 20rpx;
  width: 30rpx;
  height: 30rpx;
}

.show-select-wrap {
  overflow-x: auto;
  width: 690rpx;
  margin-bottom: 60rpx;
  padding: 20rpx 30rpx;
  background: #E0F0FC;
  white-space: nowrap;
  font-size: 22rpx;
  box-sizing: border-box;
}

.select-item {
  padding: 0 20rpx;
  border-right: 1px solid #999;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  box-sizing: border-box;
}
tree.json
{
  "component": true,
  "usingComponents": {
    "tree": "/components/cascade/tree/tree"
  }
}

2. 级联弹窗:

弹窗使用基于 Alipay Design 设计规范的小程序扩展组件库 mini-ali-ui

cascade.axml
<popup 
  show="{{show}}" 
  position="bottom"
  disableScroll="{{false}}"
  zIndex="999"
>
  <view class="popup popup-product">
    <view class="popup-header flex">
      <view>级联</view>
      <view class="close-popup" onTap="onClose"></view>
    </view>
    <view class="parents">
      <view class="show-select-wrap" a:if="{{pid!==0&&showSelect}}">
        <view class="flex">
          <view 
            a:for="{{selectList}}" 
            key="{{index}}"
            class="select-item flex-0"
            style="max-width:{{690/selectList.length}}rpx"
            data-pid="{{item.pid}}"
            data-index="{{index}}"
            onTap="selectBack"
          >
            {{item.name}}
          </view>
          <view class="fc-018FFB flex-0" style="padding: 0 30rpx 0 20rpx">请选择</view>
        </view>
      </view>
      <scroll-view scroll-y="{{true}}" class="scroll-view">
        <tree list="{{treeList}}" onSelect="select" onChildSelect="childSelect" pid="{{pid}}" cid="{{initTreeData.typeId}}"></tree>  
      </scroll-view>
    </view>
  </view>
</popup>
cascade.js
Component({
  mixins: [],
  data: {
    list: [], // 列表
    // 选中树节点数据
    treeData: {
      typeId: 0, // 选择节点id
      typeName: '', // 选择节点名称
    },
    // 初始选中树节点数据
    initTreeData: {
      typeId: 0, // 选择节点id
      typePid: 0, // 选择节点父id
      typeName: '', // 选择节点名称
    },
    pid: 0, // 父节点id
    initSelectList: [], // 初始选项切换列表
    selectList: [], // 选项切换列表
    showSelect: false, // 展示选项切换列表
    lock: false, // 级联显示时锁定子组件handleShow方法
  },
  props: {
    show: false, // 显示弹窗
    className: '', // 类名
    treeList: Array, // 树列表
    clear: false, // 清空标记
    onClose: () => { }, // 关闭
    onClear: () => { }, // 清空
    onGetName: () => { }, // 获取选中的名字
  },
  didMount() {
  },
  didUpdate() {
    if (!this.data.lock && this.props.show)  this.handleshow()
    if (this.props.clear)  this.handleClear()
  },
  didUnmount() { },
  methods: {
    /**
    * @description 根节点选择
    * @param {object} data 组件数据 name:节点名称, pid:父节点id, cid:子节点id, len:子节点长度
    */
    select(data) {
      this.handleSelectTree(data)
    },

    /**
    * @description 父/子节点选择
    * @param {object} data 组件数据 name:节点名称, pid:父节点id, cid:子节点id, len:子节点长度
    */
    childSelect(data) {
      this.handleSelectTree(data)
    },

    /**
    * @description 节点选择
    * @param {object} data 组件数据 name:节点名称, pid:父节点id, cid:子节点id, len:子节点长度
    */
    handleSelectTree(data) {
      // len=0表示点击项没有有子节点
      const { name, pid, cid, len } = data
      let selectList = this.data.selectList
      if (len !== 0) {
        selectList.push({
          name,
          cid,
          pid
        })
      }
      this.setData({
        'treeData.typeName': len ? '' : name,
        'treeData.typeId': len ? 0 : cid,
        showSelect: !!len,
        pid: cid,
        selectList: [...selectList]
      }, () => {
        if (len === 0) {
          this.onClose()
          this.setData({
            'initTreeData.typeName': name,
            'initTreeData.typeId': cid,
            'initTreeData.typePid': pid,
            pid,
            initSelectList: [...selectList],
          })
          this.props.onGetName({ typeName: this.data.initTreeData.typeName })
        }
      })
    },

    // 选项返回
    selectBack(e) {
      const { pid, index } = e.target.dataset
      let selectList = this.data.selectList
      selectList.splice(index)
      this.setData({
        pid,
        showSelect: !!pid,
        selectList: [...selectList]
      })
    },

    // 显示
    handleshow() {
      const { show } = this.props
      if (show) {
        const { initTreeData: { typeId, typePid }, initSelectList } = this.data
        this.setData({
          lock: true,
          pid: typePid,
          showSelect: !(typeId === 0),
          selectList: typeId === 0 ? [] : [...initSelectList], // 选项切换列表
        })
      }
    },

    // 关闭
    onClose() {
      this.setData({ lock: false })
      this.props.onClose({ show: false })
    },

    // 清空
    handleClear() {
      this.setData({
        'treeData.typeId': 0, // 选择节点id
        'treeData.typeName': '', // 选择节点名称
        'initTreeData.typeId': 0, // 选择节点id
        'initTreeData.typePid': 0, // 选择节点父id
        'initTreeData.typeName': '', // 选择节点名称
        pid: 0, // 产品类型-父节点id
        initSelectList: [], // 产品类型-初始选项切换列表
        selectList: [], // 产品类型-选项切换列表
      })
      this.props.onClear({ clear: false })
    }
  },
});
cascade.acss
.popup {
  height: 85vh;
  background: #fff;
  border-radius: 34rpx 34rpx 0px 0px;
}

.popup-header {
  padding: 0 30rpx;
  height: 70rpx;
  justify-content: space-between;
  align-items: center;
  font-size: 32rpx;
  line-height: 70rpx;
  font-weight: bold;
}

.popup .popup-header {
  height: 142rpx;
  line-height: 142rpx;
}

.close-popup {
  width: 70rpx;
  height: 70rpx;
  background: url(/assets/image/icon_close.png) no-repeat right center;
  background-size: 22rpx 22rpx;
}

.selected-field {
  background: #E9F5FE!important;
  border: 1px solid #429AFD!important;
  color: #018FFB!important;
}

.popup .scroll-view {
  padding: 30rpx 0;
  height: calc(85vh - 300rpx);
}

.parents {
  padding: 0 30rpx;
}
cascade.json
{
  "component": true,
  "usingComponents": {
    "popup": "mini-ali-ui-rpx/es/popup/index",
    "tree": "/components/cascade/tree/tree"
  }
}

3. 公用样式

app.acss
page {
  overflow: hidden;
}
.flex {
  display: flex;
  display: -webkit-flex;
}

.flex-0 {
  -webkit-box-flex: 0;
  flex-grow: 0;
  flex-shrink: 0;
}

.flex-1 {
  -webkit-box-flex: 1;
  flex-grow: 1;
  flex-shrink: 1;
}

.list-item {
  margin: 0 auto 30rpx;
  width: 690rpx;
  background: #FFFFFF;
  box-shadow: 0px 4rpx 30rpx 0px rgba(19, 106, 203, 0.1);
  border-radius: 20rpx;
  box-sizing: border-box;
}

.item-body {
  padding: 30rpx 30rpx 24rpx;
}

.item-body>view {
  margin-bottom: 18rpx;
  line-height: 40rpx;
}

.position-relative {
  position: relative;
}

.color-99 {
  color: #999
}

.fc-018FFB {
  color: #018FFB;
}

/* 分割线 */
.divide {
  width: 100%;
  height: 20rpx;
  background-color: #f5f5f5;
}

4. 应用

cascade.axml
<view class="scroll-item flex">
  <view class="label flex-0">请选择</view>
  <view class="flex-0 input-wrap">
    <view class="input-select position-relative type-wrap">
      <view class="type-name" onTap="handleShow">{{typeName}}</view>
      <view class="select-icon icon-arrow" a:if="{{typeName===''}}"></view>
      <view 
        class="select-icon icon-clear" 
        a:if="{{typeName!==''}}"
        onTap="handleClear" 
        data-type="typeName"
      ></view>
    </view>
  </view>
</view>

<cascade 
  show="{{show}}" 
  clear="{{clear}}"
  lock="{{lock}}"
  treeList="{{treeList}}" 
  onClose="onClose" 
  onClear="onClear"
  onGetName="getName"
/>
cascade.js
Page({
  data: {
    clear: false, // 清空标记
    typeName: '', // 名称
    show: false, // 显示级联弹窗
    treeList: [], // 树形结构列表
    list: [
      { TypeId: 1, TypeName: "一级 1", Superior: 0 },
      { TypeId: 2, TypeName: "一级 2", Superior: 0 },
      { TypeId: 3, TypeName: "一级 3", Superior: 0 },
      { TypeId: 4, TypeName: "一级 4", Superior: 0 },
      { TypeId: 5, TypeName: "二级 2-1", Superior: 2 },
      { TypeId: 6, TypeName: "二级 2-2", Superior: 2 },
      { TypeId: 7, TypeName: "二级 1-1", Superior: 1 },
      { TypeId: 8, TypeName: "二级 1-2", Superior: 1 },
      { TypeId: 9, TypeName: "二级 1-3", Superior: 1 },
      { TypeId: 10, TypeName: "三级 2-1-1", Superior: 5 },
      { TypeId: 11, TypeName: "四级 2-1-1-1", Superior: 10 },
      { TypeId: 12, TypeName: "四级 2-1-1-2", Superior: 10 },
      { TypeId: 13, TypeName: "四级 2-1-1-3", Superior: 10 },
      { TypeId: 14, TypeName: "四级 2-1-1-4", Superior: 10 },
      { TypeId: 15, TypeName: "五级 2-1-1-4-1", Superior: 14 },
      { TypeId: 16, TypeName: "五级 2-1-1-4-2", Superior: 14 },
      { TypeId: 17, TypeName: "五级 2-1-1-4-3", Superior: 14 },
      { TypeId: 18, TypeName: "四级 2-1-1-5", Superior: 10 },
      { TypeId: 19, TypeName: "四级 2-1-1-6", Superior: 10 },
      { TypeId: 20, TypeName: "三级 2-1-2", Superior: 5 },
      { TypeId: 21, TypeName: "三级 1-1-1", Superior: 7 },
      { TypeId: 22, TypeName: "三级 1-1-2", Superior: 7 },
      { TypeId: 23, TypeName: "三级 1-2-1", Superior: 8 },
      { TypeId: 24, TypeName: "三级 1-2-2", Superior: 8 }],
  },
  onLoad() {
    this.getParentList()
  },

  // 显示级联
  handleShow() {
    this.setData({ show: true })
  },

  // 清空操作
  handleClear() {
    this.setData({ clear: true, typeName: '' })
  },

  // 关闭级联
  onClose(data) {
    const { show } = data
    this.setData({ show })
  },

  // 清空 - 设置清空标记
  onClear(data) {
    const { clear } = data
    this.setData({ clear })
  },

  // 获取选择的内容
  getName(data) {
    const { typeName } = data
    this.setData({ typeName })
  },

  /**
* @description 转换为树结构
* @param {array} list 扁平数据结构列表
*/
  getParentList() {
    const { list } = this.data
    const parentList = list.filter(item => item.Superior == 0);
    const treeList = this.getOrderTreeList(list, parentList);
    this.setData({ treeList })
  },

  /**
   * @description 转换为树结构
   * @param {array} data 子列表
   * @param {array} dataArr 父列表
   */
  getOrderTreeList(data, dataArr) {
    for (let i = 0; i < dataArr.length; i++) {
      let childrenArr = data.filter(item => item.Superior == dataArr[i].TypeId);
      if (dataArr[i].Superior === 0)
        dataArr[i].icon = childrenArr.length > 0 ? 'files' : 'file'
      else
        dataArr[i].icon = childrenArr.length > 0 ? 'files2' : 'file1'
      if (childrenArr.length > 0) {
        dataArr[i].children = childrenArr;
        this.getOrderTreeList(data, childrenArr);
      }
    }
    return dataArr
  }
});

cascade.acss
.scroll-item {
  margin-bottom: 30rpx;
  justify-content: center;
  align-items: center;
}

.label {
  margin-right: 30rpx;
  font-size: 28rpx;
}

.input-select {
  padding: 15rpx 20rpx;
  min-width: 150rpx;
  min-height: 70rpx;
  background: #F6F6F6;
  border-radius: 35px;
  line-height: 40rpx;
  box-sizing: border-box;
  font-size: 24rpx;
  color: #999;
  /* text-align: center; */
  border: 1px solid #F6F6F6;
}

.input-select-wrap {
  display: flex;
  justify-content: flex-end;
}

.input-wrap .input-select {
  width: 514rpx;
}

.type-wrap .select-icon {
  position: absolute;
  top: 50%;
  right: 10%;
  transform: translateY(-50%);
}

.select-icon {
  /* margin-left: 20rpx; */
  width: 40rpx;
  height: 40rpx;
  background-repeat: no-repeat;
  background-position: right center;
}

.icon-arrow {
  background-image: url(/assets/image/icon_arrow1.png);
  background-size: 20rpx 12rpx;
}

.icon-clear {
  background-image: url(/assets/image/icon_clear1.png);
  background-size: 24rpx 24rpx;
}

.type-wrap .select-icon {
  position: absolute;
  top: 50%;
  right: 10%;
  transform: translateY(-50%);
}

.type-name {
  width: 88%;
  min-height: 40rpx;
}
cascade.json
{
  "defaultTitle": "级联",
  "usingComponents": {
    "cascade": "/components/cascade/cascade"
  }
}

5. 效果展示

级联选择

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值