当前位置: 首页 > news >正文

vue3+el-cascader-panel+多选+动态加载+默认展开+选中查询节点并展开+查询到的这一条自动滚动到顶部+tooltip效果

 动态加载之前已经发过一次,有想了解的的可在本人主页找下

一.效果展示

1.打开弹框默认展开

2.展示不全的节点才出现鼠标悬浮效果

 3.选中查询的节点并展开各级

例如:选中节点第一条节点

 备注:当前可选中的数据只是当下一级下的所有二三四级(避免卡顿,就没有实现所有的一级下的二三四级),当切换一级,更新下拉数据,因此只实现了当前被选中的一级下的子级展开

 选中后自动展开对应的三四级(因为默认已经展开了某个一级下的所有二级)

 功能总结:

  • 可点击父级展开下一个子级(效果同elementPlus中的级联面板)
  • 可在下拉中输入要查找的节点(边输入边更新与输入内容匹配的节点,与elementPlus中select可查询组件效果一致)
  • 可在下拉选中某一条数据,并展开对应的三四级,并且自动滚动到顶部
  • 部分节点展示不全,有鼠标悬浮展示这一条全部信息

 二.默认展开节点

1.监听弹框打开,展开默认一级下的二级节点

watch(

  () => props.visible,

  (val) => {

    searchData.value = ''       // 清空搜索框

    if (val) {

      time2.value = setTimeout(() => {

        const index = collectOptions.value.findIndex((item) => item.name === 'US')  // 默认选中US,通过US去当下一级的option去获取对应的的index

        toClickSecondCascaderCollect(index, 0)  //将index传给toClickSecondCascaderCollect,0代表级联第一级数据,1代表第二级数据,2代表第三级数据

      }, 1000)

    }

  }

)

 2.通过操作dom,js实现点击对应的某一级

//触发点击事件,index 节点位置,number 级联面板四级中的一级

const toClickSecondCascaderCollect = (index, number?) => {

  const el = document.querySelectorAll(`.el-cascader-menu`)[number].querySelectorAll(`.el-cascader-node`)

  if (el && el[index]) {

    return new Promise((resolve) => {

      el[index].click()   // 触发点击事件,展开传过来的index对应的节点

      time.value = setTimeout(() => {

        resolve()     // 延迟1秒执行,防止执行过快,防止上一级没有展示出来就点击而导致找不到click报错

      }, 1000)

    })

  }

  return Promise.resolve()

}

  三.鼠标悬浮效果

1.使用el-tooltip组件展示所有的节点的提示效果

<el-cascader-panel

        ref="cascaderCollect"

        v-model="collectValue"

        :props="address"

        :options="collectOptions"

        @expandChange="handleExpandChange"

      >

        <template #default="{ node, data }">

//v-if为真,展示悬浮效果

          <el-tooltip

            v-if="isTextTruncated(data.name)"

            effect="dark"

            :content="data.name"

            placement="top-start"

          >

            <span class="truncated-text">{{ data.name }}</span>

          </el-tooltip>

//v-if为假,不展示悬浮效果,只展示纯文本

          <span v-else class="regular-text">{{ data.name }}</span>

        </template>

      </el-cascader-panel>

//对应css

.truncated-text {

  display: inline-block;

  max-width: 213px; /* 设置你希望的宽度 */

  white-space: nowrap;

  overflow: hidden;

  text-overflow: ellipsis;

  vertical-align: middle;

}

.regular-text {

  white-space: normal; /* 显示完整文本,没有省略 */

}

 2.判断当下节点文本长度,判断是否展示鼠标悬浮效果

const isTextTruncated = (text) => {

  // 你可以实现逻辑检查文本是否超过某个长度

  const maxLength = 28 // 可根据需要调整最大长度

  return text.length > maxLength

}

四.展开选中节点

1.select下拉添加@change事件

<el-select-v2

                v-model="searchData"

                filterable

                clearable

                :options="searchDataOptions"

                placeholder="请输入"

                style="width: 700px"

                @change="searchChange"

              />

2.select选中改变,就循环遍历选中节点,实现一级级逐层点击,以选择这一条为例"Health & Household,Food Wrap, Foils"

