修复部分显示问题

This commit is contained in:
yoyuzh
2026-03-26 14:29:30 +08:00
parent b2d9db7be9
commit 448e2a6886
14 changed files with 630 additions and 163 deletions

View File

@@ -16,6 +16,7 @@ interface ApiUploadRequestInit {
headers?: HeadersInit;
method?: 'POST' | 'PUT' | 'PATCH';
onProgress?: (progress: {loaded: number; total: number}) => void;
signal?: AbortSignal;
}
interface ApiBinaryUploadRequestInit {
@@ -23,6 +24,7 @@ interface ApiBinaryUploadRequestInit {
headers?: HeadersInit;
method?: 'PUT' | 'POST';
onProgress?: (progress: {loaded: number; total: number}) => void;
signal?: AbortSignal;
}
const API_BASE_URL = (import.meta.env?.VITE_API_BASE_URL || '/api').replace(/\/$/, '');
@@ -197,6 +199,10 @@ export function toNetworkApiError(error: unknown) {
return new ApiError(message === 'Failed to fetch' ? fallbackMessage : message, 0);
}
function toUploadAbortApiError() {
return new ApiError('上传已取消', 0);
}
export function shouldRetryRequest(
path: string,
init: ApiRequestInit = {},
@@ -296,6 +302,46 @@ function apiUploadRequestInternal<T>(path: string, init: ApiUploadRequestInit, a
return new Promise<T>((resolve, reject) => {
const xhr = new XMLHttpRequest();
let settled = false;
const detachAbortSignal = () => {
init.signal?.removeEventListener('abort', handleAbortSignal);
};
const resolveOnce = (value: T | PromiseLike<T>) => {
if (settled) {
return;
}
settled = true;
detachAbortSignal();
resolve(value);
};
const rejectOnce = (error: unknown) => {
if (settled) {
return;
}
settled = true;
detachAbortSignal();
reject(error);
};
const handleAbortSignal = () => {
if (settled) {
return;
}
xhr.abort();
rejectOnce(toUploadAbortApiError());
};
if (init.signal?.aborted) {
rejectOnce(toUploadAbortApiError());
return;
}
xhr.open(init.method || 'POST', resolveUrl(path));
headers.forEach((value, key) => {
@@ -316,7 +362,16 @@ function apiUploadRequestInternal<T>(path: string, init: ApiUploadRequestInit, a
}
xhr.onerror = () => {
reject(toNetworkApiError(new TypeError('Failed to fetch')));
if (init.signal?.aborted) {
rejectOnce(toUploadAbortApiError());
return;
}
rejectOnce(toNetworkApiError(new TypeError('Failed to fetch')));
};
xhr.onabort = () => {
rejectOnce(toUploadAbortApiError());
};
xhr.onload = () => {
@@ -326,26 +381,26 @@ function apiUploadRequestInternal<T>(path: string, init: ApiUploadRequestInit, a
refreshAccessToken()
.then((refreshed) => {
if (refreshed) {
resolve(apiUploadRequestInternal<T>(path, init, false));
resolveOnce(apiUploadRequestInternal<T>(path, init, false));
return;
}
clearStoredSession();
reject(new ApiError('登录状态已失效,请重新登录', 401));
rejectOnce(new ApiError('登录状态已失效,请重新登录', 401));
})
.catch((error) => {
clearStoredSession();
reject(error instanceof ApiError ? error : toNetworkApiError(error));
rejectOnce(error instanceof ApiError ? error : toNetworkApiError(error));
});
return;
}
if (!contentType.includes('application/json')) {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(undefined as T);
resolveOnce(undefined as T);
return;
}
reject(new ApiError(`请求失败 (${xhr.status})`, xhr.status));
rejectOnce(new ApiError(`请求失败 (${xhr.status})`, xhr.status));
return;
}
@@ -354,13 +409,17 @@ function apiUploadRequestInternal<T>(path: string, init: ApiUploadRequestInit, a
if (xhr.status === 401) {
clearStoredSession();
}
reject(new ApiError(payload.msg || `请求失败 (${xhr.status})`, xhr.status, payload.code));
rejectOnce(new ApiError(payload.msg || `请求失败 (${xhr.status})`, xhr.status, payload.code));
return;
}
resolve(payload.data);
resolveOnce(payload.data);
};
if (init.signal) {
init.signal.addEventListener('abort', handleAbortSignal, {once: true});
}
xhr.send(init.body);
});
}
@@ -374,6 +433,46 @@ export function apiBinaryUploadRequest(path: string, init: ApiBinaryUploadReques
return new Promise<void>((resolve, reject) => {
const xhr = new XMLHttpRequest();
let settled = false;
const detachAbortSignal = () => {
init.signal?.removeEventListener('abort', handleAbortSignal);
};
const resolveOnce = () => {
if (settled) {
return;
}
settled = true;
detachAbortSignal();
resolve();
};
const rejectOnce = (error: unknown) => {
if (settled) {
return;
}
settled = true;
detachAbortSignal();
reject(error);
};
const handleAbortSignal = () => {
if (settled) {
return;
}
xhr.abort();
rejectOnce(toUploadAbortApiError());
};
if (init.signal?.aborted) {
rejectOnce(toUploadAbortApiError());
return;
}
xhr.open(init.method || 'PUT', resolveUrl(path));
headers.forEach((value, key) => {
@@ -394,18 +493,31 @@ export function apiBinaryUploadRequest(path: string, init: ApiBinaryUploadReques
}
xhr.onerror = () => {
reject(toNetworkApiError(new TypeError('Failed to fetch')));
if (init.signal?.aborted) {
rejectOnce(toUploadAbortApiError());
return;
}
rejectOnce(toNetworkApiError(new TypeError('Failed to fetch')));
};
xhr.onabort = () => {
rejectOnce(toUploadAbortApiError());
};
xhr.onload = () => {
if (xhr.status >= 200 && xhr.status < 300) {
resolve();
resolveOnce();
return;
}
reject(new ApiError(`请求失败 (${xhr.status})`, xhr.status));
rejectOnce(new ApiError(`请求失败 (${xhr.status})`, xhr.status));
};
if (init.signal) {
init.signal.addEventListener('abort', handleAbortSignal, {once: true});
}
xhr.send(init.body);
});
}