liurongli пре 3 недеља
родитељ
комит
b58bf8bfaf

+ 88 - 48
components/nav-bar.vue

@@ -1,27 +1,33 @@
 <template>
-	<uni-nav-bar :class="{'custom_uni_nav_bar':!onlyTitle,'custom_navbar_only_title':onlyTitle}" :height="`${height}rpx`" :backgroundColor="backgroundColor" :title="title" color="#333333" :fixed="true" :border="showHeaderborder" :statusBar="true" :leftWidth="leftWidth" :rightWidth="rightWidth" left-icon="left" @clickLeft="GoBack()">
+	<uni-nav-bar :class="{'custom_uni_nav_bar':!onlyTitle,'custom_navbar_only_title':onlyTitle}"
+		:height="`${height}rpx`" :backgroundColor="backgroundColor" :title="title" color="#333333" :fixed="true"
+		:border="showHeaderborder" :statusBar="true" :leftWidth="leftWidth" :rightWidth="rightWidth" left-icon="left"
+		@clickLeft="GoBack()">
 		<!-- 表头内容 -->
 		<view class="nav_head" v-if="!onlyTitle">
 			<text class="nav_title">{{title}}</text>
-			<uni-easyinput class="search_box" v-model="state.searchWord" trim="all" :styles="state.searchInputStyles" :placeholderStyle="state.searchInputPlaceholderStyle" placeholder="请输入关键字搜索" @input="handleSearchInput">
+			<uni-easyinput class="search_box" v-model="state.searchWord" trim="all" :styles="state.searchInputStyles"
+				:placeholderStyle="state.searchInputPlaceholderStyle" placeholder="请输入关键字搜索" @input="HandleSearchInput">
 				<template #left>
-					<image src="/static/image/overview/search.png" class="search_icon" ></image>
+					<image src="/static/image/overview/search.png" class="search_icon"></image>
 				</template>
 			</uni-easyinput>
 		</view>
 		<!-- 条件选择器 -->
 		<view class="filter_picker" v-if="showFilterPicker">
-			<picker mode="selector" v-for="(item,index) in filterPickerData" :key="index" :range="item" :value="state.pickerIndex" @change="handleTypeChange" style="width: calc((100% - 32rpx * 2) / 3);">
-				<view class="filter_item">
-					<text class="filter_text">{{ item[state.pickerIndex] }}</text>
-					<uni-icons type="down" size="16" color="#999999"></uni-icons>
-				</view>
-			</picker>
+			<template v-for="(item,index) in filterPickerData" :key="index">
+				<picker mode="selector" v-if="item?.list?.length > 0" :range="item.list" :value="item.valueIndex" range-key="label" style="width: calc((100% - 32rpx * 2) / 3);" @change="(e)=>ChangeFilterPicker(e,index)">
+					<view class="filter_item">
+						<text class="filter_text">{{ item?.list?.[item.valueIndex]?.label || '' }}</text>
+						<uni-icons type="down" size="16" color="#999999"></uni-icons>
+					</view>
+				</picker>
+			</template>
 		</view>
 		<!-- tabs -->
 		<scroll-view v-if="showTabs" class="scroll_tabs" scroll-x="true" scroll-left="0" :show-scrollbar="false">
-			<view :class="['tab_item',{'selected':item.sortType==state.tabsSelected}]" v-for="item in tabList" :key="item.sortType" @click="SelectMenu(item.sortType)">
-				{{item.name}}
+			<view :class="['tab_item',{'selected':item.value==state.tabsSelected}]" v-for="item in tabList" :key="item.value" @click="SelectTabsValue(item.value)">
+				{{item.label}}
 			</view>
 		</scroll-view>
 	</uni-nav-bar>
@@ -30,77 +36,106 @@
 <script setup>
 	import uniNavBar from '@/uni_modules/uni-nav-bar/components/uni-nav-bar/uni-nav-bar.vue';
 	import uniIcons from '@/uni_modules/uni-icons/components/uni-icons/uni-icons.vue';
