Compare commits
18 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
e5c872493c | 2 months ago |
|
|
fb93301585 | 3 months ago |
|
|
bf8d9a5a49 | 3 months ago |
|
|
86b72ae916 | 3 months ago |
|
|
14c198b985 | 3 months ago |
|
|
7ceb1c731c | 3 months ago |
|
|
8d5f906b8a | 4 months ago |
|
|
88fa1ce11a | 4 months ago |
|
|
4ca9409216 | 4 months ago |
|
|
387e7afc33 | 4 months ago |
|
|
c91b673487 | 4 months ago |
|
|
e996449376 | 4 months ago |
|
|
369a6dd341 | 4 months ago |
|
|
970cadcca8 | 4 months ago |
|
|
9e7040ec83 | 4 months ago |
|
|
75c2bd64ad | 4 months ago |
|
|
7e8c0a1607 | 4 months ago |
|
|
f8f85d653f | 4 months ago |
18 changed files with 5642 additions and 1598 deletions
@ -0,0 +1,468 @@ |
|||||
|
<template> |
||||
|
<div class="shore-power-usage"> |
||||
|
<div class="card digital-twin-card--deep-blue"> |
||||
|
<div style="display: flex; align-items: center; justify-content: space-between;"> |
||||
|
<div class="card-title"> |
||||
|
<div class="vertical-line"></div> |
||||
|
<img src="@/assets/svgs/data.svg" class="title-icon" /> |
||||
|
<span class="title-text">岸电使用率</span> |
||||
|
</div> |
||||
|
<div class="show-value"> |
||||
|
|
||||
|
<button v-for="option in timeRangeOptions" :key="option.value" |
||||
|
:class="['time-range-btn', { active: timeRange === option.value }]" |
||||
|
@click.stop="handleTimeRangeChange(option.value)"> |
||||
|
{{ option.label }} |
||||
|
</button> |
||||
|
<!-- <span class="show-value-label">{{timeRangeOptions.find(option => option.value === timeRange)?.label || |
||||
|
''}}量:</span> |
||||
|
<div class="show-value-value">{{ chartData[timeRange][card.value] }}</div> --> |
||||
|
<el-button class="close-btn" size="small" type="text" @click.stop="handleClose()">x</el-button> |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
</div> |
||||
|
<div style="display: flex; align-items: center; justify-content: space-between;"> |
||||
|
<div class="time-range-item">{{ startTimeDisplay }} 至 {{ realtimeDeviceDataTime }}</div> |
||||
|
|
||||
|
<div v-show="timeRange !== 'year'" class="comparison-type-selector"> |
||||
|
<button :class="['comparison-type-btn', { active: comparisonType === 'chain' }]" |
||||
|
@click="comparisonType = 'chain'"> |
||||
|
环比 |
||||
|
</button> |
||||
|
<button :class="['comparison-type-btn', { active: comparisonType === 'yearOnYear' }]" |
||||
|
@click="comparisonType = 'yearOnYear'"> |
||||
|
同比 |
||||
|
</button> |
||||
|
</div> |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
<div v-if="timeRange === 'year'" class="card-content"> |
||||
|
<ComparisonBarChart :chartData="getYearlyData" title="年度用电量对比" yAxisName="用电量(kWh)" currentColor="#36A2EB" |
||||
|
previousColor="#FF6384" chartType="percentage" /> |
||||
|
</div> |
||||
|
<div v-if="timeRange === 'month'" class="card-content"> |
||||
|
<ComparisonBarChart :chartData="getMonthlyComparisonData" |
||||
|
:title="comparisonType === 'yearOnYear' ? '月度用电量同比对比' : '月度用电量环比对比'" yAxisName="用电量(kWh)" |
||||
|
currentColor="#36A2EB" previousColor="#FF6384" chartType="percentage" /> |
||||
|
</div> |
||||
|
<div class="card-content" v-if="timeRange === 'day'"> |
||||
|
<ComparisonBarChart :chartData="getDailyComparisonData" |
||||
|
:title="comparisonType === 'yearOnYear' ? '日用电量同比对比' : '日用电量环比对比'" yAxisName="用电量(kWh)" currentColor="#36A2EB" |
||||
|
previousColor="#FF6384" chartType="percentage" /> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script setup lang="ts"> |
||||
|
import { ref, computed, onMounted, onBeforeUnmount } from 'vue' |
||||
|
import { MapApi } from "@/api/shorepower/map"; |
||||
|
import ComparisonBarChart from './charts/ComparisonBarChart.vue' |
||||
|
import { ComparativeData, MetricData, RealtimeDeviceData } from '@/types/shorepower'; |
||||
|
|
||||
|
interface Props { |
||||
|
// realtimeDeviceData: RealtimeDeviceData[]; |
||||
|
// activeHeadGroup?: number; |
||||
|
handleClose: () => void; |
||||
|
realtimeDeviceDataTime: string; |
||||
|
comparativeData: MetricData; |
||||
|
initialTimeRange?: 'realtime' | 'day' | 'month' | 'year'; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
const props = defineProps<Props>() |
||||
|
|
||||
|
export interface MeasureDataRealtimeRespVO { |
||||
|
/** |
||||
|
* 创建时间 |
||||
|
*/ |
||||
|
createTime: Date; |
||||
|
/** |
||||
|
* 设备编号code |
||||
|
*/ |
||||
|
deviceCode: string; |
||||
|
/** |
||||
|
* 设备编号 |
||||
|
*/ |
||||
|
deviceId: number; |
||||
|
/** |
||||
|
* 设备名称 |
||||
|
*/ |
||||
|
deviceName: string; |
||||
|
/** |
||||
|
* 设备状态 |
||||
|
*/ |
||||
|
deviceStatus: number; |
||||
|
/** |
||||
|
* 编号 |
||||
|
*/ |
||||
|
id: number; |
||||
|
/** |
||||
|
* 增量费用 |
||||
|
*/ |
||||
|
incrementCost?: number; |
||||
|
/** |
||||
|
* 增量值 |
||||
|
*/ |
||||
|
incrementValue: number; |
||||
|
/** |
||||
|
* 采集时间 |
||||
|
*/ |
||||
|
measureTime: Date; |
||||
|
/** |
||||
|
* 采集值 |
||||
|
*/ |
||||
|
measureValue: number; |
||||
|
} |
||||
|
|
||||
|
export interface deviceData { |
||||
|
[key: string]: MeasureDataRealtimeRespVO |
||||
|
} |
||||
|
|
||||
|
const getYearlyData = computed(() => { |
||||
|
return [{ |
||||
|
name: '本期上期对比', |
||||
|
growthRate: props.comparativeData.year.growthRate, |
||||
|
currentValue: convertPowerUsage(props.comparativeData.year.current.value, selectedCard.value), |
||||
|
previousValue: convertPowerUsage(props.comparativeData.year.previous.value, selectedCard.value), |
||||
|
currentPeriod: props.comparativeData.year.current.period, |
||||
|
previousPeriod: props.comparativeData.year.previous.period, |
||||
|
}] |
||||
|
}) |
||||
|
|
||||
|
// 日对比数据(根据对比类型选择环比或同比) |
||||
|
const getDailyComparisonData = computed(() => { |
||||
|
const comparisonData = comparisonType.value === 'yearOnYear' |
||||
|
? props.comparativeData.dayYearOnYear |
||||
|
: props.comparativeData.day; |
||||
|
|
||||
|
return [{ |
||||
|
name: comparisonType.value === 'yearOnYear' ? '本期去年同期对比' : '本期上期对比', |
||||
|
growthRate: comparisonData.growthRate, |
||||
|
currentValue: convertPowerUsage(comparisonData.current.value, selectedCard.value), |
||||
|
previousValue: convertPowerUsage(comparisonData.previous.value, selectedCard.value), |
||||
|
currentPeriod: comparisonData.current.period, |
||||
|
previousPeriod: comparisonData.previous.period, |
||||
|
}] |
||||
|
}) |
||||
|
|
||||
|
// 月对比数据(根据对比类型选择环比或同比) |
||||
|
const getMonthlyComparisonData = computed(() => { |
||||
|
const comparisonData = comparisonType.value === 'yearOnYear' |
||||
|
? props.comparativeData.monthYearOnYear |
||||
|
: props.comparativeData.month; |
||||
|
|
||||
|
return [{ |
||||
|
name: comparisonType.value === 'yearOnYear' ? '本期去年同期对比' : '本期上期对比', |
||||
|
growthRate: comparisonData.growthRate, |
||||
|
currentValue: convertPowerUsage(comparisonData.current.value, selectedCard.value), |
||||
|
previousValue: convertPowerUsage(comparisonData.previous.value, selectedCard.value), |
||||
|
currentPeriod: comparisonData.current.period, |
||||
|
previousPeriod: comparisonData.previous.period, |
||||
|
}] |
||||
|
}) |
||||
|
|
||||
|
// 根据时间范围计算起始时间 |
||||
|
const startTimeDisplay = computed(() => { |
||||
|
const now = new Date(); |
||||
|
let startDate: Date; |
||||
|
|
||||
|
switch (timeRange.value) { |
||||
|
case 'day': |
||||
|
// 今日的零点 |
||||
|
startDate = new Date(now.getFullYear(), now.getMonth(), now.getDate()); |
||||
|
break; |
||||
|
case 'month': |
||||
|
// 当月的第一天零点 |
||||
|
startDate = new Date(now.getFullYear(), now.getMonth(), 1); |
||||
|
break; |
||||
|
case 'year': |
||||
|
// 当年的第一天零点 |
||||
|
startDate = new Date(now.getFullYear(), 0, 1); |
||||
|
break; |
||||
|
/* case 'realtime': |
||||
|
// 总数据从2020年开始 |
||||
|
startDate = new Date(2020, 0, 1); |
||||
|
break; */ |
||||
|
default: |
||||
|
// 默认为今日的零点 |
||||
|
startDate = new Date(now.getFullYear(), now.getMonth(), now.getDate()); |
||||
|
} |
||||
|
|
||||
|
// 格式化日期时间 |
||||
|
const year = startDate.getFullYear(); |
||||
|
const month = String(startDate.getMonth() + 1).padStart(2, '0'); |
||||
|
const day = String(startDate.getDate()).padStart(2, '0'); |
||||
|
const hours = String(startDate.getHours()).padStart(2, '0'); |
||||
|
const minutes = String(startDate.getMinutes()).padStart(2, '0'); |
||||
|
const seconds = String(startDate.getSeconds()).padStart(2, '0'); |
||||
|
|
||||
|
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; |
||||
|
}); |
||||
|
|
||||
|
const selectedCard = ref<string>('overview') |
||||
|
|
||||
|
// 时间范围选项 |
||||
|
const timeRange = ref<'day' | 'month' | 'year'>( |
||||
|
(props.initialTimeRange === 'realtime' ? 'year' : props.initialTimeRange) || 'day' |
||||
|
) |
||||
|
|
||||
|
// 比较类型选项:chain(环比)或 yearOnYear(同比) |
||||
|
const comparisonType = ref<'chain' | 'yearOnYear'>('chain') |
||||
|
|
||||
|
// 时间范围选项定义 |
||||
|
const timeRangeOptions = [ |
||||
|
{ value: 'day', label: '当日' }, |
||||
|
{ value: 'month', label: '当月' }, |
||||
|
{ value: 'year', label: '当年' }, |
||||
|
// { value: 'realtime', label: '汇总' }, |
||||
|
] |
||||
|
|
||||
|
// 处理时间范围选择 |
||||
|
const handleTimeRangeChange = (range: 'day' | 'month' | 'year') => { |
||||
|
timeRange.value = range |
||||
|
// 这里可以添加根据时间范围切换数据源的逻辑 |
||||
|
} |
||||
|
|
||||
|
// 通用数据转换函数 |
||||
|
const convertPowerUsage = (powerUsage: number, type: string): number => { |
||||
|
const conversionFactors: Record<string, number> = { |
||||
|
totalPower: 1, // 直接使用原值 |
||||
|
fuel: 0.22 / 1, // 转化为千克 |
||||
|
co2: 670 / 1000, // 克转化为千克 |
||||
|
pm25: 1.46 / 1000, // 克转化为千克 |
||||
|
nox: 18.1 / 1000, // 克转化为千克 |
||||
|
so2: 10.5 / 1000 // 克转化为千克 |
||||
|
}; |
||||
|
|
||||
|
const factor = conversionFactors[type] || 1; |
||||
|
return Number((powerUsage * factor).toFixed(2)); |
||||
|
}; |
||||
|
|
||||
|
|
||||
|
/* watch(() => props.realtimeDeviceData, (newValue) => { |
||||
|
handleGetRealTimeAllData(newValue) |
||||
|
}) */ |
||||
|
|
||||
|
// 组件挂载时初始化数据和定时器 |
||||
|
// 添加一个变量来保存事件处理函数的引用 |
||||
|
let cardSelectedHandler: any = null |
||||
|
|
||||
|
onMounted(() => { |
||||
|
// handleGetAllDeviceDataByTimeRange() |
||||
|
// handleGetRealTimeAllData(props.realtimeDeviceData) |
||||
|
|
||||
|
// 添加卡片选择事件监听器 |
||||
|
// cardSelectedHandler = (event: CustomEvent) => { |
||||
|
// selectedCard.value = event.detail |
||||
|
// } |
||||
|
// window.addEventListener('cardSelected', cardSelectedHandler) |
||||
|
}) |
||||
|
|
||||
|
// 监听 activeHeadGroup 变化,当组件变为可见时触发 resize |
||||
|
watch(() => props.activeHeadGroup, (newVal) => { |
||||
|
if (newVal === 1) { |
||||
|
// 延迟执行 resize,确保 DOM 已经更新 |
||||
|
setTimeout(() => { |
||||
|
window.dispatchEvent(new Event('resize')) |
||||
|
}, 100) |
||||
|
} |
||||
|
}, { immediate: true }) |
||||
|
|
||||
|
// 监听 initialTimeRange 变化,当父组件传递新的时间范围时更新当前时间范围 |
||||
|
watch(() => props.initialTimeRange, (newVal) => { |
||||
|
console.log('newVal', newVal) |
||||
|
if (newVal) { |
||||
|
if (newVal === 'realtime') { |
||||
|
timeRange.value = 'year' |
||||
|
} else { |
||||
|
timeRange.value = newVal as 'day' | 'month' | 'year' |
||||
|
} |
||||
|
} |
||||
|
}) |
||||
|
|
||||
|
// 组件销毁前清除定时器 |
||||
|
onBeforeUnmount(() => { |
||||
|
if (cardSelectedHandler) { |
||||
|
window.removeEventListener('cardSelected', cardSelectedHandler) |
||||
|
} |
||||
|
}) |
||||
|
</script> |
||||
|
|
||||
|
<style lang="scss" scoped> |
||||
|
.shore-power-usage { |
||||
|
display: flex; |
||||
|
height: 100%; |
||||
|
width: 100%; |
||||
|
gap: 10px; |
||||
|
} |
||||
|
|
||||
|
.card .card-content { |
||||
|
overflow: hidden !important; |
||||
|
} |
||||
|
|
||||
|
.average { |
||||
|
|
||||
|
.overview-value { |
||||
|
font-size: 36px; |
||||
|
} |
||||
|
|
||||
|
.overview-label { |
||||
|
font-size: 18px; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.magnify { |
||||
|
width: 100%; |
||||
|
padding: 12px; |
||||
|
height: calc(100vh - 72px); |
||||
|
position: absolute; |
||||
|
top: 72px; |
||||
|
left: 0px; |
||||
|
display: flex; |
||||
|
// flex-direction: column; |
||||
|
/* background-color: red; */ |
||||
|
gap: 8px; |
||||
|
overflow-y: auto; |
||||
|
|
||||
|
.big { |
||||
|
height: 100%; |
||||
|
width: calc(55%); |
||||
|
|
||||
|
.card { |
||||
|
height: 100%; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
.card .card-title .title-text { |
||||
|
width: 120px; |
||||
|
} |
||||
|
|
||||
|
.overview-value { |
||||
|
font-size: 48px; |
||||
|
} |
||||
|
|
||||
|
.overview-label { |
||||
|
font-size: 28px; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.right-row { |
||||
|
width: calc(45% - 8px); |
||||
|
height: 100%; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
flex: 1; |
||||
|
gap: 8px; |
||||
|
// background-color: red; |
||||
|
} |
||||
|
|
||||
|
.one-row { |
||||
|
height: 30%; |
||||
|
display: flex; |
||||
|
gap: 8px; |
||||
|
|
||||
|
.card .card-title .title-text { |
||||
|
font-size: 14px; |
||||
|
} |
||||
|
|
||||
|
.card { |
||||
|
flex: 1; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.card { |
||||
|
padding: 6px; |
||||
|
cursor: pointer; |
||||
|
} |
||||
|
|
||||
|
.time-range-selector { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
gap: 8px; |
||||
|
} |
||||
|
|
||||
|
.show-value { |
||||
|
font-size: 18px; |
||||
|
font-weight: 600; |
||||
|
font-weight: bold; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
gap: 6px; |
||||
|
margin-bottom: 8px; |
||||
|
color: #FFF; |
||||
|
} |
||||
|
|
||||
|
.show-value-label { |
||||
|
font-size: 18px; |
||||
|
font-weight: 600; |
||||
|
font-weight: bold; |
||||
|
} |
||||
|
|
||||
|
.show-value-value { |
||||
|
font-size: 20px; |
||||
|
font-weight: 600; |
||||
|
font-weight: bold; |
||||
|
} |
||||
|
|
||||
|
.time-range-item { |
||||
|
font-size: 24px; |
||||
|
font-weight: 600; |
||||
|
font-weight: bold; |
||||
|
color: rgba(255, 255, 255, 0.7); |
||||
|
} |
||||
|
|
||||
|
.growth-rate { |
||||
|
flex: 1; |
||||
|
text-align: right; |
||||
|
margin-left: 10px; |
||||
|
font-weight: bold; |
||||
|
padding: 2px 8px; |
||||
|
border-radius: 4px; |
||||
|
font-size: 32px; |
||||
|
} |
||||
|
|
||||
|
.growth-up { |
||||
|
color: #52C41A; |
||||
|
} |
||||
|
|
||||
|
.growth-down { |
||||
|
color: #F5222D; |
||||
|
} |
||||
|
|
||||
|
.growth-icon { |
||||
|
margin-right: 4px; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
.right { |
||||
|
/* gap: 0 */ |
||||
|
} |
||||
|
|
||||
|
.close-btn { |
||||
|
width: 24px; |
||||
|
height: 24px; |
||||
|
// border-radius: 50%; |
||||
|
// background-color: #ff4d4f; |
||||
|
color: white; |
||||
|
border: none; |
||||
|
font-weight: bold; |
||||
|
font-size: 16px; |
||||
|
cursor: pointer; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
margin-left: 10px; |
||||
|
// margin-bottom: 8px; |
||||
|
} |
||||
|
|
||||
|
.close-btn:hover { |
||||
|
color: #f5222d; |
||||
|
} |
||||
|
</style> |
||||
File diff suppressed because it is too large
File diff suppressed because it is too large
File diff suppressed because it is too large
@ -0,0 +1,196 @@ |
|||||
|
<template> |
||||
|
<div ref="chartContainer" class="comparison-bar-chart-container"></div> |
||||
|
</template> |
||||
|
|
||||
|
<script setup lang="ts"> |
||||
|
import { ref, onMounted, onBeforeUnmount, watch } from 'vue' |
||||
|
import * as echarts from 'echarts' |
||||
|
|
||||
|
// 定义组件属性 |
||||
|
interface Props { |
||||
|
chartData?: Array<{ |
||||
|
name: string |
||||
|
currentValue: number |
||||
|
previousValue: number |
||||
|
growthRate?: number |
||||
|
currentPeriod?: string |
||||
|
previousPeriod?: string |
||||
|
}> |
||||
|
title?: string |
||||
|
currentColor?: string |
||||
|
previousColor?: string |
||||
|
showYAxis?: boolean |
||||
|
yAxisName?: string |
||||
|
currentLabel?: string |
||||
|
previousLabel?: string |
||||
|
chartType?: 'default' | 'percentage' |
||||
|
} |
||||
|
|
||||
|
const props = withDefaults(defineProps<Props>(), { |
||||
|
chartData: () => [], |
||||
|
title: '', |
||||
|
currentColor: '#1890FF', |
||||
|
previousColor: '#A9A9A9', |
||||
|
showYAxis: true, |
||||
|
yAxisName: '数值', |
||||
|
currentLabel: '本期', |
||||
|
previousLabel: '上期', |
||||
|
chartType: 'default' |
||||
|
}) |
||||
|
|
||||
|
// 图表容器引用和实例 |
||||
|
const chartContainer = ref<HTMLDivElement | null>(null) |
||||
|
let chartInstance: echarts.ECharts | null = null |
||||
|
|
||||
|
// 创建图表配置 |
||||
|
const createBarOption = (currentLabel: string, previousLabel: string, currentValue: number, previousValue: number) => { |
||||
|
// 根据图表类型决定格式化方式 |
||||
|
const isPercentage = props.chartType === 'percentage'; |
||||
|
|
||||
|
return { |
||||
|
tooltip: { |
||||
|
trigger: 'axis', |
||||
|
axisPointer: { type: 'shadow' }, |
||||
|
formatter: (params: any) => { |
||||
|
const value = params[0].value; |
||||
|
const formattedValue = isPercentage ? `${(value * 100).toFixed(1)}%` : value.toFixed(2); |
||||
|
return `${params[0].name}: ${formattedValue}`; |
||||
|
} |
||||
|
}, |
||||
|
grid: { |
||||
|
left: '15%', |
||||
|
right: '10%', |
||||
|
bottom: '20%', |
||||
|
top: '20%' |
||||
|
}, |
||||
|
xAxis: { |
||||
|
type: 'category', |
||||
|
data: [previousLabel, currentLabel], |
||||
|
axisTick: { show: false }, |
||||
|
axisLine: { show: false }, |
||||
|
axisLabel: { |
||||
|
color: 'rgba(255, 255, 255, 0.7)', |
||||
|
fontSize: 22 |
||||
|
} |
||||
|
}, |
||||
|
yAxis: { |
||||
|
type: 'value', |
||||
|
axisLabel: { |
||||
|
formatter: isPercentage ? (value: number) => `${(value * 100).toFixed(0)}%` : '{value}', |
||||
|
color: 'rgba(255, 255, 255, 0.7)' |
||||
|
}, |
||||
|
splitLine: { |
||||
|
lineStyle: { |
||||
|
color: 'rgba(255, 255, 255, 0.1)' |
||||
|
} |
||||
|
}, |
||||
|
axisLine: { |
||||
|
lineStyle: { |
||||
|
color: 'rgba(255, 255, 255, 0.3)' |
||||
|
} |
||||
|
}, |
||||
|
max: isPercentage ? 1 : undefined |
||||
|
}, |
||||
|
series: [{ |
||||
|
name: isPercentage ? '百分比' : '用量', |
||||
|
type: 'bar', |
||||
|
barWidth: '60%', |
||||
|
data: [ |
||||
|
{ value: previousValue, itemStyle: { color: props.previousColor } }, |
||||
|
{ value: currentValue, itemStyle: { color: props.currentColor } } |
||||
|
], |
||||
|
label: { |
||||
|
show: true, |
||||
|
position: 'top', |
||||
|
fontWeight: 'bold', |
||||
|
color: '#fff', |
||||
|
formatter: (params: any) => { |
||||
|
if (params.value <= 0) return ''; |
||||
|
return isPercentage ? `${(params.value * 100).toFixed(1)}%` : params.value.toFixed(2); |
||||
|
} |
||||
|
} |
||||
|
}], |
||||
|
// 添加标题显示增长率 |
||||
|
title: props.chartData[0]?.growthRate !== undefined ? { |
||||
|
text: ` |
||||
|
数差: ${(currentValue - previousValue).toFixed(2)}${props.chartType === 'percentage' ? '%' : ''} |
||||
|
${props.chartData[0].growthRate >= 0 ? '↑' : '↓'} ${(props.chartData[0].growthRate * 100).toFixed(1)}%`, |
||||
|
left: 'center', |
||||
|
top: '0%', |
||||
|
textStyle: { |
||||
|
fontSize: 36, |
||||
|
fontWeight: 'bold', |
||||
|
color: props.chartData[0].growthRate >= 0 ? '#52c41a' : '#f5222d', |
||||
|
fontFamily: 'Arial, sans-serif' |
||||
|
} |
||||
|
} : undefined |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
// 初始化图表 |
||||
|
const initChart = () => { |
||||
|
if (chartContainer.value) { |
||||
|
chartInstance = echarts.init(chartContainer.value) |
||||
|
updateChart() |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 更新图表 |
||||
|
const updateChart = () => { |
||||
|
if (!chartInstance || !props.chartData.length) return |
||||
|
|
||||
|
const dataItem = props.chartData[0] |
||||
|
|
||||
|
// 优先使用period字段作为标签,如果没有则使用默认标签 |
||||
|
const currentLabel = dataItem.currentPeriod || props.currentLabel |
||||
|
const previousLabel = dataItem.previousPeriod || props.previousLabel |
||||
|
|
||||
|
const option = createBarOption( |
||||
|
currentLabel, |
||||
|
previousLabel, |
||||
|
dataItem.currentValue, |
||||
|
dataItem.previousValue |
||||
|
) |
||||
|
|
||||
|
chartInstance.setOption(option) |
||||
|
} |
||||
|
|
||||
|
// 监听数据变化 |
||||
|
watch( |
||||
|
() => props.chartData, |
||||
|
() => { |
||||
|
updateChart() |
||||
|
}, |
||||
|
{ deep: true } |
||||
|
) |
||||
|
|
||||
|
// 窗口大小改变时重绘图表 |
||||
|
const handleResize = () => { |
||||
|
if (chartInstance) { |
||||
|
chartInstance.resize() |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 组件挂载时初始化图表 |
||||
|
onMounted(() => { |
||||
|
initChart() |
||||
|
|
||||
|
window.addEventListener('resize', handleResize) |
||||
|
}) |
||||
|
|
||||
|
// 组件销毁前清理资源 |
||||
|
onBeforeUnmount(() => { |
||||
|
window.removeEventListener('resize', handleResize) |
||||
|
if (chartInstance) { |
||||
|
chartInstance.dispose() |
||||
|
chartInstance = null |
||||
|
} |
||||
|
}) |
||||
|
</script> |
||||
|
|
||||
|
<style scoped> |
||||
|
.comparison-bar-chart-container { |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
} |
||||
|
</style> |
||||
File diff suppressed because it is too large
Loading…
Reference in new issue