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