【vue3】 实现PC端自定义左侧组件的封装以及调用

列表页

详情页

 需求:

点击左侧菜单一级标题,加载右侧列表

点击左侧菜单二级标题,加载右侧列表,同时加载班级或者科目切换列表

切换左侧任意标题后,刷新当前列表页,左侧点击值和右侧列表数据需保持不变

通过右侧列表点击到详情页后,也调用左侧菜单

1、当刷新当前详情页时,左侧选中的是哪个值还是定位到哪个值

2、当在详情页点击切换左侧菜单时,定位到当前值对应的列表页数据

3、当点击详情页的返回按钮时,需返回进入当前详情页的列表页,以及定位到对应的左侧菜单值

解决思路:
1、当点击左侧标题时,获取点击的id,传给需要id参数的接口

2、从详情页传递的id,从新赋值给左侧菜单,并显示对应的选中效果

一、左侧组件 MenuLeft.vue

调用页面传递过来的 menuList数组数据

   "menuList": [
        {
            "Id": 1,
            "Name": "奉节第一中学",
            "Children": [
                {
                    "Id": 2,
                    "Name": "初中",
                    "Children": null
                },
                {
                    "Id": 12,
                    "Name": "高中",
                    "Children": null
                }
            ]
        },
        {
            "Id": 22,
            "Name": "奉节第二中学",
            "Children": [
                {
                    "Id": 23,
                    "Name": "初中",
                    "Children": null
                },
                {
                    "Id": 33,
                    "Name": "高中",
                    "Children": null
                }
            ]
        }
    ]

组件封装详情

<template>
    <div class="menu_box">
        <el-collapse v-model="person.activeName" accordion @change="ParentChange">
            <el-collapse-item v-for="(meu,meIndex) of person.menuList" :key="meIndex" title="标题" :name="meIndex+''">
                <template #title>
                    <div :class="['sn-menu-title', { 'sn-menu_active': meIndex === Number.parseInt(person.activeName) }]">
                        <el-icon>
                            <OfficeBuilding />
                        </el-icon>
                        <span>{{ meu.Name }}</span>
                    </div>
                </template>
                <div class="menu_item" v-for="(chi,chiIndex) of meu.Children" :key="chiIndex" :class="[person.nenuActive===chi.Id?'menu_active':'']" @click="menuChange(meu,chi,chiIndex)">
                    <div class="sn-border-top"></div>
                    <div class="sn-sub-item">
                        <el-icon>
                            <Document />
                        </el-icon>
                        <span>{{chi.Name}}</span>
                    </div>
                    <div class="sn-border-botton" v-if="!(chi.Children?.length === chiIndex)"></div>
                </div>
            </el-collapse-item>
        </el-collapse>
    </div>
</template>
<script lang="ts" setup>
import {
    Document,
    Menu as IconMenu
} from '@element-plus/icons-vue'
import { reactive } from 'vue';

let person: any = reactive({
    activeName:'0', // 一级index
    nenuActive: 0, // 二级index
    menuList: []
})

type Props = {
    menuList: Array<any>
}
const props = withDefaults(defineProps<Props>(), {
    menuList: () => []
})

const emit = defineEmits(['onToIndex'])
// 父级切换
const ParentChange = (index: number) => {
    emit("onToIndex", person.menuList[index].Id, person.menuList[index].Id)
}
// 子级切换
const menuChange = (meu:any,item:any,index:number)=>{
    person.nenuActive = item.Id
    emit("onToIndex", meu.Id, person.nenuActive)
}

// 刷新后定位
let ParentsId = Number(localStorage.getItem('ParentId'))
let CurrsId = Number(localStorage.getItem('CurrId'))
if (JSON.parse(JSON.parse(JSON.stringify(window.localStorage.getItem("dataMenu"))))){
    person.menuList = JSON.parse(JSON.parse(JSON.stringify(window.localStorage.getItem("dataMenu"))));
}else{
    person.menuList = props.menuList
}

