소스 검색

资源上传

natasha 1 주 전
부모
커밋
1e93da1d6d

+ 16 - 0
src/api/book.js

@@ -123,3 +123,19 @@ export function ContentGetCoursewareComponentContent(data) {
 export function ContentGetCoursewareContent_View(data) {
   return http.post(`${process.env.VUE_APP_EepServer}?MethodName=book_content_manager-GetCoursewareContent_View`, data);
 }
+
+/**
+ * @description 新增项目资源
+ * @param {object} data
+ */
+export function MangerAddResource(data) {
+  return http.post(`${process.env.VUE_APP_EepServer}?MethodName=project_resource_manager-AddResource`, data);
+}
+
+/**
+ * @description 删除项目资源
+ * @param {object} data
+ */
+export function MangerDeleteResource(data) {
+  return http.post(`${process.env.VUE_APP_EepServer}?MethodName=project_resource_manager-DeleteResource`, data);
+}

+ 170 - 12
src/views/personal_workbench/project/ProductionResourceManage.vue

@@ -31,7 +31,7 @@
               >{{ item.label }}</label
             >
             <el-input v-model="search_content"></el-input>
-            <el-button type="primary" @click="queryList">查询</el-button>
+            <el-button type="primary" @click="queryList('')">查询</el-button>
           </div>
           <div class="search-right">
             <label>排序:</label>
@@ -47,24 +47,76 @@
             />
           </div>
         </div>
-        <div class="sources-box" :style="{ height: height + 'px' }" v-if="height > 0"></div>
+        <div class="sources-box" :style="{ height: height + 'px' }" v-if="height > 0" v-loading="boxLoading">
+          <div v-for="(item, index) in list" :key="index" @click="selectSourceNode(item)" class="sources-item">
+            <template v-if="type_index === 0">
+              <el-image :src="item.file_url" :preview-src-list="[item.file_url]" fit="contain"></el-image
+            ></template>
+            <template v-if="type_index === 1">
+              <AudioLine
+                ref="audioLine"
+                :audio-id="'resource-audio-' + index"
+                :mp3="item.file_url"
+                :get-cur-time="getCurTime"
+                :width="200"
+              />
+            </template>
+            <template v-if="type_index === 2">
+              <video controls :src="item.file_url" width="100%" height="140px"></video>
+            </template>
+
+            <p class="name">{{ item.name }}</p>
+            <b class="label">{{ item.label }}</b>
+          </div>
+        </div>
         <PaginationPage ref="pagination" :total="total" @getList="queryList" />
       </div>
     </div>
+    <!-- 上传 -->
+    <el-dialog
+      :visible.sync="sourceAddFlag"
+      width="500px"
+      append-to-body
+      :show-close="true"
+      title="上传资源"
+      :close-on-click-modal="false"
+      class="module-content"
+    >
+      <UploadFile
+        key="upload_resources"
+        :type="'upload_resources_manager'"
+        :total-size="20000"
+        :file-list="file_list"
+        :file-id-list="file_id_list"
+        :label-text="labelText"
+        :accept-file-type="acceptFileType"
+        :icon-class="iconClass"
+        :limit="limit"
+        :single-size="200"
+        :uploadTip="uploadTip"
+        @updateFileList="updateFileList"
+      />
+      <footer style="text-align: right">
+        <el-button @click="handleCancle">取 消</el-button>
+        <el-button :loading="loading" type="primary" @click="submitAdd">确 定</el-button>
+      </footer>
+    </el-dialog>
   </div>
 </template>
 
 <script>
 import MenuPage from '../common/menu.vue';
 import MenuTree from './components/MenuTree.vue';
-import { ChapterGetBookChapterStruct } from '@/api/book';
+import { ChapterGetBookChapterStruct, MangerAddResource, MangerDeleteResource } from '@/api/book';
 import { GetProjectBaseInfo } from '@/api/project';
 import PaginationPage from '@/components/PaginationPage.vue';
 import { PageQueryProjectResourceList } from '@/api/list';
+import UploadFile from './components/UploadFile.vue';
+import AudioLine from './components/AudioLine.vue';
 
 export default {
   name: 'ProjectResourceManager',
-  components: { MenuPage, MenuTree, PaginationPage },
+  components: { MenuPage, MenuTree, PaginationPage, UploadFile, AudioLine },
   data() {
     return {
       book_id: this.$route.params.id,
@@ -121,6 +173,17 @@ export default {
       height: 0,
       page_capacity: 10,
       cur_page: 1,
+      sourceAddFlag: false, // 新增flag
+      labelText: '资源',
+      acceptFileType: '.jpg,.png,.jpeg',
+      iconClass: '',
+      file_id_list: [],
+      file_list: [],
+      loading: false,
+      acceptFileTypeList: ['.jpg,.png,.jpeg', '.mp3', '.mp4', '*', '*', '*'],
+      limit: 10,
+      uploadTip: '',
+      boxLoading: false,
     };
   },
   created() {
@@ -138,6 +201,7 @@ export default {
   methods: {
     selectNode(nodeId) {
       this.select_node = nodeId;
+      this.queryList('');
     },
     /**
      * 获取项目基本信息
@@ -193,13 +257,39 @@ export default {
       }
     },
     // 上传
-    handleAdd() {},
+    handleAdd() {
+      this.limit = 10;
+      this.uploadTip = '最多上传 10 个文件,多文件分批上传';
+      this.sourceAddFlag = true;
+    },
     // 删除
-    handleDelete() {},
+    handleDelete() {
+      if (this.select_sources_id) {
+        this.$confirm('确定要删除此条资源吗?', '提示', {
+          confirmButtonText: '确定',
+          cancelButtonText: '取消',
+          type: 'warning',
+        })
+          .then(() => {
+            MangerDeleteResource({ id: this.select_sources_id }).then(() => {
+              this.$message.success('删除成功!');
+              this.select_sources_id = '';
+              this.queryList('');
+            });
+          })
+          .catch(() => {});
+      } else {
+        this.$message.warning('请先选择文件');
+      }
+    },
     // 设置信息
     handleSetInfo() {},
     // 更换文件
-    handleChangeFile() {},
+    handleChangeFile() {
+      this.limit = 1;
+      this.uploadTip = '只能上传 1 个文件';
+      this.sourceAddFlag = true;
+    },
 
     // 设置项目成员资源使用权限
     handlePersonal() {},
@@ -210,10 +300,12 @@ export default {
     changeType(index) {
       this.type_index = index;
       this.select_sources_id = '';
-      this.queryList();
+      this.acceptFileType = this.acceptFileTypeList[index];
+      this.queryList('');
     },
     // 查询列表
     queryList(data) {
+      this.boxLoading = true;
       if (data) {
         this.page_capacity = data.page_capacity;
         this.cur_page = data.cur_page;
@@ -235,16 +327,56 @@ export default {
         name_or_label: this.search_content,
         order_column_list: order_column_list,
       };
-      PageQueryProjectResourceList(datas).then(({ total_count, resource_list }) => {
-        this.total = total_count;
-        this.list = resource_list;
-      });
+      PageQueryProjectResourceList(datas)
+        .then(({ total_count, resource_list }) => {
+          this.boxLoading = false;
+          this.total = total_count;
+          this.list = resource_list;
+        })
+        .catch(() => {
+          this.boxLoading = false;
+        });
     },
 
     // 切换排序升序降序
     changeSort() {
       this.isDesc = !this.isDesc;
     },
+    updateFileList({ file_list, file_id_list }) {
+      this.file_list = file_list;
+      this.file_id_list = file_id_list;
+    },
+    handleCancle() {
+      this.sourceAddFlag = false;
+      this.file_list = [];
+      this.file_id_list = [];
+    },
+    // 确定新增资源
+    submitAdd() {
+      this.loading = true;
+      let data = {
+        project_id: this.book_id,
+        book_chapter_node_id: this.select_node,
+        type: this.type_list[this.type_index].value,
+        file_id_list: this.file_id_list,
+      };
+      MangerAddResource(data)
+        .then((res) => {
+          this.loading = false;
+          this.sourceAddFlag = false;
+          if (res.status === 1) {
+            this.queryList('');
+          }
+        })
+        .catch(() => {
+          this.loading = false;
+        });
+    },
+    // 点击资源
+    selectSourceNode(item) {
+      this.select_sources_id = item.id;
+    },
+    getCurTime() {},
   },
 };
 </script>
@@ -336,7 +468,33 @@ export default {
     }
 
     .sources-box {
+      display: flex;
+      flex-flow: wrap;
+      gap: 10px;
+      place-content: start start;
+      padding: 5px;
       overflow: auto;
+
+      .sources-item {
+        width: 200px;
+
+        .el-image {
+          width: 100%;
+          height: 140px;
+          border: 1px solid #ccc;
+        }
+
+        .name,
+        .label {
+          margin: 5px 0;
+          font-size: 14px;
+          line-height: 1.3;
+        }
+
+        :deep .audioLine {
+          background: #f2f3f5;
+        }
+      }
     }
   }
 }

+ 404 - 0
src/views/personal_workbench/project/components/AudioLine.vue

@@ -0,0 +1,404 @@
+<!-- eslint-disable vue/no-v-html -->
+<template>
+  <div :class="['AudioNNPE']">
+    <div class="audioLine">
+      <div class="playBox" @click="PlayAudio">
+        <div class="play" :class="[audio.loading ? 'loadBtn' : audio.playing ? 'playBtn' : 'pauseBtn']">
+          <SvgIcon v-if="audio.playing" icon-class="pause-large-fill" />
+          <SvgIcon v-else icon-class="play-large-fill" />
+        </div>
+      </div>
+
+      <template v-if="!isRepeat">
+        <el-slider
+          v-model="playValue"
+          :style="{ width: sliderWidth + 'px', height: '2px' }"
+          :format-tooltip="formatProcessToolTip"
+          @change="changeCurrentTime"
+        />
+        <span
+          ><template v-if="audio.playing">-</template
+          >{{ audio.maxTime ? realFormatSecond(audio.maxTime - audio.currentTime) : '' }}</span
+        >
+      </template>
+    </div>
+    <audio
+      :id="audioId"
+      :ref="audioId"
+      :src="mp3"
+      preload="metadata"
+      @loadedmetadata="onLoadedmetadata"
+      @timeupdate="onTimeupdate"
+      @canplaythrough="oncanplaythrough"
+    ></audio>
+  </div>
+</template>
+
+<script>
+export default {
+  props: [
+    'mp3',
+    'mp3Source',
+    'getCurTime',
+    'stopAudio',
+    'width',
+    'isRepeat',
+    'themeColor',
+    'hideSlider',
+    'ed',
+    'bg',
+    'audioId',
+    'type',
+    'audioData',
+  ],
+  data() {
+    // 这里存放数据
+    return {
+      playValue: 0,
+      audio: {
+        // 该字段是音频是否处于播放状态的属性
+        playing: false,
+        // 音频当前播放时长
+        currentTime: 0,
+        // 音频最大播放时长
+        maxTime: 0,
+        isPlaying: false,
+        loading: false,
+      },
+      audioAllTime: null, // 展示总时间
+      duioCurrentTime: null, // 剩余时间
+      count: 0,
+      isClick: false,
+      activeIndex: null,
+      activeContent: 1,
+    };
+  },
+  // 计算属性 类似于data概念
+  computed: {
+    sliderWidth() {
+      let width = 0;
+      if (this.width) {
+        width = this.width;
+      } else {
+        width = 662;
+      }
+      return width;
+    },
+  },
+  // 监控data中数据变化
+  watch: {
+    stopAudio: {
+      handler(val) {
+        if (val) {
+          this.$refs[this.audioId].pause();
+          this.audio.playing = false;
+        }
+      },
+      // 深度观察监听
+      deep: true,
+    },
+    'audio.playing': {
+      handler(val) {
+        this.$emit('playChange', val);
+        if (val) this.$emit('handleChangeStopAudio');
+      },
+    },
+  },
+  mounted() {
+    let audioId = this.audioId;
+    this.$refs[audioId].addEventListener('loadstart', () => {});
+    this.$refs[audioId].addEventListener('play', () => {
+      this.audio.playing = true;
+      this.audio.isPlaying = true;
+      this.audio.loading = false;
+    });
+    this.$refs[audioId].addEventListener('pause', () => {
+      this.audio.playing = false;
+      if (this.hideSlider && this.audio.currentTime * 1000 + 500 > this.ed) {
+        this.$emit('sentPause', true);
+      }
+      this.$emit('handleListenRead', false);
+    });
+    this.$refs[audioId].addEventListener('ended', () => {
+      this.audio.playing = false;
+      this.audio.isPlaying = false;
+      this.isClick = false;
+      this.$emit('handleListenRead', false);
+    });
+
+    this.$nextTick(() => {
+      if (
+        document.getElementsByClassName('el-slider__button-wrapper') &&
+        document.getElementsByClassName('el-slider__button-wrapper')[0]
+      ) {
+        document.getElementsByClassName('el-slider__button-wrapper')[0].addEventListener('mousedown', () => {
+          this.$refs[audioId].pause();
+          this.audio.playing = false;
+        });
+      }
+    });
+  },
+  methods: {
+    PlayAudio() {
+      let audioId = this.audioId;
+      let audio = document.getElementsByTagName('audio');
+      if (audio && audio.length > 0) {
+        Array.from(audio).forEach((item) => {
+          if (item.src === this.mp3) {
+            if (item.id !== audioId) {
+              item.pause();
+            }
+          } else {
+            item.pause();
+          }
+        });
+      }
+      if (this.audio.playing) {
+        this.$refs[audioId].pause();
+        this.audio.playing = false;
+        this.$emit('handleListenRead', false);
+        this.isClick = false;
+      } else {
+        if (this.count === 0) {
+          this.audio.loading = true;
+          this.count += 1;
+        }
+        if (this.hideSlider) {
+          this.$refs[audioId].play();
+          this.onTimeupdateTime(this.bg / 1000);
+        } else {
+          this.$refs[audioId].pause();
+          this.$refs[audioId].play();
+        }
+
+        this.$emit('handleChangeStopAudio');
+        this.$emit('handleListenRead', true);
+        this.isClick = true;
+      }
+    },
+    oncanplaythrough() {
+      this.audio.loading = false;
+    },
+    // 点击 拖拽播放音频
+    changeCurrentTime(value) {
+      let audioId = this.audioId;
+      this.$refs[audioId].play();
+      this.audio.playing = true;
+      this.$refs[audioId].currentTime = parseInt((value / 100) * this.audio.maxTime);
+    },
+    mousedown() {
+      let audioId = this.audioId;
+      this.$refs[audioId].pause();
+      this.audio.playing = false;
+    },
+    // 进度条格式化toolTip
+    formatProcessToolTip(index) {
+      return this.realFormatSecond(parseInt((this.audio.maxTime / 100) * index));
+    },
+    // 音频加载完之后
+    onLoadedmetadata(res) {
+      this.audio.maxTime = parseInt(res.target.duration);
+      this.audioAllTime = this.realFormatSecond(this.audio.maxTime);
+    },
+    // 当音频当前时间改变后,进度条也要改变
+    onTimeupdate(res) {
+      let audioId = this.audioId;
+      this.audio.currentTime = res.target.currentTime;
+      this.getCurTime(res.target.currentTime);
+      this.playValue = (this.audio.currentTime / this.audio.maxTime) * 100;
+      if (this.type === 'audioLine') {
+        setTimeout(() => {
+          if (!this.isClick && this.audio.currentTime * 1000 > this.ed) {
+            if (this.$refs[audioId]) {
+              this.$refs[audioId].pause();
+              this.$emit('emptyEd');
+            }
+          }
+        }, 50);
+      } else if (this.hideSlider) {
+        if (this.audio.currentTime * 1000 + 500 > this.ed) {
+          this.$refs[audioId].pause();
+        }
+      }
+    },
+    onTimeupdateTime(res, playFlag) {
+      if (!res && res !== 0) return;
+      let audioId = this.audioId;
+      this.$refs[audioId].currentTime = res;
+      this.playValue = (res / this.audio.maxTime) * 100;
+      if (playFlag) {
+        let audio = document.getElementsByTagName('audio');
+        if (audio && audio.length > 0 && window.location.href.indexOf('GCLS-Learn') === -1) {
+          audio.forEach((item) => {
+            if (item.id !== audioId) {
+              item.pause();
+            }
+          });
+        }
+        this.$refs[audioId].play();
+      }
+    },
+    // 将整数转换成 时:分:秒的格式
+    realFormatSecond(value) {
+      let theTime = parseInt(value); // 秒
+      let theTime1 = 0; // 分
+      let theTime2 = 0; // 小时
+      if (theTime > 60) {
+        theTime1 = parseInt(theTime / 60);
+        theTime = parseInt(theTime % 60);
+        if (theTime1 > 60) {
+          theTime2 = parseInt(theTime1 / 60);
+          theTime1 = parseInt(theTime1 % 60);
+        }
+      }
+      let result = String(parseInt(theTime));
+      if (result < 10) {
+        result = `0${result}`;
+      }
+      if (theTime1 > 0) {
+        result = `${String(parseInt(theTime1))}:${result}`;
+        if (theTime1 < 10) {
+          result = `0${result}`;
+        }
+      } else {
+        result = `00:${result}`;
+      }
+      if (theTime2 > 0) {
+        result = `${String(parseInt(theTime2))}:${result}`;
+        if (theTime2 < 10) {
+          result = `0${result}`;
+        }
+      } else {
+        // result = "00:" + result;
+      }
+      return result;
+    },
+    showPopover(e) {
+      this.activeIndex = e;
+    },
+    hidePopover() {
+      this.activeIndex = null;
+      this.activeContent = 1;
+    },
+  },
+};
+</script>
+<style lang="scss" scoped>
+.AudioNNPE {
+  width: 100%;
+
+  .audioLine {
+    box-sizing: border-box;
+    display: flex;
+    align-items: center;
+    width: 100%;
+    height: 40px;
+    background: #fff;
+    border-radius: 8px;
+
+    .playBox {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      min-width: 40px;
+      height: 40px;
+      margin-right: 7px;
+      cursor: pointer;
+    }
+
+    .play {
+      display: block;
+      width: 16px;
+      height: 16px;
+      font-size: 0;
+      cursor: pointer;
+    }
+
+    span {
+      min-width: 56px;
+      margin-right: 12px;
+      margin-left: 8px;
+      font-size: 16px;
+      line-height: 19px;
+      color: #000;
+      text-align: right;
+    }
+  }
+
+  .audioLine2 {
+    .play-icon {
+      width: 16px;
+      height: 16px;
+      cursor: pointer;
+
+      &-big {
+        width: 24px;
+        height: 24px;
+      }
+
+      &.playBtn-icon {
+        background: url('@/assets/voice_matrix/pauseC-16-normal-blue.png') no-repeat left top;
+        background-size: 100% 100%;
+      }
+
+      &.pauseBtn-icon {
+        background: url('@/assets/voice_matrix/compare-pause-blue.png') no-repeat left top;
+        background-size: 100% 100%;
+      }
+    }
+  }
+
+  .loadBtn {
+    background: url('@/assets/voice_matrix/loading-blue.png') no-repeat left top;
+    background-size: 100% 100%;
+  }
+
+  &.Audio-tts {
+    .audioLine {
+      .playBtn {
+        background: url('@/assets/voice_matrix/tts-play-blue.png') no-repeat left top;
+        background-size: 100% 100%;
+      }
+
+      .pauseBtn {
+        background: url('@/assets/voice_matrix/tts-blue.png') no-repeat left top;
+        background-size: 100% 100%;
+      }
+    }
+  }
+}
+</style>
+<style lang="scss">
+.AudioNNPE {
+  .el-slider__button-wrapper {
+    position: relative;
+    z-index: 0;
+  }
+
+  .el-slider__button {
+    position: absolute;
+    top: 12px;
+    width: 8px;
+    height: 8px;
+    background: #165dff;
+    border: none;
+  }
+
+  .el-slider__runway {
+    height: 2px;
+    padding: 0;
+    margin: 0;
+    background: #e5e5e5;
+    border-radius: 0;
+  }
+
+  .el-slider {
+    position: relative;
+  }
+
+  .el-slider__bar {
+    height: 2px;
+    background: #165dff;
+  }
+}
+</style>

+ 1 - 1
src/views/personal_workbench/project/components/MenuTree.vue

@@ -66,7 +66,7 @@ export default {
      * @param {boolean} isLeaf - 是否是叶子节点
      */
     selectNode(nodeId, isLeaf) {
-      if (!isLeaf) return;
+      // if (!isLeaf) return;
       if (this.curSelectId === nodeId) return;
       this.curSelectId = nodeId;
       this.$emit('selectNode', nodeId);

+ 365 - 0
src/views/personal_workbench/project/components/UploadFile.vue

@@ -0,0 +1,365 @@
+<template>
+  <div>
+    <div class="file-area">
+      <span class="label-text">{{ labelText }}</span>
+      <div class="upload-box">
+        <el-upload
+          ref="upload"
+          class="file-uploader"
+          action="no"
+          :accept="acceptFileType"
+          :multiple="limit === null || limit > 1"
+          :show-file-list="false"
+          :auto-upload="false"
+          :file-list="fileList"
+          :on-change="onFileChange"
+          :on-exceed="handleExceed"
+          :limit="limit"
+        >
+          <el-button>选取{{ labelText }}文件</el-button>
+        </el-upload>
+        <el-button size="small" type="primary" @click="uploadFiles">上传</el-button>
+      </div>
+    </div>
+    <el-divider />
+    <div class="upload-tip">{{ uploadTip }}</div>
+    <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">
+          <span>
+            <SvgIcon v-if="iconClass" :icon-class="iconClass" size="12" />
+            <span>{{ file.file_name ?? file.name }}</span>
+            <!-- <span>({{ file.size }})</span> -->
+          </span>
+          <el-progress
+            v-if="file.progress > 0 && file.progress < 100"
+            type="circle"
+            :percentage="file.progress"
+            width="20"
+            color="#2A5AF6"
+            stroke-linecap="butt"
+            :show-text="false"
+          />
+          <span v-else-if="file.file_id"> 完成 </span>
+        </div>
+        <SvgIcon icon-class="delete-black" size="12" @click="removeFile(file, i)" />
+        <SvgIcon
+          v-show="type === 'picture' && file.file_id"
+          icon-class="mark"
+          size="12"
+          @click="viewDialog(file.file_id)"
+        />
+      </li>
+    </ul>
+  </div>
+</template>
+
+<script>
+import { fileUpload } from '@/api/app';
+import { conversionSize } from '@/utils/common';
+
+export default {
+  name: 'UploadFile',
+  components: {},
+  props: {
+    // 课件id
+    coursewareId: {
+      type: String,
+      default: '',
+    },
+    // 组件id
+    componentId: {
+      type: String,
+      default: '',
+    },
+    // 组件标签
+    labelText: {
+      type: String,
+      default: '',
+    },
+    // 上传支持的文件格式
+    acceptFileType: {
+      type: String,
+      default: '',
+    },
+    // 提示语
+    uploadTip: {
+      type: String,
+      default: '',
+    },
+    // 图标
+    iconClass: {
+      type: String,
+      default: '',
+    },
+    fileList: {
+      type: Array,
+      default: () => [],
+    },
+    fileIdList: {
+      type: Array,
+      default: () => [],
+    },
+    fileInfoList: {
+      type: Array,
+      default: () => [],
+    },
+    type: {
+      type: String,
+      default: '',
+    },
+    singleSize: {
+      type: Number,
+      default: 100,
+    },
+    totalSize: {
+      type: Number,
+      default: 1024,
+    },
+    limit: {
+      type: Number,
+      default: null,
+    },
+  },
+  data() {
+    return {
+      curFile: null,
+      conversionSize,
+      visible: false,
+      content: {
+        file_list: this.fileList,
+        file_id_list: this.fileIdList,
+        file_info_list: this.fileInfoList,
+      },
+    };
+  },
+  watch: {
+    content: {
+      handler(val) {
+        this.$emit('updateFileList', val);
+      },
+      deep: true,
+    },
+  },
+  methods: {
+    // 显示自定义样式文件列表
+    onFileChange(file, fileList) {
+      this.afterSelectFile(file);
+      fileList.forEach((file) => {
+        if (!file.progress || file.progress <= 0) file.progress = 0;
+      });
+      const lists = this.$refs.upload.uploadFiles;
+      if (lists.length === 0) return;
+      const files = lists.filter((item) => {
+        let find = this.content.file_list.findIndex((p) => p.uid === item.uid);
+        return find === -1;
+      });
+      if (this.limit !== null && this.content.file_list.length + files.length > this.limit) {
+        this.$message.warning(
+          `当前限制选择 ${this.limit} 个文件,本次选择了 ${files.length} 个文件,共选择了 ${files.length + this.content.file_list.length} 个文件`,
+        );
+        return;
+      }
+      this.content.file_list = [...this.content.file_list, ...files];
+    },
+
+    handleExceed(files, fileList) {
+      this.$message.warning(
+        `当前限制选择 ${this.limit} 个文件,本次选择了 ${files.length} 个文件,共选择了 ${files.length + fileList.length} 个文件`,
+      );
+    },
+
+    // 删除文件
+    removeFile(file, i) {
+      this.$confirm('是否删除当前文件?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning',
+      })
+        .then(() => {
+          this.$refs.upload.handleRemove(file);
+          this.content.file_list.splice(i, 1);
+          this.content.file_id_list.splice(i, 1);
+        })
+        .catch(() => {});
+    },
+
+    // 文件校验
+    afterSelectFile(file) {
+      const fileName = file.name;
+      let singleSizeTip = `文件[${fileName}]大小超过${conversionSize(this.singleSize * 1024 * 1024)},被移除!`;
+      if (file.size > this.singleSize * 1024 * 1024) {
+        this.$message.error(singleSizeTip);
+        this.$refs.upload.handleRemove(file);
+        return false;
+      }
+
+      const suffix = fileName.slice(fileName.lastIndexOf('.'), fileName.length).toLowerCase();
+      let fileType = this.acceptFileType.split(',');
+      let typeTip = '文件类型不支持';
+      const isNeedType = fileType.includes(suffix);
+      if (!isNeedType) {
+        typeTip += `,[${fileName}]被移除!`;
+        this.$message.error(typeTip);
+        this.$refs.upload.handleRemove(file);
+        return false;
+      }
+    },
+
+    // 上传文件
+    uploadFiles() {
+      const files = (this.content.file_list || []).filter((file) => file.uid && file.status !== 'success');
+      if (files.length <= 0) {
+        this.$message.error('没有需要上传的文件!');
+        return false;
+      }
+      const totalSize = files.reduce((sum, cur) => sum + Number(cur.size || 0), 0);
+      if (totalSize > this.totalSize * 1024 * 1024) {
+        this.$message.error(`文件总大小不能超过${conversionSize(this.totalSize * 1024 * 1024)}!`);
+        return false;
+      }
+
+      files
+        .filter((p) => {
+          let pro = p.progress || -1;
+          return pro <= 0;
+        })
+        .forEach((file) => {
+          let form = new FormData();
+          form.append(file.name, file.raw, file.name);
+          fileUpload('Mid', form, {
+            handleUploadProgress: (progressEvent) => {
+              // 进度到99等服务器返回文件信息后才算实际完成
+              let per = Number((progressEvent.progress * 99).toFixed(2) || 0);
+              let en = this.content.file_list.find((p) => p.uid === file.uid);
+              if (en) {
+                en.progress = per;
+                this.$forceUpdate();
+              }
+            },
+          }).then(({ file_info_list }) => {
+            let file_index = this.content.file_list.findIndex((p) => p.uid === file.uid);
+            if (file_index > -1) {
+              if (this.type === 'picture') {
+                this.content.file_info_list[file_index] = {
+                  file_id: file_info_list[0].file_id,
+                  file_name: file_info_list[0].file_name,
+                  title: '',
+                  intro: '',
+                };
+              }
+              this.content.file_list[file_index] = {
+                file_id: file_info_list[0].file_id,
+                file_name: file_info_list[0].file_name,
+                file_url: file_info_list[0].file_url,
+              };
+              this.content.file_id_list.push(file_info_list[0].file_id);
+              this.$refs.upload.uploadFiles = [];
+              this.$forceUpdate();
+            }
+          });
+        });
+    },
+
+    // 显示弹窗
+    viewDialog(file_id) {
+      if (file_id) this.visible = true;
+      this.curFile = this.content.file_info_list.find((file) => file.file_id === file_id);
+    },
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+.module-content {
+  .file-area {
+    display: flex;
+    column-gap: 16px;
+    align-items: center;
+
+    .label-text {
+      font-size: 14px;
+      color: $font-light-color;
+    }
+
+    div {
+      flex: 1;
+    }
+  }
+
+  .el-divider {
+    margin: 16px 0;
+  }
+
+  .upload-tip {
+    margin-bottom: 16px;
+    font-size: 12px;
+    color: #86909c;
+  }
+
+  .upload-box {
+    display: flex;
+    justify-content: space-between;
+
+    .file-uploader {
+      flex: 1;
+
+      :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;
+  }
+
+  .file-list {
+    display: flex;
+    flex-direction: column;
+    row-gap: 16px;
+
+    li {
+      display: flex;
+      column-gap: 12px;
+      align-items: center;
+
+      .file-name {
+        display: flex;
+        column-gap: 14px;
+        align-items: center;
+        justify-content: space-between;
+        max-width: 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;
+      }
+    }
+  }
+}
+</style>