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

964 lines
26 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="province-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="metrics-grid">
<view class="metric-card">
<view class="metric-icon total-areas"></view>
<text class="metric-title">服务区总数</text>
<view class="metric-value-container">
<text class="metric-value">{{ totalServiceAreas }}</text>
<text class="metric-unit"></text>
</view>
<text class="metric-desc">覆盖全省主要高速</text>
</view>
<view class="metric-card">
<view class="metric-icon total-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 avg-profit"></view>
<text class="metric-title">平均利润率</text>
<view class="metric-value-container">
<text class="metric-value">{{ avgProfitRate }}%</text>
<text class="metric-unit">百分比</text>
</view>
<text class="metric-trend" :class="profitTrend > 0 ? 'positive' : profitTrend < 0 ? 'negative' : 'neutral'">
{{ formatTrend(profitTrend) }}
</text>
</view>
<view class="metric-card">
<view class="metric-icon total-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>
<!-- 服务区类型分布饼图 -->
<view class="chart-card">
<view class="chart-header">
<text class="chart-title">服务区类型分布</text>
<text class="chart-subtitle">A类B类C类服务区数量占比</text>
</view>
<view class="chart-content">
<view class="pie-chart-container">
<QiunDataCharts
type="pie"
:opts="typeDistributionChartOpts"
:chartData="typeDistributionChartData"
:canvas2d="true"
:inScrollView="true"
canvasId="typeDistributionChart"
/>
<view class="pie-legend">
<view class="legend-item" v-for="(item, index) in typeDistributionData" :key="index">
<view class="legend-color" :style="{ backgroundColor: item.color }"></view>
<text class="legend-name">{{ item.name }}</text>
<text class="legend-value">{{ item.count }}个({{ 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="column-chart-container">
<QiunDataCharts
type="column"
:opts="statusChartOpts"
:chartData="statusChartData"
:canvas2d="true"
:inScrollView="true"
canvasId="statusChart"
/>
</view>
</view>
</view>
<!-- 全省服务区营业额排名 -->
<view class="chart-card">
<view class="chart-header">
<text class="chart-title">服务区营业额TOP15</text>
<text class="chart-subtitle">全省营业额最高的服务区排行</text>
</view>
<view class="chart-content">
<view class="ranking-chart-container">
<QiunDataCharts
type="column"
:opts="rankingChartOpts"
:chartData="rankingChartData"
:canvas2d="true"
:inScrollView="true"
canvasId="rankingChart"
/>
</view>
</view>
</view>
<!-- 月度趋势分析图 -->
<view class="chart-card">
<view class="chart-header">
<text class="chart-title">月度经营趋势分析</text>
<text class="chart-subtitle">最近12个月营业额和客流量变化</text>
</view>
<view class="chart-content">
<view class="line-chart-container">
<QiunDataCharts
type="line"
:opts="trendChartOpts"
:chartData="trendChartData"
:canvas2d="true"
:inScrollView="true"
canvasId="trendChart"
/>
<view class="chart-legend">
<view class="legend-item">
<view class="legend-dot revenue"></view>
<text class="legend-text">营业额(万元)</text>
</view>
<view class="legend-item">
<view class="legend-dot traffic"></view>
<text class="legend-text">客流量(万人次)</text>
</view>
<view class="legend-item">
<view class="legend-dot profit"></view>
<text class="legend-text">利润率(%)</text>
</view>
</view>
</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="province-service-cards">
<view class="service-card" v-for="(item, index) in detailData" :key="index">
<view class="card-header">
<view class="service-info">
<text class="service-name">{{ item.serviceAreaName }}</text>
<view class="service-badges">
<text class="type-badge"
:class="item.serviceType === 'A类' ? 'type-a' :
item.serviceType === 'B类' ? 'type-b' :
item.serviceType === 'C类' ? 'type-c' : ''">
{{ item.serviceType }}
</text>
<text class="status-badge"
:class="item.operationalStatus === '正常运营' ? 'success' :
item.operationalStatus === '升级改造' ? 'warning' :
item.operationalStatus === '暂停营业' ? 'error' : 'info'">
{{ item.operationalStatus }}
</text>
</view>
</view>
</view>
<view class="card-content">
<view class="metrics-grid">
<view class="metric-item">
<text class="metric-label">经营面积</text>
<text class="metric-value">{{ item.businessArea }}</text>
</view>
<view class="metric-item">
<text class="metric-label">客流量</text>
<text class="metric-value">{{ formatNumber(item.passengerTraffic) }}</text>
</view>
<view class="metric-item">
<text class="metric-label">车流量</text>
<text class="metric-value">{{ formatNumber(item.vehicleTraffic) }}</text>
</view>
<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 profit-rate"
:class="item.profitRate >= 15 ? 'high' :
item.profitRate >= 10 ? 'positive' :
item.profitRate >= 5 ? 'neutral' : 'negative'">
{{ formatProfit(item.profitRate) }}
</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月',
totalServiceAreas: 85,
totalRevenue: 123456,
avgProfitRate: 15.8,
totalTraffic: 5678,
revenueTrend: 12.3,
profitTrend: 2.1,
trafficTrend: 8.7,
// 服务区类型分布数据
typeDistributionData: [
{ name: 'A类服务区', count: 18, percentage: 21, color: '#576EFF' },
{ name: 'B类服务区', count: 35, percentage: 41, color: '#52C41A' },
{ name: 'C类服务区', count: 32, percentage: 38, color: '#FAAD14' }
],
// 经营状态数据
statusData: [
{ name: '正常运营', count: 65, percentage: 76, color: '#52C41A' },
{ name: '升级改造', count: 8, percentage: 9, color: '#FAAD14' },
{ name: '暂停营业', count: 7, percentage: 8, color: '#FF7875' },
{ name: '规划建设', count: 5, percentage: 7, color: '#576EFF' }
],
// 营业额排名数据
rankingData: [
{ name: '昆明服务区', revenue: 8976 },
{ name: '曲靖服务区', revenue: 7654 },
{ name: '大理服务区', revenue: 6543 },
{ name: '玉溪服务区', revenue: 5432 },
{ name: '红河服务区', revenue: 4876 },
{ name: '保山服务区', revenue: 4321 },
{ name: '昭通服务区', revenue: 3987 },
{ name: '文山服务区', revenue: 3543 },
{ name: '普洱服务区', revenue: 3210 },
{ name: '临沧服务区', revenue: 2987 },
{ name: '楚雄服务区', revenue: 2765 },
{ name: '西双版纳服务区', revenue: 2543 },
{ name: '德宏服务区', revenue: 2321 },
{ name: '怒江服务区', revenue: 2109 },
{ name: '迪庆服务区', revenue: 1987 }
],
// 月度趋势数据
monthlyTrendData: {
months: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
revenue: [8234, 7890, 9123, 9876, 10234, 11567, 12345, 11876, 10987, 12345, 11678, 13234],
traffic: [456, 423, 489, 512, 534, 587, 623, 598, 567, 612, 587, 654],
profitRate: [12.1, 11.8, 13.2, 14.1, 14.8, 15.2, 15.8, 15.3, 14.9, 15.8, 15.1, 16.2]
},
// 详细数据
detailData: [
{
serviceAreaName: '昆明服务区',
serviceType: 'A类',
operationalStatus: '正常运营',
businessArea: 8500,
passengerTraffic: 156.7,
vehicleTraffic: 45.6,
revenue: 8976,
profitRate: 18.5
},
{
serviceAreaName: '曲靖服务区',
serviceType: 'A类',
operationalStatus: '正常运营',
businessArea: 7800,
passengerTraffic: 134.5,
vehicleTraffic: 38.9,
revenue: 7654,
profitRate: 16.2
},
{
serviceAreaName: '大理服务区',
serviceType: 'A类',
operationalStatus: '升级改造',
businessArea: 9200,
passengerTraffic: 98.7,
vehicleTraffic: 28.4,
revenue: 6543,
profitRate: -2.3
},
{
serviceAreaName: '玉溪服务区',
serviceType: 'B类',
operationalStatus: '正常运营',
businessArea: 5500,
passengerTraffic: 112.3,
vehicleTraffic: 32.1,
revenue: 5432,
profitRate: 14.8
},
{
serviceAreaName: '红河服务区',
serviceType: 'B类',
operationalStatus: '正常运营',
businessArea: 6200,
passengerTraffic: 87.6,
vehicleTraffic: 25.3,
revenue: 4876,
profitRate: 13.5
}
]
}
},
computed: {
// 类型分布饼图数据
typeDistributionChartData() {
return {
series: [{
data: this.typeDistributionData.map(item => ({
name: item.name,
value: item.count
}))
}]
}
},
// 类型分布饼图配置
typeDistributionChartOpts() {
return {
color: this.typeDistributionData.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'
}
}
}
},
// 经营状态图表数据
statusChartData() {
return {
categories: this.statusData.map(item => item.name),
series: [{
name: '服务区数量',
data: this.statusData.map(item => item.count)
}]
}
},
// 经营状态图表配置
statusChartOpts() {
return {
color: this.statusData.map(item => item.color),
padding: [15, 15, 15, 15],
dataLabel: true,
legend: {
show: false
},
xAxis: {
itemCount: 3,
scrollShow: true,
scrollAlign: 'right'
},
yAxis: {
gridType: 'dash',
dashLength: 2,
data: [{
min: 0
}]
},
extra: {
column: {
type: 'group',
width: 40,
activeBgColor: '#000000',
activeBgOpacity: 0.08,
linearType: 'custom',
barBorderCircle: true
}
}
}
},
// 排名图表数据
rankingChartData() {
return {
categories: this.rankingData.map(item =>
item.name.length > 6 ? item.name.substring(0, 6) + '...' : item.name
),
series: [{
name: '营业额(万元)',
data: this.rankingData.map(item => item.revenue)
}]
}
},
// 排名图表配置
rankingChartOpts() {
return {
color: ['#576EFF'],
padding: [15, 15, 15, 15],
dataLabel: false,
enableScroll: true,
xAxis: {
itemCount: 5,
scrollShow: true,
scrollAlign: 'right',
scrollColor: '#576EFF',
scrollBackgroundColor: 'rgba(87, 110, 255, 0.1)',
scrollWidth: 4,
scrollHeight: 8,
rotateLabel: true,
rotateAngle: 45
},
yAxis: {
gridType: 'dash',
dashLength: 2,
data: [{
min: 0
}]
},
extra: {
column: {
type: 'group',
width: 20,
activeBgColor: '#000000',
activeBgOpacity: 0.08,
linearType: 'custom',
barBorderCircle: true
}
}
}
},
// 趋势图数据
trendChartData() {
return {
categories: this.monthlyTrendData.months,
series: [
{
name: '营业额',
data: this.monthlyTrendData.revenue
},
{
name: '客流量',
data: this.monthlyTrendData.traffic.map(v => v * 20) // 缩放客流量数据
},
{
name: '利润率',
data: this.monthlyTrendData.profitRate.map(v => v * 600) // 缩放利润率数据
}
]
}
},
// 趋势图配置
trendChartOpts() {
return {
color: ['#576EFF', '#52C41A', '#FAAD14'],
padding: [15, 15, 15, 15],
dataLabel: false,
legend: {
show: false
},
xAxis: {
itemCount: 6,
scrollShow: true,
scrollAlign: 'right',
rotateLabel: true,
rotateAngle: 45
},
yAxis: {
gridType: 'dash',
dashLength: 2,
data: [
{
min: 0,
position: 'left'
},
{
min: 0,
position: 'right'
}
]
},
extra: {
line: {
type: 'curve',
width: 2,
activeType: 'hollow'
}
}
}
}
},
methods: {
getTrendClass(trend) {
return trend > 0 ? 'positive' : trend < 0 ? 'negative' : 'neutral';
},
getTypeClass(type) {
const classMap = {
'A类': 'type-a',
'B类': 'type-b',
'C类': 'type-c'
};
return classMap[type] || '';
},
getStatusClass(status) {
const classMap = {
'正常运营': 'success',
'升级改造': 'warning',
'暂停营业': 'error',
'规划建设': 'info'
};
return classMap[status] || '';
},
getProfitClass(profit) {
if (profit >= 15) return 'high';
if (profit >= 10) return 'positive';
if (profit >= 5) return 'neutral';
return 'negative';
},
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) + '%'
},
formatProfit(profit) {
return profit + '%'
},
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);
.province-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;
}
}
}
.metrics-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 24rpx;
margin-bottom: 32rpx;
.metric-card {
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;
&.total-areas {
background: linear-gradient(135deg, #576EFF, #7C8FFF);
&::after { content: '🏪'; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); font-size: 24rpx; }
}
&.total-revenue {
background: linear-gradient(135deg, #52C41A, #73D13D);
&::after { content: '💰'; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); font-size: 24rpx; }
}
&.avg-profit {
background: linear-gradient(135deg, #FAAD14, #FFC53D);
&::after { content: '📈'; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); font-size: 24rpx; }
}
&.total-traffic {
background: linear-gradient(135deg, #FF7875, #FF9C99);
&::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-desc {
font-size: 20rpx;
color: @text-light;
}
.metric-trend {
font-size: 22rpx;
font-weight: 500;
margin-top: 4rpx;
&.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;
}
}
}
}
.column-chart-container,
.ranking-chart-container,
.line-chart-container {
width: 100%;
}
.chart-legend {
display: flex;
justify-content: center;
gap: 24rpx;
margin-top: 16rpx;
flex-wrap: wrap;
.legend-item {
display: flex;
align-items: center;
gap: 8rpx;
.legend-dot {
width: 12rpx;
height: 12rpx;
border-radius: 50%;
&.revenue { background: #576EFF; }
&.traffic { background: #52C41A; }
&.profit { background: #FAAD14; }
}
.legend-text {
font-size: 20rpx;
color: @text-secondary;
}
}
}
}
.province-service-cards {
display: flex;
flex-direction: column;
gap: 16rpx;
.service-card {
background: #f8f9fa;
border-radius: 12rpx;
padding: 20rpx;
border: 1rpx solid #e9ecef;
.card-header {
margin-bottom: 16rpx;
.service-info {
.service-name {
display: block;
font-size: 32rpx;
font-weight: 600;
color: @text-primary;
margin-bottom: 12rpx;
}
.service-badges {
display: flex;
gap: 8rpx;
.type-badge {
padding: 6rpx 12rpx;
border-radius: 8rpx;
font-size: 20rpx;
color: white;
font-weight: 500;
&.type-a {
background: #576EFF;
}
&.type-b {
background: #52C41A;
}
&.type-c {
background: #FAAD14;
}
}
.status-badge {
padding: 6rpx 12rpx;
border-radius: 8rpx;
font-size: 20rpx;
color: white;
font-weight: 500;
&.success {
background: @success-color;
}
&.warning {
background: @warning-color;
}
&.error {
background: @error-color;
}
&.info {
background: @info-color;
}
}
}
}
}
.card-content {
.metrics-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12rpx;
.metric-item {
background: white;
padding: 16rpx;
border-radius: 8rpx;
border: 1rpx solid #f0f0f0;
text-align: center;
.metric-label {
display: block;
font-size: 22rpx;
color: @text-secondary;
margin-bottom: 8rpx;
}
.metric-value {
display: block;
font-size: 28rpx;
font-weight: 600;
color: @text-primary;
&.profit-rate {
&.high {
color: @success-color;
}
&.positive {
color: @success-color;
}
&.neutral {
color: @warning-color;
}
&.negative {
color: @error-color;
}
}
}
}
}
}
}
}
}
}
</style>