组件:Vue实现树形结构,单选、多选、全选、查询功能

文章详细描述了一个Vue组件中如何实现selectTreeDemo的功能,包括组件的模板、数据绑定、事件处理以及props和watch的使用,展示了如何通过点击按钮控制多级树形组件的显示和数据筛选。
摘要由CSDN通过智能技术生成

组件的书写逻辑
请添加图片描述

组件调用demo,selectTreeDemo 为功能组件

<template>
  <div >
    <h1>Test View</h1>
    <!-- <selectTree style="overflow: hidden;"></selectTree> -->
    <button @click="changePikerStyle">click me</button>
    <button @click="selectAll">数组设置</button>
    <van-popup v-model="showPicker" position="bottom" style="">
      <selectTreeDemo ref="selectTree" :treesData="data16.treeData" :isMainStyle="isMain" @selectNodes="onSelectNodes" @changePikerStyle="changePikerStyle" :tempSelect="data16.tempSelect" :show="data16.showPicker">
      </selectTreeDemo>
    </van-popup>
    <br>
    <br>
    <freeze></freeze>
  </div>
</template>

<script>
// import selectTree from '../components/selectTree/index.vue'
import selectTreeDemo from '../components/selectTreeNew/index.vue'
import freeze from '../components/freeze/index.vue'
import { data } from './data.js'
export default {
  components: {
    // selectTree,
    selectTreeDemo,
    freeze
  },
  data () {
    return {
      showPicker: false,
      treeData:  [],
      dataObj: data.second,
      isMain: '',
      data16: {
        treeData: data.arr,
        tempSelect: [],
        showPicker: false
      },
      value: [],
      isAll: true
    }
  },
  methods: {
    onSelectNodes (nodes, isMain) {
      this.data16.tempSelect = nodes.map(node => node.label)
      this.isMain = isMain
      console.log(this.data16.tempSelect,'value')
      this.changePikerStyle()
    },
    changePikerStyle(){
      this.isMain = this.dataObj.isMain
      this.data16.tempSelect = this.data16.tempSelect.length > 0 ? this.data16.tempSelect : this.dataObj.selected
      console.log('changePikerStyle',this.data16) 
      this.showPicker = !this.showPicker
      this.data16.showPicker = this.showPicker
    },
    selectAll(){
      if(this.isAll){
        this.data16.treeData = data.arr
      } else {
        this.data16.treeData = data.arr1
      }
      this.isAll = !this.isAll
      console.log('selectAll',this.isAll,this.data16.treeData)
    }
  }
}
</script>

<style>

</style>

以下为实际的组件功能实现代码

<template>
  <div>
    <div class="van-picker__toolbar">
      <button type="button" class="van-picker__cancel" style="padding: 0;font-size: 14px;" @click="cancel">取消</button>
      <div class="van-ellipsis van-picker__title">门店</div>
      <button type="button" class="van-picker__confirm" style="padding: 0;font-size: 14px; font-weight: 600;" @click="onConfirm">确认</button>
    </div>
    <div style="display: flex;">
      <van-search  v-model="value" shape="round" style="width: 100%;height: fit-content;padding-top: 0;padding-left: 0;padding-right: 0;" placeholder="请输入搜索关键词" input-align="center" @change="onChange" @clear="onChange" />
      <!-- <div style="width: 20%;" @click="onChange">🔍</div>
      <div style="width: 20%;" @click="reset">重置</div> -->
    </div>
    <div class="changeBox">
      <div style="
        height: 24px;
        background: #EBEBEB;
        border-radius: 13px;
        display: flex;">
        <div class="storeStype" :class="{ active: isMain }" @click="changeisMain(true)">主店</div>
        <div class="storeStype" :class="{ active: !isMain }" @click="changeisMain(false)">从属店</div>
      </div>
      <div>
        全选
        <input class="checkbox" style="top: 3px; margin-left: 8px;" type="checkbox" :checked="checkedAll"
          @change="handleCheckedAll" />
      </div>
    </div>
    <div style="overflow-y: auto;height: 200px;margin-top: 5px;">
      <div v-for="child in treeData" :key="child.id">
        <TreeItem1 ref="treeItem" :node="child" :isMain="isMain" @node-checked="onNodeChecked"></TreeItem1>
      </div>
    </div>
  </div>
