This commit is contained in:
张成
2025-12-22 16:26:59 +08:00
parent aa2d03ee30
commit e17d5610f5
54 changed files with 11735 additions and 3 deletions

409
app/utils/api.js Normal file
View 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;