if (ParentsId && CurrsId) {
    person.menuList.forEach((me:any, meIndex:number) => {
        if (ParentsId === me.Id) {
            person.activeName = meIndex + ''
            me.Children.forEach((chi: any, chiIndex: number) => {
                if (CurrsId === chi.Id) {
                    person.nenuActive = chi.Id
                }
            })
        }
    })
}
</script>
<style lang="scss" scoped>
.menu_box {
    padding: 20px;
    background: #fff;

    .sn-menu-title {
        display: flex;
        align-items: center;
        .el-icon {
            margin-right: 5px;
            font-size: 18px;
        }
    }
}
.menu_active {
    color: #6B86FF !important;
}
.menu_item {
    padding: 0px 40px;
    font-size: 14px;
    position: relative;
    .sn-border-top,
    .sn-border-botton {
        height: 25px;
        border-left: 1px solid #E0E0E0;
        position: absolute;
        top: 0;
    }
    .sn-border-botton {
        top: 25px;
    }
    .sn-sub-item {
        display: flex;
        align-items: center;
        background: url('../../assets/img/semicircle.png') no-repeat;
        background-position: 0 50%;
        padding-left: 15px;
        height: 50px;
        line-height: 50px;
        font-size: 15px;
        .el-icon {
            margin: 0px 5px;
            font-size: 16px;
        }
    }
}

.el-collapse{
    width: 260px;
    border: 0;
}

/deep/ .el-collapse-item__header.is-active{
    background: #F1F4FF;
    color: #6B86FF;
    border-radius: 14px;
}
/deep/ .el-collapse-item__header{
    padding: 5px 10px;
    font-size: 16px;
    color: #666;
    border: 0;
    height: inherit;
}
/deep/ .el-collapse-item__wrap{
    border: 0;
}
/deep/ .el-collapse-item__content{
    padding: 0;
}
</style>

二、页面调用

/**
*@Author: meihang
*@Date: 2022/4/20
*@Description:专题录播
*/
<template>
    <div class="home_box">
        <LeftMenus v-if="recordMenu" :menuList="recordMenu" @onToIndex="onToIndex"></LeftMenus>
        <div class="right_box">
            <div class="seachTop" v-if="gradeList&&gradeList.length>0">
                <div class="search_list" >
                    <div class="search_item" id="search_item" :v-model="person.searchActive"
                        v-for="(grade,grIndex) in gradeList" :key="grIndex"
                        :class="[person.searchActive===grIndex?'search_item_active':'']"
                        @click="gradeChange(grade,grIndex)">{{grade.Name}}</div>
                </div>
                <div class="search_list" v-if="person.classList && person.classList.length>0">
                    <div class="search_item1" v-for="(child, chIndex) in person.classList" :key="chIndex"
                        :class="[person.subjectActive === chIndex?'search_item1_active':'']"
                        @click="subjectChange(child,chIndex)">{{ child.Name}}</div>
                </div>
            </div>
            <div class="main_scroll">
                <div class="curriculumDiv" v-if="datalist&&datalist.length>0">
                    <div class="main_item" v-for="(item,index) in datalist" :key="index" @click="toStudy(item)">
                        <div class="video_img">
                            <img :src="item.CoverUrl" alt="">
                            <div class="play_icon">
                                <img src="@/assets/img/home/play_icon.png" alt="">
                            </div>
                            <div class="course_icon" v-if="item.Chapter.length<2">
                                <div class="course_flex" v-if="item.ClassHour"><img
                                        src="@/assets/img/home/ketime_icon.png" alt=""> 课时:{{item.ClassHour}}</div>
                                <!-- <div class="course_flex"><img src="@/assets/img/home/time_length_icon.png"
                                        alt="">总时长:{{item.SumTime}}小时</div> -->
                                <div class="course_flex" v-if="item.UpdateTime">最近更新:{{item.UpdateTime}}天前</div>
                            </div>
                            <div class="course_icon1 course_flex" v-else @click.stop="sessClick(item,index)">
                                <img src="@/assets/img/home/ketime_icon.png" alt="">
                                课时:{{item.ClassHour}}
                                <img v-if="!item.selectShow" src="@/assets/img/home/down_change_icon.png" alt="">
                                <img v-else src="@/assets/img/home/up_change_icon.png" alt="">
                            </div>
                        </div>
                        <div class="video_list" v-if="item.selectShow" :class="{'open': item.selectShow}">
                            <div class="video_scorll">
                                <div class="video_item" v-for="(curr,currIndex) in item.Chapter" :key="currIndex"
                                    :class="[person.videoActive===currIndex?'video_item_active':'']"
                                    @click.stop="changeVideo(item,currIndex)">{{curr.Name}}</div>
                            </div>
                        </div>
                        <!-- <div class="video_name">
                            <p v-if="item.Chapter.length>0">{{item.Chapter[0].Name}}</p> <span v-if="item.TeacherName">主讲:{{item.TeacherName}}</span>
                        </div> -->
                        <div class="video_name">
                            <p >{{item.Name}}</p> <span v-if="item.TeacherName">主讲:{{item.TeacherName}}</span>
                        </div>
                    </div>
                </div>
                <el-empty v-else description="暂无录播数据" />
            </div>
        </div>
    </div>
