From 430c8f314de065e8fe1cb1b8215d86d450eb44e7 Mon Sep 17 00:00:00 2001 From: zhouwentao Date: Sat, 27 Dec 2025 09:39:24 +0800 Subject: [PATCH] updates --- project_codebase.md | 7 ++ project_doing.md | 8 +++ project_index.md | 2 + project_task.md | 5 ++ public/download.png | Bin 0 -> 31981 bytes src/components.d.ts | 1 + src/components/LoginForm.vue | 4 ++ src/components/TheNavigation.vue | 1 + src/components/ui/WLoading.vue | 16 +++++ src/service/api/auth.ts | 2 +- src/service/api/score.ts | 9 ++- src/service/request/index.ts | 107 +++++++++++++++++-------------- src/utils/loading.ts | 37 +++++++++++ 13 files changed, 147 insertions(+), 52 deletions(-) create mode 100644 public/download.png create mode 100644 src/components/ui/WLoading.vue create mode 100644 src/utils/loading.ts diff --git a/project_codebase.md b/project_codebase.md index c7298dd..c7049d8 100644 --- a/project_codebase.md +++ b/project_codebase.md @@ -8,6 +8,11 @@ - **Implementation**: Programmatically mounts `WMessage.vue`. - **New Feature**: Supports `position` argument: `'top-center' | 'top-left' | 'top-right' | 'bottom-center' | 'bottom-left' | 'bottom-right'`. +### `src/utils/loading.ts` +- **Purpose**: Provides a global interface for showing a full-screen blocking loading overlay. +- **Methods**: `show()`, `hide()`. +- **Implementation**: Programmatically mounts `WLoading.vue` with a reference counter to handle concurrent requests. + ### `src/service/request/index.ts` - **Purpose**: Encapsulates Axios for API requests. - **Features**: @@ -15,7 +20,9 @@ - Adds `Authorization: Bearer `. - Adds `X-App-Sign` and `X-App-Timestamp` headers. - Starts `NProgress`. + - **New**: Shows blocking loading overlay if `config.showLoading` is true. - Response Interceptor: Unwraps `data`; handles global errors (401, 403, 500); prioritizes backend error messages; stops `NProgress`. + - **New**: Hides blocking loading overlay if `config.showLoading` is true. - Config: `showLoading`, `showError`. ### `src/service/api/auth.ts` diff --git a/project_doing.md b/project_doing.md index d2a9d6d..d1f9479 100644 --- a/project_doing.md +++ b/project_doing.md @@ -41,3 +41,11 @@ - **Scope**: - `src/utils/message.ts` (Update logic to support multiple containers) - `src/components/ui/WMessage.vue` (No changes needed, styles handled by container) + +### [Task 4] Implement Fullscreen Loading +- **Time**: 2025-12-18 +- **Goal**: Add blocking loading overlay controlled by request config. +- **Scope**: + - `src/components/ui/WLoading.vue` (Create) + - `src/utils/loading.ts` (Create) + - `src/service/request/index.ts` (Update interceptors) diff --git a/project_index.md b/project_index.md index 62022df..fc288d1 100644 --- a/project_index.md +++ b/project_index.md @@ -1,11 +1,13 @@ # Project File Index - `src/utils/message.ts`: Global message utility. +- `src/utils/loading.ts`: Global loading utility (blocking overlay). - `src/service/request/index.ts`: Axios wrapper. - `src/service/api/auth.ts`: Authentication API. - `src/service/api/score.ts`: Score API. - `src/stores/user.ts`: User Pinia store. - `src/stores/score.ts`: Score Pinia store. - `src/components/ui/WMessage.vue`: Message component UI. +- `src/components/ui/WLoading.vue`: Full-screen loading component UI. - `src/components/TheNavigation.vue`: Main navigation component. - `src/components/ScoreForm.vue`: Score editing form. diff --git a/project_task.md b/project_task.md index 537635c..369ae9e 100644 --- a/project_task.md +++ b/project_task.md @@ -25,3 +25,8 @@ - [x] [Task 3] Enhance WMessage Component - [x] Add position support to `src/utils/message.ts` (top/bottom/left/right/center) + +- [x] [Task 4] Implement Fullscreen Loading + - [x] Create `src/components/ui/WLoading.vue` + - [x] Create `src/utils/loading.ts` logic + - [x] Update `src/service/request/index.ts` to handle `showLoading` parameter diff --git a/public/download.png b/public/download.png new file mode 100644 index 0000000000000000000000000000000000000000..8344fb690498f534c95b4880729bc53007bf7b34 GIT binary patch literal 31981 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 F{vW}gCz1dF literal 0 HcmV?d00001 diff --git a/src/components.d.ts b/src/components.d.ts index e2ea1d0..b8911dd 100644 --- a/src/components.d.ts +++ b/src/components.d.ts @@ -20,6 +20,7 @@ declare module 'vue' { TheFooter: typeof import('./components/TheFooter.vue')['default'] TheInput: typeof import('./components/TheInput.vue')['default'] TheNavigation: typeof import('./components/TheNavigation.vue')['default'] + WLoading: typeof import('./components/ui/WLoading.vue')['default'] WMessage: typeof import('./components/ui/WMessage.vue')['default'] WOption: typeof import('./components/ui/WOption.vue')['default'] WPopconfirm: typeof import('./components/ui/WPopconfirm.vue')['default'] diff --git a/src/components/LoginForm.vue b/src/components/LoginForm.vue index 682b27e..8a60f0c 100644 --- a/src/components/LoginForm.vue +++ b/src/components/LoginForm.vue @@ -1,10 +1,12 @@ + + diff --git a/src/service/api/auth.ts b/src/service/api/auth.ts index 40685d3..553be47 100644 --- a/src/service/api/auth.ts +++ b/src/service/api/auth.ts @@ -12,7 +12,7 @@ export interface LoginResult { } export function login(params: LoginParams) { - return request.post('/user/auth/login', params) + return request.post('/user/auth/login', params, { showLoading: true }) } export function logout() { diff --git a/src/service/api/score.ts b/src/service/api/score.ts index 32b4501..bb035f0 100644 --- a/src/service/api/score.ts +++ b/src/service/api/score.ts @@ -31,9 +31,14 @@ export interface ScoreInfo { } export function saveScore(data: SaveScoreRequest) { - return request.post('/user/score/save-score', data) + return request.post('/user/score/save-score', data, { + showLoading: true + }) } export function getScore() { - return request.get('/user/score', {params: { educationalLevel: '' }, showLoading: false}) + return request.get('/user/score', { + params: { educationalLevel: '' }, + showLoading: false, + }) } diff --git a/src/service/request/index.ts b/src/service/request/index.ts index ae87a4b..ba7a26f 100644 --- a/src/service/request/index.ts +++ b/src/service/request/index.ts @@ -3,6 +3,7 @@ import NProgress from 'nprogress' import CryptoJS from 'crypto-js' import { useUserStore } from '~/stores/user' import message from '~/utils/message' +import loading from '~/utils/loading' import { InternalAxiosRequestConfig } from 'axios' interface CustomRequestConfig extends InternalAxiosRequestConfig { @@ -10,6 +11,11 @@ interface CustomRequestConfig extends InternalAxiosRequestConfig { showError?: boolean } +interface RequestConfig extends AxiosRequestConfig { + showLoading?: boolean, + showError?: boolean +} + interface ApiResponse { code: number message: string @@ -24,37 +30,45 @@ class Request { // Request interceptor this.instance.interceptors.request.use( - (config: InternalAxiosRequestConfig & { showLoading?: boolean }) => { - const customConfig = config as CustomRequestConfig - - if (customConfig.showLoading !== false) { - NProgress.start() - } - const userStore = useUserStore() - customConfig.headers = customConfig.headers || {} - - if (userStore.token) { - customConfig.headers['Authorization'] = 'Bearer ' + userStore.token - } - // Add Signature - const timestamp = Date.now().toString() - const secret = import.meta.env.VITE_API_SECRET || '' - const sign = CryptoJS.MD5(timestamp + secret).toString() - customConfig.headers['X-App-Sign'] = sign - customConfig.headers['X-App-Timestamp'] = timestamp - return customConfig - }, - (error) => { - return Promise.reject(error) - } -) + (config: InternalAxiosRequestConfig & { showLoading?: boolean }) => { + const customConfig = config as CustomRequestConfig + + // NProgress runs by default for visual feedback + NProgress.start() + + // Full screen blocking loading controlled by showLoading parameter + if (customConfig.showLoading) { + loading.show() + } + + const userStore = useUserStore() + customConfig.headers = customConfig.headers || {} + + if (userStore.token) { + customConfig.headers['Authorization'] = 'Bearer ' + userStore.token + } + // Add Signature + const timestamp = Date.now().toString() + const secret = import.meta.env.VITE_API_SECRET || '' + const sign = CryptoJS.MD5(timestamp + secret).toString() + customConfig.headers['X-App-Sign'] = sign + customConfig.headers['X-App-Timestamp'] = timestamp + return customConfig + }, + (error) => { + return Promise.reject(error) + } + ) // Response interceptor this.instance.interceptors.response.use( (response: AxiosResponse) => { const config = response.config as CustomRequestConfig - if (config.showLoading !== false) { - NProgress.done() + + NProgress.done() + + if (config.showLoading) { + loading.hide() } const res = response.data @@ -69,8 +83,11 @@ class Request { }, (error) => { const config = error.config as CustomRequestConfig - if (config?.showLoading !== false) { - NProgress.done() + + NProgress.done() + + if (config?.showLoading) { + loading.hide() } let msg = 'Network Error' @@ -101,43 +118,35 @@ class Request { msg = backendMsg || 'Internal Server Error' break default: - msg = backendMsg || error.message + msg = backendMsg || `Error: ${error.response.status}` } + } else if (error.request) { + msg = 'No response from server' } - // if (config?.showError !== false) { - // message.error(msg) - // } - // 返回包含具体错误信息的 Error 对象 - return Promise.reject(new Error(msg)) + if (config?.showError !== false) { + message.error(msg) + } + + return Promise.reject(error) } ) } - request(config: CustomRequestConfig): Promise { + request(config: RequestConfig): Promise { return this.instance.request(config) } - get(url: string, params?: any, config?: CustomRequestConfig): Promise { - return this.instance.get(url, { ...config, params }) + get(url: string, config?: RequestConfig): Promise { + return this.instance.get(url, config) } - post(url: string, data?: any, config?: CustomRequestConfig): Promise { + post(url: string, data?: any, config?: RequestConfig): Promise { return this.instance.post(url, data, config) } - - put(url: string, data?: any, config?: CustomRequestConfig): Promise { - return this.instance.put(url, data, config) - } - - delete(url: string, config?: CustomRequestConfig): Promise { - return this.instance.delete(url, config) - } } -const request = new Request({ +export default new Request({ baseURL: import.meta.env.VITE_API_BASE_URL, timeout: 10000, }) - -export default request diff --git a/src/utils/loading.ts b/src/utils/loading.ts new file mode 100644 index 0000000..ba327b0 --- /dev/null +++ b/src/utils/loading.ts @@ -0,0 +1,37 @@ +import { createVNode, render } from 'vue' +import WLoading from '~/components/ui/WLoading.vue' + +let loadingCount = 0 +let container: HTMLElement | null = null + +const startLoading = () => { + if (loadingCount === 0) { + container = document.createElement('div') + document.body.appendChild(container) + const vnode = createVNode(WLoading) + render(vnode, container) + } + loadingCount++ +} + +const endLoading = () => { + if (loadingCount <= 0) return + loadingCount-- + if (loadingCount === 0 && container) { + // Add a small delay to prevent flickering if another request starts immediately + setTimeout(() => { + if (loadingCount === 0 && container) { + render(null, container) + container.remove() + container = null + } + }, 100) + } +} + +export const loading = { + show: startLoading, + hide: endLoading, +} + +export default loading