|
|
|
|
@ -9,15 +9,17 @@
|
|
|
|
|
label-width="auto"
|
|
|
|
|
>
|
|
|
|
|
<el-form-item :label="t('EnergyOverview.filters.org')" prop="orgId">
|
|
|
|
|
<el-select
|
|
|
|
|
<el-tree-select
|
|
|
|
|
v-model="queryParams.orgId"
|
|
|
|
|
:data="orgOptions"
|
|
|
|
|
:props="orgTreeProps"
|
|
|
|
|
check-strictly
|
|
|
|
|
clearable
|
|
|
|
|
filterable
|
|
|
|
|
default-expand-all
|
|
|
|
|
: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-select>
|
|
|
|
|
/>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item :label="t('EnergyOverview.filters.energyType')" prop="energyTypeId">
|
|
|
|
|
<el-select
|
|
|
|
|
@ -31,15 +33,20 @@
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item :label="t('EnergyOverview.filters.timeRange')" prop="timeRange">
|
|
|
|
|
<el-date-picker
|
|
|
|
|
v-model="queryParams.timeRange"
|
|
|
|
|
:model-value="queryParams.timeRange"
|
|
|
|
|
type="datetimerange"
|
|
|
|
|
value-format="YYYY-MM-DD HH:mm:ss"
|
|
|
|
|
format="YYYY-MM-DD HH:mm:ss"
|
|
|
|
|
:disabled-date="disabledFutureDate"
|
|
|
|
|
:disabled-hours="disabledFutureHours"
|
|
|
|
|
:disabled-minutes="disabledFutureMinutes"
|
|
|
|
|
:disabled-seconds="disabledFutureSeconds"
|
|
|
|
|
:start-placeholder="t('common.startTimeText')"
|
|
|
|
|
:end-placeholder="t('common.endTimeText')"
|
|
|
|
|
range-separator="~"
|
|
|
|
|
:default-time="[new Date('2000-01-01 00:00:00'), new Date('2000-01-01 23:59:59')]"
|
|
|
|
|
:default-time="datePickerDefaultTime"
|
|
|
|
|
class="!w-420px"
|
|
|
|
|
@update:model-value="handleTimeRangeChange"
|
|
|
|
|
/>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item>
|
|
|
|
|
@ -92,7 +99,7 @@
|
|
|
|
|
<Icon icon="ep:info-filled" class="panel-info" />
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<Echart :height="300" :options="trendChartOptions" />
|
|
|
|
|
<Echart :height="300" :options="trendChartDisplayOptions" />
|
|
|
|
|
</ContentWrap>
|
|
|
|
|
</el-col>
|
|
|
|
|
<el-col :xl="12" :lg="12" :md="24" :sm="24" :xs="24">
|
|
|
|
|
@ -103,7 +110,7 @@
|
|
|
|
|
<Icon icon="ep:info-filled" class="panel-info" />
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<Echart :height="300" :options="regionPieOptions" />
|
|
|
|
|
<Echart :height="300" :options="regionPieDisplayOptions" />
|
|
|
|
|
</ContentWrap>
|
|
|
|
|
</el-col>
|
|
|
|
|
</el-row>
|
|
|
|
|
@ -183,7 +190,9 @@ import {
|
|
|
|
|
EnergyOverviewRankVO,
|
|
|
|
|
EnergyOverviewRespVO
|
|
|
|
|
} from '@/api/mes/energydevice'
|
|
|
|
|
import { OrganizationApi, OrganizationVO } from '@/api/mes/organization'
|
|
|
|
|
import { OrganizationApi } from '@/api/mes/organization'
|
|
|
|
|
import type { DeviceParameterAnalysisNodeVO } from '@/api/mes/organization'
|
|
|
|
|
import { handleTree } from '@/utils/tree'
|
|
|
|
|
|
|
|
|
|
defineOptions({ name: 'EnergyOverview' })
|
|
|
|
|
|
|
|
|
|
@ -202,16 +211,41 @@ interface MetricCard {
|
|
|
|
|
|
|
|
|
|
type DetailRow = EnergyOverviewDetailVO
|
|
|
|
|
|
|
|
|
|
interface OrganizationTreeOption {
|
|
|
|
|
id: number | string
|
|
|
|
|
name: string
|
|
|
|
|
parentId?: number | string | null
|
|
|
|
|
lineId?: number | string | null
|
|
|
|
|
filterId?: number | string | null
|
|
|
|
|
deviceId?: number | string | null
|
|
|
|
|
paramId?: number | string | null
|
|
|
|
|
nodeType?: 'org' | 'equipment' | 'param'
|
|
|
|
|
disabled?: boolean
|
|
|
|
|
children?: OrganizationTreeOption[]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const { t } = useI18n()
|
|
|
|
|
const message = useMessage()
|
|
|
|
|
|
|
|
|
|
const padTimeUnit = (value: number) => String(value).padStart(2, '0')
|
|
|
|
|
|
|
|
|
|
const formatDateTime = (date: Date) => {
|
|
|
|
|
const year = date.getFullYear()
|
|
|
|
|
const month = padTimeUnit(date.getMonth() + 1)
|
|
|
|
|
const day = padTimeUnit(date.getDate())
|
|
|
|
|
const hour = padTimeUnit(date.getHours())
|
|
|
|
|
const minute = padTimeUnit(date.getMinutes())
|
|
|
|
|
const second = padTimeUnit(date.getSeconds())
|
|
|
|
|
return `${year}-${month}-${day} ${hour}:${minute}:${second}`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const getTodayTimeRange = () => {
|
|
|
|
|
const now = new Date()
|
|
|
|
|
const year = now.getFullYear()
|
|
|
|
|
const month = String(now.getMonth() + 1).padStart(2, '0')
|
|
|
|
|
const day = String(now.getDate()).padStart(2, '0')
|
|
|
|
|
const month = padTimeUnit(now.getMonth() + 1)
|
|
|
|
|
const day = padTimeUnit(now.getDate())
|
|
|
|
|
const date = `${year}-${month}-${day}`
|
|
|
|
|
return [`${date} 00:00:00`, `${date} 23:59:59`]
|
|
|
|
|
return [`${date} 00:00:00`, formatDateTime(now)]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const parseRangeTime = (value?: string) => {
|
|
|
|
|
@ -232,8 +266,90 @@ const isSingleDayRange = (timeRange?: string[]) => {
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const isSameDay = (left: Date, right: Date) =>
|
|
|
|
|
left.getFullYear() === right.getFullYear() &&
|
|
|
|
|
left.getMonth() === right.getMonth() &&
|
|
|
|
|
left.getDate() === right.getDate()
|
|
|
|
|
|
|
|
|
|
const disabledFutureDate = (date: Date) => {
|
|
|
|
|
const today = new Date()
|
|
|
|
|
today.setHours(0, 0, 0, 0)
|
|
|
|
|
return date.getTime() > today.getTime()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const getComparingDate = (value?: unknown) => {
|
|
|
|
|
if (!value) return undefined
|
|
|
|
|
if (value instanceof Date) return value
|
|
|
|
|
const maybeDate = value as { toDate?: () => Date; valueOf?: () => number }
|
|
|
|
|
if (typeof maybeDate.toDate === 'function') {
|
|
|
|
|
const date = maybeDate.toDate()
|
|
|
|
|
return Number.isNaN(date.getTime()) ? undefined : date
|
|
|
|
|
}
|
|
|
|
|
if (typeof maybeDate.valueOf === 'function') {
|
|
|
|
|
const date = new Date(maybeDate.valueOf())
|
|
|
|
|
return Number.isNaN(date.getTime()) ? undefined : date
|
|
|
|
|
}
|
|
|
|
|
return undefined
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const getDisabledFutureUnits = (max: number, current: number) =>
|
|
|
|
|
Array.from({ length: max + 1 }, (_, index) => index).filter((value) => value > current)
|
|
|
|
|
|
|
|
|
|
const shouldDisableFutureTime = (roleOrDate?: unknown, comparingDate?: unknown) => {
|
|
|
|
|
const role = typeof roleOrDate === 'string' ? roleOrDate : undefined
|
|
|
|
|
if (role && role !== 'end' && role !== 'start') return false
|
|
|
|
|
const date = getComparingDate(role ? comparingDate : roleOrDate)
|
|
|
|
|
return !!date && isSameDay(date, new Date())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const disabledFutureHours = (roleOrDate?: unknown, comparingDate?: unknown) => {
|
|
|
|
|
if (!shouldDisableFutureTime(roleOrDate, comparingDate)) return []
|
|
|
|
|
return getDisabledFutureUnits(23, new Date().getHours())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const disabledFutureMinutes = (hour: number, roleOrDate?: unknown, comparingDate?: unknown) => {
|
|
|
|
|
const now = new Date()
|
|
|
|
|
if (!shouldDisableFutureTime(roleOrDate, comparingDate) || hour !== now.getHours()) return []
|
|
|
|
|
return getDisabledFutureUnits(59, now.getMinutes())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const disabledFutureSeconds = (
|
|
|
|
|
hour: number,
|
|
|
|
|
minute: number,
|
|
|
|
|
roleOrDate?: unknown,
|
|
|
|
|
comparingDate?: unknown
|
|
|
|
|
) => {
|
|
|
|
|
const now = new Date()
|
|
|
|
|
if (
|
|
|
|
|
!shouldDisableFutureTime(roleOrDate, comparingDate) ||
|
|
|
|
|
hour !== now.getHours() ||
|
|
|
|
|
minute !== now.getMinutes()
|
|
|
|
|
) {
|
|
|
|
|
return []
|
|
|
|
|
}
|
|
|
|
|
return getDisabledFutureUnits(59, now.getSeconds())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const defaultTimeRange = getTodayTimeRange()
|
|
|
|
|
const orgOptions = ref<{ label: string; value: string | number; raw?: OrganizationVO }[]>([])
|
|
|
|
|
const defaultPickerEndTime = new Date()
|
|
|
|
|
const datePickerDefaultTime = [
|
|
|
|
|
new Date(2000, 0, 1, 0, 0, 0),
|
|
|
|
|
new Date(
|
|
|
|
|
2000,
|
|
|
|
|
0,
|
|
|
|
|
1,
|
|
|
|
|
defaultPickerEndTime.getHours(),
|
|
|
|
|
defaultPickerEndTime.getMinutes(),
|
|
|
|
|
defaultPickerEndTime.getSeconds()
|
|
|
|
|
)
|
|
|
|
|
]
|
|
|
|
|
const orgOptions = ref<OrganizationTreeOption[]>([])
|
|
|
|
|
const orgTreeProps = {
|
|
|
|
|
value: 'id',
|
|
|
|
|
label: 'name',
|
|
|
|
|
children: 'children',
|
|
|
|
|
disabled: 'disabled'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const loading = ref(false)
|
|
|
|
|
const queryFormRef = ref()
|
|
|
|
|
@ -242,11 +358,39 @@ const defaultEnergyTypeId = ref<number>()
|
|
|
|
|
const queryParams = reactive({
|
|
|
|
|
pageNo: 1,
|
|
|
|
|
pageSize: 10,
|
|
|
|
|
orgId: undefined as number | undefined,
|
|
|
|
|
orgId: undefined as string | undefined,
|
|
|
|
|
energyTypeId: undefined as number | undefined,
|
|
|
|
|
timeRange: [...defaultTimeRange]
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const normalizeTimeRange = (value?: unknown) => {
|
|
|
|
|
const range = Array.isArray(value) && value.length >= 2 ? value : defaultTimeRange
|
|
|
|
|
const now = new Date()
|
|
|
|
|
let start = parseRangeTime(String(range[0] || '')) || parseRangeTime(defaultTimeRange[0])!
|
|
|
|
|
let end = parseRangeTime(String(range[1] || '')) || now
|
|
|
|
|
|
|
|
|
|
if (start.getTime() > now.getTime()) start = now
|
|
|
|
|
if (end.getTime() > now.getTime()) end = now
|
|
|
|
|
if (start.getTime() > end.getTime()) start = end
|
|
|
|
|
|
|
|
|
|
return [formatDateTime(start), formatDateTime(end)]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const syncSafeTimeRange = () => {
|
|
|
|
|
const safeTimeRange = normalizeTimeRange(queryParams.timeRange)
|
|
|
|
|
if (
|
|
|
|
|
safeTimeRange[0] !== queryParams.timeRange[0] ||
|
|
|
|
|
safeTimeRange[1] !== queryParams.timeRange[1]
|
|
|
|
|
) {
|
|
|
|
|
queryParams.timeRange = safeTimeRange
|
|
|
|
|
}
|
|
|
|
|
return safeTimeRange
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const handleTimeRangeChange = (value: unknown) => {
|
|
|
|
|
queryParams.timeRange = normalizeTimeRange(value)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const metricCards = ref<MetricCard[]>([])
|
|
|
|
|
const rankList = ref<EnergyOverviewRankVO[]>([])
|
|
|
|
|
const pageDetailList = ref<DetailRow[]>([])
|
|
|
|
|
@ -262,7 +406,7 @@ const trendSeries = ref<number[]>([])
|
|
|
|
|
const regionItems = ref<{ name: string; value: number; percent: string }[]>([])
|
|
|
|
|
const regionTotal = ref('0')
|
|
|
|
|
|
|
|
|
|
const trendChartOptions = reactive<EChartsOption>({
|
|
|
|
|
const trendChartOptions = ref<EChartsOption>({
|
|
|
|
|
color: ['#2f7df6'],
|
|
|
|
|
tooltip: {
|
|
|
|
|
trigger: 'axis',
|
|
|
|
|
@ -325,7 +469,7 @@ const trendChartOptions = reactive<EChartsOption>({
|
|
|
|
|
]
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const regionPieOptions = reactive<EChartsOption>({
|
|
|
|
|
const regionPieOptions = ref<EChartsOption>({
|
|
|
|
|
color: ['#2f7df6', '#52c41a', '#faad14', '#13c2c2', '#fa8c16'],
|
|
|
|
|
tooltip: {
|
|
|
|
|
trigger: 'item',
|
|
|
|
|
@ -371,6 +515,9 @@ const regionPieOptions = reactive<EChartsOption>({
|
|
|
|
|
]
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const trendChartDisplayOptions = computed<EChartsOption>(() => trendChartOptions.value)
|
|
|
|
|
const regionPieDisplayOptions = computed<EChartsOption>(() => regionPieOptions.value)
|
|
|
|
|
|
|
|
|
|
const metricThemeMap: Record<string, { icon: string; theme: string }> = {
|
|
|
|
|
total: { icon: 'ep:lightning', theme: 'blue' },
|
|
|
|
|
deviceCount: { icon: 'ep:cpu', theme: 'green' },
|
|
|
|
|
@ -379,8 +526,172 @@ const metricThemeMap: Record<string, { icon: string; theme: string }> = {
|
|
|
|
|
range: { icon: 'ep:calendar', theme: 'orange' }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const extractDeviceParameterAnalysisNodes = (res: any): DeviceParameterAnalysisNodeVO[] => {
|
|
|
|
|
if (Array.isArray(res)) return res
|
|
|
|
|
if (Array.isArray(res?.data)) return res.data
|
|
|
|
|
if (Array.isArray(res?.result)) return res.result
|
|
|
|
|
if (Array.isArray(res?.list)) return res.list
|
|
|
|
|
return []
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const normalizeDeviceParameterAnalysisTree = (
|
|
|
|
|
nodes: DeviceParameterAnalysisNodeVO[] = []
|
|
|
|
|
): OrganizationTreeOption[] => {
|
|
|
|
|
const normalizeNode = (node: DeviceParameterAnalysisNodeVO): any => {
|
|
|
|
|
const rawId = Number(node?.id) || node?.id
|
|
|
|
|
const parentId =
|
|
|
|
|
node?.parentId == null || node?.parentId === ''
|
|
|
|
|
? 0
|
|
|
|
|
: typeof node.parentId === 'number'
|
|
|
|
|
? node.parentId
|
|
|
|
|
: Number(node.parentId) || 0
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
...node,
|
|
|
|
|
id: rawId,
|
|
|
|
|
name: String(node?.name ?? ''),
|
|
|
|
|
parentId,
|
|
|
|
|
children: Array.isArray(node?.children) ? node.children.map(normalizeNode) : undefined
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const normalizedNodes = Array.isArray(nodes) ? nodes.map(normalizeNode) : []
|
|
|
|
|
const isNestedTree = normalizedNodes.some(
|
|
|
|
|
(node) => Array.isArray(node?.children) && node.children.length > 0
|
|
|
|
|
)
|
|
|
|
|
const orgTree = isNestedTree
|
|
|
|
|
? normalizedNodes
|
|
|
|
|
: (handleTree(normalizedNodes, 'id', 'parentId', 'children') as any[])
|
|
|
|
|
|
|
|
|
|
const toOption = (node: any): OrganizationTreeOption => {
|
|
|
|
|
const orgChildren = Array.isArray(node.children) ? node.children.map(toOption) : []
|
|
|
|
|
const equipmentChildren = Array.isArray(node.equipments)
|
|
|
|
|
? node.equipments.map((equipment: any) => {
|
|
|
|
|
const parameterChildren = Array.isArray(equipment.parameters)
|
|
|
|
|
? equipment.parameters.map((param: any) => ({
|
|
|
|
|
id: `param-${equipment.id}-${param.id}`,
|
|
|
|
|
name: String(param?.name ?? param?.code ?? param?.id ?? ''),
|
|
|
|
|
parentId: `equipment-${node.id}-${equipment.id}`,
|
|
|
|
|
lineId: String(node.id),
|
|
|
|
|
filterId: String(param.id),
|
|
|
|
|
deviceId: String(equipment.id),
|
|
|
|
|
paramId: String(param.id),
|
|
|
|
|
nodeType: 'param' as const
|
|
|
|
|
}))
|
|
|
|
|
: []
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
id: `equipment-${node.id}-${equipment.id}`,
|
|
|
|
|
name: String(equipment?.name ?? equipment?.id ?? ''),
|
|
|
|
|
parentId: `org-${node.id}`,
|
|
|
|
|
lineId: String(node.id),
|
|
|
|
|
filterId: String(equipment.id),
|
|
|
|
|
deviceId: String(equipment.id),
|
|
|
|
|
nodeType: 'equipment' as const,
|
|
|
|
|
children: parameterChildren.length ? parameterChildren : undefined
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
: []
|
|
|
|
|
const children = [...orgChildren, ...equipmentChildren]
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
id: `org-${node.id}`,
|
|
|
|
|
name: node.name,
|
|
|
|
|
parentId: node.parentId != null && Number(node.parentId) > 0 ? `org-${node.parentId}` : node.parentId,
|
|
|
|
|
lineId: String(node.id),
|
|
|
|
|
filterId: String(node.id),
|
|
|
|
|
nodeType: 'org',
|
|
|
|
|
children: children.length ? children : undefined
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Array.isArray(orgTree) ? orgTree.map(toOption) : []
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const findOrgNode = (orgId?: string): OrganizationTreeOption | undefined => {
|
|
|
|
|
if (!orgId) return undefined
|
|
|
|
|
const stack = [...orgOptions.value]
|
|
|
|
|
while (stack.length > 0) {
|
|
|
|
|
const node = stack.pop()!
|
|
|
|
|
if (String(node.id) === String(orgId)) return node
|
|
|
|
|
if (Array.isArray(node.children)) stack.push(...node.children)
|
|
|
|
|
}
|
|
|
|
|
return undefined
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const getRequestOrgId = (orgId?: string) => {
|
|
|
|
|
if (!orgId) return undefined
|
|
|
|
|
const node = findOrgNode(orgId)
|
|
|
|
|
if (node?.nodeType === 'org' && node.filterId != null) return node.filterId
|
|
|
|
|
if (node?.lineId != null) return node.lineId
|
|
|
|
|
const matched = /^org-(.+)$/.exec(String(orgId))
|
|
|
|
|
return matched?.[1] || orgId
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const isDateTimeInRange = (date: Date, start: Date, end: Date) =>
|
|
|
|
|
date.getTime() >= start.getTime() && date.getTime() <= end.getTime()
|
|
|
|
|
|
|
|
|
|
const createDateInRangeDay = (base: Date, hour = 0, minute = 0, second = 0) =>
|
|
|
|
|
new Date(base.getFullYear(), base.getMonth(), base.getDate(), hour, minute, second)
|
|
|
|
|
|
|
|
|
|
const parseTrendAxisTime = (label: string, rangeStart: Date, rangeEnd: Date) => {
|
|
|
|
|
const text = String(label || '').trim()
|
|
|
|
|
if (!text) return undefined
|
|
|
|
|
|
|
|
|
|
if (/\d{4}[-/]\d{1,2}[-/]\d{1,2}/.test(text)) {
|
|
|
|
|
const parsed = parseRangeTime(text)
|
|
|
|
|
if (parsed) return parsed
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const monthDayMatch = text.match(/(?:^|\D)(\d{1,2})[-/](\d{1,2})(?:\s+(\d{1,2}):(\d{2})(?::(\d{2}))?)?/)
|
|
|
|
|
if (monthDayMatch) {
|
|
|
|
|
const [, month, day, hourText = '0', minuteText = '0', secondText = '0'] = monthDayMatch
|
|
|
|
|
const candidates = [rangeStart.getFullYear(), rangeEnd.getFullYear()].map(
|
|
|
|
|
(year) =>
|
|
|
|
|
new Date(
|
|
|
|
|
year,
|
|
|
|
|
Number(month) - 1,
|
|
|
|
|
Number(day),
|
|
|
|
|
Number(hourText),
|
|
|
|
|
Number(minuteText),
|
|
|
|
|
Number(secondText)
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
return candidates.find((item) => isDateTimeInRange(item, rangeStart, rangeEnd)) || candidates[0]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const hourMatch = text.match(/^(\d{1,2}):(\d{2})(?::(\d{2}))?$/)
|
|
|
|
|
if (hourMatch) {
|
|
|
|
|
const [, hourText, minuteText, secondText = '0'] = hourMatch
|
|
|
|
|
return createDateInRangeDay(rangeStart, Number(hourText), Number(minuteText), Number(secondText))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return undefined
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const filterTrendChartData = (axis: string[], data: number[]) => {
|
|
|
|
|
const [startTime, endTime] = syncSafeTimeRange()
|
|
|
|
|
const rangeStart = parseRangeTime(startTime)
|
|
|
|
|
const rangeEnd = parseRangeTime(endTime)
|
|
|
|
|
if (!rangeStart || !rangeEnd) return { axis, data }
|
|
|
|
|
|
|
|
|
|
const filtered = axis
|
|
|
|
|
.map((label, index) => ({
|
|
|
|
|
label,
|
|
|
|
|
value: data[index] ?? 0,
|
|
|
|
|
time: parseTrendAxisTime(label, rangeStart, rangeEnd)
|
|
|
|
|
}))
|
|
|
|
|
.filter((item) => item.time && isDateTimeInRange(item.time, rangeStart, rangeEnd))
|
|
|
|
|
|
|
|
|
|
if (!filtered.length && axis.length) return { axis: [], data: [] }
|
|
|
|
|
return {
|
|
|
|
|
axis: filtered.map((item) => item.label),
|
|
|
|
|
data: filtered.map((item) => item.value)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const buildFallbackTrendXAxis = () => {
|
|
|
|
|
const timeRange = queryParams.timeRange?.length === 2 ? queryParams.timeRange : defaultTimeRange
|
|
|
|
|
const timeRange = syncSafeTimeRange()
|
|
|
|
|
const [startTime, endTime] = timeRange
|
|
|
|
|
const start = new Date(startTime.replace(/-/g, '/'))
|
|
|
|
|
const end = new Date(endTime.replace(/-/g, '/'))
|
|
|
|
|
@ -396,7 +707,7 @@ const buildFallbackTrendXAxis = () => {
|
|
|
|
|
const axis: string[] = []
|
|
|
|
|
if (sameDay) {
|
|
|
|
|
const current = new Date(start)
|
|
|
|
|
current.setMinutes(0, 0, 0)
|
|
|
|
|
current.setMinutes(current.getMinutes() === 0 && current.getSeconds() === 0 ? 0 : 60, 0, 0)
|
|
|
|
|
const limit = new Date(end)
|
|
|
|
|
limit.setMinutes(0, 0, 0)
|
|
|
|
|
while (current <= limit) {
|
|
|
|
|
@ -437,12 +748,15 @@ const normalizeMetricCards = (metrics: EnergyOverviewMetricVO[]) => {
|
|
|
|
|
|
|
|
|
|
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 =
|
|
|
|
|
const rawTrendAxis = data.trendChart?.xAxis?.length ? data.trendChart.xAxis : fallbackXAxis
|
|
|
|
|
const rawTrendSeries =
|
|
|
|
|
responseSeries.length > 0
|
|
|
|
|
? responseSeries
|
|
|
|
|
: new Array(trendXAxis.value.length).fill(0)
|
|
|
|
|
: new Array(rawTrendAxis.length).fill(0)
|
|
|
|
|
const filteredTrend = filterTrendChartData(rawTrendAxis, rawTrendSeries)
|
|
|
|
|
trendXAxis.value = filteredTrend.axis
|
|
|
|
|
trendSeries.value = filteredTrend.data
|
|
|
|
|
regionItems.value = (data.regionChart?.items || []).map((item) => ({
|
|
|
|
|
name: item.name,
|
|
|
|
|
value: Number(item.value) || 0,
|
|
|
|
|
@ -450,27 +764,28 @@ const updateCharts = (data: EnergyOverviewRespVO) => {
|
|
|
|
|
}))
|
|
|
|
|
regionTotal.value = data.regionChart?.totalValue || '0'
|
|
|
|
|
|
|
|
|
|
trendChartOptions.xAxis = {
|
|
|
|
|
...(trendChartOptions.xAxis as any),
|
|
|
|
|
trendChartOptions.value.xAxis = {
|
|
|
|
|
...(trendChartOptions.value.xAxis as any),
|
|
|
|
|
data: trendXAxis.value,
|
|
|
|
|
name: t('EnergyOverview.chart.time')
|
|
|
|
|
}
|
|
|
|
|
trendChartOptions.yAxis = {
|
|
|
|
|
...(trendChartOptions.yAxis as any),
|
|
|
|
|
trendChartOptions.value.yAxis = {
|
|
|
|
|
...(trendChartOptions.value.yAxis as any),
|
|
|
|
|
name: selectedEnergyUnit.value
|
|
|
|
|
}
|
|
|
|
|
if (Array.isArray(trendChartOptions.legend?.data)) {
|
|
|
|
|
trendChartOptions.legend.data = [t('EnergyOverview.chart.usage')]
|
|
|
|
|
const trendLegend = trendChartOptions.value.legend as any
|
|
|
|
|
if (Array.isArray(trendLegend?.data)) {
|
|
|
|
|
trendLegend.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(trendChartOptions.value.series) && trendChartOptions.value.series[0]) {
|
|
|
|
|
;(trendChartOptions.value.series[0] as any).name = t('EnergyOverview.chart.usage')
|
|
|
|
|
;(trendChartOptions.value.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,
|
|
|
|
|
if (Array.isArray(regionPieOptions.value.series) && regionPieOptions.value.series[0]) {
|
|
|
|
|
;(regionPieOptions.value.series[0] as any).name = t('EnergyOverview.chart.regionEnergy')
|
|
|
|
|
;(regionPieOptions.value.series[0] as any).data = regionItems.value
|
|
|
|
|
;(regionPieOptions.value.series[0] as any).label = {
|
|
|
|
|
...(regionPieOptions.value.series[0] as any).label,
|
|
|
|
|
formatter: `${t('EnergyOverview.chart.totalUsage')}\n{total|${regionTotal.value} ${selectedEnergyUnit.value}}`
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@ -487,9 +802,10 @@ const getList = async () => {
|
|
|
|
|
|
|
|
|
|
loading.value = true
|
|
|
|
|
try {
|
|
|
|
|
const timeRange = queryParams.timeRange?.length === 2 ? queryParams.timeRange : defaultTimeRange
|
|
|
|
|
const timeRange = syncSafeTimeRange()
|
|
|
|
|
const requestOrgId = getRequestOrgId(queryParams.orgId)
|
|
|
|
|
const res = await EnergyDeviceApi.queryOverviewData({
|
|
|
|
|
orgId: queryParams.orgId,
|
|
|
|
|
orgId: requestOrgId,
|
|
|
|
|
energyTypeId: queryParams.energyTypeId,
|
|
|
|
|
startTime: timeRange[0],
|
|
|
|
|
endTime: timeRange[1],
|
|
|
|
|
@ -521,8 +837,9 @@ const normalizeEnergyTypeList = (data: unknown): EnergyTypeVO[] => {
|
|
|
|
|
const getDefaultEnergyType = (list: EnergyTypeVO[]) => list[0]
|
|
|
|
|
|
|
|
|
|
const getEnergyTypes = async () => {
|
|
|
|
|
const requestOrgId = getRequestOrgId(queryParams.orgId)
|
|
|
|
|
try {
|
|
|
|
|
const data = await EnergyTypeApi.getEnergyTypeList({ orgId: queryParams.orgId })
|
|
|
|
|
const data = await EnergyTypeApi.getEnergyTypeList({ orgId: requestOrgId })
|
|
|
|
|
energyTypeOptions.value = normalizeEnergyTypeList(data)
|
|
|
|
|
} catch (error) {
|
|
|
|
|
energyTypeOptions.value = []
|
|
|
|
|
@ -530,7 +847,7 @@ const getEnergyTypes = async () => {
|
|
|
|
|
|
|
|
|
|
let defaultEnergyType = getDefaultEnergyType(energyTypeOptions.value)
|
|
|
|
|
try {
|
|
|
|
|
const deviceRes = await EnergyDeviceApi.getList({ orgId: queryParams.orgId })
|
|
|
|
|
const deviceRes = await EnergyDeviceApi.getList({ orgId: requestOrgId })
|
|
|
|
|
const devices = ((deviceRes as any)?.data || deviceRes || []) as Array<{ deviceTypeId?: number }>
|
|
|
|
|
const deviceTypeIds = new Set(devices.map((item) => item.deviceTypeId).filter(Boolean))
|
|
|
|
|
defaultEnergyType =
|
|
|
|
|
@ -543,7 +860,8 @@ const getEnergyTypes = async () => {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const handleQuery = () => {
|
|
|
|
|
if (!isSingleDayRange(queryParams.timeRange)) {
|
|
|
|
|
const safeTimeRange = syncSafeTimeRange()
|
|
|
|
|
if (!isSingleDayRange(safeTimeRange)) {
|
|
|
|
|
message.warning(t('EnergyOverview.messages.singleDayOnly'))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
@ -559,27 +877,16 @@ const resetQuery = () => {
|
|
|
|
|
handleQuery()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const handleExport = async () => {
|
|
|
|
|
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 }))
|
|
|
|
|
const deviceLineTree = await OrganizationApi.deviceParameterAnalysis({
|
|
|
|
|
keyword: undefined,
|
|
|
|
|
showDevices: 1,
|
|
|
|
|
filterNoDeviceLine: 1
|
|
|
|
|
})
|
|
|
|
|
orgOptions.value = normalizeDeviceParameterAnalysisTree(
|
|
|
|
|
extractDeviceParameterAnalysisNodes(deviceLineTree)
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onMounted(async () => {
|
|
|
|
|
|