const searchChange = async (changeData) => {

  // 点击后获取的changeData数据分3段数据,分别是对应二三四级要展开的节点

  if (!changeData) return

  const currdata = changeData?.split(',')

  console.log('currdata', currdata)   // ["Health & Household","Food Wrap", "Foils"]

  for (let i = 0; i < 2; i++) {

    let index = -1

    if (i === 0) {

//先拿“Health & Household”去当前已经展开的第二级的options中去遍历,获取到index

      index = optionSecond.value.findIndex((item) => item.name === currdata[0])

    } else if (i === 1) {

//再拿“Food Wrap”去当前已经展开的第三级的options中去遍历,获取到index

      let secondParams = optionSecond.value.find((item) => item.name === currdata[0])

      console.log('secondParams', secondParams)

      const res = await ApiBusiType.marketDataCollection.queryGraduallyCollectionConfig(secondParams)

      optionThird.value = res.result || []

      console.log('res1111', res)

      index = optionThird.value.findIndex((item) => item.name === currdata[1])

    }else if(i === 2){

//这一层判断仅仅是用来让第四级滚动到顶部

//再拿“Foils”去当前已经展开的第三级的options中去遍历,获取到index,

      index = optionFourth.value.findIndex((item) => item.name === currdata[2])

    }

    if (index !== -1) {

      await nextTick()  // 等待数据渲染完成

      await toClickSecondCascaderCollect(index, i + 1)  执行点击事件

    }

  }

}

//第一次index就是“Health & Household”在第二级所在的index,number是1,表示要点击第二级,展示出第三级别。

//第二次index就是“Food Wrap”在第二级所在的index,number是2,表示要点击刚展示的第三级,展示出第四级。此时就完成了展示一二三四级

const toClickSecondCascaderCollect = (index, number?) => {

  const el = document.querySelectorAll(`.el-cascader-menu`)[number].querySelectorAll(`.el-cascader-node`)

  if (el && el[index]) {

    return new Promise((resolve) => {

      el[index].click()   // 触发点击事件,展开传过来的index对应的节点

      time.value = setTimeout(() => {

        resolve()     // 延迟1秒执行,防止执行过快,防止上一级没有展示出来就点击而导致找不到click报错

      }, 1000)

    })

  }

  return Promise.resolve()

}

五.选中节点自动滚动到顶部

//触发点击事件,index 节点位置,number 级联面板四级中的一级

const toClickSecondCascaderCollect = (index, number?) => {

  const el = document.querySelectorAll(`.el-cascader-menu`)[number].querySelectorAll(`.el-cascader-node`)

  if (el && el[index]) {

    return new Promise((resolve) => {

      el[index].click()   // 触发点击事件,展开传过来的index对应的节点

      el[index].scrollIntoView({ behavior: 'smooth', block: 'start' })  //自动滚到顶部

      time.value = setTimeout(() => {

        resolve()     // 延迟1秒执行,防止执行过快,防止上一级没有展示出来就点击而导致找不到click报错

      }, 1000)

    })

  }

  return Promise.resolve()

}

六.组件源码

