Przeglądaj źródła

Merge branch 'beta'

SanHQin 13 godzin temu
rodzic
commit
a2f7e84b08
27 zmienionych plików z 2153 dodań i 50 usunięć
  1. 1 1
      dist/index.html
  2. 0 0
      dist/static/css/app.37450c9ddf5adf85e401ccee50f7eb4c.css
  3. 0 0
      dist/static/css/app.37450c9ddf5adf85e401ccee50f7eb4c.css.map
  4. 0 0
      dist/static/css/workPage.400638381461a510a5a06437d97a7ff4.css
  5. 0 0
      dist/static/css/workPage.400638381461a510a5a06437d97a7ff4.css.map
  6. 0 0
      dist/static/css/workPage.eb039ffafd131baf9f7d229e1701a61b.css.map
  7. 0 0
      dist/static/js/app.967f4c22e2c119d0e9b1.js
  8. 0 0
      dist/static/js/app.967f4c22e2c119d0e9b1.js.map
  9. 0 0
      dist/static/js/manifest.23ea04dc469b57e2b4f8.js.map
  10. 0 0
      dist/static/js/workPage-manifest.2ece51fa34be51c8610a.js.map
  11. 0 0
      dist/static/js/workPage.8eddbd275bd20a8ea258.js
  12. 0 0
      dist/static/js/workPage.8eddbd275bd20a8ea258.js.map
  13. 0 0
      dist/static/js/workPage.a7ca1e8681db98a7e40f.js
  14. 0 0
      dist/static/js/workPage.a7ca1e8681db98a7e40f.js.map
  15. 1 1
      dist/workPage.html
  16. 58 2
      src/components/pages/pptEasy/addCourse3.vue
  17. 45 38
      src/components/pages/studentManageNew/component/addstu.vue
  18. 9 0
      src/components/pages/studentManageNew/component/allClaAddStu.vue
  19. 513 0
      src/components/pages/workPage/components/file.vue
  20. 926 0
      src/components/pages/workPage/components/kwlPage.vue
  21. 1 1
      src/components/pages/workPage/components/photo.vue
  22. 449 0
      src/components/pages/workPage/components/setFile.vue
  23. 43 3
      src/components/pages/workPage/index_new.vue
  24. 11 1
      src/components/pages/workPage/setIndex.vue
  25. 32 1
      src/lang/cn.json
  26. 32 1
      src/lang/en.json
  27. 32 1
      src/lang/hk.json

+ 1 - 1
dist/index.html

@@ -37,7 +37,7 @@
       width: 100%;
       background: #e6eaf0;
       font-family: '黑体';
