黄伟杰 21 hours ago
commit 0d75fe9994

@ -42,6 +42,61 @@ export interface EnergyDeviceDataRecordVO {
latestDataTime?: string latestDataTime?: string
} }
export interface EnergyOverviewMetricVO {
key: string
label: string
value: string
unit?: string
subLabel?: string
subValue?: string
change?: string
down?: boolean
}
export interface EnergyOverviewTrendVO {
unit?: string
xAxis: string[]
data: string[]
}
export interface EnergyOverviewRegionItemVO {
name: string
value: string
percent: string
}
export interface EnergyOverviewRegionVO {
unit?: string
totalValue: string
items: EnergyOverviewRegionItemVO[]
}
export interface EnergyOverviewRankVO {
id: number
name: string
region: string
value: string
}
export interface EnergyOverviewDetailVO {
id: number
name: string
energyType: string
region: string
value: string
startTime: string
endTime: string
}
export interface EnergyOverviewRespVO {
metrics: EnergyOverviewMetricVO[]
trendChart: EnergyOverviewTrendVO
regionChart: EnergyOverviewRegionVO
rankList: EnergyOverviewRankVO[]
total: number
detailList: EnergyOverviewDetailVO[]
}
// 能源设备 API // 能源设备 API
export const EnergyDeviceApi = { export const EnergyDeviceApi = {
// 查询能源设备分页 // 查询能源设备分页
@ -66,6 +121,10 @@ export const EnergyDeviceApi = {
return await request.get({ url: `/mes/energy-device/queryDataRecords`, params }) return await request.get({ url: `/mes/energy-device/queryDataRecords`, params })
}, },
queryOverviewData: async (params: any) => {
return await request.get({ url: `/mes/energy-device/queryOverviewData`, params })
},
exportQueryDataRecords: async (params: any) => { exportQueryDataRecords: async (params: any) => {
return await request.download({ url: `/mes/energy-device/record-export-excel`, params }) return await request.download({ url: `/mes/energy-device/record-export-excel`, params })
}, },

@ -4727,5 +4727,42 @@ export default {
warningNoPlanData: 'No plan data to save', warningNoPlanData: 'No plan data to save',
saveSuccess: 'Schedule saved successfully' saveSuccess: 'Schedule saved successfully'
} }
},
EnergyOverview: {
messages: {
singleDayOnly: 'The time range can only query data within a single day'
},
filters: {
org: 'Region',
orgPlaceholder: 'Please select region',
energyType: 'Energy Type',
energyTypePlaceholder: 'Please select energy type',
timeRange: 'Time Range'
},
panels: {
trend: 'Energy Usage Trend',
region: 'Regional Energy Share',
top5: 'Top 5 Energy Consumers',
detail: 'Energy Details'
},
table: {
rank: 'Rank',
name: 'Name',
region: 'Region',
usage: 'Usage',
meterName: 'Meter Name',
energyType: 'Energy Type',
energyUsage: 'Energy Usage',
startTime: 'Start Time',
endTime: 'End Time'
},
chart: {
usage: 'Usage',
time: 'Time',
regionEnergy: 'Regional Energy',
totalUsage: 'Total Usage'
},
empty: 'Please configure energy types first',
exportSuccess: 'Export started'
} }
} }

@ -4938,5 +4938,42 @@ export default {
warningNoPlanData: '暂无可保存的计划数据', warningNoPlanData: '暂无可保存的计划数据',
saveSuccess: '排产计划保存成功' saveSuccess: '排产计划保存成功'
} }
},
EnergyOverview: {
messages: {
singleDayOnly: '时间范围只能查询同一天的数据'
},
filters: {
org: '所属区域',
orgPlaceholder: '请选择所属区域',
energyType: '能源类型',
energyTypePlaceholder: '请选择能源类型',
timeRange: '时间范围'
},
panels: {
trend: '能源用量趋势',
region: '区域能耗占比',
top5: '能耗排行 TOP5',
detail: '能源明细表'
},
table: {
rank: '排名',
name: '名称',
region: '所属区域',
usage: '用量',
meterName: '表名称',
energyType: '能源类型',
energyUsage: '能源用量',
startTime: '开始时间',
endTime: '结束时间'
},
chart: {
usage: '用量',
time: '时间',
regionEnergy: '区域能耗',
totalUsage: '总用量'
},
empty: '请先配置能源类型',
exportSuccess: '导出已发起'
} }
} }

