Forráskód Böngészése

Merge branch 'beta' into HK

lsc 1 éve
szülő
commit
b56d360d3b
57 módosított fájl, 6909 hozzáadás és 1683 törlés
  1. 2 1
      build/webpack.dev.conf.js
  2. 4 0
      dist/index.html
  3. 1 0
      dist/static/css/app.2e612ced980c0991d3d17f0736bf5b65.css
  4. 1 0
      dist/static/css/app.2e612ced980c0991d3d17f0736bf5b65.css.map
  5. 1 0
      dist/static/css/app.4a3f26dd5e364e5fb92365473d97bbf5.css
  6. 0 0
      dist/static/css/app.4a3f26dd5e364e5fb92365473d97bbf5.css.map
  7. BIN
      dist/static/img/rt-ch.5bf72bb.png
  8. BIN
      dist/static/img/rt-ch_echarts.8000b59.png
  9. 1 0
      dist/static/js/app.c9c32b6680f881f3ba84.js
  10. 1 0
      dist/static/js/app.c9c32b6680f881f3ba84.js.map
  11. 1 0
      dist/static/js/app.e7a81609a2b5f3200f33.js
  12. 0 0
      dist/static/js/app.e7a81609a2b5f3200f33.js.map
  13. 6 1
      dist/static/js/manifest.0ad6c5648a38a6d1fa8f.js
  14. 0 0
      dist/static/js/manifest.0ad6c5648a38a6d1fa8f.js.map
  15. 7 0
      dist/static/js/manifest.571c38d63f24b1ae9e16.js
  16. 1 0
      dist/static/js/manifest.571c38d63f24b1ae9e16.js.map
  17. 0 0
      dist/static/js/vendor.8db9a3f753f5644e3c5e.js
  18. 1 0
      dist/static/js/vendor.8db9a3f753f5644e3c5e.js.map
  19. 0 0
      dist/static/js/vendor.a82b79982b082928b294.js
  20. 0 0
      dist/static/js/vendor.a82b79982b082928b294.js.map
  21. 119 118
      package-lock.json
  22. 1 0
      package.json
  23. BIN
      src/assets/icon/classroomObservation/rt-ch_echarts.png
  24. 1 0
      src/assets/icon/course/arrow.svg
  25. 278 143
      src/components/pages/aiAddCourse/addCourse.vue
  26. 16 3
      src/components/pages/aiAddCourse/aiBox.vue
  27. 16 3
      src/components/pages/aiAddCourse/aiBox2.vue
  28. 474 182
      src/components/pages/aiAddCourse/aiBoxRight.vue
  29. 29 4
      src/components/pages/aiAddCourse/aiCreateDialog.vue
  30. 324 92
      src/components/pages/aiAddCourse/evaList.vue
  31. 641 0
      src/components/pages/aiAddCourse/jsmind2.vue
  32. 195 92
      src/components/pages/classroomObservation/components/addNewTeacherVoiceprintDialog.vue
  33. 354 49
      src/components/pages/classroomObservation/components/analysis.vue
  34. 177 19
      src/components/pages/classroomObservation/components/analysisItem.vue
  35. 308 20
      src/components/pages/classroomObservation/components/analysisSpecialItem.vue
  36. 643 448
      src/components/pages/classroomObservation/components/chatArea.vue
  37. 97 0
      src/components/pages/classroomObservation/components/echartsRTCH.vue
  38. 165 0
      src/components/pages/classroomObservation/components/echartsSpectrogram.vue
  39. 147 220
      src/components/pages/classroomObservation/components/messageArea.vue
  40. 276 98
      src/components/pages/classroomObservation/index.vue
  41. 18 2
      src/components/pages/pblCourse/component/chatArea.vue
  42. 17 2
      src/components/pages/pocAi/index.vue
  43. 19 3
      src/components/pages/pocAiClassroom/chatArea.vue
  44. 1 1
      src/components/pages/student/addCourse.vue
  45. 1 1
      src/components/pages/studentManage/student.vue
  46. 1 1
      src/components/pages/studio/addCourse.vue
  47. 50 44
      src/components/pages/synergyCourse/addCourse.vue
  48. 1 0
      src/components/pages/teacherOffice/index.vue
  49. 13 12
      src/components/pages/test/add/components/course2/courseIndex.vue
  50. 2127 0
      src/components/pages/test/check/aiBoxRight.vue
  51. 121 98
      src/components/pages/test/check/index.vue
  52. 9 0
      src/components/pages/test/dataCom/cascader.vue
  53. 28 8
      src/components/pages/test/dataCom/radarZong.vue
  54. 15 6
      src/components/pages/test/databoard.vue
  55. 96 10
      src/components/pages/testPerson/info/index.vue
  56. 2 2
      src/components/pages/testPerson/info/infoDialog/index.vue
  57. 102 0
      src/lib/shengyang.js

+ 2 - 1
build/webpack.dev.conf.js