<template>
  <div>
    <Dialog title="添加采集" :visible.sync="isShow" width="1030px" @close="handleClose">
      <div class="header-title">
        <div class="add-header">
          <el-form :inline="true">
            <el-form-item label="关键词搜索">
              <el-select-v2
                v-model="searchData"
                filterable
                clearable
                :options="searchDataOptions"
                placeholder="请输入"
                style="width: 700px"
                @change="searchChange"
              />
            </el-form-item>
            <el-form-item>
              <!-- <el-button type="primary" @click="searchAddData">搜索</el-button> -->
            </el-form-item>
          </el-form>
        </div>
      </div>
      <div class="selection-container">
        <div class="selection-column">
          <h3>商店名称</h3>
          <!-- <el-checkbox-group v-model="ruleForm.collectSite">
            <el-checkbox v-for="item in collectSiteOptions" :key="item" :label="item">
              {{ item }}
            </el-checkbox>
          </el-checkbox-group> -->
        </div>

        <div class="selection-column">
          <h3>类别</h3>
          <!-- <el-checkbox-group v-model="ruleForm.category">
            <el-checkbox v-for="item in categoryOptions" :key="item" :label="item">
              {{ item }}
            </el-checkbox>
          </el-checkbox-group> -->
        </div>

        <div class="selection-column">
          <h3>商品类型</h3>
          <!-- <el-checkbox-group v-model="ruleForm.productType">
            <el-checkbox v-for="item in productTypeOption" :key="item" :label="item">
              {{ item }}
            </el-checkbox>
          </el-checkbox-group> -->
        </div>

        <div class="selection-column">
          <h3>商品类型关键词</h3>
          <!-- <el-checkbox-group v-model="ruleForm.productTypeKeyword">
            <el-checkbox v-for="item in keywordOption" :key="item" :label="item">
              {{ item }}
            </el-checkbox>
          </el-checkbox-group> -->
        </div>
      </div>
      <el-cascader-panel
        ref="cascaderCollect"
        v-model="collectValue"
        :props="address"
        :options="collectOptions"
        @expandChange="handleExpandChange"
      >
        <template #default="{ node, data }">
          <el-tooltip
            v-if="isTextTruncated(data.name)"
            effect="dark"
            :content="data.name"
            placement="top-start"
          >
            <span class="truncated-text">{{ data.name }}</span>
          </el-tooltip>
          <span v-else class="regular-text">{{ data.name }}</span>
        </template>
      </el-cascader-panel>
      <template #footer>
        <div class="footer">
          <el-button @click="handleClose" plain>取消</el-button>
          <el-button type="primary" @click="handleSumit">确认添加</el-button>
        </div>
      </template>
    </Dialog>
  </div>
</template>
<script lang="ts" setup>
import { ref, reactive, defineProps, computed, watch, nextTick, onMounted, onUnmounted } from 'vue'
import { ApiBusiType } from '@/api/index'
import { ElMessage } from 'element-plus'
import dayjs from 'dayjs'
import { convertLegacyProps } from 'ant-design-vue/es/button/buttonTypes'
import { deepCopy } from '@/utils/helper'
const props = defineProps({
  visible: {
    type: Boolean,
    default: false
  },
  data: {
    type: Object || Array,
    default: () => {
      return {}
    }
  }
})
const ruleForm = reactive({
  collectSite: '',
  category: '',
  productType: '',
  productTypeKeyword: ''
})
const cascaderCollect = ref()
const collectOptions = ref([])
const currentOptions = ref([])
const otherOptions = ref([])
const searchData = ref('')
const selectData = ref([])
// 我是选中的值
const checkData = ref([])
const currentPathNode = ref('')
const currentPathNode2 = ref('')
const currentPathNode1 = ref('')
const searchDataOptions = ref([])
const secondNode = ref('')
const fourCollectOptions = ref([])
const loading = ref(false)
const secondParams = reactive({})
const productTypeOption = ref([])
const keywordOption = ref([])
const collectValue = ref([])
const optionFirst = ref([])
const optionSecond = ref([])
const optionThird = ref([])
const optionFourth = ref([])
const optionAll = ref([])
const oneOptions = ref([])
const twoOptions = ref([])
const time = ref()
const time2 = ref()
const $emit = defineEmits(['update:visible', 'close'])
let address = {
  value: 'name',
  label: 'name',
  children: 'children',
  multiple: true,
  leaf: 'leaf',
  lazy: true, // 开启懒加载
  // checkStrictly: true, //可选择任意节点
  /**
   * 异步懒加载节点数据的函数
   * @param {Object} node - 当前被点击的节点对象
   * @param {Function} resolve - 数据加载完成后的回调函数,必须调用
   * 该函数根据当前节点的信息构造查询条件,调用接口获取下一级节点数据。
   * 当节点层级达到 4 级时,不再请求接口。获取到的数据经过处理后通过 resolve 返回。
   */

  async lazyLoad(node, resolve) {
    console.log('node', node)
    const { level } = node
    // level 节点层级
    console.log('level', level)
    const nodes = []
    const params = {
      managerCombination: node.pathLabels?.join(',') || '',
      code: node.data.code || '',
      name: node.data.name || '',
      note: node.data.note || '',
      parentCode: node.data.parentCode || ''
    }
    const res = await ApiBusiType.marketDataCollection.queryGraduallyCollectionConfig(params)
    currentOptions.value = res.result || []
    switch (level) {
      case 0:
        optionFirst.value = res.result || []
        break
      case 1:
        optionSecond.value = res.result || []
        twoOptions.value.push(res.result)
        break
      case 2:
        optionThird.value = res.result || []
        break
      case 3:
        optionFourth.value = res.result || []
        break
      default:
        break
    }
    if (level === 0) {
      collectOptions.value = res.result || []
      resolve(collectOptions.value)
    } else {
      res.result.map((item) => {
        let obj = {
          code: item?.code,
          name: item?.name,
          note: item?.note,
          disabled: item.disabled,
          parentCode: item?.parentCode,
          leaf: node?.level >= 3
        }
        nodes.push(obj)
      })
      resolve(nodes)
    }
  }
}
const isShow = computed({
  get() {
    return props.visible
  },
  set(val: boolean) {
    $emit('update:visible', val)
  }
})
//触发点击事件,index 节点位置,number 级联面板四级中的一级
const toClickSecondCascaderCollect = (index, number?) => {
  const el = document.querySelectorAll(`.el-cascader-menu`)[number].querySelectorAll(`.el-cascader-node`)
  if (el && el[index]) {
    return new Promise((resolve) => {
      el[index].click()   // 触发点击事件,展开传过来的index对应的节点
      el[index].scrollIntoView({ behavior: 'smooth', block: 'start' })
      time.value = setTimeout(() => {
        resolve()     // 延迟1秒执行,防止执行过快,防止上一级没有展示出来就点击而导致找不到click报错
      }, 1000)
    })
  }
  return Promise.resolve()
}
const isTextTruncated = (text) => {
  // 你可以实现逻辑检查文本是否超过某个长度
  const maxLength = 28 // 可根据需要调整最大长度
  return text.length > maxLength
}

