Bläddra i källkod

Merge branch 'beta'

SanHQin 3 dagar sedan
förälder
incheckning
24538f7615

+ 1 - 1
dist/index.html

@@ -32,7 +32,7 @@
     html,
     body{
       font-family: '黑体';
-    }</style><link href=./static/css/app.8df06ce626f3e3047693cf38bbd7c5d9.css rel=stylesheet></head><body><div id=app></div><script type=text/javascript src=./static/js/manifest.3ad1d5771e9b13dbdad2.js></script><script type=text/javascript src=./static/js/vendor.becc9317549795ada446.js></script><script type=text/javascript src=./static/js/app.a515ab9a779251fb0682.js></script></body></html><script>function stopSafari() {
+    }</style><link href=./static/css/app.a3cf8c4db422f7b2eef865620be211a9.css rel=stylesheet></head><body><div id=app></div><script type=text/javascript src=./static/js/manifest.3ad1d5771e9b13dbdad2.js></script><script type=text/javascript src=./static/js/vendor.becc9317549795ada446.js></script><script type=text/javascript src=./static/js/app.6df7cd3728cfbd020eeb.js></script></body></html><script>function stopSafari() {
     //阻止safari浏览器双击放大功能
     let lastTouchEnd = 0  //更新手指弹起的时间
     document.documentElement.addEventListener("touchstart", function (event) {

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 0 - 0
dist/static/css/app.a3cf8c4db422f7b2eef865620be211a9.css


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 0 - 0
dist/static/css/app.a3cf8c4db422f7b2eef865620be211a9.css.map


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 0 - 0
dist/static/js/app.6df7cd3728cfbd020eeb.js


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 0 - 0
dist/static/js/app.6df7cd3728cfbd020eeb.js.map


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 0 - 0
dist/static/js/manifest.3ad1d5771e9b13dbdad2.js.map


+ 6 - 0
src/assets/share.svg

@@ -0,0 +1,6 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 32 32" width="24" height="24" style="" filter="none">
+    
+    <g>
+    <path d="M13.333 4v2.667h-6.667v18.667h18.667v-6.667h2.667v8c0 0.736-0.597 1.333-1.333 1.333v0h-21.333c-0.736 0-1.333-0.597-1.333-1.333v0-21.333c0-0.736 0.597-1.333 1.333-1.333v0h8zM23.448 6.667h-6.115v-2.667h10.667v10.667h-2.667v-6.115l-9.333 9.333-1.885-1.885 9.333-9.333z" fill="rgba(79,79,79,1)"></path>
+    </g>
+  </svg>

+ 16 - 0
src/components/courseDetail.vue

@@ -29,6 +29,19 @@
             <div>{{ lang.ssBack }}</div>
             <!-- <img src="../assets/icon/return.png" alt="" /> -->
           </div>
+          <div
+            v-if="screenType == 2 && tType == 1"
+            class="return"
+            @click.stop="
+              gotoCourseManage()
+            "
+          >
+            <div class="returnIndexImg">
+              <img src="../assets/icon/newIcon/returnIndex.png" alt="" />
+            </div>
+            <div>{{ lang.ssBack }}</div>
+            <!-- <img src="../assets/icon/return.png" alt="" /> -->
+          </div>
           <div class="box_course">
             <div class="wheel">
               <img
@@ -1280,6 +1293,9 @@ export default {
     goTo(path) {
       this.$router.push(path);
     },
+    gotoCourseManage(){
+      parent.setCourseUrl();
+    },
     gotoCourse(id) {
 			if(!id){
 				this.insertMemorandum(`${this.lang.ssStartTeachingFor}<span class='variable'>${this.lang.ssAllClassesText}</span>${this.lang.ssTeaching}`)

+ 3 - 6
src/components/dialog/addClassDialog.vue

@@ -175,7 +175,7 @@
           console.error(err);
         });
     },
-      submit() {
+      async submit() {
         if (
           this.courseDetail.userid == this.$route.query.userid &&
           this.checkboxList2.length &&
@@ -184,11 +184,8 @@
         ) {
 
           // 获取endTime为现在
-          let endDate = new Date();
-          let endTime = endDate.toLocaleString("zh-CN", {
-            hour12: false,
-            timeZone: "Asia/Shanghai"
-          }).replace(/\//g, "-");
+          // let endDate = new Date();
+          let endTime = await this.getServerTime()
 
           // 随机20~50分钟
           let randomMinutes = Math.floor(Math.random() * 31) + 20;

+ 37 - 11
src/components/easy2/studyStudent.vue

@@ -16202,6 +16202,8 @@ export default {
       getPickLoading:false,
       selectPzLoading:false,
       getSplitScreenDataLoading:false,
+      timeoutPan: null,
+      isTimeOutPan: false,
     };
   },
   watch:{
@@ -25767,12 +25769,10 @@ export default {
           console.error(err);
         });
     },
-    doSyncClassData(){
-      if(this.courseDetail.userid == this.userid && this.org == '16ace517-b5c7-4168-a9bb-a9e0035df840'){
-        let endTime = new Date().toLocaleString("zh-CN", { 
-            hour12: false, 
-            timeZone: "Asia/Shanghai" 
-          }).replace(/\//g, "-")
+    async doSyncClassData(){
+      let userArray = ['6fbc6471-1d48-11ed-8c78-005056b86db5','333d0dfc-1cd9-11ef-bee5-005056b86db5','6fbce5ef-1d48-11ed-8c78-005056b86db5','66feffcc-ad35-11ed-b13d-005056b86db5']
+      if (this.courseDetail.userid == this.userid && (this.org == '16ace517-b5c7-4168-a9bb-a9e0035df840' || userArray.includes(this.userid))) {
+        let endTime = await this.getServerTime()
         let courseTime = Math.floor((new Date(endTime) - new Date(this.startTime)) / (1000 * 60))
         this.syncClassData({
           courseId: this.id,
@@ -25786,6 +25786,28 @@ export default {
         console.log('同步数据')
       }
     },
+    resetIdleOp(){
+      this.$message({
+        message: this.lang.ssLongTimeNoOperation,
+        type: "warning",
+      });
+      this.goTo(
+                    '/courseDetail?userid=' +
+                      this.userid +
+                      '&oid=' +
+                      this.oid +
+                      '&org=' +
+                      this.org +
+                      '&cid=' +
+                      this.id +
+                      '&courseId=' +
+                      this.id +
+                      '&tType=' +
+                      this.tType +
+                      '&screenType=' +
+                      this.screenType
+                  )
+    }
   },
   directives: {
     // 使用局部注册指令的方式
@@ -25835,6 +25857,10 @@ export default {
     this.opertimer = null;
     this.updateSplitScreenData(1);
     this.doSyncClassData();
+
+    clearTimeout(this.timeoutPan);
+    this.timeoutPan = null;
+    this.stopIdleDetection(this.resetIdleOp);
   },
   computed: {
     renderedFormula2() {
@@ -26045,12 +26071,12 @@ export default {
     },
     
   },
-  mounted() {
+  async mounted() {
     this.setoTime("1");
-    this.startTime = new Date().toLocaleString("zh-CN", { 
-      hour12: false, 
-      timeZone: "Asia/Shanghai" 
-    }).replace(/\//g, "-")
+    this.startTime = await this.getServerTime()
+    this.timeoutPan = setTimeout(() => {
+      this.startIdleDetection(this.resetIdleOp);
+    }, 40 * 60 * 1000)
     this.updateSplitScreenData(2);
     this.splitScreenData.myUid = uuidv4();
     document.body.addEventListener("click", e => {

+ 36 - 11
src/components/easy3/studyStudent.vue

@@ -12523,6 +12523,8 @@ export default {
       getPickLoading:false,
       selectPzLoading:false,
       getSplitScreenDataLoading:false,
+      timeoutPan: null,
+      isTimeOutPan: false,
     };
   },
   methods: {
@@ -21089,12 +21091,10 @@ export default {
       this.IsStulook = flag;
       this.updateIsStulook();
     },
-    doSyncClassData(){
-      if(this.courseDetail.userid == this.userid && this.org == '16ace517-b5c7-4168-a9bb-a9e0035df840'){
-        let endTime = new Date().toLocaleString("zh-CN", { 
-            hour12: false, 
-            timeZone: "Asia/Shanghai" 
-          }).replace(/\//g, "-")
+    async doSyncClassData(){
+      let userArray = ['6fbc6471-1d48-11ed-8c78-005056b86db5','333d0dfc-1cd9-11ef-bee5-005056b86db5','6fbce5ef-1d48-11ed-8c78-005056b86db5','66feffcc-ad35-11ed-b13d-005056b86db5']
+      if (this.courseDetail.userid == this.userid && (this.org == '16ace517-b5c7-4168-a9bb-a9e0035df840' || userArray.includes(this.userid))) {
+        let endTime = await this.getServerTime()
         let courseTime = Math.floor((new Date(endTime) - new Date(this.startTime)) / (1000 * 60))
         this.syncClassData({
           courseId: this.id,
@@ -21108,6 +21108,28 @@ export default {
         console.log('同步数据')
       }
     },
+    resetIdleOp(){
+      this.$message({
+        message: this.lang.ssLongTimeNoOperation,
+        type: "warning",
+      });
+      this.goTo(
+                    '/courseDetail?userid=' +
+                      this.userid +
+                      '&oid=' +
+                      this.oid +
+                      '&org=' +
+                      this.org +
+                      '&cid=' +
+                      this.id +
+                      '&courseId=' +
+                      this.id +
+                      '&tType=' +
+                      this.tType +
+                      '&screenType=' +
+                      this.screenType
+                  )
+    }    
   },
   directives: {
     // 使用局部注册指令的方式
@@ -21155,6 +21177,9 @@ export default {
     this.opertimer = null;
 		this.updateSplitScreenData(1);
     this.doSyncClassData();
+    clearTimeout(this.timeoutPan);
+    this.timeoutPan = null;
+    this.stopIdleDetection(this.resetIdleOp);
   },
   computed: {
     renderedFormula2() {
@@ -21360,12 +21385,12 @@ export default {
     },
 
   },
-  mounted() {
+  async mounted() {
     this.setoTime("1");
-    this.startTime = new Date().toLocaleString("zh-CN", { 
-      hour12: false, 
-      timeZone: "Asia/Shanghai" 
-    }).replace(/\//g, "-")
+    this.startTime = await this.getServerTime()
+    this.timeoutPan = setTimeout(() => {
+      this.startIdleDetection(this.resetIdleOp);
+    }, 40 * 60 * 1000)
     // if (this.tType == 1) {
     //    // 开局关闭学生查看内容
     //   this.StulookMode(false)

+ 103 - 19
src/components/pptEasyClass/index.vue

@@ -10,6 +10,9 @@
           <div @click.stop="back" class="backBtn" v-if="screenType != 2">
             <img src="../../assets/icon/newIcon/return.svg" alt="" />
           </div>
+          <div @click.stop="gotoCourseManage" class="backBtn" v-if="screenType == 2 && tType == 1">
+            <img src="../../assets/icon/newIcon/return.svg" alt="" />
+          </div>
           <div v-if="tcid" class="class-info-group">
             <span class="class-label">{{ lang.ssClass }}</span>
             <span class="class-value class-value2" @click="openSelectClass">
@@ -26,6 +29,9 @@
             <span class="class-label" v-if="inviteCode">{{ lang.ssInviteCode }}</span>
             <span class="class-value" v-if="inviteCode">{{ inviteCode }}</span>
           </div>
+          <div @click.stop="openShareDialog" class="shareBtn" v-if="tcid && tType == 1">
+            <img src="../../assets/share.svg" alt="" />
+          </div>
         </div>
         <div class="pec_h_center">
           <el-tooltip effect="dark" :content="lang.ssRefresh" placement="bottom">
@@ -59,6 +65,11 @@
             <span class="switch-label" :class="{ active: freeBrowse }">{{ freeBrowse ? lang.ssFreeBrowse :
               lang.ssFollowMode }}</span>
           </div>
+          <div class="free-browse-switch" v-if="courseDetail.userid == userid">
+            <el-switch v-model="isCan" :active-value="true" :inactive-value="false" class="custom-switch"
+              active-color="#03ae2b" inactive-color="#d8d8d8" @change="onIsCanChange"></el-switch>
+            <span class="switch-label" :class="{ active: isCan }">{{ isCan ? lang.ssShowResult : lang.ssHideResult }}</span>
+          </div>
           <div class="free-browse-switch" v-if="tType == 2">
             <span class="switch-label" :class="{ active: freeBrowse }">{{ freeBrowse ? lang.ssFreeBrowse :
               lang.ssFollowMode }}</span>
@@ -107,6 +118,8 @@
     <messageInstruction ref="messageInstructionRef"></messageInstruction>
     <!-- 确认提示组件 -->
     <confirmInstruction ref="confirmInstructionRef"></confirmInstruction>
+    <!-- 分享弹窗 -->
+    <shareDialog ref="shareDialogRef"></shareDialog>
   </div>
 </template>
 
@@ -116,12 +129,14 @@ import selectTeachingClassDialog from "../dialog/selectTeachingClassDialog2.vue"
 
 import messageInstruction from '../components/messageInstruction.vue';
 import confirmInstruction from '../components/confirmInstruction.vue';
+import shareDialog from './shareDialog.vue';
 export default {
   mixins: [myMixin],
   components: {
     messageInstruction,
     confirmInstruction,
-    selectTeachingClassDialog
+    selectTeachingClassDialog,
+    shareDialog
   },
   data() {
     return {
@@ -175,6 +190,10 @@ export default {
       // 录音时间记录
       recordingStartTime: "", // 开始录音时间
       recordingEndTime: "", // 结束录音时间
+      isResultArray: [],
+      isCan: false,
+      timeoutPan: null,
+      isTimeOutPan: false,
     };
   },
   computed: {
@@ -426,10 +445,7 @@ export default {
               // 存储文件和文本到全局对象
               await this.storeRecordingData(file);
               // 记录结束录音时间
-              this.recordingEndTime = new Date().toLocaleString("zh-CN", {
-                hour12: false,
-                timeZone: "Asia/Shanghai"
-              }).replace(/\//g, "-");
+              this.recordingEndTime = await this.getServerTime()
               // 调用 addPPTClass 接口
               this.addPPTClass(file);
               iiframe.contentWindow.onSessionStopped = null;
@@ -448,10 +464,7 @@ export default {
             // 存储文件和文本到全局对象
             await this.storeRecordingData(file);
             // 记录结束录音时间
-            this.recordingEndTime = new Date().toLocaleString("zh-CN", {
-              hour12: false,
-              timeZone: "Asia/Shanghai"
-            }).replace(/\//g, "-");
+            this.recordingEndTime = await this.getServerTime()
             // 调用 addPPTClass 接口
             this.addPPTClass(file);
             resolve(true)
@@ -743,6 +756,9 @@ export default {
       )
       // }
     },
+    gotoCourseManage(){
+      parent.setCourseUrl();
+    },
     afterClass() {
       if (this.recordedForm.status == 1) {
         this.toggleRecording().then((flag) => {
@@ -754,6 +770,7 @@ export default {
               cancelText: this.lang.ssCancel,
               submitText: this.lang.ssConfirm,
               submitCallback: () => {
+                this.isTimeOutPan = false;
                 this.$refs.ppt.contentWindow.PPTistStudent.forceLogout();
                 this.$refs.messageInstructionRef.pptMessage(this.lang.ssStudentLoggedOut)
                 setTimeout(() => {
@@ -762,6 +779,7 @@ export default {
                 }, 1000)
               },
               cancelCallback: () => {
+                this.isTimeOutPan = false;
                 console.log("取消")
               }
             })
@@ -820,6 +838,19 @@ export default {
       console.log('自由浏览模式已切换为1:', this.freeBrowse);
       this.$refs.ppt.contentWindow.PPTistStudent.toggleFollowMode()
     },
+    onIsCanChange(value) {
+      this.isCan = value;
+      for(var i = 0; i < this.isResultArray.length; i++){
+        let item = this.isResultArray[i];
+        if(value && item.isTool){
+          item.can = true
+        }else if(item.isTool){
+          item.can = false
+          item.like = false
+        }
+      }
+      this.$refs.ppt.contentWindow.PPTistStudent.setCan(this.isResultArray)
+    },
     setOperationTime() {
       let _this = this;
       if (_this.opertimer) {
@@ -856,12 +887,10 @@ export default {
         this.jArray = Array;
       })
     },
-    doSyncClassData() {
-      if (this.courseDetail.userid == this.userid && this.org == '16ace517-b5c7-4168-a9bb-a9e0035df840') {
-        let endTime = new Date().toLocaleString("zh-CN", {
-          hour12: false,
-          timeZone: "Asia/Shanghai"
-        }).replace(/\//g, "-")
+    async doSyncClassData() {
+      let userArray = ['6fbc6471-1d48-11ed-8c78-005056b86db5','333d0dfc-1cd9-11ef-bee5-005056b86db5','6fbce5ef-1d48-11ed-8c78-005056b86db5','66feffcc-ad35-11ed-b13d-005056b86db5']
+      if (this.courseDetail.userid == this.userid && (this.org == '16ace517-b5c7-4168-a9bb-a9e0035df840' || userArray.includes(this.userid))) {
+        let endTime = await this.getServerTime()
         let courseTime = Math.floor((new Date(endTime) - new Date(this.startTime)) / (1000 * 60))
         this.syncClassData({
           courseId: this.id,
@@ -916,19 +945,46 @@ export default {
         //   };
         // }
       }
+    },
+    // 打开分享弹窗
+    openShareDialog() {
+      let code = this.userJson.oidCode || this.userJson.orgCode
+      this.$refs.shareDialogRef.open(code, this.inviteCode)
+    },
+    resetIdleOp(){
+      if(this.courseDetail.userId == this.userid){
+        this.isTimeOutPan = true;
+        this.afterClass()
+        setTimeout(() => {
+          if(this.isTimeOutPan){
+            this.$refs.ppt.contentWindow.PPTistStudent.forceLogout();
+            this.$refs.messageInstructionRef.pptMessage(this.lang.ssStudentLoggedOut)
+            setTimeout(() => {
+              this.tcid2 = ""
+              this.refreshCourse()
+            }, 1000)
+          }
+        }, 5000);
+      }
+      // console.log('resetIdleOp','重置空闲定时器')
     }
   },
   destroyed() {
     clearInterval(this.opertimer);
     this.opertimer = null;
+    clearTimeout(this.timeoutPan);
+    this.timeoutPan = null;
     this.doSyncClassData();
+    this.stopIdleDetection(this.resetIdleOp);
   },
   async mounted() {
     this.setoTime("1");
-    this.startTime = new Date().toLocaleString("zh-CN", {
-      hour12: false,
-      timeZone: "Asia/Shanghai"
-    }).replace(/\//g, "-")
+    this.startTime = await this.getServerTime()
+    console.log('this.startTime', this.startTime)
+    this.timeoutPan = setTimeout(() => {
+      this.startIdleDetection(this.resetIdleOp);
+    }, 40 * 60 * 1000)
+    // this.startIdleDetection(this.resetIdleOp);
     this.getClassName()
     this.getAIJ();
     this.getCourseDetail();
@@ -937,6 +993,20 @@ export default {
       this.freeBrowse = value;
       console.log('自由浏览模式已切换为:', this.freeBrowse);
     }
+    window.setIsResultArray = (value) => {
+      this.isResultArray = value;
+      // 判断数组中isTool为true的项的can是否都为true
+      const toolItems = value.filter(item => item.isTool);
+      if (toolItems.length > 0) {
+        const allCanTrue = toolItems.every(item => item.can === true);
+        const allCanFalse = toolItems.every(item => item.can === false);
+        if (allCanTrue) {
+          this.isCan = true;
+        } else if (allCanFalse) {
+          this.isCan = false;
+        }
+      }
+    }
     if (!this.userJson || !this.userJson.accountNumber) {
       let res = await this.ajax.get(this.$store.state.api + "selectUser", {
         userid: this.$route.query.userid
@@ -1123,6 +1193,20 @@ export default {
   height: 100%;
 }
 
+.shareBtn {
+  width: 25px;
+  height: 25px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  cursor: pointer;
+}
+
+.shareBtn img {
+  width: 100%;
+  height: 100%;
+}
+
 .class-info-group {
   display: flex;
   align-items: center;

+ 103 - 19
src/components/pptEasyClass/indexPS.vue

@@ -10,6 +10,9 @@
           <div @click.stop="back" class="backBtn" v-if="screenType != 2">
             <img src="../../assets/icon/newIcon/return.svg" alt="" />
           </div>
+          <div @click.stop="gotoCourseManage" class="backBtn" v-if="screenType == 2 && tType == 1">
+            <img src="../../assets/icon/newIcon/return.svg" alt="" />
+          </div>
           <div v-if="tcid" class="class-info-group">
             <span class="class-label">{{ lang.ssClass }}</span>
             <span class="class-value class-value2" @click="openSelectClass">
@@ -26,6 +29,9 @@
             <span class="class-label" v-if="inviteCode">{{ lang.ssInviteCode }}</span>
             <span class="class-value" v-if="inviteCode">{{ inviteCode }}</span>
           </div>
+          <div @click.stop="openShareDialog" class="shareBtn" v-if="tcid && tType == 1">
+            <img src="../../assets/share.svg" alt="" />
+          </div>
         </div>
         <div class="pec_h_center">
           <el-tooltip effect="dark" :content="lang.ssRefresh" placement="bottom">
@@ -59,6 +65,11 @@
             <span class="switch-label" :class="{ active: freeBrowse }">{{ freeBrowse ? lang.ssFreeBrowse :
               lang.ssFollowMode }}</span>
           </div>
+          <div class="free-browse-switch" v-if="courseDetail.userid == userid">
+            <el-switch v-model="isCan" :active-value="true" :inactive-value="false" class="custom-switch"
+              active-color="#03ae2b" inactive-color="#d8d8d8" @change="onIsCanChange"></el-switch>
+            <span class="switch-label" :class="{ active: isCan }">{{ isCan ? lang.ssShowResult : lang.ssHideResult }}</span>
+          </div>
           <div class="free-browse-switch" v-if="tType == 2">
             <span class="switch-label" :class="{ active: freeBrowse }">{{ freeBrowse ? lang.ssFreeBrowse :
               lang.ssFollowMode }}</span>
@@ -107,6 +118,8 @@
     <messageInstruction ref="messageInstructionRef"></messageInstruction>
     <!-- 确认提示组件 -->
     <confirmInstruction ref="confirmInstructionRef"></confirmInstruction>
+    <!-- 分享弹窗 -->
+    <shareDialog ref="shareDialogRef"></shareDialog>
   </div>
 </template>
 
@@ -116,12 +129,14 @@ import selectTeachingClassDialog from "../dialog/selectTeachingClassDialog2.vue"
 
 import messageInstruction from '../components/messageInstruction.vue';
 import confirmInstruction from '../components/confirmInstruction.vue';
+import shareDialog from './shareDialog.vue';
 export default {
   mixins: [myMixin],
   components: {
     messageInstruction,
     confirmInstruction,
-    selectTeachingClassDialog
+    selectTeachingClassDialog,
+    shareDialog
   },
   data() {
     return {
@@ -175,6 +190,10 @@ export default {
       // 录音时间记录
       recordingStartTime: "", // 开始录音时间
       recordingEndTime: "", // 结束录音时间
+      isResultArray: [],
+      isCan: false,
+      timeoutPan: null,
+      isTimeOutPan: false,
     };
   },
   computed: {
@@ -426,10 +445,7 @@ export default {
               // 存储文件和文本到全局对象
               await this.storeRecordingData(file);
               // 记录结束录音时间
-              this.recordingEndTime = new Date().toLocaleString("zh-CN", {
-                hour12: false,
-                timeZone: "Asia/Shanghai"
-              }).replace(/\//g, "-");
+              this.recordingEndTime = await this.getServerTime()
               // 调用 addPPTClass 接口
               this.addPPTClass(file);
               iiframe.contentWindow.onSessionStopped = null;
@@ -448,10 +464,7 @@ export default {
             // 存储文件和文本到全局对象
             await this.storeRecordingData(file);
             // 记录结束录音时间
-            this.recordingEndTime = new Date().toLocaleString("zh-CN", {
-              hour12: false,
-              timeZone: "Asia/Shanghai"
-            }).replace(/\//g, "-");
+            this.recordingEndTime = await this.getServerTime()
             // 调用 addPPTClass 接口
             this.addPPTClass(file);
             resolve(true)
@@ -743,6 +756,9 @@ export default {
       )
       // }
     },
+    gotoCourseManage(){
+      parent.setCourseUrl();
+    },
     afterClass() {
       if (this.recordedForm.status == 1) {
         this.toggleRecording().then((flag) => {
@@ -754,6 +770,7 @@ export default {
               cancelText: this.lang.ssCancel,
               submitText: this.lang.ssConfirm,
               submitCallback: () => {
+                this.isTimeOutPan = false;
                 this.$refs.ppt.contentWindow.PPTistStudent.forceLogout();
                 this.$refs.messageInstructionRef.pptMessage(this.lang.ssStudentLoggedOut)
                 setTimeout(() => {
@@ -762,6 +779,7 @@ export default {
                 }, 1000)
               },
               cancelCallback: () => {
+                this.isTimeOutPan = false;
                 console.log("取消")
               }
             })
@@ -820,6 +838,19 @@ export default {
       console.log('自由浏览模式已切换为1:', this.freeBrowse);
       this.$refs.ppt.contentWindow.PPTistStudent.toggleFollowMode()
     },
+    onIsCanChange(value) {
+      this.isCan = value;
+      for(var i = 0; i < this.isResultArray.length; i++){
+        let item = this.isResultArray[i];
+        if(value && item.isTool){
+          item.can = true
+        }else if(item.isTool){
+          item.can = false
+          item.like = false
+        }
+      }
+      this.$refs.ppt.contentWindow.PPTistStudent.setCan(this.isResultArray)
+    },
     setOperationTime() {
       let _this = this;
       if (_this.opertimer) {
@@ -856,12 +887,10 @@ export default {
         this.jArray = Array;
       })
     },
-    doSyncClassData() {
-      if (this.courseDetail.userid == this.userid && this.org == '16ace517-b5c7-4168-a9bb-a9e0035df840') {
-        let endTime = new Date().toLocaleString("zh-CN", {
-          hour12: false,
-          timeZone: "Asia/Shanghai"
-        }).replace(/\//g, "-")
+    async doSyncClassData() {
+      let userArray = ['6fbc6471-1d48-11ed-8c78-005056b86db5','333d0dfc-1cd9-11ef-bee5-005056b86db5','6fbce5ef-1d48-11ed-8c78-005056b86db5','66feffcc-ad35-11ed-b13d-005056b86db5']
+      if (this.courseDetail.userid == this.userid && (this.org == '16ace517-b5c7-4168-a9bb-a9e0035df840' || userArray.includes(this.userid))) {
+        let endTime = await this.getServerTime()
         let courseTime = Math.floor((new Date(endTime) - new Date(this.startTime)) / (1000 * 60))
         this.syncClassData({
           courseId: this.id,
@@ -916,19 +945,46 @@ export default {
         //   };
         // }
       }
+    },
+    // 打开分享弹窗
+    openShareDialog() {
+      let code = this.userJson.oidCode || this.userJson.orgCode
+      this.$refs.shareDialogRef.open(code, this.inviteCode)
+    },
+    resetIdleOp(){
+      if(this.courseDetail.userId == this.userid){
+        this.isTimeOutPan = true;
+        this.afterClass()
+        setTimeout(() => {
+          if(this.isTimeOutPan){
+            this.$refs.ppt.contentWindow.PPTistStudent.forceLogout();
+            this.$refs.messageInstructionRef.pptMessage(this.lang.ssStudentLoggedOut)
+            setTimeout(() => {
+              this.tcid2 = ""
+              this.refreshCourse()
+            }, 1000)
+          }
+        }, 5000);
+      }
+      // console.log('resetIdleOp','重置空闲定时器')
     }
   },
   destroyed() {
     clearInterval(this.opertimer);
     this.opertimer = null;
+    clearTimeout(this.timeoutPan);
+    this.timeoutPan = null;
     this.doSyncClassData();
+    this.stopIdleDetection(this.resetIdleOp);
   },
   async mounted() {
     this.setoTime("1");
-    this.startTime = new Date().toLocaleString("zh-CN", {
-      hour12: false,
-      timeZone: "Asia/Shanghai"
-    }).replace(/\//g, "-")
+    this.startTime = await this.getServerTime()
+    console.log('this.startTime', this.startTime)
+    this.timeoutPan = setTimeout(() => {
+      this.startIdleDetection(this.resetIdleOp);
+    }, 40 * 60 * 1000)
+    // this.startIdleDetection(this.resetIdleOp);
     this.getClassName()
     this.getAIJ();
     this.getCourseDetail();
@@ -937,6 +993,20 @@ export default {
       this.freeBrowse = value;
       console.log('自由浏览模式已切换为:', this.freeBrowse);
     }
+    window.setIsResultArray = (value) => {
+      this.isResultArray = value;
+      // 判断数组中isTool为true的项的can是否都为true
+      const toolItems = value.filter(item => item.isTool);
+      if (toolItems.length > 0) {
+        const allCanTrue = toolItems.every(item => item.can === true);
+        const allCanFalse = toolItems.every(item => item.can === false);
+        if (allCanTrue) {
+          this.isCan = true;
+        } else if (allCanFalse) {
+          this.isCan = false;
+        }
+      }
+    }
     if (!this.userJson || !this.userJson.accountNumber) {
       let res = await this.ajax.get(this.$store.state.api + "selectUser", {
         userid: this.$route.query.userid
@@ -1123,6 +1193,20 @@ export default {
   height: 100%;
 }
 
+.shareBtn {
+  width: 25px;
+  height: 25px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  cursor: pointer;
+}
+
+.shareBtn img {
+  width: 100%;
+  height: 100%;
+}
+
 .class-info-group {
   display: flex;
   align-items: center;

+ 359 - 0
src/components/pptEasyClass/shareDialog.vue

@@ -0,0 +1,359 @@
+<template>
+  <div>
+    <el-dialog
+      :title="lang.ssShareSetting"
+      :visible.sync="dialogVisible"
+      width="600px"
+      v-loading="loading"
+    >
+      <div class="sc_formUrl" v-show="form.url">
+        <el-input
+          class="sc_fu_input"
+          style="color: black"
+          disabled
+          v-model="form.url"
+        >
+          <template slot="append"
+            ><div class="sc_fu_copyBtn" @click.stop="copyUrl()">
+              {{ lang.ssCopyLink }}
+            </div></template
+          >
+        </el-input>
+        <!-- <div class="qrcode" ref="qrCodeRef"></div>
+				<el-link class="qrcodeBtn" type="primary" @click="downloadQrCode()">下载二维码</el-link> -->
+      </div>
+      <!-- <span slot="footer" class="shareFooter">
+        <el-button @click="close()">{{ lang.ssCancelBtn }}</el-button>
+      </span> -->
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import QRCode from "qrcodejs2";
+export default {
+  data() {
+    return {
+      dialogVisible: false,
+      loading: false,
+      date: "0",
+      courseId: "",
+      form: {
+        setPassword: false,
+        password: "",
+        setStudentLook: false,
+        studentLook: "1",
+        setComment: false,
+        comment: "1",
+        setDownload: false,
+        download: "1",
+        setHomeWorkUpload: false,
+        url: ""
+      }
+    };
+  },
+  watch: {
+    "form.url"(newValue) {
+      console.log(newValue)
+      // this.$nextTick(() => {
+      //   this.$refs.qrCodeRef.innerHTML = "";
+      //   if (!newValue) return;
+      //   new QRCode(this.$refs.qrCodeRef, {
+      //     text: `https://beta.cloud.cocorobo.cn/#/?shareCourseId=${this.courseId}`, // 需要转换为二维码的内容
+      //     width: 100,
+      //     height: 100,
+      //     colorDark: "#000000",
+      //     colorLight: "#ffffff",
+      //     correctLevel: QRCode.CorrectLevel.H
+      //   });
+      // });
+    }
+  },
+  methods: {
+    open(courseId, classid) {
+      this.courseId = courseId;
+      this.classId = classid;
+      this.dialogVisible = true;
+      this.form.id = "default";
+      let url = 'https://beta.cloud.cocorobo.cn'
+      if (window.location.href.includes('beta')) {
+        url = url
+      }
+      else if (this.lang.lang === 'cn') {
+        url = 'https://cloud.cocorobo.cn'
+      }
+      else if (this.lang.lang === 'hk') {
+        url = 'https://cloud.cocorobo.hk'
+      }
+      else if (this.lang.lang === 'en') {
+        url = 'https://cloud.cocorobo.com'
+      }
+      this.form.url = `${url}/admin.html?orgnum=${this.courseId}&classcode=${this.classId}`;
+      // this.getData();
+    },
+    close() {
+      this.dialogVisible = false;
+      this.form = {
+        setPassword: false,
+        password: "",
+        setStudentLook: false,
+        studentLook: "1",
+        setComment: false,
+        comment: "1",
+        setDownload: false,
+        download: "1",
+        setHomeWorkUpload: false,
+        url: ""
+      };
+      this.date = "0";
+      this.courseId = "";
+    },
+    generate() {
+      if (!this.courseId) return this.$message.error("错误:无课程ID");
+      if (this.loading) return;
+      this.loading = true;
+      if (this.form.setPassword) {
+        // 判断密码是否没输入
+        if (!this.form.password) {
+          this.loading = false;
+          return this.$message.error("请输入密码");
+        }
+        // 判断密码是否符合规则
+        const reg = /^[A-Za-z0-9]+$/;
+        if (!reg.test(this.form.password)) {
+          this.loading = false;
+          return this.$message.error("密码仅支持数字及英文字母");
+        }
+      }
+      this.form.url = `https://beta.cloud.cocorobo.cn/#/?shareCourseId=${this.courseId}`;
+      let pram = [
+        {
+          cid: this.courseId,
+          jsonData: JSON.stringify(this.form),
+          date: this.date,
+          type: 1
+        }
+      ];
+      this.ajax
+        .post(this.$store.state.api + "addShareCourse", pram)
+        .then(res => {
+          if (res.data == 1) {
+            this.$message.success("生成成功");
+          } else {
+            this.$message.error("生成失败");
+          }
+          this.getData();
+        })
+        .catch(err => {
+          console.log(err);
+          this.$message.error("生成失败");
+          this.getData();
+          this.loading = false;
+        });
+
+      // this.$message.success("生成成功");
+      this.loading = false;
+    },
+    resetGenerate() {
+      this.$confirm("此操作将重置分享链接, 是否继续?", "提示", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(() => {
+        this.loading = true;
+        let pram = [
+          {
+            cid: this.courseId,
+            type: 1
+          }
+        ];
+        this.ajax
+          .post(this.$store.state.api + "delShareCourse", pram)
+          .then(res => {
+            if (res.data == 1) {
+              this.$message.success("重置成功");
+            } else {
+              this.$message.error("重置失败");
+            }
+            this.getData();
+          })
+          .catch(err => {
+            console.log(err);
+            this.$message.error("重置失败");
+            this.getData();
+            this.loading = false;
+          });
+        // this.form = {
+        // 	setPassword: false,
+        // 	password: "",
+        // 	setStudentLook: false,
+        // 	studentLook: "1",
+        // 	setComment: false,
+        // 	comment: "1",
+        // 	setDownload: false,
+        // 	download: "1",
+        // 	setHomeWorkUpload: false,
+        // 	url: "",
+        // };
+        // this.date = "0";
+        // this.$message.success('重置成功')
+      });
+    },
+    copyUrl() {
+      if (!this.form.url) {
+        return this.$message.info(this.lang.ssPleaseGenerateLink);
+      }
+      const input = document.createElement("input");
+      // 设置 display为none会导致无法复制
+      // input.style.display = "none";
+      // 所以只能用其他方法隐藏
+      input.style.opacity = 0;
+      // 为了不影响布局
+      input.style.position = "fixed";
+      input.style.left = "-100%";
+      input.style.top = "-100%";
+      input.value = this.form.url;
+      document.body.appendChild(input);
+      input.select();
+      const success = document.execCommand("copy");
+      document.body.removeChild(input);
+      if (!success) {
+        return this.$message.error(this.lang.ssCopyFailed);
+      } else {
+        return this.$message.success(this.lang.ssCopySuccess);
+      }
+    },
+    getData() {
+      this.form = {
+        setPassword: false,
+        password: "",
+        setStudentLook: false,
+        studentLook: "1",
+        setComment: false,
+        comment: "1",
+        setDownload: false,
+        download: "1",
+        setHomeWorkUpload: false,
+        url: ""
+      };
+      this.date = "0";
+      this.loading = true;
+      // 这里查询数据库是否有数据
+      this.ajax
+        .get(this.$store.state.api + "getShareCourse", {
+          cid: this.courseId,
+          type: 1
+        })
+        .then(res => {
+          let _data = res.data[0];
+          if (_data.length == 0) return (this.loading = false);
+          let jsonData = _data[0].json ? JSON.parse(_data[0].json) : {};
+          this.form = { ...jsonData, id: _data[0].id };
+          this.date = _data[0].time;
+          this.loading = false;
+        })
+        .catch(err => {
+          this.loading = false;
+          console.error(err);
+        });
+    },
+		downloadQrCode(){
+			let canvas = this.$refs.qrCodeRef.getElementsByTagName('canvas')[0];
+			let ctx = canvas.getContext('2d');
+
+			// 创建一个新的canvas,大小为原来的两倍
+			var newCanvas = document.createElement("canvas");
+			newCanvas.width = canvas.width * 2;
+			newCanvas.height = canvas.height * 2;
+
+			// 获取新canvas的context,并绘制原来的canvas到新canvas上
+			var newCtx = newCanvas.getContext("2d");
+			newCtx.drawImage(canvas, 0, 0, newCanvas.width, newCanvas.height);
+
+			// 创建一个链接来下载图片
+			var link = document.createElement("a");
+			link.download = "课程分享二维码.png";
+			link.href = newCanvas.toDataURL();
+			link.click();
+		}
+  }
+};
+</script>
+
+<style scoped>
+.shareContent {
+  width: 600px;
+  max-width: 600px;
+  height: auto;
+}
+
+.shareFooter {
+  display: flex;
+  justify-content: flex-end;
+}
+
+.sc_formItemDate {
+  width: 100%;
+  height: auto;
+  display: flex;
+  align-items: center;
+  margin: 10px 0;
+}
+
+.sc_fd_radio {
+  margin-left: 55px;
+}
+
+.sc_formAuthorityItem {
+  margin-top: 30px;
+  margin-bottom: 20px;
+}
+
+.sc_formAuthorityItem > span {
+  font-size: 14px;
+  color: #7f7f7f;
+  margin-left: 20px;
+}
+
+.sc_fai_switch {
+  width: 190px;
+}
+
+.sc_fai_input {
+  width: 120px;
+  text-align: center;
+  margin-left: 20px;
+}
+
+.sc_fai_radio {
+  width: 120px;
+  margin-left: 20px;
+}
+.sc_fu_copyBtn {
+  color: black;
+  cursor: pointer;
+}
+
+.qrcode {
+  width: 100px;
+  height: 100px;
+  margin-top: 20px;
+}
+
+.sc_fu_input /deep/.el-input__inner {
+  color: black !important;
+  cursor: text !important;
+}
+
+.sc_fu_input >>> .el-input__inner {
+  color: black !important;
+  cursor: text !important;
+}
+
+.qrcodeBtn{
+	width: 100px;
+	display: flex;
+	justify-content: center;
+	margin-top: 10px;
+}
+</style>

+ 36 - 13
src/components/studyStudent.vue

@@ -12449,6 +12449,8 @@ export default {
       getPickLoading:false,
       selectPzLoading:false,
       getSplitScreenDataLoading:false,
+      timeoutPan: null,
+      isTimeOutPan: false,
     };
   },
   methods: {
@@ -21040,12 +21042,10 @@ export default {
       this.IsStulook = flag;
       this.updateIsStulook();
     },
-    doSyncClassData(){
-      if(this.courseDetail.userid == this.userid && this.org == '16ace517-b5c7-4168-a9bb-a9e0035df840'){
-        let endTime = new Date().toLocaleString("zh-CN", {
-            hour12: false,
-            timeZone: "Asia/Shanghai"
-          }).replace(/\//g, "-")
+    async doSyncClassData(){
+      let userArray = ['6fbc6471-1d48-11ed-8c78-005056b86db5','333d0dfc-1cd9-11ef-bee5-005056b86db5','6fbce5ef-1d48-11ed-8c78-005056b86db5','66feffcc-ad35-11ed-b13d-005056b86db5']
+      if (this.courseDetail.userid == this.userid && (this.org == '16ace517-b5c7-4168-a9bb-a9e0035df840' || userArray.includes(this.userid))) {
+        let endTime = await this.getServerTime()
         let courseTime = Math.floor((new Date(endTime) - new Date(this.startTime)) / (1000 * 60))
         this.syncClassData({
           courseId: this.id,
@@ -21059,6 +21059,28 @@ export default {
         console.log('同步数据')
       }
     },
+    resetIdleOp(){
+      this.$message({
+        message: this.lang.ssLongTimeNoOperation,
+        type: "warning",
+      });
+      this.goTo(
+                    '/courseDetail?userid=' +
+                      this.userid +
+                      '&oid=' +
+                      this.oid +
+                      '&org=' +
+                      this.org +
+                      '&cid=' +
+                      this.id +
+                      '&courseId=' +
+                      this.id +
+                      '&tType=' +
+                      this.tType +
+                      '&screenType=' +
+                      this.screenType
+                  )
+    }
   },
   directives: {
     // 使用局部注册指令的方式
@@ -21106,6 +21128,9 @@ export default {
     this.opertimer = null;
 		this.updateSplitScreenData(1);
     this.doSyncClassData();
+    clearTimeout(this.timeoutPan);
+    this.timeoutPan = null;
+    this.stopIdleDetection(this.resetIdleOp);
   },
   computed: {
     renderedFormula2() {
@@ -21305,15 +21330,13 @@ export default {
         return c;
       };
     },
-
   },
-  mounted() {
+  async mounted() {
     this.setoTime("1");
-    this.startTime = new Date().toLocaleString("zh-CN", {
-      hour12: false,
-      timeZone: "Asia/Shanghai"
-    }).replace(/\//g, "-");
-
+    this.startTime = await this.getServerTime()
+    this.timeoutPan = setTimeout(() => {
+      this.startIdleDetection(this.resetIdleOp);
+    }, 40 * 60 * 1000)
     // if (this.tType == 1) {
     //    // 开局关闭学生查看内容
     //   this.StulookMode(false)

+ 8 - 1
src/lang/cn.json

@@ -900,5 +900,12 @@
   "ssBeginClassRecording": "开始课堂录制",
   "ssFinishClassRecording": "结束课堂录制",
   "ssNoPermStop":"未开启录音权限,已停止录音",
-  "ssStudentLoggedOut": "当前登录学生已退出账户"
+  "ssStudentLoggedOut": "当前登录学生已退出账户",
+  "ssShareSetting": "分享链接",
+  "ssCopyLink": "复制链接",
+  "ssPleaseGenerateLink": "请先生成链接",
+  "ssCopyFailed": "复制失败",
+  "ssShowResult": "展示结果",
+  "ssHideResult": "隐藏结果",
+  "ssLongTimeNoOperation": "长时间无操作,将自动退出"
 }

+ 8 - 1
src/lang/en.json

@@ -900,5 +900,12 @@
   "ssBeginClassRecording": "Begin Class Recording",
   "ssFinishClassRecording": "End Class Recording",
   "ssNoPermStop":"Recording permission not enabled, recording has been stopped",
-  "ssStudentLoggedOut": "The currently logged-in student has logged out"
+  "ssStudentLoggedOut": "The currently logged-in student has logged out",
+  "ssShareSetting": "Share Link",
+  "ssCopyLink": "Copy Link",
+  "ssPleaseGenerateLink": "Please generate link first",
+  "ssCopyFailed": "Copy failed",
+  "ssShowResult": "Show Result",
+  "ssHideResult": "Hide Result",
+  "ssLongTimeNoOperation": "Long time no operation, will be logged out"
 }

+ 8 - 1
src/lang/hk.json

@@ -900,5 +900,12 @@
   "ssBeginClassRecording": "開始課堂錄製",
   "ssFinishClassRecording": "結束課堂錄製",
   "ssNoPermStop":"未開啟錄音權限,已停止錄音",
-  "ssStudentLoggedOut": "當前登入學生已退出賬戶"
+  "ssStudentLoggedOut": "當前登入學生已退出賬戶",
+  "ssShareSetting": "分享鏈接",
+  "ssCopyLink": "複製鏈接",
+  "ssPleaseGenerateLink": "請先生成鏈接",
+  "ssCopyFailed": "複製失敗",
+  "ssShowResult": "展示結果",
+  "ssHideResult": "隱藏結果",
+  "ssLongTimeNoOperation": "長時間無操作,將自動退出"
 }

+ 55 - 0
src/mixins/mixin.js

@@ -3,9 +3,64 @@ export const myMixin = {
     return {
         userJson: {},
         packageArray: [],
+        idleTimer: null,
+        idleTimeout: 600000,
     };
   },
+  mounted() {
+    // this.startIdleDetection();
+  },
+  beforeUnmount() {
+    // this.stopIdleDetection();
+  },
   methods: {
+    async getServerTime() {
+      try {
+        let res = await this.ajax.get(this.$store.state.api + 'getServerTime'); 
+        console.log(res)
+        let time = res.data.time
+        return new Date(time).toLocaleString("zh-CN", {
+          hour12: false,
+          timeZone: "Asia/Shanghai"
+        }).replace(/\//g, "-")
+      } catch (error) {
+        return new Date().toLocaleString("zh-CN", {
+          hour12: false,
+          timeZone: "Asia/Shanghai"
+        }).replace(/\//g, "-")
+      }
+    },
+    startIdleDetection(callback) {
+      const events = ['mousedown', 'mousemove', 'keydown', 'touchstart', 'scroll'];
+      events.forEach(event => {
+        document.addEventListener(event, this.handleUserActivity.bind(null, callback));
+      });
+    },
+    stopIdleDetection(callback) {
+      const events = ['mousedown', 'mousemove', 'keydown', 'touchstart', 'scroll'];
+      if (this.idleTimer) {
+        clearTimeout(this.idleTimer);
+        this.idleTimer = null;
+      }
+      events.forEach(event => {
+        document.removeEventListener(event, this.handleUserActivity.bind(null, callback));
+      });
+    },
+    resetIdleTimer(callback) {
+      if (this.idleTimer) {
+        clearTimeout(this.idleTimer);
+        this.idleTimer = null;
+      }
+      this.idleTimer = setTimeout(() => {
+        callback ? callback() : this.onIdle();
+      }, this.idleTimeout);
+    },
+    handleUserActivity(callback) {
+      this.resetIdleTimer(callback);
+    },
+    onIdle() {
+      console.log('用户已空闲10秒,执行空闲回调函数');
+    },
     detectBrowser() {
       const ua = navigator.userAgent;
 

Vissa filer visades inte eftersom för många filer har ändrats