@@ -43,7 +43,8 @@ const devWebpackConfig = merge(baseWebpackConfig, {
     quiet: true, // necessary for FriendlyErrorsPlugin
     watchOptions: {
       poll: config.dev.poll,
-    }
+    },
+    // https: true // Enable HTTPS
   },
   plugins: [
     new webpack.DefinePlugin({

+ 4 - 0
dist/index.html

@@ -32,7 +32,11 @@
       width: 100%;
       background: #e6eaf0;
       font-family: '黑体';
+<<<<<<< HEAD
     }</style><link href=./static/css/app.4a3f26dd5e364e5fb92365473d97bbf5.css rel=stylesheet></head><body><div id=app></div><script type=text/javascript src=./static/js/manifest.0ad6c5648a38a6d1fa8f.js></script><script type=text/javascript src=./static/js/vendor.a82b79982b082928b294.js></script><script type=text/javascript src=./static/js/app.e7a81609a2b5f3200f33.js></script></body></html><script>function stopSafari() {
+=======
+    }</style><link href=./static/css/app.2e612ced980c0991d3d17f0736bf5b65.css rel=stylesheet></head><body><div id=app></div><script type=text/javascript src=./static/js/manifest.571c38d63f24b1ae9e16.js></script><script type=text/javascript src=./static/js/vendor.8db9a3f753f5644e3c5e.js></script><script type=text/javascript src=./static/js/app.c9c32b6680f881f3ba84.js></script></body></html><script>function stopSafari() {
+>>>>>>> beta
     //阻止safari浏览器双击放大功能
     let lastTouchEnd = 0  //更新手指弹起的时间
     document.documentElement.addEventListener("touchstart", function (event) {

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 1 - 0
dist/static/css/app.2e612ced980c0991d3d17f0736bf5b65.css


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 1 - 0
dist/static/css/app.2e612ced980c0991d3d17f0736bf5b65.css.map


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 1 - 0
dist/static/css/app.4a3f26dd5e364e5fb92365473d97bbf5.css


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 0 - 0
dist/static/css/app.4a3f26dd5e364e5fb92365473d97bbf5.css.map


BIN
dist/static/img/rt-ch.5bf72bb.png


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


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 1 - 0
dist/static/js/app.c9c32b6680f881f3ba84.js


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 1 - 0
dist/static/js/app.c9c32b6680f881f3ba84.js.map


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 1 - 0
dist/static/js/app.e7a81609a2b5f3200f33.js


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 0 - 0
dist/static/js/app.e7a81609a2b5f3200f33.js.map


+ 6 - 1
dist/static/js/manifest.0ad6c5648a38a6d1fa8f.js

@@ -1,2 +1,7 @@
+<<<<<<<< HEAD:dist/static/js/manifest.0ad6c5648a38a6d1fa8f.js
 !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:"7da288988865a9378b49",1:"d8d4e9b1fe43bbb0a681",2:"da6498e5ed226bc25eb5",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.0ad6c5648a38a6d1fa8f.js.map
+//# sourceMappingURL=manifest.0ad6c5648a38a6d1fa8f.js.map
+========
+!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:"7da288988865a9378b49",1:"d8d4e9b1fe43bbb0a681",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.571c38d63f24b1ae9e16.js.map
+>>>>>>>> beta:dist/static/js/manifest.571c38d63f24b1ae9e16.js

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 0 - 0
dist/static/js/manifest.0ad6c5648a38a6d1fa8f.js.map


+ 7 - 0
dist/static/js/manifest.571c38d63f24b1ae9e16.js

@@ -0,0 +1,7 @@
+<<<<<<<< HEAD:dist/static/js/manifest.0ad6c5648a38a6d1fa8f.js
+!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:"7da288988865a9378b49",1:"d8d4e9b1fe43bbb0a681",2:"da6498e5ed226bc25eb5",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.0ad6c5648a38a6d1fa8f.js.map
+========
+!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:"7da288988865a9378b49",1:"d8d4e9b1fe43bbb0a681",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.571c38d63f24b1ae9e16.js.map
+>>>>>>>> beta:dist/static/js/manifest.571c38d63f24b1ae9e16.js

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 1 - 0
dist/static/js/manifest.571c38d63f24b1ae9e16.js.map


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 0 - 0
dist/static/js/vendor.8db9a3f753f5644e3c5e.js


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 1 - 0
dist/static/js/vendor.8db9a3f753f5644e3c5e.js.map


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 0 - 0
dist/static/js/vendor.a82b79982b082928b294.js


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 0 - 0
dist/static/js/vendor.a82b79982b082928b294.js.map


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 119 - 118
package-lock.json


+ 1 - 0
package.json

@@ -44,6 +44,7 @@
     "pptxgenjs": "^3.12.0",
     "qrcodejs2": "^0.0.2",
     "qs": "^6.10.1",
+    "recorder-core": "^1.3.24040900",
     "relation-graph": "^1.1.0",
     "turndown": "^7.2.0",
     "v-viewer": "^1.6.4",

BIN
src/assets/icon/classroomObservation/rt-ch_echarts.png


+ 1 - 0
src/assets/icon/course/arrow.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1727575870183" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4244" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128"><path d="M312.888889 995.555556c-17.066667 0-28.444444-5.688889-39.822222-17.066667-22.755556-22.755556-17.066667-56.888889 5.688889-79.644445l364.088888-329.955555c11.377778-11.377778 17.066667-22.755556 17.066667-34.133333 0-11.377778-5.688889-22.755556-17.066667-34.133334L273.066667 187.733333c-22.755556-22.755556-28.444444-56.888889-5.688889-79.644444 22.755556-22.755556 56.888889-28.444444 79.644444-5.688889l364.088889 312.888889c34.133333 28.444444 56.888889 73.955556 56.888889 119.466667s-17.066667 85.333333-51.2 119.466666l-364.088889 329.955556c-11.377778 5.688889-28.444444 11.377778-39.822222 11.377778z" fill="#000000" p-id="4245"></path></svg>

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 278 - 143
src/components/pages/aiAddCourse/addCourse.vue


+ 16 - 3
src/components/pages/aiAddCourse/aiBox.vue

@@ -157,7 +157,8 @@ export default {
       ],
       part: "全部内容",
       checkBool: false,
-      loading: false
+      loading: false,
+      username: "",
     };
   },
   watch: {
@@ -474,13 +475,25 @@ export default {
         }
       };
     },
+    async getUserName() {
+      let params = { uid: this.userid };
+      try {
+        let res = await this.ajax.get(this.$store.state.api + "getUser", params);
+        this.username = res.data[0][0].name;
+      } catch (err) {
+        console.error(err);
+      }
+    },
     //保存消息
-    insertChat(_uid) {
+    async insertChat(_uid) {
       let _data = this.array.find(i => i.uid == _uid);
       if (!_data) return;
+      if(!this.username){
+        await this.getUserName()
+      }
       let params = {
         userId: this.userid,
-        userName: "qgt",
+        userName: this.username,
         groupId: "602def61-005d-11ee-91d8-005056b8q12w",
         answer: _data.aiContent,
         problem: _data.content,

+ 16 - 3
src/components/pages/aiAddCourse/aiBox2.vue

@@ -157,7 +157,8 @@ export default {
       ],
       part: "全部内容",
       checkBool: false,
-      loading: false
+      loading: false,
+      username: "",
     };
   },
   watch: {
@@ -458,13 +459,25 @@ export default {
         }
       };
     },
+    async getUserName() {
+      let params = { uid: this.userid };
+      try {
+        let res = await this.ajax.get(this.$store.state.api + "getUser", params);
+        this.username = res.data[0][0].name;
+      } catch (err) {
+        console.error(err);
+      }
+    },
     //保存消息
-    insertChat(_uid) {
+    async insertChat(_uid) {
       let _data = this.array.find(i => i.uid == _uid);
       if (!_data) return;
+      if(!this.username){
+        await this.getUserName()
+      }
       let params = {
         userId: this.userid,
-        userName: "qgt",
+        userName: this.username,
         groupId: "602def61-005d-11ee-91d8-005056b8q12w",
         answer: _data.aiContent,
         problem: _data.content,

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 474 - 182
src/components/pages/aiAddCourse/aiBoxRight.vue


+ 29 - 4
src/components/pages/aiAddCourse/aiCreateDialog.vue

@@ -45,6 +45,12 @@
 import Pptxgen from "pptxgenjs";
 import wOffice from "../components/wOffice.vue";
 import { v4 as uuidv4 } from "uuid";
+var OpenCC = require("opencc-js");
+
+let converter2 = OpenCC.Converter({
+		from:'cn',
+		to:'hk'
+})
 
 export default {
     components: {
@@ -79,6 +85,10 @@ export default {
         },
         unitJson: {
             type: Array,
+        },
+        languageSetting: {
+            type: Number,
+            default: 0,
         }
     },
     // 根据用户给你的参考资料
@@ -834,6 +844,17 @@ export default {
         },
     },
     methods: {
+        getLang(){
+            let lang = ''
+            if(this.languageSetting == 0){
+                lang = 'Chinese.'
+            }else if(this.languageSetting == 1){
+                lang = 'Traditional Chinese.'
+            }else if(this.languageSetting == 2){
+                lang = 'English.'
+            }
+            return lang
+        },
         cancelAjax(){
             this.$message.success("已经成功停止")
             this.stopPpt.cancel('Request canceled by the user.');
@@ -942,7 +963,7 @@ export default {
                 message = `NOTICE
 Role: 你是ppt内容设计大师,能力是从用户提供的文件资料中提取最重要的学科概念作为ppt参考内容,最后根据Context要求的流程要求输出ppt内容。
 Output: Provide your output in json format.
-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.
+Language: ${this.getLang()}
 ATTENTION: Use '##' to SPLIT SECTIONS, not '#'. Output format carefully referenced "Format example".
 Instruction: Based on the context, follow "Format example", write content.
 
@@ -966,7 +987,9 @@ ${_this.outline.replaceAll('#', '').replaceAll('*', '').replaceAll('-', '').repl
 [{"page": "页码(数字)","title": "学科概念(请从给你的大纲中摘取)(标题)","task": "知识点(请从给你的大纲中摘取)(子标题)","points": "内容:用亲切的口吻告诉学生本步骤应做什么?或者是用亲切的口吻向学生介绍知识点,多条时可使用bullet point;或者是给学生测试题。"}]`;
             } else {
                 if (this.courseState == 4) {
-                    message = `# 任务
+                    message = `NOTICE Language: ${this.getLang()}
+
+# 任务
 请根据参考资料,生成关于${this.courseName},为教师生成这节课的教学ppt的大纲,大纲的主要内容课程知识点的讲解与相关练习和测试。你的输出应该符合#输出格式
 
 ${mclass.length ? "#参考资料\n面向年级:" + mclass.join(",") : ""}
@@ -980,7 +1003,9 @@ ${mclass.length ? "#参考资料\n面向年级:" + mclass.join(",") : ""}
 - 你不能输出错误的知识,如果你实在不清楚,输出“对不起,我不确定”
 - 你不能输出违反伦理的内容`;
                 } else if (this.courseState == 5) {
-                    message = `# 任务
+                    message = `NOTICE Language: ${this.getLang()}
+
+# 任务
 请根据<任务教案>,为教师生成本的教学ppt的大纲,大纲包含各环节的步骤,学科知识点。
 
 # 参考资料
@@ -1044,7 +1069,7 @@ ${mclass.length ? "面向年级:" + mclass.join(",") : ""}
                     type == 1
                         ? "6063369f-289a-11ef-8bf4-12e77c4cb76b"
                         : "f8e1ebb2-2e0d-11ef-8bf4-12e77c4cb76b",
-                message: [{ type: "text", text: message.replaceAll('\n', " ") }],
+                message: [{ type: "text", text:this.languageSetting == 1 ? converter2(message.replaceAll('\n', " ")) : message.replaceAll('\n', " ")  }],
                 session_name: uuidv4(),
                 userId: this.userid,
                 file_ids: fileid.length ? [...fileid] : "",

+ 324 - 92
src/components/pages/aiAddCourse/evaList.vue

@@ -1,15 +1,21 @@
 <template>
-    <div v-loading="loading" :style="{ minHeight: loading ? '250px' : 'auto' }" element-loading-text="小可正在努力生成中,请稍等...">
+    <div style="position: relative;" v-loading="loading" :style="{ minHeight: loading ? '250px' : 'auto' }"
+        element-loading-text="小可正在努力生成中,请稍等...">
+        <div class="c_pub_button_confirm stopBtn" v-if="loading" @click="cancelAjax('elist')">停止</div>
         <div class="elist_title">
             <div style=" display: flex; flex-direction: row;align-items: center; margin: 20px 0; ">
                 <div class="lineTitle clineTitle">评价设置</div>
                 <div style="margin-left:auto;display: flex;">
-                    <div class="r_pub_button_op" style="margin-left:10px;" @contextmenu.prevent="openAiDialog(1)"
-                        @click="openAiDialog(2)">{{ (eList && eList.length) ? '重新生成评价' : '生成评价' }}</div>
+                    <div class="r_pub_button_op" style="margin-left:10px;" @click="openAiDialog('elist')">{{ (eList &&
+                        eList.length) ? '重新生成评价' : '生成评价' }}</div>
+                    <div  v-if="eList && eList.length" class="show_taskD" :class="{ show: interPan }" @click="toggleInterPan">
+                        <img src="../../../assets/icon/new/u_up.png" />
+                        {{ interPan ? '收起' : '展开' }}
+                    </div>
                 </div>
             </div>
         </div>
-        <div class="mbCss">
+        <div class="mbCss" v-show="interPan">
             <div class="pjCss">
                 <div v-if="eList && eList.length" class="elist_input">
                     <div v-for="(eItem, eIndex) in eList" :key="eIndex" class="elist_input_box">
@@ -34,10 +40,18 @@
                         <div class="elist_inptu_text" style="align-items: flex-start;">
                             <span>评价细则:</span>
                             <div style="width: calc(100%);">
-                                <div @click="openRule(eIndex)" class="ruleBtn">{{ eItem.isrule ? '收起细则' : '展开细则' }}</div>
-                                <div style="width: calc(100%);" class='op_task_box' v-if="eItem.isrule">
+                                <div @click="openRule(eIndex)" class="ruleBtn">{{ eItem.isrule ? '收起细则' : '展开细则' }}
+                                </div>
+                                <div style="width: calc(100%);" class='op_task_box' v-if="eItem.isrule" v-loading="ruleLoading.length && ruleLoading[eIndex]" element-loading-text='小可正在努力生成中,请稍等...'>
+                                    <div class="c_pub_button_confirm stopBtn" v-if="ruleLoading.length && ruleLoading[eIndex]" @click="cancelAjax('rule', eIndex)">停止</div>
                                     <textarea v-autoHeight="68" rows="2" class="binfo_input binfo_textarea" cols
                                         v-model="eItem.rule" placeholder="请输入评价细则"></textarea>
+                                    <div class="op_box">
+                                        <div class="op_remark"></div>
+                                        <div style="display: flex;">
+                                                <div class="r_pub_button_op" @click="openAiDialog('rule', eIndex)">{{ eList[eIndex].rule ? '重新生成' : '生成细则'}}</div>
+                                        </div>
+                                    </div>
                                 </div>
                             </div>
 
@@ -67,7 +81,7 @@ let converter2 = OpenCC.Converter({
 
 export default {
     props: {
-        toolsData:{
+        toolsData: {
             type: Object,
             default: () => ({})
         },
@@ -95,19 +109,27 @@ export default {
             type: Number,
             required: true
         },
+        aiJson: {
+            type: Object,
+            default: () => { }
+        }
     },
     data() {
         return {
             eList: [],
             isEvaFold: true,
-            loading: false
+            loading: false,
+            cancelToken: null,
+            ruleLoading: [],
+            cancelRuleToken: [],
+            interPan: true
         }
     },
     watch: {
         eList: {
             handler(newVal) {
-                if(newVal.length > 0) {
-                    this.$emit('setEvaList', this.itemTaskIndex, this.toolIndex ,newVal)
+                if (newVal.length > 0) {
+                    this.$emit('setEvaList', this.itemTaskIndex, this.toolIndex, newVal)
                 }
             },
             immediate: true,
@@ -128,9 +150,9 @@ export default {
             update(el, binding) {
                 const { value } = binding;
                 if (value && typeof value === "number") {
-                el.style.height = `${value}px`;
+                    el.style.height = `${value}px`;
                 } else {
-                el.style.height = "auto";
+                    el.style.height = "auto";
                 }
             },
             componentUpdated(el) {
@@ -139,6 +161,34 @@ export default {
         },
     },
     methods: {
+        toggleInterPan() {
+            this.interPan = !this.interPan;
+            this.forceUpdate();
+        },
+        cancelAjax(type, index) {
+            if(type == 'elist'){
+                this.$message.success("已经成功停止生成评价设置")
+                if (this.cancelToken) {
+                    this.cancelToken.cancel('Request canceled by the user.');
+                    this.cancelToken = null;
+                }
+                if (this.loading) {
+                    this.loading = false
+                }
+                this.$forceUpdate()
+            }else if(type == 'rule'){
+                this.$message.success("已经成功停止生成评价细则")
+                if (this.cancelRuleToken && this.cancelRuleToken[index]) {
+                    this.cancelRuleToken[index].cancel('Request canceled by the user.');
+                    this.cancelRuleToken[index] = null;
+                }
+                if (this.ruleLoading[index]) {
+                    this.ruleLoading[index] = false
+                }
+                this.$forceUpdate()
+            }
+
+        },
         forceUpdate() {
             this.$forceUpdate();
         },
@@ -153,12 +203,30 @@ export default {
             }
             return lang
         },
-        openAiDialog() {
+        openAiDialog(type, index) {
+            if (type == 'elist') {
+                this.aiElist();
+                if(type == 1){
+                    this.$emit('addCourseBehavior','courseBehavior', `右键学历案-任务${this.itemTaskIndex + 1}-工具${this.toolIndex + 1}-评价设置-${(this.eList && this.eList.length) ? '重新生成评价' : '生成评价'}按钮`)
+                }else{
+                    this.$emit('addCourseBehavior','courseBehavior', `右键学历案-任务${this.itemTaskIndex + 1}-任务${this.toolIndex + 1}-评价设置-${(this.eList && this.eList.length) ? '重新生成评价' : '生成评价'}按钮`)
+                }
+                this.$emit('')
+            } else if (type == 'rule') {
+                this.aiRule(index);
+                this.$emit('addCourseBehavior','courseBehavior', `右键学历案-任务${this.itemTaskIndex + 1}-任务${this.toolIndex + 1}-评价设置-评价${index + 1}-重新生成细则按钮`)
+            }
+        },
+        aiElist() {
             let _this = this
+            if(!_this.knowFileids.length){
+                this.$message.error(`请添加文件后,再生成评价`)
+                return
+            }
             _this.loading = true
 
             let fileid = _this.knowFileids.length ? [..._this.knowFileids] : []
-            let taskDetail = _this.unitJson[0].chapterInfo[0].taskJson[_this.itemTaskIndex].taskDetail.replaceAll('\n', ' ')
+            let taskDetail = _this.unitJson[0].chapterInfo[0].taskJson[_this.itemTaskIndex].taskDetail ? _this.unitJson[0].chapterInfo[0].taskJson[_this.itemTaskIndex].taskDetail.replaceAll('\n', ' ') : ''
             let tool = _this.toolsData[_this.unitJson[0].chapterInfo[0].taskJson[_this.itemTaskIndex].toolChoose[_this.toolIndex].tool[0]].name
             let toolDetail = _this.unitJson[0].chapterInfo[0].taskJson[_this.itemTaskIndex].toolChoose[_this.toolIndex].toolDetail.replaceAll('\n', ' ')
             let messages = `
@@ -177,14 +245,17 @@ Language: ${this.getLang()}
 【排序】通过设置卡片内容及正确顺序,用于教师使用排序类题目检查学生对知识的掌握和理解,参考指标为题目的正确率。。
 【选择匹配】通过上传题目图片与设置正确回答,用于教师使用选择匹配(连线)检查学生对知识的掌握和理解,参考指标为题目的正确率。
 
-你要学习上传的文件。文件中包含了该学科的“核心素养”和“课程目标”。“学科核心素养”指学生应具备的,能够适应终身发展和社会发展需要的必备品格和关键能力。“课程目标”指课程本身要实现的具体目标和意图。它规定了某一教育阶段的学生通过课程学习以后,在发展品德、智力、体质等方面期望实现的程度,它是确定课程内容、教学目标和教学方法的基础。当选择多个学科时,上传的文件中会有《跨学科素养参考资料》,跨学科素养能力指具有跨文化一致性的、能够跨越一系列情境长期存在和发挥作用的、个体用于应对和解决日常工作和生活中遇到的各种复杂挑战和需求的能力;例如“个人社会责任”“任务规划”“自我管理”“承担风险”等。你要理解学科核心素养和学科目标之间的关系。你生成的评价标准可以被理解为课程目标。
+你要学习上传的文件。你只需要学习文件中“课程目标”一级标题下的内容,其中包含了该学科的“核心素养”和“目标”。“学科核心素养”指学生应具备的,能够适应终身发展和社会发展需要的必备品格和关键能力。“目标”指课程本身要实现的具体目标和意图。它规定了某一教育阶段的学生通过课程学习以后,在发展品德、智力、体质等方面期望实现的程度,它是确定课程内容、教学目标和教学方法的基础。
+当选择多个学科时,上传的文件中会有《跨学科素养参考资料》,跨学科素养能力指具有跨文化一致性的、能够跨越一系列情境长期存在和发挥作用的、个体用于应对和解决日常工作和生活中遇到的各种复杂挑战和需求的能力;例如“个人社会责任”“任务规划”“自我管理”“承担风险”等。
+你要理解学科核心素养和学科目标之间的关系。你生成的评价标准可以被理解为课程目标。
 
 #目标#
 你需要完成以下五个步骤:
 1.你要结合<任务描述:${taskDetail}><工具名称:${tool}>和<工具描述:${toolDetail}>生成适合当前作业的评价标准。评价标准用于评估学生的能力。
-2.你要严格按照上传的文件中的“核心素养”和“课程目标”将评价标准对应到课程目标中,并且用学科核心素养归类。3.只有当上传的文件包含多个学科的课程标准时,允许你检索《跨学科素养参考资料》。你需要先检索学科的课程标准,如果你判断当前的评价标准无法对应具体学科的核心素养,可以检索《跨学科素养参考资料》,找到最合适的并输出。输出的内容一定要包含“核心素养”和“上传文件对应的学科”。
+2.你要严格检索和匹配上传文件的核心素养和课程目标内容。你要先将评价标准对应到课程目标中,然后检索上传的文件中的“核心素养”与评价标准进行匹配。所有的核心素养信息必须以上传文件的内容为依据,不可以使用其他的默认信息或者模版!每个评价标准必须与文件中明确的核心素养对应。避免使用文件中未出现的任何术语,不允许输出范例内容!。
+3.只有当上传的文件包含多个学科的课程标准时,允许你检索《跨学科素养参考资料》。你需要先检索学科的课程标准,如果你判断当前的评价标准无法对应具体学科的核心素养,可以检索《跨学科素养参考资料》,找到最合适的并输出。
 4.你要根据第一步生成的评价标准制定每一条标准的六级评价细则,这个评价细则将用于对学生能力的打分。
-5.你要将前四步生成的内容整理成json格式。
+5.只输出json格式的内容,其他的不要输出!不要输出范例内容!
 
 #风格#
 专业的教育教学用语,简练易懂,逻辑性强。
@@ -197,7 +268,7 @@ Language: ${this.getLang()}
 
 #输出要求#
 1.评价标准的个数为3个,最多不能超过5个。每一个评价标准不能超过30字。生成的评价标准只能和当前的工具描述相关。选择题、排序、选择匹配工具只生成1个评价标准。输出内容和格式要求参考#评价标准范例#。注意!输出内容不能复制范例内容。注意!不要出现#评价标准错误范例#中的情况。
-2.每1个评价标准只对应1个关联度最高的核心素养。学科核心素养输出参考#核心素养输出范例#。注意!1个评价标准不能对应多个核心素养。
+2.每1个评价标准只对应1个关联度最高的核心素养。学科核心素养输出参考#核心素养输出范例#。注意!1个评价标准不能对应多个核心素养。不允许出现上传文件中没有的核心素养。严格按照上传文件中检索到的核心素养输出。每个评价标准必须与文件中明确的核心素养对应。避免使用文件中未出现的任何术语。不要出现#核心素养错误范例#中的情况,不允许输出范例内容!。
 3.具体的评价细则分为6级——0星,1星,2星,3星,4星,5星。输出格式参考#评价细则范例#。
 4.参考#json格式范例#,将内容整理成json格式。**之间的内容为json格式中的代码。以*[*开头,以*]*结尾。每一条评价标准、核心素养以及评价细则以*{*开头,以*}*结尾。核心素养前有*"core":*。评价标准前有*"std":*。评价细则前有*"rule":*。具体内容放在""之间。格式如下:
 [
@@ -205,7 +276,7 @@ Language: ${this.getLang()}
     {"std":"评价标准","core":"核心素养(识别上传文件对应的学科)","rule":"评价细则"},
     {"std":"评价标准","core":"核心素养(识别上传文件对应的学科)","rule":"评价细则"},
 ]
-5.只输出json格式的内容,其他的不要输出
+5.只输出json格式的内容,其他的不要输出!不要输出范例内容!
 
 #评价标准范例#
 学生能够准确描述红树林生态功能及动植物之间的关系。 
@@ -224,12 +295,16 @@ Language: ${this.getLang()}
 4 星,问题或需求的大部分被识别;
 5 星,问题或需求都被识别。 
 
+#核心素养错误范例#
+错误范例1:评价标准是“学生能够展示诗歌与美术作品的创作主题和过程”。输出“审美创造(艺术)”是错误的,因为艺术学科的核心素养中没有“审美创造”。
+错误范例2:评价标准是“学生能通过文档工具记录并总结对诗词的人文感悟。”。输出“表达与交流(语文)”是错误的,因为艺术学科的核心素养中没有“表达与交流”。
+
 #核心素养输出范例#
 评价标准:学生应该能准确指出光合作用发生的具体部位(叶绿体),并解释叶绿体在光合作用中的重要性。
 这一条标准对应了《义务教育科学课程标准(2022年版)》中“科学观念”这一条核心素养,识别上传文件对应的学科为“科学”。所以输出内容为:科学观念(科学)。
 
 评价标准:学生能够通过文档工具清晰表达小学生活的回忆与未来期望。
-这一条标准对应了《义务教育艺术课程标准(2022年版)》中“艺术表现”这一条核心素养,识别上传文件对应的学科为“艺术”。所以输出内容为:科学观念(艺术)。
+这一条标准对应了《义务教育艺术课程标准(2022年版)》中“艺术表现”这一条核心素养,识别上传文件对应的学科为“艺术”。所以输出内容为:艺术表现(艺术)。
 
 评价标准:学生能够在小组讨论中积极表达观点。
 这一条标准对应了《跨学科素养参考资料》中“合作能力”这一条核心素养,识别上传文件对应的学科为“跨学科”。所以输出内容为:合作能力(跨学科)。
@@ -260,7 +335,7 @@ Language: ${this.getLang()}
 "},
 ]`
             _this.loading = true
-
+            _this.cancelToken = _this.ajax.setCancelSource();
             let params = {
                 assistant_id: '6063369f-289a-11ef-8bf4-12e77c4cb76b',
                 message: [{ "type": "text", "text": this.languageSetting == 1 ? converter2(messages.replaceAll('\n', " ").replaceAll('*', "")) : messages.replaceAll('\n', " ").replaceAll('*', "") }],
@@ -270,7 +345,7 @@ Language: ${this.getLang()}
                 model: 'gpt-4o-2024-08-06',
                 temperature: 0.1,
             }
-            _this.ajax.post('https://gpt4.cocorobo.cn/ai_agent_park_chat', params).then(function (response) {
+            _this.ajax.post('https://gpt4.cocorobo.cn/ai_agent_park_chat', params, _this.cancelToken).then(function (response) {
                 console.log(response);
                 let data = response.data.FunctionResponse
                 if (data.message) {
@@ -279,14 +354,14 @@ Language: ${this.getLang()}
                     try {
                         elist = JSON.parse(data.message.replaceAll("```json", "").replaceAll("```", ""))
                         elist = elist.map(el => {
-                                return {
-                                    isai : "1",
-                                    target: el.core,
-                                    value: "",
-                                    detail: el.std,
-                                    rule: el.rule.replace(/([;。])/g, '$1\n')
-                                }
-                            })
+                            return {
+                                isai: "1",
+                                target: el.core,
+                                value: "",
+                                detail: el.std,
+                                rule: el.rule.replace(/([;。])/g, '$1\n')
+                            }
+                        })
                     } catch (e) {
                         console.log("error_________________" + e);
                         try {
@@ -295,7 +370,7 @@ Language: ${this.getLang()}
                             elist = JSON.parse(match[0])
                             elist = elist.map(el => {
                                 return {
-                                    isai : "1",
+                                    isai: "1",
                                     target: el.core,
                                     value: "",
                                     detail: el.std,
@@ -311,28 +386,130 @@ Language: ${this.getLang()}
                     _this.eList = elist
                     _this.$forceUpdate()
                 }
+                _this.cancelToken = null
                 _this.loading = false
             }).catch(function (error) {
+                _this.cancelToken = null
                 _this.loading = false
                 console.log(error);
             });
         },
+        aiRule(index) {
+            if (!this.eList[index].target || !this.eList[index].detail) {
+                this.$message.error(`评价信息完善信息后再生成细则`)
+                return;
+            }
+            this.ruleLoading[index] = true
+            this.$forceUpdate();
+            let message = `NOTICE
+Role: 你是一个专业的项目式学习导师
+Language: ${this.getLang()}
+ATTENTION: Use '##' to SPLIT SECTIONS, not '#'. Output format carefully referenced "Format example".
+Instruction: Based on the context, follow "Format example", write content.
+
+#Context
+##要求
+${this.aiJson['aiRateRule']}
+
+<评价标准:${this.eList[index].detail}>
+<素养:${this.eList[index].target}>
+
+参考#Format example#,将内容整理成字符串格式。。以*====*开头,以*====*结尾。格式如下:
+==== 
+0 星,作业内容与作业要求无关;
+1 星,没有识别问题和需求;
+2 星,问题或需求没有被清晰理解或准确识别;
+3 星,问题或需求的一部分被识别;
+4 星,问题或需求的大部分被识别;
+5 星,问题或需求都被识别。 
+==== 
+
+# Format example
+==== 
+0 星,作业内容与作业要求无关;
+1 星,没有识别问题和需求;
+2 星,问题或需求没有被清晰理解或准确识别;
+3 星,问题或需求的一部分被识别;
+4 星,问题或需求的大部分被识别;
+5 星,问题或需求都被识别。 
+==== `
+            this.cancelRuleToken[index] = this.ajax.setCancelSource();
+            let parm = {
+                assistant_id: 'b19f1a1a-7586-11ef-8ce0-12e77c4cb76b',
+                message: [{ "type": "text", "text": this.languageSetting == 1 ? converter2(message.replaceAll('\n', " ").replaceAll('*', "")) : message.replaceAll('\n', " ").replaceAll('*', "") }],
+                session_name: uuidv4(),
+                userId: this.userid,
+                file_ids: [],
+                model: 'gpt-4o-2024-08-06',
+                temperature: this.mode == 3 ? 0.5 : 0.1,
+            }
+            this.ajax
+                .post("https://gpt4.cocorobo.cn/ai_agent_park_chat", parm, this.cancelRuleToken[index])
+                .then(async (response) => {
+                    console.log(response);
+                    let data = response.data.FunctionResponse
+                    if (data.message) {
+                        console.log(data.message);
+
+                        let content = data.message;
+
+                        var regex = new RegExp('====([^=]+)====');
+                        let match = content.match(regex);
+                        if (match) {
+                            content = match[1].trim().replaceAll(' ', '');
+                            // 获取匹配到的内容并去除两端空格 
+                            console.log('contentInsideDoubleEquals', content);
+                            if (content.indexOf('0星') == -1 || content.indexOf('2星') == -1 || content.indexOf('3星') == -1 || content.indexOf('4星') == -1 || content.indexOf('5星') == -1) {
+                                console.log("未有星级。");
+                                this.cancelRuleToken[index] = null
+                                this.aiRule(index)
+                                return
+                            }
+                            //  return 
+                        } else {
+                            console.log("未找到匹配的内容。");
+                            this.cancelRuleToken[index] = null
+                            this.aiRule(index)
+                            return
+                        }
+
+                        let elist = JSON.parse(JSON.stringify(this.eList[index]))
+                        elist.rule = JSON.parse(JSON.stringify(content.replaceAll("#", "")))
+                        this.$set(this.eList, index, elist)
+                        this.$forceUpdate();
+                        this.ruleLoading[index] = false
+                        this.cancelRuleToken[index] = null
+                        this.$message.success(`评价细则生成成功`)
+                    } else {
+                        this.ruleLoading[index] = false
+                        this.cancelRuleToken[index] = null
+                    }
+                    this.$forceUpdate();
+
+                })
+                .catch((error) => {
+                    this.cancelRuleToken[index] = null
+                    this.ruleLoading[index] = false
+                    this.$forceUpdate();
+                    console.log(error);
+                });
+        },
         addEList() {
 
         },
         deletEList(index) {
             this.$confirm('确定删除吗?').then(() => {
                 this.eList.splice(index, 1)
-                if(!this.eList.length){
-                    this.$emit('setEvaList', this.itemTaskIndex, this.toolIndex ,_this.eList)
+                if (!this.eList.length) {
+                    this.$emit('setEvaList', this.itemTaskIndex, this.toolIndex, this.eList)
                 }
             })
 
         },
-        openRule(i){
-            if(this.eList[i].isrule){
+        openRule(i) {
+            if (this.eList[i].isrule) {
                 this.eList[i].isrule = false
-            }else {
+            } else {
                 this.eList[i].isrule = true
             }
             setTimeout(() => {
@@ -346,7 +523,15 @@ Language: ${this.getLang()}
     },
     mounted() {
         let elist = this.unitJson[0].chapterInfo[0].taskJson[this.itemTaskIndex].toolChoose[this.toolIndex].eList
-        this.eList = elist ? JSON.parse(JSON.stringify(elist))  : []
+        this.eList = elist ? JSON.parse(JSON.stringify(elist)) : []
+        for (let i = 0; i < this.eList.length; i++) {
+            setTimeout(() => {
+                this.eList[i].rule += "*0*%*";
+                setTimeout(() => {
+                    this.eList[i].rule = this.eList[i].rule.replaceAll("*0*%*", "");
+                }, 0);
+            }, 500);
+        }
     }
 }
 </script>
@@ -400,15 +585,15 @@ Language: ${this.getLang()}
 }
 
 .remove {
-  background-image: url("../../../assets/icon/new/delete_u.png");
-  cursor: pointer;
-  opacity: 0.5;
-  width: 30px;
-  min-width: 30px;
-  height: 30px;
-  background-size: 100% 100%;
-  background-repeat: no-repeat;
-  margin-left: 10px;
+    background-image: url("../../../assets/icon/new/delete_u.png");
+    cursor: pointer;
+    opacity: 0.5;
+    width: 30px;
+    min-width: 30px;
+    height: 30px;
+    background-size: 100% 100%;
+    background-repeat: no-repeat;
+    margin-left: 10px;
 }
 
 .elist_input .elist_input_box .remove {
@@ -813,77 +998,124 @@ Language: ${this.getLang()}
 }
 
 .ruleBtn {
-  width: fit-content;
-  height: 36px;
-  line-height: 36px;
-  cursor: pointer;
-  color: #0061FF;
+    width: fit-content;
+    height: 36px;
+    line-height: 36px;
+    cursor: pointer;
+    color: #0061FF;
 }
 
 .op_task_box,
 .op_tool_box,
 .outline_detail {
-  position: relative;
+    position: relative;
 }
 
 .op_task_box:hover .op_box,
 .op_tool_box:hover .op_box,
 .outline_detail:hover .op_box {
-  display: flex
+    display: flex
+}
+
+.op_box {
+  position: absolute;
+  bottom: 10px;
+  display: none;
+  align-items: center;
+  justify-content: space-between;
+  width: calc(100% - 20px);
+  left: 50%;
+  transform: translateX(-50%);
+}
+
+.op_box .op_remark {
+  color: #8E8E8E;
+  font-size: 14px;
 }
 
 .binfo_input {
-  width: 100%;
-  margin: 0;
-  padding: 12px 14px;
-  display: block;
-  min-width: 0;
-  outline: none;
-  box-sizing: border-box;
-  background: none;
-  border: none;
-  border-radius: 4px;
-  background: #fff;
-  font-size: 16px;
-  resize: none;
-  font-family: "Microsoft YaHei";
-  min-height: 48px;
-  /* border: 1px solid #3682fc00; */
-  border: 1.5px solid #cad1dc;
+    width: 100%;
+    margin: 0;
+    padding: 12px 14px;
+    display: block;
+    min-width: 0;
+    outline: none;
+    box-sizing: border-box;
+    background: none;
+    border: none;
+    border-radius: 4px;
+    background: #fff;
+    font-size: 16px;
+    resize: none;
+    font-family: "Microsoft YaHei";
+    min-height: 48px;
+    /* border: 1px solid #3682fc00; */
+    border: 1.5px solid #cad1dc;
 }
 
 .binfo_textarea {
-  border: 1.5px solid #cad1dc;
-  font-size: 16px;
-  resize: none;
-  /* background: #f6f6f6; */
-  font-family: "Microsoft YaHei";
+    border: 1.5px solid #cad1dc;
+    font-size: 16px;
+    resize: none;
+    /* background: #f6f6f6; */
+    font-family: "Microsoft YaHei";
 }
 
 .binfo_input:focus-visible {
-  border: 1.5px solid #3681fc !important;
+    border: 1.5px solid #3681fc !important;
 }
-.rateSwitch >>> .el-switch__label.is-active {
-  color: #0061FF;
+
+.rateSwitch>>>.el-switch__label.is-active {
+    color: #0061FF;
+}
+
+.rateSwitch>>>.el-switch__label--right {
+    display: flex;
+    align-items: center;
+}
+
+.rateSwitch>>>.el-switch__label--right::before {
+    content: "";
+    width: 14px;
+    height: 14px;
+    min-width: 14px;
+    min-height: 14px;
+    background-size: 100% 100%;
+    display: block;
+    margin-right: 7px;
+    background-image: url(../../../assets/icon/new/u_op2.png);
+}
+
+.rateSwitch>>>.el-switch__label--right.is-active::before {
+    background-image: url(../../../assets/icon/new/u_op.png);
+}
+
+.stopBtn {
+    z-index: 999999;
+    position: absolute;
+    left: 50%;
+    top: calc(50% + 70px);
+    transform: translateX(-50%);
 }
 
-.rateSwitch >>> .el-switch__label--right{
+.show_taskD {
+  min-width: fit-content;
+  margin-left: 10px;
   display: flex;
   align-items: center;
+  font-size: 14px;
+  cursor: pointer;
+  color: #0061FF;
 }
-.rateSwitch >>> .el-switch__label--right::before{
-  content: "";
-  width: 14px;
-  height: 14px;
-  min-width: 14px;
-  min-height: 14px;
-  background-size: 100% 100%;
-  display: block;
-  margin-right: 7px;
-  background-image: url(../../../assets/icon/new/u_op2.png);
-}
-.rateSwitch >>> .el-switch__label--right.is-active::before{
-  background-image: url(../../../assets/icon/new/u_op.png);
+
+.show_taskD>img {
+  width: 15px;
+  margin-right: 5px;
+  transition: 0.2s all;
+  transform: rotate(-90deg);
 }
 
+.show_taskD.show>img {
+  transform: rotate(0deg);
+}
 </style>

+ 641 - 0
src/components/pages/aiAddCourse/jsmind2.vue

@@ -0,0 +1,641 @@
+<template>
+  <!-- 普通菜单 -->
+  <div class="jsmind_layout">
+    <div class="noMind" v-if="mindV">
+      <span>暂无数据</span>
+    </div>
+    <div :id="jsmindId" ref="container" class="jsmind_container"></div>
+  </div>
+</template>
+
+<script>
+import "jsmind/style/jsmind.css";
+import jsMind from "jsmind/js/jsmind.js";
+window.jsMind = jsMind;
+
+require("jsmind/js/jsmind.draggable.js");
+require("jsmind/js/jsmind.screenshot.js");
+export default {
+  props: {
+    showBar: {
+      // 是否显示工具栏,显示启用编辑
+      type: Boolean,
+      default: true,
+    },
+    theme: {
+      // 主题
+      type: String,
+      default: "primary",
+    },
+    lineColor: {
+      // 线条颜色
+      type: String,
+      default: "skyblue",
+    },
+    mindData: {
+      type: Object,
+      default: {},
+    },
+    jsmindId: {
+      type: String,
+      default: "jsmind_container",
+    },
+  },
+  data() {
+    return {
+      mindV: false,
+      i: 0,
+      mind: {},
+      jm: null,
+      isZoomIn: false,
+      isZoomOut: false,
+      level: 0,
+      nodeOptions: [
+        { value: 1, label: "展开到一级节点" },
+        { value: 2, label: "展开到二级节点" },
+        { value: 3, label: "展开到三级节点" },
+        { value: 0, label: "展开全部节点" },
+        { value: -1, label: "隐藏全部节点" },
+      ],
+      themeOptions: [
+        { value: "default", label: "default" },
+        { value: "primary", label: "primary" },
+        { value: "warning", label: "warning" },
+        { value: "danger", label: "danger" },
+        { value: "success", label: "success" },
+        { value: "info", label: "info" },
+        { value: "greensea", label: "greensea" },
+        { value: "nephrite", label: "nephrite" },
+        { value: "belizehole", label: "belizehole" },
+        { value: "wisteria", label: "wisteria" },
+        { value: "asphalt", label: "asphalt" },
+        { value: "orange", label: "orange" },
+        { value: "pumpkin", label: "pumpkin" },
+        { value: "pomegranate", label: "pomegranate" },
+        { value: "clouds", label: "clouds" },
+        { value: "asbestos", label: "asbestos" },
+      ],
+      localTheme: this.theme,
+      dialogVisible: false,
+      nodeOption: {
+        content: "",
+        bgColor: "",
+        fontColor: "",
+        fontSize: "",
+        fontWeight: "",
+        fontStyle: "",
+      },
+    };
+  },
+  watch: {
+    mindData: {
+      handler: function (cur, old) {
+        console.log(cur.task);
+        let data = this.setMindData(cur)
+        this.mind = data;
+        this.$forceUpdate()
+        if (data.data.length) {
+          if (data.data[0].topic === "" && data.data.length === 1) {
+            this.mindV = true;
+          } else {
+            this.mindV = false;
+          }
+          setTimeout(() => {
+            if (this.jm) {
+              this.jm.show(this.mind);
+            } else {
+              this.open_empty();
+            }
+          }, 1000);
+        }
+      },
+      deep: true, //对象内部的属性监听,也叫深度监听
+    },
+  },
+  created() { },
+  mounted() {
+    this.getData();
+    // this.mouseWheel();
+  },
+  methods: {
+    refresh(){
+      // console.log(cur.task);
+      let data = this.setMindData(this.mindData)
+      this.mind = data;
+      this.$forceUpdate()
+      if (data.data.length) {
+        if (data.data[0].topic === "" && data.data.length === 1) {
+          this.mindV = true;
+        } else {
+          this.mindV = false;
+        }
+        setTimeout(() => {
+          if (this.jm) {
+            this.jm.show(this.mind);
+          } else {
+            this.open_empty();
+          }
+        }, 1000);
+      }
+    },
+    beforeUpload(file) {
+      // 上传文件之前钩子
+      if (file) {
+        jsMind.util.file.read(file, (jsmindData) => {
+          const mind = jsMind.util.json.string2json(jsmindData);
+          if (mind) {
+            this.jm.show(mind);
+            this.$message({ type: "success", message: "打开成功" });
+          } else {
+            this.prompt_info("不能打开mindmap文件");
+          }
+        });
+      } else {
+        this.prompt_info("请先选择文件");
+        return false;
+      }
+    },
+    upload() { },
+    getData() {
+      //   this.$API({
+      //     name: "getMind",
+      //   })
+      //     .then((res) => {
+      //       this.mind = res.data;
+      //       this.open_empty();
+      //     })
+      //     .catch((error) => {
+      //       this.$message.error(error);
+      //     });
+      let data = this.setMindData(this.mindData)
+      this.mind = data;
+      // debugger
+      this.$forceUpdate()
+      if (data.data.length) {
+        if (data.data[0].topic === "" && data.data.length === 1) {
+          this.mindV = true;
+        } else {
+          this.mindV = false;
+        }
+        setTimeout(() => {
+          if (this.jm) {
+            this.jm.show(this.mind);
+          } else {
+            this.open_empty();
+          }
+        }, 1000);
+      }
+    },
+    setMindData(json) {
+      let data = {
+        meta: {
+          name: json.task,
+          author: "dd@163.com",
+          version: "0.2",
+        },
+        format: "node_array",
+        data: []
+      };
+      data.data.push({ id: "root", isroot: true, topic: json.task });
+      console.log(`111`, json.task);
+      let elist = []
+      let toolChoose = json.toolChoose;
+      for (let i = 0; i < toolChoose.length; i++) {
+        if(toolChoose[i].eList && toolChoose[i].eList.length){
+          let list = toolChoose[i].eList.map(item=>{
+            return {
+              ...item,
+              index: i
+            }
+          })
+          elist.push(...list)
+        }
+      }
+      if(elist.length){
+        let listName = []
+      for(var i = 0;i<elist.length;i++){
+        let item = elist[i]
+        if(listName.indexOf(item.target) == -1){
+          listName.push(item.target)
+        }
+      }
+      let _eJson = {}
+      for(var i = 0;i<listName.length;i++){
+        let listItem = listName[i]
+        _eJson[listItem] = {
+          child: {}
+        }
+        for(var j = 0;j<elist.length;j++){
+          let item2 = elist[j]
+          if(item2.target == listItem){
+            if(_eJson[listItem].child[item2.detail]){
+              if(_eJson[listItem].child[item2.detail].child.indexOf(item2.index) == -1){
+                _eJson[listItem].child[item2.detail].child.push(item2.index)
+              }
+            }else{
+              _eJson[listItem].child[item2.detail] = {
+                child: [item2.index]
+              }
+            }
+          }
+        }
+      }
+      let _JsonName = Object.keys(_eJson)
+      for(var i = 0;i<_JsonName.length;i++){
+        let item = _eJson[_JsonName[i]]
+        data.data.push({
+          id: _JsonName[i],
+          parentid: "root",
+          topic: _JsonName[i],
+        })
+        let _eJsonz = Object.keys(item.child)
+        let _e3 = item.child
+        for(var j = 0;j<_eJsonz.length;j++){
+          let item2 = _e3[_eJsonz[j]]
+          data.data.push({
+            id: _eJsonz[j],
+            parentid: _JsonName[i],
+            topic: _eJsonz[j],
+          })
+          // let _eJsonz2 = Object.keys(item2.child)
+          let _e4 = item2.child
+          // for(var z = 0;z<_eJsonz2.length;z++){
+            // let item3 = _e4[_eJsonz2[z]]
+          for(var zz = 0;zz<_e4.length;zz++){
+            data.data.push({
+              // id: `${_eJsonz[j]}-${_eJsonz2[z]}-${zz}`,
+              id: `${_eJsonz[j]}-${zz}`,
+              parentid: _eJsonz[j],
+              topic: `工具${_e4[zz] + 1}`,
+            })
+          }
+
+          // }
+          }
+        }
+      }
+
+      console.log(json.task, data);
+      return data
+    },
+    open_empty() {
+      const options = {
+        container: this.jsmindId, // 必选,容器ID
+        editable: this.showBar, // 可选,是否启用编辑
+        theme: this.localTheme, // 可选,主题
+        view: {
+          line_width: 2, // 思维导图线条的粗细
+          // line_color: this.lineColor, // 思维导图线条的颜色
+        },
+        shortcut: {
+          enable: true, // 禁用快捷键
+        },
+        layout: {
+          hspace: 20, // 节点之间的水平间距
+          vspace: 10, // 节点之间的垂直间距
+          pspace: 13, // 节点与连接线之间的水平间距(用于容纳节点收缩/展开控制器)
+        },
+        mode: "side", // 显示模式,子节点只分布在根节点右侧
+      };
+      this.jm = jsMind.show(options, this.mind);
+      // 改变窗口大小重置画布
+      window.onresize = () => {
+        this.jm.resize();
+      };
+      this.getDepth(this.jm.mind.root, 1);
+      this.$forceUpdate();
+      setTimeout(()=>{
+        this.jm.show(this.mind);
+      },1000)
+    },
+    // 获取层级数 i
+    getDepth(obj, k) {
+      this.i = Math.max(this.i, k);
+      if (obj.children) {
+        obj.children.forEach((v) => {
+          this.getDepth(v, k + 1);
+        });
+      }
+    },
+    save_nodearray_file() {
+      const mindData = this.jm.get_data("node_array");
+      const mindName = mindData.meta.name;
+      const mindStr = jsMind.util.json.json2string(mindData);
+      jsMind.util.file.save(mindStr, "text/jsmind", mindName + ".jm");
+    },
+    screen_shot() {
+      this.jm.screenshot.shootDownload();
+    },
+    expand_all() {
+      this.jm.expand_all();
+    },
+    collapse_all() {
+      this.jm.collapse_all();
+    },
+    expand_to_level(num) {
+      switch (num) {
+        case -1:
+          this.collapse_all();
+          break;
+        case 0:
+          this.expand_all();
+          break;
+        default:
+          this.jm.expand_to_depth(num);
+          break;
+      }
+    },
+    zoomIn() {
+      if (this.jm.view.zoomIn()) {
+        this.isZoomOut = false;
+      } else {
+        this.isZoomIn = true;
+      }
+    },
+    zoomOut() {
+      // debugger;
+      if (this.jm.view.zoomOut()) {
+        this.isZoomIn = false;
+      } else {
+        this.isZoomOut = true;
+      }
+    },
+    prompt_info(msg) {
+      this.$message({ type: "warning", message: msg });
+    },
+    get_nodearray_data() {
+      const mindData = this.jm.get_data("node_array");
+      const mindString = jsMind.util.json.json2string(mindData);
+      this.$message({ type: "info", message: mindString });
+    },
+    set_theme(themeName) {
+      this.jm.set_theme(themeName);
+    },
+    scrollFunc(e) {
+      e = e || window.event;
+      if (e.wheelDelta) {
+        if (e.wheelDelta > 0) {
+          this.zoomIn();
+        } else {
+          this.zoomOut();
+        }
+      } else if (e.detail) {
+        if (e.detail > 0) {
+          this.zoomIn();
+        } else {
+          this.zoomOut();
+        }
+      }
+      this.jm.resize();
+    },
+    // 鼠标滚轮放大缩小
+    mouseWheel() {
+      if (document.addEventListener) {
+        document.addEventListener("domMouseScroll", this.scrollFunc, false);
+      }
+      this.$refs.container.onmousewheel = this.scrollFunc;
+    },
+    // 新增节点
+    addNode() {
+      let selectedNode = this.jm.get_selected_node();
+      if (!selectedNode) {
+        this.$message({ type: "warning", message: "请先选择一个节点!" });
+        return;
+      }
+      let nodeid = jsMind.util.uuid.newid();
+      let topic = "new Node";
+      let newNode = this.jm.add_node(selectedNode, nodeid, topic);
+      if (newNode) {
+        this.jm.select_node(nodeid);
+        this.jm.begin_edit(nodeid);
+        this.getDepth(this.jm.mind.root, 1);
+      }
+    },
+    // 新增兄弟节点
+    addBrotherNode() {
+      let selectedNode = this.jm.get_selected_node();
+      if (!selectedNode) {
+        this.$message({ type: "warning", message: "请先选择一个节点!" });
+        return;
+      } else if (selectedNode.isroot) {
+        this.$message({
+          type: "warning",
+          message: "不能在根节点添加,请重新选择节点!",
+        });
+        return;
+      }
+      let nodeid = jsMind.util.uuid.newid();
+      let topic = "new Node";
+      let newNode = this.jm.insert_node_after(selectedNode, nodeid, topic);
+      if (newNode) {
+        this.jm.select_node(nodeid);
+        this.jm.begin_edit(nodeid);
+      }
+    },
+    // 获取选中标签的 ID
+    get_selected_nodeid() {
+      let selectedNode = this.jm.get_selected_node();
+      if (selectedNode) {
+        return selectedNode.id;
+      } else {
+        return null;
+      }
+    },
+    // 删除节点
+    removeNode() {
+      let selectedId = this.get_selected_nodeid();
+      if (!selectedId) {
+        this.$message({
+          type: "warning",
+          message: "请先选择一个节点!",
+        });
+        return;
+      }
+      this.jm.remove_node(selectedId);
+      this.i = 0;
+      this.getDepth(this.jm.mind.root, 1);
+    },
+    // 编辑节点
+    editNode() {
+      let selectedId = this.get_selected_nodeid();
+      if (!selectedId) {
+        this.$message({ type: "warning", message: "请先选择一个节点!" });
+        return;
+      }
+      let nodeObj = this.jm.get_node(selectedId);
+      this.nodeOption.content = nodeObj.topic;
+      this.nodeOption.bgColor = nodeObj.data["background-color"];
+      this.nodeOption.fontColor = nodeObj.data["foreground-color"];
+      this.nodeOption.fontSize = nodeObj.data["font-size"];
+      this.nodeOption.fontWeight = nodeObj.data["font-weight"];
+      this.nodeOption.fontStyle = nodeObj.data["font-style"];
+      this.dialogVisible = true;
+    },
+    sureEditNode() {
+      let selectedId = this.get_selected_nodeid();
+      this.jm.update_node(selectedId, this.nodeOption.content);
+      this.jm.set_node_font_style(
+        selectedId,
+        this.nodeOption.fontSize,
+        this.nodeOption.fontWeight,
+        this.nodeOption.fontStyle
+      );
+      this.jm.set_node_color(
+        selectedId,
+        this.nodeOption.bgColor,
+        this.nodeOption.fontColor
+      );
+      this.nodeOption = {
+        content: "",
+        bgColor: "",
+        fontColor: "",
+        fontSize: "",
+        fontWeight: "",
+        fontStyle: "",
+      };
+      this.dialogVisible = false;
+    },
+  },
+  beforeDestroy() {
+    // document.removeEventListener("domMouseScroll", this.scrollFunc, false);
+  },
+};
+</script>
+
+<style scoped>
+.jsmind_layout {
+  display: flex;
+  flex-direction: column;
+  /* width: 700px; */
+  height: calc(100%);
+  /* height: 500px; */
+  /* margin: 15px 5px 0 0; */
+  /* background: #fff; */
+  overflow: hidden;
+  flex-shrink: 0;
+  position: relative;
+  width: 100%;
+  width: 100%;
+  background: #f1f1f1;
+  border-radius: 5px;
+}
+
+.jsmind_title {
+  position: absolute;
+  top: 20px;
+  left: 20px;
+  font-size: 20px;
+  color: #8d8d8d;
+}
+
+.noMind {
+  /* position: absolute; */
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  width: 100%;
+  height: 200px;
+  background: #fff;
+}
+
+.jsmind_layout .jsmind_toolbar {
+  width: 100%;
+  padding: 0 10px 10px 10px;
+  height: auto;
+  flex-shrink: 0;
+  display: flex;
+  align-items: center;
+  flex-wrap: wrap;
+  background-color: #f8f9fa;
+  box-shadow: 0 0 4px #b8b8b8;
+}
+
+.jsmind_layout>>>.el-button--medium,
+.jsmind_layout>>>.el-input--medium {
+  margin-top: 10px;
+}
+
+.jsmind_layout #jsmind_container {
+  /* flex: 1 1 auto; */
+  height: 100%;
+}
+
+.jsmind_layout .jsmind_container {
+  /* flex: 1 1 auto; */
+  height: 100%;
+}
+
+.jsmind_layout>>>.jsmind-inner {
+  /* overflow: hidden auto !important; */
+  /* height: auto; */
+}
+
+.jsmind_layout>>>.el-upload-list {
+  display: none !important;
+}
+
+/* 隐藏滚动条 */
+.jsmind_layout .jsmind-inner::-webkit-scrollbar {
+  display: none;
+}
+
+.jsmind_layout .pad {
+  margin-right: 10px;
+}
+
+.jsmind_layout .pad-left {
+  margin-left: 10px;
+}
+
+.jsmind_layout>>>jmnode {
+  white-space: inherit;
+  word-wrap: break-word;
+  max-width: 500px;
+  max-height: 75px;
+  overflow: auto;
+}
+
+.jsmind_layout>>>jmnode::-webkit-scrollbar {
+  /*滚动条整体样式*/
+  width: 6px;
+  /*高宽分别对应横竖滚动条的尺寸*/
+  height: 6px;
+}
+
+/*定义滚动条轨道 内阴影+圆角*/
+.jsmind_layout>>>jmnode::-webkit-scrollbar-track {
+  border-radius: 10px;
+  background-color: rgba(0, 0, 0, 0.1);
+}
+
+/*定义滑块 内阴影+圆角*/
+.jsmind_layout>>>jmnode::-webkit-scrollbar-thumb {
+  border-radius: 10px;
+  -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, .3);
+  background-color: rgba(0, 0, 0, 0.1);
+}
+
+.jsmind_layout>>>jmnode.selected {
+  background-color: #b9b9b9;
+  color: #fff;
+  box-shadow: 2px 2px 8px #777;
+}
+
+.jsmind_layout>>>jmnode.selected {
+  background-color: #b9b9b9;
+  color: #fff;
+  box-shadow: 2px 2px 8px #777;
+}
+
+.jsmind_layout>>>jmnode:hover {
+  box-shadow: 2px 2px 8px #777;
+}
+
+.jsmind_layout .form-con {
+  padding-top: 20px;
+}
+
+.jsmind_layout .ele-width {
+  width: 96%;
+}
+</style>

+ 195 - 92
src/components/pages/classroomObservation/components/addNewTeacherVoiceprintDialog.vue

@@ -1,14 +1,11 @@
 <template>
   <div>
-    <el-dialog
-      :center="true"
-      :visible.sync="dialogVisible"
-      width="600px"
-      class="addTemplateDialog"
-    >
+    <el-dialog :center="true" :visible.sync="dialogVisible" width="600px" class="addTemplateDialog" :before-close="close">
       <!-- <div v-if="showDialog == true" class="a-dialog" v-el-drag-dialog> -->
       <div class="a-d-top">
-        <div class="a-d-topTit"><div>新增声纹</div></div>
+        <div class="a-d-topTit">
+          <div>新增声纹</div>
+        </div>
 
         <div class="a-d-t-right">
           <span @click.stop="close()">×</span>
@@ -16,9 +13,9 @@
       </div>
       <div class="a_box">
         <div class="a_b_form">
-          <el-form label-position="top" :model="form" :rules="rules">
+          <el-form ref="form$" label-position="top" :model="form" :rules="rules" :disabled="status === 1">
             <el-form-item class="a_b_f_item" label="教师名称" prop="name">
-              <el-input v-model="form.name" :disabled="![0].includes(status)"></el-input>
+              <el-input v-model="form.name"></el-input>
             </el-form-item>
 
             <el-form-item class="a_b_f_item" label="声纹录制">
@@ -33,45 +30,39 @@
             <span>请使用正常语速朗读以上内容</span>
           </div>
           <div class="a_b_b_bottom">
-            <div class="a_b_b_b_btn" @click.stop="start()" v-if="status===0">
-              <svg
-                width="16"
-                height="22"
-                viewBox="0 0 16 22"
-                fill="none"
-                xmlns="http://www.w3.org/2000/svg"
-              >
-                <path
-                  fill-rule="evenodd"
-                  clip-rule="evenodd"
+            <div class="a_b_b_b_btn" @click.stop="start()" v-if="status === 0">
+              <svg width="16" height="22" viewBox="0 0 16 22" fill="none" xmlns="http://www.w3.org/2000/svg">
+                <path fill-rule="evenodd" clip-rule="evenodd"
                   d="M8.00009 13.8098C10.5962 13.8098 12.6876 11.6655 12.6876 9.00351V5.30633C12.6876 2.64436 10.5962 0.5 8.00009 0.5C5.40393 0.5 3.31259 2.64436 3.31259 5.30633V9.00351C3.31259 11.6655 5.40393 13.8098 8.00009 13.8098ZM4.75489 5.30633C4.75489 3.45774 6.1972 1.97887 8.00009 1.97887C9.80297 1.97887 11.2453 3.45774 11.2453 5.30633V9.00351C11.2453 10.8521 9.80297 12.331 8.00009 12.331C6.1972 12.331 4.75489 10.8521 4.75489 9.00351V5.30633ZM15.5 10.1132C15.5 9.70651 15.1755 9.37377 14.7788 9.37377C14.4183 9.37377 14.0938 9.66954 14.0577 10.0393C13.5529 13.034 11.0288 15.2892 8 15.2892C4.97115 15.2892 2.44712 13.034 1.94231 10.0393C1.90625 9.66954 1.58173 9.37377 1.22115 9.37377C0.824519 9.37377 0.5 9.70651 0.5 10.1132V10.2241C1.07692 13.6995 3.85337 16.3984 7.27885 16.7311V19.9223H4.15379C3.72896 19.9223 3.38456 20.2755 3.38456 20.7111C3.38456 21.1467 3.72896 21.4998 4.15379 21.4998H11.8461C12.2709 21.4998 12.6153 21.1467 12.6153 20.7111C12.6153 20.2755 12.2709 19.9223 11.8461 19.9223H8.72115V16.7311C12.1466 16.3984 14.9231 13.6995 15.4639 10.2611C15.4639 10.2426 15.473 10.2149 15.482 10.1872L15.482 10.1871C15.491 10.1594 15.5 10.1317 15.5 10.1132Z"
-                  fill="white"
-                />
+                  fill="white" />
               </svg>
               点击录制
             </div>
+            <div class="a_b_b_b_btn" style="background: red;cursor: default" v-else>
+              <svg width="16" height="22" viewBox="0 0 16 22" fill="none" xmlns="http://www.w3.org/2000/svg">
+                <path fill-rule="evenodd" clip-rule="evenodd"
+                  d="M8.00009 13.8098C10.5962 13.8098 12.6876 11.6655 12.6876 9.00351V5.30633C12.6876 2.64436 10.5962 0.5 8.00009 0.5C5.40393 0.5 3.31259 2.64436 3.31259 5.30633V9.00351C3.31259 11.6655 5.40393 13.8098 8.00009 13.8098ZM4.75489 5.30633C4.75489 3.45774 6.1972 1.97887 8.00009 1.97887C9.80297 1.97887 11.2453 3.45774 11.2453 5.30633V9.00351C11.2453 10.8521 9.80297 12.331 8.00009 12.331C6.1972 12.331 4.75489 10.8521 4.75489 9.00351V5.30633ZM15.5 10.1132C15.5 9.70651 15.1755 9.37377 14.7788 9.37377C14.4183 9.37377 14.0938 9.66954 14.0577 10.0393C13.5529 13.034 11.0288 15.2892 8 15.2892C4.97115 15.2892 2.44712 13.034 1.94231 10.0393C1.90625 9.66954 1.58173 9.37377 1.22115 9.37377C0.824519 9.37377 0.5 9.70651 0.5 10.1132V10.2241C1.07692 13.6995 3.85337 16.3984 7.27885 16.7311V19.9223H4.15379C3.72896 19.9223 3.38456 20.2755 3.38456 20.7111C3.38456 21.1467 3.72896 21.4998 4.15379 21.4998H11.8461C12.2709 21.4998 12.6153 21.1467 12.6153 20.7111C12.6153 20.2755 12.2709 19.9223 11.8461 19.9223H8.72115V16.7311C12.1466 16.3984 14.9231 13.6995 15.4639 10.2611C15.4639 10.2426 15.473 10.2149 15.482 10.1872L15.482 10.1871C15.491 10.1594 15.5 10.1317 15.5 10.1132Z"
+                  fill="white" />
+              </svg>
+              录制中
+              <span>{{ recorderCountdown }}</span>
+            </div>
 
-						<!-- <div class="a_b_b_b_record">
-							<div class="a_b_b_b_r_left">
-								<img src="../../../../assets/icon/classroomObservation/recordLeft.png" alt="">
-								<div>
-									<div>{{recordData.status==0?"录制中":recordData.status==1?"暂停":"结束"}}</div>
-									<span>{{ recordData.time }}</span>
-								</div>
-							</div>
-							<div class="a_b_b_b_r_right">
-								<div class="a_b_b_b_r_r_start">
-									<span></span>
-								</div>
-								<div class="a_b_b_b_r_r_stop">
-									<span></span>
-									<span></span>
-								</div>
-								<div class="a_b_b_b_r_r_end">
-									<span></span>
-								</div>
-							</div>
-						</div> -->
+            <div class="a_b_b_b_record">
+              <div class="a_b_b_b_r_left">
+                <!-- <img src="../../../../assets/icon/classroomObservation/recordLeft.png" alt=""> -->
+                <div>
+                  <div v-if="status === 1">
+                    <el-button @click="stop({ drop: true })">取消录制</el-button>
+                    <el-button :disabled="isRecorderStopDisabled" @click="stop({ drop: false })">停止录制</el-button>
+                  </div>
+                  <div v-else-if="recorderContext.file">
+                    <el-tag style="cursor: pointer" @click="download">{{ recorderContext.file.name }}</el-tag>
+                    <el-button :loading="registerLoading" @click="register()">注册该声纹</el-button>
+                  </div>
+                </div>
+              </div>
+            </div>
           </div>
         </div>
       </div>
@@ -81,17 +72,27 @@
 </template>
 
 <script>
+// import Recorder from 'recorder-core'
+// import Recorder from 'recorder-core/recorder.mp3.min'
+import Recorder from 'recorder-core/recorder.wav.min'
+import * as Sy from '@/lib/shengyang'
+import { saveAs } from 'file-saver';
+
 export default {
+  emits: ['complete'],
   data() {
     return {
       dialogVisible: false,
-      userId: this.$route.query["userid"],
+      userId: this.$route.query["userid"] || '',
+      organizeId: this.$route.query["org"] || '',
       status: 0, //0:初始状态   1:录制
-			recordData:{
-				time:0,
-				status:0,//0 录制中  1暂停  2结束
-			},
-      form: { 
+      recorderContext: {
+        startTime: null,
+        endTime: null,
+        timer: null,
+        file: null,
+      },
+      form: {
         name: ""
       },
       textAreaVale:
@@ -106,21 +107,121 @@ export default {
             message: "长度需在1-20个字符之间"
           }
         ]
-      }
+      },
+      recorder: null,
+      registerLoading: false,
     };
   },
-
+  computed: {
+    recorderCountdown() {
+      const elapsed = this.recorderContext.endTime - this.recorderContext.startTime
+      if (elapsed < 0) {
+        return '00:00'
+      }
+      const minutes = Math.floor(elapsed / 60000)
+      const seconds = Math.floor((elapsed % 60000) / 1000)
+      return `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`
+    },
+    isRecorderStopDisabled() {
+      return (this.recorderContext.endTime - this.recorderContext.startTime) <= 60 * 1000
+    }
+  },
   methods: {
     open() {
       this.dialogVisible = true;
     },
+    beforeClose(done) {
+      if (this.status === 1) {
+        this.$message.info('请先停止录制')
+        return false
+      }
+      done && done()
+      this.$emit('complete')
+      return true
+    },
     close() {
-      this.dialogVisible = false;
+      if (this.beforeClose()) {
+        this.dialogVisible = false;
+      }
+    },
+    start() {
+      if (!this.form.name) {
+        this.$message.info('请先填写教师名字')
+        return
+      }
+      this.status = 1;
+      this.recorder = Recorder({
+        type: 'wav',
+        sampleRate: 16000,
+        bitRate: 16,
+        disableEnvInFix: false,
+      })
+      this.recorder.open(
+        () => {
+          this.recorder.start()
+          this.recorderContext.startTime = Date.now()
+          this.startTimer()
+        },
+        (msg, isUserNotAllow) => {
+          console.log('Recorder Info: isUserNotAllow', isUserNotAllow, msg)
+        }
+      )
+    },
+    async stop({ drop }) {
+      this.stopTimer()
+      this.recorder.stop(async (blob, duration, mime) => {
+        try {
+          if (drop) {
+            return
+          }
+          if (duration < 60 * 1000) {
+            this.$message.error('录制时间不足一分钟,请重试')
+            return
+          }
+          this.recorderContext.file = new File([blob], `voiceprint_${Date.now()}.wav`, { type: mime });
+
+        } finally {
+          this.status = 0
+          this.recorder = null
+        }
+      })
+    },
+    async register() {
+      this.registerLoading = true
+      try {
+        await Sy.registerVoiceprint({
+          file: this.recorderContext.file, name: this.form.name, userId: this.userId, organizeId: this.organizeId
+        })
+        this.$message.success('注册完成')
+        this.$refs.form$.resetFields()
+        this.recorderContext = {
+          startTime: null,
+          endTime: null,
+          timer: null,
+          file: null,
+        }
+      } finally {
+        this.registerLoading = false
+      }
     },
-		start(){
-			this.status = 1;
-		}
-  }
+    startTimer() {
+      this.recorderContext.timer = setInterval(() => {
+        this.recorderContext.endTime = Date.now()
+      }, 1000)
+    },
+    stopTimer() {
+      if (this.recorderContext.timer) {
+        clearInterval(this.recorderContext.timer)
+        this.recorderContext.timer = null
+      }
+    },
+    download() {
+      if (this.recorderContext.file) {
+        saveAs(this.recorderContext.file, `voiceprint_${Date.now()}.wav`);
+      }
+    }
+  },
+
 };
 </script>
 
@@ -151,6 +252,7 @@ export default {
   justify-content: center;
   /* text-align: left; */
 }
+
 .a-d-t-right {
   width: 40px;
   height: 40px;
@@ -161,7 +263,7 @@ export default {
   color: black !important;
 }
 
-.a-d-t-right > span {
+.a-d-t-right>span {
   width: 25px;
   height: 25px;
   border-radius: 25px;
@@ -177,7 +279,7 @@ export default {
   color: #adadad;
 }
 
-.addTemplateDialog >>> .el-dialog {
+.addTemplateDialog>>>.el-dialog {
   min-width: 600px;
 
   height: 700px;
@@ -188,7 +290,8 @@ export default {
   /* margin: 0 auto; */
   overflow: hidden;
 }
-.addTemplateDialog >>> .el-dialog__body {
+
+.addTemplateDialog>>>.el-dialog__body {
   height: 100%;
   min-width: 600px;
   flex-shrink: 0;
@@ -196,7 +299,8 @@ export default {
   padding-bottom: 50px;
   padding-top: 10px;
 }
-.addTemplateDialog >>> .el-dialog__header {
+
+.addTemplateDialog>>>.el-dialog__header {
   display: none;
 }
 
@@ -235,7 +339,6 @@ export default {
   width: 100%;
   height: 100%;
   display: flex;
-  justify-content: center;
   align-items: center;
 }
 
@@ -251,7 +354,7 @@ export default {
   cursor: pointer;
 }
 
-.a_b_b_b_btn > svg {
+.a_b_b_b_btn>svg {
   width: 20px;
   height: 20px;
   margin-right: 5px;
@@ -261,7 +364,7 @@ export default {
   margin-bottom: 10px;
 }
 
-.a_b_f_item >>> .el-form-item__label {
+.a_b_f_item>>>.el-form-item__label {
   padding: 0;
   font-size: 20px;
   font-weight: bold;
@@ -276,7 +379,8 @@ export default {
   padding: 10px;
   overflow: auto;
 }
-.a_b_f_itemTextArea > div {
+
+.a_b_f_itemTextArea>div {
   background-color: #f0f2f566;
   border: 1px dashed #f0f2f5;
   border-radius: 4px;
@@ -284,45 +388,44 @@ export default {
   box-sizing: border-box;
   padding: 5px;
   font-size: 16px;
-	line-height: 30px;
+  line-height: 30px;
 }
 
-.a_b_b_b_record{
-	width: 100%;
-	height: 100%;
-	display: flex;
-	justify-content: space-between;
-	align-items: center;
+.a_b_b_b_record {
+  height: 100%;
+  flex: 1;
+  display: flex;
+  justify-content: flex-end;
+  align-items: center;
 }
 
-.a_b_b_b_r_left{
-	display: flex;
-	align-items: center;
-	justify-content: center;
+.a_b_b_b_r_left {
+  display: flex;
+  align-items: center;
+  justify-content: center;
 }
 
-.a_b_b_b_r_left>div{
-	height: 100%;
-	width: auto;
-	display: flex;
-	flex-direction: column;
-	justify-content: space-between;
-	margin-left: 10px;
+.a_b_b_b_r_left>div {
+  height: 100%;
+  width: auto;
+  display: flex;
+  flex-direction: column;
+  justify-content: space-between;
+  margin-left: 10px;
 }
 
-.a_b_b_b_r_left>div>div{
-	font-weight: bold;
-	margin-bottom: 5px;
+.a_b_b_b_r_left>div>div {
+  font-weight: bold;
+  margin-bottom: 5px;
 }
 
-.a_b_b_b_r_left>div>span{
-	color: #3681FC;
+.a_b_b_b_r_left>div>span {
+  color: #3681FC;
 }
 
-.a_b_b_b_r_right{
-	display: flex;
-	align-items: center;
-	justify-content: flex-end;
+.a_b_b_b_r_right {
+  display: flex;
+  align-items: center;
+  justify-content: flex-end;
 }
-
 </style>

+ 354 - 49
src/components/pages/classroomObservation/components/analysis.vue

@@ -5,17 +5,23 @@
         <span :class="['a-h-l-icon', showItem ? 'a-h-l-showIcon' : '']"></span>
         <span class="a-h-l-title" v-if="!editTitle">{{ title }}</span>
         <div class="a_h_l_t_input" v-if="editTitle" @click.stop="() => {}">
-          <el-form ref="form" :model="form" :rules="rules" label-position="top" @submit.native.prevent>
+          <el-form
+            ref="form"
+            :model="form"
+            :rules="rules"
+            label-position="top"
+            @submit.native.prevent
+          >
             <el-form-item prop="name">
               <el-input
                 v-model="form.name"
-               	@blur="editNameCheckFn()"
-								@keyup.enter.native="editNameCheckFn"
+                @blur="editNameCheckFn()"
+                @keyup.enter.native="editNameCheckFn"
                 ref="editNameInputRef"
                 placeholder="请输入新的名称"
               ></el-input>
             </el-form-item>
-						<!--   @blur="editNameCheckFn()" -->
+            <!--   @blur="editNameCheckFn()" -->
           </el-form>
         </div>
         <el-tooltip
@@ -39,11 +45,17 @@
         <div
           class="a_h_r_more"
           slot="reference"
+					v-if="!isDrag"
           @click.stop="visible = !visible"
         >
           <img src="@/assets/icon/classroomObservation/moreIcon.svg" alt="" />
         </div>
 
+				<div class="a_h_r_scBtn" v-if="isDrag">
+					<span class="a_h_r_cancel" @click.stop="dragCancel()">取消</span>
+					<span class="a_h_r_submit" @click.stop="dragSubmit()">完成</span>
+				</div>
+
         <div
           class="a_h_r_boxList"
           v-if="visible"
@@ -82,16 +94,199 @@
 
             添加模块
           </div>
-					<div @click="addNewAnalysisGroup(0)"><svg t="1727073223211" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3257" width="200" height="200"><path d="M546.901333 314.282667a46.805333 46.805333 0 0 0-69.802666 0l-296.021334 340.736c-24.32 28.032-3.328 70.314667 34.944 70.314666h591.957334c38.272 0 59.306667-42.282667 34.944-70.314666l-296.021334-340.736z" p-id="3258"></path></svg>添加分析</div>
-					<div @click="addNewAnalysisGroup(1)"><svg style="transform: rotate(180deg);" t="1727073223211" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3257" width="200" height="200"><path d="M546.901333 314.282667a46.805333 46.805333 0 0 0-69.802666 0l-296.021334 340.736c-24.32 28.032-3.328 70.314667 34.944 70.314666h591.957334c38.272 0 59.306667-42.282667 34.944-70.314666l-296.021334-340.736z" p-id="3258"></path></svg>添加分析</div>
-					<div @click="deleteAnalysisGroup()"><svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path fill-rule="evenodd" clip-rule="evenodd" d="M6.25 3.125C6.25 2.77982 6.52982 2.5 6.875 2.5H13.125C13.4702 2.5 13.75 2.77982 13.75 3.125C13.75 3.47018 13.4702 3.75 13.125 3.75H6.875C6.52982 3.75 6.25 3.47018 6.25 3.125Z" fill="black" fill-opacity="0.6"/>
-<path fill-rule="evenodd" clip-rule="evenodd" d="M2.5 5.625C2.5 5.26481 2.77982 5 3.125 5H16.875C17.2202 5 17.5 5.26481 17.5 5.625C17.5 6.03952 17.2202 6.25 16.875 6.25L15.625 6.30435V17.5H4.375V6.25H3.125C2.77982 6.25 2.5 6.03952 2.5 5.625ZM5.625 6.25V16.1957L14.375 16.25V6.30435L5.625 6.25Z" fill="black" fill-opacity="0.6"/>
-<path fill-rule="evenodd" clip-rule="evenodd" d="M8.125 8.06838C8.47018 8.06838 8.75 8.32276 8.75 8.63656V13.7502C8.75 14.064 8.47018 14.3184 8.125 14.3184C7.77982 14.3184 7.5 14.064 7.5 13.7502V8.63656C7.5 8.32276 7.77982 8.06838 8.125 8.06838Z" fill="black" fill-opacity="0.6"/>
-<path fill-rule="evenodd" clip-rule="evenodd" d="M11.875 8.06838C12.2202 8.06838 12.5 8.32276 12.5 8.63656V13.7502C12.5 14.064 12.2202 14.3184 11.875 14.3184C11.5298 14.3184 11.25 14.064 11.25 13.7502V8.63656C11.25 8.32276 11.5298 8.06838 11.875 8.06838Z" fill="black" fill-opacity="0.6"/>
-</svg>
-删除分析</div>
-				</div>
+					<div @click="moveAnalysis()" v-if="tid">
+						<svg t="1727591669869" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4289" width="200" height="200"><path d="M514.64 511m-84.76 0a84.76 84.76 0 1 0 169.52 0 84.76 84.76 0 1 0-169.52 0Z" fill="#000000" p-id="4290"></path><path d="M661.36 190.85L548.54 78a47.85 47.85 0 0 0-32-14c-1.21-0.09-2.43-0.15-3.66-0.15A48.95 48.95 0 0 0 478.58 78L365.77 190.85a48 48 0 0 0 0 67.88 48 48 0 0 0 67.88 0l31.21-31.21v115.22a48 48 0 0 0 48 48 48 48 0 0 0 48-48V226.11l32.61 32.62a48 48 0 0 0 67.89 0 48 48 0 0 0 0-67.88zM364.23 832.86l112.82 112.81a47.82 47.82 0 0 0 32 14c1.21 0.09 2.43 0.15 3.66 0.15A48.9 48.9 0 0 0 547 945.67l112.83-112.81a48 48 0 0 0 0-67.88 48 48 0 0 0-67.89 0l-31.21 31.2V681a48 48 0 0 0-48-48 48 48 0 0 0-48 48v116.59L432.12 765a48 48 0 0 0-67.89 0 48 48 0 0 0 0 67.86zM831.12 663.76L943.93 551a47.86 47.86 0 0 0 14-32c0.09-1.21 0.15-2.43 0.15-3.67A48.86 48.86 0 0 0 943.93 481L831.12 368.17a48 48 0 0 0-67.89 0 48 48 0 0 0 0 67.88l31.21 31.21H679.23a48 48 0 0 0-48 48 48 48 0 0 0 48 48h116.62l-32.62 32.62a48 48 0 0 0 0 67.88 48 48 0 0 0 67.89 0zM187.88 366.64L75.06 479.45a47.82 47.82 0 0 0-14 32c-0.1 1.21-0.16 2.43-0.16 3.67a48.86 48.86 0 0 0 14.17 34.27l112.81 112.84a48 48 0 0 0 67.88 0 48 48 0 0 0 0-67.88l-31.21-31.21h115.21a48 48 0 0 0 48-48 48 48 0 0 0-48-48H223.14l32.62-32.62a48 48 0 0 0 0-67.88 48 48 0 0 0-67.88 0z" fill="#000000" p-id="4291"></path></svg>
+						移动模块
+					</div>
+          <div @click="addNewAnalysisGroup(0)">
+            <svg
+              t="1727073223211"
+              class="icon"
+              viewBox="0 0 1024 1024"
+              version="1.1"
+              xmlns="http://www.w3.org/2000/svg"
+              p-id="3257"
+              width="200"
+              height="200"
+            >
+              <path
+                d="M546.901333 314.282667a46.805333 46.805333 0 0 0-69.802666 0l-296.021334 340.736c-24.32 28.032-3.328 70.314667 34.944 70.314666h591.957334c38.272 0 59.306667-42.282667 34.944-70.314666l-296.021334-340.736z"
+                p-id="3258"
+              ></path></svg
+            >添加分析
+          </div>
+          <div @click="addNewAnalysisGroup(1)">
+            <svg
+              style="transform: rotate(180deg);"
+              t="1727073223211"
+              class="icon"
+              viewBox="0 0 1024 1024"
+              version="1.1"
+              xmlns="http://www.w3.org/2000/svg"
+              p-id="3257"
+              width="200"
+              height="200"
+            >
+              <path
+                d="M546.901333 314.282667a46.805333 46.805333 0 0 0-69.802666 0l-296.021334 340.736c-24.32 28.032-3.328 70.314667 34.944 70.314666h591.957334c38.272 0 59.306667-42.282667 34.944-70.314666l-296.021334-340.736z"
+                p-id="3258"
+              ></path></svg
+            >添加分析
+          </div>
+
+          <div @click="setTheLocation(0)" v-if="![0].includes(index)">
+            <svg
+              width="16"
+              height="16"
+              viewBox="0 0 16 16"
+              fill="none"
+              xmlns="http://www.w3.org/2000/svg"
+            >
+              <g clip-path="url(#clip0_1551_2752)">
+                <path
+                  d="M11 7.5L8 4L5 7.5H7.47826V13.5C7.47826 13.7761 7.71185 14 8 14C8.28815 14 8.52174 13.7761 8.52174 13.5V7.5H11Z"
+                  fill="black"
+                  fill-opacity="0.9"
+                />
+                <path
+                  fill-rule="evenodd"
+                  clip-rule="evenodd"
+                  d="M2 3L14 3V2L2 2L2 3Z"
+                  fill="black"
+                  fill-opacity="0.9"
+                />
+              </g>
+              <defs>
+                <clipPath id="clip0_1551_2752">
+                  <rect width="16" height="16" fill="white" />
+                </clipPath>
+              </defs>
+            </svg>
+            置顶
+          </div>
+          <div @click="setTheLocation(1)" v-if="![maxIndex].includes(index)">
+            <svg
+              width="16"
+              height="16"
+              style="transform: rotate(180deg);"
+              viewBox="0 0 16 16"
+              fill="none"
+              xmlns="http://www.w3.org/2000/svg"
+            >
+              <g clip-path="url(#clip0_1551_2752)">
+                <path
+                  d="M11 7.5L8 4L5 7.5H7.47826V13.5C7.47826 13.7761 7.71185 14 8 14C8.28815 14 8.52174 13.7761 8.52174 13.5V7.5H11Z"
+                  fill="black"
+                  fill-opacity="0.9"
+                />
+                <path
+                  fill-rule="evenodd"
+                  clip-rule="evenodd"
+                  d="M2 3L14 3V2L2 2L2 3Z"
+                  fill="black"
+                  fill-opacity="0.9"
+                />
+              </g>
+              <defs>
+                <clipPath id="clip0_1551_2752">
+                  <rect width="16" height="16" fill="white" />
+                </clipPath>
+              </defs>
+            </svg>
+            置底
+          </div>
+
+          <div @click="setTheLocation(2)" v-if="![0].includes(index)">
+            <svg
+              width="16"
+              height="16"
+              viewBox="0 0 16 16"
+              fill="none"
+              xmlns="http://www.w3.org/2000/svg"
+            >
+              <g clip-path="url(#clip0_1552_2795)">
+                <path
+                  d="M11 5.5L8 2L5 5.5H7.47826V13.5C7.47826 13.7761 7.71185 14 8 14C8.28815 14 8.52174 13.7761 8.52174 13.5V5.5H11Z"
+                  fill="black"
+                  fill-opacity="0.9"
+                />
+              </g>
+              <defs>
+                <clipPath id="clip0_1552_2795">
+                  <rect width="16" height="16" fill="white" />
+                </clipPath>
+              </defs>
+            </svg>
+            上移
+          </div>
+
+          <div @click="setTheLocation(3)" v-if="![maxIndex].includes(index)">
+            <svg
+              style="transform: rotate(180deg);"
+              width="16"
+              height="16"
+              viewBox="0 0 16 16"
+              fill="none"
+              xmlns="http://www.w3.org/2000/svg"
+            >
+              <g clip-path="url(#clip0_1552_2795)">
+                <path
+                  d="M11 5.5L8 2L5 5.5H7.47826V13.5C7.47826 13.7761 7.71185 14 8 14C8.28815 14 8.52174 13.7761 8.52174 13.5V5.5H11Z"
+                  fill="black"
+                  fill-opacity="0.9"
+                />
+              </g>
+              <defs>
+                <clipPath id="clip0_1552_2795">
+                  <rect width="16" height="16" fill="white" />
+                </clipPath>
+              </defs>
+            </svg>
+            下移
+          </div>
+
+          <div @click="deleteAnalysisGroup()" v-if="maxIndex!==0">
+            <svg
+              width="20"
+              height="20"
+              viewBox="0 0 20 20"
+              fill="none"
+              xmlns="http://www.w3.org/2000/svg"
+            >
+              <path
+                fill-rule="evenodd"
+                clip-rule="evenodd"
+                d="M6.25 3.125C6.25 2.77982 6.52982 2.5 6.875 2.5H13.125C13.4702 2.5 13.75 2.77982 13.75 3.125C13.75 3.47018 13.4702 3.75 13.125 3.75H6.875C6.52982 3.75 6.25 3.47018 6.25 3.125Z"
+                fill="black"
+                fill-opacity="0.6"
+              />
+              <path
+                fill-rule="evenodd"
+                clip-rule="evenodd"
+                d="M2.5 5.625C2.5 5.26481 2.77982 5 3.125 5H16.875C17.2202 5 17.5 5.26481 17.5 5.625C17.5 6.03952 17.2202 6.25 16.875 6.25L15.625 6.30435V17.5H4.375V6.25H3.125C2.77982 6.25 2.5 6.03952 2.5 5.625ZM5.625 6.25V16.1957L14.375 16.25V6.30435L5.625 6.25Z"
+                fill="black"
+                fill-opacity="0.6"
+              />
+              <path
+                fill-rule="evenodd"
+                clip-rule="evenodd"
+                d="M8.125 8.06838C8.47018 8.06838 8.75 8.32276 8.75 8.63656V13.7502C8.75 14.064 8.47018 14.3184 8.125 14.3184C7.77982 14.3184 7.5 14.064 7.5 13.7502V8.63656C7.5 8.32276 7.77982 8.06838 8.125 8.06838Z"
+                fill="black"
+                fill-opacity="0.6"
+              />
+              <path
+                fill-rule="evenodd"
+                clip-rule="evenodd"
+                d="M11.875 8.06838C12.2202 8.06838 12.5 8.32276 12.5 8.63656V13.7502C12.5 14.064 12.2202 14.3184 11.875 14.3184C11.5298 14.3184 11.25 14.064 11.25 13.7502V8.63656C11.25 8.32276 11.5298 8.06838 11.875 8.06838Z"
+                fill="black"
+                fill-opacity="0.6"
+              />
+            </svg>
+            删除分析
+          </div>
+
+					
+        </div>
         <!-- </el-popover> -->
         <!-- <div class="a-h-r-btn" @click.stop="addTemplate">
           <img src="@/assets/icon/classroomObservation/newcon.svg" alt="" />
@@ -99,29 +294,36 @@
         </div> -->
       </div>
     </div>
-    <div class="a-main" v-show="showItem">
+    <div class="a-main" v-show="showItem || isDrag">
       <template v-for="(item, index) in analysisItemList">
+				<div class="a_m_dragBox" ref="dragBoxRefTop" :type="`${type}_${index}_${item.tIndex}_0`" v-if="isDrag && index===0"></div>
         <analysisItem
           ref="analysisItemRef"
           v-if="
-            converter(item.jsonData.name) != converter('词频词汇分析') &&
               ![
                 converter('S-T分析:课堂时间分配'),
                 converter('S-T分析:师生互动分析'),
                 converter('S-T分析:教学模式分析')
-              ].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"
+          :key="item.id + item.Type + '-' + index"
           :data="item"
           :tid="tid"
+					:dialogTagList="dialogTagList"
           :fileId="fileId"
           :index="index"
           :showBrief="showBrief"
+					:isDrag="isDrag"
           @delItem="delItem"
           @editItem="editItem"
           @saveItem="saveItem"
+					@moveAnalysis="moveAnalysisSubmit"
         />
         <analysisSpecialItem
           v-if="
@@ -129,24 +331,32 @@
               converter('S-T分析:课堂时间分配'),
               converter('S-T分析:师生互动分析'),
               converter('S-T分析:教学模式分析')
-            ].includes(converter(item.jsonData.name))
+            ].includes(converter(item.jsonData.name)) ||
+              ['bfe844b1-7a45-11ef-9b30-005056b86db5'].includes(
+                item.jsonData.mId
+              )
           "
           :dialogTagDataList="dialogTagDataList"
           ref="analysisItemRef"
           :bmData="bmData"
-          :key="item.id + item.Type+'-' + index"
+          :key="item.id + item.Type + '-' + index"
           :data="item"
           :tid="tid"
+					:dialogTagList="dialogTagList"
           :fileId="fileId"
           :index="index"
+					:isDrag="isDrag"
           :showBrief="showBrief"
           @delItem="delItem"
           @editItem="editItem"
           @saveItem="saveItem"
+					@moveAnalysis="moveAnalysisSubmit"
         />
+				<div class="a_m_dragBox" ref="dragBoxRefBottom" :type="`${type}_${index}_${item.tIndex}_1`" v-if="isDrag"></div>
       </template>
       <div class="a_m_empty" v-if="analysisItemList.length == 0">
-        暂无模块...
+        <span v-if="!isDrag">暂无模块...</span>
+				<div class="a_m_dragBox" ref="dragBoxRefBottom" :type="`${type}_0_0_2`" v-if="isDrag"></div>
       </div>
     </div>
     <editNameDialog ref="editNameDialogRef" @success="editNameSuccess" />
@@ -226,7 +436,29 @@ export default {
     fileId: {
       type: String,
       require: true
-    }
+    },
+    dataList: {
+      type: Array,
+      default: () => {
+        return [];
+      }
+    },
+    index: {
+      type: Number,
+      default: 0
+    },
+    maxIndex: {
+      type: Number,
+      default: 0
+    },
+		isDrag:{
+			type:Boolean,
+			default:false
+		},
+		dialogTagList:{
+			type:Array,
+			default:()=>{return []}
+		},
   },
   components: {
     analysisItem,
@@ -238,7 +470,7 @@ export default {
       return function(word) {
         return converter2(word);
       };
-    }
+    },
   },
   data() {
     return {
@@ -305,7 +537,7 @@ export default {
       // this.$refs.editNameDialogRef.open(this.title)
     },
     editNameCheckFn() {
-			if(!this.editTitle)return;
+      if (!this.editTitle) return;
       this.$refs["form"].validate(valid => {
         if (valid) {
           this.editTitle = false;
@@ -313,7 +545,7 @@ export default {
         } else {
           this.$nextTick(() => {
             this.$refs.editNameInputRef.focus();
-						this.$message.error("该名称不符合规则")
+            this.$message.error("该名称不符合规则");
           });
         }
       });
@@ -322,31 +554,55 @@ export default {
       this.$emit("changeAnalysisName", { name: name, type: this.type });
       // this.$refs.editNameDialogRef.close()
     },
-		addNewAnalysisGroup(po=1){//0:上面   1:下面
-			if (!this.tid) return this.$message.error("请新建课堂,或选择历史课堂");
-			this.visible = false;
-			this.$confirm("确定添加新的分析分组吗?", "添加分析", {
+    addNewAnalysisGroup(po = 1) {
+      //0:上面   1:下面
+      if (!this.tid) return this.$message.error("请新建课堂,或选择历史课堂");
+      this.visible = false;
+      this.$confirm("确定添加新的分析分组吗?", "添加分析", {
         confirmButtonText: "确定",
         cancelButtonText: "取消",
-        type: "waring",
+        type: "waring"
       })
         .then(() => {
-					this.$emit("addNewAnalysisGroup",{type:this.type,po:po})
-			}).catch(()=>{})
-		},
-		deleteAnalysisGroup(){
-			if (!this.tid) return this.$message.error("请新建课堂,或选择历史课堂");
-			this.visible = false;
-			this.$confirm("删除后无法恢复,确定删除该分析吗?", "删除分析", {
+          this.$emit("addNewAnalysisGroup", { type: this.type, po: po });
+        })
+        .catch(() => {});
+    },
+    deleteAnalysisGroup() {
+      if (!this.tid) return this.$message.error("请新建课堂,或选择历史课堂");
+      this.visible = false;
+      this.$confirm("删除后无法恢复,确定删除该分析吗?", "删除分析", {
         confirmButtonText: "确定",
         cancelButtonText: "取消",
-        type: "waring",
+        type: "waring"
       })
-        .then((res) => {
-					let _groupId = this.analysisItemList.map(i=>i.id)
-					this.$emit("delAnalysisGroup",{type:this.type,groupId:_groupId})
-				}).catch(e=>{})
-			
+        .then(res => {
+          let _groupId = this.analysisItemList.map(i => i.id);
+          this.$emit("delAnalysisGroup", {
+            type: this.type,
+            groupId: _groupId
+          });
+        })
+        .catch(e => {});
+    },
+    setTheLocation(type = 0) {
+      this.visible = false;
+      if (!this.tid) return this.$message.error("请新建课堂,或选择历史课堂");
+      this.$emit("setTheLocation", { value: this.type, type: type });
+    },
+		moveAnalysis(){
+			this.visible = false;
+			this.$parent.isDrag = true;
+		},
+		dragSubmit(){
+			this.visible = false;
+			this.$parent.isDrag = false;
+		},
+		moveAnalysisSubmit(data){
+			this.$emit('moveAnalysis',data)
+		},
+		dragCancel(){
+			this.$parent.moveAnalysisCancel()
 		}
   }
 };
@@ -757,19 +1013,68 @@ export default {
   height: 100%;
 }
 .a_h_l_t_input {
-	width: 25%;
-	min-width: 220px;
+  width: 25%;
+  min-width: 220px;
   height: 100%;
   display: flex;
   align-items: center;
 }
 
-.a_h_l_t_input>>>.el-form{
+.a_h_l_t_input >>> .el-form {
+  width: 100%;
+  min-width: 220px;
+}
+
+.a_h_l_t_input >>> .el-form-item {
+  margin-bottom: 0;
+}
+
+.a_m_dragBox{
 	width: 100%;
-	min-width: 220px;
+	height: 40px;
+	border-radius: 3px;
+	box-sizing: border-box;
+	margin-bottom: 10px;
+	border: 1px dashed rgba(54, 129, 252, 1);
+	background-color: #F8F9FA;
+	transition: .3s;
+	cursor: pointer;
+}
+
+.a_h_r_scBtn{
+	height: 100%;
+	padding: 0 10px 0;
+	display: flex;
+	justify-content: center;
+	align-items: center;
+}
+
+.a_h_r_scBtn>.a_h_r_submit{
+	padding: 5px 10px;
+	background-color: rgba(54, 129, 252, 1);
+	border-radius: 3px;
+	color: #fff;
+	cursor: pointer;
+	font-size: 14px;
+}
+
+.a_h_r_submit>a_h_r_submit:hover{
+	background-color: rgb(19, 106, 247);
+}
+
+.a_h_r_scBtn>.a_h_r_cancel{
+	padding: 5px 10px;
+	background-color: #fff;
+	border-radius: 3px;
+	color: black;
+	cursor: pointer;
+	border: solid 1px #e7e7e7;
+	font-size: 14px;
+	margin-right: 10px;
 }
 
-.a_h_l_t_input>>>.el-form-item{
-	margin-bottom: 0;
+.a_h_r_submit>.a_h_r_cancel:hover{
+	background-color: #e0d9d9;
 }
+
 </style>

+ 177 - 19
src/components/pages/classroomObservation/components/analysisItem.vue

@@ -1,6 +1,15 @@
 <template>
-  <div class="analysisItem">
-    <div class="ai-header" v-show="data.jsonData.name != '词频词汇分析'">
+  <div
+    class="analysisItem"
+    ref="analysisItemRef"
+    :style="
+      `top:${moveTop}px;transition:${isDragging ? '0' : '.3s'}s;${
+        isDragging ? 'z-index:999' : ''
+      }`
+    "
+		@mousedown="moveDown($event)"
+  >
+    <div class="ai-header">
       <div class="ai-h-left" @click="changeOpenItem(!openItem)">
         <span
           :class="['ai-h-l-icon', openItem ? 'ai-h-l-iconActive' : '']"
@@ -37,7 +46,7 @@
           content="修改名称"
           placement="top"
         >
-          <span class="a_h_l_edit" @click.stop="editTitleFn()">
+          <span class="a_h_l_edit" @click.stop="editTitleFn()" v-if="!isDrag">
             <img
               src="../../../../assets/icon/classroomObservation/editIcon2.svg"
             />
@@ -45,10 +54,10 @@
         </el-tooltip>
       </div>
       <div class="ai-h-right">
-        <span style="width: 100px;" class="generateError" v-if="loadNum == 2"
+        <span style="width: 100px;" class="generateError" v-if="loadNum == 2&& !isDrag"
           >优化失败
         </span>
-        <span style="width: 100px" v-if="loadNum == 1">
+        <span style="width: 100px" v-if="loadNum == 1 && !isDrag">
           <span v-if="loading" class="generate">
             <img
               :src="
@@ -66,8 +75,9 @@
             生成完成
           </span>
         </span>
+
         <span
-          v-if="loadNum == 0 && !openItem && tid"
+          v-if="loadNum == 0 && !openItem && tid && !isDrag""
           class="ai-h-r-icon4"
           @click="delBtn()"
         >
@@ -86,7 +96,7 @@
         </span>
 
         <span
-          v-if="loadNum == 0 && openItem"
+          v-if="loadNum == 0 && openItem && !isDrag"
           :class="['ai-h-r-icon1', showIndex <= 0 ? 'ai_h_r_iconOpacity' : '']"
           @click="changeShowIndex(-1)"
         >
@@ -104,8 +114,9 @@
           </el-tooltip>
         </span>
 
+
         <span
-          v-if="loadNum != 1 && openItem"
+          v-if="loadNum != 1 && openItem && !isDrag"
           :class="[
             showIndex >= historyResult.length - 1 ? 'ai_h_r_iconOpacity' : ''
           ]"
@@ -125,7 +136,7 @@
           </el-tooltip>
         </span>
 
-        <span v-if="loadNum != 1 && openItem && tid" @click="editBtn()">
+        <span v-if="loadNum != 1 && openItem && tid && !isDrag" @click="editBtn()">
           <el-tooltip
             class="item"
             effect="light"
@@ -145,7 +156,7 @@
             loadNum != 1 &&
               openItem &&
               tid &&
-              ['1', '2', '3'].includes(data.jsonData.echartsType)
+              ['1', '2', '3'].includes(data.jsonData.echartsType) && !isDrag
           "
           @click="editEcharts()"
         >
@@ -166,7 +177,7 @@
         <!-- <span class="ai-h-r-icon4" @click.stop="delBtn()"></span> -->
       </div>
     </div>
-    <div class="ai-main" v-if="openItem">
+    <div class="ai-main" v-if="openItem && !isDrag">
       <div class="a-m-brief" v-if="showBrief">
         {{ data.jsonData.result }}
         <!-- <mdView :text="data.jsonData.result" /> -->
@@ -256,6 +267,22 @@ export default {
       default: () => {
         return {};
       }
+    },
+    dataList: {
+      type: Array,
+      default: () => {
+        return [];
+      }
+    },
+    isDrag: {
+      type: Boolean,
+      default: false
+    },
+    dialogTagList: {
+      type: Array,
+      default: () => {
+        return [];
+      }
     }
   },
   data() {
@@ -292,7 +319,13 @@ export default {
             message: "长度需在1-20个字符之间"
           }
         ]
-      }
+      },
+      startY: 0,
+      startTop: 0,
+      moveTop: 0,
+      isDragging: false,
+      dragBoxList: [],
+      enterDrag: null
     };
   },
   computed: {
@@ -312,6 +345,7 @@ export default {
   },
   methods: {
     changeOpenItem(newValue) {
+			if(this.isDrag)return;
       if (this.loading == true && this.loadNum != 0)
         return this.$message("请稍后...");
       this.loadNum = 0;
@@ -323,6 +357,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 +373,15 @@ 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 {
@@ -359,7 +403,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 +455,8 @@ 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 = [];
@@ -644,7 +690,7 @@ export default {
             res.forEach((item, index) => {
               if (index == 0) return; //去掉表头
               radarData.push({ name: item[0], max: 5 });
-							let _valueItem = item[1] ? item[1] :"0";
+              let _valueItem = item[1] ? item[1] : "0";
               let _value = _valueItem.match(/(\d+)/);
               _value = _value ? parseInt(_value[0]) : 0;
               seriesData.value.push(_value);
@@ -701,7 +747,7 @@ export default {
             stepList = this.calculateTopValues(res.length - 1);
             res.forEach((item, index) => {
               if (index == 0) return;
-							let _valueItem = item[1] ? item[1] : "0";
+              let _valueItem = item[1] ? item[1] : "0";
               let _value = _valueItem.match(/(\d+)/);
               _value = _value ? parseInt(_value[0]) : 0;
               // 求百分比
@@ -801,7 +847,7 @@ export default {
       }
       this.loadNum = 2;
       this.loading = false;
-			return this.$message.error("生成图表失败");
+      return this.$message.error("生成图表失败");
     },
     calculateTopValues(len, minTop = -80, maxTop = 70, maxStep = 40) {
       const length = len;
@@ -849,9 +895,9 @@ export default {
 
           // 匹配每个单元格 (th 或 td)
           while ((cellMatch = cellRegex.exec(rowContent)) !== null) {
-						let _text = cellMatch[2].trim();
-						_text = _text.replace(/&[a-zA-Z]+;/g, '');
-						_text = _text.replace(/<\/?[^>]+(>|$)/g, '')
+            let _text = cellMatch[2].trim();
+            _text = _text.replace(/&[a-zA-Z]+;/g, "");
+            _text = _text.replace(/<\/?[^>]+(>|$)/g, "");
             rowData.push(_text); // 将每个单元格的内容添加到当前行的数组中
           }
 
@@ -875,6 +921,101 @@ export default {
 
       // 组合三种颜色(红、绿、蓝)的随机值
       return `#${randomHex()}${randomHex()}${randomHex()}`;
+    },
+    moveDown(e) {
+			if(!this.isDrag)return;
+      this.isDragging = true;
+      this.startY = e.clientY;
+      this.$nextTick(() => {
+        this.dialogTagList.forEach(i => {
+          this.dragBoxList.push(
+            ...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
+              .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);
+					}
+          
+        });
+        // 禁用页面文本选择
+        document.body.style.userSelect = "none";
+        document.addEventListener("mousemove", this.onMouseMove);
+        document.addEventListener("mouseup", this.stopDragging);
+      });
+    },
+    onMouseMove(e) {
+      if (!this.isDragging) return;
+
+      const newTop = e.clientY - this.startY; // 计算鼠标移动的Y轴距离
+
+      // // 更新div的top样式,使其跟随鼠标移动
+      this.moveTop = newTop;
+      // this.dragBoxList.forEach(i=>{
+      // 	let _i = i.getBoundingClientRect();
+      // 	if(e.clientX>=_i.left && e.clientX<=_i.right && e.clientY>=_i.top && e.clientY<=_i.bottom){
+      // 		this.enterDrag = i;
+      // 		console.log("👇")
+      // 		console.log(e)
+      // 	}else{
+      // 		this.enterDrag = null;
+      // 	}
+      // })
+      // for (let i = 0; i <= this.dragBoxList.length; i++) {
+      //   let _i = this.dragBoxList[i].getBoundingClientRect();
+      //   if (
+      //     e.clientX >= _i.left &&
+      //     e.clientX <= _i.right &&
+      //     e.clientY >= _i.top &&
+      //     e.clientY <= _i.bottom
+      //   ) {
+      //     this.enterDrag = this.dragBoxList[i];
+      //     console.log("👇");
+      //     console.log(e);
+      // 		break;
+      //   } else {
+      //     this.enterDrag = null;
+      //   }
+      // }
+    },
+    stopDragging(e) {
+      this.isDragging = false;
+      try {
+        for (let i = 0; i <= this.dragBoxList.length; i++) {
+          let _i = this.dragBoxList[i].getBoundingClientRect();
+          if (
+            e.clientX >= _i.left &&
+            e.clientX <= _i.right &&
+            e.clientY >= _i.top &&
+            e.clientY <= _i.bottom
+          ) {
+            this.enterDrag = this.dragBoxList[i];
+            break;
+          } else {
+            this.enterDrag = null;
+          }
+        }
+        // 恢复页面的文本选择
+        document.body.style.userSelect = "";
+        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})
+        }
+        // 移除全局的鼠标移动和释放事件
+        document.removeEventListener("mousemove", this.onMouseMove);
+        document.removeEventListener("mouseup", this.stopDragging);
+      } catch (error) {
+				this.moveTop = 0;
+				document.removeEventListener("mousemove", this.onMouseMove);
+        document.removeEventListener("mouseup", this.stopDragging);
+			}
     }
   },
   mounted() {
@@ -898,6 +1039,9 @@ export default {
   border: 1px solid #e7e7e7;
   border-radius: 4px;
   transition: 0.3s;
+  background-color: #fff;
+  position: relative;
+  top: 0;
 }
 
 .analysisItem:hover {
@@ -1003,6 +1147,20 @@ export default {
   display: block;
 }
 
+.ai-h-r-moveIcon {
+  display: none;
+  cursor: move !important;
+}
+
+.analysisItem:hover .ai-h-r-moveIcon {
+  display: block;
+}
+
+.ai-h-r-moveIcon > svg {
+  width: 18px;
+  height: 18px;
+}
+
 .ai-main {
   width: 100%;
   height: auto;

+ 308 - 20
src/components/pages/classroomObservation/components/analysisSpecialItem.vue

@@ -1,6 +1,6 @@
 <template>
   <div class="analysisItem">
-    <div class="ai-header" v-show="data.jsonData.name != '词频词汇分析'">
+    <div class="ai-header">
       <div class="ai-h-left" @click.stop="changeOpenItem(!openItem)">
         <span
           :class="['ai-h-l-icon', openItem ? 'ai-h-l-iconActive' : '']"
@@ -38,14 +38,14 @@
           content="修改名称"
           placement="top"
         >
-          <span class="a_h_l_edit" @click.stop="editTitleFn()">
+          <span class="a_h_l_edit" @click.stop="editTitleFn()" v-if="!isDrag">
             <img
               src="../../../../assets/icon/classroomObservation/editIcon2.svg"
             />
           </span>
         </el-tooltip>
       </div>
-      <div class="ai-h-right">
+      <div class="ai-h-right" v-if="!isDrag">
         <span style="width: 100px" class="generateError" v-if="loadNum == 2"
           >优化失败
         </span>
@@ -162,7 +162,7 @@
         <!-- <span class="ai-h-r-icon4" @click.stop="delBtn()"></span> -->
       </div>
     </div>
-    <div class="ai-main" v-if="openItem">
+    <div class="ai-main" v-if="openItem && !isDrag">
       <div class="a-m-brief" v-if="showBrief">
         {{ data.jsonData.result }}
       </div>
@@ -176,6 +176,13 @@
         "
       />
 
+      <!-- 光谱图 -->
+      <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'"
+        :data="data.jsonData.spectrogramData"
+      />
+
       <eChartTemplate
         style="width: 100%;height: 400px;"
         :data="data.jsonData.eChartData"
@@ -184,7 +191,9 @@
             converter(data.jsonData.name) != converter('S-T分析:师生互动分析')
         "
       />
-      <div class="rtCh" v-if="data.jsonData.RT && data.jsonData.CH">
+
+			<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="
             require('../../../../assets/icon/classroomObservation/rt-ch.png')
@@ -194,7 +203,7 @@
           <span>RT={{ data.jsonData.RT }}</span>
           <span>CH={{ data.jsonData.CH }}</span>
         </div>
-      </div>
+      </div> -->
       <mdView :text="data.jsonData.content" />
     </div>
     <!-- <editNameDialog ref="editNameDialogRef" @success="changeNameSuccess"/> -->
@@ -210,12 +219,17 @@ let converter2 = OpenCC.Converter({
 import mdView from "./mdView.vue";
 import eChartTemplate from "./eChartTemplate";
 import { v4 as uuidv4 } from "uuid";
+import echartsSpectrogram from "./echartsSpectrogram";
 // import editNameDialog from './editNameDialog.vue'
+import markdownIt from "markdown-it";
+import echartsRTCH from './echartsRTCH'
 export default {
   emits: ["delItem", "editItem", "saveItem"],
   components: {
     mdView,
-    eChartTemplate
+    eChartTemplate,
+    echartsSpectrogram,
+		echartsRTCH
     // editNameDialog
   },
   props: {
@@ -252,6 +266,16 @@ export default {
     showBrief: {
       type: Boolean,
       default: true
+    },
+		isDrag: {
+      type: Boolean,
+      default: false
+    },
+    dialogTagList: {
+      type: Array,
+      default: () => {
+        return [];
+      }
     }
   },
   data() {
@@ -289,7 +313,13 @@ export default {
             message: "长度需在1-20个字符之间"
           }
         ]
-      }
+      },
+			startY: 0,
+      startTop: 0,
+      moveTop: 0,
+      isDragging: false,
+      dragBoxList: [],
+      enterDrag: null
     };
   },
   computed: {
@@ -314,6 +344,7 @@ export default {
   },
   methods: {
     changeOpenItem(newValue) {
+			if(this.isDrag)return;
       if (this.loading == true && this.loadNum != 0)
         return this.$message("请稍后...");
       this.loadNum = 0;
@@ -422,10 +453,18 @@ export default {
       };
     },
     getData(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("请上传表格形式的转录文稿");
       }
+
       try {
         let _result = [];
         let _data = this.bmData.editorBarData.content;
@@ -446,12 +485,22 @@ export default {
           _result.push(obj);
         });
         if (_result.length == 0) return this.$message.error("未找到表格数据");
-        if (this.data.jsonData.name == "S-T分析:课堂时间分配") {
+        if (
+          this.data.jsonData.name == this.converter("S-T分析:课堂时间分配")
+        ) {
           this.getTimeAllocationData(_result, type);
-        } else if (this.data.jsonData.name == "S-T分析:师生互动分析") {
+        } else if (
+          this.data.jsonData.name == this.converter("S-T分析:师生互动分析")
+        ) {
           this.getInteractionAnalysisData(_result, type);
-        } else if (this.data.jsonData.name == "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("未找到对应的分析");
         }
@@ -466,7 +515,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 == "学生") {
@@ -481,11 +529,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%,师生时间占比比较均衡。
@@ -493,7 +544,7 @@ export default {
 老师占比:${_dataPercentage[0].percentage}
 学生占比:${_dataPercentage[1].percentage}
 `;
-        return this.getAiContent(_msg)
+        return this.getAiContent(_msg);
       }
 
       const _option = {
@@ -544,7 +595,6 @@ export default {
         ]
       };
 
-			console.log(_option)
 
       let _copyData = JSON.parse(JSON.stringify(this.data));
       _copyData.jsonData.eChartData = _option;
@@ -799,7 +849,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;
@@ -832,6 +881,245 @@ 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("时间点")))
+
+					_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;
+        let type = 0; //0 用agentId  1:用提示词 3:啥都没有
+        let assistant =
+          this.dialogTagDataList.find(i => i.id == this.data.jsonData.mId) ||
+          this.dialogTagDataList.find(i => i.name == this.data.jsonData.name);
+
+        let _msg = `使用文件检索的方式完整的去分析文件内容,并请完全按照要求输出。`;
+
+        if (!assistant) {
+          this.loading = false;
+          this.loadNum = 2;
+          type = 3;
+          return this.$message.error("未找到对应的AI助手");
+        }
+
+        if (assistant.tips) {
+          type = 1;
+        } else if (assistant.agentid) {
+          type = 0;
+        } else {
+          this.loading = false;
+          this.loadNum = 2;
+          type = 3;
+          return this.$message.error("未找到对应的AI助手");
+        }
+
+        // console.log('👇')
+        // return console.log(_msg)
+        let parm = {
+          assistant_id:
+            type == 0
+              ? assistant.agentid
+              : "f8e1ebb2-2e0d-11ef-8bf4-12e77c4cb76b",
+          message: type == 0 ? _msg : assistant.tips,
+          session_name: uuidv4(),
+          userId: this.userId,
+          file_ids: this.fileId ? [this.fileId] : "",
+          model: "gpt-4o-2024-08-06"
+        };
+
+        // 👇
+        // const _uid = uuidv4();
+        // let parm = {
+        // 	assistant_id: assistant ? assistant.value : null,
+        // 	userId: this.userId, //602def61-005d-11ee-91d8-005056b86db5
+        // 	message:
+        // 		"请使用代码解析器获取文件,帮我根据要求完整的分析,输出请按照要求。",
+        // 	session_name: new Date().getTime(),
+        // 	uid: _uid,
+        // 	file_ids: this.fileId ? [this.fileId] : [],
+        // };
+        if (!parm.assistant_id) {
+          this.loading = false;
+          this.loadNum = 2;
+          return this.$message.error("未找到对应的AI助手");
+        }
+        // this.ajax
+        // 	.post("https://gpt4.cocorobo.cn/ai_agent_park_chat_new", parm)
+        // 	.then((res) => {
+        // 		if (res.data.FunctionResponse.result == "发送成功") {
+        // 		} else {
+        // 			this.$message.warning(res.data.FunctionResponse.result);
+        // 		}
+        // 	})
+        // 	.catch((err) => {
+        // 		console.log(err);
+        // 	});
+        // this.getAtAuContent(_uid);
+        // 👆
+
+        this.ajax
+          .post("https://gpt4.cocorobo.cn/ai_agent_park_chat", parm)
+          .then(res => {
+            let _data = res.data.FunctionResponse;
+            // if (
+            // 	!_data.message ||
+            // 	_data.message.indexOf("由于我无法直接访问您上传的文件内容") > -1
+            // ) {
+            // 	this.loading = false;
+            // 	this.loadNum = 2;
+            // 	// this.$message.error("AI无法识别优化");
+            // 	return
+            // }
+            let _copyData = JSON.parse(JSON.stringify(this.data));
+            // _copyData.jsonData.result = "";
+            _copyData.jsonData.content = _data.message;
+            _copyData.jsonData.dataFileList = [];
+            _copyData.jsonData.fileList = [];
+            _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;
+          })
+          .catch(err => {
+            this.loadNum = 2;
+            // this.$message.error("AI无法识别优化");
+            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() {

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 643 - 448
src/components/pages/classroomObservation/components/chatArea.vue


+ 97 - 0
src/components/pages/classroomObservation/components/echartsRTCH.vue

@@ -0,0 +1,97 @@
+<template>
+  <div>
+    <canvas class="canvas" ref="canvasRef"></canvas>
+  </div>
+</template>
+
+<script>
+export default {
+  props: {
+    data: {
+      type: Object,
+      default: () => {
+        return { RT: 0, CH: 0 };
+      }
+    }
+  },
+  data() {
+    return {
+      canvas: null
+    };
+  },
+  methods: {
+    init() {
+      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;
+      let fontColor = "#1a7ad3";
+      ctx.imageSmoothingEnabled = false;
+      ctx.lineWidth = 1;
+      const img = new Image();
+      img.src = require("../../../../assets/icon/classroomObservation/rt-ch_echarts.png");
+      img.onload = () => {
+        ctx.drawImage(img, 25, 25, 250, 250);
+        ctx.beginPath();
+        ctx.arc((250*parseFloat(this.data.RT))+25,(250-(250*parseFloat(this.data.CH))+25), 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=${this.data.RT}    CH=${this.data.CH}`, 40, 330);
+    },
+    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", () => {
+      if (!this.$refs.canvasRef) return;
+      this.init();
+    });
+  }
+};
+</script>
+
+<style scoped>
+.canvas {
+  width: 300px;
+  height: 350px;
+}
+</style>

+ 165 - 0
src/components/pages/classroomObservation/components/echartsSpectrogram.vue

@@ -0,0 +1,165 @@
+<template>
+  <div>
+    <canvas class="canvas" ref="canvasRef"></canvas>
+  </div>
+</template>
+
+<script>
+export default {
+  props: {
+    data: {
+      type: Object,
+      default: () => {
+        return { data: [], breakpoint: [] };
+      }
+    }
+  },
+  data() {
+    return {
+      canvas: null,
+      step: 300
+    };
+  },
+  methods: {
+    init() {
+      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 canvasWidth2 = canvasWidth - 20
+      let canvasHeight = this.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 = this.data.data.reduce((pre, cur) => (pre += cur.value), 0);
+			console.log(sum)
+      // 当前x位置的起始点
+      let currentX = 10;
+      // 计算并绘制每个区域
+      this.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;
+
+      this.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((this.step / (sum / canvasWidth2)).toFixed(2));
+      //绘制竖线
+      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);
+				}
+      }
+
+			ctx.beginPath();
+			ctx.strokeStyle = '#BFBFBF'
+			ctx.moveTo(10, canvasHeight - 55);
+			ctx.lineTo(canvasWidth2+10, canvasHeight - 55);
+			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(); // 填充颜色
+    },
+		formatTime(seconds) {
+  let h = Math.floor(seconds / 3600); // 小时
+  let m = Math.floor((seconds % 3600) / 60); // 分钟
+  let s = Math.floor(seconds % 60); // 秒数
+  
+  // 格式化为两位数
+  let formattedTime;
+  if (h > 0) {
+    // 如果有小时部分,格式化为 hh:mm:ss
+    formattedTime = `${h.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`;
+  } else {
+    // 否则,格式化为 mm:ss
+    formattedTime = `${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`;
+  }
+  return formattedTime;
+}
+  },
+  mounted() {
+    this.init();
+    window.addEventListener("resize", () => {
+      if (!this.$refs.canvasRef) return;
+      this.init();
+    });
+  }
+};
+</script>
+
+<style scoped>
+.canvas {
+  width: 100%;
+  height: 100%;
+}
+</style>

