首页 > 其他分享 >使用vue在移动端显示树状多选功能

使用vue在移动端显示树状多选功能

时间:2023-10-13 13:25:44浏览次数:28  
标签:vue const 多选 树状 value item let checkStatus border

最近的项目要求是做一个树状的多选功能,然而该项目是使用vant4作为前端的框架,vant4只有树状单选,没有多选的,那只能自己写一个了。

借鉴博主 https://blog.csdn.net/m0_68428581/article/details/130641982,

我将他的代码转成了vue3的语法,并且根据自己的项目需求进了相关改动,终于实现了这个需求。

首先需要完成两个封装组件的文件:

 ba-tree-picker.vue中:

<!-- 创建返利条件树形多选 --> <!-- 树形层级选择器--> <!-- 支持单选、多选 --> <template>     <div>         <div class="tree-cover" :class="{'show':showDialog}" @click="_cancel"></div>         <div class="tree-dialog" :class="{'show':showDialog}">             <div class="tree-bar">                 <div class="tree-bar-cancel" :style="{'color':cancelColor}"                      hover-class="hover-c" @click="_cancel"                 >取消</div>                 <div class="tree-bar-title" :style="{'color':titleColor}">{{title}}</div>                 <div class="tree-bar-confirm" :style="{'color':confirmColor}"                    hover-class="hover-c" @click="_confirm"                 >                     {{multiple?'确定':''}}                 </div>             </div>             <div class="tree-div">                 <scroll-view class="tree-list" :scroll-y="true">                             <div v-for="(item, index) in treeList" :key="index">                         <div class="tree-item"                           :style="[{paddingLeft: item.level*30 + 'px'}]"                           :class="{itemBorder: border === true,show: item.isShow}"                         >                           <div class="item-label">                               <div class="item-icon uni-inline-item"                                    @click="_onItemSwitch(item, index)"                               >                                     <div v-if="!item.isLastLevel&&item.isShowChild"                                          class="switch-on"                                         :style="{'border-left-color':switchColor}">                                     </div>                                     <div v-else-if="!item.isLastLevel&&!item.isShowChild"                                         class="switch-off"                                         :style="{'border-top-color':switchColor}"                                     ></div>                                     <div v-else class="item-last-dot"                                         :style="{'border-top-color':switchColor}">                                     </div>                              </div>                              <div class="item-name">                                {{item.name+(item.childCount?"("+item.childCount+")":'')}}                              </div>                              <div class="uni-flex-item uni-inline-item"                                  @click="_onItemSelect(item, index)"                              >                                   <div class="item-check"                                      v-if="selectParent?true:item.isLastLevel"                                   >                                      <div class="item-check-yes"                                         v-if="item.checkStatus==1"                                         :class="{'radio':!multiple}"                                         :style="{'border-color':confirmColor}"                                      >                                         <div class="item-check-yes-part"                                             :style="{'background-color':confirmColor}"                                         ></div>                                      </div>                                      <div class="item-check-yes"                                          v-else-if="item.checkStatus==2"                                          :class="{'radio':!multiple}"                                          :style="{'border-color':confirmColor}"                                      >                                         <div class="item-check-yes-all"                                              :style="{'background-color':confirmColor}"                                         ></div>                                      </div>                                      <div class="item-check-no" v-else                                          :class="{'radio':!multiple}"                                          :style="{'border-color':confirmColor}"                                      ></div>                                   </div>                                </div>                             </div>                         </div>                     </div>                 </scroll-view>             </div>         </div>     </div> </template>   <script setup> import scrollView from './scrollView.vue' import { ref,onMounted,computed } from 'vue' const emit = defineEmits(['selectChange']) const props = defineProps({     valueKey: {         type: String,         default: 'id'     },     textKey: {         type: String,         default: 'name'     },     childrenKey: {         type: String,         default: 'children'     },     localdata: {         type: Array,         default: function() {             return []         }     },     localTreeList: { //在已经格式化好的数据         type: Array,         default: function() {             return []         }     },     selectedData: {         type: Array,         default: function() {             return []         }     },     title: {         type: String,         default: ''     },     multiple: { // 是否可以多选         type: Boolean,         default: true     },     selectParent: { //是否可以选父级         type: Boolean,         default: true     },     confirmColor: { // 确定按钮颜色         type: String,         default: '' // #0055ff     },     cancelColor: { // 取消按钮颜色         type: String,         default: '' // #757575     },     titleColor: { // 标题颜色         type: String,         default: '' //     },     switchColor: { // 节点切换图标颜色         type: String,         default: '' // #666     },     border: { // 是否有分割线         type: Boolean,         default: false     },     showState: {         type: Boolean,         default: false     } }) //const showDialog = ref(false) const treeList = ref([]) const _show = () =>{     showDialog.value = true } const _hide = () =>{     showDialog.value = false } const _cancel =() => {     emit("canceled", false) } const _confirm = () => {     let selectedList = []; //如果子集全部选中,只返回父级 id     let selectedNames;     let currentLevel = -1;     treeList.value.forEach((item, index) => {         //console.log(item)         if (currentLevel >= 0 && item.level > currentLevel) {           } else {             if (item.checkStatus === 2) {                 currentLevel = item.level;                 selectedList.push(item.id);                 selectedNames = selectedNames ? selectedNames + ' / ' +                 item.name : item.name;             } else {                 currentLevel = -1;             }         }     })     //console.log('_confirm', selectedList);     _cancel()     emit("selectChange", selectedList, selectedNames); } //格式化原数据(原数据为tree结构) const _formatTreeData = (list = [], level = 0, parentItem, isShowChild = true) => {     let nextIndex = 0;     let parentId = -1;     let initCheckStatus = 0;     if (parentItem) {         nextIndex = treeList.value.findIndex(item =>         item.id === parentItem.id) + 1;         parentId = parentItem.id;         if (!props.multiple) { //单选             initCheckStatus = 0;         } else             initCheckStatus = parentItem.checkStatus == 2 ? 2 : 0;     }     list.forEach(item => {         let isLastLevel = true;         if (item && item[props.childrenKey]) {             let children = item[props.childrenKey];             if (Array.isArray(children) && children.length > 0) {                 isLastLevel = false;             }         }         let itemT = {             id: item[props.valueKey],             name: item[props.textKey],             level,             isLastLevel,             isShow: isShowChild,             isShowChild: false,             checkStatus: initCheckStatus,             orCheckStatus: 0,             parentId,             children: item[props.childrenKey],             childCount:item[props.childrenKey]?item[props.childrenKey].length:0,             childCheckCount: 0,             childCheckPCount: 0         }         if (props.selectedData.indexOf(itemT.id) >= 0) {                 itemT.checkStatus = 2;                 itemT.orCheckStatus = 2;                 itemT.childCheckCount = itemT.children?itemT.children.length :0;                 _onItemParentSelect(itemT, nextIndex);             }             treeList.value.splice(nextIndex, 0, itemT);             nextIndex++;         })         //console.log(this.treeList); } const _onItemSwitch = (item, index) => {// 节点打开、关闭切换     // console.log(item)     //console.log('_itemSwitch')     if (item.isLastLevel === true) {         return;     }     item.isShowChild = !item.isShowChild;     if (item.children) {         _formatTreeData(item.children, item.level + 1, item);         item.children = undefined;     } else {         _onItemChildSwitch(item, index);     } } const _onItemChildSwitch = (item, index) => {     //console.log('_onItemChildSwitch')     const firstChildIndex = index + 1;     if (firstChildIndex > 0)         for (var i = firstChildIndex; i < treeList.value.length; i++) {             let itemChild = treeList.value[i];             if (itemChild.level > item.level) {                 if (item.isShowChild) {                     if (itemChild.parentId === item.id) {                         itemChild.isShow = item.isShowChild;                         if (!itemChild.isShow) {                             itemChild.isShowChild = false;                         }                     }                 } else {                     itemChild.isShow = item.isShowChild;                     itemChild.isShowChild = false;                 }             } else {                 return;             }         } } // 节点选中、取消选中 const _onItemSelect = (item, index) => {     //console.log('_onItemSelect')     //console.log(item)     if (!props.multiple) { //单选         item.checkStatus = item.checkStatus == 0 ? 2 : 0;         treeList.value.forEach((v, i) => {             if (i != index) {                 treeList.value[i].checkStatus = 0             } else {                 treeList.value[i].checkStatus = 2             }         })         let selectedList = [];         let selectedNames;         selectedList.push(item.id);         selectedNames = item.name;         _hide()         emit("selectChange", selectedList, selectedNames);         return     }     let oldCheckStatus = item.checkStatus;     switch (oldCheckStatus) {         case 0:             item.checkStatus = 2;             item.childCheckCount = item.childCount;             item.childCheckPCount = 0;             break;         case 1:         case 2:             item.checkStatus = 0;             item.childCheckCount = 0;             item.childCheckPCount = 0;             break;         default:             break;     }     //子节点 全部选中     _onItemChildSelect(item, index);     //父节点 选中状态变化     _onItemParentSelect(item, index, oldCheckStatus); } const _onItemChildSelect = (item, index) => {     //console.log('_onItemChildSelect')     let allChildCount = 0;     if (item.childCount && item.childCount > 0) {         index++;         while (index < treeList.value.length &&             treeList.value[index].level > item.level)         {             let itemChild = treeList.value[index];             itemChild.checkStatus = item.checkStatus;             if (itemChild.checkStatus == 2) {                 itemChild.childCheckCount = itemChild.childCount;                 itemChild.childCheckPCount = 0;             } else if (itemChild.checkStatus == 0) {                 itemChild.childCheckCount = 0;                 itemChild.childCheckPCount = 0;             }                 index++;         }     } } const _onItemParentSelect = (item, index, oldCheckStatus) => {     //console.log('_onItemParentSelect')     //console.log(item)     const parentIndex = treeList.value.findIndex(itemP => itemP.id ==         item.parentId);         //console.log('parentIndex:' + parentIndex)     if (parentIndex >= 0) {         let itemParent = treeList.value[parentIndex];         let count = itemParent.childCheckCount;         let oldCheckStatusParent = itemParent.checkStatus;         if (oldCheckStatus == 1) {             itemParent.childCheckPCount -= 1;         } else if (oldCheckStatus == 2) {             itemParent.childCheckCount -= 1;         }         if (item.checkStatus == 1) {             itemParent.childCheckPCount += 1;         } else if (item.checkStatus == 2) {             itemParent.childCheckCount += 1;         }         if (itemParent.childCheckCount<=0 && itemParent.childCheckPCount<=0){             itemParent.childCheckCount = 0;             itemParent.childCheckPCount = 0;             itemParent.checkStatus = 0;         } else if (itemParent.childCheckCount >= itemParent.childCount) {             itemParent.childCheckCount = itemParent.childCount;             itemParent.childCheckPCount = 0;             itemParent.checkStatus = 2;         } else {             itemParent.checkStatus = 1;         }         //console.log('itemParent:', itemParent)         _onItemParentSelect(itemParent, parentIndex,             oldCheckStatusParent);     } } // 重置数据 const _reTreeList = () => {     treeList.value.forEach((v, i) => {         treeList.value[i].checkStatus = v.orCheckStatus     }) } const _initTree = () => {     treeList.value = [];     _formatTreeData(props.localdata); } // watch([localdata,localTreeList],(newValue, oldValue)=>{ //  _initTree() //  treeList.value = props.localTreeList; // }) const showDialog = computed(()=>{   return props.showState; }) onMounted(()=>{     _initTree() }) </script> <style scoped>     .tree-cover {         position: fixed;         top: 0px;         right: 0px;         bottom: 0px;         left: 0px;         z-index: 100;         background-color: rgba(0, 0, 0, .4);         opacity: 0;         transition: all 0.3s ease;         visibility: hidden;     }       .tree-cover.show {         visibility: visible;         opacity: 1;     }       .tree-dialog {         position: fixed;         top: 0px;         right: 0px;         bottom: 0px;         left: 0px;         background-color: #fff;         border-top-left-radius: 10px;         border-top-right-radius: 10px;         /* #ifndef APP-NVUE */         display: flex;         /* #endif */         flex-direction: column;         z-index: 102;         top: 20%;         transition: all 0.3s ease;         transform: translateY(100%);     }       .tree-dialog.show {         transform: translateY(0);     }       .tree-bar {         /* */         height: 40px;         padding-left: 15px;         padding-right: 25px;         display: flex;         justify-content: space-between;         align-items: center;         box-sizing: border-box;         border-bottom-width: 1px !important;         border-bottom-style: solid;         border-bottom-color: #f5f5f5;         font-size: 15px;         color: #757575;         line-height: 1;     }       .tree-bar-confirm {         color: #0055ff;         padding: 15px;     }       .tree-bar-title {         color: #007aff     }       .tree-bar-cancel {         color: #757575;         padding: 10px;     }       .tree-div {         flex: 1;         padding: 20px 0 10px 0px;         /* #ifndef APP-NVUE */         display: flex;         /* #endif */         flex-direction: column;         overflow: hidden;         height: 100%;     }       .tree-list {         flex: 1;         height: 100%;         overflow: hidden;     }       .tree-item {         display: flex;         justify-content: space-between;         align-items: center;         line-height: 1;         height: 0;         opacity: 0;         transition: 0.2s;         overflow: hidden;     }       .tree-item.show {         height: 35px;         opacity: 1;         padding: 0 15px 0 0     }       .tree-item.showchild:before {         transform: rotate(90deg);     }       .tree-item.last:before {         opacity: 0;     }       .switch-on {         width: 0;         height: 0;         border-left: 6px solid transparent;         border-right: 6px solid transparent;         border-top: 10px solid #666;     }       .switch-off {         width: 0;         height: 0;         border-bottom: 6px solid transparent;         border-top: 6px solid transparent;         border-left: 10px solid #666;     }       .item-last-dot {         position: absolute;         width: 0px;         height:0px;         border-radius: 100%;         background: #666;     }       .item-icon {         width:0px;         height: 8px;         /* margin-right: 8px; */         padding-right: 20px;         padding-left: 20px;     }       .item-label {         flex: 1;         display: flex;         align-items: center;         height: 100%;         line-height: 1.2;     }       .item-name {         flex: 1;         overflow: hidden;         text-overflow: ellipsis;         white-space: nowrap;         width: 100%;         text-align: left;         font-size: 14px;     }       .item-check {         width: 40px;         height: 40px;         display: flex;         justify-content: center;         align-items: center;     }       .item-check-yes,     .item-check-no {         width: 20px;         height: 20px;         border-top-left-radius: 20%;         border-top-right-radius: 20%;         border-bottom-right-radius: 20%;         border-bottom-left-radius: 20%;         border-top-width: 1px;         border-left-width: 1px;         border-bottom-width: 1px;         border-right-width: 1px;         border-style: solid;         border-color: #0055ff;         display: flex;         justify-content: center;         align-items: center;         box-sizing: border-box;     }       .item-check-yes-part {         width: 12px;         height: 12px;         border-top-left-radius: 20%;         border-top-right-radius: 20%;         border-bottom-right-radius: 20%;         border-bottom-left-radius: 20%;         background-color: #0055ff;     }       .item-check-yes-all {         margin-bottom: 5px;         border: 2px solid #007aff;         border-left: 0;         border-top: 0;         height: 12px;         width: 6px;         transform-origin: center;         /* #ifndef APP-NVUE */         transition: all 0.3s;         /* #endif */         transform: rotate(45deg);     }       .item-check .radio {         border-top-left-radius: 50%;         border-top-right-radius: 50%;         border-bottom-right-radius: 50%;         border-bottom-left-radius: 50%;     }       .item-check .radio .item-check-yes-b {         border-top-left-radius: 50%;         border-top-right-radius: 50%;         border-bottom-right-radius: 50%;         border-bottom-left-radius: 50%;     }       .hover-c {         opacity: 0.6;     }       .itemBorder {         border-bottom: 1px solid #e5e5e5;     } </style>
  scrollView.vue中:
<template>     <div v-bind:class="{'my_scroll_container':true}"        @scroll="getScroll"        :style="`${scrollX==='true'||             scrollX===true?'overflow-x:scroll;overflow-y:hidden':''};${scrollY==='true'||             scrollY===true?'overflow-x:hidden;overflow-y:scroll':''};`"     >         <slot></slot>     </div> </template> <script setup> import { ref,onActivated  } from 'vue' const props = defineProps({     scrollX:{         type:[String,Boolean],         value:false     },     scrollY:{         type:[String,Boolean],         value:false     } }) const scrollTop = ref(0) const getScroll = (e) => { //滚动事件     let wScrollY = e.target.scrollTop; // 当前滚动条位置     scrollTop.value = wScrollY;     let wInnerH = e.target.clientHeight; // 设备窗口的高度(不会变)     let bScrollH = e.target.scrollHeight; // 元素总高度     // if (wScrollY + wInnerH >= bScrollH) {     //     emit("reachBottom");     // } } // onActivated(()=>{ //     emit('getScrollTop',{scrollTop:scrollTop.value}) // }) </script> <style scoped> .my_scroll_container{    width: 100%;   height: 250px; } </style>
父组件使用:
<template> ......             <van-field                 v-model="fieldValue"                 is-link                 readonly                 label="地区"                 placeholder="请选择所在地区"                 @click="showTree"             /> <baTreePicker                 :multiple='true'                 @selectChange="selectChange"                 title="选择所在地区"                 :localdata="copyAreaOptions"                 valueKey="areaId"                 textKey="areaName"                 childrenKey="children"                 @canceled="cancelTree"                 :showState="show"             /> ...... </template> //multiple可以选择单选和多选,copyAreaOptions是需要显示的树状数据,valueKey需要改成你的数据id,textKey需要改成显示的文字,showState是显示状态(显示or隐藏) <script setup> import baTreePicker from "../../component/treePicker/ba-tree-picker.vue" const fieldValue = ref('') const show = ref(false) const copyAreaOptions = ref([]) const cancelTree = (val) => {//关闭弹窗     show.value = val } const showTree = () => {//显示弹窗     show.value = true } const selectChange = (ids, names) => {//监听选择      注意,这里 如果选择的是树的父级,返回的ids也只是一个父级id,如需要获取父级下面的所以子集,则需要对数据进行操作     //console.log(ids, names)     fieldValue.value = names } </script>

 

   

标签:vue,const,多选,树状,value,item,let,checkStatus,border
From: https://www.cnblogs.com/luzanzan/p/17761841.html

相关文章

  • 在vue中使用XLSX库实现Excel的导入导出
    XLSX库是个十分强大的利用前端js处理excel文档的库,官网:https://www.sheetjs.com/vue中安装即使用:安装pnpminstallxlsxpnpminstallxlsx-style-hzx//设置边框与格式用 使用<div@click="exportExcel"style="width:40px;height:40px;position:absolute;z-index......
  • Vue学习笔记(十):全局事件总线
      之前博客中介绍了prop和调用事件的方式在父-子组件之间进行数据,这种方式在只有一层嵌套层时可以使用,但是路过存在多层嵌套,多层多个“兄弟”组件之间传递数据,就非常麻烦。对此,vue中提供了一种全局事件总线机制,数据传递是通过一个空的Vue实例作为中央事件总线,通过它......
  • vue - alias
    3.别名alias配置:build\webpack.base.conf.js:module.exports={...,resolve:{extensions:['.js','.vue','.json'],alias:{'@':resolve('src'),'common':resolve('src......
  • vue el-select/el-cascader获取选中的对象label值
    1.el-select获取选中对象label值<el-form-itemlabel="车辆配置"prop="sales_name"><el-selectv-if="!showSaleNameInput"v-model="form.sales_name"clearableref="itemSelect"......
  • 2. Vue简介
    三大问题(是什么,为什么,怎么办):Vue是帮助前端优化开发的一个工具,是一个框架渐进式框架的概念Vue2-->Vue3......
  • Vue监听路由的变化
    方式一:watch监听通过watch监听,当路由发生变化的时候执行watch:{$route(to,from){console.log(to.path);}},//或者watch:{$route:{handler:function(val,oldVal){console.log(val);},//深度观察监听deep:true}},//......
  • 如何提供 或 访问 vuex 的数据、mutations
    目标:明确如何给仓库提供数据,如何使用仓库的数据一、提供数据:State提供唯一的公共数据源,所有共享的数据都要统一放到Store中的State中存储。在state对象中可以添加我们要共享的数据。 //state:状态,即数据,类似于vue组件中的data(区别:data是组件自己的数据,state是......
  • vue中v-bind绑定元素属性
    vue中v-bind绑定元素属性<html>  <head>  <metacharset="UTF-8">  <metaname="viewport"content="width=device-width,initial-scale=1.0">  <title>vue.js</title>  </head>  <b......
  • vue中的v-text指令和v-html指令区别
    <html>  <head>  <metacharset="UTF-8">  <metaname="viewport"content="width=device-width,initial-scale=1.0">  <title>vue.js</title>  </head>  <body>    &......
  • 【vue2】实现css动效逐个顺序展示的效果(简陋版)
    效果(进入预约里程碑模块后,小人从第一个台阶逐个闪烁出现在当前预约等级之前的台阶并消失,最终停留在当前预约等级的台阶上): 虽然很low但是!就是这么设计的!于是在原本静态的代码上稍加了些修改(为什么,为什么,想问天问大地)首先是台阶部分的代码:<div:class="$style.reserveBox......