Ver código fonte

使用资源功能

dusenyao 1 dia atrás
pai
commit
605f03698f

+ 14 - 6
src/views/book/courseware/create/components/CreateCanvas.vue

@@ -134,12 +134,11 @@ export default {
     },
   },
   data() {
-    const { book_id, chapter_id } = this.$route.query;
+    const { project_id } = this.$route.query;
 
     return {
       courseware_id: this.$route.params.courseware_id,
-      book_id,
-      chapter_id,
+      project_id,
       data: {
         background_image_url: '',
         background_position: {
@@ -1080,9 +1079,18 @@ export default {
      * @returns {Object} 样式对象
      */
     computedGroupLine(rowIdList) {
-      const canvasTop = this.$refs.canvas.getBoundingClientRect().top; // 获取画布顶部位置
-      const firstRowTop = this.$el.querySelector(`.row-checkbox.${rowIdList[0]}`).getBoundingClientRect().top; // 获取第一个复选框的顶部位置
-      const secRowTop = this.$el.querySelector(`.row-checkbox.${rowIdList[1]}`).getBoundingClientRect().top; // 获取第二个复选框的顶部位置
+      // 获取画布顶部位置
+      const canvas = this.$refs.canvas;
+      const firstCheckbox = this.$el.querySelector(`.row-checkbox.${rowIdList[0]}`);
+      const secCheckbox = this.$el.querySelector(`.row-checkbox.${rowIdList[1]}`);
+      // DOM 未渲染,返回默认样式或空对象
+      if (!canvas || !firstCheckbox || !secCheckbox) {
+        return {};
+      }
+
+      const canvasTop = canvas.getBoundingClientRect().top;
+      const firstRowTop = firstCheckbox.getBoundingClientRect().top;
+      const secRowTop = secCheckbox.getBoundingClientRect().top;
 
       return {
         top: `${firstRowTop - canvasTop + 22}px`,

+ 15 - 0
src/views/book/courseware/create/components/base/3d_model/3DModel.vue

@@ -0,0 +1,15 @@
+<template>
+  <div></div>
+</template>
+
+<script>
+export default {
+  name: '3DModelPage',
+  data() {
+    return {};
+  },
+  methods: {},
+};
+</script>
+
+<style lang="scss" scoped></style>

+ 350 - 0
src/views/book/courseware/create/components/base/common/SelectResource.vue

@@ -0,0 +1,350 @@
+<template>
+  <el-dialog
+    custom-class="select-resource-dialog"
+    title="选择资源"
+    :visible="visible"
+    :close-on-click-modal="false"
+    @close="dialogClose"
+  >
+    <div class="search-box">
+      <div class="search-left">
+        <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>
+    </div>
+
+    <div v-loading="loading" class="sources-box">
+      <div
+        v-for="(item, index) in list"
+        :key="index"
+        class="sources-item"
+        :class="[
+          select_sources_id === item.id ? 'active' : '',
+          type_index === 5 ? 'sources-item-txt' : type_index === 3 ? 'sources-item-zip' : '',
+        ]"
+        @click="selectSourceNode(item)"
+      >
+        <template v-if="type_index === 0"> <el-image :src="item.file_url" fit="contain" /></template>
+        <template v-else-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-else-if="type_index === 2">
+          <video controls :src="item.file_url" width="100%" height="140px"></video>
+        </template>
+        <template v-else-if="type_index === 3">
+          <iframe class="sources-item-border" :src="item.new_path" width="100%" height="300px" frameborder="0"></iframe>
+        </template>
+        <template v-else-if="type_index === 4"> </template>
+        <template v-else-if="type_index === 5">
+          <iframe class="sources-item-border" :src="item.new_path" width="100%" height="300px" frameborder="0"></iframe>
+        </template>
+
+        <el-popover placement="bottom" width="300" trigger="hover">
+          <div class="sources-info">
+            <p class="name">名称:{{ item.name }}</p>
+            <p class="label">标签:{{ item.label }}</p>
+            <p class="name">简介:{{ item.intro }}</p>
+            <p class="label">修改时间:{{ item.last_modify_time }}</p>
+            <p class="label">文件大小:{{ item.file_size_desc }}</p>
+          </div>
+          <template #reference>
+            <div class="sources-info">
+              <p class="name">{{ item.name }}</p>
+              <b class="label">{{ item.label }}</b>
+            </div>
+          </template>
+        </el-popover>
+      </div>
+    </div>
+    <PaginationPage ref="pagination" :total="total" @getList="queryList" />
+
+    <div slot="footer">
+      <el-button @click="dialogClose">取消</el-button>
+      <el-button type="primary" @click="confirmSelect">选择</el-button>
+    </div>
+  </el-dialog>
+</template>
+
+<script>
+import PaginationPage from '@/components/PaginationPage.vue';
+
+import { PageQueryProjectResourceList } from '@/api/list';
+import { H5StartupFile } from '@/api/app';
+
+const Base64 = require('js-base64').Base64;
+
+export default {
+  name: 'SelectResource',
+  components: {
+    PaginationPage,
+  },
+  props: {
+    visible: {
+      type: Boolean,
+      default: false,
+    },
+    // 教材 id
+    projectId: {
+      type: String,
+      default: '',
+    },
+    // 课件 id
+    coursewareId: {
+      type: String,
+      default: '',
+    },
+    // 允许的文件类型
+    accept: {
+      type: String,
+      default: '*',
+    },
+  },
+  data() {
+    const type_list = [
+      {
+        value: 0,
+        key: 'image',
+        label: '图片',
+      },
+      {
+        value: 1,
+        key: 'audio',
+        label: '音频',
+      },
+      {
+        value: 2,
+        key: 'video',
+        label: '视频',
+      },
+      {
+        value: 3,
+        key: 'h5_game',
+        label: 'H5 游戏',
+      },
+      {
+        value: 4,
+        key: '3d_model',
+        label: '3D 模型',
+      },
+      {
+        value: 5,
+        key: 'text',
+        label: '文本',
+      },
+    ];
+
+    return {
+      loading: false,
+      list: [],
+      total: 0,
+      page_capacity: 10,
+      cur_page: 1,
+      type_list,
+      type_index: type_list.findIndex((item) => item.key === this.accept) || 0,
+      sort_list: [
+        {
+          value: 'name',
+          label: '名称',
+        },
+        {
+          value: 'label',
+          label: '标签',
+        },
+        {
+          value: 'last_modify_time',
+          label: '修改时间',
+        },
+        {
+          value: 'file_size',
+          label: '文件大小',
+        },
+      ], // 排序分类
+      sort_value: '', // 排序值
+      isDesc: false, // 排序是否为倒序
+      search_content: '',
+      select_sources_id: '',
+    };
+  },
+  created() {},
+  methods: {
+    dialogClose() {
+      this.$emit('update:visible', false);
+      this.$emit('close');
+    },
+    // 查询列表
+    queryList(data) {
+      this.loading = true;
+      this.list = [];
+      if (data) {
+        this.page_capacity = data.page_capacity;
+        this.cur_page = data.cur_page;
+      } else {
+        this.page_capacity = 10;
+        this.cur_page = 1;
+      }
+
+      let order_column_list = [];
+      if (this.sort_value) {
+        order_column_list = [this.isDesc ? `${this.sort_value}:desc` : this.sort_value];
+      }
+      PageQueryProjectResourceList({
+        page_capacity: this.page_capacity,
+        cur_page: this.cur_page,
+        project_id: this.projectId,
+        book_chapter_node_id: this.coursewareId,
+        type: this.type_list[this.type_index].value,
+        name_or_label: this.search_content,
+        order_column_list,
+      })
+        .then(({ total_count, resource_list }) => {
+          this.total = total_count;
+          if (this.type_index === 5) {
+            resource_list.forEach((item) => {
+              item.new_path = `${this.file_preview_url}onlinePreview?url=${Base64.encode(item.file_url)}`;
+            });
+            this.loading = false;
+          } else if (this.type_index === 3) {
+            resource_list.forEach((item) => {
+              H5StartupFile({ file_id: item.file_id, index_file_name: 'index.html' }).then((res) => {
+                item.new_path = res.file_url;
+                this.loading = false;
+              });
+            });
+          } else {
+            this.loading = false;
+          }
+          this.list = resource_list;
+        })
+        .catch(() => {
+          this.loading = false;
+        });
+    },
+    selectSourceNode(item) {
+      this.select_sources_id = item.id;
+    },
+    // 切换排序升序降序
+    changeSort() {
+      this.isDesc = !this.isDesc;
+    },
+    getCurTime() {},
+    // 选择资源
+    confirmSelect() {
+      if (!this.select_sources_id) {
+        this.$message.error('请选择资源');
+        return;
+      }
+      const selectedResource = this.list.find((item) => item.id === this.select_sources_id);
+
+      this.$emit('selectResource', {
+        file_id: selectedResource.file_id,
+        file_name: selectedResource.name,
+        file_url: selectedResource.file_url,
+        intro: selectedResource.intro,
+      });
+    },
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+.select-resource-dialog {
+  .search-box {
+    display: flex;
+    justify-content: space-between;
+    padding: 5px;
+
+    .search-left {
+      display: flex;
+      flex-flow: wrap;
+      gap: 5px;
+      align-items: center;
+    }
+
+    .label-btn {
+      padding: 5px 10px;
+      cursor: pointer;
+      background: #ebebeb;
+      border-radius: 4px;
+
+      &.active {
+        color: #f90;
+        background: #ffefd6;
+      }
+    }
+
+    .el-input,
+    .el-select {
+      width: 120px;
+    }
+  }
+
+  .sources-box {
+    display: flex;
+    flex-flow: wrap;
+    gap: 10px;
+    place-content: start start;
+    height: 400px;
+    padding: 5px;
+    overflow: auto;
+
+    .sources-item {
+      width: 200px;
+      cursor: pointer;
+
+      &-txt {
+        width: 400px;
+      }
+
+      &-zip {
+        width: 500px;
+      }
+
+      &-border {
+        border: 1px solid #ccc;
+      }
+
+      .el-image {
+        width: 100%;
+        height: 140px;
+        border: 1px solid #ccc;
+      }
+
+      .name,
+      .label {
+        margin: 5px 0;
+        font-size: 14px;
+        font-weight: normal;
+        line-height: 1.3;
+      }
+
+      :deep .audioLine {
+        background: #f2f3f5;
+      }
+
+      &.active {
+        .el-image {
+          border-color: #f90;
+        }
+
+        .name,
+        .label {
+          color: #f90;
+        }
+      }
+    }
+  }
+}
+</style>

+ 46 - 3
src/views/book/courseware/create/components/base/common/UploadFile.vue

@@ -19,6 +19,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="useResource">使用资源</el-button>
       </div>
     </div>
     <el-divider />
@@ -29,7 +30,6 @@
           <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"
@@ -53,20 +53,31 @@
     </ul>
 
     <FillDescribe :file-data="curFile" :visible.sync="visible" @fillDescribeToFile="fillDescribeToFile" />
+
+    <SelectResource
+      :visible.sync="visibleResource"
+      :project-id="project_id"
+      :accept="accept"
+      :courseware-id="courseware_id"
+      @selectResource="selectResource"
+    />
   </div>
 </template>
 
 <script>
 import { fileUpload } from '@/api/app';
 import { conversionSize } from '@/utils/common';
+
 import FillDescribe from '../../common/FillDescribe.vue';
+import SelectResource from './SelectResource.vue';
 
 export default {
   name: 'UploadFile',
   components: {
     FillDescribe,
+    SelectResource,
   },
-  inject: ['property'],
+  inject: ['property', 'courseware_id', 'project_id'],
   props: {
     // 课件id
     coursewareId: {
@@ -86,7 +97,7 @@ export default {
     // 上传支持的文件格式
     acceptFileType: {
       type: String,
-      default: '',
+      default: '*',
     },
     // 提示语
     uploadTip: {
@@ -137,8 +148,26 @@ export default {
         file_id_list: this.fileIdList,
         file_info_list: this.fileInfoList,
       },
+      visibleResource: false,
     };
   },
+  computed: {
+    accept() {
+      let accept = '*';
+      if (this.acceptFileType.includes('.mp3')) {
+        accept = 'audio';
+      } else if (this.acceptFileType.includes('.mp4')) {
+        accept = 'video';
+      } else if (this.acceptFileType.includes('.jpg')) {
+        accept = 'image';
+      } else if (this.acceptFileType.includes('.zip')) {
+        accept = 'h5_game';
+      } else if (this.acceptFileType.includes('.txt')) {
+        accept = 'text';
+      }
+      return accept;
+    },
+  },
   watch: {
     content: {
       handler(val) {
@@ -325,6 +354,16 @@ export default {
         Object.assign(en, file);
       }
     },
+    // 使用资源
+    useResource() {
+      this.visibleResource = true;
+    },
+    selectResource({ file_id, file_name, file_url, intro }) {
+      this.content.file_list.push({ file_id, file_name, file_url });
+      this.content.file_id_list.push(file_id);
+      this.content.file_info_list.push({ file_id, file_name, title: '', intro });
+      this.visibleResource = false;
+    },
   },
 };
 </script>
@@ -360,6 +399,10 @@ export default {
     display: flex;
     justify-content: space-between;
 
+    .el-button + .el-button {
+      margin-left: 2px;
+    }
+
     .file-uploader {
       flex: 1;
 

+ 0 - 1
src/views/book/courseware/create/components/base/stem/Stem.vue

@@ -102,7 +102,6 @@ export default {
           id: Math.random().toString(36).substring(2, 12),
         },
       ]);
-      // console.log(this.data.mind_map);
     },
     /**
      * 过滤 html,防止 xss 攻击

+ 1 - 1
src/views/book/courseware/create/components/base/upload_preview/UploadPreview.vue

@@ -17,7 +17,7 @@
         :limit="100"
         @updateFileList="updateFileList"
       />
-      <el-form :model="data" label-width="72px" label-position="left"> </el-form>
+      <el-form :model="data" label-width="72px" label-position="left" />
     </template>
   </ModuleBase>
 </template>

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

@@ -55,7 +55,7 @@ const mixin = {
       this.borderColorObj.value = newVal;
     },
   },
-  inject: ['courseware_id'],
+  inject: ['courseware_id', 'project_id'],
   created() {
     ContentGetCoursewareComponentContent({ courseware_id: this.courseware_id, component_id: this.id }).then(
       ({ content }) => {

+ 0 - 1
src/views/book/courseware/create/components/question/fill/Fill.vue

@@ -77,7 +77,6 @@ export default {
     identifyText() {
       this.data.model_essay = [];
       this.data.answer.answer_list = [];
-      console.log('识别文本', this.data.content);
 
       this.data.content
         .split(/<(p|div)[^>]*>(.*?)<\/(p|div)>/g)

+ 0 - 4
src/views/book/courseware/create/components/question/write/Write.vue

@@ -45,8 +45,6 @@
 
 <script>
 import ModuleMixin from '../../common/ModuleMixin';
-import SoundRecord from '@/views/book/courseware/create/components/question/fill/components/SoundRecord.vue';
-import UploadAudio from '@/views/book/courseware/create/components/question/fill/components/UploadAudio.vue';
 import UploadFile from '@/views/book/courseware/create/components/base/common/UploadFile.vue';
 
 import { getWriteData } from '@/views/book/courseware/data/write';
@@ -58,8 +56,6 @@ import CorrectPinyin from '@/views/book/courseware/create/components/base/common
 export default {
   name: 'WritePage',
   components: {
-    SoundRecord,
-    UploadAudio,
     UploadFile,
     CorrectPinyin,
   },

+ 1 - 0
src/views/book/courseware/create/index.vue

@@ -60,6 +60,7 @@ export default {
     return {
       getCurSettingId: () => this.curSettingId,
       courseware_id: this.courseware_id,
+      project_id: this.$route.query.project_id,
     };
   },
   data() {

+ 19 - 0
src/views/book/courseware/data/3dModel.js

@@ -0,0 +1,19 @@
+import { commonSetProperty } from '@/views/book/courseware/data/common';
+
+export function get3DModelProperty() {
+  return {
+    ...commonSetProperty,
+  };
+}
+
+export function get3DModelData() {
+  return {
+    type: '3DModel',
+    title: '3D模型',
+    property: get3DModelProperty(),
+    model_url: '',
+    mind_map: {
+      node_list: [{ name: '3D模型' }],
+    },
+  };
+}

+ 1 - 3
src/views/book/courseware/data/character.js

@@ -60,9 +60,7 @@ export function getCharacterData() {
     content: '',
     content_list: [],
     mind_map: {
-      node_list: [
-        { name: '汉字' }
-      ], // 思维导图数据
+      node_list: [{ name: '汉字' }], // 思维导图数据
     },
     answer: {
       answer_list: [],

+ 16 - 16
src/views/personal_workbench/project/ProductionResourceManage.vue

@@ -23,22 +23,22 @@
         <div class="search-box">
           <div class="search-left">
             <label
-              class="label-btn"
-              :class="[type_index === index ? 'active' : '']"
               v-for="(item, index) in type_list"
               :key="index"
+              class="label-btn"
+              :class="[type_index === index ? 'active' : '']"
               @click="changeType(index)"
-              >{{ item.label }}</label
             >
-            <el-input v-model="search_content"></el-input>
+              {{ item.label }}
+            </label>
+            <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-option
-            ></el-select>
+            <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"
@@ -47,18 +47,18 @@
             />
           </div>
         </div>
-        <div class="sources-box" :style="{ height: height + 'px' }" v-if="height > 0" v-loading="boxLoading">
+        <div v-if="height > 0" v-loading="boxLoading" class="sources-box" :style="{ height: height + 'px' }">
           <div
             v-for="(item, index) in list"
             :key="index"
-            @click="selectSourceNode(item)"
             class="sources-item"
             :class="[
               select_sources_id === item.id ? 'active' : '',
               type_index === 5 ? 'sources-item-txt' : type_index === 3 ? 'sources-item-zip' : '',
             ]"
+            @click="selectSourceNode(item)"
           >
-            <template v-if="type_index === 0"> <el-image :src="item.file_url" fit="contain"></el-image></template>
+            <template v-if="type_index === 0"> <el-image :src="item.file_url" fit="contain" /></template>
             <template v-else-if="type_index === 1">
               <AudioLine
                 ref="audioLine"
@@ -132,7 +132,7 @@
         :icon-class="iconClass"
         :limit="limit"
         :single-size="200"
-        :uploadTip="uploadTip"
+        :upload-tip="uploadTip"
         @updateFileList="updateFileList"
       />
       <footer style="text-align: right">
@@ -176,7 +176,7 @@
     >
       <el-form ref="form" label-width="80px">
         <el-form-item prop="name" label="移动到">
-          <el-cascader v-model="sourceMoveId" :props="{ checkStrictly: true }" :options="node_list"></el-cascader>
+          <el-cascader v-model="sourceMoveId" :props="{ checkStrictly: true }" :options="node_list" />
         </el-form-item>
         <el-form-item style="text-align: right">
           <el-button @click="sourceMoveFlag = false">取 消</el-button>
@@ -339,7 +339,7 @@ export default {
             is_leaf_chapter: 'false',
             level_index: '0',
             label: name,
-            nodes: nodes,
+            nodes,
             children: nodes,
             value: '',
           },
@@ -441,7 +441,7 @@ export default {
 
       let order_column_list = [];
       if (this.sort_value) {
-        order_column_list = [this.isDesc ? this.sort_value + ':desc' : this.sort_value];
+        order_column_list = [this.isDesc ? `${this.sort_value}:desc` : this.sort_value];
       }
       let datas = {
         page_capacity: this.page_capacity,
@@ -450,7 +450,7 @@ export default {
         book_chapter_node_id: this.select_node,
         type: this.type_list[this.type_index].value,
         name_or_label: this.search_content,
-        order_column_list: order_column_list,
+        order_column_list,
       };
       PageQueryProjectResourceList(datas)
         .then(({ total_count, resource_list }) => {