-	import { reactive } from 'vue';
-	const emit = defineEmits(['GoBack']);
+	import {
+		reactive,
+		onMounted
+	} from 'vue';
+	const emit = defineEmits(['GoBack','CommonFilterData']);
 	const props = defineProps({
-		height: {//高度
+		height: { //高度
 			type: String,
 			default: () => ''
 		},
-		leftWidth: {//导航栏左侧插槽宽度
+		leftWidth: { //导航栏左侧插槽宽度
 			type: String,
 			default: () => '96rpx'
 		},
-		rightWidth: {//导航栏右侧插槽宽度
+		rightWidth: { //导航栏右侧插槽宽度
 			type: String,
 			default: () => '96rpx'
 		},
-		title: {//标题
+		title: { //标题
 			type: String,
 			default: () => ''
 		},
-		showHeaderborder:{//是否显示边框 默认不显示
-			type:Boolean,
-			default:false
+		showHeaderborder: { //是否显示边框 默认不显示
+			type: Boolean,
+			default: false
 		},
-		backgroundColor: {//背景色
+		backgroundColor: { //背景色
 			type: String,
 			default: () => '#FFFFFF'
 		},
-		backPath: {//返回路径
+		backPath: { //返回路径
 			type: String,
 			default: () => ''
 		},
-		onlyTitle:{//是否只显示标题
-			type:Boolean,
-			default:true
+		onlyTitle: { //是否只显示标题
+			type: Boolean,
+			default: true
 		},
-		filterPickerData:{//条件选择器
-			type:Array,
-			default:()=>[]
+		filterPickerData: { //条件选择器
+			type: Array,
+			default: () => []
 		},
-		tabList:{//tabs
-			type:Array,
-			default:()=>[]
+		tabList: { //tabs
+			type: Array,
+			default: () => []
 		},
-		showFilterPicker:{//是否显示条件选择器
-			type:Boolean,
-			default:false
+		showFilterPicker: { //是否显示条件选择器
+			type: Boolean,
+			default: false
 		},
-		showTabs:{//是否显示条件选择器tabs
-			type:Boolean,
-			default:false
+		showTabs: { //是否显示条件选择器tabs
+			type: Boolean,
+			default: false
 		}
 	});
 	const state = reactive({
-		pickerIndex:0,
-		tabsSelected:'1',
-		searchWord:'',
-		searchInputStyles:{
-			borderColor:'#DCDFE6',
-			color:'#333333'
+		searchWord: '', //关键字
+		filterPickerValue: [], //条件选择器默认值
+		tabsSelected: '', //tabs 默认选中
+		searchInputStyles: { //搜素框样式
+			borderColor: '#DCDFE6',
+			color: '#333333'
 		},
-		searchInputPlaceholderStyle:{
-			color: '#C0C4CC',
-			fontSize: '28rpx'
-		}
+		searchInputPlaceholderStyle: 'color: #C0C4CC;fontSize: 28rpx' //搜素框样式
 	})
+	//关键字搜索
+	const HandleSearchInput = (e) => {
+		state.searchWord = e;
+		CommonFilterData(state.searchWord, {
+			filterPickerValue: [...state.filterPickerValue]
+		}, state.tabsSelected, 'search');
+	}
+	//切换条件选择器  index:数组filterPickerData索引
+	const ChangeFilterPicker = (e, index) => {
+		const valueIndex = e.detail.value; //下拉框选中值的索引
+		const selectedValue = props.filterPickerData?.[index]?.list?.[valueIndex]?.value ?? '';
+		state.filterPickerValue[index] = selectedValue;
+		CommonFilterData(state.searchWord, {
+			valueIndex,
+			index,
+			filterPickerValue: [...state.filterPickerValue]
+		}, state.tabsSelected, 'filterPicker');
+	}
+	//tabs切换
+	const SelectTabsValue = (value) => {
+		state.tabsSelected = value;
+		CommonFilterData(state.searchWord, {
+			filterPickerValue: [...state.filterPickerValue]
+		}, state.tabsSelected, 'tabs');
+	}
+	//条件发生改变进行数据查询
+	const CommonFilterData = (searchWord, filterPickerValue, tabsSelected, type) => {
+		emit('CommonFilterData', searchWord, filterPickerValue, tabsSelected, type);
+	}
 	const GoBack = () => {
 		if (props.backPath == 'myIndex') {
 			uni.switchTab({
 				url: '/pages/my/index'
 			});
-		}else if(props.backPath == 'workspace') {//跳转工作台
+		} else if (props.backPath == 'workspace') { //跳转工作台
 			uni.switchTab({
 				url: '/pages/workspace/index'
 			});
@@ -108,6 +143,10 @@
 			emit('GoBack');
 		}
 	}
+	onMounted(() => {
+		state.tabsSelected = props?.tabList?.[0]?.value || ''; //tab 默认值
+		state.filterPickerValue = props.filterPickerData.map(item => item?.defaultValue ?? '');
+	})
 </script>
 
 <style scoped lang="scss">
@@ -116,6 +155,7 @@
 		position: relative;
 		z-index: 999;
 	}
+
 	:deep(.uni-navbar__header-container) {
 		padding: 0;
 	}

+ 245 - 96
pages/teacherStudy/courseCenter/index.vue

@@ -1,126 +1,275 @@
 <template>
 	<!-- 头部 -->
-	<nav-bar height="312" backPath="workspace" title="教师研修" :filterPickerData="state.filterPickerData" :tabList="state.tabList" :onlyTitle="false" :showFilterPicker="true" :showTabs="true" rightWidth="0rpx" @GoBack="GoBack"></nav-bar>
+	<nav-bar height="312" backPath="workspace" title="教师研修" :filterPickerData="state.filterPickerData" :tabList="state.tabList" :onlyTitle="false" :showFilterPicker="true" :showTabs="true" rightWidth="0rpx" @CommonFilterData="CommonFilterData"></nav-bar>
 	<view class="page_content">
-		<view class="list_item">
-			<image class="item_img" mode="aspectFill" src="https://jtzx001.oss-accelerate.aliyuncs.com/huijiaoyan_test/20260609_255638/09101629_53c71c9b-3072-4fa5-9944-3598c9650666.jpg"></image>
-			<view class="item_title">2025学年高一化学公开课全国评比大赛赛赛赛赛赛赛赛赛赛学公开课全国评比大赛赛赛赛赛赛赛赛赛赛</view>
-			<view class="item_time"><text class="time">2025-11-12</text></view>
-		</view>
-		<view class="list_item">
-			<image class="item_img" mode="aspectFill" src="https://jtzx001.oss-accelerate.aliyuncs.com/huijiaoyan_test/20260609_255638/09101629_53c71c9b-3072-4fa5-9944-3598c9650666.jpg"></image>
-			<view class="item_title">2025学年高一化学公开课全国评比大赛赛赛赛赛赛赛赛赛赛学公开课全国评比大赛赛赛赛赛赛赛赛赛赛</view>
-		</view>
-		<view class="list_item">
-			<image class="item_img" mode="aspectFill" src="https://jtzx001.oss-accelerate.aliyuncs.com/huijiaoyan_test/20260609_255638/09101629_53c71c9b-3072-4fa5-9944-3598c9650666.jpg"></image>
-			<view class="item_title">2025学年高一化学公开国赛</view>
-		</view>
-		<view class="list_item">
-			<image class="item_img" mode="aspectFill" src="https://jtzx001.oss-accelerate.aliyuncs.com/huijiaoyan_test/20260609_255638/09101629_53c71c9b-3072-4fa5-9944-3598c9650666.jpg"></image>
-			<view class="item_title">2025学年高一化学公开课全国评比大赛赛赛赛赛赛赛赛赛赛学公开课全国评比大赛赛赛赛赛赛赛赛赛赛</view>
-		</view>
-		<view class="list_item">
-			<image class="item_img" mode="aspectFill" src="https://jtzx001.oss-accelerate.aliyuncs.com/huijiaoyan_test/20260609_255638/09101629_53c71c9b-3072-4fa5-9944-3598c9650666.jpg"></image>
-			<view class="item_title">2025学年高一化学公开课全国评比大赛赛赛赛赛赛赛赛赛赛学公开课全国评比大赛赛赛赛赛赛赛赛赛赛</view>
-		</view>
-		<view class="list_item">
-			<image class="item_img" mode="aspectFill" src="https://jtzx001.oss-accelerate.aliyuncs.com/huijiaoyan_test/20260609_255638/09101629_53c71c9b-3072-4fa5-9944-3598c9650666.jpg"></image>
-			<view class="item_title">2025学年高一化学公开课全国评比大赛赛赛赛赛赛赛赛赛赛学公开课全国评比大赛赛赛赛赛赛赛赛赛赛</view>
-		</view>
-		<view class="list_item">
-			<image class="item_img" mode="aspectFill" src="https://jtzx001.oss-accelerate.aliyuncs.com/huijiaoyan_test/20260609_255638/09101629_53c71c9b-3072-4fa5-9944-3598c9650666.jpg"></image>
-			<view class="item_title">2025学年高一化学公开课全国评比大赛赛赛赛赛赛赛赛赛赛学公开课全国评比大赛赛赛赛赛赛赛赛赛赛</view>
-		</view>
-		<view class="list_item">
-			<image class="item_img" mode="aspectFill" src="https://jtzx001.oss-accelerate.aliyuncs.com/huijiaoyan_test/20260609_255638/09101629_53c71c9b-3072-4fa5-9944-3598c9650666.jpg"></image>
-			<view class="item_title">2025学年高一化学公开课全国评比大赛赛赛赛赛赛赛赛赛赛学公开课全国评比大赛赛赛赛赛赛赛赛赛赛</view>
-		</view>
-		<view class="list_item">
-			<image class="item_img" mode="aspectFill" src="https://jtzx001.oss-accelerate.aliyuncs.com/huijiaoyan_test/20260609_255638/09101629_53c71c9b-3072-4fa5-9944-3598c9650666.jpg"></image>
-			<view class="item_title">2025学年高一化学公开课全国评比大赛赛赛赛赛赛赛赛赛赛学公开课全国评比大赛赛赛赛赛赛赛赛赛赛</view>
-		</view>
-		<view class="list_item">
-			<image class="item_img" mode="aspectFill" src="https://jtzx001.oss-accelerate.aliyuncs.com/huijiaoyan_test/20260609_255638/09101629_53c71c9b-3072-4fa5-9944-3598c9650666.jpg"></image>
-			<view class="item_title">2025学年高一化学公开课全国评比大赛赛赛赛赛赛赛赛赛赛学公开课全国评比大赛赛赛赛赛赛赛赛赛赛</view>
-		</view>
-		<view class="list_item">
-			<image class="item_img" mode="aspectFill" src="https://jtzx001.oss-accelerate.aliyuncs.com/huijiaoyan_test/20260609_255638/09101629_53c71c9b-3072-4fa5-9944-3598c9650666.jpg"></image>
-			<view class="item_title">2025学年高一化学公开课全国评比大赛赛赛赛赛赛赛赛赛赛学公开课全国评比大赛赛赛赛赛赛赛赛赛赛</view>
-		</view>
-		<view class="list_item">
-			<image class="item_img" mode="aspectFill" src="https://jtzx001.oss-accelerate.aliyuncs.com/huijiaoyan_test/20260609_255638/09101629_53c71c9b-3072-4fa5-9944-3598c9650666.jpg"></image>
-			<view class="item_title">2025学年高一化学公开课全国评比大赛赛赛赛赛赛赛赛赛赛学公开课全国评比大赛赛赛赛赛赛赛赛赛赛</view>
-		</view>
+		<scroll-view
+		    class="scroll_view_y"
+			:scroll-top="state.scrollTop"
+			scroll-y="true"
+			:refresher-enabled="false" 
+			:enable-back-to-top="true"
+			:show-scrollbar="false" 
+			:scroll-with-animation="true" 
+			refresher-default-style="none"
+			refresher-background="#F8F9FD" 
+			:refresher-triggered="state.isRefreshing" 
+			@scrolltolower="OnLoadMore"
+			@refresherrefresh="OnRefresh"
+			@scroll="OnScroll">
+			<view class="refresh_loading" v-if="state.isRefreshing"><uni-load-more status="loading" /></view>
+			<view class="page_list">
+				<view class="list_item" v-for="item in state.pageList" :key="item.id">
+					<image class="item_img" mode="aspectFill" :src="item.videoCoverUrl"></image>
+					<view class="item_title">2025{{item.courseName}}</view>
+					<view class="item_time">
+						<image class="img" src="@/static/image/icon/time2x.png"></image>
+						<text class="time">{{item.courseStartDatetime}}</text>
+					</view>
+				</view>
+			</view>
+			<view class="no_data" v-if="!state.isLoading && state.pageList.length == 0">
+				<image class="no_data_img" src="@/static/image/bg/no_data.png"></image>
+				<text class="no_data_text">暂无数据</text>
+			</view>
+			<uni-load-more v-if="state.pageList.length > 0" :status="state.loadStatus" />
+		</scroll-view>
 	</view>
 </template>
 
 <script setup>
 	import navBar from '@/components/nav-bar.vue';
+	import {queryCourseTypeDataList,centerPageListData} from '@/reqApi/teacherStudy.js';
 	import {
 		reactive,
-		ref
+		ref,
+		onMounted,
+		nextTick
 	} from 'vue';
 	const state = reactive({
-		filterPickerData: [
-			['全部类型', '全部类型1'],
-			['全部类型', '全部类型2'],
-			['全部类型', '全部类型2']
-		],
+		courseTypeData:[],//顶部查询数据 
+		filterPickerData: [{
+			valueIndex:0,//默认值索引
+			defaultValue:'',//默认值
+			list:[]
+		},{
+			valueIndex:0,//默认值索引
+			defaultValue:'',//默认值
+			list:[]
+		}],
 		tabList: [{
-			name: '最新发布',
-			sortType: '0'
+			label: '最新发布',
+			value: '0'
 		}, {
-			name: '播放最多',
-			sortType: '1'
+			label: '播放最多',
+			value: '1'
 		}, {
-			name: '关注最多',
-			sortType: '2'
-		}]
+			label: '关注最多',
+			value: '2'
+		}],
+		pageList:[],//列表数据
+		queryStr: '',
+		sortType: '0',//排序方式,课程中心参数,0-最新发布,1-播放最多,2-关注最多
+		teachCourseTypeClassId: '',//研修专题	
+		teachCourseTypeId: '',//内容领域	
+		pageSize: 30,
+		pageNum: 1,
+		scrollTop:0,
+		oldScrollTop:0,
+		isRefreshing:false,//设置当前下拉刷新状态,true 表示下拉刷新已经被触发,false 表示下拉刷新未被触发
+		isLoading:true,//加载中
+		noMoreData:false,//没有更多数据了
+		loadStatus:'more',//loading状态
 	})
+	onMounted(() => {
+		getCourseTypeDataList();
+		OnLoadData(true);
+	});
+	//获取顶部查询项
+	const getCourseTypeDataList = () => {
+		queryCourseTypeDataList().then(res => {
+			if(res.code == 200){
+				const defaultData = [{
+					label:'内容领域',
+					id:'',
+					value:''
+				}];
+				const resData = res?.data || [];
+				state.courseTypeData = defaultData.concat(resData);
+				//一级条件
+				state.filterPickerData[0].list = state.courseTypeData.map(item=>({
+					...item,
+					value:item.id
+				}))
+			}else{
+				state.courseTypeData = [];
+				state.filterPickerData = [{
+					valueIndex:0,//默认值索引
+					defaultValue:'',//默认值
+					list:[],
+				},{
+					valueIndex:0,
+					defaultValue:'',//默认值
+					list:[],
+				}]
+			}
+		})
+	}
+	//切换条件选择器
+	const CommonFilterData = (searchWord,filterPicker,tabsSelected,type) => {
+		const {valueIndex, index,filterPickerValue} = filterPicker;
+		if(type == 'filterPicker'){
+			if(index==0){//索引
+				state.filterPickerData[index].valueIndex = valueIndex;//一级条件选中项的索引
+				state.filterPickerData[1].valueIndex = 0;//二级条件选中项的索引
+				const childrenData = state.courseTypeData?.[valueIndex]?.children || [];
+				const childrenList = childrenData.map(item=>({
+					...item,
+					value:item?.id ?? ''
+				}))
+				if(childrenList.length){
+					childrenList.unshift({
+						label:'内容领域',
+						id:'',
+						value:''
+					})
+				}
+				state.filterPickerData[1].list = childrenList;//二级条件数据
+			}else{
+				state.filterPickerData[index].valueIndex = valueIndex;//二级条件选中项的索引
+			}
+		}
+		state.queryStr = searchWord;
+		state.teachCourseTypeId = filterPickerValue[0];
+		state.teachCourseTypeClassId = filterPickerValue[1];
+		state.sortType = tabsSelected;
+		OnLoadData(true)
+	}
+	/*
+	*获取数据
+	* initPage:初始分页 从第一开始
+	*/
+	const OnLoadData = (initPage) => {
+		// uni.showLoading({
+		// 	title: '加载中'
+		// });
+		if (initPage) state.pageNum = 1;//如果是下拉刷新、重新查询 默认加载第一页
+		state.isLoading = true;
+		state.loadStatus = 'loading';
+		centerPageListData({
+			queryStr:state.queryStr,
+			teachCourseTypeId:state.teachCourseTypeId,
+			teachCourseTypeClassId:state.teachCourseTypeClassId,
+			sortType:state.sortType,
+			pageSize:state.pageSize,
+			pageNum:state.pageNum
+		}).then(res=>{
+			if(res.code == 200){
+				const total = Number(res.data.total) || 0;//总数
+				const pages = Math.ceil(total / state.pageSize);//总页数
+				const recordsData = res?.data?.records || [];
+				state.pageList = initPage ? recordsData : [...state.pageList,...recordsData];
+				state.noMoreData = state.pageNum == pages;
+				state.pageNum++;
+			}
+		}).finally(()=>{
+			// uni.hideLoading();
+			state.isLoading = false;
+			state.isRefreshing = false;//下拉刷新未被触发
+			if(state.noMoreData){
+				state.loadStatus = 'no-more';
+			}else{
+				state.loadStatus = 'more';
+			}
+			//返回到页面顶部
+			if(initPage){
+				GoTop();
+			}
+		})
+	}
+	// 自定义下拉刷新被触发	 下拉刷新
+	const OnRefresh = () =>{
+		state.isRefreshing = true;//下拉刷新已经被触发
+		OnLoadData(true);
+	}
+	//滚动到底部/右边 触发上拉刷新
+	const OnLoadMore = () => {
+		if (state.isLoading || state.noMoreData) return;
+		OnLoadData(false);
+	}
+	//	滚动时触发
+	const OnScroll = (e) => {
+		state.oldScrollTop = e.detail.scrollTop;
+	}
+	//返回到顶部
+	const GoTop = () => {
+		// 解决view层不同步的问题
+		state.scrollTop = state.oldScrollTop;
+		nextTick(() => {
+			state.scrollTop = 0
+		});
+	}
 </script>
 
-<style lang="scss">
+<style lang="scss" scoped>
 	.page_content{
-		gap:32rpx;
-		.list_item{
-			&:nth-child(1){
-				margin-top: 16rpx;
-			}
-			&:nth-child(2){
-				margin-top: 16rpx;
+		height: calc(100% - 448rpx);
+		.scroll_view_y{
+			height: 100%;
+			.refresh_loading{
+				padding: 10rpx 0;
 			}
-			width: calc((100% - 32rpx) / 2);
-			overflow: hidden;
+		}
+		.page_list{
 			display: flex;
-			flex-direction: column;
-			.item_img{
-				width: 100%;
-				height: 220rpx;
-				border-radius: 8rpx;
-			}
-			.item_title{
-				display: flex;
-				width: 100%;
-				margin:8rpx 0;
-				font-weight: 400;
-				font-size: 24rpx;
-				color: #333333;
-				display: -webkit-box;
-				-webkit-line-clamp: 2;
-				-webkit-box-orient: vertical;
+			width: 100%;
+			flex-wrap: wrap;
+			gap:32rpx;
+			.list_item{
+				&:nth-child(1){
+					margin-top: 16rpx;
+				}
+				&:nth-child(2){
+					margin-top: 16rpx;
+				}
+				width: calc((100% - 32rpx) / 2);
 				overflow: hidden;
-				text-overflow: ellipsis;
-			}
-			.item_time{
 				display: flex;
-				width: 100%;
-				.time{
+				flex-direction: column;
+				.item_img{
+					width: 100%;
+					height: 220rpx;
+					border-radius: 8rpx;
+				}
+				.item_title{
+					display: flex;
+					width: 100%;
+					margin:8rpx 0;
 					font-weight: 400;
 					font-size: 24rpx;
-					color: #999999;
+					color: #333333;
+					display: -webkit-box;
+					-webkit-line-clamp: 2;
+					-webkit-box-orient: vertical;
+					overflow: hidden;
+					text-overflow: ellipsis;
+				}
+				.item_time{
+					display: flex;
+					align-items: center;
+					width: 100%;
+					.img{
+						width: 28rpx;
+						height: 28rpx;
+						margin-right: 8rpx;
+					}
+					.time{
+						font-weight: 400;
+						font-size: 24rpx;
+						color: #999999;
+					}
 				}
 			}
 		}
+		
 	}
-	
 </style>

+ 13 - 3
pages/teacherStudy/index.vue

@@ -1,7 +1,11 @@
 <template>
 	<view class="page_body">
 		<!-- 课程中心 -->
-		<course-center></course-center>
+		<course-center v-if="state.tabBarValue=='courseCenter'"></course-center>
+		<!-- 我的研修 -->
+		<my-learning v-if="state.tabBarValue=='myLearning'"></my-learning>
+		<!-- 研修监控 -->
+		<learning-monitor v-if="state.tabBarValue=='learningMonitor'"></learning-monitor>
 		<!-- 底部tabbar -->
 		<page-tabbar :tabBarValue="state.tabBarValue" :tabBarList="state.tabBarList" @ChangeTabBarValue="ChangeTabBarValue"></page-tabbar>
 	</view>
@@ -9,7 +13,9 @@
 
 <script setup>
 	import pageTabbar from '@/components/page-tabbar.vue';//头部
-	import courseCenter from './courseCenter/index.vue';
+	import courseCenter from './courseCenter/index.vue';//课程中心
+	import myLearning from './myLearning/index.vue';//我的研修
+	import learningMonitor from './learningMonitor/index.vue';//研修监控
 	import { reactive, ref } from 'vue';
 	const state = reactive({
 		tabBarValue:'courseCenter',
@@ -36,5 +42,9 @@
 	}
 </script>
 
-<style lang="scss">
+<style lang="scss" scoped>
+	.page_body{
+		height: 100%;
+		min-height: auto;
+	}
 </style>

+ 82 - 0
pages/teacherStudy/learningMonitor/index.vue

@@ -0,0 +1,82 @@
+<template>
+	<!-- 头部 -->
+	<nav-bar height="312" backPath="workspace" title="教师研修" :filterPickerData="state.filterPickerData" :tabList="state.tabList" :onlyTitle="false" :showFilterPicker="true" :showTabs="true" rightWidth="0rpx" @GoBack="GoBack"></nav-bar>
+	<view class="page_content">
+		<view class="list_item">
+			<image class="item_img" mode="aspectFill" src="https://jtzx001.oss-accelerate.aliyuncs.com/huijiaoyan_test/20260609_255638/09101629_53c71c9b-3072-4fa5-9944-3598c9650666.jpg"></image>
+			<view class="item_title">2025学年高一化学公开课全国评比大赛赛赛赛赛赛赛赛赛赛学公开课全国评比大赛赛赛赛赛赛赛赛赛赛</view>
+			<view class="item_time"><text class="time">2025-11-12</text></view>
+		</view>
+	</view>
+</template>
+
+<script setup>
+	import navBar from '@/components/nav-bar.vue';
+	import {
+		reactive,
+		ref
+	} from 'vue';
+	const state = reactive({
+		filterPickerData: [
+			['全部类型', '全部类型1'],
+			['全部类型', '全部类型2'],
+			['全部类型', '全部类型2']
+		],
+		tabList: [{
+			name: '最新发布',
+			sortType: '0'
+		}, {
+			name: '播放最多',
+			sortType: '1'
+		}, {
+			name: '关注最多',
+			sortType: '2'
+		}]
+	})
+</script>
+
+<style lang="scss" scoped>
+	.page_content{
+		gap:32rpx;
+		height: 100%;
+		.list_item{
+			&:nth-child(1){
+				margin-top: 16rpx;
+			}
+			&:nth-child(2){
+				margin-top: 16rpx;
+			}
+			width: calc((100% - 32rpx) / 2);
+			overflow: hidden;
+			display: flex;
+			flex-direction: column;
+			.item_img{
+				width: 100%;
+				height: 220rpx;
+				border-radius: 8rpx;
+			}
+			.item_title{
+				display: flex;
+				width: 100%;
+				margin:8rpx 0;
+				font-weight: 400;
+				font-size: 24rpx;
+				color: #333333;
+				display: -webkit-box;
+				-webkit-line-clamp: 2;
+				-webkit-box-orient: vertical;
+				overflow: hidden;
+				text-overflow: ellipsis;
+			}
+			.item_time{
+				display: flex;
+				width: 100%;
+				.time{
+					font-weight: 400;
+					font-size: 24rpx;
+					color: #999999;
+				}
+			}
+		}
+	}
+</style>

+ 315 - 0
pages/teacherStudy/myLearning/index.vue

@@ -0,0 +1,315 @@
+<template>
+	<!-- 头部 -->
+	<nav-bar height="312" backPath="workspace" title="教师研修" :filterPickerData="state.filterPickerData" :tabList="state.tabList" :onlyTitle="false" :showFilterPicker="true" :showTabs="true" rightWidth="0rpx" @CommonFilterData="CommonFilterData"></nav-bar>
+	<view class="page_content">
+		<scroll-view
+		    class="scroll_view_y"
+			:scroll-top="state.scrollTop"
+			scroll-y="true"
+			:refresher-enabled="false" 
+			:enable-back-to-top="true"
+			:show-scrollbar="false" 
+			:scroll-with-animation="true" 
+			refresher-default-style="none"
+			refresher-background="#F8F9FD" 
+			:refresher-triggered="state.isRefreshing" 
+			@scrolltolower="OnLoadMore"
+			@refresherrefresh="OnRefresh"
+			@scroll="OnScroll">
+			<view class="refresh_loading" v-if="state.isRefreshing"><uni-load-more status="loading" /></view>
+			<view class="page_list">
+				<view class="list_item" v-for="item in state.pageList" :key="item.id">
+					<view class="item_img_box">
+						<image class="item_img" mode="aspectFill" :src="item.videoCoverUrl"></image>
+						<view class="remaining_days">剩余{{item.remainingTime}}</view>
+						<view class="progress"><view class="done" :style="{width:item.progress}"></view></view>
+					</view>
+					<view class="item_title">2025{{item.courseName}}</view>
+					<view class="item_time">
+						<image class="img" src="@/static/image/icon/time2x.png"></image>
+						<text class="time">{{item.courseStartDatetime}}</text>
+					</view>
+				</view>
+			</view>
+			<view class="no_data" v-if="!state.isLoading && state.pageList.length == 0">
+				<image class="no_data_img" src="@/static/image/bg/no_data.png"></image>
+				<text class="no_data_text">暂无数据</text>
+			</view>
+			<uni-load-more v-if="state.pageList.length > 0" :status="state.loadStatus" />
+		</scroll-view>
+	</view>
+</template>
+
+<script setup>
+	import navBar from '@/components/nav-bar.vue';
+	import {queryCourseTypeDataList,recordPageListData} from '@/reqApi/teacherStudy.js';
+	import {
+		reactive,
+		ref,
+		onMounted,
+		nextTick
+	} from 'vue';
+	const state = reactive({
+		courseTypeData:[],//顶部查询数据 
+		filterPickerData: [{
+			valueIndex:0,//默认值索引
+			defaultValue:'',//默认值
+			list:[]
+		},{
+			valueIndex:0,//默认值索引
+			defaultValue:'',//默认值
+			list:[]
+		}],
+		tabList: [{
+			label: '最新发布',
+			value: '0'
+		}, {
+			label: '播放最多',
+			value: '1'
+		}, {
+			label: '关注最多',
+			value: '2'
+		}],
+		pageList:[],//列表数据
+		queryStr: '',
+		courseType: '0',//排序方式,课程中心参数,0-最新发布,1-播放最多,2-关注最多
+		teachCourseTypeClassId: '',//研修专题	
+		teachCourseTypeId: '',//内容领域	
+		pageSize: 30,
+		pageNum: 1,
+		scrollTop:0,
+		oldScrollTop:0,
+		isRefreshing:false,//设置当前下拉刷新状态,true 表示下拉刷新已经被触发,false 表示下拉刷新未被触发
+		isLoading:true,//加载中
+		noMoreData:false,//没有更多数据了
+		loadStatus:'more',//loading状态
+	})
+	onMounted(() => {
+		getCourseTypeDataList();
+		OnLoadData(true);
+	});
+	//获取顶部查询项
+	const getCourseTypeDataList = () => {
+		queryCourseTypeDataList().then(res => {
+			if(res.code == 200){
+				const defaultData = [{
+					label:'内容领域',
+					id:'',
+					value:''
+				}];
+				const resData = res?.data || [];
+				state.courseTypeData = defaultData.concat(resData);
+				//一级条件
+				state.filterPickerData[0].list = state.courseTypeData.map(item=>({
+					...item,
+					value:item.id
+				}))
+			}else{
+				state.courseTypeData = [];
+				state.filterPickerData = [{
+					valueIndex:0,//默认值索引
+					defaultValue:'',//默认值
+					list:[],
+				},{
+					valueIndex:0,
+					defaultValue:'',//默认值
+					list:[],
+				}]
+			}
+		})
+	}
+	//切换条件选择器
+	const CommonFilterData = (searchWord,filterPicker,tabsSelected,type) => {
+		const {valueIndex, index,filterPickerValue} = filterPicker;
+		if(type == 'filterPicker'){
+			if(index==0){//索引
+				state.filterPickerData[index].valueIndex = valueIndex;//一级条件选中项的索引
+				state.filterPickerData[1].valueIndex = 0;//二级条件选中项的索引
+				const childrenData = state.courseTypeData?.[valueIndex]?.children || [];
+				const childrenList = childrenData.map(item=>({
+					...item,
+					value:item?.id ?? ''
+				}))
+				if(childrenList.length){
+					childrenList.unshift({
+						label:'内容领域',
+						id:'',
+						value:''
+					})
+				}
+				state.filterPickerData[1].list = childrenList;//二级条件数据
+			}else{
+				state.filterPickerData[index].valueIndex = valueIndex;//二级条件选中项的索引
+			}
+		}
+		state.queryStr = searchWord;
+		state.teachCourseTypeId = filterPickerValue[0];
+		state.teachCourseTypeClassId = filterPickerValue[1];
+		state.courseType = tabsSelected;
+		OnLoadData(true)
+	}
+	/*
+	*获取数据
+	* initPage:初始分页 从第一开始
+	*/
+	const OnLoadData = (initPage) => {
+		// uni.showLoading({
+		// 	title: '加载中'
+		// });
+		if (initPage) state.pageNum = 1;//如果是下拉刷新、重新查询 默认加载第一页
+		state.isLoading = true;
+		state.loadStatus = 'loading';
+		recordPageListData({
+			queryStr:state.queryStr,
+			teachCourseTypeId:state.teachCourseTypeId,
+			teachCourseTypeClassId:state.teachCourseTypeClassId,
+			courseType:'0',
+			learnStatus:'',
+			pageSize:state.pageSize,
+			pageNum:state.pageNum
+		}).then(res=>{
+			if(res.code == 200){
+				const total = Number(res?.data?.dateList?.total) || 0;//总数
+				const pages = Math.ceil(total / state.pageSize);//总页数
+				const recordsData = res?.data?.dateList?.records || [];
+				state.pageList = initPage ? recordsData : [...state.pageList,...recordsData];
+				state.noMoreData = state.pageNum == pages;
+				state.pageNum++;
+			}
+		}).finally(()=>{
+			// uni.hideLoading();
+			state.isLoading = false;
+			state.isRefreshing = false;//下拉刷新未被触发
+			if(state.noMoreData){
+				state.loadStatus = 'no-more';
+			}else{
+				state.loadStatus = 'more';
+			}
+			//返回到页面顶部
+			if(initPage){
+				GoTop();
+			}
+		})
+	}
+	// 自定义下拉刷新被触发	 下拉刷新
+	const OnRefresh = () =>{
+		state.isRefreshing = true;//下拉刷新已经被触发
+		OnLoadData(true);
+	}
+	//滚动到底部/右边 触发上拉刷新
+	const OnLoadMore = () => {
+		if (state.isLoading || state.noMoreData) return;
+		OnLoadData(false);
+	}
+	//	滚动时触发
+	const OnScroll = (e) => {
+		state.oldScrollTop = e.detail.scrollTop;
+	}
+	//返回到顶部
+	const GoTop = () => {
+		// 解决view层不同步的问题
+		state.scrollTop = state.oldScrollTop;
+		nextTick(() => {
+			state.scrollTop = 0
+		});
+	}
+</script>
+
+<style lang="scss" scoped>
+	.page_content{
+		height: calc(100% - 448rpx);
+		.scroll_view_y{
+			height: 100%;
+			.refresh_loading{
+				padding: 10rpx 0;
+			}
+		}
+		.page_list{
+			display: flex;
+			width: 100%;
+			flex-wrap: wrap;
+			gap:32rpx;
+			.list_item{
+				&:nth-child(1){
+					margin-top: 16rpx;
+				}
+				&:nth-child(2){
+					margin-top: 16rpx;
+				}
+				width: calc((100% - 32rpx) / 2);
+				overflow: hidden;
+				display: flex;
+				flex-direction: column;
+				.item_img_box{
+					width: 100%;
+					height: 220rpx;
+					border-radius: 8rpx;
+					overflow: hidden;
+					position: relative;
+					.item_img{
+						width: 100%;
+						height: 100%;
+						border-radius: 8rpx;
+					}
+					.remaining_days{
+						position: absolute;
+						top:0;
+						right: 0;
+						width: 134rpx;
+						height: 48rpx;
+						background-color: #2E64FA;
+						border-radius: 0px 8px 0px 8px;
+						font-weight: 400;
+						font-size: 28rpx;
+						color: #FFFFFF;
+						text-align: center;
+						line-height: 48rpx;
+					}
+					.progress{
+						width: 100%;
+						height: 12rpx;
+						position: absolute;
+						left: 0;
+						bottom: 0;
+						.done{
+							height: 12rpx;
+							background-color: #2E64FA;
+							display: flex;
+							width: 70%;
+						}
+					}
+				}
+				
+				.item_title{
+					display: flex;
+					width: 100%;
+					margin:8rpx 0;
+					font-weight: 400;
+					font-size: 24rpx;
+					color: #333333;
+					display: -webkit-box;
+					-webkit-line-clamp: 2;
+					-webkit-box-orient: vertical;
+					overflow: hidden;
+					text-overflow: ellipsis;
+				}
+				.item_time{
+					display: flex;
+					align-items: center;
+					width: 100%;
+					.img{
+						width: 28rpx;
+						height: 28rpx;
+						margin-right: 8rpx;
+					}
+					.time{
+						font-weight: 400;
+						font-size: 24rpx;
+						color: #999999;
+					}
+				}
+			}
+		}
+		
+	}
+</style>

+ 12 - 0
reqApi/teacherStudy.js

@@ -0,0 +1,12 @@
+// 教师研修
+import request from '@/common/request.js';
+
+export function queryCourseTypeDataList(data) {//获取顶部查询项
+	return request.get('/api/v1/teachCourseType/queryCourseTypeDataList', data)
+}
+export function centerPageListData(data) {//课程中心分页列表
+	return request.post('/api/v1/teachCourseCenter/centerPageListData', data)
+}
+export function recordPageListData(data) {//我的研修分页列表
+	return request.post('/api/v1/teachCourseRecord/recordPageListData', data)
+}

+ 19 - 0
style/common.scss

@@ -324,6 +324,23 @@ uni-page-body {
 		flex-wrap: wrap;
 		padding:0 24rpx;
 		box-sizing: border-box;
+		.no_data{
+			display: flex;
+			flex-direction: column;
+			justify-content: center;
+			align-items: center;
+			height: 100%;
+			.no_data_img{
+				width: 464rpx;
+				height: 278rpx;
+			}
+			.no_data_text{
+				color: #999999;
+				font-weight: 400;
+				font-size: 24rpx;
+				line-height: 36rpx;
+			}
+		}
 	}
 	
 	//底部tabbar样式
@@ -416,6 +433,8 @@ uni-page-body {
 				color: #333333;
 				font-weight: 400;
 				margin-right: 8rpx;
+				white-space: nowrap;
+				overflow: hidden;
 			}
 		}
 	}

+ 1 - 1
uni_modules/uni-load-more/components/uni-load-more/uni-load-more.vue

@@ -166,7 +166,7 @@
 		height: 40px;
 		align-items: center;
 		justify-content: center;
-		background-color: #F8F9FD;
+		// background-color: #F8F9FD;
 	}
 
 	.uni-load-more__text {