- 资产卡片区域:深色骨架屏匹配原有卡片风格 - 持仓列表区域:模拟卡片布局的骨架占位 - 添加 loading 状态控制,数据加载完成后切换 - 骨架屏带渐变动画效果,提升用户体验
181 lines
5.9 KiB
Vue
Executable File
181 lines
5.9 KiB
Vue
Executable File
<template>
|
||
<view class="page-container">
|
||
<view class="profile-header">
|
||
<view class="avatar-container">
|
||
<!-- 如果有头像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 || 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() || 0 }}</text>
|
||
</view>
|
||
<view class="stat-card">
|
||
<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>
|
||
|
||
<view class="menu-list">
|
||
<view class="menu-item">
|
||
<view class="flex-row items-center gap-3">
|
||
<uni-icons type="checkbox" size="20" color="#9CA3AF"></uni-icons>
|
||
<text class="menu-text">账户安全中心</text>
|
||
</view>
|
||
<uni-icons type="right" size="16" color="#D1D5DB"></uni-icons>
|
||
</view>
|
||
|
||
<view class="menu-item">
|
||
<view class="flex-row items-center gap-3">
|
||
<uni-icons type="gear" size="20" color="#9CA3AF"></uni-icons>
|
||
<text class="menu-text">全局执行偏好</text>
|
||
</view>
|
||
<uni-icons type="right" size="16" color="#D1D5DB"></uni-icons>
|
||
</view>
|
||
|
||
<view class="menu-item">
|
||
<view class="flex-row items-center gap-3">
|
||
<uni-icons type="staff" size="20" color="#EF4444"></uni-icons>
|
||
<text class="menu-text text-red">退出登录</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, onMounted, getCurrentInstance } from 'vue';
|
||
|
||
// 用户信息
|
||
const userInfo = ref({
|
||
userName: '',
|
||
memberLevel: '',
|
||
runningDays: 0
|
||
});
|
||
|
||
// 用户统计数据
|
||
const userStats = ref({
|
||
signalsCaptured: 0,
|
||
winRate: 0
|
||
});
|
||
|
||
// 获取全局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);
|
||
}
|
||
};
|
||
|
||
// 从后端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);
|
||
}
|
||
};
|
||
|
||
|
||
|
||
// 页面加载时获取数据
|
||
onMounted(async () => {
|
||
console.log('个人中心页面加载,开始获取数据...');
|
||
|
||
await Promise.all([
|
||
fetchUserInfo(),
|
||
fetchUserStats()
|
||
]);
|
||
});
|
||
</script>
|
||
|
||
<style scoped>
|
||
.page-container {
|
||
min-height: 100vh;
|
||
padding: 40rpx;
|
||
padding-top: 100rpx;
|
||
background-color: #F9FAFB;
|
||
/* 浅灰色背景,与其他页面保持一致 */
|
||
}
|
||
.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 {
|
||
flex: 1;
|
||
background: #FFFFFF;
|
||
padding: 32rpx;
|
||
border-radius: 32rpx;
|
||
text-align: center;
|
||
border: 1rpx solid #E5E7EB;
|
||
box-shadow: 0 12rpx 32rpx rgba(0, 0, 0, 0.06);
|
||
transition: all 0.2s ease;
|
||
}
|
||
|
||
.stat-card:active {
|
||
transform: translateY(2rpx);
|
||
box-shadow: 0 6rpx 16rpx rgba(0, 0, 0, 0.04);
|
||
}
|
||
.stat-label { font-size: 20rpx; color: #9CA3AF; display: block; margin-bottom: 8rpx; }
|
||
.stat-val { font-size: 36rpx; font-weight: 700; color: #111827; }
|
||
|
||
.menu-list {
|
||
background: #FFFFFF;
|
||
border-radius: 32rpx;
|
||
padding: 0 32rpx;
|
||
border: 1rpx solid #E5E7EB;
|
||
box-shadow: 0 12rpx 32rpx rgba(0, 0, 0, 0.06);
|
||
}
|
||
.menu-item { display: flex; justify-content: space-between; align-items: center; padding: 32rpx 0; border-bottom: 1rpx solid #F3F4F6; }
|
||
.menu-item:last-child { border-bottom: none; }
|
||
.menu-text { font-size: 28rpx; font-weight: 500; color: #374151; margin-left: 20rpx; }
|
||
.text-red { color: #EF4444; }
|
||
</style> |