const handleSumit = async () => {
  console.log('collectValue', collectValue.value)
  if (collectValue.value.length === 0) {
    ElMessage.warning('无可添加采集节点,请重新选择')
    return
  }
  const params = {
    categoryList: collectValue.value.map((item) => item?.join(',')) || []
  }
  console.log('params', params)
  const res = await ApiBusiType.marketDataRelationship.marketCollection(params)
  if (res.code === '1') {
    ElMessage.success('操作成功!')
    $emit('close', 'refresh')
  }
}
// 转换数据结构的函数
const transformToArray = (data) => {
  return data.map((item) => {
    const keyName = Object.keys(item)[0] // 获取每个对象的第一个属性名
    return {
      value: keyName,
      label: keyName // 将 value 和 label 都设置为同一个属性名
    }
  })
}
const searchChange = async (changeData) => {
  // 点击后获取的数据分3段数据
  // 第一段数据返回后触发 toClickSecondCascaderCollect 第一段数据在2里面的位置 2
  // 第二段数据返回后触发 toClickSecondCascaderCollect 第二段数据在3里面的位置 3
  // 第三段数据返回后触发 toClickSecondCascaderCollect 第三段数据在4里面的位置 4

  if (!changeData) return
  const currdata = changeData?.split(',')
  console.log('currdata', currdata)
  for (let i = 0; i < 3; i++) {
    let index = -1
    if (i === 0) {
      index = optionSecond.value.findIndex((item) => item.name === currdata[0])
    } else if (i === 1) {
      let secondParams = optionSecond.value.find((item) => item.name === currdata[0])
      console.log('secondParams', secondParams)
      const res = await ApiBusiType.marketDataCollection.queryGraduallyCollectionConfig(secondParams)
      optionThird.value = res.result || []
      console.log('res1111', res)
      index = optionThird.value.findIndex((item) => item.name === currdata[1])
    }else if(i === 2){
      index = optionFourth.value.findIndex((item) => item.name === currdata[2])
    }
    if (index !== -1) {
      await nextTick() // 等待数据渲染完成
      await toClickSecondCascaderCollect(index, i + 1)
    }
  }
}
const searchAddData = async () => {
  // loading.value = true
  const params = {
    name: currentPathNode.value,
    search: searchData.value
  }
  const res = await ApiBusiType.marketDataRelationship.queryLikeConfig(params)
  if (res.code === '1') {
    const result = transformToArray(res.result)
    searchDataOptions.value = result || []
  }
}
// 获取第二级当前点击的节点
// 遍历二级的数据拿到这一集的参数,去调用第三级
const handleExpandChange = (val) => {
  console.log('展开节点触发了', val)
  if (val.length === 1) {
    currentPathNode.value = val[0]
  }
  if (val.length > 1) secondNode.value = val[1]
}
const handleClose = () => {
  isShow.value = false
  collectValue.value = []
  $emit('close')
}
watch(
  () => currentPathNode.value,
  (val) => {
    if (val) {
      let isupdate=oneOptions.value.includes(val)
      if (oneOptions.value.length > 1 && isupdate) {
        console.log('oneOptions.value312', oneOptions.value)
        console.log('twoOptions.value213', twoOptions.value)
        let index = oneOptions.value.findIndex((item) => item === val)
        optionSecond.value = twoOptions.value[index]
      }
      isupdate ? '' : oneOptions.value.push(val)
      searchData.value = ''
      searchAddData()
    }
  }
)
watch(
  () => props.visible,
  (val) => {
    searchData.value = ''       // 清空搜索框
    if (val) {
      time2.value = setTimeout(() => {
        const index = collectOptions.value.findIndex((item) => item.name === 'US')  // 默认选中US,通过US去当下一级的option去获取对应的的index
        toClickSecondCascaderCollect(index, 0)  //将index传给toClickSecondCascaderCollect,0代表级联第一级数据,1代表第二级数据,2代表第三级数据
      }, 1000)
    }
  }
)
onUnmounted(() => {
  time.value && clearTimeout(time.value)
  time2.value && clearTimeout(time2.value)
})
</script>
<style scoped lang="less">
.selection-container {
  display: flex;
}

