第一步 先创建组件目录结构 

android 树形结构多选 移动端树形多选_Boo

 

第二步 封装组件

index.vue

<template>

  <div class="tree-select-box">

    <air-cell type="text" :label="label" :placeholder="placeholder" :downIcon="downIcon" v-model="text" @click="onSelect"></air-cell>

    <mt-popup v-model="showPopup" position="bottom" :closeOnClickModal="closeOnClickModal">

      <div class="popup-box">

        <div class="popup-header">

          <span @click="handleCancel">取消</span>

          <span class="title">{{ label }}</span>

          <span @click="handleConfirm">确认</span>

        </div>

        <div class="popup-content">

          <Tree ref="tree" :data="treeData"/>

        </div>

      </div>

    </mt-popup>

  </div>

</template>

<script>

import { MessageBox } from 'mint-ui'

import { filterMultipleData } from '@/utils/air-service/commonMethods.js'

import { mannagerModule, airServiceModule } from '@/service/api/commonApi.js'

import { getStorage } from 'hr-utils'

import AirCell from '@/components/air-service/common/search/cell.vue'

import Tree from './tree.vue'

export default {

  name: 'treeSelect',

  components: {

    AirCell,

    Tree

  },

  props: {

    label: {

      type: String,

      default: '所属部门'

    },

    downIcon: {

      type: Boolean,

      default: true

    },

    // 展示的文字内容

    text: {

      type: [String, Number],

      default: ''

    },

    value: {

      type: [String, Number],

      default: ''

    },

    // 默认是部门

    type: {

      type: String,

      default: ''

    },

    placeholder: {

      type: String,

      default: '请选择'

    },

    loading: {

      type: Boolean,

      default: false

    },

    data: {

      type: Array,

      default () {

        return []

      }

    },

    // 兼容不同字段的tree数据

    treeParams: {

      type: Object,

      default () {

        return {

          id: 'id',

          name: 'label',

          children: 'children',

          isLeaf: ''

        }

      }

    },

    // 是否展开子级

    showTree: {

      type: Boolean,

      default: false

    },

    // 处理单选和多选

    checkbox: {

      type: Boolean,

      default: false

    },

    // 是否可点击遮罩层关闭

    closeOnClickModal: {

      type: Boolean,

      required: false,

      default: function () {

        return true

      },

    },

    // 懒加载

    lazy: {

      type: Boolean,

      default: false,

    },

    // 判断值是否能为空

    require: {

      type: Boolean,

      default: false

    },

    // 判断是否全选

    checkAll: {

      type: Boolean,

      default: false

    }

  },

  provide() {

    return {

      treeParams: this.treeParams,

      showTree: this.showTree,

      checkbox: this.checkbox,

      type: this.type,

      lazy: this.lazy

    }

  },

  data () {

    return {

      showPopup: false,

      treeData: this.data,

      // 用户信息

      userinfo: getStorage('userinfo')

    }

  },

  created () {

    this.initData()

  },

  methods: {

    // 初始话

    async initData () {

      try {

        await this.getShowData()

        if (this.checkAll) {

          await this.$refs.tree.handleCheckAllChange(true)

          this.setUpData()

        }

      } catch (error) {

      }

    },

    // 选择内容

    onSelect () {

      this.showPopup = true

    },

    // 取消按钮

    handleCancel () {

      this.showPopup = false

      this.$emit('cancel')

    },

    // 确认按钮

    handleConfirm () {

      this.selectData = this.$refs.tree.getSelectData()

      if (this.require && !this.selectData.length) {

        MessageBox('温馨提示', '请选择数据')

        return

      }

      this.setUpData()

    },

    // 获取选中的数据并更新

    setUpData () {

      const { id, name } = this.treeParams

      this.selectData = this.$refs.tree.getSelectData()

      let selectId = []

      let selectName = []

      this.showPopup = false

      this.selectData.map(item => {

        selectId.push(item[id])

        selectName.push(item[name])

      })

      this.upData(selectName.join(','), selectId.join(','))

    },

    // 更新值

    upData (text, value) {

      this.$emit('update:text', text)

      this.$emit('update:value', value)

      this.$emit('change', { text: text, value: value })

      this.$nextTick(() => {

        this.$refs.tree.setSelectData(value.split(','))

      })

    },

    // 获取展示数据

    async getShowData () {

      try {

        const { id, name, isLeaf } = this.treeParams

        this.$emit('update:loading', true)

        // 部门

        if (this.type === 'department') {

          const res = await mannagerModule.getUserMDept()

          let deptData = res.data.deptList.map(item => {

            return {

              ...item,

              [isLeaf]: item[isLeaf] === '1' ? true : false

            }

          })

          this.treeData = filterMultipleData([...deptData] || [])

          // 部门单选设置默认值

          if (!this.checkbox) {

            let defaultData = deptData.find(item => item[id] === this.userinfo.pk_dept)

            this.upData(defaultData[name], defaultData[id])

          }

        }

        // 人员类别

        if (this.type === 'category') {

          const res = await airServiceModule.queryReferencePsn({

            refType: 'pk_psncl',

            test: 'test',

          })

          let list = res && res.data && res.data.body ? res.data.body : []

          list = list.slice(1) || [] // 第一条是目录,剔除掉

          this.treeData = list

        }

      } catch (error) {

        console.log(error)

      } finally {

      }

    }

  }

}

