Files
tra-app/src/pages/course/index.vue
T
2025-09-12 16:59:05 +08:00

754 lines
19 KiB
Vue

<template>
<view class="course-center max_page">
<view class="top-bg"></view>
<view class="bot-bg"></view>
<view class="course-center-body">
<view class="body-title">
<!-- @click="test.testApi" -->
<uv-icon class="arrow-icon" name="arrow-left" @click="common.switchTab('/pages/index/index')"/>
AI学习
</view>
<view class="input-view" @click="toSearchPage">
<uv-input shape="circle"
placeholderStyle='color:#999999;font-size:28rpx'
placeholder="请输入课程名"
suffixIcon="search"
:customStyle="customStyle"
:suffixIconStyle="suffixIconStyle"
:readonly="true">
</uv-input>
</view>
<view class="tabs-view">
<uv-tabs :list="tabList" @click="changeTab" :scrollable="false"
active-style="font-weight:bold; color:#000;"
line-color="#06f"
></uv-tabs>
</view>
<view class="list-view">
<view class="filter-btn" @click="()=>{showFilterList = !showFilterList; activeTagPop = activeTag}">筛选
<uv-icon class="filter-icon" name="list"/>
</view>
<view :class="['filter-list',showFilterList?'show':'']">
<scroll-view :scroll-y="true" class="scroll-box-tags">
<view class="filter-type" v-for="item in tagsTree" :key="item.value">
<!-- .filter(t=>(t.children||[]).length>0) -->
<view class="filter-type-title">{{item.label}}</view>
<scroll-view :scroll-x="true" class="filter-type-list">
<view :class="['filter-type-item', activeTagPop.includes(itemT.value)?'is-active':'']" v-for="itemT in (item.children||[])" @click="chooseTagPop(itemT)">
{{itemT.label}}
</view>
</scroll-view>
</view>
</scroll-view>
<view class="btns-box">
<view class="btn-sub cencle-btn" @click="showFilterList = false">取消</view>
<view class="btn-sub" @click="changeChooseTagsList">筛选</view>
</view>
</view>
<view :class="['model-filter-list',showFilterList?'show':'']" @click="showFilterList = false"></view>
<uv-scroll-list class="tags-view" :indicator="false">
<uv-tags text="全部"
bgColor="#f6f8fa"
:borderColor="activeTag.length === 0 ? '#06f' : '#f6f8fa'"
:color="activeTag.length === 0 ? '#06f' : '#333'"
:class="activeTag.length === 0?'tags-item active-tags-item':'tags-item'"
size="medium"
:type="activeTag.length === 0?'primary':'info'"
@click="chooseTag({value:''})"
></uv-tags>
<uv-tags :text="item.label"
v-for="item in tagsList"
:key="item.value"
size="medium"
:bgColor="'#f6f8fa'"
:borderColor="activeTag.includes(item.value)?'#06f' : '#f6f8fa'"
:color="activeTag.includes(item.value)?'#06f' : '#333'"
@click="chooseTag(item)"
:class="activeTag.includes(item.value)?'tags-item active-tags-item':'tags-item'"
></uv-tags>
</uv-scroll-list>
<view class="course-list">
<view class="left-type-list" v-if="'全部' === activeTab">
<view
v-for="item in courseTypeList"
:key="item.value"
:class="['type-item',activeCourseType == item.value ?'is-active':'']"
@click="changeCourseType(item)"
>
{{item.label}}
</view>
</view>
<view class="left-type-list" v-if="'生产线'===activeTab">
<view
v-for="item in coursePrdlineList"
:key="item.value"
:class="['type-item', activePrdlineType == item.value ?'is-active':'']"
@click="changePrdLine(item)"
>
{{item.label}}
</view>
</view>
<scroll-view class="right-course-list" :scroll-y="true" @scrolltolower="getMoreList()">
<view
class="type-list"
v-for="item in courseListTreeShow"
>
<view
class="type-title"
v-if="item.courseList.length > 0 && showTypeName"
>
<!-- v-if="itemT.courseList.length>0 && " -->
<view class="left-color"></view>
{{item.label}}
</view>
<CourseItem v-for="itemC in item.courseList" :itemInfo="itemC" :activeTag="activeTag" @clickItem="toCourseDetail(itemC)"/>
<view
class="type-list"
v-for="itemT in item.children"
>
<view class="type-title" v-if="itemT.courseList.length>0">
<view class="left-color"></view>
{{itemT.label}}
</view>
<CourseItem v-for="itemTC in itemT.courseList" :activeTag="activeTag" :itemInfo="itemTC" @clickItem="toCourseDetail(itemTC)"/>
</view>
</view>
<view class="loading-box" v-show="loading">
<CommonLoading color="#06f" class="loading-val"/>
</view>
</scroll-view>
</view>
</view>
</view>
</view>
</template>
<script setup>
import CourseItem from "@/components/course-item/course-item.vue"
import common from '@/common/common'
import {
getPrdLineTypeListApi,
getTraTagListApi,
getCourseTypeListApi,
queryCrsInfoByCatalogApi,
queryCrsInfoByPrdLineApi,
queryCrsInfoByLastnewApi,
queryCrsInfoByPopularApi,
queryCrsInfoByRecmdApi
} from "@/api/course.js"
import { onMounted, onActivated, ref, computed, onUnmounted } from 'vue'
import { onShow } from "@dcloudio/uni-app"
const customStyle = {
backgroundColor: "#ffffff",
paddingLeft: "40rpx",
height:'60rpx',
paddingTop: "5px",
paddingBottom: "5px",
border: "none"
}
const suffixIconStyle = {
color: "#2c8ef2",
fontSize: "40rpx"
}
const tabsCustomStyle ={
backgroundColor: "#ffffff",
borderRadius: '23rpx',
}
const tabList = [
{ name:'全部'},
{ name:'热门'},
{ name:'最新'},
{ name:'推荐'},
{ name:'生产线'},
]
const activeTab = ref('全部')
const activeCourseType = ref('')
const activePrdlineType = ref('')
const activeTag = ref([])
const courseTypeList = ref([])
const coursePrdlineList = ref([])
const tagsTree = ref([])
const showFilterList = ref(false)
const {
statusBarHeight
} = uni.getWindowInfo()
const pageTopPadding = computed(() => statusBarHeight + 'px')
const activeTagPop = ref([])
const chooseTagPop = (item) => {
if(!item.value){
activeTagPop.value = []
return
}
activeTagPop.value = activeTagPop.value.filter((t=>!!t))
if(activeTagPop.value.includes(item.value)){
activeTagPop.value = activeTagPop.value.filter((t=>t !== item.value))
}else{
activeTagPop.value.push(item.value)
}
}
// 点击标签
const chooseTag = (item)=>{
if(!item.value){
activeTag.value = []
} else{
activeTag.value = activeTag.value.filter((t=>!!t))
if(activeTag.value.includes(item.value)){
activeTag.value = activeTag.value.filter((t=>t !== item.value))
}else{
activeTag.value.push(item.value)
}
}
pageObj.value.page = 1
pageObj.value.total = 0
getMoreList(1)
// showFilterList.value = false
}
// 点击生产线分类
const changePrdLine = (item)=>{
activePrdlineType.value = item.value === activePrdlineType.value ? '' : item.value
getMoreList(1)
}
// 点击课程分类
const changeCourseType = (item)=>{
activeCourseType.value = item.value === activeCourseType.value ? '' : item.value
getMoreList(1)
}
const showTypeName = computed(()=>{
return (activeTab.value === '全部' && activeCourseType.value === '') || (activeTab.value === '生产线' && activePrdlineType.value === '')
})
const tagsList = computed(()=>{
let _arr = []
tagsTree.value.forEach(t=>{
let _tarr = t.children || []
_arr = _arr.concat(_tarr)
})
let _reqList = _arr.filter(t=>t.required === "Y")
let _reqListN = _arr.filter(t=>t.required === "N")
return _reqList.concat(_reqListN)
})
const changeChooseTagsList = () => {
showFilterList.value = false;
activeTag.value = activeTagPop.value
pageObj.value.page = 1
pageObj.value.total = 0
getMoreList(1)
}
const courseListBase = ref([])
const courseList = computed(()=>{
return courseListBase.value
// if(activeTag.value.length > 0 && !!activeTag.value[0]){
// return courseListBase.value
// .filter(t=>{
// let _tags = (t.tagCataList||[]).map(k=>k.tagId)
// let _pass = false
// activeTag.value.forEach(k => {
// if(_tags.includes(k)){
// _pass = true
// }
// })
// return _pass
// })
// }else{
// return courseListBase.value
// }
})
// 整理树形
const courseListTree = computed(()=>{
let _arr = activeTab.value === '全部'? courseTypeList.value
: activeTab.value === '生产线'? coursePrdlineList.value: []
let _idKey = activeTab.value === '全部'? 'tagId': 'prdLineId'
let _tree = _arr.map(t=>{
let _children = t.children||[]
return {
label: t.label,
required: t.required,
value:t.value,
courseList: courseList.value.filter(k=>k[_idKey] === t.value),
children: _children.map(k=>{
return {
...k,
courseList: courseList.value.filter(m=>m[_idKey] === k.value),
}
})
}
})
return _tree
})
// 过滤树形
const courseListTreeShow = computed(()=>{
let _type = activeTab.value === '全部'? activeCourseType.value : activePrdlineType.value
if(activeTab.value === '全部'|| activeTab.value === '生产线'){
if(_type){
return courseListTree.value.filter(t=> t.value === _type)
}else{
return courseListTree.value
}
}else{
return [{
children: [],
courseList: courseList.value,
label: "",
value: "333"
}]
}
})
const pageObj = ref({
page: 1,
limit: 20,
total: 0
})
const getMoreList = (flag) => {
if(!flag){
if(pageObj.value.total <= (pageObj.value.page * pageObj.value.limit)) return
pageObj.value.page += 1
}else{
courseListBase.value = []
pageObj.value.page = 1
}
pageObj.value.total = 0
loading.value = true
switch (activeTab.value){
case '全部':
queryCrsInfoByCatalogApi({
...pageObj.value,
tagId: activeTag.value.join(','),
catalogId: activeCourseType.value
}).then(res=>{
courseListBase.value = courseListBase.value.concat(res.body || [])
pageObj.value.total = res.total
}).finally(()=>{
loading.value = false
})
break;
case '热门':
break;
case '最新':
break;
case '推荐':
break;
case '生产线':
queryCrsInfoByPrdLineApi({
...pageObj.value,
tagId: activeTag.value.join(','),
prdLineId: activePrdlineType.value
}).then(res=>{
courseListBase.value = courseListBase.value.concat(res.body || [])
pageObj.value.total = res.total
}).finally(()=>{
loading.value = false
})
break;
}
}
const loading = ref(false)
const changeTab = (item) => {
if(!item) return
courseListBase.value = []
activeTab.value = item.name
activeTag.value = []
pageObj.value.page = 1
pageObj.value.total = 0
// activeTag.value = ''
activeCourseType.value = ''
activePrdlineType.value = ''
loading.value = true
switch (item.name){
case '全部':
queryCrsInfoByCatalogApi(pageObj.value).then(res=>{
courseListBase.value = res.body || []
pageObj.value.total = res.total
}).finally(()=>{
loading.value = false
})
break;
case '热门':
queryCrsInfoByPopularApi(pageObj.value).then(res=>{
courseListBase.value = res.body || []
}).finally(()=>{
loading.value = false
})
break;
case '最新':
queryCrsInfoByLastnewApi(pageObj.value).then(res=>{
courseListBase.value = res.body || []
}).finally(()=>{
loading.value = false
})
break;
case '推荐':
queryCrsInfoByRecmdApi(pageObj.value).then(res=>{
courseListBase.value = res.body || []
}).finally(()=>{
loading.value = false
})
break;
case '生产线':
queryCrsInfoByPrdLineApi(pageObj.value).then(res=>{
courseListBase.value = res.body || []
pageObj.value.total = res.total
}).finally(()=>{
loading.value = false
})
break;
}
}
// 跳转搜索页
const toSearchPage = () => {
common.navigateTo('/pages/search/search?from=course')
}
// 跳转 课程详情
const toCourseDetail = (item) => {
common.navigateTo('/pages/courseDetail/index?crsNum=' + item.crsNum + '&crsId=' + item.crsId)
// common.navigateTo('/pages/course/study?crsNum=' + item.crsNum)
}
onMounted(()=>{
getCourseTypeListApi().then(res=>{
courseTypeList.value = res.body||[]
})
getPrdLineTypeListApi().then(res=>{
coursePrdlineList.value = res.body||[]
})
getTraTagListApi().then(res=>{
tagsTree.value = res.body||[]
})
changeTab({name:'全部'})
uni.$on('refresh_course_list', () => {
changeTab({name:activeTab.value || '全部'})
});
})
onUnmounted(() => {
uni.$off('refresh_course_list');
})
onActivated(() => {
// 重新获取标签信息
getTraTagListApi().then(res=>{
tagsTree.value = res.body||[]
})
changeTab({name:activeTab.value || '全部'})
});
onShow(()=>{
changeTab({name:activeTab.value || '全部'})
})
</script>
<style lang="scss" scoped>
.course-center{
position: relative;
display: flex;
flex-direction: column;
.top-bg{
background: linear-gradient(16deg,rgba(234,246,255,0) 0%,#c3e6ff 100%);
height: 480rpx;
width: 750rpx;
}
.bot-bg{
// background-color: #F6F8FF;
background: linear-gradient(136deg,rgba(234,246,255,0) 0%,#c3e6ff 100%);
width: 750rpx;
flex: 1;
}
.course-center-body{
position: absolute;
overflow: hidden;
left:0;
top: 0;
height: 100%;
width: 100%;
padding:0 15rpx;
box-sizing: border-box;
display: flex;
flex-direction: column;
padding-top: v-bind(pageTopPadding);
// padding-top: var(--status-bar-height);
.body-title{
height: 90rpx;
// background-color: red;
text-align: center;
font-size: 33rpx;
padding-top: 10rpx;
line-height: 46rpx;
box-sizing: border-box;
font-weight: 500;
color:#000;
position: relative;
// margin-bottom: 23rpx;
.arrow-icon{
position: absolute;
left:36rpx;
top:20rpx;
color: #000;
}
}
.input-view{
}
.tabs-view{
background: #ffffff;
margin-top: 23rpx;
border-radius: 23rpx;
padding: 6rpx 10rpx 12rpx;
margin-bottom:15rpx;
}
.list-view{
flex:1;
overflow: hidden;
margin-bottom: 15rpx;
background: #ffffff;
border-radius: 23rpx;
display: flex;
flex-direction: column;
position: relative;
.filter-list{
position: absolute;
top: 84rpx;
left:0;
width: 100%;
height: 588rpx;
padding-bottom: 80rpx;
overflow-y: hidden;
z-index: 15;
background-color: #f9f9f9;
box-shadow: 0rpx 12rpx 20rpx rgba(160,160,160,.25);
border-radius: 0 0 0rpx 0rpx;
box-sizing: border-box;
position: relative;
.scroll-box-tags{
height: 508rpx;
padding: 0rpx 25rpx;
box-sizing: border-box;
}
.filter-type{
padding: 10rpx 0;
box-sizing: border-box;
.filter-type-title{
font-size: 29rpx;
line-height: 40rpx;
margin-bottom: 15rpx;
color: #000;
}
.filter-type-list{
overflow: hidden;
.filter-type-item{
// width: calc(25% - 18rpx);
display: inline-block;
font-size: 29rpx;
box-sizing: border-box;
padding:0 20rpx;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
text-align: center;
float: left;
margin-right: 24rpx;
height: 65rpx;
line-height: 65rpx;
background-color: #f1f3f8;
border-radius: 6rpx;
margin-bottom: 20rpx;
&:nth-child(4n){
margin-right: 24rpx;
}
&.is-active{
color: #06f;
}
}
}
&+.filter-type{
margin-top: 20rpx;
}
}
.btns-box{
height: 80rpx;
position: absolute;
background-color: #fff;
bottom: 0;
left: 0;
width: 100%;
text-align: center;
padding-top: 10rpx;
.btn-sub{
display: inline-block;
text-align: center;
background-color: #06f;
height: 60rpx;
line-height: 58rpx;
border: 1rpx solid #06f;
padding: 0 40rpx;
color: #fff;
border-radius: 8rpx;
&.cencle-btn{
background-color: #fff;
color: #06f;
margin-right: 40rpx;
}
}
}
}
.model-filter-list{
position: fixed;
width: calc(100vw - 33rpx);
height: 80vh;
overflow: hidden;
top: 500rpx;
left: 17rpx;
z-index: 1;
background-color: rgba(0,0,0,.2);
}
.filter-list,
.model-filter-list{
display: none;
&.show{
display: block;
}
}
.filter-btn{
position: absolute;
right: 0;
top: 0;
height: 83rpx;
width: 120rpx;
line-height: 83rpx;
font-size: 26rpx;
background: #fff;
z-index: 2;
text-align: center;
box-shadow: -4rpx 0 6rpx rgba(0,0,0,.05);
white-space: nowrap;
padding-right: 30rpx;
box-sizing: border-box;
.filter-icon{
// display: inline-block;
font-size: 26rpx;
position: absolute;
right: 15rpx;
top: 50%;
transform: translateY(-50%);
}
}
.tags-view{
height: 84rpx;
display: block;
padding: 15rpx 120rpx 15rpx 20rpx;
box-sizing: border-box;
white-space: nowrap;
border-bottom: 2rpx solid #eee;
.tags-item{
margin-right: 30rpx;
display: inline-block;
&.active-tags-item{
position: relative;
overflow: hidden;
border-radius: 4rpx;
overflow: hidden;
&::before{
content: '';
display: block;
width: 40rpx;
height: 40rpx;
position: absolute;
right: -20rpx;
bottom: -20rpx;
transform: rotateZ(-45deg);
background-color: #06f;
overflow: hidden;
z-index: 10;
}
&::after{
content: '';
display: block;
width: 8rpx;
height: 4rpx;
position: absolute;
right: 3rpx;
bottom: 8rpx;
transform: rotateZ(-45deg);
border-left: 1px solid #fff;
border-bottom: 1px solid #fff;
z-index: 11;
}
}
::v-deep .uv-tags{
padding: 0 20rpx;
.uv-tags__text{
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 200rpx;
}
}
}
}
.course-list{
display: flex;
flex:1;
overflow: hidden;
.left-type-list{
width: 152rpx;
border-right: 2rpx solid #eee;
overflow-y: auto;
.type-item{
min-height: 90rpx;
line-height: 40rpx;
font-size: 22rpx;
box-sizing: border-box;
padding: 25rpx 18rpx;
overflow: hidden;
// text-overflow: ellipsis;
word-break: break-all;
text-align: center;
white-space: pre-wrap;
&.is-active{
background-color: rgba(44,142,242,.1);
color: #06f;
}
}
}
.right-course-list{
flex:1;
overflow-y: auto;
.type-list{
.type-title{
font-size: 33rpx;
line-height: 43rpx;
margin: 20rpx 15rpx 5rpx;
min-height: 43rpx;
// overflow: hidden;
// text-overflow: ellipsis;
// white-space: nowrap;
word-break: break-all;
font-weight: 600;
.left-color{
width: 8rpx;
height: 33rpx;
margin-top: 5rpx;
float: left;
overflow: hidden;
background-color: #06f;
margin-right: 8rpx;
}
}
}
}
}
}
.loading-box{
width: 100%;
height: 100rpx;
position: relative;
.loading-val{
position: absolute;
left:50%;
top:50%;
transform: translate(-50%, -50%);
}
}
}
}
</style>