SanHQin 2 maanden geleden
bovenliggende
commit
4f070343b1

+ 10 - 6
src/components/pages/classroomObservation/components/analysis.vue

@@ -104,15 +104,16 @@
         <analysisItem
           ref="analysisItemRef"
           v-if="
-            converter(item.jsonData.name) != converter('词频词汇分析') &&
+            converter(item.jsonData.name) != converter('词频词汇分析') &&(
               ![
                 converter('S-T分析:课堂时间分配'),
                 converter('S-T分析:师生互动分析'),
                 converter('S-T分析:教学模式分析'),
-								// converter('课堂活动光谱图')
-              ].includes(converter(item.jsonData.name))
+              ].includes(converter(item.jsonData.name)) && !['bfe844b1-7a45-11ef-9b30-005056b86db5'].includes(item.jsonData.mId)
+			)
           "
           :dialogTagDataList="dialogTagDataList"
+					:dataList="dataList"
           :bmData="bmData"
           :key="item.id + item.Type + '-'+index"
           :data="item"
@@ -130,8 +131,7 @@
               converter('S-T分析:课堂时间分配'),
               converter('S-T分析:师生互动分析'),
               converter('S-T分析:教学模式分析'),
-							// converter('课堂活动光谱图')
-            ].includes(converter(item.jsonData.name))
+            ].includes(converter(item.jsonData.name)) || ['bfe844b1-7a45-11ef-9b30-005056b86db5'].includes(item.jsonData.mId)
           "
           :dialogTagDataList="dialogTagDataList"
           ref="analysisItemRef"
