SanHQin 3 месяцев назад
Родитель
Сommit
58bef89e35

+ 1 - 1
dist/index.html

@@ -32,7 +32,7 @@
       width: 100%;
       background: #e6eaf0;
       font-family: '黑体';
-    }</style><link href=./static/css/app.f5613df24116890eac85bcc699ec5aae.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.d69ce91ced94f6be5cf8.js></script></body></html><script>function stopSafari() {
+    }</style><link href=./static/css/app.7f6f3b73c99f76935981f686bc33bd2f.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.8be4ed35b3f4a2fc2a29.js></script></body></html><script>function stopSafari() {
     //阻止safari浏览器双击放大功能
     let lastTouchEnd = 0  //更新手指弹起的时间
     document.documentElement.addEventListener("touchstart", function (event) {

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
dist/static/css/app.7f6f3b73c99f76935981f686bc33bd2f.css


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
dist/static/css/app.7f6f3b73c99f76935981f686bc33bd2f.css.map


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


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
dist/static/js/app.8be4ed35b3f4a2fc2a29.js


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
dist/static/js/app.8be4ed35b3f4a2fc2a29.js.map


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
dist/static/js/manifest.161e82026ac2ae03ab6f.js.map


+ 112 - 11
src/components/pages/classroomObservation/dialog/batchCreationClassDialog.vue

@@ -60,10 +60,14 @@
                 >
               </div>
               <span>
-                <img
+                <span @click="exportDoc" style="color:#409EFF">批量导出doc</span>
+              </span>
+              <span>
+                <span @click="delSelectBtn" style="color: red;">删除</span>
+                <!-- <img
                   @click="delSelectBtn"
                   src="../../../../assets/icon/classroomObservation/del2.svg"
-                />
+                /> -->
               </span>
             </div>
           </div>
@@ -113,6 +117,7 @@ import uploadFileToCreateClassDialog from "./uploadFileToCreateClassDialog.vue";
 import editBaseMessageDialog from "./editBaseMessageDialog.vue";
 import { v4 as uuidv4 } from "uuid";
 import { toolMixin } from "../tools/mixin";
+import JSZip from "jszip";
 export default {
   mixins: [toolMixin],
   components: {
@@ -259,12 +264,12 @@ export default {
             file_ids: "",
             baseMessage: {
               courseName: i.name,
-              teacherName: "",
-              time: "",
-              grade: "",
-              subject: "",
-              textbook: "",
-              studentNum: 0,
+              teacherName: "老师",
+              time: this.getFileLastUpdateTime(i.fileObj),
+              grade: "小学五年级",
+              subject: "科学",
+              textbook: "教科版",
+              studentNum: 45,
               imageList: {
                 fileList: [],
                 fileList1: [],
@@ -301,6 +306,11 @@ export default {
               text: "获取文件fileid",
               status: "0"
             },
+            {
+                type:"getWordCloudMap",
+                text:"获取词云图",
+                status:"0"
+              },
             {
               type: "generateReport",
               text: "生成报告",
@@ -353,6 +363,11 @@ export default {
                 text: "获取文件fileid",
                 status: "0"
               },
+              {
+                type:"getWordCloudMap",
+                text:"获取词云图",
+                status:"0"
+              },
               {
                 type: "generateReport",
                 text: "生成报告",
@@ -410,6 +425,11 @@ export default {
                 text: "获取文件fileid",
                 status: "0"
               },
+              {
+                type:"getWordCloudMap",
+                text:"获取词云图",
+                status:"0"
+              },
               {
                 type: "generateReport",
                 text: "生成报告",
@@ -999,7 +1019,34 @@ export default {
             );
             console.log("data👉",JSON.parse(JSON.stringify(_startData)))
             console.log(`${_startData.id}:已获取fileId`);
+          }else if(_step.type=="getWordCloudMap"){
+            //文件获取fileId
+            _stepList[i].status = "2";
+            this.dataList.find(
+              i => i.id === _startData.id
+            ).jsonData.steps = _stepList;
+
 
+            let _fileData = _startData.jsonData.baseMessage.editorBarData;
+
+            let _content = await this.getFile(_fileData.url);
+
+            let _result = await this.getWordCloudMapMixin( _content.data);
+
+            this.dataList.find(
+              i => i.id === _startData.id
+            ).jsonData.baseMessage.imageList.NephogramList.push(_result);
+
+            _stepList[i].status = "1";
+            this.dataList.find(
+              i => i.id === _startData.id
+            ).jsonData.steps = _stepList;
+            this.updateTask(_startData.id);
+            _startData.jsonData = JSON.parse(
+              JSON.stringify(
+                this.dataList.find(i => i.id === _startData.id).jsonData
+              )
+            );
           } else if (_step.type === "generateReport") {
             //生成报告
             _stepList[i].status = "2";
@@ -1196,7 +1243,56 @@ export default {
     },
     test() {
       this.testMixin();
-    }
+    },
+    exportDoc(){
+      let _exportList = this.dataList.filter(
+        i =>
+          this.selectList.includes(i.id) &&
+          (this.selectStatus === "99" ||
+            this.statusList
+              .find(i2 => i2.value === this.selectStatus)
+              .allow.includes(i.status)) && i.status == "2"
+      );
+
+      if (_exportList.length <= 0) return this.$message.info("请先选择已经完成的任务列表");
+
+
+      let promise = [];
+
+      let _notify = this.$notify.info({
+          title: '提示',
+          message: '正在批量导出word中,请稍等...',
+          duration: 0,
+          showClose: false,
+        });
+
+      _exportList.forEach((i,index)=>{
+        i.jsonData.baseMessage.courseName = i.jsonData.baseMessage.courseName+"_"+index;
+        promise.push(this.getDocFnPromise(i))
+      })
+
+      Promise.all(promise).then(res=>{
+        _notify.close();
+        console.log(res)
+        if(res.length==1){
+          saveAs(res[0], `${res[0].name}`);
+          this.$message.success("导出报告成功")
+        }else if(res.length>1){
+          const zip = new JSZip();
+          res.forEach(i=>{
+            zip.file(`${i.name}`, i, { binary: true });
+          })
+          zip.generateAsync({ type: "blob" }).then(content => {
+            // 生成二进制流
+            saveAs(content, `课堂观察报告.zip`); // 利用file-saver保存文件  自定义文件名
+          });
+          this.$message.success("导出报告成功")
+        }
+      }).catch(err=>{
+        console.log("err",err)
+      })
+
+    },
   },
   mounted() {
     this.getTaskList();
@@ -1363,11 +1459,12 @@ export default {
 }
 
 .b_b_o_right {
-  width: 100px;
+  width: fit-content;
   height: 100%;
   display: flex;
   align-items: center;
   justify-content: space-between;
+
 }
 
 .b_b_o_right > div {
@@ -1381,9 +1478,13 @@ export default {
 }
 
 .b_b_o_right > span {
-  width: 20px;
+  width: fit-content;
   height: 20px;
   cursor: pointer;
+  display: flex;
+  align-items: center;
+  margin: 0 10px;
+  white-space: nowrap;
 }
 
 .b_b_o_right > span > img {

+ 1 - 1
src/components/pages/classroomObservation/dialog/editBaseMessageDialog.vue

@@ -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

+ 22 - 9
src/components/pages/classroomObservation/dialog/uploadFileToCreateClassDialog.vue

@@ -224,6 +224,8 @@ export default {
             file: uploadingFile.file
           });
         });
+      }else if(this.fileList.find(file => file.status === "uploading")){
+        console.log("还有在上传的文件")
       } else {
         console.log("上传完成");
       }
@@ -264,18 +266,29 @@ export default {
           }
         }
         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){

+ 64 - 49
src/components/pages/classroomObservation/index.vue

@@ -504,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) {
@@ -512,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 = ``;
@@ -550,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
@@ -571,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>";
@@ -615,6 +618,8 @@ export default {
 				${analysisHtml}
 			</div>
 			`;
+      // return console.log(analysisHtml.replace(/<img.*?>/g, ''))
+
         this.generateDocx(`《${bmData.courseName}》课堂观察报告`, _html);
         this.loading = false;
       } catch (e) {
@@ -642,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);
@@ -658,7 +663,7 @@ export default {
           // 获取图表的图片
           let base64Image = myChart.getDataURL({
             type: "png", // 图片格式
-            pixelRatio: 1, // 图像清晰度
+            pixelRatio: 0.9, // 图像清晰度
             backgroundColor: "#fff" // 背景颜色
           });
 
@@ -737,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";
@@ -763,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();
@@ -801,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);
           };
@@ -898,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
 						}
@@ -912,8 +929,6 @@ export default {
       ${html}
       </body>
       </html>`;
-      // console.log(content)
-      // return console.log(content)
       // debugger
       let blob = htmlDocx.asBlob(content);
 

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

@@ -12,7 +12,39 @@ 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() {
 
@@ -1471,6 +1503,574 @@ CH:${_CH}
 
         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)
+
+					})
+
+      })
     }
   }
 };

Некоторые файлы не были показаны из-за большого количества измененных файлов