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 @@
@@ -24,28 +19,7 @@ function isNavActive(path: string) {
-
+
@@ -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 @@