列表页
详情页
需求:
点击左侧菜单一级标题,加载右侧列表
点击左侧菜单二级标题,加载右侧列表,同时加载班级或者科目切换列表
切换左侧任意标题后,刷新当前列表页,左侧点击值和右侧列表数据需保持不变
通过右侧列表点击到详情页后,也调用左侧菜单
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>
希望我的愚见能够帮助你哦~,若有不足之处,还望指出,你们有更好的解决方法,欢迎大家在评论区下方留言支持,大家一起相互学习参考呀~