AssetManager.UniApp/pages/index/index.vue
niannian zheng 4ea7c5c2d0 refactor(页面): 优化页面生命周期和组件逻辑
重构页面生命周期钩子,统一使用uni-app的onShow替代vue的onShow
移除重复请求检查逻辑,简化数据获取函数
在配置页面自动填充策略参数并禁用相关输入
移除不必要的添加/删除股票行功能
2026-03-02 19:16:14 +08:00

515 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 } from 'vue';
import { onShow } from '@dcloudio/uni-app';
import { api } from '../../utils/api';
// 资产数据
const assetData = ref({
totalValue: 0,
todayProfit: 0,
totalReturnRate: 0
});
// 持仓组合数据
const holdings = ref([]);
// 防止重复请求的标志
let isFetching = false;
// 从后端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);
}
};
const goConfig = () => {
uni.navigateTo({ url: '/pages/config/config' });
};
const goDetail = (holdingId) => {
uni.navigateTo({ url: `/pages/detail/detail?id=${holdingId}` });
};
onShow(async () => {
console.log('首页显示,刷新数据...');
isFetching = true;
await Promise.all([
fetchAssetData(),
fetchHoldingsData()
]);
isFetching = false;
});
</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>