+ 147 - 220
src/components/pages/classroomObservation/components/messageArea.vue

@@ -41,8 +41,11 @@
       <analysis
         v-for="(item, index) in dialogTagList"
         :key="item.value"
+        :index="index"
+        :dialogTagList="dialogTagList"
+        :maxIndex="dialogTagList.length - 1"
         @updateMessage="updateMessage"
-        :ref="`analysis${item.value}`"
+        :ref="`analysis_${item.value}`"
         :showBrief="showBrief"
         @delItem="delAnalysisItem"
         @editItem="editAnalysisItem"
@@ -50,6 +53,9 @@
         @changeAnalysisName="changeAnalysisName"
         @addNewAnalysisGroup="addNewAnalysisGroup"
         @delAnalysisGroup="delAnalysisGroup"
+        @setTheLocation="setTheLocation"
+        @moveAnalysis="moveAnalysis"
+        :dataList="dataList.filter(i => !(item.value == 0 && i.tIndex == 2))"
         :bmData="bmData.jsonData"
         :title="item.name"
         :dialogTagDataList="dialogTagDataList"
@@ -61,207 +67,10 @@
         :type="item.value"
         :tid="tid"
         :fileId="fileId"
+        :isDrag="isDrag"
         v-loading="item.loading"
       />
-
-      <!-- <div class="ma_m_addNewAnalysisBox"></div> -->
-
-      <!-- <analysis
-				@updateMessage="updateMessage"
-				ref="analysis0"
-				:showBrief="showBrief"
-				@delItem="delAnalysisItem"
-				@editItem="editAnalysisItem"
-				@saveItem="saveAnalysis"
-				:bmData="bmData.jsonData"
-				title="通用课堂分析"
-				:dialogTagDataList="dialogTagDataList"
-				:analysisItemList="dataList.filter((i) => i.Type === 0 && i.tIndex!=2)"
-				:type="0"
-				:tid="tid"
-				:fileId="fileId"
-				v-loading="currencyLoading"
-			/>
-			<analysis
-				@updateMessage="updateMessage"
-				ref="analysis1"
-				:showBrief="showBrief"
-				:bmData="bmData.jsonData"
-				@delItem="delAnalysisItem"
-				@editItem="editAnalysisItem"
-				@saveItem="saveAnalysis"
-				title="学科课堂分析"
-				:dialogTagDataList="dialogTagDataList"
-				:analysisItemList="dataList.filter((i) => i.Type === 1)"
-				:type="1"
-				:tid="tid"
-				:fileId="fileId"
-				v-loading="scienceLoading"
-			/>
-			<analysis
-				@updateMessage="updateMessage"
-				ref="analysis2"
-				:bmData="bmData.jsonData"
-				:showBrief="showBrief"
-				@delItem="delAnalysisItem"
-				@editItem="editAnalysisItem"
-				@saveItem="saveAnalysis"
-				title="扩展分析"
-				:dialogTagDataList="dialogTagDataList"
-				:analysisItemList="dataList.filter((i) => i.Type === 2)"
-				:type="2"
-				:tid="tid"
-				:fileId="fileId"
-				v-loading="extendLoading"
-			/> -->
-      <!-- <analysis
-				@updateMessage="updateMessage"
-				ref="analysis3"
-				:showBrief="showBrief"
-				@delItem="delAnalysisItem"
-				@editItem="editAnalysisItem"
-				title="增值性分析"
-				:dialogTagDataList="dialogTagDataList"
-				:analysisItemList="dataList.filter((i) => i.Type === 3)"
-				:type="3"
-				:tid="tid"
-				:fileId="fileId"
-				v-loading="valueAddedLoading"
-			/> -->
-      <!-- <currencyAnalysis
-				@updateMessage="updateMessage"
-				@delItem="delAnalysisItem"
-				@editItem="editAnalysisItem"
-				:analysisItemList="dataList.filter((i) => i.Type === 0)"
-				:tid="tid"
-				v-loading="currencyLoading"
-				ref="currencyAnalysisRef"
-			/>
-
-			<scienceAnalysis
-				@updateMessage="updateMessage"
-				@delItem="delAnalysisItem"
-				@editItem="editAnalysisItem"
-				:analysisItemList="dataList.filter((i) => i.Type === 1)"
-				:tid="tid"
-				v-loading="scienceLoading"
-				ref="scienceAnalysisRef"
-			/>
-
-			<extendAnalysis
-				@updateMessage="updateMessage"
-				@delItem="delAnalysisItem"
-				@editItem="editAnalysisItem"
-				:analysisItemList="dataList.filter((i) => i.Type === 2)"
-				:tid="tid"
-				v-loading="extendLoading"
-				ref="extendAnalysisRef"
-			/> -->
-      <!-- <div style="height: 10000px;"></div> -->
     </div>
