zq преди 1 година
родител
ревизия
b283abab2b
променени са 21 файла, в които са добавени 580 реда и са изтрити 384 реда
  1. 1 1
      src/api/app.js
  2. 3 0
      src/icons/svg/components/mark.svg
  3. 16 139
      src/views/book/courseware/create/components/base/audio/Audio.vue
  4. 34 36
      src/views/book/courseware/create/components/base/audio/AudioSetting.vue
  5. 278 0
      src/views/book/courseware/create/components/base/common/UploadFile.vue
  6. 2 2
      src/views/book/courseware/create/components/base/divider/Divider.vue
  7. 4 4
      src/views/book/courseware/create/components/base/divider/DividerSetting.vue
  8. 18 0
      src/views/book/courseware/create/components/base/label/Label.vue
  9. 18 119
      src/views/book/courseware/create/components/base/picture/Picture.vue
  10. 29 32
      src/views/book/courseware/create/components/base/picture/PictureSetting.vue
  11. 1 1
      src/views/book/courseware/create/components/base/spacing/Spacing.vue
  12. 3 3
      src/views/book/courseware/create/components/base/spacing/SpacingSetting.vue
  13. 67 0
      src/views/book/courseware/create/components/common/FillDescribe.vue
  14. 0 1
      src/views/book/courseware/create/components/common/ModuleBase.vue
  15. 7 7
      src/views/book/courseware/create/components/common/ModuleMixin.js
  16. 5 5
      src/views/book/courseware/create/components/common/SettingMixin.js
  17. 16 13
      src/views/book/courseware/data/audio.js
  18. 57 5
      src/views/book/courseware/data/common.js
  19. 3 2
      src/views/book/courseware/data/divider.js
  20. 16 13
      src/views/book/courseware/data/picture.js
  21. 2 1
      src/views/book/courseware/data/spacing.js

+ 1 - 1
src/api/app.js

@@ -72,7 +72,7 @@ export async function fileUpload(
     formData = file;
   } else {
     formData = new FormData();
-    formData.append(file.filename, file.file, file.file.name);
+    formData.append(file.name, file.raw, file.name);
   }
 
   let onUploadProgress = handleUploadProgress || null;

+ 3 - 0
src/icons/svg/components/mark.svg

@@ -0,0 +1,3 @@
+<svg width="10" height="10" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M9 10H1C0.72386 10 0.5 9.77615 0.5 9.5V0.5C0.5 0.22386 0.72386 0 1 0H9C9.27615 0 9.5 0.22386 9.5 0.5V9.5C9.5 9.77615 9.27615 10 9 10ZM8.5 9V1H1.5V9H8.5ZM3 2.5H7V3.5H3V2.5ZM3 4.5H7V5.5H3V4.5ZM3 6.5H5.5V7.5H3V6.5Z" fill="black"/>
+</svg>

+ 16 - 139
src/views/book/courseware/create/components/base/audio/Audio.vue

@@ -1,76 +1,38 @@
 <template>
   <ModuleBase :type="data.type">
     <template #content>
-      <div class="audio-area">
-        <span class="label-text">音频</span>
-        <div class="upload-box">
-          <el-upload
-            ref="upload"
-            class="audio-uploader"
-            action="no"
-            accept=".mp3,.acc,.wma"
-            :multiple="true"
-            :show-file-list="false"
-            :file-list="file_info_list"
-            :auto-upload="false"
-            :before-upload="beforeAudioUpload"
-            :on-change="onFileChange"
-          >
-            <el-button>选取音频文件</el-button>
-          </el-upload>
-          <el-button size="small" type="primary" @click="uploadAudio">上传</el-button>
-        </div>
-      </div>
-      <el-divider />
-      <div class="upload-tip">支持上传mp3、acc、wma,等格式音频文件,单个文件最大100MB,总文件体积不超1G。</div>
-      <ul slot="file-list" class="file-list">
-        <li v-for="(file, i) in file_info_list" :key="i">
-          <div class="file-name">
-            <span>
-              <SvgIcon icon-class="note" size="12" />
-              <span>{{ file.name }}</span>
-            </span>
-            <span>
-              <SvgIcon icon-class="progress" size="12" />
-            </span>
-          </div>
-          <SvgIcon icon-class="delete-black" size="12" @click="removeFile(file, i)" />
-        </li>
-      </ul>
+      <UploadFile
+        id="AudioUploadPage"
+        :moduleData="data"
+        :labelText="labelText"
+        :acceptFileType="acceptFileType"
+        :uploadTip="uploadTip"
+        :iconClass="iconClass"
+      />
     </template>
   </ModuleBase>
 </template>
 
 <script>
-import { AudioData } from '@/views/book/courseware/data/audio';
+import { getAudioData } from '@/views/book/courseware/data/audio';
 import ModuleMixin from '../../common/ModuleMixin';