</script>

<style lang="less" scoped>

.popup-box {

  width: 100vw;

  height: 50vh;

  display: flex;

  flex-direction: column;

  .popup-header {

    height: 0.9rem;

    display: flex;

    flex-shrink: 0;

    align-items: center;

    justify-content: space-between;

    padding: 0 0.3rem;

    border-bottom: 1px solid #f2f2f2;

    span {

      color: #b4c4d4;

    }

    span.title {

      color: #333333;

    }

  }

  .popup-content {

    flex-grow: 1;

    overflow: auto;

    padding: 0.3rem;

    box-sizing: border-box;

  }

}

</style>

tree.vue

<template>

  <div class="popup-select-tree">

    <div class="select-box" v-if="checkbox">

      <span @click="handleCheckAllChange(true)">全选</span>

      <span @click="handleCheckAllChange(false)">取消全选</span>

    </div>

    <tree-item

      v-for="node in treeData"

      :key="node[treeParams.id]"

      :node="node"

      :treeParams="treeParams"

      @selectNode="selectNode">

    </tree-item>

  </div>

</template>

<script>

import treeItem from './tree-item.vue'

export default {

  name: 'popup-select-tree',

  components: {

    treeItem

  },

  inject: ['treeParams', 'checkbox'],

  props: {

    data: {

      type: Array,

      default () {

        return []

      }

    }

  },

  watch: {

    data: {

      handler(newVal, oldVal) {

        this.treeData = newVal

      },

      deep: true,

    }

  },

  data () {

    return {

      treeData: this.data,

      // 模拟数据

      data2: [{

        id: 1,

        label: '一级 1',

        children: [{

          id: 4,

          label: '二级 1-1',

          children: [{

            id: 9,

            label: '三级 1-1-1'

          }, {

            id: 10,

            label: '三级 1-1-2'

          }]

        }]

      }, {

        id: 2,

        label: '一级 2',

        children: [{

          id: 5,

          label: '二级 2-1'

        }, {

          id: 6,

          label: '二级 2-2'

        }]

      }, {

        id: 3,

        label: '一级 3',

        children: [{

          id: 7,

          label: '二级 3-1'

        }, {

          id: 8,

          label: '二级 3-2',

          children: [{

            id: 11,

            label: '三级 3-2-1'

          }, {

            id: 12,

            label: '三级 3-2-2'

          }, {

            id: 13,

            label: '三级 3-2-3'

          }]

        }]

      }],

      // 存放选择的数据

      selects: [],

      // 用于记录单选上一次选中的节点,作清除处理

      oldNode: null

    }

  },

  methods: {

    // 仅支持父子级非严格关联情况,严格关联未开发

    selectNode (node, type = '') {

      const { children } = this.treeParams

      let nodeChildren = node[children]

      if (this.checkbox) { // 多选

        // 判断是否已选中该节点,有则删除、没有则添加

        if (node.checkbox && !type) {

          this.$set(node, 'checkbox', false) // 取消选中状态

        } else {

          if (!node.checkbox) {

            this.$set(node, 'checkbox', true) // 设置选中状态

          }

          // 选择父级节点默认将子节点也选中

          if (nodeChildren && nodeChildren.length) {

            nodeChildren.map(nodes => {

              this.selectNode(nodes, 'add')

            })

          }

        }

      } else { // 单选

        if (this.oldNode) {

          this.$set(this.oldNode, 'checkbox', false)

        }

        this.$set(node, 'checkbox', true)

      }

      this.oldNode = node

    },

    handleSelectData (data = this.treeData) {

      const { children } = this.treeParams

      data.map(item => {

        if (item.checkbox) {

          this.selects.push({...item})

        }

        if (item[children] && item[children].length) {

          this.handleSelectData(item[children])

        }

      })

    },

    // 获取选中状态的数据

    getSelectData () {

      this.selects = []

      this.handleSelectData()

      return this.selects

    },

    // 设置选中值

    setSelectData (ids, data = this.treeData) {

      const { id, children } = this.treeParams

      data.map(node => {

        let nodeChildren = node[children]

        this.$set(node, 'checkbox', false)

        if (ids.includes(node[id])) {

          this.oldNode = node

          this.$set(node, 'checkbox', true)

        }

        if (nodeChildren && nodeChildren.length) {

          this.setSelectData(ids, nodeChildren)

        }

      })

    },

    // 获取到data内所有的ids、labels,用来做全选、反选处理

    getAllKeyData (data = this.treeData, ids = [], labels = []) {

      const { id, name, children} = this.treeParams

      data.map(item => {

        labels.push(item[name])

        ids.push(item[id])

        if (item[children] && item[children].length) {

          this.getAllKeyData(item[children], ids, labels)

        }

      })

      return {

        labels,

        ids

      }

    },

    // 全选 和 反选

    handleCheckAllChange (bool) {

      if (bool) {

        const { ids } = this.getAllKeyData()

        this.setSelectData(ids)

      } else {

        this.setSelectData([])

      }

    }

  }

}