-    <!-- <el-dialog
-			:center="true"
-			:visible.sync="dialogVisible"
-			width="1200px"
-			class="addTemplateDialog"
-		>
-			<div class="a-d-top">
-				<div class="a-d-topTit"><div style="width: 136px">添加模块</div></div>
-				<div>
-					<el-input
-						placeholder="请输入内容"
-						prefix-icon="el-icon-search"
-						v-model="input2"
-						clearable
-					>
-					</el-input>
-				</div>
-				<div class="a-d-t-right">
-					<span @click.stop="dialogVisible = false">×</span>
-				</div>
-			</div>
-			<div style="display: flex; height: 100%">
-				<div class="a-d-t-left">
-					<div
-						:style="
-							tagIndex == index
-								? 'background: rgba(226, 238, 255, 1);color: rgba(54, 129, 252, 1)'
-								: ''
-						"
-						class="a-d-t-l-item"
-						v-for="(item, index) in dialogTagList"
-						:key="item.id"
-						@click.stop="tagIndex = index"
-					>
-						{{ item.name }}
-					</div>
-				</div>
-				<div class="a-d-box">
-					<div
-						style="
-							font-family: PingFang SC;
-							font-size: 16px;
-							font-weight: 600;
-							line-height: 22px;
-							text-align: left;
-							margin: 20px 0;
-							margin-bottom: 10px;
-						"
-					>
-						推荐
-					</div>
-					<div class="a-d-b-subject">
-						<span
-							:class="[
-								'a_d_b_s_btn',
-								tagSubject == item ? 'a_d_b_s_ActiveBtn' : '',
-							]"
-							v-for="(item, index) in tagSubjectList"
-							:key="index + '-' + tagIndex"
-							@click.stop="tagSubject = item"
-							>{{ item }}</span
-						>
-					</div>
-					<div style="display: flex; flex-wrap: wrap">
-						<div
-							class="a-d-b-item"
-							v-for="(item, index) in searchDataList"
-							:key="index"
-						>
-							<div class="a-d-b-i-top">
-								<img
-									style="height: 22px; width: 22px"
-									:src="
-										require('../../../../assets/icon/classroomObservation/digImg.svg')
-									"
-								/>
-								<div class="a-d-b-i-t-title">{{ item.name }}</div>
-							</div>
-							<div class="a-d-b-i-bottom">{{ item.detail }}</div>
-							<div class="a-d-b-i-bottomPer" style="display: block">
-								{{ item.count }}人已使用
-							</div>
-							<div class="a-d-b-i-bottomBtn" style="display: none">
-								<div
-									style="
-										display: flex;
-										width: 100%;
-										justify-content: space-around;
-									"
-								>
-									<div
-										class="a-d-b-i-t-btn1"
-										@click="addAnalysisItem(item.name)"
-									>
-										添加
-									</div>
-								</div>
-							</div>
-						</div>
-					</div>
-				</div>
-			</div>
-		</el-dialog> -->
     <!-- 添加模块 -->
     <addNewAnalysisDialog
       @update="getDialogTagDataList"