</template>

<script>
import TreeItem1 from './treeItem.vue'
export default {
  components: {
    TreeItem1
  },
  props: {
    treesData: { type: Array, required: true },
    isMainStyle: { type: Boolean, required: false, default: true },
    tempSelect: { type: Array, required: false },
    show: { type: Boolean, required: false }
  },
  data() {
    return {
      treeData: [],
      tempTreeData: [],
      value: '',
      checkedAll: false,
      isMain: true,
      selectedNodes: [],
      index: 0
    }
  },
  watch: {
    show(val) {
      if (val) {
        console.log('show', val, this.treesData, this.tempSelect)
        this.reset()
      }
    },
    treesData: {
      deep: true, // 添加深度监听
      handler(newVal) {
        this.reset()
      }
    },
    treeData: {
      deep: true, // 添加深度监听
      handler(newVal) {
        console.log('treeData', this.index++, newVal)
      }
    }
  },
  mounted() {
    this.reset()
  },
  methods: {
    // 1.依据输入数据进行重置
    reset() {
      this.value = ''
      this.isMain = this.isMainStyle
      this.tempTreeData = this.transformData(this.treesData)
      this.tempTreeData = Object.freeze(this.transformData(this.treesData))
      this.treeData = this.formTreeData(this.tempTreeData, this.isMain)
      if (this.tempSelect && this.tempSelect.length > 0) {
        this.selectedNodes = this.setSelectedNodes(this.tempSelect, this.treeData, this.isMain)
      } else {
        this.selectedNodes = []
      }
      // 设置treeData的选中状态
      this.setTreeDataChecked(this.treeData, this.selectedNodes, this.isMain)
      console.log('treeDataFr', Object.isFrozen(this.treeData))
      console.log('treeData', this.treeData)
      this.checkedAll = this.isAllChecked(this.treeData, this.selectedNodes, this.isMain)
    },
    // 对数据进行清洗,满足使用标准
    transformData(data) {
      return data.map(shop => {
        const transformedShop = {
          id: shop.shopCode, // 使用 shopCode 作为 id
          label: shop.shopName, // 将 shopName 作为 label
          label1: shop.shopStatus, // 将 shopName 作为 label
          checked: false,
          collapsed: true,
          children: []
        }
        if (shop.rooms && shop.rooms.length > 0) {
          transformedShop.children = shop.rooms.map(room => {
            return {
              id: room.roomCode, // 使用 roomCode 作为 id
              label: room.roomName, // 将 roomName 作为 label
              label1: room.roomStatus, // 将 shopName 作为 label
              parentNode: shop.shopCode, // 将 shopCode 作为 parentNode
              checked: false,
              collapsed: true,
              children: []
            }
          })
        }
        return transformedShop
      })
    },
    // 依据一二级确定结构
    formTreeData (tree, isMain) {
      if (isMain) {
        // let trees = [...tree]
        // return trees
        return tree
      } else {
        return this.getCoTreeData(tree)
      }
    },
    // 获取从店的数据结构,将有从店的节点筛选出来
    getCoTreeData(treeData) {
      let tree = []
      treeData.forEach(node => {
        if (node.children.length) {
          tree.push({ ...node, collapsed: false })
        }
      })
      return tree
    },
    // 设置selected数组的值
    setSelectedNodes (arr, tree, isMain) {
      let arrNode = []
      if (isMain) {
        tree.forEach(node => {
          if (arr.includes(node.label) || arr.includes(node.id)) {
            arrNode.push(node)
          }
        })
      } else {
        tree.forEach(node => {
          node.children.forEach(child => {
            if (arr.includes(child.label) || arr.includes(child.id)) {
              arrNode.push(child)
            }
          })
        })
      }
      return arrNode
    },
    // 对treeData树选中的节点进行设置
    setTreeDataChecked (tree, arr, isMain) {
      console.log('setTreeDataChecked001',tree[0].checked,arr[0],isMain)
      // tree的节点node的label与arr的arrnode的label匹配,设置node的checked为true
      if (isMain) {
        tree.forEach(node => {
          if (arr.some(arrnode => arrnode.label === node.label)) {
            this.$set(node, 'checked', true);
          } else {
            this.$set(node, 'checked', false);
          }
        })
      } else {
        tree.forEach(treeson => {
          treeson.children.forEach(childnode => {
            if (arr.some(arrnode => arrnode.label === childnode.label)) {
              this.$set(childnode, 'checked', true);
            } else {
              this.$set(childnode, 'checked', false);
            }
          })
        })
      }
      console.log('setTreeDataChecked002',tree[0].checked,arr[0],isMain)
    },
    // 判断是否全选
    isAllChecked (tree, selectedNodes, isMain) {
      let allTree = isMain ? tree : tree.map(node => node.children).flat()
      const isAllChecked = allTree.every(node => {
        const correspondingNode = selectedNodes.find(selectedNode => selectedNode.label === node.label)
        console.log('isAllChecked', correspondingNode, node.checked)
        return correspondingNode && node.checked
      })
      return isAllChecked
    },
    // 2.主从店切换
    changeisMain (isMain) {
      if (this.isMain === isMain) return
      this.isMain = isMain
      this.checkedAll = false
      // 重置treeData
      this.treeData = this.formTreeData(this.tempTreeData, isMain)
      // 置空selectedNodes
      this.selectedNodes = []
      this.setTreeDataChecked(this.treeData, this.selectedNodes, isMain)
      // 筛选数据
      this.treeData = this.filterTreeData(this.treeData, this.value)
    },
    // 3.主从店不切换时的筛选
    onChange() {
      // 从tempTreeData中筛选数据
      this.treeData = this.formTreeData(this.tempTreeData, this.isMain)
      this.treeData = this.filterTreeData(this.treeData, this.value)
      this.setTreeDataChecked(this.treeData, this.selectedNodes, this.isMain)
      this.checkedAll = this.isAllChecked(this.treeData, this.selectedNodes, this.isMain)
    },
    // 4.全选
    handleCheckedAll() {
      this.checkedAll = !this.checkedAll
      console.log('handleCheckedAll', this.checkedAll, this.isMain, this.treeData[0].checked)
      // 全选/全不选-设置selecteddNodes
      if (this.isMain) {
        this.treeData.forEach(treenode => {
          treenode.checked = this.checkedAll
          this.nodeChecked(treenode)
        })
      } else {
        this.treeData.forEach(treenode => {
          treenode.children.forEach(child => {
            child.checked = this.checkedAll
            this.nodeChecked(child)
          })
        })
      }
      console.log('handleCheckedAllselectedNodes', this.selectedNodes)
      // 节点去重
      this.selectedNodes = this.selectedNodes.filter((node, index, self) =>
        index === self.findIndex((t) => (
          t.label === node.label
        ))
      )
      console.log('treeDataFr', Object.isFrozen(this.treeData))
      this.setTreeDataChecked(this.treeData, this.selectedNodes, this.isMain)
      console.log('treeDataFr', Object.isFrozen(this.treeData))
      console.log('handleCheckedAlltreeData', this.treeData)
    },
    // 进行选中数据处理
    onNodeChecked (node) {
      this.nodeChecked(node)
      this.checkedAll = this.isAllChecked(this.treeData, this.selectedNodes, this.isMain)
      // console.log('onNodeChecked', this.selectedNodes)
    },
    nodeChecked(node) {
      if (node.checked) {
        this.selectedNodes.push(node)
      } else {
        this.selectedNodes.splice(this.selectedNodes.findIndex(selectedNode => selectedNode.label === node.label), 1)
      }
    },
    // 返回查询的数据
    onConfirm() {
      this.$emit('selectNodes', this.selectedNodes, this.isMain)
      this.value = ''
    },
    cancel() {
      this.$emit('changePikerStyle')
      this.value = ''
      console.log('cancel')
    },
    // 按照value过滤treeData树形结构的数据
    filterTreeData(treeData, value) {
      return treeData.map(node => {
        if (node.label.includes(value)) {
          return {
            ...node,
            children: node.children ? this.filterTreeData(node.children, value) : []
          }
        } else if (node.children) {
          const filteredChildren = this.filterTreeData(node.children, value)
          if (filteredChildren.length > 0) {
            return {
              ...node,
              children: filteredChildren
            }
          }
        }
        return null
      }).filter(Boolean)
    }
  }
}
</script>
<style  scoped>
.changeBox {
  display: flex;
  justify-content: space-between;
  padding: 0 0 8px;
  border-bottom: 1px solid #F2F4F7;
}