.selection-column {
  flex: 1;
  margin-left: 10px;
}

.footer {
  float: right;
}
::v-deep(.el-cascader-menu:nth-child(1) .el-checkbox),
::v-deep(.el-cascader-menu:nth-child(2) .el-checkbox) {
  display: none;
}
::v-deep(.el-cascader-menu:nth-child(1)) {
  min-width: 135px;
}

.truncated-text {
  display: inline-block;
  max-width: 213px; /* 设置你希望的宽度 */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  vertical-align: middle;
}

.regular-text {
  white-space: normal; /* 显示完整文本,没有省略 */
}
</style>

相关文章:

  • 高铁监控存储扩容-DS SAN存储磁盘阵列
  • Python中数据结构元组详解
  • LangChain开发(一)介绍和第一个例子
  • 什么是 BA ?BA怎么样?BA和BI是什么关系?
  • Nginx的HTTPS配置
  • 【论文笔记】Transformer
  • OpenCV专利收费免费模块介绍
  • QT二 QT使用generate form 生成常用UI,各种UI控件
  • Function Calling的核心机制与挑战
  • 【k8s】利用Kubernetes卷快照实现高效的备份和恢复
  • Three.js世界中的三要素:场景、相机、渲染器
  • 一个完整的小项目案例,涉及到项目的规划,模块的设计功能的衔接等。
  • tcpdump-快速查询版-常用后缀
  • 【sql靶场】第23、25,25a关过滤绕过保姆级教程
  • 蓝桥杯之AT24C02的页写页读
  • 【Spring 默认是否管理 Request 和 Session Bean 的生命周期?】
  • OpenCV 基础模块 Python 版
  • MySQL 设置允许远程连接完整指南:安全与效率并重
  • 小白闯AI:Llama模型Lora中文微调实战
  • k8s运维 设置Pod实现JVM内存根据容器内存动态调整
  • 人才争夺战,二三线城市和一线城市拼什么?洛阳官方调研剖析
  • 这些网红果蔬正在收割你的钱包,营养师:吃了个寂寞
  • 均价19.5万元/平米!上海徐汇滨江地王项目“日光”,销售额近70亿元
  • 马克龙称法英正与乌克兰商议“在乌部署欧洲军队”
  • 人民日报刊文:加快解放和发展新质战斗力
  • 印度证实印巴已同意停火