liurongli hai 3 semanas
pai
achega
8234070965

+ 2 - 1
components/nav-bar.vue

@@ -25,7 +25,7 @@
 			</template>
 		</view>
 		<!-- tabs -->
-		<scroll-view v-if="showTabs" class="scroll_tabs" scroll-x="true" scroll-left="0" :show-scrollbar="false">
+		<scroll-view v-if="showTabs && tabList.length > 0" class="scroll_tabs" scroll-x="true" scroll-left="0" :show-scrollbar="false">
 			<view :class="['tab_item',{'selected':item.value==state.tabsSelected}]" v-for="item in tabList" :key="item.value" @click="SelectTabsValue(item.value)">
 				{{item.label}}
 			</view>
@@ -36,6 +36,7 @@
 <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 uniEasyinput from '@/uni_modules/uni-easyinput/components/uni-easyinput/uni-easyinput.vue';
 	import {
 		reactive,
 		onMounted,

+ 21 - 5
components/popup-dialog.vue

@@ -1,14 +1,14 @@
 <template>
 	<uni-popup ref="popupDialog" type="dialog">
-		<uni-popup-dialog :class="{'white':!showTitle}" @confirm="DialogConfirm" @close="CloseDialog">
+		<uni-popup-dialog :class="{'white':!showTitle,'footBorder':showFootBorder}" :showClose="showCloseBtn" :style="{'width': `${dialogWidth}rpx`}" @confirm="DialogConfirm" @close="CloseDialog">
 			<view class="dialog_title" v-if="showTitle">
 				<view class="dialog_title_lt">
 					<image v-if="popType=='alert'" src="@/static/image/icon/icon_warn.png" style="width: 40rpx;height: 40rpx;"></image>
 					<text class="title">{{title}}</text>
 				</view>
-				<image src="@/static/image/icon/close.png" style="width: 48rpx;height: 48rpx;" @click="CloseDialog"></image>
+				<image v-if="showDialogClose" src="@/static/image/icon/close.png" style="width: 48rpx;height: 48rpx;" @click="CloseDialog"></image>
 			</view>
-			<view class="dialog_content" v-if="popType=='alert'">{{content}}</view>
+			<view class="dialog_content alert" v-if="popType=='alert'">{{content}}</view>
 			<view class="dialog_content" v-else style="height: auto;"><slot></slot></view>
 		</uni-popup-dialog>
 	</uni-popup>
@@ -27,13 +27,29 @@
 			type: String,
 			default: '提示'
 		},
-		content:{
+		content:{//提示框内容
 			type: String,
 			default: ''
 		},
-		popType:{
+		popType:{//custom 自定义
 			type: String,
 			default: 'alert'
+		},
+		dialogWidth:{//弹框宽度
+			type: String,
+			default: '610'
+		},
+		showDialogClose:{//是否显示关闭按钮
+			type:Boolean,
+			default:true
+		},
+		showCloseBtn:{//是否显示取消按钮
+			type:Boolean,
+			default:true
+		},
+		showFootBorder:{//foot是否显示上边框
+			type:Boolean,
+			default:false
 		}
 	})
 	const emit = defineEmits(['DialogConfirm']);

+ 7 - 0
pages.json

@@ -104,6 +104,13 @@
 				"navigationBarTitleText": "教师研修",
 				"navigationStyle": "custom"
 			}
+		},
+		{
+			"path": "pages/teacherStudy/videoDetails",
+			"style": {
+				"navigationBarTitleText": "教师研修-视频详情",
+				"navigationStyle": "custom"
+			}
 		}
 	],
 	"easycom": {

+ 7 - 1
pages/teacherStudy/courseCenter/index.vue

@@ -18,7 +18,7 @@
 			@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="list_item" v-for="item in state.pageList" :key="item.id" @click="GoVideoDetails(item.id)">
 					<image class="item_img" mode="aspectFill" :src="item.videoCoverUrl"></image>
 					<view class="item_title">{{item.courseName}}</view>
 					<view class="item_time">
@@ -208,6 +208,12 @@
 			state.scrollTop = 0
 		});
 	}