.treeBox {
  margin-top: 8px;
  overflow: scroll;
  height: 200px;
}

.treeBox::-webkit-scrollbar {
  display: none;
  /* 隐藏滚动条 */
}

.search-wrapper {
  position: relative;
}

.clear-btn {
  position: absolute;
  right: 10px;
  top: 50%;
  transform: translateY(-50%);
  cursor: pointer;
}

.storeStype {
  text-align: center;
  width: 80px;
  height: 24px;
  line-height: 24px;
  color: #999999;
  border-radius: 13px;
}

.active {
  background: #3C60C3;
  color: #fff;
}

.van-search .van-cell {
  padding: 0 !important;
}
.van-search__content--round{
  background-color: #EDF2FC;
}
</style>

单组件设置

<template>
  <div>
    <!-- 父节点 -->
    <div class="treeItem">
      <div @click="toggleCollapse" style="display: flex; align-items: center; position: relative;">
        <span style="padding: 3px; border-radius: 10%;display: flex;justify-content: center;align-items: center;">
          <img src="../../images/saling.png" style="width: 48px;" v-if="node.label1 === '营业中'">
          <img src="../../images/saling1.png" style="width: 48px;" v-else-if="node.label1 === '待开业'">
          <img src="../../images/saling2.png" style="width: 48px;" v-else-if="node.label1 === '已撤销'">
        </span>
        {{ node.label }}
        <img src="../../images/angle.png" :class="{ 'downIcon--down': !collapsed }" class="downIcon" v-if="node.children.length">
      </div>
      <div>
        <input v-show="isMain" class="checkbox" type="checkbox" :checked="node.checked" @change="handleCheck('',node)" />
      </div>
    </div>

    <!-- 子节点区域 -->
    <transition name="collapse">
      <ul v-show="!collapsed">
        <li v-for="child in node.children" :key="child.id">
          <!-- 递归渲染子节点 -->
          <div class="treeItem">
            <div style="display: flex;align-items: center;">
              <span style="padding: 3px;padding-left: 15px; border-radius: 10%;display: flex;justify-content: center;align-items: center;">
                <img src="../../images/saling.png" style="width: 48px;" v-if="child.label1 === '营业中'">
                <img src="../../images/saling1.png" style="width: 48px;" v-else-if="child.label1 === '待开业'">
                <img src="../../images/saling2.png" style="width: 48px;" v-else-if="child.label1 === '已撤销'">
              </span>
              {{ child.label }}
            </div>
            <input v-if="!isMain" class="checkbox" type="checkbox" :checked="child.checked"
              @change="handleCheck(child, node)" />
          </div>
        </li>
      </ul>
    </transition>
  </div>