@@ -354,7 +163,9 @@ export default {
       dialogTagDataList: [],
       bmData: {},
       dataList: [],
-      imageList: {}
+      imageList: {},
+      isDrag: false,
+      copyDataList: []
     };
   },
   computed: {
@@ -383,6 +194,11 @@ export default {
     // },
   },
   watch: {
+    isDrag(newValue) {
+      if (newValue) {
+        this.copyDataList = JSON.parse(JSON.stringify(this.dataList));
+      }
+    }
     // tagIndex() {
     // 	this.tagSubject = "";
     // },
@@ -556,7 +372,7 @@ export default {
               // } else if (_result.Type == 2) {
               //   this.$refs.analysis2[0].getReport(_result.id);
               // }
-              this.$refs[`analysis${_result.Type}`][0].getReport(_result.id);
+              this.$refs[`analysis_${_result.Type}`][0].getReport(_result.id);
               this.$message.success("添加成功");
               resolve();
             } else {
@@ -747,6 +563,7 @@ export default {
     // },
     getAnalysisData(type) {
       if (!this.tid) return;
+      let _copyTid = this.tid;
       return new Promise(resolve => {
         let params = {
           tid: this.tid,
@@ -788,6 +605,7 @@ export default {
             params
           )
           .then(res => {
+            if (_copyTid != this.tid) return;
             let _data = res.data.FunctionResponse.result.length
               ? JSON.parse(res.data.FunctionResponse.result)
               : [];
@@ -917,19 +735,27 @@ export default {
         });
       }
     },
-    getData() {
+    getData(arr = []) {
       return new Promise(resolve => {
         this.dataList = [];
+        this.isDrag = false;
+        this.$forceUpdate();
         if (this.tid) {
-          this.getAnalysisData(0).then(res => {
+          return this.getAnalysisData(0).then(res => {
             this.$nextTick(() => {
               let promises = [];
-              this.dialogTagList.forEach(i => {
-                if (i.value === 0) return;
-                promises.push(this.getAnalysisData(i.value));
-              });
-              Promise.all(promises).then(res => {
-                resolve();
+              this.$nextTick(() => {
+                let forData = arr.length ? arr : this.dialogTagList;
+                this.dialogTagList = forData;
+                forData.forEach(i => {
+                  if (i.value === 0) return;
+                  promises.push(this.getAnalysisData(i.value));
+                });
+                Promise.all(promises).then(res => {
+                  this.dataList.sort((a, b) => a.tIndex - b.tIndex);
+                  this.dataList.sort((a, b) => a.Type - b.Type);
+                  resolve();
+                });
               });
             });
           });
@@ -1151,9 +977,12 @@ export default {
           type: "warning"
         })
           .then(() => {
-            this.$refs["analysis0"][0].getReport();
-            this.$refs["analysis1"][0].getReport();
-            this.$refs["analysis2"][0].getReport();
+            this.dialogTagList.forEach(i => {
+              this.$refs[`analysis_${i.value}`][0].getReport();
+            });
+            // this.$refs["analysis0"][0].getReport();
+            // this.$refs["analysis1"][0].getReport();
+            // this.$refs["analysis2"][0].getReport();
             this.$message.success("一键分析成功");
             // this.$refs.analysis3.getReport();
           })
@@ -1295,7 +1124,7 @@ export default {
     },
     delAnalysisGroup(data) {
       let _index = this.dialogTagList.findIndex(i => i.value === data.type);
-			this.dialogTagList[_index].loading = true
+      this.dialogTagList[_index].loading = true;
       let promises = [];
       data.groupId.forEach(i => {
         promises.push(this.delAnalysisItem2(i));
@@ -1303,14 +1132,14 @@ export default {
 
       if (promises.length > 0) {
         return Promise.all(promises).then(res => {
-					this.dialogTagList.splice(_index,1);
-					this.$message.success("删除分析分组成功");
-          this.saveData(this.bmData)
+          this.dialogTagList.splice(_index, 1);
+          this.$message.success("删除分析分组成功");
+          this.saveData(this.bmData);
         });
       } else {
-				this.dialogTagList.splice(_index,1);
-				this.$message.success("删除分析分组成功");
-        return this.saveData(this.bmData)
+        this.dialogTagList.splice(_index, 1);
+        this.$message.success("删除分析分组成功");
+        return this.saveData(this.bmData);
       }
     },
     delAnalysisItem2(id) {
@@ -1341,8 +1170,105 @@ export default {
         }
       });
     },
+    setTheLocation(data) {
+      let moveList = JSON.parse(JSON.stringify(this.dialogTagList));
+      let _index = moveList.findIndex(i => i.value === data.value);
+      if (_index === -1) return; // 如果没找到,直接退出
+      if (data.type == 0) {
+        // 置顶
+        moveList = this.moveListFn(moveList, _index, 0);
+      } else if (data.type == 1) {
+        // 置底
+        moveList = this.moveListFn(moveList, _index, moveList.length - 1);
+      } else if (data.type == 2) {
+        // 上移
+        moveList = this.moveListFn(moveList, _index, _index - 1);
+      } else if (data.type == 3) {
+        // 下移
+        moveList = this.moveListFn(moveList, _index, _index + 1);
+      }
+      this.dialogTagList = moveList;
+      this.bmData.jsonData["dialogTagList"] = this.dialogTagList;
+      this.saveData(this.bmData);
+      this.$forceUpdate();
+    },
+    moveListFn(arr, fromIndex, toIndex) {
+      // 确保索引在数组范围内
+      if (
+        fromIndex >= 0 &&
+        fromIndex < arr.length &&
+        toIndex >= 0 &&
+        toIndex < arr.length
+      ) {
+        // 删除要移动的元素并存储
+        const element = arr.splice(fromIndex, 1)[0];
+        // 在新的位置插入元素
+        arr.splice(toIndex, 0, element);
+      }
+      return arr;
+    },
+    moveArrayElement(arr, fromIndex, toIndex) {
+      const element = arr[fromIndex];
+      arr.splice(fromIndex, 1); // 删除原位置的元素
+
+      // 调整目标下标以确保正确插入
+      const newIndex = toIndex < fromIndex ? toIndex : toIndex - 1;
+
+      arr.splice(newIndex, 0, element); // 插入到新位置
+      return arr;
+    },
+    moveAnalysis(data) {
+      let _form = data.form.split("_");
+      let _to = data.to.split("_");
+
+      if (_form[0] === _to[0] && _form[1] === _to[1] && _form[2] === _to[2])
+        return console.log("位置不变");
+      if (
+        _form[0] === _to[0] &&
+        parseInt(_form[1]) - 1 === parseInt(_to[1]) &&
+        _to[3] == "1"
+      )
+        return console.log("位置不变2");
+      let _formIndex = this.dataList.findIndex(
+        i => i.Type == parseInt(_form[0]) && i.tIndex == parseInt(_form[2])
+      );
+      let _toIndex = -1;
+      if (["0", "1"].includes(_to[3])) {
+        _toIndex = this.dataList.findIndex(
+          i => i.Type == parseInt(_to[0]) && i.tIndex == parseInt(_to[2])
+        );
+      }
+      // 更新Type与tIndex
+      this.dataList[_formIndex].Type = parseInt(_to[0]);
+      if (_to[3] === "1") {
+        //下面
+        this.moveArrayElement(this.dataList, _formIndex, _toIndex+1);
+      } else if (_to[3] === "0") {
+        //上面
+        this.moveArrayElement(this.dataList, _formIndex, _toIndex);
+      } else if (_to[3] === "2") {
+        console.log("移到了空模块");
+      }
+
+      this.dialogTagList.forEach(i => {
+        let _data = this.dataList.filter(i2 => i2.Type === i.value);
+        let _fTIndex = i.value === 0 ? 2 : 0;
+        _data.forEach(i3 => {
+          if (this.dataList.find(i4 => i4.id === i3.id).tIndex != _fTIndex) {
+            this.dataList.find(i4 => i4.id === i3.id).tIndex = _fTIndex;
+          }
+          _fTIndex++;
+        });
+      });
+      this.$forceUpdate();
+      console.log("更换成功");
+    },
+    moveAnalysisCancel() {
+      this.isDrag = false;
+      this.dataList = JSON.parse(JSON.stringify(this.copyDataList));
+      this.copyDataList = [];
+    },
     init() {
-      console.log("初始化");
       this.bmData = {};
       this.getDefaultData();
       this.imageList = {};
@@ -1408,6 +1334,7 @@ export default {
   width: 100%;
   height: calc(100vh - 123px);
   overflow: auto;
+  position: relative;
 }
 
 .a-d-top {

+ 276 - 98
src/components/pages/classroomObservation/index.vue

@@ -225,9 +225,26 @@ export default {
       tag: {
         0: "一",
         1: "二",
-        2: "三"
-      }
-    };
+        2: "三",
+				3: '四',
+				4: '五',
+				5: '六',
+				6: '七',
+				7:'八',
+				8:'九',
+				9:'十',
+				10:'十一',
+				11:'十二',
+				12:'十三',
+				13:'十四',
+				14:'十五',
+				15:'十六',
+				16:'十七',
+				17:'十八',
+				18:'十九',
+				19:'二十',
+    	},
+		}
   },
   methods: {
     //切换了课堂
@@ -378,15 +395,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();
@@ -420,14 +437,16 @@ export default {
                     this.$nextTick(() => {
                       this.getCourseList().then(_ => {
                         this.getFileIdId();
-                        this.$refs.messageAreaRef.getData().then(res => {
-                          if (tagList) {
-                            this.$refs.messageAreaRef.changeAnalysisName(
-                              tagList.dialogTagList,
-                              1
-                            );
-                          }
-                        });
+                        this.$refs.messageAreaRef
+                          .getData(tagList ? tagList.dialogTagList : [])
+                          .then(res => {
+                            if (tagList) {
+                              this.$refs.messageAreaRef.changeAnalysisName(
+                                tagList.dialogTagList,
+                                1
+                              );
+                            }
+                          });
                         this.$refs.chatAreaRef.getData();
                         resolve();
                       });
@@ -503,56 +522,6 @@ export default {
 
         let analysisHtml = ``;
 
-        // tagList.forEach(i => {
-        //   let dire = `<div>`;
-        //   let tagHtml = `<div style="margin-bottom:100px">`;
-        //   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:30px">${
-        //     this.tag[i.value]
-        //   }、${i.name}</h1>`;
-        //   dire += `<div style="font-size:14pt;margin:15px 0">${
-        //     this.tag[i.value]
-        //   }、${i.name}</div>`;
-        //   i.dataList.forEach(async (i2, i2Index) => {
-        //     tagHtml += `<h2 style="font-size:14pt;margin-bottom:20px">${i2Index +
-        //       1}、${
-        //       i2.jsonData.anotherName
-        //         ? i2.jsonData.anotherName
-        //         : i2.jsonData.name
-        //     }</h2>`;
-        //     dire += `<div style="font-size:11pt;margin:10px 0;margin-left:50px">${i2Index +
-        //       1}、${
-        //       i2.jsonData.anotherName
-        //         ? i2.jsonData.anotherName
-        //         : i2.jsonData.name
-        //     }</div>`;
-        //     if (showBrief) {
-        //       tagHtml += `<div style="font-size:10.5pt;font-style:italic;margin-bottom:10px;color:#6b798e">${i2.jsonData.result}</div>`;
-        //     }
-        //     if (i2.jsonData.eChartData) {
-        //       console.log(
-        //         await this.getEChartsImageSrc(i2.jsonData.eChartData)
-        //       );
-        //       // tagHtml += `<div>${await this.getEChartsImageSrc(
-        //       //   i2.jsonData.eChartData
-        //       // )}</div>`;
-        //       // tagHtml+=`<img src="${await this.getEChartsImageSrc(i2.jsonData.eChartData)}" style="width:400px;height:400px;"/>`
-        //     }
-        //     let _content = md.render(i2.jsonData.content);
-
-        //     tagHtml += `<div style="font-size:10.5pt;margin-bottom:10px">${_content}</div>`;
-        //   });
-
-        //   tagHtml += "</div>";
-        //   dire += "</div>";
-        //   analysisHtml += tagHtml;
-        //   directoryHtml += dire;
-        // });
-
         for (let c = 0; c < tagList.length; c++) {
           let i = tagList[c];
           let dire = `<div>`;
@@ -592,11 +561,18 @@ 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>
-								</div>`;
+              tagHtml += `<div style="width:100vw;text-align:center;"><img style="margin:auto" src="${await this.getEChartsechartsRTCHImage({
+                RT: i2.jsonData.RT,
+                CH: i2.jsonData.CH
+              })}"/></div>`;
             }
 
             let _content = md.render(i2.jsonData.content);
@@ -614,15 +590,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 +673,196 @@ 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 = 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));
+          //绘制竖线
+          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);
+            }
+          }
+
+          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_echarts.png");
+          img.onload = () => {
+            ctx.drawImage(img, 25, 25, 250, 250);
+						ctx.fillStyle = fontColor;
+            ctx.beginPath();
+            ctx.arc(
+              250 * parseFloat(data.RT) + 25,
+              250 - 250 * parseFloat(data.CH) + 25,
+              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 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 +871,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">
@@ -1035,6 +1191,28 @@ export default {
     //保存词频词汇分析
     saveWordFrequency({ _sentence = 0, _words = 0 }) {
       this.$refs.messageAreaRef.saveWordFrequency({ _sentence, _words });
+    },
+    formatTime(seconds) {
+      let h = Math.floor(seconds / 3600); // 小时
+      let m = Math.floor((seconds % 3600) / 60); // 分钟
+      let s = Math.floor(seconds % 60); // 秒数
+
+      // 格式化为两位数
+      let formattedTime;
+      if (h > 0) {
+        // 如果有小时部分,格式化为 hh:mm:ss
+        formattedTime = `${h
+          .toString()
+          .padStart(2, "0")}:${m
+          .toString()
+          .padStart(2, "0")}:${s.toString().padStart(2, "0")}`;
+      } else {
+        // 否则,格式化为 mm:ss
+        formattedTime = `${m
+          .toString()
+          .padStart(2, "0")}:${s.toString().padStart(2, "0")}`;
+      }
+      return formattedTime;
     }
   },
   mounted() {

+ 18 - 2
src/components/pages/pblCourse/component/chatArea.vue

@@ -121,6 +121,7 @@ export default {
 			cid: this.$route.query.cid,
 			chatList: [],
 			textValue: "",
+			userName:"",
 		};
 	},
 	methods: {
@@ -257,12 +258,12 @@ export default {
 		},
 		// 保存对话
 		//保存消息
-		insertChat(_uid) {
+		async insertChat(_uid) {
 			let _data = this.chatList.find((i) => i.uid == _uid);
 			if (!_data) return;
 			let params = {
 				userId: this.userId,
-				userName: "qgt",
+				userName: this.userName?this.userName:await this.getUser(this.userId),
 				groupId: "602def61-005d-11ee-91d8-005056b8q12w",
 				answer: _data.aiContent,
 				problem: _data.content,
@@ -278,6 +279,21 @@ export default {
 					console.log("保存对话")
 				});
 		},
+		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.userName = data.username;
+						resolve(data.username)
+          })
+          .catch(err => {
+            console.error(err);
+          });
+      });
+    },
 		// 获取对话记录
 		getChatList() {
 			return new Promise((resolve, reject) => {

+ 17 - 2
src/components/pages/pocAi/index.vue

@@ -427,13 +427,13 @@ export default {
       };
     },
 		//保存消息
-    insertChat(_uid) {
+    async insertChat(_uid) {
       if (_uid == "") return;
       let _data = this.chatList.find(i => i.uid == _uid);
       if (!_data) return;
       let params = {
         userId: this.userId,
-        userName: "qgt",
+        userName: this.userName?this.userName:await this.getUser(this.userId),
         groupId: "qwertyuiop-poc",
         answer: _data.aiContent,
         problem: _data.content,
@@ -446,6 +446,21 @@ export default {
       this.ajax
         .post("https://gpt4.cocorobo.cn/insert_chat", params)
         .then(res => {});
+    },
+		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.userName = data.username;
+						resolve(data.username)
+          })
+          .catch(err => {
+            console.error(err);
+          });
+      });
     },
 		// 获取对应的聊天记录
     getChatList() {

+ 19 - 3
src/components/pages/pocAiClassroom/chatArea.vue

@@ -529,7 +529,8 @@ export default {
         Image: /^https?:\/\/(.+\/)+.+(\.(gif|png|jpg|jpeg|webp|svg|psd|bmp|tif))$/i,
         File: /^https?:\/\/(.+\/)+.+(\.(docx|doc|xlsx|ppt|pdf|pptx|txt))$/i,
         video: /^https?:\/\/(.+\/)+.+(\.(avi|wmv|mpg|mpeg|mov|rm|ram|mp4|swf|flv))$/i
-      }
+      },
+			userName:"",
     };
   },
 	directives: {
@@ -701,13 +702,13 @@ export default {
       };
     },
     //保存消息
-    insertChat(_uid) {
+    async insertChat(_uid) {
       if (_uid == "") return;
       let _data = this.chatList.find(i => i.uid == _uid);
       if (!_data) return;
       let params = {
         userId: this.userId,
-        userName: "qgt",
+        userName: this.userName?this.userName:await this.getUser(this.userId),
         groupId: "qwertyuiop-poc",
         answer: _data.aiContent,
         problem: _data.content,
@@ -720,6 +721,21 @@ export default {
       this.ajax
         .post("https://gpt4.cocorobo.cn/insert_chat", params)
         .then(res => {});
+    },
+		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.userName = data.username;
+						resolve(data.username)
+          })
+          .catch(err => {
+            console.error(err);
+          });
+      });
     },
     // 获取对应的聊天记录
     getChatList() {

+ 1 - 1
src/components/pages/student/addCourse.vue

@@ -5494,7 +5494,7 @@ export default {
         }
       }
       let params = [{
-        uid: _user.join(","),
+        uid: array.join(","),
       }];
       this.ajax
         .post(this.$store.state.api + "getAllUserByIdP", params)

+ 1 - 1
src/components/pages/studentManage/student.vue

@@ -191,7 +191,7 @@
           <el-input disabled style="width: 300px" v-model="schoolName"></el-input>
         </el-form-item>
         <el-form-item label="班级" :label-width="formLabelWidth">
-          <el-select multiple collapse-tags v-model="sByClass" placeholder="请选择班级">
+          <el-select multiple collapse-tags v-model="sByClass" placeholder="请选择班级" filterable>
             <el-option v-for="(item, index) in classJuri" :key="index" :label="item.name" :value="item.id"></el-option>
           </el-select>
         </el-form-item>

+ 1 - 1
src/components/pages/studio/addCourse.vue

@@ -6537,7 +6537,7 @@ export default {
         }
       }
       let params = [{
-        uid: _user.join(","),
+        uid: array.join(","),
       }];
       this.ajax
         .post(this.$store.state.api + "getAllUserByIdP", params)

+ 50 - 44
src/components/pages/synergyCourse/addCourse.vue

@@ -1749,54 +1749,56 @@
       </span>
     </el-dialog> -->
     <el-dialog title="添加协同成员" :visible.sync="dialogVisibleMember" :append-to-body="true" width="25%" height="80%"
-      :before-close="handleClose" class="addNewPP customWidth">
-      <div class="people">
-        <div class="people_top">
-          <div class="people_top_right">
-            <div class="people_search">
-              <el-input placeholder="搜索完整的姓名/账号(不含邮箱后缀)" v-model="searchTN" @keyup.enter.native="getTeacher"></el-input>
-              <div class="search_img" @click="getTeacher">
-                <img src="../../../assets/icon/search.png" alt />
+      :before-close="handleClose" class="addNewPP customWidth" >
+      <div v-loading="teacherLoading">
+        <div class="people">
+          <div class="people_top">
+            <div class="people_top_right">
+              <div class="people_search">
+                <el-input placeholder="搜索完整的姓名/账号(不含邮箱后缀)" v-model="searchTN" @keyup.enter.native="getTeacher"></el-input>
+                <div class="search_img" @click="getTeacher">
+                  <img src="../../../assets/icon/search.png" alt />
+                </div>
               </div>
             </div>
+            <div class="people_nav">选择成员</div>
           </div>
-          <div class="people_nav">选择成员</div>
-        </div>
-        <div class="t_j_box" style="
-            padding: 20px 0 0 25px;
-            width: calc(100% - 55px);
-            margin-left: 25px;
-          ">
-          <span>姓名</span>
-          <span>身份</span>
-          <span>账号</span>
-          <span>学校</span>
+          <div class="t_j_box" style="
+              padding: 20px 0 0 25px;
+              width: calc(100% - 55px);
+              margin-left: 25px;
+            ">
+            <span>姓名</span>
+            <span>身份</span>
+            <span>账号</span>
+            <span>学校</span>
+          </div>
+          <div style="margin-left:25px;">
+            <el-checkbox :indeterminate="isIndeterminate" v-model="checkAll" @change="handleCheckAllChange" style="display: flex;align-items: center;">全选</el-checkbox>
+          </div>
+          <el-checkbox-group v-model="checkboxList3" @change="handleCheckedTeacherJuriChange" class="people_name" v-if="teacherJuri.length">
+            <el-checkbox v-for="item in teacherJuri" :key="item.userid" :label="item.userid">
+              <div class="t_j_box">
+                <el-tooltip placement="top" :content="item.name ? item.name : '暂无姓名'">
+                  <span>{{ item.name ? item.name : "暂无姓名" }}</span>
+                </el-tooltip>
+                <span>{{ item.type == "1" ? "老师" : "学生" }}</span>
+                <el-tooltip placement="top" :content="item.username">
+                  <span>{{ item.username }}</span>
+                </el-tooltip>
+                <el-tooltip placement="top" :content="item.school">
+                  <span>{{ item.school }}</span>
+                </el-tooltip>
+              </div>
+            </el-checkbox>
+          </el-checkbox-group>
+          <div style="text-align: center; margin-top: 10px" v-else>暂无数据</div>
         </div>
-        <div style="margin-left:25px;">
-          <el-checkbox :indeterminate="isIndeterminate" v-model="checkAll" @change="handleCheckAllChange" style="display: flex;align-items: center;">全选</el-checkbox>
+        <div style="margin-top: 10px;" >
+          <el-pagination background layout="prev, pager, next" :page-size="pageSize" :total="total"
+            v-if="page && teacherJuri.length" style="padding-bottom: 20px"
+            @current-change="handleCurrentChange"></el-pagination>
         </div>
-        <el-checkbox-group v-model="checkboxList3" @change="handleCheckedTeacherJuriChange" class="people_name" v-if="teacherJuri.length">
-          <el-checkbox v-for="item in teacherJuri" :key="item.userid" :label="item.userid">
-            <div class="t_j_box">
-              <el-tooltip placement="top" :content="item.name ? item.name : '暂无姓名'">
-                <span>{{ item.name ? item.name : "暂无姓名" }}</span>
-              </el-tooltip>
-              <span>{{ item.type == "1" ? "老师" : "学生" }}</span>
-              <el-tooltip placement="top" :content="item.username">
-                <span>{{ item.username }}</span>
-              </el-tooltip>
-              <el-tooltip placement="top" :content="item.school">
-                <span>{{ item.school }}</span>
-              </el-tooltip>
-            </div>
-          </el-checkbox>
-        </el-checkbox-group>
-        <div style="text-align: center; margin-top: 10px" v-else>暂无数据</div>
-      </div>
-      <div style="margin-top: 10px;">
-        <el-pagination background layout="prev, pager, next" :page-size="pageSize" :total="total"
-          v-if="page && teacherJuri.length" style="padding-bottom: 20px"
-          @current-change="handleCurrentChange"></el-pagination>
       </div>
       <span slot="footer" class="dialog-footer">
         <el-button @click="page = 0;dialogVisibleMember = false;">取 消</el-button>
@@ -2946,10 +2948,11 @@ export default {
       imageList: [],
       heightPx: '100%',
       toolsData: JSON.parse(converter(JSON.stringify(toolsData))),
-      pageSize: 20,
+      pageSize: 200,
       total: 0,
       page: 0,
       isLoading2: false,
+      teacherLoading: false,
     };
   },
   directives: {
@@ -4682,6 +4685,7 @@ export default {
         });
     },
     getTeacher() {
+      this.teacherLoading = true;
       let params = {
         org:
           this.org && this.org != "undefined" && this.org != "null"
@@ -4712,8 +4716,10 @@ export default {
             }
           }
           this.teacherJuri = teacherJuri;
+          this.teacherLoading = false;
         })
         .catch((err) => {
+          this.teacherLoading = false;
           console.error(err);
         });
     },

+ 1 - 0
src/components/pages/teacherOffice/index.vue

@@ -648,6 +648,7 @@ export default {
           this.total = res.data[0].length > 0 ? res.data[0][0].num : 0;
           this.tableData = res.data[0];
           console.log(this.tableData, "tableData");
+          this.selectGrage()
         })
         .catch((err) => {
           this.isLoading = false;

+ 13 - 12
src/components/pages/test/add/components/course2/courseIndex.vue

@@ -96,10 +96,10 @@
                 </div>
               </div>
             </div>
-            <!-- <div class="all_choose" v-if="role == '1'">
+            <div class="all_choose">
               <span><span>所有者</span></span>
               <div class="typeCss">
-                <div class="cName" :class="groupA == '4' ? 'isCType' : ''" @click="groupA = '4'; search();">
+                <div class="cName" :class="groupA == '0' ? 'isCType' : ''" @click="groupA = '0'; search();">
                   全部
                 </div>
                 <div class="cName" :class="groupA == '2' ? 'isCType' : ''" @click="groupA = '2'; search();">
@@ -108,15 +108,12 @@
                 <div class="cName" :class="groupA == '3' ? 'isCType' : ''" @click="groupA = '3'; search();">
                   协同课程
                 </div>
-                <div class="cName" :class="groupA == '1' ? 'isCType' : ''" @click="groupA = '1'; search();">
-                  他人课程
-                </div>
               </div>
-            </div>
-            <div class="all_choose" v-else>
+            </div> 
+            <!-- <div class="all_choose" v-if="role == '1'">
               <span><span>所有者</span></span>
               <div class="typeCss">
-                <div class="cName" :class="groupA == '0' ? 'isCType' : ''" @click="groupA = '0'; search();">
+                <div class="cName" :class="groupA == '4' ? 'isCType' : ''" @click="groupA = '4'; search();">
                   全部
                 </div>
                 <div class="cName" :class="groupA == '2' ? 'isCType' : ''" @click="groupA = '2'; search();">
@@ -125,8 +122,12 @@
                 <div class="cName" :class="groupA == '3' ? 'isCType' : ''" @click="groupA = '3'; search();">
                   协同课程
                 </div>
+                <div class="cName" :class="groupA == '1' ? 'isCType' : ''" @click="groupA = '1'; search();">
+                  他人课程
+                </div>
               </div>
-            </div> -->
+            </div>
+          -->
 						<div class="chooseAll">
 							<el-checkbox v-model="checkedAll" style="display: flex;">全选</el-checkbox>
 						</div>
@@ -370,7 +371,7 @@
         userid: this.$route.query.userid,
         oid: this.$route.query.oid,
         org: this.$route.query.org,
-        role: this.$route.query.role,
+        role: 0,
         Juri: "",
         groupList: [],
         JuriList: [],
@@ -885,8 +886,8 @@
         console.log("typea", this.typea);
         this.isLoading = true;
         let params = {
-          // type: this.groupA,
-					type: "0",
+          type: this.groupA,
+					// type: "0",
           uid: this.userid,
           oid: this.oid,
           org: this.org,

+ 2127 - 0
src/components/pages/test/check/aiBoxRight.vue

@@ -0,0 +1,2127 @@
+<template>
+  <div class="ai_body" v-loading="loading">
+    <div class="ai_body_dialog" ref="chatDialog">
+      <div class="dialog_content" v-for="item in array" :key="item.uid">
+        <div v-if="item.content" style="margin-left: auto;">
+          <div class="content content2" v-html="item.content"></div>
+          <div class="role">
+            <img src="../../../../assets/icon/new/role2.png" />
+          </div>
+        </div>
+        <div
+          style="margin-top:20px;margin-bottom:20px ; margin-right: auto;"
+          v-if="item.aiContent || item.loading"
+        >
+          <div class="role">
+            <img
+              :src="
+                item.fileid
+                  ? item.fileid
+                  : require('../../../../assets/icon/new/role1.png')
+              "
+            />
+          </div>
+          <div
+            element-loading-background="#f6f9ff"
+            :style="{
+              minHeight: item.loading ? '50px' : 'unset',
+              minWidth: item.loading ? '50px' : 'unset'
+            }"
+            class="content"
+            v-loading="item.loading"
+          >
+            <span class="vditor-reset" v-html="item.aiContent"></span>
+            <span class="createTime" v-text="item.createtime"></span>
+          </div>
+          <div
+            class="ai_btn_box"
+            v-if="!pan(item.aiContent).length && !item.loading"
+          >
+            <img
+              src="../../../../assets/icon/course/pasete.png"
+              @click="onCopy(item.aiContent)"
+            />
+          </div>
+        </div>
+      </div>
+    </div>
+    <div class="ai_body_select" v-if="false">
+      <div class="checkBox" v-if="checkBool">
+        <div class="task">
+          <div class="title">选择需要优化的任务:</div>
+          <div class="content">
+            <div class="span" @click="addAllTask()">
+              <div class="check">
+                <img
+                  :src="checkImg"
+                  alt=""
+                  v-if="checkArray.length !== course.length"
+                />
+                <img :src="checkIsImg" alt="" v-else />
+              </div>
+              <span>全选</span>
+            </div>
+            <div
+              class="span"
+              v-for="(item, index) in course"
+              :key="index"
+              @click="addTask(index)"
+            >
+              <div class="check">
+                <img
+                  :src="checkImg"
+                  alt=""
+                  v-if="checkArray.indexOf(index) === -1"
+                />
+                <img :src="checkIsImg" alt="" v-else />
+              </div>
+              <span>任务{{ index + 1 }}</span>
+            </div>
+          </div>
+        </div>
+        <div class="part">
+          <div class="title">选择优化的部分:</div>
+          <div class="content">
+            <div
+              class="span"
+              v-for="(item, index) in partArray"
+              :key="index"
+              :class="{ active: part == item.name }"
+              @click="checkPart(item.name)"
+            >
+              {{ item.name }}
+            </div>
+          </div>
+        </div>
+      </div>
+      <span
+        class="check"
+        :class="{ isCheck: checkBool }"
+        v-if="!checkArray.length && !part"
+        @click="checkBool = !checkBool"
+        >选择优化内容</span
+      >
+      <span
+        class="check"
+        :class="{ isCheck: checkBool }"
+        @click="checkBool = !checkBool"
+        v-else
+      >
+        <el-tooltip :content="taskName" placement="top" effect="dark">
+          <!-- content to trigger tooltip here -->
+          <span>{{ taskName }}</span>
+        </el-tooltip>
+      </span>
+    </div>
+    <div class="ai_body_input">
+      <div class="ai_b_i_btnArea">
+        <span class="clear" @click.stop="clear()">
+          <svg
+            width="20"
+            height="20"
+            viewBox="0 0 20 20"
+            fill="none"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              fill-rule="evenodd"
+              clip-rule="evenodd"
+              d="M2.5 3.125C2.5 2.77982 2.77982 2.5 3.125 2.5H16.875C17.2202 2.5 17.5 2.77982 17.5 3.125V8.02715C17.5 8.37233 17.2202 8.65215 16.875 8.65215C16.5298 8.65215 16.25 8.37233 16.25 8.02715V3.75H3.75V16.25H8.125C8.47018 16.25 8.75 16.5298 8.75 16.875C8.75 17.2202 8.47018 17.5 8.125 17.5H3.125C2.77982 17.5 2.5 17.2202 2.5 16.875V3.125Z"
+            />
+            <path
+              fill-rule="evenodd"
+              clip-rule="evenodd"
+              d="M5.625 6.1521C5.625 5.80692 5.90482 5.5271 6.25 5.5271H13.125C13.4702 5.5271 13.75 5.80692 13.75 6.1521C13.75 6.49728 13.4702 6.7771 13.125 6.7771H6.25C5.90482 6.7771 5.625 6.49728 5.625 6.1521Z"
+            />
+            <path
+              fill-rule="evenodd"
+              clip-rule="evenodd"
+              d="M5.625 9.2771C5.625 8.93192 5.90482 8.6521 6.25 8.6521H9.37496C9.72014 8.6521 9.99996 8.93192 9.99996 9.2771C9.99996 9.62228 9.72014 9.9021 9.37496 9.9021H6.25C5.90482 9.9021 5.625 9.62228 5.625 9.2771Z"
+            />
+            <path
+              fill-rule="evenodd"
+              clip-rule="evenodd"
+              d="M12.465 11.507L15.9141 14.9048C16.1279 14.5365 16.25 14.1088 16.25 13.6521C16.25 12.2714 15.1307 11.1521 13.75 11.1521C13.2799 11.1521 12.8406 11.2815 12.465 11.507ZM15.0374 15.7957L11.5873 12.397C11.3726 12.7659 11.25 13.1944 11.25 13.6521C11.25 15.0328 12.3693 16.1521 13.75 16.1521C14.2211 16.1521 14.6613 16.0222 15.0374 15.7957ZM11.0797 11.0192C11.759 10.3303 12.7051 9.9021 13.75 9.9021C15.8211 9.9021 17.5 11.581 17.5 13.6521C17.5 14.6767 17.0882 15.6064 16.4226 16.2827C15.7431 16.9729 14.7961 17.4021 13.75 17.4021C11.6789 17.4021 10 15.7232 10 13.6521C10 12.6263 10.4127 11.6957 11.0797 11.0192Z"
+            />
+          </svg>
+          <span>清屏</span>
+        </span>
+        <span
+          class="clear"
+          @click.stop="showjList = !showjList"
+          v-if="jArray.length"
+        >
+          <span>查看</span>
+        </span>
+        <!-- <div style="margin-left: auto;">
+          <el-switch v-model="continuous"></el-switch>
+          <span @click.stop="continuous = !continuous">连续对话</span>
+        </div> -->
+      </div>
+
+      <div
+        class="ai_b_i_roleListBox"
+        ref="roleListRef"
+        v-if="showRoleList && choseRoleList.length > 0"
+      >
+        <div
+          :class="[
+            'ai_b_i_rlb_item',
+            index == choseRoleItem ? 'ai_b_i_rlb_itemActive' : ''
+          ]"
+          :ref="`roleItem${index}Ref`"
+          v-for="(item, index) in choseRoleList"
+          :key="item.id"
+          @mouseover="choseRoleItem = index"
+          @click.stop="choseRole(item)"
+        >
+          <div class="ai_b_i_rlb_itemTop">
+            <img
+              :src="
+                item.headUrl
+                  ? item.headUrl
+                  : require('../../../../assets/icon/new/role1.png')
+              "
+              alt=""
+            />
+            <div class="ai_b_i_rlb_i_name">
+              <span>{{ item.assistantName }}</span>
+              <span>作者:{{ item.username }}</span>
+            </div>
+          </div>
+          <div class="ai_b_i_rlb_itemBottom">
+            {{ item.prologue }}
+          </div>
+        </div>
+      </div>
+      <!-- <div class="ai_b_i_textListBox">
+				<div class="ai_b_i_tlb_left"></div>
+				<div class="ai_b_i_tlb_right"></div>
+			</div> -->
+      <!-- @input="inputChange" -->
+
+      <textarea
+        @input="inputChange"
+        class="ai_body_input_textarea"
+        @keydown="textareaKeydown"
+        :disabled="isVoice"
+        ref="textareaRef"
+        v-model.trim="courseText"
+        :placeholder="isVoice ? isTalk?'':'点击按钮开始录音' : '在此输入您想了解的内容'"
+      ></textarea>
+
+      <span
+        class="c_voiceBtn"
+        v-if="!courseText && !isVoice"
+        @click.stop="changeVoice(true)"
+      >
+        <svg
+          width="22"
+          height="22"
+          viewBox="0 0 22 22"
+          fill="none"
+          xmlns="http://www.w3.org/2000/svg"
+        >
+          <path
+            fill-rule="evenodd"
+            clip-rule="evenodd"
+            d="M11.4583 14.0308C13.8381 14.0308 15.7551 12.0651 15.7551 9.62496V6.23588C15.7551 3.79574 13.8381 1.83008 11.4583 1.83008C9.07845 1.83008 7.16138 3.79574 7.16138 6.23588V9.62496C7.16138 12.0651 9.07845 14.0308 11.4583 14.0308ZM8.4835 6.23588C8.4835 4.54134 9.80561 3.18571 11.4583 3.18571C13.1109 3.18571 14.433 4.54134 14.433 6.23588V9.62496C14.433 11.3195 13.1109 12.6751 11.4583 12.6751C9.80561 12.6751 8.4835 11.3195 8.4835 9.62496V6.23588ZM18.3333 10.6405C18.3333 10.2677 18.0358 9.96264 17.6722 9.96264C17.3417 9.96264 17.0442 10.2338 17.0111 10.5727C16.5484 13.3178 14.2347 15.3852 11.4583 15.3852C8.68181 15.3852 6.36811 13.3178 5.90537 10.5727C5.87231 10.2338 5.57484 9.96264 5.24431 9.96264C4.88073 9.96264 4.58325 10.2677 4.58325 10.6405V10.7421C5.1121 13.9279 7.65717 16.4019 10.7972 16.7069V19.635H7.93254C7.54315 19.635 7.22748 19.9587 7.22748 20.358C7.22748 20.7572 7.54315 21.0809 7.93254 21.0809H14.9832C15.3726 21.0809 15.6883 20.7572 15.6883 20.358C15.6883 19.9587 15.3726 19.635 14.9832 19.635H12.1193V16.7069C15.2593 16.4019 17.8044 13.9279 18.3002 10.776C18.3002 10.7591 18.3085 10.7337 18.3167 10.7082L18.3167 10.7082L18.3167 10.7082C18.325 10.6828 18.3333 10.6574 18.3333 10.6405Z"
+            fill="black"
+            fill-opacity="0.9"
+          />
+        </svg>
+      </span>
+
+      <span
+        class="c_voiceBtn"
+        style="right: 70px;"
+        v-if="!courseText && isVoice && !isTalk"
+        @click.stop="changeVoice(false)"
+      >
+        <svg
+          width="22"
+          height="22"
+          viewBox="0 0 22 22"
+          fill="none"
+          xmlns="http://www.w3.org/2000/svg"
+        >
+          <path
+            fill-rule="evenodd"
+            clip-rule="evenodd"
+            d="M2.75 3.4375C2.75 3.0578 3.0578 2.75 3.4375 2.75H18.5625C18.9422 2.75 19.25 3.0578 19.25 3.4375V18.5625C19.25 18.9422 18.9422 19.25 18.5625 19.25H3.4375C3.0578 19.25 2.75 18.9422 2.75 18.5625V3.4375ZM4.125 4.125V17.875H17.875V4.125H4.125Z"
+            fill="black"
+            fill-opacity="0.9"
+          />
+          <path
+            fill-rule="evenodd"
+            clip-rule="evenodd"
+            d="M6.875 6.875C6.875 6.4953 7.1828 6.1875 7.5625 6.1875H14.4375C14.8172 6.1875 15.125 6.4953 15.125 6.875V8.25C15.125 8.6297 14.8172 8.9375 14.4375 8.9375C14.0578 8.9375 13.75 8.6297 13.75 8.25V7.5625H11.6875V14.4375H12.375C12.7547 14.4375 13.0625 14.7453 13.0625 15.125C13.0625 15.5047 12.7547 15.8125 12.375 15.8125H9.625C9.2453 15.8125 8.9375 15.5047 8.9375 15.125C8.9375 14.7453 9.2453 14.4375 9.625 14.4375H10.3125V7.5625H8.25V8.25C8.25 8.6297 7.9422 8.9375 7.5625 8.9375C7.1828 8.9375 6.875 8.6297 6.875 8.25V6.875Z"
+            fill="black"
+            fill-opacity="0.9"
+          />
+        </svg>
+      </span>
+
+      <div
+        :class="[
+          'c_pub_button_confirm',
+          courseText ? '' : 'c_pub_button_confirmDisabled'
+        ]"
+        v-if="!faloading && !isVoice"
+        @click="addContent"
+      >
+        发送
+      </div>
+
+      <div
+        :class="['c_pub_button_confirmVoice']"
+        v-if="!faloading && isVoice && !isTalk"
+        @click="startVoice"
+      >
+        <svg
+          width="22"
+          height="22"
+          viewBox="0 0 22 22"
+          fill="none"
+          xmlns="http://www.w3.org/2000/svg"
+        >
+          <path
+            fill-rule="evenodd"
+            clip-rule="evenodd"
+            d="M11.4583 14.0308C13.8381 14.0308 15.7551 12.0651 15.7551 9.62496V6.23588C15.7551 3.79574 13.8381 1.83008 11.4583 1.83008C9.07845 1.83008 7.16138 3.79574 7.16138 6.23588V9.62496C7.16138 12.0651 9.07845 14.0308 11.4583 14.0308ZM8.4835 6.23588C8.4835 4.54134 9.80561 3.18571 11.4583 3.18571C13.1109 3.18571 14.433 4.54134 14.433 6.23588V9.62496C14.433 11.3195 13.1109 12.6751 11.4583 12.6751C9.80561 12.6751 8.4835 11.3195 8.4835 9.62496V6.23588ZM18.3333 10.6405C18.3333 10.2677 18.0358 9.96264 17.6722 9.96264C17.3417 9.96264 17.0442 10.2338 17.0111 10.5727C16.5484 13.3178 14.2347 15.3852 11.4583 15.3852C8.68181 15.3852 6.36811 13.3178 5.90537 10.5727C5.87231 10.2338 5.57484 9.96264 5.24431 9.96264C4.88073 9.96264 4.58325 10.2677 4.58325 10.6405V10.7421C5.1121 13.9279 7.65717 16.4019 10.7972 16.7069V19.635H7.93254C7.54315 19.635 7.22748 19.9587 7.22748 20.358C7.22748 20.7572 7.54315 21.0809 7.93254 21.0809H14.9832C15.3726 21.0809 15.6883 20.7572 15.6883 20.358C15.6883 19.9587 15.3726 19.635 14.9832 19.635H12.1193V16.7069C15.2593 16.4019 17.8044 13.9279 18.3002 10.776C18.3002 10.7591 18.3085 10.7337 18.3167 10.7082L18.3167 10.7082L18.3167 10.7082C18.325 10.6828 18.3333 10.6574 18.3333 10.6405Z"
+            fill-opacity="0.9"
+          />
+        </svg>
+      </div>
+
+      <div
+        :class="['c_pub_button_StopConfirmVoice']"
+        v-if="!faloading && isVoice && isTalk"
+        @click="stopVoice"
+      >
+        <svg
+          width="22"
+          height="22"
+          viewBox="0 0 22 22"
+          fill="none"
+          xmlns="http://www.w3.org/2000/svg"
+        >
+          <path
+            d="M11 19.25C6.4625 19.25 2.75 15.5375 2.75 11C2.75 6.4625 6.4625 2.75 11 2.75C15.5375 2.75 19.25 6.4625 19.25 11C19.25 15.5375 15.5375 19.25 11 19.25ZM11 17.1875C14.4031 17.1875 17.1875 14.4031 17.1875 11C17.1875 7.59687 14.4031 4.8125 11 4.8125C7.59687 4.8125 4.8125 7.59687 4.8125 11C4.8125 14.4031 7.59687 17.1875 11 17.1875Z"
+            fill="#EE3E3E"
+          />
+          <path
+            d="M12.75 8.25H9.25C8.69772 8.25 8.25 8.69772 8.25 9.25V12.75C8.25 13.3023 8.69772 13.75 9.25 13.75H12.75C13.3023 13.75 13.75 13.3023 13.75 12.75V9.25C13.75 8.69772 13.3023 8.25 12.75 8.25Z"
+            fill="#EE3E3E"
+          />
+        </svg>
+      </div>
+
+      <div v-if="!faloading && isVoice"></div>
+      <div class="c_pub_button_confirm" v-if="faloading" @click="stopSend">
+        终止
+      </div>
+    </div>
+		<iframe
+      allow="camera *; microphone *;display-capture;midi;encrypted-media;"
+      src="https://beta.cloud.cocorobo.cn/browser/public/index.html"
+      ref="iiframe"
+      v-show="false"
+    ></iframe>
+    <!-- <div class="ai_body_input">
+      <textarea
+        style="padding-right: 85px;"
+        rows="3"
+        @keyup.enter="addContent"
+        class="binfo_input binfo_textarea"
+        cols
+        v-model.trim="courseText"
+        placeholder="在此输入您想了解的内容"
+      ></textarea>
+      <div
+        class="c_pub_button_confirm"
+        v-if="!loading && courseText"
+        @click="addContent"
+      >
+        发送
+      </div>
+      <div class="c_pub_button_confirm" @click="promptTit" v-else>发送</div>
+    </div> -->
+  </div>
+</template>
+
+<script>
+import checkImg from "../../../../assets/icon/sourceFile/check.png";
+import checkIsImg from "../../../../assets/icon/sourceFile/check_is.png";
+import { v4 as uuidv4 } from "uuid";
+import MarkdownIt from "markdown-it";
+import TurndownService from "turndown";
+
+const OpenCC = require("opencc-js");
+let converter = OpenCC.Converter({
+  from: "cn",
+  to: "hk"
+});
+
+export default {
+  props: {
+    courseId: {
+      type: String,
+      default: ""
+    },
+    worksArray: {
+      type: Array,
+      default: () => []
+    }
+  },
+  data() {
+    return {
+      array: [],
+      jArray: [],
+      courseText: "",
+      checkImg: checkImg,
+      checkIsImg: checkIsImg,
+      userid: this.$route.query.userid,
+      oid: this.$route.query.oid,
+      org: this.$route.query.org,
+      checkArray: [],
+      course: [{ title: "任务1" }, { title: "任务2" }, { title: "任务3" }],
+      partArray: [
+        { name: "全部内容" },
+        { name: "任务设计" },
+        { name: "评价设计" }
+      ],
+      part: "全部内容",
+      checkBool: false,
+      loading: false,
+      textareaHeight: 50,
+      publicRoleList: [],
+      roleList: [],
+      textList: [
+        {
+          title: "项目式学习",
+          dataList: [
+            "请给我一些学生开展项目式学习可以使用的主题或问题参考,请说出学生将要解决的问题,以及学生要经历怎样的学习活动。",
+            "请将一个关于生态保护项目的项目式学习展开描述,你需要描述学生如何解决这个问题,你需要至少写出四个活动,这些活动需要按照前后逻辑关系排列。",
+            "请对驱动问题为“如何为学校建造一个富有特色的花坛?”的项目式学习进行子问题拆解,至少拆解为5个子问题,并根据子问题对应写出各环节的主要活动。"
+          ]
+        },
+        {
+          title: "教学评价",
+          dataList: [
+            "如果需要给学生的社区服务进行评价,给出评价维度和至少3个等级的表现描述。",
+            "为6年级学生设计一份关于梧桐山研究报告的评估任务表,并给出参考的报告流程,至少包含8个步骤,并包括地图、图片和至少300个词。",
+            "创建一个给5年级学生使用的课堂小测试,包含5道多选题,评价学生对于太阳能这个概念的理解。你需要给出题目和正确答案。"
+          ]
+        },
+        {
+          title: "教学设计",
+          dataList: [
+            "如果需要5年级学生感受“移步换景”的景观写作手法,你有什么合适的阅读材料推荐?你需要给出材料名称,以及材料的哪部分内容。",
+            "设计一个针对8年级学生且关于人类迁徙主题的地理课,并在课程中设计至少1项小组活动。",
+            "设计一个针对5年级学生的课程,课程综合科学和信息技术领域,解决生物与环境领域的生活问题,你需要给出完整的课程框架和活动。",
+            "如果3年级的学生不能理解光合作用的实现过程,需要你帮我设计一个支持他们理解的教学活动,需要包含活动的形式、实施材料和清单。",
+            "请基于贝叶斯定理为8年级学生出三道题目。",
+            "如果需要八年级学生了解尼罗河流域的文化发展史,你有哪些推荐的网站或参考书籍?"
+          ]
+        },
+        {
+          title: "班级管理",
+          dataList: [
+            "创建一组给一年级学生使用的班级口号,要求大家注意卫生、保护环境,口号需要对仗工整,符合一年级学生的理解水平。",
+            " 设计一套用于6年级学生的班级管理规章制度,内容需要包括学习、纪律、卫生、思想品德方面。"
+          ]
+        },
+        {
+          title: "课堂组织",
+          dataList: [
+            "请为“制作垃圾分类宣传单”的小组活动设计小组分工表,每个小组的成员为4-6人。",
+            "请给5年级“校园植物图鉴”社团课程设计一份小组合作公约,需包含小组成员信息、小组项目目标、填写日期,总长度不超过300字,并且提供至少3处学生自行填写的部分。",
+            "请用苏格拉底提问的方式,引导5年级学生拆解驱动问题:如何解决教室黑板反光的问题?其中需包含对于反光原因的分析与实验探究。"
+          ]
+        },
+        {
+          title: "教师发展",
+          dataList: [
+            "教师需要理解项目式学习的理论基础和基础概念,你需要生成一份阅读清单,要求内容为中文书籍或文献。",
+            "设计一个教师进行个人学期总结的框架,需要体现在教学、教研、个人学习方面的进步。"
+          ]
+        },
+        {
+          title: "代码分析",
+          dataList: [
+            "这段代码实现了什么效果?",
+            "请描述这段代码。",
+            "根据这段代码,给我一些修改意见。"
+          ]
+        }
+      ],
+      showTextList: false,
+      showRoleList: false,
+      choseRoleItem: 0,
+      choseTextItem: 0,
+      continuous: true,
+      showjList: false,
+      faloading: false,
+      fasource: null,
+      saveUid: "",
+      isVoice: false,
+      isTalk: false,
+      languageSetting: 0,
+      username: "",
+      options2: {
+        1: "选择题",
+        // 2: "问答题",
+        3: "问答题",
+        4: "添加文档",
+        5: "附件",
+        6: "课程",
+        7: "评分",
+        8: "日期",
+        9: "单选题",
+        10: "多选题",
+        11: "课程",
+      },
+      answerArray: [],
+      fileId: ""
+    };
+  },
+  watch: {
+    courseId: {
+      immediate: true,
+      deep: true,
+      handler(newValue, oldValue) {
+        if (newValue) {
+          this.getChatList().then(_ => {
+            this.$nextTick(() => {
+              console.log(this.$refs.chatDialog.scrollHeight);
+              this.$refs.chatDialog.scrollTop = this.$refs.chatDialog.scrollHeight;
+            });
+          });
+        }
+      }
+    },
+    worksArray :{
+      immediate: true,
+      deep: true,
+      handler(newValue, oldValue) {
+        if (newValue.length) {
+          console.log('newValue',newValue);
+          this.setJson(newValue)
+        }
+      }
+    }
+  },
+  methods: {
+    // setJson(array){
+    //   const getAnswer = (j) => {
+    //     switch (j.type) {
+    //       case 1:
+    //         return j.json.array
+    //           .filter((_, idx) => j.json.answer2.includes(idx))
+    //           .map(item => `${item.img}${item.option}`)
+    //           .join(',');
+    //       case 3:
+    //       case 6:
+    //       case 7:
+    //       case 8:
+    //       case 11:
+    //         return j.json.answer2;
+    //       case 5:
+    //         if (!Array.isArray(j.json.file) || j.json.file.length === 0) {
+    //           return '无附件';
+    //         }
+    //         return j.json.file.map(file => `${file.name}(${file.url})`).join(',');
+    //       default:
+    //         return '';
+    //     }
+    //   };
+
+    //   this.answerArray = array.map(i => ({
+    //     "用户名": i.name,
+    //     "提交时间": i.time,
+    //     "表单内容": i.array.map((j, index) => ({
+    //       "序号": index + 1,
+    //       "题目": j.json.title,
+    //       "题目类型": this.options2[j.type],
+    //       "答案": getAnswer(j)
+    //     }))
+    //   }))
+    //   console.log(this.answerArray);
+
+    //   // 将JSON对象转换为字符串
+    //   const jsonString = JSON.stringify(this.answerArray, null, 2);
+
+    //   // 创建Blob对象
+    //   const blob = new Blob([jsonString], { type: "application/json" });
+    //   blob.lastModifiedDate = new Date();
+    //   blob.name = `表单数据.json`;
+
+    //   // 如果仍需要上传文件,可以保留这行
+    //   return this.uploadFile(blob);
+    // },
+    setJson(array){
+      const getAnswer = (j) => {
+        switch (j.type) {
+          case 1:
+            return j.json.array
+              .filter((_, idx) => j.json.answer2.includes(idx))
+              .map(item => `${item.img}${item.option}`)
+              .join(',');
+          case 3:
+          case 6:
+          case 7:
+          case 8:
+          case 11:
+            return j.json.answer2.replace(/\n/g, ' '); // 去除回车
+          case 5:
+            if (!Array.isArray(j.json.file) || j.json.file.length === 0) {
+              return '无附件';
+            }
+            return j.json.file.map(file => `${file.name}(${file.url})`).join(',');
+          default:
+            return '';
+        }
+      };
+
+      // 获取所有题目类型和题目
+      const questions = array[0].array.map((j, index) => ({
+        "序号": index + 1,
+        "题目类型": this.options2[j.type],
+        "题目": j.json.title
+      }));
+
+      // 构建CSV内容
+      let csvContent = "用户名 | 提交时间 | " + questions.map(q => `${q.序号}-${q.题目类型}-${q.题目}`).join(' | ') + "\n";
+
+      // 添加每个用户的答案
+      array.forEach(i => {
+        let row = [i.name, i.time];
+        i.array.forEach(j => {
+          row.push(getAnswer(j));
+        });
+        csvContent += row.join(' | ') + "\n";
+      });
+
+      // 创建Blob对象
+      const blob = new Blob([csvContent], { type: "text/plain;charset=utf-8" });
+      blob.lastModifiedDate = new Date();
+      blob.name = `表单数据.txt`;
+
+      // 如果仍需要上传文件,可以保留这行
+      return this.uploadFile(blob);
+    },
+    uploadFile(file) {
+      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) {
+        // this.loading = true;
+        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 {
+              console.log(data.Location);
+
+              _this.ajax
+                .put("https://gpt4.cocorobo.cn/upload_file_knowledge", {
+                  url: data.Location
+                })
+                .then(res => {
+                  let _data = res.data.FunctionResponse;
+                  if (_data.result && _data.result.id) {
+                    _this.fileId = _data.result.id;
+                  } else {
+                    console.error("获取fileId失败");
+                  }
+
+                })
+                .catch(e => {
+                  console.log(e);
+                  console.error("获取fileId失败");
+                });
+              // }
+
+              // console.log(data.Location)
+            }
+          });
+      }
+    },
+    getLang() {
+      let lang = "";
+      if (this.languageSetting == 0) {
+        lang = "Chinese.";
+      } else if (this.languageSetting == 1) {
+        lang = "Traditional Chinese.";
+      } else if (this.languageSetting == 2) {
+        lang = "English.";
+      }
+      return lang;
+    },
+    promptTit() {
+      if (!this.loading && !this.courseText) {
+        this.$message({
+          message: "请输入您想要了解的内容",
+          type: "warning"
+        });
+      } else {
+        this.$message({
+          message: "请回答完毕后再次发送",
+          type: "warning"
+        });
+      }
+    },
+    addContent() {
+      if (this.courseText.trim().length == 0)
+        return this.$message.error("请输入内容");
+      let message = this.courseText;
+      if (this.courseText) {
+        let msg = ``;
+        if (this.answerArray.length) {
+          // msg += `
+          // ## 表单资料
+          // ${JSON.stringify(this.answerArray)}
+          // `;
+
+          // msg += `
+          // ## 要求
+          // 根据<参考资料>中的内容实现以下要求:${this.courseText}
+          // `;
+          msg += `## 要求
+根据上传文件中的内容实现以下要求:${this.courseText}`;
+          message = msg;
+        }
+        // 这里处理@的角色
+        let _atRoleList = [];
+        let _roleList = [...this.roleList, ...this.publicRoleList];
+        _roleList.forEach(i => {
+          if (message.indexOf(`@${i.assistantName}`) != -1) {
+            _atRoleList.push(i);
+          }
+        });
+        this.faloading = true;
+        if (_atRoleList.length > 0) {
+          //有@角色
+          let _replaceText = `Role: 你是数据检索大师,可以利用file_search的方式完整的去分析文件内容 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.\n${message}`;
+          let _htmlText = message;
+          _atRoleList.forEach(_i => {
+            _replaceText = _replaceText.replaceAll(`@${_i.assistantName}`, ``);
+            _htmlText = _htmlText.replaceAll(
+              `@${_i.assistantName}`,
+              `<span class='aite-name'>@${_i.assistantName}</span>`
+            );
+          });
+
+          _atRoleList.forEach((_item, _index) => {
+            const _uid = uuidv4();
+            if (_index == 0) {
+              this.array.push({
+                loading: true,
+                role: "user",
+                content: _htmlText,
+                uid: _uid,
+                AI: "AI",
+                aiContent: "",
+                oldContent: "",
+                isShowSynchronization: false,
+                filename: _item.assistantName,
+                index: this.array.length,
+                is_mind_map: false,
+                fileid: _item.headUrl,
+                createtime: new Date().toLocaleString().replaceAll("/", "-")
+              });
+            } else {
+              this.array.push({
+                loading: true,
+                role: "user",
+                content: "",
+                uid: _uid,
+                AI: "AI",
+                aiContent: "",
+                oldContent: "",
+                isShowSynchronization: false,
+                filename: _item.assistantName,
+                index: this.array.length,
+                is_mind_map: false,
+                fileid: _item.headUrl,
+                createtime: new Date().toLocaleString().replaceAll("/", "-")
+              });
+            }
+            this.$nextTick(() => {
+              this.$refs.chatDialog.scrollTop = this.$refs.chatDialog.scrollHeight;
+            });
+
+            let params = {
+              assistant_id: _item.assistant_id,
+              userId: this.userid,
+              message: _replaceText,
+              session_name: `${this.courseId}-${this.userid}-test`,
+              uid: _uid,
+              file_ids: this.fileId ? [this.fileId] : [],
+              // model: "gpt-4o-2024-08-06"
+              model: "qwen-plus"
+            };
+
+            this.ajax
+              .post("https://gpt4.cocorobo.cn/ai_agent_park_chat_new", params)
+              .then(res => {
+                if (
+                  converter(res.data.FunctionResponse.result) ==
+                  converter("发送成功")
+                ) {
+                } else {
+                  this.$message.warning(res.data.FunctionResponse.result);
+                }
+              })
+              .catch(err => {
+                console.log(err);
+              });
+            this.getAtAuContent(_uid);
+            this.saveUid = _uid;
+          });
+          this.courseText = "";
+        } else {
+          let _uuid = uuidv4();
+          this.array.push({
+            role: "user",
+            content: `${this.courseText}`,
+            uid: _uuid,
+            AI: "AI",
+            aiContent: "",
+            oldContent: "",
+            isShowSynchronization: false,
+            filename: "",
+            index: this.array.length,
+            is_mind_map: false,
+            createtime: new Date().toLocaleString().replaceAll("/", "-"),
+            loading: true
+          });
+
+          // let history = [];
+          // if (this.continuous) {
+          //   this.array.forEach((i, index) => {
+          //     if (i.content) history.push({ role: "user", content: index == this.array.length - 1 ? message : i.content });
+          //     if (i.aiContent)
+          //       history.push({ role: "assistant", content: i.aiContent });
+          //   });
+          // } else {
+          //   history.push({ role: "user", content: message });
+          // }
+          // history = history.filter(
+          //   i =>
+          //     i.content !=
+          //     "您好,我是您的助手小可"
+          // );
+
+          // history = history.map(i => ({
+          //   role: i.role,
+          //   content: `${i.content}`
+          // }));
+          // this.$nextTick(() => {
+          //   this.$refs.chatDialog.scrollTop = this.$refs.chatDialog.scrollHeight;
+          // });
+          // let params = JSON.stringify({
+          //   // model: "gpt-3.5-turbo",
+          //   // model: "gpt-4o-2024-08-06",
+          //   model: "qwen-plus",
+          //   temperature: 0,
+          //   max_tokens: 4096,
+          //   top_p: 1,
+          //   frequency_penalty: 0,
+          //   presence_penalty: 0,
+          //   messages: history,
+          //   uid: _uuid,
+          //   mind_map_question: ""
+          // });
+          // this.courseText = "";
+
+          // this.ajax
+          //   .post("https://gpt4.cocorobo.cn/chat", params)
+          //   .then(res => {
+          //     if (
+          //       converter(res.data.FunctionResponse.result) ==
+          //       converter("发送成功")
+          //     ) {
+          //     } else {
+          //       this.$message.warning(res.data.FunctionResponse.result);
+          //     }
+          //   })
+          //   .catch(e => {
+          //     console.log(e);
+          //   });
+          // this.getAiContent(_uuid);\
+          let params = {
+            assistant_id: 'cd72354e-7be5-11ef-a263-12e77c4cb76b',
+            userId: this.userid,
+            message: `Role: 你是数据检索大师,可以利用file_search的方式完整的去分析文件内容 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.\n${message}`,
+            session_name: `${this.courseId}-${this.userid}-test`,
+            uid: _uuid,
+            file_ids: this.fileId ? [this.fileId] : [],
+            model: "gpt-4o-2024-08-06"
+          };
+          this.$nextTick(() => {
+            this.$refs.chatDialog.scrollTop = this.$refs.chatDialog.scrollHeight;
+          });
+          this.courseText = "";
+          this.ajax
+            .post("https://gpt4.cocorobo.cn/ai_agent_park_chat_new", params)
+            .then(res => {
+              if (
+                converter(res.data.FunctionResponse.result) ==
+                converter("发送成功")
+              ) {
+              } else {
+                this.$message.warning(res.data.FunctionResponse.result);
+              }
+            })
+            .catch(err => {
+              console.log(err);
+            });
+          this.getAtAuContent(_uuid);
+          this.saveUid = _uuid;
+        }
+      }
+    },
+    getAiContent(_uid) {
+      this.fasource = new EventSource(
+        `https://gpt4.cocorobo.cn/stream/${_uid}`
+      ); //http://gpt4.cocorobo.cn:8011/stream/     https://gpt4.cocorobo.cn/stream/
+      let _allText = "";
+      let _mdText = "";
+      const md = new MarkdownIt();
+      this.fasource.onmessage = _e => {
+        if (_e.data.replace("'", "").replace("'", "") == "[DONE]") {
+          //对话已经完成
+          _mdText = _mdText.replace("_", "");
+          this.fasource.close();
+          this.fasource = null;
+          this.$nextTick(() => {
+            this.$refs.chatDialog.scrollTop = this.$refs.chatDialog.scrollHeight;
+          });
+          this.array.find(i => i.uid == _uid).aiContent = _mdText;
+          this.array.find(i => i.uid == _uid).isalltext = true;
+          this.array.find(i => i.uid == _uid).isShowSynchronization = true;
+          this.array.find(i => i.uid == _uid).loading = false;
+          // 这里保存对话
+          if (this.courseId) {
+            this.insertChat(_uid);
+          }
+          return;
+        } else {
+          //对话还在继续
+          let _text = "";
+          _text = _e.data.replaceAll("'", "");
+          if (_allText == "") {
+            _allText = _text.replace(/^\n+/, ""); //去掉回复消息中偶尔开头就存在的连续换行符
+          } else {
+            _allText += _text;
+          }
+          _mdText = _allText + "_";
+          _mdText = _mdText.replace(/\\n/g, "\n");
+          _mdText = _mdText.replace(/\\/g, "");
+          if (_allText.split("```").length % 2 == 0) _mdText += "\n```\n";
+          //转化返回的回复流数据
+          _mdText = md.render(_mdText);
+          this.array.find(i => i.uid == _uid).aiContent = _mdText;
+          this.array.find(i => i.uid == _uid).loading = false;
+          this.$nextTick(() => {
+            this.$refs.chatDialog.scrollTop = this.$refs.chatDialog.scrollHeight;
+          });
+          // 处理流数据
+        }
+      };
+    },
+    getAtAuContent(_uid) {
+      this.fasource = new EventSource(
+        `https://gpt4.cocorobo.cn/question/${_uid}`
+      ); //http://gpt4.cocorobo.cn:8011/question/   https://gpt4.cocorobo.cn/question/
+      let _allText = "";
+      let _mdText = "";
+      const md = new MarkdownIt();
+      this.fasource.onmessage = _e => {
+        let _eData = JSON.parse(_e.data);
+        if (_eData.content.replace("'", "").replace("'", "") == "[DONE]") {
+          let _result = [];
+          if ("result" in _eData) {
+            _result = _eData.result;
+            for (let i = 0; i < _result.length; i++) {
+              _mdText = _mdText.replace(_result[i].text, _result[i].fileName);
+            }
+          }
+          _mdText = _mdText.replace("_", "");
+          this.array.find(i => i.uid == _uid).aiContent = _mdText;
+          this.array.find(i => i.uid == _uid).isalltext = true;
+          this.array.find(i => i.uid == _uid).isShowSynchronization = true;
+          this.array.find(i => i.uid == _uid).loading = false;
+          this.$nextTick(() => {
+            this.$refs.chatDialog.scrollTop = this.$refs.chatDialog.scrollHeight;
+          });
+          this.fasource.close();
+          this.fasource = null;
+          if (this.courseId) {
+            this.insertChat(_uid);
+          }
+        } else {
+          let _text = _eData.content.replace("'", "").replace("'", "");
+          if (_allText == "") {
+            _allText = _text.replace(/^\n+/, ""); //去掉回复消息中偶尔开头就存在的连续换行符
+          } else {
+            _allText += _text;
+          }
+          _mdText = _allText + "_";
+          _mdText = _mdText.replace(/\\n/g, "\n");
+          _mdText = _mdText.replace(/\\/g, "");
+          if (_allText.split("```").length % 2 == 0) _mdText += "\n```\n";
+          //转化返回的回复流数据
+          _mdText = md.render(_mdText);
+          this.array.find(i => i.uid == _uid).aiContent = _mdText;
+          this.array.find(i => i.uid == _uid).loading = false;
+          this.$nextTick(() => {
+            this.$refs.chatDialog.scrollTop = this.$refs.chatDialog.scrollHeight;
+          });
+          // 处理流数据
+        }
+      };
+    },
+    async getUserName() {
+      let params = { uid: this.userid };
+      try {
+        let res = await this.ajax.get(this.$store.state.api + "getUser", params);
+        this.username = res.data[0][0].name;
+      } catch (err) {
+        console.error(err);
+      }
+    },
+    //保存消息
+    async insertChat(_uid) {
+      let _data = this.array.find(i => i.uid == _uid);
+      this.saveUid = ''
+      this.faloading = false
+      if(!this.username){
+        await this.getUserName()
+      }
+      if (!_data) return;
+      let params = {
+        userId: this.userid,
+        userName: this.username,
+        groupId: "602def61-005d-11ee-91d8-005056b8q12w",
+        answer: _data.aiContent,
+        problem: _data.content,
+        file_id: _data.fileid ? _data.fileid : "",
+        alltext: _data.aiContent,
+        type: "chat",
+        filename: _data.filename,
+        session_name: `${this.courseId}-${this.userid}-test` //这是对话记录位置
+      };
+      this.ajax
+        .post("https://gpt4.cocorobo.cn/insert_chat", params)
+        .then(res => {});
+    },
+    // 获取对应的聊天记录
+    getChatList() {
+      return new Promise((resolve, reject) => {
+        if (this.loading) return;
+        this.array = [];
+        this.loading = true;
+        let params = {
+          userid: this.userid,
+          groupid: "602def61-005d-11ee-91d8-005056b8q12w",
+          // session_name:``
+          session_name: `${this.courseId}-${this.userid}-test`
+        };
+        this.ajax
+          .post("https://gpt4.cocorobo.cn/get_agent_park_chat", params)
+          .then(res => {
+            let _data = JSON.parse(res.data.FunctionResponse);
+            if (_data.length > 0) {
+              let _chatList = [];
+              for (let i = 0; i < _data.length; i++) {
+                _chatList.push({
+                  loading: false,
+                  role: "user",
+                  content: _data[i].problem,
+                  uid: _data[i].id,
+                  AI: "AI",
+                  aiContent: _data[i].answer,
+                  oldContent: _data[i].answer,
+                  isShowSynchronization: false,
+                  filename: _data[i].filename,
+                  index: i,
+                  is_mind_map: false,
+                  fileid: _data[i].fileid,
+                  createtime: _data[i].createtime
+                });
+              }
+              this.array = _chatList;
+              this.loading = false;
+            } else {
+              let _uid = uuidv4();
+              let _chatList = [];
+              _chatList.push({
+                loading: false,
+                role: "",
+                content: "",
+                uid: _uid,
+                AI: "AI",
+                aiContent:
+                  "您好,我是您的助手小可",
+                oldContent:
+                  "您好,我是您的助手小可",
+                isShowSynchronization: false,
+                filename: "",
+                index: 0,
+                is_mind_map: false,
+                fileid: ""
+              });
+              this.array = _chatList;
+              if (this.courseId) {
+                this.insertChat(_uid);
+              }
+              //没有对话记录
+              this.loading = false;
+            }
+            resolve();
+          })
+          .catch(err => {
+            console.log(err);
+            this.$message.error("获取对话记录失败");
+            this.loading = false;
+            resolve();
+          });
+      });
+    },
+    addTask(index) {
+      if (this.checkArray.indexOf(index) !== -1) {
+        this.checkArray.splice(this.checkArray.indexOf(index), 1);
+      } else {
+        this.checkArray.push(index);
+      }
+      console.log(index);
+    },
+    addAllTask() {
+      if (this.checkArray.length === this.course.length) {
+        this.checkArray = [];
+      } else {
+        this.checkArray = [];
+        this.course.forEach((item, index) => {
+          this.checkArray.push(index);
+        });
+      }
+    },
+    checkPart(name) {
+      this.part = name;
+    },
+    inputChange() {
+      if (this.courseText.at(-1) == "@") {
+        // this.showRoleList = true;
+      }
+      if (this.courseText.at(-1) == "/") {
+        console.log("哇卡ka2");
+      }
+
+      this.$nextTick(() => {
+        this.$refs.textareaRef.style.height = "35px";
+        this.$refs.textareaRef.style.height =
+          this.$refs.textareaRef.scrollHeight + "px";
+        this.textareaHeight = this.$refs.textareaRef.style.height;
+      });
+    },
+    textareaKeydown(_e) {
+      if (this.showRoleList && this.choseRoleList.length > 0) {
+        console.log(_e.keyCode);
+        switch (_e.keyCode) {
+          case 38: //小键盘上
+            _e.preventDefault();
+            if (this.choseRoleItem == 0) return;
+            this.choseRoleItem--;
+            // 修改滚动条高度
+            this.$refs.roleListRef.scrollTo({
+              top:
+                this.$refs[`roleItem${this.choseRoleItem}Ref`][0].offsetTop -
+                10,
+              behavior: "smooth"
+            });
+            // this.$refs.roleListRef.scrollTop = this.choseRoleItem * 107;
+            break;
+          case 40: //小键盘下
+            _e.preventDefault();
+            if (this.choseRoleItem == this.choseRoleList.length - 1) return;
+            this.choseRoleItem++;
+            this.$refs.roleListRef.scrollTo({
+              top:
+                this.$refs[`roleItem${this.choseRoleItem}Ref`][0].offsetTop -
+                10,
+              behavior: "smooth"
+            });
+            // this.$refs.roleListRef.scrollTop = this.choseRoleItem * 107;
+            break;
+          case 13: //回车
+            _e.preventDefault();
+            this.choseRole(this.choseRoleList[this.choseRoleItem]);
+            break;
+        }
+      } else if (_e.key === "Enter") {
+        _e.preventDefault();
+        if (_e.shiftKey) {
+          this.courseText += "\n";
+        } else {
+          this.addContent();
+        }
+      }
+    },
+    clear() {
+      this.$confirm("确定清空聊天记录吗?", "提示", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      })
+        .then(_ => {
+          this.loading = true;
+          let params = {
+            user_id: this.userid,
+            id: "602def61-005d-11ee-91d8-005056b8q12w",
+            session_name: `${this.courseId}-${this.userid}-test`
+          };
+
+          this.ajax
+            .post("https://gpt4.cocorobo.cn/delete_park_session", params)
+            .then(res => {
+              this.array = [];
+              this.$message.success("清除聊天记录成功");
+              this.loading = false;
+            })
+            .catch(err => {
+              this.loading = false;
+              this.$message.error("清除聊天记录失败");
+            });
+        })
+        .catch(_ => {});
+    },
+    getRoleList() {
+      this.roleList = [];
+      let params = {
+        userId: this.userid
+      };
+      this.ajax
+        .post("https://gpt4.cocorobo.cn/get_ai_agent_assistant_list", params)
+        .then(res => {
+          let _data = res.data.FunctionResponse.result;
+          if (_data) {
+            this.roleList = JSON.parse(_data);
+          }
+        })
+        .catch(e => {
+          console.log(e);
+          // this.$message.error("获取角色列表失败");
+          this.roleList = [];
+        });
+    },
+    getPublicRoleList() {
+      this.publicRoleList = [];
+      let params = {
+        userId: this.userid,
+        organizeid: this.oid
+      };
+      this.ajax
+        .post(
+          "https://gpt4.cocorobo.cn/get_ai_agent_assistant_share_list",
+          params
+        )
+        .then(res => {
+          let _data = res.data.FunctionResponse.result;
+          if (_data) {
+            this.publicRoleList = JSON.parse(_data);
+          }
+        })
+        .catch(e => {
+          this.publicRoleList = [];
+          console.log(e);
+          // console.log("获取公共角色失败", e);
+        });
+    },
+    choseRole(item) {
+      let _lastAtIndex = this.courseText.lastIndexOf("@");
+      this.courseText = `${this.courseText.slice(0, _lastAtIndex)}@${
+        item.assistantName
+      } `;
+      this.$refs.textareaRef.focus();
+      this.showRoleList = false;
+    },
+    onCopy(content) {
+      const turndownService = new TurndownService();
+      // 添加自定义规则来处理表格
+      turndownService.addRule("table", {
+        filter: "table",
+        replacement: (content, node) => {
+          const rows = node.querySelectorAll("tr");
+          let markdown = "";
+
+          rows.forEach(row => {
+            const cells = row.querySelectorAll("th, td");
+            const rowMarkdown = Array.from(cells)
+              .map(cell => cell.textContent)
+              .join(" | ");
+            markdown += `| ${rowMarkdown} |\n`;
+            if (cells && cells.length && cells[0].tagName == "TH") {
+              let a = Array.from(cells)
+                .map(cell => "")
+                .join(" --- |");
+              markdown += `| --- |${a}\n`;
+            }
+          });
+
+          // 添加分隔行
+          // markdown = markdown.replace(/^/, '| --- |\n');
+          return markdown;
+        }
+      });
+      // 创建临时textarea元素
+      const tempInput = document.createElement("textarea");
+
+      tempInput.value = turndownService.turndown(content); // 设置要复制的内容
+      // 隐藏元素
+      tempInput.style.position = "absolute";
+      tempInput.style.left = "-9999px";
+      // 将元素添加到DOM中
+      document.body.appendChild(tempInput);
+      // 选中元素内容
+      tempInput.select();
+      // 执行复制操作
+      document.execCommand("copy");
+      // 移除临时元素
+      document.body.removeChild(tempInput);
+      this.$message({
+        message: "复制成功",
+        type: "success"
+      });
+    },
+    stopSend() {
+      if (this.fasource) {
+        this.fasource.close();
+        if (this.array[this.array.length - 1].content == "wanSearch") {
+          this.array.pop();
+        }
+        this.array.find(i => i.uid == this.saveUid).loading = false;
+        this.faloading = false;
+        this.fasource = null;
+        this.insertChat(this.saveUid);
+      }
+    },
+    changeVoice(flag) {
+      this.isVoice = flag;
+    },
+    startVoice() {
+			let iiframe = this.$refs["iiframe"];
+      iiframe.contentWindow.window.document.getElementById(
+        "languageOptions"
+      ).selectedIndex = 2; //普通话
+      iiframe.contentWindow.testdoContinuousPronunciationAssessment();
+      this.isTalk = true;
+      iiframe.contentWindow.onRecognizedResult = e => {
+        let _msg = e.privText;
+        console.log(_msg);
+        if (_msg) this.courseText += _msg;
+      };
+    },
+		stopVoice(){
+			try {
+        if (!this.isTalk) return this.$message.info("请先开始录音");
+        let iiframe = this.$refs["iiframe"];
+        iiframe.contentWindow.window.document
+          .getElementById("scenarioStopButton")
+          .click();
+        iiframe.contentWindow.onSessionStopped = (s, e) => {
+          this.isTalk = false;
+          if (this.courseText) {
+            this.addContent();
+          }
+        };
+      } catch (error) {
+        console.log(error);
+        this.isTalk = false;
+        if (this.courseText) {
+          this.addContent();
+        }
+      }
+		}
+  },
+  computed: {
+    pan() {
+      return content => {
+        try {
+          return JSON.parse(content);
+        } catch (error) {
+          return [];
+        }
+      };
+    },
+    courseTextLength() {
+      return this.courseText.length;
+    },
+    taskName() {
+      let task = "";
+      if (this.checkArray.length) {
+        task = "任务";
+        this.checkArray = this.checkArray.sort((a, b) => a - b);
+        let a = JSON.parse(JSON.stringify(this.checkArray));
+        for (let index = 0; index < a.length; index++) {
+          a[index]++;
+        }
+        task += a.join("/");
+      }
+      return task + " " + this.part;
+    },
+    choseRoleList() {
+      let result = [...this.roleList, ...this.publicRoleList];
+      const _index = this.courseText.lastIndexOf("@");
+      if (_index !== -1) {
+        let roleName = this.courseText.substring(_index + 1);
+        result = result.filter(i => i.assistantName.indexOf(roleName) != -1);
+      } else {
+        return [];
+      }
+      this.choseRoleItem = 0;
+      return result;
+    }
+  },
+  mounted() {
+    // this.getChatList().then(_ => {
+    //   this.$nextTick(() => {
+    //     console.log(this.$refs.chatDialog.scrollHeight);
+    //     this.$refs.chatDialog.scrollTop = this.$refs.chatDialog.scrollHeight;
+    //   });
+    // });
+    if(this.worksArray.length){
+      this.setJson(this.worksArray);
+    }
+    this.getRoleList();
+    this.getPublicRoleList();
+  }
+};
+</script>
+
+<style scoped>
+.ai_body {
+  height: calc(100% - 158px - 46px);
+  width: 500px;
+  margin: 0 auto;
+  display: flex;
+  flex-direction: column;
+  position: fixed;
+  z-index: 999;
+  background: #fff;
+  right: 30px;
+  bottom: 10px;
+  padding: 10px 20px;
+  box-sizing: border-box;
+  box-shadow: 0 0 5px 2px #00000045;
+  border-radius: 5px;
+}
+
+.binfo_input {
+  width: 100%;
+  margin: 0;
+  padding: 12px 14px;
+  display: block;
+  min-width: 0;
+  outline: none;
+  box-sizing: border-box;
+  background: none;
+  border: none;
+  border-radius: 4px;
+  background: #fff;
+  font-size: 14px;
+  resize: none;
+  font-family: "Microsoft YaHei";
+  min-height: 48px;
+  /* border: 1px solid #3682fc00; */
+  border: 1.5px solid #cad1dc;
+}
+
+.binfo_textarea {
+  border: 1.5px solid #cad1dc;
+  font-size: 14px;
+  resize: none;
+  /* background: #f6f6f6; */
+  font-family: "Microsoft YaHei";
+}
+
+.binfo_textarea::-webkit-scrollbar {
+  /*滚动条整体样式*/
+  width: 6px;
+  /*高宽分别对应横竖滚动条的尺寸*/
+  height: 6px;
+}
+
+/*定义滚动条轨道 内阴影+圆角*/
+.binfo_textarea::-webkit-scrollbar-track {
+  border-radius: 10px;
+  background-color: rgba(0, 0, 0, 0.1);
+}
+
+/*定义滑块 内阴影+圆角*/
+.binfo_textarea::-webkit-scrollbar-thumb {
+  border-radius: 10px;
+  -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
+  background-color: rgba(0, 0, 0, 0.1);
+}
+
+.binfo_input:focus-visible {
+  border: 1.5px solid #3681fc !important;
+}
+
+.ai_body_input {
+  position: relative;
+  display: flex;
+
+  margin-top: auto;
+  width: 100%;
+  /* height: auto; */
+  /* max-height: 80vh; */
+  justify-content: space-between;
+  align-items: flex-end;
+  border-radius: 10px;
+  border: 1.5px solid #3681fc !important;
+  /* padding: 10px;
+	padding-top: 20px; */
+  /* overflow: auto; */
+}
+
+.ai_b_i_btnArea {
+  width: calc(100% - 10px);
+  position: absolute;
+  bottom: calc(100% + 5px);
+  height: 30px;
+  display: flex;
+  /* justify-content: space-between; */
+}
+
+.ai_b_i_btnArea > div {
+  display: flex;
+  align-items: center;
+}
+
+.ai_b_i_btnArea > div > span {
+  margin-left: 5px;
+  cursor: pointer;
+}
+
+.ai_b_i_btnArea > .clear + .clear {
+  margin-left: 10px;
+}
+
+.ai_b_i_btnArea > .clear {
+  box-sizing: border-box;
+  padding: 5px 10px;
+  box-sizing: border-box;
+  cursor: pointer;
+  border: solid 1px #3781fb;
+  border-radius: 15px;
+  display: flex;
+  font-size: 15px;
+  align-items: center;
+  justify-content: center;
+  background-color: #fff;
+}
+
+.ai_b_i_btnArea > .clear > svg {
+  width: 17px;
+  height: 17px;
+  margin-right: 5px;
+  fill: black;
+}
+
+.ai_b_i_btnArea > .clear:hover {
+  background-color: #ebf4fe;
+  color: #409eff;
+}
+
+.ai_b_i_btnArea > .clear:hover > svg {
+  fill: #409eff;
+}
+
+.ai_b_i_textListBox {
+  width: 100%;
+  height: 300px;
+  background-color: #fff;
+  position: absolute;
+  bottom: calc(100% + 5px);
+  box-sizing: border-box;
+  padding: 10px;
+  overflow: auto;
+  border-radius: 8px;
+  border: 1px solid #e7e7e7;
+  box-shadow: 0 4px 10px 0 rgba(29, 57, 131, 0.08),
+    1px 1px 20px 4px rgba(29, 57, 131, 0.05);
+  display: flex;
+}
+
+.ai_b_i_tlb_left {
+  width: 60px;
+  height: 100%;
+  background-color: red;
+}
+
+.ai_b_i_tlb_right {
+  flex: 1;
+  height: 100%;
+  background-color: yellow;
+}
+
+.ai_b_i_roleListBox {
+  width: 100%;
+  height: 300px;
+  background-color: #fff;
+  position: absolute;
+  bottom: calc(100% + 5px);
+  box-sizing: border-box;
+  padding: 10px;
+  overflow: auto;
+  border-radius: 8px;
+  border: 1px solid #e7e7e7;
+  box-shadow: 0 4px 10px 0 rgba(29, 57, 131, 0.08),
+    1px 1px 20px 4px rgba(29, 57, 131, 0.05);
+}
+
+.ai_b_i_rlb_item {
+  width: calc(100% - 20px);
+  height: auto;
+  padding: 10px;
+  background-color: #f3f7fd;
+  margin-bottom: 20px;
+  border-radius: 8px;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  transition: 0.3s;
+  cursor: pointer;
+}
+
+.ai_b_i_rlb_itemActive {
+  background-color: #d1d5db !important;
+}
+
+/* .ai_b_i_rlb_item:hover{
+	background-color: #d1d5db;
+} */
+
+.ai_b_i_rlb_itemTop {
+  display: flex;
+}
+
+.ai_b_i_rlb_itemTop > img {
+  width: 40px;
+  height: 40px;
+  margin-right: 10px;
+  border-radius: 100%;
+  overflow: hidden;
+}
+
+.ai_b_i_rlb_itemTop > div {
+  display: flex;
+  flex-direction: column;
+}
+
+.ai_b_i_rlb_itemTop > div > span {
+  font-size: 16px;
+  font-weight: bold;
+}
+
+.ai_b_i_rlb_itemTop > div > span:last-child {
+  font-size: 14px;
+  color: #999;
+}
+
+.ai_b_i_rlb_itemBottom {
+  margin-top: 10px;
+  display: flex;
+}
+
+.ai_b_i_rlb_itemBottom > span {
+  width: 60px;
+  height: 30px;
+  border-radius: 15px;
+  display: flex;
+}
+
+.ai_body_input_textarea {
+  flex: 1;
+  margin: 10px 5px 10px 5px;
+  min-height: 35px;
+  height: 35px;
+  max-height: 100px;
+  border: none;
+  outline: none;
+  resize: none;
+  font-size: 16px;
+  overflow: auto;
+  padding-right: 100px;
+  background-color: #fff !important;
+}
+
+.ai_body_input_textarea::-webkit-input-placeholder {
+  font-size: 16px;
+  /* 修改placeholder字体大小 */
+  color: grey;
+  /* 修改placeholder文字颜色 */
+}
+
+.ai_body_input_textarea::-moz-placeholder {
+  font-size: 16px;
+  /* 修改placeholder字体大小 */
+  color: grey;
+  /* 修改placeholder文字颜色 */
+  opacity: 1;
+  /* 修复Firefox的透明度问题 */
+}
+
+.ai_body_input_textarea::-moz-placeholder {
+  font-size: 16px;
+  /* 修改placeholder字体大小 */
+  color: grey;
+  /* 修改placeholder文字颜色 */
+  opacity: 1;
+  /* 修复Firefox的透明度问题 */
+}
+
+.ai_body_input_textarea::-ms-input-placeholder {
+  font-size: 16px;
+  /* 修改placeholder字体大小 */
+  color: grey;
+  /* 修改placeholder文字颜色 */
+}
+
+.ai_body_input_textarea::-webkit-scrollbar {
+  width: 6px;
+}
+
+.ai_body_input_textarea::-webkit-scrollbar-track {
+  background: #d8d9dc;
+  border-radius: 2px;
+}
+
+.ai_body_input_textarea::-webkit-scrollbar-thumb {
+  background: #c9c9c9;
+  border-radius: 10px;
+}
+
+.ai_body_input_textarea::-webkit-scrollbar-thumb:hover {
+  background: #c9c9c9;
+}
+
+.c_pub_button_confirm {
+  /* position: absolute;
+    bottom: 13px;
+    right: 13px; */
+  /* margin-top: 10px; */
+  width: 60px;
+  margin-right: 5px;
+  display: flex;
+  justify-content: center;
+  margin-bottom: 10px;
+  position: absolute;
+  right: 10px;
+  bottom: 0px;
+  white-space: nowrap;
+  border-radius: 10px;
+}
+.c_pub_button_confirmVoice {
+  width: 30px;
+  height: 36px;
+  min-width: auto;
+  margin-right: 5px;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  margin-bottom: 10px;
+  position: absolute;
+  right: 10px;
+  bottom: 0px;
+  white-space: nowrap;
+  border-radius: 10px;
+  background-color: #3681fc;
+  cursor: pointer;
+  padding: 0 10px;
+}
+.c_pub_button_confirmVoice > svg {
+  fill: #fff;
+  width: 25px;
+  height: 25px;
+}
+
+.c_pub_button_StopConfirmVoice {
+  width: 30px;
+  height: 36px;
+  min-width: auto;
+  margin-right: 5px;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  margin-bottom: 10px;
+  position: absolute;
+  right: 10px;
+  bottom: 0px;
+  white-space: nowrap;
+  border-radius: 10px;
+  background-color: #dde2e2;
+  cursor: pointer;
+  padding: 0 10px;
+}
+
+.c_pub_button_StopConfirmVoice > svg {
+  width: 25px;
+  height: 25px;
+}
+
+.c_voiceBtn {
+  width: 25px;
+  height: 25px;
+  position: absolute;
+  right: 85px;
+  bottom: 0px;
+  margin-bottom: 17px;
+  cursor: pointer;
+}
+
+.c_voiceBtn > svg {
+  width: 100%;
+  height: 100%;
+}
+
+.c_pub_button_confirmDisabled {
+  background-color: #aeccfe;
+}
+
+.ai_body_dialog {
+  padding: 10px 0;
+  box-sizing: border-box;
+  /* min-height: calc(20vh - 10px); */
+  /* height: calc(100%); */
+  overflow: auto;
+  margin-bottom: 10px;
+}
+
+.dialog_content {
+  width: 100%;
+  display: flex;
+  flex-direction: column;
+}
+
+.dialog_content > div {
+  display: flex;
+  align-items: flex-start;
+  width: 100%;
+}
+
+.dialog_content + .dialog_content {
+  margin: 15px 0;
+}
+
+.dialog_content > div .right {
+  flex-direction: row-reverse;
+}
+
+.dialog_content > div .right .role {
+  margin-right: 0;
+  margin-left: 10px;
+}
+
+.dialog_content > div .role {
+  min-width: 30px;
+  width: 30px;
+  height: 30px;
+  margin-right: 10px;
+  border-radius: 50%;
+}
+
+.dialog_content > div .role > img {
+  height: 100%;
+  width: 100%;
+  border-radius: 100%;
+}
+
+.dialog_content > div .content {
+  padding: 10px 10px;
+  border-radius: 2px 8px 8px 8px;
+  width: auto;
+  word-break: break-word;
+  box-sizing: border-box;
+  /* white-space: pre-line; */
+  max-width: calc(100% - 85px);
+  background: #f6f9ff;
+  /* overflow: hidden; */
+  margin: 0 10px;
+  position: relative;
+}
+
+.createTime {
+  width: 100%;
+  height: 20px;
+  position: absolute;
+  bottom: -25px;
+  left: 0;
+  font-size: 14px;
+  white-space: nowrap;
+  color: #919191;
+}
+
+.dialog_content > div .content2 {
+  background: #3681fc;
+  color: #fff;
+  border-radius: 8px 2px 8px 8px;
+  margin-left: auto;
+}
+
+.ai_body_select {
+  position: relative;
+}
+
+.ai_body_select > .check {
+  background: #e7e7e7;
+  display: flex;
+  width: fit-content;
+  padding: 0 10px;
+  height: 30px;
+  border-radius: 21px;
+  font-size: 14px;
+  align-items: center;
+  justify-content: center;
+  color: #0061ff;
+  font-weight: 700;
+  margin: 10px 0;
+  cursor: pointer;
+}
+
+.ai_body_select > .check::before {
+  content: "";
+  width: 15px;
+  height: 15px;
+  display: block;
+  background-image: url("../../../../assets/icon/course/aiPart.png");
+  background-size: 100% 100%;
+  margin-right: 5px;
+}
+
+.ai_body_select > .check::after {
+  content: "";
+  width: 15px;
+  height: 15px;
+  display: block;
+  background-image: url("../../../../assets/icon/course/aiPart_arrow.png");
+  background-size: 100% 100%;
+  margin-right: 5px;
+}
+
+.ai_body_select > .isCheck {
+  background: #0061ff;
+  display: flex;
+  width: fit-content;
+  padding: 0 10px;
+  height: 30px;
+  border-radius: 21px;
+  font-size: 14px;
+  align-items: center;
+  justify-content: center;
+  color: #fff;
+  font-weight: 700;
+  margin: 10px 0;
+  cursor: pointer;
+  max-width: 100%;
+  box-sizing: border-box;
+}
+
+.ai_body_select > .isCheck > span {
+  width: calc(100% - 40px);
+  display: block;
+  overflow: hidden;
+  white-space: nowrap;
+  text-overflow: ellipsis;
+}
+
+.ai_body_select > .isCheck::before {
+  content: "";
+  width: 15px;
+  height: 15px;
+  display: block;
+  background-image: url("../../../../assets/icon/course/aiPart_active.png");
+  background-size: 100% 100%;
+  margin-right: 5px;
+}
+
+.ai_body_select > .isCheck::after {
+  content: "";
+  width: 15px;
+  height: 15px;
+  display: block;
+  background-image: url("../../../../assets/icon/course/aiPart_arrow_active.png");
+  background-size: 100% 100%;
+  margin-right: 5px;
+}
+
+.ai_body_select > .checkBox {
+  position: absolute;
+  bottom: 40px;
+  border: 1px solid #e0eafb;
+  width: 100%;
+  height: 300px;
+  background: #fff;
+  border-radius: 5px;
+  padding: 10px;
+  box-sizing: border-box;
+}
+
+.ai_body_select > .checkBox > .task > .title,
+.ai_body_select > .checkBox > .part > .title {
+  font-size: 14px;
+  font-weight: 700;
+  margin-bottom: 5px;
+}
+
+.ai_body_select > .checkBox > .task {
+  height: calc(100% - 60px);
+}
+
+.ai_body_select > .checkBox > .part {
+}
+
+.ai_body_select > .checkBox > .task > .content {
+  height: calc(100% - 40px);
+  overflow: auto;
+}
+
+.ai_body_select > .checkBox > .task > .content > .span + .span {
+  margin-top: 5px;
+}
+
+.ai_body_select > .checkBox > .task > .content > .span {
+  display: flex;
+  align-items: center;
+  font-size: 14px;
+  cursor: pointer;
+}
+
+.ai_body_select > .checkBox > .task > .content > .span > .check {
+  width: 13px;
+  height: 13px;
+  display: flex;
+  align-items: center;
+  margin-right: 5px;
+}
+
+.ai_body_select > .checkBox > .task > .content > .span > .check > img {
+  width: 100%;
+  height: 100%;
+}
+
+.ai_body_select > .checkBox > .part > .content {
+  display: flex;
+  align-items: center;
+  font-size: 14px;
+  justify-content: space-between;
+}
+
+.ai_body_select > .checkBox > .part > .content > .span {
+  padding: 3px 6px;
+  border: 1px solid #e0eafb;
+  border-radius: 40px;
+  cursor: pointer;
+}
+
+.ai_body_select > .checkBox > .part > .content > .span.active {
+  color: #0061ff;
+  border-color: #0061ff;
+}
+
+.ai_b_i_jListPanel {
+  width: 400px;
+  height: 100%;
+  top: 0;
+  right: 0;
+  position: fixed;
+  /* background-color: #000; */
+  z-index: 990;
+}
+
+.ai_b_i_jListBox {
+  width: 100%;
+  max-height: 300px;
+  background-color: #fff;
+  position: absolute;
+  bottom: calc(100% + 5px);
+  box-sizing: border-box;
+  padding: 10px;
+  overflow: auto;
+  border-radius: 8px;
+  border: 1px solid #e7e7e7;
+  box-shadow: 0 4px 10px 0 rgba(29, 57, 131, 0.08),
+    1px 1px 20px 4px rgba(29, 57, 131, 0.05);
+  z-index: 999;
+}
+
+.jlist_box {
+  background: #f1f1f1;
+  border-radius: 5px;
+  margin-bottom: 10px;
+  padding: 8px 18px 8px 8px;
+  box-sizing: border-box;
+  width: 100%;
+  position: relative;
+}
+
+.jlist_box .cancel {
+  position: absolute;
+  top: 8px;
+  right: 5px;
+  cursor: pointer;
+  font-size: 14px;
+}
+
+.jlist_box > span {
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  -webkit-line-clamp: 3;
+  text-overflow: ellipsis;
+  overflow: hidden;
+  word-break: break-all;
+}
+
+.ai_btn_box {
+  min-width: fit-content;
+  margin-top: auto;
+}
+
+.ai_btn_box > img {
+  cursor: pointer;
+  width: 15px;
+}
+</style>

