能源概览

mcm
liutao 1 week ago
parent 13133c28be
commit 3745ef5bbc

@ -42,6 +42,61 @@ export interface EnergyDeviceDataRecordVO {
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
export const EnergyDeviceApi = {
// 查询能源设备分页
@ -66,6 +121,10 @@ export const EnergyDeviceApi = {
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) => {
return await request.download({ url: `/mes/energy-device/record-export-excel`, params })
},

@ -9,7 +9,13 @@
label-width="auto"
>
<el-form-item label="所属区域" prop="orgId">
<el-select v-model="queryParams.orgId" clearable filterable placeholder="请选择所属区域" class="!w-220px">
<el-select
v-model="queryParams.orgId"
clearable
filterable
placeholder="请选择所属区域"
class="!w-220px"
>
<el-option v-for="item in orgOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
@ -63,7 +69,7 @@
<div class="metric-label">{{ item.label }}</div>
<div class="metric-value">
{{ item.value }}
<span class="metric-unit">{{ item.unit }}</span>
<span v-if="item.unit" class="metric-unit">{{ item.unit }}</span>
</div>
<div class="metric-sub">
<span>{{ item.subLabel }}</span>
@ -85,14 +91,6 @@
能源用量趋势
<Icon icon="ep:info-filled" class="panel-info" />
</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>
<Echart :height="300" :options="trendChartOptions" />
</ContentWrap>
@ -104,14 +102,6 @@
区域能耗占比
<Icon icon="ep:info-filled" class="panel-info" />
</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>
<Echart :height="300" :options="regionPieOptions" />
</ContentWrap>
@ -176,6 +166,14 @@
import { EChartsOption } from 'echarts'
import { Echart } from '@/components/Echart'
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' })
@ -192,24 +190,12 @@ interface MetricCard {
subValue?: string
}
interface DetailRow {
id: number
name: string
energyType: string
region: string
value: string
startTime: string
endTime: string
}
type DetailRow = EnergyOverviewDetailVO
const message = useMessage()
const defaultTimeRange = ['2026-05-04 00:00:00', '2026-05-05 23:59:59']
const orgOptions = [
{ label: '成型系统', value: 1 },
{ label: '蛋托线01', value: 2 },
{ label: '烘干系统', value: 3 }
]
const orgOptions = ref<{ label: string; value: string | number; raw?: OrganizationVO }[]>([])
const loading = ref(false)
const queryFormRef = ref()
@ -224,55 +210,19 @@ const queryParams = reactive({
})
const metricCards = ref<MetricCard[]>([])
const rankList = ref([
{ 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 rankList = ref<EnergyOverviewRankVO[]>([])
const pageDetailList = ref<DetailRow[]>([])
const total = ref(0)
const hasEnergyTypes = computed(() => energyTypeOptions.value.length > 0)
const selectedEnergyType = computed(() =>
energyTypeOptions.value.find((item) => item.id === queryParams.energyTypeId)
)
const selectedEnergyName = computed(() => selectedEnergyType.value?.name || '电')
const selectedEnergyUnit = computed(() => selectedEnergyType.value?.unit || 'kWh')
const selectedUnitConsumptionUnit = computed(() => `${selectedEnergyUnit.value}/件`)
const hours = [
'00:00',
'01:00',
'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 trendXAxis = ref<string[]>([])
const trendSeries = ref<number[]>([])
const regionItems = ref<{ name: string; value: number; percent: string }[]>([])
const regionTotal = ref('0')
const trendChartOptions = reactive<EChartsOption>({
color: ['#2f7df6'],
@ -280,12 +230,12 @@ const trendChartOptions = reactive<EChartsOption>({
trigger: 'axis',
formatter: (params: any) => {
const item = params?.[0]
return `${item.axisValue}<br />用电量:${item.data} kWh`
return `${item?.axisValue || ''}<br />用量:${item?.data || 0} ${selectedEnergyUnit.value}`
}
},
legend: {
top: 4,
data: ['用 (kWh)']
data: ['用量']
},
grid: {
left: 24,
@ -297,25 +247,23 @@ const trendChartOptions = reactive<EChartsOption>({
xAxis: {
type: 'category',
boundaryGap: false,
name: '时间(小时)',
name: '时间',
nameLocation: 'middle',
nameGap: 28,
data: hours,
data: trendXAxis.value,
axisLine: { lineStyle: { color: '#dcdfe6' } },
axisLabel: { color: '#606266' }
},
yAxis: {
type: 'value',
name: 'kWh',
name: selectedEnergyUnit.value,
min: 0,
max: 180,
interval: 30,
axisLabel: { color: '#606266' },
splitLine: { lineStyle: { color: '#e8edf5' } }
},
series: [
{
name: '用 (kWh)',
name: '用量',
type: 'line',
smooth: true,
symbol: 'circle',
@ -334,21 +282,16 @@ const trendChartOptions = reactive<EChartsOption>({
]
}
},
data: trendData,
markPoint: {
symbolSize: 48,
label: { color: '#2f7df6', formatter: '{c}' },
data: [{ type: 'max', name: '峰值' }]
}
data: trendSeries.value
}
]
}) as EChartsOption
})
const regionPieOptions = reactive<EChartsOption>({
color: ['#2f7df6', '#52c41a', '#faad14'],
color: ['#2f7df6', '#52c41a', '#faad14', '#13c2c2', '#fa8c16'],
tooltip: {
trigger: 'item',
formatter: '{b}<br />{c} kWh ({d}%)'
formatter: `{b}<br />{c} ${selectedEnergyUnit.value} ({d}%)`
},
legend: {
orient: 'vertical',
@ -357,10 +300,10 @@ const regionPieOptions = reactive<EChartsOption>({
itemWidth: 12,
itemHeight: 12,
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 percent = value > 0 ? '100.00' : '0.00'
return `${name} ${value.toLocaleString()} kWh (${percent}%)`
const percent = item?.percent ?? '0.00'
return `${name} ${value.toLocaleString()} ${selectedEnergyUnit.value} (${percent}%)`
}
},
series: [
@ -373,7 +316,7 @@ const regionPieOptions = reactive<EChartsOption>({
label: {
show: true,
position: 'center',
formatter: '总用电量\n{total|2,139.09 kWh}',
formatter: `总用量\n{total|${regionTotal.value} ${selectedEnergyUnit.value}}`,
color: '#303133',
rich: {
total: {
@ -385,116 +328,93 @@ const regionPieOptions = reactive<EChartsOption>({
}
},
labelLine: { show: false },
data: regionData
data: regionItems.value
}
]
}) as EChartsOption
})
const buildMetrics = () => {
metricCards.value = [
{
key: 'total',
label: '总用电量',
value: '2,139.09',
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 metricThemeMap: Record<string, { icon: string; theme: string }> = {
total: { icon: 'ep:lightning', theme: 'blue' },
deviceCount: { icon: 'ep:cpu', theme: 'green' },
topDevice: { icon: 'ep:histogram', theme: 'purple' },
topRegion: { icon: 'ep:pie-chart', theme: 'cyan' },
range: { icon: 'ep:calendar', theme: 'orange' }
}
const buildDetails = () => {
const timeRange = queryParams.timeRange?.length === 2 ? queryParams.timeRange : defaultTimeRange
detailList.value = [
{
id: 1,
name: '蛋托线真空柜用电量',
energyType: selectedEnergyName.value,
region: '蛋托线01',
value: '0',
startTime: timeRange[0],
endTime: timeRange[1]
},
{
id: 2,
name: '蛋托成型柜用电量',
energyType: selectedEnergyName.value,
region: '成型系统',
value: '2,139.09',
startTime: timeRange[0],
endTime: timeRange[1]
},
{
id: 3,
name: '蛋托干燥柜用电量',
energyType: selectedEnergyName.value,
region: '烘干系统',
value: '0',
startTime: timeRange[0],
endTime: timeRange[1]
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) => {
trendXAxis.value = data.trendChart?.xAxis || []
trendSeries.value = (data.trendChart?.data || []).map((item) => Number(item) || 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
}
trendChartOptions.yAxis = {
...(trendChartOptions.yAxis as any),
name: selectedEnergyUnit.value
}
if (Array.isArray(trendChartOptions.series) && trendChartOptions.series[0]) {
;(trendChartOptions.series[0] as any).data = trendSeries.value
}
if (Array.isArray(regionPieOptions.series) && regionPieOptions.series[0]) {
;(regionPieOptions.series[0] as any).data = regionItems.value
;(regionPieOptions.series[0] as any).label = {
...(regionPieOptions.series[0] as any).label,
formatter: `总用量\n{total|${regionTotal.value} ${selectedEnergyUnit.value}}`
}
}
]
total.value = detailList.value.length
}
const getList = () => {
const getList = async () => {
if (!hasEnergyTypes.value) {
metricCards.value = []
detailList.value = []
rankList.value = []
pageDetailList.value = []
total.value = 0
return
}
loading.value = true
buildMetrics()
buildDetails()
const start = (queryParams.pageNo - 1) * queryParams.pageSize
pageDetailList.value = detailList.value.slice(start, start + queryParams.pageSize)
try {
const timeRange = queryParams.timeRange?.length === 2 ? queryParams.timeRange : defaultTimeRange
const res = await EnergyDeviceApi.queryOverviewData({
orgId: queryParams.orgId,
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[] => {
@ -508,14 +428,7 @@ const normalizeEnergyTypeList = (data: unknown): EnergyTypeVO[] => {
return []
}
const getDefaultEnergyType = (list: EnergyTypeVO[]) => {
return (
list.find((item) => item.name === '电') ||
list.find((item) => item.name === '水') ||
list.find((item) => item.name === '气') ||
list[0]
)
}
const getDefaultEnergyType = (list: EnergyTypeVO[]) => list[0]
const getEnergyTypes = async () => {
try {
@ -531,10 +444,6 @@ const getEnergyTypes = async () => {
}
const handleQuery = () => {
if (!hasEnergyTypes.value) {
getList()
return
}
queryParams.pageNo = 1
getList()
}
@ -547,11 +456,28 @@ const resetQuery = () => {
handleQuery()
}
const handleExport = () => {
message.info('示例页暂未接入导出接口')
const handleExport = async () => {
const timeRange = queryParams.timeRange?.length === 2 ? queryParams.timeRange : defaultTimeRange
await EnergyDeviceApi.exportQueryDataRecords({
orgId: queryParams.orgId,
startTime: timeRange[0],
endTime: timeRange[1]
})
message.success('导出已发起')
}
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 () => {
await loadOrgOptions()
await getEnergyTypes()
if (hasEnergyTypes.value) {
getList()
@ -705,19 +631,6 @@ onMounted(async () => {
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 {
display: inline-flex;
width: 22px;

Loading…
Cancel
Save