Xwite 2 лет назад
Родитель
Сommit
41e2c4f177

+ 3 - 0
modules/web/.prettierignore

@@ -0,0 +1,3 @@
+# d.ts files Generated by unplugin-auto unplugin-vue-components
+auto-imports.d.ts
+components.d.ts

+ 9 - 11
modules/web/src/api/index.ts

@@ -1,16 +1,12 @@
 import type { AxiosResponse } from 'axios'
 import type { LeagdoApiResponse } from './api'
-import API, {
-  setWebsocketOnError,
-  legado_http_entry_point,
-  legado_webSocket_entry_point,
-  setApiEntryPoint,
-} from './api'
+import API, { setWebsocketOnError, setApiEntryPoint } from './api'
 import ajax from './axios'
 import { validatorHttpUrl } from '@/utils/utils'
 
 const LeagdoApiResponseKeys: string[] = Array.of('isSuccess', 'errorMsg')
 
+const notification = ElMessage
 /** Axios.Interceptor: check if resp is LeagaoLeagdoApiResponse*/
 const responseCheckInterceptor = (resp: AxiosResponse) => {
   let isLeagdoApiResponse = true
@@ -26,21 +22,23 @@ const responseCheckInterceptor = (resp: AxiosResponse) => {
     if ((data as LeagdoApiResponse<unknown>).isSuccess === true) {
       if (!('data' in data)) {
         isLeagdoApiResponse = false
-        LeagdoApiResponseKeys.length = 0
       }
     }
   } catch {
     isLeagdoApiResponse = false
   }
   if (isLeagdoApiResponse === false) {
-    ElNotification.warning('后端返回内容格式错误')
+    notification.warning({ message: '后端返回内容格式错误', grouping: true })
     throw new Error()
   }
   return resp
 }
 
 const axiosErrorInterceptor = (err: unknown) => {
-  ElNotification.error('后端连接失败,请检查阅读WEB服务或者设置其它可用链接')
+  notification.error({
+    message: '后端连接失败,请检查阅读WEB服务或者设置其它可用链接',
+    grouping: true,
+  })
   throw err
 }
 // http全局
@@ -80,8 +78,8 @@ export const parseLeagdoHttpUrlWithDefault = (
 
   console.info('legado_api_config:')
   console.table({
-    'http API入口': legado_http_entry_point,
-    'webSocket API入口': legado_webSocket_entry_point,
+    'http API入口': http_entry_point,
+    'webSocket API入口': webSocket_entry_point,
   })
   return [http_entry_point, webSocket_entry_point]
 }

+ 0 - 1
modules/web/src/auto-imports.d.ts

