diff --git a/api/config.ts b/api/config.ts index 26ae193..741930c 100644 --- a/api/config.ts +++ b/api/config.ts @@ -7,8 +7,8 @@ export const ENV = process.env.NODE_ENV || 'development'; */ const BASE_URL_MAP = { development: { - MAIN: 'http://192.168.110.100:9300/pb/', - // MAIN: 'https://global.nuttyreading.com/', + MAIN: 'http://192.168.110.100:9300/pb/', // 张川川 + // MAIN: 'https://global.nuttyreading.com/', // 线上 // PAYMENT: 'https://dev-pay.example.com', // 暂时用不到 // CDN: 'https://cdn-dev.example.com', // 暂时用不到 }, @@ -24,6 +24,6 @@ export const APP_INFO = { VERSION_CODE: '1.0.0', // APP 版本号,可能升级的时候会用,这里需要再确定? } -export const REQUEST_TIMEOUT = 15000; +export const REQUEST_TIMEOUT = 30000; export const SERVICE_MAP = (BASE_URL_MAP as any)[ENV] ?? BASE_URL_MAP.development; diff --git a/api/interceptors/request.ts b/api/interceptors/request.ts index 0492791..593b7fb 100644 --- a/api/interceptors/request.ts +++ b/api/interceptors/request.ts @@ -1,4 +1,3 @@ -// 片段示例 - requestInterceptor 更稳健的写法 import type { IRequestOptions } from '../types' import { useUserStore } from '@/stores/user' import { APP_INFO } from '@/api/config' diff --git a/api/interceptors/response.ts b/api/interceptors/response.ts index 65d1b30..aa90e85 100644 --- a/api/interceptors/response.ts +++ b/api/interceptors/response.ts @@ -1,5 +1,5 @@ -// api/interceptors/response.ts import type { IApiResponse } from '../types'; +import { t } from '@/utils/i18n' /** * 响应拦截器:严格兼容原项目返回约定 @@ -12,23 +12,24 @@ import type { IApiResponse } from '../types'; */ function handleAuthExpired() { - // 清空本地登录信息(保持与原项目一致) + // 清空本地登录信息 try { uni.removeStorageSync('userInfo'); } catch (e) {} // 跳转 login,与原项目保持一致的路径 - // 在小程序/APP/H5 情况下原项目分别做了适配,简单通用处理如下: - uni.showToast({ title: '登录失效,请重新登录', icon: 'none' }); + uni.showToast({ title: t('global.loginExpired'), icon: 'none' }); setTimeout(() => { - uni.navigateTo({ url: '/pages/login/login' }); + uni.reLaunch({ url: '/pages/login/login' }); }, 600); } export function responseInterceptor(res: UniApp.RequestSuccessCallbackResult) { // 先处理非 200 的 http 状态 if (res.statusCode && res.statusCode !== 200) { - const msg = `网络错误(${res.statusCode})`; - uni.showToast({ title: msg, icon: 'none' }); + const msg = `${t('global.networkConnectionError')} (${res.statusCode})`; + setTimeout(() => { + uni.showToast({ title: msg, icon: 'none' }); + }, 10); return Promise.reject({ statusCode: res.statusCode, errMsg: msg, response: res }); } @@ -59,17 +60,17 @@ export function responseInterceptor(res: UniApp.RequestSuccessCallbackResult) { if (code === '401' || code === 401) { // 触发登出流程 handleAuthExpired(); - return Promise.reject({ statusCode: 0, errMsg: '登录失效', data: httpData }); + return Promise.reject({ statusCode: 0, errMsg: t('global.loginExpired'), data: httpData }); } // 原项目还将 1000,1001,1100,402 等视作需要强制登录 if (code === '1000' || code === '1001' || code === 1000 || code === 1001 || code === 1100 || code === '402' || code === 402) { handleAuthExpired(); - return Promise.reject({ statusCode: 0, errMsg: message || '请登录', data: httpData }); + return Promise.reject({ statusCode: 0, errMsg: message || t('global.loginExpired'), data: httpData }); } // 其他后端业务错误:toast 并 reject - const errMsg = message || '请求异常'; + const errMsg = message || t('global.requestException'); if (errMsg) { uni.showToast({ title: errMsg, icon: 'none' }); } diff --git a/api/modules/auth.ts b/api/modules/auth.ts index d10c8ff..651f47c 100644 --- a/api/modules/auth.ts +++ b/api/modules/auth.ts @@ -1,7 +1,6 @@ -// api/modules/auth.ts import { mainClient } from '@/api/clients/main' import type { IApiResponse } from '@/api/types' -import type { IUserInfo, ILoginResponse } from '@/types/user' +import type { ILoginResponse } from '@/types/user' /** * 验证码登录/注册 diff --git a/api/request.ts b/api/request.ts index f04261d..b908f14 100644 --- a/api/request.ts +++ b/api/request.ts @@ -3,6 +3,7 @@ import { requestInterceptor } from './interceptors/request'; import { responseInterceptor } from './interceptors/response'; import type { IRequestOptions, ICreateClientConfig } from './types'; import { REQUEST_TIMEOUT } from './config'; +import { t } from '@/utils/i18n' export function createRequestClient(cfg: ICreateClientConfig) { const baseURL = cfg.baseURL; @@ -17,14 +18,22 @@ export function createRequestClient(cfg: ICreateClientConfig) { header: options.header || {}, }; - // run request interceptor to mutate headers, etc. + // 运行请求拦截器,修改 headers 等 const intercepted = requestInterceptor(final as IRequestOptions); + // 全局处理请求 loading + const loading = !cfg.loading ? true : cfg.loading // 接口请求参数不传loading,默认显示loading + loading && uni.showLoading() + return new Promise((resolve, reject) => { uni.request({ ...intercepted, + complete() { + // 请求完成关闭 loading + loading && uni.hideLoading() + }, success(res: any) { - // delegate to response interceptor + // 委托给响应拦截器处理 responseInterceptor(res) .then((r) => { resolve(r as any); @@ -34,7 +43,7 @@ export function createRequestClient(cfg: ICreateClientConfig) { }); }, fail(err: any) { - uni.showToast({ title: '网络连接失败', icon: 'none' }); + uni.showToast({ title: t('global.networkConnectionError'), icon: 'none' }); reject(err); }, } as any); diff --git a/api/types.d.ts b/api/types.d.ts index 7e42f1d..7165b8f 100644 --- a/api/types.d.ts +++ b/api/types.d.ts @@ -22,4 +22,5 @@ export interface IRequestOptions extends UniApp.RequestOptions { export interface ICreateClientConfig { baseURL: string; timeout?: number; + loading?: boolean; } diff --git a/locale/en.json b/locale/en.json index cec5beb..ad9ba65 100644 --- a/locale/en.json +++ b/locale/en.json @@ -13,7 +13,10 @@ "tips": "Tips", "searchNoResult": "No search results found", "more": "More", - "dataNull": "No data available" + "dataNull": "No data available", + "networkConnectionError": "Network connection error.", + "loginExpired": "Login expired. Please log in again.", + "requestException": "Request exception" }, "tabar.course": "COURSE", "tabar.book": "EBOOK", @@ -130,6 +133,7 @@ "sex": "Gender", "male": "Male", "female": "Female", + "secrecy": "Secrecy", "avatar": "Avatar", "changeAvatar": "Change Avatar", "setAvatar": "Set Avatar", diff --git a/locale/zh-Hans.json b/locale/zh-Hans.json index 70bef0a..acd4c5a 100644 --- a/locale/zh-Hans.json +++ b/locale/zh-Hans.json @@ -13,7 +13,10 @@ "tips": "提示", "searchNoResult": "暂无搜索结果", "more": "更多", - "dataNull": "暂无数据" + "dataNull": "暂无数据", + "networkConnectionError": "网络连接错误。", + "loginExpired": "登录失效,请重新登录。", + "requestException": "请求异常" }, "tabar.course": "课程", "tabar.book": "图书", @@ -131,6 +134,7 @@ "sex": "性别", "male": "男", "female": "女", + "secrecy": "保密", "avatar": "头像", "changeAvatar": "更换头像", "setAvatar": "设置头像", diff --git a/manifest.json b/manifest.json index 79e0a48..2bb601b 100644 --- a/manifest.json +++ b/manifest.json @@ -33,7 +33,8 @@ "", "", "", - "" + "", + "" ], "abiFilters" : [ "armeabi-v7a", "arm64-v8a", "x86" ], "minSdkVersion" : 23, diff --git a/pages/book/detail.vue b/pages/book/detail.vue index 23425ec..dcee6e7 100644 --- a/pages/book/detail.vue +++ b/pages/book/detail.vue @@ -128,7 +128,7 @@ - + {{ $t('bookDetails.buy') }} @@ -222,15 +222,12 @@ function initScrollHeight() { // 加载书籍详情 async function loadBookInfo() { try { - uni.showLoading({ title: t('global.loading') }) const res = await bookApi.getBookInfo(bookId.value) - uni.hideLoading() if (res.bookInfo) { bookInfo.value = res.bookInfo } } catch (error) { - uni.hideLoading() console.error('Failed to load book info:', error) } } @@ -531,6 +528,7 @@ function goToDetail(id: number) { &.buy-full { flex: 1; padding: 0 30rpx; + text-align: center; } } } diff --git a/pages/book/index.vue b/pages/book/index.vue index e9ebd38..b377ee3 100644 --- a/pages/book/index.vue +++ b/pages/book/index.vue @@ -330,7 +330,6 @@ const getBooksByLabel = async ( labelId: number, type: 'activity' | 'category' ) => { - uni.showLoading({ title: t('common.loading') }) try { const res = await homeApi.getBooksByLabel(labelId) if (type === 'activity') { @@ -348,8 +347,6 @@ const getBooksByLabel = async ( } } catch (error) { console.error('获取图书列表失败:', error) - } finally { - uni.hideLoading() } } diff --git a/pages/book/listen/index.vue b/pages/book/listen/index.vue index 53fa183..5c52f2f 100644 --- a/pages/book/listen/index.vue +++ b/pages/book/listen/index.vue @@ -130,11 +130,9 @@ async function loadBookInfo() { // 加载章节列表 async function loadChapterList() { try { - uni.showLoading({ title: t('global.loading') }) const res = await bookApi.getBookChapter({ bookId: bookId.value }) - uni.hideLoading() if (res.chapterList && res.chapterList.length > 0) { chapterList.value = res.chapterList @@ -142,7 +140,6 @@ async function loadChapterList() { nullText.value = t('common.data_null') } } catch (error) { - uni.hideLoading() nullText.value = t('common.data_null') console.error('Failed to load chapter list:', error) } diff --git a/pages/book/listen/player.vue b/pages/book/listen/player.vue index 1ffb6fc..39ac74a 100644 --- a/pages/book/listen/player.vue +++ b/pages/book/listen/player.vue @@ -258,8 +258,6 @@ async function loadBookInfo() { // 加载章节列表 async function loadChapterList() { try { - uni.showLoading({ title: t('common.loading') }) - const res = await bookApi.getBookChapter({ bookId: bookId.value }) @@ -269,8 +267,6 @@ async function loadChapterList() { if (res.chapterList && res.chapterList.length > 0) { chapterList.value = res.chapterList - uni.hideLoading() - // 加载当前章节内容 if (currentChapterIndex.value < chapterList.value.length) { const currentChapter = chapterList.value[currentChapterIndex.value] @@ -280,14 +276,12 @@ async function loadChapterList() { playChapter(currentChapter) } } else { - uni.hideLoading() uni.showToast({ title: '章节列表为空', icon: 'none' }) } } catch (error) { - uni.hideLoading() console.error('Failed to load chapter list:', error) uni.showToast({ title: '加载章节失败', @@ -298,9 +292,7 @@ async function loadChapterList() { // 加载章节内容(带音频时间点) async function loadChapterContent(chapterId: number) { - try { - uni.showLoading({ title: t('common.loading') }) - + try { console.log('加载章节内容, chapterId:', chapterId) const res = await bookApi.getChapterContentListen(chapterId) console.log('章节内容响应:', res) @@ -327,11 +319,8 @@ async function loadChapterContent(chapterId: number) { console.log('章节内容为空') currentContent.value = '暂无内容' } - - uni.hideLoading() } catch (error) { console.error('Failed to load chapter content:', error) - uni.hideLoading() uni.showToast({ title: '加载内容失败', icon: 'none' @@ -364,7 +353,6 @@ function playChapter(chapter: IChapter) { if (audioContext.value) { console.log('设置音频源:', chapter.voices) - uni.showLoading({ title: t('common.readAudio') }) audioContext.value.src = chapter.voices audioContext.value.playbackRate = playbackRate.value @@ -372,7 +360,6 @@ function playChapter(chapter: IChapter) { // 监听音频准备就绪 audioContext.value.onCanplay(() => { console.log('音频准备就绪') - uni.hideLoading() }) audioContext.value.play() diff --git a/pages/book/order.vue b/pages/book/order.vue new file mode 100644 index 0000000..e69de29 diff --git a/pages/book/reader.vue b/pages/book/reader.vue index bbd1386..48e15b0 100644 --- a/pages/book/reader.vue +++ b/pages/book/reader.vue @@ -2,12 +2,27 @@ - + {{ currentChapterTitle }} + + + - + @@ -99,8 +114,6 @@ - - @@ -165,13 +178,11 @@ - - - {{ nullStatus }} + {{ $t('common.data_null') }} @@ -183,6 +194,7 @@ import { useI18n } from 'vue-i18n' import { useBookStore } from '@/stores/book' import { bookApi } from '@/api/modules/book' import type { IChapter, IChapterContent, IReadProgress } from '@/types/book' +import { onPageBack } from '@/utils/index' const { t } = useI18n() const bookStore = useBookStore() @@ -199,7 +211,6 @@ const currentChapterIndex = ref(0) const currentChapterId = ref(0) const currentContentId = ref(0) const loading = ref(false) -const nullStatus = ref('') // 阅读设置 const fontSizeLevel = ref(1) @@ -260,7 +271,6 @@ const currentChapterTitle = computed(() => { return chapter.chapter + (chapter.content ? ' - ' + chapter.content : '') }) -// 生命周期 onLoad((options: any) => { if (options.bookId) bookId.value = Number(options.bookId) if (options.isBuy) isBuy.value = options.isBuy @@ -290,20 +300,23 @@ onShow(() => { }, 300) } else { currentPage.value = 1 - calculatePages() + } }) onMounted(() => { initHeights() + calculatePages() }) onHide(() => { saveProgress() + console.log('onHide') }) onBackPress(() => { saveProgress() + console.log('onBackPress') return false }) @@ -313,7 +326,7 @@ function initHeights() { const windowHeight = systemInfo.windowHeight // 翻页模式高度 - const math = Math.floor((windowHeight - (notchHeight.value + 130)) / lineHeight.value) + const math = Math.floor((windowHeight - (notchHeight.value + 80)) / lineHeight.value) wrapHeight.value = math * lineHeight.value // 滚动模式高度 @@ -359,11 +372,8 @@ async function loadChapterList() { // 默认加载第一章 await loadChapterContent(chapterList.value[0].id, 0) - } else { - nullStatus.value = t('common.data_null') } } catch (error) { - nullStatus.value = t('common.data_null') console.error('Failed to load chapter list:', error) } finally { loading.value = false @@ -373,9 +383,7 @@ async function loadChapterList() { // 加载章节内容 async function loadChapterContent(chapterId: number, index: number) { try { - uni.showLoading({ title: t('global.loading') }) const res = await bookApi.getChapterContent(chapterId) - uni.hideLoading() if (res.contentPage && res.contentPage.length > 0) { contentList.value = res.contentPage @@ -402,7 +410,6 @@ async function loadChapterContent(chapterId: number, index: number) { catalogVisible.value = false } } catch (error) { - uni.hideLoading() console.error('Failed to load chapter content:', error) } } @@ -524,6 +531,7 @@ function toggleControls() { function showCatalog() { catalogVisible.value = true settingsVisible.value = false + showControls.value = false } // 关闭目录 @@ -534,9 +542,9 @@ function closeCatalog() { // 显示设置 function showSettings() { - console.log('currentLanguage', currentLanguage.value) settingsVisible.value = true catalogVisible.value = false + showControls.value = false } // 关闭设置 function closeSettings() { @@ -615,21 +623,12 @@ const changeBookLanguage = (language: any) => { // 保存进度 async function saveProgress() { - if (isBuy.value === '0') { - try { - await bookApi.saveReadProgress(bookId.value, currentChapterId.value, currentContentId.value) - } catch (error) { - console.error('Failed to save progress:', error) - } + try { + await bookApi.saveReadProgress(bookId.value, currentChapterId.value, currentContentId.value) + } catch (error) { + console.error('Failed to save progress:', error) } } - -// 返回 -function goBack() { - uni.navigateTo({ - url: '/pages/book/detail?id=' + bookId.value - }) -} diff --git a/pages/book/review.vue b/pages/book/review.vue index e896ab6..246772f 100644 --- a/pages/book/review.vue +++ b/pages/book/review.vue @@ -181,9 +181,7 @@ async function loadComments() { } try { - uni.showLoading({ title: t('global.loading') }) const res = await bookApi.getBookComments(bookId.value, page.value.current, page.value.limit) - uni.hideLoading() commentsCount.value = res.commentsCount || 0 @@ -194,7 +192,6 @@ async function loadComments() { nullText.value = t('common.data_null') } } catch (error) { - uni.hideLoading() nullText.value = t('common.data_null') console.error('Failed to load comments:', error) } @@ -472,7 +469,7 @@ function toggleEmoji() { } .editor { - border: 1rpx solid #ddd; + border: 1px solid #ddd; width: 100%; min-height: 200rpx; height: 200rpx; diff --git a/pages/component/component.vue b/pages/component/component.vue deleted file mode 100644 index 151b350..0000000 --- a/pages/component/component.vue +++ /dev/null @@ -1,22 +0,0 @@ - - - - - diff --git a/pages/login/forget.vue b/pages/login/forget.vue index c3a6206..e91fa68 100644 --- a/pages/login/forget.vue +++ b/pages/login/forget.vue @@ -192,16 +192,13 @@ const getCode = async () => { if (!isEmailVerified(email.value)) return try { - uni.showLoading() await commonApi.sendMailCaptcha(email.value) - uni.hideLoading() uni.showToast({ title: t('login.sendCodeSuccess'), icon: 'none' }) getCodeState() } catch (error) { - uni.hideLoading() console.error('Send code error:', error) } } @@ -268,9 +265,7 @@ const onSubmit = async () => { if (!isPasswordMatch()) return try { - uni.showLoading() await resetPassword(email.value, code.value, password.value) - uni.hideLoading() uni.showModal({ title: t('global.tips'), @@ -281,7 +276,6 @@ const onSubmit = async () => { } }) } catch (error) { - uni.hideLoading() console.error('Reset password error:', error) } } diff --git a/pages/login/login.vue b/pages/login/login.vue index 19d517d..d0203a5 100644 --- a/pages/login/login.vue +++ b/pages/login/login.vue @@ -115,11 +115,11 @@ - + @@ -357,16 +357,13 @@ const onSetCode = async () => { if (!isEmailVerified(email.value)) return false try { - uni.showLoading() await commonApi.sendMailCaptcha(email.value) - uni.hideLoading() uni.showToast({ title: t('login.sendCodeSuccess'), icon: 'none' }) getCodeState() } catch (error) { - uni.hideLoading() submitClickNum.value++ // 发送验证码失败时增加提交点击次数 console.error('Send code error:', error) } diff --git a/pages/user/about/index.vue b/pages/user/about/index.vue index af16d6f..0a7c6bf 100644 --- a/pages/user/about/index.vue +++ b/pages/user/about/index.vue @@ -36,10 +36,7 @@ --> - - 022-24142321 - - + @@ -86,7 +83,7 @@ const getAppVersion = () => { * 拨打电话 */ const handlePhoneCall = () => { - makePhoneCall('022-24142321', t('user.hotline'), t) + makePhoneCall('022-24142321', t('user.hotline')) } /** diff --git a/pages/user/feedback/index.vue b/pages/user/feedback/index.vue index 9d75852..e359a29 100644 --- a/pages/user/feedback/index.vue +++ b/pages/user/feedback/index.vue @@ -11,6 +11,7 @@ { } try { - uni.showLoading() - // 处理图片 if (imageUrl.value.length > 0) { form.value.image = imageUrl.value.join(',') @@ -236,8 +235,6 @@ const handleSubmit = async () => { // 提交反馈 await submitFeedback(form.value) - - uni.hideLoading() uni.showModal({ title: t('global.tips'), @@ -265,7 +262,6 @@ const handleSubmit = async () => { }) } catch (error) { console.error('提交反馈失败:', error) - uni.hideLoading() uni.showToast({ title: t('user.feedbackFailed'), icon: 'none' diff --git a/pages/user/index.vue b/pages/user/index.vue index a993067..8e2fff4 100644 --- a/pages/user/index.vue +++ b/pages/user/index.vue @@ -1,7 +1,7 @@