Browse Source

公式组件

dusenyao 5 days ago
parent
commit
d684f0540c

+ 2 - 0
README.md

@@ -27,3 +27,5 @@ npm run lint
 ### Customize configuration
 
 See [Configuration Reference](https://cli.vuejs.org/config/).
+
+打包时 nsis 是普通安装,portable 是无需安装

+ 1 - 1
main.js

@@ -24,7 +24,7 @@ const createWindow = () => {
       pathname: path.join(__dirname, './dist', 'index.html'),
       protocol: 'file:',
       slashes: true, // true: file://, false: file:
-      hash: '/login',
+      hash: '/login', // /image_change 图片切换页
     }),
   );
 

File diff suppressed because it is too large
+ 20139 - 5
package-lock.json


+ 2 - 1
package.json

@@ -27,6 +27,7 @@
     "hanzi-writer": "^3.7.0",
     "js-audio-recorder": "^1.0.7",
     "js-cookie": "^3.0.5",
+    "mathjax": "^3.2.2",
     "md5": "^2.3.0",
     "nprogress": "^0.2.0",
     "tinymce": "^5.10.9",
@@ -78,7 +79,7 @@
     },
     "asar": true,
     "win": {
-      "target": "nsis",
+      "target": "portable",
       "icon": "./public/icon.png",
       "artifactName": "${productName}-Setup.${ext}"
     },

BIN
src/assets/1.jpg


BIN
src/assets/2.png


+ 54 - 0
src/components/ImageChange.vue

@@ -0,0 +1,54 @@
+<template>
+  <div class="hello">
+    <div class="image-switcher">
+      <img :src="images[currentImageIndex]" alt="Switchable image" />
+      <div class="controls">
+        <button @click="switchImage">切换图片</button>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'ImageChange',
+  data() {
+    return {
+      images: [require('@/assets/1.jpg'), require('@/assets/2.png')],
+      currentImageIndex: 0,
+    };
+  },
+  methods: {
+    switchImage() {
+      this.currentImageIndex = (this.currentImageIndex + 1) % this.images.length;
+    },
+  },
+};
+</script>
+
+<style scoped>
+.image-switcher {
+  margin: 20px 0;
+  text-align: center;
+}
+
+.image-switcher img {
+  width: 300px;
+  max-width: 100%;
+  height: 300px;
+  margin-bottom: 10px;
+}
+
+.controls button {
+  padding: 8px 16px;
+  color: white;
+  cursor: pointer;
+  background-color: #42b983;
+  border: none;
+  border-radius: 4px;
+}
+
+.controls button:hover {
+  background-color: #3aa876;
+}
+</style>

+ 3 - 0
src/main.js

@@ -13,6 +13,9 @@ import '@/styles/font/font.css';
 import ElementUI from 'element-ui';
 import '@/styles/element-variables.scss';
 