+	//视频详情
+	const GoVideoDetails = (id) => {
+		uni.navigateTo({
+			url: `/pages/teacherStudy/videoDetails?id=${id}`
+		});
+	}
 </script>
 
 <style lang="scss" scoped>

+ 14 - 6
pages/teacherStudy/index.vue

@@ -16,7 +16,9 @@
 	import courseCenter from './courseCenter/index.vue';//课程中心
 	import myLearning from './myLearning/index.vue';//我的研修
 	import learningMonitor from './learningMonitor/index.vue';//研修监控
-	import { reactive, ref } from 'vue';
+	import { reactive, ref,onMounted } from 'vue';
+	import { useStore } from 'vuex';
+	const store = useStore();
 	const state = reactive({
 		tabBarValue:'courseCenter',
 		tabBarList:[{
@@ -29,17 +31,23 @@
 			"iconPath": "/static/image/tabbar/myLearning.png",
 			"selectedIconPath": "/static/image/tabbar/myLearning_cur.png",
 			"text": "我的研修"
-		},{
-			"pathName":"learningMonitor",
-			"iconPath": "/static/image/tabbar/learningMonitor.png",
-			"selectedIconPath": "/static/image/tabbar/learningMonitor_cur.png",
-			"text": "研修监控"
 		}]
 	})
 	//切换tabBar
 	const ChangeTabBarValue = (name) => {
 		state.tabBarValue = name;
 	}
+	onMounted(()=>{
+		const roleCodes  = store.state?.userStore?.userInfo?.roleCodes || [];
+		if(roleCodes.includes('SCHOOL_MANAGER')){//管理员
+			state.tabBarList.push({
+				"pathName":"learningMonitor",
+				"iconPath": "/static/image/tabbar/learningMonitor.png",
+				"selectedIconPath": "/static/image/tabbar/learningMonitor_cur.png",
+				"text": "研修监控"
+			})
+		}
+	})
 </script>
 
 <style lang="scss" scoped>

+ 319 - 54
pages/teacherStudy/learningMonitor/index.vue

@@ -1,82 +1,347 @@
 <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>
+	<nav-bar :height="state.courseType == '0'?'312':'224'" 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',{'self_content_height':state.courseType == '1'}]">
+		<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_title">
+						<image class="img" src="@/static/image/icon/video.png"></image>
+						<view class="title">{{item.courseName}}</view>
+					</view>
+					<view class="item_status">
+						<view v-if="state.courseType == '0'" :class="['status',{going:item.courseStatus === 0,finished:item.courseStatus === 2}]">{{item.courseStatus === 0?'进行中':'已结束'}}</view>
+						<view class="item_field" v-if="state.courseType == '0' && item.courseType">{{item.courseType}}</view>
+						<view class="item_field" v-if="state.courseType == '1' && item.teachCourseTypeName">{{item.teachCourseTypeName}}</view>
+						<view class="item_field" v-if="item.teachCourseTypeClassName">{{item.teachCourseTypeClassName}}</view>
+						<view class="item_field" v-if="item.teacherName">{{item.teacherName}}</view>
+						<view class="item_field" v-if="state.courseType == '0' && item.remainingTime && item.remainingTime!='-'">剩余{{item.remainingTime}}</view>
+					</view>
+					<view class="item_complete_status">
+						<text class="complete_status">完成情况:{{item.learnOver ?? '-'}}/{{item.learnCount ?? '-'}}</text>
+						<text class="btn_detail">查看详情</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,minitorPageListData} from '@/reqApi/teacherStudy.js';
 	import {
 		reactive,
-		ref
+		ref,
+		onMounted,
+		nextTick
 	} from 'vue';
 	const state = reactive({
-		filterPickerData: [
-			['全部类型', '全部类型1'],
-			['全部类型', '全部类型2'],
-			['全部类型', '全部类型2']
-		],
-		tabList: [{
-			name: '最新发布',
-			sortType: '0'
+		courseTypeData:[],//顶部查询数据 
+		filterPickerData: [{
+			valueIndex:0,//默认值索引
+			defaultValue:'0',//默认值
+			list:[{
+				label:'必修任务',
+				value:'0',
+			},{
+				label:'自主研修',
+				value:'1',
+			}]
+		},{
+			valueIndex:0,//默认值索引
+			defaultValue:'',//默认值
+			list:[]
+		},{
+			valueIndex:0,//默认值索引
+			defaultValue:'',//默认值
+			list:[]
+		}],//必修任务tab
+		tabList:[],
+		mustTabList: [{
+			label: '全部',
+			value: ''
 		}, {
-			name: '播放最多',
-			sortType: '1'
+			label: '进行中',
+			value: '0'
 		}, {
-			name: '关注最多',
-			sortType: '2'
-		}]
+			label: '已结束',
+			value: '2'
+		}],
+		pageList:[],//列表数据
+		queryStr: '',
+		courseType:'0',//默认必修任务
+		teachCourseTypeId: '',//内容领域	
+		teachCourseTypeClassId: '',//研修专题
+		courseStatus: '',//tab选中值
+		pageSize: 10,
+		pageNum: 1,
+		scrollTop:0,
+		oldScrollTop:0,
+		isRefreshing:false,//设置当前下拉刷新状态,true 表示下拉刷新已经被触发,false 表示下拉刷新未被触发
+		isLoading:true,//加载中
+		noMoreData:false,//没有更多数据了
+		loadStatus:'more',//loading状态
 	})