</script>

<style lang="less" scoped>

.select-box {

  margin-bottom: 15px;

  display: flex;

  span {

    padding: 2px 5px;

    color: #00BBBB;

    cursor: pointer;

    font-size: 14px;

    border: 1px solid #ddd;

    border-radius: 4px;

    margin-right: 10px;

    user-select:none;

  }

}

</style>

tree-item.vue

<template>

  <div class="tree-item-box">

    <div class="tree-item-list">

      <span class="arrow-box">

        <img

          v-if="(node[treeParams.children] && node[treeParams.children].length) || node[treeParams.isLeaf]"

          class="arrow"

          :class="{'arrow-down': show}"  

          :src="!laoding.require ? arrowIcon : loadingIcon"

          alt=""

          @click="showchildren(node)">

      </span>

      <span class="name" @click="selectNode(node)">{{ node[treeParams.name] }}</span>

      <img class="tick" :src="tickTrue" v-show="node.checkbox" alt="" @click="selectNode(node)">

    </div>

    <tree-item

      v-show="show"

      class="tree-children"

      v-for="item in node[treeParams.children]"

      :key="item[treeParams.id]"

      :node="item"

      @selectNode="selectNode"/>

  </div>

</template>

<script>

/*

 * showTree false为默认不展开子级 true默认展开子级

 * checkbox false为单选 true为多选

 */

import { mannagerModule } from '@/service/api/commonApi.js'

export default {

  name: 'tree-item',

  inject: ['treeParams', 'showTree', 'checkbox', 'type', 'lazy'],

  props: {

    node: {

      type: Object,

      default() {

        return {};

      }

    }

  },

  data () {

    return {

      laoding: {

        require: false

      },

      show: this.showTree,

      tick: require('../../../../../static/img/pages/airService/tick.png'),

      tickTrue: require('../../../../../static/img/pages/airService/tick-true.png'),

      arrowIcon: require('../../../../../static/img/pages/airService/arrow.png'),

      loadingIcon: require('../../../../../static/img/pages/airService/loading.gif'),

    }

  },

  methods: {

    // 显示或隐藏子级

    async showchildren (node) {

      try {

        console.log(node)

        await this.hanldeChildrenNode(node)

        this.show = !this.show

      } catch (error) {

        console.log(error)

      }

    },

    // 处理子节点数据

    async hanldeChildrenNode (node) {

      try {

        const { id, children, isLeaf } = this.treeParams

        let childrenNode = []

        // 懒加载情况下,更新node节点数据

        if (this.lazy && node[isLeaf] && node[children] && !node[children].length) {

          if (this.type === 'department') {

            this.laoding.require = true

            const res = await mannagerModule.getUserMDept({'pk_dept': node[id]})

            childrenNode = res.data.deptList.map(item => {

              return {

                ...item,

                [isLeaf]: item[isLeaf] === '1' ? true : false,

                [children]: []

              }

            })

          }

          this.updataChildrenNode(node, childrenNode)

        }

      } catch (error) {

        console.log(error)

      } finally {

        this.laoding.require = false

      }

    },

    // 更新子节点数据

    updataChildrenNode (node, childrenNode) {

      const { children, isLeaf } = this.treeParams

      node[children] = childrenNode

      this.$set(node, node[children], childrenNode)

      if (!childrenNode.length) {

        this.$set(node, isLeaf, false)

      }

    },

    // 选择树节点

    selectNode (node) {

      this.$emit('selectNode', node)

    }

  }

}

</script>

<style lang="less" scoped>

.tree-item-box {

  .tree-item-list {

    display: flex;

    align-items: center;

    height: 0.5rem;

    touch-action: none;

    .arrow-box {

      display: flex;

      align-items: center;

      width: 0.3rem;

      .arrow {

        width: 100%;

        &.arrow-down {

          transform: rotate(90deg);

        }

      }

    }

    .name {

      margin-left: 0.1rem;

      flex-grow: 1;

    }

    .tick {

      width: 0.5rem;

      margin-left: auto;

    }

  }

  .tree-children {

    margin-left: 0.2rem;

  }

}

</style>

 第三步引入组件并使用

android 树形结构多选 移动端树形多选_Boo_02

效果预览

android 树形结构多选 移动端树形多选_Boo_03