+ 121 - 98
src/components/pages/test/check/index.vue

@@ -24,116 +24,121 @@
           <div v-if="!peopleId" class="r_pub_button_retrun" @click="retrunCourse">返回</div>
         </div>
         <div class="step_box" ref="stepBox" v-loading="pdfLoading">
-          <div class="test_title" :style="!isDesktop?'justify-content: center;':''">
-            <div class="left">
-              <div class="title">{{ testJson.title }}</div>
-              <div class="info"  v-if="isDesktop">
-                <div class="info_box" v-if="testJson.typeN">
-                  <span>类型:</span>
-                  <span>{{ testJson.typeN }}</span>
-                </div>
-                <div class="info_box">
-                  <span>填写范围:</span>
-                  <span>{{ testJson.juriP ? testJson.juriP : '所有人' }}</span>
-                </div>
-                <div class="info_box" v-if="testJson.overtime">
-                  <span>截止时间:</span>
-                  <span>{{ testJson.overtime }}</span>
+          <div class="boxSticky" ref="boxSticky">
+            <div class="test_title" :style="!isDesktop?'justify-content: center;':''">
+              <div class="left">
+                <div class="title">{{ testJson.title }}</div>
+                <div class="info"  v-if="isDesktop">
+                  <div class="info_box" v-if="testJson.typeN">
+                    <span>类型:</span>
+                    <span>{{ testJson.typeN }}</span>
+                  </div>
+                  <div class="info_box">
+                    <span>填写范围:</span>
+                    <span>{{ testJson.juriP ? testJson.juriP : '所有人' }}</span>
+                  </div>
+                  <div class="info_box" v-if="testJson.overtime">
+                    <span>截止时间:</span>
+                    <span>{{ testJson.overtime }}</span>
+                  </div>
                 </div>
               </div>
