一个级联选择器

166 阅读1分钟

调用组件:

<template>
  <div class="flex">
    <div
      v-for="(box, idx) in resource"
      :key="idx"
      class="c-select-item">
      <select-box
        v-model="box.current"
        :data="box.data"
        :level="box.level"
        :title="box.title"
        @on-child="pushChild"/>
    </div>
  </div>
</template>
<script>
import SelectBox from './select-box.vue'

export default {
  components: {
    SelectBox
  },
  data () {
    return {
      resource: [{
        current: '',
        data: [{ // 数据格式
          id: 1,
          title: 'APP',
          check: false,
          children: [{
            title: '系统安全',
            check: false,
            id: 2
          }]
        }],
        level: 1,
        title: '省份'
      }]
    }
  },
  methods: {
    pushChild (params) {
      const { item, level } = params
      const len = this.resource.length
      if (level <= len - 1) {
        this.resource.splice(level, len - level)
      }
      this.resource.push({
        data: item.children,
        current: '',
        level: level + 1,
        title: '全选'
      })
      this.resource[level - 1].current = item.id
    }
  }
}
</script>
<style scoped>
.c-select-item {
  background-color: #fff;
  border: solid 1px #D3D5E0;
  border-radius: 3px;
}

.flex {
  display: flex;
}
</style>

子组件:select-box.vue

<template>
  <div class="c-select-box">
    <div class="c-check-all flex active-switch">
      <span class="is-active active-text"></span>
      <el-switch
        v-model="all"
        @change="selectAll"
        class="switch-item"
      />
      <div style="width: 120px">{{ title }}</div>
    </div>
    <div class="c-select-content">
      <div
        v-for="(item, i) in data"
        :key="item.Id">
        <div
          v-if="item.children && item.children.length"
          @click="clickCheckItem(item, i)"
          class="c-check-item flex c-check-item-flex"
          :class="[index == i ? 'c-check-item-active' : '']"
        >
          <div class="active-switch flex">
            <span class="el-icon-check is-active" />
            <el-switch
              v-model="item.check"
              class="switch-item"
              @change="selectItem($event, item)"
            />
            <div style="width: 120px">{{ item.title }}</div>
          </div>
          <i class="el-icon-arrow-right" />
        </div>
        <div
          v-else
          class="c-check-item flex">
          <div class="active-switch flex">
            <span class="el-icon-check is-active" />
            <el-switch
              v-model="item.check"
              class="switch-item"
              @change="selectItem($event, item)"
            />
            <div style="width: 120px">{{ item.title }}</div>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>
<script>
export default {
  name: 'SelectBox',
  props: {
    data: {
      type: Array,
      default: () => []
    },
    level: {
      type: Number,
      default: 0
    },
    title: {
      type: String,
      default: ''
    }
  },
  computed: {
    all () {
      const len = this.data.filter((ret) => ret.check).length
      return this.data.length === len
    }
  },
  data () {
    return {
      index: -1,
      result: []
    }
  },
  methods: {
    selectAll (e) {
      this.data.forEach((item) => (item.check = e))
      this.checkAllChild(this.data, this.all)
    },
    selectItem (e, item) {
      // 选中某一个时,子集是跟当前同一状态,子集有一个不选时父级状态是不选
      this.checkChild(e, item)
    },
    checkChild (e, list) {
      if (list.children && list.children.length) {
        list.children.forEach((child) => {
          child.check = e
          this.checkChild(e, child)
        })
      }
    },
    checkAllChild (list, check) {
      // 递归把每一项都改变状态为全选的状态
      list.forEach((item) => {
        if (item.children && item.children.length) {
          item.children.forEach((child) => {
            child.check = check
            this.checkAllChild(item.children, check)
          })
        }
      })
    },
    itemIndeterminate (child) {
      const hasChild = (meta) => {
        return meta.children.reduce((sum, item) => {
          let foundChilds = []
          if (item.check) sum.push(item)
          if (item.children) foundChilds = hasChild(item)
          return sum.concat(foundChilds)
        }, [])
      }
      const some = hasChild(child).length > 0
      const every = child.children && child.children.every((ret) => ret.check)
      return some && !every
    },
    computeChild (list, Vue) {
      list.forEach((item) => {
        if (item.children && item.children.length) {
          const child = item.children
          if (child.every((ret) => ret.check)) {
            item.check = true
          } else {
            item.check = false
          }
          this.computeChild(child, Vue)
        }
      })
    },
    clickCheckItem (item, i) {
      this.index = i
      this.$emit('on-child', { item, level: this.level })
    }
  },
  watch: {
    data: {
      handler (nVal) {
        this.computeChild(nVal, this)
      },
      deep: true
    }
  },
  mounted () {
    this.computeChild(this.data, this)
  }
}
</script>
<style lang="scss" scoped>
// @import "~assets/styles/mixin.styl"
.c-cataract {
  display: block;
  position: absolute;
  top: 0;
  left: 0;
  z-index: 8;
  cursor: pointer;
}

.c-select-content {
  height: 166px;
  overflow: auto;
}

.c-check-all {
  height: 26px;
  position: relative;
  z-index: 9;
  background-color: #f5f6fa;
  padding-left: 10px;
  border-bottom: 1px solid #d3d5e0;
  font-weight: 700;
  color: #0c1733;
  .c-item-select {
    width: 100%;
    height: 100%;
  }
}

.c-check-item-active {
  background: #dce5ff;
  .el-icon-arrow-right {
    color: #4371ff;
  }
}

.c-check-item {
  margin: 0;
  padding: 0 10px;
  position: relative;
  height: 36px;
  line-height: 36px;
  &:hover {
    background-color: #dce5ff;
  }

  &.active {
    .c-check-arrow {
      color: #598fe6 !important;
    }
  }

  .c-check-arrow {
    float: right;
    margin-top: 10px;
  }

  .c-item-checkbox {
    width: 36px;
    height: 36px;
  }
}

.c-check-item-flex {
  justify-content: space-between;
}

.switch-item {
  padding-right: 8px;
}

.active-switch {
  position: relative;
  .is-active {
    position: absolute;
    top: 12px;
    left: 2px;
    z-index: 1;
    color: #fff;
  }
  .active-text {
    font-size: 12px;
    line-height: 12px;
    top: 8px;
    left: 14px;
  }
}

.flex {
  display: flex;
  align-items: center;
}
</style>