style:表管理/设备运行概览-接口更换设备台账的tree接口

main
黄伟杰 5 days ago
parent a3ca79dd4d
commit c117dd3e06

@ -542,6 +542,7 @@ const productQueryParams = reactive({
pageNo: 1,
pageSize: 10,
categoryType: 1,
stockNotZero: true,
barCode: undefined,
name: undefined
})

@ -1,5 +1,9 @@
<template>
<div ref="fullscreenTargetRef" class="run-overview-page" :class="{ 'is-fullscreen': isFullscreen }">
<div
ref="fullscreenTargetRef"
class="run-overview-page"
:class="{ 'is-fullscreen': isFullscreen }"
>
<div class="run-overview-page__floating-tools">
<el-tooltip
:content="
@ -49,15 +53,14 @@
import dayjs from 'dayjs'
import { useFullscreen } from '@vueuse/core'
import { DeviceOperationOverviewApi } from '@/api/iot/deviceOperationOverview'
import { OrganizationApi } from '@/api/mes/organization'
import { handleTree } from '@/utils/tree'
import { DeviceLedgerApi, DeviceLedgerVO } from '@/api/mes/deviceledger'
import { DeviceLineApi, DeviceLineTreeVO } from '@/api/mes/deviceline'
import OverviewFilterBar from './components/OverviewFilterBar.vue'
import OverviewMetricCards from './components/OverviewMetricCards.vue'
import OperationTimelineChart from './components/OperationTimelineChart.vue'
import StatusDistributionChart from './components/StatusDistributionChart.vue'
import { buildDefaultQueryParams } from './mock'
import type {
OrganizationFilterItem,
OrganizationTreeOption,
OverviewOption,
QuickRangeKey,
@ -71,21 +74,29 @@ const { t } = useI18n()
const message = useMessage()
const fullscreenTargetRef = ref()
const { isFullscreen, toggle: toggleFullscreen } = useFullscreen(fullscreenTargetRef)
const organizationList = ref<OrganizationFilterItem[]>([])
const deviceLedgerList = ref<DeviceLedgerVO[]>([])
const groupOptions = ref<OrganizationTreeOption[]>([])
const createDateRangeByQuickKey = (key: QuickRangeKey): [string, string] => {
const format = 'YYYY-MM-DD HH:mm:ss'
if (key === 'today') return [dayjs().startOf('day').format(format), dayjs().endOf('day').format(format)]
if (key === 'today')
return [dayjs().startOf('day').format(format), dayjs().endOf('day').format(format)]
if (key === 'yesterday') {
return [
dayjs().subtract(1, 'day').startOf('day').format(format),
dayjs().subtract(1, 'day').endOf('day').format(format)
]
}
if (key === 'last7Days') return [dayjs().subtract(6, 'day').startOf('day').format(format), dayjs().endOf('day').format(format)]
if (key === 'last7Days')
return [
dayjs().subtract(6, 'day').startOf('day').format(format),
dayjs().endOf('day').format(format)
]
if (key === 'last30Days') {
return [dayjs().subtract(29, 'day').startOf('day').format(format), dayjs().endOf('day').format(format)]
return [
dayjs().subtract(29, 'day').startOf('day').format(format),
dayjs().endOf('day').format(format)
]
}
return [dayjs().startOf('day').format(format), dayjs().endOf('day').format(format)]
}
@ -94,59 +105,66 @@ const quickRanges = computed(() => [
{ label: t('DataCollection.RunOverview.quickRange.today'), value: 'today' as const },
{ label: t('DataCollection.RunOverview.quickRange.yesterday'), value: 'yesterday' as const },
{ label: t('DataCollection.RunOverview.quickRange.last7Days'), value: 'last7Days' as const },
{ label: t('DataCollection.RunOverview.quickRange.last30Days'), value: 'last30Days' as const },
/* { label: t('DataCollection.RunOverview.quickRange.custom'), value: 'custom' as const }*/
{ label: t('DataCollection.RunOverview.quickRange.last30Days'), value: 'last30Days' as const }
/* { label: t('DataCollection.RunOverview.quickRange.custom'), value: 'custom' as const }*/
])
const queryParams = ref<RunOverviewQueryParams>(buildDefaultQueryParams())
const pageNo = ref(1)
const pageSize = ref(10)
const organizationMap = computed(() => {
return organizationList.value.reduce<Record<string, OrganizationFilterItem>>((acc, item) => {
acc[String(item.id)] = item
return acc
}, {})
})
const childrenByParentId = computed(() => {
return organizationList.value.reduce<Record<string, OrganizationFilterItem[]>>((acc, item) => {
const parentKey = String(item.parentId ?? 0)
if (!acc[parentKey]) acc[parentKey] = []
acc[parentKey].push(item)
return acc
}, {})
})
const collectDeviceOptionsByGroup = (groupId?: string) => {
const deviceMap = new Map<string, OverviewOption>()
const pushDevice = (node?: OrganizationFilterItem) => {
if (!node?.dvId || !node.machineName?.trim()) return
const deviceId = String(node.dvId)
if (!deviceMap.has(deviceId)) {
deviceMap.set(deviceId, {
label: node.machineName.trim(),
value: deviceId
})
const normalizeDeviceLineTree = (nodes: DeviceLineTreeVO[] = []): OrganizationTreeOption[] =>
nodes.map((node) => ({
id: String(node.id),
name: node.name,
parentId: node.parentId != null ? String(node.parentId) : node.parentId,
children: Array.isArray(node.children) ? normalizeDeviceLineTree(node.children) : undefined
}))
const collectGroupIds = (groupId?: string) => {
if (!groupId) return undefined
const ids = new Set<string>()
const stack = [...groupOptions.value]
while (stack.length > 0) {
const node = stack.pop()!
if (String(node.id) === groupId) {
const collectStack = [node]
while (collectStack.length > 0) {
const current = collectStack.pop()!
ids.add(String(current.id))
if (Array.isArray(current.children)) collectStack.push(...current.children)
}
break
}
if (Array.isArray(node.children)) stack.push(...node.children)
}
if (!groupId) {
organizationList.value.forEach(pushDevice)
return Array.from(deviceMap.values())
}
return ids
}
const queue = [groupId]
while (queue.length > 0) {
const currentId = queue.shift() as string
pushDevice(organizationMap.value[currentId])
const children = childrenByParentId.value[currentId] || []
children.forEach((child) => queue.push(String(child.id)))
}
const hasIotDeviceCode = (item: DeviceLedgerVO) => String(item.iotDeviceCode ?? '').trim() !== ''
return Array.from(deviceMap.values())
const getDeviceOptionLabel = (item: DeviceLedgerVO) => {
const deviceName = String(item.deviceName ?? '').trim()
const deviceCode = String(item.deviceCode ?? '').trim()
return deviceName || deviceCode || String(item.iotDeviceName ?? item.iotDeviceCode ?? '').trim()
}
const collectDeviceOptionsByGroup = (groupId?: string) => {
const deviceMap = new Map<string, OverviewOption>()
const groupIds = collectGroupIds(groupId)
deviceLedgerList.value.forEach((item) => {
if (groupIds && !groupIds.has(String(item.deviceLine ?? ''))) return
const deviceCode = String(item.iotDeviceCode ?? '').trim()
if (!deviceCode || deviceMap.has(deviceCode)) return
deviceMap.set(deviceCode, {
label: getDeviceOptionLabel(item),
value: deviceCode
})
})
return Array.from(deviceMap.values())
}
const deviceOptions = computed(() => collectDeviceOptionsByGroup(queryParams.value.groupId))
const overviewData = ref<RunOverviewData>({
metrics: [],
@ -158,7 +176,9 @@ const overviewData = ref<RunOverviewData>({
})
const currentDeviceIds = computed(() =>
queryParams.value.deviceId.length > 0 ? queryParams.value.deviceId : deviceOptions.value.map((item) => item.value)
queryParams.value.deviceId.length > 0
? queryParams.value.deviceId
: deviceOptions.value.map((item) => item.value)
)
const buildOverviewRequestParams = () => ({
@ -224,24 +244,14 @@ watch(pageNo, () => {
void refreshData()
})
const getOrganizationOptions = async () => {
const data = await OrganizationApi.getOrganizationList()
organizationList.value = Array.isArray(data)
? data.map((item) => ({
id: String(item.id),
name: item.name,
parentId: item.parentId != null ? String(item.parentId) : item.parentId,
dvId: item.dvId,
machineName: item.machineName
}))
: []
groupOptions.value = handleTree(
organizationList.value.map((item) => ({ ...item })),
'id',
'parentId'
) as OrganizationTreeOption[]
const getFilterOptions = async () => {
const [deviceLineTree, ledgerList] = await Promise.all([
DeviceLineApi.getDeviceLineTree(),
DeviceLedgerApi.getDeviceLedgerList()
])
groupOptions.value = normalizeDeviceLineTree(Array.isArray(deviceLineTree) ? deviceLineTree : [])
deviceLedgerList.value = Array.isArray(ledgerList) ? ledgerList.filter(hasIotDeviceCode) : []
}
watch(
deviceOptions,
(options) => {
@ -260,7 +270,7 @@ watch(
)
onMounted(async () => {
await getOrganizationOptions()
await getFilterOptions()
await refreshData()
})
</script>

@ -23,7 +23,10 @@
/>
</el-form-item>
<el-form-item :label="t('EnergyManagement.EnergyDevice.dialogEnergyTypeLabel')" prop="deviceTypeId">
<el-form-item
:label="t('EnergyManagement.EnergyDevice.dialogEnergyTypeLabel')"
prop="deviceTypeId"
>
<el-select
v-model="formData.deviceTypeId"
@change="handleDeviceTypeChange"
@ -52,11 +55,16 @@
/>
</el-form-item>
<el-form-item :label="t('EnergyManagement.EnergyDevice.dialogRulesLabel')" prop="operationRulesVOList">
<el-form-item
:label="t('EnergyManagement.EnergyDevice.dialogRulesLabel')"
prop="operationRulesVOList"
>
<div class="w-full flex flex-col gap-8px">
<div
v-for="(rule, index) in formData.operationRulesVOList" :key="index"
class="w-full flex items-center gap-8px">
v-for="(rule, index) in formData.operationRulesVOList"
:key="index"
class="w-full flex items-center gap-8px"
>
<el-tree-select
v-model="rule.pointValue"
:data="equipmentTree"
@ -84,7 +92,12 @@ v-for="(rule, index) in formData.operationRulesVOList" :key="index"
<el-button link type="primary" @click="addRule">
<Icon icon="ep:plus" />
</el-button>
<el-button link type="danger" :disabled="formData.operationRulesVOList.length <= 1" @click="removeRule">
<el-button
link
type="danger"
:disabled="formData.operationRulesVOList.length <= 1"
@click="removeRule"
>
<el-icon>
<Remove />
</el-icon>
@ -92,7 +105,9 @@ v-for="(rule, index) in formData.operationRulesVOList" :key="index"
</div>
</template>
</div>
<div class="text-12px text-[#909399] leading-20px">计算规则添加顺序从上到下依次计算不按乘除优先</div>
<div class="text-12px text-[#909399] leading-20px"
>计算规则添加顺序从上到下依次计算不按乘除优先</div
>
</div>
</el-form-item>
@ -115,11 +130,10 @@ v-for="(rule, index) in formData.operationRulesVOList" :key="index"
</template>
<script setup lang="ts">
import { EnergyDeviceApi, EnergyDeviceVO } from '@/api/mes/energydevice'
import { EnergyTypeApi, EnergyTypeVO } from "@/api/mes/energytype";
import { OrganizationApi, DeviceParameterAnalysisNodeVO } from '@/api/mes/organization'
import { EnergyTypeApi, EnergyTypeVO } from '@/api/mes/energytype'
import { DeviceLineApi, DeviceLineTreeVO } from '@/api/mes/deviceline'
import { DeviceApi } from '@/api/iot/device'
import { Remove } from '@element-plus/icons-vue'
import { handleTree } from '@/utils/tree'
/** 能源设备 表单 */
defineOptions({ name: 'EnergyDeviceForm' })
@ -147,61 +161,53 @@ const formData = ref({
orgId: undefined,
orgName: undefined,
rules: undefined,
operationRulesVOList: [{ deviceId: undefined, pointId: undefined, operator: undefined, pointValue: undefined }],
operationRulesVOList: [
{ deviceId: undefined, pointId: undefined, operator: undefined, pointValue: undefined }
]
})
const formRules = reactive({
name: [{ required: true, message: t('EnergyManagement.EnergyDevice.validatorNameRequired'), trigger: 'blur' }],
code: [{ required: true, message: t('EnergyManagement.EnergyDevice.validatorCodeRequired'), trigger: 'blur' }],
orgId: [{ required: true, message: t('EnergyManagement.EnergyDevice.validatorOrgRequired'), trigger: 'change' }],
operationRulesVOList: [{ required: true, validator: (_rule, _value, callback) => validateRules(callback), trigger: 'change' }],
isEnable: [{ required: true, message: t('EnergyManagement.EnergyDevice.validatorIsEnableRequired'), trigger: 'blur' }]
name: [
{
required: true,
message: t('EnergyManagement.EnergyDevice.validatorNameRequired'),
trigger: 'blur'
}
],
code: [
{
required: true,
message: t('EnergyManagement.EnergyDevice.validatorCodeRequired'),
trigger: 'blur'
}
],
orgId: [
{
required: true,
message: t('EnergyManagement.EnergyDevice.validatorOrgRequired'),
trigger: 'change'
}
],
operationRulesVOList: [
{
required: true,
validator: (_rule, _value, callback) => validateRules(callback),
trigger: 'change'
}
],
isEnable: [
{
required: true,
message: t('EnergyManagement.EnergyDevice.validatorIsEnableRequired'),
trigger: 'blur'
}
]
})
const formRef = ref() // Ref
const analysisLoading = ref(false)
type OrgAnalysisNode = DeviceParameterAnalysisNodeVO & { children?: OrgAnalysisNode[] }
const analysisList = ref<OrgAnalysisNode[]>([])
type OrgSelectNode = DeviceLineTreeVO & { children?: OrgSelectNode[] }
const analysisTree = computed(() => {
const list = analysisList.value ?? []
if (!list.length) return [] as OrgAnalysisNode[]
const hasChildren = list.some((n) => Array.isArray((n as any).children) && (n as any).children.length)
if (hasChildren) {
return list.map((n) => ({ ...n })) as OrgAnalysisNode[]
}
const hasParentId = list.some((n) => (n as any).parentId !== undefined && (n as any).parentId !== null)
if (!hasParentId) {
return list.map((n) => ({ ...n })) as OrgAnalysisNode[]
}
const cloned = list.map((n) => ({ ...n })) as OrgAnalysisNode[]
return handleTree(cloned as any[], 'id', 'parentId', 'children') as OrgAnalysisNode[]
})
type OrgSelectNode = {
id: number | string
name: string
children?: OrgSelectNode[]
}
const orgSelectTree = computed(() => {
const tree = analysisTree.value ?? []
if (!tree.length) return [] as OrgSelectNode[]
const pruneAndAttach = (nodes: OrgAnalysisNode[]): OrgSelectNode[] => {
const kept: OrgSelectNode[] = []
nodes.forEach((node) => {
const childNodes = Array.isArray(node.children) ? pruneAndAttach(node.children) : []
kept.push({ id: node.id, name: node.name, children: childNodes.length ? childNodes : undefined })
})
return kept
}
return pruneAndAttach(tree)
})
const orgSelectTree = ref<OrgSelectNode[]>([])
const orgTreeSelectProps = {
label: 'name',
@ -222,7 +228,7 @@ const isSameId = (a: any, b: any) => {
return String(a ?? '') === String(b ?? '')
}
const findOrgNode = (nodes: OrgAnalysisNode[], id: any): OrgAnalysisNode | undefined => {
const findOrgNode = (nodes: OrgSelectNode[], id: any): OrgSelectNode | undefined => {
for (const node of nodes) {
if (isSameId(node.id, id)) return node
const children = Array.isArray(node.children) ? node.children : []
@ -251,22 +257,39 @@ const loadDevicePointTree = async () => {
const deviceGroups = new Map<string, { deviceId: string; deviceName: string; points: any[] }>()
const pushPoint = (deviceId: any, deviceName: any, pointId: any, pointName: any, dataType?: any) => {
const pushPoint = (
deviceId: any,
deviceName: any,
pointId: any,
pointName: any,
dataType?: any
) => {
const dId = normalizeToString(deviceId)
if (!dId) return
const group = deviceGroups.get(dId) ?? { deviceId: dId, deviceName: String(deviceName ?? ''), points: [] }
const group = deviceGroups.get(dId) ?? {
deviceId: dId,
deviceName: String(deviceName ?? ''),
points: []
}
if (!group.deviceName) group.deviceName = String(deviceName ?? '')
const pId = normalizeToString(pointId)
if (!pId) {
deviceGroups.set(dId, group)
return
}
group.points.push({ id: `${dId}:${pId}`, name: `${group.deviceName}: ${String(pointName ?? '')}`.trim(), dataType })
group.points.push({
id: `${dId}:${pId}`,
name: `${group.deviceName}: ${String(pointName ?? '')}`.trim(),
dataType
})
deviceGroups.set(dId, group)
}
if (isNonEmptyArray(list) && ((list[0] as any)?.deviceId !== undefined || (list[0] as any)?.deviceName !== undefined)) {
; (list as any[]).forEach((row) => {
if (
isNonEmptyArray(list) &&
((list[0] as any)?.deviceId !== undefined || (list[0] as any)?.deviceName !== undefined)
) {
;(list as any[]).forEach((row) => {
const deviceId = row?.deviceId ?? row?.devId ?? row?.device_id
const deviceName = row?.deviceName ?? row?.devName ?? row?.device_name
const points =
@ -289,7 +312,7 @@ const loadDevicePointTree = async () => {
}
})
} else if (isNonEmptyArray(list)) {
; (list as any[]).forEach((row) => {
;(list as any[]).forEach((row) => {
const deviceId = row?.deviceId ?? row?.devId
const deviceName = row?.deviceName ?? row?.devName
const pointId = row?.pointId ?? row?.id
@ -346,7 +369,7 @@ const open = async (type: string, id?: number) => {
try {
formData.value = await EnergyDeviceApi.getEnergyDevice(id)
if (!Array.isArray((formData.value as any).operationRulesVOList)) {
; (formData.value as any).operationRulesVOList = [
;(formData.value as any).operationRulesVOList = [
{ deviceId: undefined, pointId: undefined, operator: undefined, pointValue: undefined }
]
}
@ -369,11 +392,11 @@ const submitForm = async () => {
const base = formData.value as unknown as EnergyDeviceVO
const payload: any = {
...(base as any),
deviceTypeName: typeList.value.find(item => item.id === base.deviceTypeId)?.name,
deviceTypeName: typeList.value.find((item) => item.id === base.deviceTypeId)?.name,
operationRulesVOList: buildOperationRulesPayload()
}
const org = findOrgNode(analysisTree.value ?? [], base.orgId)
const org = findOrgNode(orgSelectTree.value ?? [], base.orgId)
if (org) {
payload.orgName = org.name
}
@ -410,7 +433,9 @@ const resetForm = () => {
orgId: undefined,
orgName: undefined,
rules: undefined,
operationRulesVOList: [{ deviceId: undefined, pointId: undefined, operator: undefined, pointValue: undefined }]
operationRulesVOList: [
{ deviceId: undefined, pointId: undefined, operator: undefined, pointValue: undefined }
]
}
formRef.value?.resetFields()
}
@ -421,25 +446,26 @@ onMounted(async () => {
})
const loadAnalysis = async () => {
if (analysisList.value.length > 0) {
if (orgSelectTree.value.length > 0) {
return
}
analysisLoading.value = true
try {
const res: any = await OrganizationApi.deviceParameterAnalysis({ showDevices: 2 })
const list = Array.isArray(res) ? res : Array.isArray(res?.data) ? res.data : []
analysisList.value = list as OrgAnalysisNode[]
const res = await DeviceLineApi.getDeviceLineTree()
orgSelectTree.value = (Array.isArray(res) ? res : []) as OrgSelectNode[]
} finally {
analysisLoading.value = false
}
}
const handleDeviceTypeChange = () => {
formData.value.unitName = typeList.value.find(item => item.id === formData.value.deviceTypeId)?.unit
formData.value.unitName = typeList.value.find(
(item) => item.id === formData.value.deviceTypeId
)?.unit
}
const handleOrgChange = () => {
const org = findOrgNode(analysisTree.value ?? [], formData.value.orgId)
const org = findOrgNode(orgSelectTree.value ?? [], formData.value.orgId)
formData.value.orgName = org?.name
}
@ -452,7 +478,9 @@ const handlePointSelected = (index: number, val: any) => {
const [deviceIdStr, pointIdStr] = val.split(':')
const deviceId = Number(deviceIdStr)
const pointId = Number(pointIdStr)
formData.value.operationRulesVOList[index].deviceId = Number.isNaN(deviceId) ? undefined : deviceId
formData.value.operationRulesVOList[index].deviceId = Number.isNaN(deviceId)
? undefined
: deviceId
formData.value.operationRulesVOList[index].pointId = Number.isNaN(pointId) ? undefined : pointId
}
@ -501,17 +529,17 @@ const hydratePointValues = () => {
const validateRules = (callback: any) => {
const list = formData.value.operationRulesVOList ?? []
if (!list.length) {
callback(new Error(t('EnergyManagement.EnergyDevice.validatorRulesRequired')))
callback(new Error(t('EnergyManagement.EnergyDevice.validatorRulesRequired')))
return
}
for (let i = 0; i < list.length; i++) {
const r = list[i] as any
if (!r?.deviceId || !r?.pointId) {
callback(new Error(t('EnergyManagement.EnergyDevice.validatorRulesPointRequired')))
callback(new Error(t('EnergyManagement.EnergyDevice.validatorRulesPointRequired')))
return
}
if (i > 0 && !list[i - 1]?.operator) {
callback(new Error(t('EnergyManagement.EnergyDevice.validatorRulesOperatorRequired')))
callback(new Error(t('EnergyManagement.EnergyDevice.validatorRulesOperatorRequired')))
return
}
const dt = findPointDataType(r.deviceId, r.pointId)
@ -528,6 +556,6 @@ const validateRules = (callback: any) => {
<style scoped>
.energy-device-dialog-form :deep(.el-form-item__label) {
min-width: 100px;
min-width: 100px;
}
</style>

Loading…
Cancel
Save