-            </div>
-            <div v-if="!peopleId">
-              <div class="right" v-if="isDesktop">
-                <div class="data_box">
-                  <span>提交数量</span>
-                  <span><span class="big">{{ works.length }}</span>份</span>
-                </div>
-                <div class="data_box" v-if="testJson.juriP">
-                  <span>表单完成率</span>
-                  <span><span class="big">{{ (iscount / pcount * 100).toFixed(0) }}</span>%</span>
-                </div>
-                <div class="data_box" v-if="testJson.juriP">
-                  <span>未完成人数</span>
-                  <span><span class="big">{{ pcount - iscount }}</span></span>
-                </div>
-                <div class="btn_box" @click="dialogVisibleShare = true">
-                  <span></span>
-                  <span>提醒</span>
+              <div v-if="!peopleId">
+                <div class="right" v-if="isDesktop">
+                  <div class="data_box">
+                    <span>提交数量</span>
+                    <!-- <span><span class="big">{{ works.length }}</span>份</span> -->
+                    <span><span class="big">{{ worksArray.length }}</span>份</span>
+                  </div>
+                  <div class="data_box" v-if="testJson.juriP">
+                    <span>表单完成率</span>
+                    <span><span class="big">{{ (iscount / pcount * 100).toFixed(0) }}</span>%</span>
+                  </div>
+                  <div class="data_box" v-if="testJson.juriP">
+                    <span>未完成人数</span>
+                    <span><span class="big">{{ pcount - iscount }}</span></span>
+                  </div>
+                  <div class="btn_box" @click="dialogVisibleShare = true">
+                    <span></span>
+                    <span>提醒</span>
+                  </div>
                 </div>
               </div>
             </div>
