diff --git a/pages/detail/detail.vue b/pages/detail/detail.vue index 3d2bf28..0881802 100755 --- a/pages/detail/detail.vue +++ b/pages/detail/detail.vue @@ -85,11 +85,26 @@ - + + + + 最新净值 + {{ (navHistory[navHistory.length - 1]?.nav || 1).toFixed(4) }} + + + {{ navStatistics?.totalReturn >= 0 ? '↑' : '↓' }} + {{ Math.abs(navStatistics?.totalReturn || 0).toFixed(2) }}% + + + + + + + @@ -102,7 +117,7 @@ 最大回撤 - {{ navStatistics.maxDrawdown.toFixed(2) }}% + -{{ navStatistics.maxDrawdown.toFixed(2) }}% 夏普比率 @@ -494,10 +509,10 @@ const drawNavChart = () => { nextTick(() => { const ctx = uni.createCanvasContext('navChart', this); - // 画布尺寸 (rpx -> px) - const width = 320; - const height = 200; - const padding = { top: 20, right: 20, bottom: 30, left: 45 }; + // 画布尺寸 (rpx -> px) - 全宽 + const width = 350; + const height = 180; + const padding = { top: 10, right: 10, bottom: 25, left: 10 }; const chartWidth = width - padding.left - padding.right; const chartHeight = height - padding.top - padding.bottom; @@ -511,39 +526,23 @@ const drawNavChart = () => { 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)'; + const fillColorTop = isPositive ? 'rgba(5, 150, 105, 0.3)' : 'rgba(220, 38, 38, 0.3)'; + const fillColorBottom = isPositive ? 'rgba(5, 150, 105, 0.02)' : 'rgba(220, 38, 38, 0.02)'; // 清空画布 ctx.clearRect(0, 0, width, height); - // 绘制网格线 - ctx.setStrokeStyle('#E5E7EB'); - ctx.setLineWidth(0.5); - for (let i = 0; i <= 4; i++) { - const y = padding.top + (chartHeight / 4) * i; - ctx.beginPath(); - ctx.moveTo(padding.left, y); - ctx.lineTo(width - padding.right, y); - ctx.stroke(); - } - - // Y轴标签 - ctx.setFillStyle('#9CA3AF'); - ctx.setFontSize(10); - ctx.setTextAlign('right'); - for (let i = 0; i <= 4; i++) { - const y = padding.top + (chartHeight / 4) * i; - const val = maxVal - (range / 4) * i; - ctx.fillText(val.toFixed(2), padding.left - 5, y + 3); - } - // 计算点坐标 const points = data.map((item, index) => ({ x: padding.left + (chartWidth / (data.length - 1 || 1)) * index, y: padding.top + chartHeight - ((item.nav - minVal) / range) * chartHeight })); - // 绘制填充区域 + // 绘制渐变填充区域 + const gradient = ctx.createLinearGradient(0, padding.top, 0, padding.top + chartHeight); + gradient.addColorStop(0, fillColorTop); + gradient.addColorStop(1, fillColorBottom); + ctx.beginPath(); points.forEach((p, i) => { if (i === 0) ctx.moveTo(p.x, p.y); @@ -552,16 +551,29 @@ const drawNavChart = () => { ctx.lineTo(points[points.length - 1].x, padding.top + chartHeight); ctx.lineTo(points[0].x, padding.top + chartHeight); ctx.closePath(); - ctx.setFillStyle(fillColor); + ctx.setFillStyle(gradient); ctx.fill(); - // 绘制曲线 + // 绘制平滑曲线 ctx.beginPath(); ctx.setStrokeStyle(lineColor); ctx.setLineWidth(2); + ctx.setLineCap('round'); + ctx.setLineJoin('round'); + + // 使用贝塞尔曲线平滑 points.forEach((p, i) => { - if (i === 0) ctx.moveTo(p.x, p.y); - else ctx.lineTo(p.x, p.y); + if (i === 0) { + ctx.moveTo(p.x, p.y); + } else { + // 二次贝塞尔曲线 + const prev = points[i - 1]; + const cpX = (prev.x + p.x) / 2; + ctx.quadraticCurveTo(prev.x, prev.y, cpX, (prev.y + p.y) / 2); + if (i === points.length - 1) { + ctx.quadraticCurveTo(cpX, (prev.y + p.y) / 2, p.x, p.y); + } + } }); ctx.stroke(); @@ -575,7 +587,7 @@ const drawNavChart = () => { const idx = Math.min(i * step, data.length - 1); const x = padding.left + (chartWidth / (data.length - 1 || 1)) * idx; const dateStr = data[idx].date.split('-').slice(1).join('/'); - ctx.fillText(dateStr, x, height - 8); + ctx.fillText(dateStr, x, height - 5); } ctx.draw(); @@ -1354,7 +1366,90 @@ const deletePortfolio = async () => { background-color: #FFFFFF; border-radius: 20rpx; padding: 24rpx; - min-height: 400rpx; + min-height: 300rpx; +} + +.nav-chart-wrapper { + display: flex; + flex-direction: column; +} + +/* 净值指标 */ +.nav-indicator { + display: flex; + justify-content: space-between; + align-items: flex-start; + padding: 16rpx 0 24rpx; + border-bottom: 1rpx solid #F3F4F6; + margin-bottom: 16rpx; +} + +.indicator-main { + display: flex; + flex-direction: column; + gap: 8rpx; +} + +.indicator-label { + font-size: 24rpx; + color: #9CA3AF; +} + +.indicator-value { + font-size: 44rpx; + font-weight: 700; + font-family: 'DIN Alternate', sans-serif; + color: #1F2937; +} + +.indicator-change { + display: flex; + align-items: center; + gap: 4rpx; + padding: 8rpx 16rpx; + border-radius: 8rpx; + margin-top: 8rpx; +} + +.indicator-change.positive { + background-color: #ECFDF5; +} + +.indicator-change.negative { + background-color: #FEF2F2; +} + +.change-arrow { + font-size: 28rpx; + font-weight: 600; +} + +.indicator-change.positive .change-arrow, +.indicator-change.positive .change-value { + color: #059669; +} + +.indicator-change.negative .change-arrow, +.indicator-change.negative .change-value { + color: #DC2626; +} + +.change-value { + font-size: 28rpx; + font-weight: 600; + font-family: 'DIN Alternate', sans-serif; +} + +/* 图表区域 */ +.chart-area { + width: 100%; + height: 360rpx; + position: relative; +} + +.nav-canvas { + width: 100%; + height: 100%; } .nav-loading, @@ -1363,7 +1458,7 @@ const deletePortfolio = async () => { flex-direction: column; align-items: center; justify-content: center; - height: 400rpx; + height: 300rpx; gap: 16rpx; } @@ -1426,21 +1521,23 @@ const deletePortfolio = async () => { } .nav-stats { - display: flex; - justify-content: space-between; - margin-top: 24rpx; - padding-top: 24rpx; - border-top: 1rpx solid #E5E7EB; + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 16rpx; + margin-top: 20rpx; + padding-top: 20rpx; + border-top: 1rpx solid #F3F4F6; } .nav-stats .stat-item { text-align: center; + padding: 12rpx 0; } .nav-stats .stat-label { display: block; font-size: 22rpx; - color: #6B7280; + color: #9CA3AF; margin-bottom: 8rpx; } @@ -1448,14 +1545,15 @@ const deletePortfolio = async () => { display: block; font-size: 28rpx; font-weight: 600; - color: #111827; + font-family: 'DIN Alternate', sans-serif; + color: #1F2937; } .nav-stats .text-red { - color: #EF4444; + color: #059669; } .nav-stats .text-green { - color: #10B981; + color: #DC2626; } \ No newline at end of file