【vue3】使用elmentui中的el-cascader联级菜单筛选切换数据列表

示例一:PC端(el-cascader 带下拉框的级联筛选)

 

需求:点击联级中的某一节点,点击确认,筛选符合条件的数据列表

1、创建文件 cascader.vue:

<template>
<div class="home_box">
    <div class="right_box">
        <div class="seachTop">
            <div class="big_select">
              <el-cascader v-model="person.itemValue" :props="person.props1" @change="handleChange" clearable placeholder="请选择章节"/>
            </div>
            <el-button @click="Submit">确认</el-button>
        </div>
        <div class="mainShow">
            <div class="main_title">共有{{person.data.total}}个符合条件的课程</div>
            <div class="main_scroll">
                <div class="curriculumDiv" v-if="person.data.list.length>0">
                    <div class="main_item" v-for="(item,index) in person.data.list" :key="index" @click="toStudy(item.Id)">
                        <div class="video_img">
                            <img src="@/assets/img/videoBgImg.png" alt="">
                            <div class="play_icon">
                            <img src="@/assets/img/home/play_icon.png" alt="">
                            </div>
                        </div>
                        <div class="video_name">{{item.Name}}</div>
                        <div class="video_status video_start_status">2022-5-12 14:00开播</div>
                    </div>
                </div>
                <el-empty v-else description="暂无录播数据" />
            </div>
        </div>
    </div>
</div>
</template>

<script lang="ts" setup>
import { reactive } from 'vue'
import {getDicList,getRecordList} from "@/api/record";
let person: any = reactive ({
    itemValue:[],
    activeId:0,
    props1: {
     checkStrictly: true,
     lazy: true,
        async lazyLoad(node:any, resolve:any) {
            const { data } = node
            let childNodes = await GetDicList(data.value)
            if(childNodes.length == 0){
                resolve(null)
            }else{
                resolve(childNodes)
            }
        },
    },
    //视频总条数
    total: 0,
    //分页
    data: {
      list: [],
      total: 0,
      pageSize: 9999,
      pageNumber: 1,
    }
})

// 获取章节接口
let GetDicList=async (id:number)=>{
    let res:any=await getDicList({ParentId:id})
    let {Code,Data}=res
    if(Code===200){
        let items= JSON.parse(JSON.stringify(Data).replace(/Id/g, 'value').replace(/Name/g, 'label'))
        return items
    }else{
        return []
    }
}
// 获取录播列表
let GetRecordList=async ()=>{
    let obj={
        NodeId: person.activeId,
        SchoolId: 0,
        GradeName: '',
        SubjectId: 0,
        PageIndex: person.data.pageNumber,
        PageSize: person.data.pageSize
    }
    let res:any=await getRecordList(obj)
    let {Code,Data}=res
    if(Code===200){
        person.data.list = Data.Data
        person.data.total = Data.Total
    }
}
GetRecordList()
// 章节下拉选中
const handleChange = (value:any) => {
    if(value){
        // 获取当前联级菜单的最后一级id
        person.activeId = value[value.length-1]
    }else{
        person.activeId = 0
    }
}
// 确认
let Submit=()=>{
  GetRecordList()
}

