ccy_DIB/pages/DigitalIntelligenceDashboard/components/RouteServiceAreaAnalysis.vue
ylj20011123 c78652a8d1 update
2025-10-23 18:35:54 +08:00

912 lines
24 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="route-service-analysis">
<!-- 报表标题 -->
<view class="report-header">
<text class="report-title">按路线的服务区经营状态分析</text>
<view class="report-period">
<text class="period-label">分析周期</text>
<text class="period-value">{{ analysisPeriod }}</text>
</view>
</view>
<!-- 对比分析切换 -->
<view class="analysis-tabs">
<view class="tab-item"
:class="{ active: activeAnalysisType === 'compare' }"
@click="switchAnalysisType('compare')">
<text class="tab-text">对比分析</text>
</view>
<view class="tab-item"
:class="{ active: activeAnalysisType === 'yearOverYear' }"
@click="switchAnalysisType('yearOverYear')">
<text class="tab-text">同比分析</text>
</view>
<view class="tab-item"
:class="{ active: activeAnalysisType === 'monthOverMonth' }"
@click="switchAnalysisType('monthOverMonth')">
<text class="tab-text">环比分析</text>
</view>
</view>
<!-- 关键指标卡片 -->
<view class="metrics-row">
<view class="metric-card">
<view class="metric-icon revenue"></view>
<text class="metric-title">总营业额</text>
<view class="metric-value-container">
<text class="metric-value">¥{{ formatMoney(totalRevenue) }}</text>
<text class="metric-unit">万元</text>
</view>
<text class="metric-trend" :class="revenueTrend > 0 ? 'positive' : revenueTrend < 0 ? 'negative' : 'neutral'">
{{ formatTrend(revenueTrend) }}
</text>
</view>
<view class="metric-card">
<view class="metric-icon traffic"></view>
<text class="metric-title">总客流量</text>
<view class="metric-value-container">
<text class="metric-value">{{ formatNumber(totalTraffic) }}</text>
<text class="metric-unit">万人次</text>
</view>
<text class="metric-trend" :class="trafficTrend > 0 ? 'positive' : trafficTrend < 0 ? 'negative' : 'neutral'">
{{ formatTrend(trafficTrend) }}
</text>
</view>
<view class="metric-card">
<view class="metric-icon vehicles"></view>
<text class="metric-title">总车流量</text>
<view class="metric-value-container">
<text class="metric-value">{{ formatNumber(totalVehicles) }}</text>
<text class="metric-unit">万辆</text>
</view>
<text class="metric-trend" :class="vehicleTrend > 0 ? 'positive' : vehicleTrend < 0 ? 'negative' : 'neutral'">
{{ formatTrend(vehicleTrend) }}
</text>
</view>
</view>
<!-- 路线营业额对比图 -->
<view class="chart-card">
<view class="chart-header">
<text class="chart-title">各路线营业额{{ getAnalysisTypeName() }}</text>
<text class="chart-subtitle">不同路线服务区营业额表现</text>
</view>
<view class="chart-content">
<view class="bar-chart-container">
<QiunDataCharts
type="column"
:opts="revenueChartOpts"
:chartData="revenueChartData"
:canvas2d="true"
:inScrollView="true"
canvasId="routeRevenueChart"
/>
</view>
</view>
</view>
<!-- 服务区经营状态分布图 -->
<view class="chart-card">
<view class="chart-header">
<text class="chart-title">服务区经营状态分布</text>
<text class="chart-subtitle">各路线服务区经营状态占比</text>
</view>
<view class="chart-content">
<view class="pie-chart-container">
<QiunDataCharts
type="pie"
:opts="statusChartOpts"
:chartData="statusChartData"
:canvas2d="true"
:inScrollView="true"
canvasId="serviceAreaStatusChart"
/>
<view class="pie-legend">
<view class="legend-item" v-for="(item, index) in statusData" :key="index">
<view class="legend-color" :style="{ backgroundColor: item.color }"></view>
<text class="legend-name">{{ item.name }}</text>
<text class="legend-value">{{ item.percentage }}%</text>
</view>
</view>
</view>
</view>
</view>
<!-- 综合指标雷达图 -->
<view class="chart-card">
<view class="chart-header">
<text class="chart-title">服务区综合指标分析</text>
<text class="chart-subtitle">多维度经营指标雷达图</text>
</view>
<view class="chart-content">
<view class="radar-chart-container">
<QiunDataCharts
type="radar"
:opts="radarChartOpts"
:chartData="radarChartData"
:canvas2d="true"
:inScrollView="true"
canvasId="radarChart"
/>
</view>
</view>
</view>
<!-- 服务区详细数据卡片 -->
<view class="chart-card">
<view class="chart-header">
<text class="chart-title">服务区详细数据</text>
<view class="table-actions">
<text class="action-btn" @click="exportData">导出数据</text>
</view>
</view>
<view class="service-area-cards">
<view class="area-card" v-for="(item, index) in detailData" :key="index">
<view class="card-header">
<text class="area-name">{{ item.serviceAreaName }}</text>
<text class="route-name">{{ item.routeName }}</text>
<text class="service-type">{{ item.serviceType }}</text>
</view>
<view class="card-content">
<view class="metrics-row">
<view class="metric-item">
<text class="metric-label">营业额</text>
<text class="metric-value">¥{{ formatMoney(item.revenue) }}</text>
</view>
<view class="metric-item">
<text class="metric-label">增长率</text>
<text :class="'metric-value ' + (item.growthRate > 10 ? 'growth-high' : item.growthRate > 0 ? 'growth-positive' : item.growthRate < -10 ? 'growth-negative' : 'growth-neutral')">
{{ formatGrowth(item.growthRate) }}
</text>
</view>
<view class="metric-item">
<text class="metric-label">运营状态</text>
<text :class="'status-badge ' + (item.operationalStatus === '正常运营' ? 'status-success' : item.operationalStatus === '升级改造' ? 'status-warning' : item.operationalStatus === '暂停营业' ? 'status-error' : 'status-info')">
{{ item.operationalStatus }}
</text>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
import QiunDataCharts from './qiun-data-charts/components/qiun-data-charts/qiun-data-charts.vue'
export default {
components: {
QiunDataCharts
},
data() {
return {
analysisPeriod: '2024年10月',
activeAnalysisType: 'compare',
totalRevenue: 45678,
totalTraffic: 1234,
totalVehicles: 567,
revenueTrend: 12.5,
trafficTrend: 8.3,
vehicleTrend: 15.7,
// 服务区状态分布数据
statusData: [
{ name: '正常运营', value: 65, percentage: 65, color: '#52C41A' },
{ name: '升级改造', value: 15, percentage: 15, color: '#FAAD14' },
{ name: '暂停营业', value: 12, percentage: 12, color: '#FF7875' },
{ name: '规划建设', value: 8, percentage: 8, color: '#576EFF' }
],
// 路线数据
routeData: [
{
routeName: 'G56杭瑞高速',
serviceAreas: ['昆明服务区', '大理服务区', '保山服务区'],
totalRevenue: 12567,
revenue: [4567, 3890, 4110],
traffic: [234, 189, 201],
vehicles: [123, 98, 105]
},
{
routeName: 'G85渝昆高速',
serviceAreas: ['昭通服务区', '曲靖服务区'],
totalRevenue: 9876,
revenue: [5432, 4444],
traffic: [178, 156],
vehicles: [89, 78]
},
{
routeName: 'G80广昆高速',
serviceAreas: ['文山服务区', '红河服务区', '玉溪服务区'],
totalRevenue: 8765,
revenue: [3234, 2789, 2742],
traffic: [145, 123, 134],
vehicles: [67, 56, 62]
},
{
routeName: 'G5京昆高速',
serviceAreas: ['攀枝花服务区', '丽江服务区'],
totalRevenue: 7654,
revenue: [4123, 3531],
traffic: [134, 112],
vehicles: [61, 51]
}
],
// 详细数据
detailData: [
{
routeName: 'G56杭瑞高速',
serviceAreaName: '昆明服务区',
serviceType: 'A类',
operationalStatus: '正常运营',
revenue: 4567,
growthRate: 15.2
},
{
routeName: 'G56杭瑞高速',
serviceAreaName: '大理服务区',
serviceType: 'B类',
operationalStatus: '正常运营',
revenue: 3890,
growthRate: 8.7
},
{
routeName: 'G85渝昆高速',
serviceAreaName: '昭通服务区',
serviceType: 'A类',
operationalStatus: '升级改造',
revenue: 5432,
growthRate: -5.3
},
{
routeName: 'G80广昆高速',
serviceAreaName: '文山服务区',
serviceType: 'C类',
operationalStatus: '正常运营',
revenue: 3234,
growthRate: 22.1
}
]
}
},
computed: {
// 营业额图表数据
revenueChartData() {
return {
categories: this.routeData.map(item => item.routeName),
series: [
{
name: this.activeAnalysisType === 'compare' ? '本期' :
this.activeAnalysisType === 'yearOverYear' ? '今年' : '本月',
data: this.routeData.map(item => item.totalRevenue)
},
...(this.activeAnalysisType !== 'compare' ? [{
name: this.activeAnalysisType === 'yearOverYear' ? '去年' : '上月',
data: this.routeData.map(item => Math.round(item.totalRevenue * 0.85))
}] : [])
]
}
},
// 营业额图表配置
revenueChartOpts() {
return {
color: ['#576EFF', '#52C41A', '#FAAD14'],
padding: [15, 15, 15, 15],
dataLabel: false,
legend: {
show: true,
position: 'top'
},
xAxis: {
itemCount: 3,
scrollShow: true,
scrollAlign: 'right'
},
yAxis: {
gridType: 'dash',
dashLength: 2,
data: [{
min: 0
}]
},
extra: {
column: {
type: 'stack',
width: this.activeAnalysisType === 'compare' ? 40 : 30,
activeBgColor: '#000000',
activeBgOpacity: 0.08,
linearType: 'custom',
barBorderCircle: true
}
}
}
},
// 状态分布图表数据
statusChartData() {
return {
series: [{
data: this.statusData.map(item => ({
name: item.name,
value: item.value
}))
}]
}
},
// 状态分布图表配置
statusChartOpts() {
return {
color: this.statusData.map(item => item.color),
padding: [5, 5, 5, 5],
dataLabel: true,
legend: {
show: false
},
extra: {
pie: {
activeOpacity: 0.5,
activeRadius: 10,
offsetAngle: 0,
labelWidth: 15,
border: false,
borderWidth: 3,
borderColor: '#FFFFFF'
}
}
}
},
// 雷达图数据
radarChartData() {
return {
categories: ['客流量', '车流量', '营业额', '利润率', '增长率', '经营面积'],
series: [
{
name: 'G56杭瑞高速',
data: [85, 78, 92, 68, 75, 88]
},
{
name: 'G85渝昆高速',
data: [72, 85, 76, 82, 65, 70]
},
{
name: 'G80广昆高速',
data: [68, 72, 78, 75, 88, 65]
}
]
}
},
// 雷达图配置
radarChartOpts() {
return {
color: ['#576EFF', '#52C41A', '#FAAD14'],
padding: [15, 15, 15, 15],
dataLabel: false,
legend: {
show: true,
position: 'top'
},
extra: {
radar: {
gridType: 'radar',
gridColor: '#E0E0E0',
gridCount: 4,
opacity: 0.8,
max: 100,
border: false
}
}
}
}
},
methods: {
switchAnalysisType(type) {
this.activeAnalysisType = type;
},
getAnalysisTypeName() {
const names = {
compare: '对比',
yearOverYear: '同比',
monthOverMonth: '环比'
};
return names[this.activeAnalysisType] || '';
},
getTrendClass(trend) {
return trend > 0 ? 'positive' : trend < 0 ? 'negative' : 'neutral';
},
getStatusClass(status) {
const classMap = {
'正常运营': 'success',
'升级改造': 'warning',
'暂停营业': 'error',
'规划建设': 'info'
};
return classMap[status] || '';
},
getGrowthClass(growth) {
return growth > 10 ? 'high' : growth > 0 ? 'positive' : growth < -10 ? 'negative' : 'neutral';
},
formatNumber(num) {
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
},
formatMoney(amount) {
return amount.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
},
formatTrend(trend) {
const sign = trend > 0 ? '↑' : trend < 0 ? '↓' : '→';
return `${sign} ${Math.abs(trend)}%`;
},
formatGrowth(growth) {
const sign = growth > 0 ? '+' : '';
return sign + growth + '%'
},
exportData() {
console.log('导出路线服务区数据');
}
}
}
</script>
<style scoped lang="less">
@primary-color: #667eea;
@success-color: #52c41a;
@warning-color: #faad14;
@error-color: #ff4d4f;
@info-color: #1890ff;
@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);
.route-service-analysis {
.report-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 32rpx;
padding: 0 8rpx;
.report-title {
font-size: 36rpx;
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;
}
}
}
.analysis-tabs {
display: flex;
background: @bg-white;
border-radius: @border-radius;
padding: 8rpx;
margin-bottom: 32rpx;
box-shadow: @shadow;
.tab-item {
flex: 1;
text-align: center;
padding: 16rpx 12rpx;
border-radius: 12rpx;
transition: all 0.3s ease;
&.active {
background: @primary-color;
.tab-text {
color: white;
font-weight: 600;
}
}
.tab-text {
font-size: 26rpx;
color: @text-secondary;
transition: all 0.3s ease;
}
}
}
.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;
.metric-icon {
width: 48rpx;
height: 48rpx;
margin: 0 auto 16rpx;
border-radius: 50%;
position: relative;
&.revenue {
background: linear-gradient(135deg, #faad14, #ffc53d);
&::after { content: '💰'; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); font-size: 24rpx; }
}
&.traffic {
background: linear-gradient(135deg, #52c41a, #73d13d);
&::after { content: '👥'; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); font-size: 24rpx; }
}
&.vehicles {
background: linear-gradient(135deg, #1890ff, #40a9ff);
&::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 {
margin-bottom: 8rpx;
.metric-value {
font-size: 32rpx;
font-weight: 600;
color: @text-primary;
display: block;
}
.metric-unit {
font-size: 20rpx;
color: @text-light;
}
}
.metric-trend {
font-size: 22rpx;
font-weight: 500;
&.positive {
color: @success-color;
}
&.negative {
color: @error-color;
}
&.neutral {
color: @text-secondary;
}
}
}
}
.chart-card {
background: @bg-white;
border-radius: @border-radius;
padding: 24rpx;
box-shadow: @shadow;
margin-bottom: 24rpx;
.chart-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 20rpx;
.chart-title {
font-size: 28rpx;
font-weight: 600;
color: @text-primary;
}
.chart-subtitle {
font-size: 22rpx;
color: @text-light;
margin-top: 4rpx;
}
.table-actions {
display: flex;
gap: 16rpx;
.action-btn {
font-size: 24rpx;
color: @primary-color;
padding: 8rpx 16rpx;
border: 1rpx solid @primary-color;
border-radius: 8rpx;
}
}
}
.chart-content {
.pie-chart-container {
display: flex;
flex-direction: column;
align-items: center;
.pie-legend {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 16rpx;
margin-top: 16rpx;
.legend-item {
display: flex;
align-items: center;
gap: 8rpx;
.legend-color {
width: 12rpx;
height: 12rpx;
border-radius: 2rpx;
}
.legend-name {
font-size: 22rpx;
color: @text-secondary;
}
.legend-value {
font-size: 22rpx;
color: @text-primary;
font-weight: 600;
}
}
}
}
.bar-chart-container,
.radar-chart-container {
width: 100%;
}
}
.data-table {
.table-header {
display: flex;
background: #f5f7fa;
border-radius: 8rpx 8rpx 0 0;
font-weight: 600;
.header-cell {
flex: 1;
padding: 16rpx 12rpx;
font-size: 24rpx;
color: @text-primary;
text-align: center;
border-right: 1rpx solid #e0e0e0;
&:last-child {
border-right: none;
}
}
}
.table-body {
.table-row {
display: flex;
border-bottom: 1rpx solid #f0f0f0;
&:last-child {
border-bottom: none;
}
.body-cell {
flex: 1;
padding: 16rpx 12rpx;
font-size: 24rpx;
color: @text-secondary;
text-align: center;
border-right: 1rpx solid #f0f0f0;
&:last-child {
border-right: none;
}
.status-tag {
padding: 4rpx 12rpx;
border-radius: 12rpx;
font-size: 20rpx;
color: white;
font-weight: 500;
&.success {
background: @success-color;
}
&.warning {
background: @warning-color;
}
&.error {
background: @error-color;
}
&.info {
background: @info-color;
}
}
.growth-rate {
font-weight: 600;
&.high {
color: @success-color;
}
&.positive {
color: @success-color;
}
&.negative {
color: @error-color;
}
&.neutral {
color: @text-secondary;
}
}
}
}
}
}
.service-area-cards {
display: flex;
flex-direction: column;
gap: 16rpx;
.area-card {
background: #f8f9fa;
border-radius: 12rpx;
padding: 20rpx;
border: 1rpx solid #e9ecef;
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16rpx;
padding-bottom: 12rpx;
border-bottom: 1rpx solid #e9ecef;
.area-name {
font-size: 28rpx;
font-weight: 600;
color: @text-primary;
flex: 1;
}
.route-name {
font-size: 22rpx;
color: @text-secondary;
background: #e3f2fd;
padding: 4rpx 12rpx;
border-radius: 8rpx;
margin: 0 8rpx;
}
.service-type {
font-size: 22rpx;
color: @primary-color;
background: #f0f5ff;
padding: 4rpx 12rpx;
border-radius: 8rpx;
font-weight: 500;
}
}
.card-content {
.metrics-row {
display: flex;
justify-content: space-between;
gap: 16rpx;
.metric-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
.metric-label {
font-size: 22rpx;
color: @text-secondary;
margin-bottom: 8rpx;
}
.metric-value {
font-size: 26rpx;
font-weight: 600;
color: @text-primary;
&.growth-high {
color: #52c41a;
}
&.growth-positive {
color: #52c41a;
}
&.growth-negative {
color: #ff4d4f;
}
&.growth-neutral {
color: @text-secondary;
}
}
.status-badge {
padding: 6rpx 16rpx;
border-radius: 16rpx;
font-size: 22rpx;
color: white;
font-weight: 500;
&.status-success {
background: @success-color;
}
&.status-warning {
background: @warning-color;
}
&.status-error {
background: @error-color;
}
&.status-info {
background: @info-color;
}
}
}
}
}
}
}
}
}
</style>