vue3封装树型组件

 在此作者基础上封装:https://blog.csdn.net/m0_68428581/article/details/130641982

1.页面结构

<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"
        >
          {{ multiple ? '确定' : '' }}
        </div>
      </div>
      <div class="tree-div">
        <scrollView class="tree-list" :scroll-y="true">
          <div v-for="(item, index) in treeList" :key="index" class="tree-box">
            <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)"
                  ref="switchRef"
                >
                  <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" @click="_onItemSelect(item, index)">
                  {{
                    item.name +
                    (item.childCount ? '(' + item.childCount + ')' : '')
                  }}
                </div>
              </div>
            </div>
          </div>
        </scrollView>
      </div>
    </div>
  </div>

2.逻辑结构

<script setup lang="ts">
import {
  ref,
  onMounted,
  watch,
  defineProps,
  defineEmits,
  defineExpose,
} from 'vue'
import scrollView from '@/components/tree-picker/scrollView.vue'
let showDialog = ref<boolean>(false)
let treeList = ref<any>([])
let $emit = defineEmits(['select-change'])
let switchRef = ref<any>()
let 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,
  },
})
const _hide = () => {
  showDialog.value = false
}
const _cancel = () => {
  _hide()
  // this.$emit('cancel', '')
}
const _show = () => {
  showDialog.value = true
}
//格式化原数据(原数据为tree结构)
const _formatTreeData = (
  list: any = [],
  level = 0,
  parentItem: any,
  isShowChild = true,
) => {
  let nextIndex = 0
  let parentId = -1
  let initCheckStatus = 0
  if (parentItem) {
    nextIndex =
      treeList.value.findIndex((item: any) => item.id === parentItem.id) + 1
    parentId = parentItem.id
    if (!props.multiple) {
      //单选
      initCheckStatus = 0
    } else initCheckStatus = parentItem.checkStatus == 2 ? 2 : 0
  }
  list.forEach((item: any) => {
    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.groupId,
      name: item.groupName,
      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(treeList.value)
}
// 节点打开、关闭切换
const _onItemSwitch = (item: any, index: number) => {
  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: any, index: any) => {
  // 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: any, index: any) => {
  //console.log('_onItemSelect')
  //console.log(item)
  if (!props.multiple) {
    //单选
    item.checkStatus = item.checkStatus == 0 ? 2 : 0

    treeList.value.forEach((itemv: any, i: number) => {
      console.log(itemv)

      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('select-change', 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: any, index: any) => {
  //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: any, index: any, oldCheckStatus: any) => {
  //console.log('_onItemParentSelect')
  //console.log(item)
  console.log(index)
  const parentIndex = treeList.value.findIndex(
    (itemP: any) => 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: any, i: any) => {
    treeList.value[i].checkStatus = v.orCheckStatus
  })
}
const treeToArray = (tree: any) => {
  tree.forEach((item: any, index: number) => {
    if (item.children) {
      _onItemSwitch(item, index)
      item.children = undefined
      treeToArray(treeList.value)
    }
  })
}
const _initTree = () => {
  treeList.value = []
  _formatTreeData(props.localdata, 0, '', true)
  if (treeList.value.length > 0) {
    treeToArray(treeList.value)
  }
}

defineExpose({
  showDialog,
  _show,
  _formatTreeData,
  treeList,
  _hide,
  _cancel,
  _onItemSwitch,
  _onItemChildSwitch,
  _onItemSelect,
  _onItemChildSelect,
  _onItemParentSelect,
  _reTreeList,
  _initTree,
})
onMounted(() => {
  _initTree()
})
</script>

<style lang="scss" scoped>
.tree-cover {
  position: fixed;
  top: 0px;
  right: 0px;
  bottom: 0px;
  left: 0px;
  z-index: 100;
  background-color: rgba(0, 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 {
  /* background-color: #fff; */
  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;
  /* #ifndef APP-NVUE */
  display: flex;
  /* #endif */
  flex-direction: column;
  overflow: hidden;
  height: 100%;
}

.tree-list {
  flex: 1;
  height: 100%;
  overflow: hidden;
}
.tree-box {
  :active {
    background: #eeeeee;
  }
}
.tree-item {
  display: flex;
  justify-content: space-between;
  align-items: center;
  line-height: 1;
  height: 0;
  opacity: 0;
  transition: 0.2s;
  overflow: hidden;
  :active {
    background: none;
  }
}

.tree-item.show {
  height: 5vh;
  opacity: 1;
  padding: 0 15px 0 0;
  border: 1px solid #f0f8f8;
  margin-bottom: -1px;
}

.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: 16px;
}

.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>

3.控制样式的组件

<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 lang="ts">
import { ref, defineProps, defineEmits } from 'vue'
// const scrollX = ref<string | boolean>(false)
// const scrollY = ref<string | boolean>(false)
const scrollTop = ref<number>(0)
let $emit = defineEmits(['reachBottom'])
defineProps({
  scrollX: {
    type: [String, Boolean],
    value: false,
  },
  scrollY: {
    type: [String, Boolean],
    value: false,
  },
})
// 滚动事件
const getScroll = (e: any) => {
  let wScrollY = e.target.scrollTop // 当前滚动条位置
  scrollTop.value = wScrollY
  let wInnerH = e.target.clientHeight // 设备窗口的高度(不会变)
  let bScrollH = e.target.scrollHeight // 元素总高度
  if (wScrollY + wInnerH >= bScrollH) {
    $emit('reachBottom')
  }
}
defineExpose({
  scrollTop,
  getScroll,
})
// export default {
//   props: {
//     scrollX: {
//       type: [String, Boolean],
//       value: false,
//     },
//     scrollY: {
//       type: [String, Boolean],
//       value: false,
//     },
//   },
//   data() {
//     return {
//       scrollTop: 0,
//     }
//   },

//   methods: {
//     //滚动事件
//     // getScroll(e) {
//     //   let wScrollY = e.target.scrollTop // 当前滚动条位置
//     //   this.scrollTop = wScrollY
//     //   let wInnerH = e.target.clientHeight // 设备窗口的高度(不会变)
//     //   let bScrollH = e.target.scrollHeight // 元素总高度
//     //   if (wScrollY + wInnerH >= bScrollH) {
//     //     this.$emit('reachBottom')
//     //   }
//     // },
//   },
//   activated() {
//     this.$el.scrollTop = this.scrollTop
//     this.$emit('getScrollTop', { scrollTop: this.scrollTop })
//   },
//   updated() {},
//   mounted() {},
// }
</script>
<style scoped>
.my_scroll_container {
  width: 100%;
  height: 250px;
}
</style>

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
好的,关于Vue3封装ECharts组件的方法,我可以给你一些提示。 首先,我们需要安装ECharts。在Vue项目中,可以使用npm或yarn来安装ECharts。 ```bash npm install echarts --save # 或者 yarn add echarts ``` 现在,我们就可以在Vue3项目中使用ECharts了。 接下来,我们需要创建一个封装ECharts的Vue组件。我们可以使用Vue3中的`<script setup>`语法来创建组件。 ```vue <template> <div ref="chart" :style="{ width: width, height: height }"></div> </template> <script setup> import * as echarts from 'echarts'; const props = defineProps({ option: { type: Object, required: true, }, width: { type: String, default: '100%', }, height: { type: String, default: '400px', }, }); const chart = ref(null); onMounted(() => { const echartsInstance = echarts.init(chart.value); echartsInstance.setOption(props.option); window.addEventListener('resize', () => { echartsInstance.resize(); }); }); onUnmounted(() => { window.removeEventListener('resize', () => {}); }); </script> ``` 在这个组件中,我们使用了`<div>`元素来包含ECharts图表,并使用`ref`属性来获取DOM元素。我们还通过`props`定义了一些参数,包括ECharts的配置参数(`option`)、组件的宽度(`width`)和高度(`height`)。 在组件的`onMounted`生命周期钩子函数中,我们使用ECharts的`init`方法来创建一个ECharts实例,并将图表的配置参数传递给`setOption`方法。我们还添加了一个`resize`事件监听器,以便在窗口大小变化时自动调整图表的大小。 最后,在组件的`onUnmounted`生命周期钩子函数中,我们移除了`resize`事件监听器,以避免出现内存泄漏。 这就是一个简单的Vue3封装ECharts组件的示例。你可以在父组件中使用这个组件,并通过`option`属性传递ECharts的配置参数。 ```vue <template> <ECharts :option="chartOption" /> </template> <script> import ECharts from './ECharts.vue'; export default { components: { ECharts, }, data() { return { chartOption: { // ECharts配置参数 }, }; }, }; </script> ``` 希望这些提示能够帮到你!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值