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

949 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="custom-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-mode-section">
<view class="section-title">
<text class="title-text">自定义分析模式</text>
<text class="title-desc">管理方可根据管理逻辑自定义数据分析维度</text>
</view>
<view class="mode-selector">
<view class="mode-card"
:class="{ active: activeMode === 'performance' }"
@click="switchMode('performance')">
<view class="mode-icon performance"></view>
<text class="mode-title">综合效益分析</text>
<text class="mode-desc">基于营业额利润率客流量综合评分</text>
</view>
<view class="mode-card"
:class="{ active: activeMode === 'potential' }"
@click="switchMode('potential')">
<view class="mode-icon potential"></view>
<text class="mode-title">发展潜力分析</text>
<text class="mode-desc">基于增长率区域位置基础设施建设</text>
</view>
<view class="mode-card"
:class="{ active: activeMode === 'custom' }"
@click="switchMode('custom')">
<view class="mode-icon custom"></view>
<text class="mode-title">自定义权重分析</text>
<text class="mode-desc">管理方自定义各指标权重进行综合评价</text>
</view>
</view>
</view>
<!-- 自定义等级展示 -->
<view class="custom-levels-section">
<view class="section-title">
<text class="title-text">服务区自定义等级分布</text>
<text class="title-desc">{{ getModeDescription() }}</text>
</view>
<view class="level-cards">
<view class="level-card" v-for="(level, index) in currentLevels" :key="index"
:class="'level-' + level.grade">
<view class="level-header">
<view class="level-grade">{{ level.grade }}级</view>
<view class="level-count">{{ level.count }}个</view>
</view>
<view class="level-metrics">
<view class="metric-item">
<text class="metric-label">平均营业额</text>
<text class="metric-value">¥{{ formatMoney(level.avgRevenue) }}</text>
</view>
<view class="metric-item">
<text class="metric-label">平均利润率</text>
<text class="metric-value">{{ level.avgProfitRate }}%</text>
</view>
<view class="metric-item">
<text class="metric-label">数量占比</text>
<text class="metric-value">{{ level.percentage }}%</text>
</view>
</view>
</view>
</view>
</view>
<!-- 自定义分析图表 -->
<view class="chart-card">
<view class="chart-header">
<text class="chart-title">自定义等级{{ getChartTitle() }}</text>
<text class="chart-subtitle">{{ getChartDescription() }}</text>
</view>
<view class="chart-content">
<view class="radar-chart-container">
<QiunDataCharts
type="radar"
:opts="radarChartOpts"
:chartData="radarChartData"
:canvas2d="true"
:inScrollView="true"
canvasId="customRadarChart"
/>
</view>
</view>
</view>
<!-- 综合评价矩阵 -->
<view class="matrix-section">
<view class="section-title">
<text class="title-text">综合评价矩阵</text>
<text class="title-desc">多维度交叉分析服务区表现</text>
</view>
<view class="matrix-grid">
<view class="matrix-legend">
<view class="legend-item excellent">优秀</view>
<view class="legend-item good">良好</view>
<view class="legend-item average">一般</view>
<view class="legend-item poor">较差</view>
</view>
<view class="matrix-chart-container">
<QiunDataCharts
type="bubble"
:opts="bubbleChartOpts"
:chartData="bubbleChartData"
:canvas2d="true"
:inScrollView="true"
canvasId="bubbleMatrixChart"
/>
</view>
</view>
</view>
<!-- 权重配置(仅在自定义模式下显示) -->
<view class="weight-config-section" v-if="activeMode === 'custom'">
<view class="section-title">
<text class="title-text">自定义权重配置</text>
<text class="title-desc">调整各指标权重重新计算综合评分</text>
</view>
<view class="weight-sliders">
<view class="weight-item" v-for="(weight, index) in customWeights" :key="index">
<view class="weight-header">
<text class="weight-label">{{ weight.label }}</text>
<text class="weight-value">{{ weight.value }}%</text>
</view>
<view class="weight-bar">
<view class="weight-progress" :style="{ width: weight.value + '%' }"></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="recalculateScores">重新计算</text>
<text class="action-btn" @click="exportData">导出数据</text>
</view>
</view>
<view class="score-cards">
<view class="score-card" v-for="(item, index) in scoreData" :key="index">
<view class="card-header">
<view class="service-info">
<text class="service-name">{{ item.serviceAreaName }}</text>
<text class="level-tag"
:class="item.customLevel === 'S' ? 'level-s' :
item.customLevel === 'A' ? 'level-a' :
item.customLevel === 'B' ? 'level-b' : 'level-c'">
{{ item.customLevel }}
</text>
</view>
<view class="total-score">
<text class="score-value"
:class="item.totalScore >= 90 ? 'excellent' :
item.totalScore >= 80 ? 'good' :
item.totalScore >= 70 ? 'average' : 'poor'">
{{ item.totalScore }}
</text>
</view>
</view>
<view class="card-content">
<view class="scores-grid">
<view class="score-item">
<text class="score-label">营业额得分</text>
<text class="score-number">{{ item.revenueScore }}</text>
</view>
<view class="score-item">
<text class="score-label">利润率得分</text>
<text class="score-number">{{ item.profitScore }}</text>
</view>
<view class="score-item">
<text class="score-label">客流量得分</text>
<text class="score-number">{{ item.trafficScore }}</text>
</view>
<view class="score-item">
<text class="score-label">增长率得分</text>
<text class="score-number">{{ item.growthScore }}</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月',
activeMode: 'performance',
// 自定义等级数据
customLevels: {
performance: [
{ grade: 'S', count: 8, avgRevenue: 9876, avgProfitRate: 22.5, percentage: 9 },
{ grade: 'A', count: 18, avgRevenue: 6543, avgProfitRate: 18.2, percentage: 21 },
{ grade: 'B', count: 35, avgRevenue: 4321, avgProfitRate: 14.1, percentage: 41 },
{ grade: 'C', count: 24, avgRevenue: 2876, avgProfitRate: 10.3, percentage: 29 }
],
potential: [
{ grade: 'S', count: 12, avgRevenue: 7654, avgProfitRate: 15.8, percentage: 14 },
{ grade: 'A', count: 22, avgRevenue: 5432, avgProfitRate: 13.2, percentage: 26 },
{ grade: 'B', count: 28, avgRevenue: 3876, avgProfitRate: 11.5, percentage: 33 },
{ grade: 'C', count: 23, avgRevenue: 2543, avgProfitRate: 8.7, percentage: 27 }
],
custom: [
{ grade: 'S', count: 10, avgRevenue: 8765, avgProfitRate: 19.8, percentage: 12 },
{ grade: 'A', count: 20, avgRevenue: 5987, avgProfitRate: 16.3, percentage: 24 },
{ grade: 'B', count: 30, avgRevenue: 3987, avgProfitRate: 12.8, percentage: 35 },
{ grade: 'C', count: 25, avgRevenue: 2654, avgProfitRate: 9.2, percentage: 29 }
]
},
// 自定义权重
customWeights: [
{ label: '营业额', value: 30 },
{ label: '利润率', value: 25 },
{ label: '客流量', value: 20 },
{ label: '增长率', value: 15 },
{ label: '经营面积', value: 10 }
],
// 评分数据
scoreData: [
{
serviceAreaName: '昆明服务区',
customLevel: 'S',
totalScore: 92.5,
revenueScore: 95,
profitScore: 88,
trafficScore: 94,
growthScore: 93
},
{
serviceAreaName: '曲靖服务区',
customLevel: 'A',
totalScore: 85.2,
revenueScore: 87,
profitScore: 82,
trafficScore: 86,
growthScore: 86
},
{
serviceAreaName: '大理服务区',
customLevel: 'B',
totalScore: 76.8,
revenueScore: 72,
profitScore: 68,
trafficScore: 82,
growthScore: 85
},
{
serviceAreaName: '玉溪服务区',
customLevel: 'A',
totalScore: 81.3,
revenueScore: 84,
profitScore: 79,
trafficScore: 78,
growthScore: 84
},
{
serviceAreaName: '红河服务区',
customLevel: 'B',
totalScore: 73.5,
revenueScore: 76,
profitScore: 72,
trafficScore: 70,
growthScore: 76
}
],
// 气泡图数据
bubbleData: [
{ name: '昆明服务区', x: 92, y: 88, r: 18, level: 'S' },
{ name: '曲靖服务区', x: 85, y: 82, r: 15, level: 'A' },
{ name: '大理服务区', x: 72, y: 68, r: 12, level: 'B' },
{ name: '玉溪服务区', x: 84, y: 79, r: 14, level: 'A' },
{ name: '红河服务区', x: 76, y: 72, r: 11, level: 'B' },
{ name: '保山服务区', x: 68, y: 75, r: 10, level: 'C' },
{ name: '昭通服务区', x: 62, y: 65, r: 9, level: 'C' },
{ name: '文山服务区', x: 78, y: 71, r: 13, level: 'B' }
]
}
},
computed: {
// 当前模式的等级数据
currentLevels() {
return this.customLevels[this.activeMode] || this.customLevels.performance;
},
// 雷达图数据
radarChartData() {
return {
categories: ['营业额', '利润率', '客流量', '增长率', '综合评分'],
series: this.currentLevels.map((level, index) => ({
name: level.grade + '级服务区',
data: [
this.getNormalizedScore(level.avgRevenue, 10000),
this.getNormalizedScore(level.avgProfitRate, 25),
this.getNormalizedScore(level.count, 40),
this.getNormalizedScore(level.percentage, 40),
this.getNormalizedScore((5 - index) * 20, 100)
]
}))
}
},
// 雷达图配置
radarChartOpts() {
return {
color: ['#576EFF', '#52C41A', '#FAAD14', '#FF7875'],
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
}
}
}
},
// 气泡图数据
bubbleChartData() {
return {
series: [
{
name: 'S级服务区',
data: this.bubbleData.filter(item => item.level === 'S').map(item => [item.x, item.y, item.r])
},
{
name: 'A级服务区',
data: this.bubbleData.filter(item => item.level === 'A').map(item => [item.x, item.y, item.r])
},
{
name: 'B级服务区',
data: this.bubbleData.filter(item => item.level === 'B').map(item => [item.x, item.y, item.r])
},
{
name: 'C级服务区',
data: this.bubbleData.filter(item => item.level === 'C').map(item => [item.x, item.y, item.r])
}
]
}
},
// 气泡图配置
bubbleChartOpts() {
return {
color: ['#576EFF', '#52C41A', '#FAAD14', '#FF7875'],
padding: [15, 15, 15, 15],
dataLabel: false,
legend: {
show: false
},
xAxis: {
min: 50,
max: 100,
gridType: 'dash',
dashLength: 2,
titleText: '综合评分',
titleFontSize: 22
},
yAxis: {
min: 50,
max: 100,
gridType: 'dash',
dashLength: 2,
titleText: '经营效率',
titleFontSize: 22
},
extra: {
bubble: {
opacity: 0.8,
border: 2,
borderColor: '#FFFFFF'
}
}
}
}
},
methods: {
switchMode(mode) {
this.activeMode = mode;
},
getModeDescription() {
const descriptions = {
performance: '基于营业额、利润率、客流量等经营指标的综合效益分级',
potential: '基于增长率、区域位置、发展潜力等指标的发展潜力分级',
custom: '基于管理方自定义权重的综合评价分级'
};
return descriptions[this.activeMode] || '';
},
getChartTitle() {
const titles = {
performance: '综合效益对比',
potential: '发展潜力对比',
custom: '综合评价对比'
};
return titles[this.activeMode] || '';
},
getChartDescription() {
const descriptions = {
performance: '各等级服务区在关键经营指标上的表现对比',
potential: '各等级服务区在发展潜力维度上的表现对比',
custom: '各等级服务区在自定义权重下的综合表现对比'
};
return descriptions[this.activeMode] || '';
},
getNormalizedScore(value, maxValue) {
return Math.round((value / maxValue) * 100);
},
getLevelClass(level) {
const classMap = {
'S': 'level-s',
'A': 'level-a',
'B': 'level-b',
'C': 'level-c'
};
return classMap[level] || '';
},
getScoreClass(score) {
if (score >= 90) return 'excellent';
if (score >= 80) return 'good';
if (score >= 70) return 'average';
return 'poor';
},
formatNumber(num) {
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
},
formatMoney(amount) {
return amount.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
},
recalculateScores() {
console.log('重新计算自定义评分');
// 这里可以重新计算评分逻辑
},
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;
@bg-light: #f8f9fb;
@border-radius: 16rpx;
@shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
.custom-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;
line-height: 1.3;
}
.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;
}
}
}
.section-title {
margin-bottom: 24rpx;
.title-text {
font-size: 28rpx;
font-weight: 600;
color: @text-primary;
display: block;
margin-bottom: 8rpx;
}
.title-desc {
font-size: 22rpx;
color: @text-light;
line-height: 1.4;
}
}
.analysis-mode-section {
background: @bg-white;
border-radius: @border-radius;
padding: 24rpx;
box-shadow: @shadow;
margin-bottom: 32rpx;
.mode-selector {
display: flex;
gap: 16rpx;
.mode-card {
flex: 1;
background: @bg-light;
border-radius: 12rpx;
padding: 20rpx 16rpx;
text-align: center;
transition: all 0.3s ease;
cursor: pointer;
border: 2rpx solid transparent;
&.active {
background: @bg-white;
border-color: @primary-color;
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.15);
}
.mode-icon {
width: 48rpx;
height: 48rpx;
margin: 0 auto 16rpx;
border-radius: 50%;
position: relative;
&.performance {
background: linear-gradient(135deg, #576EFF, #7C8FFF);
&::after { content: '📊'; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); font-size: 24rpx; }
}
&.potential {
background: linear-gradient(135deg, #52C41A, #73D13D);
&::after { content: '🚀'; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); font-size: 24rpx; }
}
&.custom {
background: linear-gradient(135deg, #FAAD14, #FFC53D);
&::after { content: '⚙️'; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); font-size: 24rpx; }
}
}
.mode-title {
font-size: 24rpx;
font-weight: 600;
color: @text-primary;
display: block;
margin-bottom: 8rpx;
}
.mode-desc {
font-size: 20rpx;
color: @text-secondary;
line-height: 1.3;
}
}
}
}
.custom-levels-section {
margin-bottom: 32rpx;
.level-cards {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16rpx;
.level-card {
background: @bg-white;
border-radius: @border-radius;
padding: 20rpx;
box-shadow: @shadow;
position: relative;
overflow: hidden;
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4rpx;
}
&.level-S::before { background: linear-gradient(90deg, #576EFF, #7C8FFF); }
&.level-A::before { background: linear-gradient(90deg, #52C41A, #73D13D); }
&.level-B::before { background: linear-gradient(90deg, #FAAD14, #FFC53D); }
&.level-C::before { background: linear-gradient(90deg, #FF7875, #FF9C99); }
.level-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16rpx;
.level-grade {
font-size: 32rpx;
font-weight: 700;
color: @text-primary;
}
.level-count {
font-size: 24rpx;
color: @text-secondary;
background: @bg-light;
padding: 4rpx 12rpx;
border-radius: 12rpx;
}
}
.level-metrics {
.metric-item {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8rpx;
&:last-child {
margin-bottom: 0;
}
.metric-label {
font-size: 20rpx;
color: @text-secondary;
}
.metric-value {
font-size: 20rpx;
color: @text-primary;
font-weight: 600;
}
}
}
}
}
}
.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 {
.radar-chart-container,
.matrix-chart-container {
width: 100%;
}
}
.score-cards {
display: flex;
flex-direction: column;
gap: 16rpx;
.score-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;
.service-info {
flex: 1;
.service-name {
display: block;
font-size: 32rpx;
font-weight: 600;
color: @text-primary;
margin-bottom: 8rpx;
}
.level-tag {
display: inline-block;
padding: 6rpx 12rpx;
border-radius: 8rpx;
font-size: 20rpx;
color: white;
font-weight: 500;
&.level-s {
background: #576EFF;
}
&.level-a {
background: #52C41A;
}
&.level-b {
background: #FAAD14;
}
&.level-c {
background: #FF7875;
}
}
}
.total-score {
text-align: center;
.score-value {
display: block;
font-size: 36rpx;
font-weight: 700;
margin-bottom: 4rpx;
&.excellent {
color: #576EFF;
}
&.good {
color: #52C41A;
}
&.average {
color: #FAAD14;
}
&.poor {
color: #FF7875;
}
}
}
}
.card-content {
.scores-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12rpx;
.score-item {
background: white;
padding: 16rpx;
border-radius: 8rpx;
border: 1rpx solid #f0f0f0;
text-align: center;
.score-label {
display: block;
font-size: 22rpx;
color: @text-secondary;
margin-bottom: 8rpx;
}
.score-number {
display: block;
font-size: 28rpx;
font-weight: 600;
color: @text-primary;
}
}
}
}
}
}
}
.matrix-section {
background: @bg-white;
border-radius: @border-radius;
padding: 24rpx;
box-shadow: @shadow;
margin-bottom: 32rpx;
.matrix-grid {
.matrix-legend {
display: flex;
justify-content: center;
gap: 24rpx;
margin-bottom: 20rpx;
.legend-item {
font-size: 20rpx;
color: @text-secondary;
padding: 4rpx 12rpx;
border-radius: 12rpx;
&.excellent {
background: rgba(87, 110, 255, 0.1);
color: #576EFF;
}
&.good {
background: rgba(82, 196, 26, 0.1);
color: #52C41A;
}
&.average {
background: rgba(250, 173, 20, 0.1);
color: #FAAD14;
}
&.poor {
background: rgba(255, 120, 117, 0.1);
color: #FF7875;
}
}
}
}
}
.weight-config-section {
background: @bg-white;
border-radius: @border-radius;
padding: 24rpx;
box-shadow: @shadow;
margin-bottom: 32rpx;
.weight-sliders {
.weight-item {
margin-bottom: 24rpx;
&:last-child {
margin-bottom: 0;
}
.weight-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12rpx;
.weight-label {
font-size: 24rpx;
color: @text-primary;
font-weight: 500;
}
.weight-value {
font-size: 24rpx;
color: @primary-color;
font-weight: 600;
}
}
.weight-bar {
width: 100%;
height: 12rpx;
background: #f0f0f0;
border-radius: 6rpx;
position: relative;
overflow: hidden;
.weight-progress {
height: 100%;
background: linear-gradient(90deg, @primary-color, #7C8FFF);
border-radius: 6rpx;
transition: width 0.3s ease;
}
}
}
}
}
}
</style>