能源概览

release
liutao 1 week ago
parent 13133c28be
commit 3745ef5bbc

@ -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 })
}, },

@ -9,7 +9,13 @@
label-width="auto" label-width="auto"
> >
<el-form-item label="所属区域" prop="orgId"> <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-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>
@ -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>
@ -85,14 +91,6 @@
能源用量趋势 能源用量趋势
<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>
@ -104,14 +102,6 @@
区域能耗占比 区域能耗占比
<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>
@ -176,6 +166,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 +190,12 @@ 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 message = useMessage() const message = useMessage()
const defaultTimeRange = ['2026-05-04 00:00:00', '2026-05-05 23:59:59'] const defaultTimeRange = ['2026-05-04 00:00:00', '2026-05-05 23:59:59']
const orgOptions = [ const orgOptions = ref<{ label: string; value: string | number; raw?: OrganizationVO }[]>([])
{ label: '成型系统', value: 1 },
{ label: '蛋托线01', value: 2 },
{ label: '烘干系统', value: 3 }
]
const loading = ref(false) const loading = ref(false)
const queryFormRef = ref() const queryFormRef = ref()
@ -224,55 +210,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 +230,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 />用量:${item?.data || 0} ${selectedEnergyUnit.value}`
} }
}, },
legend: { legend: {
top: 4, top: 4,
data: ['用 (kWh)'] data: ['用量']
}, },
grid: { grid: {
left: 24, left: 24,
@ -297,25 +247,23 @@ const trendChartOptions = reactive<EChartsOption>({
xAxis: { xAxis: {
type: 'category', type: 'category',
boundaryGap: false, boundaryGap: false,
name: '时间(小时)', name: '时间',
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: '用量',
type: 'line', type: 'line',
smooth: true, smooth: true,
symbol: 'circle', symbol: 'circle',
@ -334,21 +282,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,10 +300,10 @@ 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: [
@ -373,7 +316,7 @@ const regionPieOptions = reactive<EChartsOption>({
label: { label: {
show: true, show: true,
position: 'center', position: 'center',
formatter: '总用电量\n{total|2,139.09 kWh}', formatter: `总用量\n{total|${regionTotal.value} ${selectedEnergyUnit.value}}`,
color: '#303133', color: '#303133',
rich: { rich: {
total: { total: {
@ -385,117 +328,94 @@ 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 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 buildDetails = () => { const updateCharts = (data: EnergyOverviewRespVO) => {
const timeRange = queryParams.timeRange?.length === 2 ? queryParams.timeRange : defaultTimeRange trendXAxis.value = data.trendChart?.xAxis || []
detailList.value = [ trendSeries.value = (data.trendChart?.data || []).map((item) => Number(item) || 0)
{ regionItems.value = (data.regionChart?.items || []).map((item) => ({
id: 1, name: item.name,
name: '蛋托线真空柜用电量', value: Number(item.value) || 0,
energyType: selectedEnergyName.value, percent: item.percent
region: '蛋托线01', }))
value: '0', regionTotal.value = data.regionChart?.totalValue || '0'
startTime: timeRange[0],
endTime: timeRange[1] trendChartOptions.xAxis = {
}, ...(trendChartOptions.xAxis as any),
{ data: trendXAxis.value
id: 2, }
name: '蛋托成型柜用电量', trendChartOptions.yAxis = {
energyType: selectedEnergyName.value, ...(trendChartOptions.yAxis as any),
region: '成型系统', name: selectedEnergyUnit.value
value: '2,139.09', }
startTime: timeRange[0], if (Array.isArray(trendChartOptions.series) && trendChartOptions.series[0]) {
endTime: timeRange[1] ;(trendChartOptions.series[0] as any).data = trendSeries.value
}, }
{ if (Array.isArray(regionPieOptions.series) && regionPieOptions.series[0]) {
id: 3, ;(regionPieOptions.series[0] as any).data = regionItems.value
name: '蛋托干燥柜用电量', ;(regionPieOptions.series[0] as any).label = {
energyType: selectedEnergyName.value, ...(regionPieOptions.series[0] as any).label,
region: '烘干系统', formatter: `总用量\n{total|${regionTotal.value} ${selectedEnergyUnit.value}}`
value: '0', }
startTime: timeRange[0],
endTime: timeRange[1]
} }
]
total.value = detailList.value.length
} }
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,
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 loading.value = false
} }
}
const normalizeEnergyTypeList = (data: unknown): EnergyTypeVO[] => { const normalizeEnergyTypeList = (data: unknown): EnergyTypeVO[] => {
const maybeData = data as { data?: EnergyTypeVO[] } const maybeData = data as { data?: EnergyTypeVO[] }
@ -508,14 +428,7 @@ 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 {
@ -531,10 +444,6 @@ const getEnergyTypes = async () => {
} }
const handleQuery = () => { const handleQuery = () => {
if (!hasEnergyTypes.value) {
getList()
return
}
queryParams.pageNo = 1 queryParams.pageNo = 1
getList() getList()
} }
@ -547,11 +456,28 @@ const resetQuery = () => {
handleQuery() handleQuery()
} }
const handleExport = () => { const handleExport = async () => {
message.info('示例页暂未接入导出接口') 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 () => { onMounted(async () => {
await loadOrgOptions()
await getEnergyTypes() await getEnergyTypes()
if (hasEnergyTypes.value) { if (hasEnergyTypes.value) {
getList() getList()
@ -705,19 +631,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