Browse Source

Merge branch 'lhd'

natasha 1 tuần trước cách đây
mục cha
commit
88288e5c93

+ 13 - 10
src/components/CommonPreview.vue

@@ -352,13 +352,7 @@
       class="remark-dialog"
       @close="dialogClose('')"
     >
-      <RichText
-        v-model="remark_content"
-        toolbar="fontselect fontsizeselect forecolor backcolor | underline | bold italic strikethrough alignleft aligncenter alignright image media link"
-        :wordlimit-num="false"
-        :height="240"
-        page-from="audit"
-      />
+      <Remark :remark="remark" :project-id="projectId" :courseware-id="select_node ? select_node : id"></Remark>
       <div slot="footer">
         <el-button @click="dialogClose('')">取消</el-button>
         <el-button type="primary" :loading="submit_loading" @click="addCoursewareAuditRemark(select_node)">
@@ -424,6 +418,7 @@ import AuditRemark from '@/components/AuditRemark.vue';
 import ExplanatoryNoteDialog from '@/components/ExplanatoryNoteDialog.vue';
 import SimAnswerPermissionControl from '@/components/SimAnswerPermissionControl.vue';
 import PreviewURL from '@/views/project_manage/common/PreviewURL.vue';
+import Remark from './Remark.vue';
 
 import {
   GetBookCoursewareInfo,
@@ -469,6 +464,7 @@ export default {
     VisNetwork,
     SimAnswerPermissionControl,
     PreviewURL,
+    Remark,
   },
   provide() {
     return {
@@ -568,7 +564,10 @@ export default {
       searchList: [],
       searchContent: '',
       visible: false,
-      remark_content: '',
+      remark: {
+        remark_content: '',
+        file_id_list: [],
+      },
       submit_loading: false,
       isTrue,
       menuPosition: {
@@ -872,7 +871,10 @@ export default {
       });
     },
     addRemark(selectNode, x, y, componentId, content_select) {
-      this.remark_content = '';
+      this.remark = {
+        remark_content: '',
+        file_id_list: [],
+      };
       this.visible = true;
       if (selectNode) {
         this.menuPosition = {
@@ -898,11 +900,12 @@ export default {
       this.submit_loading = true;
       AddCoursewareAuditRemark({
         courseware_id: id || this.id,
-        content: this.remark_content,
+        content: this.remark.remark_content,
         component_id: this.menuPosition.componentId,
         position_x: this.menuPosition.x,
         position_y: this.menuPosition.y,
         content_select: this.menuPosition.content_select,
+        file_id_list: this.remark.file_id_list,
       })
         .then(() => {
           this.submit_loading = false;

+ 85 - 0
src/components/Remark.vue

@@ -0,0 +1,85 @@
+<template>
+  <div class="remark-wrapper">
+    <RichText
+      v-model="remark.remark_content"
+      toolbar="fontselect fontsizeselect forecolor backcolor | underline | bold italic strikethrough alignleft aligncenter alignright image media link"
+      :wordlimit-num="false"
+      :height="240"
+      page-from="audit"
+    />
+    <SoundRecord @updateFileList="updateFileList"></SoundRecord>
+    <UploadFile
+      key="upload_remark"
+      :total-size="200"
+      :file-list="file_list"
+      :file-id-list="remark.file_id_list"
+      :label-text="labelText"
+      :accept-file-type="acceptFileType"
+      :icon-class="iconClass"
+      :limit="100"
+      :single-size="200"
+      :projectId="projectId"
+      :coursewareId="coursewareId"
+      :upload-tip="uploadTip"
+      @updateFileList="updateFileLists"
+    />
+  </div>
+</template>
+
+<script>
+import RichText from '@/components/RichText.vue';
+import SoundRecord from './SoundRecord.vue';
+import UploadFile from './UploadFile.vue';
+export default {
+  name: 'Remark',
+  components: { RichText, SoundRecord, UploadFile },
+  props: {
+    remark: {
+      type: Object,
+      default: {},
+    },
+    projectId: {
+      type: String,
+      default: '',
+    },
+    // 课件id
+    coursewareId: {
+      type: String,
+      default: '',
+    },
+  },
+  data() {
+    return {
+      file_list: [],
+      labelText: '',
+      acceptFileType: '.png,.jpg,.jpeg,.txt,.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.mp3,.mp4,.zip,.rar',
+      uploadTip: '支持上传jpg、png、mp3、mp4、zip、txt、doc、docx、xsl、xslx、pdf、ppt等格式文件',
+      iconClass: '',
+    };
+  },
+  computed: {},
+  watch: {},
+  mounted() {},
+  methods: {
+    updateFileList(id) {
+      this.remark.file_id_list.push(id);
+      this.file_list.push({
+        id,
+        file_name: '录制音频',
+      });
+    },
+    updateFileLists({ file_list, file_id_list, file_info_list, file_info }) {
+      this.file_list = file_list;
+      this.remark.file_id_list = file_id_list;
+    },
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+.remark-wrapper {
+  :deep .sound-record-wrapper {
+    margin: 20px 0;
+  }
+}
+</style>

+ 169 - 0
src/components/SoundRecord.vue

@@ -0,0 +1,169 @@
+<template>
+  <div class="sound-record-wrapper">
+    <div class="sound-microphone" @click="microphone">
+      <img v-if="microphoneStatus" :src="require('@/assets/fill/record-ing.png')" class="voice-play" />
+      <SvgIcon v-else icon-class="mic-line" class="record" />
+      <span class="auto-btn">录制音频</span>
+      <span>{{ secondFormatConversion(recordTimes) }}</span>
+    </div>
+  </div>
+</template>
+
+<script>
+import Recorder from 'js-audio-recorder'; // 录音插件
+import { secondFormatConversion } from '@/utils/transform';
+import { GetStaticResources, GetFileStoreInfo } from '@/api/app';
+export default {
+  name: 'SoundRecord',
+  props: {},
+  data() {
+    return {
+      secondFormatConversion,
+      recorder: new Recorder({
+        sampleBits: 16, // 采样位数,支持 8 或 16,默认是16
+        sampleRate: 16000, // 采样率,支持 11025、16000、22050、24000、44100、48000,根据浏览器默认值,我的chrome是48000
+        numChannels: 1, // 声道,支持 1 或 2, 默认是1
+      }),
+      timer: null, // 计时器
+      microphoneStatus: false, // 是否录音
+      hasMicro: '', // 录音后的样式class
+      audio: {
+        paused: true,
+      },
+      playtime: 0, // 播放时间
+      recordTimes: 0,
+      file_url: '',
+      recordTime: 0,
+    };
+  },
+  computed: {},
+  watch: {},
+  mounted() {
+    this.$refs.audio.addEventListener('ended', () => {
+      this.audio.paused = true;
+    });
+    this.$refs.audio.addEventListener('pause', () => {
+      this.audio.paused = true;
+    });
+    this.$refs.audio.addEventListener('play', () => {
+      this.audio.paused = false;
+    });
+  },
+  methods: {
+    // 开始录音
+    microphone() {
+      let _this = this;
+      if (this.microphoneStatus) {
+        this.handleStop();
+      } else {
+        this.hasMicro = '';
+        // 开始录音
+        this.recorder.start();
+        this.microphoneStatus = true;
+        this.recordTimes = 0;
+        clearInterval(this.timer);
+        this.timer = setInterval(() => {
+          // 每条录音不能超过10分钟
+          if (_this.recordTimes < 600) {
+            _this.recordTimes += 1;
+          } else {
+            _this.handleStop();
+          }
+        }, 1000);
+      }
+    },
+    handleStop() {
+      this.hasMicro = 'normal';
+      this.recorder.stop();
+      clearInterval(this.timer);
+      // 录音结束,获取取录音数据
+      let wav = this.recorder.getWAVBlob(); // 获取 WAV 数据
+      this.microphoneStatus = false;
+      let reader = new window.FileReader();
+      reader.readAsDataURL(wav);
+      reader.onloadend = () => {
+        let MethodName = 'file_store_manager-SaveFileByteBase64Text';
+        let data = {
+          base64_text: reader.result.replace('data:audio/wav;base64,', ''),
+          file_suffix_name: 'mp3',
+        };
+        GetStaticResources(MethodName, data).then((res) => {
+          if (res.status === 1) {
+            this.$emit('updateFileList', res.file_id);
+          }
+        });
+      };
+    },
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+.sound-record-wrapper {
+  display: flex;
+  column-gap: 12px;
+  align-items: center;
+  width: 200px;
+  padding: 5px 12px;
+  background: $fill-color;
+  border-radius: 2px;
+
+  .audio-play-btn {
+    cursor: pointer;
+
+    &.not-url {
+      color: #a1a1a1;
+      cursor: not-allowed;
+    }
+  }
+
+  .record-time {
+    flex: 1;
+    min-width: 52px;
+    font-size: 14px;
+    font-weight: 400;
+    line-height: 22px;
+    color: #a1a1a1;
+
+    &.record-ing {
+      color: #000;
+    }
+  }
+
+  .record {
+    cursor: pointer;
+  }
+
+  .voice-play {
+    width: 16px;
+    height: 16px;
+  }
+
+  .delete-btn {
+    margin-left: 12px;
+    color: #4e4e4e;
+    cursor: pointer;
+
+    &.not-url {
+      color: #a1a1a1;
+      cursor: not-allowed;
+    }
+  }
+
+  .auto-btn {
+    font-size: 16px;
+    font-weight: 400;
+    line-height: 22px;
+    color: #1d2129;
+    cursor: pointer;
+  }
+
+  .sound-microphone {
+    display: flex;
+    column-gap: 12px;
+    align-items: center;
+    justify-content: space-between;
+    width: 100%;
+  }
+}
+</style>

+ 73 - 73
src/components/UploadFile.vue

@@ -1,7 +1,7 @@
 <template>
   <div class="upload-file">
     <div class="file-area">
-      <span class="label-text">{{ labelText }}</span>
+      <span class="label-text" v-if="labelText">{{ labelText }}</span>
       <div class="upload-box">
         <el-upload
           ref="upload"
@@ -16,14 +16,14 @@
           :on-exceed="handleExceed"
           :limit="limit"
         >
-          <el-button>{{ type === 'h5_games' ? '选择Zip压缩包或单个html文件' : '选取' + labelText + '文件' }}</el-button>
+          <el-button>上传附件</el-button>
         </el-upload>
         <el-button size="small" type="primary" @click="uploadFiles"> 上传 </el-button>
         <el-button size="small" type="primary" @click="useResource"> 使用资源 </el-button>
       </div>
     </div>
-    <el-divider v-if="showDivider" />
     <div class="upload-tip">{{ uploadTip }}</div>
+    <el-divider v-if="showDivider" />
     <ul v-if="fileList.length > 0" slot="file-list" class="file-list">
       <li v-for="(file, i) in fileList" :key="i">
         <div class="file-name">
@@ -72,12 +72,12 @@
         </template>
       </li>
     </ul>
-
     <SelectResource
       :visible.sync="visibleResource"
       :project-id="projectId"
       :accept="accept"
       :courseware-id="coursewareId"
+      from-page="remark"
       @selectResource="selectResource"
     />
   </div>
@@ -202,12 +202,12 @@ export default {
   computed: {
     accept() {
       let accept = '*';
-      if (this.acceptFileType.includes('.mp3')) {
-        accept = 'audio';
+      if (this.acceptFileType.includes('.jpg')) {
+        accept = 'image';
       } else if (this.acceptFileType.includes('.mp4')) {
         accept = 'video';
-      } else if (this.acceptFileType.includes('.jpg')) {
-        accept = 'image';
+      } else if (this.acceptFileType.includes('.mp3')) {
+        accept = 'audio';
       } else if (this.acceptFileType.includes('.zip')) {
         accept = 'h5_game';
       } else if (this.acceptFileType.includes('.txt')) {
@@ -404,6 +404,8 @@ export default {
             }
           });
         });
+      // 选中文件后自动上传
+      this.uploadFiles();
     },
 
     // 显示弹窗
@@ -446,98 +448,96 @@ export default {
 </script>
 
 <style lang="scss" scoped>
-.module-content {
-  .file-area {
-    display: flex;
-    column-gap: 16px;
-    align-items: center;
+.file-area {
+  display: flex;
+  column-gap: 16px;
+  align-items: center;
 
-    .label-text {
-      font-size: 14px;
-      color: $font-light-color;
-    }
-
-    div {
-      flex: 1;
-    }
+  .label-text {
+    font-size: 14px;
+    color: $font-light-color;
   }
 
-  .el-divider {
-    margin: 16px 0;
+  div {
+    flex: 1;
   }
+}
 
-  .upload-tip {
-    margin-bottom: 16px;
-    font-size: 12px;
-    color: #86909c;
-  }
+.el-divider {
+  margin: 16px 0;
+}
 
-  .upload-box {
-    display: flex;
-    justify-content: space-between;
+.upload-tip {
+  margin-bottom: 16px;
+  font-size: 12px;
+  color: #86909c;
+}
 
-    .el-button + .el-button {
-      margin-left: 2px;
-    }
+.upload-box {
+  display: flex;
+  justify-content: space-between;
 
-    .file-uploader {
-      flex: 1;
+  .el-button + .el-button {
+    margin-left: 2px;
+  }
 
-      :deep .el-upload {
-        &--text {
-          width: 100%;
-          background-color: $fill-color;
-          border-radius: 2px 0 0 2px;
+  .file-uploader {
+    flex: 1;
 
-          .el-button {
-            width: 100%;
-            color: #86909c;
-            text-align: left;
-          }
+    :deep .el-upload {
+      &--text {
+        width: 100%;
+        background-color: $fill-color;
+        border-radius: 2px 0 0 2px;
+
+        .el-button {
+          width: 100%;
+          color: #86909c;
+          text-align: left;
         }
       }
     }
-
-    .el-button {
-      border-radius: 0 2px 2px 0;
-    }
   }
 
-  .old_file_list {
-    margin-top: 16px;
+  .el-button {
+    border-radius: 0 2px 2px 0;
   }
+}
 
-  .file-list {
+.old_file_list {
+  margin-top: 16px;
+}
+
+.file-list {
+  display: flex;
+  flex-direction: column;
+  row-gap: 16px;
+
+  li {
     display: flex;
-    flex-direction: column;
-    row-gap: 16px;
+    column-gap: 12px;
+    align-items: center;
 
-    li {
+    .file-name {
       display: flex;
-      column-gap: 12px;
+      column-gap: 14px;
       align-items: center;
+      justify-content: space-between;
+      max-width: 500px; // 360px有点窄
+      padding: 8px 12px;
+      font-size: 14px;
+      color: #1d2129;
+      background-color: #f7f8fa;
 
-      .file-name {
+      span {
         display: flex;
         column-gap: 14px;
         align-items: center;
-        justify-content: space-between;
-        max-width: 500px; // 360px有点窄
-        padding: 8px 12px;
-        font-size: 14px;
-        color: #1d2129;
-        background-color: #f7f8fa;
-
-        span {
-          display: flex;
-          column-gap: 14px;
-          align-items: center;
-        }
       }
+    }
 
-      .svg-icon {
-        cursor: pointer;
-      }
+    .svg-icon {
+      cursor: pointer;
     }
   }
 }

+ 29 - 6
src/views/book/courseware/create/components/base/common/SelectResource.vue

@@ -12,12 +12,25 @@
         <el-input v-model="search_content" />
         <el-button type="primary" @click="queryList()">查询</el-button>
       </div>
-      <div class="search-right">
-        <label>排序:</label>
-        <el-select v-model="sort_value" placeholder="请选择">
-          <el-option v-for="item in sort_list" :key="item.value" :label="item.label" :value="item.value" />
-        </el-select>
-        <SvgIcon :icon-class="isDesc ? 'sort-down' : 'sort-up'" size="16" style="cursor: pointer" @click="changeSort" />
+      <div class="search-content-box">
+        <div class="search-right" v-if="fromPage">
+          <label>类型:</label>
+          <el-select v-model="type_index" placeholder="请选择" @change="queryList()">
+            <el-option v-for="item in type_list" :key="item.value" :label="item.label" :value="item.value" />
+          </el-select>
+        </div>
+        <div class="search-right">
+          <label>排序:</label>
+          <el-select v-model="sort_value" placeholder="请选择">
+            <el-option v-for="item in sort_list" :key="item.value" :label="item.label" :value="item.value" />
+          </el-select>
+          <SvgIcon
+            :icon-class="isDesc ? 'sort-down' : 'sort-up'"
+            size="16"
+            style="cursor: pointer"
+            @click="changeSort"
+          />
+        </div>
       </div>
     </div>
 
@@ -133,6 +146,11 @@ export default {
       type: String,
       default: '*',
     },
+    // 父级页面
+    fromPage: {
+      type: String,
+      default: '',
+    },
   },
   data() {
     const type_list = [
@@ -348,6 +366,11 @@ export default {
     justify-content: space-between;
     padding: 5px;
 
+    .search-content-box {
+      display: flex;
+      gap: 10px;
+    }
+
     .search-left {
       display: flex;
       flex-flow: wrap;

+ 7 - 1
src/views/book/courseware/create/components/common/SelectUpload.vue

@@ -15,7 +15,7 @@
       >
         <el-button>{{ showText }}</el-button>
       </el-upload>
-      <el-button size="small" class="upload-button" type="primary" @click="uploadFiles">上传</el-button>
+      <el-button size="small" class="upload-button" type="primary" @click="uploadFiles">本地上传</el-button>
       <el-button v-if="isShowResource" size="small" type="primary" @click="useResource">使用资源</el-button>
     </div>
     <SelectResource
@@ -102,6 +102,12 @@ export default {
   methods: {
     onFileChange(file, fileList) {
       this.fileList = fileList;
+      fileList.forEach((file) => {
+        if (!file.progress || file.progress <= 0) file.progress = 0;
+      });
+
+      // 选中文件后自动上传
+      this.uploadFiles();
     },
     uploadFiles() {
       const list = this.$refs.upload.uploadFiles;

+ 3 - 3
src/views/book/courseware/create/components/question/image_text/ImageText.vue

@@ -25,7 +25,7 @@
         />
       </div>
       <SelectUpload label="音频" type="audio" width="500px" @uploadSuccess="uploadAudioSuccess" />
-      <div v-if="data.mp3_list.length > 0" class="upload-file">
+      <div v-if="data.mp3_list.length > 0" class="upload-files">
         <div class="file-name">
           <span>
             <SvgIcon icon-class="mp3" size="12" />
@@ -654,8 +654,8 @@ export default {
 };
 </script>
 <style lang="scss" scoped>
-.upload-file {
-  // display: flex;
+.upload-files {
+  display: flex;
   column-gap: 12px;
   align-items: center;
   margin: 8px 0;

+ 2 - 1
src/views/personal_workbench/project/components/UploadFile.vue

@@ -18,7 +18,7 @@
         >
           <el-button>选取{{ labelText }}文件</el-button>
         </el-upload>
-        <el-button size="small" type="primary" @click="uploadFiles">上传</el-button>
+        <el-button size="small" type="primary" @click="uploadFiles">本地上传</el-button>
       </div>
     </div>
     <el-divider />
@@ -161,6 +161,7 @@ export default {
         return;
       }
       this.content.file_list = [...this.content.file_list, ...files];
+      this.uploadFiles();
     },
 
     handleExceed(files, fileList) {