</template>

<script>
export default {
  name: 'TreeItem',
  props: {
    node: { type: Object, required: true },
    isMain: { type: Boolean, default: true },
  },
  data() {
    return {
      collapsed: true
    }
  },
  watch: {
    'node.collapsed'(newVal) {
      // console.log('collapsed',this.node.id,newVal)
      this.collapsed = newVal
    },
    'isMain': {
      handler(newVal) {
      }
    },
    'node.checked': {
      handler(newVal) {
        console.log('node.checked',newVal)
      },
      deep: true
    }
  },
  mounted() {
    this.collapsed = this.node.collapsed
  },
  methods: {
    toggleCollapse() {
      this.collapsed = !this.collapsed
    },
    handleCheck(child, node) {
      if (!child) {
        node.checked = !node.checked
        let nodeT = { ...node, checked: node.checked }
        this.$emit('node-checked', nodeT)
      } else {
        child.checked = !child.checked
        child.parentNode = node.id
        this.$emit('node-checked', child)
      }
    }
  }
}
</script>
<style>
.labelStyle {
  font-weight: 400;
  font-family: PingFangSC, PingFang SC;
  font-size: 12px;
  color: #3C60C3;
  line-height: 17px;
  height: 18px;
  text-align: left;
  font-style: normal;
  background: #F0F4FF;
  border-radius: 2px;
  border: 1px solid #3C60C3;
  padding: 0px 2px;
  margin-right: 5px;
}