</script>
<style lang="scss" scoped>
.home_box{
    width: 100%;
    display: flex;
    .right_box{
        padding: 30px;
        padding-bottom: 0px;
        width: 100%;
    }
    /deep/ .seachTop{
        background: #FFFFFF;
        width: 100%;
        overflow: hidden;
        padding: 10px 20px;
        border-radius: 5px;
        display: flex;
        .big_select{
            margin-right: 20px;
        }
        .el-button{
            background: #6B86FF;
            color: #fff;
        }
        .el-cascader{
            width: 500px !important;
        }
    }
    .mainShow{
        margin-top: 35px;
        .main_title{
            color: #7E85A0;
            font-size: 24px;
            font-weight: 500;
        }
        .main_scroll::-webkit-scrollbar{
            width: 0;
        }
        .main_scroll{
            height: 670px;
            overflow-y: scroll;
            margin: 20px 0;
            .el-empty{
                height: 90%;
            }
        }
        .curriculumDiv{
            display: flex;
            flex-wrap: wrap;
            padding: 5px;
            .main_item:hover{
                box-shadow: 0px 3px 10px rgba(107, 134, 255,.8);
            }
            .main_item{
                width: 23.4%;
                height: 323px;
                margin-bottom: 34px;
                background: #fff;
                margin-right: 33px;
                padding: 15px;
                cursor: pointer;
                border-radius: 30px;
                box-shadow: 0px 3px 12px rgba(0, 0, 0, 0.05);
                overflow: hidden;
                position: relative;
                .video_img{
                    width: 100%;
                    height: 190px;
                    border-radius: 25px;
                    overflow: hidden;
                    position: relative;
                    img{
                        width: 100%;
                    }
                    .play_icon{
                        width: 100%;
                        height: 100%;
                        position: absolute;
                        top: 0;
                        left: 0;
                        background: rgba(0, 0, 0, 0.4);
                        display: flex;
                        flex-direction: column;
                        justify-content: center;
                        img{
                            width: 75px;
                            margin: 0 auto;
                        }
                    }
                }
                .video_name{
                    margin: 12px 0;
                    font-size: 18px;
                    line-height: 25px;
                    color: #4F4F4F;
                    display: -webkit-box;
                    -webkit-box-orient: vertical;
                    -webkit-line-clamp: 2;
                    overflow: hidden;
                }
                .video_status{
                    font-size: 16px;
                    color: #BDBDBD;
                }
                .video_start_status{
                    color: #FF7A17 !important;
                }
                .video_live{
                    position: absolute;
                    bottom: 5px;
                    right: 15px;
                    img{
                        width: 40px;
                    }
                }
            }
            .main_item:nth-child(4n){
                margin-right: 0px !important;
            }
        }
    }
}
</style>

示例二:ipad端(el-cascader-panel 动态加载面板)

1、left-menu.vue 组件

<template>
<div class="search_dialog">
  <div class="dialog_box">
      <el-cascader-panel ref="myCascader" v-model="state.selectValue" :options="menusList" :props="props" :show-all-levels="true"></el-cascader-panel>
  </div>
  <div class="find-more">
    <span @click="clickDetermine">确定</span>
  </div>
</div>
</template>

<script setup>
import { ref, reactive, defineEmits, onMounted, nextTick } from 'vue'
import { ElCascaderPanel } from 'element-plus'
import useResource from '@/hooks/useResource/index.js'
const { leftMenuData, getLeftMenuData } = useResource()
const prop = defineProps({
  code: {
    type: Number,
    default: 0,
    required: false
  },
  isShowPopup: {
    type: Boolean,
    default: false,
    required: false
  }
})

let state = reactive({
  itemValue: '',
  activeId: 0,
  lastId: 0,
  bookId: 0,
  selectValue: [],
  activeValue: [],
  activeLabel: []
})
const myCascader = ref()
const menusList = ref([])
const props = {
  checkStrictly: false,
  expandTrigger: 'click',
  value: 'value',
  label: 'label',
  lazy: true,
  async lazyLoad(node, resolve) {
    const { data, pathLabels, pathValues } = node
    setTimeout(async () => {
      // 当前的id
      state.activeId = data.value
      state.activeLabel = pathLabels
      state.activeValue = pathValues

      // 请求下级接口
      if (leftMenuData.value === null
        || leftMenuData.value.length === 0) {
        resolve(null)
      } else {
        if (data.value) {
          await getleftMenu(data.value)
          // 设置是否为子级,显示下级箭头
          leftMenuData.value.forEach(chi => {
            if (chi.children==undefined||chi.children.length===0) {
               chi.leaf = true
            }
          })
          resolve(leftMenuData.value)
        }
      }
    }, 500)
  }
}
const emits = defineEmits(['onOkLeft'])