@@ -228,7 +228,11 @@ export default {
     fileId: {
       type: String,
       require: true
-    }
+    },
+		dataList:{
+			type:Array,
+			default:()=>{return []}
+		},
   },
   components: {
     analysisItem,

+ 17 - 2
src/components/pages/classroomObservation/components/analysisItem.vue

@@ -256,7 +256,11 @@ export default {
       default: () => {
         return {};
       }
-    }
+    },
+		dataList:{
+			type:Array,
+			default:()=>{return []}
+		},
   },
   data() {
     return {
@@ -323,6 +327,7 @@ export default {
         this.openItem = false;
         this.loadNum = 1;
         let type = 0; //0 用agentId  1:用提示词 3:啥都没有
+				let tips = "";
         let assistant =
           this.dialogTagDataList.find(i => i.id == this.data.jsonData.mId) ||
           this.dialogTagDataList.find(i => i.name == this.data.jsonData.name);
@@ -338,6 +343,13 @@ export default {
 
         if (assistant.tips) {
           type = 1;
+					let analysisData = ``;
+					for(let i = 0;i<this.dataList.length;i++){
+							let _json = this.dataList[i].jsonData;
+							analysisData +=`### ${_json.anotherName?_json.anotherName:_json.name}\n`
+							analysisData +=`${_json.content}\n`
+					}
+					tips = assistant.tips.replaceAll('${analysisData}',analysisData)
         } else if (assistant.agentid) {
           type = 0;
         } else {
@@ -352,6 +364,8 @@ export default {
 课堂名称:${this.bmData.courseName}  搜课年级:${this.bmData.grade}  授课科目:${this.bmData.subject}`;
         }
 
+
+
         // console.log('👇')
         // return console.log(_msg)
         let parm = {
@@ -359,7 +373,7 @@ export default {
             type == 0
               ? assistant.agentid
               : "f8e1ebb2-2e0d-11ef-8bf4-12e77c4cb76b",
-          message: type == 0 ? _msg : assistant.tips,
+          message: type == 0 ? _msg : tips,
           session_name: uuidv4(),
           userId: this.userId,
           file_ids: this.fileId ? [this.fileId] : "",
@@ -411,6 +425,7 @@ export default {
             // }
             let _copyData = JSON.parse(JSON.stringify(this.data));
             // _copyData.jsonData.result = "";
+						if(!_copyData.jsonData.mId && assistant.agentid)_copyData.jsonData.mId = assistant.agentid
             _copyData.jsonData.content = _data.message;
             _copyData.jsonData.dataFileList = [];
             _copyData.jsonData.fileList = [];

+ 170 - 29
src/components/pages/classroomObservation/components/analysisSpecialItem.vue

@@ -176,8 +176,12 @@
         "
       />
 
-			<!-- 光谱图 -->
-			<!-- <echartsSpectrogram style="max-height: 200px;max-width: 90%;width: 90%;height: 200px;margin: auto;"  v-if=" converter(data.jsonData.name) == converter('课堂活动光谱图')" :data="{}"/> -->
+      <!-- 光谱图 -->
+      <echartsSpectrogram
+        style="max-height: 150px;max-width: 90%;width: 90%;height: 150px;margin: auto;margin-bottom: 50px;"
+        v-if="data.jsonData.spectrogramData && data.jsonData.mId == 'bfe844b1-7a45-11ef-9b30-005056b86db5'"
+        :data="data.jsonData.spectrogramData"
+      />
 
       <eChartTemplate
         style="width: 100%;height: 400px;"
@@ -213,14 +217,15 @@ let converter2 = OpenCC.Converter({
 import mdView from "./mdView.vue";
 import eChartTemplate from "./eChartTemplate";
 import { v4 as uuidv4 } from "uuid";
-import echartsSpectrogram from './echartsSpectrogram'
+import echartsSpectrogram from "./echartsSpectrogram";
 // import editNameDialog from './editNameDialog.vue'
+import markdownIt from "markdown-it";
 export default {
   emits: ["delItem", "editItem", "saveItem"],
   components: {
     mdView,
     eChartTemplate,
-		echartsSpectrogram
+    echartsSpectrogram
     // editNameDialog
   },
   props: {
@@ -427,15 +432,19 @@ export default {
       };
     },
     getData(type = 0) {
-      if (this.data.jsonData.name != this.converter("课堂活动光谱图") &&(!this.bmData.editorBarData || this.bmData.editorBarData.type != 0)) {
+      if (
+        this.data.jsonData.mId == 'bfe844b1-7a45-11ef-9b30-005056b86db5' &&
+        type == 1
+      ) {
+        return this.getSpectrogram("", type);
+      }
+
+      if (!this.bmData.editorBarData || this.bmData.editorBarData.type != 0) {
         this.loadNum = 2;
         return this.$message.info("请上传表格形式的转录文稿");
       }
 
-			if(this.data.jsonData.name == this.converter("课堂活动光谱图")){
-				return this.getSpectrogram(type)
-			}
-      try {	
+      try {
         let _result = [];
         let _data = this.bmData.editorBarData.content;
         let _div = document.createElement("div");
@@ -455,12 +464,22 @@ export default {
           _result.push(obj);
         });
         if (_result.length == 0) return this.$message.error("未找到表格数据");
-        if (this.data.jsonData.name == this.converter("S-T分析:课堂时间分配")) {
+        if (
+          this.data.jsonData.name == this.converter("S-T分析:课堂时间分配")
+        ) {
           this.getTimeAllocationData(_result, type);
-        } else if (this.data.jsonData.name == this.converter("S-T分析:师生互动分析")) {
+        } else if (
+          this.data.jsonData.name == this.converter("S-T分析:师生互动分析")
+        ) {
           this.getInteractionAnalysisData(_result, type);
-        } else if (this.data.jsonData.name == this.converter("S-T分析:教学模式分析")) {
+        } else if (
+          this.data.jsonData.name == this.converter("S-T分析:教学模式分析")
+        ) {
           this.getTeachingModeData(_result, type);
+        } else if (
+          this.data.jsonData.mId == 'bfe844b1-7a45-11ef-9b30-005056b86db5'
+        ) {
+          return this.getSpectrogram(_result, type);
         } else {
           return this.$message.error("未找到对应的分析");
         }
@@ -475,7 +494,6 @@ export default {
     getTimeAllocationData(_dataList, type = 0) {
       this.loading = true;
       this.openItem = false;
-			console.log(_dataList)
       let _data = _dataList.reduce(
         (pre, cur) => {
           if (cur.role == "学生") {
@@ -490,11 +508,14 @@ export default {
           { value: 0, name: "学生" }
         ]
       );
-				console.log(_data)
-			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)+'%'
-			})
+      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) + "%";
+      });
 
       if (type == 1) {
         let _msg = `这是某一节课的师生时间占比,请你分析,写出结论,并给出指导建议。请使用3句完整的话,分析并给出建议。 请注意,当老师或学生的时间占比在【40~59%】之间的时候,也认为师生占比约为1:1,各占50%,师生时间占比比较均衡。
@@ -502,7 +523,7 @@ export default {
 老师占比:${_dataPercentage[0].percentage}
 学生占比:${_dataPercentage[1].percentage}
 `;
-        return this.getAiContent(_msg)
+        return this.getAiContent(_msg);
       }
 
       const _option = {
@@ -553,7 +574,6 @@ export default {
         ]
       };
 
-			console.log(_option)
 
       let _copyData = JSON.parse(JSON.stringify(this.data));
       _copyData.jsonData.eChartData = _option;
@@ -808,7 +828,6 @@ CH:${_CH}
       let seconds = +parts[0] * 3600 + +parts[1] * 60 + +parts[2];
       return seconds;
     },
-
     changeNameSuccess(newName) {
       let _copyData = JSON.parse(JSON.stringify(this.data));
       _copyData.jsonData.anotherName = newName;
@@ -842,13 +861,100 @@ CH:${_CH}
         }
       });
     },
-		getSpectrogram(type=1){
-			if(type===0){
-				this.$message.info("图表");
-				this.loading = false;
-				return;
-			}
-			this.$nextTick(() => {
+    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("时间点")))
+
+					_tableData = _tableData.slice(_delIndex+1)
+
+          let _result = [];
+          let identity = "老师"; //0:老师 1:学生
+          let startTime = "";
+          let endTime = "";
+          let sumTime = 0;
+
+          _dataList.forEach((item, index) => {
+            if (index == 0) {
+              //第一个
+              identity = item.role;
+              startTime = item.startTime;
+              endTime = item.endTime;
+              sumTime = this.convertToSeconds(item.time);
+              return;
+            }
+            if (item.role == identity) {
+              //没更换角色
+              sumTime += this.convertToSeconds(item.time);
+              endTime = item.endTime;
+            } else {
+              //更换角色了
+              _result.push({
+                startTime: startTime,
+                endTime: endTime,
+                identity: identity,
+                sumTime: sumTime
+              });
+              identity = item.role;
+              startTime = item.startTime;
+              endTime = item.endTime;
+              sumTime = this.convertToSeconds(item.time);
+            }
+          });
+
+          let breakpoint = [];
+
+					breakpoint = _tableData.map(i=>this.convertToSeconds(i[0]))
+
+          _result = _result.filter(
+            i =>
+              i.identity == this.converter("老师") ||
+              i.identity == this.converter("学生")
+          );
+          // 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;
+          this.loading = false;
+          return this.$message.error("数据格式错误");
+				}
+      }
+      this.$nextTick(() => {
         this.loading = true;
         this.openItem = false;
         this.loadNum = 1;
@@ -958,7 +1064,42 @@ CH:${_CH}
             this.loading = false;
           });
       });
-		}
+    },
+    getContentTable() {
+      return new Promise(resolve => {
+        let _content = this.data.jsonData.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);
+      });
+    }
   },
   mounted() {
     if (this.data.jsonData.content) {

+ 81 - 29
src/components/pages/classroomObservation/components/echartsSpectrogram.vue

@@ -7,63 +7,115 @@
 <script>
 export default {
   props: {
-    data: {}
+    data: {
+      type: Object,
+      default: () => {
+        return { data: [], breakpoint: [] };
+      }
+    }
+  },
+  data() {
+    return {
+      canvas: null
+    };
   },
   methods: {
     init() {
-      const canvas = this.$refs.canvasRef;
-      const ctx = canvas.getContext("2d");
+      this.canvas = this.$refs.canvasRef;
+      if (!this.canvas) return;
+      let ctx = this.canvas.getContext("2d");
+      this.canvas.width = this.canvas.clientWidth * window.devicePixelRatio;
+      this.canvas.height = this.canvas.clientHeight * window.devicePixelRatio;
+      // 缩放绘图上下文
+      ctx.scale(1, 1);
+
+      let canvasWidth = this.canvas.width;
+      let canvasHeight = this.canvas.height;
+      ctx.imageSmoothingEnabled = false;
+      ctx.lineWidth = 1;
 
-      // 定义各区域数据(宽度比例)
-      const segments = [
-        { label: "老师", color: "#0000FF", percentage: 30 },
-        { label: "学生", color: "#008000", percentage: 20 },
-        { label: "老师", color: "#0000FF", percentage: 10 },
-        { label: "学生", color: "#008000", percentage: 20 },
-        { label: "老师", color: "#0000FF", percentage: 20 }
-      ];
+      // 设置颜色和文字
+      const teacherColor = "#5470C6"; // 老师的颜色
+      const studentColor = "#91CC75"; // 学生的颜色
+      const fontSize = 14; //字体大小
 
-      // 画布宽度
-      const canvasWidth = canvas.width;
+      ctx.fillStyle = teacherColor;
+      this.drawRoundedRect(ctx, 0, canvasHeight - 20, 20, 15, 5);
+      // 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, 5);
+      ctx.fillStyle = "black";
+      ctx.font = `${fontSize}px serif`;
+      ctx.fillText("学生", 128, canvasHeight - 7);
+      let sum = this.data.data.reduce((pre, cur) => (pre += cur.value), 0);
 
       // 当前x位置的起始点
       let currentX = 0;
-
       // 计算并绘制每个区域
-      segments.forEach(segment => {
-        const segmentWidth = (segment.percentage / 100) * canvasWidth;
-        ctx.fillStyle = segment.color;
-        ctx.fillRect(currentX, 10, segmentWidth, canvas.height - 20);
+      this.data.data.forEach(i => {
+        const segmentWidth = parseFloat(
+          (i.value / (sum / canvasWidth)).toFixed(2)
+        );
+        ctx.fillStyle = i.type == 0 ? teacherColor : studentColor;
+        ctx.fillRect(currentX, 20, segmentWidth, canvasHeight - 60);
 
         // 更新x位置
         currentX += segmentWidth;
       });
 
       // 绘制红色垂直线(指定位置)
-      const redLinePositions = [80, 160, 400, 420, 520, 540, 680, 720]; // 像素位置
       ctx.strokeStyle = "red";
       ctx.lineWidth = 2;
 
-      redLinePositions.forEach(position => {
+      this.data.breakpoint.forEach(i => {
+        const breakpointPo = parseFloat((i / (sum / canvasWidth)).toFixed(2));
         ctx.beginPath();
-        ctx.moveTo(position, 0);
-        ctx.lineTo(position, canvas.height + 20);
+        ctx.moveTo(breakpointPo, 0);
+        ctx.lineTo(breakpointPo, canvasHeight - 20);
         ctx.stroke();
       });
+    },
+    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(); // 填充颜色
     }
   },
   mounted() {
-		this.init();
-		window.addEventListener("resize", () => {
-			this.init();
+    this.init();
+    window.addEventListener("resize", () => {
+      if (!this.$refs.canvasRef) return;
+      this.init();
     });
-	}
+  }
 };
 </script>
 
 <style scoped>
-.canvas{
-	width: 100%;
-	height: 150px;
+.canvas {
+  width: 100%;
+  height: 100%;
 }
 </style>

+ 1 - 0
src/components/pages/classroomObservation/components/messageArea.vue

@@ -50,6 +50,7 @@
         @changeAnalysisName="changeAnalysisName"
         @addNewAnalysisGroup="addNewAnalysisGroup"
         @delAnalysisGroup="delAnalysisGroup"
+				:dataList="dataList.filter(i => !(item.value == 0 && i.tIndex == 2))"
         :bmData="bmData.jsonData"
         :title="item.name"
         :dialogTagDataList="dialogTagDataList"

+ 147 - 35
src/components/pages/classroomObservation/index.vue

@@ -378,15 +378,15 @@ export default {
           !i.isOtherData &&
           converter(i.jsonData.name) != converter("词频词汇分析")
       );
-			json.forEach(i=>{
-				if(i.jsonData.eChartData){
-					delete i.jsonData.eChartData
-				}
-				if(i.jsonData.RT && i.jsonData.CH){
-					delete i.jsonData.RT
-					delete i.jsonData.CH
-				}
-			})
+      json.forEach(i => {
+        if (i.jsonData.eChartData) {
+          delete i.jsonData.eChartData;
+        }
+        if (i.jsonData.RT && i.jsonData.CH) {
+          delete i.jsonData.RT;
+          delete i.jsonData.CH;
+        }
+      });
       return new Promise((resolve, reject) => {
         this.loading = true;
         const _newTid = uuidv4();
@@ -592,10 +592,21 @@ export default {
               )}"/>`;
             }
 
+            if (i2.jsonData.spectrogramData) {
+              tagHtml += `<img src="${await this.getEChartsSpectrogramImage(
+                i2.jsonData.spectrogramData
+              )}"/>`;
+              // console.log()
+            }
+
             if (i2.jsonData.CH && i2.jsonData.RT) {
               tagHtml += `<div style="width:100vw;text-align:center;">
-								<img src="${await this.getImageSrcToBase64(require('../../../assets/icon/classroomObservation/rt-ch.png'))}" style="width:200px;height:200px;margin:auto;">
-								<div style="color:#1A7AD3;font-style:italic;font-weight:blob;">RT:${i2.jsonData.RT}CH:${i2.jsonData.CH}</div>
+								<img src="${await this.getImageSrcToBase64(
+                  require("../../../assets/icon/classroomObservation/rt-ch.png")
+                )}" style="width:200px;height:200px;margin:auto;">
+								<div style="color:#1A7AD3;font-style:italic;font-weight:blob;">RT:${
+                  i2.jsonData.RT
+                }CH:${i2.jsonData.CH}</div>
 								</div>`;
             }
 
@@ -614,15 +625,23 @@ export default {
         // return console.log(analysisHtml);
         let _html = `
 			<div>
-				<p style="width:100vw;margin-bottom:1.5in">*分析结果仅供参考</p> 
+				<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>
+					<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;"/>
@@ -689,24 +708,117 @@ export default {
         });
       });
     },
-		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);
-				}
-			})
-		},
+    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 = 150 * window.devicePixelRatio;
+          // 缩放绘图上下文
+          ctx.scale(1, 1);
+          let canvasWidth = canvas.width;
+          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, 5);
+          // 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, 5);
+          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 = 0;
+          // 计算并绘制每个区域
+          data.data.forEach(i => {
+            const segmentWidth = parseFloat(
+              (i.value / (sum / canvasWidth)).toFixed(2)
+            );
+            ctx.fillStyle = i.type == 0 ? teacherColor : studentColor;
+            ctx.fillRect(currentX, 20, segmentWidth, canvasHeight - 60);
+
+            // 更新x位置
+            currentX += segmentWidth;
+          });
+
+          // 绘制红色垂直线(指定位置)
+          ctx.strokeStyle = "red";
+          ctx.lineWidth = 2;
+
+          data.breakpoint.forEach(i => {
+            const breakpointPo = parseFloat(
+              (i / (sum / canvasWidth)).toFixed(2)
+            );
+            ctx.beginPath();
+            ctx.moveTo(breakpointPo, 0);
+            ctx.lineTo(breakpointPo, canvasHeight - 20);
+            ctx.stroke();
+          });
+
+          // 将 canvas 转换为 base64 格式的图片地址
+          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(); // 填充颜色
+    },
     // 导出docx
     async generateDocx(name, html) {
       // 将html文件中需要用到的数据挂载到store上
@@ -715,7 +827,7 @@ export default {
       :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/TR/REC-html40'
       xmlns='http://www.w3.org/1999/xhtml'>
       <head>
           <meta charset="UTF-8">