2 Commits 33b096c0fc ... 0e3a3d4cc9

Tác giả SHA1 Thông báo Ngày
  dsy 0e3a3d4cc9 富文本批量应用或取消,表格下划线批量取消 2 ngày trước cách đây
  dsy f502506a80 预览优化 2 ngày trước cách đây

+ 1 - 1
.env

@@ -11,4 +11,4 @@ VUE_APP_BookWebSI = '/GCLSBookWebSI/ServiceInterface'
 VUE_APP_EepServer = '/EEPServer/SI'
 
 #version
-VUE_APP_VERSION = '2026.09.01'
+VUE_APP_VERSION = '2026.09.17'

+ 43 - 3
packages/BookEep.vue

@@ -9,6 +9,7 @@
             :style="{ backgroundColor: background.background?.is_global ? '' : '#fff' }"
           ></div>
           <CoursewarePreview
+            v-if="!isMobile"
             ref="courseware"
             :is-show-group="isShowGroup"
             :group-show-all="groupShowAll"
@@ -27,6 +28,26 @@
             @saveCollect="saveCollect"
             @getTranslate="getTranslate"
           />
+          <MobileCoursewarePreview
+            v-else
+            ref="mobileCourseware"
+            :is-show-group="isShowGroup"
+            :group-show-all="groupShowAll"
+            :group-row-list="groupRowList"
+            :row-checked-list="rowCheckedList"
+            :data="data"
+            :courseware-id="curSelectId"
+            :component-list="cList"
+            :background="background"
+            :can-remark="false"
+            :show-remark="false"
+            :component-remark-obj="{}"
+            :project="project"
+            :is-mobile="isMobile"
+            @editNote="handEditNote"
+            @saveCollect="saveCollect"
+            @getTranslate="getTranslate"
+          />
           <div
             v-if="!isMobile"
             class="preview-right"
@@ -60,6 +81,7 @@
 import CoursewarePreview from '@/views/book/courseware/preview/CoursewarePreview.vue';
 import ExplanatoryNoteDialog from '@/components/ExplanatoryNoteDialog.vue';
 import TranslateDialog from '@/components/TranslateDialog.vue';
+import MobileCoursewarePreview from './MobileCoursewarePreview.vue';
 
 import { isTrue } from '@/utils/validate';
 import { buildCoursewareStyle } from '@/views/book/courseware/preview/common/utils/coursewareStyle';
