vue组件tree-select封装

241 阅读1分钟
  • package.json
 "dependencies": {
    "@better-scroll/core": "^2.4.2",
    "core-js": "^3.8.3",
    "vue": "^3.2.13",
  },
  • data.js
{
    value: '110000',
    text: '北京市',
    children: [
      {
        value: '110100',
        text: '北京市',
        children: [
          {
            value: '110101',
            text: '东城区',
          },
          {
            value: '110102',
            text: '西城区',
          },
          {
            value: '110103',
            text: '崇文区',
          },
          {
            value: '110104',
            text: '宣武区',
          },
          {
            value: '110105',
            text: '朝阳区',
          },
          {
            value: '110106',
            text: '丰台区',
          },
          {
            value: '110107',
            text: '石景山区',
          },
          {
            value: '110108',
            text: '海淀区',
          },
          {
            value: '110109',
            text: '门头沟区',
          },
          {
            value: '110111',
            text: '房山区',
          },
          {
            value: '110112',
            text: '通州区',
          },
          {
            value: '110113',
            text: '顺义区',
          },
          {
            value: '110114',
            text: '昌平区',
          },
          {
            value: '110115',
            text: '大兴区',
          },
          {
            value: '110116',
            text: '怀柔区',
          },
          {
            value: '110117',
            text: '平谷区',
          },
          {
            value: '110228',
            text: '密云县',
          },
          {
            value: '110229',
            text: '延庆县',
          },
          {
            value: '110230',
            text: '其它区',
          },
        ],
      },
    ],
  },
  • App.vue
<template>
  <div>
    <Tree :list="list" @on-confirm="onConfirm"></Tree>
  </div>
</template>
<script>
import {cityData} from './data.js'
import Tree from './components/tree.vue'
export default {
  name: 'App',
  components:{
    Tree
  },
  data() {
    return {
      list: cityData
    }
  },
  methods: {
    onConfirm(data) {
      console.log(data)
      window.alert(`选中,${data[0].text},${data[0].value}`)
    }
  }
}
</script>
  • tree.vue
<template>
  <div class="tree-container">
    <div class="button-container">
      <div @click="onLastLevel">返回上一级</div>
      <div @click="onConfirm" v-if="selectAll">确定</div>
    </div>
    <div class="wrapper" ref="wrapper">
      <div class="content">
        <div class="tree-item">
          <tree-item
          v-for="item in dataList"
          :key="item[value]"
          :data="item"
          :checkedList="checkedList"
          @on-select="onSelect"
          @on-show-children="onShowChildren"
        ></tree-item>
        </div>
      </div>
    </div>
  </div>
</template>
<script>
import BScroll from '@better-scroll/core';
import TreeItem from './tree-item.vue';
export default {
  name: 'Tree',
  components: {
    TreeItem,
  },
  provide() {
    return {
      children: this.children,
      value: this.value,
      text: this.text,
      selectAll: this.selectAll
    };
  },
  props: {
    list: {
      type: Array,
      default: () => [],
    },
    children: {
      type: String,
      default: 'children',
    },
    value: {
      type: String,
      default: 'value',
    },
    text: {
      type: String,
      default: 'text',
    },
    selectAll: {
      type: Boolean,
      default: false,
    },
  },
  data() {
    return {
      dataList: [],
      currentList: [],
      checkedList: [],
      currenParentId: -1,
      treeItems: [],
      scroll: null,
    };
  },
  created() {
    this.currentList = this.list.map((item) => {
      item.parentId = -1;
      const objValue = item[this.value];
      if (item.children && item.children.length) {
        item.children.forEach((it) => {
          this.setParentId(objValue, it);
        });
      }
      return item;
    });
    this.dataList = this.currentList;
  },
  mounted() {
    this.$nextTick(() => {
      this.scroll = new BScroll(this.$refs.wrapper, {
        click: true,
        probeType: 3,
        // tap: true,
      });
    });
    console.log('mounted')
  },
  watch: {
    currenParentId(id) {
      console.log(id)
      if (id) {
        this.checkedList = [];
        console.log('currenParentId');
      }
    },
    checkedList(list) {
      this.treeItems.forEach((tItem) => {
        tItem.onChecked(list);
      });
    },
  },
  methods: {
    onSelect(item, isAdd) {
      if (isAdd) {
        this.onAdd(item);
      } else {
        this.onSub(item);
      }
    },
    onShowChildren(children) {
      this.dataList = children;
      this.onscrollTo(0);
    },
    onLastLevel() {
      if (this.dataList[0].parentId > 0) {
        let target = this.findParent(this.dataList[0].parentId, this.list);
        const parentId = target[0].parentId;
        this.currenParentId = parentId;
        this.dataList = this.findParent(parentId, this.list, 'parentId');
        this.onscrollTo(0)
      } else {
        this.currenParentId = -1;
        this.dataList = this.currentList;
      }
    },
    findParent(parentId, list, key) {
      key ? '' : (key = this.value);
      let target = [];
      for (let i = 0; i < list.length; i++) {
        const item = list[i];
        if (item[key] === parentId) {
          target.push(item);
        } else {
          if (item.children && item.children.length) {
            let children = this.findParent(parentId, item.children, key) || [];
            target.push(...children);
          }
        }
      }
      return target;
    },
    setParentId(parentId, obj) {
      obj.parentId = parentId;
      const objValue = obj[this.value];
      if (obj.children && obj.children.length) {
        obj.children.forEach((child) => {
          this.setParentId(objValue, child);
        });
      }
    },
    onAdd(item) {
      if (!this.selectAll) {
        this.checkedList = [item];
        this.onConfirm()
      } else {
        this.checkedList.push(item);
      }
    },
    onSub(item) {
      if (!this.selectAll) {
        this.checkedList = [item];
      } else {
        this.checkedList = this.checkedList.filter((it) => {
          return it[this.value] !== item[this.value];
        });
      }
    },
    onAddTreeItem(item) {
      this.treeItems.push(item);
    },
    onConfirm() {
      console.log(this.checkedList)
      this.$emit('on-confirm', this.checkedList);
    },
    onscrollTo(x) {
      this.scroll.scrollTo(x, 0, 0);
      this.$nextTick(() => {
        this.scroll.refresh();
      });
    },
  },
};
</script>
<style scoped>
.tree-container {
  height: 100%;
}
.button-container {
  display: flex;
  -webkit-box-align: center;
  -webkit-align-items: center;
  align-items: center;
  -webkit-box-pack: justify;
  -webkit-justify-content: space-between;
  justify-content: space-between;
  height: 44px;
  border-bottom: 1px solid #ccc;
}
.button-container div {
  height: 100%;
  padding: 0 16px;
  font-size: 14px;
  line-height: 44px;
  background-color: transparent;
  border: none;
  cursor: pointer;
}
.wrapper {
  height: 40vh;
  margin-top: 10px;
  /* padding-bottom: 10px; */
  position: relative;
  overflow: hidden;
  /* border: 1px solid red; */
}
</style>

  • treeItem.vue
