refactor(auth): 重构用户认证逻辑并迁移至Pinia存储
将微信静默登录逻辑从App.vue迁移至Pinia的user store,实现状态集中管理 全局注册api模块并优化请求重试机制,增强错误处理和日志记录 移除App.vue中的冗余代码,简化页面组件的数据获取方式
This commit is contained in:
parent
a95cef8159
commit
dc857a98a3
39
App.vue
39
App.vue
@ -1,52 +1,13 @@
|
|||||||
<script>
|
<script>
|
||||||
import api from './utils/api'
|
|
||||||
export default {
|
export default {
|
||||||
onLaunch: function() {
|
onLaunch: function() {
|
||||||
console.log('App Launch')
|
console.log('App Launch')
|
||||||
// 执行微信静默登录
|
|
||||||
this.wechatSilentLogin()
|
|
||||||
},
|
},
|
||||||
onShow: function() {
|
onShow: function() {
|
||||||
console.log('App Show')
|
console.log('App Show')
|
||||||
},
|
},
|
||||||
onHide: function() {
|
onHide: function() {
|
||||||
console.log('App Hide')
|
console.log('App Hide')
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
// 微信静默登录
|
|
||||||
wechatSilentLogin() {
|
|
||||||
// 检查是否在微信小程序环境中
|
|
||||||
if (uni.getSystemInfoSync().platform === 'devtools' || uni.getSystemInfoSync().platform === 'mp-weixin') {
|
|
||||||
// 调用微信登录API获取code
|
|
||||||
uni.login({
|
|
||||||
provider: 'weixin',
|
|
||||||
success: (loginRes) => {
|
|
||||||
if (loginRes.code) {
|
|
||||||
// 将code发送到后端进行登录
|
|
||||||
api.auth.wechatLogin(loginRes.code)
|
|
||||||
.then(res => {
|
|
||||||
if (res.code === 200) {
|
|
||||||
// 登录成功,存储token
|
|
||||||
uni.setStorageSync('token', res.data.token)
|
|
||||||
uni.setStorageSync('userInfo', res.data.userInfo)
|
|
||||||
console.log('微信静默登录成功')
|
|
||||||
} else {
|
|
||||||
console.log('微信静默登录失败:', res.message)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
console.log('微信登录API调用失败:', err)
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
console.log('获取微信登录code失败:', loginRes.errMsg)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
fail: (err) => {
|
|
||||||
console.log('微信登录失败:', err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
9
main.js
9
main.js
@ -13,8 +13,17 @@ app.$mount()
|
|||||||
|
|
||||||
// #ifdef VUE3
|
// #ifdef VUE3
|
||||||
import { createSSRApp } from 'vue'
|
import { createSSRApp } from 'vue'
|
||||||
|
import api from './utils/api'
|
||||||
|
|
||||||
|
console.log('🚀 应用启动,导入api模块')
|
||||||
|
|
||||||
export function createApp() {
|
export function createApp() {
|
||||||
const app = createSSRApp(App)
|
const app = createSSRApp(App)
|
||||||
|
|
||||||
|
// 全局注册api
|
||||||
|
app.config.globalProperties.$api = api
|
||||||
|
console.log('✅ api已全局注册为 $api')
|
||||||
|
|
||||||
return {
|
return {
|
||||||
app
|
app
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,17 +18,17 @@
|
|||||||
|
|
||||||
<view class="card-row main-row">
|
<view class="card-row main-row">
|
||||||
<text class="currency-symbol">¥</text>
|
<text class="currency-symbol">¥</text>
|
||||||
<text class="big-number">{{ assetData.totalValue.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) }}</text>
|
<text class="big-number">{{ (assetData.totalValue || 0).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) }}</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="card-row bottom-row">
|
<view class="card-row bottom-row">
|
||||||
<view class="stat-col">
|
<view class="stat-col">
|
||||||
<text class="stat-label">今日账面变动</text>
|
<text class="stat-label">今日账面变动</text>
|
||||||
<text class="stat-value">{{ assetData.todayProfit >= 0 ? '+' : '' }}¥{{ assetData.todayProfit.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) }}</text>
|
<text class="stat-value">{{ (assetData.todayProfit || 0) >= 0 ? '+' : '' }}¥{{ (assetData.todayProfit || 0).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) }}</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="stat-col align-right">
|
<view class="stat-col align-right">
|
||||||
<text class="stat-label">历史总变动</text>
|
<text class="stat-label">历史总变动</text>
|
||||||
<text class="stat-value">{{ assetData.totalReturnRate >= 0 ? '+' : '' }}{{ assetData.totalReturnRate }}%</text>
|
<text class="stat-value">{{ (assetData.totalReturnRate || 0) >= 0 ? '+' : '' }}{{ assetData.totalReturnRate || 0 }}%</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
@ -75,11 +75,11 @@
|
|||||||
<view class="card-bottom">
|
<view class="card-bottom">
|
||||||
<view class="data-col">
|
<view class="data-col">
|
||||||
<text class="data-label">当前估值</text>
|
<text class="data-label">当前估值</text>
|
||||||
<text class="data-val">¥ {{ holding.value.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) }}</text>
|
<text class="data-val">¥ {{ (holding.value || 0).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) }}</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="data-col align-right">
|
<view class="data-col align-right">
|
||||||
<text class="data-label">历史总变动</text>
|
<text class="data-label">历史总变动</text>
|
||||||
<text class="data-val" :class="holding.returnType === 'positive' ? 'text-red' : 'text-green'">{{ holding.returnRate >= 0 ? '+' : '' }}{{ holding.returnRate }}%</text>
|
<text class="data-val" :class="holding.returnType === 'positive' ? 'text-red' : 'text-green'">{{ (holding.returnRate || 0) >= 0 ? '+' : '' }}{{ holding.returnRate || 0 }}%</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@ -107,9 +107,11 @@ const holdings = ref([]);
|
|||||||
// 从后端API获取资产数据的函数
|
// 从后端API获取资产数据的函数
|
||||||
const fetchAssetData = async () => {
|
const fetchAssetData = async () => {
|
||||||
try {
|
try {
|
||||||
|
console.log('开始获取资产数据...');
|
||||||
const response = await api.assets.getAssetData();
|
const response = await api.assets.getAssetData();
|
||||||
if (response.code === 200) {
|
if (response.code === 200) {
|
||||||
assetData.value = response.data;
|
assetData.value = response.data;
|
||||||
|
console.log('资产数据获取成功');
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取资产数据失败:', error);
|
console.error('获取资产数据失败:', error);
|
||||||
@ -119,17 +121,23 @@ const fetchAssetData = async () => {
|
|||||||
// 从后端API获取持仓组合数据的函数
|
// 从后端API获取持仓组合数据的函数
|
||||||
const fetchHoldingsData = async () => {
|
const fetchHoldingsData = async () => {
|
||||||
try {
|
try {
|
||||||
|
console.log('开始获取持仓数据...');
|
||||||
const response = await api.assets.getHoldings();
|
const response = await api.assets.getHoldings();
|
||||||
if (response.code === 200) {
|
if (response.code === 200) {
|
||||||
holdings.value = response.data;
|
// 处理响应数据结构,获取 items 数组
|
||||||
|
holdings.value = response.data.items || [];
|
||||||
|
console.log('持仓数据获取成功,items数量:', holdings.value.length);
|
||||||
|
console.log('持仓数据:', holdings.value);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取持仓数据失败:', error);
|
console.error('获取持仓数据失败:', error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 页面加载时获取数据
|
// 页面加载时检查登录状态
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
console.log('首页加载,开始加载数据...');
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
fetchAssetData(),
|
fetchAssetData(),
|
||||||
fetchHoldingsData()
|
fetchHoldingsData()
|
||||||
@ -143,10 +151,6 @@ const goConfig = () => {
|
|||||||
const goDetail = (holdingId) => {
|
const goDetail = (holdingId) => {
|
||||||
uni.navigateTo({ url: `/pages/detail/detail?id=${holdingId}` });
|
uni.navigateTo({ url: `/pages/detail/detail?id=${holdingId}` });
|
||||||
};
|
};
|
||||||
|
|
||||||
const goStrategies = () => {
|
|
||||||
uni.switchTab({ url: '/pages/strategies/strategies' });
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@ -2,21 +2,32 @@
|
|||||||
<view class="page-container">
|
<view class="page-container">
|
||||||
<view class="profile-header">
|
<view class="profile-header">
|
||||||
<view class="avatar-container">
|
<view class="avatar-container">
|
||||||
<view class="avatar-circle"></view>
|
<!-- 如果有头像URL,使用图片显示 -->
|
||||||
|
<image v-if="userInfo.avatar" :src="userInfo.avatar" class="avatar-image" mode="aspectFill"></image>
|
||||||
|
<view v-else class="avatar-circle"></view>
|
||||||
<view class="online-badge"></view>
|
<view class="online-badge"></view>
|
||||||
</view>
|
</view>
|
||||||
<text class="user-name">{{ userInfo.userName }}</text>
|
<text class="user-name">{{ userInfo.userName || '加载中...' }}</text>
|
||||||
<text class="user-info">会员等级: {{ userInfo.memberLevel }} | 连续运行 {{ userInfo.runningDays }}天</text>
|
<text class="user-info">会员等级: {{ userInfo.memberLevel || '普通会员' }} | 连续运行 {{ userInfo.runningDays || 0 }}天</text>
|
||||||
|
<text v-if="userInfo.email" class="user-email">{{ userInfo.email }}</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="stats-grid">
|
<view class="stats-grid">
|
||||||
<view class="stat-card">
|
<view class="stat-card">
|
||||||
<text class="stat-label">已记录事件</text>
|
<text class="stat-label">已记录事件</text>
|
||||||
<text class="stat-val">{{ userStats.signalsCaptured.toLocaleString() }}</text>
|
<text class="stat-val">{{ userStats.signalsCaptured?.toLocaleString() || 0 }}</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="stat-card">
|
<view class="stat-card">
|
||||||
<text class="stat-label">记录完整度</text>
|
<text class="stat-label">胜率</text>
|
||||||
<text class="stat-val">{{ userStats.winRate }}%</text>
|
<text class="stat-val">{{ userStats.winRate || 0 }}%</text>
|
||||||
|
</view>
|
||||||
|
<view class="stat-card">
|
||||||
|
<text class="stat-label">总交易数</text>
|
||||||
|
<text class="stat-val">{{ userStats.totalTrades || 0 }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="stat-card">
|
||||||
|
<text class="stat-label">平均收益</text>
|
||||||
|
<text class="stat-val">{{ userStats.averageProfit || 0 }}%</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
@ -44,17 +55,11 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="version-info">
|
|
||||||
<text class="v-text">ASSET STRATEGY ADVISOR {{ appInfo.version }}</text>
|
|
||||||
<text class="v-text">当前日期: {{ appInfo.currentDate }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted } from 'vue';
|
import { ref, onMounted, getCurrentInstance } from 'vue';
|
||||||
import { api } from '../../utils/api';
|
|
||||||
|
|
||||||
// 用户信息
|
// 用户信息
|
||||||
const userInfo = ref({
|
const userInfo = ref({
|
||||||
@ -69,18 +74,25 @@ const userStats = ref({
|
|||||||
winRate: 0
|
winRate: 0
|
||||||
});
|
});
|
||||||
|
|
||||||
// 应用信息
|
// 获取全局api对象
|
||||||
const appInfo = ref({
|
const getApi = () => {
|
||||||
version: '',
|
const instance = getCurrentInstance();
|
||||||
currentDate: ''
|
return instance?.appContext.config.globalProperties.$api;
|
||||||
});
|
};
|
||||||
|
|
||||||
// 从后端API获取用户信息的函数
|
// 从后端API获取用户信息的函数
|
||||||
const fetchUserInfo = async () => {
|
const fetchUserInfo = async () => {
|
||||||
try {
|
try {
|
||||||
|
const api = getApi();
|
||||||
|
if (!api || !api.user) {
|
||||||
|
console.error('API模块未加载');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const response = await api.user.getUserInfo();
|
const response = await api.user.getUserInfo();
|
||||||
if (response.code === 200) {
|
if (response.code === 200) {
|
||||||
userInfo.value = response.data;
|
userInfo.value = response.data;
|
||||||
|
console.log('用户信息获取成功:', response.data);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取用户信息失败:', error);
|
console.error('获取用户信息失败:', error);
|
||||||
@ -90,33 +102,31 @@ const fetchUserInfo = async () => {
|
|||||||
// 从后端API获取用户统计数据的函数
|
// 从后端API获取用户统计数据的函数
|
||||||
const fetchUserStats = async () => {
|
const fetchUserStats = async () => {
|
||||||
try {
|
try {
|
||||||
|
const api = getApi();
|
||||||
|
if (!api || !api.user) {
|
||||||
|
console.error('API模块未加载');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const response = await api.user.getUserStats();
|
const response = await api.user.getUserStats();
|
||||||
if (response.code === 200) {
|
if (response.code === 200) {
|
||||||
userStats.value = response.data;
|
userStats.value = response.data;
|
||||||
|
console.log('用户统计数据获取成功:', response.data);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取用户统计数据失败:', error);
|
console.error('获取用户统计数据失败:', error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 从后端API获取应用信息的函数
|
|
||||||
const fetchAppInfo = async () => {
|
|
||||||
try {
|
|
||||||
const response = await api.app.getAppInfo();
|
|
||||||
if (response.code === 200) {
|
|
||||||
appInfo.value = response.data;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取应用信息失败:', error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 页面加载时获取数据
|
// 页面加载时获取数据
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
console.log('个人中心页面加载,开始获取数据...');
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
fetchUserInfo(),
|
fetchUserInfo(),
|
||||||
fetchUserStats(),
|
fetchUserStats()
|
||||||
fetchAppInfo()
|
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
@ -132,9 +142,11 @@ onMounted(async () => {
|
|||||||
.profile-header { display: flex; flex-direction: column; align-items: center; margin-bottom: 60rpx; }
|
.profile-header { display: flex; flex-direction: column; align-items: center; margin-bottom: 60rpx; }
|
||||||
.avatar-container { position: relative; margin-bottom: 24rpx; }
|
.avatar-container { position: relative; margin-bottom: 24rpx; }
|
||||||
.avatar-circle { width: 160rpx; height: 160rpx; border-radius: 50%; background-color: #E5E7EB; border: 4rpx solid #fff; box-shadow: 0 10rpx 20rpx rgba(0,0,0,0.1); }
|
.avatar-circle { width: 160rpx; height: 160rpx; border-radius: 50%; background-color: #E5E7EB; border: 4rpx solid #fff; box-shadow: 0 10rpx 20rpx rgba(0,0,0,0.1); }
|
||||||
|
.avatar-image { width: 160rpx; height: 160rpx; border-radius: 50%; border: 4rpx solid #fff; box-shadow: 0 10rpx 20rpx rgba(0,0,0,0.1); }
|
||||||
.online-badge { width: 32rpx; height: 32rpx; background-color: #10B981; border: 4rpx solid #fff; border-radius: 50%; position: absolute; bottom: 8rpx; right: 8rpx; }
|
.online-badge { width: 32rpx; height: 32rpx; background-color: #10B981; border: 4rpx solid #fff; border-radius: 50%; position: absolute; bottom: 8rpx; right: 8rpx; }
|
||||||
.user-name { font-size: 40rpx; font-weight: 700; color: #111827; }
|
.user-name { font-size: 40rpx; font-weight: 700; color: #111827; }
|
||||||
.user-info { font-size: 24rpx; color: #9CA3AF; margin-top: 8rpx; }
|
.user-info { font-size: 24rpx; color: #9CA3AF; margin-top: 8rpx; }
|
||||||
|
.user-email { font-size: 20rpx; color: #6B7280; margin-top: 4rpx; }
|
||||||
|
|
||||||
.stats-grid { display: flex; gap: 32rpx; margin-bottom: 64rpx; }
|
.stats-grid { display: flex; gap: 32rpx; margin-bottom: 64rpx; }
|
||||||
.stat-card {
|
.stat-card {
|
||||||
@ -166,7 +178,4 @@ onMounted(async () => {
|
|||||||
.menu-item:last-child { border-bottom: none; }
|
.menu-item:last-child { border-bottom: none; }
|
||||||
.menu-text { font-size: 28rpx; font-weight: 500; color: #374151; margin-left: 20rpx; }
|
.menu-text { font-size: 28rpx; font-weight: 500; color: #374151; margin-left: 20rpx; }
|
||||||
.text-red { color: #EF4444; }
|
.text-red { color: #EF4444; }
|
||||||
|
|
||||||
.version-info { text-align: center; margin-top: 80rpx; }
|
|
||||||
.v-text { display: block; font-size: 20rpx; color: #D1D5DB; line-height: 1.5; }
|
|
||||||
</style>
|
</style>
|
||||||
549
utils/api.js
549
utils/api.js
@ -2,12 +2,225 @@
|
|||||||
* API工具类 - 统一封装后端API请求
|
* API工具类 - 统一封装后端API请求
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
console.log('📦 api.js 模块加载成功')
|
||||||
|
|
||||||
// API基础URL
|
// API基础URL
|
||||||
const BASE_URL = 'https://api.assetmanager.com';
|
const BASE_URL = 'https://localhost:7040/';
|
||||||
|
|
||||||
// 请求超时时间
|
// 请求超时时间
|
||||||
const TIMEOUT = 10000;
|
const TIMEOUT = 10000;
|
||||||
|
|
||||||
|
// 登录锁,防止并发登录
|
||||||
|
let loginLock = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取微信登录码
|
||||||
|
* @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和userId');
|
||||||
|
uni.setStorageSync('token', res.data.data.token);
|
||||||
|
// 存储userId到本地存储
|
||||||
|
const userId = res.data.data.userId || res.data.data.user?.userId;
|
||||||
|
if (userId) {
|
||||||
|
uni.setStorageSync('userId', userId);
|
||||||
|
console.log('✅ userId已存储:', userId);
|
||||||
|
// 验证存储是否成功
|
||||||
|
const storedUserId = uni.getStorageSync('userId');
|
||||||
|
console.log('✅ 验证userId存储结果:', storedUserId);
|
||||||
|
} else {
|
||||||
|
console.warn('⚠️ 登录响应中未找到userId');
|
||||||
|
console.log('登录响应数据:', res.data.data);
|
||||||
|
}
|
||||||
|
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()
|
||||||
|
});
|
||||||
|
|
||||||
|
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
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.statusCode === 200) {
|
||||||
|
return res.data;
|
||||||
|
} else if (res.statusCode === 401) {
|
||||||
|
// 未授权,清除token和userId并重新登录(使用登录锁防止并发)
|
||||||
|
console.log('🔒 登录已过期,开始重新登录...');
|
||||||
|
uni.removeStorageSync('token');
|
||||||
|
uni.removeStorageSync('userId');
|
||||||
|
|
||||||
|
// 重新登录后重试请求
|
||||||
|
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.showToast({ title: '系统异常,请稍后重试', icon: 'none', duration: 2000 });
|
||||||
|
throw new Error('登录已过期,重试次数超限');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('❌ API 请求失败:', {
|
||||||
|
statusCode: res.statusCode,
|
||||||
|
message: res.data?.message || `请求失败: ${res.statusCode}`
|
||||||
|
});
|
||||||
|
throw new Error(`请求失败: ${res.statusCode}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// 请求失败日志
|
||||||
|
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} url - 请求URL
|
||||||
@ -17,39 +230,7 @@ const TIMEOUT = 10000;
|
|||||||
* @returns {Promise} - 返回Promise对象
|
* @returns {Promise} - 返回Promise对象
|
||||||
*/
|
*/
|
||||||
const request = (url, method = 'GET', data = {}, headers = {}) => {
|
const request = (url, method = 'GET', data = {}, headers = {}) => {
|
||||||
// 获取存储的token
|
return requestWithRetry(url, method, data, headers);
|
||||||
const token = uni.getStorageSync('token');
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
uni.request({
|
|
||||||
url: BASE_URL + url,
|
|
||||||
method,
|
|
||||||
data,
|
|
||||||
header: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Authorization': token ? `Bearer ${token}` : '',
|
|
||||||
...headers
|
|
||||||
},
|
|
||||||
timeout: TIMEOUT,
|
|
||||||
success: (res) => {
|
|
||||||
if (res.statusCode === 200) {
|
|
||||||
resolve(res.data);
|
|
||||||
} else if (res.statusCode === 401) {
|
|
||||||
// 未授权,清除token并重新登录
|
|
||||||
uni.removeStorageSync('token');
|
|
||||||
uni.removeStorageSync('userInfo');
|
|
||||||
console.log('登录已过期,请重新登录');
|
|
||||||
reject(new Error('登录已过期'));
|
|
||||||
} else {
|
|
||||||
reject(new Error(`请求失败: ${res.statusCode}`));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
fail: (err) => {
|
|
||||||
// 当实际API不可用时,使用模拟数据
|
|
||||||
console.log('API请求失败,使用模拟数据:', err);
|
|
||||||
resolve(getMockData(url, method, data));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -60,7 +241,6 @@ const request = (url, method = 'GET', data = {}, headers = {}) => {
|
|||||||
* @returns {Promise} - 返回Promise对象
|
* @returns {Promise} - 返回Promise对象
|
||||||
*/
|
*/
|
||||||
export const get = (url, params = {}, headers = {}) => {
|
export const get = (url, params = {}, headers = {}) => {
|
||||||
// 构建查询字符串
|
|
||||||
const queryString = Object.keys(params)
|
const queryString = Object.keys(params)
|
||||||
.map(key => `${key}=${encodeURIComponent(params[key])}`)
|
.map(key => `${key}=${encodeURIComponent(params[key])}`)
|
||||||
.join('&');
|
.join('&');
|
||||||
@ -79,291 +259,46 @@ export const post = (url, data = {}, headers = {}) => {
|
|||||||
return request(url, 'POST', 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} params - 请求参数
|
|
||||||
* @param {object} headers - 请求头
|
|
||||||
* @returns {Promise} - 返回Promise对象
|
|
||||||
*/
|
|
||||||
export const del = (url, params = {}, headers = {}) => {
|
|
||||||
const queryString = Object.keys(params)
|
|
||||||
.map(key => `${key}=${encodeURIComponent(params[key])}`)
|
|
||||||
.join('&');
|
|
||||||
const fullUrl = queryString ? `${url}?${queryString}` : url;
|
|
||||||
return request(fullUrl, 'DELETE', {}, headers);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 模拟数据
|
|
||||||
* @param {string} url - 请求URL
|
|
||||||
* @param {string} method - 请求方法
|
|
||||||
* @param {object} data - 请求数据
|
|
||||||
* @returns {object} - 返回模拟数据
|
|
||||||
*/
|
|
||||||
const getMockData = (url, method, data) => {
|
|
||||||
// 模拟API延迟
|
|
||||||
return new Promise(resolve => {
|
|
||||||
setTimeout(() => {
|
|
||||||
// 资产相关模拟数据
|
|
||||||
if (url.includes('/assets')) {
|
|
||||||
resolve({
|
|
||||||
code: 200,
|
|
||||||
data: {
|
|
||||||
totalValue: 1284592.40,
|
|
||||||
currency: 'CNY',
|
|
||||||
todayProfit: 12482.00,
|
|
||||||
todayProfitCurrency: 'CNY',
|
|
||||||
totalReturnRate: 24.82
|
|
||||||
},
|
|
||||||
message: 'success'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 持仓相关模拟数据
|
|
||||||
if (url.includes('/holdings')) {
|
|
||||||
resolve({
|
|
||||||
code: 200,
|
|
||||||
data: [
|
|
||||||
{
|
|
||||||
id: 'hfea-001',
|
|
||||||
name: '美股全天候杠杆',
|
|
||||||
tags: 'HFEA · 季度调仓',
|
|
||||||
status: '监控中',
|
|
||||||
statusType: 'green',
|
|
||||||
iconChar: 'H',
|
|
||||||
iconBgClass: 'bg-green-100',
|
|
||||||
iconTextClass: 'text-green-700',
|
|
||||||
value: 156240.00,
|
|
||||||
currency: 'USD',
|
|
||||||
returnRate: 42.82,
|
|
||||||
returnType: 'positive'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'ma-002',
|
|
||||||
name: '纳指双均线趋势',
|
|
||||||
tags: '趋势跟踪 · 日线',
|
|
||||||
status: '等待信号',
|
|
||||||
statusType: 'gray',
|
|
||||||
iconChar: 'T',
|
|
||||||
iconBgClass: 'bg-blue-100',
|
|
||||||
iconTextClass: 'text-blue-700',
|
|
||||||
value: 412500.00,
|
|
||||||
currency: 'USD',
|
|
||||||
returnRate: -1.79,
|
|
||||||
returnType: 'negative'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'hk-003',
|
|
||||||
name: '港股价值投资',
|
|
||||||
tags: '价值投资 · 蓝筹',
|
|
||||||
status: '持有中',
|
|
||||||
statusType: 'green',
|
|
||||||
iconChar: 'H',
|
|
||||||
iconBgClass: 'bg-green-100',
|
|
||||||
iconTextClass: 'text-green-700',
|
|
||||||
value: 896000.00,
|
|
||||||
currency: 'HKD',
|
|
||||||
returnRate: 12.56,
|
|
||||||
returnType: 'positive'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
message: 'success'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 策略相关模拟数据
|
|
||||||
if (url.includes('/strategies')) {
|
|
||||||
resolve({
|
|
||||||
code: 200,
|
|
||||||
data: [
|
|
||||||
{
|
|
||||||
id: 'hfea',
|
|
||||||
iconChar: 'H',
|
|
||||||
title: 'HFEA 风险平价逻辑',
|
|
||||||
tag: '高风险 · 高预期收益',
|
|
||||||
desc: '针对杠杆ETF平衡的对冲策略,核心逻辑为 TMF (3x长债) 与 UPRO (3x标普) 的季度平衡。',
|
|
||||||
bgClass: 'bg-emerald-900',
|
|
||||||
tagClass: 'text-emerald-700',
|
|
||||||
tags: ['季调', '止损机制'],
|
|
||||||
btnText: '配置参数',
|
|
||||||
btnClass: 'btn-primary'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'ma',
|
|
||||||
iconChar: 'T',
|
|
||||||
title: '双均线趋势跟随',
|
|
||||||
tag: '中风险 · 低回撤要求',
|
|
||||||
desc: '利用快线(EMA10)上穿慢线(EMA60)捕捉强势波段,金叉买入,死叉离场。',
|
|
||||||
bgClass: 'bg-blue-600',
|
|
||||||
tagClass: 'text-blue-700',
|
|
||||||
tags: ['日线', '左侧止盈'],
|
|
||||||
btnText: '预览模型',
|
|
||||||
btnClass: 'btn-secondary'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'chandelier',
|
|
||||||
iconChar: 'S',
|
|
||||||
title: '吊灯止损策略',
|
|
||||||
tag: '风险控制 · 辅助',
|
|
||||||
desc: '基于 ATR 波动率计算的动态止损线,锁住利润,防止回撤过大。',
|
|
||||||
bgClass: 'bg-orange-500',
|
|
||||||
tagClass: 'text-orange-700',
|
|
||||||
tags: ['ATR', '动态止盈'],
|
|
||||||
btnText: '配置参数',
|
|
||||||
btnClass: 'btn-secondary'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
message: 'success'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 用户信息模拟数据
|
|
||||||
if (url.includes('/user/info')) {
|
|
||||||
resolve({
|
|
||||||
code: 200,
|
|
||||||
data: {
|
|
||||||
userName: '首席策略员 0x42',
|
|
||||||
memberLevel: '全球实验室 Pro',
|
|
||||||
runningDays: 412
|
|
||||||
},
|
|
||||||
message: 'success'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 用户统计数据模拟数据
|
|
||||||
if (url.includes('/user/stats')) {
|
|
||||||
resolve({
|
|
||||||
code: 200,
|
|
||||||
data: {
|
|
||||||
signalsCaptured: 1248,
|
|
||||||
winRate: 58.4
|
|
||||||
},
|
|
||||||
message: 'success'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 应用信息模拟数据
|
|
||||||
if (url.includes('/app/info')) {
|
|
||||||
resolve({
|
|
||||||
code: 200,
|
|
||||||
data: {
|
|
||||||
version: 'v2.4.0',
|
|
||||||
currentDate: new Date().toISOString().split('T')[0]
|
|
||||||
},
|
|
||||||
message: 'success'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 微信登录模拟数据
|
|
||||||
if (url.includes('/api/auth/wechat/login')) {
|
|
||||||
resolve({
|
|
||||||
code: 200,
|
|
||||||
data: {
|
|
||||||
token: 'mock-wechat-token-123456',
|
|
||||||
userInfo: {
|
|
||||||
id: 'user-001',
|
|
||||||
openid: 'mock-openid-123456',
|
|
||||||
nickname: '微信用户',
|
|
||||||
avatar: 'https://via.placeholder.com/100',
|
|
||||||
memberLevel: '全球实验室 Pro',
|
|
||||||
runningDays: 412
|
|
||||||
}
|
|
||||||
},
|
|
||||||
message: 'success'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 微信绑定模拟数据
|
|
||||||
if (url.includes('/api/auth/wechat/bind')) {
|
|
||||||
resolve({
|
|
||||||
code: 200,
|
|
||||||
data: {
|
|
||||||
token: 'mock-wechat-token-789012',
|
|
||||||
userInfo: {
|
|
||||||
id: 'user-001',
|
|
||||||
openid: 'mock-openid-123456',
|
|
||||||
email: 'user@example.com',
|
|
||||||
nickname: '微信用户',
|
|
||||||
avatar: 'https://via.placeholder.com/100',
|
|
||||||
memberLevel: '全球实验室 Pro',
|
|
||||||
runningDays: 412
|
|
||||||
}
|
|
||||||
},
|
|
||||||
message: 'success'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 默认返回
|
|
||||||
resolve({
|
|
||||||
code: 200,
|
|
||||||
data: null,
|
|
||||||
message: 'success'
|
|
||||||
});
|
|
||||||
}, 500);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* API接口封装
|
* API接口封装
|
||||||
*/
|
*/
|
||||||
|
console.log('📦 api.js 开始导出api对象')
|
||||||
export const api = {
|
export const api = {
|
||||||
// 资产相关API
|
|
||||||
assets: {
|
assets: {
|
||||||
// 获取总资产数据
|
getAssetData: () => {
|
||||||
getAssetData: () => get('/assets'),
|
const userId = uni.getStorageSync('userId');
|
||||||
// 获取持仓组合数据
|
console.log('📤 发起 getAssetData 请求,userId:', userId);
|
||||||
getHoldings: () => get('/holdings')
|
return get('/api/v1/portfolio/assets', { userId });
|
||||||
|
},
|
||||||
|
getHoldings: () => {
|
||||||
|
const userId = uni.getStorageSync('userId');
|
||||||
|
console.log('📤 发起 getHoldings 请求,userId:', userId);
|
||||||
|
return get('/api/v1/portfolio', { userId });
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// 策略相关API
|
|
||||||
strategies: {
|
strategies: {
|
||||||
// 获取策略列表
|
getStrategies: () => {
|
||||||
getStrategies: () => get('/strategies'),
|
const userId = uni.getStorageSync('userId');
|
||||||
// 获取单个策略详情
|
console.log('📤 发起 getStrategies 请求,userId:', userId);
|
||||||
getStrategyDetail: (id) => get(`/strategies/${id}`),
|
return get('/api/v1/strategies', { userId });
|
||||||
// 创建新策略
|
}
|
||||||
createStrategy: (data) => post('/strategies', data),
|
|
||||||
// 更新策略
|
|
||||||
updateStrategy: (id, data) => put(`/strategies/${id}`, data),
|
|
||||||
// 删除策略
|
|
||||||
deleteStrategy: (id) => del(`/strategies/${id}`)
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// 用户相关API
|
|
||||||
user: {
|
user: {
|
||||||
// 获取用户信息
|
getUserInfo: () => {
|
||||||
getUserInfo: () => get('/user/info'),
|
const userId = uni.getStorageSync('userId');
|
||||||
// 获取用户统计数据
|
console.log('📤 发起 getUserInfo 请求,userId:', userId);
|
||||||
getUserStats: () => get('/user/stats'),
|
return get('/api/user/info', { userId });
|
||||||
// 更新用户信息
|
|
||||||
updateUserInfo: (data) => put('/user/info', data)
|
|
||||||
},
|
},
|
||||||
|
getUserStats: () => {
|
||||||
// 认证相关API
|
const userId = uni.getStorageSync('userId');
|
||||||
auth: {
|
console.log('📤 发起 getUserStats 请求,userId:', userId);
|
||||||
// 微信登录
|
return get('/api/user/stats', { userId });
|
||||||
wechatLogin: (code) => post('/api/auth/wechat/login', { code }),
|
}
|
||||||
// 微信绑定
|
|
||||||
wechatBind: (data) => post('/api/auth/wechat/bind', data)
|
|
||||||
},
|
|
||||||
|
|
||||||
// 应用相关API
|
|
||||||
app: {
|
|
||||||
// 获取应用信息
|
|
||||||
getAppInfo: () => get('/app/info')
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
console.log('📦 api.js 导出默认api对象')
|
||||||
|
|
||||||
export default api;
|
export default api;
|
||||||
Loading…
Reference in New Issue
Block a user