-          </div>
 
-          <!-- pc端 查看切换 isDesktop-->
-          <div class="search_nav" v-if="isDesktop">
-            <div class="right">
-              <span :class="{ active: stype == 1 }" @click="checkDataType(1)" v-if="!peopleId">按题目查看</span>
-              <span :class="{ active: stype == 2 }" @click="checkDataType(2)">按人员查看</span>
-              <span :class="{ active: stype == 3 }" @click="checkDataType(3)" v-show="false">按数量查看</span>
-            </div>
-            <div class="left"  v-if="isDesktop">
-              <!-- <div style="margin-right: 10px;position: relative;" v-if="stype == 2 || stype == 3"> -->
-              <!-- <div style="margin-right: 10px;position: relative;">
-                <el-select v-model="TeachingValue" @change="searchCourse" placeholder="按教研室">
-                  <el-option
-                    label="全部"
-                    value="">
-                  </el-option>
-                  <el-option
-                    v-for="item in TeachingOptions"
-                    :key="item.id"
-                    :label="item.name"
-                    :value="item.id">
-                  </el-option>
-                </el-select>
-              </div> -->
-              <div style="margin-right: 10px;position: relative;" v-if="stype == 2 || stype == 3">
-                <el-date-picker
-                  v-model="SubmitTime"
-                  type="date"
-                   @change="searchCourse"
-                  value-format="yyyy-MM-dd"
-                  placeholder="选择日期">
-                </el-date-picker>
-                <!-- <el-select v-model="SubmitTime" placeholder="按提交时间筛选">
-                  <el-option
-                    label="全部"
-                    value="">
-                  </el-option>
-                  <el-option
-                    v-for="item in SubmitTimeOptions"
-                    :key="item.id"
-                    :label="item.name"
-                    :value="item.id">
-                  </el-option>
-                </el-select> -->
-              </div>
-              <div style="margin-right: 10px;position: relative;" v-if="stype == 2 || stype == 3">
-                <el-input v-model="courseName" class="student_input" :disabled="!(!peopleId)" placeholder="请输入需要搜索的姓名"></el-input>
-                <span class="serach_icon" @click="searchCourse" ></span>
+            <!-- pc端 查看切换 isDesktop-->
+            <div class="search_nav" v-if="isDesktop">
+              <div class="right">
+                <span :class="{ active: stype == 1 }" @click="checkDataType(1)" v-if="!peopleId">按题目查看</span>
+                <span :class="{ active: stype == 2 }" @click="checkDataType(2)">按人员查看</span>
+                <span :class="{ active: stype == 3 }" @click="checkDataType(3)" v-show="false">按数量查看</span>
               </div>
-              
-              <div class="btnA" v-if="stype == 1 && !pdfLoading" @click="exportPDF">导出PDF</div>
-              <div class="btnA" v-if="stype == 2" @mouseenter="btnDisplay = true" @mouseleave="btnDisplay = false">
-                导出数据
-                <div v-show="btnDisplay" class="buttonBox">
-                  <div type="primary" @click="exportExcel">下载汇总表格</div>
-                  <div type="primary" @click="exportAllWord">导出人员数据</div>
+              <div class="left"  v-if="isDesktop">
+                <!-- <div style="margin-right: 10px;position: relative;" v-if="stype == 2 || stype == 3"> -->
+                <!-- <div style="margin-right: 10px;position: relative;">
+                  <el-select v-model="TeachingValue" @change="searchCourse" placeholder="按教研室">
+                    <el-option
+                      label="全部"
+                      value="">
+                    </el-option>
+                    <el-option
+                      v-for="item in TeachingOptions"
+                      :key="item.id"
+                      :label="item.name"
+                      :value="item.id">
+                    </el-option>
+                  </el-select>
+                </div> -->
+                <div style="margin-right: 10px;position: relative;" v-if="stype == 2 || stype == 3">
+                  <el-date-picker
+                    v-model="SubmitTime"
+                    type="date"
+                    @change="searchCourse"
+                    value-format="yyyy-MM-dd"
+                    placeholder="选择日期">
+                  </el-date-picker>
+                  <!-- <el-select v-model="SubmitTime" placeholder="按提交时间筛选">
+                    <el-option
+                      label="全部"
+                      value="">
+                    </el-option>
+                    <el-option
+                      v-for="item in SubmitTimeOptions"
+                      :key="item.id"
+                      :label="item.name"
+                      :value="item.id">
+                    </el-option>
+                  </el-select> -->
+                </div>
+                <div style="margin-right: 10px;position: relative;" v-if="stype == 2 || stype == 3">
+                  <el-input v-model="courseName" class="student_input" :disabled="!(!peopleId)" placeholder="请输入需要搜索的姓名"></el-input>
+                  <span class="serach_icon" @click="searchCourse" ></span>
+                </div>
+                
+                <div class="btnA" v-if="stype == 1 && !pdfLoading" @click="exportPDF">导出PDF</div>
+                <div class="btnA" v-if="stype == 2" @mouseenter="btnDisplay = true" @mouseleave="btnDisplay = false">
+                  导出数据
+                  <div v-show="btnDisplay" class="buttonBox">
+                    <div type="primary" @click="exportExcel">下载汇总表格</div>
+                    <div type="primary" @click="exportAllWord">导出人员数据</div>
+                  </div>
                 </div>
+                <div class="btnA" v-if="stype == 3" @click="exportAllWord2">导出人员数据</div>
+                <div class="btnA" @click="openChat" v-if="this.worksArray.length">AI分析</div><!--v-if="this.worksArray.length"-->
               </div>
-              <div class="btnA" v-if="stype == 3" @click="exportAllWord2">导出人员数据</div>
+            </div>
+             <!-- 手机端 查看切换 isDesktop-->
+            <div class="search_nav" style="display:flex;justify-content:space-evenly;border: none;" v-if="!isDesktop">
+              <div class="right">
+                <span :class="{ active2: stype == 1 }" @click="checkDataType(1)">按题目查看</span>
+                <span :class="{ active2: stype == 2 }" @click="checkDataType(2)">按人员查看</span>
+                <span :class="{ active2: stype == 3 }" @click="checkDataType(3)" v-show="false">按数量查看</span>
             </div>
           </div>
 
-          <!-- 手机端 查看切换 isDesktop-->
-          <div class="search_nav" style="display:flex;justify-content:space-evenly;border: none;" v-if="!isDesktop">
-            <div class="right">
-              <span :class="{ active2: stype == 1 }" @click="checkDataType(1)">按题目查看</span>
-              <span :class="{ active2: stype == 2 }" @click="checkDataType(2)">按人员查看</span>
-              <span :class="{ active2: stype == 3 }" @click="checkDataType(3)" v-show="false">按数量查看</span>
-            </div>
           </div>
 
+
           <!-- 按题目pc端 isDesktop-->
           <div class="title_content" v-if="stype == 1 && isDesktop" v-loading="isLoading">
             <div class="title_box" v-if="!testArray.length"
@@ -563,7 +568,7 @@
               </div>
             </div>
           </div>
-
+          <aiBoxRight v-show="aiChatV && isDesktop" :courseId="cid" :worksArray="worksArray" ref="aiChat"></aiBoxRight>
           <!-- 按人员pc端 isDesktop-->
           <div class="table_content" v-if="stype == 2 && isDesktop">
             <el-table class="el-table" ref="table" :data="worksArray" border :fit="true" :key="2" v-loading="isLoading"
@@ -909,6 +914,7 @@ import JSZip from "jszip";
 import FileSaver from "file-saver";
 
 import XLSX from "xlsx-js-style";
+import aiBoxRight from './aiBoxRight.vue'
 
 const getFile = (url) => {
     return new Promise((resolve, reject) => {
@@ -956,7 +962,8 @@ export default {
     wpdf,
     wOffice,
     checkPie,
-    wordcloud
+    wordcloud,
+    aiBoxRight
   },
   data() {
     return {
@@ -969,6 +976,7 @@ export default {
       peopleId: this.$route.query.peopleId,
       screenWidth: window.innerWidth,
       isDesktop: false,
+      aiChatV: false,
       title: "",
       testType: [],
       see: false,
@@ -1105,6 +1113,14 @@ export default {
 		}
   },
   methods: {
+    openChat(){
+      this.aiChatV = !this.aiChatV;
+      this.$nextTick(() => {
+        const boxStickyHeight = this.$refs.boxSticky ? this.$refs.boxSticky.offsetHeight : 0;
+        const chatHeight = `calc(100% - ${boxStickyHeight}px - 46px)`;
+        this.$refs.aiChat.$el.style.height = chatHeight;
+      });
+    },
     fileClose(done){
       done()
     },
@@ -3230,4 +3246,11 @@ export default {
   width: 40px;
   margin-right: 20px;
 }
+
+.boxSticky{
+  position: sticky;
+  top: 0;
+  z-index: 999;
+  background: #fff;
+}
 </style>

+ 9 - 0
src/components/pages/test/dataCom/cascader.vue

@@ -143,14 +143,23 @@ export default {
             this.show = false
         },
         selectOption(option) {
+            if(this.checkF == option.id){
+                return
+            }
             console.log(option);
             this.checkF2 = '';
             this.teacher = []
             this.checkF = option.id;
             this.children = option.child;
+            if(option.child.length){
+                this.selectOption2(option.child[0])
+            }
             this.$forceUpdate();
         },
         selectOption2(option) {
+            if(this.checkF2 == option.id){
+                return
+            }
             this.checkF2 = option.id;
             this.teacher = []
             this.loading = true

+ 28 - 8
src/components/pages/test/dataCom/radarZong.vue

@@ -117,6 +117,8 @@ export default {
                         value: option.xdata2,
                         name: '平均得分率'
                     }
+                }else {
+                    this.option.series[0].data[1] = []
                 }
                 // this.option.series[0].data[0].value = option.xdata;
                 // 初始化雷达图
@@ -125,21 +127,36 @@ export default {
             });
         },
         setArray(array, type) {
-            this.ooption.sdata = Object.keys(array).map((item) => {
-                return {
-                    name: item,
-                    max: 100,
-                };
-            });
+            if(Object.keys(array).length){
+                this.ooption.sdata =  Object.keys(array).map((item) => {
+                    return {
+                        name: item,
+                        max: 100,
+                    };
+                });
+            }else if(this.evCourseArray.length){
+                this.ooption.sdata =  Object.keys(this.evCourseArray).map((item) => {
+                    return {
+                        name: item,
+                        max: 100,
+                    };
+                });
+                this.ooption.xdata = Object.keys(this.evCourseArray).map((item) => {
+                    return array[item].cogScore;
+                })
+                this.ooption.xdata2 = []
+            }
             if (type == 1) {
                 this.ooption.xdata = Object.keys(array).map((item) => {
                     return array[item].cogScore;
                 })
+                this.ooption.xdata2 = []
             }
             if (type == 2) {
-                this.ooption.xdata2 = Object.keys(array).map((item) => {
+
+                this.ooption.xdata2 = Object.keys(array).length ? Object.keys(array).map((item) => {
                     return array[item].cogScore;
-                })
+                }) : []
             }
             if (!this.chartObj) {
                 this.setChart(this.ooption);
@@ -176,6 +193,8 @@ export default {
                         value: this.ooption.xdata2,
                         name: '平均得分率'
                     }
+                }else {
+                    this.option.series[0].data[1] = []
                 }
                 this.chartObj.setOption(this.option);
             }
@@ -203,6 +222,7 @@ export default {
             handler(newValue, oldValue) {
                 this.ooption.xdata2 = [];
                 this.setArray(newValue, 2)
+                console.log(2222222222222222);
                 this.$forceUpdate();
             },
         },

+ 15 - 6
src/components/pages/test/databoard.vue

@@ -53,15 +53,15 @@
 
             </div>
             <div class="randarZong">
-                <radarZong :evCourseArray="zongJson" :evCourseArray2="zongJson2" v-if="teacherArray.length"></radarZong>
-                <radarZong :evCourseArray="zongJson" v-else></radarZong>
+                <radarZong :evCourseArray="zongJson" :evCourseArray2="zongJson2" v-if="checkSet"></radarZong>
+                <radarZong :evCourseArray="zongJson2" :evCourseArray2="{}" v-else></radarZong>
             </div>
             <div class="randarBox">
                 <div class="title">教师详情</div>
                 <div class="randarTeacher" v-for="(item, index) in tableData" :key="index">
                     <div class="randarTitle">
                         <span>{{ item.username }}</span>
-                        <span>{{ getScore(item.json) }}</span>
+                        <span>{{ item.score }}</span>
                     </div>
                     <div class="randar">
                         <radarTeacher :evCourseArray="zongJson2" :evCourseArray2="item.json"></radarTeacher>
@@ -221,7 +221,12 @@ export default {
                         zongJson[zongArray[i]].cogScore = (zongJson[zongArray[i]].cogScore / data.length).toFixed(0)
                     }
 
-                    this.tableData = _data;
+                    this.tableData = _data
+                        .filter((e) => {
+                            e.score = this.getScore(e.json);
+                            return e;
+                        })
+                        .sort((a, b) => b.score - a.score);
                     console.log(this.tableData);
                     this.zongJson = zongJson
                     this.zongJson2 = zongJson
@@ -247,7 +252,7 @@ export default {
             let checkSet = this.checkSet
             let _data = []
             let zongJson = {}
-            let data = this.data
+            let data = JSON.parse(JSON.stringify(this.data))
             if(this.teacherArray.length){
                 data = data.filter((e) => {
                     return this.teacherArray.includes(e.userid)
@@ -301,7 +306,11 @@ export default {
                 zongJson[zongArray[i]].evaScore = (zongJson[zongArray[i]].evaScore / data.length).toFixed(0)
                 zongJson[zongArray[i]].cogScore = (zongJson[zongArray[i]].cogScore / data.length).toFixed(0)
             }
-            this.tableData = _data;
+            this.tableData = _data.filter((e) => {
+                e.score = this.getScore(e.json);
+                return e;
+            })
+            .sort((a, b) => b.score - a.score);
             console.log(this.tableData);
             this.zongJson2 = zongJson
             console.log(zongJson);

+ 96 - 10
src/components/pages/testPerson/info/index.vue

@@ -14,12 +14,24 @@
       </div>
     </div>
     <div class="i_bottom">
-        <div class="i_bottom_span">
+        <!-- <div class="i_bottom_span">
           <span>教研室</span>
           <el-tooltip :content="info.classname ? info.classname : '暂无'" placement="top" effect="dark">
             <span>{{info.classname ? info.classname : '暂无'}}</span>
           </el-tooltip>
+        </div> -->
+        <div class="i_bottom_box">
+          <div class="i_bottom_span" v-for="item in teaType" :key="item.id">
+            <span>
+              {{ item.name + ":" }}
+            </span>
+            <!-- <span>{{ item.value }}</span> -->
+            <el-tooltip :content="getTypeC(item.child, item.value)" placement="top" effect="dark">
+              <span>{{ getTypeC(item.child, item.value) }}</span>
+            </el-tooltip>
+          </div>
         </div>
+
         <!-- <div v-if="!oidArray.includes(oid)" class="i_bottom_span">
           <span>学科</span>
           <el-tooltip :content="info.subject ? info.subject : '暂无'" placement="top" effect="dark">
@@ -61,7 +73,8 @@ export default {
       avator: avator,
       info: {},
       dialogVisibleInfo:false,
-      oidArray: ["d67940a5-510c-40ea-9c9a-2631ab03013a"]
+      oidArray: ["d67940a5-510c-40ea-9c9a-2631ab03013a"],
+      teaType: []
     }
   },
   watch: {
@@ -69,7 +82,65 @@ export default {
       this.getData()
     }
   },
+  computed: {
+    getTypeC() {
+      return function(array, value) {
+        let name = []
+        for(var i = 0; i < array.length; i++) {
+          if(value.indexOf(array[i].id) != -1) {
+            name.push(array[i].name)
+          }
+        }
+        return name.length ? name.join("/") : '暂无'
+      }
+    }
+  },
   methods: {
+    arrayToArray(arrayo, arrayt) {
+      let array1 = arrayo;
+      let array2 = arrayt;
+
+      let commonElements = [];
+
+      for (let i = 0; i < array1.length; i++) {
+        for (let j = 0; j < array2.length; j++) {
+          if (array1[i] === array2[j]) {
+            commonElements.push(array1[i]);
+          }
+        }
+      }
+      return commonElements;
+    },
+    //获取分类类名
+    getTypeInfo() {
+      let params = {
+        oid: this.oid
+      };
+      this.ajax
+        .get(this.$store.state.api + "selectPerInfoAllTea", params)
+        .then((res) => {
+          
+          this.teaType = res.data[0];
+          let typeInfo = res.data[1];
+
+          this.teaType.forEach((e) => {
+            e.child = [];
+            e.value = [];
+            typeInfo.forEach((i) => {
+              if (e.id == i.parentid) {
+                e.child.push({ id: i.id, name: i.name })
+              }
+            })
+          })
+          console.log(this.teaType, "teaType");
+          // this.options = res.data[2];
+          this.getData();
+
+        })
+        .catch((err) => {
+          console.error(err);
+        });
+    },
     getData() {
       let params = {
         uid: this.userid,
@@ -79,7 +150,13 @@ export default {
         .then((res) => {
           this.info = res.data[0][0]
           // 用于存储归类后的数据的对象
-  
+          this.teaType.forEach(e =>{
+            let array2 = []
+            for (var i = 0; i < e.child.length; i++) {
+              array2.push(e.child[i].id)
+            }
+            e.value = this.arrayToArray(this.info.cclassid.split(','), array2)
+          })
         })
         .catch((err) => {
           console.error(err);
@@ -90,7 +167,7 @@ export default {
     }
   },
   mounted () {
-    this.getData();
+    this.getTypeInfo();
   },
 }
 </script>
@@ -103,7 +180,8 @@ export default {
         background: #fff;
         border-radius: 5px;
         margin-bottom: 10px;
-        overflow: hidden;
+        /* overflow: hidden; */
+        overflow: auto;
     }
     .i_top{
       height: 55%;
@@ -172,7 +250,7 @@ export default {
       justify-content: center;
     }
 
-    .i_bottom > .i_bottom_span{
+    .i_bottom .i_bottom_span{
       width: 90%;
       margin: 0 auto;
       display: flex;
@@ -180,17 +258,17 @@ export default {
       font-size: 15px;
     }
 
-    .i_bottom > .i_bottom_span + .i_bottom_span{
+    .i_bottom .i_bottom_span + .i_bottom_span{
       margin-top: 10px;
     }
 
-    .i_bottom > .i_bottom_span > span:nth-child(1){
+    .i_bottom .i_bottom_span > span:nth-child(1){
       width: 50px;
       min-width: 50px;
       text-align: right;
       color: #a1a1a1;
     }
-    .i_bottom > .i_bottom_span > span:nth-child(2){
+    .i_bottom .i_bottom_span > span:nth-child(2){
       width: calc(100% - 30px);
       overflow: hidden;
       margin-left: 20px;
@@ -198,6 +276,14 @@ export default {
       text-overflow: ellipsis;
     }
 
+    .i_bottom_box {
+      width: 100%;
+      height: calc(100% - 55px);
+      overflow: auto;
+      padding-top: 10px;
+      box-sizing: border-box;
+    }
+
     .i_bottom > .i_bottom_btn{
       cursor: pointer;
       border-radius: 5px;
@@ -205,7 +291,7 @@ export default {
       box-sizing: border-box;
       width: 90%;
       background: rgb(252, 252, 252);
-      margin: 30px auto 0;
+      margin: auto auto 10px;
       height: 35px;
       font-weight: 600;
       font-size: 12px;

+ 2 - 2
src/components/pages/testPerson/info/infoDialog/index.vue

@@ -14,12 +14,12 @@
           <span>姓名:</span>
           <el-input v-model="info.username" placeholder="请输入姓名"></el-input>
         </div>
-        <div class="info_span">
+        <!-- <div class="info_span">
           <span>教研室:</span>
           <el-select v-model="info.teacherOffice"  multiple placeholder="类型" @change="Ochange">
             <el-option v-for="item in options" :key="item.id" :label="item.name" :value="item.id"></el-option>
           </el-select>
-        </div>
+        </div> -->
 
         <div class="info_span" v-for="item in teaType" :key="item.id">
           <span>

+ 102 - 0
src/lib/shengyang.js

@@ -0,0 +1,102 @@
+import { v4 as uuid4 } from 'uuid'
+import _ from 'lodash'
+
+export async function registerVoiceprint({ name, userId, organizeId, file }) {
+  // 检查文件的 MIME 类型是否为 WAV 文件
+  if (file.type !== 'audio/wav') {
+    throw new Error('文件类型必须是 WAV');
+  }
+  const formData = new FormData();
+  formData.append('file', file, file.name);
+  const registerRes = await fetch('https://conference.voiceaitech.com/api/convoice/feature/extract', { // 替换为实际的上传URL
+    method: 'POST',
+    body: formData,
+  }).then(response => response.json())
+  if (!_.get(registerRes, ['result', 'featureData'])) {
+    throw new Error("声音文件不达标,请注意文件时长大于1分钟和音量");
+  }
+  const featureData = registerRes.result.featureData
+
+  const insertRes = await fetch('https://gpt4.cocorobo.cn/insert_sy_voiceprint', {
+    method: 'POST',
+    body: JSON.stringify({
+      name,
+      featureData,
+      user_id: userId,
+      organize_id: organizeId,
+    }),
+    headers: {
+      'Content-Type': 'application/json',
+      hwMac: organizeId
+    }
+  }).then(res => res.json())
+  if (!_.get(insertRes, ['FunctionResponse'])) {
+    throw new Error('上传失败')
+  }
+  return featureData
+}
+
+export async function getVoiceprints({ userId, organizeId }) {
+  const res = await fetch('https://gpt4.cocorobo.cn/get_sy_voiceprint', {
+    method: 'POST',
+    body: JSON.stringify({
+      user_id: userId,
+    }),
+    headers: {
+      'Content-Type': 'application/json',
+      hwMac: organizeId
+    }
+  }).then(res => res.json())
+  if (!_.get(res, ['FunctionResponse'])) {
+    throw new Error('获取声纹列表失败')
+  }
+  return JSON.parse(res.FunctionResponse)
+}
+
+export async function createWs({ enroll_infos }) {
+  // 创建一个WebSocket实例
+
+  const ws = new WebSocket(
+    'wss://conference.voiceaitech.com/api/convoice/streaming'
+  );
+  let _resolve
+  // const p = new Promise(resolve => _resolve = resolve)
+  ws.onopen = () => {
+    const data = {
+      eventName: 'start',
+      sessionId: uuid4(),
+      timestamp: new Date().getTime(),
+      convoiceInfo: {
+        options: 2,
+        hotwords: [
+          {
+            weight: 5,
+            hotword: '上课',
+          },
+          // TODO
+        ],
+        enroll_infos,
+      },
+    };
+    ws.send(JSON.stringify(data));
+    // setTimeout(() => {
+    //   _resolve()
+    // }, 3000)
+  };
+  // await p
+  // 处理来自服务器的消息
+  // ws.onmessage = (e) => {
+  //   const res = JSON.parse(e.data);
+  //   if (res?.userdata?.results?.length > 0) {
+  //     this.role_arr = res.userdata.results;
+  //   }
+  //   if (res?.userdata?.last_block) {
+  //     ws.close();
+  //   }
+  // };
+  // 处理错误
+  // ws.onerror = (error) => {
+  //   console.error('WebSocket error:', error);
+  // };
+  return ws
+}

Nem az összes módosított fájl került megjelenítésre, mert túl sok fájl változott