.labelStyleDyy {
  color: #3CC3C3;
  background: #F0FFFF;
  border: 1px solid #3CC3C3;
}

.labelStyleYcx {
  color: #ABAEB3;
  background: #fff;
  border: 1px solid #ABAEB3;
}

li {
  list-style: none;
  line-height: 25px;
}

.treeItem {
  display: flex;
  justify-content: space-between;
  margin: 8px 0px 16px;
}

.downIcon {
  margin-left: 6px;
  width: 10px;
  height: 10px;
}

.downIcon--down {
  transform: rotate(90deg);
}

.checkbox {
  position: relative;
  /* 隐藏原生复选框 */
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
  /* 设置自定义复选框样式 */
  width: 16px;
  height: 16px;
  border: 1px solid #ccc;
  border-radius: 50%;
  cursor: pointer;
}

/* 自定义复选框选中状态样式 */
.checkbox:checked {
  color: #fff;
  background-color: #007bff;
  border-color: #007bff;
}

.checkbox:checked::after {
  content: '\e728';
  font-family: vant-icon;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}</style>

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
实现Vue Select多选全选功能,你可以使用以下步骤: 1. 在Vue组件中,使用`v-model`指令将选择的值绑定到一个数据属性上,例如`selectedOptions`。 2. 添加一个全选的复选框,用于选择或取消选择所有选项。你可以使用一个单独的数据属性,例如`selectAll`,来保存全选复选框的状态。 3. 在Vue的模板中,为每个选项添加一个复选框,并将其选择状态与对应的选项绑定。使用`v-model`指令将选中状态绑定到一个临时的`isChecked`属性上。 4. 监听全选复选框的变化,当全选复选框的状态改变时,更新每个选项的选择状态。 下面是一个示例代码: ```html <template> <div> <input type="checkbox" v-model="selectAll" @change="selectAllOptions"> <label for="select-all">全选</label> <select multiple v-model="selectedOptions"> <option v-for="option in options" :value="option.value" :key="option.value"> <input type="checkbox" :value="option.value" v-model="option.isChecked"> {{ option.label }} </option> </select> </div> </template> <script> export default { data() { return { options: [ { label: '选项1', value: 'option1', isChecked: false }, { label: '选项2', value: 'option2', isChecked: false }, { label: '选项3', value: 'option3', isChecked: false }, // 添加更多选项... ], selectedOptions: [], selectAll: false, }; }, methods: { selectAllOptions() { this.options.forEach((option) => { option.isChecked = this.selectAll; }); if (this.selectAll) { this.selectedOptions = this.options.map((option) => option.value); } else { this.selectedOptions = []; } }, }, }; </script> ``` 在上面的示例中,我们使用了一个`options`数组来存储所有的选项。每个选项都有一个`isChecked`属性来表示其选择状态。当全选复选框改变时,我们遍历所有选项并更新它们的选择状态。同时,我们根据选择状态更新`selectedOptions`数组,以便在多选框中显示被选择的选项。 希望这能帮助到你实现Vue Select多选全选功能
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值