refactor(auth): 重构用户认证逻辑并迁移至Pinia存储

将微信静默登录逻辑从App.vue迁移至Pinia的user store,实现状态集中管理
全局注册api模块并优化请求重试机制,增强错误处理和日志记录
移除App.vue中的冗余代码,简化页面组件的数据获取方式
This commit is contained in:
niannian zheng 2026-02-26 13:33:57 +08:00
parent a95cef8159
commit dc857a98a3
5 changed files with 339 additions and 421 deletions

95
App.vue
View File

@ -1,71 +1,32 @@
<script>
import api from './utils/api'
export default {
onLaunch: function() {
console.log('App Launch')
//
this.wechatSilentLogin()
},
onShow: function() {
console.log('App Show')
},
onHide: function() {
console.log('App Hide')
},
methods: {
//
wechatSilentLogin() {
//
if (uni.getSystemInfoSync().platform === 'devtools' || uni.getSystemInfoSync().platform === 'mp-weixin') {
// APIcode
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)
}
})
}
}
}
}
export default {
onLaunch: function() {
console.log('App Launch')
},
onShow: function() {
console.log('App Show')
},
onHide: function() {
console.log('App Hide')
}
}
</script>
<style>
/* 每个页面公共css */
page {
background-color: #F3F4F6;
font-family: 'Inter', 'Noto Sans SC', sans-serif;
--brand-color: #064e3b;
--brand-light: #10B981;
}
/* 通用工具类 */
.bg-white { background-color: #ffffff; }
.rounded-2xl { border-radius: 32rpx; }
.shadow-sm { box-shadow: 0 2rpx 6rpx rgba(0,0,0,0.05); }
.flex-row { display: flex; flex-direction: row; }
.flex-col { display: flex; flex-direction: column; }
.items-center { align-items: center; }
.justify-between { justify-content: space-between; }
</style>
/* 每个页面公共css */
page {
background-color: #F3F4F6;
font-family: 'Inter', 'Noto Sans SC', sans-serif;
--brand-color: #064e3b;
--brand-light: #10B981;
}
/* 通用工具类 */
.bg-white { background-color: #ffffff; }
.rounded-2xl { border-radius: 32rpx; }
.shadow-sm { box-shadow: 0 2rpx 6rpx rgba(0,0,0,0.05); }
.flex-row { display: flex; flex-direction: row; }
.flex-col { display: flex; flex-direction: column; }
.items-center { align-items: center; }
.justify-between { justify-content: space-between; }
</style>

View File

@ -13,8 +13,17 @@ app.$mount()
// #ifdef VUE3
import { createSSRApp } from 'vue'
import api from './utils/api'
console.log('🚀 应用启动导入api模块')
export function createApp() {
const app = createSSRApp(App)
// 全局注册api
app.config.globalProperties.$api = api
console.log('✅ api已全局注册为 $api')
return {
app
}

View File

@ -18,17 +18,17 @@
<view class="card-row main-row">
<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 class="card-row bottom-row">
<view class="stat-col">
<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 class="stat-col align-right">
<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>
@ -75,11 +75,11 @@
<view class="card-bottom">
<view class="data-col">
<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 class="data-col align-right">
<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>
@ -107,9 +107,11 @@ const holdings = ref([]);
// API
const fetchAssetData = async () => {
try {
console.log('开始获取资产数据...');
const response = await api.assets.getAssetData();
if (response.code === 200) {
assetData.value = response.data;
console.log('资产数据获取成功');
}
} catch (error) {
console.error('获取资产数据失败:', error);
@ -119,17 +121,23 @@ const fetchAssetData = async () => {
// API
const fetchHoldingsData = async () => {
try {
console.log('开始获取持仓数据...');
const response = await api.assets.getHoldings();
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) {
console.error('获取持仓数据失败:', error);
}
};
//
//
onMounted(async () => {
console.log('首页加载,开始加载数据...');
await Promise.all([
fetchAssetData(),
fetchHoldingsData()
@ -143,10 +151,6 @@ const goConfig = () => {
const goDetail = (holdingId) => {
uni.navigateTo({ url: `/pages/detail/detail?id=${holdingId}` });
};
const goStrategies = () => {
uni.switchTab({ url: '/pages/strategies/strategies' });
};
</script>
<style scoped>

View File

@ -2,21 +2,32 @@
<view class="page-container">
<view class="profile-header">
<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>
<text class="user-name">{{ userInfo.userName }}</text>
<text class="user-info">会员等级: {{ userInfo.memberLevel }} | 连续运行 {{ userInfo.runningDays }}</text>
<text class="user-name">{{ userInfo.userName || '加载中...' }}</text>
<text class="user-info">会员等级: {{ userInfo.memberLevel || '普通会员' }} | 连续运行 {{ userInfo.runningDays || 0 }}</text>
<text v-if="userInfo.email" class="user-email">{{ userInfo.email }}</text>
</view>
<view class="stats-grid">
<view class="stat-card">
<text class="stat-label">已记录事件</text>
<text class="stat-val">{{ userStats.signalsCaptured.toLocaleString() }}</text>
<text class="stat-val">{{ userStats.signalsCaptured?.toLocaleString() || 0 }}</text>
</view>
<view class="stat-card">
<text class="stat-label">记录完整度</text>
<text class="stat-val">{{ userStats.winRate }}%</text>
<text class="stat-label">胜率</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>
@ -44,17 +55,11 @@
</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>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { api } from '../../utils/api';
import { ref, onMounted, getCurrentInstance } from 'vue';
//
const userInfo = ref({
@ -69,18 +74,25 @@ const userStats = ref({
winRate: 0
});
//
const appInfo = ref({
version: '',
currentDate: ''
});
// api
const getApi = () => {
const instance = getCurrentInstance();
return instance?.appContext.config.globalProperties.$api;
};
// API
const fetchUserInfo = async () => {
try {
const api = getApi();
if (!api || !api.user) {
console.error('API模块未加载');
return;
}
const response = await api.user.getUserInfo();
if (response.code === 200) {
userInfo.value = response.data;
console.log('用户信息获取成功:', response.data);
}
} catch (error) {
console.error('获取用户信息失败:', error);
@ -90,33 +102,31 @@ const fetchUserInfo = async () => {
// API
const fetchUserStats = async () => {
try {
const api = getApi();
if (!api || !api.user) {
console.error('API模块未加载');
return;
}
const response = await api.user.getUserStats();
if (response.code === 200) {
userStats.value = response.data;
console.log('用户统计数据获取成功:', response.data);
}
} catch (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 () => {
console.log('个人中心页面加载,开始获取数据...');
await Promise.all([
fetchUserInfo(),
fetchUserStats(),
fetchAppInfo()
fetchUserStats()
]);
});
</script>
@ -132,9 +142,11 @@ onMounted(async () => {
.profile-header { display: flex; flex-direction: column; align-items: center; margin-bottom: 60rpx; }
.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-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; }
.user-name { font-size: 40rpx; font-weight: 700; color: #111827; }
.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; }
.stat-card {
@ -166,7 +178,4 @@ onMounted(async () => {
.menu-item:last-child { border-bottom: none; }
.menu-text { font-size: 28rpx; font-weight: 500; color: #374151; margin-left: 20rpx; }
.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>

View File

@ -2,12 +2,225 @@
* API工具类 - 统一封装后端API请求
*/
console.log('📦 api.js 模块加载成功')
// API基础URL
const BASE_URL = 'https://api.assetmanager.com';
const BASE_URL = 'https://localhost:7040/';
// 请求超时时间
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
@ -17,39 +230,7 @@ const TIMEOUT = 10000;
* @returns {Promise} - 返回Promise对象
*/
const request = (url, method = 'GET', data = {}, headers = {}) => {
// 获取存储的token
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));
}
});
});
return requestWithRetry(url, method, data, headers);
};
/**
@ -60,7 +241,6 @@ const request = (url, method = 'GET', data = {}, headers = {}) => {
* @returns {Promise} - 返回Promise对象
*/
export const get = (url, params = {}, headers = {}) => {
// 构建查询字符串
const queryString = Object.keys(params)
.map(key => `${key}=${encodeURIComponent(params[key])}`)
.join('&');
@ -79,291 +259,46 @@ 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} 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接口封装
*/
console.log('📦 api.js 开始导出api对象')
export const api = {
// 资产相关API
assets: {
// 获取总资产数据
getAssetData: () => get('/assets'),
// 获取持仓组合数据
getHoldings: () => get('/holdings')
getAssetData: () => {
const userId = uni.getStorageSync('userId');
console.log('📤 发起 getAssetData 请求userId:', userId);
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: {
// 获取策略列表
getStrategies: () => get('/strategies'),
// 获取单个策略详情
getStrategyDetail: (id) => get(`/strategies/${id}`),
// 创建新策略
createStrategy: (data) => post('/strategies', data),
// 更新策略
updateStrategy: (id, data) => put(`/strategies/${id}`, data),
// 删除策略
deleteStrategy: (id) => del(`/strategies/${id}`)
getStrategies: () => {
const userId = uni.getStorageSync('userId');
console.log('📤 发起 getStrategies 请求userId:', userId);
return get('/api/v1/strategies', { userId });
}
},
// 用户相关API
user: {
// 获取用户信息
getUserInfo: () => get('/user/info'),
// 获取用户统计数据
getUserStats: () => get('/user/stats'),
// 更新用户信息
updateUserInfo: (data) => put('/user/info', data)
},
// 认证相关API
auth: {
// 微信登录
wechatLogin: (code) => post('/api/auth/wechat/login', { code }),
// 微信绑定
wechatBind: (data) => post('/api/auth/wechat/bind', data)
},
// 应用相关API
app: {
// 获取应用信息
getAppInfo: () => get('/app/info')
getUserInfo: () => {
const userId = uni.getStorageSync('userId');
console.log('📤 发起 getUserInfo 请求userId:', userId);
return get('/api/user/info', { userId });
},
getUserStats: () => {
const userId = uni.getStorageSync('userId');
console.log('📤 发起 getUserStats 请求userId:', userId);
return get('/api/user/stats', { userId });
}
}
};
export default api;
console.log('📦 api.js 导出默认api对象')
export default api;