fix(web): 统一 API 请求语义并修复鼠标移动发送间隔

- 新增统一 request:同时处理 HTTP 非 2xx 与 success=false,并用 i18n toast 提示错误
- api/index.ts 与 api/config.ts 统一使用同一 request,避免错误处理不一致
- "发送间隔" 仅控制鼠标移动事件频率,WebRTC/WS 行为一致,不影响点击/滚轮
This commit is contained in:
mofeng-git
2026-01-11 11:37:35 +08:00
parent 206594e292
commit 0f52168e75
8 changed files with 296 additions and 210 deletions

View File

@@ -1,97 +1,9 @@
// API client for One-KVM backend
import { toast } from 'vue-sonner'
import { request, ApiError } from './request'
const API_BASE = '/api'
// Toast debounce mechanism - prevent toast spam (5 seconds)
const toastDebounceMap = new Map<string, number>()
const TOAST_DEBOUNCE_TIME = 5000
function shouldShowToast(key: string): boolean {
const now = Date.now()
const lastToastTime = toastDebounceMap.get(key)
if (!lastToastTime || now - lastToastTime >= TOAST_DEBOUNCE_TIME) {
toastDebounceMap.set(key, now)
return true
}
return false
}
class ApiError extends Error {
status: number
constructor(status: number, message: string) {
super(message)
this.name = 'ApiError'
this.status = status
}
}
async function request<T>(
endpoint: string,
options: RequestInit = {}
): Promise<T> {
const url = `${API_BASE}${endpoint}`
try {
const response = await fetch(url, {
...options,
headers: {
'Content-Type': 'application/json',
...options.headers,
},
credentials: 'include',
})
// Parse response body (all responses are 200 OK with success field)
const data = await response.json().catch(() => ({
success: false,
message: 'Failed to parse response'
}))
// Check success field - all errors are indicated by success=false
if (data && typeof data.success === 'boolean' && !data.success) {
const errorMessage = data.message || 'Operation failed'
const apiError = new ApiError(response.status, errorMessage)
console.info(`[API] ${endpoint} failed:`, errorMessage)
// Show toast notification to user (with debounce)
if (shouldShowToast(`error_${endpoint}`)) {
toast.error('Operation Failed', {
description: errorMessage,
duration: 4000,
})
}
throw apiError
}
return data
} catch (error) {
// Network errors or JSON parsing errors
if (error instanceof ApiError) {
throw error // Already handled above
}
// Network connectivity issues
console.info(`[API] Network error for ${endpoint}:`, error)
// Show toast for network errors (with debounce)
if (shouldShowToast('network_error')) {
toast.error('Network Error', {
description: 'Unable to connect to server. Please check your connection.',
duration: 4000,
})
}
throw new ApiError(0, 'Network error')
}
}
// Auth API
export const authApi = {
login: (username: string, password: string) =>