dusenyao 1 éve
szülő
commit
d5bf87b204

+ 46 - 6
src/views/book/courseware/create/components/createCanvas.vue

@@ -294,6 +294,7 @@ export default {
         let delW = gridList[k].width; // 删除的 grid 的 width
         gridList.splice(k, 1);
 
+        // 如果删除后没有 grid 了则删除列
         const colList = this.data.row_list[i].col_list[j];
         if (colList.grid_list.length === 0) {
           this.data.row_list[i].col_list.splice(j, 1);
@@ -305,10 +306,12 @@ export default {
           });
         }
 
+        // 如果删除后没有列了则删除行
         if (this.data.row_list[i].col_list.length === 0) {
           this.data.row_list.splice(i, 1);
         }
 
+        // 如果删除后还有 grid 则重新计算 grid 的 row 和 width
         if (gridList?.length > 0) {
           let maxCol = 0; // 最大列数
           let rowList = new Map();
@@ -339,7 +342,7 @@ export default {
               }
             });
           }
-          // 计算 grid_template_areas 和 grid_template_rows
+          // 计算 grid_template_areas
           let gridStr = '';
           let gridArr = [];
           gridList.forEach(({ grid_area, row }) => {
@@ -349,7 +352,17 @@ export default {
             if (curMaxRow === row) {
               gridArr[row - 1].push(`left-${grid_area} ${grid_area} right-${grid_area}`);
             } else {
-              const str = ` ${grid_area} `.repeat(maxCol * 3 - 2);
+              let filter = gridList.filter((item) => item.row === row);
+              let find = filter.findIndex((item) => item.grid_area === grid_area);
+              let needNum = (maxCol - filter.length) * 3; // 需要的数量
+
+              let str = '';
+              if (filter.length === 1) {
+                str = ` ${grid_area} `.repeat(needNum);
+              } else {
+                let arr = this.splitInteger(needNum, filter.length);
+                str = arr[find] === 0 ? ` ${grid_area} ` : ` ${grid_area} `.repeat(arr[find]);
+              }
               gridArr[row - 1].push(`left-${grid_area} ${str} right-${grid_area}`);
             }
           });
@@ -361,6 +374,7 @@ export default {
 
           // 计算 grid_template_columns
           let gridTemCols = '';
+          let rowOneNum = gridList.filter((item) => item.row === 1).length;
           gridList.forEach((item) => {
             // 如果第一行只有一个 grid 则不需要 - 8px
             if (maxCol === 1 && item.row === 1) {
@@ -368,7 +382,7 @@ export default {
               return;
             }
             if (item.row === 1) {
-              gridTemCols += `calc(${item.width} - 8px) 8px 8px `;
+              gridTemCols += `calc(${item.width} - ${((rowOneNum - 1) * 16) / rowOneNum}px) 8px 8px `;
             }
           });
           colList.grid_template_columns = `0 ${maxCol === 1 ? gridTemCols : gridTemCols.slice(0, gridTemCols.length - 8)} 0`;
@@ -376,6 +390,21 @@ export default {
       };
     },
     /**
+     * 分割整数为多个 3 的倍数
+     * @param {number} num
+     * @param {number} parts
+     */
+    splitInteger(num, parts) {
+      let base = Math.floor(num / parts / 3) * 3;
+      let arr = Array(parts).fill(base);
+      let remainder = num - base * parts;
+      for (let i = 0; remainder > 0; i = (i + 1) % parts) {
+        arr[i] += 3;
+        remainder -= 3;
+      }
+      return arr;
+    },
+    /**
      * 拖拽开始
      * 用点击模拟拖拽
      * @param {MouseEvent} event
@@ -560,7 +589,7 @@ export default {
           curMaxRow = key;
         }
       });
-      // 计算 grid_template_areas 和 grid_template_rows
+      // 计算 grid_template_areas
       let gridStr = '';
       let gridArr = [];
       grid.forEach(({ grid_area, row }) => {
@@ -570,7 +599,17 @@ export default {
         if (curMaxRow === row) {
           gridArr[row - 1].push(`left-${grid_area} ${grid_area} right-${grid_area}`);
         } else {
-          const str = ` ${grid_area} `.repeat(maxCol * 3 - 2);
+          let filter = grid.filter((item) => item.row === row);
+          let find = filter.findIndex((item) => item.grid_area === grid_area);
+          let needNum = (maxCol - filter.length) * 3; // 需要的数量
+
+          let str = '';
+          if (filter.length === 1) {
+            str = ` ${grid_area} `.repeat(needNum + 1);
+          } else {
+            let arr = this.splitInteger(needNum, filter.length);
+            str = arr[find] === 0 ? ` ${grid_area} ` : ` ${grid_area} `.repeat(arr[find] + 1);
+          }
           gridArr[row - 1].push(`left-${grid_area} ${str} right-${grid_area}`);
         }
       });
@@ -582,9 +621,10 @@ export default {
 
       // 计算 grid_template_columns
       let gridTemCols = '';
+      let rowOneNum = grid.filter((item) => item.row === 1).length;
       grid.forEach((item) => {
         if (item.row === 1) {
-          gridTemCols += `calc(${item.width} - 8px) 8px 8px `;
+          gridTemCols += `calc(${item.width} - ${((rowOneNum - 1) * 16) / rowOneNum}px) 8px 8px `;
         }
       });
       col.grid_template_columns = `0 ${gridTemCols.slice(0, gridTemCols.length - 8)} 0`;

+ 10 - 95
src/views/book/courseware/preview/components/audio/Audio.vue

@@ -1,95 +1,28 @@
 <template>
-  <ModuleBase :type="data.type">
-    <template #content>
-      <UploadFile
-        :id="id"
-        ref="audioUploadFile"
-        :module-data="data"
-        :label-text="labelText"
-        :accept-file-type="acceptFileType"
-        :upload-tip="uploadTip"
-        :icon-class="iconClass"
-        @saveDate="saveDate"
-      />
-      <!-- <ul>
-        <li v-for="(file, i) in file_list" :key="i">
-          <audio
-            :id="file.file_id"
-            :src="file.file_url"
-            controls
-            @error=""
-            @play=""
-            @pause=""
-            @timeupdate=""
-            @ended=""
-          ></audio>
-        </li>
-      </ul> -->
-    </template>
-  </ModuleBase>
+  <ul>
+    <li v-for="(file, i) in file_list" :key="i">
+      <audio :id="file.file_id" :src="file.file_url" controls></audio>
+    </li>
+  </ul>
 </template>
 
 <script>
 import { getAudioData } from '@/views/book/courseware/data/audio';
-import { GetCoursewareComponentContent_View } from '@/api/book';
-import ModuleMixin from '../../common/ModuleMixin';
-import UploadFile from '../common/UploadFile.vue';
+
+import PreviewMixin from '../common/PreviewMixin';
 
 export default {
-  name: 'AudioPage',
-  components: { UploadFile },
-  mixins: [ModuleMixin],
+  name: 'AudioPreview',
+  mixins: [PreviewMixin],
   data() {
     return {
       data: getAudioData(),
-      componentId: this.id,
-      coursewareId: this.courseware_id,
       file_list: [
         {
           file_id: '1',
           file_url:
             'https://file-kf.helxsoft.cn/CSFileServer/URL/002/69F3878866F2A1B7DF04C4EFE9ACD04120240402145413PKA8LLFPXRT1BKURSBNIFXGK8EV5VCBIVL9WGCFB_00201-20240402-14-UAG696HY.mp3',
         },
-        {
-          file_id: '2',
-          file_url:
-            'https://file-kf.helxsoft.cn/CSFileServer/URL/002/69F3878866F2A1B7DF04C4EFE9ACD04120240402145413PKA8LLFPXRT1BKURSBNIFXGK8EV5VCBIVL9WGCFB_00201-20240402-14-UAG696HY.mp3',
-        },
-        {
-          file_id: '3',
-          file_url:
-            'https://file-kf.helxsoft.cn/CSFileServer/URL/002/69F3878866F2A1B7DF04C4EFE9ACD04120240402145413PKA8LLFPXRT1BKURSBNIFXGK8EV5VCBIVL9WGCFB_00201-20240402-14-UAG696HY.mp3',
-        },
-        {
-          file_id: '4',
-          file_url:
-            'https://file-kf.helxsoft.cn/CSFileServer/URL/002/69F3878866F2A1B7DF04C4EFE9ACD04120240402145413PKA8LLFPXRT1BKURSBNIFXGK8EV5VCBIVL9WGCFB_00201-20240402-14-UAG696HY.mp3',
-        },
-        {
-          file_id: '5',
-          file_url:
-            'https://file-kf.helxsoft.cn/CSFileServer/URL/002/69F3878866F2A1B7DF04C4EFE9ACD04120240402145413PKA8LLFPXRT1BKURSBNIFXGK8EV5VCBIVL9WGCFB_00201-20240402-14-UAG696HY.mp3',
-        },
-        {
-          file_id: '6',
-          file_url:
-            'https://file-kf.helxsoft.cn/CSFileServer/URL/002/69F3878866F2A1B7DF04C4EFE9ACD04120240402145413PKA8LLFPXRT1BKURSBNIFXGK8EV5VCBIVL9WGCFB_00201-20240402-14-UAG696HY.mp3',
-        },
-        {
-          file_id: '7',
-          file_url:
-            'https://file-kf.helxsoft.cn/CSFileServer/URL/002/69F3878866F2A1B7DF04C4EFE9ACD04120240402145413PKA8LLFPXRT1BKURSBNIFXGK8EV5VCBIVL9WGCFB_00201-20240402-14-UAG696HY.mp3',
-        },
-        {
-          file_id: '8',
-          file_url:
-            'https://file-kf.helxsoft.cn/CSFileServer/URL/002/69F3878866F2A1B7DF04C4EFE9ACD04120240402145413PKA8LLFPXRT1BKURSBNIFXGK8EV5VCBIVL9WGCFB_00201-20240402-14-UAG696HY.mp3',
-        },
-        {
-          file_id: '9',
-          file_url:
-            'https://file-kf.helxsoft.cn/CSFileServer/URL/002/69F3878866F2A1B7DF04C4EFE9ACD04120240402145413PKA8LLFPXRT1BKURSBNIFXGK8EV5VCBIVL9WGCFB_00201-20240402-14-UAG696HY.mp3',
-        },
       ],
       labelText: '音频',
       acceptFileType: '.mp3,.acc,.wma',
@@ -97,25 +30,7 @@ export default {
       iconClass: 'note',
     };
   },
-  created() {
-    this.getCoursewareComponentContent_View();
-  },
-  methods: {
-    saveDate(file) {
-      this.data.id = this.componentId;
-      this.data.file_list.push(file);
-      this.data.file_id_list.push(file.file_id);
-    },
-    getCoursewareComponentContent_View() {
-      GetCoursewareComponentContent_View({ courseware_id: this.coursewareId, component_id: this.componentId }).then(
-        (res) => {
-          this.data = JSON.parse(JSON.parse(res.replace(/\n/g, '').replace(/\t/g, '')).content);
-          // if (content) this.data = JSON.parse(content);
-          // console.log(JSON.parse(content));
-        },
-      );
-    },
-  },
+  methods: {},
 };
 </script>
 

+ 8 - 6
src/views/book/courseware/preview/components/common/PreviewMixin.js

@@ -2,22 +2,24 @@ import { GetCoursewareComponentContent_View } from '@/api/book';
 
 const mixin = {
   data() {
-    return {
-      courseware_id: this.$route.params.courseware_id,
-    };
+    return {};
   },
   props: {
-    componentId: {
+    id: {
+      type: String,
+      required: true,
+    },
+    courseware_id: {
       type: String,
       required: true,
     },
   },
   created() {
-    this.getCoursewareComponentContent();
+    this.getCoursewareComponentContent_View();
   },
   methods: {
     getCoursewareComponentContent_View() {
-      GetCoursewareComponentContent_View({ courseware_id: this.courseware_id, component_id: this.componentId }).then(
+      GetCoursewareComponentContent_View({ courseware_id: this.courseware_id, component_id: this.id }).then(
         ({ content }) => {
           if (content) this.data = JSON.parse(content);
         },

+ 7 - 0
src/views/book/courseware/preview/components/common/data.js

@@ -0,0 +1,7 @@
+import AudioPreview from '../audio/Audio.vue';
+import DividerPreview from '../divider/Divider.vue';
+
+export const previewComponentMap = {
+  audio: AudioPreview,
+  divider: DividerPreview,
+};

+ 12 - 12
src/views/book/courseware/preview/components/divider/Divider.vue

@@ -1,19 +1,15 @@
 <template>
-  <ModuleBase :type="data.type">
-    <template #content>
-      <hr :style="settingStyle" />
-    </template>
-  </ModuleBase>
+  <hr :style="settingStyle" />
 </template>
 
 <script>
 import { getDividerData } from '@/views/book/courseware/data/divider';
 
-import ModuleMixin from '../../common/ModuleMixin';
+import PreviewMixin from '../common/PreviewMixin';
 
 export default {
-  name: 'DividerPage',
-  mixins: [ModuleMixin],
+  name: 'DividerPreview',
+  mixins: [PreviewMixin],
   data() {
     return {
       data: getDividerData(),
@@ -23,13 +19,17 @@ export default {
     settingStyle() {
       return {
         margin: `${this.data.property.height / 2}px 0`,
-        border: 'none',
-        borderTop: `1px ${this.data.property.line_type} #ebebeb`,
+        borderTopStyle: this.data.property.line_type,
       };
     },
   },
-  methods: {},
 };
 </script>
 
-<style lang="scss" scoped></style>
+<style lang="scss" scoped>
+hr {
+  border: none;
+  border-top-color: #ebebeb;
+  border-top-width: 1px;
+}
+</style>

+ 169 - 57
src/views/book/courseware/preview/index.vue

@@ -9,40 +9,64 @@
       <div class="exit">
         <el-button icon="el-icon-close" @click="goBack">退出预览</el-button>
       </div>
-      <div v-for="(data, i) in coursewareDataList" :key="`data-${i}`" class="content">
-        <span class="content-title">
-          <SvgIcon icon-class="menu-2" size="24" />
-          <template v-for="(item, m) in menuList">
-            <span :key="m">{{ item }}</span>
-            <span v-if="i < menuList.length - 1" :key="`separator-${m}`" class="separator">/</span>
-          </template>
-        </span>
-        <div
-          class="courserware"
-          :style="[
-            {
-              backgroundImage: data.background_image_url ? `url(${data.background_image_url})` : '',
-              backgroundSize: data.background_image_url
-                ? `${data.background_position.width}% ${data.background_position.height}%`
-                : '',
-              backgroundPosition: data.background_image_url
-                ? `${data.background_position.left}% ${data.background_position.top}%`
-                : '',
-            },
-          ]"
-        ></div>
-      </div>
+      <template v-for="(data, index) in coursewareDataList">
+        <div v-if="data.hasOwnProperty('row_list')" :key="`data-${index}`" class="content">
+          <span class="content-title">
+            <SvgIcon icon-class="menu-2" size="24" />
+            <template v-for="(item, m) in menuList">
+              <span :key="m">{{ item }}</span>
+              <span v-if="index < menuList.length - 1" :key="`separator-${m}`" class="separator">/</span>
+            </template>
+          </span>
+          <!-- 课件 -->
+          <div
+            class="courserware"
+            :style="[
+              {
+                backgroundImage: data.background_image_url ? `url(${data.background_image_url})` : '',
+                backgroundSize: data.background_image_url
+                  ? `${data.background_position.width}% ${data.background_position.height}%`
+                  : '',
+                backgroundPosition: data.background_image_url
+                  ? `${data.background_position.left}% ${data.background_position.top}%`
+                  : '',
+              },
+            ]"
+          >
+            <template v-for="(row, i) in data.row_list">
+              <div :key="i" class="row" :style="getMultipleColStyle(index, i)">
+                <!-- 列 -->
+                <template v-for="(col, j) in row.col_list">
+                  <div :key="j" :class="['col', `col-${i}-${j}`]" :style="computedColStyle(col)">
+                    <!-- 网格 -->
+                    <template v-for="(grid, k) in col.grid_list">
+                      <component
+                        :is="previewComponentMap[grid.type]"
+                        :id="grid.id"
+                        ref="preview"
+                        :key="k"
+                        :courseware_id="data.courseware_id"
+                        :class="[grid.id]"
+                        :style="{
+                          gridArea: grid.grid_area,
+                          height: grid.height,
+                        }"
+                      />
+                    </template>
+                  </div>
+                </template>
+              </div>
+            </template>
+          </div>
+        </div>
+      </template>
     </div>
   </div>
 </template>
 
 <script>
-import {
-  GetCoursewareContent,
-  GetBookChapterStruct,
-  GetCoursewareList_Chapter,
-  GetCoursewareComponentContent_View,
-} from '@/api/book';
+import { GetCoursewareContent, GetBookChapterStruct, GetCoursewareList_Chapter } from '@/api/book';
+import { previewComponentMap } from './components/common/data';
 
 import CatalogueTree from '@/views/book/components/catalogueTree.vue';
 
@@ -63,6 +87,7 @@ export default {
       book_id: this.$route.params.book_id,
       chapter_id,
       curChapterId: chapter_id,
+      previewComponentMap,
       nodes: [],
       curPosition: [],
       courseware_list: [],
@@ -77,21 +102,11 @@ export default {
   created() {
     GetBookChapterStruct({ book_id: this.book_id, node_deep_mode: 0 }).then(({ nodes }) => {
       this.nodes = nodes ?? [];
-      this.setCurPosition(this.chapter_id);
+      if (this.chapter_id) this.setCurPosition(this.chapter_id);
     });
-    this.getCoursewareList_Chapter(this.chapter_id);
-    this.getCoursewareComponentContent_View();
+    if (this.chapter_id) this.getCoursewareList_Chapter(this.chapter_id);
   },
   methods: {
-    // 获取课件数据
-    getCoursewareComponentContent_View() {
-      GetCoursewareComponentContent_View({ courseware_id: this.coursewareId, component_id: this.componentId }).then(
-        ({ content }) => {
-          // if (content) this.file_list = JSON.parse(content).file_list;
-        },
-      );
-    },
-
     goBack() {
       this.$router.push(`/chapter?chapter_id=${this.chapter_id}&book_id=${this.book_id}`);
     },
@@ -132,14 +147,17 @@ export default {
       });
     },
     async getCoursewareContent() {
-      Promise.all(
+      this.coursewareDataList = await Promise.all(
         this.courseware_list.map(async ({ courseware_id }) => {
           const { content } = await GetCoursewareContent({ id: courseware_id });
-          return content ? JSON.parse(content) : {};
+          if (content) {
+            let _content = JSON.parse(content);
+            _content.courseware_id = courseware_id;
+            return _content;
+          }
+          return {};
         }),
-      ).then((coursewareDataList) => {
-        this.coursewareDataList = coursewareDataList;
-      });
+      );
     },
     getNodeName(index) {
       let node = this.nodes;
@@ -151,6 +169,101 @@ export default {
     getCatalogueName() {
       return this.curPosition.map((item, index) => this.getNodeName(index)).join(' / ');
     },
+    getMultipleColStyle(index, i) {
+      let row = this.coursewareDataList[index].row_list[i];
+      let col = row.col_list;
+      if (col.length <= 1) {
+        return {
+          gridTemplateColumns: '100%',
+        };
+      }
+      let str = row.width_list
+        .map((item) => {
+          return `calc(${item} - ${(16 * (row.width_list.length - 1)) / row.width_list.length}px)`;
+        })
+        .join(' 16px ');
+      let gridTemplateColumns = `${str}`;
+
+      return {
+        gridAutoFlow: 'column',
+        gridTemplateColumns,
+        gridTemplateRows: 'auto',
+      };
+    },
+    /**
+     * 分割整数为多个 1的倍数
+     * @param {number} num
+     * @param {number} parts
+     */
+    splitInteger(num, parts) {
+      let base = Math.floor(num / parts);
+      let arr = Array(parts).fill(base);
+      let remainder = num - base * parts;
+      for (let i = 0; remainder > 0; i = (i + 1) % parts) {
+        arr[i] += 1;
+        remainder -= 1;
+      }
+      return arr;
+    },
+    computedColStyle(col) {
+      const grid = col.grid_list;
+
+      let maxCol = 0; // 最大列数
+      let rowList = new Map();
+      grid.forEach(({ row }) => {
+        rowList.set(row, (rowList.get(row) || 0) + 1);
+      });
+      let curMaxRow = 0; // 当前数量最大 row 的值
+      rowList.forEach((value, key) => {
+        if (value > maxCol) {
+          maxCol = value;
+          curMaxRow = key;
+        }
+      });
+      // 计算 grid_template_areas
+      let gridTemplateAreas = '';
+      let gridArr = [];
+      grid.forEach(({ grid_area, row }) => {
+        if (!gridArr[row - 1]) {
+          gridArr[row - 1] = [];
+        }
+        if (curMaxRow === row) {
+          gridArr[row - 1].push(`${grid_area}`);
+        } else {
+          let filter = grid.filter((item) => item.row === row);
+          let find = filter.findIndex((item) => item.grid_area === grid_area);
+          let needNum = (maxCol - filter.length) * 3; // 需要的数量
+
+          let str = '';
+          if (filter.length === 1) {
+            str = ` ${grid_area} `.repeat(needNum + 1);
+          } else {
+            let arr = this.splitInteger(needNum, filter.length);
+            str = arr[find] === 0 ? ` ${grid_area} ` : ` ${grid_area} `.repeat(arr[find] + 1);
+          }
+          gridArr[row - 1].push(`${str}`);
+        }
+      });
+      gridArr.forEach((item) => {
+        gridTemplateAreas += `'${item.join(' ')}' `;
+      });
+
+      // 计算 grid_template_columns
+      let gridTemplateColumns = '';
+      let rowOneNum = grid.filter((item) => item.row === 1).length;
+      grid.forEach((item) => {
+        if (item.row === 1) {
+          gridTemplateColumns += `calc(${item.width} - ${((rowOneNum - 1) * 16) / rowOneNum}px) `;
+        }
+      });
+
+      return {
+        width: col.width,
+        gridTemplateAreas,
+        gridTemplateColumns,
+        gridTemplateRows: `${grid.map(({ height }) => height).join(' ')}`,
+      };
+    },
   },
 };
 </script>