-import { fileUpload, GetFileStoreInfo } from '@/api/app';
+import UploadFile from '../common/UploadFile.vue';
 
 export default {
   name: 'AudioPage',
-  components: {},
+  components: { UploadFile },
   mixins: [ModuleMixin],
   data() {
     return {
       data: getAudioData(),
       file_info_list: [],
+      labelText: '音频',
+      acceptFileType: '.mp3,.acc,.wma',
+      uploadTip: '支持上传mp3、acc、wma,等格式音频文件,单个文件最大100MB,总文件体积不超1G。',
+      iconClass: 'note',
     };
   },
   computed: {},
-  watch: {
-    // fileIdList: {
-    //   handler(list) {
-    //     this.file_info_list = [];
-    //     if (!list || list.length === 0) return;
-    //     list.forEach((p) => {
-    //       GetFileStoreInfo({ file_id: p }).then((res) => {
-    //         this.file_info_list.push(res);
-    //       });
-    //     });
-    //   },
-    //   immediate: true,
-    // },
-  },
   methods: {
     onFileChange(file, fileList) {
       console.log(fileList);
@@ -110,89 +72,4 @@ export default {
 };
 </script>
 
-<style lang="scss" scoped>
-.module-content {
-  .audio-area {
-    display: flex;
-    column-gap: 16px;
-    align-items: center;
-
-    .label-text {
-      font-size: 14px;
-      color: #4e5969;
-    }
-
-    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;
-
-    .audio-uploader {
-      flex: 1;
-
-      :deep .el-upload {
-        &--text {
-          width: 100%;
-          background-color: #f2f3f5;
-          border-radius: 2px 0 0 2px;
-
-          .el-button {
-            width: 100%;
-            color: #86909c;
-            text-align: left;
-          }
-        }
-      }
-    }
-
-    .el-button {
-      border-radius: 0 2px 2px 0;
-    }
-  }
-
-  .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;
-        width: 360px;
-        max-width: 360px;
-        padding: 8px 12px;
-        font-size: 14px;
-        color: #1d2129;
-        background-color: #f7f8fa;
-
-        span {
-          display: flex;
-          column-gap: 14px;
-          align-items: center;
-        }
-      }
-    }
-  }
-}
-</style>
+<style lang="scss" scoped></style>

+ 34 - 36
src/views/book/courseware/create/components/base/audio/AudioSetting.vue

@@ -1,15 +1,15 @@
 <template>
   <div>
-    <el-form :model="setting" :label-position="labelPosition" label-width="72px">
+    <el-form :model="property" :label-position="labelPosition" label-width="72px">
       <el-form-item label="序号" class="serial-number">
-        <el-input v-model="setting.serialNumber" />
-        <SvgIcon icon-class="switch" size="14" />
+        <el-input v-model="property.serial_number" />
+        <SvgIcon icon-class="switch" size="14" @click="switchSerialNumber(property.serial_number)" />
       </el-form-item>
       <el-form-item>
         <el-radio
-          v-for="{ value, label } in serialNumberTypeList"
+          v-for="{ value, label } in snGenerationMethodList"
           :key="value"
-          v-model="setting.other.serial_number_type"
+          v-model="property.sn_generation_method"
           :label="value"
         >
           {{ label }}
@@ -19,58 +19,58 @@
         <div class="grid-container">
           <div class="top">
             <el-button
-              :class="['top-start', { active: 'top-start' === setting.numberPosition }]"
+              :class="['top-start', { active: 'top-start' === property.sn_position }]"
               @click="changeNumberPosition('top-start')"
             />
             <el-button
-              :class="['top', { active: 'top' === setting.numberPosition }]"
+              :class="['top', { active: 'top' === property.sn_position }]"
               @click="changeNumberPosition('top')"
             />
             <el-button
-              :class="['top-end', { active: 'top-end' === setting.numberPosition }]"
+              :class="['top-end', { active: 'top-end' === property.sn_position }]"
               @click="changeNumberPosition('top-end')"
             />
           </div>
           <div class="left">
             <el-button
-              :class="['left-start', { active: 'left-start' === setting.numberPosition }]"
+              :class="['left-start', { active: 'left-start' === property.sn_position }]"
               @click="changeNumberPosition('left-start')"
             />
             <el-button
-              :class="['left', { active: 'left' === setting.numberPosition }]"
+              :class="['left', { active: 'left' === property.sn_position }]"
               @click="changeNumberPosition('left')"
             />
             <el-button
-              :class="['left-end', { active: 'left-end' === setting.numberPosition }]"
+              :class="['left-end', { active: 'left-end' === property.sn_position }]"
               @click="changeNumberPosition('left-end')"
             />
           </div>
           <div class="main"></div>
           <div class="right">
             <el-button
-              :class="['right-start', { active: 'right-start' === setting.numberPosition }]"
+              :class="['right-start', { active: 'right-start' === property.sn_position }]"
               @click="changeNumberPosition('right-start')"
             />
             <el-button
-              :class="['right', { active: 'right' === setting.numberPosition }]"
+              :class="['right', { active: 'right' === property.sn_position }]"
               @click="changeNumberPosition('right')"
             />
             <el-button
-              :class="['right-end', { active: 'right-end' === setting.numberPosition }]"
+              :class="['right-end', { active: 'right-end' === property.sn_position }]"
               @click="changeNumberPosition('right-end')"
             />
           </div>
           <div class="bottom">
             <el-button
-              :class="['bottom-start', { active: 'bottom-start' === setting.numberPosition }]"
+              :class="['bottom-start', { active: 'bottom-start' === property.sn_position }]"
               @click="changeNumberPosition('bottom-start')"
             />
             <el-button