@@ -9,7 +9,6 @@ declare global {
   const EffectScope: typeof import('vue')['EffectScope']
   const ElMessage: typeof import('element-plus/es')['ElMessage']
   const ElMessageBox: typeof import('element-plus/es')['ElMessageBox']
-  const ElNotification: typeof import('element-plus/es')['ElNotification']
   const acceptHMRUpdate: typeof import('pinia')['acceptHMRUpdate']
   const computed: typeof import('vue')['computed']
   const createApp: typeof import('vue')['createApp']

+ 1 - 1
modules/web/src/components/BookItems.vue

@@ -76,7 +76,7 @@ const subJustify = computed(() =>
 )
 </script>
 
-<style scoped>
+<style lang="scss" scoped>
 .books-wrapper {
   overflow: auto;
 

+ 1 - 1
modules/web/src/components/CatalogItem.vue

@@ -33,7 +33,7 @@ const catas = computed(() => {
 })
 </script>
 
-<style scoped>
+<style lang="scss" scoped>
 .selected {
   color: #eb4259;
 }

+ 2 - 1
modules/web/src/components/ChapterContent.vue

@@ -26,6 +26,7 @@ import type { webReadConfig } from '@/web'
 const store = useBookStore()
 const readWidth = computed(() => store.config.readWidth)
 const bookUrl = computed(() => store.readingBook.bookUrl)
+
 const props = defineProps<{
   chapterIndex: number
   contents: Array<string>
@@ -116,7 +117,7 @@ onUnmounted(() => {
 })
 </script>
 
-<style scoped>
+<style lang="scss" scoped>
 .title {
   margin-bottom: 57px;
   font:

+ 1 - 1
modules/web/src/components/PopCatalog.vue

@@ -92,7 +92,7 @@ const gotoChapter = (chapter: BookChapter) => {
 }
 </script>
 
-<style scoped>
+<style lang="scss" scoped>
 .cata-wrapper {
   margin: -16px;
   padding: 18px 0 24px 25px;

+ 1 - 1
modules/web/src/components/ReadSettings.vue

@@ -356,7 +356,7 @@ const setInfiniteLoading = (loading: boolean) => {
 }
 </script>
 
-<style scoped>
+<style lang="scss" scoped>
 :deep(.iconfont) {
   font-family: iconfont;
   font-style: normal;

+ 1 - 1
modules/web/src/components/SourceDebug.vue

@@ -60,7 +60,7 @@ const isBookSource = computed(() => {
 })
 </script>
 
-<style scoped>
+<style lang="scss" scoped>
 :deep(#debug-text) {
   height: calc(100vh - 45px - 36px - 5px);
 }

+ 1 - 1
modules/web/src/components/SourceHelp.vue

@@ -53,7 +53,7 @@ import { Link } from '@element-plus/icons-vue'
   </div>
 </template>
 
-<style scoped>
+<style lang="scss" scoped>
 .el-link {
   padding: 4px;
 }

+ 1 - 1
modules/web/src/components/SourceItem.vue

@@ -36,7 +36,7 @@ const isSaveError = computed(() => {
   return !map.has(sourceUrl.value)
 })
 </script>
-<style scoped>
+<style lang="scss" scoped>
 :deep(.el-checkbox__label) {
   flex: 1;
   display: flex;

+ 1 - 1
modules/web/src/components/SourceJson.vue

@@ -34,7 +34,7 @@ watchEffect(async () => {
   }
 })
 </script>
-<style scoped>
+<style lang="scss" scoped>
 :deep(.el-input) {
   width: 100%;
 }

+ 1 - 1
modules/web/src/components/SourceList.vue

@@ -143,7 +143,7 @@ const outExport = () => {
 }
 </script>
 
-<style scoped>
+<style lang="scss" scoped>
 .tool {
   display: flex;
   margin: 4px 0;

+ 1 - 1
modules/web/src/components/ToolBar.vue

@@ -309,7 +309,7 @@ onMounted(() => {
 })
 </script>
 
-<style scoped>
+<style lang="scss" scoped>
 .flex-space-between {
   display: flex;
   justify-content: space-between;

+ 0 - 5
modules/web/src/config/sourceConfig.d.ts

@@ -2,11 +2,6 @@ import type { Source } from '@/source'
 import bookSourceEditConfig from './bookSourceEditConfig'
 import rssSourceEditConfig from './rssSourceEditConfig'
 
-type PickAnyValueKey<T> = {
-  [K in keyof T]: T[K] extends { [prop: string]: string } ? K : never
-}
-type b = keyof PickAnyValueKey<BookSoure>
-
 type SourceConfigKey =
   | keyof typeof bookSourceEditConfig
   | keyof typeof rssSourceEditConfig

+ 14 - 0
modules/web/src/router/bookRouter.ts

@@ -19,4 +19,18 @@ const router = createRouter({
   routes: bookRoutes,
 })
 
+import { createApp } from 'vue'
+import App from '@/App.vue'
+import store, { useBookStore } from '@/store'
+
+//  init pinia instance
+createApp(App).use(store)
+router.beforeEach((to, from, next) => {
+  // 自动加载阅读配置
+  useBookStore()
+    .loadWebConfig()
+    .then(() => next())
+    .catch(() => next())
+})
+
 export default router

+ 15 - 0
modules/web/src/router/index.ts

@@ -11,4 +11,19 @@ const router = createRouter({
 router.afterEach(to => {
   if (to.name == 'shelf') document.title = '书架'
 })
+
+import { createApp } from 'vue'
+import App from '@/App.vue'
+import store, { useBookStore } from '@/store'
+
+//  init pinia instance
+createApp(App).use(store)
+router.beforeEach((to, from, next) => {
+  // 自动加载阅读配置
+  useBookStore()
+    .loadWebConfig()
+    .then(() => next())
+    .catch(() => next())
+})
+
 export default router

+ 119 - 21
modules/web/src/store/bookStore.ts

@@ -8,6 +8,23 @@ import type {
   SeachBook,
 } from '@/book'
 import type { webReadConfig } from '@/web'
+import { ElMessage } from 'element-plus/es'
+
+const default_config: webReadConfig = {
+  theme: 0,
+  font: 0,
+  fontSize: 18,
+  readWidth: 800,
+  infiniteLoading: false,
+  customFontName: '',
+  jumpDuration: 1000,
+  spacing: {
+    paragraph: 1,
+    line: 0.8,
+    letter: 0,
+  },
+}
+let webReadConfigLoadedDate: Date | undefined
 
 export const useBookStore = defineStore('book', {
   state: () => {
@@ -18,7 +35,7 @@ export const useBookStore = defineStore('book', {
       searchBooks: [] as SeachBook[],
       shelf: [] as Book[],
       catalog: [] as BookChapter[],
-      readingBook: {} as BaseBook & {
+      readingBook: { chapterPos: 0, chapterIndex: 0 } as BaseBook & {
         chapterPos: number
         chapterIndex: number
         isSeachBook?: boolean
@@ -26,20 +43,7 @@ export const useBookStore = defineStore('book', {
       popCataVisible: false,
       contentLoading: true,
       showContent: false,
-      config: {
-        theme: -1,
-        font: 0,
-        fontSize: 18,
-        readWidth: 800,
-        infiniteLoading: false,
-        customFontName: '',
-        jumpDuration: 1000,
-        spacing: {
-          paragraph: 1,
-          line: 0.8,
-          letter: 0,
-        },
-      } as webReadConfig,
+      config: default_config as webReadConfig,
       miniInterface: false,
       readSettingsVisible: false,
     }
@@ -62,7 +66,6 @@ export const useBookStore = defineStore('book', {
     theme: state => {
       return state.config.theme
     },
-    configInited: state => state.config.theme !== -1,
     isNight: state => state.config.theme == 6,
   },
   actions: {
@@ -75,12 +78,83 @@ export const useBookStore = defineStore('book', {
     setNewConnect(newConnect: boolean) {
       this.newConnect = newConnect
     },
-    addBooks(books: Book[]) {
-      this.shelf = books
+    /** 从后端加载书架书籍,优先返回内存缓存 */
+    async loadBookShelf(): Promise<Book[]> {
+      const fetchBookshellf_promise = API.getBookShelf().then(resp => {
+        console.log('API.getBookShelf数据返回')
+        const { isSuccess, data, errorMsg } = resp.data
+        if (isSuccess === true) {
+          if (
+            this.shelf.length !== data.length &&
+            this.shelf.length > 0 &&
+            data.length > 0
+          ) {
+            ElMessage.info(`书架数据已更新`)
+          }
+          this.shelf = data.sort((a, b) => {
+            const x = a['durChapterTime'] || 0
+            const y = b['durChapterTime'] || 0
+            return y - x
+          })
+        } else {
+          if (errorMsg.includes('还没有添加小说') && this.shelf.length > 0) {
+            ElMessage.info('当前书架上的书籍已经被删除')
+            return (this.shelf = [])
+          }
+          ElMessage.error(errorMsg ?? '后端返回格式错误!')
+        }
+        console.log('书架数据已更新')
+        return this.shelf
+      })
+
+      if (this.shelf.length > 0) {
+        // bookshelf data fetched before:do not await
+        console.log('返回缓存书架数据')
+        return this.shelf
+      } else {
+        console.log('从阅读后端获取书架数据...')
+        return await fetchBookshellf_promise
+      }
     },
     clearBooks() {
       this.shelf = []
     },
+    /** 从后端加载书籍目录,优先返回内存缓存 */
+    async loadWebCatalog(
+      book: typeof this.readingBook,
+    ): Promise<BookChapter[]> {
+      const { bookUrl, name, chapterIndex } = book
+      const fetchChapterList_promise = API.getChapterList(
+        book.bookUrl as string,
+      ).then(res => {
+        const { isSuccess, data, errorMsg } = res.data
+        if (isSuccess === false) {
+          ElMessage.error(errorMsg)
+          throw new Error()
+        }
+        if (
+          data.length !== this.catalog.length &&
+          data.length > 0 &&
+          this.catalog.length > 0
+        ) {
+          ElMessage.info(`书籍${name}: 章节目录已更新`)
+        }
+        this.setCatalog(data)
+        console.log(`书籍${name}: 章节目录已更新`)
+        return this.catalog
+      })
+      if (
+        bookUrl === this.readingBook.bookUrl &&
+        this.catalog.length > 0 &&
+        this.catalog.length - 1 >= chapterIndex
+      ) {
+        console.log(`返回书籍《${name}》 缓存的章节目录`)
+        return this.catalog
+      } else {
+        console.log(`从阅读后端获取书籍《${name}》 章节目录数据...`)
+        return await fetchChapterList_promise
+      }
+    },
     setCatalog(catalog: BookChapter[]) {
       this.catalog = catalog
     },
@@ -93,8 +167,22 @@ export const useBookStore = defineStore('book', {
     setReadingBook(readingBook: typeof this.readingBook) {
       this.readingBook = readingBook
     },
-    setConfig(config: webReadConfig) {
-      this.config = Object.assign({}, this.config, config)
+    /** 只从从后端加载一次web阅读配置 */
+    async loadWebConfig() {
+      if (webReadConfigLoadedDate === undefined) {
+        const _config = await API.getReadConfig()
+        webReadConfigLoadedDate = new Date()
+        console.log(
+          `${this.$id}.loadWebConfig: ${webReadConfigLoadedDate.toLocaleString()}成功加载阅读配置`,
+        )
+        return this.setConfig(_config)
+      }
+      console.log(
+        `${this.$id}.loadWebConfig: 已于${webReadConfigLoadedDate.toLocaleString()}成功加载`,
+      )
+    },
+    setConfig(config?: webReadConfig) {
+      this.config = Object.assign({}, this.config ?? default_config, config)
     },
     setReadSettingsVisible(visible: boolean) {
       this.readSettingsVisible = visible
@@ -118,9 +206,19 @@ export const useBookStore = defineStore('book', {
     clearSearchBooks() {
       this.searchBooks = []
     },
-    //保存进度到app
+    /** 1.保存进度到app 2.修改内存中的数据*/
     async saveBookProgress() {
       if (!this.bookProgress) return Promise.resolve()
+      const { bookUrl } = this.readingBook
+      const shelfRaw = toRaw(this.shelf)
+      const findIndex = shelfRaw.findIndex(book => book.bookUrl === bookUrl)
+      if (findIndex > -1) {
+        this.shelf[findIndex] = Object.assign(
+          {},
+          shelfRaw[findIndex],
+          this.bookProgress,
+        )
+      }
       return API.saveBookProgress(this.bookProgress)
     },
   },

+ 17 - 54
modules/web/src/views/BookChapter.vue

@@ -17,7 +17,7 @@
         >
           <PopCatalog @getContent="getContent" class="popup" />
           <template #reference>
-            <div class="tool-icon" :class="{ 'no-point': noPoint }">
+            <div class="tool-icon" :class="{ 'no-point': false }">
               <div class="iconfont">&#58905;</div>
               <div class="icon-text">目录</div>
             </div>
@@ -318,7 +318,6 @@ const getContent = (index: number, reloadChapter = true, chapterPos = 0) => {
         }
       },
       err => {
-        ElMessage({ message: '获取章节内容失败', type: 'error' })
         const content = ['获取章节内容失败!']
         chapterData.value.push({ index, content, title })
         store.setShowContent(true)
@@ -496,61 +495,25 @@ onMounted(() => {
     chapterPos,
     isSeachBook,
   }
-  /*   const bookStr = localStorage.getItem(bookUrl);
-  if (isNullOrBlank(bookStr)) {
-    return setTimeout(toShelf, 500);
-  }
-  book = JSON.parse(bookStr as string);
-  if (
-    book == null ||
-    chapterIndex != book.chapterIndex ||
-    chapterPos != book.chapterPos
-  ) {
-    book = {
-      name: bookName!!,
-      author: bookAuthor!!,
-      bookUrl,
-      chapterIndex,
-      chapterPos,
-      isSeachBook
-    };
-    localStorage.setItem(bookUrl, JSON.stringify(book));
-  } */
   onResize()
   window.addEventListener('resize', onResize)
   loadingWrapper(
-    API.getChapterList(bookUrl as string).then(
-      res => {
-        if (!res.data.isSuccess) {
-          ElMessage({ message: res.data.errorMsg, type: 'error' })
-          setTimeout(toShelf, 500)
-          return
-        }
-        const data = res.data.data
-        store.setCatalog(data)
-        store.setReadingBook(book)
-
-        getContent(chapterIndex, true, chapterPos)
-        window.addEventListener('keyup', handleKeyPress)
-        window.addEventListener('keydown', ignoreKeyPress)
-        // 兼容Safari < 14
-        document.addEventListener('visibilitychange', onVisibilityChange)
-        //监听底部加载
-        scrollObserver = new IntersectionObserver(onReachBottom, {
-          rootMargin: '-100% 0% 20% 0%',
-        })
-        if (infiniteLoading.value === true)
-          scrollObserver.observe(loading.value)
-        //第二次点击同一本书 页面标题不会变化
-        document.title = '...'
-        document.title =
-          (name as string) + ' | ' + catalog.value[chapterIndex].title
-      },
-      err => {
-        ElMessage({ message: '获取书籍目录失败', type: 'error' })
-        throw err
-      },
-    ),
+    store.loadWebCatalog(book).then(chapters => {
+      store.setReadingBook(book)
+      getContent(chapterIndex, true, chapterPos)
+      window.addEventListener('keyup', handleKeyPress)
+      window.addEventListener('keydown', ignoreKeyPress)
+      // 兼容Safari < 14
+      document.addEventListener('visibilitychange', onVisibilityChange)
+      //监听底部加载
+      scrollObserver = new IntersectionObserver(onReachBottom, {
+        rootMargin: '-100% 0% 20% 0%',
+      })
+      if (infiniteLoading.value === true) scrollObserver.observe(loading.value)
+      //第二次点击同一本书 页面标题不会变化
+      document.title = '...'
+      document.title = (name as string) + ' | ' + chapters[chapterIndex].title
+    }),
   )
 })
 

+ 7 - 35
modules/web/src/views/BookShelf.vue

@@ -288,11 +288,7 @@ const toDetail = (
 
 const loadShelf = async () => {
   try {
-    if (store.configInited === false) {
-      const config = await API.getReadConfig()
-      applyReadConfig(config)
-    } else {
-    }
+    //await store.loadWebConfig() called in router.beforeEach
     await store.saveBookProgress()
     //确保各种网络情况下同步请求先完成
     await fetchBookShelfData()
@@ -304,35 +300,11 @@ const loadShelf = async () => {
   }
 }
 
-const fetchBookShelfData = () => {
-  return API.getBookShelf().then(response => {
-    store.setConnectType('success')
-    if (response.data.isSuccess) {
-      //store.increaseBookNum(response.data.data.length);
-      store.addBooks(
-        response.data.data.sort(function (a, b) {
-          const x = a['durChapterTime'] || 0
-          const y = b['durChapterTime'] || 0
-          return y - x
-        }),
-      )
-    } else {
-      if (
-        response.data.errorMsg.includes('还没有添加小说') &&
-        shelf.value.length > 0
-      ) {
-        ElNotification.warning({
-          title: '提示',
-          message: '当前书架上的书籍已经被删除',
-          position: 'bottom-right',
-        })
-        return store.clearBooks()
-      }
-      ElMessage.error(response.data.errorMsg ?? '后端返回格式错误!')
-    }
-    store.setConnectStatus('已连接 ' + legado_http_entry_point)
-    store.setNewConnect(false)
-  })
+const fetchBookShelfData = async () => {
+  await store.loadBookShelf()
+  store.setConnectType('primary')
+  store.setConnectStatus('已连接 ' + legado_http_entry_point)
+  store.setNewConnect(false)
 }
 onMounted(() => {
   //获取最近阅读书籍
@@ -348,7 +320,7 @@ onMounted(() => {
 })
 </script>
 
-<style scoped>
+<style lang="scss" scoped>
 .index-wrapper {
   height: 100%;
   width: 100%;

+ 1 - 1
modules/web/vite.config.ts

@@ -52,7 +52,7 @@ export default defineConfig(({ mode }) => {
       },
     },
     esbuild: {
-      drop: ["console"],
+      drop: mode === "development" ? undefined : ["console", "debugger"],
     },
     build: {
       rollupOptions: {