Ver código fonte

Merge branch 'beta'

lsc 1 mês atrás
pai
commit
1d8fbe62ab
30 arquivos alterados com 4085 adições e 413 exclusões
  1. 1 1
      dist/index.html
  2. 0 0
      dist/static/css/app.a48046a97091992c49c965216837369a.css
  3. 0 0
      dist/static/css/app.a48046a97091992c49c965216837369a.css.map
  4. BIN
      dist/static/img/rt-ch_echarts.8000b59.png
  5. 0 0
      dist/static/js/0.4f3b05586c3acc102a54.js
  6. 0 0
      dist/static/js/0.4f3b05586c3acc102a54.js.map
  7. 0 0
      dist/static/js/0.df8814bab917ab2583e0.js.map
  8. 0 0
      dist/static/js/app.00520943d9d01208c21b.js
  9. 0 0
      dist/static/js/app.00520943d9d01208c21b.js.map
  10. 2 2
      dist/static/js/manifest.161e82026ac2ae03ab6f.js
  11. 0 0
      dist/static/js/manifest.161e82026ac2ae03ab6f.js.map
  12. 4 0
      src/assets/icon/classroomObservation/isDoStatus_icon.svg
  13. 10 0
      src/assets/icon/classroomObservation/successStatus_icon.svg
  14. 4 0
      src/assets/icon/classroomObservation/waitStatus_icon.svg
  15. 80 7
      src/components/pages/classroomObservation/components/analysisItem.vue
  16. 203 160
      src/components/pages/classroomObservation/components/analysisSpecialItem.vue
  17. 5 3
      src/components/pages/classroomObservation/components/resourceLibraryDialog.vue
  18. 889 70
      src/components/pages/classroomObservation/dialog/batchCreationClassDialog.vue
  19. 142 49
      src/components/pages/classroomObservation/dialog/editBaseMessageDialog.vue
  20. 163 25
      src/components/pages/classroomObservation/dialog/uploadFileToCreateClassDialog.vue
  21. 72 52
      src/components/pages/classroomObservation/index.vue
  22. 199 29
      src/components/pages/classroomObservation/newComponents/batchClassCard.vue
  23. 2227 0
      src/components/pages/classroomObservation/tools/mixin.js
  24. 21 1
      src/components/pages/components/appDialog.vue
  25. 2 1
      src/components/pages/newCourse/addCourse.vue
  26. 27 6
      src/components/pages/sassPlatform/index.vue
  27. 2 2
      src/components/pages/sz/dataBoardArea/dataCenter/chartList/courseRank/index.vue
  28. 2 2
      src/components/pages/sz/dataBoardCity/dataCenter/chartList/courseRank/index.vue
  29. 29 2
      src/components/pages/test/check/index.vue
  30. 1 1
      src/components/pages/testPerson/examine/index.vue

+ 1 - 1
dist/index.html

@@ -32,7 +32,7 @@
       width: 100%;
       background: #e6eaf0;
       font-family: '黑体';
-    }</style><link href=./static/css/app.8f19f756a441c1214dc059ca3531cf96.css rel=stylesheet></head><body><div id=app></div><script type=text/javascript src=./static/js/manifest.9811ebe9d5c4458a1b2a.js></script><script type=text/javascript src=./static/js/vendor.bb486323f0fa002ba2e7.js></script><script type=text/javascript src=./static/js/app.8210568261f9561a212b.js></script></body></html><script>function stopSafari() {
+    }</style><link href=./static/css/app.a48046a97091992c49c965216837369a.css rel=stylesheet></head><body><div id=app></div><script type=text/javascript src=./static/js/manifest.161e82026ac2ae03ab6f.js></script><script type=text/javascript src=./static/js/vendor.bb486323f0fa002ba2e7.js></script><script type=text/javascript src=./static/js/app.00520943d9d01208c21b.js></script></body></html><script>function stopSafari() {
     //阻止safari浏览器双击放大功能
     let lastTouchEnd = 0  //更新手指弹起的时间
     document.documentElement.addEventListener("touchstart", function (event) {

Diferenças do arquivo suprimidas por serem muito extensas
+ 0 - 0
dist/static/css/app.a48046a97091992c49c965216837369a.css


Diferenças do arquivo suprimidas por serem muito extensas
+ 0 - 0
dist/static/css/app.a48046a97091992c49c965216837369a.css.map


BIN
dist/static/img/rt-ch_echarts.8000b59.png


Diferenças do arquivo suprimidas por serem muito extensas
+ 0 - 0
dist/static/js/0.4f3b05586c3acc102a54.js


Diferenças do arquivo suprimidas por serem muito extensas
+ 0 - 0
dist/static/js/0.4f3b05586c3acc102a54.js.map


Diferenças do arquivo suprimidas por serem muito extensas
+ 0 - 0
dist/static/js/0.df8814bab917ab2583e0.js.map


Diferenças do arquivo suprimidas por serem muito extensas
+ 0 - 0
dist/static/js/app.00520943d9d01208c21b.js


Diferenças do arquivo suprimidas por serem muito extensas
+ 0 - 0
dist/static/js/app.00520943d9d01208c21b.js.map


+ 2 - 2
dist/static/js/manifest.9811ebe9d5c4458a1b2a.js → dist/static/js/manifest.161e82026ac2ae03ab6f.js

@@ -1,2 +1,2 @@
-!function(e){var n=window.webpackJsonp;window.webpackJsonp=function(r,a,c){for(var i,u,f,s=0,l=[];s<r.length;s++)u=r[s],t[u]&&l.push(t[u][0]),t[u]=0;for(i in a)Object.prototype.hasOwnProperty.call(a,i)&&(e[i]=a[i]);for(n&&n(r,a,c);l.length;)l.shift()();if(c)for(s=0;s<c.length;s++)f=o(o.s=c[s]);return f};var r={},t={6:0};function o(n){if(r[n])return r[n].exports;var t=r[n]={i:n,l:!1,exports:{}};return e[n].call(t.exports,t,t.exports,o),t.l=!0,t.exports}o.e=function(e){var n=t[e];if(0===n)return new Promise(function(e){e()});if(n)return n[2];var r=new Promise(function(r,o){n=t[e]=[r,o]});n[2]=r;var a=document.getElementsByTagName("head")[0],c=document.createElement("script");c.type="text/javascript",c.charset="utf-8",c.async=!0,c.timeout=12e4,o.nc&&c.setAttribute("nonce",o.nc),c.src=o.p+"static/js/"+e+"."+{0:"df8814bab917ab2583e0",1:"14e8e8c7e44fc858e4a6",2:"94e1427bfc7ef0b4c685",3:"3a9f53a78da16650e6b8"}[e]+".js";var i=setTimeout(u,12e4);function u(){c.onerror=c.onload=null,clearTimeout(i);var n=t[e];0!==n&&(n&&n[1](new Error("Loading chunk "+e+" failed.")),t[e]=void 0)}return c.onerror=c.onload=u,a.appendChild(c),r},o.m=e,o.c=r,o.d=function(e,n,r){o.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:r})},o.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return o.d(n,"a",n),n},o.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},o.p="./",o.oe=function(e){throw console.error(e),e}}([]);
-//# sourceMappingURL=manifest.9811ebe9d5c4458a1b2a.js.map
+!function(e){var n=window.webpackJsonp;window.webpackJsonp=function(r,c,a){for(var i,u,f,s=0,l=[];s<r.length;s++)u=r[s],t[u]&&l.push(t[u][0]),t[u]=0;for(i in c)Object.prototype.hasOwnProperty.call(c,i)&&(e[i]=c[i]);for(n&&n(r,c,a);l.length;)l.shift()();if(a)for(s=0;s<a.length;s++)f=o(o.s=a[s]);return f};var r={},t={6:0};function o(n){if(r[n])return r[n].exports;var t=r[n]={i:n,l:!1,exports:{}};return e[n].call(t.exports,t,t.exports,o),t.l=!0,t.exports}o.e=function(e){var n=t[e];if(0===n)return new Promise(function(e){e()});if(n)return n[2];var r=new Promise(function(r,o){n=t[e]=[r,o]});n[2]=r;var c=document.getElementsByTagName("head")[0],a=document.createElement("script");a.type="text/javascript",a.charset="utf-8",a.async=!0,a.timeout=12e4,o.nc&&a.setAttribute("nonce",o.nc),a.src=o.p+"static/js/"+e+"."+{0:"4f3b05586c3acc102a54",1:"14e8e8c7e44fc858e4a6",2:"94e1427bfc7ef0b4c685",3:"3a9f53a78da16650e6b8"}[e]+".js";var i=setTimeout(u,12e4);function u(){a.onerror=a.onload=null,clearTimeout(i);var n=t[e];0!==n&&(n&&n[1](new Error("Loading chunk "+e+" failed.")),t[e]=void 0)}return a.onerror=a.onload=u,c.appendChild(a),r},o.m=e,o.c=r,o.d=function(e,n,r){o.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:r})},o.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return o.d(n,"a",n),n},o.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},o.p="./",o.oe=function(e){throw console.error(e),e}}([]);
+//# sourceMappingURL=manifest.161e82026ac2ae03ab6f.js.map

Diferenças do arquivo suprimidas por serem muito extensas
+ 0 - 0
dist/static/js/manifest.161e82026ac2ae03ab6f.js.map


+ 4 - 0
src/assets/icon/classroomObservation/isDoStatus_icon.svg

@@ -0,0 +1,4 @@
+<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path opacity="0.5" d="M7.00008 1.16663C5.84636 1.16663 4.71854 1.50875 3.75926 2.14972C2.79997 2.79069 2.0523 3.70174 1.61079 4.76764C1.16928 5.83354 1.05376 7.00643 1.27884 8.13799C1.50392 9.26954 2.05949 10.3089 2.87529 11.1247C3.6911 11.9406 4.7305 12.4961 5.86206 12.7212C6.99361 12.9463 8.1665 12.8308 9.2324 12.3893C10.2983 11.9477 11.2093 11.2001 11.8503 10.2408C12.4913 9.2815 12.8334 8.15368 12.8334 6.99996C12.8334 6.23391 12.6825 5.47537 12.3894 4.76764C12.0962 4.05991 11.6665 3.41684 11.1249 2.87517C10.5832 2.33349 9.94014 1.90381 9.2324 1.61066C8.52467 1.31751 7.76613 1.16663 7.00008 1.16663ZM7.00008 11.6666C6.0771 11.6666 5.17485 11.3929 4.40742 10.8801C3.63999 10.3674 3.04186 9.63854 2.68865 8.78581C2.33544 7.93309 2.24302 6.99478 2.42309 6.08954C2.60315 5.18429 3.04761 4.35277 3.70025 3.70013C4.3529 3.04748 5.18442 2.60303 6.08966 2.42296C6.99491 2.2429 7.93322 2.33531 8.78594 2.68852C9.63866 3.04173 10.3675 3.63987 10.8803 4.4073C11.3931 5.17473 11.6667 6.07698 11.6667 6.99996C11.6667 8.23764 11.1751 9.42462 10.2999 10.2998C9.42475 11.175 8.23776 11.6666 7.00008 11.6666Z" fill="#3681FC"/>
+<path d="M11.6667 6.99996L12.8333 6.99996C12.8333 6.23392 12.6825 5.47537 12.3893 4.76764C12.0961 4.05991 11.6665 3.41685 11.1248 2.87517C10.5831 2.33349 9.94005 1.90381 9.23232 1.61066C8.52459 1.31751 7.76604 1.16663 7 1.16663V2.33329C8.23768 2.33329 9.42466 2.82496 10.2998 3.70013C11.175 4.5753 11.6667 5.76228 11.6667 6.99996Z" fill="#3681FC"/>
+</svg>

+ 10 - 0
src/assets/icon/classroomObservation/successStatus_icon.svg

@@ -0,0 +1,10 @@
+<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0_2421_25813)">
+<path d="M7 0.875C5.78859 0.875 4.60439 1.23423 3.59713 1.90725C2.58988 2.58027 1.80483 3.53687 1.34124 4.65606C0.877654 5.77526 0.756358 7.00679 0.992693 8.19493C1.22903 9.38306 1.81238 10.4744 2.66897 11.331C3.52557 12.1876 4.61694 12.771 5.80507 13.0073C6.99321 13.2436 8.22474 13.1223 9.34394 12.6588C10.4631 12.1952 11.4197 11.4101 12.0928 10.4029C12.7658 9.39562 13.125 8.21141 13.125 7C13.125 5.37555 12.4797 3.81763 11.331 2.66897C10.1824 1.52031 8.62445 0.875 7 0.875ZM6.125 9.44563L3.9375 7.25813L4.63313 6.5625L6.125 8.05437L9.36688 4.8125L10.0651 5.50638L6.125 9.44563Z" fill="#BCE685"/>
+</g>
+<defs>
+<clipPath id="clip0_2421_25813">
+<rect width="14" height="14" fill="white"/>
+</clipPath>
+</defs>
+</svg>

+ 4 - 0
src/assets/icon/classroomObservation/waitStatus_icon.svg

@@ -0,0 +1,4 @@
+<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path opacity="0.5" d="M7.00008 1.16663C5.84636 1.16663 4.71854 1.50875 3.75926 2.14972C2.79997 2.79069 2.0523 3.70174 1.61079 4.76764C1.16928 5.83354 1.05376 7.00643 1.27884 8.13799C1.50392 9.26954 2.05949 10.3089 2.87529 11.1247C3.6911 11.9406 4.7305 12.4961 5.86206 12.7212C6.99361 12.9463 8.1665 12.8308 9.2324 12.3893C10.2983 11.9477 11.2093 11.2001 11.8503 10.2408C12.4913 9.2815 12.8334 8.15368 12.8334 6.99996C12.8334 6.23391 12.6825 5.47537 12.3894 4.76764C12.0962 4.05991 11.6665 3.41684 11.1249 2.87517C10.5832 2.33349 9.94014 1.90381 9.2324 1.61066C8.52467 1.31751 7.76613 1.16663 7.00008 1.16663ZM7.00008 11.6666C6.0771 11.6666 5.17485 11.3929 4.40742 10.8801C3.63999 10.3674 3.04186 9.63854 2.68865 8.78581C2.33544 7.93309 2.24302 6.99478 2.42309 6.08954C2.60315 5.18429 3.04761 4.35277 3.70025 3.70013C4.3529 3.04748 5.18442 2.60303 6.08966 2.42296C6.99491 2.2429 7.93322 2.33531 8.78594 2.68852C9.63866 3.04173 10.3675 3.63987 10.8803 4.4073C11.3931 5.17473 11.6667 6.07698 11.6667 6.99996C11.6667 8.23764 11.1751 9.42462 10.2999 10.2998C9.42475 11.175 8.23776 11.6666 7.00008 11.6666Z" fill="#969BA3"/>
+<path d="M11.6667 6.99996L12.8333 6.99996C12.8333 6.23392 12.6825 5.47537 12.3893 4.76764C12.0961 4.05991 11.6665 3.41685 11.1248 2.87517C10.5831 2.33349 9.94005 1.90381 9.23232 1.61066C8.52459 1.31751 7.76604 1.16663 7 1.16663V2.33329C8.23768 2.33329 9.42466 2.82496 10.2998 3.70013C11.175 4.5753 11.6667 5.76228 11.6667 6.99996Z" fill="#969BA3"/>
+</svg>

+ 80 - 7
src/components/pages/classroomObservation/components/analysisItem.vue

@@ -500,7 +500,7 @@ export default {
                 _copyData.jsonData
               );
             }
-            this.changeShowIndex(1);
+            this.changeShowIndex(1,false);
             this.loading = false;
             this.$nextTick(() => {
               if (
@@ -508,6 +508,8 @@ export default {
                 ["1", "2", "3"].includes(this.data.jsonData.echartsType)
               ) {
                 this.editEcharts();
+              }else{
+                this.changeShowIndex(1);
               }
             });
           })
@@ -586,7 +588,7 @@ export default {
           console.log("取消");
         });
     },
-    changeShowIndex(value) {
+    changeShowIndex(value,save=true) {
       if (this.historyResult.length == 0) return;
       if (value == -1) {
         if (this.showIndex > 0) this.showIndex--;
@@ -599,10 +601,11 @@ export default {
       _copyData.json_data = JSON.stringify(_copyData.jsonData);
       _oldCopyDate.json_data = JSON.stringify(_oldCopyDate.jsonData);
       // 一样就不用更新了
-      if (JSON.stringify(_copyData) == JSON.stringify(_oldCopyDate)) return;
+      // if (JSON.stringify(_copyData) == JSON.stringify(_oldCopyDate)) return;
       // this.data = _copyData;
       this.$emit("editItem", this.data.id, _copyData);
-      this.$emit("saveItem", this.data.id, _copyData);
+      console.log("save👉",save)
+      if(save)this.$emit("saveItem", this.data.id, _copyData);
     },
     // editTitle(){
     // 	this.$refs.editNameDialogRef.open(this.data.jsonData.anotherName?this.data.jsonData.anotherName:this.data.jsonData.name)
@@ -641,6 +644,7 @@ export default {
       });
     },
     editEcharts() {
+      console.log("生成图表")
       this.loading = true;
       this.loadNum = 1;
       this.openItem = false;
@@ -651,6 +655,18 @@ export default {
             if (res.length <= 0) {
               this.loadNum = 2;
               this.loading = false;
+              let _copyData = JSON.parse(JSON.stringify(this.data));
+            _copyData.json_data = JSON.stringify(_copyData.jsonData);
+            if (this.historyResult.length == 0) {
+              this.historyResult.push(_copyData.jsonData);
+            } else {
+              this.historyResult.splice(
+                this.showIndex + 1,
+                0,
+                _copyData.jsonData
+              );
+            }
+            this.changeShowIndex(1);
               return this.$message.error("在正文中未找到表格数据");
             }
 
@@ -708,6 +724,18 @@ export default {
             console.log(e);
             this.loadNum = 2;
             this.loading = false;
+            let _copyData = JSON.parse(JSON.stringify(this.data));
+            _copyData.json_data = JSON.stringify(_copyData.jsonData);
+            if (this.historyResult.length == 0) {
+              this.historyResult.push(_copyData.jsonData);
+            } else {
+              this.historyResult.splice(
+                this.showIndex + 1,
+                0,
+                _copyData.jsonData
+              );
+            }
+            this.changeShowIndex(1);
             this.$message.error("生成图表失败");
           }
         });
@@ -719,6 +747,18 @@ export default {
             if (res.length <= 0) {
               this.loadNum = 2;
               this.loading = false;
+              let _copyData = JSON.parse(JSON.stringify(this.data));
+            _copyData.json_data = JSON.stringify(_copyData.jsonData);
+            if (this.historyResult.length == 0) {
+              this.historyResult.push(_copyData.jsonData);
+            } else {
+              this.historyResult.splice(
+                this.showIndex + 1,
+                0,
+                _copyData.jsonData
+              );
+            }
+            this.changeShowIndex(1);
               return this.$message.error("在正文中未找到表格数据");
             }
 
@@ -779,6 +819,18 @@ export default {
             console.log(e);
             this.loadNum = 2;
             this.loading = false;
+            let _copyData = JSON.parse(JSON.stringify(this.data));
+            _copyData.json_data = JSON.stringify(_copyData.jsonData);
+            if (this.historyResult.length == 0) {
+              this.historyResult.push(_copyData.jsonData);
+            } else {
+              this.historyResult.splice(
+                this.showIndex + 1,
+                0,
+                _copyData.jsonData
+              );
+            }
+            this.changeShowIndex(1);
             this.$message.error("生成图表失败");
           }
         });
@@ -789,6 +841,18 @@ export default {
             if (res.length <= 0) {
               this.loadNum = 2;
               this.loading = false;
+              let _copyData = JSON.parse(JSON.stringify(this.data));
+            _copyData.json_data = JSON.stringify(_copyData.jsonData);
+            if (this.historyResult.length == 0) {
+              this.historyResult.push(_copyData.jsonData);
+            } else {
+              this.historyResult.splice(
+                this.showIndex + 1,
+                0,
+                _copyData.jsonData
+              );
+            }
+            this.changeShowIndex(1);
               return this.$message.error("在正文中未找到表格数据");
             }
 
@@ -888,6 +952,18 @@ export default {
             console.log(e);
             this.loadNum = 2;
             this.loading = false;
+            let _copyData = JSON.parse(JSON.stringify(this.data));
+            _copyData.json_data = JSON.stringify(_copyData.jsonData);
+            if (this.historyResult.length == 0) {
+              this.historyResult.push(_copyData.jsonData);
+            } else {
+              this.historyResult.splice(
+                this.showIndex + 1,
+                0,
+                _copyData.jsonData
+              );
+            }
+            this.changeShowIndex(1);
             this.$message.error("生成图表失败");
           }
         });
@@ -896,9 +972,6 @@ export default {
         this.loadNum = 2;
         return this.$message.error("该模板不是图表模板");
       }
