diff --git a/modules/web/src/api/api.ts b/modules/web/src/api/api.ts index f210971ee..dfc08194d 100644 --- a/modules/web/src/api/api.ts +++ b/modules/web/src/api/api.ts @@ -22,9 +22,10 @@ export let legado_http_entry_point = '' export let legado_webSocket_entry_point = '' let wsOnError: typeof WebSocket.prototype.onerror = () => {} -export const setWebsocketOnError = ( - callback: typeof WebSocket.prototype.onerror, -) => { +let wsOnMessage: typeof WebSocket.prototype.onmessage = () => {} +export const setWebsocketOnMessage = (callback: typeof wsOnMessage) => + (wsOnMessage = callback) +export const setWebsocketOnError = (callback: typeof wsOnError) => { //WebSocket.prototype.onerror = callback wsOnError = callback } @@ -33,15 +34,15 @@ export const setApiEntryPoint = ( http_entry_point: string, webSocket_entry_point: string, ) => { - legado_http_entry_point = http_entry_point - legado_webSocket_entry_point = webSocket_entry_point - ajax.defaults.baseURL = http_entry_point.toString() + legado_http_entry_point = new URL(http_entry_point).toString() + legado_webSocket_entry_point = new URL(webSocket_entry_point).toString() + ajax.defaults.baseURL = legado_http_entry_point } // 书架API // Http const getReadConfig = async (http_url = legado_http_entry_point) => { - const { data } = await ajax.get>('/getReadConfig', { + const { data } = await ajax.get>('getReadConfig', { baseURL: http_url.toString(), timeout: 3000, }) @@ -52,27 +53,27 @@ const getReadConfig = async (http_url = legado_http_entry_point) => { } } const saveReadConfig = (config: webReadConfig) => - ajax.post>('/saveReadConfig', config) + ajax.post>('saveReadConfig', config) /** @deprecated: 使用`API.saveBookProgressWithBeacon`以确保在页面或者直接关闭的情况下保存进度 */ const saveBookProgress = (bookProgress: BookProgress) => - ajax.post('/saveBookProgress', bookProgress) + ajax.post('saveBookProgress', bookProgress) /**主要在直接关闭浏览器情况下可靠发送书籍进度 */ const saveBookProgressWithBeacon = (bookProgress: BookProgress) => { if (!bookProgress) return // 常规请求可能会被取消 使用Fetch keep-alive 或者 navigator.sendBeacon navigator.sendBeacon( - new URL('/saveBookProgress', legado_http_entry_point), + new URL('saveBookProgress', legado_http_entry_point), JSON.stringify(bookProgress), ) } -const getBookShelf = () => ajax.get>('/getBookshelf') +const getBookShelf = () => ajax.get>('getBookshelf') const getChapterList = (/** @type {string} */ bookUrl: string) => ajax.get>( - '/getChapterList?url=' + encodeURIComponent(bookUrl), + 'getChapterList?url=' + encodeURIComponent(bookUrl), ) const getBookContent = ( @@ -80,7 +81,7 @@ const getBookContent = ( /** @type {number} */ chapterIndex: number, ) => ajax.get>( - '/getBookContent?url=' + + 'getBookContent?url=' + encodeURIComponent(bookUrl) + '&index=' + chapterIndex, @@ -93,16 +94,17 @@ const search = ( onFinish: () => void, ) => { const socket = new WebSocket( - new URL('/searchBook', legado_webSocket_entry_point), + new URL('searchBook', legado_webSocket_entry_point), ) socket.onerror = wsOnError socket.onopen = () => { socket.send(`{"key":"${searchKey}"}`) } - socket.onmessage = ({ data }) => { + socket.onmessage = event => { try { - onReceive(JSON.parse(data)) + onReceive(JSON.parse(event.data)) + wsOnMessage?.call(socket, event) } catch { onFinish() } @@ -114,31 +116,31 @@ const search = ( } const saveBook = (book: BaseBook) => - ajax.post>('/saveBook', book) + ajax.post>('saveBook', book) const deleteBook = (book: BaseBook) => - ajax.post>('/deleteBook', book) + ajax.post>('deleteBook', book) const isBookSource = /bookSource/i.test(location.href) // 源编辑API // Http const getSources = () => - isBookSource ? ajax.get('/getBookSources') : ajax.get('/getRssSources') + isBookSource ? ajax.get('getBookSources') : ajax.get('getRssSources') const saveSource = (data: Source) => isBookSource - ? ajax.post>('/saveBookSource', data) - : ajax.post>('/saveRssSource', data) + ? ajax.post>('saveBookSource', data) + : ajax.post>('saveRssSource', data) const saveSources = (data: Source[]) => isBookSource - ? ajax.post>('/saveBookSources', data) - : ajax.post>('/saveRssSources', data) + ? ajax.post>('saveBookSources', data) + : ajax.post>('saveRssSources', data) const deleteSource = (data: Source[]) => isBookSource - ? ajax.post>('/deleteBookSources', data) - : ajax.post>('/deleteRssSources', data) + ? ajax.post>('deleteBookSources', data) + : ajax.post>('deleteRssSources', data) // webSocket const debug = ( @@ -148,7 +150,7 @@ const debug = ( /** @type {() => void} */ onFinish: () => void, ) => { const url = new URL( - `/${isBookSource ? 'bookSource' : 'rssSource'}Debug`, + `${isBookSource ? 'bookSource' : 'rssSource'}Debug`, legado_webSocket_entry_point, ) @@ -157,7 +159,10 @@ const debug = ( socket.onopen = () => { socket.send(JSON.stringify({ tag: sourceUrl, key: searchKey })) } - socket.onmessage = ({ data }) => onReceive(data) + socket.onmessage = event => { + onReceive(event.data) + wsOnMessage?.call(socket, event) + } socket.onclose = () => { onFinish() @@ -171,7 +176,7 @@ const debug = ( const getProxyCoverUrl = (coverUrl: string) => { if (coverUrl.startsWith(legado_http_entry_point)) return coverUrl return new URL( - '/cover?path=' + encodeURIComponent(coverUrl), + 'cover?path=' + encodeURIComponent(coverUrl), legado_http_entry_point, ).toString() } @@ -188,7 +193,7 @@ const getProxyImageUrl = ( ) => { if (src.startsWith(legado_http_entry_point)) return src return new URL( - '/image?path=' + + 'image?path=' + encodeURIComponent(src) + '&url=' + encodeURIComponent(bookUrl) + diff --git a/modules/web/src/api/index.ts b/modules/web/src/api/index.ts index 9c50ba556..7e84cc935 100644 --- a/modules/web/src/api/index.ts +++ b/modules/web/src/api/index.ts @@ -1,9 +1,21 @@ import type { AxiosResponse } from 'axios' import type { LeagdoApiResponse } from './api' -import API, { setWebsocketOnError, setApiEntryPoint } from './api' +import API, { + setWebsocketOnError, + setApiEntryPoint, + legado_http_entry_point, + setWebsocketOnMessage, +} from './api' import ajax from './axios' import { validatorHttpUrl } from '@/utils/utils' +import { createApp } from 'vue' +import App from '@/App.vue' +import store, { useConnectionStore } from '@/store' + +createApp(App).use(store) +const connectionStore = useConnectionStore() + const LeagdoApiResponseKeys: string[] = Array.of('isSuccess', 'errorMsg') const notification = ElMessage @@ -31,6 +43,8 @@ const responseCheckInterceptor = (resp: AxiosResponse) => { notification.warning({ message: '后端返回内容格式错误', grouping: true }) throw new Error() } + connectionStore.setConnectType('primary') + connectionStore.setConnectStatus('已连接 ' + legado_http_entry_point) return resp } @@ -39,13 +53,18 @@ const axiosErrorInterceptor = (err: unknown) => { message: '后端连接失败,请检查阅读WEB服务或者设置其它可用链接', grouping: true, }) + connectionStore.setConnectType('danger') + connectionStore.setConnectStatus('连接异常') throw err } // http全局 ajax.interceptors.response.use(responseCheckInterceptor, axiosErrorInterceptor) // websocket setWebsocketOnError(axiosErrorInterceptor) - +setWebsocketOnMessage(() => { + connectionStore.setConnectType('primary') + connectionStore.setConnectStatus('已连接 ' + legado_http_entry_point) +}) /** * 按照阅读的默认规则 解析阅读HTTP WebSocket API入口地址 * @returns [http_url, webSocekt_url] diff --git a/modules/web/src/auto-imports.d.ts b/modules/web/src/auto-imports.d.ts index f258fcdc4..a29bc75d0 100644 --- a/modules/web/src/auto-imports.d.ts +++ b/modules/web/src/auto-imports.d.ts @@ -70,6 +70,7 @@ declare global { const unref: typeof import('vue')['unref'] const useAttrs: typeof import('vue')['useAttrs'] const useBookStore: typeof import('./store/bookStore')['useBookStore'] + const useConnectionStore: typeof import('./store/connectionStore')['useConnectionStore'] const useCssModule: typeof import('vue')['useCssModule'] const useCssVars: typeof import('vue')['useCssVars'] const useId: typeof import('vue')['useId'] diff --git a/modules/web/src/store/bookStore.ts b/modules/web/src/store/bookStore.ts index 9297f7d68..0b7a1398d 100644 --- a/modules/web/src/store/bookStore.ts +++ b/modules/web/src/store/bookStore.ts @@ -29,9 +29,6 @@ let webReadConfigLoadedDate: Date | undefined export const useBookStore = defineStore('book', { state: () => { return { - connectStatus: '正在连接后端服务器……', - connectType: 'primary' as 'primary' | 'success' | 'danger', - newConnect: true, searchBooks: [] as SeachBook[], shelf: [] as Book[], catalog: [] as BookChapter[], @@ -69,15 +66,6 @@ export const useBookStore = defineStore('book', { isNight: state => state.config.theme == 6, }, actions: { - setConnectStatus(connectStatus: string) { - this.connectStatus = connectStatus - }, - setConnectType(connectType: 'primary' | 'success' | 'danger') { - this.connectType = connectType - }, - setNewConnect(newConnect: boolean) { - this.newConnect = newConnect - }, /** 从后端加载书架书籍,优先返回内存缓存 */ async loadBookShelf(): Promise { const fetchBookshellf_promise = API.getBookShelf().then(resp => { @@ -116,16 +104,13 @@ export const useBookStore = defineStore('book', { return await fetchBookshellf_promise } }, - clearBooks() { - this.shelf = [] - }, /** 从后端加载书籍目录,优先返回内存缓存 */ async loadWebCatalog( book: typeof this.readingBook, ): Promise { const { bookUrl, name, chapterIndex } = book const fetchChapterList_promise = API.getChapterList( - book.bookUrl as string, + bookUrl as string, ).then(res => { const { isSuccess, data, errorMsg } = res.data if (isSuccess === false) { @@ -133,13 +118,14 @@ export const useBookStore = defineStore('book', { throw new Error() } if ( + bookUrl === this.readingBook.bookUrl && data.length !== this.catalog.length && data.length > 0 && this.catalog.length > 0 ) { ElMessage.info(`书籍${name}: 章节目录已更新`) } - this.setCatalog(data) + this.catalog = data console.log(`书籍${name}: 章节目录已更新`) return this.catalog }) @@ -155,9 +141,6 @@ export const useBookStore = defineStore('book', { return await fetchChapterList_promise } }, - setCatalog(catalog: BookChapter[]) { - this.catalog = catalog - }, setPopCataVisible(visible: boolean) { this.popCataVisible = visible }, diff --git a/modules/web/src/store/connectionStore.ts b/modules/web/src/store/connectionStore.ts new file mode 100644 index 000000000..7bfc758b6 --- /dev/null +++ b/modules/web/src/store/connectionStore.ts @@ -0,0 +1,24 @@ +import { defineStore } from 'pinia' + +export const useConnectionStore = defineStore('connection', { + state: () => { + return { + connectStatus: '正在连接后端服务器……', + connectType: 'primary' as 'primary' | 'success' | 'danger', + newConnect: false, + } + }, + actions: { + setConnectStatus(connectStatus: string) { + if (this.newConnect === true) return + this.connectStatus = connectStatus + }, + setConnectType(connectType: 'primary' | 'success' | 'danger') { + if (this.newConnect === true) return + this.connectType = connectType + }, + setNewConnect(newConnect: boolean) { + this.newConnect = newConnect + }, + }, +}) diff --git a/modules/web/src/store/index.ts b/modules/web/src/store/index.ts index 0f478c091..7daa3098e 100644 --- a/modules/web/src/store/index.ts +++ b/modules/web/src/store/index.ts @@ -2,4 +2,5 @@ import { createPinia } from 'pinia' export * from './bookStore' export * from './sourceStore' +export * from './connectionStore' export default createPinia() diff --git a/modules/web/src/views/BookShelf.vue b/modules/web/src/views/BookShelf.vue index 83abdd746..80666bf3e 100644 --- a/modules/web/src/views/BookShelf.vue +++ b/modules/web/src/views/BookShelf.vue @@ -175,9 +175,9 @@ const searchBook = () => { } //连接状态 -const connectStatus = computed(() => store.connectStatus) -const connectType = computed(() => store.connectType) -const newConnect = computed(() => store.newConnect) +const connectionStore = useConnectionStore() +const { connectStatus, connectType, newConnect } = storeToRefs(connectionStore) + const setLegadoRetmoteUrl = () => { ElMessageBox.prompt( '请输入 后端地址 ( 如:http://127.0.0.1:9527 或者通过内网穿透的地址)', @@ -190,34 +190,30 @@ const setLegadoRetmoteUrl = () => { inputErrorMessage: '输入的格式不对', beforeClose: (action, instance, done) => { if (action === 'confirm') { - store.setNewConnect(true) + connectionStore.setNewConnect(true) instance.confirmButtonLoading = true instance.confirmButtonText = '校验中……' // instance.inputValue const url = new URL(instance.inputValue).toString() API.getReadConfig(url) - //API.getBookShelf() .then(function (config) { + connectionStore.setNewConnect(false) applyReadConfig(config) instance.confirmButtonLoading = false - store.setConnectType('success') store.clearSearchBooks() - store.setNewConnect(false) setApiEntryPoint(...parseLeagdoHttpUrlWithDefault(url)) if (url === location.origin) { localStorage.removeItem(baseURL_localStorage_key) } else { localStorage.setItem(baseURL_localStorage_key, url) } - store.setConnectStatus('已连接 ' + url.toString()) - fetchBookShelfData() + store.loadBookShelf() done() }) .catch(function (error) { + connectionStore.setNewConnect(false) instance.confirmButtonLoading = false instance.confirmButtonText = '确定' - ElMessage.error('访问失败,请检查您输入的 url') - store.setNewConnect(false) throw error }) } else { @@ -287,25 +283,12 @@ const toDetail = ( } const loadShelf = async () => { - try { - await store.loadWebConfig() - await store.saveBookProgress() - //确保各种网络情况下同步请求先完成 - await fetchBookShelfData() - } catch (error: unknown) { - store.setConnectType('danger') - store.setConnectStatus('连接异常') - store.setNewConnect(false) - throw error - } + await store.loadWebConfig() + await store.saveBookProgress() + //确保各种网络情况下同步请求先完成 + await store.loadBookShelf() } -const fetchBookShelfData = async () => { - await store.loadBookShelf() - store.setConnectType('primary') - store.setConnectStatus('已连接 ' + legado_http_entry_point) - store.setNewConnect(false) -} onMounted(() => { //获取最近阅读书籍 const readingRecentStr = localStorage.getItem('readingRecent')