ylj20011123 309cdacdc9 update
2025-11-18 17:52:08 +08:00

559 lines
14 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>
<!-- AI分析按钮 -->
<view class="ai-analysis-wrapper">
<!-- <AIAnalysis :chartInfo="chartInfo" @disableScroll="handleDisableScroll" @enableScroll="handleEnableScroll" /> -->
</view>
</view>
<!-- 断面流量趋势图 -->
<view class="chart-card">
<view class="chart-content" id="traffic-chart">
<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 AIAnalysis from './AIAnalysis.vue'
import request from "@/util/index.js";
import moment from 'moment'
export default {
components: {
QiunDataCharts,
ChartLoading,
AIAnalysis
},
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',
// AI分析相关
chartInfo: {
width: 0,
height: 0
}
}
},
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()
// 获取图表尺寸
this.getChartDimensions()
},
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个月的对比数据
if (currentData && currentData.length > 0) {
currentData.forEach(item => {
const month = `${item.Statistics_Month}`;
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
});
});
}
// 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, ",");
},
// 获取图表尺寸
getChartDimensions() {
this.$nextTick(() => {
const query = uni.createSelectorQuery().in(this)
query.select('#traffic-chart').boundingClientRect()
query.exec((res) => {
if (res[0]) {
this.chartInfo = {
width: Math.round(res[0].width),
height: Math.round(res[0].height)
}
}
})
})
},
// 处理禁用滚动
handleDisableScroll() {
// 通过uni全局事件通知主页面禁用滚动
uni.$emit('disableScroll')
},
// 处理启用滚动
handleEnableScroll() {
// 通过uni全局事件通知主页面启用滚动
uni.$emit('enableScroll')
}
}
}
</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;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
position: relative;
.chart-title {
font-size: 30rpx;
font-weight: 600;
color: @text-primary;
}
.chart-subtitle {
font-size: 24rpx;
color: @text-light;
margin-top: 4rpx;
}
.ai-analysis-wrapper {
position: absolute;
top: -8rpx;
right: 0;
z-index: 10;
}
}
.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>