</template>

<script lang="ts" setup>
import { onMounted, onUnmounted, reactive, toRaw } from 'vue'
import { useRoute,useRouter } from 'vue-router'
import { GetRecordMenu,GetRecordList, datalist, recordMenu, gradeList } from './hooks/record_menu' // 混入菜单栏
import LeftMenus from "@/components/leftMenus/index.vue" //静态引入
let person: any = reactive ({
    searchActive:-1, //年级选中
    subjectActive:-1, // 科目选中
    videoActive:0,
    recordMenu: [], // 录播菜单
    recordId:0,
    classList:[], // 科目列表
    ParentId: 0,
    CurrId: 0,
    FristId:0
})
onMounted(() => {
    GetRecordMenu(0).then(res=>{
        if(res){
            GrecordMenu(res[0].Id)
        }
    })
})

// 获取左菜单数据
const GrecordMenu = async (id=0) => {
    let CurrsId = Number(localStorage.getItem('CurrId'))
    if (CurrsId) { // 刷新时获取第一个id
        GetRecordList(CurrsId)
    }else{ // 登录时获取第一个id
        GetRecordList(id)
    }
}
// 获取点击的id
const onToIndex = (perId: number, currId: number) => {
    person.ParentId = perId
    person.CurrId = currId
    GetRecordMenu(Number(currId))
    GetRecordList(currId)
    localStorage.setItem('ParentId', person.ParentId)
    localStorage.setItem('CurrId', person.CurrId)
}

// 详情页返回列表页
const localChi=()=>{
    let ParentsId = Number(localStorage.getItem('ParentId'))
    let CurrsId = Number(localStorage.getItem('CurrId'))
    if (ParentsId && CurrsId) {
        GetRecordMenu(CurrsId)
        GetRecordList(CurrsId)
    }
}
localChi()

// 年级切换
let gradeChange=(grade:any,grIndex:number)=>{
    person.searchActive=grIndex
    person.subjectActive = -1
    person.classList=grade.Children
    GetRecordList(grade.Id)
}
// 科目切换
let subjectChange = (sub: any, index: number) => {
    person.subjectActive = index
    GetRecordList(sub.Id)
}

