fix: 改用原生 canvas 绘制收益曲线,移除 qiun-data-charts 依赖
- 微信小程序对 qiun-data-charts 兼容性差 - 原生 canvas 更轻量、更可靠 - 实现平滑曲线 + 渐变填充 + 网格线 - Y轴自适应数据范围
This commit is contained in:
parent
632b5b6f6d
commit
97149ca59e
@ -81,13 +81,11 @@
|
|||||||
|
|
||||||
<!-- 收益曲线 -->
|
<!-- 收益曲线 -->
|
||||||
<view v-else class="nav-chart-wrapper">
|
<view v-else class="nav-chart-wrapper">
|
||||||
<qiun-data-charts
|
<canvas
|
||||||
type="line"
|
canvas-id="navChart"
|
||||||
:opts="chartOpts"
|
id="navChart"
|
||||||
:chartData="chartData"
|
class="nav-canvas"
|
||||||
:canvas2d="true"
|
></canvas>
|
||||||
canvasId="navChartCanvas"
|
|
||||||
/>
|
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 统计指标 -->
|
<!-- 统计指标 -->
|
||||||
@ -434,7 +432,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted, getCurrentInstance, computed } from 'vue';
|
import { ref, onMounted, getCurrentInstance, nextTick } from 'vue';
|
||||||
import { api } from '../../utils/api';
|
import { api } from '../../utils/api';
|
||||||
|
|
||||||
// 获取 u-toast 实例
|
// 获取 u-toast 实例
|
||||||
@ -483,51 +481,95 @@ const navPeriod = ref('30d');
|
|||||||
const navHistory = ref([]);
|
const navHistory = ref([]);
|
||||||
const navStatistics = ref(null);
|
const navStatistics = ref(null);
|
||||||
|
|
||||||
// 图表数据
|
// 绘制收益曲线
|
||||||
const chartData = computed(() => {
|
const drawNavChart = () => {
|
||||||
if (!navHistory.value || navHistory.value.length === 0) {
|
const data = navHistory.value;
|
||||||
return { categories: [], series: [] };
|
if (!data || data.length === 0) return;
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
nextTick(() => {
|
||||||
categories: navHistory.value.map(item => item.date.split('-').slice(1).join('/')),
|
const ctx = uni.createCanvasContext('navChart');
|
||||||
series: [{
|
|
||||||
name: '净值',
|
|
||||||
data: navHistory.value.map(item => item.nav)
|
|
||||||
}]
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
// 图表配置
|
// 画布尺寸 (rpx -> px)
|
||||||
const chartOpts = ref({
|
const width = 320;
|
||||||
color: ['#064E3B'],
|
const height = 200;
|
||||||
padding: [15, 15, 0, 15],
|
const padding = { top: 20, right: 20, bottom: 30, left: 45 };
|
||||||
enableScroll: false,
|
const chartWidth = width - padding.left - padding.right;
|
||||||
legend: {
|
const chartHeight = height - padding.top - padding.bottom;
|
||||||
show: false
|
|
||||||
},
|
// 数据范围
|
||||||
xAxis: {
|
const values = data.map(item => item.nav);
|
||||||
disableGrid: true,
|
const minVal = Math.min(...values) * 0.98;
|
||||||
axisLine: false,
|
const maxVal = Math.max(...values) * 1.02;
|
||||||
fontSize: 10,
|
const range = maxVal - minVal || 1;
|
||||||
fontColor: '#6B7280'
|
|
||||||
},
|
// 清空画布
|
||||||
yAxis: {
|
ctx.clearRect(0, 0, width, height);
|
||||||
data: [{ min: 0 }],
|
|
||||||
gridColor: '#E5E7EB',
|
// 绘制网格线
|
||||||
gridType: 'dash',
|
ctx.setStrokeStyle('#E5E7EB');
|
||||||
dashLength: 4,
|
ctx.setLineWidth(0.5);
|
||||||
fontSize: 10,
|
for (let i = 0; i <= 4; i++) {
|
||||||
fontColor: '#6B7280'
|
const y = padding.top + (chartHeight / 4) * i;
|
||||||
},
|
ctx.beginPath();
|
||||||
extra: {
|
ctx.moveTo(padding.left, y);
|
||||||
line: {
|
ctx.lineTo(width - padding.right, y);
|
||||||
type: 'curve',
|
ctx.stroke();
|
||||||
width: 2,
|
|
||||||
activeType: 'hollow'
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
// 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
|
||||||
|
}));
|
||||||
|
|
||||||
|
// 绘制填充区域
|
||||||
|
ctx.beginPath();
|
||||||
|
points.forEach((p, i) => {
|
||||||
|
if (i === 0) ctx.moveTo(p.x, p.y);
|
||||||
|
else ctx.lineTo(p.x, p.y);
|
||||||
|
});
|
||||||
|
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.fill();
|
||||||
|
|
||||||
|
// 绘制曲线
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.setStrokeStyle('#064E3B');
|
||||||
|
ctx.setLineWidth(2);
|
||||||
|
points.forEach((p, i) => {
|
||||||
|
if (i === 0) ctx.moveTo(p.x, p.y);
|
||||||
|
else ctx.lineTo(p.x, p.y);
|
||||||
|
});
|
||||||
|
ctx.stroke();
|
||||||
|
|
||||||
|
// X轴日期标签
|
||||||
|
ctx.setFillStyle('#9CA3AF');
|
||||||
|
ctx.setFontSize(10);
|
||||||
|
ctx.setTextAlign('center');
|
||||||
|
const labelCount = Math.min(5, data.length);
|
||||||
|
const step = Math.floor(data.length / labelCount) || 1;
|
||||||
|
for (let i = 0; i < labelCount; i++) {
|
||||||
|
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.draw();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// 交易表单
|
// 交易表单
|
||||||
const showTransactionForm = ref(false);
|
const showTransactionForm = ref(false);
|
||||||
@ -697,6 +739,11 @@ const fetchNavHistory = async () => {
|
|||||||
if (response.code === 200 && response.data) {
|
if (response.code === 200 && response.data) {
|
||||||
navHistory.value = response.data.navHistory || [];
|
navHistory.value = response.data.navHistory || [];
|
||||||
navStatistics.value = response.data.statistics || null;
|
navStatistics.value = response.data.statistics || null;
|
||||||
|
|
||||||
|
// 数据加载后绘制图表
|
||||||
|
if (navHistory.value.length > 0) {
|
||||||
|
drawNavChart();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取净值历史失败:', error);
|
console.error('获取净值历史失败:', error);
|
||||||
@ -1291,6 +1338,14 @@ const deletePortfolio = async () => {
|
|||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 400rpx;
|
height: 400rpx;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-canvas {
|
||||||
|
width: 640rpx;
|
||||||
|
height: 400rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.period-tabs {
|
.period-tabs {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user