feat: 收益曲线优化

1. 修复 Canvas 实例,添加 this 参数
2. 曲线颜色随收益正负变化(绿涨红跌)
3. 空状态增加回填按钮,引导用户生成收益曲线
This commit is contained in:
claw_bot 2026-03-16 00:22:15 +00:00
parent 8f7b64fa7f
commit 86a3f57c4a

View File

@ -77,6 +77,10 @@
<!-- 无数据 -->
<view v-else-if="!navHistory || navHistory.length === 0" class="nav-empty">
<text class="empty-text">暂无收益数据</text>
<text class="empty-hint">回填历史净值后可生成收益曲线</text>
<button class="backfill-btn" @click="handleBackfillNav" :disabled="backfillLoading">
{{ backfillLoading ? '回填中...' : '生成收益曲线' }}
</button>
</view>
<!-- 收益曲线 -->
@ -480,6 +484,7 @@ const navLoading = ref(false);
const navPeriod = ref('30d');
const navHistory = ref([]);
const navStatistics = ref(null);
const backfillLoading = ref(false);
// 线
const drawNavChart = () => {
@ -487,7 +492,7 @@ const drawNavChart = () => {
if (!data || data.length === 0) return;
nextTick(() => {
const ctx = uni.createCanvasContext('navChart');
const ctx = uni.createCanvasContext('navChart', this);
// (rpx -> px)
const width = 320;
@ -502,6 +507,12 @@ const drawNavChart = () => {
const maxVal = Math.max(...values) * 1.02;
const range = maxVal - minVal || 1;
//
const totalReturn = navStatistics.value?.totalReturn || 0;
const isPositive = totalReturn >= 0;
const lineColor = isPositive ? '#059669' : '#DC2626';
const fillColor = isPositive ? 'rgba(5, 150, 105, 0.15)' : 'rgba(220, 38, 38, 0.15)';
//
ctx.clearRect(0, 0, width, height);
@ -541,12 +552,12 @@ const drawNavChart = () => {
ctx.lineTo(points[points.length - 1].x, padding.top + chartHeight);
ctx.lineTo(points[0].x, padding.top + chartHeight);
ctx.closePath();
ctx.setFillStyle('rgba(6, 78, 59, 0.15)');
ctx.setFillStyle(fillColor);
ctx.fill();
// 线
ctx.beginPath();
ctx.setStrokeStyle('#064E3B');
ctx.setStrokeStyle(lineColor);
ctx.setLineWidth(2);
points.forEach((p, i) => {
if (i === 0) ctx.moveTo(p.x, p.y);
@ -571,6 +582,32 @@ const drawNavChart = () => {
});
};
//
const handleBackfillNav = async () => {
if (!portfolioId.value) return;
backfillLoading.value = true;
try {
const response = await api.assets.backfillNavHistory(portfolioId.value, true);
if (response.code === 200) {
uni.showToast({
title: `成功生成 ${response.data.recordsCreated} 条记录`,
icon: 'success'
});
//
await fetchNavHistory();
}
} catch (error) {
console.error('回填净值失败:', error);
uni.showToast({
title: '生成失败,请重试',
icon: 'none'
});
} finally {
backfillLoading.value = false;
}
};
//
const showTransactionForm = ref(false);
const transactionType = ref('buy'); // buy sell
@ -1323,9 +1360,11 @@ const deletePortfolio = async () => {
.nav-loading,
.nav-empty {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 400rpx;
gap: 16rpx;
}
.loading-text,
@ -1334,6 +1373,26 @@ const deletePortfolio = async () => {
color: #9CA3AF;
}
.empty-hint {
font-size: 24rpx;
color: #D1D5DB;
margin-bottom: 8rpx;
}
.backfill-btn {
margin-top: 16rpx;
padding: 16rpx 32rpx;
font-size: 26rpx;
color: #FFFFFF;
background-color: #059669;
border-radius: 8rpx;
border: none;
}
.backfill-btn[disabled] {
background-color: #9CA3AF;
}
.nav-chart-wrapper {
position: relative;
width: 100%;