- 资产卡片区域:深色骨架屏匹配原有卡片风格 - 持仓列表区域:模拟卡片布局的骨架占位 - 添加 loading 状态控制,数据加载完成后切换 - 骨架屏带渐变动画效果,提升用户体验
469 lines
13 KiB
JavaScript
Executable File
469 lines
13 KiB
JavaScript
Executable File
/**
|
||
* 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;
|