@ -8,29 +8,35 @@
:inline="true" :inline="true"
label-width="auto" label-width="auto"
> >
<el-form-item label="所属区域" prop="orgId"> <el-form-item :label="t('EnergyOverview.filters.org')" prop="orgId">
<el-select v-model="queryParams.orgId" clearable filterable placeholder="请选择所属区域" class="!w-220px"> <el-select
v-model="queryParams.orgId"
clearable
filterable
:placeholder="t('EnergyOverview.filters.orgPlaceholder')"
class="!w-220px"
>
<el-option v-for="item in orgOptions" :key="item.value" :label="item.label" :value="item.value" /> <el-option v-for="item in orgOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="能源类型" prop="energyTypeId"> <el-form-item :label="t('EnergyOverview.filters.energyType')" prop="energyTypeId">
<el-select <el-select
v-model="queryParams.energyTypeId" v-model="queryParams.energyTypeId"
:disabled="!hasEnergyTypes" :disabled="!hasEnergyTypes"
placeholder="请选择能源类型" :placeholder="t('EnergyOverview.filters.energyTypePlaceholder')"
class="!w-220px" class="!w-220px"
> >
<el-option v-for="item in energyTypeOptions" :key="item.id" :label="item.name" :value="item.id" /> <el-option v-for="item in energyTypeOptions" :key="item.id" :label="item.name" :value="item.id" />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="时间范围" prop="timeRange"> <el-form-item :label="t('EnergyOverview.filters.timeRange')" prop="timeRange">
<el-date-picker <el-date-picker
v-model="queryParams.timeRange" v-model="queryParams.timeRange"
type="datetimerange" type="datetimerange"
value-format="YYYY-MM-DD HH:mm:ss" value-format="YYYY-MM-DD HH:mm:ss"
format="YYYY-MM-DD HH:mm:ss" format="YYYY-MM-DD HH:mm:ss"
start-placeholder="开始时间" :start-placeholder="t('common.startTimeText')"
end-placeholder="结束时间" :end-placeholder="t('common.endTimeText')"
range-separator="~" range-separator="~"
:default-time="[new Date('2000-01-01 00:00:00'), new Date('2000-01-01 23:59:59')]" :default-time="[new Date('2000-01-01 00:00:00'), new Date('2000-01-01 23:59:59')]"
class="!w-420px" class="!w-420px"
@ -39,15 +45,15 @@
<el-form-item> <el-form-item>
<el-button type="primary" @click="handleQuery"> <el-button type="primary" @click="handleQuery">
<Icon icon="ep:search" class="mr-5px" /> <Icon icon="ep:search" class="mr-5px" />
查询 {{ t('common.query') }}
</el-button> </el-button>
<el-button @click="resetQuery"> <el-button @click="resetQuery">
<Icon icon="ep:refresh" class="mr-5px" /> <Icon icon="ep:refresh" class="mr-5px" />
重置 {{ t('common.reset') }}
</el-button> </el-button>
<el-button type="success" plain @click="handleExport"> <el-button type="success" plain @click="handleExport">
<Icon icon="ep:download" class="mr-5px" /> <Icon icon="ep:download" class="mr-5px" />
导出 {{ t('action.export') }}
</el-button> </el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
@ -63,7 +69,7 @@
<div class="metric-label">{{ item.label }}</div> <div class="metric-label">{{ item.label }}</div>
<div class="metric-value"> <div class="metric-value">
{{ item.value }} {{ item.value }}
<span class="metric-unit">{{ item.unit }}</span> <span v-if="item.unit" class="metric-unit">{{ item.unit }}</span>
</div> </div>
<div class="metric-sub"> <div class="metric-sub">
<span>{{ item.subLabel }}</span> <span>{{ item.subLabel }}</span>
@ -82,17 +88,9 @@
<ContentWrap class="overview-panel"> <ContentWrap class="overview-panel">
<div class="panel-header"> <div class="panel-header">
<div class="panel-title"> <div class="panel-title">
能源用量趋势 {{ t('EnergyOverview.panels.trend') }}
<Icon icon="ep:info-filled" class="panel-info" /> <Icon icon="ep:info-filled" class="panel-info" />
</div> </div>
<div class="panel-actions">
<el-button text type="primary">
<Icon icon="ep:trend-charts" />
</el-button>
<el-button text type="primary">
<Icon icon="ep:download" />
</el-button>
</div>
</div> </div>
<Echart :height="300" :options="trendChartOptions" /> <Echart :height="300" :options="trendChartOptions" />
</ContentWrap> </ContentWrap>
@ -101,17 +99,9 @@
<ContentWrap class="overview-panel"> <ContentWrap class="overview-panel">
<div class="panel-header"> <div class="panel-header">
<div class="panel-title"> <div class="panel-title">
区域能耗占比 {{ t('EnergyOverview.panels.region') }}
<Icon icon="ep:info-filled" class="panel-info" /> <Icon icon="ep:info-filled" class="panel-info" />
</div> </div>
<div class="panel-actions">
<el-button text type="primary">
<Icon icon="ep:pie-chart" />
</el-button>
<el-button text type="primary">
<Icon icon="ep:download" />
</el-button>
</div>
</div> </div>
<Echart :height="300" :options="regionPieOptions" /> <Echart :height="300" :options="regionPieOptions" />
</ContentWrap> </ContentWrap>
@ -123,19 +113,24 @@
<ContentWrap class="overview-panel"> <ContentWrap class="overview-panel">
<div class="panel-header"> <div class="panel-header">
<div class="panel-title"> <div class="panel-title">
能耗排行 TOP5 {{ t('EnergyOverview.panels.top5') }}
<Icon icon="ep:info-filled" class="panel-info" /> <Icon icon="ep:info-filled" class="panel-info" />
</div> </div>
</div> </div>
<el-table :data="rankList" :stripe="true" :show-overflow-tooltip="true"> <el-table :data="rankList" :stripe="true" :show-overflow-tooltip="true">
<el-table-column label="排名" align="center" width="70"> <el-table-column :label="t('EnergyOverview.table.rank')" align="center" width="70">
<template #default="scope"> <template #default="scope">
<span class="rank-index" :class="`rank-${scope.$index + 1}`">{{ scope.$index + 1 }}</span> <span class="rank-index" :class="`rank-${scope.$index + 1}`">{{ scope.$index + 1 }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="名称" prop="name" min-width="150" /> <el-table-column :label="t('EnergyOverview.table.name')" prop="name" min-width="150" />
<el-table-column label="所属区域" prop="region" width="110" /> <el-table-column :label="t('EnergyOverview.table.region')" prop="region" width="110" />
<el-table-column :label="`用量 (${selectedEnergyUnit})`" prop="value" align="right" width="120" /> <el-table-column
:label="`${t('EnergyOverview.table.usage')} (${selectedEnergyUnit})`"
prop="value"
align="right"
width="120"
/>
</el-table> </el-table>
</ContentWrap> </ContentWrap>
</el-col> </el-col>
@ -143,17 +138,22 @@
<ContentWrap class="overview-panel"> <ContentWrap class="overview-panel">
<div class="panel-header"> <div class="panel-header">
<div class="panel-title"> <div class="panel-title">
能源明细表 {{ t('EnergyOverview.panels.detail') }}
<Icon icon="ep:info-filled" class="panel-info" /> <Icon icon="ep:info-filled" class="panel-info" />
</div> </div>
</div> </div>
<el-table v-loading="loading" :data="pageDetailList" :stripe="true" :show-overflow-tooltip="true"> <el-table v-loading="loading" :data="pageDetailList" :stripe="true" :show-overflow-tooltip="true">
<el-table-column label="表名称" prop="name" min-width="170" /> <el-table-column :label="t('EnergyOverview.table.meterName')" prop="name" min-width="170" />
<el-table-column label="能源类型" prop="energyType" align="center" width="100" /> <el-table-column :label="t('EnergyOverview.table.energyType')" prop="energyType" align="center" width="100" />
<el-table-column label="所属区域" prop="region" align="center" width="120" /> <el-table-column :label="t('EnergyOverview.table.region')" prop="region" align="center" width="120" />
<el-table-column :label="`能源用量 (${selectedEnergyUnit})`" prop="value" align="right" width="150" /> <el-table-column
<el-table-column label="开始时间" prop="startTime" align="center" width="180" /> :label="`${t('EnergyOverview.table.energyUsage')} (${selectedEnergyUnit})`"
<el-table-column label="结束时间" prop="endTime" align="center" width="180" /> prop="value"
align="right"
width="150"
/>
<el-table-column :label="t('EnergyOverview.table.startTime')" prop="startTime" align="center" width="180" />
<el-table-column :label="t('EnergyOverview.table.endTime')" prop="endTime" align="center" width="180" />
</el-table> </el-table>
<Pagination <Pagination
:total="total" :total="total"
@ -167,7 +167,7 @@
</template> </template>
<ContentWrap v-else class="empty-panel"> <ContentWrap v-else class="empty-panel">
<el-empty description="请先配置能源类型" :image-size="120" /> <el-empty :description="t('EnergyOverview.empty')" :image-size="120" />
</ContentWrap> </ContentWrap>
</div> </div>
</template> </template>
@ -176,6 +176,14 @@
import { EChartsOption } from 'echarts' import { EChartsOption } from 'echarts'
import { Echart } from '@/components/Echart' import { Echart } from '@/components/Echart'
import { EnergyTypeApi, EnergyTypeVO } from '@/api/mes/energytype' import { EnergyTypeApi, EnergyTypeVO } from '@/api/mes/energytype'
import {
EnergyDeviceApi,
EnergyOverviewDetailVO,
EnergyOverviewMetricVO,
EnergyOverviewRankVO,
EnergyOverviewRespVO
} from '@/api/mes/energydevice'
import { OrganizationApi, OrganizationVO } from '@/api/mes/organization'
defineOptions({ name: 'EnergyOverview' }) defineOptions({ name: 'EnergyOverview' })
@ -192,24 +200,40 @@ interface MetricCard {
subValue?: string subValue?: string
} }
interface DetailRow { type DetailRow = EnergyOverviewDetailVO
id: number
name: string
energyType: string
region: string
value: string
startTime: string
endTime: string
}
const { t } = useI18n()
const message = useMessage() const message = useMessage()
const defaultTimeRange = ['2026-05-04 00:00:00', '2026-05-05 23:59:59'] const getTodayTimeRange = () => {
const orgOptions = [ const now = new Date()
{ label: '成型系统', value: 1 }, const year = now.getFullYear()
{ label: '蛋托线01', value: 2 }, const month = String(now.getMonth() + 1).padStart(2, '0')
{ label: '烘干系统', value: 3 } const day = String(now.getDate()).padStart(2, '0')
] const date = `${year}-${month}-${day}`
return [`${date} 00:00:00`, `${date} 23:59:59`]
}
const parseRangeTime = (value?: string) => {
if (!value) return undefined
const date = new Date(value.replace(/-/g, '/'))
return Number.isNaN(date.getTime()) ? undefined : date
}
const isSingleDayRange = (timeRange?: string[]) => {
if (!timeRange || timeRange.length !== 2) return false
const start = parseRangeTime(timeRange[0])
const end = parseRangeTime(timeRange[1])
if (!start || !end) return false
return (
start.getFullYear() === end.getFullYear() &&
start.getMonth() === end.getMonth() &&
start.getDate() === end.getDate()
)
}
const defaultTimeRange = getTodayTimeRange()
const orgOptions = ref<{ label: string; value: string | number; raw?: OrganizationVO }[]>([])
const loading = ref(false) const loading = ref(false)
const queryFormRef = ref() const queryFormRef = ref()
@ -224,55 +248,19 @@ const queryParams = reactive({
}) })
const metricCards = ref<MetricCard[]>([]) const metricCards = ref<MetricCard[]>([])
const rankList = ref([ const rankList = ref<EnergyOverviewRankVO[]>([])
{ name: '蛋托成型柜用电量', region: '成型系统', value: '2,139.09' },
{ name: '蛋托线真空柜用电量', region: '蛋托线01', value: '0' },
{ name: '蛋托干燥柜用电量', region: '烘干系统', value: '0' },
{ name: '蛋托线其它用电量', region: '蛋托线01', value: '0' },
{ name: '烘干系统其它用电量', region: '烘干系统', value: '0' }
])
const detailList = ref<DetailRow[]>([])
const pageDetailList = ref<DetailRow[]>([]) const pageDetailList = ref<DetailRow[]>([])
const total = ref(0) const total = ref(0)
const hasEnergyTypes = computed(() => energyTypeOptions.value.length > 0) const hasEnergyTypes = computed(() => energyTypeOptions.value.length > 0)
const selectedEnergyType = computed(() => const selectedEnergyType = computed(() =>
energyTypeOptions.value.find((item) => item.id === queryParams.energyTypeId) energyTypeOptions.value.find((item) => item.id === queryParams.energyTypeId)
) )
const selectedEnergyName = computed(() => selectedEnergyType.value?.name || '电')
const selectedEnergyUnit = computed(() => selectedEnergyType.value?.unit || 'kWh') const selectedEnergyUnit = computed(() => selectedEnergyType.value?.unit || 'kWh')
const selectedUnitConsumptionUnit = computed(() => `${selectedEnergyUnit.value}/件`)
const trendXAxis = ref<string[]>([])
const hours = [ const trendSeries = ref<number[]>([])
'00:00', const regionItems = ref<{ name: string; value: number; percent: string }[]>([])
'01:00', const regionTotal = ref('0')
'02:00',
'03:00',
'04:00',
'05:00',
'06:00',
'07:00',
'08:00',
'09:00',
'10:00',
'11:00',
'12:00',
'13:00',
'14:00',
'15:00',
'16:00',
'17:00',
'18:00',
'19:00',
'20:00',
'21:00',
'22:00'
]
const trendData = [90, 80, 72, 75, 74, 85, 105, 116, 120, 112, 123, 127, 122, 146, 156.8, 147, 116, 94, 88, 102, 103, 116, 106]
const regionData = [
{ name: '成型系统', value: 2139.09 },
{ name: '蛋托线01', value: 0 },
{ name: '烘干系统', value: 0 }
]
const trendChartOptions = reactive<EChartsOption>({ const trendChartOptions = reactive<EChartsOption>({
color: ['#2f7df6'], color: ['#2f7df6'],
@ -280,12 +268,12 @@ const trendChartOptions = reactive<EChartsOption>({
trigger: 'axis', trigger: 'axis',
formatter: (params: any) => { formatter: (params: any) => {
const item = params?.[0] const item = params?.[0]
return `${item.axisValue}<br />用电量:${item.data} kWh` return `${item?.axisValue || ''}<br />${t('EnergyOverview.chart.usage')}${item?.data || 0} ${selectedEnergyUnit.value}`
} }
}, },
legend: { legend: {
top: 4, top: 4,
data: ['用电量 (kWh)'] data: [t('EnergyOverview.chart.usage')]
}, },
grid: { grid: {
left: 24, left: 24,
@ -297,25 +285,23 @@ const trendChartOptions = reactive<EChartsOption>({
xAxis: { xAxis: {
type: 'category', type: 'category',
boundaryGap: false, boundaryGap: false,
name: '时间(小时)', name: t('EnergyOverview.chart.time'),
nameLocation: 'middle', nameLocation: 'middle',
nameGap: 28, nameGap: 28,
data: hours, data: trendXAxis.value,
axisLine: { lineStyle: { color: '#dcdfe6' } }, axisLine: { lineStyle: { color: '#dcdfe6' } },
axisLabel: { color: '#606266' } axisLabel: { color: '#606266' }
}, },
yAxis: { yAxis: {
type: 'value', type: 'value',
name: 'kWh', name: selectedEnergyUnit.value,
min: 0, min: 0,
max: 180,
interval: 30,
axisLabel: { color: '#606266' }, axisLabel: { color: '#606266' },
splitLine: { lineStyle: { color: '#e8edf5' } } splitLine: { lineStyle: { color: '#e8edf5' } }
}, },
series: [ series: [
{ {
name: '用电量 (kWh)', name: t('EnergyOverview.chart.usage'),
type: 'line', type: 'line',
smooth: true, smooth: true,
symbol: 'circle', symbol: 'circle',
@ -334,21 +320,16 @@ const trendChartOptions = reactive<EChartsOption>({
] ]
} }
}, },
data: trendData, data: trendSeries.value
markPoint: {
symbolSize: 48,
label: { color: '#2f7df6', formatter: '{c}' },
data: [{ type: 'max', name: '峰值' }]
}
} }
] ]
}) as EChartsOption })
const regionPieOptions = reactive<EChartsOption>({ const regionPieOptions = reactive<EChartsOption>({
color: ['#2f7df6', '#52c41a', '#faad14'], color: ['#2f7df6', '#52c41a', '#faad14', '#13c2c2', '#fa8c16'],
tooltip: { tooltip: {
trigger: 'item', trigger: 'item',
formatter: '{b}<br />{c} kWh ({d}%)' formatter: `{b}<br />{c} ${selectedEnergyUnit.value} ({d}%)`
}, },
legend: { legend: {
orient: 'vertical', orient: 'vertical',
@ -357,15 +338,15 @@ const regionPieOptions = reactive<EChartsOption>({
itemWidth: 12, itemWidth: 12,
itemHeight: 12, itemHeight: 12,
formatter: (name: string) => { formatter: (name: string) => {
const item = regionData.find((row) => row.name === name) const item = regionItems.value.find((row) => row.name === name)
const value = item?.value ?? 0 const value = item?.value ?? 0
const percent = value > 0 ? '100.00' : '0.00' const percent = item?.percent ?? '0.00'
return `${name} ${value.toLocaleString()} kWh (${percent}%)` return `${name} ${value.toLocaleString()} ${selectedEnergyUnit.value} (${percent}%)`
} }
}, },
series: [ series: [
{ {
name: '区域能耗', name: t('EnergyOverview.chart.regionEnergy'),
type: 'pie', type: 'pie',
radius: ['58%', '76%'], radius: ['58%', '76%'],
center: ['34%', '50%'], center: ['34%', '50%'],
@ -373,7 +354,7 @@ const regionPieOptions = reactive<EChartsOption>({
label: { label: {
show: true, show: true,
position: 'center', position: 'center',
formatter: '总用电量\n{total|2,139.09 kWh}', formatter: `${t('EnergyOverview.chart.totalUsage')}\n{total|${regionTotal.value} ${selectedEnergyUnit.value}}`,
color: '#303133', color: '#303133',
rich: { rich: {
total: { total: {
@ -385,116 +366,145 @@ const regionPieOptions = reactive<EChartsOption>({
} }
}, },
labelLine: { show: false }, labelLine: { show: false },
data: regionData data: regionItems.value
} }
] ]
}) as EChartsOption })
const buildMetrics = () => { const metricThemeMap: Record<string, { icon: string; theme: string }> = {
metricCards.value = [ total: { icon: 'ep:lightning', theme: 'blue' },
{ deviceCount: { icon: 'ep:cpu', theme: 'green' },
key: 'total', topDevice: { icon: 'ep:histogram', theme: 'purple' },
label: '总用电量', topRegion: { icon: 'ep:pie-chart', theme: 'cyan' },
value: '2,139.09', range: { icon: 'ep:calendar', theme: 'orange' }
unit: selectedEnergyUnit.value,
icon: 'ep:lightning',
theme: 'blue',
subLabel: '较昨日',
change: '12.53%'
},
{
key: 'today',
label: '今日用电量',
value: '860.32',
unit: selectedEnergyUnit.value,
icon: 'ep:calendar',
theme: 'green',
subLabel: '较昨日',
change: '12.15%'
},
{
key: 'peak',
label: '峰值用电',
value: '156.8',
unit: selectedEnergyUnit.value,
icon: 'ep:histogram',
theme: 'purple',
subLabel: '时间:',
subValue: '2026-05-04 14:00 ~ 15:00'
},
{
key: 'unit',
label: '单位产量能耗',
value: '0.38',
unit: selectedUnitConsumptionUnit.value,
icon: 'ep:orange',
theme: 'orange',
subLabel: '较昨日',
change: '5.21%',
down: true
},
{
key: 'region',
label: '最大耗能区域',
value: '成型系统',
unit: '',
icon: 'ep:pie-chart',
theme: 'cyan',
subLabel: '占比:',
subValue: '100.00%'
}
]
} }
const buildDetails = () => { const buildFallbackTrendXAxis = () => {
const timeRange = queryParams.timeRange?.length === 2 ? queryParams.timeRange : defaultTimeRange const timeRange = queryParams.timeRange?.length === 2 ? queryParams.timeRange : defaultTimeRange
detailList.value = [ const [startTime, endTime] = timeRange
{ const start = new Date(startTime.replace(/-/g, '/'))
id: 1, const end = new Date(endTime.replace(/-/g, '/'))
name: '蛋托线真空柜用电量', if (Number.isNaN(start.getTime()) || Number.isNaN(end.getTime()) || start > end) {
energyType: selectedEnergyName.value, return []
region: '蛋托线01', }
value: '0',
startTime: timeRange[0], const sameDay =
endTime: timeRange[1] start.getFullYear() === end.getFullYear() &&
}, start.getMonth() === end.getMonth() &&
{ start.getDate() === end.getDate()
id: 2,
name: '蛋托成型柜用电量', const axis: string[] = []
energyType: selectedEnergyName.value, if (sameDay) {
region: '成型系统', const current = new Date(start)
value: '2,139.09', current.setMinutes(0, 0, 0)
startTime: timeRange[0], const limit = new Date(end)
endTime: timeRange[1] limit.setMinutes(0, 0, 0)
}, while (current <= limit) {
{ axis.push(`${String(current.getHours()).padStart(2, '0')}:00`)
id: 3, current.setHours(current.getHours() + 1)
name: '蛋托干燥柜用电量',
energyType: selectedEnergyName.value,
region: '烘干系统',
value: '0',
startTime: timeRange[0],
endTime: timeRange[1]
} }
] return axis
total.value = detailList.value.length }
const current = new Date(start)
current.setHours(0, 0, 0, 0)
const limit = new Date(end)
limit.setHours(0, 0, 0, 0)
while (current <= limit) {
const year = current.getFullYear()
const month = String(current.getMonth() + 1).padStart(2, '0')
const day = String(current.getDate()).padStart(2, '0')
axis.push(`${year}-${month}-${day}`)
current.setDate(current.getDate() + 1)
}
return axis
}
const normalizeMetricCards = (metrics: EnergyOverviewMetricVO[]) => {
metricCards.value = metrics.map((item) => ({
key: item.key,
label: item.label,
value: item.value,
unit: item.unit || '',
icon: metricThemeMap[item.key]?.icon || 'ep:data-line',
theme: metricThemeMap[item.key]?.theme || 'blue',
subLabel: item.subLabel || '',
subValue: item.subValue || '',
change: item.change,
down: item.down
}))
}
const updateCharts = (data: EnergyOverviewRespVO) => {
const fallbackXAxis = buildFallbackTrendXAxis()
trendXAxis.value = data.trendChart?.xAxis?.length ? data.trendChart.xAxis : fallbackXAxis
const responseSeries = (data.trendChart?.data || []).map((item) => Number(item) || 0)
trendSeries.value =
responseSeries.length > 0
? responseSeries
: new Array(trendXAxis.value.length).fill(0)
regionItems.value = (data.regionChart?.items || []).map((item) => ({
name: item.name,
value: Number(item.value) || 0,
percent: item.percent
}))
regionTotal.value = data.regionChart?.totalValue || '0'
trendChartOptions.xAxis = {
...(trendChartOptions.xAxis as any),
data: trendXAxis.value,
name: t('EnergyOverview.chart.time')
}
trendChartOptions.yAxis = {
...(trendChartOptions.yAxis as any),
name: selectedEnergyUnit.value
}
if (Array.isArray(trendChartOptions.legend?.data)) {
trendChartOptions.legend.data = [t('EnergyOverview.chart.usage')]
}
if (Array.isArray(trendChartOptions.series) && trendChartOptions.series[0]) {
;(trendChartOptions.series[0] as any).name = t('EnergyOverview.chart.usage')
;(trendChartOptions.series[0] as any).data = trendSeries.value
}
if (Array.isArray(regionPieOptions.series) && regionPieOptions.series[0]) {
;(regionPieOptions.series[0] as any).name = t('EnergyOverview.chart.regionEnergy')
;(regionPieOptions.series[0] as any).data = regionItems.value
;(regionPieOptions.series[0] as any).label = {
...(regionPieOptions.series[0] as any).label,
formatter: `${t('EnergyOverview.chart.totalUsage')}\n{total|${regionTotal.value} ${selectedEnergyUnit.value}}`
}
}
} }
const getList = () => { const getList = async () => {
if (!hasEnergyTypes.value) { if (!hasEnergyTypes.value) {
metricCards.value = [] metricCards.value = []
detailList.value = [] rankList.value = []
pageDetailList.value = [] pageDetailList.value = []
total.value = 0 total.value = 0
return return
} }
loading.value = true loading.value = true
buildMetrics() try {
buildDetails() const timeRange = queryParams.timeRange?.length === 2 ? queryParams.timeRange : defaultTimeRange
const start = (queryParams.pageNo - 1) * queryParams.pageSize const res = await EnergyDeviceApi.queryOverviewData({
pageDetailList.value = detailList.value.slice(start, start + queryParams.pageSize) orgId: queryParams.orgId,
loading.value = false energyTypeId: queryParams.energyTypeId,
startTime: timeRange[0],
endTime: timeRange[1],
pageNo: queryParams.pageNo,
pageSize: queryParams.pageSize
})
const data = ((res as any)?.data || res) as EnergyOverviewRespVO
normalizeMetricCards(data.metrics || [])
rankList.value = data.rankList || []
pageDetailList.value = data.detailList || []
total.value = data.total || 0
updateCharts(data)
} finally {
loading.value = false
}
} }
const normalizeEnergyTypeList = (data: unknown): EnergyTypeVO[] => { const normalizeEnergyTypeList = (data: unknown): EnergyTypeVO[] => {
@ -508,31 +518,33 @@ const normalizeEnergyTypeList = (data: unknown): EnergyTypeVO[] => {
return [] return []
} }
const getDefaultEnergyType = (list: EnergyTypeVO[]) => { const getDefaultEnergyType = (list: EnergyTypeVO[]) => list[0]
return (
list.find((item) => item.name === '电') ||
list.find((item) => item.name === '水') ||
list.find((item) => item.name === '气') ||
list[0]
)
}
const getEnergyTypes = async () => { const getEnergyTypes = async () => {
try { try {
const data = await EnergyTypeApi.getEnergyTypeList() const data = await EnergyTypeApi.getEnergyTypeList({ orgId: queryParams.orgId })
energyTypeOptions.value = normalizeEnergyTypeList(data) energyTypeOptions.value = normalizeEnergyTypeList(data)
} catch (error) { } catch (error) {
energyTypeOptions.value = [] energyTypeOptions.value = []
} }
const defaultEnergyType = getDefaultEnergyType(energyTypeOptions.value) let defaultEnergyType = getDefaultEnergyType(energyTypeOptions.value)
try {
const deviceRes = await EnergyDeviceApi.getList({ orgId: queryParams.orgId })
const devices = ((deviceRes as any)?.data || deviceRes || []) as Array<{ deviceTypeId?: number }>
const deviceTypeIds = new Set(devices.map((item) => item.deviceTypeId).filter(Boolean))
defaultEnergyType =
energyTypeOptions.value.find((item) => deviceTypeIds.has(item.id)) || defaultEnergyType
} catch (error) {
// ignore and use the first energy type
}
defaultEnergyTypeId.value = defaultEnergyType?.id defaultEnergyTypeId.value = defaultEnergyType?.id
queryParams.energyTypeId = defaultEnergyType?.id queryParams.energyTypeId = defaultEnergyType?.id
} }
const handleQuery = () => { const handleQuery = () => {
if (!hasEnergyTypes.value) { if (!isSingleDayRange(queryParams.timeRange)) {
getList() message.warning(t('EnergyOverview.messages.singleDayOnly'))
return return
} }
queryParams.pageNo = 1 queryParams.pageNo = 1
@ -543,20 +555,51 @@ const resetQuery = () => {
queryFormRef.value?.resetFields() queryFormRef.value?.resetFields()
queryParams.orgId = undefined queryParams.orgId = undefined
queryParams.energyTypeId = defaultEnergyTypeId.value queryParams.energyTypeId = defaultEnergyTypeId.value
queryParams.timeRange = [...defaultTimeRange] queryParams.timeRange = getTodayTimeRange()
handleQuery() handleQuery()
} }
const handleExport = () => { const handleExport = async () => {
message.info('示例页暂未接入导出接口') if (!isSingleDayRange(queryParams.timeRange)) {
message.warning(t('EnergyOverview.messages.singleDayOnly'))
return
}
const timeRange = queryParams.timeRange?.length === 2 ? queryParams.timeRange : defaultTimeRange
await EnergyDeviceApi.exportQueryDataRecords({
orgId: queryParams.orgId,
startTime: timeRange[0],
endTime: timeRange[1]
})
message.success(t('EnergyOverview.exportSuccess'))
}
const loadOrgOptions = async () => {
if (orgOptions.value.length) return
const data: any = await OrganizationApi.getOrganizationList({})
const rows = (Array.isArray(data) ? data : data?.list ?? data?.data ?? []) as OrganizationVO[]
orgOptions.value = rows
.filter((r) => r?.id !== undefined && r?.name)
.map((r) => ({ label: r.name, value: r.id, raw: r }))
} }
onMounted(async () => { onMounted(async () => {
await loadOrgOptions()
await getEnergyTypes() await getEnergyTypes()
if (hasEnergyTypes.value) { if (hasEnergyTypes.value) {
getList() getList()
} }
}) })
watch(
() => queryParams.orgId,
async () => {
await getEnergyTypes()
queryParams.pageNo = 1
if (hasEnergyTypes.value) {
getList()
}
}
)
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@ -705,19 +748,6 @@ onMounted(async () => {
color: var(--el-color-primary-light-3); color: var(--el-color-primary-light-3);
} }
.panel-actions {
display: inline-flex;
align-items: center;
gap: 4px;
.el-button {
width: 30px;
height: 30px;
padding: 0;
border: 1px solid var(--el-border-color-light);
}
}
.rank-index { .rank-index {
display: inline-flex; display: inline-flex;
width: 22px; width: 22px;

Loading…
Cancel
Save