Jelajahi Sumber

feat:视频学习

qianxinyu 3 bulan lalu
induk
melakukan
e545510fb8
6 mengubah file dengan 1178 tambahan dan 448 penghapusan
  1. 28 20
      pages.json
  2. 6 0
      pages/user/index.vue
  3. 456 201
      pages/video/detail.vue
  4. 228 0
      pages/video/exam.vue
  5. 245 227
      pages/video/index.vue
  6. 215 0
      pages/video/record.vue

+ 28 - 20
pages.json

@@ -221,32 +221,40 @@
       }
     },
     {
-    	"path" : "pages/article/index",
-    	"style" : 
-    	{
-    		"navigationBarTitleText" : "资讯列表"
-    	}
+      "path": "pages/article/index",
+      "style": {
+        "navigationBarTitleText": "资讯列表"
+      }
+    },
+    {
+      "path": "pages/article/detail",
+      "style": {
+        "navigationBarTitleText": "文章详情"
+      }
+    },
+    {
+      "path": "pages/video/index",
+      "style": {
+        "navigationBarTitleText": "课程列表"
+      }
     },
     {
-    	"path" : "pages/article/detail",
-    	"style" : 
-    	{
-    		"navigationBarTitleText" : "文章详情"
-    	}
+      "path": "pages/video/detail",
+      "style": {
+        "navigationBarTitleText": "视频学习"
+      }
     },
     {
-    	"path" : "pages/video/index",
-    	"style" : 
-    	{
-    		"navigationBarTitleText" : "课程列表"
-    	}
+      "path": "pages/video/record",
+      "style": {
+        "navigationBarTitleText": "学习报告"
+      }
     },
     {
-    	"path" : "pages/video/detail",
-    	"style" : 
-    	{
-    		"navigationBarTitleText" : "视频学习"
-    	}
+      "path": "pages/video/exam",
+      "style": {
+        "navigationBarTitleText": "课后测评"
+      }
     }
   ],
   "globalStyle": {

+ 6 - 0
pages/user/index.vue

@@ -51,10 +51,16 @@
         <image class="navigator_image" src="https://kailin-mp.oss-cn-shenzhen.aliyuncs.com/static/icon/score_gift.png" mode=""></image>
         <view class="navigator_title">拉新活动</view>
       </navigator>
+    </view>
+    <view class="navigator_list">
       <navigator class="navigator_item" url="/pages/article/index">
         <image class="navigator_image" src="https://kailin-mp.oss-cn-shenzhen.aliyuncs.com/static/user/article.png" mode=""></image>
         <view class="navigator_title">资讯</view>
       </navigator>
+      <navigator class="navigator_item" url="/pages/video/index">
+        <image class="navigator_image" src="https://kailin-mp.oss-cn-shenzhen.aliyuncs.com/static/user/article.png" mode=""></image>
+        <view class="navigator_title">学习</view>
+      </navigator>
     </view>
     <view class="alter_info">本程序暂不提供在线交易以及支付功能,您所提交的预约,我们将验证您的购药资质并交由有售卖药品资质的商业公司与您联系确认并提供线下后续服务。</view>
     <view class="packet_content" v-if="show_packet">

+ 456 - 201
pages/video/detail.vue

@@ -1,209 +1,464 @@
 <template>
-	<view>
-		<view class="video_play" v-if="videoInfo.video_src">
-			<video id="myVideo" class="video_control" :src="videoInfo.video_src" @timeupdate="timeUpdate" :enable-progress-gesture="false" :enable-play-gesture="true"></video>
-		</view>
-		<view class="video_title">{{videoInfo.name}}</view>
-		<view class="rich_text">
-			<rich-text :nodes="videoInfo.content"></rich-text>
-		</view>
-	</view>
+  <view>
+    <view class="video_play" v-if="videoInfo.video_src">
+      <video
+        id="myVideo"
+        class="video_control"
+        @play="_videoPlay"
+        :src="videoInfo.video_src"
+        @timeupdate="timeUpdate"
+        :enable-progress-gesture="false"
+        :enable-play-gesture="true"
+        :enable-auto-rotation="true"
+        :initial-time="videoInfo.inittime || 0"
+        :show-bottom-progress="videoInfo.learn_status == 1"
+        @ended="_videoEnd"
+        @fullscreenchange="screenChange"
+      ></video>
+      <cover-view v-if="videoInfo.learn_status == 0" :class="bigsScreen ? 'bigScreen' : 'cover'"></cover-view>
+    </view>
+    <view class="video_title">{{ videoInfo.name }}</view>
+    <view class="rich_text">
+      <rich-text :nodes="videoInfo.content"></rich-text>
+    </view>
+    <uni-popup ref="questionRef" type="center" :is-mask-click="false">
+      <view class="popup_content">
+        <!-- 答题区域 -->
+        <view class="content" v-if="!show_answer">
+          <!-- 问题内容 -->
+          <view class="question_content">{{ questinInfo.question_title }}</view>
+          <!-- 问题选项 -->
+          <view class="question_options">
+            <view v-for="(option, index) in questinInfo.answer_list" :key="index" :class="['question_answer', { active: answer_id == option.id }]" @click="_selectAnswer(option.id)">
+              {{ option.value }}
+            </view>
+          </view>
+          <view :class="['submit_btn', { active: answer_id }]" @click="_submitAnswer">提交</view>
+        </view>
+        <!-- 结果区域 -->
+        <view class="content" v-else>
+          <icon :type="answerInfo.is_answer == 1 ? 'success' : 'cancel'" size="64" />
+          <view class="title">{{ answerInfo.is_answer == 1 ? "恭喜您,答对啦!" : "很遗憾,答错了" }}</view>
+          <view class="tip">{{ answerInfo.is_answer == 1 ? "继续保持,你正在进步!" : "别灰心,再接再厉!" }}</view>
+          <view class="score_content">
+            <view>得分</view>
+            <view :style="answerInfo.get_score == 0 ? 'color:red' : 'color:green'">+{{ answerInfo.get_score }}</view>
+          </view>
+          <view class="submit_btn active" @click="_goPlayVedio">继续学习视频</view>
+        </view>
+      </view>
+    </uni-popup>
+  </view>
 </template>
 
 <script>
-	export default {
-		data() {
-			return {
-				videoInfo:{
-					id:0,
-					name:"",
-					content:"",
-					video_src:""
-				},
-				// 请求参数
-				requestParam: {
-				  id: 0
-				},
-				isReqing:false,
-				videoContext:null,
-			}
-		},
-		onLoad(param) {
-			// 参数接收
-			this.requestParam.id = param.id;
-			// #ifdef MP-WEIXIN
-			//分享按钮
-			uni.showShareMenu({
-			  withShareTicket: true,
-			  menus: ["shareAppMessage", "shareTimeline"],
-			});
-			// #endif
-		},
-		onShareAppMessage(obj) {
-			return {
-				title: `999智控终端平台\n${this.videoInfo.title}`,
-				path: "/pages/video/detail?id=" + this.videoInfo.id,
-				promise: new Promise((resolve, reject) => {
-				  this.$http.request("api/share_message/get_item", { item_id: this.videoInfo.id, pages: "/pages/video/detail" }).then((callback) => {
-					console.log(callback, "api/share_message/get_item");
-					let obj = {
-					  title: callback.data?.title == "" ? `999智控终端平台\n${this.videoInfo.title}` : callback.data.title,
-					  path: "/pages/video/detail?id=" + this.videoInfo.id,
-					};
-					if (callback.data?.image_url !== "") {
-					  obj.imageUrl = callback.data.image_url;
-					}
-					resolve(obj);
-				  });
-				}),
-			};
-		},
-		onReady: function (res) {
-			
-		},
-		onShow() {
-			// 如果存在产品ID的话
-			if (this.requestParam.id > 0) {
-			  // 请求详情
-			  this.$http.request("api/video_course/get_detail", this.requestParam).then((re) => {
-			    // 成功渲染数据
-			    if (re.code == "success") {
-			      // 刷新数据
-			      this.videoInfo = re.data;
-				  // 获取视频容器的上下文
-				  this.videoContext = uni.createVideoContext('myVideo');
-				  // 暂停播放
-				  this.videoContext.stop();
-			    } else {
-			      if (re.code != "no_login") {
-			        uni.showModal({
-			          content: re.msg,
-			          showCancel: false,
-			        });
-			      }
-			    }
-			  });
-			}
-		},
-		methods: {
-			timeUpdate(event){
-				// 播放到对应的
-				if( event.detail.currentTime ){
-					
-					
-				}
-			}
-		}
-	}
+export default {
+  data() {
+    return {
+      videoInfo: {
+        id: 0,
+        name: "",
+        content: "",
+        video_src: "",
+      },
+      // 请求参数
+      requestParam: {
+        id: 0,
+      },
+      isReqing: false,
+      videoContext: null,
+      isAnswerQuestion: false,
+      timer: null,
+      currentTime: 0, // 新增变量用于存储当前播放时间
+      questionTime_list: [],
+      questinInfo: {},
+      answer_id: null,
+      is_correct: false,
+      show_answer: false,
+      answeredQuestions: new Set(), // 新增变量用于存储已回答的问题
+      countdown: 0, // 新增变量用于存储倒计时时间
+      bigsScreen: false,
+    };
+  },
+  onLoad(param) {
+    // 参数接收
+    this.requestParam.id = param.id;
+    // #ifdef MP-WEIXIN
+    //分享按钮
+    uni.showShareMenu({
+      withShareTicket: true,
+      menus: ["shareAppMessage", "shareTimeline"],
+    });
+    // #endif
+    uni.enableAlertBeforeUnload({
+      message: "您确定要退出学习吗?",
+      success: function (res) {
+        console.log("方法注册成功:", res);
+      },
+      fail: function (errMsg) {
+        console.log("方法注册失败:", errMsg);
+      },
+    });
+  },
+  onUnload() {
+    console.log("页面卸载");
+    clearInterval(this.timer);
+  },
+  onShareAppMessage(obj) {
+    return {
+      title: `999智控终端平台\n${this.videoInfo.title}`,
+      path: "/pages/video/detail?id=" + this.videoInfo.id,
+      promise: new Promise((resolve, reject) => {
+        this.$http.request("api/share_message/get_item", { item_id: this.videoInfo.id, pages: "/pages/video/detail" }).then((callback) => {
+          console.log(callback, "api/share_message/get_item");
+          let obj = {
+            title: callback.data?.title == "" ? `999智控终端平台\n${this.videoInfo.title}` : callback.data.title,
+            path: "/pages/video/detail?id=" + this.videoInfo.id,
+          };
+          if (callback.data?.image_url !== "") {
+            obj.imageUrl = callback.data.image_url;
+          }
+          resolve(obj);
+        });
+      }),
+    };
+  },
+  onReady: function (res) {},
+  onShow() {
+    // 如果存在产品ID的话
+    if (this.requestParam.id > 0) {
+      // 请求详情
+      this.$http.request("api/video_course/get_detail", this.requestParam).then((re) => {
+        // 成功渲染数据
+        if (re.code == "success") {
+          // 刷新数据
+          this.videoInfo = re.data;
+          if (re.data.question_list) {
+            this.questionTime_list = re.data?.question_list.map((item) => item.play_time);
+          }
+          // 获取视频容器的上下文
+          this.videoContext = uni.createVideoContext("myVideo");
+          // 暂停播放
+          this.videoContext.stop();
+        } else {
+          if (re.code != "no_login") {
+            uni.showModal({
+              content: re.msg,
+              showCancel: false,
+            });
+          }
+        }
+      });
+    }
+  },
+  methods: {
+    timeUpdate(event) {
+      this.currentTime = event.detail.currentTime; // 更新当前播放时间
+
+      // 播放到对应的
+      if (this.currentTime) {
+        if (!this.timer) {
+          this.timer = setInterval(() => {
+            this._postVideoDuration(this.currentTime, 0);
+          }, 3000);
+        }
+        if (this.currentTime == event.detail.duration) {
+          this._postVideoDuration(this.currentTime, 1);
+        }
+        // 判断当前时间是否接近答题时间,并且该问题尚未回答
+        if (this.questionTime_list.includes(parseInt(this.currentTime) + 3) && !this.answeredQuestions.has(parseInt(this.currentTime) + 3)) {
+          this.countdown = 3;
+          this._startCountdown();
+        }
+        if (this.questionTime_list.includes(parseInt(this.currentTime)) && !this.answeredQuestions.has(parseInt(this.currentTime))) {
+          this.questinInfo = this.videoInfo.question_list.find((item) => item.play_time == parseInt(this.currentTime));
+          this.videoContext.pause();
+          this.videoContext.exitFullScreen();
+          this.$refs.questionRef.open("center");
+          //答题时间停止上报
+          clearInterval(this.timer);
+        }
+      }
+    },
+    _videoPlay() {
+      console.log("开始播放");
+      this.videoContext.requestFullScreen();
+    },
+    //上报视频播放时间
+    _postVideoDuration(video_playtime, status) {
+      if (status == 1) clearInterval(this.timer);
+      this.$http.request("api/video_learn_record/update_playtime", { record_id: this.videoInfo.record_id, video_playtime, status }).then((re) => {
+        if (re.code == "success") {
+          console.log("上报成功");
+        }
+      });
+    },
+    _selectAnswer(answer_id) {
+      this.answer_id = answer_id;
+    },
+    _submitAnswer() {
+      this.$http
+        .request("api/video_learn_answer/play_exam", {
+          record_id: this.videoInfo.record_id,
+          course_id: this.questinInfo.course_id,
+          question_id: this.questinInfo.question_id,
+          answer_id: this.answer_id,
+        })
+        .then((re) => {
+          if (re.code == "success") {
+            this.show_answer = true;
+            this.answerInfo = re.data;
+            // 将当前问题标记为已回答
+            this.answeredQuestions.add(this.questinInfo.play_time);
+          }
+        });
+    },
+    _goPlayVedio() {
+      this.questinInfo.isAnswer = true;
+      this.show_answer = false;
+      this.$refs.questionRef.close();
+      uni.showToast({
+        title: "2秒后切换横屏",
+        icon: "none",
+        duration: 2000,
+      });
+      setTimeout(() => {
+        this.videoContext.play();
+      }, 2000);
+    },
+    _startCountdown() {
+      const countdownInterval = setInterval(() => {
+        if (this.countdown > 0) {
+          uni.showToast({
+            title: `${this.countdown}秒后进入答题`,
+            icon: "none",
+            duration: 1000,
+          });
+          this.countdown--;
+        } else {
+          clearInterval(countdownInterval);
+        }
+      }, 1000);
+    },
+    _videoEnd() {
+      this.videoContext.pause();
+      this.videoContext.exitFullScreen();
+      uni.showModal({
+        title: "学习结束",
+        content: "恭喜您学习结束,是否查看报告?",
+        success: (res) => {
+          if (res.confirm) {
+            uni.redirectTo({
+              url: `/pages/video/record?type=learn&record_id=${this.videoInfo.record_id}`,
+            });
+          }
+        },
+      });
+    },
+    screenChange(event) {
+      let fullScreen = event.detail.fullScreen;
+      console.log("fullScreen", fullScreen);
+      this.bigsScreen = fullScreen;
+    },
+  },
+};
 </script>
 
 <style lang="less">
-	.video_play{
-		width: 750rpx;
-		display: block;
-		font-size: 36rpx;
-		overflow: hidden;
-		font-weight: bold;
-		line-height: 60rpx;
-		background-color: #FFFFFF;
-		.video_control{
-			width: 750rpx;
-			display: block;
-			font-size: 36rpx;
-			overflow: hidden;
-			font-weight: bold;
-			line-height: 60rpx;
-			background-color: #FFFFFF;
-		}
-	}
-	.video_title{
-		width: 700rpx;
-		display: block;
-		font-size: 36rpx;
-		overflow: hidden;
-		font-weight: bold;
-		line-height: 60rpx;
-		padding: 0rpx 25rpx;
-		padding-top: 60rpx;
-		background-color: #FFFFFF;
-	}
-	.video_time{
-		width: 700rpx;
-		color: #999999;
-		display: block;
-		font-size: 26rpx;
-		overflow: hidden;
-		line-height: 40rpx;
-		padding: 0rpx 25rpx;
-		background-color: #FFFFFF;
-	}
-	.rich_text{
-		width: 700rpx;
-		display: block;
-		overflow: hidden;
-		font-size: 26rpx;
-		margin: 0rpx auto;
-		min-height: 800rpx;
-		line-height: 50rpx;
-		padding: 10rpx 25rpx;
-		background-color: #FFFFFF;
-		[alt] {
-		  //web_view图片
-		  max-width: 100%; // 避免图片超宽
-		  vertical-align: bottom; // 避免图片之间间隙
-		}
-	}
-	.video_poster{
-		width: 700rpx;
-		display: block;
-		overflow: hidden;
-		margin: 6rpx auto;
-		padding: 10rpx 25rpx;
-		background-color: #FFFFFF;
-		.poster_img{
-			width: 700rpx;
-			display: block;
-		}
-	}
-	.read_total{
-		width: 700rpx;
-		color: #999999;
-		display: block;
-		font-size: 26rpx;
-		overflow: hidden;
-		line-height: 60rpx;
-		padding: 0rpx 25rpx;
-		margin-bottom: 122rpx;
-		background-color: #FFFFFF;
-	}
-	.handle_box {
-	  left: 0rpx;
-	  width: 700rpx;
-	  height: 120rpx;
-	  display: block;
-	  position: fixed;
-	  overflow: hidden;
-	  padding: 20rpx 25rpx;
-	  background-color: #FFFFFF;
-	  bottom: var(--window-bottom);
-	  border-top: 2rpx solid #DDDDDD;
-	  .click_box{
-		  border: none;
-		  float: right;
-		  display: block;
-		  height: 120rpx;
-		  padding: 0rpx 0rpx;
-		  line-height: 120rpx;
-		  margin-right: 20rpx;
-		  font-size: 24rpx !important;
-		  background-color: transparent;
-		  .uni-icons{
-			  font-size: 36rpx !important;
-		  }
-	  }
-	  .click_box::after{
-		  border: none;
-		  background-color: transparent;
-	  }
-	}
+.video_play {
+  width: 750rpx;
+  display: block;
+  font-size: 36rpx;
+  overflow: hidden;
+  font-weight: bold;
+  line-height: 60rpx;
+  background-color: #ffffff;
+  position: relative;
+  .video_control {
+    width: 750rpx;
+    display: block;
+    font-size: 36rpx;
+    overflow: hidden;
+    font-weight: bold;
+    line-height: 60rpx;
+    background-color: #ffffff;
+  }
+  .cover {
+    position: absolute;
+    bottom: 0;
+    z-index: 998;
+    height: 20%;
+    /* background-color: red; */
+    background-color: rgba(255, 255, 255, 0.007);
+    margin-left: 60px;
+    width: 70%;
+  }
+  .bigScreen {
+    position: absolute;
+    bottom: -60%;
+    z-index: 998;
+    height: 20%;
+    /* background-color: red; */
+    background-color: rgba(255, 255, 255, 0.007);
+    margin-left: 70px;
+    width: 80%;
+  }
+}
+.video_title {
+  width: 700rpx;
+  display: block;
+  font-size: 36rpx;
+  overflow: hidden;
+  font-weight: bold;
+  line-height: 60rpx;
+  padding: 0rpx 25rpx;
+  padding-top: 60rpx;
+  background-color: #ffffff;
+}
+.video_time {
+  width: 700rpx;
+  color: #999999;
+  display: block;
+  font-size: 26rpx;
+  overflow: hidden;
+  line-height: 40rpx;
+  padding: 0rpx 25rpx;
+  background-color: #ffffff;
+}
+.rich_text {
+  width: 700rpx;
+  display: block;
+  overflow: hidden;
+  font-size: 26rpx;
+  margin: 0rpx auto;
+  min-height: 800rpx;
+  line-height: 50rpx;
+  padding: 10rpx 25rpx;
+  background-color: #ffffff;
+  [alt] {
+    //web_view图片
+    max-width: 100%; // 避免图片超宽
+    vertical-align: bottom; // 避免图片之间间隙
+  }
+}
+.video_poster {
+  width: 700rpx;
+  display: block;
+  overflow: hidden;
+  margin: 6rpx auto;
+  padding: 10rpx 25rpx;
+  background-color: #ffffff;
+  .poster_img {
+    width: 700rpx;
+    display: block;
+  }
+}
+.read_total {
+  width: 700rpx;
+  color: #999999;
+  display: block;
+  font-size: 26rpx;
+  overflow: hidden;
+  line-height: 60rpx;
+  padding: 0rpx 25rpx;
+  margin-bottom: 122rpx;
+  background-color: #ffffff;
+}
+.handle_box {
+  left: 0rpx;
+  width: 700rpx;
+  height: 120rpx;
+  display: block;
+  position: fixed;
+  overflow: hidden;
+  padding: 20rpx 25rpx;
+  background-color: #ffffff;
+  bottom: var(--window-bottom);
+  border-top: 2rpx solid #dddddd;
+  .click_box {
+    border: none;
+    float: right;
+    display: block;
+    height: 120rpx;
+    padding: 0rpx 0rpx;
+    line-height: 120rpx;
+    margin-right: 20rpx;
+    font-size: 24rpx !important;
+    background-color: transparent;
+    .uni-icons {
+      font-size: 36rpx !important;
+    }
+  }
+  .click_box::after {
+    border: none;
+    background-color: transparent;
+  }
+}
+.popup_content {
+  width: 600rpx;
+  height: 800rpx;
+  background-color: #ffffff;
+  padding: 40rpx;
+  border-radius: 10rpx;
+  overflow: auto;
+  .content {
+    display: flex;
+    flex-direction: column;
+    gap: 20rpx;
+    justify-content: space-around;
+    height: 100%;
+    align-items: center;
+    .question_content {
+      overflow-y: auto;
+      font-size: 30rpx;
+      font-weight: bold;
+    }
+    .question_options {
+      display: flex;
+      flex-direction: column;
+      gap: 40rpx;
+      width: 100%;
+      .question_answer {
+        height: 80rpx;
+        width: 100%;
+        border: 4rpx solid #e3e3e3;
+        border-radius: 20rpx;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        &.active {
+          border-color: #3c7aff;
+          background-color: #dbeafe;
+        }
+      }
+    }
+    .submit_btn {
+      width: 100%;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      color: #fff;
+      height: 100rpx;
+      background-color: #dddddd;
+      border-radius: 20rpx;
+      &.active {
+        background-color: #5045e6;
+      }
+    }
+    .title {
+      font-size: 64rpx;
+      font-weight: bold;
+    }
+    .score_content {
+      background-color: #f3f5f7;
+      padding: 40rpx;
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      width: 100%;
+      border-radius: 20rpx;
+      box-sizing: border-box;
+    }
+  }
+}
 </style>

+ 228 - 0
pages/video/exam.vue

@@ -0,0 +1,228 @@
+<template>
+  <view>
+    <swiper class="question_content">
+      <swiper-item v-for="(item, index) in question_Info.question_list" :key="index">
+        <view class="question_item">
+          <view class="question_content_text">{{ item.question_title }}</view>
+          <view class="question_options">
+            <view v-for="(option, cIndex) in item.answer_list" :key="index" :class="['question_answer', { active: item.checkAnswer == option.id }]" @click="_selectAnswer(index, option.id)">
+              <view :class="['question_index', { active: item.checkAnswer == option.id }]">{{ option.value.substring(0, 1) }}</view>
+              {{ option.value.substring(2) }}
+            </view>
+          </view>
+        </view>
+      </swiper-item>
+    </swiper>
+    <view class="bottom_btn">
+      <view class="answer_info"
+        >已答&nbsp;<view style="color: #5045e6">{{ answeredCount }}</view
+        >&nbsp;题,共&nbsp;{{ question_Info.question_total }}&nbsp;题</view
+      >
+      <view class="submit-btn" @click="_handleSubmit">交卷</view>
+    </view>
+    <uni-popup ref="tipRef" type="center" @change="_changeTip">
+      <view class="tip_content">
+        <view code>&nbsp;{{ "<" }}&nbsp; &nbsp;{{ ">" }}&nbsp;</view>
+        <view>左右滑动切换题目</view>
+      </view>
+    </uni-popup>
+  </view>
+</template>
+
+<script>
+export default {
+  data() {
+    return {
+      course_id: null,
+      question_Info: {},
+    };
+  },
+  onLoad(param) {
+    this.course_id = param.id;
+    if (!uni.getStorageSync("isShowExamTip")) {
+      this.$refs.tipRef.open("center");
+    }
+
+    uni.enableAlertBeforeUnload({
+      message: "您确定要退出答题吗?",
+      success: function (res) {
+        console.log("方法注册成功:", res);
+      },
+      fail: function (errMsg) {
+        console.log("方法注册失败:", errMsg);
+      },
+    });
+  },
+  onShow() {
+    this._getDetail();
+  },
+  computed: {
+    answeredCount() {
+      if (!this.question_Info.question_list) return 0;
+      return this.question_Info.question_list.filter((item) => item.checkAnswer).length;
+    },
+  },
+  methods: {
+    _changeTip() {
+      uni.setStorageSync("isShowExamTip", true);
+    },
+    _getDetail() {
+      this.$http
+        .request("api/video_exam_question/get_list", {
+          course_id: this.course_id,
+        })
+        .then((re) => {
+          if (re.code == "success") {
+            this.question_Info = re.data;
+          }
+        });
+    },
+    _selectAnswer(index, id) {
+      this.question_Info.question_list[index].checkAnswer = id;
+    },
+    _handleSubmit() {
+      if (this.answeredCount !== this.question_Info.question_total) {
+        uni.showModal({
+          title: "温馨提示",
+          content: "还有未完成的题目,是否交卷",
+          confirmText: "继续交卷",
+          cancelText: "继续答题",
+          success: (res) => {
+            if (res.confirm) {
+              this._handleIn();
+            }
+          },
+        });
+      } else {
+        this._handleIn();
+      }
+    },
+    _handleIn() {
+      const question_list = this.question_Info.question_list.filter((item) => item.checkAnswer);
+      const answer_list = question_list.map((item) => {
+        return { question_id: item.question_id, answer_id: item.checkAnswer };
+      });
+      this.$http
+        .request(
+          "api/video_exam_record/hand_in",
+          {
+            record_id: this.question_Info.record_id,
+            answer_list: JSON.stringify(answer_list),
+          },
+          "POST"
+        )
+        .then((re) => {
+          if (re.code == "success") {
+            uni.showModal({
+              title: "完成测评",
+              content: "恭喜您完成测评,是否查看报告",
+              confirmText: "查看报告",
+              cancelText: "返回列表",
+              success(res) {
+                if (res.confirm) {
+                  uni.redirectTo({
+                    url: `/pages/video/record?type=exam&record_id=${this.question_Info.record_id}`,
+                  });
+                } else {
+                  uni.redirectTo({
+                    url: `/pages/video/index`,
+                  });
+                }
+              },
+            });
+          }
+        });
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.tip_content {
+  font-size: 64rpx;
+  font-weight: bold;
+  color: #fff;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
+  gap: 20rpx;
+}
+.question_content {
+  height: calc(100vh - 150rpx);
+  background-color: #f2f2f2;
+  width: 100%;
+  box-sizing: border-box;
+  padding: 30rpx 20rpx;
+  .question_item {
+    width: 100%;
+    padding: 30rpx 20rpx;
+    box-sizing: border-box;
+    border: 2rpx solid #ddd;
+    border-radius: 8rpx;
+    .question_content_text {
+      font-size: 32rpx;
+      margin-bottom: 80rpx;
+      font-weight: bold;
+    }
+    .question_options {
+      display: flex;
+      flex-direction: column;
+      gap: 40rpx;
+      width: 100%;
+      .question_index {
+        border: 2rpx solid #ddd;
+        border-radius: 50%;
+        width: 50rpx;
+        height: 50rpx;
+        text-align: center;
+        line-height: 50rpx;
+        margin-right: 20rpx;
+        &.active {
+          border-color: #5045e6;
+          background-color: #5045e6;
+          color: #fff;
+        }
+      }
+      .question_answer {
+        height: 80rpx;
+        width: 100%;
+        border-radius: 20rpx;
+        display: flex;
+        align-items: center;
+        &.active {
+          color: #5045e6;
+        }
+      }
+    }
+  }
+}
+.bottom_btn {
+  position: fixed;
+  bottom: 0;
+  left: 0;
+  width: 100%;
+  height: 150rpx;
+  border-top: 4rpx solid #ddd;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 0 40rpx;
+  box-sizing: border-box;
+  .answer_info {
+    display: flex;
+    align-items: baseline;
+  }
+  .submit-btn {
+    background-color: #5045e6;
+    color: #fff;
+    font-size: 28rpx;
+    width: 200rpx;
+    height: 80rpx;
+    line-height: 80rpx;
+    border-radius: 8rpx;
+    box-shadow: 2rpx 2rpx 8rpx rgba(0, 0, 0, 0.1);
+    text-align: center;
+  }
+}
+</style>

+ 245 - 227
pages/video/index.vue

@@ -1,235 +1,253 @@
 <template>
-	<view>
-		<view class="video_list">
-			<view class="video_item" v-for="(item,index) in videoList" @click.stop="goDetail(item.id)">
-				<image class="thumb_img" :src="item.thumb" mode="aspectFit"></image>
-				<view class="video_info">
-					<view class="video_type">
-						<text class="type_name">{{item.type_name}}</text>
-						<button class="to_learn">开始学习</button>
-					</view>
-					<view class="video_title">{{item.name}}</view>
-					<view class="video_time">
-						<text class="time_info">{{item.end_time}} </text>
-						<button class="after_exam" v-if="item.learn_status > 0">课后评测</button>
-					</view>
-				</view>
-			</view>
-		</view>
-		<view class="to_bottom" v-if="!videoList.length"> -----您还没有可学习课程-----</view>
-		<view class="contact_follow">
-			<button class="contact_btn" open-type="contact">
-				<uni-icons type="headphones" size="30" color="#FFFFFF"></uni-icons>
-			</button>
-		</view>
-	</view>
+  <view>
+    <view class="video_list">
+      <view class="video_item" v-for="(item, index) in videoList">
+        <view class="video_info">
+          <image class="thumb_img" :src="item.thumb" mode="aspectFit"></image>
+          <view>
+            <view class="video_type">
+              <text class="type_name">{{ item.type_name }}</text>
+            </view>
+            <view class="video_title">{{ item.name }}</view>
+            <view class="video_time">
+              <text class="time_info">{{ item.end_time }} </text>
+            </view>
+          </view>
+        </view>
+        <view class="video_btn">
+          <button class="to_learn" @click.stop="goDetail(item.id)">开始学习</button>
+          <button class="after_exam" v-if="item.learn_status > 0" @click="goExam(item.id, item.report_id)">{{ item.report_id > 0 ? "查看报告" : "课后评测" }}</button>
+        </view>
+      </view>
+    </view>
+    <view class="to_bottom" v-if="!videoList.length"> -----您还没有可学习课程-----</view>
+    <view class="contact_follow">
+      <button class="contact_btn" open-type="contact">
+        <uni-icons type="headphones" size="30" color="#FFFFFF"></uni-icons>
+      </button>
+    </view>
+  </view>
 </template>
 
 <script>
-	export default {
-		data() {
-		  return {
-		    // 产品列表
-		    videoList: [],
-		    // 请求参数
-		    requestParam: {
-		      page: 1,
-		      status: 0,
-		    },
-		    // 是否最后一页
-		    isLast: false,
-		    // 是否请求中
-		    isReqing: false,
-		  };
-		},
-		onLoad() {},
-		onShow() {
-		  // 没有数据的话,或者请求中,不允许刷新
-		  if (this.isReqing) return;
-		  // 初始化页码为1
-		  this.requestParam.page = 1;
-		  // 是否是最后一页
-		  this.isLast = false;
-		  // 设置请求中
-		  this.isReqing = true;
-		  // 请求列表
-		  this.$http.request("api/video_course/get_list", this.requestParam).then((re) => {
-		    // 设置非请求中
-		    this.isReqing = false;
-		    // 成功结果
-		    if (re.code == "success") {
-		      if (re.data.last_page <= this.requestParam.page) this.isLast = true;
-		      this.videoList = re.data.data;
-		    }
-		  });
-		},
-		onPullDownRefresh() {
-		  // 如果请求中,不允许请求,
-		  if (this.isReqing) return false;
-		  // 初始化页码为1
-		  this.requestParam.page = 1;
-		  // 是否是最后一页
-		  this.isLast = false;
-		  // 设置请求中
-		  this.isReqing = true;
-		  // 请求列表
-		  this.$http.request("api/video_course/get_list", this.requestParam).then((re) => {
-		    // 设置非请求中
-		    this.isReqing = false;
-		    // 成功结果
-		    if (re.code == "success") {
-		      if (re.data.last_page <= this.requestParam.page) this.isLast = true;
-		      this.videoList = re.data.data;
-		    }
-		  });
-		  uni.stopPullDownRefresh();
-		},
-		onReachBottom() {
-		  // 如果页码是0,避免第一页重复
-		  if (this.requestParam.page < 1) return;
-		  // 最后一页不请求
-		  if (this.isLast) return;
-		  // 请求中,不再请求
-		  if (this.isReqing) return;
-		  // 增加一页
-		  this.requestParam.page = this.requestParam.page + 1;
-		  // 设置请求中
-		  this.isReqing = true;
-		  // 请求列表
-		  this.$http.request("api/video_course/get_list", this.requestParam).then((re) => {
-		    // 设置非请求中
-		    this.isReqing = false;
-		    // 成功结果
-		    if (re.code == "success") {
-			  // 数据
-		      if (re.data.last_page <= this.requestParam.page) this.isLast = true;
-		      // 追加数据
-		      this.videoList.push(...re.data.data);
-		    }
-		  });
-		},
-		methods: {
-			goDetail(id) {
-			  uni.navigateTo({
-			    url: "/pages/video/detail?id=" + id,
-			  });
-			},
-		}
-	}
+export default {
+  data() {
+    return {
+      // 产品列表
+      videoList: [],
+      // 请求参数
+      requestParam: {
+        page: 1,
+        status: 0,
+      },
+      // 是否最后一页
+      isLast: false,
+      // 是否请求中
+      isReqing: false,
+    };
+  },
+  onLoad() {},
+  onShow() {
+    // 没有数据的话,或者请求中,不允许刷新
+    if (this.isReqing) return;
+    // 初始化页码为1
+    this.requestParam.page = 1;
+    // 是否是最后一页
+    this.isLast = false;
+    // 设置请求中
+    this.isReqing = true;
+    // 请求列表
+    this.$http.request("api/video_course/get_list", this.requestParam).then((re) => {
+      // 设置非请求中
+      this.isReqing = false;
+      // 成功结果
+      if (re.code == "success") {
+        if (re.data.last_page <= this.requestParam.page) this.isLast = true;
+        this.videoList = re.data.data;
+      }
+    });
+  },
+  onPullDownRefresh() {
+    // 如果请求中,不允许请求,
+    if (this.isReqing) return false;
+    // 初始化页码为1
+    this.requestParam.page = 1;
+    // 是否是最后一页
+    this.isLast = false;
+    // 设置请求中
+    this.isReqing = true;
+    // 请求列表
+    this.$http.request("api/video_course/get_list", this.requestParam).then((re) => {
+      // 设置非请求中
+      this.isReqing = false;
+      // 成功结果
+      if (re.code == "success") {
+        if (re.data.last_page <= this.requestParam.page) this.isLast = true;
+        this.videoList = re.data.data;
+      }
+    });
+    uni.stopPullDownRefresh();
+  },
+  onReachBottom() {
+    // 如果页码是0,避免第一页重复
+    if (this.requestParam.page < 1) return;
+    // 最后一页不请求
+    if (this.isLast) return;
+    // 请求中,不再请求
+    if (this.isReqing) return;
+    // 增加一页
+    this.requestParam.page = this.requestParam.page + 1;
+    // 设置请求中
+    this.isReqing = true;
+    // 请求列表
+    this.$http.request("api/video_course/get_list", this.requestParam).then((re) => {
+      // 设置非请求中
+      this.isReqing = false;
+      // 成功结果
+      if (re.code == "success") {
+        // 数据
+        if (re.data.last_page <= this.requestParam.page) this.isLast = true;
+        // 追加数据
+        this.videoList.push(...re.data.data);
+      }
+    });
+  },
+  methods: {
+    goDetail(id) {
+      uni.navigateTo({
+        url: "/pages/video/detail?id=" + id,
+      });
+    },
+    goExam(id, report_id) {
+      if (report_id > 0) {
+        uni.navigateTo({
+          url: "/pages/video/record?type=exam&record_id=" + id,
+        });
+        return;
+      }
+      uni.navigateTo({
+        url: "/pages/video/exam?id=" + id,
+      });
+    },
+  },
+};
 </script>
 
 <style lang="less">
-	.video_list{
-		float: left;
-		width: 750rpx;
-		display: block;
-		overflow: hidden;
-		.video_item{
-			display: block;
-			width: 700rpx;
-			height: 200rpx;
-			overflow: hidden;
-			margin-bottom: 10rpx;
-			padding: 20rpx 25rpx;
-			border-radius: 10rpx;
-			background-color: #FFFFFF;
-			.thumb_img{
-				float: left;
-				width: 200rpx;
-				height: 200rpx;
-				display: block;
-				border-radius: 10rpx;
-			}
-			.video_info{
-				float: right;
-				width: 480rpx;
-				display: block;
-				margin-left: 20rpx;
-				.video_type{
-					color: #999999;
-					width: 480rpx;
-					display: block;
-					height: 60rpx;
-					font-size: 26rpx;
-					line-height: 60rpx;
-					.to_learn{
-						color: #66b66A;
-						float: right;
-						border: none;
-						width: 140rpx;
-						height: 40rpx;
-						font-size: 26rpx;
-						margin-top: 10rpx;
-						line-height: 40rpx;
-						padding: 0rpx 0rpx;
-						text-align: center;
-						border-radius: 10rpx;
-						background-color: #E8F4E8;
-					}
-					.to_learn::after{
-						border: none;
-					}
-				}
-				.video_title{
-					width: 480rpx;
-					display: block;
-					height: 80rpx;
-					font-size: 32rpx;
-					overflow: hidden;
-					font-weight: bold;
-					line-height: 40rpx;
-					text-overflow: ellipsis;
-				}
-				.video_time{
-					color: #999999;
-					width: 480rpx;
-					display: block;
-					height: 60rpx;
-					font-size: 26rpx;
-					line-height: 60rpx;
-					.after_exam{
-						float: right;
-						border: none;
-						width: 140rpx;
-						height: 50rpx;
-						font-size: 26rpx;
-						margin-top: 5rpx;
-						line-height: 50rpx;
-						padding: 0rpx 0rpx;
-						text-align: center;
-						border-radius: 10rpx;
-						color: #66b66A;
-						background-color: #E8F4E8;
-					}
-					.after_exam::after{
-						border: none;
-					}
-				}
-			}
-		}
-	}
-	.contact_follow{
-		bottom: 30%;
-		right: 20rpx;
-		width: 100rpx;
-		height: 100rpx;
-		display: block;
-		position: fixed;
-		border-radius: 50%;
-		background-color: #1296DB;
-		.contact_btn{
-			border: none;
-			width: 100rpx;
-			color: #FFFFFF;
-			height: 100rpx;
-			padding: 0rpx 0rpx;
-			line-height: 100rpx;
-			text-align: center;
-			border-radius: 50%;
-			background-color: #1296DB;
-		}
-		.contact_btn::after{
-			border: none;
-		}
-	}
+.video_list {
+  float: left;
+  width: 750rpx;
+  display: block;
+  overflow: hidden;
+  .video_item {
+    display: block;
+    width: 700rpx;
+    overflow: hidden;
+    margin-bottom: 10rpx;
+    padding: 20rpx 25rpx;
+    border-radius: 10rpx;
+    background-color: #ffffff;
+    .video_btn {
+      display: flex;
+      align-items: center;
+      justify-content: flex-end;
+      gap: 20rpx;
+      margin-top: 10rpx;
+      .to_learn {
+        margin: 0;
+        color: #66b66a;
+        border: none;
+        width: 140rpx;
+        height: 50rpx;
+        font-size: 26rpx;
+        line-height: 50rpx;
+        padding: 0rpx 0rpx;
+        text-align: center;
+        border-radius: 10rpx;
+        background-color: #e8f4e8;
+      }
+      .to_learn::after {
+        border: none;
+      }
+      .after_exam {
+        margin: 0;
+        color: #66b66a;
+        border: none;
+        width: 140rpx;
+        height: 50rpx;
+        font-size: 26rpx;
+        line-height: 50rpx;
+        padding: 0rpx 0rpx;
+        text-align: center;
+        border-radius: 10rpx;
+        background-color: #e8f4e8;
+      }
+      .after_exam::after {
+        border: none;
+      }
+    }
+
+    .video_info {
+      width: 100%;
+      display: flex;
+
+      .thumb_img {
+        width: 200rpx;
+        height: 200rpx;
+        border-radius: 10rpx;
+        margin-right: 20rpx;
+      }
+      .video_type {
+        color: #999999;
+        width: 480rpx;
+        display: block;
+        height: 60rpx;
+        font-size: 26rpx;
+        line-height: 60rpx;
+      }
+      .video_title {
+        width: 480rpx;
+        display: block;
+        height: 80rpx;
+        font-size: 32rpx;
+        overflow: hidden;
+        font-weight: bold;
+        line-height: 40rpx;
+        text-overflow: ellipsis;
+      }
+      .video_time {
+        color: #999999;
+        width: 480rpx;
+        display: block;
+        height: 60rpx;
+        font-size: 26rpx;
+        line-height: 60rpx;
+      }
+    }
+  }
+}
+.contact_follow {
+  bottom: 30%;
+  right: 20rpx;
+  width: 100rpx;
+  height: 100rpx;
+  display: block;
+  position: fixed;
+  border-radius: 50%;
+  background-color: #1296db;
+  .contact_btn {
+    border: none;
+    width: 100rpx;
+    color: #ffffff;
+    height: 100rpx;
+    padding: 0rpx 0rpx;
+    line-height: 100rpx;
+    text-align: center;
+    border-radius: 50%;
+    background-color: #1296db;
+  }
+  .contact_btn::after {
+    border: none;
+  }
+}
 </style>

+ 215 - 0
pages/video/record.vue

@@ -0,0 +1,215 @@
+<template>
+  <view class="report_content">
+    <view class="header">
+      <view class="header_content">
+        <view class="title">本次{{ type == "exam" ? "测评" : "学习" }}概览</view>
+        <view class="content">
+          <view class="content_row">
+            <view class="main">{{ reportInfo.exam_time }}</view>
+            <view class="tip">学习时长(分钟)</view>
+          </view>
+          <view class="content_row">
+            <view class="main">{{ reportInfo.isanswer_total + "/" + reportInfo.answer_total }}</view>
+            <view class="tip">答对题目</view>
+          </view>
+          <view class="content_row">
+            <view class="main">{{ reportInfo.get_score }}</view>
+            <view class="tip">获得积分</view>
+          </view>
+        </view>
+      </view>
+    </view>
+    <view class="content">
+      <view class="title">答题情况</view>
+      <view class="content_list">
+        <view class="conent_main" v-for="(item, index) in queston_list" :key="index">
+          <view class="item_num">
+            <view class="item_answer"><icon :type="item.is_answer == 0 ? 'cancel' : 'success'" size="20" /> &nbsp;第{{ index + 1 }}题</view>
+            <view :style="item.get_score > 0 ? 'color:green' : 'color:red'">+{{ item.get_score }}&nbsp;积分</view>
+          </view>
+          <view class="item_title">{{ item.question_title }}</view>
+        </view>
+      </view>
+    </view>
+    <view class="bottom_btn">
+      <view class="submit_btn defult" @click="_backList">返回课程列表</view>
+      <view class="submit_btn" @click="_continue">{{ type == "exam" ? "重新练习" : "继续课后评测" }}</view>
+    </view>
+  </view>
+</template>
+
+<script>
+export default {
+  data() {
+    return {
+      reportInfo: {
+        exam_time: 0,
+        isanswer_total: 0,
+        answer_total: 0,
+        get_score: 0,
+      },
+      queston_list: [],
+      record_id: null,
+      type: null, // 确保 type 在 data 中定义
+    };
+  },
+  onLoad(params) {
+    console.log("params:", params); // 获取到query参数
+    this.record_id = params.record_id;
+    this.type = params.type;
+    //分享按钮
+    uni.showShareMenu({
+      withShareTicket: true,
+      menus: ["shareAppMessage", "shareTimeline"],
+    });
+  },
+  onShow() {
+    this._getRecord(this.type);
+    this._getAnswerList(this.type);
+  },
+  onShareAppMessage(obj) {
+    return {
+      title: `999智控终端平台\n学习报告`,
+      path: `/pages/video/record?type=${this.type}&record_id=${this.record_id}`,
+    };
+  },
+  methods: {
+    _backList() {
+      uni.redirectTo({
+        url: `/pages/video/index`,
+      });
+    },
+    _continue() {
+      uni.redirectTo({
+        url: `/pages/video/exam?id=${this.record_id}`,
+      });
+    },
+    _getRecord(type) {
+      this.$http.request(`api/video_${type}_record/get_report`, { record_id: this.record_id }).then((re) => {
+        if (re.code == "success") {
+          console.log(re.data);
+          this.reportInfo = re.data;
+        }
+      });
+    },
+    _getAnswerList(type) {
+      this.$http.request(`api/video_${type}_answer/get_list`, { record_id: this.record_id }).then((re) => {
+        if (re.code == "success") {
+          console.log(re.data);
+          this.queston_list = re.data;
+        }
+      });
+    },
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.report_content {
+  .header {
+    width: 100vw;
+    padding: 36rpx;
+    box-sizing: border-box;
+    .header_content {
+      height: 360rpx;
+      width: 100%;
+      background: linear-gradient(to bottom right, #6765f1, #a556f7);
+      border-radius: 20rpx;
+      color: #fff;
+      padding: 36rpx;
+      box-sizing: border-box;
+      .content {
+        height: calc(100% - 55rpx);
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        .content_row {
+          display: flex;
+          flex-direction: column;
+          align-items: center;
+          .main {
+            font-size: 52rpx;
+            font-weight: bold;
+            margin-bottom: 20rpx;
+          }
+          .tip {
+            color: #bba6ff;
+            font-size: 28rpx;
+          }
+        }
+      }
+    }
+  }
+  > .content {
+    width: 100vw;
+    padding: 36rpx;
+    box-sizing: border-box;
+    > .title {
+      font-size: 32rpx;
+      font-weight: bold;
+      margin-bottom: 36rpx;
+    }
+    > .content_list {
+      width: 100%;
+      height: calc(100vh - 750rpx);
+      overflow: auto;
+      box-sizing: border-box;
+      display: flex;
+      flex-direction: column;
+      gap: 20rpx;
+      .conent_main {
+        border: 1px solid #ddd;
+        box-shadow: 0 2px 4px 0 rgba(204, 204, 204, 0.5); /* 添加背景阴影 */
+        width: 100%;
+        padding: 20rpx;
+        box-sizing: border-box;
+        border-radius: 6rpx;
+        display: flex;
+        flex-direction: column;
+        gap: 20rpx;
+        .item_num {
+          display: flex;
+          justify-content: space-between;
+          align-items: center;
+        }
+        .item_answer {
+          display: flex;
+          align-items: center;
+        }
+      }
+    }
+  }
+  .bottom_btn {
+    position: fixed;
+    bottom: 0;
+    left: 0;
+    width: 100%;
+    height: 150rpx;
+    border-top: 4rpx solid #ddd;
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding: 0 40rpx;
+    box-sizing: border-box;
+    .answer_info {
+      display: flex;
+      align-items: baseline;
+    }
+    .submit_btn {
+      background-color: #5045e6;
+      color: #fff;
+      font-size: 28rpx;
+      width: 45%;
+      height: 80rpx;
+      line-height: 80rpx;
+      border-radius: 8rpx;
+      box-shadow: 2rpx 2rpx 8rpx rgba(0, 0, 0, 0.1);
+      text-align: center;
+      &.defult {
+        background-color: #fff;
+        color: #333;
+      }
+    }
+  }
+}
+</style>