diff --git a/frontend/components.d.ts b/frontend/components.d.ts index 04e0c93..a72c81d 100644 --- a/frontend/components.d.ts +++ b/frontend/components.d.ts @@ -32,6 +32,7 @@ declare module 'vue' { ElTableColumn: typeof import('element-plus/es')['ElTableColumn'] ElTag: typeof import('element-plus/es')['ElTag'] ElTimePicker: typeof import('element-plus/es')['ElTimePicker'] + MobileBottomNav: typeof import('./src/components/MobileBottomNav.vue')['default'] RouterLink: typeof import('vue-router')['RouterLink'] RouterView: typeof import('vue-router')['RouterView'] VanButton: typeof import('vant/es')['Button'] diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts index a5554f0..7290097 100644 --- a/frontend/src/api/client.ts +++ b/frontend/src/api/client.ts @@ -1,5 +1,15 @@ import axios, { type AxiosResponse, type InternalAxiosRequestConfig } from 'axios' import { ElMessage } from 'element-plus' +import { showToast } from 'vant' + +function showError(message: string) { + const isMobile = window.location.pathname.startsWith('/m') + if (isMobile) { + showToast({ message, icon: 'cross' }) + } else { + ElMessage.error(message) + } +} declare module 'axios' { export interface AxiosRequestConfig { @@ -115,7 +125,7 @@ apiClient.interceptors.response.use( if (!originalRequest || error?.response?.status !== 401 || originalRequest._retry) { if (originalRequest && !originalRequest.silent) { const msg = error.response?.data?.message || error.message || '网络连接异常,请稍后重试' - ElMessage.error(msg) + showError(msg) } return Promise.reject(error) } @@ -125,7 +135,7 @@ apiClient.interceptors.response.use( if (isRefreshRequest(requestUrl)) { redirectToLogin(scope) if (originalRequest && !originalRequest.silent) { - ElMessage.error('登录已失效,请重新登录') + showError('登录已失效,请重新登录') } return Promise.reject(error) } @@ -152,7 +162,7 @@ apiClient.interceptors.response.use( rejectPendingRequests(scope, refreshError) redirectToLogin(scope) if (originalRequest && !originalRequest.silent) { - ElMessage.error('会话已过期,请重新登录') + showError('会话已过期,请重新登录') } return Promise.reject(refreshError) } finally { diff --git a/frontend/src/components/MobileBottomNav.vue b/frontend/src/components/MobileBottomNav.vue new file mode 100644 index 0000000..47d9f44 --- /dev/null +++ b/frontend/src/components/MobileBottomNav.vue @@ -0,0 +1,111 @@ + + + + + diff --git a/frontend/src/views/mobile/MobileHomeView.css b/frontend/src/views/mobile/MobileHomeView.css index 0251000..c06b63f 100644 --- a/frontend/src/views/mobile/MobileHomeView.css +++ b/frontend/src/views/mobile/MobileHomeView.css @@ -2,7 +2,7 @@ min-height: 100vh; background: #f5f7fa; color: #17233d; - padding-bottom: 58px; + padding-bottom: calc(58px + env(safe-area-inset-bottom)); } /* ========== 顶部信息区 ========== */ @@ -569,55 +569,6 @@ } } -/* ========== 底部导航:原生App风格 ========== */ -.bottom-nav { - position: fixed; - left: 0; - right: 0; - bottom: 0; - z-index: 100; - display: flex; - height: 50px; - background: #fff; - border-top: 1px solid #eee; -} - -.nav-item { - flex: 1; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - gap: 2px; - color: #999; - font-size: 10px; - text-decoration: none; -} - -.nav-item.active { - color: #1477ff; -} - -.nav-item span { - font-weight: 600; -} - -.publish-pill { - width: 36px; - height: 26px; - display: grid; - place-items: center; - border-radius: 13px; - background: #ff6a00; - color: #fff; - font-size: 18px; - font-weight: 900; -} - -.nav-publish span { - color: #ff6a00; -} - /* ========== 响应式适配 ========== */ @media (min-width: 520px) { .mobile-shell { @@ -625,9 +576,4 @@ margin: 0 auto; box-shadow: 0 0 0 1px rgba(23, 35, 61, 0.08); } - - .bottom-nav { - left: calc((100vw - 430px) / 2); - right: calc((100vw - 430px) / 2); - } } diff --git a/frontend/src/views/mobile/MobileHomeView.vue b/frontend/src/views/mobile/MobileHomeView.vue index 3d97c9a..7a2b58f 100644 --- a/frontend/src/views/mobile/MobileHomeView.vue +++ b/frontend/src/views/mobile/MobileHomeView.vue @@ -1,7 +1,8 @@ @@ -53,7 +27,7 @@ function isNavActive(path: string) { .mobile-messages { min-height: 100dvh; background: #f5f7fa; - padding-bottom: 56px; + padding-bottom: calc(56px + env(safe-area-inset-bottom)); } /* ========== 顶部导航 ========== */ @@ -91,53 +65,4 @@ function isNavActive(path: string) { .header-spacer { width: 36px; } - -/* ========== 底部导航 ========== */ -.bottom-nav { - position: fixed; - left: 0; - right: 0; - bottom: 0; - z-index: 100; - display: flex; - height: 50px; - background: #fff; - border-top: 1px solid #eee; -} - -.nav-item { - flex: 1; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - gap: 2px; - color: #999; - font-size: 10px; - text-decoration: none; -} - -.nav-item.active { - color: #1477ff; -} - -.nav-item span { - font-weight: 600; -} - -.publish-pill { - width: 36px; - height: 26px; - display: grid; - place-items: center; - border-radius: 13px; - background: #ff6a00; - color: #fff; - font-size: 18px; - font-weight: 900; -} - -.nav-publish span { - color: #ff6a00; -} diff --git a/frontend/src/views/mobile/MobileOrdersView.vue b/frontend/src/views/mobile/MobileOrdersView.vue index caf6959..e943499 100644 --- a/frontend/src/views/mobile/MobileOrdersView.vue +++ b/frontend/src/views/mobile/MobileOrdersView.vue @@ -1,26 +1,20 @@