+	onMounted(() => {
+		state.tabList = [...state.mustTabList];
+		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[1].list = state.courseTypeData.map(item=>({
+					...item,
+					value:item.id
+				}))
+			}
+		})
+	}
+	//切换条件选择器
+	const CommonFilterData = (searchWord,filterPicker,tabsSelected,type) => {
+		const {valueIndex, index,filterPickerValue} = filterPicker;
+		state.queryStr = searchWord;
+		state.courseType = filterPickerValue[0];
+		state.teachCourseTypeId = filterPickerValue[1];
+		state.teachCourseTypeClassId = filterPickerValue[2];
+		state.courseStatus = tabsSelected;
+		if(type == 'filterPicker'){
+			state.filterPickerData[index].valueIndex = valueIndex;//研修场景、内容领域、研修专题选中项的索引
+			if(index==1){//切换内容领域选中项
+				//研修专题默认切换到全部 同时获取研修专题所有选项
+				state.filterPickerData[2].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[2].list = childrenList;
+			}else{
+				if(index == 0){
+					state.filterPickerData[1].valueIndex = 0;//内容领域
+					state.filterPickerData[2].valueIndex = 0;//研修专题
+					state.filterPickerData[2].list = [];//研修专题选项
+					state.tabList = valueIndex == 0 ? [...state.mustTabList] : [];
+					state.courseStatus = '';
+				}
+			}
+		}
+		OnLoadData(true)
+	}
+	/*
+	*获取数据
+	* initPage:初始分页 从第一开始
+	*/
+	const OnLoadData = (initPage) => {
+		// uni.showLoading({
+		// 	title: '加载中'
+		// });
+		if (initPage) state.pageNum = 1;//如果是下拉刷新、重新查询 默认加载第一页
+		state.isLoading = true;
+		state.loadStatus = 'loading';
+		minitorPageListData({
+			queryStr:state.queryStr,
+			courseType:state.courseType,
+			teachCourseTypeId:state.teachCourseTypeId,
+			teachCourseTypeClassId:state.teachCourseTypeClassId,
+			courseStatus:state.courseStatus,
+			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{
-		gap:32rpx;
-		height: 100%;
-		.list_item{
-			&:nth-child(1){
-				margin-top: 16rpx;
-			}
-			&:nth-child(2){
-				margin-top: 16rpx;
+		height: calc(100% - 448rpx);
+		&.self_content_height{
+			height: calc(100% - 360rpx);
+		}
+		.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%;
+			flex-wrap: wrap;
+			gap:32rpx;
+			.list_item{
+				&:nth-child(1){
+					margin-top: 16rpx;
+				}
 				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;
+				flex-direction: column;
+				background: #F9FAFF;
+				border-radius: 20rpx;
+				border: 2rpx solid #E4E7ED;
+				
+				.item_title{
+					display: flex;
+					align-items: center;
+					width: 100%;
+					padding:24rpx 16rpx 16rpx;
+					box-sizing: border-box;
+					.img{
+						width: 32rpx;
+						height: 32rpx;
+						margin-right: 16rpx;
+					}
+					.title{
+						flex: 1;
+						white-space: nowrap;
+						overflow: hidden;
+						text-overflow: ellipsis;
+						font-weight: 500;
+						font-size: 28rpx;
+						color: #333333;
+					}
+				}
+				.item_status{
+					display: flex;
+					align-items: center;
+					flex-wrap: wrap;
+					gap: 16rpx;
+					width: 100%;
+					padding: 0 16rpx 24rpx;
+					box-sizing: border-box;
+					.status{
+						height: 48rpx;
+						display: inline-flex;
+						align-items: center;
+						padding: 0 12rpx;
+						border-radius: 8rpx;
+						font-weight: 400;
+						font-size: 24rpx;
+						&.going{
+							background-color: rgba(43,198,68,0.1);
+							color: #2BC644;
+						}
+						&.finished{
+							background-color: rgba(245,108,108,0.1);
+							color: #F56C6C;
+						}
+					}
+					.item_field{
+						font-weight: 400;
+						font-size: 24rpx;
+						color: #999999;
+						word-break: break-all;
+					}
+				}
+				.item_complete_status{
+					display: flex;
+					align-items: center;
+					justify-content: space-between;
+					width: 100%;
+					padding: 24rpx 16rpx;
+					box-sizing: border-box;
+					border-top: 2rpx solid #E4E7ED;
+					.complete_status{
+						font-weight: 400;
+						font-size: 24rpx;
+						color: #999999;
+					}
+					.btn_detail{
+						width: 160rpx;
+						height: 64rpx;
+						background-color: #2E64FA;
+						border-radius: 8rpx;
+						border: 2rpx solid #2E64FA;
+						display: inline-flex;
+						align-items: center;
+						justify-content: center;
+						font-weight: 400;
+						font-size: 28rpx;
+						color: #FFFFFF;
+					}
 				}
 			}
 		}
+		
 	}
 </style>

+ 7 - 1
pages/teacherStudy/myLearning/index.vue

@@ -18,7 +18,7 @@
 			@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="list_item" v-for="item in state.pageList" :key="item.id" @click="GoVideoDetails(item.id)">
 					<view class="item_img_box">
 						<image class="item_img" mode="aspectFill" :src="item.videoCoverUrl"></image>
 						<view v-if="(item.learnStatus == 3 || item.learnStatus == 4 || item.learnStatus == 1) && state.courseType == '0'" :class="['remaining_days',{wait:item.learnStatus == 3,finish:item.learnStatus == 4,overtime:item.learnStatus == 1}]">
@@ -240,6 +240,12 @@
 			state.scrollTop = 0
 		});
 	}
+	//视频详情
+	const GoVideoDetails = (id) => {
+		uni.navigateTo({
+			url: `/pages/teacherStudy/videoDetails?id=${id}`
+		});
+	}
 </script>
 
 <style lang="scss" scoped>

+ 235 - 0
pages/teacherStudy/videoDetails.vue

@@ -0,0 +1,235 @@
+<template>
+	<view class="page_body">
+		<!-- 头部 -->
+		<nav-bar height="96" title="视频详情" :onlyTitle="true" :showHeaderborder="true" @GoBack="GoBack"></nav-bar>
+		<view class="page_content">
+			<view class="video_content">
+				<!-- duration:指定视频时长,单位为秒(s) initial-time:指定视频初始播放位置,单位为秒(s) -->
+				<video 
+					id="myVideo"
+					:title="state?.videoInfo?.courseName || ''"
+					:initial-time="state?.videoInfo?.initialTime"
+					:duration="state?.videoInfo?.duration"
+					:show-mute-btn="true"
+					:poster="state?.videoInfo?.videoCoverUrl"
+					:src="state?.videoInfo?.attachmentUrl"
+					style="width: 100%; height: 100%; object-fit: contain;"
+					@timeupdate="videoTimeupdate"
+				>
+				</video>
+			</view>
+			<view class="video_title">{{state?.videoInfo?.courseName}}</view>
+			<view class="video_progress">
+				<text class="video_progress_text" v-if="state?.videoInfo?.courseType===0">必修任务:{{state.videoInfo.courseStatus===0?`剩余${state.videoInfo.remainingTime}`:'已结束'}}</text>
+				<text class="video_progress_text">观看进度:{{state?.videoInfo?.progress}}</text>
+			</view>
+			<view class="video_class_name">
+				<text class="class_name_item">{{state?.videoInfo?.teachCourseTypeName}}</text>
+				<text class="class_name_item">{{state?.videoInfo?.teachCourseTypeClassName}}</text>
+				<text class="class_name_item">{{state?.videoInfo?.createdBy}}</text>
+			</view>
+			<view class="video_time">
+				<text class="time_item">{{state?.videoInfo?.courseType===0 ?'开始':'上传'}}时间:{{state?.videoInfo?.courseStartDatetime}}</text>
+				<text class="time_item" v-if="state?.videoInfo?.courseType===0">结束时间:{{state.videoInfo.courseEndDatetime}}</text>
+			</view>
+			<view class="video_desc">
+				课程简介:{{state?.videoInfo?.courseContent}}
+			</view>
+		</view>
+	</view>
+	<!-- 提示框 -->
+	<popup-dialog ref="popupDialogRef" popType="custom" title="试题弹出" :showDialogClose="false" :showCloseBtn="false" :showFootBorder="true" dialogWidth="702" @DialogConfirm="DialogConfirm">
+		<view class="dialog_questions_input">
+			<view class="questions_title">{{state?.question?.codeName}}:{{state?.question?.questionsName}}</view>
+			<uni-easyinput type="textarea" v-model="value" :style="state.easyinputStyle" placeholder="请输入你的回答" placeholderStyle="font-weight: 400;font-size: 28rpx;color: #999999;"></uni-easyinput>
+		</view>
+	</popup-dialog>	
+</template>
+
+<script setup>
+	import uniEasyinput from '@/uni_modules/uni-easyinput/components/uni-easyinput/uni-easyinput.vue';
+	import navBar from '@/components/nav-bar.vue';
+	import popupDialog from '@/components/popup-dialog.vue';
+	import { selectCourseDetail } from '@/reqApi/teacherStudy.js';
+	import {
+		onLoad,
+		onUnload
+	} from '@dcloudio/uni-app';
+	import {
+		reactive,
+		ref
+	} from 'vue';
+	const state = reactive({
+		id: '', //视频id
+		videoInfo:{},
+		currentTime:'',//播放进度时分秒
+		question:null,//试题弹出信息
+		easyinputStyle:{
+			color: '#303133',
+			borderColor: '#E9E9E9'
+		}
+	});
+	//提示框
+	const popupDialogRef = ref(null);
+	const videoCtx = ref(null);//播放器
+	onLoad((option) => {
+		state.id = option.id;
+		videoCtx.value = uni.createVideoContext('myVideo')
+		GetCourseDetail();
+	})
+	//视频详情
+	const GetCourseDetail = () => {
+		selectCourseDetail(state.id).then(res => {
+			if(res.code == 200){
+				state.videoInfo = res.data;
+				state.videoInfo.initialTime = timeToSecond(state.videoInfo.courseLearnTime || '');
+				state.videoInfo.duration = timeToSecond(state.videoInfo.videoTime || '')
+			}
+		})
+	}
+	//计算秒数
+	const timeToSecond = (timeStr) => {
+		const timeArr = timeStr.split(':');
+		const [h, m, s] = timeArr.map(item => Number(item));
+		return h * 3600 + m * 60 + s;
+    }
+	//播放进度变化时触发,event.detail = {currentTime, duration} 。触发频率 250ms 一次
+	const videoTimeupdate = (event) => {
+		const currentTime = event.detail.currentTime;
+		state.currentTime = formatSecondsToHms(currentTime);
+		const questionsList = state?.videoInfo?.questionsList || [];
+		const question = questionsList.find(item=>item.showTime == state.currentTime);//视频显示的时间
+		state.question = question;//试题弹框信息
+		if(question){
+			videoCtx.value?.pause();//暂停播放
+			popupDialogRef.value.OpenDialog();
+		}
+		console.log(state.currentTime,323)
+	}
+	// 数字补零工具
+	const addZero = (num) => {
+	  return num < 10 ? '0' + num : String(num);
+	}
+	//转换时分秒
+	const formatSecondsToHms = (seconds) => {
+	  let total = Math.floor(seconds);
+	  const h = Math.floor(total / 3600);
+	  const m = Math.floor((total % 3600) / 60);
+	  const s = total % 60;
+	
+	  const hh = addZero(h);
+	  const mm = addZero(m);
+	  const ss = addZero(s);
+	  return `${hh}:${mm}:${ss}`;
+	}
+	//页面销毁时触发
+	onUnload(()=>{
+		
+	})
+	//返回
+	const GoBack = () => {
+		uni.navigateBack({
+			delta: 1
+		});
+	}
+</script>
+
+<style scoped lang="scss">
+	.video_content {
+		width: 100%;
+		border-radius: 8rpx;
+		margin-top: 24rpx;
+		height: 398rpx;
+		overflow: hidden;
+	}
+	.video_title{
+		width: 100%;
+		margin-top: 32rpx;
+		font-weight: 500;
+		font-size: 36rpx;
+		color: #333333;
+		display: -webkit-box;
+		-webkit-line-clamp: 2;
+		-webkit-box-orient: vertical;
+		overflow: hidden;
+		text-overflow: ellipsis;
+	}
+	.video_progress{
+		display: flex;
+		justify-content: space-between;
+		width: 100%;
+		margin-top: 16rpx;
+		.video_progress_text{
+			font-weight: 400;
+			font-size: 24rpx;
+			color: #2E64FA;
+		}
+	}
+	.video_class_name{
+		display: flex;
+		width: 100%;
+		margin-top: 16rpx;
+		gap: 16rpx 24rpx;
+		flex-wrap: wrap;
+		.class_name_item{
+			font-weight: 400;
+			font-size: 24rpx;
+			color: #666666;
+		}
+	}
+	.video_time{
+		display: flex;
+		width: 100%;
+		margin-top: 16rpx;
+		gap: 16rpx;
+		flex-wrap: wrap;
+		.time_item{
+			font-weight: 400;
+			font-size: 24rpx;
+			color: #666666;
+		}
+	}
+	.video_desc{
+		display: flex;
+		width: 100%;
+		margin-top: 32rpx;
+		flex-wrap: wrap;
+		word-break: break-all;
+		font-weight: 400;
+		font-size: 24rpx;
+		color: #999999;
+		line-height: 44rpx;
+	}
+	.dialog_questions_input{
+		padding: 40rpx 30rpx;
+		display: flex;
+		width: 100%;
+		flex-direction: column;
+		.questions_title{
+			display: flex;
+			width: 100%;
+			font-weight: 400;
+			font-size: 32rpx;
+			color: #303133;
+			text-align: left;
+			word-break: break-all;
+		}
+		:deep(.uni-easyinput){
+			margin-top: 20rpx;
+			font-size: 28rpx;
+			.is-input-border{
+				border-radius: 8rpx;
+				border-color: #E9E9E9 !important;
+			}
+			.uni-easyinput__content-textarea{
+				height: 440rpx;
+				min-height: 440rpx;
+				font-size: 28rpx;
+				margin:20rpx 24rpx 20rpx 14rpx;
+			}
+			.input-padding{
+				padding-left: 10rpx;
+			}
+		}
+	}
+</style>

+ 6 - 0
reqApi/teacherStudy.js

@@ -9,4 +9,10 @@ export function centerPageListData(data) {//课程中心分页列表
 }
 export function recordPageListData(data) {//我的研修分页列表
 	return request.post('/api/v1/teachCourseRecord/recordPageListData', data)
+}
+export function minitorPageListData(data) {//研修监控分页列表
+	return request.post('/api/v1/teachCourseMinitor/minitorPageListData', data)
+}
+export function selectCourseDetail(id) {//视频详情
+	return request.get(`/api/v1/teachCourseRecord/selectCourseDetail/${id}`)
 }

BIN=BIN
static/image/icon/video.png


+ 37 - 11
style/common.scss

@@ -341,6 +341,22 @@ uni-page-body {
 				line-height: 36rpx;
 			}
 		}