@@ -193,9 +306,12 @@ export default {
   .content-wrapper {
     flex: 1;
     padding: 24px 60px;
+    overflow: auto;
     background: url('~@/assets/mask_group.png') repeat 0 0;
 
     .exit {
+      position: sticky;
+      top: 0;
       display: flex;
       justify-content: flex-end;
 
@@ -234,23 +350,19 @@ export default {
         row-gap: 6px;
         width: 100%;
         min-height: 500px;
+        padding: 24px;
         background-color: #fff;
         background-repeat: no-repeat;
         border-bottom-right-radius: 12px;
         border-bottom-left-radius: 12px;
-      }
-
-      .navigation {
-        margin: -10px 0 0 -24px;
 
-        &-label {
-          padding: 18px 24px;
-          color: #fff;
-          background-color: #f44444;
-          border-radius: 16px 0;
+        .row {
+          display: grid;
+          row-gap: 16px;
 
-          .svg-icon {
-            margin: 0 22px 0 1px;
+          .col {
+            display: grid;
+            gap: 16px;
           }
         }
       }

+ 4 - 1
src/views/book/setting.vue

@@ -35,7 +35,7 @@
             <div class="operation">
               <el-button><SvgIcon icon-class="palette" /> 主色设置</el-button>
               <el-button @click="isEdit = true"><SvgIcon icon-class="edit" /> 编辑目录</el-button>
-              <el-button><SvgIcon icon-class="browse" /> 预览</el-button>
+              <el-button @click="enterPreview"><SvgIcon icon-class="browse" /> 预览</el-button>
             </div>
           </div>
 
@@ -227,6 +227,9 @@ export default {
     edit() {
       this.$router.push({ path: '/book/create', query: { id: this.book_id } });
     },
+    enterPreview() {
+      this.$router.push(`/preview/courseware/${this.book_id}`);
+    },
     editBookContent(chapter_id) {
       this.$router.push({ path: '/chapter', query: { chapter_id, book_id: this.book_id } });
     },