// ===============methods================
onMounted(() => {
  load()
})
// 点击 “科目筛选” 第一次赋值
const load = async () => {
  if (prop.isShowPopup) {
    await getleftMenu(0)
    menusList.value = leftMenuData.value
    menusList.value.forEach(chi => {
      chi.children = []
      if (chi.children==undefined||chi.children.length===0) {
        chi.leaf = true
      }
    })
  }
}
// 调用学段接口
const getleftMenu = async (Id) => {
  let dataReq = {
    code: prop.code, // 2云校 1海豚
    parentId: Id
  }
  await getLeftMenuData(dataReq)
  // 数据处理
  if (menusList.value.length>0) {
    menusList.value.forEach(me => {
      if (me.value === state.activeId) {
        me.children = leftMenuData.value
        childrens(me.children)
      }
    })
  }
}
// 多级嵌套处理
const childrens=(item)=> {
  item.forEach((it) => {
    if (it.value === state.activeId) {
      it.children = leftMenuData.value
      childrens(it.children)
    }
  })
}
// 确定
const clickDetermine = () => {
  nextTick(() => {
    // 获取选中的名称
    if (myCascader.value.checkedNodes[0]) {  // 最后一级别
      state.itemValue = myCascader.value.checkedNodes[0].pathLabels.join("/")
    } else {
      state.itemValue = state.activeLabel.join("/")
    }
    // 获取选中的节点值
    if (state.selectValue&&state.selectValue.length>0) { // 最后一级别
      state.bookId = state.selectValue.slice(-2, -1)[0]
      state.lastId = state.selectValue.pop()
    } else {
      state.bookId = state.activeValue.slice(-2, -1)[0]
      state.lastId = state.activeValue.pop()
    }
    emits('onOkLeft', state.bookId, state.lastId, state.itemValue)
  })
}
</script>