let router = useRouter();
let toStudy = (item: any)=> {
    router.push({ name: 'recordDetails', query: { NodeId: item.Id, Id: item.Chapter[0].Id, ParentId: person.ParentId, CurrId: person.CurrId }})
}
// 查看课时
let sessClick=(item:any,index:number)=>{
    item.selectShow=!item.selectShow
}
// 选中当前课时
let changeVideo=(item:any,itemIndex:number)=>{
    person.videoActive=itemIndex
    router.push({ name: 'recordDetails', query: { NodeId: item.Id, Id: item.Chapter[itemIndex].Id, ParentId: person.ParentId, CurrId: person.CurrId } })
}
</script>
<style lang="scss" scoped >
.home_box{
    width: 100%;
    display: flex;
    .right_box{
        padding: 30px;
        padding-bottom: 0px;
        width: 100%;
    }
    .seachTop::-webkit-scrollbar {
        width: 0;
    }
    /deep/ .seachTop {
        background: #FFFFFF;
        width: 100%;
        padding: 15px 40px;
        border-radius: 10px;
        margin-bottom: 20px;
        .search_list:nth-child(2) {
            margin-top: 10px;
            padding-top: 10px;
            border-top: 1px solid #E0E0E0;
        }
        .search_list {
            display: flex;
            flex-wrap: wrap;
            width: 100%;
            .search_item {
                padding: 5px 22px;
                margin-right: 20px;
                font-size: 18px;
                white-space: nowrap;
                border-radius: 8px;
                color: #828282;
                background: #F2F2F2;
            }
            .search_item_active {
                background: #E9EDFF;
                color: #6B86FF !important;
            }
            .search_item1 {
                border: 1px solid #E0E0E0;
                border-radius: 8px;
                color: #828282;
                font-size: 18px;
                padding: 3px 20px;
                margin-right: 20px;
            }
            .search_item1_active {
                border: 1px solid #6B86FF !important;
                color: #6B86FF !important;
            }
        }
    }
    .main_scroll::-webkit-scrollbar{
        width: 0;
    }
    .main_scroll{
        height: calc(100vh - 238px);
        overflow-y: scroll;
        /deep/ .el-empty{
            height: 90%;
            .el-empty__image{
                width: 100px;
            }
        }
        .curriculumDiv{
            display: flex;
            flex-wrap: wrap;
            padding: 5px;
            .main_item:hover{
                box-shadow: 0px 5px 15px rgba(102, 102, 102,.5);
            }
            .main_item{
                width: 23.4%;
                margin-bottom: 20px;
                background: #fff;
                margin-right: 2.133%;
                padding: 15px;
                cursor: pointer;
                border-radius: 20px;
                box-shadow: 0px 3px 12px rgba(0, 0, 0, 0.05);
                position: relative;
                .video_img{
                    width: 100%;
                    height: 190px;
                    border-radius: 10px;
                    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.2);
                        display: flex;
                        flex-direction: column;
                        justify-content: center;
                        img{
                            width: 65px;
                            margin: 0 auto;
                        }
                    }
                    .course_icon{
                        position: absolute;
                        bottom: 0;
                        display: flex;
                        align-items: center;
                        justify-content: space-between;
                        padding: 10px 26px;
                        color: #fff;
                        background: rgba(0, 0, 0, 0.4);
                        width: 100%;
                        font-size: 14px;

                    }
                    .course_flex{
                        display: flex;
                        align-items: center;
                        img{
                            width: 16px;
                            margin-right: 5px;
                        }
                    }
                    .course_icon1{
                        position: absolute;
                        bottom: 0;
                        padding: 10px 26px;
                        color: #fff;
                        background: rgba(0, 0, 0, 0.4);
                        width: 100%;
                        font-size: 14px;
                        display: flex;
                        align-items: center;
                        justify-content: center;
                        img{
                            width: 14px;
                            margin-left: 5px;
                        }
                    }
                }
                /* 显示或关闭动画*/
                .open {
                    animation: fadeInDown .5s;
                }
                @keyframes fadeInDown
                {
                    from {
                        opacity: 0;
                        transform: stranslate(0,-1000px); /* 标准语法 */
                    }
                    to {
                        opacity:1;
                        transform: stranslate(0,10px); /* 标准语法 */
                    }
                }

                .video_list{
                    position: absolute;
                    left: 0;
                    // bottom: -180px;
                    box-shadow: 0px 3px 12px rgba(0, 0, 0, 0.1);
                    // height: 232px;
                    width: 100%;
                    z-index: 1000;
                    border-radius: 0 0 20px 20px;
                    background: #fff;
                    padding: 0px 20px 20px 20px;
                    .video_scorll::-webkit-scrollbar-thumb {
                        border-radius: 20px;
                        background-color: #E8E8E8;
                    }
                    .video_scorll::-webkit-scrollbar {
                        width: 5px;
                    }
                    .video_scorll{
                        height: 255px;
                        overflow-y: scroll;
                        .video_item{
                            padding-bottom: 15px;
                            color: #4F4F4F;
                            padding-top: 15px;
                            font-size: 18px;
                            cursor: pointer;
                            border-bottom: 1px solid #eee;
                        }
                        .video_item_active{
                            color: #6B86FF !important;
                        }
                    }
                }
                .video_name{
                    margin-top: 15px;
                    color: #4F4F4F;
                    display: flex;
                    align-items: center;
                    justify-content: space-between;
                    p{
                        line-height: 25px;
                        font-size: 18px;
                        font-weight: 500;
                        display: -webkit-box;
                        -webkit-box-orient: vertical;
                        -webkit-line-clamp: 1;
                        overflow: hidden;
                        width: 70%;
                    }
                    span{
                        font-size: 14px;
                    }
                }
            }
            .main_item:nth-child(4n){
                margin-right: 0px !important;
            }
        }
    }
}
</style>

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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值