-              :class="['bottom', { active: 'bottom' === setting.numberPosition }]"
+              :class="['bottom', { active: 'bottom' === property.sn_position }]"
               @click="changeNumberPosition('bottom')"
             />
             <el-button
-              :class="['bottom-end', { active: 'bottom-end' === setting.numberPosition }]"
+              :class="['bottom-end', { active: 'bottom-end' === property.sn_position }]"
               @click="changeNumberPosition('bottom-end')"
             />
           </div>
@@ -79,9 +79,9 @@
       <el-divider />
       <el-form-item label="查看方式">
         <el-radio
-          v-for="{ value, label } in audioViewingMethodsList"
+          v-for="{ value, label } in audioViewMethodList"
           :key="value"
-          v-model="setting.audioViewingMethods"
+          v-model="property.view_method"
           :label="value"
         >
           {{ label }}
@@ -93,24 +93,22 @@
 
 <script>
 import SettingMixin from '@/views/book/courseware/create/components/common/SettingMixin';
-import { serialNumberTypeList, audioViewingMethodsList } from '@/views/book/courseware/data/common';
+import { snGenerationMethodList, audioViewMethodList, switchSerialNumber } from '@/views/book/courseware/data/common';
 
 export default {
   name: 'AudioSetting',
   mixins: [SettingMixin],
   data() {
     return {
-      serialNumberTypeList,
-      audioViewingMethodsList,
+      switchSerialNumber,
+      snGenerationMethodList,
+      audioViewMethodList,
       labelPosition: 'left',
-      setting: {
-        serialNumber: 1, // 序号
-        numberPosition: 'top-start', // 序号位置
-        audioViewingMethods: audioViewingMethodsList[0].value, // 查看方式
-        // 其他属性
-        other: {
-          serial_number_type: serialNumberTypeList[0].value, // 题号类型
-        },
+      property: {
+        serial_number: 1, // 序号
+        sn_position: 'top-start', // 序号位置:top-start top top-end 等
+        sn_generation_method: snGenerationMethodList[0].value, // 序号生成方式:recalculate 重新计算follow 跟随
+        view_method: audioViewMethodList[0].value, // 查看方式:independent 独立 list 列表icon 图标
       },
     };
   },
@@ -127,19 +125,19 @@ export default {
   methods: {
     /**
      * @description 设置属性
-     * @param {Object} setting 属性
+     * @param {Object} property 属性
      */
-    setSetting(setting) {
+    setSetting(property) {
       this.isSet = true;
-      this.setting = setting;
+      this.property = property;
     },
 
     /**
      * @description 改变序号位置
-     * @param {String} numberPosition
+     * @param {String} sn_position
      */
-    changeNumberPosition(numberPosition) {
-      this.setting.numberPosition = numberPosition;
+    changeNumberPosition(sn_position) {
+      this.property.sn_position = sn_position;
     },
   },
 };

+ 278 - 0
src/views/book/courseware/create/components/base/common/UploadFile.vue

@@ -0,0 +1,278 @@
+<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="true"
+          :show-file-list="false"
+          :auto-upload="false"
+          :on-change="onFileChange"
+        >
+          <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 slot="file-list" class="file-list">
+      <li v-for="(file, i) in file_info_list" :key="i">
+        <div class="file-name">
+          <span>
+            <SvgIcon :icon-class="iconClass" size="12" />
+            <span>{{ file.name }}</span>
+            <!-- <span>({{ file.size }})</span> -->
+          </span>
+          <span v-show="file.progress > 0">
+            <!-- <SvgIcon icon-class="progress" size="12" /> -->
+            {{ file.progress }}%
+          </span>
+        </div>
+        <SvgIcon icon-class="delete-black" size="12" @click="removeFile(file, i)" />
+        <SvgIcon v-show="moduleData.type == 'picture'" icon-class="mark" size="12" @click="viewDialog" />
+      </li>
+    </ul>
+
+    <FillDescribe :visible.sync="visible" @fillDescribeToFile="fillDescribeToFile" />
+  </div>
+</template>
+
+<script>
+import ModuleMixin from '../../common/ModuleMixin';
+import { fileUpload, GetFileStoreInfo } from '@/api/app';
+import { conversionSize } from '@/utils/common';
+import FillDescribe from '../../common/FillDescribe';
+
+export default {
+  name: 'UploadFile',
+  components: {
+    FillDescribe,
+  },
+  mixins: [ModuleMixin],
+  props: {
+    // 组件标签
+    labelText: {
+      type: String,
+      default: '',
+    },
+    // 上传支持的文件格式
+    acceptFileType: {
+      type: String,
+      default: '',
+    },
+    // 提示语
+    uploadTip: {
+      type: String,
+      default: '',
+    },
+    // 图标
+    iconClass: {
+      type: String,
+      default: '',
+    },
+    moduleData: {
+      type: Object,
+      default: () => ({}),
+    },
+  },
+  data() {
+    return {
+      conversionSize,
+      file_info_list: [],
+      visible: false,
+      form: {
+        title: '',
+        describe: '',
+      },
+    };
+  },
+  computed: {},
+  watch: {},
+  methods: {
+    // 显示自定义样式文件列表
+    onFileChange(file, fileList) {
+      this.afterSelectFile(file);
+
+      fileList.forEach((file) => {
+        if (!file.progress || file.progress <= 0) file.progress = 0;
+      });
+      this.file_info_list = fileList;
+    },
+
+    // 删除文件
+    removeFile(file, i) {
+      this.$confirm('是否删除当前文件?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning',
+      })
+        .then(() => {
+          this.$refs.upload.handleRemove(file);
+          // this.file_info_list.splice(i, 1);
+        })
+        .catch(() => {});
+    },
+
+    // 文件校验
+    afterSelectFile(file) {
+      const fileName = file.name;
+      let singleSizeTip = '文件[' + fileName + ']大小超过 ' + conversionSize(this.moduleData.single_size) + ',被移除!';
+
+      if (file.size > this.moduleData.single_size * 1024 * 1024) {
+        this.$message.error(singleSizeTip);
+        this.$refs.upload.handleRemove(file);
+        return false;
+      }
+
+      const suffix = fileName.slice(fileName.lastIndexOf('.') + 1, fileName.length).toLowerCase();
+      let fileType = [];
+      let typeTip = '';
+
+      if (this.moduleData.type === 'audio') {
+        fileType = ['mp3', 'acc', 'wma'];
+        typeTip = '音频文件只能是 mp3、acc、wma 格式!';
+      } else if (this.moduleData.type === 'picture') {
+        fileType = ['jpg', 'png', 'jpeg'];
+        typeTip = '图片文件只能是 jpg、png、jpeg 格式!';
+      }
+      const isNeedType = fileType.includes(suffix);
+      if (!isNeedType) {
+        typeTip += ',[' + fileName + ']被移除!';
+        this.$message.error(typeTip);
+        this.$refs.upload.handleRemove(file);
+        return false;
+      }
+    },
+
+    // 上传文件
+    uploadFiles() {
+      const files = this.$refs.upload.uploadFiles || [];
+      const totalSize = files.reduce((sum, cur) => sum + Number(cur.size || 0), 0);
+      if (totalSize > this.moduleData.total_size * 1024 * 1024) {
+        this.$message.error('文件总大小不能超过' + conversionSize(this.moduleData.total_size));
+        return false;
+      }
+      var that = this;
+      files.forEach((file) => {
+        fileUpload('Mid', file, {
+          handleUploadProgress: function (progressEvent) {
+            var en = that.file_info_list.find((p) => p.uid == file.uid);
+            var per = Number((progressEvent.progress * 100).toFixed(2) || 0);
+            if (en) {
+              // var _i = that.file_info_list.findIndex((p) => p.uid == file.uid);
+              en.progress = per;
+              // that.$set(that.file_info_list, _i, en);
+              that.$forceUpdate();
+            }
+          },
+        }).then(({ file_info_list }) => {
+          if (file_info_list.length > 0) {
+            // console.log(file_info_list);
+            this.file_info_list = file_info_list;
+          }
+        });
+      });
+    },
+
+    // 显示弹窗
+    viewDialog() {
+      this.visible = true;
+    },
+
+    //给文件加介绍
+    fillDescribeToFile() {},
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+.module-content {
+  .file-area {
+    display: flex;
+    column-gap: 16px;
+    align-items: center;
+
+    .label-text {
+      font-size: 14px;
+      color: #4e5969;
+    }
+
+    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: #f2f3f5;
+          border-radius: 2px 0 0 2px;
+
+          .el-button {
+            width: 100%;
+            color: #86909c;
+            text-align: left;
+          }
+        }
+      }
+    }
+
+    .el-button {
+      border-radius: 0 2px 2px 0;
+    }
+  }
+
+  .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;
+        width: 360px;
+        max-width: 360px;
+        padding: 8px 12px;
+        font-size: 14px;
+        color: #1d2129;
+        background-color: #f7f8fa;
+
+        span {
+          display: flex;
+          column-gap: 14px;
+          align-items: center;
+        }
+      }
+    }
+  }
+}
+</style>

