Files
autoAiWorkSys/app/components/DeliveryTrendChart.vue
张成 e17d5610f5 1
2025-12-22 16:26:59 +08:00

224 lines
5.8 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>
<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>