/** * 前端 API 工具类 * 用于在渲染进程(Vue)中直接调用后端 API */ // API 基础 URL(从环境变量或配置中获取) /** * HTTP 请求工具 */ class ApiClient { constructor() { this.token = this.getToken(); // 缓存 electronAPI 可用状态,避免每次检查 this._electronAPIChecked = false; this._electronAPIAvailable = false; } /** * 检查 window.electronAPI 是否可用(快速检查,不等待) * @param {boolean} forceCheck 强制重新检查(默认 false) * @returns {boolean} 是否可用 */ isElectronAPIAvailable(forceCheck = false) { // 如果已经检查过且不需要强制检查,直接返回缓存结果 if (this._electronAPIChecked && !forceCheck) { return this._electronAPIAvailable; } // 快速检查 const available = !!(window.electronAPI && window.electronAPI.http); // 缓存结果 this._electronAPIChecked = true; this._electronAPIAvailable = available; return available; } /** * 等待 window.electronAPI 可用(仅在首次检查时等待) * @param {number} maxWaitTime 最大等待时间(毫秒),默认500ms(减少等待时间) * @param {number} checkInterval 检查间隔(毫秒),默认50ms(加快检查频率) * @returns {Promise} 是否可用 */ async waitForElectronAPI(maxWaitTime = 500, checkInterval = 50) { // 如果已经确认可用,直接返回 if (this.isElectronAPIAvailable()) { return true; } // 如果已经检查过但不可用,直接返回 false(不重复等待) if (this._electronAPIChecked && !this._electronAPIAvailable) { return false; } // 首次检查时,短暂等待(preload 脚本是同步的,通常立即可用) const startTime = Date.now(); return new Promise((resolve) => { const check = () => { if (window.electronAPI && window.electronAPI.http) { this._electronAPIChecked = true; this._electronAPIAvailable = true; resolve(true); return; } const elapsed = Date.now() - startTime; if (elapsed >= maxWaitTime) { // 超时后标记为不可用 this._electronAPIChecked = true; this._electronAPIAvailable = false; resolve(false); return; } setTimeout(check, checkInterval); }; check(); }); } /** * 使用 electronAPI.http 发送请求 * @param {string} method HTTP 方法 * @param {string} endpoint API 端点 * @param {Object} data 请求数据 * @returns {Promise} */ /** * 将响应式对象(Proxy)转换为普通对象,以便通过 IPC 传递 * @param {any} obj 要转换的对象 * @returns {any} 普通对象 */ toPlainObject(obj) { if (obj === null || obj === undefined) { return obj; } // 如果是基本类型,直接返回 if (typeof obj !== 'object') { return obj; } // 如果是 Date 对象,转换为 ISO 字符串 if (obj instanceof Date) { return obj.toISOString(); } // 如果是 RegExp 对象,转换为字符串 if (obj instanceof RegExp) { return obj.toString(); } // 尝试使用 JSON 序列化/反序列化(最可靠的方法) try { return JSON.parse(JSON.stringify(obj)); } catch (e) { // 如果 JSON 序列化失败,使用递归方法 console.warn('[API] JSON 序列化失败,使用递归方法:', e); // 如果是数组,递归处理每个元素 if (Array.isArray(obj)) { return obj.map(item => this.toPlainObject(item)); } // 如果是对象,递归处理每个属性 const plainObj = {}; for (const key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { try { plainObj[key] = this.toPlainObject(obj[key]); } catch (err) { // 如果无法访问属性,跳过 console.warn(`[API] 无法访问属性 ${key}:`, err); } } } return plainObj; } } async requestWithElectronAPI(method, endpoint, data = null) { const fullEndpoint = endpoint.startsWith('/') ? endpoint : `/${endpoint}`; // 将响应式对象转换为普通对象(避免 Proxy 无法序列化的问题) const plainData = data ? this.toPlainObject(data) : null; // 主进程不再缓存 token,每次请求都从浏览器端实时获取,这里不需要同步 try { let result; if (method === 'GET') { result = await window.electronAPI.http.get(fullEndpoint, plainData || {}); } else { result = await window.electronAPI.http.post(fullEndpoint, plainData || {}); } // 输出详细的响应日志 console.log('requestWithElectronAPI result', method, fullEndpoint ,plainData, result); return result; } catch (error) { // 输出详细的错误日志 console.error('========================================'); console.error(`[API错误] ${method}`); console.error(`[错误信息]`, error.message || error); console.error(`[错误堆栈]`, error.stack); console.error('========================================'); throw error; } } /** * 设置认证 token * @param {string} token */ setToken(token) { this.token = token; if (token) { localStorage.setItem('api_token', token); // 主进程不再缓存 token,每次请求都从浏览器端实时获取 } else { localStorage.removeItem('api_token'); } } /** * 获取认证 token * @returns {string|null} */ getToken() { if (!this.token) { this.token = localStorage.getItem('api_token'); } return this.token; } /** * 构建请求头 * @returns {Object} */ buildHeaders() { const headers = { 'Content-Type': 'application/json' }; const token = this.getToken(); if (token) { headers['applet-token'] = `${token}`; } return headers; } /** * 处理错误 * @param {Error|Response} error * @returns {Promise} */ async handleError(error) { if (error.response) { // 服务器返回了错误响应 const { status, data } = error.response; return { code: status, message: data?.message || `请求失败: ${status}`, data: null }; } else if (error.request) { // 请求已发出但没有收到响应 return { code: -1, message: '网络错误,请检查网络连接', data: null }; } else { // 其他错误 return { code: -1, message: error.message || '未知错误', data: null }; } } /** * 显示错误提示 * @param {string} message 错误消息 */ showError(message) { // 优先使用 electronAPI 的日志功能 if (window.electronAPI && typeof window.electronAPI.log === 'function') { try { window.electronAPI.log('error', `[API错误] ${message}`); } catch (error) { console.error('调用 electronAPI.log 失败:', error); } } console.error('[API错误]', message); } /** * 确保 electronAPI 可用并发送请求 * @param {string} method HTTP 方法 * @param {string} endpoint API 端点 * @param {Object} data 请求数据 * @returns {Promise} */ async _ensureElectronAPIAndRequest(method, endpoint, data = null) { // 检查 electronAPI 是否可用 if (!this.isElectronAPIAvailable()) { // 首次检查时,短暂等待一次 const electronAPIAvailable = await this.waitForElectronAPI(); if (!electronAPIAvailable) { const errorMsg = 'electronAPI 不可用,无法发送请求'; this.showError(errorMsg); throw new Error(errorMsg); } } // 发送请求 const result = await this.requestWithElectronAPI(method, endpoint, data); // 检查返回结果,如果 code 不为 0,显示错误提示 if (result && result.code !== undefined && result.code !== 0) { const errorMsg = result.message || '请求失败'; this.showError(errorMsg); } return result; } /** * 发送 HTTP 请求 * @param {string} method HTTP 方法 * @param {string} endpoint API 端点 * @param {Object} data 请求数据 * @returns {Promise} */ async request(method, endpoint, data = null) { try { const res = await this._ensureElectronAPIAndRequest(method, endpoint, data); return res } catch (error) { const errorMsg = error.message || '请求失败'; this.showError(errorMsg); throw error; } } /** * GET 请求 * @param {string} endpoint API 端点 * @param {Object} params 查询参数 * @returns {Promise} */ async get(endpoint, params = {}) { try { return await this._ensureElectronAPIAndRequest('GET', endpoint, params); } catch (error) { const errorMsg = error.message || '请求失败'; this.showError(errorMsg); throw error; } } /** * POST 请求 * @param {string} endpoint API 端点 * @param {Object} data 请求数据 * @returns {Promise} */ async post(endpoint, data = {}) { try { const result = await this.request('POST', endpoint, data); return result; } catch (error) { console.error(`[ApiClient] POST 请求失败:`, { error: error.message, endpoint, data }); const errorResult = await this.handleError(error); // 如果 handleError 返回了错误结果,显示错误提示 if (errorResult && errorResult.code !== 0) { this.showError(errorResult.message || '请求失败'); } return errorResult; } } } // 创建默认实例 const apiClient = new ApiClient(); /** * 用户登录 * @param {string} email 邮箱 * @param {string} password 密码 * @param {string} deviceId 设备ID(可选) * @returns {Promise} */ export async function login(email, password, deviceId = null) { const requestData = { login_name: email, // 后端使用 login_name 字段 password }; if (deviceId) { requestData.device_id = deviceId; } const response = await apiClient.post('/user/login', requestData); // 如果登录成功,保存 token if (response.code === 0 && response.data && response.data.token) { apiClient.setToken(response.data.token); } return response; } /** * 设置 API token(用于其他地方设置的 token) * @param {string} token */ export function setToken(token) { apiClient.setToken(token); } /** * 获取 API token * @returns {string|null} */ export function getToken() { return apiClient.getToken(); } /** * 清除 API token */ export function clearToken() { apiClient.setToken(null); } // 导出默认实例,方便直接使用 export default apiClient;