<template>
  <div class="tree-item-container">
    <label
      class="for-input-label"
      :class="{ hasChecked: isChecked }"
      v-if="selectAll"
    >
      <input
        type="checkbox"
        @change="onSelect($event)"
        v-model="isChecked"
        class="multi-checkbox"
      />
    </label>
    <label class="single-label" v-else
      >选择
      <input
        type="checkbox"
        @change="onSelect($event)"
        v-model="isChecked"
        class="single-input"
      />
    </label>

    <span @click="onShowChildren" class="desc-text">{{ data[text] }}</span>
  </div>
</template>
<script>
export default {
  name: 'TreeItem',
  inject: ['children', 'value', 'text', 'selectAll'],
  props: {
    data: {
      type: Object,
      default: () => {},
    },
    checkedList: {
      type: Array,
      default: () => [],
    },
  },
  data() {
    return {
      isChecked: false,
    };
  },
  created() {
    this.$parent.onAddTreeItem(this);
  },
  methods: {
    onSelect(event) {
      console.log('onSelect');
      this.isChecked = event.currentTarget.checked;
      this.$emit('on-select', this.data, this.isChecked);
    },
    onShowChildren() {
      console.log('onShowChildren');
      if (this.data[this.children] && this.data[this.children].length) {
        this.$emit('on-show-children', this.data[this.children]);
      }
    },
    onChecked(list) {
      let flag = false;
      // console.log(this.checkedList)
      list.forEach((item) => {
        if (item[this.value] === this.data[this.value]) {
          flag = true;
        }
      });
      this.isChecked = flag;
    },
  },
};
</script>
<style scoped>
.tree-item-container {
  display: flex;
  align-items: center;
  margin: 20px 0;
}
.for-input-label:before {
  content: '\2713';
  color: transparent;
  display: inline-block;
  border: 1px solid #ccc;
  font-size: 14px;
  line-height: 20px;
  margin: -5px -5px 0 0;
  height: 16px;
  width: 16px;
  text-align: center;
  vertical-align: middle;
  transition: color ease 0.3s;
  border-radius: 2px;
}
.tree-item-container .for-input-label.hasChecked::before {
  color: #fff;
  background-color: #1989fa;
  border-color: #1989fa;
}
input[type='checkbox'] {
  visibility: hidden;
}
.desc-text {
  font-size: 16px;
}
.single-label {
  width: 60px;
  margin-right: 20px;
  border-radius: 2px;
  text-align: center;
  color: white;
  background-color: #1989fa;
}
.single-input {
  width: 0;
  height: 0;
  border: 0;
  padding: 0;
  margin: 0;
}
</style>