AssetManager.UniApp/utils/api.js
claw_bot 99094eeed8 feat(P4): 首页添加骨架屏,加载时显示占位动画
- 资产卡片区域:深色骨架屏匹配原有卡片风格
- 持仓列表区域:模拟卡片布局的骨架占位
- 添加 loading 状态控制,数据加载完成后切换
- 骨架屏带渐变动画效果,提升用户体验
2026-03-13 03:02:33 +00:00

469 lines
13 KiB
JavaScript
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* API工具类 - 统一封装后端API请求
*/
console.log('📦 api.js 模块加载成功')
// API基础URL
const BASE_URL = import.meta.env.VITE_API_BASE_URL || 'https://localhost:7040/';
// 请求超时时间
const TIMEOUT = 10000;
// 登录锁,防止并发登录
let loginLock = null;
// 全局loading计数避免多个请求重复显示/隐藏loading
let loadingCount = 0;
const showLoading = () => {
if (loadingCount === 0) {
uni.$u.toast.loading('加载中...');
}
loadingCount++;
};
const hideLoading = () => {
loadingCount--;
if (loadingCount <= 0) {
loadingCount = 0;
uni.$u.toast.hide();
}
};
/**
* 获取微信登录码
* @returns {Promise<string>} - 返回微信登录码
*/
const getWechatCode = () => {
return new Promise((resolve, reject) => {
uni.login({
provider: 'weixin',
success: (res) => {
if (res.code) {
console.log('📱 微信登录码获取成功:', res.code);
resolve(res.code);
} else {
console.error('📱 微信登录码获取失败:', res.errMsg);
reject(new Error(`微信登录码获取失败: ${res.errMsg}`));
}
},
fail: (err) => {
console.error('📱 微信登录失败:', err);
reject(new Error(`微信登录失败: ${err.errMsg}`));
}
});
});
};
/**
* 执行微信登录
* @returns {Promise<object>} - 返回登录结果
*/
const doWechatLogin = async () => {
try {
console.log('🔐 开始执行微信登录');
const code = await getWechatCode();
console.log('🔐 开始调用后端登录接口:', code);
const res = await new Promise((resolve, reject) => {
uni.request({
url: `${BASE_URL}api/auth/wechat-login`,
method: 'POST',
data: { code },
header: {
'Content-Type': 'application/json'
},
timeout: TIMEOUT,
success: resolve,
fail: reject
});
});
console.log('🔐 后端登录接口响应:', res);
if (res.statusCode === 200 && res.data?.code === 200 && res.data?.data?.token) {
console.log('✅ 微信登录成功获取到token');
uni.setStorageSync('token', res.data.data.token);
return res.data;
} else {
console.error('❌ 微信登录失败:', res.data?.message || '登录失败');
throw new Error(res.data?.message || '微信登录失败');
}
} catch (error) {
console.error('❌ 微信登录异常:', error);
throw error;
}
};
/**
* 带重试机制的请求方法
* @param {string} url - 请求URL
* @param {string} method - 请求方法
* @param {object} data - 请求数据
* @param {object} headers - 请求头
* @param {number} retryCount - 重试次数
* @returns {Promise} - 返回Promise对象
*/
const requestWithRetry = async (url, method = 'GET', data = {}, headers = {}, retryCount = 0) => {
try {
// 获取存储的token
let token = uni.getStorageSync('token');
// 如果没有token先执行微信登录使用登录锁防止并发
if (!token) {
if (loginLock) {
console.log('🔒 等待其他请求完成登录...');
await loginLock;
token = uni.getStorageSync('token');
} else {
console.log('🔒 未检测到token开始微信登录...');
loginLock = doWechatLogin();
await loginLock;
loginLock = null;
token = uni.getStorageSync('token');
console.log('🔒 微信登录成功获取到token');
}
}
// 正确处理URL拼接避免双斜杠
let fullUrl;
if (BASE_URL.endsWith('/') && url.startsWith('/')) {
fullUrl = BASE_URL + url.substring(1);
} else if (!BASE_URL.endsWith('/') && !url.startsWith('/')) {
fullUrl = BASE_URL + '/' + url;
} else {
fullUrl = BASE_URL + url;
}
// 请求开始日志
console.log('🚀 API 请求开始:', {
method,
url: fullUrl,
data: method === 'GET' ? null : data,
hasToken: !!token,
retryCount,
timestamp: new Date().toISOString()
});
// 显示全局loading
showLoading();
const res = await new Promise((resolve, reject) => {
uni.request({
url: fullUrl,
method,
data,
header: {
'Content-Type': 'application/json',
'Authorization': token ? `Bearer ${token}` : '',
...headers
},
timeout: TIMEOUT,
success: resolve,
fail: reject
});
});
// 响应日志
console.log('✅ API 请求成功:', {
method,
url: fullUrl,
statusCode: res.statusCode,
responseTime: new Date().toISOString(),
data: res.data
});
// 隐藏loading
hideLoading();
if (res.statusCode === 200) {
return res.data;
} else if (res.statusCode === 401) {
// 未授权清除token并重新登录使用登录锁防止并发
console.log('🔒 登录已过期,开始重新登录...');
uni.removeStorageSync('token');
// 重新登录后重试请求
if (retryCount < 3) {
if (loginLock) {
console.log('🔒 等待其他请求完成登录...');
await loginLock;
} else {
loginLock = doWechatLogin();
await loginLock;
loginLock = null;
}
console.log('🔒 重新登录成功,开始重试请求...');
return await requestWithRetry(url, method, data, headers, retryCount + 1);
} else {
console.error('❌ 达到最大重试次数');
uni.$u.toast.error('系统异常,请稍后重试');
throw new Error('登录已过期,重试次数超限');
}
} else {
console.log('❌ API 请求失败:', {
statusCode: res.statusCode,
message: res.data?.message || `请求失败: ${res.statusCode}`
});
throw new Error(`请求失败: ${res.statusCode}`);
}
} catch (error) {
// 隐藏loading
hideLoading();
// 请求失败日志
console.log('❌ API 请求失败:', {
url,
error,
retryCount,
timestamp: new Date().toISOString()
});
// 如果是网络错误且未达到最大重试次数,尝试重试
if (retryCount < 3 && error.message && error.message.includes('网络请求失败')) {
console.log('🔄 网络错误,开始重试...');
return await requestWithRetry(url, method, data, headers, retryCount + 1);
}
// 达到最大重试次数,提示用户
if (retryCount >= 2) {
uni.showToast({ title: '系统异常,请稍后重试', icon: 'none', duration: 2000 });
}
throw error;
}
};
/**
* 基础请求方法
* @param {string} url - 请求URL
* @param {string} method - 请求方法
* @param {object} data - 请求数据
* @param {object} headers - 请求头
* @returns {Promise} - 返回Promise对象
*/
const request = (url, method = 'GET', data = {}, headers = {}) => {
return requestWithRetry(url, method, data, headers);
};
/**
* GET请求
* @param {string} url - 请求URL
* @param {object} params - 请求参数
* @param {object} headers - 请求头
* @returns {Promise} - 返回Promise对象
*/
export const get = (url, params = {}, headers = {}) => {
const queryString = Object.keys(params)
.map(key => `${key}=${encodeURIComponent(params[key])}`)
.join('&');
const fullUrl = queryString ? `${url}?${queryString}` : url;
return request(fullUrl, 'GET', {}, headers);
};
/**
* POST请求
* @param {string} url - 请求URL
* @param {object} data - 请求数据
* @param {object} headers - 请求头
* @returns {Promise} - 返回Promise对象
*/
export const post = (url, data = {}, headers = {}) => {
return request(url, 'POST', data, headers);
};
/**
* PUT请求
* @param {string} url - 请求URL
* @param {object} data - 请求数据
* @param {object} headers - 请求头
* @returns {Promise} - 返回Promise对象
*/
export const put = (url, data = {}, headers = {}) => {
return request(url, 'PUT', data, headers);
};
/**
* DELETE请求
* @param {string} url - 请求URL
* @param {object} headers - 请求头
* @returns {Promise} - 返回Promise对象
*/
export const del = (url, headers = {}) => {
return request(url, 'DELETE', {}, headers);
};
/**
* API接口封装
*/
console.log('📦 api.js 开始导出api对象')
export const api = {
/**
* 资产相关接口
*/
assets: {
/**
* 获取资产数据
* @returns {Promise} 返回资产数据
*/
getAssetData: () => {
console.log('📤 发起 getAssetData 请求');
return get('/api/v1/portfolio/assets');
},
/**
* 获取持仓信息
* @returns {Promise} 返回持仓信息
*/
getHoldings: () => {
console.log('📤 发起 getHoldings 请求');
return get('/api/v1/portfolio');
},
/**
* 获取投资组合详情
* @param {string|number} id - 投资组合ID
* @returns {Promise} 返回投资组合详情
*/
getPortfolio: (id) => {
console.log('📤 发起 getPortfolio 请求:', id);
return get(`/api/v1/portfolio/${id}`);
},
/**
* 获取交易记录
* @param {object} params - 查询参数
* @returns {Promise} 返回交易记录列表
*/
getTransactions: (params) => {
console.log('📤 发起 getTransactions 请求:', params);
return get('/api/v1/portfolio/transactions', params);
},
/**
* 创建交易记录
* @param {object} data - 交易数据
* @returns {Promise} 返回创建结果
*/
createTransaction: (data) => {
console.log('📤 发起 createTransaction 请求:', data);
return post('/api/v1/portfolio/transactions', data);
},
/**
* 创建投资组合
* @param {object} data - 投资组合数据
* @returns {Promise} 返回创建结果
*/
createPortfolio: (data) => {
console.log('📤 发起 createPortfolio 请求:', data);
return post('/api/v1/portfolio', data);
},
/**
* 获取投资组合策略信号
* @param {string|number} id - 投资组合ID
* @returns {Promise} 返回策略信号
*/
getPortfolioSignal: (id) => {
console.log('📤 发起 getPortfolioSignal 请求:', id);
return get(`/api/v1/portfolio/${id}/signal`);
}
},
/**
* 策略相关接口
*/
strategies: {
/**
* 获取策略列表
* @returns {Promise} 返回策略列表
*/
getStrategies: () => {
console.log('📤 发起 getStrategies 请求');
return get('/api/v1/strategy/strategies');
},
/**
* 获取策略详情
* @param {string|number} id - 策略ID
* @returns {Promise} 返回策略详情
*/
getStrategy: (id) => {
console.log('📤 发起 getStrategy 请求:', id);
return get(`/api/v1/strategy/${id}`);
},
/**
* 创建策略
* @param {object} data - 策略数据
* @returns {Promise} 返回创建结果
*/
createStrategy: (data) => {
console.log('📤 发起 createStrategy 请求:', data);
return post('/api/v1/strategy', data);
},
/**
* 更新策略
* @param {string|number} id - 策略ID
* @param {object} data - 更新数据
* @returns {Promise} 返回更新结果
*/
updateStrategy: (id, data) => {
console.log('📤 发起 updateStrategy 请求:', id, data);
return put(`/api/v1/strategy/${id}`, data);
},
/**
* 删除策略
* @param {string|number} id - 策略ID
* @returns {Promise} 返回删除结果
*/
deleteStrategy: (id) => {
console.log('📤 发起 deleteStrategy 请求:', id);
return del(`/api/v1/strategy/${id}`);
}
},
/**
* 用户相关接口
*/
user: {
/**
* 获取用户信息
* @returns {Promise} 返回用户信息
*/
getUserInfo: () => {
console.log('📤 发起 getUserInfo 请求');
return get('/api/v1/user/info');
},
/**
* 获取用户统计数据
* @returns {Promise} 返回用户统计数据
*/
getUserStats: () => {
console.log('📤 发起 getUserStats 请求');
return get('/api/v1/user/stats');
},
/**
* 更新用户信息
* @param {object} data - 更新数据
* @returns {Promise} 返回更新结果
*/
updateUserInfo: (data) => {
console.log('📤 发起 updateUserInfo 请求:', data);
return put('/api/v1/user/info', data);
}
},
/**
* 股票代码相关接口
*/
ticker: {
/**
* 模糊搜索股票代码
* @param {string} keyword - 搜索关键词
* @param {number} limit - 返回数量上限
* @returns {Promise} 返回搜索结果
*/
search: (keyword, limit = 20) => {
console.log('📤 发起 ticker.search 请求:', { keyword, limit });
return get('/api/v1/ticker/search', { keyword, limit });
}
}
};
console.log('📦 api.js 导出默认api对象')
export default api;