This commit is contained in:
张成
2025-12-22 16:26:59 +08:00
parent aa2d03ee30
commit e17d5610f5
54 changed files with 11735 additions and 3 deletions

View File

@@ -0,0 +1,223 @@
<template>
<div class="delivery-trend-chart">
<div class="chart-container">
<canvas ref="chartCanvas" :width="chartWidth" :height="chartHeight"></canvas>
</div>
<div class="chart-legend">
<div v-for="(item, index) in chartData" :key="index" class="legend-item">
<span class="legend-date">{{ item.date }}</span>
<span class="legend-value">{{ item.value }}</span>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'DeliveryTrendChart',
props: {
data: {
type: Array,
default: () => []
}
},
data() {
return {
chartWidth: 600,
chartHeight: 200,
padding: { top: 20, right: 20, bottom: 30, left: 40 }
};
},
computed: {
chartData() {
// 如果没有数据生成7天的空数据
if (!this.data || this.data.length === 0) {
const today = new Date();
return Array.from({ length: 7 }, (_, i) => {
const date = new Date(today);
date.setDate(date.getDate() - (6 - i));
return {
date: this.formatDate(date),
value: 0
};
});
}
return this.data;
},
maxValue() {
const values = this.chartData.map(item => item.value);
const max = Math.max(...values, 1); // 至少为1避免除零
return Math.ceil(max * 1.2); // 增加20%的顶部空间
}
},
mounted() {
this.drawChart();
},
watch: {
chartData: {
handler() {
this.$nextTick(() => {
this.drawChart();
});
},
deep: true
}
},
methods: {
formatDate(date) {
const month = date.getMonth() + 1;
const day = date.getDate();
return `${month}/${day}`;
},
drawChart() {
const canvas = this.$refs.chartCanvas;
if (!canvas) return;
const ctx = canvas.getContext('2d');
const { width, height } = canvas;
const { padding } = this;
// 清空画布
ctx.clearRect(0, 0, width, height);
// 计算绘图区域
const chartWidth = width - padding.left - padding.right;
const chartHeight = height - padding.top - padding.bottom;
// 绘制背景
ctx.fillStyle = '#f9f9f9';
ctx.fillRect(padding.left, padding.top, chartWidth, chartHeight);
// 绘制网格线
ctx.strokeStyle = '#e0e0e0';
ctx.lineWidth = 1;
// 水平网格线5条
for (let i = 0; i <= 5; i++) {
const y = padding.top + (chartHeight / 5) * i;
ctx.beginPath();
ctx.moveTo(padding.left, y);
ctx.lineTo(padding.left + chartWidth, y);
ctx.stroke();
}
// 垂直网格线7条对应7天
for (let i = 0; i <= 7; i++) {
const x = padding.left + (chartWidth / 7) * i;
ctx.beginPath();
ctx.moveTo(x, padding.top);
ctx.lineTo(x, padding.top + chartHeight);
ctx.stroke();
}
// 绘制数据点和折线
if (this.chartData.length > 0) {
const points = this.chartData.map((item, index) => {
const x = padding.left + (chartWidth / (this.chartData.length - 1)) * index;
const y = padding.top + chartHeight - (item.value / this.maxValue) * chartHeight;
return { x, y, value: item.value };
});
// 绘制折线
ctx.strokeStyle = '#4CAF50';
ctx.lineWidth = 2;
ctx.beginPath();
points.forEach((point, index) => {
if (index === 0) {
ctx.moveTo(point.x, point.y);
} else {
ctx.lineTo(point.x, point.y);
}
});
ctx.stroke();
// 绘制数据点
ctx.fillStyle = '#4CAF50';
points.forEach(point => {
ctx.beginPath();
ctx.arc(point.x, point.y, 4, 0, Math.PI * 2);
ctx.fill();
// 显示数值
ctx.fillStyle = '#666';
ctx.font = '12px Arial';
ctx.textAlign = 'center';
ctx.fillText(point.value.toString(), point.x, point.y - 10);
ctx.fillStyle = '#4CAF50';
});
// 绘制区域填充(渐变)
const gradient = ctx.createLinearGradient(
padding.left,
padding.top,
padding.left,
padding.top + chartHeight
);
gradient.addColorStop(0, 'rgba(76, 175, 80, 0.2)');
gradient.addColorStop(1, 'rgba(76, 175, 80, 0.05)');
ctx.fillStyle = gradient;
ctx.beginPath();
ctx.moveTo(padding.left, padding.top + chartHeight);
points.forEach(point => {
ctx.lineTo(point.x, point.y);
});
ctx.lineTo(padding.left + chartWidth, padding.top + chartHeight);
ctx.closePath();
ctx.fill();
}
// 绘制Y轴标签
ctx.fillStyle = '#666';
ctx.font = '11px Arial';
ctx.textAlign = 'right';
for (let i = 0; i <= 5; i++) {
const value = Math.round((this.maxValue / 5) * (5 - i));
const y = padding.top + (chartHeight / 5) * i;
ctx.fillText(value.toString(), padding.left - 10, y + 4);
}
}
}
};
</script>
<style lang="less" scoped>
.delivery-trend-chart {
.chart-container {
width: 100%;
overflow-x: auto;
canvas {
display: block;
max-width: 100%;
}
}
.chart-legend {
display: flex;
justify-content: space-around;
padding: 10px 0;
border-top: 1px solid #f0f0f0;
margin-top: 10px;
.legend-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
.legend-date {
font-size: 11px;
color: #999;
}
.legend-value {
font-size: 13px;
font-weight: 600;
color: #4CAF50;
}
}
}
}
</style>