+ 2 - 2
src/views/book/courseware/create/components/base/divider/Divider.vue

@@ -22,9 +22,9 @@ export default {
   computed: {
     settingStyle() {
       return {
-        margin: `${this.data.setting.height / 2}px 0`,
+        margin: `${this.data.property.height / 2}px 0`,
         border: 'none',
-        borderTop: `1px ${this.data.setting.type} #ebebeb`,
+        borderTop: `1px ${this.data.property.line_type} #ebebeb`,
       };
     },
   },

+ 4 - 4
src/views/book/courseware/create/components/base/divider/DividerSetting.vue

@@ -1,11 +1,11 @@
 <template>
   <div>
-    <el-form :model="setting" :label-position="labelPosition" label-width="56px">
+    <el-form :model="property" :label-position="labelPosition" label-width="56px">
       <el-form-item label="高度">
-        <el-input v-model="setting.height" />
+        <el-input v-model="property.height" />
       </el-form-item>
       <el-form-item label="类型">
-        <el-select v-model="setting.type">
+        <el-select v-model="property.line_type">
           <el-option label="实线" value="solid" />
         </el-select>
       </el-form-item>
@@ -22,7 +22,7 @@ export default {
   data() {
     return {
       labelPosition: 'left',
-      setting: {
+      property: {
         height: 100,
         type: 'solid',
       },

+ 18 - 0
src/views/book/courseware/create/components/base/label/Label.vue

@@ -0,0 +1,18 @@
+<template>
+  <ModuleBase :type="标签">
+    <template #content>
+      <div>农行</div>
+    </template>
+  </ModuleBase>
+</template>
+<script>
+export default {
+  name: 'LabelPage',
+  data() {
+    return {
+    };
+  },
+};
+</script>
+
+<style lang="scss" scoped></style>

+ 18 - 119
src/views/book/courseware/create/components/base/picture/Picture.vue

@@ -1,58 +1,35 @@
 <template>
   <ModuleBase :type="data.type">
     <template #content>
-      <div class="audio-area">
-        <span class="label-text">音频</span>
-        <div class="upload-box">
-          <el-upload
-            class="picture-uploader"
-            action="no"
-            accept=".mp3,.acc,.wma"
-            :show-file-list="false"
-            :auto-upload="false"
-            :before-upload="beforePictureUpload"
-          >
-            <el-button>选取图片文件</el-button>
-          </el-upload>
-          <el-button size="small" type="primary" @click="uploadPicture">上传</el-button>
-        </div>
-      </div>
-      <el-divider />
-      <div class="upload-tip">支持上传mp3、acc、wma,等格式音频文件,单个文件最大100MB,总文件体积不超1G。</div>
-      <ul class="file-list">
-        <li v-for="({ name, url }, i) in fileList" :key="url">
-          <div class="file-name">
-            <SvgIcon icon-class="picture" size="12" />
-            <span>{{ name }}</span>
-          </div>
-          <SvgIcon icon-class="delete-black" size="12" @click="removeFile(i)" />
-        </li>
-      </ul>
+      <UploadFile
+        id="PictureUploadPage"
+        :moduleData="data"
+        :labelText="labelText"
+        :acceptFileType="acceptFileType"
+        :uploadTip="uploadTip"
+        :iconClass="iconClass"
+      />
     </template>
   </ModuleBase>
 </template>
 
 <script>
-import { PictureData } from '@/views/book/courseware/data/picture';
+import { getPictureData } from '@/views/book/courseware/data/picture';
 import ModuleMixin from '../../common/ModuleMixin';
+import UploadFile from '../common/UploadFile.vue';
 
 export default {
   name: 'PicturePage',
-  components: {},
   mixins: [ModuleMixin],
+  components: { UploadFile },
   data() {
     return {
-      data: PictureData,
-      fileList: [
-        {
-          name: 'food.jpeg',
-          url: 'https://fuss10.elemecdn.com/3/63/4e7f3a15429bfda99bce42a18cdd1jpeg.jpeg?imageMogr2/thumbnail/360x360/format/webp/quality/100'
-        },
-        {
-          name: 'food2.jpeg',
-          url: 'https://fuss10.elemecdn.com/3/63/4e7f3a15429bfda99bce42a18cdd1jpeg.jpeg?imageMogr2/thumbnail/360x360/format/webp/quality/100'
-        },
-      ],
+      data: getPictureData(),
+      file_info_list: [],
+      labelText: '图片',
+      acceptFileType: '.jpg,.png,.jpeg',
+      uploadTip: '支持上传jpg、png、jpeg,等格式图片文件,单个文件最大1MB,总文件体积不超100MB。',
+      iconClass: 'picture',
     };
   },
   computed: {},
@@ -60,82 +37,4 @@ export default {
 };
 </script>
 
-<style lang="scss" scoped>
-.module-content {
-  .audio-area {
-    display: flex;
-    column-gap: 16px;
-    align-items: center;
-
-    .label-text {
-      font-size: 14px;
-      color: #4e5969;
-    }
-
-    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;
-
-    .picture-uploader {
-      flex: 1;
-
-      :deep .el-upload {
-        &--text {
-          width: 100%;
-          background-color: #f2f3f5;
-          border-radius: 2px 0 0 2px;
-
-          .el-button {
-            width: 100%;
-            color: #86909c;
-            text-align: left;
-          }
-        }
-      }
-    }
-
-    .el-button {
-      border-radius: 0 2px 2px 0;
-    }
-  }
-
-  .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;
-        width: 360px;
-        max-width: 360px;
-        padding: 8px 12px;
-        font-size: 14px;
-        color: #1d2129;
-        background-color: #f7f8fa;
-      }
-    }
-  }
-}
-</style>
+<style lang="scss" scoped></style>

+ 29 - 32
src/views/book/courseware/create/components/base/picture/PictureSetting.vue

@@ -1,15 +1,15 @@
 <template>
   <div>
-    <el-form :model="setting" :label-position="labelPosition" label-width="72px">
+    <el-form :model="property" :label-position="labelPosition" label-width="72px">
       <el-form-item label="序号" class="serial-number">
-        <el-input v-model="setting.serialNumber" />
+        <el-input v-model="property.serial_number" />
         <SvgIcon icon-class="switch" size="14" />
       </el-form-item>
       <el-form-item>
         <el-radio
-          v-for="{ value, label } in serialNumberTypeList"
+          v-for="{ value, label } in snGenerationMethodList"
           :key="value"
-          v-model="setting.other.serial_number_type"
+          v-model="property.sn_generation_method"
           :label="value"
         >
           {{ label }}
@@ -19,58 +19,58 @@
         <div class="grid-container">
           <div class="top">
             <el-button
-              :class="['top-start', { active: 'top-start' === setting.numberPosition }]"
+              :class="['top-start', { active: 'top-start' === property.sn_position }]"
               @click="changeNumberPosition('top-start')"
             />
             <el-button
-              :class="['top', { active: 'top' === setting.numberPosition }]"
+              :class="['top', { active: 'top' === property.sn_position }]"
               @click="changeNumberPosition('top')"
             />
             <el-button
-              :class="['top-end', { active: 'top-end' === setting.numberPosition }]"
+              :class="['top-end', { active: 'top-end' === property.sn_position }]"
               @click="changeNumberPosition('top-end')"
             />
           </div>
           <div class="left">
             <el-button
-              :class="['left-start', { active: 'left-start' === setting.numberPosition }]"
+              :class="['left-start', { active: 'left-start' === property.sn_position }]"
               @click="changeNumberPosition('left-start')"
             />
             <el-button
-              :class="['left', { active: 'left' === setting.numberPosition }]"
+              :class="['left', { active: 'left' === property.sn_position }]"
               @click="changeNumberPosition('left')"
             />
             <el-button
-              :class="['left-end', { active: 'left-end' === setting.numberPosition }]"
+              :class="['left-end', { active: 'left-end' === property.sn_position }]"
               @click="changeNumberPosition('left-end')"
             />
           </div>
           <div class="main"></div>
           <div class="right">
             <el-button
-              :class="['right-start', { active: 'right-start' === setting.numberPosition }]"
+              :class="['right-start', { active: 'right-start' === property.sn_position }]"
               @click="changeNumberPosition('right-start')"
             />
             <el-button
-              :class="['right', { active: 'right' === setting.numberPosition }]"
+              :class="['right', { active: 'right' === property.sn_position }]"
               @click="changeNumberPosition('right')"
             />
             <el-button
-              :class="['right-end', { active: 'right-end' === setting.numberPosition }]"
+              :class="['right-end', { active: 'right-end' === property.sn_position }]"
               @click="changeNumberPosition('right-end')"
             />
           </div>
           <div class="bottom">
             <el-button
-              :class="['bottom-start', { active: 'bottom-start' === setting.numberPosition }]"
+              :class="['bottom-start', { active: 'bottom-start' === property.sn_position }]"
               @click="changeNumberPosition('bottom-start')"
             />
             <el-button
-              :class="['bottom', { active: 'bottom' === setting.numberPosition }]"
+              :class="['bottom', { active: 'bottom' === property.sn_position }]"
               @click="changeNumberPosition('bottom')"
             />
             <el-button
-              :class="['bottom-end', { active: 'bottom-end' === setting.numberPosition }]"
+              :class="['bottom-end', { active: 'bottom-end' === property.sn_position }]"
               @click="changeNumberPosition('bottom-end')"
             />
           </div>
@@ -79,9 +79,9 @@
       <el-divider />
       <el-form-item label="查看方式">
         <el-radio
-          v-for="{ value, label } in viewingMethodsList"
+          v-for="{ value, label } in viewMethodList"
           :key="value"
-          v-model="setting.viewingMethods"
+          v-model="property.view_method"
           :label="value"
         >
           {{ label }}
@@ -92,24 +92,21 @@
 </template>
 
 <script>
-import { serialNumberTypeList, viewingMethodsList } from '@/views/book/courseware/data/common';
+import { snGenerationMethodList, viewMethodList } from '@/views/book/courseware/data/common';
 
 export default {
   name: 'AudioSetting',
   data() {
     return {
-      serialNumberTypeList,
-      viewingMethodsList,
+      snGenerationMethodList,
+      viewMethodList,
       labelPosition: 'left',
       isSet: false, // 父组件是否已设置
-      setting: {
-        serialNumber: 1, // 序号
-        numberPosition: 'top-start', // 序号位置
-        viewingMethods: viewingMethodsList[0].value, // 查看方式
-        // 其他属性
-        other: {
-          serial_number_type: serialNumberTypeList[0].value, // 题号类型
-        },
+      property: {
+        serial_number: 1, // 序号
+        sn_position: 'top-start', // 序号位置:top-start top top-end 等
+        sn_generation_method: snGenerationMethodList[0].value, // 序号生成方式:recalculate 重新计算follow 跟随
+        view_method: viewMethodList[0].value, // 查看方式:independent 独立 list 列表icon 图标
       },
     };
   },
@@ -135,10 +132,10 @@ export default {
 
     /**
      * @description 改变序号位置
-     * @param {String} numberPosition
+     * @param {String} sn_position
      */
-    changeNumberPosition(numberPosition) {
-      this.setting.numberPosition = numberPosition;
+    changeNumberPosition(sn_position) {
+      this.property.sn_position = sn_position;
     },
   },
 };

+ 1 - 1
src/views/book/courseware/create/components/base/spacing/Spacing.vue

@@ -23,7 +23,7 @@ export default {
   computed: {
     settingStyle() {
       return {
-        height: `${this.data.setting.height}px`,
+        height: `${this.data.property.height}px`,
         width: '100%',
         backgroundColor: '#f0f0f0',
       };

+ 3 - 3
src/views/book/courseware/create/components/base/spacing/SpacingSetting.vue

@@ -1,8 +1,8 @@
 <template>
   <div>
-    <el-form :model="setting" :label-position="labelPosition" label-width="56px">
+    <el-form :model="property" :label-position="labelPosition" label-width="56px">
       <el-form-item label="高度">
-        <el-input v-model="setting.height" />
+        <el-input v-model="property.height" />
       </el-form-item>
     </el-form>
   </div>
@@ -17,7 +17,7 @@ export default {
   data() {
     return {
       labelPosition: 'left',
-      setting: {
+      property: {
         height: 40,
       },
     };

+ 67 - 0
src/views/book/courseware/create/components/common/FillDescribe.vue

@@ -0,0 +1,67 @@
+<template>
+  <el-dialog title="" :visible="visible" width="260px" top="38vh" :show-close="false" @close="dialogClose">
+    <el-input v-model="form.title" autocomplete="off" placeholder="标题"></el-input>
+    <el-input type="textarea" v-model="form.describe" placeholder="介绍"></el-input>
+    <template slot="footer">
+      <el-button size="medium" @click="dialogClose">取消</el-button>
+      <el-button type="primary" size="medium" @click="confirm">确定</el-button>
+    </template>
+  </el-dialog>
+</template>
+
+<script>
+export default {
+  name: 'FillDescribe',
+  props: {
+    visible: {
+      type: Boolean,
+      required: true,
+    },
+  },
+  data() {
+    return {
+      form: {
+        title: '',
+        describe: '',
+      },
+    };
+  },
+  methods: {
+    dialogClose() {
+      this.$emit('update:visible', false);
+    },
+    confirm() {
+      this.$emit('update:visible', false);
+      // this.$emit('fillDescribeToFile');
+    },
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+.el-dialog {
+  :deep &__header {
+    padding: 0;
+  }
+
+  :deep &__body {
+    display: flex;
+    flex-direction: column;
+    row-gap: 8px;
+    padding: 8px 8px 0;
+
+    .el-textarea__inner {
+      height: 108px;
+    }
+  }
+
+  :deep &__footer {
+    display: flex;
+    padding: 8px;
+
+    .el-button {
+      flex: 1;
+    }
+  }
+}
+</style>

+ 0 - 1
src/views/book/courseware/create/components/common/ModuleBase.vue

@@ -30,7 +30,6 @@ export default {
   },
   data() {
     return {
-      isShow: false,
       componentNameList,
     };
   },

+ 7 - 7
src/views/book/courseware/create/components/common/ModuleMixin.js

@@ -1,13 +1,13 @@
 // 组件混入
 import ModuleBase from './ModuleBase.vue';
-import { serialNumberTypeList, viewingMethodsList, audioViewingMethodsList } from '@/views/book/courseware/data/common';
+import { snGenerationMethodList, viewMethodList, audioViewMethodList } from '@/views/book/courseware/data/common';
 
 const mixin = {
   data() {
     return {
-      serialNumberTypeList,
-      viewingMethodsList,
-      audioViewingMethodsList,
+      snGenerationMethodList,
+      viewMethodList,
+      audioViewMethodList,
     };
   },
   props: {
@@ -31,15 +31,15 @@ const mixin = {
      * @description 显示设置
      */
     showSetting() {
-      this.$emit('showSetting', this.data.setting, this.data.type, this.id);
+      this.$emit('showSetting', this.data.property, this.data.type, this.id);
     },
     /**
      * @description 更新属性
      * @param {object} setting 属性
      * @param {string} type 属性类型
      */
-    updateSetting(setting) {
-      this.data.setting = setting;
+    updateSetting(property) {
+      this.data.property = property;
     },
   },
 };

+ 5 - 5
src/views/book/courseware/create/components/common/SettingMixin.js

@@ -1,18 +1,18 @@
-import { questionNumberTypeList } from '../../../data/common';
+import { snGenerationMethodList } from '../../../data/common';
 
 const mixin = {
   data() {
     return {
-      questionNumberTypeList,
+      snGenerationMethodList,
     };
   },
   methods: {
     /**
      * @description 设置属性
-     * @param {object} setting 属性
+     * @param {object} property 属性
      */
-    setSetting(setting) {
-      this.setting = setting;
+    setSetting(property) {
+      this.property = property;
     },
   },
 };

+ 16 - 13
src/views/book/courseware/data/audio.js

@@ -1,15 +1,18 @@
-import { serialNumberTypeList, audioViewingMethodsList } from '@/views/book/courseware/data/common';
+import { snGenerationMethodList, audioViewMethodList } from '@/views/book/courseware/data/common';
 
-export let AudioData = {
-  type: 'audio',
-  title: '音频',
-  setting: {
-    serialNumber: 1, // 序号
-    numberPosition: 'top-start', // 序号位置
-    audioViewingMethods: audioViewingMethodsList[0].value, // 查看方式
-    // 其他属性
-    other: {
-      serial_number_type: serialNumberTypeList[0].value, // 题号类型
+export function getAudioData() {
+  return {
+    id: '1',
+    type: 'audio',
+    title: '音频',
+    single_size: 10, // 单位MB
+    total_size: 1024, // 单位MB
+    property: {
+      serial_number: 1, // 序号
+      sn_type: 'letter', //序号类型:letter字母 number数字  capital大写字母 bracket_number括号数字
+      sn_position: 'top-start', // 序号位置:top-start top top-end,left-start left left-end等
+      sn_generation_method: snGenerationMethodList[0].value, // 序号生成方式:recalculate重新计算 follow 跟随
+      view_method: audioViewMethodList[0].value, // 查看方式:independent独立 list列表 icon图标
     },
-  },
-};
+  };
+}

+ 57 - 5
src/views/book/courseware/data/common.js

@@ -1,18 +1,70 @@
-// 序号类型
-export const serialNumberTypeList = [
+// 序号生成方式
+export const snGenerationMethodList = [
   { value: 'recalculate', label: '重新计算' },
   { value: 'follow', label: '跟随上题' },
 ];
 
 // 查看方式
-export const viewingMethodsList = [
+export const viewMethodList = [
   { value: 'independent', label: '独立排放' },
   { value: 'list', label: '播放列表' },
 ];
 
-// 查看方式
-export const audioViewingMethodsList = [
+// 音频查看方式
+export const audioViewMethodList = [
   { value: 'independent', label: '独立' },
   { value: 'list', label: '列表' },
   { value: 'icon', label: '图标' },
 ];
+
+// 序号类型
+export const serialNumberTypeList = [
+  { value: 'letter', label: '字母' },
+  { value: 'number', label: '数字' },
+  { value: 'capital', label: '大写字母' },
+  { value: 'bracket_number', label: '括号数字' },
+];
+
+/**
+ * 判断序号类型
+ * @param {string} str
+ */
+export function checkString(str) {
+  const number = /\d/.test(str); // 判断是否包含数字
+  const letter = /[a-zA-Z]/.test(str); // 判断是否包含字母
+  const capital = /[A-Z]/.test(str); // 判断是否包含大写字母
+  const bracket_number = /\(\d+\)/.test(str); // 判断是否包含括号数字,例如 (123)
+  return { number, letter, capital, bracket_number };
+}
+
+/**
+ * 改变选项类型
+ * @param {string} serial_number 序号
+ */
+export function switchSerialNumber(serial_number) {
+  let sn_type = checkString(serial_number);
+  let index = serialNumberTypeList.findIndex(({ value }) => value === sn_type);
+  sn_type = serialNumberTypeList[index + 1]?.value || serialNumberTypeList[0].value;
+}
+
+// 计算选项方法
+export const computeOptionMethods = {
+  [serialNumberTypeList[0].value]: (i) => `${String.fromCharCode(97 + i)}.`,
+  [serialNumberTypeList[1].value]: (i) => `${i + 1}.`,
+  [serialNumberTypeList[2].value]: (i) => `${String.fromCharCode(65 + i)}.`,
+  [serialNumberTypeList[3].value]: (i) => `(${i + 1})`,
+};
+
+/**
+ * 计算选项序号
+ * @param {Number} i 序号
+ * @param {String} sn_type 选项类型
+ * @returns String 题号
+ */
+export function computedSerialNumber(i, sn_type) {
+  const computationMethod = computeOptionMethods[sn_type];
+  if (computationMethod) {
+    return computationMethod(i);
+  }
+  return '';
+}

+ 3 - 2
src/views/book/courseware/data/divider.js

@@ -1,10 +1,11 @@
 export function getDividerData() {
   return {
+    id: '1',
     type: 'divider',
     title: '分割线',
-    setting: {
+    property: {
       height: 40,
-      type: 'solid', // dotted 虚线
+      line_type: 'solid', // dotted 虚线
     },
   };
 }

+ 16 - 13
src/views/book/courseware/data/picture.js

@@ -1,15 +1,18 @@
-import { serialNumberTypeList, viewingMethodsList } from '@/views/book/courseware/data/common';
+import { snGenerationMethodList, viewMethodList } from '@/views/book/courseware/data/common';
 
-export let PictureData = {
-  type: 'picture',
-  title: '图片',
-  setting: {
-    serialNumber: 1, // 序号
-    numberPosition: 'top-start', // 序号位置
-    viewingMethods: viewingMethodsList[0].value, // 查看方式
-    // 其他属性
-    other: {
-      serial_number_type: serialNumberTypeList[0].value, // 题号类型
+export function getPictureData() {
+  return {
+    id: '1',
+    type: 'picture',
+    title: '图片',
+    single_size: 1, // 单位MB
+    total_size: 100, // 单位MB
+    property: {
+      serial_number: 1, // 序号
+      sn_type: 'letter', //序号类型:letter字母 number数字  capital大写字母 bracket_number括号数字
+      sn_position: 'top-start', // 序号位置:top-start top top-end,left-start left left-end等
+      sn_generation_method: snGenerationMethodList[0].value, // 序号生成方式:recalculate重新计算 follow跟随
+      view_method: viewMethodList[0].value, // 查看方式:independent独立 list列表 icon图标
     },
-  },
-};
+  };
+}

+ 2 - 1
src/views/book/courseware/data/spacing.js

@@ -1,8 +1,9 @@
 export function getSpacingData() {
   return {
+    id: '1',
     type: 'spacing',
     title: '间距',
-    setting: {
+    property: {
       height: 40,
     },
   };