+		//视频播放器样式
+		// .uni-video-container{
+		// 	background-color: #FFFFFF;
+		// }
+		.uni-video-cover-play-button{
+			width: 100rpx;
+			height: 100rpx;
+			line-height: 100rpx;
+			background-color: rgba(0,0,0,0.8);
+			font-size: 40rpx;
+			border-radius: 50%;
+			color: #FFFFFF;
+		}
+		.uni-video-cover{
+			background-color:transparent;
+		}
 	}
 	
 	//底部tabbar样式
@@ -472,7 +488,7 @@ uni-page-body {
 .custom_navbar_only_title{
 	.uni-navbar--border{
 		border-bottom-width: 2rpx !important;
-		border-bottom-color:#F3F3F3;
+		border-bottom-color:#F3F3F3 !important;
 	}
 	.uni-navbar__header-btns-left {
 		.uni-icons{
@@ -606,9 +622,9 @@ uni-page-body {
 }
 // 提示框样式
 .uni-popup-dialog {
-	width: 310px !important;
-	border-radius: 20rpx;
-	background: linear-gradient(180deg, #dae4ff 0%, #ffffff 35.1%);
+	// width: 610rpx !important;
+	border-radius: 20rpx !important;
+	// background: linear-gradient(180deg, #dae4ff 0%, #ffffff 35.1%);
 	&.white {
 		background: #ffffff;
 	}
@@ -624,6 +640,8 @@ uni-page-body {
 		height: 96rpx;
 		padding: 0 40rpx;
 		width: 100%;
+		background-color: #F5F7FA;
+		border-radius: 20rpx 20rpx 0 0;
 		box-sizing: border-box;
 		display: flex;
 		justify-content: space-between;
@@ -638,7 +656,7 @@ uni-page-body {
 				overflow: hidden;
 				text-overflow: ellipsis;
 				color: #303133;
-				font-weight: bold;
+				font-weight: 500;
 				font-size: 32rpx;
 			}
 		}
@@ -649,21 +667,29 @@ uni-page-body {
 		height: auto;
 		display: flex;
 		// overflow: auto;
-		justify-content: center;
-		align-items: center;
-		color: #666666;
-		font-size: 28rpx;
 		width: 100%;
 		box-sizing: border-box;
 		padding: 0 10rpx;
 		flex-wrap: wrap;
-		line-height: 40rpx;
-		text-align: center;
+		&.alert{
+			justify-content: center;
+			align-items: center;
+			color: #666666;
+			font-size: 28rpx;
+			line-height: 40rpx;
+			text-align: center;
+		}
 	}
 	.uni-dialog-button-group {
 		border-top-width: 0 !important;
 		padding: 40rpx 44rpx 28rpx;
 		box-sizing: border-box;
+		&.footBorder{
+			.uni-dialog-button-group{
+				border-top-color: #EBEEF5;
+				border-top-width:1px !important;
+			}
+		}
 		.uni-dialog-button {
 			height: 72rpx;
 			background-color: #f0f2f9;

BIN=BIN
unpackage/res/icons/1024x1024.png


BIN=BIN
unpackage/res/icons/120x120.png


BIN=BIN
unpackage/res/icons/144x144.png


BIN=BIN
unpackage/res/icons/152x152.png


BIN=BIN
unpackage/res/icons/167x167.png


BIN=BIN
unpackage/res/icons/180x180.png


BIN=BIN
unpackage/res/icons/192x192.png


BIN=BIN
unpackage/res/icons/20x20.png


BIN=BIN
unpackage/res/icons/29x29.png


BIN=BIN
unpackage/res/icons/40x40.png


BIN=BIN
unpackage/res/icons/58x58.png


BIN=BIN
unpackage/res/icons/60x60.png


BIN=BIN
unpackage/res/icons/72x72.png


BIN=BIN
unpackage/res/icons/76x76.png


BIN=BIN
unpackage/res/icons/80x80.png


BIN=BIN
unpackage/res/icons/87x87.png


BIN=BIN
unpackage/res/icons/96x96.png