ylj20011123 d832cfa05d update
2025-10-30 11:38:09 +08:00

485 lines
12 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>
<view class="trend-traffic-flow">
<view class="chart-header">
<text class="chart-title">断面流量</text>
</view>
<!-- 断面流量趋势图 -->
<view class="chart-card">
<view class="chart-content">
<view class="bar-chart-container">
<!-- 图表加载效果 -->
<ChartLoading v-if="isLoading" text="数据加载中..." />
<!-- 实际图表 -->
<QiunDataCharts v-else type="column" :opts="chartOpts" :chartData="chartData" :animation="false"
:canvas2d="true" :inScrollView="true" :ontap="true" :ontouch="true" canvasId="trendTrafficFlowChart"
tooltipFormat="TrendTrafficFlow" />
</view>
</view>
</view>
</view>
</template>
<script>
import QiunDataCharts from './qiun-data-charts/components/qiun-data-charts/qiun-data-charts.vue'
import ChartLoading from './ChartLoading.vue'
import request from "@/util/index.js";
import moment from 'moment'
export default {
components: {
QiunDataCharts,
ChartLoading
},
data() {
return {
isLoading: false,
currentYear: moment().format('YYYY'),
previousYear: moment().subtract(1, 'y').format('YYYY'),
// 原始数据
currentYearData: [],
previousYearData: [],
// 处理后的月度数据
monthlyData: [],
// 统计数据
currentYearTotal: 0,
previousYearTotal: 0,
averageGrowthRate: '0.00'
}
},
props: {
selectTime: {
type: String,
default: ""
},
},
computed: {
// 判断图表数据是否已加载
hasChartData() {
// 检查是否有真实的流量数据至少有一个月的数据大于0
return this.monthlyData && this.monthlyData.length > 0 &&
this.monthlyData.some(item => item.currentYearValue > 0 || item.previousYearValue > 0);
},
// 图表数据
chartData() {
return {
categories: this.monthlyData.map(item => item.month),
series: [
{
name: `${this.currentYear}年断面流量`,
data: this.monthlyData.map(item => item.currentYearValue)
},
{
name: `${this.previousYear}年断面流量`,
data: this.monthlyData.map(item => item.previousYearValue)
}
]
}
},
// 图表配置
chartOpts() {
return {
padding: [15, 0, 0, 5],
dataLabel: false, // 关闭数据标签显示
// enableScroll: true, // 开启滚动
xAxis: {
disableGrid: true,
itemCount: 12, // 默认显示6个月的数据
fontSize: 10
},
yAxis: {
showTitle: true,
data: [{
min: 0,
title: '断面流量(万辆)',
titleFontSize: 12,
titleOffsetY: -5,
titleOffsetX: -5,
}]
},
legend: {},
extra: {
column: {
type: 'group',
width: 6, // 柱子宽度调小一点
seriesGap: 0, // 同一组别的柱子紧贴在一起
barBorderRadius: [3, 3, 0, 0]
}
}
}
}
},
watch: {
selectTime: {
handler(newVal, oldVal) {
if (newVal !== oldVal) {
this.handleGetTrafficFlowData()
}
},
immediate: false
}
},
onReady() {
// 获取数据
this.handleGetTrafficFlowData()
},
methods: {
// 获取断面流量数据
async handleGetTrafficFlowData() {
try {
this.isLoading = true
// 并行调用两个API
const [currentData, previousData] = await Promise.all([
this.getCurrentYearData(),
this.getPreviousYearData()
]);
this.isLoading = false
// 处理数据
this.processTrafficData(currentData.Result_Data.List || [], previousData.Result_Data.List || []);
} catch (error) {
console.error('获取断面流量数据失败:', error);
this.isLoading = false
}
},
// 获取当年数据
async getCurrentYearData() {
const currentReq = {
ProvinceCode: '530000',
StatisticsDate: this.selectTime ? moment(this.selectTime).endOf('M').format('YYYY-MM-DD') : moment().format('YYYY-MM-DD'),
StartDate: this.selectTime ? moment(this.selectTime).startOf('y').format('YYYY-MM-DD') : moment().startOf('y').format('YYYY-MM-DD'),
EndDate: this.selectTime ? moment(this.selectTime).endOf('M').format('YYYY-MM-DD') : moment().endOf('M').format('YYYY-MM-DD'),
}
return await request.$webGet(
"CommercialApi/BigData/GetMonthAnalysis",
currentReq
);
},
// 获取去年数据
async getPreviousYearData() {
const previousReq = {
ProvinceCode: '530000',
StatisticsDate: this.selectTime ? moment(this.selectTime).subtract(1, 'y').endOf('M').format('YYYY-MM-DD') : moment().subtract(1, 'y').endOf('M').format('YYYY-MM-DD'),
StartDate: this.selectTime ? moment(this.selectTime).subtract(1, 'y').startOf('y').format('YYYY-MM-DD') : moment().subtract(1, 'y').startOf('y').format('YYYY-MM-DD'),
EndDate: this.selectTime ? moment(this.selectTime).subtract(1, 'y').endOf('M').format('YYYY-MM-DD') : moment().subtract(1, 'y').endOf('M').format('YYYY-MM-DD'),
}
return await request.$webGet(
"CommercialApi/BigData/GetMonthAnalysis",
previousReq
);
},
// 处理流量数据
processTrafficData(currentData, previousData) {
const monthlyData = [];
let currentYearTotal = 0;
let previousYearTotal = 0;
let totalGrowthRate = 0;
let validMonthCount = 0;
// 处理当年数据
const currentDataMap = {};
if (currentData && currentData.length > 0) {
currentData.forEach(item => {
const month = `${item.Statistics_Month}`;
const valueInWan = Number((item.SectionFlow_Count / 10000).toFixed(2));
currentDataMap[month] = {
value: valueInWan,
realValue: item.SectionFlow_Count
};
currentYearTotal += item.SectionFlow_Count;
});
}
// 处理去年数据并生成月度对比
const previousDataMap = {};
if (previousData && previousData.length > 0) {
previousData.forEach(item => {
const month = `${item.Statistics_Month}`;
const valueInWan = Number((item.SectionFlow_Count / 10000).toFixed(2));
previousDataMap[month] = {
value: valueInWan,
realValue: item.SectionFlow_Count
};
previousYearTotal += item.SectionFlow_Count;
});
}
// 生成12个月的对比数据
for (let i = 1; i <= 12; i++) {
const month = `${i}`;
const currentMonthData = currentDataMap[month] || { value: 0, realValue: 0 };
const previousMonthData = previousDataMap[month] || { value: 0, realValue: 0 };
let growthRate = '0.00';
if (previousMonthData.realValue > 0 && currentMonthData.realValue > 0) {
growthRate = (((currentMonthData.realValue - previousMonthData.realValue) / previousMonthData.realValue) * 100).toFixed(2);
totalGrowthRate += parseFloat(growthRate);
validMonthCount++;
}
monthlyData.push({
month: month,
currentYearValue: currentMonthData.value,
previousYearValue: previousMonthData.value,
growthRate: growthRate
});
}
// 计算平均增长率
const averageGrowth = validMonthCount > 0 ? (totalGrowthRate / validMonthCount).toFixed(2) : '0.00';
// 更新数据
this.monthlyData = monthlyData;
this.currentYearTotal = currentYearTotal;
this.previousYearTotal = previousYearTotal;
this.averageGrowthRate = averageGrowth;
},
// 获取增长率样式类
getGrowthClass(growthRate) {
const rate = parseFloat(growthRate);
if (rate > 0) {
return 'growth-positive';
} else if (rate < 0) {
return 'growth-negative';
} else {
return 'growth-neutral';
}
},
// 格式化数字
formatNumber(num) {
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
}
}
</script>
<style scoped lang="less">
@primary-color: #46B8F3;
@secondary-color: #3CD495;
@danger-color: #FF5E5E;
@success-color: #52C41A;
@text-primary: #333;
@text-secondary: #666;
@text-light: #999;
@bg-white: #ffffff;
@border-radius: 16rpx;
@shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
.trend-traffic-flow {
margin-top: 24rpx;
.report-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 32rpx;
padding: 0 8rpx;
.report-title {
font-size: 30rpx;
font-weight: 600;
color: @text-primary;
}
.report-period {
display: flex;
align-items: center;
.period-label {
font-size: 24rpx;
color: @text-secondary;
margin-right: 8rpx;
}
.period-value {
font-size: 24rpx;
color: @primary-color;
font-weight: 500;
}
}
}
.metrics-row {
display: flex;
gap: 24rpx;
margin-bottom: 32rpx;
.metric-card {
flex: 1;
background: @bg-white;
border-radius: @border-radius;
padding: 32rpx 24rpx;
box-shadow: @shadow;
text-align: center;
position: relative;
overflow: hidden;
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4rpx;
background: linear-gradient(90deg, @primary-color, @secondary-color);
}
.metric-icon {
width: 48rpx;
height: 48rpx;
margin: 0 auto 16rpx;
border-radius: 50%;
position: relative;
&.current {
background: linear-gradient(135deg, #46B8F3, #1A4AF6);
&::after {
content: '📊';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 24rpx;
}
}
&.previous {
background: linear-gradient(135deg, #3CD495, #2BAE66);
&::after {
content: '📈';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 24rpx;
}
}
&.growth {
background: linear-gradient(135deg, #52C41A, #73D13D);
&::after {
content: '📊';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 24rpx;
}
}
}
.metric-title {
font-size: 24rpx;
color: @text-secondary;
margin-bottom: 12rpx;
}
.metric-value-container {
display: flex;
flex-direction: column;
align-items: center;
gap: 4rpx;
}
.metric-value {
font-size: 28rpx;
font-weight: 600;
color: @text-primary;
font-family: 'DINAlternate-Bold', sans-serif;
line-height: 1;
}
.metric-unit {
font-size: 24rpx;
color: @text-light;
line-height: 1;
}
}
}
.chart-header {
display: flex;
flex-direction: column;
margin-bottom: 20rpx;
.chart-title {
font-size: 30rpx;
font-weight: 600;
color: @text-primary;
}
.chart-subtitle {
font-size: 24rpx;
color: @text-light;
margin-top: 4rpx;
}
}
.chart-card {
background: @bg-white;
border-radius: @border-radius;
padding: 24rpx;
box-shadow: @shadow;
margin-bottom: 24rpx;
.chart-content {
.bar-chart-container {
width: 100%;
height: 400rpx;
}
}
}
.chart-legend {
display: flex;
justify-content: center;
gap: 32rpx;
margin-top: 16rpx;
.legend-item {
display: flex;
align-items: center;
gap: 8rpx;
.legend-dot {
width: 12rpx;
height: 12rpx;
border-radius: 2rpx;
&.current-year {
background: linear-gradient(135deg, #46B8F3, #1A4AF6);
}
&.previous-year {
background: #3CD495;
}
}
.legend-text {
font-size: 22rpx;
color: @text-secondary;
}
}
}
}
</style>