<style lang="scss" scoped>
.find-more {
  width: 100%;
  text-align: center;
  padding: 20px 0;
  span {
    display: inline-block;
    width: 200px;
    height: 70px;
    line-height: 70px;
    color: #FFF;
    font-size: 30px;
    border-radius: 40px;
    border: 2px solid #FFF;

  }
}
.dialog_box{
  :deep(.el-cascader-panel.is-bordered){
    border: 0;
    background: rgba(255, 255, 255, 0.03);
    height: 500px;
  }
  :deep(.el-cascader-menu){
    color: #B0D3FF;
    font-size: 24px;
    width: 300px;
    border-right: 1px solid #35659C;
    &:hover{
      background: inherit !important;
    }
  }
  :deep(.el-cascader-menu__wrap.el-scrollbar__wrap){
    height: 100%;
  }
  :deep(.el-cascader-node) {
    padding: 20px 0 20px 30px;
    &:hover {
      color: #fff;
      background: linear-gradient(90deg, #1567D8 0.07%, rgba(21, 102, 215, 0.00) 99.94%);
    }
  }
  :deep(.el-cascader-node.in-active-path){
    color: #fff;
    background: linear-gradient(90deg, #1567D8 0.07%, rgba(21, 102, 215, 0.00) 99.94%);
  }
  :deep(.el-cascader-node.is-active) {
    color: #fff;
    background: linear-gradient(90deg, #1567D8 0.07%, rgba(21, 102, 215, 0.00) 99.94%);
  }
  :deep(.el-cascader-node.is-selectable.in-checked-path){
    color: #fff;
    background: linear-gradient(90deg, #1567D8 0.07%, rgba(21, 102, 215, 0.00) 99.94%);
  }
  :deep(.el-icon-check) {
    // 去掉选中小对勾
    display: none !important;
  }
  :deep(.el-cascader-menu__empty-text){
    display: none;
  }
}
</style>

2、取值方式(多种)

(1) v-model 取值

<el-cascader-panel v-model="state.selectValue" :options="menusList" :props="props" :show-all-levels="true"></el-cascader-panel>

<script setup>
import { reactive, nextTick } from 'vue'
let state = reactive({
  lastId: 0,
  bookId: 0,
  selectValue: []
})

// 确定
const clickDetermine = () => {
  nextTick(() => {
    // 获取选中的节点值
    if (state.selectValue&&state.selectValue.length>0) {
      state.bookId = state.selectValue.slice(-2, -1)[0]
      state.lastId = state.selectValue.pop()
    }
  })
}
</script>

(2) ref —— dom取值

<el-cascader-panel ref="myCascader" :options="menusList" :props="props" :show-all-levels="true"></el-cascader-panel>

<script setup>
import { ref, reactive, nextTick } from 'vue'
let state = reactive({
  itemValue: '',
  selectValue: []
})
const myCascader = ref()

// 确定
const clickDetermine = () => {
  nextTick(() => {
    // 获取名称
    if (myCascader.value.checkedNodes[0]) {
      state.itemValue = myCascader.value.checkedNodes[0].pathLabels.join("/")
    }
  })
}
</script>

(3) 【推荐】props 取值,通过变量去接

<el-cascader-panel :options="menusList" :props="props" :show-all-levels="true"></el-cascader-panel>
 
<script setup>
import { reactive, nextTick } from 'vue'
let state = reactive({
  itemValue: '',
  activeId: 0,
  lastId: 0,
  bookId: 0,
  activeValue: [],
  activeLabel: []
})
const props = {
  checkStrictly: false,
  expandTrigger: 'click',
  value: 'value',
  label: 'label',
  lazy: true,
  async lazyLoad(node, resolve) {
    const { data, pathValues, pathLabels } = node
    setTimeout(async () => {
      // 当前的id
      state.activeId = data.value
      state.activeLabel = pathLabels
      state.activeValue = pathValues
    },200)
  }
}

// 确定
const clickDetermine = () => {
  nextTick(() => {
    // 获取名称
    state.itemValue = state.activeLabel.join("/")
    // 获取选中的节点值
    state.bookId = state.activeValue.slice(-2, -1)[0]
    state.lastId = state.activeValue.pop()
  })
}
</script>

3、选择任意一级选项及点文字即可选中以及回显

分析:

问题1:回显功能在不点击radio时,某一中间节点不能回显,只回显至选中的最后一项,只有选中radio时才能回显每个经过的节点,此处必须用radio,并修改radio的样式,作为选中以及回显样式

问题2:点击radio时需要展示选中后的加载数据,radio 与 lazyLoad 懒加载是两个独立的功能,当点击radio时不改变 lazyLoad 后的数据或者lazyLoad数据没被执行,此处需要给radio上加 lazyLoad 的方法,并强制更新 node 数据。

(1)设置任意一级选项(radio)

const props = {
  expandTrigger: 'click',
  checkStrictly: true,
  value: 'value',
  label: 'label',
  lazy: true,
  async lazyLoad(node, resolve) {
    ......
  }
}

(2)将radio标签做成文字框一样大小并且透明覆盖在整个文字上方,点击文字的时候其实是在点击radio标签

:deep(.el-cascader-panel){
    .el-radio {
      width: 180%;
      height: 100%;
      z-index: 10;
      position: absolute;
      top: 10px;
      right: 10px;
    }
    .el-radio input {
      visibility: hidden;
    }
}

(3)设置change 方法中调用 lazyLoad

<el-cascader-panel ref="myCascader" :key="cascaderKey" v-model="state.selectValue" :show-all-levels="false" @change="handleChange" @expand-change="handleChange" :options="menusList" :props="props" >
</el-cascader-panel>

const myCascader = ref()
const cascaderKey = ref(0)
const handleChange = () => {
  // 当选项变化时,如果需要懒加载,则调用 lazyLoad 函数
  if (myCascader.value.checkedNodes.length > 0) {
    const lastSelectedNode = myCascader.value.checkedNodes[myCascader.value.checkedNodes.length - 1]
    if (lastSelectedNode && !lastSelectedNode.isLeaf) {
      props.lazyLoad(lastSelectedNode, nodes => {
        lastSelectedNode.children = nodes
        cascaderKey.value++ // 强制更新数据
      })
    }
  }
}

(4)change事件监听实现懒加载选择任意一级(有bug不建议使用)

const handleChange = () => {
  // 事件监听实现懒加载选择任意一级
  nextTick(()=> {
    //获取label
    const labelDoms = document.querySelectorAll('.el-cascader-node .el-cascader-node__label')
    //获取radio
    const radioDoms = document.querySelectorAll('.el-cascader-node .el-radio')
    //由于label是被radio覆盖,所以循环raidoDoms,反之循环labelDoms
    radioDoms.forEach((item, index) => {
      item.removeEventListener('click', function () {
        const labelDom = labelDoms[index]
        labelDom.click()
      })
      item.addEventListener('click', function () {
        const labelDom = labelDoms[index]
        labelDom.click()
      })
    })
  })
}

(5)回显

onMounted(() => {
  // 用v-model获取存的数组数据即可
  state.selectValue = storage.getStorage(config.CONFIG_MENUIDS)
})

4、页面调用

<template>
<div>
  <div class="select-btn">
    <div class="select-item" :class="[state.isShowCourse?'select-item-active':'']" @click="courseSelect">{{ state.courseTitle }}></div>
  </div>

  <div class="screenbox">
    <div class="condition-screen" v-if="isRefresh && state.isShowCourse">
      <div class="condition-wrap">
        <div class="condition">
          <LeftMenu :code="props.code" :isShowPopup="state.isShowCourse" @on-ok-left="onOkLeft"></LeftMenu>
        </div>
      </div>
      <div class="condition-screen-bg"></div>
    </div>
  </div>
</div>
</template>

<script setup>
import { ref, reactive, onMounted } from 'vue';
import LeftMenu from '@/components/left-menu/index.vue'

const state = reactive({
  isShowCourse: false,
  courseTitle: '教程筛选',
  BookId: 0,
  KnowId: 0,

})

// 教程筛选
const courseSelect = () => {
  state.isShowCourse = !state.isShowCourse
}

// 点击确定按钮
const onOkLeft = (prentId,lastId,title) => {
  state.isShowCourse = false
  state.courseTitle = title
  // 列表
  state.BookId = prentId
  state.KnowId = lastId
  // 筛选列表接口
  ......
}
</script>

<style lang='scss' scoped>
.select-btn{
  padding: 0 98px;
  .select-item{
    font-size: 34px;
    color: #B0D3FF;
    margin-right: 70px;
    cursor: pointer;
    margin-bottom: 20px;
  }
  .select-item-active{
    color: #fff !important;
  }
}
.screenbox {
  width: 100%;
  .subject-screen {
    padding: 0 98px;
  }
  .condition-screen {
    width: 100%;
    position: absolute;
    z-index: 100;
    top: 220px;
    .condition-screen-bg {
      width: 100%;
      position: absolute;
      background-color: rgba(0, 0, 0, .5);
      height: calc(100vh - 220px);
    }
    .condition-wrap {
      width: 92%;
      padding: 30px 80px 0 80px;
      position: absolute;
      z-index: 110;
      background: linear-gradient(90deg, #001A46 0.02%, #002858 23.97%, #004075 54.69%, #002B5B 76.03%, #002654 85.41%, #00224E 88.01%, #001D49 94.26%);
      .condition {
        box-sizing: border-box;
        color: #fff;
      }
    }
  }
}
</style>

       希望我的愚见能够帮助你哦~,若有不足之处,还望指出,你们有更好的解决方法,欢迎大家在评论区下方留言支持,大家一起相互学习参考呀~

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值