AssetManager.UniApp/pages/index/index.vue
niannian zheng 43280fa447 refactor: 统一API路径和字段命名,优化数据请求逻辑
- 将API路径从'/api/user/info'改为'api/v1/user/info'以统一版本前缀
- 将数据字段从大驼峰改为小驼峰命名规范
- 使用onShow替代onMounted并添加防重复请求机制
- 移除策略编辑页面的导航栏,简化页面结构
- 优化交易记录页面按钮样式和文案
2026-03-02 19:05:21 +08:00

517 lines
11 KiB
Vue
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.

<template>
<view class="page-container">
<view class="header-section">
<view class="asset-card">
<view class="card-row top-row">
<view class="row-left">
<text class="label-text">账本总额 (CNY)</text>
<view class="eye-btn">
<uni-icons type="eye-filled" size="18" color="rgba(255,255,255,0.7)"></uni-icons>
</view>
</view>
</view>
<view class="card-row main-row">
<text class="currency-symbol">¥</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) >= 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) >= 0 ? '+' : '' }}{{ assetData.totalReturnRate || 0 }}%</text>
</view>
</view>
</view>
</view>
<view class="part-add-portfolio">
<view class="dashed-btn" @click="goConfig">
<uni-icons type="plus" size="20" color="#9CA3AF"></uni-icons>
<text class="btn-text">新建组合</text>
</view>
</view>
<view class="part-holdings-list">
<view class="section-header">
<text class="section-title">当前记录组合</text>
</view>
<view
v-for="holding in holdings"
:key="holding.id"
class="holding-card"
@click="goDetail(holding.id)"
>
<view class="card-top">
<view class="flex-row items-center gap-2">
<view class="strategy-icon" :class="holding.iconBgClass">
<text class="icon-text" :class="holding.iconTextClass">{{ holding.iconChar }}</text>
</view>
<view class="flex-col">
<text class="card-name">{{ holding.name }}</text>
<text class="card-tags">{{ holding.tags }}</text>
</view>
</view>
<view class="status-badge" :class="holding.statusType === 'green' ? 'bg-green-50' : 'bg-gray-100'">
<text class="status-text" :class="holding.statusType === 'green' ? 'text-green-600' : 'text-gray-500'">● {{ holding.status }}</text>
</view>
</view>
<view class="card-divider"></view>
<view class="card-bottom">
<view class="data-col">
<text class="data-label">当前估值</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) >= 0 ? '+' : '' }}{{ holding.returnRate || 0 }}%</text>
</view>
</view>
</view>
<view style="height: 100rpx;"></view>
</view>
</view>
</template>
<script setup>
import { ref, onShow } from 'vue';
import { api } from '../../utils/api';
// 资产数据
const assetData = ref({
totalValue: 0,
todayProfit: 0,
totalReturnRate: 0
});
// 持仓组合数据
const holdings = ref([]);
// 防止重复请求的标志
let isFetching = false;
// 从后端API获取资产数据的函数
const fetchAssetData = async () => {
if (isFetching) return;
try {
console.log('开始获取资产数据...');
const response = await api.assets.getAssetData();
if (response.code === 200) {
// 映射资产数据字段
const data = response.data;
assetData.value = {
totalValue: data.totalValue,
currency: data.currency,
todayProfit: data.todayProfit,
todayProfitCurrency: data.todayProfitCurrency,
totalReturnRate: data.totalReturnRate
};
console.log('资产数据获取成功');
}
} catch (error) {
console.error('获取资产数据失败:', error);
}
};
// 从后端API获取持仓组合数据的函数
const fetchHoldingsData = async () => {
if (isFetching) return;
try {
console.log('开始获取持仓数据...');
const response = await api.assets.getHoldings();
if (response.code === 200) {
// 处理响应数据结构,获取 items 数组并映射字段
const items = response.data.items || [];
holdings.value = items.map(item => ({
id: item.id,
name: item.name,
tags: item.tags,
status: item.status,
statusType: item.statusType,
iconChar: item.iconChar,
iconBgClass: item.iconBgClass,
iconTextClass: item.iconTextClass,
value: item.value,
currency: item.currency,
returnRate: item.returnRate,
returnType: item.returnType
}));
console.log('持仓数据获取成功items数量:', holdings.value.length);
console.log('持仓数据:', holdings.value);
}
} catch (error) {
console.error('获取持仓数据失败:', error);
}
};
// 页面显示时刷新数据
onShow(async () => {
console.log('首页显示,刷新数据...');
isFetching = true;
await Promise.all([
fetchAssetData(),
fetchHoldingsData()
]);
isFetching = false;
});
const goConfig = () => {
uni.navigateTo({ url: '/pages/config/config' });
};
const goDetail = (holdingId) => {
uni.navigateTo({ url: `/pages/detail/detail?id=${holdingId}` });
};
</script>
<style scoped>
/* 通用布局 */
.page-container {
min-height: 100vh;
background-color: #F9FAFB;
/* 浅灰色背景,与添加按钮背景一致 */
}
.flex-row {
display: flex;
flex-direction: row;
}
.flex-col {
display: flex;
flex-direction: column;
}
.items-center {
align-items: center;
}
.gap-2 {
gap: 16rpx;
}
.mt-2 {
margin-top: 16rpx;
}
/* 字体颜色工具类 */
.text-white {
color: #fff;
}
.text-gray-500 {
color: #6B7280;
}
.text-green-600 {
color: #059669;
}
.text-green-700 {
color: #047857;
}
.text-blue-700 {
color: #1D4ED8;
}
.text-red {
color: #EF4444;
}
/* 涨 */
.text-green {
color: #10B981;
}
/* 跌 */
/* ============================ */
/* Part 1: 资产卡片样式 (精修版) */
/* ============================ */
.header-section {
padding: 20rpx 32rpx;
background-color: #F9FAFB;
/* 浅灰色背景,与页面背景一致 */
}
.asset-card {
background-color: #064E3B;
/* 深墨绿色 */
border-radius: 40rpx;
padding: 40rpx 48rpx;
position: relative;
overflow: hidden;
box-shadow: 0 10rpx 30rpx rgba(6, 78, 59, 0.25);
min-height: 320rpx;
display: flex;
flex-direction: column;
justify-content: space-between;
}
/* 内容行通用设置 */
.card-row {
position: relative;
z-index: 1;
}
/* 第一行:顶部布局 (关键修复:两端对齐) */
.top-row {
display: flex;
justify-content: space-between;
/* 让铃铛靠右 */
align-items: center;
margin-bottom: 20rpx;
}
.row-left {
display: flex;
align-items: center;
}
.label-text {
font-size: 26rpx;
color: rgba(255, 255, 255, 0.7);
font-weight: 400;
}
.eye-btn {
margin-left: 12rpx;
opacity: 0.8;
}
/* 第二行:大数字 */
.main-row {
display: flex;
align-items: baseline;
margin-bottom: 30rpx;
}
.currency-symbol {
font-size: 40rpx;
color: #FFFFFF;
font-weight: bold;
margin-right: 8rpx;
font-family: 'DIN Alternate', sans-serif;
}
.big-number {
font-size: 68rpx;
color: #FFFFFF;
font-weight: 800;
letter-spacing: 1rpx;
font-family: 'DIN Alternate', sans-serif;
}
/* 第三行:底部数据 */
.bottom-row {
display: flex;
justify-content: space-between;
align-items: flex-end;
}
.stat-col {
display: flex;
flex-direction: column;
gap: 8rpx;
}
.align-right {
align-items: flex-end;
}
.stat-label {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.6);
}
.stat-value {
font-size: 36rpx;
color: #FFFFFF;
font-weight: 700;
font-family: 'DIN Alternate', sans-serif;
}
/* ============================ */
/* Part 2: 添加组合按钮样式 */
/* ============================ */
.part-add-portfolio {
padding: 10rpx 32rpx 30rpx 32rpx;
}
.dashed-btn {
width: 100%;
height: 96rpx;
background-color: #F9FAFB;
border: 2rpx dashed #D1D5DB;
border-radius: 24rpx;
display: flex;
align-items: center;
justify-content: center;
gap: 12rpx;
transition: all 0.2s;
}
.dashed-btn:active {
background-color: #F3F4F6;
border-color: #9CA3AF;
}
.btn-text {
font-size: 28rpx;
color: #6B7280;
font-weight: 500;
}
/* ============================ */
/* Part 3: 持仓列表样式 */
/* ============================ */
.part-holdings-list {
padding: 0 32rpx;
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24rpx;
padding-left: 8rpx;
}
.section-title {
font-size: 32rpx;
font-weight: 800;
color: #1F2937;
}
.view-all {
font-size: 24rpx;
color: #064E3B;
font-weight: 600;
margin-right: 4rpx;
}
/* 卡片样式 */
.holding-card {
background-color: #FFFFFF;
border: 1rpx solid #E5E7EB;
border-radius: 32rpx;
padding: 32rpx;
margin-bottom: 24rpx;
box-shadow: 0 12rpx 32rpx rgba(0, 0, 0, 0.06);
transition: all 0.2s ease;
}
.holding-card:active {
transform: translateY(2rpx);
box-shadow: 0 6rpx 16rpx rgba(0, 0, 0, 0.04);
}
.card-top {
display: flex;
justify-content: space-between;
align-items: flex-start;
}
.strategy-icon {
width: 80rpx;
height: 80rpx;
border-radius: 20rpx;
display: flex;
align-items: center;
justify-content: center;
}
.bg-green-100 {
background-color: #D1FAE5;
}
.bg-blue-100 {
background-color: #DBEAFE;
}
.bg-green-50 {
background-color: #ECFDF5;
}
.bg-gray-100 {
background-color: #F3F4F6;
}
.icon-text {
font-size: 36rpx;
font-weight: 800;
}
.card-name {
font-size: 30rpx;
font-weight: 700;
color: #1F2937;
margin-bottom: 6rpx;
}
.card-tags {
font-size: 22rpx;
color: #9CA3AF;
}
.status-badge {
padding: 6rpx 16rpx;
border-radius: 100rpx;
}
.status-text {
font-size: 20rpx;
font-weight: 600;
}
.card-divider {
height: 1rpx;
background-color: #F3F4F6;
margin: 24rpx 0;
}
.card-bottom {
display: flex;
justify-content: space-between;
}
.data-col {
display: flex;
flex-direction: column;
}
.data-label {
font-size: 22rpx;
color: #9CA3AF;
margin-bottom: 8rpx;
}
.data-val {
font-size: 32rpx;
font-weight: 700;
font-family: 'DIN Alternate', sans-serif;
color: #1F2937;
}
</style>