+import '@/utils/mathjax'; // 必须在引入mathjax前引入mathjax的配置文件
+import 'mathjax/es5/tex-mml-chtml';
+
 import { setupRouterGuard } from '@/router/guard';
 
 Vue.use(ElementUI, {

+ 1 - 1
src/router/guard/index.js

@@ -4,7 +4,7 @@ import NProgress from 'nprogress';
 import 'nprogress/nprogress.css';
 NProgress.configure({ showSpinner: false });
 
-const whiteList = ['/login']; // 重定向白名单
+const whiteList = ['/login', '/image_change']; // 重定向白名单
 
 export function setupRouterGuard(router) {
   // 全局前置守卫

+ 6 - 0
src/router/modules/basic.js

@@ -6,6 +6,12 @@ export const loginPage = {
   component: () => import('@/views/login'),
 };
 
+export const ImageChangePage = {
+  path: '/image_change',
+  name: 'ImageChange',
+  component: () => import('@/components/ImageChange.vue'),
+};
+
 export const homePage = {
   path: '/',
   component: DEFAULT,

+ 2 - 2
src/router/modules/index.js

@@ -1,5 +1,5 @@
-import { homePage, loginPage, NotFoundPage } from './basic';
+import { homePage, loginPage, NotFoundPage, ImageChangePage } from './basic';
 import { createBookRouters } from './book';
 import CoursewareRouters from './courseware';
 
-export const routes = [homePage, loginPage, ...createBookRouters, ...CoursewareRouters, NotFoundPage];
+export const routes = [homePage, loginPage, ...createBookRouters, ...CoursewareRouters, ImageChangePage, NotFoundPage];

+ 27 - 0
src/utils/mathjax.js

@@ -0,0 +1,27 @@
+// mathJax全局配置文件
+window.MathJax = {
+  tex: {
+    inlineMath: [
+      ['$', '$'],
+      ['(', ')'],
+    ], // 行内公式选择符
+    displayMath: [
+      ['$$', '$$'],
+      ['[', ']'],
+    ], // 段内公式选择符
+  },
+  // 使用 chtml 渲染器
+  chtml: {
+    displayAlign: 'center', // 左对齐
+    displayIndent: '0', // 不缩进
+    scale: 1, // 缩放比例
+    minScaleAdjust: 50, // 最小缩放比例
+    exFactor: 2, // 行高因子
+    linebreaks: true, // 自动换行
+  },
+  startup: {
+    ready() {
+      window.MathJax.startup.defaultReady();
+    },
+  },
+};

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

@@ -14,7 +14,7 @@
 
 <script>
 import SettingMixin from '@/views/book/courseware/create/components/common/SettingMixin';
-import { viewMethodList, snGenerationMethodList } from '@/views/book/courseware/data/common';
+import { viewMethodList } from '@/views/book/courseware/data/common';
 import { getPictureProperty } from '@/views/book/courseware/data/picture';
 
 export default {

+ 1 - 1
src/views/book/courseware/create/components/question/article/Article.vue

@@ -3,7 +3,7 @@
     <template #content>
       <!-- eslint-disable max-len -->
       <div class="fill-wrapper">
-        <el-input v-model="data.content" placeholder="输入" type="textarea" @change="handleChangeContent"></el-input>
+        <el-input v-model="data.content" placeholder="输入" type="textarea" @change="handleChangeContent" />
       </div>
     </template>
   </ModuleBase>

+ 67 - 0
src/views/book/courseware/create/components/question/math/Math.vue

@@ -0,0 +1,67 @@
+<!-- eslint-disable vue/no-v-html -->
+<template>
+  <ModuleBase :type="data.type">
+    <template #content>
+      <el-input
+        v-model="data.math"
+        type="textarea"
+        :autosize="{ minRows: 4, maxRows: 8 }"
+        resize="none"
+        placeholder="请输入公式"
+      />
+
+      <div ref="mathContainer" class="formula-render" v-html="data.math"></div>
+    </template>
+  </ModuleBase>
+</template>
+
+<script>
+import ModuleMixin from '../../common/ModuleMixin';
+
+import { getMathData } from '@/views/book/courseware/data/math';
+
+export default {
+  name: 'MathPage',
+  mixins: [ModuleMixin],
+  data() {
+    return {
+      data: getMathData(),
+    };
+  },
+  watch: {
+    // 监听数据变化,重新渲染 MathJax
+    'data.math': {
+      handler() {
+        this.renderMath();
+      },
+      deep: true,
+    },
+  },
+  mounted() {
+    this.renderMath();
+  },
+  methods: {
+    async renderMath() {
+      await this.$nextTick();
+      try {
+        await window.MathJax.typesetPromise([this.$refs.mathContainer]);
+      } catch (err) {
+        console.error('公式渲染失败:', err);
+      }
+    },
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+@use '@/styles/mixin.scss' as *;
+
+.formula-render {
+  min-height: 50px;
+  padding: 15px;
+  margin-top: 8px;
+  background: #f8f9fa;
+  border: 1px solid #e9ecef;
+  border-radius: 4px;
+}
+</style>

+ 32 - 0
src/views/book/courseware/create/components/question/math/MathSetting.vue

@@ -0,0 +1,32 @@
+<template>
+  <div>
+    <el-form :model="property" label-width="42px">
+      <SerailNumber :property="property" />
+    </el-form>
+  </div>
+</template>
+
+<script>
+import SettingMixin from '@/views/book/courseware/create/components/common/SettingMixin';
+
+import { getFillProperty } from '@/views/book/courseware/data/math';
+
+export default {
+  name: 'MatchingSetting',
+  mixins: [SettingMixin],
+  data() {
+    return {
+      property: getFillProperty(),
+    };
+  },
+  methods: {},
+};
+</script>
+
+<style lang="scss" scoped>
+@use '@/styles/mixin.scss' as *;
+
+.el-form {
+  @include setting-base;
+}
+</style>

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

@@ -9,8 +9,6 @@ import {
 
 export { arrangeTypeList, switchOption, isEnable };
 
-
-
 // 显示
 export const positionList = [
   {
@@ -65,6 +63,6 @@ export function getArticleData() {
     detail: [], // 分段分句分词详情
     word_time: [], // 字幕时间
     new_word_list: [], // 生词列表
-    sentence_list_mp: [], //句子+分词数组
+    sentence_list_mp: [], // 句子+分词数组
   };
 }

+ 13 - 0
src/views/book/courseware/data/bookType.js

@@ -1,3 +1,4 @@
+// 基础、题型组件编辑和设置页面
 import DividerPage from '../create/components/base/divider/Divider.vue';
 import DividerSetting from '../create/components/base/divider/DividerSetting.vue';
 import SpacingPage from '../create/components/base/spacing/Spacing.vue';
@@ -46,7 +47,10 @@ import OtherWord from '../create/components/question/other_word/OtherWord.vue';
 import OtherWordSetting from '../create/components/question/other_word/OtherWordSetting.vue';
 import Article from '../create/components/question/article/Article.vue';
 import ArticleSetting from '../create/components/question/article/ArticleSetting.vue';
+import Math from '../create/components/question/math/Math.vue';
+import MathSetting from '../create/components/question/math/MathSetting.vue';
 
+// 预览组件页面列表
 import AudioPreview from '@/views/book/courseware/preview/components/audio/AudioPreview.vue';
 import DividerPreview from '@/views/book/courseware/preview/components/divider/DividerPreview.vue';
 import SpacingPreview from '@/views/book/courseware/preview/components/spacing/SpacingPreview.vue';
@@ -71,6 +75,7 @@ import NewWordPreview from '../preview/components/new_word/NewWordPreview.vue';
 import NotesPreview from '../preview/components/notes/NotesPreview.vue';
 import OtherWordPreview from '../preview/components/other_word/OtherWordPreview.vue';
 import ArticlePreview from '../preview/components/article/index.vue';
+import MathPreview from '../preview/components/math/MathPreview.vue';
 
 export const bookTypeOption = [
   {
@@ -276,6 +281,14 @@ export const bookTypeOption = [
         set: WriteSetting,
         preview: WritePreview,
       },
+      {
+        value: 'math',
+        label: '公式组件',
+        icon: '',
+        component: Math,
+        set: MathSetting,
+        preview: MathPreview,
+      },
     ],
   },
 ];

+ 8 - 0
src/views/book/courseware/data/common.js

@@ -34,6 +34,14 @@ export const serialNumberPositionList = [
   { value: 'bottom-end', justifyContent: 'flex-end' },
 ];
 
+// 公用设置属性
+export let commonSetProperty = {
+  serial_number: 1, // 序号
+  sn_type: serialNumberTypeList[0].value, // 序号类型:letter字母 number数字  capital大写字母 bracket_number括号数字
+  sn_position: serialNumberPositionList[0].value, // 序号位置:top-start top top-end,left-start left left-end等
+  sn_display_mode: displayList[0].value, // 序号显示方式:true显示 false隐藏
+};
+
 // 序号样式
 export const serialNumberStyleList = [
   { value: 'solidBlockStyle' }, // 实心方块

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

@@ -0,0 +1,19 @@
+// 数学公式组件
+// 该组件用于展示数学公式,使用 MathJax 渲染公式内容
+
+import { commonSetProperty } from './common';
+
+export function getFillProperty() {
+  return {
+    ...commonSetProperty,
+  };
+}
+
+export function getMathData() {
+  return {
+    type: 'math',
+    title: '公式',
+    property: getFillProperty(),
+    math: '', // 公式内容
+  };
+}

+ 1 - 1
src/views/book/courseware/data/picture.js

@@ -10,7 +10,7 @@ export function getPictureProperty() {
     serial_number: 1, // 序号
     sn_type: serialNumberTypeList[0].value, // 序号类型:letter字母 number数字  capital大写字母 bracket_number括号数字
     sn_position: serialNumberPositionList[0].value, // 序号位置:top-start top top-end,left-start left left-end等
-    sn_display_mode: displayList[0].value,
+    sn_display_mode: displayList[0].value, // 序号显示方式:true显示 false隐藏
     view_method: viewMethodList[0].value, // 查看方式:independent独立 list列表
   };
 }

+ 54 - 0
src/views/book/courseware/preview/components/math/MathPreview.vue

@@ -0,0 +1,54 @@
+<!-- eslint-disable vue/no-v-html -->
+<template>
+  <div class="math-preview" :style="getAreaStyle()">
+    <SerialNumberPosition v-if="isEnable(data.property.sn_display_mode)" :property="data.property" />
+
+    <div ref="mainMath" class="main" v-html="data.math"></div>
+  </div>
+</template>
+
+<script>
+import PreviewMixin from '../common/PreviewMixin';
+
+import { getMathData } from '@/views/book/courseware/data/math';
+
+export default {
+  name: 'MathPreview',
+  mixins: [PreviewMixin],
+  data() {
+    return {
+      data: getMathData(),
+    };
+  },
+  watch: {
+    // 监听数据变化,重新渲染 MathJax
+    'data.math': {
+      handler() {
+        this.renderMath();
+      },
+      deep: true,
+    },
+  },
+  mounted() {
+    this.renderMath();
+  },
+  methods: {
+    async renderMath() {
+      await this.$nextTick();
+      try {
+        await window.MathJax.typesetPromise([this.$refs.mainMath]);
+      } catch (err) {
+        console.error('公式渲染失败:', err);
+      }
+    },
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+@use '@/styles/mixin.scss' as *;
+
+.math-preview {
+  @include preview-base;
+}
+</style>

+ 54 - 55
src/views/book/courseware/preview/components/new_word/components/WordPhraseDetail.vue

@@ -1,7 +1,7 @@
 <template>
   <div class="wordDetailModule wordDetailChs">
     <div class="module-inner">
-      <div class="top" v-if="data">
+      <div v-if="data" class="top">
         <div class="operation">
           <div>
             <template v-if="optionRes && optionRes.length > 0">
@@ -11,47 +11,47 @@
                 v-if="!notshowNext"
               /> -->
               <img
+                v-if="!notshowNext"
                 style="margin-right: 8px"
                 src="@/assets/word_detail/Left-16-normal-Black.png"
                 alt=""
                 @click="lastDetail"
-                v-if="!notshowNext"
               />
               <img
+                v-if="!notshowNext"
                 src="@/assets/word_detail/Right-16-normal-Black.png"
                 alt=""
                 @click="nextDetail"
-                v-if="!notshowNext"
               />
             </template>
             <img
               style="margin-right: 0"
-              @click="closeWordShow"
               src="@/assets/word_detail/Cross-16-normal-Black.png"
               alt=""
+              @click="closeWordShow"
             />
           </div>
         </div>
-        <div class="wordDetail" v-if="data.new_word_str">
+        <div v-if="data.new_word_str" class="wordDetail">
           <div
+            v-if="data.new_word_str.length <= 4"
             :class="[
               'bwc-Strockplay',
               themeColor == 'green' ? 'green-border' : themeColor == 'red' ? 'red-border' : 'brown-border',
             ]"
-            v-if="data.new_word_str.length <= 4"
           >
-            <div class="strockplay" v-for="(conItem, conindex) in data.new_word_str" :key="'new_word_' + conindex">
+            <div v-for="(conItem, conindex) in data.new_word_str" :key="'new_word_' + conindex" class="strockplay">
               <Strockplayredline
                 :key="conItem + detailIndex + conindex"
                 :Book_text="conItem"
-                :playStorkes="true"
-                :targetDiv="'bwcHanziIntp' + conItem + conindex"
-                :wordNum="data.new_word_str.length"
-                :themeColor="themeColor"
+                :play-storkes="true"
+                :target-div="'bwcHanziIntp' + conItem + conindex"
+                :word-num="data.new_word_str.length"
+                :theme-color="themeColor"
               />
               <div
-                :class="['bwc-line', themeColor == 'green' ? 'green-bg' : themeColor == 'red' ? 'red-bg' : 'brown-bg']"
                 v-if="conindex < data.new_word_str.length - 1"
+                :class="['bwc-line', themeColor == 'green' ? 'green-bg' : themeColor == 'red' ? 'red-bg' : 'brown-bg']"
               ></div>
             </div>
           </div>
@@ -68,20 +68,20 @@
             <div class="yinpin">
               <span class="pinyintext" v-html="data.pinyin.toLowerCase()"></span>
               <template v-if="data.newWordMp3">
-                <Audio :mp3="data.newWordMp3" :themeColor="themeColor" :bg="bg" :ed="ed" />
+                <Audio :mp3="data.newWordMp3" :theme-color="themeColor" :bg="bg" :ed="ed" />
               </template>
               <template v-else-if="data.mp3_list && data.mp3_list[0] && data.mp3_list[0].id">
-                <Audio :mp3="data.mp3_list[0].id" :themeColor="themeColor" />
+                <Audio :mp3="data.mp3_list[0].id" :theme-color="themeColor" />
               </template>
               <template v-else-if="data.mp3Url">
-                <Audio :mp3="data.mp3Url" :themeColor="themeColor" :bg="bg" :ed="ed" />
+                <Audio :mp3="data.mp3Url" :theme-color="themeColor" :bg="bg" :ed="ed" />
               </template>
             </div>
             <p class="jieshu" v-html="data.definition_list"></p>
           </div>
         </div>
         <div class="zhedie-white">
-          <div class="merge" v-loading="loading3">
+          <div v-loading="loading3" class="merge">
             <div class="topTitle">
               <div class="left">
                 <!-- <div
@@ -104,17 +104,17 @@
                   </span>
                 </div> -->
                 <div
-                  @click="cutLiju(1)"
                   v-if="list1.sentence_list && list1.sentence_list.length > 0"
                   :class="[lijuIndex == 1 ? 'sele' : '']"
+                  @click="cutLiju(1)"
                 >
                   <span> 本课例句 </span>
                   <span>{{ list1.sentence_list.length }} </span>
                 </div>
                 <div
-                  @click="cutLiju(2)"
                   v-if="list2.sentence_list && list2.sentence_list.length > 0"
                   :class="[lijuIndex == 2 ? 'sele' : '']"
+                  @click="cutLiju(2)"
                 >
                   <span> 本书例句 </span>
                   <span>
@@ -122,9 +122,9 @@
                   </span>
                 </div>
                 <div
-                  @click="cutLiju(3)"
                   v-if="list3.sentence_list && list3.sentence_list.length > 0"
                   :class="[lijuIndex == 3 ? 'sele' : '']"
+                  @click="cutLiju(3)"
                 >
                   <span>本套例句</span>
                   <span>{{ list3.sentence_list.length }}</span>
@@ -159,7 +159,7 @@ import Audio from '../../voice_matrix/components/AudioRed.vue';
 import { GetBookWebSIContent } from '@/api/app';
 
 export default {
-  //import引入的组件需要注入到对象中才能使用
+  // import引入的组件需要注入到对象中才能使用
   components: {
     Strockplayredline,
     Audio,
@@ -180,7 +180,7 @@ export default {
     'ed',
   ],
   data() {
-    //这里存放数据
+    // 这里存放数据
     return {
       height: '',
       margintop: '',
@@ -205,12 +205,12 @@ export default {
       punctuationList: ['(', ')', '……', '(', ')', '…', '/'], // 标点数组
     };
   },
-  //计算属性 类似于data概念
+  // 计算属性 类似于data概念
   computed: {},
-  //监控data中数据变化
+  // 监控data中数据变化
   watch: {
     data: {
-      handler: function (val, oldVal) {
+      handler(val, oldVal) {
         const _this = this;
         if (val) {
           _this.initData();
@@ -223,7 +223,23 @@ export default {
       deep: true,
     },
   },
-  //方法集合
+  // 生命周期 - 创建完成(可以访问当前this实例)
+  created() {},
+  // 生命周期 - 挂载完成(可以访问DOM元素)
+  mounted() {
+    let _this = this;
+    _this.initData();
+    if (_this.type == 'newWordDetail') {
+      // _this.getChineseInfo();
+    }
+  },
+  // 生命周期-挂载之前
+  beforeMount() {},
+  // 生命周期-更新之后
+  updated() {},
+  // 如果页面有keep-alive缓存功能,这个函数会触发
+  activated() {},
+  // 方法集合
   methods: {
     // 切换 例句
     cutLiju(index, type) {
@@ -294,8 +310,8 @@ export default {
       // 获取本课的 本教材的 本套的 的例句
       GetBookWebSIContent(Mname, {
         courseware_id: this.currentTreeID, // 课件id
-        word: this.data.new_word, //生词
-        search_scope: 0, //检索范围0 本课件  1本教材 2本套
+        word: this.data.new_word, // 生词
+        search_scope: 0, // 检索范围0 本课件  1本教材 2本套
         is_contain_word_variants: false,
         is_filter_repetitive_sentence: 'true',
         book_publish_status: 1,
@@ -345,8 +361,8 @@ export default {
           }
           GetBookWebSIContent(Mname, {
             courseware_id: this.currentTreeID, // 课件id
-            word: this.data.new_word, //生词
-            search_scope: 1, //检索范围0 本课件  1本教材 2本套
+            word: this.data.new_word, // 生词
+            search_scope: 1, // 检索范围0 本课件  1本教材 2本套
             is_contain_word_variants: false,
             is_filter_repetitive_sentence: 'true',
             book_publish_status: 1,
@@ -389,8 +405,8 @@ export default {
 
               GetBookWebSIContent(Mname, {
                 courseware_id: this.currentTreeID, // 课件id
-                word: this.data.new_word, //生词
-                search_scope: 2, //检索范围0 本课件  1本教材 2本套
+                word: this.data.new_word, // 生词
+                search_scope: 2, // 检索范围0 本课件  1本教材 2本套
                 is_contain_word_variants: false,
                 is_filter_repetitive_sentence: 'true',
                 book_publish_status: 1,
@@ -501,7 +517,7 @@ export default {
       if (list.length > 0) {
         list = list.map((item, index) => {
           let str = item.source_courseware_name_path;
-          item.show_source_courseware_name_path = str.slice(0, 8) + '...';
+          item.show_source_courseware_name_path = `${str.slice(0, 8)}...`;
           if (type == 'left') {
             let sentence = JSON.parse(JSON.stringify(item.sentence));
             let arr = JSON.parse(JSON.stringify(item.sentence_fc_list));
@@ -556,16 +572,15 @@ export default {
                 part2 = newsentence.substring(item.position_list[i].begin, item.position_list[i].end);
                 part3 = newsentence.substring(item.position_list[i].end);
               }
-              res += part1 + '<span style="color:#DE4444;">' + part2 + '</span>' + part3;
+              res += `${part1}<span style="color:#DE4444;">${part2}</span>${part3}`;
             }
             item.res = res;
           }
           return item;
         });
         return list;
-      } else {
-        return [];
       }
+      return [];
     },
     changefiveword(arr, index, type) {
       let num = 6;
@@ -677,30 +692,14 @@ export default {
       }
     },
   },
-  //生命周期 - 创建完成(可以访问当前this实例)
-  created() {},
-  //生命周期 - 挂载完成(可以访问DOM元素)
-  mounted() {
-    let _this = this;
-    _this.initData();
-    if (_this.type == 'newWordDetail') {
-      // _this.getChineseInfo();
-    }
-  },
-  //生命周期-创建之前
+  // 生命周期-创建之前
   beforeCreated() {},
-  //生命周期-挂载之前
-  beforeMount() {},
-  //生命周期-更新之前
+  // 生命周期-更新之前
   beforUpdate() {},
-  //生命周期-更新之后
-  updated() {},
-  //生命周期-销毁之前
+  // 生命周期-销毁之前
   beforeDestory() {},
-  //生命周期-销毁完成
+  // 生命周期-销毁完成
   destoryed() {},
-  //如果页面有keep-alive缓存功能,这个函数会触发
-  activated() {},
 };
 </script>
 <style lang="scss" scoped>

+ 3 - 1
src/views/book/create.vue

@@ -218,7 +218,9 @@ export default {
         .then(() => {
           this.$router.push(`/book/setting/${this.book_id}`);
         })
-        .catch(() => {});
+        .catch(() => {
+          this.$message.error('更新失败');
+        });
     },
   },
 };

Some files were not shown because too many files changed in this diff