diff --git a/frontend/auto-imports.d.ts b/frontend/auto-imports.d.ts new file mode 100644 index 0000000..9d24007 --- /dev/null +++ b/frontend/auto-imports.d.ts @@ -0,0 +1,10 @@ +/* eslint-disable */ +/* prettier-ignore */ +// @ts-nocheck +// noinspection JSUnusedGlobalSymbols +// Generated by unplugin-auto-import +// biome-ignore lint: disable +export {} +declare global { + +} diff --git a/frontend/components.d.ts b/frontend/components.d.ts index e2383c8..04e0c93 100644 --- a/frontend/components.d.ts +++ b/frontend/components.d.ts @@ -11,6 +11,27 @@ export {} /* prettier-ignore */ declare module 'vue' { export interface GlobalComponents { + ElAlert: typeof import('element-plus/es')['ElAlert'] + ElButton: typeof import('element-plus/es')['ElButton'] + ElCarousel: typeof import('element-plus/es')['ElCarousel'] + ElCarouselItem: typeof import('element-plus/es')['ElCarouselItem'] + ElDialog: typeof import('element-plus/es')['ElDialog'] + ElEmpty: typeof import('element-plus/es')['ElEmpty'] + ElForm: typeof import('element-plus/es')['ElForm'] + ElFormItem: typeof import('element-plus/es')['ElFormItem'] + ElIcon: typeof import('element-plus/es')['ElIcon'] + ElInput: typeof import('element-plus/es')['ElInput'] + ElInputNumber: typeof import('element-plus/es')['ElInputNumber'] + ElOption: typeof import('element-plus/es')['ElOption'] + ElPagination: typeof import('element-plus/es')['ElPagination'] + ElRadioButton: typeof import('element-plus/es')['ElRadioButton'] + ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup'] + ElSelect: typeof import('element-plus/es')['ElSelect'] + ElSwitch: typeof import('element-plus/es')['ElSwitch'] + ElTable: typeof import('element-plus/es')['ElTable'] + ElTableColumn: typeof import('element-plus/es')['ElTableColumn'] + ElTag: typeof import('element-plus/es')['ElTag'] + ElTimePicker: typeof import('element-plus/es')['ElTimePicker'] RouterLink: typeof import('vue-router')['RouterLink'] RouterView: typeof import('vue-router')['RouterView'] VanButton: typeof import('vant/es')['Button'] @@ -29,4 +50,7 @@ declare module 'vue' { VanSwipeItem: typeof import('vant/es')['SwipeItem'] VanTag: typeof import('vant/es')['Tag'] } + export interface GlobalDirectives { + vLoading: typeof import('element-plus/es')['ElLoadingDirective'] + } } diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts index 9550d07..a5554f0 100644 --- a/frontend/src/api/client.ts +++ b/frontend/src/api/client.ts @@ -1,4 +1,11 @@ import axios, { type AxiosResponse, type InternalAxiosRequestConfig } from 'axios' +import { ElMessage } from 'element-plus' + +declare module 'axios' { + export interface AxiosRequestConfig { + silent?: boolean + } +} import { clearAuthStorage, @@ -30,7 +37,10 @@ type RefreshState = { pendingRequests: RetryRequest[] } -type RetriableRequestConfig = InternalAxiosRequestConfig & { _retry?: boolean } +type RetriableRequestConfig = InternalAxiosRequestConfig & { + _retry?: boolean + silent?: boolean +} const refreshStates: Record = { user: { @@ -103,6 +113,10 @@ apiClient.interceptors.response.use( async (error) => { const originalRequest = error.config as RetriableRequestConfig | undefined if (!originalRequest || error?.response?.status !== 401 || originalRequest._retry) { + if (originalRequest && !originalRequest.silent) { + const msg = error.response?.data?.message || error.message || '网络连接异常,请稍后重试' + ElMessage.error(msg) + } return Promise.reject(error) } @@ -110,6 +124,9 @@ apiClient.interceptors.response.use( const scope = getRequestScope(requestUrl) if (isRefreshRequest(requestUrl)) { redirectToLogin(scope) + if (originalRequest && !originalRequest.silent) { + ElMessage.error('登录已失效,请重新登录') + } return Promise.reject(error) } @@ -134,6 +151,9 @@ apiClient.interceptors.response.use( } catch (refreshError) { rejectPendingRequests(scope, refreshError) redirectToLogin(scope) + if (originalRequest && !originalRequest.silent) { + ElMessage.error('会话已过期,请重新登录') + } return Promise.reject(refreshError) } finally { state.refreshing = false diff --git a/frontend/src/main.ts b/frontend/src/main.ts index 3a172da..d38a3ea 100644 --- a/frontend/src/main.ts +++ b/frontend/src/main.ts @@ -1,8 +1,8 @@ -import "element-plus/dist/index.css"; +import "element-plus/theme-chalk/el-message.css"; +import "element-plus/theme-chalk/el-message-box.css"; import "vant/lib/index.css"; import "./styles/base.css"; -import ElementPlus from "element-plus"; import { createPinia } from "pinia"; import { createApp } from "vue"; @@ -14,7 +14,7 @@ import { useSessionStore } from "./stores/session"; const app = createApp(App); const pinia = createPinia(); -app.use(pinia).use(router).use(ElementPlus); +app.use(pinia).use(router); window.addEventListener("auth-storage-changed", () => { useSessionStore(pinia).syncFromStorage(); diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index f8b1a56..93cc4f7 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -2,14 +2,19 @@ import { fileURLToPath, URL } from "node:url"; import vue from "@vitejs/plugin-vue"; import { defineConfig } from "vite"; +import AutoImport from "unplugin-auto-import/vite"; import Components from "unplugin-vue-components/vite"; +import { ElementPlusResolver } from "unplugin-vue-components/resolvers"; import { VantResolver } from "@vant/auto-import-resolver"; export default defineConfig({ plugins: [ vue(), + AutoImport({ + resolvers: [ElementPlusResolver()], + }), Components({ - resolvers: [VantResolver()], + resolvers: [ElementPlusResolver(), VantResolver()], }), ], resolve: {