web端优化数据加载

This commit is contained in:
Xwite
2024-10-15 14:55:40 +08:00
parent b326d2a3c0
commit 123d9b48bb
21 changed files with 197 additions and 139 deletions

View File

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

View File

@@ -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]
}

View File

@@ -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']

View File

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

View File

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

View File

@@ -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:

View File

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

View File

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

View File

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

View File

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

View File

@@ -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;

View File

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

View File

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

View File

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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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)
},
},

View File

@@ -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
}),
)
})

View File

@@ -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%;

View File

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