-      this.loadNum = 2;
-      this.loading = false;
-      return this.$message.error("生成图表失败");
     },
     calculateTopValues(len, minTop = -80, maxTop = 70, maxStep = 40) {
       const length = len;

+ 203 - 160
src/components/pages/classroomObservation/components/analysisSpecialItem.vue

@@ -1,10 +1,14 @@
 <template>
-  <div class="analysisItem" ref="analysisItemRef" :style="
+  <div
+    class="analysisItem"
+    ref="analysisItemRef"
+    :style="
       `top:${moveTop}px;transition:${isDragging ? '0' : '.3s'}s;${
         isDragging ? 'z-index:999' : ''
       }`
     "
-		@mousedown="moveDown($event)">
+    @mousedown="moveDown($event)"
+  >
     <div class="ai-header">
       <div class="ai-h-left" @click.stop="changeOpenItem(!openItem)">
         <span
@@ -184,7 +188,10 @@
       <!-- 光谱图 -->
       <echartsSpectrogram
         style="max-height: 200px;max-width: 90%;width: 90%;height: 200px;margin: auto;margin-bottom: 50px;"
-        v-if="data.jsonData.spectrogramData && data.jsonData.mId == 'bfe844b1-7a45-11ef-9b30-005056b86db5'"
+        v-if="
+          data.jsonData.spectrogramData &&
+            data.jsonData.mId == 'bfe844b1-7a45-11ef-9b30-005056b86db5'
+        "
         :data="data.jsonData.spectrogramData"
       />
 
@@ -197,7 +204,11 @@
         "
       />
 
-			<echartsRTCH v-if="data.jsonData.RT && data.jsonData.CH" :data="{RT:data.jsonData.RT,CH:data.jsonData.CH}" style="width: 100%;height: 350px;display: flex;justify-content: center;margin-bottom: 20px;"/>
+      <echartsRTCH
+        v-if="data.jsonData.RT && data.jsonData.CH"
+        :data="{ RT: data.jsonData.RT, CH: data.jsonData.CH }"
+        style="width: 100%;height: 350px;display: flex;justify-content: center;margin-bottom: 20px;"
+      />
       <!-- <div class="rtCh" v-if="data.jsonData.RT && data.jsonData.CH">
         <img
           :src="
@@ -227,14 +238,14 @@ import { v4 as uuidv4 } from "uuid";
 import echartsSpectrogram from "./echartsSpectrogram";
 // import editNameDialog from './editNameDialog.vue'
 import markdownIt from "markdown-it";
-import echartsRTCH from './echartsRTCH'
+import echartsRTCH from "./echartsRTCH";
 export default {
   emits: ["delItem", "editItem", "saveItem"],
   components: {
     mdView,
     eChartTemplate,
     echartsSpectrogram,
-		echartsRTCH
+    echartsRTCH
     // editNameDialog
   },
   props: {
@@ -272,7 +283,7 @@ export default {
       type: Boolean,
       default: true
     },
-		isDrag: {
+    isDrag: {
       type: Boolean,
       default: false
     },
@@ -319,7 +330,7 @@ export default {
           }
         ]
       },
-			startY: 0,
+      startY: 0,
       startTop: 0,
       moveTop: 0,
       isDragging: false,
@@ -349,7 +360,7 @@ export default {
   },
   methods: {
     changeOpenItem(newValue) {
-			if(this.isDrag)return;
+      if (this.isDrag) return;
       if (this.loading == true && this.loadNum != 0)
         return this.$message("请稍后...");
       this.loadNum = 0;
@@ -459,7 +470,7 @@ export default {
     },
     getData(type = 0) {
       if (
-        this.data.jsonData.mId == 'bfe844b1-7a45-11ef-9b30-005056b86db5' &&
+        this.data.jsonData.mId == "bfe844b1-7a45-11ef-9b30-005056b86db5" &&
         type == 1
       ) {
         return this.getSpectrogram("", type);
@@ -503,7 +514,7 @@ export default {
         ) {
           this.getTeachingModeData(_result, type);
         } else if (
-          this.data.jsonData.mId == 'bfe844b1-7a45-11ef-9b30-005056b86db5'
+          this.data.jsonData.mId == "bfe844b1-7a45-11ef-9b30-005056b86db5"
         ) {
           return this.getSpectrogram(_result, type);
         } else {
@@ -513,6 +524,14 @@ export default {
         console.log(error);
         this.loadNum = 2;
         this.loading = false;
+        let _copyData = JSON.parse(JSON.stringify(this.data));
+        _copyData.json_data = JSON.stringify(_copyData.jsonData);
+        if (this.historyResult.length == 0) {
+          this.historyResult.push(_copyData.jsonData);
+        } else {
+          this.historyResult.splice(this.showIndex + 1, 0, _copyData.jsonData);
+        }
+        this.changeShowIndex(1);
         return this.$message.error("生成分析失败");
       }
     },
@@ -558,14 +577,14 @@ export default {
           trigger: "item",
           formatter: "{a} <br/>{b}: {d}%",
           textStyle: {
-            color: '#000000'
+            color: "#000000"
           }
         },
         legend: {
           top: "5%",
           left: "center",
           textStyle: {
-            color: '#000000'
+            color: "#000000"
           }
         },
         series: [
@@ -575,13 +594,13 @@ export default {
             radius: ["40%", "70%"],
             label: {
               formatter: "{b}: {d}%",
-              color: '#000000'
+              color: "#000000"
             },
             emphasis: {
               label: {
                 show: true,
                 formatter: "{b}: {d}%",
-                color: '#000000'
+                color: "#000000"
               },
               itemStyle: {
                 shadowBlur: 10,
@@ -594,7 +613,6 @@ export default {
         ]
       };
 
-
       let _copyData = JSON.parse(JSON.stringify(this.data));
       _copyData.jsonData.eChartData = _option;
       _copyData.json_data = JSON.stringify(_copyData.jsonData);
@@ -672,8 +690,7 @@ ${JSON.stringify(_dataList)}
             step: "start",
             data: _result,
             type: "line",
-            lineStyle: {
-            }
+            lineStyle: {}
           },
           {
             name: "对角线",
@@ -683,7 +700,7 @@ ${JSON.stringify(_dataList)}
               [_maxValue, _maxValue]
             ],
             lineStyle: {
-              type: "dashed",
+              type: "dashed"
             },
             markLine: {
               symbol: ["none", "none"]
@@ -800,18 +817,18 @@ CH:${_CH}
       // };
 
       let parm = {
-          id:"f8e1ebb2-2e0d-11ef-8bf4-12e77c4cb76b",
-          message: _msg,
-          session_name: uuidv4(),
-          userId: this.userId,
-          file_ids: this.fileId ? [this.fileId] : [],
-          model: "gpt-4o-2024-11-20",
-          sound_url:"",
-          temperature:0.2,
-          top_p:1,
-          max_completion_tokens:4096,
-          stream:false,
-          uid:uuidv4()
+        id: "f8e1ebb2-2e0d-11ef-8bf4-12e77c4cb76b",
+        message: _msg,
+        session_name: uuidv4(),
+        userId: this.userId,
+        file_ids: this.fileId ? [this.fileId] : [],
+        model: "gpt-4o-2024-11-20",
+        sound_url: "",
+        temperature: 0.2,
+        top_p: 1,
+        max_completion_tokens: 4096,
+        stream: false,
+        uid: uuidv4()
       };
 
       this.ajax
@@ -832,12 +849,11 @@ CH:${_CH}
               _copyData.jsonData
             );
           }
-          this.changeShowIndex(1);
+          this.changeShowIndex(1, false);
           this.loading = false;
-					this.$nextTick(()=>{
-
-						this.editEcharts(true);
-						})
+          this.$nextTick(() => {
+            this.editEcharts(true);
+          });
         })
         .catch(err => {
           this.loadNum = 2;
@@ -858,7 +874,7 @@ CH:${_CH}
           console.log("取消");
         });
     },
-    changeShowIndex(value) {
+    changeShowIndex(value, save = true) {
       if (this.historyResult.length == 0) return;
       if (value == -1) {
         if (this.showIndex > 0) this.showIndex--;
@@ -871,10 +887,11 @@ CH:${_CH}
       _copyData.json_data = JSON.stringify(_copyData.jsonData);
       _oldCopyDate.json_data = JSON.stringify(_oldCopyDate.jsonData);
       // 一样就不用更新了
-      if (JSON.stringify(_copyData) == JSON.stringify(_oldCopyDate)) return;
+      // if (JSON.stringify(_copyData) == JSON.stringify(_oldCopyDate)) return;
       // this.data = _copyData;
       this.$emit("editItem", this.data.id, _copyData);
-      this.$emit("saveItem", this.data.id, _copyData);
+      console.log("save👉", save);
+      if (save) this.$emit("saveItem", this.data.id, _copyData);
     },
     convertToSeconds(time) {
       let parts = time.split(":");
@@ -916,108 +933,124 @@ CH:${_CH}
     },
     getSpectrogram(_dataList, type = 1) {
       if (type === 0) {
-				try {
-					this.loading = true;
-				this.openItem = false;
-        return this.getContentTable().then(res => {
-          if (res.length <= 0) {
-            this.loadNum = 2;
-            this.loading = false;
-            return this.$message.error("在正文中未找到表格数据");
-          }
-
-          let _tableData = res;
-
-					let _delIndex = _tableData.findIndex(i=>i.includes(this.converter("时间点")))
-          console.log("👉===",_dataList,_tableData)
-					_tableData = _tableData.slice(_delIndex+1)
-          console.log()
-          let _result = [];
-          let identity = "老师"; //0:老师 1:学生
-          let startTime = "";
-          let endTime = "";
-          let sumTime = 0;
-          let upTime = '00:00:00';
-
-          _dataList.forEach((item, index) => {
-            if (index == 0) {
-              //第一个
-              identity = item.role;
-              startTime = item.startTime;
-              endTime = item.endTime;
-              sumTime = (this.convertToSeconds(item.endTime) - this.convertToSeconds(upTime));
-              upTime = item.endTime
-              // console.log(item.endTime,item.startTime,(this.convertToSeconds(item.endTime) - this.convertToSeconds(item.startTime)))
-              return;
-            }
-            if (item.role == identity) {
-              //没更换角色
-              sumTime += (this.convertToSeconds(item.endTime) - this.convertToSeconds(upTime));
-              endTime = item.endTime;
-              upTime = item.endTime
-              // console.log(item.endTime,item.startTime,(this.convertToSeconds(item.endTime) - this.convertToSeconds(item.startTime)))
-            } else {
-              //更换角色了
-              _result.push({
-                startTime: startTime,
-                endTime: endTime,
-                identity: identity,
-                sumTime: sumTime
-              });
-              identity = item.role;
-              startTime = item.startTime;
-              endTime = item.endTime;
-              sumTime = (this.convertToSeconds(item.endTime) - this.convertToSeconds(upTime));
-              upTime = item.endTime
-              // console.log(item.endTime,item.startTime,(this.convertToSeconds(item.endTime) - this.convertToSeconds(item.startTime)))
-            }
-            if(index==_dataList.length-1){
-              console.log("👉???",this.convertToSeconds(item.endTime))
+        try {
+          this.loading = true;
+          this.openItem = false;
+          return this.getContentTable().then(res => {
+            if (res.length <= 0) {
+              this.loadNum = 2;
+              this.loading = false;
+              return this.$message.error("在正文中未找到表格数据");
             }
-          });
 
-           console.log(_result.reduce((pre,cur)=>{return pre+=cur.sumTime},0))
-          console.log("→xxx",_result)
-          let breakpoint = [];
+            let _tableData = res;
 
-					breakpoint = _tableData.map(i=>this.convertToSeconds(i[0]))
+            let _delIndex = _tableData.findIndex(i =>
+              i.includes(this.converter("时间点"))
+            );
+            console.log("👉===", _dataList, _tableData);
+            _tableData = _tableData.slice(_delIndex + 1);
+            console.log();
+            let _result = [];
+            let identity = "老师"; //0:老师 1:学生
+            let startTime = "";
+            let endTime = "";
+            let sumTime = 0;
+            let upTime = "00:00:00";
+
+            _dataList.forEach((item, index) => {
+              if (index == 0) {
+                //第一个
+                identity = item.role;
+                startTime = item.startTime;
+                endTime = item.endTime;
+                sumTime =
+                  this.convertToSeconds(item.endTime) -
+                  this.convertToSeconds(upTime);
+                upTime = item.endTime;
+                // console.log(item.endTime,item.startTime,(this.convertToSeconds(item.endTime) - this.convertToSeconds(item.startTime)))
+                return;
+              }
+              if (item.role == identity) {
+                //没更换角色
+                sumTime +=
+                  this.convertToSeconds(item.endTime) -
+                  this.convertToSeconds(upTime);
+                endTime = item.endTime;
+                upTime = item.endTime;
+                // console.log(item.endTime,item.startTime,(this.convertToSeconds(item.endTime) - this.convertToSeconds(item.startTime)))
+              } else {
+                //更换角色了
+                _result.push({
+                  startTime: startTime,
+                  endTime: endTime,
+                  identity: identity,
+                  sumTime: sumTime
+                });
+                identity = item.role;
+                startTime = item.startTime;
+                endTime = item.endTime;
+                sumTime =
+                  this.convertToSeconds(item.endTime) -
+                  this.convertToSeconds(upTime);
+                upTime = item.endTime;
+                // console.log(item.endTime,item.startTime,(this.convertToSeconds(item.endTime) - this.convertToSeconds(item.startTime)))
+              }
+              if (index == _dataList.length - 1) {
+                console.log("👉???", this.convertToSeconds(item.endTime));
+              }
+            });
 
-          _result = _result.filter(
-            i =>
-              i.identity == this.converter("老师") ||
-              i.identity == this.converter("学生")
-          );
-          // let
-          let _data = {
-            data: [],
-            breakpoint: []
-          };
+            console.log(
+              _result.reduce((pre, cur) => {
+                return (pre += cur.sumTime);
+              }, 0)
+            );
+            console.log("→xxx", _result);
+            let breakpoint = [];
 
-          _data.data = _result.map(i => ({role:i.identity,type:i.identity==this.converter("老师")?0:1,value:i.sumTime}));
-          _data.breakpoint = breakpoint;
-          let _copyData = JSON.parse(JSON.stringify(this.data));
-          _copyData.jsonData.spectrogramData = _data;
-          _copyData.json_data = JSON.stringify(_copyData.jsonData);
-          if (this.historyResult.length == 0) {
-            this.historyResult.push(_copyData.jsonData);
-          } else {
-            this.historyResult.splice(
-              this.showIndex + 1,
-              0,
-              _copyData.jsonData
+            breakpoint = _tableData.map(i => this.convertToSeconds(i[0]));
+
+            _result = _result.filter(
+              i =>
+                i.identity == this.converter("老师") ||
+                i.identity == this.converter("学生")
             );
-          }
-          this.changeShowIndex(1);
-          this.loading = false;
+            // let
+            let _data = {
+              data: [],
+              breakpoint: []
+            };
+
+            _data.data = _result.map(i => ({
+              role: i.identity,
+              type: i.identity == this.converter("老师") ? 0 : 1,
+              value: i.sumTime
+            }));
+            _data.breakpoint = breakpoint;
+            let _copyData = JSON.parse(JSON.stringify(this.data));
+            _copyData.jsonData.spectrogramData = _data;
+            _copyData.json_data = JSON.stringify(_copyData.jsonData);
+            if (this.historyResult.length == 0) {
+              this.historyResult.push(_copyData.jsonData);
+            } else {
+              this.historyResult.splice(
+                this.showIndex + 1,
+                0,
+                _copyData.jsonData
+              );
+            }
+            this.changeShowIndex(1);
+            this.loading = false;
 
-          // console.log(_dataList);
-          // console.log(_tableData);
-        });
-				} catch (e) {
-					this.loadNum = 2;
+            // console.log(_dataList);
+            // console.log(_tableData);
+          });
+        } catch (e) {
+          this.loadNum = 2;
           this.loading = false;
           return this.$message.error("数据格式错误");
-				}
+        }
       }
       this.$nextTick(() => {
         this.loading = true;
@@ -1060,13 +1093,13 @@ CH:${_CH}
           userId: this.userId,
           file_ids: this.fileId ? [this.fileId] : [],
           model: "gpt-4o-2024-11-20",
-          sound_url:"",
-          temperature:0.2,
-          top_p:1,
-          max_completion_tokens:4096,
-          stream:false,
-          uid:uuidv4()
-					// model: "gpt-4o-mini"
+          sound_url: "",
+          temperature: 0.2,
+          top_p: 1,
+          max_completion_tokens: 4096,
+          stream: false,
+          uid: uuidv4()
+          // model: "gpt-4o-mini"
         };
 
         // 👇
@@ -1130,10 +1163,9 @@ CH:${_CH}
             }
             this.changeShowIndex(1);
             this.loading = false;
-						this.$nextTick(()=>{
-
-							this.editEcharts(true);
-						})
+            this.$nextTick(() => {
+              this.editEcharts(true);
+            });
           })
           .catch(err => {
             this.loadNum = 2;
@@ -1177,8 +1209,8 @@ CH:${_CH}
         resolve(_contentTableList);
       });
     },
-		moveDown(e) {
-			if(!this.isDrag)return;
+    moveDown(e) {
+      if (!this.isDrag) return;
       this.isDragging = true;
       this.startY = e.clientY;
       this.$nextTick(() => {
@@ -1187,15 +1219,23 @@ CH:${_CH}
             ...this.$parent.$parent.$refs[`analysis_${i.value}`][0].$refs
               .dragBoxRefTop
           );
-					if(this.$parent.$parent.$refs[`analysis_${i.value}`][0].$refs.dragBoxRefBottom.length>0){
-						this.dragBoxList.push(
-            ...this.$parent.$parent.$refs[`analysis_${i.value}`][0].$refs
+          if (
+            this.$parent.$parent.$refs[`analysis_${i.value}`][0].$refs
+              .dragBoxRefBottom.length > 0
+          ) {
+            this.dragBoxList.push(
+              ...this.$parent.$parent.$refs[`analysis_${i.value}`][0].$refs
+                .dragBoxRefBottom
+            );
+          } else if (
+            this.$parent.$parent.$refs[`analysis_${i.value}`][0].$refs
               .dragBoxRefBottom
-          );
-					}else if(this.$parent.$parent.$refs[`analysis_${i.value}`][0].$refs.dragBoxRefBottom){
-						this.dragBoxList.push(this.$parent.$parent.$refs[`analysis_${i.value}`][0].$refs.dragBoxRefBottom);
-					}
-
+          ) {
+            this.dragBoxList.push(
+              this.$parent.$parent.$refs[`analysis_${i.value}`][0].$refs
+                .dragBoxRefBottom
+            );
+          }
         });
         // 禁用页面文本选择
         document.body.style.userSelect = "none";
@@ -1259,18 +1299,21 @@ CH:${_CH}
         if (!this.enterDrag) {
           this.moveTop = 0;
         } else {
-					this.moveTop = 0;
-					let moveData = this.enterDrag.getAttribute('type');
-					this.$emit("moveAnalysis",{form:`${this.data.Type}_${this.index}_${this.data.tIndex}`,to:moveData})
+          this.moveTop = 0;
+          let moveData = this.enterDrag.getAttribute("type");
+          this.$emit("moveAnalysis", {
+            form: `${this.data.Type}_${this.index}_${this.data.tIndex}`,
+            to: moveData
+          });
         }
         // 移除全局的鼠标移动和释放事件
         document.removeEventListener("mousemove", this.onMouseMove);
         document.removeEventListener("mouseup", this.stopDragging);
       } catch (error) {
-				this.moveTop = 0;
-				document.removeEventListener("mousemove", this.onMouseMove);
+        this.moveTop = 0;
+        document.removeEventListener("mousemove", this.onMouseMove);
         document.removeEventListener("mouseup", this.stopDragging);
-			}
+      }
     }
   },
   mounted() {
@@ -1294,7 +1337,7 @@ CH:${_CH}
   border: 1px solid #e7e7e7;
   border-radius: 4px;
   transition: 0.3s;
-	position: relative;
+  position: relative;
 }
 
 .analysisItem:hover {

+ 5 - 3
src/components/pages/classroomObservation/components/resourceLibraryDialog.vue

@@ -4,7 +4,7 @@
 			<div style="width: 100%;height: 100%;">
 					<fileBox type="2" ref="fileBoxRef" @addFile="addFile"></fileBox>
 			</div>
-	</el-dialog>    
+	</el-dialog>
 </template>
 
 <script>
@@ -38,7 +38,9 @@ export default {
 	methods: {
 		open(){
 			this.show = true;
-			this.$refs.fileBoxRef.getData();
+			this.$nextTick(()=>{
+        this.$refs.fileBoxRef.getData();
+      })
 		},
 		handleClose(done) {
 			this.close();
@@ -55,7 +57,7 @@ export default {
 					this.close();
 			}
 
-	}   
+	}
 };
 </script>
 

Diferenças do arquivo suprimidas por serem muito extensas
+ 889 - 70
src/components/pages/classroomObservation/dialog/batchCreationClassDialog.vue


+ 142 - 49
src/components/pages/classroomObservation/dialog/editBaseMessageDialog.vue

@@ -211,7 +211,7 @@
   					>
 						<div class="m_m_box">
 							<el-button-group style="width: 100%;display: flex;justify-content: center;">
-							  <el-button size="small" @click="localUploadVideo()">本地上传</el-button>
+							  <el-button size="small" @click="addVideo()">本地上传</el-button>
 								<el-button size="small" @click="resourceUploadVideo()">资源库上传</el-button>
 							</el-button-group>
 						</div>
@@ -279,7 +279,7 @@
 									 v-loading="uploadNephogramLoading"
 									v-if="(data.imageList.NephogramList&&data.imageList.NephogramList.length>0)"
 								>
-									<wordcloudEChart :data="imageList.NephogramList[0]"/>
+									<wordcloudEChart :data="data.imageList.NephogramList[0]"/>
 									<span @click.stop="delNephogram('NephogramList')"></span>
 								</div>
 							<div
@@ -340,6 +340,7 @@ export default {
 		return {
 			loading: false,
 			show: false,
+      userId: this.$route.query["userid"],
       uploadVideoLoading:false,
       uploadImageLoading:false,
       uploadNephogramLoading:false,
@@ -458,11 +459,29 @@ export default {
     },
     //资源库上传
     resourceUploadVideo(){
-      this.$message.info("资源库上传")
+      this.$refs.resourceLibraryDialogRef.open();
     },
     //添加视频
     addVideo(){
-      this.$message.info("添加视频")
+      let input = document.createElement("input");
+			input.type = "file";
+			input.accept = "video/*";
+			input.click();
+			input.onchange = () => {
+				this.progressData.uploadVideo = true;
+				// this.uploadVideoLoading = true;
+				this.progressData.stop = false;
+				this.progressData.status = "";
+				this.progressData.value = 0;
+				let file = input.files[0];
+				this.uploadFileObj = file;
+				this.$nextTick(()=>{
+					this.$refs.uploadFileRef.awsupload({
+					file:file,
+					folderName:this.editId
+				})
+				})
+			}
     },
     //删除词云图
     delNephogram(){
@@ -470,8 +489,20 @@ export default {
     },
     //生成词云图
     addNephogram(){
-      this.$message.info("生成词云图")
+      // this.$message.info("生成词云图")
     },
+    delVideo(key){
+			this.$confirm("确定删除该视频吗?", "提示", {
+				confirmButtonText: "确定",
+				cancelButtonText: "取消",
+				type: "warning",
+			}).then(() => {
+				this.data.imageList.videoList = [];
+        this.$message.success("删除成功")
+			}).catch(e=>{
+				console.log("取消删除")
+			});
+		},
     //资源库添加文件
     resourceLibraryDialogAddFile(file){
       let _file = file[0];
@@ -481,17 +512,13 @@ export default {
 			}
       console.log(_file)
       this.$message.info("上传文件")
-			// this.$emit('saveVideo',{
-			// 		name: _file.name,
-			// 		status: "success",
-			// 		uid: _file.id,
-			// 		url: _file.file,
-			// })
-			// this.$message.success("上传成功")
-
-			// this.$confirm("是否提取视频音频并上传?","提示").then(()=>{
-			// 	this.getVideoVoice({url:_file.file});
-			// })
+      this.data.imageList.videoList = [{
+        name: _file.name,
+				status: "success",
+				uid: _file.id,
+				url: _file.file,
+      }]
+			this.$message.success("上传成功")
     },
     videoProgressUpdate(data){
 			if(data.status=="processing"){
@@ -538,42 +565,108 @@ export default {
 			let {data} = res;
 			this.$refs.uploadFileRef.file = null;
       console.log(data)
-			// this.$emit('saveVideo',{
-			// 		name: data.Key,
-			// 		status: "success",
-			// 		uid: "qgt",
-			// 		url: data.Location,
-			// })
-			// this.$message.success("上传成功");
-			// // this.uploadFileObj = null;
-			// this.$confirm("是否提取视频音频并上传?","提示").then(()=>{
-			// 	this.getVideoVoice({file:this.uploadFileObj});
-			// 	this.uploadFileObj = null;
-			// }).catch(err=>{
-			// 	this.uploadFileObj = null;
-			// })
+      this.data.imageList.videoList = [{
+        name: data.Key,
+					status: "success",
+					uid: this.userId,
+					url: data.Location,
+      }]
+      this.$message.success("上传成功");
 		},
     //上传课堂图片
     addImage2(){
-      this.$message.info("上传课堂图片")
-			// let input = document.createElement("input");
-			// input.type = "file";
-			// input.accept = "image/*";
-			// input.multiple = true;
-			// input.click();
-
-
-			// input.onchange = () => {
-			// 	this.uploadImageLoading = true;
-      //   let promise =[];
-      //   for(let i=0;i<input.files.length;i++){
-      //     promise.push(this.uploadFile(input.files[i]));
-      //   }
-      //   Promise.all(promise).then(res=>{
-      //     this.$emit("saveImage2",res);
-      //     this.uploadImageLoading = false;
-      //   })
-			// };
+			let input = document.createElement("input");
+			input.type = "file";
+			input.accept = "image/*";
+			input.multiple = true;
+			input.click();
+
+
+			input.onchange = () => {
+				this.uploadImageLoading = true;
+        let promise =[];
+        for(let i=0;i<input.files.length;i++){
+          promise.push(this.uploadFile(input.files[i]));
+        }
+        Promise.all(promise).then(res=>{
+          this.saveImage2(res)
+          // this.$emit("saveImage2",res);
+          this.uploadImageLoading = false;
+        })
+			};
+    },
+    saveImage2(dataList){
+      for(let i=0;i<dataList.length;i++){
+        if (this.data.imageList.fileList1.length == 0) {
+          this.data.imageList.fileList1.push(dataList[i]);
+        } else if (this.data.imageList.fileList2.length == 0) {
+          this.data.imageList.fileList2.push(dataList[i]);
+        } else if (this.data.imageList.fileList3.length == 0) {
+          this.data.imageList.fileList3.push(dataList[i]);
+        } else {
+          this.$message.error("最多只能上传3张图片");
+          break;
+        }
+      }
+    },
+    delImage(key){
+      this.$confirm("确定删除该图片吗?", "提示", {
+				confirmButtonText: "确定",
+				cancelButtonText: "取消",
+				type: "warning",
+			}).then(() => {
+        this.data.imageList[key] = [];
+				// this.$emit("delImage", key);
+			});
+    },
+    uploadFile(file){
+      return new Promise(resolve=>{
+        var credentials = {
+					accessKeyId: "AKIATLPEDU37QV5CHLMH",
+					secretAccessKey: "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",
+				}; //秘钥形式的登录上传
+				window.AWS.config.update(credentials);
+				window.AWS.config.region = "cn-northwest-1"; //设置区域
+
+				var bucket = new window.AWS.S3({ params: { Bucket: "ccrb" } }); //选择桶
+				var _this = this;
+        if (file) {
+					var params = {
+						Key:
+							file.name.split(".")[0] +
+							new Date().getTime() +
+							"." +
+							file.name.split(".")[file.name.split(".").length - 1],
+						ContentType: file.type,
+						Body: file,
+						"Access-Control-Allow-Credentials": "*",
+						ACL: "public-read",
+					}; //key可以设置为桶的相抵路径,Body为文件, ACL最好要设置
+					var options = {
+						partSize: 2048 * 1024 * 1024,
+						queueSize: 2,
+						leavePartsOnError: true,
+					};
+					bucket
+						.upload(params, options)
+						.on("httpUploadProgress", function (evt) {
+							//这里可以写进度条
+							// console.log("Uploaded : " + parseInt((evt.loaded * 80) / evt.total) + '%');
+						})
+						.send(function (err, data) {
+							if (err) {
+								resolve(0)
+							} else {
+                resolve({
+									name: data.key,
+									status: "success",
+									uid: "1",
+									url: data.Location,
+								})
+							}
+						});
+				}
+      })
     },
     previewVideo(url){
       this.$refs.previewVideoDialogRef.open(url)

+ 163 - 25
src/components/pages/classroomObservation/dialog/uploadFileToCreateClassDialog.vue

@@ -24,17 +24,17 @@
               <div class="fl_item" v-for="item in fileList" :key="item.index">
                 <div class="fl_i_left">
                   <img
-                   v-if="item.type =='text/plain'"
+                   v-if="['text/plain'].includes(item.type)"
                     src="../../../../assets/icon/classroomObservation/textFile_icon.svg"
                   />
 
                   <img
-                   v-if="item.type =='audio/wav'"
+                   v-if="['audio/wav','audio/x-m4a','audio/mpeg'].includes(item.type)"
                     src="../../../../assets/icon/classroomObservation/audio_file.svg"
                   />
 
                   <img
-                   v-if="item.type =='video/mp4'"
+                   v-if="['video/mp4'].includes(item.type)"
                     src="../../../../assets/icon/classroomObservation/videoFile_icon.svg"
                   />
                 </div>
@@ -75,12 +75,16 @@
                   />
                 </div>
               </div>
+              <div class="fl_item addFile" v-if="fileList.length > 0" @click="addFile()">
+              <span>继续添加文件</span>
             </div>
-            <div class="b_m_l_noFile" v-else @click="addFile()">
+            </div>
+
+            <div class="b_m_l_noFile" v-if="fileList.length==0" @click="addFile()">
               <img
                 src="../../../../assets/icon/classroomObservation/file_processing.svg"
               />
-              <span>文件格式支持:mp4、wav、txt 文件</span>
+              <span>文件格式支持:mp4、mp3、wav、m4a、txt 文件</span>
             </div>
           </div>
           <div class="b_m_right">
@@ -207,12 +211,13 @@ export default {
       let _name = this.fileList.find(i => i.index == res.index).file.name;
       let size = this.fileList.find(i => i.index == res.index).file.size;
       let _type = this.fileList.find(i => i.index == res.index).type;
-      console.log(data);
       this.fileList.find(i => i.index == res.index).successData = {
         name: _name,
         url: data.Location,
         type: _type,
-        size: size
+        size: size,
+        duration: this.fileList.find(i => i.index == res.index).duration,
+        fileObj:this.fileList.find(i => i.index == res.index).fileObj,
       };
       this.fileList.find(i => i.index == res.index).status = "success";
       let uploadingFile = this.fileList.find(file => file.status === "wait");
@@ -223,50 +228,90 @@ export default {
             file: uploadingFile.file
           });
         });
+      }else if(this.fileList.find(file => file.status === "uploading")){
+        console.log("还有在上传的文件")
       } else {
         console.log("上传完成");
       }
     },
-    addFile() {
+    async addFile() {
       let input = document.createElement("input");
       input.type = "file";
-      input.accept = "video/mp4, audio/wav, text/plain";
+      input.accept = "video/mp4, audio/wav, audio/x-m4a, audio/mpeg, text/plain";
       input.multiple = true; // 支持多文件上传
       input.style.display = "none";
 
       input.click();
 
-      input.addEventListener("change", e => {
+      input.addEventListener("change", async e => {
         let files = e.target.files;
+        console.log(files)
         for (let i = 0; i < files.length; i++) {
+
           if (
-            ["video/mp4", "audio/wav", "text/plain"].includes(files[i].type)
+            ["video/mp4", "audio/wav", "audio/x-m4a", "text/plain","audio/mpeg"].includes(files[i].type)
           ) {
+            let _file = files[i];
+            if(_file.type==='audio/wav'){
+              _file = new File([_file], _file.name.toLowerCase(), {type: _file.type});
+            }
             this.fileList.push({
-              file: files[i],
+              file: _file,
               index: uuidv4(),
               successData: null,
-              name: files[i].name,
-              type: files[i].type,
+              name: _file.name,
+              fileObj:_file,
+              type: _file.type,
               progress: { status: "", percent: 0, key: "", uploadid: "" },
               status: "wait"
             });
           } else {
-            this.$message.info("文件格式不支持,仅支持mp4、wav、txt文件。");
+            this.$message.info("文件格式不支持,仅支持mp4、mp3、wav、m4a、txt文件。");
           }
         }
         if (!this.fileList.some(i => i.status === "uploading")) {
-          let uploadingFile = this.fileList.find(
-            file => file.status === "wait"
-          );
-          if (uploadingFile) {
-            this.fileList.find(file => file.status === "wait").status =
-              "uploading";
-            this.$nextTick(() => {
-              this.$refs[`uploadFileRef_${uploadingFile.index}`][0].awsupload({
-                file: uploadingFile.file
+          let uploadingFile = this.fileList.filter(file => file.status === "wait").slice(0, 3);
+          if(uploadingFile.length>0){
+            uploadingFile.forEach(i=>{
+              this.fileList.find(file => i.index===file.index).status = "uploading";
+              this.$nextTick(() => {
+              this.$refs[`uploadFileRef_${i.index}`][0].awsupload({
+                file: i.file
               });
             });
+            })
+          }
+          // let uploadingFile = this.fileList.find(
+          //   file => file.status === "wait"
+          // );
+          // if (uploadingFile) {
+          //   this.fileList.find(file => file.status === "wait").status =
+          //     "uploading";
+          //   this.$nextTick(() => {
+          //     this.$refs[`uploadFileRef_${uploadingFile.index}`][0].awsupload({
+          //       file: uploadingFile.file
+          //     });
+          //   });
+          // }
+        }
+
+        if(this.fileList.length>0){
+          console.log("获取音频时长")
+          for(let i = 0;i<this.fileList.length;i++){
+            console.log(this.fileList[i])
+            if(["audio/wav", "audio/x-m4a","audio/mpeg"].includes(this.fileList[i].type)){
+              try {
+                let _time = await this.getAudioDuration(this.fileList[i].file);
+                this.fileList[i].duration = this.updateRecordedTimeMixin({duration:_time.toFixed(2)});
+                console.log(`音频时长:${this.updateRecordedTimeMixin({duration:_time.toFixed(2)})}`)
+              } catch (error) {
+                console.log(error)
+                continue
+              }
+
+            }else{
+              continue;
+            }
           }
         }
       });
@@ -306,7 +351,86 @@ export default {
       } catch (error) {
         console.log("获取模板失败");
       }
-    }
+    },
+    //获取音频文件的时长
+    async getAudioDuration(file) {
+      // 方法1:使用 <audio> 元素(更快更轻量)
+      const mediaElementMethod = () =>
+        new Promise((resolve, reject) => {
+          const audio = document.createElement("audio");
+          const url = URL.createObjectURL(file);
+
+          audio.preload = "metadata";
+          audio.src = url;
+
+          const cleanup = () => {
+            URL.revokeObjectURL(url);
+            audio.remove();
+          };
+
+          audio.onloadedmetadata = () => {
+            if (isFinite(audio.duration) && audio.duration > 0) {
+              cleanup();
+              resolve(audio.duration);
+            } else {
+              reject(new Error("无法通过元数据获取时长"));
+            }
+          };
+
+          audio.onerror = () => {
+            cleanup();
+            reject(new Error("音频加载错误"));
+          };
+
+          // iOS Safari 兼容处理
+          audio.load();
+          document.body.appendChild(audio);
+        });
+
+      // 方法2:使用 Web Audio API(更精确但更耗资源)
+      const webAudioMethod = () =>
+        new Promise((resolve, reject) => {
+          const reader = new FileReader();
+
+          reader.onload = async () => {
+            try {
+              const audioContext = new (window.AudioContext ||
+                window.webkitAudioContext)();
+              const buffer = await audioContext.decodeAudioData(reader.result);
+              resolve(buffer.duration);
+            } catch (e) {
+              reject(new Error("音频解码失败: " + e.message));
+            }
+          };
+
+          reader.onerror = () => reject(new Error("文件读取失败"));
+          reader.readAsArrayBuffer(file);
+        });
+
+      // 优先尝试快速方法,失败时回退到精确方法
+      try {
+        return await mediaElementMethod();
+      } catch (firstError) {
+        console.warn("快速方法失败:", firstError.message, "尝试精确方法...");
+        try {
+          return await webAudioMethod();
+        } catch (secondError) {
+          throw new Error(`所有方法均失败: ${firstError.message} > ${secondError.message}`);
+        }
+      }
+    },
+    //时间
+    updateRecordedTimeMixin({ duration }) {
+      // 更新currentTime,将秒数转换为时分秒格式
+      let hours = Math.floor(duration / 3600);
+      let minutes = Math.floor((duration % 3600) / 60);
+      let seconds = Math.floor(duration % 60);
+      return `${hours
+        .toString()
+        .padStart(2, "0")}:${minutes
+          .toString()
+          .padStart(2, "0")}:${seconds.toString().padStart(2, "0")}`;
+    },
   }
 };
 </script>
@@ -544,4 +668,18 @@ export default {
 .uploadingProgress {
   background-color: rgba(54, 129, 252, 1);
 }
+
+.addFile{
+  width: 95%;
+  min-height: 45px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  cursor: pointer;
+  margin-top: 10px;
+  box-sizing: border-box;
+  border: dashed  1px #969BA3;
+  border-radius: 10px;
+  margin:0 auto;
+}
 </style>

+ 72 - 52
src/components/pages/classroomObservation/index.vue

@@ -83,10 +83,10 @@
       </div>
       <div class="co-h2-right">
 
-        <!--<div class="co-h2-r-btn" style="background: rgba(54, 129, 252, 1)" @click.stop="batchBtn()">
+        <div class="co-h2-r-btn" style="background: rgba(54, 129, 252, 1)" @click.stop="batchBtn()">
           <span class="co-h2-r-b-icon3"></span>
           <div style="color: #fff;">批量创建</div>
-        </div> -->
+        </div>
         <div
           :class="['co-h2-r-btn', fileId && tid ? '' : 'ca-h2-r-noActive']"
           style="background: rgba(54, 129, 252, 1)"
@@ -206,7 +206,7 @@
       ref="changeCourseNameDialogRef"
       @success="changeCourseSuccess"
     />
-    <batchCreationClassDialog ref="batchCreationClassDialogRef"/>
+    <batchCreationClassDialog ref="batchCreationClassDialogRef" @addNewCourseOption="addNewCourseOption" @changeClass="changeTid"/>
 
     <!-- <addNewCourseDialog
 			:courseList="optionData"
@@ -293,6 +293,7 @@ export default {
   methods: {
     //切换了课堂
     changeTid(newValue) {
+      if(this.tid!=newValue)this.tid = newValue;
       this.$nextTick(async () => {
         this.getFileIdId();
         this.$refs.messageAreaRef.getData();
@@ -388,6 +389,7 @@ export default {
           userid: this.userId,
           template: json
         };
+        console.log("创建新课堂",params);
         this.ajax
           .post(
             "https://gpt4.cocorobo.cn/insert_classroom_observation_template",
@@ -502,6 +504,7 @@ export default {
         tagList.forEach(i => (i.dataList = []));
         let url = `https://beta.cloud.cocorobo.cn/aigpt/#/classroom_observation_board?tid=${this.tid}`;
         const qRCodeSrc = await this.getQrCodeImageSrc(url);
+        dataList.sort((a, b) => a.tIndex - b.tIndex);
         dataList.forEach(i1 => {
           tagList.forEach(i2 => {
             if (i2.value == i1.Type) {
@@ -510,6 +513,9 @@ export default {
           });
         });
 
+
+
+
         let directoryHtml = `<div style="margin-bottom:1in"><div style="text-align:center;font-size:20pt;margin-bottom:0.5in">目录</div>`;
 
         let analysisHtml = ``;
@@ -548,20 +554,20 @@ export default {
               tagHtml += `<p style="font-size:10.5pt;font-style:italic;margin-bottom:-0.7in;color:#6b798e">${i2.jsonData.result}</p>`;
             }
             if (i2.jsonData.eChartData) {
-              tagHtml += `<img src="${await this.getEChartsImageSrc(
+              tagHtml += `<div style="width:100vw;padding:70%;box-sizing: border-box;text-align:center"><img style="margin:auto" src="${await this.getEChartsImageSrc(
                 i2.jsonData.eChartData
-              )}"/>`;
+              )}"/></div>`;
             }
 
             if (i2.jsonData.spectrogramData) {
-              tagHtml += `<img src="${await this.getEChartsSpectrogramImage(
+              tagHtml += `<div style="width:100vw;padding:70%;box-sizing: border-box;text-align:center"><img style="margin:auto" src="${await this.getEChartsSpectrogramImage(
                 i2.jsonData.spectrogramData
-              )}"/>`;
+              )}"/></div>`;
               // console.log()
             }
 
             if (i2.jsonData.CH && i2.jsonData.RT) {
-              tagHtml += `<div style="width:100vw;text-align:center;"><img style="margin:auto" src="${await this.getEChartsechartsRTCHImage(
+              tagHtml += `<div style="width:100vw;text-align:center;padding:70%;box-sizing: border-box;"><img style="margin:auto" src="${await this.getEChartsechartsRTCHImage(
                 {
                   RT: i2.jsonData.RT,
                   CH: i2.jsonData.CH
@@ -569,8 +575,7 @@ export default {
               )}"/></div>`;
             }
 
-            let _content = md.render(i2.jsonData.content);
-
+            let _content = md.render(i2.jsonData.content).replace(/<p>/g, '').replace(/<\/p>/g, '').replace(/<strong>/g, '<span style="font-weight: bold;">').replace(/<\/strong>/g, '</span>');
             tagHtml += `<p style="font-size:10.5pt;margin-bottom:-0.5in">${_content}</p>`;
           }
           tagHtml += "</div>";
@@ -613,6 +618,8 @@ export default {
 				${analysisHtml}
 			</div>
 			`;
+      // return console.log(analysisHtml.replace(/<img.*?>/g, ''))
+
         this.generateDocx(`《${bmData.courseName}》课堂观察报告`, _html);
         this.loading = false;
       } catch (e) {
@@ -640,8 +647,8 @@ export default {
     getEChartsImageSrc(option) {
       return new Promise(resolve => {
         let hiddenDiv = document.createElement("div");
-        hiddenDiv.style.width = "600px";
-        hiddenDiv.style.height = "500px";
+        hiddenDiv.style.width = "400px";
+        hiddenDiv.style.height = "400px";
         hiddenDiv.style.position = "absolute";
         hiddenDiv.style.left = "-9999px"; // 隐藏div
         document.body.appendChild(hiddenDiv);
@@ -656,7 +663,7 @@ export default {
           // 获取图表的图片
           let base64Image = myChart.getDataURL({
             type: "png", // 图片格式
-            pixelRatio: 1, // 图像清晰度
+            pixelRatio: 0.9, // 图像清晰度
             backgroundColor: "#fff" // 背景颜色
           });
 
@@ -735,21 +742,23 @@ export default {
           });
 
           // 绘制红色垂直线(指定位置)
-          ctx.strokeStyle = "red";
-          ctx.lineWidth = 2;
-
-          data.breakpoint.forEach(i => {
-            const breakpointPo = parseFloat(
-              (i / (sum / canvasWidth2)).toFixed(2)
-            );
-            ctx.beginPath();
-            ctx.moveTo(breakpointPo, 10);
-            ctx.lineTo(breakpointPo, canvasHeight - 70);
-            ctx.stroke();
-          });
+          // ctx.strokeStyle = "red";
+          // ctx.lineWidth = 2;
+
+          // data.breakpoint.forEach(i => {
+          //   const breakpointPo = parseFloat(
+          //     (i / (sum / canvasWidth2)).toFixed(2)
+          //   );
+          //   ctx.beginPath();
+          //   ctx.moveTo(breakpointPo, 10);
+          //   ctx.lineTo(breakpointPo, canvasHeight - 70);
+          //   ctx.stroke();
+          // });
 
           let interval = parseFloat((300 / (sum / canvasWidth2)).toFixed(2));
           //绘制竖线
+          let _lastI = 0;
+          //绘制竖线
           for (let i = 0; i < canvasWidth2; i += interval) {
             ctx.beginPath();
             ctx.strokeStyle = "#BFBFBF";
@@ -761,11 +770,23 @@ export default {
             ctx.font = `${fontSize}px serif`;
             if (i == 0) {
               ctx.fillText(`${timeLabel}min`, i + 10, canvasHeight - 40);
-            } else if (i + interval > canvasWidth2) {
+            } else if (i + interval >= canvasWidth2) {
               ctx.fillText(`${timeLabel}min`, i - 20, canvasHeight - 40);
             } else {
               ctx.fillText(`${timeLabel}min`, i - 15, canvasHeight - 40);
             }
+            _lastI = i;
+          }
+          if (canvasWidth2 - _lastI >60) {
+            ctx.beginPath();
+            ctx.strokeStyle = "#BFBFBF";
+            ctx.moveTo(canvasWidth2 + 10, canvasHeight - 70);
+            ctx.lineTo(canvasWidth2 + 10, canvasHeight - 55);
+            ctx.stroke();
+            ctx.fillStyle = "#868686";
+            let timeLabel = (sum / 60).toFixed(0); // 时间标识计算
+            ctx.font = `${fontSize}px serif`;
+            ctx.fillText(`${timeLabel}min`, canvasWidth2 - 20, canvasHeight - 40);
           }
 
           ctx.beginPath();
@@ -799,33 +820,21 @@ export default {
           ctx.imageSmoothingEnabled = false;
           ctx.lineWidth = 1;
           const img = new Image();
-          img.src = require("../../../assets/icon/classroomObservation/rt-ch_echarts.png");
+          img.src = require("../../../assets/icon/classroomObservation/rt-ch_echarts2.svg");  //ch_echarts2
           img.onload = () => {
-            ctx.drawImage(img, 25, 25, 250, 250);
-            ctx.fillStyle = fontColor;
+            ctx.drawImage(img, 0, 0, canvasWidth, canvasWidth);
             ctx.beginPath();
-            ctx.arc(
-              250 * parseFloat(data.RT) + 25,
-              250 - 250 * parseFloat(data.CH) + 25,
-              4,
-              0,
-              2 * Math.PI
-            );
+            let _showWidth = canvasWidth-((canvasWidth/8.8)*2)
+            ctx.arc((canvasWidth/8.8)+(_showWidth*parseFloat(data.RT)),(canvasWidth/8.8)+_showWidth-(_showWidth*parseFloat(data.CH)),4,0,2*Math.PI);
+            ctx.lineWidth = 0.5; // 设置边框大小
+            // ctx.arc((canvasWidth*parseFloat(this.data.RT))+(canvasWidth/8.8),(canvasWidth-(canvasWidth*parseFloat(this.data.CH))+(canvasWidth/8.8)), 4, 0, 2 * Math.PI);
             ctx.fill();
             ctx.stroke();
-            ctx.fillStyle = "black";
-            ctx.font = `20px serif`;
-            ctx.fillText("1", 10, 40);
-            ctx.fillText("Ch", 0, 150);
-            ctx.fillText("0", 10, 290);
-            ctx.fillText("Rt", 140, 295);
-            ctx.fillText("1", 280, 275);
-
             ctx.fillStyle = fontColor;
             ctx.font = "italic bold 24px Arial";
-            ctx.fillText(`RT=${data.RT}    CH=${data.CH}`, 40, 330);
-
-            // 将 canvas 转换为 base64 格式的图片地址
+            const text = `RT=${data.RT}    CH=${data.CH}`;
+            const textWidth = ctx.measureText(text).width;
+            ctx.fillText(text, (canvasWidth - textWidth) / 2, canvasHeight - canvasWidth / 12);
             const base64Image = canvas.toDataURL("image/png");
             resolve(base64Image);
           };
@@ -896,11 +905,21 @@ export default {
 							margin-right:-1in;
 						}
 						li{
-							margin-bottom:0.1in
-							margin-right:-1in;
+							position: relative;
+              padding-left: 1.5em; /* 控制项目符号与文本的距离 */
+              margin-bottom: 0.5em;
+              text-indent: -1.5em; /* 负缩进使文本与符号对齐 */
+              mso-special-format: bullet;
+              margin-left: 0;
+              padding-left: 10pt;
+              text-indent: -10pt;
+              mso-style-name: "Normal";
+              mso-style-priority: 99;
+              mso-style-unhide: no;
+              mso-style-qformat: yes;
+              mso-style-parent: "";
 						}
 						p{
-							line-height:1;
 							margin:0;
 							padding:0
 						}
@@ -910,8 +929,6 @@ export default {
       ${html}
       </body>
       </html>`;
-      // console.log(content)
-      // return console.log(content)
       // debugger
       let blob = htmlDocx.asBlob(content);
 
@@ -1214,6 +1231,9 @@ export default {
     batchBtn(){
       this.$refs.batchCreationClassDialogRef.open();
     },
+    addNewCourseOption(newOption){
+      this.optionData.unshift(newOption);
+    },
   },
   mounted() {
     this.getCourseList().then(_ => {

+ 199 - 29
src/components/pages/classroomObservation/newComponents/batchClassCard.vue

@@ -8,15 +8,15 @@
         <div class="bcc_r_t_left">
           <div class="bcc_r_t_l_image">
             <img
-              v-if="cardData.jsonData.fileData.type=='text/plain'"
+              v-if="['text/plain'].includes(cardData.jsonData.fileData.type)"
               src="../../../../assets/icon/classroomObservation/file_icon.svg"
             />
             <img
-              v-if="cardData.jsonData.fileData.type=='audio/wav'"
+              v-if="['audio/wav','audio/x-m4a','audio/mpeg'].includes(cardData.jsonData.fileData.type)"
               src="../../../../assets/icon/classroomObservation/audio_file.svg"
             />
             <img
-              v-if="cardData.jsonData.fileData.type=='video/mp4'"
+              v-if="['video/mp4'].includes(cardData.jsonData.fileData.type)"
               src="../../../../assets/icon/classroomObservation/videoFile_icon.svg"
             />
           </div>
@@ -24,20 +24,67 @@
             <div>
               <span>{{ cardData.jsonData.baseMessage.courseName }}</span>
               <img
+                v-show="cardData.status!='2'"
                 src="../../../../assets/icon/classroomObservation/table_edit.svg"
                 @click="editBaseMessage"
               />
             </div>
-            <span>{{ cardData.create_at }}</span>
+            <span>
+              <div>{{ cardData.create_at }}</div>
+              <span v-if="cardData.jsonData.fileData && cardData.jsonData.fileData.duration"></span>
+              <div v-if="cardData.jsonData.fileData && cardData.jsonData.fileData.duration">{{ cardData.jsonData.fileData.duration }}</div>
+            </span>
           </div>
         </div>
         <div class="bcc_r_t_right">
-          <span class="status_wait" v-if="cardData.status == '3'">停止中</span>
+          <span class="status_wait" v-if="cardData.status == '3'">待开始</span>
           <span class="status_fail" v-if="cardData.status == '4'">失败</span>
           <span class="status_success" v-if="cardData.status == '2'"
             >已完成</span
           >
-          <span class="status_doing" v-if="cardData.status == '1'">处理中</span>
+          <span class="status_doing" v-if="cardData.status == '1'">
+            <span style="cursor: pointer;" @click="changeShowSteps(!showSteps)">处理中</span>
+            <div class="stepBox" v-show="showSteps">
+              <div
+                class="sb_item"
+                v-for="(item, index) in cardData.jsonData.steps"
+                :key="index"
+              >
+                <!-- 1完成 -->
+                <img
+                  src="../../../../assets/icon/classroomObservation/successStatus_icon.svg"
+                  v-if="item.status==='1'"
+                />
+
+
+                <!-- 0等待 -->
+                <img
+                  class="rotation"
+                  src="../../../../assets/icon/classroomObservation/waitStatus_icon.svg"
+                  v-if="item.status==='0'"
+                />
+
+
+                <!-- 2处理中 -->
+                <img
+                  class="rotation"
+                  src="../../../../assets/icon/classroomObservation/isDoStatus_icon.svg"
+                  v-if="item.status==='2'"
+                />
+                <div>{{ item.text }}</div>
+                <span>
+                  <span
+                    v-if="
+                      item.status == '2' &&
+                        item.progress &&
+                        item.progress !== '0'
+                    "
+                    >{{ item.progress }}%</span
+                  ></span
+                >
+              </div>
+            </div>
+          </span>
           <span class="status_wait" v-if="cardData.status == '0'"
             >等待处理</span
           >
@@ -45,21 +92,50 @@
       </div>
       <div class="bcc_r_bottom">
         <div class="bcc_r_b_left">
-          <el-input placeholder="备注" v-model="cardData.remarks" @change="changeRemarks"></el-input>
+          <el-input
+            placeholder="备注"
+            v-model="cardData.remarks"
+            @change="changeRemarks"
+          ></el-input>
         </div>
         <div class="bcc_r_b_right">
           <el-button
             size="small"
             @click="goToEdit"
-            v-if="[2].includes(cardData.status)"
+            v-if="['2'].includes(cardData.status) && cardData.jsonData.createId"
             >前往编辑</el-button
           >
-          <!-- <el-button size="small" @click="lookReport">查看报告</el-button> -->
           <el-button
+            size="small"
+            @click="lookReport"
+            v-if="['2'].includes(cardData.status)"
+            >查看报告</el-button
+          >
+          <!-- <el-button
             size="small"
             @click="regenerate"
-            v-if="[2, 3].includes(cardData.status)"
+            v-if="['2', '3'].includes(cardData.status)"
             >重新生成</el-button
+          > -->
+          <el-button
+            size="small"
+            type="primary"
+            v-if="['3', '0'].includes(cardData.status)"
+            @click="start"
+            >开始</el-button
+          >
+          <el-button
+            size="small"
+            v-if="['1'].includes(cardData.status)"
+            @click="pause"
+            >暂停</el-button
+          >
+          <el-button
+            size="small"
+            v-if="['0', '1'].includes(cardData.status)"
+            type="danger"
+            @click="stop"
+            >停止</el-button
           >
         </div>
       </div>
@@ -76,19 +152,20 @@ export default {
         return {};
       }
     },
-    isSelect:{
-      type:Boolean,
-      default:false
-    },
+    isSelect: {
+      type: Boolean,
+      default: false
+    }
   },
   data() {
     return {
       checked: false,
+      showSteps:true,
       cardData: {
         id: "1",
         name: "文件名称文件名称文件名称",
         create_at: "2025-05-07 16.05.03",
-        remarks: "备注1",
+        remarks: "",
         status: "0",
         jsonData: {
           baseMessage: {
@@ -152,7 +229,7 @@ export default {
     };
   },
   watch: {
-    isSelect(newValue){
+    isSelect(newValue) {
       this.checked = newValue;
     },
     // checked(newValue) {
@@ -167,10 +244,10 @@ export default {
       handler(newValue) {
         if (newValue) {
           this.cardData = JSON.parse(JSON.stringify(newValue));
-          this.$forceUpdate()
+          this.$forceUpdate();
         }
       }
-    },
+    }
     // cardData(newValue) {
     //   if (JSON.stringify(this.data) != JSON.stringify(newValue)) {
     //     this.$emit("changeData", {});
@@ -179,32 +256,51 @@ export default {
   },
   methods: {
     editBaseMessage() {
-      this.$emit("editBaseMessage",this.cardData.id);
+      this.$emit("editBaseMessage", this.cardData.id);
     },
     //前往编辑
     goToEdit() {
-      this.$message.info("前往编辑");
+      this.$emit("goToEdit",this.cardData.id)
     },
     //查看报告
     lookReport() {
-      this.$message.info("查看报告");
+      window.topU.postMessage(
+        {
+          tools: "classroom_observation_board",
+          type: this.cardData.jsonData.createId
+        },
+        "*"
+      );
+      // this.$message.info("查看报告");
     },
     //重新生成
     regenerate() {
       this.$message.info("重新生成");
     },
-    changeRemarks(newValue){
-      if(this.data.remarks !=newValue){
-        this.$emit("changeData",{field:['remarks'],data:this.cardData})
+    changeRemarks(newValue) {
+      if (this.data.remarks != newValue) {
+        this.$emit("changeData", { field: ["remarks"], data: this.cardData });
       }
     },
-    changeChecked(newValue){
-      if (newValue && newValue!=this.isSelect) {
-        this.$emit("changeChecked", {type:0,id:this.cardData.id});
+    changeChecked(newValue) {
+      if (newValue && newValue != this.isSelect) {
+        this.$emit("changeChecked", { type: 0, id: this.cardData.id });
       } else {
-        this.$emit("changeChecked", {type:1,id:this.cardData.id});
+        this.$emit("changeChecked", { type: 1, id: this.cardData.id });
       }
     },
+    start() {
+      this.$emit("taskBtn", { type: "startTask", id: this.cardData.id });
+    },
+    stop() {
+      this.$emit("taskBtn", { type: "stopTask", id: this.cardData.id });
+    },
+    pause() {
+      this.$emit("taskBtn", { type: "pauseTask", id: this.cardData.id });
+    },
+    changeShowSteps(newValue){
+      this.showSteps = newValue;
+    },
   },
   mounted() {
     if (this.data) {
@@ -281,6 +377,58 @@ export default {
 
 .bcc_r_t_right > span {
   font-size: 16px;
+  display: block;
+  position: relative;
+}
+
+.stepBox {
+  width: 200px;
+  height: auto;
+  background-color: #fff;
+  position: absolute;
+  left: calc(100% + 22px);
+  top: -21px;
+  box-sizing: border-box;
+  border: 1px solid #d9d9d9;
+  border-radius: 12px;
+  box-shadow: 0px 4px 10px 0px rgba(0, 0, 0, 0.05);
+  color: #000;
+  padding: 10px 10px;
+  cursor: default;
+}
+
+.sb_item {
+  width: 100%;
+  height: 25px;
+  display: flex;
+  align-items: center;
+  justify-content: space-around;
+}
+
+.sb_item > img {
+  width: 20px;
+  height: 20px;
+}
+
+.sb_item > div {
+  max-width: calc(100% - 20px - 35px - 15px);
+  width: calc(100% - 20px - 35px - 15px);
+  height: 100%;
+  margin: 0 5px 0 10px;
+  display: block;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+  line-height: 24px;
+}
+
+.sb_item > span {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 35px;
+  height: 100%;
+  color: rgba(54, 129, 252, 1);
 }
 
 .bcc_r_t_l_message {
@@ -314,10 +462,19 @@ export default {
 .bcc_r_t_l_message > span {
   margin-top: 10px;
   font-size: 14px;
-  display: block;
+  display: flex;
+  align-items: center;
   color: rgba(150, 155, 163, 1);
 }
 
+.bcc_r_t_l_message > span>span{
+  width: 2px;
+  height: 12px;
+  display: block;
+  background-color: #CBCBCB;
+  margin: 0 10px;
+}
+
 .bcc_r_bottom {
   width: 100%;
   display: flex;
@@ -381,4 +538,17 @@ export default {
 .status_fail {
   color: rgb(185, 2, 2);
 }
+
+.rotation {
+  animation: rotate 4s linear infinite;
+}
+
+@keyframes rotate {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
 </style>

+ 2227 - 0
src/components/pages/classroomObservation/tools/mixin.js

@@ -0,0 +1,2227 @@
+import {
+  v4 as uuidv4
+} from 'uuid';
+var OpenCC = require("opencc-js");
+
+let converter = OpenCC.Converter({
+  from: "hk",
+  to: "cn"
+});
+
+import _ from "lodash";
+import Papa from "papaparse";
+import markdownIt from "markdown-it";
+
+import QRCode from "qrcodejs2";
+import * as echarts from "echarts";
+import "echarts-wordcloud";
+// word
+import htmlDocx from "html-docx-js/dist/html-docx";
+
+export const toolMixin = {
+  data(){
+    return{
+      tag: {
+        0: "一",
+        1: "二",
+        2: "三",
+        3: "四",
+        4: "五",
+        5: "六",
+        6: "七",
+        7: "八",
+        8: "九",
+        9: "十",
+        10: "十一",
+        11: "十二",
+        12: "十三",
+        13: "十四",
+        14: "十五",
+        15: "十六",
+        16: "十七",
+        17: "十八",
+        18: "十九",
+        19: "二十"
+      }
+    }
+  },
+  methods: {
+    testMixin() {
+
+    },
+    getTextContentMixin(file) {
+      return new Promise((resolve) => {
+        const txtRegex = /\.(txt|csv)$/i;
+        if (txtRegex.test(file.url)) {
+          this.getFile(file.url).then(fileData => {
+            const arr = Papa.parse(fileData.data, {
+              header: false
+            }).data.slice(1);
+            // console.log(arr)
+            const _editorBarDataContent = `<table
+border="0"
+width="100%"
+cellpadding="0"
+cellspacing="0"
+style="text-align: center">
+<tbody>
+<tr>
+  <th>序号</th>
+  <th>开始时间</th>
+  <th>结束时间</th>
+  <th>发言内容</th>
+  <th>时长</th>
+  <th>说话人身份</th>
+  <th>行为编码</th>
+</tr>
+${arr.map(row => `<tr>
+  <td>${_.get(row, 0, "")}</td>
+  <td>${_.get(row, 1, "")}</td>
+  <td>${_.get(row, 2, "")}</td>
+  <td>${_.get(row, 3, "")}</td>
+  <td>${_.get(row, 4, "")}</td>
+  <td>${_.get(row, 5, "")}</td>
+  <td>${_.get(row, 6, "")}</td>
+</tr>
+`).join("\n")}</tbody></table>`;
+
+            var blob = new Blob([_editorBarDataContent], { type: "text/plain;charset=utf-8" });
+            blob.lastModifiedDate = new Date();
+            blob.name = `${file.name.replace('.txt', '')}_classroomObservation.txt`;
+            this.uploadFileMixin(blob).then(upload => {
+              resolve({ editorBarData: { type: "0", url: upload.Location, content: _editorBarDataContent } })
+              // this.ajax
+              //   .put("https://gpt4.cocorobo.cn/upload_file_knowledge", {
+              //     url: upload.Location
+              //   })
+              //   .then(res => {
+              //     let resData = res.data.FunctionResponse;
+              //     if (resData.result && resData.result.id) {
+              //       resolve({ fileId: resData.result.id, editorBarData: { type: "0", url: upload.Location }, })
+              //     }
+              //   })
+
+
+            })
+          })
+        }
+      })
+    },
+    uploadFileMixin(file) {
+      return new Promise((resolve, reject) => {
+        var credentials = {
+          accessKeyId: "AKIATLPEDU37QV5CHLMH",
+          secretAccessKey: "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"
+        }; //秘钥形式的登录上传
+        window.AWS.config.update(credentials);
+        window.AWS.config.region = "cn-northwest-1"; //设置区域
+
+        var bucket = new window.AWS.S3({ params: { Bucket: "ccrb" } }); //选择桶
+        var _this = this;
+
+        if (file) {
+          var params = {
+            Key:
+              file.name.split(".")[0] +
+              new Date().getTime() +
+              "." +
+              file.name.split(".")[file.name.split(".").length - 1],
+            ContentType: file.type,
+            Body: file,
+            "Access-Control-Allow-Credentials": "*",
+            ACL: "public-read"
+          }; //key可以设置为桶的相抵路径,Body为文件, ACL最好要设置
+          var options = {
+            partSize: 2048 * 1024 * 1024,
+            queueSize: 2,
+            leavePartsOnError: true
+          };
+          bucket
+            .upload(params, options)
+            .on("httpUploadProgress", function (evt) {
+              //这里可以写进度条
+              // _this.progressData.value = parseInt((evt.loaded * 100) / evt.total);
+              // console.log("Uploaded : " + parseInt((evt.loaded * 80) / evt.total) + '%');
+            })
+            .send(function (err, data) {
+              if (err) {
+                _this.$message.error("上传失败");
+              } else {
+                resolve(data);
+              }
+            });
+        }
+      })
+    },
+    getFileIdMixin(url) {
+      return new Promise((resolve) => {
+        this.ajax
+          .put("https://gpt4.cocorobo.cn/upload_file_knowledge", {
+            url: url
+          })
+          .then(res => {
+            let resData = res.data.FunctionResponse;
+            if (resData.result && resData.result.id) {
+              resolve({ fileId: resData.result.id })
+            }
+          })
+      })
+    },
+    getVideoToVoiceAndUploadMixin(fileData) {
+      return new Promise(async (resolve) => {
+        let _file = null;
+        console.log("fileData👉", fileData)
+        if (fileData.fileObj) {
+          _file = fileData.fileObj
+        } else if (fileData.url) {
+          let videoRes = await this.getFileBody(fileData.url);
+          if (videoRes.data === 1) return resolve({ data: 1 })
+          // 把uint8Array转换为视频文件
+          _file = new File([videoRes.data], 'video.mp4', { type: 'video/mp4' });
+        }
+        if (!_file) return resolve({ data: 2,err:"未找到文件" })
+        console.log("需要处理的文件👉", _file)
+        try {
+          const reader = new FileReader();
+          reader.onload = async (e) => {
+            try {
+              // 创建音频上下文
+              const audioContext = new (window.AudioContext || window.webkitAudioContext)();
+
+              //解码音频数据
+              const buffer = await audioContext.decodeAudioData(e.target.result);
+
+              //创建离线音频上下文
+              const offlineAudioContext = new OfflineAudioContext({ numberOfChannels: buffer.numberOfChannels, length: buffer.length, sampleRate: buffer.sampleRate });
+
+              //创建音源节点
+              const source = offlineAudioContext.createBufferSource();
+              source.buffer = buffer;
+              source.connect(offlineAudioContext.destination);
+              source.start();
+
+              //渲染音频
+              const renderedBuffer = await offlineAudioContext.startRendering();
+              const wavBlob = this.bufferToWav(renderedBuffer);
+
+              // blob转成file文件
+              const audioFile = new File([wavBlob], 'audio.wav', { type: 'audio/wav' });
+              this.uploadFileMixin(audioFile).then(upload => {
+                resolve({ audioUrl: upload, fileObj: audioFile })
+              })
+            } catch (error) {
+              console.log("👉", error);
+              return resolve({ data: 2,err:error })
+            }
+
+          }
+          reader.readAsArrayBuffer(_file);
+        } catch (error) {
+          console.log("👉", error);
+          return resolve({ data: 2,err:error })
+        }
+      })
+    },
+    bufferToWav(audioBuffer) {
+      const numOfChan = audioBuffer.numberOfChannels;
+      const length = audioBuffer.length * numOfChan * 2;
+      const buffer = new ArrayBuffer(44 + length);
+      const view = new DataView(buffer);
+      const channels = [];
+      let pos = 0;
+      // 获取通道数据
+      for (let i = 0; i < audioBuffer.numberOfChannels; i++) {
+        channels.push(audioBuffer.getChannelData(i));
+      }
+      // 写入WAV头
+      this.writeUTFBytes(view, 0, 'RIFF');
+      view.setUint32(4, 44 + length - 8, true);
+      this.writeUTFBytes(view, 8, 'WAVE');
+      this.writeUTFBytes(view, 12, 'fmt ');
+      view.setUint32(16, 16, true);
+      view.setUint16(20, 1, true);
+      view.setUint16(22, numOfChan, true);
+      view.setUint32(24, audioBuffer.sampleRate, true);
+      view.setUint32(28, audioBuffer.sampleRate * 2 * numOfChan, true);
+      view.setUint16(32, numOfChan * 2, true);
+      view.setUint16(34, 16, true);
+      this.writeUTFBytes(view, 36, 'data');
+      view.setUint32(40, length, true);
+      // 写入PCM数据
+      pos = 44;
+      for (let i = 0; i < audioBuffer.length; i++) {
+        for (let j = 0; j < numOfChan; j++) {
+          const sample = Math.max(-1, Math.min(1, channels[j][i]));
+          view.setInt16(pos, sample < 0 ? sample * 0x8000 : sample * 0x7FFF, true);
+          pos += 2;
+        }
+      }
+      return new Blob([buffer], { type: 'audio/wav' });
+    },
+    writeUTFBytes(view, offset, string) {
+      for (let i = 0; i < string.length; i++) {
+        view.setUint8(offset + i, string.charCodeAt(i));
+      }
+    },
+    getFile(url) {
+      return new Promise((resolve, reject) => {
+        var credentials = {
+          accessKeyId: "AKIATLPEDU37QV5CHLMH",
+          secretAccessKey: "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"
+        }; //秘钥形式的登录上传
+        window.AWS.config.update(credentials);
+        window.AWS.config.region = "cn-northwest-1"; //设置区域
+        let url2 = url;
+        let _url2 = "";
+        if (
+          url2.indexOf("https://view.officeapps.live.com/op/view.aspx?src=") != -1
+        ) {
+          _url2 = url2.split(
+            "https://view.officeapps.live.com/op/view.aspx?src="
+          )[1];
+        } else {
+          _url2 = url2;
+        }
+        var s3 = new window.AWS.S3({ params: { Bucket: "ccrb" } });
+        let name = decodeURIComponent(
+          _url2.split("https://ccrb.s3.cn-northwest-1.amazonaws.com.cn/")[1]
+        );
+        var params = {
+          Bucket: "ccrb",
+          Key: name
+        };
+        s3.getObject(params, function (err, data) {
+          if (err) {
+            console.log(err, err.stack);
+            resolve({ data: 1 });
+          } else {
+            const fileContent = data.Body.toString("utf-8");
+            resolve({ data: fileContent });
+          } // sxuccessful response
+        });
+        // axios({
+      });
+    },
+    getFileBody(url) {
+      return new Promise((resolve, reject) => {
+        var credentials = {
+          accessKeyId: "AKIATLPEDU37QV5CHLMH",
+          secretAccessKey: "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",
+        }; //秘钥形式的登录上传
+        window.AWS.config.update(credentials);
+        window.AWS.config.region = "cn-northwest-1"; //设置区域
+        let url2 = url;
+        let _url2 = "";
+        if (
+          url2.indexOf("https://view.officeapps.live.com/op/view.aspx?src=") != -1
+        ) {
+          _url2 = url2.split(
+            "https://view.officeapps.live.com/op/view.aspx?src="
+          )[1];
+        } else {
+          _url2 = url2;
+        }
+        var s3 = new window.AWS.S3({ params: { Bucket: "ccrb" } });
+        let name = decodeURIComponent(_url2.split("https://ccrb.s3.cn-northwest-1.amazonaws.com.cn/")[1])
+        var params = {
+          Bucket: "ccrb",
+          Key: name
+        };
+        s3.getObject(params, function (err, data) {
+          if (err) {
+            console.log(err, err.stack)
+            resolve({ data: 1 });
+          } else {
+            resolve({ data: data.Body });
+            console.log(data);
+          }          // sxuccessful response
+
+        });
+      });
+    },
+    getAnalysisMixin(obj) {
+      return new Promise(async (resolve) => {
+        let { fileId, assistantData, content, analysisData, baseMessage } = obj;
+        let type = 0;
+        let tips = ""
+        analysisData.mId = assistantData.id;
+        // console.log("处理数据👉", fileId, assistantData, content, analysisData, baseMessage)
+        if (['f8795150-699c-11ef-b873-005056b86db5', '01928d2b-699d-11ef-b873-005056b86db5', '069af7b9-699d-11ef-b873-005056b86db5', 'bfe844b1-7a45-11ef-9b30-005056b86db5'].includes(assistantData.id)) {//S-T分析:课堂时间分配   S-T分析:师生互动分析  S-T分析:教学模式分析 课堂活动光谱图
+          try {
+            let _result = [];
+            let _data = content;
+            let _div = document.createElement("div");
+            _div.innerHTML = _data;
+            let _tableRows = _div.querySelectorAll(`table tbody tr`);
+            _tableRows.forEach((i, index) => {
+              if (index == 0) return;
+              let obj = {
+                index: i.cells[0].textContent,
+                startTime: i.cells[1].textContent,
+                endTime: i.cells[2].textContent,
+                message: i.cells[3].textContent,
+                time: i.cells[4].textContent,
+                role: i.cells[5] ? i.cells[5].textContent : "",
+                behavior: i.cells[6] ? i.cells[6].textContent : ""
+              };
+              _result.push(obj);
+            });
+            if (_result.length == 0) return resolve({ data: 1, err: "未找到表格数据" });
+            if (assistantData.id == "f8795150-699c-11ef-b873-005056b86db5") {//课堂时间分配
+              let resultData = await this.getTimeAllocationDataMixin(_result, fileId)
+              if (resultData.message) analysisData.content = resultData.message;
+              if (resultData.eCharts) analysisData.eChartData = resultData.eCharts;
+              return resolve({ data: analysisData })
+            } else if (assistantData.id == "01928d2b-699d-11ef-b873-005056b86db5") {//师生互动分析
+              let resultData = await this.getInteractionAnalysisData(_result, fileId)
+              if (resultData.message) analysisData.content = resultData.message;
+              if (resultData.eCharts) analysisData.eChartData = resultData.eCharts;
+              return resolve({ data: analysisData })
+            } else if (assistantData.id == "069af7b9-699d-11ef-b873-005056b86db5") {//教学模式分析
+              let resultData = await this.getTeachingModeData(_result, fileId)
+              if (resultData.message) analysisData.content = resultData.message;
+              if (resultData.RT) analysisData.RT = resultData.RT;
+              if (resultData.CH) analysisData.CH = resultData.CH;
+              return resolve({ data: analysisData })
+            } else if (assistantData.id == "bfe844b1-7a45-11ef-9b30-005056b86db5") {//课堂活动光谱图
+              let resultData = await this.getSpectrogram(_result, fileId, content, assistantData)
+              if (resultData.message) analysisData.content = resultData.message;
+              if (resultData.eCharts) analysisData.getSpectrogram = resultData.eCharts;
+              return resolve({ data: analysisData })
+            }
+          } catch (error) {
+            return resolve({ data: 1, err: err })
+          }
+        } else {
+          let _msg = `使用文件检索的方式完整的去分析文件内容,并请完全按照要求输出。`;
+          if (assistantData.tips) {
+            tips = assistantData.tips;
+            type = 1;
+          } else if (assistantData.agentid) {
+            type = 0;
+          }
+          if (assistantData.id === '6b4a9650-48be-11ef-936b-12e77c4cb76b') {
+            _msg = `使用文件检索的方式完整的去分析文件内容,并基于以下的课堂基本内容,使用cpote课程设计模型改编一堂同主题的课程。
+课堂名称:${baseMessage.courseName}  搜课年级:${baseMessage.grade}  授课科目:${baseMessage.subject}`;
+          }
+          let params = {
+            id:
+              type == 0
+                ? assistantData.agentid
+                : "f8e1ebb2-2e0d-11ef-8bf4-12e77c4cb76b",
+            message: type == 0 ? _msg : tips,
+            session_name: uuidv4(),
+            userId: this.userId,
+            file_ids: fileId ? [fileId] : [],
+            model: "gpt-4o-2024-11-20",
+            sound_url: "",
+            temperature: 0.2,
+            top_p: 1,
+            max_completion_tokens: 4096,
+            stream: false,
+            uid: uuidv4()
+          };
+          this.ajax
+            .post("https://appapi.cocorobo.cn/api/agentchats/ai_agent_chat", params)
+            .then(async res => {
+              let _data = res.data;
+              analysisData.content = _data.message;
+              if (['1', '2', '3'].includes(assistantData.echartsType)) {
+                let echartsData = await this.getEChartsDataMixin(assistantData.echartsType, _data.message)
+                if (echartsData.data == 1) {
+                  console.log(`生成表格失败${echartsData.err}`)
+                  resolve({ data: analysisData })
+                } else {
+                  analysisData.eChartData = echartsData.data;
+                  resolve({ data: analysisData })
+                }
+              } else {
+                resolve({ data: analysisData })
+              }
+            })
+            .catch(err => {
+              resolve({ data: 1, err: err })
+            });
+        }
+      })
+    },
+    getContentTableMixin(content) {
+      return new Promise(resolve => {
+        let _content = content;
+        const md = new markdownIt();
+        let _contentHtml = md.render(_content);
+
+        let _contentTableList = [];
+        const rowRegex = /<tr[^>]*>([\s\S]*?)<\/tr>/g; // 匹配表格行,[\s\S] 匹配所有字符
+        const cellRegex = /<(th|td)[^>]*>([\s\S]*?)<\/\1>/g; // 匹配单元格,[\s\S] 匹配所有字符
+
+        let rowMatch;
+        while ((rowMatch = rowRegex.exec(_contentHtml)) !== null) {
+          const rowContent = rowMatch[1]; // 每一行的内容
+          const rowData = [];
+          let cellMatch;
+
+          // 匹配每个单元格 (th 或 td)
+          while ((cellMatch = cellRegex.exec(rowContent)) !== null) {
+            let _text = cellMatch[2].trim();
+            _text = _text.replace(/&[a-zA-Z]+;/g, "");
+            _text = _text.replace(/<\/?[^>]+(>|$)/g, "");
+            rowData.push(_text); // 将每个单元格的内容添加到当前行的数组中
+          }
+
+          // 如果该行有数据,推送到 _contentTableList 中
+          if (rowData.length) {
+            _contentTableList.push(rowData);
+          }
+        }
+
+        // 输出提取的表格数据
+
+        resolve(_contentTableList);
+      });
+    },
+    getEChartsDataMixin(type = "1", content) {
+      return new Promise((resolve) => {
+        if (type === "1") {
+          //词云图
+          return this.getContentTableMixin(content).then(res => {
+            try {
+              if (res.length <= 0) {
+                return resolve({ data: 0 })//未找到表格数据
+              }
+
+              let _result = [];
+              res.forEach((item, index) => {
+                if (index == 0) return; //去掉表头
+                let _valueItem = item[2] ? item[2] : item[1];
+                let _value = _valueItem.match(/(\d+)/);
+                _value = _value ? parseInt(_value[0]) : 0;
+                _result.push({
+                  value: _value,
+                  name: item[0],
+                  textStyle: { color: this.getRandomColorMixin() }
+                });
+              });
+
+              let _option = {
+                tooltip: {
+                  show: false
+                },
+                series: [
+                  {
+                    type: "wordCloud",
+                    sizeRange: [14, 38],
+                    rotationRange: [0, 0],
+                    keepAspect: false,
+                    shape: "circle",
+                    left: "center",
+                    top: "center",
+                    right: null,
+                    bottom: null,
+                    width: "100%",
+                    height: "100%",
+                    // rotationRange: [-90, 90],
+                    rotationStep: 20,
+                    data: _result
+                  }
+                ]
+              };
+              resolve({ data: _option })
+            } catch (e) {
+              resolve({ data: 1, err: e })
+            }
+          });
+        } else if (type == "2") {
+          //雷达图
+          //雷达图
+          return this.getContentTableMixin(content).then(res => {
+            try {
+              if (res.length <= 0) {
+                return resolve({ data: 0 })//未找到表格数据
+              }
+
+              let radarData = [];
+              let seriesData = { value: [] };
+
+              res.forEach((item, index) => {
+                if (index == 0) return; //去掉表头
+                radarData.push({ name: item[0], max: 5 });
+                let _valueItem = item[1] ? item[1] : "0";
+                let _value = _valueItem.match(/(\d+)/);
+                _value = _value ? parseInt(_value[0]) : 0;
+                seriesData.value.push(_value);
+              });
+
+              let _option = {
+                legend: {
+                  textStyle: {
+                    color: '#000'
+                  }
+                },
+                radar: {
+                  // shape: 'circle',
+                  indicator: radarData,
+                  name: {
+                    textStyle: {
+                      color: '#000'
+                    }
+                  }
+                },
+                series: [
+                  {
+                    type: "radar",
+                    data: [seriesData],
+                    label: {
+                      color: '#000'
+                    }
+                  }
+                ]
+              };
+              resolve({ data: _option })
+            } catch (e) {
+              resolve({ data: 1, err: e })
+            }
+          });
+        } else if (type == "3") {
+          //能量柱图
+          return this.getContentTableMixin(content).then(res => {
+            try {
+              if (res.length <= 0) {
+                return resolve({ data: 0 })//未找到表格数据
+              }
+
+              let _data = [];
+              let stepList = [];
+              stepList = this.calculateTopValuesMixin(res.length - 1);
+              res.forEach((item, index) => {
+                if (index == 0) return;
+                let _valueItem = item[1] ? item[1] : "0";
+                let _value = _valueItem.match(/(\d+)/);
+                _value = _value ? parseInt(_value[0]) : 0;
+                // 求百分比
+                _value = Math.floor((_value / 5).toFixed(2) * 100);
+                _data.push({
+                  value: _value,
+                  name: item[0],
+                  title: { offsetCenter: ["0%", `${stepList[index - 1]}%`] },
+                  detail: {
+                    valueAnimation: true,
+                    offsetCenter: ["0%", `${stepList[index - 1] + 15}%`]
+                  }
+                });
+              });
+
+              let _option = {
+                series: [
+                  {
+                    type: "gauge",
+                    startAngle: 90,
+                    endAngle: -270,
+                    pointer: {
+                      show: false
+                    },
+                    progress: {
+                      show: true,
+                      overlap: false,
+                      roundCap: true,
+                      clip: false,
+                      itemStyle: {
+                        borderWidth: 1,
+                        borderColor: "#464646"
+                      }
+                    },
+                    axisLine: {
+                      lineStyle: {
+                        width: 40
+                      }
+                    },
+                    splitLine: {
+                      show: false,
+                      distance: 0,
+                      length: 10
+                    },
+                    axisTick: {
+                      show: false
+                    },
+                    axisLabel: {
+                      show: false,
+                      distance: 50
+                    },
+                    data: _data,
+                    title: {
+                      fontSize: 14,
+                      color: "#000"
+                    },
+                    detail: {
+                      width: 50,
+                      height: 14,
+                      fontSize: 14,
+                      color: "#000",
+                      borderColor: "inherit",
+                      borderRadius: 20,
+                      borderWidth: 1,
+                      formatter: "{value}%"
+                    }
+                  }
+                ]
+              };
+              resolve({ data: _option })
+            } catch (e) {
+              resolve({ data: 1, err: e })
+            }
+          });
+        }
+      })
+    },
+    getRandomColorMixin() {
+      // 生成一个随机的 0 到 255 的数字,并将其转换为两位的十六进制形式
+      const randomHex = () => {
+        const hex = Math.floor(Math.random() * 256).toString(16);
+        return hex.length === 1 ? "0" + hex : hex; // 确保每个部分有两位
+      };
+
+      // 组合三种颜色(红、绿、蓝)的随机值
+      return `#${randomHex()}${randomHex()}${randomHex()}`;
+    },
+    calculateTopValuesMixin(len, minTop = -80, maxTop = 70, maxStep = 40) {
+      const length = len;
+      const middleIndex = Math.floor(length / 2); // 中间位置的索引
+      const totalRange = maxTop - minTop;
+      let step = totalRange / (length - 1); // 默认间隔
+
+      // 如果间隔大于 10%,则设置为最大 10%
+      if (step > maxStep) {
+        step = maxStep;
+      }
+
+      const topValues = [];
+
+      // 计算中间元素的top值
+      const middleTop = (minTop + maxTop) / 2;
+      topValues[middleIndex] = middleTop;
+
+      // 从中间向两边扩展,确保每个元素的top值
+      for (let i = middleIndex - 1; i >= 0; i--) {
+        topValues[i] = topValues[i + 1] - step; // 向上扩展
+      }
+
+      for (let i = middleIndex + 1; i < length; i++) {
+        topValues[i] = topValues[i - 1] + step; // 向下扩展
+      }
+
+      return topValues;
+    },
+    // 课堂时间分配
+    getTimeAllocationDataMixin(_dataList, fileId) {
+      return new Promise(async (resolve) => {
+        let _data = _dataList.reduce(
+          (pre, cur) => {
+            if (cur.role == "学生") {
+              pre[1].value += this.convertToSeconds(cur.time);
+            } else if (cur.role == "老师") {
+              pre[0].value += this.convertToSeconds(cur.time);
+            }
+            return pre;
+          },
+          [
+            { value: 0, name: "老师" },
+            { value: 0, name: "学生" }
+          ]
+        );
+        let _dataPercentage = JSON.parse(JSON.stringify(_data));
+        _data.forEach((i, index) => {
+          _dataPercentage[index].percentage =
+            (
+              (i.value / _data.reduce((pre, cur) => pre + cur.value, 0)) *
+              100
+            ).toFixed(2) + "%";
+        });
+
+        const _option = {
+          tooltip: {
+            left: "center",
+            trigger: "item",
+            formatter: "{a} <br/>{b}: {d}%",
+            textStyle: {
+              color: '#000000'
+            }
+          },
+          legend: {
+            top: "5%",
+            left: "center",
+            textStyle: {
+              color: '#000000'
+            }
+          },
+          series: [
+            {
+              name: "课堂时间分配",
+              type: "pie",
+              radius: ["40%", "70%"],
+              label: {
+                formatter: "{b}: {d}%",
+                color: '#000000'
+              },
+              emphasis: {
+                label: {
+                  show: true,
+                  formatter: "{b}: {d}%",
+                  color: '#000000'
+                },
+                itemStyle: {
+                  shadowBlur: 10,
+                  shadowOffsetX: 0,
+                  shadowColor: "rgba(0, 0, 0, 0.5)"
+                }
+              },
+              data: _data
+            }
+          ]
+        };
+
+        let _msg = `这是某一节课的师生时间占比,请你分析,写出结论,并给出指导建议。请使用3句完整的话,分析并给出建议。 请注意,当老师或学生的时间占比在【40~59%】之间的时候,也认为师生占比约为1:1,各占50%,师生时间占比比较均衡。
+  师生时间占比数据:
+  老师占比:${_dataPercentage[0].percentage}
+  学生占比:${_dataPercentage[1].percentage}
+  `;
+        let message = await this.getAiContentMixin({ _msg: _msg, fileId: fileId })
+
+        if (message.data == 1) {
+          return resolve({ eCharts: _option, message: "" })
+        } else {
+          return resolve({ eCharts: _option, message: message.data })
+        }
+
+      })
+      // return this.getAiContent(_msg);
+    },
+    // 师生互动分析
+    getInteractionAnalysisData(_dataList, fileId) {
+      return new Promise(async (resolve) => {
+        let _pushData = [0, 0];
+        let _result = [];
+        _dataList.forEach(i => {
+          if (i.role == "老师") {
+            _pushData[0] += this.convertToSeconds(i.time);
+          } else if (i.role == "学生") {
+            _pushData[1] += this.convertToSeconds(i.time);
+          }
+          return _result.push(JSON.parse(JSON.stringify(_pushData)));
+        });
+        let _flatArray = _result.flat();
+        const _max = Math.max(..._flatArray);
+        const _maxValue = Math.ceil(_max / 100) * 100;
+
+        const _option = {
+          xAxis: {
+            name: "老师", // X轴标题
+            nameLocation: "end", // 标题位置
+            scale: true,
+            min: 0,
+            max: _maxValue,
+            axisLabel: {
+              color: "#000" // 设置字体颜色为#000
+            },
+            nameTextStyle: {
+              color: "#000" // 设置老师字体颜色为#000
+            }
+          },
+          yAxis: {
+            name: "学生", // Y轴标题
+            nameLocation: "end", // 标题位置
+            scale: true,
+            min: 0,
+            max: _maxValue,
+            axisLabel: {
+              color: "#000" // 设置字体颜色为#000
+            },
+            nameTextStyle: {
+              color: "#000" // 设置学生字体颜色为#000
+            }
+          },
+          grid: {
+            containLabel: true
+          },
+          series: [
+            {
+              name: "数据",
+              step: "start",
+              data: _result,
+              type: "line",
+              lineStyle: {
+              }
+            },
+            {
+              name: "对角线",
+              type: "line",
+              data: [
+                [0, 0],
+                [_maxValue, _maxValue]
+              ],
+              lineStyle: {
+                type: "dashed",
+              },
+              markLine: {
+                symbol: ["none", "none"]
+              }
+            }
+          ]
+        };
+
+        let _msg = `
+  ## 任务
+  请你结合 FIAS 相关的知识,根据以下提供给你的课堂原始数据(包含S和t的数据),请你具体描述整个课堂S行为与T行为的持续性与变化性。比如,课堂一开始老师占比主导,大约5分钟之后,进入到学生为主的小组讨论环节。在整个课堂之中,老师与学生的互动比较频繁,老师会频繁询问学生问题,引导学生思考。之后是授课时间与问答时间。等等。
+  ## 输出要求 请使用自然语言进行描述,使用不超过5句完整的话进行整体性、概括性的描述,不要包含具体的时长信息。总结性概括之后,使用1句话对整个课堂的教师引导行为进行鼓励和评价,再使用1句话给出相应的优化建议。
+  ## 你的知识库 定义与目的:S-T图,即学生-教师(Student-Teacher)图,主要用于记录和分析课堂上的学生行为(S)与教师行为(T)的时间分布。这种图形能够帮助教育专家和教师可视化课堂互动的流程,从而判断课堂的教学型态,如练习型、对话型、讲授型或混合型。 绘制方法:S-T图的绘制开始于教学的起始时刻,纵轴表示学生行为(S),横轴表示教师行为(T)。实际课堂观察或录像回放中,按照固定时间间隔(通常每30秒)采样,将对应的行为按时间顺序标记在相应的轴上。通过这种方法,可以清晰看到课堂上教师行为与学生行为的交替模式及其随时间的变化。 应用场景:例如,一个典型的应用是在分析不同类型课堂活动时使用S-T图。在讲授型课堂中,教师行为的时间占比会较高,S-T图显示较长的横轴(T行为)延续;而在练习型或对话型课堂中,学生行为的时间占比增高,显示为较长的纵轴(S行为)。
+  ## 课堂实录
+  ${JSON.stringify(_dataList)}
+  `;
+
+        let message = await this.getAiContentMixin({ _msg: _msg, fileId: fileId })
+
+        if (message.data == 1) {
+          return resolve({ eCharts: _option, message: "" })
+        } else {
+          return resolve({ eCharts: _option, message: message.data })
+        }
+      })
+
+    },
+    // 教学模式分析
+    getTeachingModeData(_dataList, fileId) {
+      return new Promise(async (resolve) => {
+        let _continuousTime = 0;
+        let _totalTime = 0;
+        let _continuousRole = "老师";
+        let _teacherTime = 0;
+        _dataList.forEach((item, index) => {
+          if (index == 0) {
+            //第一个
+            _continuousRole = item.role;
+          } else if (_dataList.length - 1 == index) {
+            //最后一个
+            if (_continuousRole == item.role) {
+              //连续对话了
+              _continuousTime += this.convertToSeconds(_dataList[index - 1].time);
+              _continuousTime += this.convertToSeconds(item.time);
+            } else {
+              //没连续对话
+              if (index >= 2) {
+                if (_dataList[index - 2].role == _dataList[index - 1].role) {
+                  _continuousTime += this.convertToSeconds(
+                    _dataList[index - 1].time
+                  );
+                } else {
+                  _continuousRole = item.role;
+                }
+              } else {
+                _continuousRole = item.role;
+              }
+            }
+          } else {
+            if (_continuousRole == item.role) {
+              //连续对话了
+              _continuousTime += this.convertToSeconds(_dataList[index - 1].time);
+            } else {
+              //没连续对话
+              if (index >= 2) {
+                if (_dataList[index - 2].role == _dataList[index - 1].role) {
+                  _continuousTime += this.convertToSeconds(
+                    _dataList[index - 1].time
+                  );
+                } else {
+                  _continuousRole = item.role;
+                }
+              } else {
+                _continuousRole = item.role;
+              }
+            }
+          }
+
+          if (item.role == "老师") {
+            _teacherTime += this.convertToSeconds(item.time);
+          }
+
+          _totalTime += this.convertToSeconds(item.time);
+        });
+
+        let _RT = (_teacherTime / _totalTime).toFixed(2);
+        let _CH = (_continuousTime / _totalTime).toFixed(2);
+
+        let _msg = `## 任务
+根据FIAS(弗兰德斯互动分析系统)理论,计算获得某一节课的RT和CH值。请你结合FIAS相关知识进行分析,使用3句完整的话对整个课堂进行分析,需注意包含这些内容:分析该课堂所属的教学模型,描述课堂的整体表现与特征,肯定老师做出的努力,以及给出相应的建议。
+## 你的知识
+根据RT和CH的值,教学模式通常被分为以下几种类型: 练习型:RT ≤ 0.3,表示学生行为占主导,教师行为较少。 讲授型:RT ≥ 0.7,表示教师行为占主导,学生参与较少。 对话型:CH ≥ 0.4,表示师生之间有较多的互动和转换。 混合型:0.3 < RT < 0.7,CH < 0.4,表示教学中既有教师讲授也有学生参与,但两者都不占绝对优势。
+## 数据
+RT:${_RT}
+CH:${_CH}
+`;
+        let message = await this.getAiContentMixin({ _msg: _msg, fileId: fileId })
+
+        if (message.data == 1) {
+          return resolve({ RT: _RT, CH: _CH, message: "" })
+        } else {
+          return resolve({ RT: _RT, CH: _CH, message: message.data })
+        }
+      })
+    },
+    //光谱图
+    getSpectrogram(_dataList, fileId, content, assistant) {
+      return new Promise(async (resolve) => {
+        try {
+          this.getContentTableMixin(content).then(async res => {
+            if (res.length <= 0) {
+              resolve({ data: 1, err: "无表格数据" })
+            }
+            let _tableData = res;
+            let _delIndex = _tableData.findIndex(i => i.includes("时间点"))
+            _tableData = _tableData.slice(_delIndex + 1)
+            let _result = [];
+            let identity = "老师"; //0:老师 1:学生
+            let startTime = "";
+            let endTime = "";
+            let sumTime = 0;
+            let upTime = '00:00:00';
+
+            _dataList.forEach((item, index) => {
+              if (index == 0) {
+                //第一个
+                identity = item.role;
+                startTime = item.startTime;
+                endTime = item.endTime;
+                sumTime = (this.convertToSeconds(item.endTime) - this.convertToSeconds(upTime));
+                upTime = item.endTime
+                // console.log(item.endTime,item.startTime,(this.convertToSeconds(item.endTime) - this.convertToSeconds(item.startTime)))
+                return;
+              }
+              if (item.role == identity) {
+                //没更换角色
+                sumTime += (this.convertToSeconds(item.endTime) - this.convertToSeconds(upTime));
+                endTime = item.endTime;
+                upTime = item.endTime
+                // console.log(item.endTime,item.startTime,(this.convertToSeconds(item.endTime) - this.convertToSeconds(item.startTime)))
+              } else {
+                //更换角色了
+                _result.push({
+                  startTime: startTime,
+                  endTime: endTime,
+                  identity: identity,
+                  sumTime: sumTime
+                });
+                identity = item.role;
+                startTime = item.startTime;
+                endTime = item.endTime;
+                sumTime = (this.convertToSeconds(item.endTime) - this.convertToSeconds(upTime));
+                upTime = item.endTime
+                // console.log(item.endTime,item.startTime,(this.convertToSeconds(item.endTime) - this.convertToSeconds(item.startTime)))
+              }
+              if (index == _dataList.length - 1) {
+                // console.log("👉???",this.convertToSeconds(item.endTime))
+              }
+            });
+
+            let breakpoint = [];
+
+            breakpoint = _tableData.map(i => this.convertToSeconds(i[0]))
+
+            _result = _result.filter(
+              i =>
+                i.identity == "老师" ||
+                i.identity == "学生"
+            );
+            // let
+            let _data = {
+              data: [],
+              breakpoint: []
+            };
+
+            _data.data = _result.map(i => ({ role: i.identity, type: i.identity == "老师" ? 0 : 1, value: i.sumTime }));
+            _data.breakpoint = breakpoint;
+
+            let _msg = assistant.tips;
+
+            let message = await this.getAiContentMixin({ _msg: _msg, fileId: fileId })
+
+            if (message.data == 1) {
+              return resolve({ eCharts: _data, message: "" })
+            } else {
+              return resolve({ eCharts: _data, message: message.data })
+            }
+            // spectrogramData  _data
+
+
+          });
+        } catch (e) {
+          return resolve({ data: 1, err: e })
+        }
+      })
+
+    },
+    getAiContentMixin(obj) {
+      return new Promise((resolve) => {
+        let { _msg, fileId } = obj
+        let parm = {
+          id: "f8e1ebb2-2e0d-11ef-8bf4-12e77c4cb76b",
+          message: _msg,
+          session_name: uuidv4(),
+          userId: this.userId,
+          file_ids: fileId ? [fileId] : [],
+          model: "gpt-4o-2024-11-20",
+          sound_url: "",
+          temperature: 0.2,
+          top_p: 1,
+          max_completion_tokens: 4096,
+          stream: false,
+          uid: uuidv4()
+        };
+
+        this.ajax
+          .post("https://appapi.cocorobo.cn/api/agentchats/ai_agent_chat", parm)
+          .then(res => {
+            let _data = res.data;
+            resolve({ data: _data.message })
+          })
+          .catch(err => {
+            resolve({ data: 1, err: err })
+          });
+      })
+    },
+    convertToSeconds(time) {
+      let parts = time.split(":");
+      let seconds = +parts[0] * 3600 + +parts[1] * 60 + +parts[2];
+      return seconds;
+    },
+    //创建课堂
+    createClassMixin(data) {
+      return new Promise((resolve) => {
+        let _analysisList = data.analysisList;
+        let createJson = _analysisList.map(i => {
+          return {
+            jsonData: i.jsonData,
+            type: i.Type,
+            index: i.tIndex
+          }
+        })
+        createJson = createJson.filter(i => !i.isOtherData && converter(i.jsonData.name) != converter("词频词汇分析"));
+        let params = {
+          tid: uuidv4(),
+          userid: this.userId,
+          template: createJson
+        }
+        this.ajax
+          .post(
+            "https://gpt4.cocorobo.cn/insert_classroom_observation_template",
+            params
+          )
+          .then(res => {
+            let _data = res.data.FunctionResponse;
+            if (converter(_data.message) == converter("创建成功")) {
+              this.ajax
+                .post("https://gpt4.cocorobo.cn/insert_classroom_observation", {
+                  tid: params.tid,
+                  type: 10,
+                  index: 0,
+                  json_data: JSON.stringify({ file_ids: data.file_ids }),
+                  userid: this.userId
+                })
+                .then(res2 => {
+                  let _data2 = res2.data.FunctionResponse;
+                  if (converter(_data2.message) == converter("创建成功")) {
+                    let newOption = { id: uuidv4(), label: data.baseMessage.courseName, value: params.tid }
+                    let params2 = {
+                      tid: params.tid,
+                      type: 0,
+                    }
+                    this.ajax
+                      .post(
+                        "https://gpt4.cocorobo.cn/get_classroom_observation_new",
+                        params2
+                      ).then(res3 => {
+                        let _data = res3.data.FunctionResponse.result.length
+                          ? JSON.parse(res3.data.FunctionResponse.result)
+                          : [];
+
+                        //替换基础信息
+                        let _bmData = _data.find(i => i.tIndex == 0);
+                        let _imageList = _data.find(i => i.tIndex == 1);
+                        _imageList.jsonData = data.baseMessage.imageList
+
+                        if (_bmData) {
+                          _bmData.jsonData = data.baseMessage;
+                          delete _bmData.jsonData.imageList;
+                          if (data.tagList) {
+                            _bmData.jsonData.dialogTagList = data.tagList;
+                          } else {
+                            _bmData.jsonData.dialogTagList = [
+                              { value: 0, name: "通用课堂分析", loading: false },
+                              { value: 1, name: "学科课堂分析", loading: false },
+                              { value: 2, name: "扩展分析", loading: false }
+                            ]
+                          }
+                        }
+
+                        let arr = [{ id: _bmData.id, jsonData: JSON.stringify(_bmData.jsonData) }, { id: _imageList.id, jsonData: JSON.stringify(_imageList.jsonData) }]
+                        let promises = [];
+                        arr.forEach(i => {
+                          promises.push(new Promise((resolve) => {
+                            this.ajax
+                              .post("https://gpt4.cocorobo.cn/update_classroom_observation", {
+                                id: i.id,
+                                json_data: i.jsonData
+                              })
+                              .then(res => {
+                                resolve();
+                              })
+                              .catch(e => {
+                                console.log("保存失败", e);
+                                resolve()
+                              });
+                          }))
+                        })
+                        Promise.all(promises).then(res => {
+                          resolve({ data: newOption, tid: params.tid })
+                        })
+                      }).catch(err => {
+                        console.log("修改基础信息失败", err)
+                        resolve({ data: 3, err: err })
+                      })
+                  }
+                }).catch(err => {
+                  resolve({ data: 2, err: err })
+                  console.log("存储fileId失败")
+                  console.log(err)
+                })
+            }
+          }).catch(err => {
+            resolve({ data: 1, err: err })
+            console.log("创建课堂失败")
+            console.log(err)
+          })
+      })
+    },
+    //文本转录
+    wavAudioToTextAndObjMixin(file) {
+      return new Promise(async (resolve) => {
+        let iframeRef = this.$refs["iframeRef"];
+        iframeRef.contentWindow.window.document.getElementById(
+          "languageOptions"
+        ).selectedIndex = 2;//默认普通话
+
+        let transcriptionContent = "";
+        let tableContent = "";
+        let tableList = [];
+        let _startTime = 0;
+        let _endTime = 0;
+
+        // 转录中
+        iframeRef.contentWindow.onRecognizedResult = (e) => {
+          let privText = e.privText;
+          let privSpeakerId = e.privSpeakerId;
+          let privDuration = e.privDuration;
+          let privOffset = e.privOffset;
+          if (!privText || !privSpeakerId || privSpeakerId == "Unknown") {//不记录
+            return;
+          }
+
+          _endTime = (privOffset + privDuration) / 10000000;
+
+          tableList.push({
+            value: privText,
+            startTime: this.updateRecordedTimeMixin({ duration: _startTime }),
+            endTime: this.updateRecordedTimeMixin({ duration: _endTime }),
+            time: this.updateRecordedTimeMixin({ duration: _endTime - _startTime }),
+            role: privSpeakerId,
+            code: ""
+          });
+
+          _startTime = _endTime;
+          transcriptionContent += privText;
+        };
+
+        //转录结束
+        iframeRef.contentWindow.onSessionStopped = async (e) => {
+          tableContent = `<table
+	border="0"
+	width="100%"
+	cellpadding="0"
+	cellspacing="0"
+	style="text-align: center"
+>
+	<tbody>
+		<tr>
+			<th>序号</th>
+			<th>开始时间</th>
+			<th>结束时间</th>
+			<th>发言内容</th>
+			<th>时长</th>
+			<th>说话人身份</th>
+			<th>行为编码</th>
+	</tr>`;
+          tableList.forEach((item, index) => {
+            tableContent += `<tr>
+							<td>${index + 1}</td>
+							<td>${item.startTime}</td>
+							<td>${item.endTime}</td>
+							<td>${item.value}</td>
+							<td>${item.time}</td>
+							<td>${item.role}</td>
+							<td>${item.code}</td>
+						</tr>`
+          })
+
+          // tableContent += `<tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr></tbody></table>`
+
+          var blob = new Blob([tableContent], { type: "text/plain;charset=utf-8" });
+          blob.lastModifiedDate = new Date();
+          blob.name = `classroomObservation.txt`;
+          this.uploadFileMixin(blob).then(upload => {
+            resolve({ transcriptionContent: transcriptionContent, editorBarData: { type: "0", url: upload.Location, content: tableContent, tableList: tableList } })
+          })
+        };
+
+        //开始转录
+        iframeRef.contentWindow.ConversationTranscriber({
+          files: [file]
+        });
+      })
+
+    },
+    updateRecordedTimeMixin({ duration }) {
+      // 更新currentTime,将秒数转换为时分秒格式
+      let hours = Math.floor(duration / 3600);
+      let minutes = Math.floor((duration % 3600) / 60);
+      let seconds = Math.floor(duration % 60);
+      // this.recordedForm.time = `${hours.toString().padStart(2, "0")}:${minutes
+      // 	.toString()
+      // 	.padStart(2, "0")}:${seconds.toString().padStart(2, "0")}`;
+      return `${hours
+        .toString()
+        .padStart(2, "0")}:${minutes
+          .toString()
+          .padStart(2, "0")}:${seconds.toString().padStart(2, "0")}`;
+    },
+    //自动编码
+    automaticCodingMixin(data) {
+      return new Promise(async (resolve) => {
+        let { tableList } = data;
+
+        let roleObj = {};
+        let tableContent = `<table
+	border="0"
+	width="100%"
+	cellpadding="0"
+	cellspacing="0"
+	style="text-align: center"
+>
+	<tbody>
+		<tr>
+			<th>序号</th>
+			<th>开始时间</th>
+			<th>结束时间</th>
+			<th>发言内容</th>
+			<th>时长</th>
+			<th>说话人身份</th>
+			<th>行为编码</th>
+	</tr>`;
+
+        console.log("说话人身份编码开始")
+        // 说话人身份编码
+        while (tableList.some(i => i.role.indexOf("Guest") != -1 && i.role !== '')) {
+          let _ajaxList = tableList.filter(i => i.role.indexOf("Guest") != -1 && i.role !== '').slice(0, 10);
+          console.log(`说话人身份编码:`, _ajaxList)
+          const params = {
+            inputs: {
+              options: "老师,学生",
+              rows: JSON.stringify(
+                _ajaxList.map(i => {
+                  return { content: i.value, role: i.role };
+                })
+              )
+            },
+            response_mode: "blocking",
+            user: this.userId
+          };
+
+          let roleRes = await this.getWavRoleList(params);
+          if (roleRes.data === 1) continue;;
+          let _roleResult = roleRes.data.data.outputs.result;
+          let _numRole = [];
+
+          _roleResult.forEach((txt, index) => {
+            let _oldRole = _ajaxList[index].role;
+            if (_numRole.map(i => i.role).includes(_oldRole)) {
+              let _findIndex = _numRole.findIndex(
+                i => i.role == _oldRole
+              );
+              if (txt == "学生") {
+                _numRole[_findIndex].s += 1;
+              } else if (txt == "老师") {
+                _numRole[_findIndex].t += 1;
+              }
+            } else {
+              if (txt == "学生") {
+                _numRole.push({ role: _oldRole, t: 0, s: 1 });
+              } else if (txt == "老师") {
+                _numRole.push({ role: _oldRole, t: 1, s: 0 });
+              }
+            }
+          });
+
+          //根据数量判断是老师还是学生
+          _numRole.forEach(i => {
+            if (i.t > i.s) {
+              roleObj[i.role] = "老师";
+            } else if (i.t < i.s) {
+              roleObj[i.role] = "学生";
+            }
+          });
+
+          //已经有的role
+          let roleKeys = Object.keys(roleObj);
+
+          tableList.forEach(i => {
+            if (roleKeys.includes(i.role)) {
+              i.role = roleObj[i.role];
+            }
+          });
+        }
+        console.log("说话人身份编码完成")
+
+
+        console.log("说话人行为编码开始")
+        //说话人行为编码
+        while (tableList.some(i => i.code == "" && i.role.indexOf("Guest") == -1 && i.value != "")) {
+          let _ajaxList = tableList.filter(i => i.code == "" && i.role.indexOf("Guest") == -1 && i.value != "").slice(0, 10);
+          console.log(`说话人行为编码:`, _ajaxList)
+          let params = {
+            inputs: {
+              rows: JSON.stringify(
+                _ajaxList.map(i => ({
+                  content: i.value,
+                  role: i.role
+                }))
+              ),
+              options: "老师讲课,老师提问或点名,老师板书或操作,老师评价或反馈,老师其他,学生发言,学生小组活动,学生自主学习,学生汇报分享,学生其他",
+              attention: "- 先根据说话人角色判断,再在对应角色的选项中选择选项\n- 如果没有合适的选项,默认使用`老师其他`或者`学生其他`"
+            },
+            response_mode: "blocking",
+            user: this.userId
+          };
+
+          let _codeRes = await this.getBehavioralCoding(params)
+          if (_codeRes.data === 1) continue;
+          const _codeResult = _codeRes.data.data.outputs.result;
+
+          _ajaxList.forEach((item, index) => {
+            let _findIndex = tableList.findIndex(i => i.index === item.index);
+            if (_findIndex != -1) {
+              tableList[_findIndex].code = _codeResult[index];
+            }
+          })
+        }
+        console.log("说话人行为编码完成")
+
+        tableList.forEach((item, index) => {
+          tableContent += `<tr>
+          <td>${index + 1}</td>
+          <td>${item.startTime}</td>
+          <td>${item.endTime}</td>
+          <td>${item.value}</td>
+          <td>${item.time}</td>
+          <td>${item.role}</td>
+          <td>${item.code}</td>
+        </tr>`
+        })
+        tableContent += `<tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr></tbody></table>`
+
+        var blob = new Blob([tableContent], { type: "text/plain;charset=utf-8" });
+        blob.lastModifiedDate = new Date();
+        blob.name = `classroomObservation.txt`;
+        this.uploadFileMixin(blob).then(upload => {
+          resolve({ editorBarData: { type: "0", url: upload.Location, content: tableContent, tableList: tableList } })
+        })
+      })
+
+    },
+    //文本的自动编码
+    automaticCodingForTextMixin(data) {
+      return new Promise(async (resolve) => {
+        let { tableList } = data;
+
+        let roleObj = {};
+        let tableContent = `<table
+	border="0"
+	width="100%"
+	cellpadding="0"
+	cellspacing="0"
+	style="text-align: center"
+>
+	<tbody>
+		<tr>
+			<th>序号</th>
+			<th>开始时间</th>
+			<th>结束时间</th>
+			<th>发言内容</th>
+			<th>时长</th>
+			<th>说话人身份</th>
+			<th>行为编码</th>
+	</tr>`;
+
+        console.log("说话人身份编码开始")
+        // 说话人身份编码
+        while (tableList.some(i => i.role == '')) {
+          let _ajaxList = tableList.filter(i => i.role == '').slice(0, 10);
+          console.log(`说话人身份编码:`, _ajaxList)
+          const params = {
+            inputs: {
+              options: "老师,学生",
+              rows: JSON.stringify(
+                _ajaxList.map(i => {
+                  return { content: i.value, role: i.role };
+                })
+              )
+            },
+            response_mode: "blocking",
+            user: this.userId
+          };
+
+          let roleRes = await this.getWavRoleList(params);
+          if (roleRes.data === 1) continue;;
+          let _roleResult = roleRes.data.data.outputs.result;
+
+          _ajaxList.forEach((item, index) => {
+            let _findIndex = tableList.findIndex(i => i.index === item.index);
+            if (_findIndex != -1) {
+              tableList[_findIndex].role = _roleResult[index];
+            }
+          })
+          // let _numRole = [];
+
+          // _roleResult.forEach((txt, index) => {
+          //   let _oldRole = _ajaxList[index].role;
+          //   if (_numRole.map(i => i.role).includes(_oldRole)) {
+          //     let _findIndex = _numRole.findIndex(
+          //       i => i.role == _oldRole
+          //     );
+          //     if (txt == "学生") {
+          //       _numRole[_findIndex].s += 1;
+          //     } else if (txt == "老师") {
+          //       _numRole[_findIndex].t += 1;
+          //     }
+          //   } else {
+          //     if (txt == "学生") {
+          //       _numRole.push({ role: _oldRole, t: 0, s: 1 });
+          //     } else if (txt == "老师") {
+          //       _numRole.push({ role: _oldRole, t: 1, s: 0 });
+          //     }
+          //   }
+          // });
+
+          //根据数量判断是老师还是学生
+          // _numRole.forEach(i => {
+          //   if (i.t > i.s) {
+          //     roleObj[i.role] = "老师";
+          //   } else if (i.t < i.s) {
+          //     roleObj[i.role] = "学生";
+          //   }
+          // });
+
+          //已经有的role
+          // let roleKeys = Object.keys(roleObj);
+
+          // tableList.forEach(i => {
+          //   if (roleKeys.includes(i.role)) {
+          //     i.role = roleObj[i.role];
+          //   }
+          // });
+        }
+        console.log("说话人身份编码完成")
+
+        console.log("allRole",tableList.map(i=>i.role))
+
+        console.log("说话人行为编码开始")
+        //说话人行为编码
+        while (tableList.some(i => i.code == "" && i.role.indexOf("Guest") == -1 && i.value != "")) {
+          let _ajaxList = tableList.filter(i => i.code == "" && i.role.indexOf("Guest") == -1 && i.value != "").slice(0, 10);
+          console.log(`说话人行为编码:`, _ajaxList)
+          let params = {
+            inputs: {
+              rows: JSON.stringify(
+                _ajaxList.map(i => ({
+                  content: i.value,
+                  role: i.role
+                }))
+              ),
+              options: "老师讲课,老师提问或点名,老师板书或操作,老师评价或反馈,老师其他,学生发言,学生小组活动,学生自主学习,学生汇报分享,学生其他",
+              attention: "- 先根据说话人角色判断,再在对应角色的选项中选择选项\n- 如果没有合适的选项,默认使用`老师其他`或者`学生其他`"
+            },
+            response_mode: "blocking",
+            user: this.userId
+          };
+
+          let _codeRes = await this.getBehavioralCoding(params)
+          if (_codeRes.data === 1) continue;
+          const _codeResult = _codeRes.data.data.outputs.result;
+
+          _ajaxList.forEach((item, index) => {
+            let _findIndex = tableList.findIndex(i => i.index === item.index);
+            if (_findIndex != -1) {
+              tableList[_findIndex].code = _codeResult[index];
+            }
+          })
+        }
+        console.log("说话人行为编码完成")
+
+        tableList.forEach((item, index) => {
+          tableContent += `<tr>
+          <td>${index + 1}</td>
+          <td>${item.startTime}</td>
+          <td>${item.endTime}</td>
+          <td>${item.value}</td>
+          <td>${item.time}</td>
+          <td>${item.role}</td>
+          <td>${item.code}</td>
+        </tr>`
+        })
+        // tableContent += `<tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr></tbody></table>`
+
+        var blob = new Blob([tableContent], { type: "text/plain;charset=utf-8" });
+        blob.lastModifiedDate = new Date();
+        blob.name = `classroomObservation.txt`;
+        this.uploadFileMixin(blob).then(upload => {
+          resolve({ editorBarData: { type: "0", url: upload.Location, content: tableContent, tableList: tableList } })
+        })
+      })
+
+    },
+    getWavRoleList(params) {
+      return new Promise((resolve, reject) => {
+        this.ajax
+          .post("https://dify.cocorobo.cn/v1/workflows/run?key=role", params)
+          .then(res => {
+            resolve(res);
+          })
+          .catch(err => {
+            console.log("获取说话人身份失败", err)
+            resolve({ data: 1 })
+            // reject(err);
+          });
+      });
+    },
+    getBehavioralCoding(params) {
+      return new Promise((resolve, reject) => {
+        this.ajax
+          .post("https://dify.cocorobo.cn/v1/workflows/run?key=code", params)
+          .then(res => {
+            resolve(res);
+          })
+          .catch(err => {
+            console.log("获取说话人编码失败", err)
+            resolve({ data: 1 })
+          });
+      });
+    },
+    //m4a转wav
+    audioToWavMixin(fileObj) {
+      return new Promise((resolve) => {
+        const audioContext = new (window.AudioContext || window.webkitAudioContext)();
+
+        const reader = new FileReader();
+
+        reader.onload = (e)=>{
+          const arrayBuffer = e.target.result;
+
+          // 解码音频数据
+          audioContext.decodeAudioData(arrayBuffer)
+            .then(audioBuffer => {
+
+              let wavBlob = this.bufferToWav(audioBuffer);
+
+              let _wavFile = new File([wavBlob], "audio.wav", {
+                type: "audio/wav"
+              })
+              // 在控制台输出WAV文件对象
+              resolve({ data: _wavFile })
+              console.log('转换后的WAV文件对象:', _wavFile);
+            })
+            .catch(err => {
+              resolve({ data: 1, err: err })
+            });
+        };
+        reader.onerror = (e) => {
+          resolve({ data: 1, err: e })
+        };
+
+        reader.readAsArrayBuffer(fileObj);
+      })
+    },
+    getQrCodeImageSrc(url) {
+      return new Promise((resolve, reject) => {
+        let qrcode = new QRCode(document.createElement("div"), {
+          text: url, // 需要转换为二维码的内容
+          width: 150,
+          height: 150,
+          colorDark: "#000000",
+          colorLight: "#ffffff",
+          correctLevel: QRCode.CorrectLevel.H
+        });
+        let img = qrcode._el.getElementsByTagName("img")[0];
+        img.onload = () => {
+          resolve(img.src);
+        };
+      });
+    },
+    getEChartsImageSrc(option) {
+      return new Promise(resolve => {
+        try {
+          let hiddenDiv = document.createElement("div");
+        hiddenDiv.style.width = "400px";
+        hiddenDiv.style.height = "400px";
+        hiddenDiv.style.position = "absolute";
+        hiddenDiv.style.left = "-9999px"; // 隐藏div
+        document.body.appendChild(hiddenDiv);
+
+        // 初始化图表
+        let myChart = echarts.init(hiddenDiv);
+
+        // 设置图标配置
+        myChart.setOption(option);
+          // console.log("词云图???",option)
+        myChart.on("finished", () => {
+          // 获取图表的图片
+          let base64Image = myChart.getDataURL({
+            type: "png", // 图片格式
+            pixelRatio: 0.9, // 图像清晰度
+            backgroundColor: "#fff" // 背景颜色
+          });
+
+          resolve(base64Image);
+          // 清除隐藏的div和图表实例
+          document.body.removeChild(hiddenDiv);
+          myChart.dispose();
+        });
+        } catch (error) {
+          console.log(error,"error")
+          resolve("#")
+        }
+      });
+    },
+    getImageSrcToBase64(src) {
+      return new Promise((resolve, reject) => {
+        const image = new Image();
+        image.src = src;
+        image.onload = () => {
+          const canvas = document.createElement("canvas");
+          canvas.width = image.naturalWidth;
+          canvas.height = image.naturalHeight;
+          canvas.style.width = `${canvas.width / window.devicePixelRatio}px`;
+          canvas.style.height = `${canvas.height / window.devicePixelRatio}px`;
+
+          const context = canvas.getContext("2d");
+          context.drawImage(image, 0, 0);
+          const base64 = canvas.toDataURL("image/png");
+          resolve(base64);
+        };
+      });
+    },
+    getEChartsSpectrogramImage(data) {
+      return new Promise(resolve => {
+        try {
+          let canvas = document.createElement("canvas");
+          let ctx = canvas.getContext("2d");
+          canvas.width = 600 * window.devicePixelRatio;
+          canvas.height = 200 * window.devicePixelRatio;
+          // 缩放绘图上下文
+          ctx.scale(1, 1);
+
+          let canvasWidth = canvas.width;
+          let canvasWidth2 = canvasWidth - 20;
+          let canvasHeight = canvas.height;
+          ctx.imageSmoothingEnabled = false;
+          ctx.lineWidth = 1;
+
+          // 设置颜色和文字
+          const teacherColor = "#5470C6"; // 老师的颜色
+          const studentColor = "#91CC75"; // 学生的颜色
+          const fontSize = 14; //字体大小
+
+          ctx.fillStyle = teacherColor;
+          this.drawRoundedRect(ctx, 0, canvasHeight - 20, 20, 15, 4);
+          // ctx.fillRect(0, canvasHeight - 20, 25, 20);
+          ctx.fillStyle = "black";
+          ctx.font = `${fontSize}px serif`;
+          ctx.fillText("老师", 28, canvasHeight - 7);
+
+          ctx.fillStyle = studentColor;
+          // ctx.fillRect(100, canvasHeight - 20, 25, 20);
+          this.drawRoundedRect(ctx, 100, canvasHeight - 20, 20, 15, 4);
+          ctx.fillStyle = "black";
+          ctx.font = `${fontSize}px serif`;
+          ctx.fillText("学生", 128, canvasHeight - 7);
+          let sum = data.data.reduce((pre, cur) => (pre += cur.value), 0);
+          // 当前x位置的起始点
+          let currentX = 10;
+          // 计算并绘制每个区域
+          data.data.forEach(i => {
+            const segmentWidth = parseFloat(
+              (i.value / (sum / canvasWidth2)).toFixed(2)
+            );
+            ctx.fillStyle = i.type == 0 ? teacherColor : studentColor;
+            ctx.fillRect(currentX, 20, segmentWidth, canvasHeight - 100);
+
+            // 更新x位置
+            currentX += segmentWidth;
+          });
+
+          // 绘制红色垂直线(指定位置)
+          // ctx.strokeStyle = "red";
+          // ctx.lineWidth = 2;
+
+          // data.breakpoint.forEach(i => {
+          //   const breakpointPo = parseFloat(
+          //     (i / (sum / canvasWidth2)).toFixed(2)
+          //   );
+          //   ctx.beginPath();
+          //   ctx.moveTo(breakpointPo, 10);
+          //   ctx.lineTo(breakpointPo, canvasHeight - 70);
+          //   ctx.stroke();
+          // });
+
+          let interval = parseFloat((300 / (sum / canvasWidth2)).toFixed(2));
+          //绘制竖线
+          let _lastI = 0;
+          //绘制竖线
+          for (let i = 0; i < canvasWidth2; i += interval) {
+            ctx.beginPath();
+            ctx.strokeStyle = "#BFBFBF";
+            ctx.moveTo(i == 0 ? 10 : i, canvasHeight - 70);
+            ctx.lineTo(i == 0 ? 10 : i, canvasHeight - 55);
+            ctx.stroke();
+            ctx.fillStyle = "#868686";
+            let timeLabel = (((i / canvasWidth2) * sum) / 60).toFixed(0); // 时间标识计算
+            ctx.font = `${fontSize}px serif`;
+            if (i == 0) {
+              ctx.fillText(`${timeLabel}min`, i + 10, canvasHeight - 40);
+            } else if (i + interval >= canvasWidth2) {
+              ctx.fillText(`${timeLabel}min`, i - 20, canvasHeight - 40);
+            } else {
+              ctx.fillText(`${timeLabel}min`, i - 15, canvasHeight - 40);
+            }
+            _lastI = i;
+          }
+          if (canvasWidth2 - _lastI >60) {
+            ctx.beginPath();
+            ctx.strokeStyle = "#BFBFBF";
+            ctx.moveTo(canvasWidth2 + 10, canvasHeight - 70);
+            ctx.lineTo(canvasWidth2 + 10, canvasHeight - 55);
+            ctx.stroke();
+            ctx.fillStyle = "#868686";
+            let timeLabel = (sum / 60).toFixed(0); // 时间标识计算
+            ctx.font = `${fontSize}px serif`;
+            ctx.fillText(`${timeLabel}min`, canvasWidth2 - 20, canvasHeight - 40);
+          }
+
+          ctx.beginPath();
+          ctx.strokeStyle = "#BFBFBF";
+          ctx.moveTo(10, canvasHeight - 55);
+          ctx.lineTo(canvasWidth2 + 10, canvasHeight - 55);
+          ctx.stroke();
+
+          // 将 canvas 转换为 base64 格式的图片地址
+          const base64Image = canvas.toDataURL("image/png");
+          resolve(base64Image);
+        } catch (e) {
+          console.log(e);
+          resolve("#");
+        }
+      });
+    },
+    getEChartsechartsRTCHImage(data) {
+      return new Promise(resolve => {
+        try {
+          let canvas = document.createElement("canvas");
+          let ctx = canvas.getContext("2d");
+          canvas.width = 300 * window.devicePixelRatio;
+          canvas.height = 350 * window.devicePixelRatio;
+          // 缩放绘图上下文
+          ctx.scale(1, 1);
+
+          let canvasWidth = canvas.width;
+          let canvasHeight = canvas.height;
+          let fontColor = "#1a7ad3";
+          ctx.imageSmoothingEnabled = false;
+          ctx.lineWidth = 1;
+          const img = new Image();
+          img.src = require("../../../../assets/icon/classroomObservation/rt-ch_echarts2.svg");  //ch_echarts2
+          img.onload = () => {
+            ctx.drawImage(img, 0, 0, canvasWidth, canvasWidth);
+            ctx.beginPath();
+            let _showWidth = canvasWidth-((canvasWidth/8.8)*2)
+            ctx.fillStyle = fontColor;
+            ctx.arc((canvasWidth/8.8)+(_showWidth*parseFloat(data.RT)),(canvasWidth/8.8)+_showWidth-(_showWidth*parseFloat(data.CH)),4,0,2*Math.PI);
+            ctx.lineWidth = 0.5; // 设置边框大小
+            // ctx.arc((canvasWidth*parseFloat(this.data.RT))+(canvasWidth/8.8),(canvasWidth-(canvasWidth*parseFloat(this.data.CH))+(canvasWidth/8.8)), 4, 0, 2 * Math.PI);
+            ctx.fill();
+            ctx.stroke();
+            ctx.fillStyle = fontColor;
+            ctx.font = "italic bold 24px Arial";
+            const text = `RT=${data.RT}    CH=${data.CH}`;
+            const textWidth = ctx.measureText(text).width;
+            ctx.fillText(text, (canvasWidth - textWidth) / 2, canvasHeight - canvasWidth / 12);
+            const base64Image = canvas.toDataURL("image/png");
+            resolve(base64Image);
+          };
+        } catch (e) {
+          console.log(e);
+          resolve("#");
+        }
+      });
+    },
+    drawRoundedRect(ctx, x, y, width, height, radius) {
+      // 限制 radius 的最大值,防止它超过矩形的宽度或高度的一半
+      const actualRadius = Math.min(radius, width / 2, height / 2);
+
+      ctx.beginPath();
+      ctx.moveTo(x + actualRadius, y); // 起点,矩形顶部的左侧
+
+      // 右上角的弧线
+      ctx.arcTo(x + width, y, x + width, y + height, actualRadius);
+
+      // 右下角的弧线
+      ctx.arcTo(x + width, y + height, x, y + height, actualRadius);
+
+      // 左下角的弧线
+      ctx.arcTo(x, y + height, x, y, actualRadius);
+
+      // 左上角的弧线
+      ctx.arcTo(x, y, x + width, y, actualRadius);
+
+      ctx.closePath();
+      ctx.fill(); // 填充颜色
+    },
+    getDocFnPromise(task){
+      console.log("处理👉",task)
+      return new Promise(async (resolve)=>{
+        try {
+          let bmData = task.jsonData.baseMessage;
+        const md = new markdownIt();
+        let dataList = task.jsonData.analysisList;
+        let tagList = task.jsonData.tagList?task.jsonData.tagList:[
+          { value: 0, name: "通用课堂分析", loading: false },
+          { value: 1, name: "学科课堂分析", loading: false },
+          { value: 2, name: "扩展分析", loading: false }
+        ];
+        let showBrief = true;
+
+        // console.log(tagList,"tagList")
+
+        tagList.forEach(i => (i.dataList = []));
+        let url = `https://beta.cloud.cocorobo.cn/aigpt/#/classroom_observation_board?tid=${task.jsonData.createId}`;
+        const qRCodeSrc = await this.getQrCodeImageSrc(url);
+
+        dataList.sort((a, b) => a.tIndex - b.tIndex);
+        dataList.forEach(i1 => {
+          tagList.forEach(i2 => {
+            if (i2.value == i1.Type) {
+              i2.dataList.push(i1);
+            }
+          });
+        });
+
+        let directoryHtml = `<div style="margin-bottom:1in"><div style="text-align:center;font-size:20pt;margin-bottom:0.5in">目录</div>`;
+
+        let analysisHtml = ``;
+
+        // console.log("开始处理文件")
+        for (let c = 0; c < tagList.length; c++) {
+          // console.log(tagList[c],"tagList[c]")
+          let i = tagList[c];
+          let dire = `<div>`;
+          let tagHtml = `<div style="margin-bottom:0.5in">`;
+          if (i.value == 0) {
+            i.dataList = i.dataList.filter(i2 => i2.tIndex != 2);
+          }
+          i.dataList.sort((a, b) => a.tIndex - b.tIndex);
+          tagHtml += `<h1 style="font-size:16pt;margin-bottom:-1in">${
+            this.tag[i.value]
+          }、${i.name}</h1>`;
+          dire += `<p style="font-size:14pt;margin-bottom:-0.8in">${
+            this.tag[i.value]
+          }、${i.name}</p>`;
+
+          for (let d = 0; d < i.dataList.length; d++) {
+            let i2 = i.dataList[d];
+            // console.log(i.dataList[d],"i.dataList[d]")
+            let i2Index = d;
+            tagHtml += `<h2 style="font-size:14pt;margin-bottom:-1in">${i2Index +
+              1}、${
+              i2.jsonData.anotherName
+                ? i2.jsonData.anotherName
+                : i2.jsonData.name
+            }</h2>`;
+            dire += `<p style="font-size:11pt;margin-bottom:-0.8in;margin-left:0.1in">${i2Index +
+              1}、${
+              i2.jsonData.anotherName
+                ? i2.jsonData.anotherName
+                : i2.jsonData.name
+            }</p>`;
+            if (showBrief && i2.jsonData.result) {
+              tagHtml += `<p style="font-size:10.5pt;font-style:italic;margin-bottom:-0.7in;color:#6b798e">${i2.jsonData.result}</p>`;
+            }
+            if (i2.jsonData.eChartData) {
+              tagHtml += `<div style="width:100vw;padding:70%;box-sizing: border-box;text-align:center"><img style="margin:auto" src="${await this.getEChartsImageSrc(
+                i2.jsonData.eChartData
+              )}"/></div>`;
+            }
+
+            if (i2.jsonData.spectrogramData) {
+              tagHtml += `<div style="width:100vw;padding:70%;box-sizing: border-box;text-align:center"><img style="margin:auto" src="${await this.getEChartsSpectrogramImage(
+                i2.jsonData.spectrogramData
+              )}"/></div>`;
+              // console.log()
+            }
+
+            if (i2.jsonData.CH && i2.jsonData.RT) {
+              tagHtml += `<div style="width:100vw;text-align:center;padding:70%;box-sizing: border-box;"><img style="margin:auto" src="${await this.getEChartsechartsRTCHImage(
+                {
+                  RT: i2.jsonData.RT,
+                  CH: i2.jsonData.CH
+                }
+              )}"/></div>`;
+            }
+
+            let _content = md.render(i2.jsonData.content).replace(/<p>/g, '').replace(/<\/p>/g, '').replace(/<strong>/g, '<span style="font-weight: bold;">').replace(/<\/strong>/g, '</span>');
+            tagHtml += `<p style="font-size:10.5pt;margin-bottom:-0.5in">${_content}</p>`;
+          }
+          tagHtml += "</div>";
+          dire += "</div>";
+          analysisHtml += tagHtml;
+          directoryHtml += dire;
+        }
+
+        // console.log("处理分析完成")
+        directoryHtml += "</div>";
+        let _html = `
+			      <div>
+			      	<p style="width:100vw;margin-bottom:1.5in">*分析结果仅供参考</p>
+			      	<p style="font-size:28pt;width:100vw;text-align:center;">课堂观察报告</p>
+			      	<p style="font-size:10pt;width:100vw;text-align:center;margin-bottom:0.6in">报告生成时间:${new Date().toLocaleString()}</p>
+			      	<div style="font-size:16pt;width:100vw;text-align:center;margin-bottom:1in">
+			      		<p style="font-size:20pt;margin-bottom:0.7in">《${bmData.courseName}》</p>
+			      		<p style="margin-bottom:-1in">授课老师:${
+                  bmData.teacherName ? bmData.teacherName : "未填写"
+                }</p>
+			      		<p style="margin-bottom:-1in">授课年级:${
+                  bmData.grade ? bmData.grade : "未填写"
+                }</p>
+			      		<p style="margin-bottom:-1in">授课科目:${
+                  bmData.subject ? bmData.subject : "未填写"
+                }</p>
+			      		<p style="margin-bottom:-1in">授课时间:${
+                  bmData.time ? bmData.time : "未填写"
+                }</p>
+			      	</div>
+			      	<div style="font-size:16pt;width:100vw;text-align:center;margin-bottom:0.5in">
+			      		<img src="${qRCodeSrc}" style="width:150px;height:150px;margin:auto;"/>
+			      		<p>扫码查看网页版</p>
+			      	</div>
+			      </div>
+			      ${directoryHtml}
+
+			      <div>
+			      	${analysisHtml}
+			      </div>
+			      `;
+
+            const content = `<!DOCTYPE html>
+            <html xmlns:v='urn:schemas-microsoft-com
+            :vml'xmlns:o='urn:schemas-microsoft-com:office
+            :office'xmlns:w='urn:schemas-microsoft-com:office
+            :word'xmlns:m='http://schemas.microsoft.com/office/2004/12/omml'
+            xmlns='http://www.w3.org/TR/REC-html40'
+            xmlns='http://www.w3.org/1999/xhtml'>
+            <head>
+                <meta charset="UTF-8">
+                <meta http-equiv="X-UA-Compatible" content="IE=edge">
+                <meta name="viewport" content="width=device-width, initial-scale=1.0">
+                <title>《${bmData.courseName}》课堂观察报告</title>
+                <style>
+			      			*{
+			      				font-family: '宋体';
+			      				margin:0;
+			      				padding:0;
+			      				line-height:1;
+			      			}
+                  table {
+                    border-collapse: collapse; /* 折叠边框 */
+                    width: 100%;
+			      				font-size:10.5pt;
+                  }
+                  th, td {
+                    border: 1px solid black; /* 线条样式 */
+                    padding: 8px;
+                    text-align: left;
+			      				font-size:10.5pt;
+                  }
+			      			ol,ul{
+			      				margin:0;
+			      				padding:0;
+			      				margin-right:-1in;
+			      			}
+			      			li{
+			      				position: relative;
+                    padding-left: 1.5em; /* 控制项目符号与文本的距离 */
+                    margin-bottom: 0.5em;
+                    text-indent: -1.5em; /* 负缩进使文本与符号对齐 */
+                    mso-special-format: bullet;
+                    margin-left: 0;
+                    padding-left: 10pt;
+                    text-indent: -10pt;
+                    mso-style-name: "Normal";
+                    mso-style-priority: 99;
+                    mso-style-unhide: no;
+                    mso-style-qformat: yes;
+                    mso-style-parent: "";
+			      			}
+			      			p{
+			      				margin:0;
+			      				padding:0
+			      			}
+                </style>
+            </head>
+            <body>
+            ${_html}
+            </body>
+            </html>`;
+          // console.log("生成blob",task)
+          // debugger
+          let blob = htmlDocx.asBlob(content);
+          const file = new File([blob], `${bmData.courseName}课堂观察报告.docx`, {
+            type: ".docx",
+            lastModified: new Date().getTime()
+          });
+          resolve(file)
+        } catch (error) {
+          console.log("error",error,task)
+          resolve(error)
+        }
+      })
+    },
+    getFileLastUpdateTime(file){
+      console.log(file)
+      if(!file)return new Date().toLocaleDateString().replaceAll("/","-") + ' ' + new Date().toLocaleTimeString();
+
+      const lastModifiedTimestamp  = file.lastModified;
+      console.log("lastModifiedTimestamp",lastModifiedTimestamp)
+      if(!lastModifiedTimestamp)return new Date().toLocaleDateString().replaceAll("/","-") + ' ' + new Date().toLocaleTimeString();
+      else return new Date(lastModifiedTimestamp).toLocaleDateString().replaceAll("/","-") + ' ' + new Date().toLocaleTimeString();
+    },
+    //获取词云图
+    getWordCloudMapMixin(data){
+      return new Promise((resolve)=>{
+        const _msg = `NOTICE
+        Language: Please use the same language as the user requirement, if the user speaks Chinese, the specific text of your answer should also be in Chinese.
+        ATTENTION: Use '##' to SPLIT SECTIONS, not '#'. Output format carefully referenced "Format example".
+        Instruction: Based on the context, follow "Format example", write content.
+
+        ## 任务
+
+        请基于以下课堂实录文本(大约5000字),提炼出20-30个关键词,用于绘制词云图。请给出相应的关键词,关键词出现的频次,词云图上的大小。请确保输出的关键字准确反映课堂实录的主要内容和主题。
+
+        ## 要求
+
+        1. **提取关键词**:从提供的课堂实录文本中提取出20-30个最具代表性的关键字。关键词应该涵盖课堂实录中的主要概念、重要术语和核心主题。尽量选择多样化的关键词,避免过于集中在某一个主题或概念上。
+        2. **词频统计**:计算每个关键字在文本中出现的频率。
+        3. **词汇大小**:根据词频数量,确定每个关键字在词云图中的大小。词频越高,词汇大小数值越大,数值范围1-100。
+        4. **输出格式**:输出结果应包含每个关键字、对应的词频数量以及词汇大小数值。
+
+        ## 输出格式
+
+        ### 输出格式
+
+        [
+        	{"value":1,"name":"氯化钠","textStyle":{"color":"#ee7959"}},
+        	{"value":2,"name":"溶液","textStyle":{"color":"#db9b34"}},
+        	{"value":1,"name":"实验","textStyle":{"color":"#9d9d82"}},
+        	{"value":3,"name":"质量分数","textStyle":{"color":"#ea5514"}},
+        	{"value":1,"name":"溶质","textStyle":{"color":"#c8161d"}},
+        	{"value":2,"name":"氢氧化钠","textStyle":{"color":"#e60012"}},
+        	{"value":1,"name":"溶解度","textStyle":{"color":"#1e2732"}},
+        	{"value":4,"name":"饱和溶液","textStyle":{"color":"#e3adb9"}}
+        ]
+
+        请仅仅输出表头,输出关键词和相应的内容,无需其它任何说明文字。
+        冒号、逗号等符号均使用英文字符。
+
+        ### 输出示例
+
+        [
+        	{"value":1,"name":"氯化钠","textStyle":{"color":"#ee7959"}},
+        	{"value":2,"name":"溶液","textStyle":{"color":"#db9b34"}},
+        	{"value":1,"name":"实验","textStyle":{"color":"#9d9d82"}},
+        	{"value":3,"name":"质量分数","textStyle":{"color":"#ea5514"}},
+        	{"value":1,"name":"溶质","textStyle":{"color":"#c8161d"}},
+        	{"value":2,"name":"氢氧化钠","textStyle":{"color":"#e60012"}},
+        	{"value":1,"name":"溶解度","textStyle":{"color":"#1e2732"}},
+        	{"value":4,"name":"饱和溶液","textStyle":{"color":"#e3adb9"}}
+        ]
+
+        ## 课堂实录
+        ${data}
+        `;
+
+        const _uuid = uuidv4();
+				let params = {
+					temperature: 0,
+					max_tokens: 4096,
+					top_p: 1,
+					frequency_penalty: 0,
+					presence_penalty: 0,
+					messages: [{ role: "user", content: _msg }],
+					uid: _uuid,
+					mind_map_question: "",
+					stream: false,
+					model: "gpt-4o-2024-11-20",
+				};
+
+        this.ajax
+					.post("https://gpt4.cocorobo.cn/chat", params)
+					.then((res) => {
+						let _data = res.data.FunctionResponse.choices[0];
+						let _jsonData = _data.message.content;
+						_jsonData = _jsonData.replaceAll("```json", "").replaceAll("```", "");
+						let _result = JSON.parse(_jsonData);
+            resolve({
+              tooltip: {
+                show: false,
+              },
+              series: [
+                {
+                  type: 'wordCloud',
+                  sizeRange: [14, 38],
+                  rotationRange: [0, 0],
+                  keepAspect:false,
+                  shape: 'circle',
+                  left: 'center',
+                  top: 'center',
+                  right: null,
+                  bottom: null,
+                  width: '90%',
+                  height: '90%',
+                  rotationRange: [-90, 90],
+                  rotationStep: 45,
+                  data: _result,
+                },
+              ],
+            })
+          })
+					.catch((e) => {
+						console.log(e);
+						resolve(0)
+
+					})
+
+      })
+    }
+  }
+};

+ 21 - 1
src/components/pages/components/appDialog.vue

@@ -51,6 +51,19 @@
                   :value="item.value"
                 ></el-option>
               </el-select>
+              <el-select
+                v-model="AreaType"
+                placeholder="请选择状态"
+                @change="changeSelectType"
+                style="width: 110px;margin-right: 10px;"
+              >
+                <el-option
+                  v-for="item in AreaSelect"
+                  :key="item.value"
+                  :label="item.label"
+                  :value="item.value"
+                ></el-option>
+              </el-select>
             </div>
 
             <div>
@@ -440,6 +453,12 @@ export default {
         { value: "agent", label: "智能体" },
         { value: "workflow", label: "工作流" }
       ],
+      AreaType:'',
+      AreaSelect: [
+        { value: "cn", label: "cn" },
+        { value: "hk", label: "hk" },
+        { value: "com", label: "com" }
+      ],
       userId: this.$route.query["userid"],
       org: this.$route.query["org"],
       oid: this.$route.query["oid"],
@@ -503,6 +522,7 @@ export default {
       this.type = type;
       const { data } = await this.ajax.get(this.$store.state.api + 'select_oidArea', { oid: this.oid });
       this.region = data[0][0].area;
+      this.AreaType = data[0][0].area;
       this.getAppStoreControl().then(_ => {
         this.getData();
         this.getTypeList();
@@ -614,7 +634,7 @@ export default {
         label: this.selectLabel, //应用的标签搜索
         type: this.showType, //应用的类型
         juri: this.selectJuri, //应用权限 1:我的  2:组织内  3:所有人
-        stand: this.region ? this.region : "cn", //cn站还是hk站
+        stand: this.AreaType ? this.AreaType : "cn", //cn站还是hk站
         status: this.statusType,
         exportType:
           this.controlsObj &&

+ 2 - 1
src/components/pages/newCourse/addCourse.vue

@@ -1020,6 +1020,7 @@
                               "
                               style="margin-top: 20px"
                             >
+                              <div style="margin-bottom: 5px;font-size: 14px;">提示:如输入数学公式需加$符号包裹。例:$a^2 + b^2 = c^2$</div>
                               <editor-bar
                                 class="addEditor"
                                 style="margin: 0"
@@ -18553,7 +18554,7 @@ ol {
 
 .addEditor >>> .text {
   height: auto;
-  min-height: 100px;
+  min-height: 200px;
 }
 
 .addEditor >>> .w-e-text-container {

+ 27 - 6
src/components/pages/sassPlatform/index.vue

@@ -47,6 +47,7 @@ export default {
       oid: this.$route.query.oid,
       org: this.$route.query.org,
       role:this.$route.query.role,
+      userData:null
     }
   },
   computed:{
@@ -85,11 +86,12 @@ export default {
             i.open = false;
 
             if(i.id=='e18d88b3-e828-11ef-b508-005056924926'){
-              _children.push(...[
-                {name:"年度考核",type:"annualAssessment",navIndex:`${index}-1`},
-                {name:"考核数据可视化",type:"evaluationDataVisualization",navIndex:`${index}-2`},
-                ]
-              )
+
+
+              if(this.userData && this.userData.type === 1 && this.userData.role == 1){
+                _children.push({name:"年度考核",type:"annualAssessment",navIndex:`${index}-1`})
+              }
+              _children.push({name:"考核数据可视化",type:"evaluationDataVisualization",navIndex:`${index}-2`})
               i.open = true;
             }
             i.children = _children;
@@ -123,14 +125,33 @@ export default {
           "&back=sass"
       );
     },
+    getUser(uid) {
+      return new Promise(resolve => {
+        let params = { uid: uid };
+        this.ajax
+          .get(this.$store.state.api + "getUser", params)
+          .then(res => {
+            let data = res.data[0][0];
+            this.userData = data;
+            resolve()
+          })
+          .catch(err => {
+            console.error(err);
+            resolve()
+          });
+      });
+    },
   },
   mounted(){
-    this.getNavType().then(_=>{
+    this.getUser(this.userId).then(()=>{
+      this.getNavType().then(_=>{
       if(this.navList.length>0){
         let _data = this.navList[0].children[0];
         this.changeNavIndex(_data.navIndex,_data)
       }
     });
+    })
+
   }
 }
 </script>

+ 2 - 2
src/components/pages/sz/dataBoardArea/dataCenter/chartList/courseRank/index.vue

@@ -12,9 +12,9 @@
 					<template slot-scope="scope">
 						<div>
 							<!-- {{ Math.abs(scope.row.compare) }} -->
-							<div v-if="scope.row.compare>0" class="el-icon-top compareDown">{{ Math.abs(scope.row.compare) }}</div>
+							<div v-if="scope.row.compare>0" class="el-icon-bottom compareDown">{{ Math.abs(scope.row.compare) }}</div>
 							<div v-if="scope.row.compare==0">-</div>
-							<div v-if="scope.row.compare<0" class="el-icon-bottom compareUp">{{ Math.abs(scope.row.compare) }}</div>
+							<div v-if="scope.row.compare<0" class="el-icon-top compareUp">{{ Math.abs(scope.row.compare) }}</div>
 						</div>
 					</template>
         </el-table-column>

+ 2 - 2
src/components/pages/sz/dataBoardCity/dataCenter/chartList/courseRank/index.vue

@@ -12,9 +12,9 @@
 					<template slot-scope="scope">
 						<div>
 							<!-- {{ Math.abs(scope.row.compare) }} -->
-							<div v-if="scope.row.compare>0" class="el-icon-top compareDown">{{ Math.abs(scope.row.compare) }}</div>
+							<div v-if="scope.row.compare>0" class="el-icon-bottom compareDown">{{ Math.abs(scope.row.compare) }}</div>
 							<div v-if="scope.row.compare==0">-</div>
-							<div v-if="scope.row.compare<0" class="el-icon-bottom compareUp">{{ Math.abs(scope.row.compare) }}</div>
+							<div v-if="scope.row.compare<0" class="el-icon-top compareUp">{{ Math.abs(scope.row.compare) }}</div>
 						</div>
 					</template>
         </el-table-column>

+ 29 - 2
src/components/pages/test/check/index.vue

@@ -2,8 +2,8 @@
   <div class="pb_content" style="background: #f0f2f5" v-loading="loading">
     <div class="pb_content_body" style="position: relative; margin: 0">
       <div class="right">
-        <div class="courseTop">
-          <div class="stepsNav">
+        <div class="courseTop" >
+          <div class="stepsNav" v-if="!examineId">
             <el-breadcrumb separator-class="el-icon-arrow-right">
               <el-breadcrumb-item
               v-if="!back"
@@ -1379,8 +1379,10 @@
                   <el-button
                     @click="getTest(scope.row)"
                     type="primary"
+                    v-if="(examineData && examineData.type == 1) || !examineData"
                     size="small"
                     >查看</el-button
+
                   >
                   <!-- <el-button @click="setWordHtml(scope.row)" type="primary" size="small">导出答题信息</el-button>
 									<el-button @click="setWordTemplate(scope.row)" type="primary" size="small">word导出</el-button> -->
@@ -1388,6 +1390,7 @@
                     @click="deleteTest(scope.row.id)"
                     type="primary"
                     size="small"
+                     v-if="(examineData && examineData.type == 1) || !examineData"
                     >删除</el-button
                   >
                 </template>
@@ -1904,6 +1907,8 @@ export default {
       peopleId: this.$route.query.peopleId,
       back:this.$route.query.back,
       screenWidth: window.innerWidth,
+      examineId: this.$route.query.examineId,
+      tType: this.$route.query.tType,
       isDesktop: false,
       aiChatV: false,
       title: "",
@@ -1959,6 +1964,7 @@ export default {
       // fileLoadNum: [],
       // infoprogress: [],
       teaType: [],
+      examineData:null,
     };
   },
   watch: {},
@@ -3852,6 +3858,23 @@ ${JSON.stringify(item.array)}
             this.$message.error("生成词云图失败")
           })
 		},
+    getExamineData(){
+      let params = {
+        uid: this.peopleId,
+        type:this.tType,
+        tid:this.examineId,
+      }
+
+      this.ajax.get(this.$store.state.api +"getTestExamineByUserId2",params).then(res=>{
+        // this.examineData = res.data[0][0];
+        if(res.data[0].length>0){
+          this.examineData = res.data[0][0];
+        }
+      }).catch(e=>{
+        console.log(e);
+        this.examineData = null;
+      })
+    },
   },
 
   beforeDestroy() {
@@ -3869,6 +3892,10 @@ ${JSON.stringify(item.array)}
       } else {
         this.getData();
       }
+
+      if(this.examineId){
+        this.getExamineData();
+      }
       // this.getClass2()
       this.changeHeight();
     });

+ 1 - 1
src/components/pages/testPerson/examine/index.vue

@@ -541,7 +541,7 @@ export default {
     // 查看数据来源
     lookPrize(val) {
       // return;
-      this.ifmUrl = `https://beta.pbl.cocorobo.cn/pbl-teacher-table/dist/#/checkToTest?cid=${val}&oid=${this.oid}&org=${this.org}&type=2&role=0&peopleId=${this.userid}`;
+      this.ifmUrl = `https://beta.pbl.cocorobo.cn/pbl-teacher-table/dist/#/checkToTest?cid=${val}&oid=${this.oid}&org=${this.org}&type=2&role=0&peopleId=${this.userid}&tType=${this.pType}&examineId=${this.testExamineBaseList[0].id}`;
       // this.ifmUrl = `https://beta.pbl.cocorobo.cn/pbl-teacher-table/dist/#/test?userid=${this.userid}&oid=45facc0a-1211-11ec-80ad-005056b86db5&org=&role=0`;
       this.diaIframe = true;
     },

Alguns arquivos não foram mostrados porque muitos arquivos mudaram nesse diff