1
This commit is contained in:
409
app/utils/api.js
Normal file
409
app/utils/api.js
Normal file
@@ -0,0 +1,409 @@
|
||||
/**
|
||||
* 前端 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<boolean>} 是否可用
|
||||
*/
|
||||
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<Object>}
|
||||
*/
|
||||
/**
|
||||
* 将响应式对象(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<Object>}
|
||||
*/
|
||||
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<Object>}
|
||||
*/
|
||||
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<Object>}
|
||||
*/
|
||||
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<Object>}
|
||||
*/
|
||||
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<Object>}
|
||||
*/
|
||||
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<Object>}
|
||||
*/
|
||||
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;
|
||||
Reference in New Issue
Block a user