AssetManager.UniApp/pages/index/index.vue
niannian zheng bf4fa243ec feat: 实现资产、策略和交易模块的数据映射与API集成
refactor: 重构策略编辑页面,支持创建和更新操作

fix: 修复投资组合详情页的数据显示问题

perf: 优化API请求处理,添加PUT和DELETE方法

docs: 完善API接口文档注释

style: 统一代码格式和命名规范
2026-03-02 17:13:28 +08:00

509 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, onMounted } from 'vue';
import { api } from '../../utils/api';
// 资产数据
const assetData = ref({
totalValue: 0,
todayProfit: 0,
totalReturnRate: 0
});
// 持仓组合数据
const holdings = ref([]);
// 从后端API获取资产数据的函数
const fetchAssetData = async () => {
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 () => {
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);
}
};
// 页面加载时检查登录状态
onMounted(async () => {
console.log('首页加载,开始加载数据...');
await Promise.all([
fetchAssetData(),
fetchHoldingsData()
]);
});
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>