@@ -92,6 +114,7 @@ export default {
     CoursewarePreview,
     ExplanatoryNoteDialog,
     TranslateDialog,
+    MobileCoursewarePreview,
   },
   provide() {
     return {
@@ -281,6 +304,22 @@ export default {
     ensureExtendedFonts();
   },
   methods: {
+    /**
+     * 获取当前实际渲染的课件预览组件
+     * 桌面端为 CoursewarePreview(ref=courseware),移动端为 MobileCoursewarePreview(ref=mobileCourseware)
+     * @returns {Object|null} 课件预览组件实例
+     */
+    getActiveCoursewareComponent() {
+      return this.isMobile ? this.$refs.mobileCourseware : this.$refs.courseware;
+    },
+    /**
+     * 获取当前课件内全部子 preview 组件实例(桌面与移动端均为 $refs.preview)
+     * @returns {Array} preview 组件实例列表
+     */
+    getActivePreviewList() {
+      const cw = this.getActiveCoursewareComponent();
+      return (cw && cw.$refs && cw.$refs.preview) || [];
+    },
     backTop() {
       this.$refs.previewMain.scrollTo({
         top: 0,
@@ -343,7 +382,7 @@ export default {
      * @returns {Object} 以组件 id 为键的答案映射
      */
     getAllComponentUserAnswer() {
-      const previewList = this.$refs?.courseware?.$refs?.preview || [];
+      const previewList = this.getActivePreviewList();
       const answerMap = {};
 
       previewList.forEach((item) => {
@@ -369,7 +408,7 @@ export default {
     applyComponentUserAnswer() {
       if (!this.componentUserAnswer || this.componentUserAnswer.length === 0) return;
       this.$nextTick(() => {
-        const previewList = this.$refs?.courseware?.$refs?.preview || [];
+        const previewList = this.getActivePreviewList();
         if (!previewList.length) return;
 
         const answerMap = JSON.parse(this.componentUserAnswer || '{}');
@@ -682,7 +721,8 @@ export default {
       });
     },
     submitPreviewGroupId() {
-      return JSON.stringify(this.$refs.courseware.rowCheckList);
+      const cw = this.getActiveCoursewareComponent();
+      return JSON.stringify((cw && cw.rowCheckList) || {});
     },
   },
 };

+ 418 - 0
packages/MobileCoursewarePreview.vue

@@ -0,0 +1,418 @@
+<template>
+  <div ref="courseware" class="courseware" :style="computedCoursewareStyle">
+    <!-- 行 -->
+    <template v-for="(row, i) in data.row_list">
+      <div v-show="computedRowVisibility(row.row_id)" :key="i" class="row">
+        <el-checkbox
+          v-if="
+            isShowGroup &&
+            groupRowList.find(({ row_id, is_pre_same_group }) => row.row_id === row_id && !is_pre_same_group)
+          "
+          v-model="rowCheckList[row.row_id]"
+          :class="['row-checkbox', `${row.row_id}`]"
+        />
+        <!-- 列 -->
+        <template v-for="(col, j) in row.col_list">
+          <div :key="j" :class="['col', `col-${i}-${j}`]">
+            <!-- 网格 -->
+            <template v-for="(grid, k) in col.grid_list">
+              <component
+                :is="previewComponentList[grid.type]"
+                :id="grid.id"
+                :key="k"
+                ref="preview"
+                :background="computedColBackground(grid.id)"
+                class="grid"
+                :content="computedColContent(grid.id)"
+                :data-row="i"
+                :data-col="j"
+                :data-grid="k"
+                :data-view-order="computedGridViewOrder(grid.id)"
+                :class="[grid.id]"
+                :is-mobile="isMobile"
+                :data-id="grid.id"
+                :style="{
+                  gridArea: grid.grid_area,
+                }"
+              />
+            </template>
+          </div>
+        </template>
+      </div>
+    </template>
+  </div>
+</template>
+
+<script>
+import { previewComponentList } from '@/views/book/courseware/data/bookType';
+import { buildCoursewareStyle } from '@/views/book/courseware/preview/common/utils/coursewareStyle';
+import { ensureExtendedFonts } from '@/common/data';
+
+export default {
+  name: 'MobileCoursewarePreview',
+  provide() {
+    return {
+      getDragStatus: () => false,
+      bookInfo: this.bookInfo,
+      getCoursewareId: () => this.courseware_id,
+    };
+  },
+  props: {
+    data: {
+      type: Object,
+      default: () => ({}),
+    },
+    coursewareId: {
+      type: String,
+      default: '',
+    },
+    background: {
+      type: Object,
+      default: () => ({}),
+    },
+    componentList: {
+      type: Array,
+      required: true,
+    },
+    canRemark: {
+      type: Boolean,
+      default: false,
+    },
+    showRemark: {
+      type: Boolean,
+      default: false,
+    },
+    componentRemarkObj: {
+      type: Object,
+      default: () => ({}),
+    },
+    groupRowList: {
+      type: Array,
+      default: () => [],
+    },
+    isShowGroup: {
+      type: Boolean,
+      default: false,
+    },
+    groupShowAll: {
+      type: Boolean,
+      default: true,
+    },
+    // 外部传入的行选中状态(分组部分显示),用于初始化 rowCheckList
+    rowCheckedList: {
+      type: Object,
+      default: () => ({}),
+    },
+    project: {
+      type: Object,
+      default: () => ({}),
+    },
+    isMobile: {
+      type: Boolean,
+      default: true,
+    },
+  },
+  data() {
+    return {
+      previewComponentList,
+      courseware_id: this.coursewareId,
+      bookInfo: {
+        theme_color: '',
+      },
+      showMenu: false,
+      divPosition: {
+        left: 0,
+        top: 0,
+      }, // courseware盒子原始距离页面顶部和左边的距离
+      menuPosition: { x: 0, y: 0, select_node: '' }, // 用于存储菜单的位置
+      componentId: '', // 添加批注的组件id
+      rowCheckList: {},
+    };
+  },
+  computed: {
+    // 计算课件背景样式
+    computedCoursewareStyle() {
+      return buildCoursewareStyle(this.background, 'courseware');
+    },
+  },
+  watch: {
+    coursewareId: {
+      handler(val) {
+        this.courseware_id = val;
+      },
+    },
+    groupRowList: {
+      handler(val) {
+        if (!val) return;
+        this.rowCheckList = val
+          .filter(({ is_pre_same_group }) => !is_pre_same_group)
+          .reduce((acc, row) => {
+            acc[row.row_id] = false;
+            return acc;
+          }, {});
+      },
+    },
+    rowCheckedList: {
+      handler(val) {
+        this.rowCheckList = val;
+      },
+      immediate: true,
+      deep: true,
+    },
+  },
+  mounted() {
+    const element = this.$refs.courseware;
+    const rect = element.getBoundingClientRect();
+    this.divPosition = {
+      left: rect.left,
+      top: rect.top,
+    };
+    ensureExtendedFonts();
+  },
+  methods: {
+    /**
+     * 计算组件内容
+     * @param {string} id 组件id
+     * @returns {string} 组件内容
+     */
+    computedColContent(id) {
+      if (!id) return '';
+      return this.componentList.find((item) => item.component_id === id)?.content || '';
+    },
+    /**
+     * 计算组件背景
+     * @param {string} id 组件id
+     * @returns {object} 组件背景样式
+     */
+    computedColBackground(id) {
+      if (!id) return {};
+      const background = this.componentList.find((item) => item.component_id === id)?.background;
+      return background ? JSON.parse(background) : {};
+    },
+    getMultipleColStyle(i) {
+      let row = this.data.row_list[i];
+      let col = row.col_list;
+      if (col.length <= 1) {
+        return {
+          gridTemplateColumns: '100fr',
+        };
+      }
+      let gridTemplateColumns = row.width_list.join(' ');
+
+      return {
+        gridAutoFlow: 'column',
+        gridTemplateColumns,
+        gridTemplateRows: 'auto',
+      };
+    },
+    /**
+     * 计算行的可见性
+     * @params {string} rowId 行的ID
+     * @returns {boolean} 行是否可见
+     */
+    computedRowVisibility(rowId) {
+      if (this.groupShowAll) return true;
+      let { row_id, is_pre_same_group } = this.groupRowList.find(({ row_id }) => row_id === rowId);
+      if (is_pre_same_group) {
+        const index = this.groupRowList.findIndex(({ row_id }) => row_id === rowId);
+        if (index === -1) return false;
+        for (let i = index - 1; i >= 0; i--) {
+          if (!this.groupRowList[i].is_pre_same_group) {
+            return this.rowCheckList[this.groupRowList[i].row_id];
+          }
+        }
+        return false;
+      }
+      return this.rowCheckList[row_id];
+    },
+    /**
+     * 分割整数为多个 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 || [];
+      if (!grid.length) {
+        return {
+          width: col.width,
+          gridTemplateAreas: '',
+          gridTemplateColumns: '',
+          gridTemplateRows: '',
+        };
+      }
+
+      // 一次遍历完成按行分组,并记录最大列数所在行
+      let maxCol = 0;
+      let curMaxRow = 0;
+      const rowMap = new Map();
+      grid.forEach((item) => {
+        if (!rowMap.has(item.row)) {
+          rowMap.set(item.row, []);
+        }
+        const rowItems = rowMap.get(item.row);
+        rowItems.push(item);
+        if (rowItems.length > maxCol) {
+          maxCol = rowItems.length;
+          curMaxRow = item.row;
+        }
+      });
+
+      // 计算 grid_template_areas
+      let gridTemplateAreas = '';
+      const sortedRows = Array.from(rowMap.keys()).sort((a, b) => a - b);
+      sortedRows.forEach((row) => {
+        const rowItems = rowMap.get(row);
+        const rowAreas = [];
+        if (row === curMaxRow) {
+          rowItems.forEach(({ grid_area }) => {
+            rowAreas.push(`${grid_area}`);
+          });
+        } else {
+          const needNum = maxCol - rowItems.length;
+          const splitList = rowItems.length > 1 ? this.splitInteger(needNum, rowItems.length) : [];
+          rowItems.forEach(({ grid_area }, index) => {
+            if (rowItems.length === 1) {
+              rowAreas.push(` ${grid_area} `.repeat(needNum + 1));
+              return;
+            }
+            const repeatNum = splitList[index] + 1;
+            rowAreas.push(splitList[index] === 0 ? ` ${grid_area} ` : ` ${grid_area} `.repeat(repeatNum));
+          });
+        }
+        gridTemplateAreas += `'${rowAreas.join(' ')}' `;
+      });
+
+      // 计算 grid_template_columns
+      const maxRowItems = rowMap.get(curMaxRow) || [];
+      const gridTemplateColumns = maxRowItems.map(({ width }) => width).join(' ');
+
+      // 移动端高度统一使用 auto
+      const gridTemplateRows = `${sortedRows.map(() => 'auto').join(' ')} `;
+
+      return {
+        width: col.width,
+        gridTemplateAreas,
+        gridTemplateColumns,
+        gridTemplateRows,
+      };
+    },
+    /**
+     * 查找子组件
+     * @param {string} id 组件的唯一标识符
+     * @returns {Promise<HTMLElement|null>} 返回找到的子组件或 null
+     */
+    async findChildComponentByKey(id) {
+      await this.$nextTick();
+      return this.$refs.preview.find((child) => child.dataset.id === id);
+    },
+    /**
+     * 模拟回答
+     * @param {boolean} isJudgingRightWrong 是否判断对错
+     * @param {boolean} isShowRightAnswer 是否显示正确答案
+     * @param {boolean} disabled 是否禁用
+     */
+    simulateAnswer(isJudgingRightWrong, isShowRightAnswer, disabled = true) {
+      this.$refs.preview.forEach((item) => {
+        item.showAnswer(isJudgingRightWrong, isShowRightAnswer, null, disabled);
+      });
+    },
+    /**
+     * 获取网格 id,是第几行第几列第几个网格,通过 data.row_list 计算
+     * @param {string} id 网格 id
+     * @returns {number} 网格的显示顺序
+     */
+    computedGridViewOrder(id) {
+      let rowIndex = -1;
+      let colIndex = -1;
+      let gridIndex = -1;
+
+      this.data.row_list.forEach((row, i) => {
+        row.col_list.forEach((col, j) => {
+          col.grid_list.forEach((grid, k) => {
+            if (grid.id === id) {
+              rowIndex = i;
+              colIndex = j;
+              gridIndex = k;
+            }
+          });
+        });
+      });
+
+      let order = 0;
+      // 计算前几行的网格数量
+      if (rowIndex > 0) {
+        for (let i = 0; i < rowIndex; i++) {
+          this.data.row_list[i].col_list.forEach((col) => {
+            order += col.grid_list.length;
+          });
+        }
+      }
+      // 计算当前行前几列的网格数量
+      if (colIndex > 0) {
+        for (let j = 0; j < colIndex; j++) {
+          order += this.data.row_list[rowIndex].col_list[j].grid_list.length;
+        }
+      }
+      // 加上当前列前面的网格数量
+      order += gridIndex + 1;
+      return order;
+    },
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+.courseware {
+  position: relative;
+  display: flex;
+  flex-direction: column;
+  row-gap: 6px;
+  width: 100%;
+  height: 100%;
+  min-height: 500px;
+  padding: 5px;
+  overflow: hidden;
+  background-color: #fff;
+  background-repeat: no-repeat;
+  border-bottom-right-radius: 12px;
+  border-bottom-left-radius: 12px;
+
+  .row {
+    display: flex;
+    flex-direction: column;
+    gap: 16px;
+
+    .col {
+      display: flex;
+      flex-direction: column;
+      gap: 16px;
+      overflow: hidden;
+
+      .grid {
+        width: 100%;
+      }
+
+      .main {
+        .option-list {
+          flex-direction: column !important;
+        }
+      }
+    }
+
+    .row-checkbox {
+      position: absolute;
+      left: 4px;
+    }
+  }
+}
+</style>

+ 1 - 1
packages/package.json

@@ -1,6 +1,6 @@
 {
   "name": "eep-ui",
-  "version": "0.0.42",
+  "version": "0.0.52",
   "main": "eep/eep-ui.umd.js",
   "style": "eep/eep-ui.css",
   "exports": {

+ 3 - 0
src/common/data.js

@@ -59,6 +59,8 @@ export function loadExtendedFonts(res) {
   }
 
   const cssParts = [];
+  // 过滤掉已经存在的字体,避免重复注入
+  const existingFontNames = new Set(fontList.map((f) => f.label));
   fontListData.forEach(({ name, url }) => {
     // 注入 @font-face 到主文档
     const style = document.createElement('style');
@@ -67,6 +69,7 @@ export function loadExtendedFonts(res) {
     document.head.appendChild(style);
 
     // 合并到 fontList
+    if (existingFontNames.has(name)) return;
     fontList.push({ label: name, value: name, tinymce: `${name}=${name}` });
     cssParts.push(`@font-face { font-family: '${name}'; src: url('${url}'); }`);
   });

+ 16 - 2
src/components/RichText.vue

@@ -948,7 +948,8 @@ export default {
     /**
      * 设置整体富文本格式
      * @param {string} type 格式名称
-     * @param {string} val 格式值
+     * @param {string|boolean} val 格式值;对于开关型格式(bold/italic/underline/strikethrough 等),
+     *   传 true 表示应用、false 表示取消,其余情况沿用 toggle 切换
      * @param {boolean} [isSetDefault=false] 是否设置为默认格式
      */
     setRichFormat(type, val, isSetDefault = false) {
@@ -1012,7 +1013,20 @@ export default {
         }
 
         default: {
-          editor.formatter.toggle(type);
+          // val 为布尔值时表示明确「应用 / 取消」该格式(批量设置场景,如取消下划线)
+          // 其余情况(未传 val 或传字符串)保持原有的 toggle 行为
+          if (typeof val === 'boolean') {
+            console.log(val);
+
+            if (val) {
+              editor.formatter.apply(type);
+            } else {
+              editor.formatter.remove(type);
+            }
+          } else {
+            editor.formatter.toggle(type);
+          }
+          break;
         }
       }
     },

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

@@ -311,8 +311,14 @@ export default {
         // 填空(input)属于当前段落的一部分:若段落以填空结尾,紧随其后的闭合标签和
         // 段间换行 \n 不能合并到填空前面的文本块中,否则 PinyinText 会把 \n 渲染成
         // <br> 出现在填空之前,导致填空换到下一行显示。此时将换行保留为填空后的独立文本块。
+        // 同理,两个相邻填空之间的纯空白(空格)也必须保留在填空之后,否则会被合并到
+        // 前一个填空之前的文本块,导致空格前移、相邻下划线挤到一起。
         const lastBlock = blocks[blocks.length - 1];
-        if (lastBlock && lastBlock.type === 'input' && String(content).includes('\n')) {
+        if (
+          lastBlock &&
+          lastBlock.type === 'input' &&
+          (String(content).includes('\n') || /^\s+$/.test(String(content)))
+        ) {
           blocks.push({
             content,
             type: 'text',

+ 1 - 1
src/views/book/courseware/create/components/question/table/Table.vue

@@ -48,7 +48,7 @@
             :class="[data.styles.isUnderline ? 'active' : '']"
             @click="
               data.styles.isUnderline = !data.styles.isUnderline;
-              BatchSetFormat('underline');
+              BatchSetFormat('underline', data.styles.isUnderline);
             "
           >
             <SvgIcon icon-class="underline" size="20" />

+ 5 - 1
src/views/book/courseware/preview/CoursewarePreview.vue

@@ -625,7 +625,11 @@ export default {
       });
       const hasOperationById = (id) => {
         const component = previewById.get(id);
-        return Boolean(component && component.$el.querySelector('.operation'));
+        if (!component || !component.$el) return false;
+        const operation = component.$el.querySelector('.operation');
+        // 存在 .operation 且真实占位(有高度)时才需要额外预留高度,
+        // 避免 operation 被隐藏/未渲染时多加 48px
+        return Boolean(operation && operation.offsetHeight > 0);
       };
       const toNumberHeight = (height) => {
         const num = Number(String(height).replace('px', ''));

+ 4 - 0
src/views/book/courseware/preview/components/fill/FillPreview.vue

@@ -615,6 +615,10 @@ export default {
 
     .fill-content {
       margin: 0;
+
+      :deep .el-input__inner {
+        background-color: transparent !important;
+      }
     }
 
     .content {

+ 1 - 1
src/views/book/courseware/preview/components/judge/JudgePreview.vue

@@ -281,7 +281,7 @@ export default {
         }
 
         &.wrong {
-          box-shadow: 0 0 0 1px $error-color;
+          background-color: #ffd4d9;
         }
 
         .serial-number {

+ 14 - 2
src/views/book/courseware/preview/components/select/SelectPreview.vue

@@ -6,7 +6,13 @@
     <div class="main">
       <ul
         class="option-list"
-        :style="{ flexDirection: data.property.arrange_type === arrangeTypeList[0].value ? 'row' : 'column' }"
+        :style="{
+          flexDirection: isMobile
+            ? 'column'
+            : data.property.arrange_type === arrangeTypeList[0].value
+              ? 'row'
+              : 'column',
+        }"
       >
         <li
           v-for="({ content, mark, multilingual, paragraph_list, rich_text_list }, i) in data.option_list"
@@ -63,7 +69,13 @@
       <ul
         slot="right-answer"
         class="option-list"
-        :style="{ flexDirection: data.property.arrange_type === arrangeTypeList[0].value ? 'row' : 'column' }"
+        :style="{
+          flexDirection: isMobile
+            ? 'column'
+            : data.property.arrange_type === arrangeTypeList[0].value
+              ? 'row'
+              : 'column',
+        }"
       >
         <li
           v-for="({ content, mark, multilingual, paragraph_list, rich_text_list }, i) in data.option_list"

+ 6 - 2
src/views/book/courseware/preview/components/video_interaction/ExercisePreview.vue

@@ -36,7 +36,11 @@ export default {
     bookStatus: {
       default: 'online',
     },
-    getCoursewareId: {},
+    getCoursewareId: {
+      default() {
+        return () => '';
+      },
+    },
     readFileContent: {
       default() {
         return false;
@@ -105,7 +109,7 @@ export default {
 
       if (this.readFileContent) {
         this.loading = false;
-        const content = this.readFileContent(`EX_${this.getCoursewareId()}/${this.exercise_id}`, 'courseware');
+        const content = await this.readFileContent(`EX_${this.getCoursewareId()}/${this.exercise_id}`, 'courseware');
         if (content) {
           this.content = content.content_exercise;
           this.title = content.content_title;

+ 29 - 2
src/views/project_manage/org/offlinepackauth/index.vue

@@ -94,7 +94,22 @@
       </div>
     </el-dialog>
 
-    <el-dialog title="选择教材" width="950px" :visible.sync="dialogSearchBook" :close-on-click-modal="false">
+    <el-dialog
+      title="选择教材"
+      width="950px"
+      custom-class="select-book"
+      :visible.sync="dialogSearchBook"
+      :close-on-click-modal="false"
+    >
+      <el-input
+        v-model="bookName"
+        type="text"
+        class="book-name"
+        placeholder="请输入教材名称"
+        @keyup.enter.native="pageBookList"
+      >
+        <el-button slot="append" icon="el-icon-search" @click="pageBookList" />
+      </el-input>
       <el-table :data="bookList">
         <el-table-column label="序号" width="60" align="center" header-align="center" class-name="index-column">
           <template slot-scope="{ $index }">
@@ -185,6 +200,7 @@ export default {
       downloadWatcher: null, // 下载进度监听器
       visible: false,
       percentage: 0,
+      bookName: '', // 搜索框绑定的名称
     };
   },
   computed: {
@@ -235,9 +251,11 @@ export default {
       this.handleSearchBook(data);
     },
     handleSearchBook(data) {
-      let page = data || {
+      let page = {
         cur_page: 1,
         page_capacity: 10,
+        name: this.bookName,
+        ...data,
       };
       PageQueryYSJBookList_OrgManager(page).then(({ total_count, book_list, cur_page_begin_index }) => {
         this.bookList = book_list;
@@ -570,3 +588,12 @@ export default {
   width: 100%;
 }
 </style>
+
+<style lang="scss">
+.select-book {
+  .book-name {
+    width: 500px;
+    margin-bottom: 8px;
+  }
+}
+</style>