-    }</style><link href=./static/css/app.83d43d9a36ccef648272bce0c7e8f587.css rel=stylesheet></head><body><div id=app></div><script type=text/javascript src=./static/js/manifest.23ea04dc469b57e2b4f8.js></script><script type=text/javascript src=./static/js/vendor.714ff2bfc93f2f096b79.js></script><script type=text/javascript src=./static/js/app.ea59a979d8fefb9b183d.js></script></body></html><script>function stopSafari() {
+    }</style><link href=./static/css/app.37450c9ddf5adf85e401ccee50f7eb4c.css rel=stylesheet></head><body><div id=app></div><script type=text/javascript src=./static/js/manifest.23ea04dc469b57e2b4f8.js></script><script type=text/javascript src=./static/js/vendor.714ff2bfc93f2f096b79.js></script><script type=text/javascript src=./static/js/app.967f4c22e2c119d0e9b1.js></script></body></html><script>function stopSafari() {
     //阻止safari浏览器双击放大功能
     let lastTouchEnd = 0  //更新手指弹起的时间
     document.documentElement.addEventListener("touchstart", function (event) {

Plik diff jest za duży
+ 0 - 0
dist/static/css/app.37450c9ddf5adf85e401ccee50f7eb4c.css


Plik diff jest za duży
+ 0 - 0
dist/static/css/app.37450c9ddf5adf85e401ccee50f7eb4c.css.map


Plik diff jest za duży
+ 0 - 0
dist/static/css/workPage.400638381461a510a5a06437d97a7ff4.css


Plik diff jest za duży
+ 0 - 0
dist/static/css/workPage.400638381461a510a5a06437d97a7ff4.css.map


Plik diff jest za duży
+ 0 - 0
dist/static/css/workPage.eb039ffafd131baf9f7d229e1701a61b.css.map


Plik diff jest za duży
+ 0 - 0
dist/static/js/app.967f4c22e2c119d0e9b1.js


Plik diff jest za duży
+ 0 - 0
dist/static/js/app.967f4c22e2c119d0e9b1.js.map


Plik diff jest za duży
+ 0 - 0
dist/static/js/manifest.23ea04dc469b57e2b4f8.js.map


Plik diff jest za duży
+ 0 - 0
dist/static/js/workPage-manifest.2ece51fa34be51c8610a.js.map


Plik diff jest za duży
+ 0 - 0
dist/static/js/workPage.8eddbd275bd20a8ea258.js


Plik diff jest za duży
+ 0 - 0
dist/static/js/workPage.8eddbd275bd20a8ea258.js.map


Plik diff jest za duży
+ 0 - 0
dist/static/js/workPage.a7ca1e8681db98a7e40f.js


Plik diff jest za duży
+ 0 - 0
dist/static/js/workPage.a7ca1e8681db98a7e40f.js.map


+ 1 - 1
dist/workPage.html

@@ -1,4 +1,4 @@
-<!DOCTYPE html><html><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><title>工作页面 - CocoFlow</title><link rel=icon href=../static/logo.ico><link href=./static/css/workPage.eb039ffafd131baf9f7d229e1701a61b.css rel=stylesheet></head><body><noscript><strong>We're sorry but this app doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id=app></div><script type=text/javascript src=./static/js/workPage-manifest.2ece51fa34be51c8610a.js></script><script type=text/javascript src=./static/js/workPage-vendor.3c2261c9f54c5ab3df69.js></script><script type=text/javascript src=./static/js/workPage.8eddbd275bd20a8ea258.js></script></body></html><script>function stopSafari() {
+<!DOCTYPE html><html><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><title>工作页面 - CocoFlow</title><link rel=icon href=../static/logo.ico><link href=./static/css/workPage.400638381461a510a5a06437d97a7ff4.css rel=stylesheet></head><body><noscript><strong>We're sorry but this app doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id=app></div><script type=text/javascript src=./static/js/workPage-manifest.2ece51fa34be51c8610a.js></script><script type=text/javascript src=./static/js/workPage-vendor.3c2261c9f54c5ab3df69.js></script><script type=text/javascript src=./static/js/workPage.a7ca1e8681db98a7e40f.js></script></body></html><script>function stopSafari() {
     //阻止safari浏览器双击放大功能
     let lastTouchEnd = 0  //更新手指弹起的时间
     document.documentElement.addEventListener("touchstart", function (event) {

+ 58 - 2
src/components/pages/pptEasy/addCourse3.vue

@@ -2052,7 +2052,9 @@ export default {
       this.ajax
         .post(this.$store.state.api + "insert_teacher_work_pptCourse", params)
         .then(res => {
-          this.uploadWorkLoading = false;
+          if(type != 3){
+            this.uploadWorkLoading = false;
+          }
           console.log(this.steps);
           // if (this.steps != 1 && this.steps != 2 && this.steps != 3) {
           this.showToast(this.lang.ssSaveSuccess);
@@ -2169,7 +2171,9 @@ export default {
         .post(this.$store.state.api + "updateWorkNew2", params)
         .then(res => {
           // if (this.steps != 1 && this.steps != 2 && this.steps != 3) {
-          this.uploadWorkLoading = false;
+          if(type != 3){
+            this.uploadWorkLoading = false;
+          }
           // if (this.cidType == 1) {
           //   this.$message({
           //     message: this.lang.ssSaveSuccess,
@@ -2732,6 +2736,9 @@ export default {
       }else if (tool == 79){
         this.answerQ = "";
         this.addPhoto();
+      }else if (tool == 84){
+        this.answerQ = "";
+        this.addFile();
       } else if (tool == 72) {
         this.$refs.appDialog.openG(
           [],
@@ -2747,6 +2754,23 @@ export default {
         // this.$message.info("添加H5页面");
       }
     },
+    //添加工具
+    addTool2(tool, data) {
+      let title = ""
+      if (tool == 831){
+        title = this.lang.ssKWLTitle
+      }
+      this.dialogVisibleChoice = false;
+      let _data = { id: new Date().getTime(), tool: tool, title: title, json: data, url: "" }
+      this.pptCourseJson.toolsList.push(_data);
+      this.getWorkPageId(_data.id, tool, data).then(res => {
+        let baseUrl = this.setUrl()
+        let url = `${baseUrl}/pbl-teacher-table/dist/workPage.html#/setWorkPage?id=${res}&type=${tool}`
+        this.pptCourseJson.toolsList.find(i => i.id === _data.id).url = url;
+        this.setPPtToolList();
+        this.addContent(_data)
+      });
+    },
     // 打开视频上传弹窗
     openVideoUploadDialog() {
       this.$refs.VideoUploadDialogRef.open();
@@ -3371,6 +3395,37 @@ export default {
         this.editId = null;
       });
     },
+    addFile() {
+      // if (this.answerQ == "") {
+      //   this.$message.error(this.lang.ssEnterQuesL);
+      //   return;
+      // }
+      let _data = { id: new Date().getTime(), tool: 84, title: this.lang.ssFile, brief: this.lang.ssFileTool, json: { answerQ: this.answerQ, fileList: [] }, url: "" }
+      if (this.editId) {
+        let oldData = this.pptCourseJson.toolsList.find(i => i.id === this.editId);
+        _data.id = oldData.id;
+        _data.title = oldData.title;
+        _data.brief = oldData.brief;
+        this.pptCourseJson.toolsList.splice(this.pptCourseJson.toolsList.findIndex(i => i.id === this.editId), 1, _data);
+      } else {
+        this.pptCourseJson.toolsList.push(_data);
+      }
+
+      this.dialogVisible8 = false;
+      this.getWorkPageId(_data.id, 84, { answerQ: this.answerQ, fileList: [] }).then(res => {
+        let baseUrl = this.setUrl()
+        let url = `${baseUrl}/pbl-teacher-table/dist/workPage.html#/setWorkPage?id=${res}&type=${_data.tool}`;
+        this.pptCourseJson.toolsList.find(i => i.id === _data.id).url = url;
+        this.setPPtToolList();
+        _data.url = url
+        if (!this.editId) {
+          this.addContent(_data)
+        } else {
+          this.addContent(_data, 2)
+        }
+        this.editId = null;
+      });
+    },
     async getWorkPageId(id, tool, json) {
       let params = [{
         userid: this.userid,
@@ -3705,6 +3760,7 @@ export default {
 
     }
     window.addTool = (tool) => this.addTool(tool); // 添加工具
+    window.addTool2 = (tool, data) => this.addTool2(tool, data); // 添加工具
     window.openVideoUploadDialog = () => this.openVideoUploadDialog(); // 打开视频上传弹窗
     window.openApplicationCenter = () => this.openApplicationCenter(); // 打开应用中心弹窗
     window.toolBtn = (type, id) => this.toolBtn(type, id); // 工具按钮点击事件

+ 45 - 38
src/components/pages/studentManageNew/component/addstu.vue

@@ -573,6 +573,10 @@ export default {
     },
     // 手动输入添加学生
     addStu() {
+      if (this.uploadfileLoading) {
+        this.$message.warning(this.lang.ssUploading);
+        return;
+      }
       this.selectedStuData = this.selectedStuData.filter((item) => item != "");
       if (this.selectedStuData.length == 0) {
         this.$message.error(this.lang.ssEnterStu);
@@ -631,52 +635,55 @@ export default {
         e.mail = e.account;
       });
 
-      const batchSize = 50;
-      if (data1.length > 0) {
-        if (data1.length > batchSize) {
-          // 计算总批次数
-          const totalBatches = Math.ceil(data1.length / batchSize);
-          for (let i = 0; i < totalBatches; i++) {
-            const batch = data1.slice(i * batchSize, (i + 1) * batchSize);
-            await this.uploadBatch(batch); // 假设 uploadBatch 返回 Promise
-            console.log(`普通上传第 ${i + 1} 批上传完成`, batch);
+      try {
+        const batchSize = 50;
+        if (data1.length > 0) {
+          if (data1.length > batchSize) {
+            // 计算总批次数
+            const totalBatches = Math.ceil(data1.length / batchSize);
+            for (let i = 0; i < totalBatches; i++) {
+              const batch = data1.slice(i * batchSize, (i + 1) * batchSize);
+              await this.uploadBatch(batch); // 假设 uploadBatch 返回 Promise
+              console.log(`普通上传第 ${i + 1} 批上传完成`, batch);
+            }
+          } else {
+            // 长度不超过 10,直接上传整个数组
+            await this.uploadBatch(data1);
           }
-        } else {
-          // 长度不超过 10,直接上传整个数组
-          await this.uploadBatch(data1);
         }
-      }
 
-      if (data2.length > 0) {
-        if (data2.length > batchSize) {
-          // 计算总批次数
-          const totalBatches = Math.ceil(data2.length / batchSize);
-          for (let i = 0; i < totalBatches; i++) {
-            const batch = data2.slice(i * batchSize, (i + 1) * batchSize);
-            await this.uploadBatchAccount (batch); // 假设 uploadBatch 返回 Promise
-            console.log(`自定义账号上传第 ${i + 1} 批上传完成`, batch);
+        if (data2.length > 0) {
+          if (data2.length > batchSize) {
+            // 计算总批次数
+            const totalBatches = Math.ceil(data2.length / batchSize);
+            for (let i = 0; i < totalBatches; i++) {
+              const batch = data2.slice(i * batchSize, (i + 1) * batchSize);
+              await this.uploadBatchAccount (batch); // 假设 uploadBatch 返回 Promise
+              console.log(`自定义账号上传第 ${i + 1} 批上传完成`, batch);
+            }
+          } else {
+            // 长度不超过 10,直接上传整个数组
+            await this.uploadBatchAccount (data2);
           }
-        } else {
-          // 长度不超过 10,直接上传整个数组
-          await this.uploadBatchAccount (data2);
         }
-      }
 
 
-      this.$message({
-        message: this.lang.ssAddOk,
-        type: "success",
-      });
-      this.uploadfileLoading = false;
+        this.$message({
+          message: this.lang.ssAddOk,
+          type: "success",
+        });
 
-      this.deltypy = 0;
-      this.popBoxdia= true;
-      // this.addStuVisible = false;
-      console.log('this.selectedStuData',this.selectedStuData);
-      
-      // this.selectedStuData = [];
-      this.$emit("getCascaderData");
-      this.addOp3('1', "", { type: "student_add" }, "success")
+        this.deltypy = 0;
+        this.popBoxdia= true;
+        // this.addStuVisible = false;
+        console.log('this.selectedStuData',this.selectedStuData);
+        
+        // this.selectedStuData = [];
+        this.$emit("getCascaderData");
+        this.addOp3('1', "", { type: "student_add" }, "success")
+      } finally {
+        this.uploadfileLoading = false;
+      }
     },
     async uploadBatch(box){
       return new Promise((resolve, reject) => {

+ 9 - 0
src/components/pages/studentManageNew/component/allClaAddStu.vue

@@ -207,6 +207,7 @@ export default {
       boxLoading: false,
       tableLoading: false,
       editTabloading: false,
+      confirmLoading: false,
       cascaderDataPath: [],
       cascaderData: [],
       searchStuName: "",
@@ -275,6 +276,10 @@ export default {
       this.$emit("closeAllClaAddStu");
     },
     confirmAllClaAddStu() {
+      if (this.confirmLoading) {
+        this.$message.warning(this.lang.ssUploading);
+        return;
+      }
       if (this.selectedStuData.length === 0) {
         this.$message({
           message: this.lang.ssSelectStu,
@@ -283,6 +288,7 @@ export default {
         return;
       }
       console.log("上传学生数据");
+      this.confirmLoading = true;
       const promises2 = this.selectedStuData.map((item) => {
         let box = [...item.classid.split(","), this.classInfo.id];
         box = box.filter((item) => item != "");
@@ -304,6 +310,9 @@ export default {
             type: "error",
           });
           this.addOp3('1', "", { type: "student_move" }, err)
+        })
+        .finally(() => {
+          this.confirmLoading = false;
         });
     },
     moveClassStudentPromise(item, cid) {

+ 513 - 0
src/components/pages/workPage/components/file.vue

@@ -0,0 +1,513 @@
+<template>
+  <div class="questionsAndAnswers">
+    <div class="qaa_title">{{ work.answerQ }}</div>
+    <div class="qaa_img" v-if="work.imageList && work.imageList.length > 0">
+      <div v-for="(item, index) in work.imageList" :key="index">
+        <img :src="item.url" alt="" @click="$hevueImgPreview(item.url)">
+      </div>
+    </div>
+    <div class="qaa_type">{{ lang.ssFile }}</div>
+    <!-- 文件列表 -->
+    <div class="imageList" v-if="work.fileList && work.fileList.length > 0">
+      <div v-for="(file, fileIndex) in work.fileList" :key="fileIndex" class="image-item" @click="previewFile(file)">
+        <!-- 图片:缩略图 -->
+        <el-image v-if="isImage(file.url)" style="width: 100px; height: 100px" :src="file.url" fit="cover"></el-image>
+        <!-- 视频:缩略图 + 播放按钮 -->
+        <div v-else-if="isVideo(file.url)" class="video-thumb">
+          <video :src="file.url" muted></video>
+          <div class="play-btn">
+            <svg viewBox="0 0 24 24" width="32" height="32" fill="#fff"><path d="M8 5v14l11-7z"></path></svg>
+          </div>
+        </div>
+        <!-- 其他文件:图标 + 文件名 -->
+        <div v-else class="file-thumb">
+          <svg viewBox="0 0 24 24" width="48" height="48" fill="currentColor">
+            <path d="M14 2H6c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V8l-6-6zm2 16H8v-2h8v2zm0-4H8v-2h8v2zm-3-5V3.5L18.5 9H13z"></path>
+          </svg>
+          <div class="file-name">{{ getFileName(file.url) }}</div>
+        </div>
+        <svg class="delete-icon" @click.stop="delImage(fileIndex)" t="1774237005199" viewBox="0 0 1024 1024" width="20" height="20">
+          <path d="M512 1024A512 512 0 1 1 512 0a512 512 0 0 1 0 1024zM305.956571 370.395429L447.488 512 305.956571 653.604571a45.568 45.568 0 1 0 64.438858 64.438858L512 576.512l141.604571 141.531429a45.568 45.568 0 0 0 64.438858-64.438858L576.512 512l141.531429-141.604571a45.568 45.568 0 1 0-64.438858-64.438858L512 447.488 370.395429 305.956571a45.568 45.568 0 0 0-64.438858 64.438858z" fill="#FF2525" p-id="4716"></path>
+        </svg>
+      </div>
+    </div>
+
+    <!-- 文件预览弹窗 -->
+    <el-dialog
+      :visible.sync="previewVisible"
+      :append-to-body="true"
+      :show-close="true"
+      custom-class="filePreviewDialog"
+      width="80%"
+      @closed="onPreviewClosed"
+    >
+      <div class="previewBody" v-if="currentFile">
+        <!-- 图片预览 -->
+        <img v-if="isImage(currentFile.url)" :src="currentFile.url" class="previewImg" />
+        <!-- 视频预览 -->
+        <video v-else-if="isVideo(currentFile.url)" :src="currentFile.url" controls autoplay class="previewVideo"></video>
+        <!-- Word/PPT/Excel 预览 -->
+        <iframe v-else-if="isOffice(currentFile.url)" :src="officePreviewUrl" class="previewIframe" frameborder="0" allowfullscreen></iframe>
+        <!-- PDF 预览 -->
+        <iframe v-else-if="isPdf(currentFile.url)" :src="pdfPreviewUrl" class="previewIframe" frameborder="0" allowfullscreen></iframe>
+        <!-- 不支持预览:下载 -->
+        <div v-else class="unsupportedBox">
+          <div class="unsupportedTip">{{ lang.ssUnsupportedFile }}</div>
+          <a :href="currentFile.url" download class="downloadBtn">{{ lang.ssDownloadFile }}</a>
+        </div>
+      </div>
+    </el-dialog>
+
+    <!-- 操作按钮区域 -->
+    <div class="upload-area" v-if="!work.fileList || work.fileList.length == 0">
+      <button class="photo-btn" @click="uploadImage" v-loading="uploadLoading">
+        <span class="plus-icon">+</span>
+        <span>{{ lang.ssUpload }}</span>
+      </button>
+    </div>
+  </div>
+</template>
+
+<script>
+import { uploadOneFile } from '@/components/tools/uploadFile'
+export default {
+  props: {
+    workData: {
+      type: Object,
+      default: () => {
+        return {};
+      }
+    }
+  },
+  data() {
+    return {
+      work: {
+        fileList: []
+      },
+      recordObj: {},
+      isMobile: false,
+      uploadLoading: false,
+      previewVisible: false,
+      currentFile: null
+    };
+  },
+  computed: {
+    officePreviewUrl() {
+      if (!this.currentFile) return "";
+      return `https://view.officeapps.live.com/op/view.aspx?src=${encodeURIComponent(this.currentFile.url)}&time=${new Date().getTime()}`;
+      // return `https://view.officeapps.live.com/op/view.aspx?src=${this.currentFile.url)}&time=${new Date().getTime()}`;
+    },
+    pdfPreviewUrl() {
+      if (!this.currentFile) return "";
+      return `https://cloud.cocorobo.cn/pdf.js/web/viewer.html?file=${encodeURIComponent(this.currentFile.url)}&time=${new Date().getTime()}`;
+    }
+  },
+  watch: {
+    work: {
+      deep: true,
+      handler(newValue) {
+        if (JSON.stringify(newValue) != JSON.stringify(this.workData.json)) {
+          this.changeWorkData(newValue);
+        }
+      }
+    },
+    workData: {
+      handler: function (newValue, oldValue) {
+        let _data = JSON.parse(JSON.stringify(this.workData.json));
+        if (JSON.stringify(newValue) != JSON.stringify(oldValue) || JSON.stringify(this.work) != JSON.stringify(_data)) {
+          let _data = JSON.parse(JSON.stringify(this.workData.json));
+          if (!_data.fileList) {
+            _data.fileList = [];
+          }
+          this.work = _data;
+        }
+      },
+      deep: true,
+      immediate: true
+    }
+  },
+  methods: {
+    changeWorkData(newValue) {
+      this.$emit("changeWorkData", JSON.stringify(newValue));
+    },
+    // 获取文件扩展名
+    getFileExt(url) {
+      if (!url) return "";
+      return (url.split("?")[0].split("#")[0].split(".").pop() || "").toLowerCase();
+    },
+    // 获取文件名
+    getFileName(url) {
+      if (!url) return "";
+      return decodeURIComponent(url.split("?")[0].split("#")[0].split("/").pop() || url);
+    },
+    // 判断是否为图片
+    isImage(url) {
+      return ["jpg", "jpeg", "png", "gif", "bmp", "webp", "svg"].includes(this.getFileExt(url));
+    },
+    // 判断是否为视频
+    isVideo(url) {
+      return ["mp4", "webm", "ogg", "mov", "avi", "wmv", "flv", "m4v", "mkv"].includes(this.getFileExt(url));
+    },
+    // 判断是否为 Office 文档(Word/Excel/PPT)
+    isOffice(url) {
+      return ["doc", "docx", "xls", "xlsx", "ppt", "pptx"].includes(this.getFileExt(url));
+    },
+    // 判断是否为 PDF
+    isPdf(url) {
+      return this.getFileExt(url) === "pdf";
+    },
+    // 打开预览弹窗
+    previewFile(file) {
+      this.currentFile = file;
+      this.previewVisible = true;
+    },
+    // 预览弹窗关闭后清理
+    onPreviewClosed() {
+      this.currentFile = null;
+    },
+    takePhoto() {
+      const input = document.createElement('input');
+      input.type = 'file';
+      input.accept = 'image/*';
+      input.capture = 'environment';
+      input.click();
+      input.onchange = (e) => {
+        if (e.target.files[0]) {
+          uploadOneFile(e.target.files[0]).then(res => {
+            res.src = res.url;
+            this.work.fileList = [...this.work.fileList, res];
+            this.changeWorkData(this.work);
+          });
+        }
+      };
+    },
+    uploadImage() {
+      if(this.uploadLoading){
+        return
+      }
+      const input = document.createElement('input');
+      input.type = 'file';
+      input.accept = '*';
+      input.multiple = true;
+      input.click();
+      input.onchange = (e) => {
+        const files = e.target.files;
+        if (files.length > 1){
+          this.$message.error(this.lang.ssOnlyUploadOneFile);
+          return;
+        }
+        if (files && files.length > 0) {
+          // 限制文件大小不超过20MB
+          // const maxSize = 20 * 1024 * 1024; // 20MB
+          // for (let i = 0; i < files.length; i++) {
+          //   if (files[i].size > maxSize) {
+          //     this.$message.error(this.lang.ssFileTooBig);
+          //     return;
+          //   }
+          // }
+          this.uploadLoading = true;
+          const uploadPromises = [];
+          for (let i = 0; i < files.length; i++) {
+            uploadPromises.push(uploadOneFile(files[i]));
+          }
+          Promise.all(uploadPromises).then(results => {
+            const newImages = results.map(res => ({ ...res, src: res.url }));
+            this.work.fileList = [...this.work.fileList, ...newImages];
+            this.uploadLoading = false;
+            this.changeWorkData(this.work);
+          });
+        }
+      };
+    },
+    delImage(index) {
+      this.work.fileList.splice(index, 1);
+      this.changeWorkData(this.work);
+    },
+    checkMobile() {
+      const ua = navigator.userAgent;
+      const isMobileDevice = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini|Mobile|Android/i.test(ua);
+      const isTablet = /iPad|Android(?!.*Mobile)/i.test(ua);
+      this.isMobile = isMobileDevice || isTablet;
+    }
+  },
+  mounted() {
+    this.checkMobile();
+  },
+  beforeUnmount() {
+  }
+};
+</script>
+
+<style scoped>
+.questionsAndAnswers {
+  width: 100%;
+  height: 100%;
+  display: flex;
+  align-items: center;
+  flex-direction: column;
+  box-sizing: border-box;
+  padding: 70px 10%;
+  overflow: auto;
+}
+
+.qaa_title {
+  font-size: 2rem;
+  font-weight: bold;
+  text-align: center;
+  width: 100%;
+}
+
+.qaa_img {
+  width: 100%;
+  height: auto;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex-wrap: wrap;
+  gap: 10px;
+}
+
+.qaa_img>div {
+  max-width: 200px;
+  height: auto;
+  margin: 10px;
+}
+
+.qaa_img>div>img {
+  width: 100%;
+  height: auto;
+  object-fit: cover;
+  cursor: pointer;
+}
+
+.qaa_type {
+  margin: 20px 0 40px 0;
+  font-size: 1rem;
+  color: #808080;
+}
+
+.imageList {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 16px;
+  margin-bottom: 30px;
+  justify-content: center;
+}
+
+.image-item {
+  position: relative;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  cursor: pointer;
+}
+
+.video-thumb {
+  position: relative;
+  width: 100px;
+  height: 100px;
+  border-radius: 4px;
+  overflow: hidden;
+  background: #000;
+}
+
+.video-thumb video {
+  width: 100%;
+  height: 100%;
+  object-fit: cover;
+  pointer-events: none;
+}
+
+.play-btn {
+  position: absolute;
+  left: 50%;
+  top: 50%;
+  transform: translate(-50%, -50%);
+  width: 32px;
+  height: 32px;
+  border-radius: 50%;
+  background: rgba(0, 0, 0, 0.5);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.file-thumb {
+  width: 100px;
+  height: 100px;
+  border-radius: 4px;
+  background: #f5f7fa;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  color: #3681FC;
+  padding: 8px 4px;
+  box-sizing: border-box;
+}
+
+.file-name {
+  margin-top: 4px;
+  font-size: 12px;
+  color: #666;
+  text-align: center;
+  word-break: break-all;
+  display: -webkit-box;
+  -webkit-line-clamp: 2;
+  -webkit-box-orient: vertical;
+  overflow: hidden;
+}
+
+.delete-icon {
+  position: absolute;
+  top: -10px;
+  right: -10px;
+  cursor: pointer;
+}
+
+.delete-text {
+  margin-top: 8px;
+  color: #FF2525;
+  font-size: .9rem;
+}
+
+.upload-area {
+  display: flex;
+  align-items: center;
+  gap: 40px;
+}
+
+.photo-btn {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  padding: 12px 32px;
+  border: 2px solid #FF9500;
+  border-radius: 8px;
+  background-color: #fff;
+  color: #FF9500;
+  font-size: 1rem;
+  cursor: pointer;
+  transition: all 0.3s;
+}
+
+.photo-btn:hover {
+  background-color: #fff8f0;
+}
+
+.photo-btn + .photo-btn {
+  margin-left: 20px;
+}
+
+.plus-icon {
+  font-size: 1.2rem;
+  font-weight: bold;
+}
+
+.upload-btn {
+  color: #67C23A;
+  font-size: 1rem;
+  cursor: pointer;
+  transition: color 0.3s;
+}
+
+.upload-btn:hover {
+  color: #85CE61;
+}
+
+/* 文件预览弹窗 */
+.filePreviewDialog >>> .el-dialog {
+  background: transparent;
+  box-shadow: none;
+}
+
+.filePreviewDialog >>> .el-dialog__header {
+  padding: 0;
+  margin: 0;
+}
+
+.filePreviewDialog >>> .el-dialog__headerbtn {
+  top: -2.5rem;
+  right: -2.5rem;
+  width: 2.5rem;
+  height: 2.5rem;
+  background: rgba(0, 0, 0, 0.5);
+  border-radius: 50%;
+  z-index: 10;
+}
+
+.filePreviewDialog >>> .el-dialog__close {
+  color: #fff;
+  font-size: 1.5rem;
+}
+
+.filePreviewDialog >>> .el-dialog__body {
+  padding: 0;
+  background: transparent;
+}
+
+.filePreviewDialog >>> .el-dialog__headerbtn:hover .el-dialog__close {
+  color: #fff;
+}
+
+.previewBody {
+  width: 100%;
+  background: #000;
+  border-radius: 8px;
+  overflow: hidden;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  min-height: 400px;
+}
+
+.previewImg {
+  max-width: 100%;
+  max-height: 80vh;
+  object-fit: contain;
+}
+
+.previewVideo {
+  width: 100%;
+  max-height: 80vh;
+  display: block;
+}
+
+.previewIframe {
+  width: 100%;
+  height: 80vh;
+  border: none;
+}
+
+.unsupportedBox {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  gap: 1rem;
+  padding: 3rem 1rem;
+  color: #fff;
+  background: #000;
+}
+
+.unsupportedTip {
+  font-size: 1rem;
+  color: #bbb;
+}
+
+.downloadBtn {
+  display: inline-block;
+  padding: 0.5rem 1.5rem;
+  background: #3681FC;
+  color: #fff;
+  border-radius: 4px;
+  text-decoration: none;
+  font-size: 0.875rem;
+  transition: opacity 0.2s;
+}
+
+.downloadBtn:hover {
+  opacity: 0.85;
+  color: #fff;
+}
+</style>

+ 926 - 0
src/components/pages/workPage/components/kwlPage.vue

@@ -0,0 +1,926 @@
+<template>
+  <div class="kwlPage">
+    <div class="kwlCard">
+      <!-- 头部卡片 -->
+      <div class="kwlHeader">
+        <div class="kwlHeaderLeft">
+          <div class="kwlTitle">{{ lang.ssKWLTitle }}</div>
+          <div class="kwlSubtitle">{{ lang.ssKWLSubtitle }}</div>
+        </div>
+        <div class="kwlHeaderRight" @click="showHowTo = !showHowTo">
+          <span>{{ lang.ssKWLHowTo }}</span>
+          <i :class="['kwlArrow', { 'kwlArrowUp': showHowTo }]">
+            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="w-4 h-4 transition-transform "><polyline points="6 9 12 15 18 9"></polyline></svg>
+          </i>
+        </div>
+      </div>
+
+      <!-- 怎么做指引 -->
+      <div class="kwlHowTo" v-show="showHowTo">
+        <div class="kwlHowToItem" v-for="(step, index) in howToSteps" :key="index">
+          <div class="kwlStepNum">{{ index + 1 }}</div>
+          <div class="kwlStepText">{{ step }}</div>
+        </div>
+      </div>
+    </div>
+
+    <!-- 任务说明 -->
+    <div class="kwlCard kwlTaskDesc" v-show="work.taskDescription || (work.taskUrl && work.taskUrl.length > 0)">
+      <div class="kwlTaskHeader">
+        <span class="kwlStar">✦</span>
+        <span class="kwlTaskTitle">{{ lang.ssKWLTaskDesc }}</span>
+      </div>
+      <div class="kwlTaskContent" v-show="showTaskDesc" v-if="work.taskDescription">{{ work.taskDescription }}</div>
+      <!-- 任务附件:图片/视频预览 -->
+      <div class="kwlTaskMedia" v-show="showTaskDesc" v-if="work.taskUrl && work.taskUrl.length > 0">
+        <div class="kwlTaskMediaItem" v-for="(url, idx) in work.taskUrl" :key="'taskUrl-' + idx">
+          <img v-if="isImage(url)" :src="url" @click="$hevueImgPreview(url)" />
+          <!-- 视频:缩略图 + 播放按钮,点击打开全屏弹窗 -->
+          <div class="kwlVideoThumb" v-else-if="isVideo(url)" @click="previewVideo(url)">
+            <video :src="url" muted></video>
+            <div class="kwlVideoPlayBtn">
+              <svg viewBox="0 0 24 24" width="32" height="32" fill="#fff">
+                <path d="M8 5v14l11-7z"></path>
+              </svg>
+            </div>
+          </div>
+          <a v-else :href="url" target="_blank" class="kwlTaskMediaLink">{{ url }}</a>
+        </div>
+      </div>
+    </div>
+
+    <!-- 视频全屏播放弹窗 -->
+    <el-dialog
+      :visible.sync="videoDialogVisible"
+      :append-to-body="true"
+      :show-close="true"
+      custom-class="kwlVideoDialog"
+      width="80%"
+      @closed="onVideoDialogClosed"
+    >
+      <div class="kwlVideoDialogBody" v-if="currentVideoUrl">
+        <video :src="currentVideoUrl" controls autoplay class="kwlVideoFull"></video>
+      </div>
+    </el-dialog>
+
+    <!-- KWL 三列 -->
+    <div class="kwlColumns">
+      <!-- K - 已知 -->
+      <div class="kwlColumn kwlColKnow">
+        <div class="kwlColTitle kwlColTitleKnow">{{ lang.ssKWLColKnow }}</div>
+        <div class="kwlColBody">
+          <div class="kwlEmptyTip" v-if="!work.know || work.know.length === 0">
+            {{ lang.ssKWLAddContent }}
+          </div>
+          <div class="kwlItem" v-for="(item, idx) in work.know" :key="'k-' + idx">
+            <span class="kwlItemText">{{ item }}</span>
+            <span class="kwlItemDel" @click="removeItem('know', idx)">×</span>
+          </div>
+        </div>
+        <div class="kwlAddRow">
+          <input
+            v-model="newKnow"
+            class="kwlInput"
+            :placeholder="work.kplaceholder ? work.kplaceholder : lang.ssKWLAddPlaceholder"
+            @keyup.enter="addItem('know')"
+          />
+          <button class="kwlAddBtn kwlAddBtnKnow" @click="addItem('know')">+</button>
+        </div>
+      </div>
+
+      <!-- W - 想知 -->
+      <div class="kwlColumn kwlColWant">
+        <div class="kwlColTitle kwlColTitleWant">{{ lang.ssKWLColWant }}</div>
+        <div class="kwlColBody">
+          <div class="kwlEmptyTip" v-if="!work.want || work.want.length === 0">
+            {{ lang.ssKWLAddContent }}
+          </div>
+          <div class="kwlItem" v-for="(item, idx) in work.want" :key="'w-' + idx">
+            <span class="kwlItemText">{{ item }}</span>
+            <span class="kwlItemDel" @click="removeItem('want', idx)">×</span>
+          </div>
+        </div>
+        <div class="kwlAddRow">
+          <input
+            v-model="newWant"
+            class="kwlInput"
+            :placeholder="work.wplaceholder ? work.wplaceholder : lang.ssKWLAddPlaceholder"
+            @keyup.enter="addItem('want')"
+          />
+          <button class="kwlAddBtn kwlAddBtnWant" @click="addItem('want')">+</button>
+        </div>
+      </div>
+
+      <!-- L - 已学 -->
+      <div class="kwlColumn kwlColLearn">
+        <div class="kwlColTitle kwlColTitleLearn">{{ lang.ssKWLColLearn }}</div>
+        <div class="kwlColBody">
+          <div class="kwlEmptyTip" v-if="!work.learned || work.learned.length === 0">
+            {{ lang.ssKWLAddContent }}
+          </div>
+          <div class="kwlItem" v-for="(item, idx) in work.learned" :key="'l-' + idx">
+            <span class="kwlItemText">{{ item }}</span>
+            <span class="kwlItemDel" @click="removeItem('learned', idx)">×</span>
+          </div>
+        </div>
+        <div class="kwlAddRow">
+          <input
+            v-model="newLearned"
+            class="kwlInput"
+            :placeholder="work.lplaceholder ? work.lplaceholder : lang.ssKWLAddPlaceholder"
+            @keyup.enter="addItem('learned')"
+          />
+          <button class="kwlAddBtn kwlAddBtnLearn" @click="addItem('learned')">+</button>
+        </div>
+      </div>
+    </div>
+
+    <!-- 学习反思 -->
+    <div class="kwlCard kwlReflection" v-if="isShowReflection">
+      <div class="kwlRefTitle">{{ lang.ssKWLReflection }}</div>
+      <div class="kwlRefAreaWrap">
+        <textarea
+          v-model="work.reflection"
+          class="kwlRefTextarea"
+          :placeholder="lang.ssKWLReflectionPH"
+        ></textarea>
+        <!-- 右下角麦克风按钮:点击开始/停止录音 -->
+        <div
+          class="kwlMicBtn"
+          :class="{ kwlMicBtnActive: recordObj.status == '1' }"
+          :title="recordObj.status == '1' ? lang.ssKWLStopRec : lang.ssKWLVoiceInput"
+          @click="recordObj.status == '1' ? stopRecord() : startRecord()"
+        >
+          <!-- 录音中:声波动画 -->
+          <svg v-if="recordObj.status == '1'" width="18" height="18" viewBox="0 0 120 20">
+            <rect x="5" y="5" width="8" :height="10" rx="3" fill="currentColor">
+              <animate attributeName="height" values="10;20;10" dur="1s" repeatCount="indefinite" begin="0s" />
+              <animate attributeName="y" values="10;0;10" dur="1s" repeatCount="indefinite" begin="0s" />
+            </rect>
+            <rect x="29" y="5" width="8" :height="10" rx="3" fill="currentColor">
+              <animate attributeName="height" values="10;20;10" dur="1s" repeatCount="indefinite" begin="0.2s" />
+              <animate attributeName="y" values="10;0;10" dur="1s" repeatCount="indefinite" begin="0.2s" />
+            </rect>
+            <rect x="53" y="5" width="8" :height="10" rx="3" fill="currentColor">
+              <animate attributeName="height" values="10;20;10" dur="1s" repeatCount="indefinite" begin="0.4s" />
+              <animate attributeName="y" values="10;0;10" dur="1s" repeatCount="indefinite" begin="0.4s" />
+            </rect>
+            <rect x="77" y="5" width="8" :height="10" rx="3" fill="currentColor">
+              <animate attributeName="height" values="10;20;10" dur="1s" repeatCount="indefinite" begin="0.6s" />
+              <animate attributeName="y" values="10;0;10" dur="1s" repeatCount="indefinite" begin="0.6s" />
+            </rect>
+            <rect x="101" y="5" width="8" :height="10" rx="3" fill="currentColor">
+              <animate attributeName="height" values="10;20;10" dur="1s" repeatCount="indefinite" begin="0.8s" />
+              <animate attributeName="y" values="10;0;10" dur="1s" repeatCount="indefinite" begin="0.8s" />
+            </rect>
+          </svg>
+          <!-- 空闲:麦克风图标 -->
+          <svg v-else viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2">
+            <path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"></path>
+            <path d="M19 10v2a7 7 0 0 1-14 0v-2"></path>
+            <line x1="12" y1="19" x2="12" y2="23"></line>
+            <line x1="8" y1="23" x2="16" y2="23"></line>
+          </svg>
+        </div>
+      </div>
+    </div>
+
+    <!-- 录音转文字 iframe -->
+    <iframe allow="camera *; microphone *;display-capture;midi;encrypted-media;" :src="iframeUrl" ref="iframeRef"
+      v-show="false"></iframe>
+  </div>
+</template>
+
+<script>
+export default {
+  props: {
+    workData: {
+      type: Object,
+      default: () => {
+        return {};
+      }
+    }
+  },
+  data() {
+    return {
+      work: {
+        know: [],
+        want: [],
+        learned: [],
+        reflection: "",
+        taskDescription: "",
+        taskUrl: [],
+        showHowTo: false,
+        kplaceholder:"",
+        wplaceholder:"",
+        lplaceholder:"",
+        isShowReflection: true
+      },
+      newKnow: "",
+      newWant: "",
+      newLearned: "",
+      showHowTo: false,
+      showTaskDesc: true,
+      isShowReflection: true,
+      recordObj: {
+        status: "0", // 0:默认  1:录音中
+        loading: false,
+        startTime: 0,
+      },
+      videoDialogVisible: false,
+      currentVideoUrl: "",
+      howToSteps: [
+        this.lang.ssKWLStep1,
+        this.lang.ssKWLStep2,
+        this.lang.ssKWLStep3,
+        this.lang.ssKWLStep4
+      ]
+    };
+  },
+  computed: {
+    iframeUrl() {
+      if (this.lang && this.lang.lang == 'com') {
+        return `https://cloud.cocorobo.com/browser/public/index.html`;
+      } else if (this.lang && this.lang.lang == 'hk') {
+        return `https://cloud.cocorobo.hk/browser/public/index.html`;
+      } else {
+        return `https://beta.cloud.cocorobo.cn/browser/public/index.html`;
+      }
+    }
+  },
+  watch: {
+    work: {
+      deep: true,
+      handler(newValue) {
+        if (JSON.stringify(newValue) != JSON.stringify(this.workData.json)) {
+          this.changeWorkData(newValue);
+        }
+      }
+    },
+    workData: {
+      handler: function (newValue, oldValue) {
+        let _data = JSON.parse(JSON.stringify(this.workData.json || {}));
+        if (JSON.stringify(newValue) != JSON.stringify(oldValue) || JSON.stringify(this.work) != JSON.stringify(_data)) {
+          this.work = Object.assign({
+            know: [],
+            want: [],
+            learned: [],
+            reflection: "",
+            taskDescription: "",
+            taskUrl: [],
+            showHowTo: false,
+            kplaceholder:"",
+            wplaceholder:"",
+            lplaceholder:"",
+            isShowReflection: true
+          }, _data);
+
+          this.showHowTo = this.work.showHowTo;
+          this.isShowReflection = this.work.isShowReflection;
+        }
+      },
+      deep: true,
+      immediate: true
+    }
+  },
+  methods: {
+    changeWorkData(newValue) {
+      const str = JSON.stringify(newValue);
+      this.$emit("changeWorkData", str);
+    },
+    addItem(type) {
+      const inputMap = { know: "newKnow", want: "newWant", learned: "newLearned" };
+      const val = this[inputMap[type]];
+      if (!val || !val.trim()) return;
+      if (!this.work[type]) this.$set(this.work, type, []);
+      this.work[type].push(val.trim());
+      this[inputMap[type]] = "";
+    },
+    removeItem(type, idx) {
+      this.work[type].splice(idx, 1);
+    },
+    // 判断是否为图片
+    isImage(url) {
+      if (!url) return false;
+      return /\.(jpe?g|png|gif|bmp|webp|svg)(\?.*)?$/i.test(url);
+    },
+    // 判断是否为视频
+    isVideo(url) {
+      if (!url) return false;
+      return /\.(mp4|webm|ogg|mov|avi|wmv|flv|m4v)(\?.*)?$/i.test(url);
+    },
+    // 打开视频弹窗
+    previewVideo(url) {
+      this.currentVideoUrl = url;
+      this.videoDialogVisible = true;
+    },
+    // 视频弹窗关闭后清理
+    onVideoDialogClosed() {
+      this.currentVideoUrl = "";
+    },
+    // 开始录音
+    startRecord() {
+      let iframe = this.$refs["iframeRef"];
+      if (!iframe || !iframe.contentWindow) {
+        this.$message.error(this.lang.ssKWLRecServiceNotReady);
+        return;
+      }
+      try {
+        iframe.contentWindow.window.document.getElementById(
+          "languageOptions"
+        ).selectedIndex = 2;
+
+        iframe.contentWindow.window.onRecognizedResult = (e) => {
+          let privText = e.privText;
+          if (!privText) {
+            return;
+          }
+          // 将识别文本追加到反思区域
+          if (this.work.reflection) {
+            this.work.reflection += " " + privText;
+          } else {
+            this.work.reflection = privText;
+          }
+        };
+
+        iframe.contentWindow.ConversationTranscriber();
+        this.recordObj.status = "1";
+        this.recordObj.startTime = new Date().getTime();
+        setTimeout(() => {
+          navigator.permissions && navigator.permissions.query({ name: 'microphone' }).then(permissionStatus => {
+            if (permissionStatus.state !== "granted") {
+              this.recordObj.status = "0";
+              this.recordObj.loading = false;
+              iframe.contentWindow.onSessionStopped = null;
+              iframe.contentWindow.window.onRecognizedResult = null;
+              this.$message.warning(this.lang.ssKWLNoPermStop);
+              return;
+            }
+          }).catch(() => {});
+        }, 2000);
+      } catch (err) {
+        console.error("startRecord error:", err);
+        this.$message.error(this.lang.ssKWLRecStartFail);
+        this.recordObj.status = "0";
+      }
+    },
+    // 停止录音
+    stopRecord() {
+      if (new Date().getTime() - this.recordObj.startTime < 2000) {
+        this.$message.info(this.lang.ssKWLRecordShort);
+        return;
+      }
+      navigator.permissions && navigator.permissions.query({ name: 'microphone' }).then(permissionStatus => {
+        if (permissionStatus.state !== "granted") {
+          this.recordObj.status = "0";
+          this.recordObj.loading = false;
+          let iframe = this.$refs["iframeRef"];
+          if (iframe && iframe.contentWindow) {
+            iframe.contentWindow.onSessionStopped = null;
+            iframe.contentWindow.window.onRecognizedResult = null;
+          }
+          this.$message.warning(this.lang.ssKWLNoMicPerm);
+          return;
+        }
+        if (this.recordObj.status == '1' && !this.recordObj.loading) {
+          let iframe = this.$refs["iframeRef"];
+          if (!iframe || !iframe.contentWindow) {
+            this.recordObj.status = "0";
+            this.recordObj.loading = false;
+            return;
+          }
+          this.recordObj.loading = true;
+          try {
+            iframe.contentWindow.window.document
+              .getElementById("scenarioStopButton")
+              .click();
+          } catch (err) {
+            console.error("stop click error:", err);
+            this.recordObj.status = "0";
+            this.recordObj.loading = false;
+            return;
+          }
+          iframe.contentWindow.onSessionStopped = () => {
+            this.recordObj.status = "0";
+            this.recordObj.loading = false;
+            this.$message.success(this.lang.ssKWLRecStopped);
+            iframe.contentWindow.onSessionStopped = null;
+            iframe.contentWindow.window.onRecognizedResult = null;
+          };
+        } else {
+          this.recordObj.status = "0";
+          this.recordObj.loading = false;
+          let iframe = this.$refs["iframeRef"];
+          if (iframe && iframe.contentWindow) {
+            iframe.contentWindow.onSessionStopped = null;
+            iframe.contentWindow.window.onRecognizedResult = null;
+          }
+        }
+      }).catch(() => {
+        this.recordObj.status = "0";
+        this.recordObj.loading = false;
+        let iframe = this.$refs["iframeRef"];
+        if (iframe && iframe.contentWindow) {
+          iframe.contentWindow.onSessionStopped = null;
+          iframe.contentWindow.window.onRecognizedResult = null;
+        }
+        this.$message.success(this.lang.ssKWLRecStopped);
+      });
+    }
+  },
+  mounted() {},
+  beforeUnmount() {}
+};
+</script>
+
+<style scoped>
+.kwlPage {
+  width: 100%;
+  min-height: 100%;
+  height: 100vh;
+  box-sizing: border-box;
+  padding: 1rem;
+  background: #f5f7fa;
+  display: flex;
+  flex-direction: column;
+  gap: 1rem;
+  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
+}
+
+.kwlCard {
+  background: #ffffff;
+  border-radius: .75rem;
+  padding: 1.25rem 1.5rem;
+  box-shadow: 0 .0625rem .1875rem rgba(0, 0, 0, 0.06);
+}
+
+/* 头部 */
+.kwlHeader {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+.kwlTitle {
+  font-size: 1.25rem;
+  font-weight: 600;
+  color: #1a1a1a;
+  margin-bottom: .375rem;
+}
+
+.kwlSubtitle {
+  font-size: .8125rem;
+  color: #888;
+}
+
+.kwlHeaderRight {
+  display: flex;
+  align-items: center;
+  gap: .375rem;
+  color: #3681FC;
+  font-size: .875rem;
+  cursor: pointer;
+  user-select: none;
+  white-space: nowrap;
+}
+
+.kwlArrow {
+  display: flex;
+  align-items: center;
+  transition: transform 0.2s;
+}
+
+.kwlArrow svg {
+  width: 1rem;
+  height: 1rem;
+}
+
+.kwlArrowUp {
+  transform: rotate(180deg);
+}
+
+/* 怎么做 */
+.kwlHowTo {
+  border-top: .0625rem solid #e0e0e0;
+  display: flex;
+  flex-direction: column;
+  gap: .75rem;
+  padding-top: .9375rem;
+  margin-top: .9375rem;
+}
+
+.kwlHowToItem {
+  display: flex;
+  align-items: flex-start;
+  gap: .75rem;
+}
+
+.kwlStepNum {
+  width: 1.5rem;
+  height: 1.5rem;
+  border-radius: 50%;
+  background: #3681FC;
+  color: #fff;
+  font-size: .75rem;
+  font-weight: 600;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex-shrink: 0;
+}
+
+.kwlStepText {
+  font-size: .875rem;
+  color: #333;
+  line-height: 1.5rem;
+}
+
+/* 任务说明 */
+.kwlTaskDesc {
+  padding: 1rem .9375rem;
+  border-left: .25rem solid #F5A623;
+}
+
+.kwlTaskHeader {
+  display: flex;
+  align-items: center;
+  gap: .5rem;
+  cursor: pointer;
+  user-select: none;
+}
+
+.kwlStar {
+  color: #F5A623;
+  font-size: 1rem;
+}
+
+.kwlTaskTitle {
+  font-size: .9375rem;
+  font-weight: 600;
+  color: #1a1a1a;
+}
+
+.kwlTaskContent {
+  margin-top: .625rem;
+  font-size: .875rem;
+  color: #444;
+  line-height: 1.375rem;
+  padding-left: 1.625rem;
+}
+
+/* 任务附件:图片/视频 */
+.kwlTaskMedia {
+  margin-top: .75rem;
+  padding-left: 1.625rem;
+  display: flex;
+  flex-wrap: wrap;
+  gap: .75rem;
+}
+
+.kwlTaskMediaItem {
+  width: 9.375rem;
+  max-width: 100%;
+}
+
+.kwlTaskMediaItem img {
+  width: 100%;
+  height: 6.25rem;
+  object-fit: cover;
+  border-radius: .5rem;
+  cursor: pointer;
+  border: .0625rem solid #e0e0e0;
+  transition: transform 0.2s;
+}
+
+.kwlTaskMediaItem img:hover {
+  transform: scale(1.02);
+}
+
+/* 视频缩略图 */
+.kwlVideoThumb {
+  position: relative;
+  width: 100%;
+  height: 6.25rem;
+  border-radius: .5rem;
+  overflow: hidden;
+  cursor: pointer;
+  background: #000;
+  border: .0625rem solid #e0e0e0;
+}
+
+.kwlVideoThumb video {
+  width: 100%;
+  height: 100%;
+  object-fit: cover;
+  pointer-events: none;
+}
+
+.kwlVideoPlayBtn {
+  position: absolute;
+  left: 50%;
+  top: 50%;
+  transform: translate(-50%, -50%);
+  width: 3rem;
+  height: 3rem;
+  border-radius: 50%;
+  background: rgba(0, 0, 0, 0.55);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  transition: background 0.2s;
+}
+
+.kwlVideoThumb:hover .kwlVideoPlayBtn {
+  background: rgba(54, 129, 252, 0.85);
+}
+
+.kwlVideoPlayBtn svg {
+  margin-left: .25rem;
+}
+
+.kwlTaskMediaLink {
+  display: inline-block;
+  font-size: .8125rem;
+  color: #3681FC;
+  word-break: break-all;
+}
+
+/* 视频全屏弹窗 */
+.kwlVideoDialog >>> .el-dialog {
+  background: transparent;
+  box-shadow: none;
+}
+
+.kwlVideoDialog >>> .el-dialog__header {
+  padding: 0;
+  margin: 0;
+}
+
+.kwlVideoDialog >>> .el-dialog__headerbtn {
+  top: -2.5rem;
+  right: -2.5rem;
+  width: 2.5rem;
+  height: 2.5rem;
+  background: rgba(0, 0, 0, 0.5);
+  border-radius: 50%;
+  z-index: 10;
+}
+
+.kwlVideoDialog >>> .el-dialog__close {
+  color: #fff;
+  font-size: 1.5rem;
+}
+
+.kwlVideoDialog >>> .el-dialog__body {
+  padding: 0;
+  background: transparent;
+}
+
+.kwlVideoDialog >>> .el-dialog__headerbtn:hover .el-dialog__close {
+  color: #fff;
+}
+
+.kwlVideoDialogBody {
+  width: 100%;
+  background: #000;
+  border-radius: .5rem;
+  overflow: hidden;
+}
+
+.kwlVideoFull {
+  width: 100%;
+  height: 70vh;
+  display: block;
+  background: #000;
+}
+
+/* 三列 */
+.kwlColumns {
+  display: flex;
+  gap: 1rem;
+}
+
+.kwlColumn {
+  flex: 1;
+  background: #ffffff;
+  border-radius: .75rem;
+  box-shadow: 0 .0625rem .1875rem rgba(0, 0, 0, 0.06);
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+}
+
+.kwlColTitle {
+  font-size: .9375rem;
+  font-weight: 600;
+  padding: .875rem 1.125rem;
+  color: #fff;
+}
+
+.kwlColKnow {
+  border-top: .375rem solid #2563eb;
+}
+
+.kwlColTitleKnow {
+  color: #2563eb;
+}
+
+.kwlColWant {
+  border-top: .375rem solid #F56565;
+}
+
+.kwlColTitleWant {
+  color: #F56565;
+}
+
+.kwlColLearn {
+  border-top: .375rem solid #48BB78;
+}
+
+.kwlColTitleLearn {
+  color: #48BB78;
+}
+
+.kwlColBody {
+  flex: 1;
+  min-height: 10rem;
+  padding: 1rem;
+  display: flex;
+  flex-direction: column;
+  gap: .5rem;
+}
+
+.kwlEmptyTip {
+  text-align: center;
+  color: #bbb;
+  font-size: .8125rem;
+  padding: 1.875rem 0;
+  border: .125rem dashed #e0e0e0;
+  border-radius: .5rem;
+  flex: 1;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.kwlItem {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: .5rem .75rem;
+  background: #f5f7fa;
+  border-radius: .375rem;
+  font-size: .8125rem;
+  color: #333;
+}
+
+.kwlItemText {
+  flex: 1;
+  word-break: break-all;
+}
+
+.kwlItemDel {
+  margin-left: .5rem;
+  width: 1.25rem;
+  height: 1.25rem;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  cursor: pointer;
+  color: #999;
+  font-size: 1rem;
+  line-height: 1;
+  transition: all 0.2s;
+}
+
+.kwlItemDel:hover {
+  background: #eee;
+  color: #666;
+}
+
+.kwlAddRow {
+  display: flex;
+  align-items: center;
+  gap: .5rem;
+  padding: .75rem 1rem;
+  border-top: .0625rem solid #f0f0f0;
+}
+
+.kwlInput {
+  flex: 1;
+  border: none;
+  outline: none;
+  font-size: .8125rem;
+  padding: .375rem 0;
+  background: transparent;
+  color: #333;
+}
+
+.kwlInput::placeholder {
+  color: #bbb;
+}
+
+.kwlAddBtn {
+  width: 2rem;
+  height: 2rem;
+  border-radius: .5rem;
+  border: none;
+  color: #fff;
+  font-size: 1.125rem;
+  font-weight: 600;
+  cursor: pointer;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  transition: opacity 0.2s;
+}
+
+.kwlAddBtn:hover {
+  opacity: 0.85;
+}
+
+.kwlAddBtnKnow {
+  background: #3681FC;
+}
+
+.kwlAddBtnWant {
+  background: #F56565;
+}
+
+.kwlAddBtnLearn {
+  background: #48BB78;
+}
+
+/* 学习反思 */
+.kwlReflection {
+  padding: 1.25rem 1.5rem;
+}
+
+.kwlRefTitle {
+  font-size: 1rem;
+  font-weight: 600;
+  color: #1a1a1a;
+  margin-bottom: .75rem;
+}
+
+.kwlRefAreaWrap {
+  position: relative;
+}
+
+.kwlRefTextarea {
+  width: 100%;
+  min-height: 7.5rem;
+  padding: .75rem 3rem .75rem .875rem;
+  border: .0625rem solid #e0e0e0;
+  border-radius: .5rem;
+  font-size: .875rem;
+  line-height: 1.375rem;
+  resize: vertical;
+  outline: none;
+  box-sizing: border-box;
+  font-family: inherit;
+  transition: border-color 0.2s;
+}
+
+.kwlRefTextarea:focus {
+  border-color: #3681FC;
+}
+
+.kwlRefTextarea::placeholder {
+  color: #bbb;
+}
+
+/* 右下角麦克风按钮 */
+.kwlMicBtn {
+  position: absolute;
+  right: .75rem;
+  bottom: .75rem;
+  width: 2rem;
+  height: 2rem;
+  border-radius: 50%;
+  background: #f5f7fa;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  cursor: pointer;
+  color: #888;
+  transition: all 0.2s;
+}
+
+.kwlMicBtn:hover {
+  background: #e8ecf1;
+  color: #3681FC;
+}
+
+/* 录音中:蓝色高亮 */
+.kwlMicBtnActive {
+  background: #3681FC;
+  color: #fff;
+}
+
+.kwlMicBtnActive:hover {
+  background: #5B9BFC;
+  color: #fff;
+}
+</style>

+ 1 - 1
src/components/pages/workPage/components/photo.vue

@@ -14,7 +14,7 @@
         <svg class="delete-icon" @click="delImage(imageIndex)" t="1774237005199" viewBox="0 0 1024 1024" width="20" height="20">
           <path d="M512 1024A512 512 0 1 1 512 0a512 512 0 0 1 0 1024zM305.956571 370.395429L447.488 512 305.956571 653.604571a45.568 45.568 0 1 0 64.438858 64.438858L512 576.512l141.604571 141.531429a45.568 45.568 0 0 0 64.438858-64.438858L576.512 512l141.531429-141.604571a45.568 45.568 0 1 0-64.438858-64.438858L512 447.488 370.395429 305.956571a45.568 45.568 0 0 0-64.438858 64.438858z" fill="#FF2525" p-id="4716"></path>
         </svg>
-        <span class="delete-text">{{ lang.ssDeleteImage }}</span>
+        <!-- <span class="delete-text">{{ lang.ssDelete }}</span> -->
       </div>
     </div>
 

+ 449 - 0
src/components/pages/workPage/components/setFile.vue

@@ -0,0 +1,449 @@
+<template>
+  <div class="setChoiceQuestion">
+    <div class="testItem">
+      <div class="ti_header">
+        <span>{{ lang.ssFileTip }}</span>
+      </div>
+
+      <div class="ti_title">
+        <el-input class="ti_t_input" type="textarea" v-model="jsonData.answerQ" @input="setTestTitle"></el-input>
+        <div class="uploadImage" @click="uploadImage()">
+          <svg viewBox="0 0 1024 1024" version="1.1" p-id="15953" width="200" height="200">
+            <path
+              d="M368 480c-62.4 0-112-49.6-112-112s49.6-112 112-112 112 49.6 112 112-49.6 112-112 112z m0-160c-27.2 0-48 20.8-48 48s20.8 48 48 48 48-20.8 48-48-20.8-48-48-48z m464 608H192c-52.8 0-96-43.2-96-96V192c0-52.8 43.2-96 96-96h640c52.8 0 96 43.2 96 96v640c0 52.8-43.2 96-96 96zM192 160c-17.6 0-32 14.4-32 32v640c0 17.6 14.4 32 32 32h640c17.6 0 32-14.4 32-32V192c0-17.6-14.4-32-32-32H192z m259.2 556.8c-25.6 0-51.2-11.2-70.4-30.4l-38.4-40c-12.8-12.8-33.6-12.8-46.4 0l-49.6 52.8c-12.8 12.8-32 12.8-44.8 1.6s-12.8-32-1.6-44.8l49.6-52.8c17.6-19.2 43.2-30.4 68.8-30.4s51.2 11.2 70.4 30.4l38.4 40c12.8 12.8 33.6 12.8 46.4 0l160-168c17.6-19.2 43.2-30.4 70.4-30.4s51.2 11.2 70.4 30.4L920 628.8c12.8 12.8 11.2 33.6-1.6 44.8-12.8 12.8-33.6 11.2-44.8-1.6L728 518.4c-12.8-12.8-33.6-12.8-46.4 0L521.6 688c-19.2 17.6-44.8 28.8-70.4 28.8z"
+              fill="#333333" p-id="15954"></path>
+          </svg>
+        </div>
+      </div>
+      <!-- 图片 -->
+      <div class="imageList">
+        <div v-for="(image, imageIndex) in jsonData.imageList" :key="imageIndex">
+          <el-image style="width: 100px; height: 100px" :src="image.url" :preview-src-list="[image.url]"
+            fit="cover"></el-image>
+            <svg @click="delImage(imageIndex)" t="1774237005199" viewBox="0 0 1024 1024" width="200" height="200"><path d="M512 1024A512 512 0 1 1 512 0a512 512 0 0 1 0 1024zM305.956571 370.395429L447.488 512 305.956571 653.604571a45.568 45.568 0 1 0 64.438858 64.438858L512 576.512l141.604571 141.531429a45.568 45.568 0 0 0 64.438858-64.438858L576.512 512l141.531429-141.604571a45.568 45.568 0 1 0-64.438858-64.438858L512 447.488 370.395429 305.956571a45.568 45.568 0 0 0-64.438858 64.438858z" fill="#FF2525" p-id="4716"></path></svg>
+        </div>
+      </div>
+
+
+      <!-- 解释说明 -->
+      <div class="explanation" v-show="false">
+        <div class="e_header">
+          <span>{{ lang.ssEvaluationStandard }}</span>
+          <!-- <div v-show="false">
+            <svg t="1774233113566" viewBox="0 0 1027 1024" width="200" height="200">
+              <path
+                d="M511.979521 544.586217a54.525819 54.525819 0 0 1-23.359066-5.247791L31.038758 321.427143a54.397824 54.397824 0 0 1 0-98.236071L488.620455 5.279789c14.719411-7.039718 31.99872-7.039718 46.718131 0l457.581697 217.911283a54.397824 54.397824 0 0 1 0 98.236071l-457.581697 217.911283A54.525819 54.525819 0 0 1 511.979521 544.586217zM180.984761 272.341106L511.979521 429.966801l331.058757-157.625695L511.979521 114.587417 180.984761 272.341106z m354.417823 746.402144l457.517699-217.911283a54.397824 54.397824 0 1 0-46.718131-98.236071L511.979521 909.371625l-434.158634-206.775729a54.397824 54.397824 0 0 0-46.718131 98.236071l457.581697 217.911283a54.525819 54.525819 0 0 0 46.718131 0z m0-239.73441l457.517699-217.847286a54.397824 54.397824 0 1 0-46.718131-98.300068L511.979521 669.573217l-434.158634-206.711731a54.397824 54.397824 0 0 0-46.718131 98.300068l457.581697 217.847286a54.525819 54.525819 0 0 0 46.718131 0z"
+                p-id="9073"></path>
+            </svg>
+            <span>AI生成</span>
+          </div> -->
+        </div>
+        <el-input type="textarea" rows="3" resize="none" v-model="jsonData.evaluationCriteria" :placeholder="lang.ssEnterEvaluationStandard"
+          @input="setTestJson()"></el-input>
+      </div>
+    </div>
+
+  </div>
+</template>
+
+<script>
+import { uploadOneFile } from '@/components/tools/uploadFile'
+export default {
+  emits: ['setTestJson', 'save'],
+  props: {
+    workData: {
+      type: Object,
+      default: () => { }
+    }
+  },
+  data() {
+    return {
+      jsonData: {}
+    }
+  },
+  watch: {
+    workData: {
+      handler(newVal, oldVal) {
+        if (newVal.type == 84) {
+          if (JSON.stringify(newVal.json) != JSON.stringify(this.jsonData)) {
+            let _data = JSON.parse(JSON.stringify(newVal.json))
+
+            if(!_data.imageList){
+              _data.imageList = [];
+            }
+            if(!_data.evaluationCriteria){
+              _data.evaluationCriteria = '';
+            }
+
+            this.jsonData = _data;
+          }
+        }
+      },
+      deep: true,
+      immediate: true
+    }
+  },
+  methods: {
+    // 更新题目
+    setTestJson() {
+      // console.log(this.jsonData)
+      // this.jsonData.testCount = this.jsonData.testJson.length;
+      this.$emit('setTestJson', this.jsonData);
+    },
+    //修改标题
+    setTestTitle( title) {
+      console.log('修改标题', title)
+      this.jsonData.answerQ = title;
+      // let test = this.jsonData..find(item => item.id == id);
+      // if (test) {
+      //   test.teststitle = title;
+      // }
+      this.setTestJson();
+    },
+    // 上传图片
+    uploadImage(id) {
+      const input = document.createElement('input');
+      input.type = 'file';
+      input.accept = 'image/*';
+      input.click();
+      input.onchange = (e) => {
+        if (e.target.files[0]) {
+          uploadOneFile(e.target.files[0]).then(res => {
+            res.src = res.url
+            this.jsonData.imageList = [res];
+            this.$forceUpdate();
+            // this.jsonData.testJson.find(item => item.id == id).timuList = [res];
+            this.setTestJson();
+          })
+        }
+      }
+    },
+    // 删除图片
+    delImage(index) {
+      this.jsonData.imageList.splice(index, 1);
+      // let test = this.jsonData.testJson.find(item => item.id == id);
+      // if (test) {
+      //   test.timuList.splice(index, 1);
+      // }
+      this.setTestJson();
+    },
+  },
+}
+</script>
+
+<style scoped>
+.setChoiceQuestion {
+  width: 100%;
+  height: auto;
+  background: #fff;
+  display: flex;
+  flex-direction: column;
+  overflow: auto;
+  padding: 4rem;
+  box-sizing: border-box;
+  gap: 2rem;
+}
+
+.addTest {
+  border-radius: 5px;
+  border: 1px dashed #d1d5db;
+  padding: .5rem 1rem;
+  height: fit-content;
+  width: fit-content;
+  display: flex;
+  align-items: center;
+  gap: .5rem;
+  cursor: pointer;
+}
+
+.addTest>svg {
+  width: .7rem;
+  height: .7rem;
+  fill: #6E7583;
+}
+
+.addTest>span {
+  font-weight: bold;
+  font-size: .9rem;
+  color: #6b7280;
+}
+
+.testItem {
+  width: 100%;
+  height: auto;
+  display: flex;
+  align-items: center;
+  gap: 1rem;
+  flex-direction: column;
+}
+
+.ti_header {
+  width: 100%;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+
+}
+
+.ti_header>span {
+  font-size: 1rem;
+  font-weight: 600;
+  color: #ff9300;
+}
+
+.ti_h_edit {
+  display: flex;
+  align-items: center;
+  gap: 1rem;
+}
+
+.ti_h_edit>span {
+  width: 1.5rem;
+  height: 1.5rem;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: #F9FAFB;
+  border-radius: 4px;
+  cursor: pointer;
+}
+
+.ti_h_edit>span>svg {
+  width: 1rem;
+  height: 1rem;
+  fill: #6B7280;
+}
+
+.ti_title {
+  width: 100%;
+  height: fit-content;
+  padding-bottom: 2rem;
+  min-height: 4rem;
+  background: #FAFBFC;
+  border-radius: 8px;
+  border: 1px solid #E5E7EB;
+  position: relative;
+}
+
+.ti_t_input {
+  width: 100%;
+  height: 100%;
+  padding: 1rem;
+  box-sizing: border-box;
+  border: none;
+  outline: none;
+  background: none;
+  font-size: 1.2rem;
+  resize: none;
+  /* max-height: 10rem; */
+  overflow: auto;
+  /* padding-right: 2rem; */
+}
+
+.ti_t_input /deep/ .el-textarea__inner {
+  padding: 0;
+  width: 100%;
+  height: 100%;
+  box-sizing: border-box;
+  border: none;
+  outline: none;
+  background: none;
+  font-size: 1.2rem;
+  resize: none;
+  /* max-height: 10rem; */
+  overflow: auto;
+}
+
+.uploadImage {
+  position: absolute;
+  bottom: 0.5rem;
+  right: 0.5rem;
+  background: #fff;
+  border-radius: .5rem;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  border: 1px solid #E5E7EB;
+  cursor: pointer;
+  padding: .5rem;
+}
+
+.uploadImage>svg {
+  width: 1rem;
+  height: 1rem;
+}
+
+.checkList {
+  width: 100%;
+  height: auto;
+  display: flex;
+  flex-direction: column;
+  gap: 1rem;
+  padding-left: 4rem;
+}
+
+.checkItem {
+  width: 100%;
+  height: auto;
+  display: flex;
+  align-items: center;
+  justify-content: flex-start;
+  gap: .5rem;
+
+}
+
+.check {
+  width: 1.1rem;
+  height: 1.1rem;
+  border: solid 1px #D1D5DB;
+  border-radius: .25rem;
+  cursor: pointer;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+
+.checked {
+  background: #FF9300;
+}
+
+.checked>svg {
+  width: .7rem;
+  height: .7rem;
+  fill: #fff;
+}
+
+.checkItem>.el-input {
+  width: calc(100% - 10rem);
+}
+
+.addCheck {
+  border-radius: 5px;
+  border: 1px dashed #d1d5db;
+  padding: .3rem .8rem;
+  height: fit-content;
+  width: fit-content;
+  display: flex;
+  align-items: center;
+  gap: .5rem;
+  cursor: pointer;
+  margin-left: 2.2rem;
+}
+
+.addCheck>svg {
+  width: .5rem;
+  height: .5rem;
+  fill: #6E7583;
+}
+
+.addCheck>span {
+  font-weight: bold;
+  font-size: .8rem;
+  color: #6b7280;
+}
+
+.btn {
+  display: flex;
+  align-items: center;
+  gap: .25em;
+}
+
+.btn>div {
+  width: 1.5rem;
+  height: 1.5rem;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  background: #F9FAFB;
+  border-radius: .4px;
+  cursor: pointer;
+}
+
+.btn>div>svg {
+  width: 1rem;
+  height: 1rem;
+  fill: #6B7280;
+}
+
+.explanation {
+  width: 100%;
+  height: auto;
+  padding: 1rem;
+  background: #FAFBFC;
+  border-radius: 5px;
+  display: flex;
+  flex-direction: column;
+  gap: 1rem;
+  border-radius: .8rem;
+}
+
+.e_header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+}
+
+.e_header>span {
+  color: #6b7280;
+  font-size: .8rem;
+  font-weight: 500;
+}
+
+.e_header>div {
+  display: flex;
+  align-items: center;
+  gap: .5rem;
+  background: #ff9300;
+  color: #fff;
+  padding: .3rem .8rem;
+  font-size: .8rem;
+  font-weight: 500;
+  border-radius: .5rem;
+  cursor: pointer;
+}
+
+.e_header>div>svg {
+  width: .8rem;
+  height: .8rem;
+  fill: #fff;
+}
+
+.imageList {
+  width: 100%;
+  height: auto;
+  display: flex;
+  flex-wrap: wrap;
+  gap: 1rem;
+}
+.imageList>div{
+  width: 100px;
+  height: 100px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  /* overflow: hidden; */
+  border-radius: .5rem;
+  position: relative;
+
+}
+
+.imageList>div:hover>svg{
+  display: flex;
+}
+
+.imageList>div>svg{
+  width: 1rem;
+  height: 1rem;
+  position: absolute;
+  top: -.3rem;
+  right: -.3rem;
+  font-weight: 500;
+  border-radius: .5rem;
+  cursor: pointer;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  display: none;
+}
+
+</style>

+ 43 - 3
src/components/pages/workPage/index_new.vue

@@ -8,6 +8,10 @@
       @changeWorkData="changeWorkData" />
     <photo ref="photoRef" v-if="workData.type === '79'" :workData="workData"
       @changeWorkData="changeWorkData" />
+    <kwlPage ref="kwlPageRef" v-if="workData.type === '831'" :workData="workData"
+      @changeWorkData="changeWorkData" />
+    <file ref="fileRef" v-if="workData.type === '84'" :workData="workData"
+      @changeWorkData="changeWorkData" />
   </div>
 </template>
 
@@ -18,12 +22,16 @@ import questionsAndAnswers from "./components/questionsAndAnswers.vue";
 import choiceQuestion from "./components/choiceQuestion.vue";
 import voteQuestion from "./components/voteQuestion.vue";
 import photo from "./components/photo.vue";
+import kwlPage from "./components/kwlPage.vue";
+import file from "./components/file.vue";
 export default {
   components: {
     questionsAndAnswers,
     choiceQuestion,
     voteQuestion,
-    photo
+    photo,
+    kwlPage,
+    file,
   },
   data() {
     return {
@@ -128,6 +136,8 @@ export default {
               });
             }else if(_data.type == 79){
               _data.json.fileList = [];
+            }else if(_data.type == 84){
+              _data.json.fileList = [];
             }
 
             this.workData = _data;
@@ -279,9 +289,34 @@ export default {
             content: encodeURIComponent(JSON.stringify(this.workData.json)),
             type: "23"
           });
+        } else if (this.workData.type == "831") {
+          //跨学科kwl题
+          params.push({
+            uid: this.userid,
+            cid: this.courseid,
+            stage: 0,
+            task: task,
+            tool: 0,
+            atool: "831",
+            content: encodeURIComponent(JSON.stringify(this.workData.json)),
+            type: "831"
+          });
+        } else if (this.workData.type == "84") {
+          //文件题
+          params.push({
+            uid: this.userid,
+            cid: this.courseid,
+            stage: 0,
+            task: task,
+            tool: 0,
+            atool: "84",
+            content: encodeURIComponent(JSON.stringify(this.workData.json)),
+            type: "84"
+          });
         }
+
         if (
-          ["3", "8", "22", "23"].includes(params[0].type) &&
+          ["3", "8", "22", "23", "831", "84"].includes(params[0].type) && 
           params[0].uid &&
           params[0].cid
         ) {
@@ -348,7 +383,7 @@ export default {
             this.loading = false;
             if (_data.length) {
               _data = _data[0];
-              if (["15", "45", "78", "79"].includes(_data.atool)) {
+              if (["15", "45", "78", "79", "831", "84"].includes(_data.atool)) {
                 let _work = JSON.parse(decodeURIComponent(_data.content));
                 this.studentWork = _work;
                 this.studentWorkToWorkData();
@@ -399,6 +434,11 @@ export default {
           });
         }else if(this.workData.type == "79"){
           this.workData.json.fileList = _work.fileList;
+        }else if(this.workData.type == "831"){
+          this.workData.json = _work;
+        }else if(this.workData.type == "84"){
+          //文件题
+          this.workData.json.fileList = _work.fileList;
         }
         this.$forceUpdate();
       }

+ 11 - 1
src/components/pages/workPage/setIndex.vue

@@ -11,6 +11,8 @@
     <setQuestionsAndAnswers :workData="workData" v-if="workData.type == 15" @setTestJson="setTestJson" @save="saveTest"/>
     <setVoteQuestion :workData="workData" v-if="workData.type == 78" @setTestJson="setTestJson" @save="saveTest"/>
     <setPhoto :workData="workData" v-if="workData.type == 79" @setTestJson="setTestJson" @save="saveTest"/>
+    <kwlPage :workData="workData" v-if="workData.type == 831" @setTestJson="setTestJson" @save="saveTest"/>
+    <setFile :workData="workData" v-if="workData.type == 84" @setTestJson="setTestJson" @save="saveTest"/>
   </div>
 </template>
 
@@ -19,6 +21,8 @@ import setChoiceQuestion from './components/setChoiceQuestion.vue'
 import setQuestionsAndAnswers from './components/setQuestionsAndAnswers.vue'
 import setVoteQuestion from './components/setVoteQuestion.vue'
 import setPhoto from './components/setPhoto.vue'
+import kwlPage from './components/kwlPage.vue'
+import setFile from './components/setFile.vue'
 
 function debounce(func, wait) {
   let timeout;
@@ -35,7 +39,9 @@ export default {
     setChoiceQuestion,
     setQuestionsAndAnswers,
     setVoteQuestion,
-    setPhoto
+    setPhoto,
+    kwlPage,
+    setFile,
   },
   data() {
     return {
@@ -91,6 +97,10 @@ export default {
             }else if (_data.type == 79) {
               _data.json.answer = "";
               _data.json.fileList = [];
+            }else if (_data.type == 84) {
+              //文件题
+              _data.json.answer = "";
+              _data.json.fileList = [];
             }
             this.type = _data.type;
 

+ 32 - 1
src/lang/cn.json

@@ -2067,5 +2067,36 @@
   "ssPleaseWait": "请稍后再试",
   "ssFileTooBig":"上传文件大小不能超过20MB, 请重新上传",
   "ssAIAssistantTip":"开启后,学生提交作业将弹出 AI 助手,并基于题目引导学生检查与反思。",
-  "ssCreateCourseLimit":"创建课程每学期仅创建5次"
+  "ssCreateCourseLimit":"创建课程每学期仅创建5次",
+  "ssKWLTitle":"KWL表格",
+  "ssKWLSubtitle":"写下你已经知道的、还想知道的,学完后再回来看看自己学到了什么",
+  "ssKWLHowTo":"怎么做",
+  "ssKWLStep1":"在K栏填写:我已经知道什么(Know)",
+  "ssKWLStep2":"在W栏填写:我想知道什么(Want to know)",
+  "ssKWLStep3":"学习后在L栏填写:我学到了什么(Learned)",
+  "ssKWLStep4":"反思:对比K和L栏,总结学习收获",
+  "ssKWLTaskDesc":"任务说明",
+  "ssKWLColKnow":"K - 已知",
+  "ssKWLColWant":"W - 想知",
+  "ssKWLColLearn":"L - 已学",
+  "ssKWLAddContent":"点击下方添加内容",
+  "ssKWLAddPlaceholder":"添加...",
+  "ssKWLReflection":"学习反思",
+  "ssKWLReflectionPH":"回顾整个学习过程,你有什么新的发现或感想?",
+  "ssKWLVoiceInput":"语音输入",
+  "ssKWLStartRec":"开始录音",
+  "ssKWLStopRec":"停止录音",
+  "ssKWLRecordShort":"录音时间过短",
+  "ssKWLNoPermStop":"未获得麦克风权限,录音已停止",
+  "ssKWLNoMicPerm":"未获得麦克风权限",
+  "ssKWLRecServiceNotReady":"录音服务未就绪",
+  "ssKWLRecStartFail":"启动录音失败",
+  "ssKWLRecStopped":"录音已停止",
+  "ssFileTip": "文件上传指引",
+  "ssFile": "文件上传",
+  "ssUpload": "上传",
+  "ssDownloadFile": "下载文件",
+  "ssUnsupportedFile": "不支持预览此文件类型,请点击下载",
+  "ssFilePreviewTitle": "文件预览",
+  "ssOnlyUploadOneFile": "仅支持上传一个文件"
 }

+ 32 - 1
src/lang/en.json

@@ -2066,5 +2066,36 @@
   "ssPleaseWait": "Please wait, try again later.",
   "ssFileTooBig":"The uploaded file size cannot exceed 20MB, please re-upload.",
   "ssAIAssistantTip":"After enabling, students will be able to use the AI assistant and check with the help of the questions.",
-  "ssCreateCourseLimit":"Create course limit semester 5 times"
+  "ssCreateCourseLimit":"Create course limit semester 5 times",
+  "ssKWLTitle":"KWL Chart",
+  "ssKWLSubtitle":"Write down what you already know, what you want to know, and come back after learning to see what you have learned",
+  "ssKWLHowTo":"How to do",
+  "ssKWLStep1":"Fill in column K: What I already know (Know)",
+  "ssKWLStep2":"Fill in column W: What I want to know (Want to know)",
+  "ssKWLStep3":"After learning, fill in column L: What I learned (Learned)",
+  "ssKWLStep4":"Reflection: Compare columns K and L, summarize learning gains",
+  "ssKWLTaskDesc":"Task Description",
+  "ssKWLColKnow":"K - Know",
+  "ssKWLColWant":"W - Want",
+  "ssKWLColLearn":"L - Learned",
+  "ssKWLAddContent":"Click below to add content",
+  "ssKWLAddPlaceholder":"Add...",
+  "ssKWLReflection":"Learning Reflection",
+  "ssKWLReflectionPH":"Looking back on the whole learning process, what new discoveries or thoughts do you have?",
+  "ssKWLVoiceInput":"Voice Input",
+  "ssKWLStartRec":"Start Recording",
+  "ssKWLStopRec":"Stop Recording",
+  "ssKWLRecordShort":"Recording time is too short",
+  "ssKWLNoPermStop":"Microphone permission not granted, recording stopped",
+  "ssKWLNoMicPerm":"Microphone permission not granted",
+  "ssKWLRecServiceNotReady":"Recording service is not ready",
+  "ssKWLRecStartFail":"Failed to start recording",
+  "ssKWLRecStopped":"Recording stopped",
+  "ssFileTip": "File upload guidance",
+  "ssFile": "File upload",
+  "ssUpload": "Upload",
+  "ssDownloadFile": "Download File",
+  "ssUnsupportedFile": "This file type cannot be previewed, please click to download",
+  "ssFilePreviewTitle": "File Preview",
+  "ssOnlyUploadOneFile": "Only one file can be uploaded"
 }

+ 32 - 1
src/lang/hk.json

@@ -2067,6 +2067,37 @@
   "ssPleaseWait": "請稍後再試",
   "ssFileTooBig":"上傳文件大小不能超過20MB, 請重新上傳",
   "ssAIAssistantTip":"開啟後,學生提交作業將彈出 AI 助手,並根據題目引導學生檢查與反思。",
-  "ssCreateCourseLimit":"創建課程每学期僅創建5次"
+  "ssCreateCourseLimit":"創建課程每学期僅創建5次",
+  "ssKWLTitle":"KWL表格",
+  "ssKWLSubtitle":"寫下你已經知道的、還想知道的,學完後再回來看看自己學到了什麼",
+  "ssKWLHowTo":"怎麼做",
+  "ssKWLStep1":"在K欄填寫:我已經知道什麼(Know)",
+  "ssKWLStep2":"在W欄填寫:我想知道什麼(Want to know)",
+  "ssKWLStep3":"學習後在L欄填寫:我學到了什麼(Learned)",
+  "ssKWLStep4":"反思:對比K和L欄,總結學習收穫",
+  "ssKWLTaskDesc":"任務說明",
+  "ssKWLColKnow":"K - 已知",
+  "ssKWLColWant":"W - 想知",
+  "ssKWLColLearn":"L - 已學",
+  "ssKWLAddContent":"點擊下方添加內容",
+  "ssKWLAddPlaceholder":"添加...",
+  "ssKWLReflection":"學習反思",
+  "ssKWLReflectionPH":"回顧整個學習過程,你有什麼新的發現或感想?",
+  "ssKWLVoiceInput":"語音輸入",
+  "ssKWLStartRec":"開始錄音",
+  "ssKWLStopRec":"停止錄音",
+  "ssKWLRecordShort":"錄音時間過短",
+  "ssKWLNoPermStop":"未獲得麥克風權限,錄音已停止",
+  "ssKWLNoMicPerm":"未獲得麥克風權限",
+  "ssKWLRecServiceNotReady":"錄音服務未就緒",
+  "ssKWLRecStartFail":"啟動錄音失敗",
+  "ssKWLRecStopped":"錄音已停止",
+  "ssFileTip": "文件上傳指引",
+  "ssFile": "文件上傳",
+  "ssUpload": "上傳",
+  "ssDownloadFile": "下載文件",
+  "ssUnsupportedFile": "不支持預覽此文件類型,請點擊下載",
+  "ssFilePreviewTitle": "文件預覽",
+  "ssOnlyUploadOneFile": "僅支持上傳一個文件"
 }
 

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików