|
|
|
|
@ -1,12 +1,6 @@
|
|
|
|
|
<template>
|
|
|
|
|
<Dialog :title="dialogTitle" v-model="dialogVisible" width="800px">
|
|
|
|
|
<el-form
|
|
|
|
|
ref="formRef"
|
|
|
|
|
:model="formData"
|
|
|
|
|
:rules="formRules"
|
|
|
|
|
label-width="100px"
|
|
|
|
|
v-loading="formLoading"
|
|
|
|
|
>
|
|
|
|
|
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="100px" v-loading="formLoading">
|
|
|
|
|
<el-form-item label="表编码" prop="code">
|
|
|
|
|
<el-input v-model="formData.code" placeholder="请输入表编码" :disabled="formType === 'update'" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
@ -16,60 +10,29 @@
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
|
|
|
|
<el-form-item label="能源类型" prop="deviceTypeId">
|
|
|
|
|
<el-select
|
|
|
|
|
v-model="formData.deviceTypeId"
|
|
|
|
|
@change="handleDeviceTypeChange"
|
|
|
|
|
placeholder="请选择能源类型"
|
|
|
|
|
clearable
|
|
|
|
|
filterable
|
|
|
|
|
class="!w-full"
|
|
|
|
|
>
|
|
|
|
|
<el-select v-model="formData.deviceTypeId" @change="handleDeviceTypeChange" placeholder="请选择能源类型" clearable
|
|
|
|
|
filterable class="!w-full">
|
|
|
|
|
<el-option v-for="item in typeList" :key="item.id" :label="item.name" :value="item.id" />
|
|
|
|
|
</el-select>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
|
|
|
|
<el-form-item label="所属区域" prop="orgId">
|
|
|
|
|
<el-tree-select
|
|
|
|
|
v-model="formData.orgId"
|
|
|
|
|
:data="orgSelectTree"
|
|
|
|
|
:props="orgTreeSelectProps"
|
|
|
|
|
filterable
|
|
|
|
|
check-strictly
|
|
|
|
|
clearable
|
|
|
|
|
class="!w-full"
|
|
|
|
|
placeholder="根据区域结构筛选列表"
|
|
|
|
|
:loading="analysisLoading"
|
|
|
|
|
:render-after-expand="false"
|
|
|
|
|
@change="handleOrgChange"
|
|
|
|
|
/>
|
|
|
|
|
<el-tree-select v-model="formData.orgId" :data="orgSelectTree" :props="orgTreeSelectProps" filterable
|
|
|
|
|
check-strictly clearable class="!w-full" placeholder="根据区域结构筛选列表" :loading="analysisLoading"
|
|
|
|
|
:render-after-expand="false" @change="handleOrgChange" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
|
|
|
|
<el-form-item label="计算规则" 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"
|
|
|
|
|
>
|
|
|
|
|
<el-tree-select
|
|
|
|
|
v-model="rule.pointValue"
|
|
|
|
|
:data="equipmentTree"
|
|
|
|
|
:props="treeSelectProps"
|
|
|
|
|
filterable
|
|
|
|
|
clearable
|
|
|
|
|
class="!w-full"
|
|
|
|
|
placeholder="根据设备选择点位"
|
|
|
|
|
:disabled="!formData.orgId"
|
|
|
|
|
@change="(val) => handlePointSelected(index, val)"
|
|
|
|
|
/>
|
|
|
|
|
<div 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" :props="treeSelectProps" filterable
|
|
|
|
|
clearable class="!w-full" placeholder="根据设备选择点位" :loading="devicePointLoading"
|
|
|
|
|
@change="(val) => handlePointSelected(index, val)" />
|
|
|
|
|
|
|
|
|
|
<template v-if="index < formData.operationRulesVOList.length - 1">
|
|
|
|
|
<el-select
|
|
|
|
|
v-model="formData.operationRulesVOList[index].operator"
|
|
|
|
|
placeholder="运算符"
|
|
|
|
|
class="!w-110px"
|
|
|
|
|
clearable
|
|
|
|
|
>
|
|
|
|
|
<el-select v-model="formData.operationRulesVOList[index].operator" placeholder="运算符" class="!w-110px"
|
|
|
|
|
clearable>
|
|
|
|
|
<el-option v-for="op in operatorOptions" :key="op" :label="op" :value="op" />
|
|
|
|
|
</el-select>
|
|
|
|
|
</template>
|
|
|
|
|
@ -78,13 +41,10 @@
|
|
|
|
|
<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-icon><Remove /></el-icon>
|
|
|
|
|
<el-button link type="danger" :disabled="formData.operationRulesVOList.length <= 1" @click="removeRule">
|
|
|
|
|
<el-icon>
|
|
|
|
|
<Remove />
|
|
|
|
|
</el-icon>
|
|
|
|
|
</el-button>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
@ -107,8 +67,9 @@
|
|
|
|
|
</template>
|
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
import { EnergyDeviceApi, EnergyDeviceVO } from '@/api/mes/energydevice'
|
|
|
|
|
import {EnergyTypeApi, EnergyTypeVO} from "@/api/mes/energytype";
|
|
|
|
|
import { EnergyTypeApi, EnergyTypeVO } from "@/api/mes/energytype";
|
|
|
|
|
import { OrganizationApi, DeviceParameterAnalysisNodeVO } from '@/api/mes/organization'
|
|
|
|
|
import { DeviceApi } from '@/api/iot/device'
|
|
|
|
|
import { Remove } from '@element-plus/icons-vue'
|
|
|
|
|
import { handleTree } from '@/utils/tree'
|
|
|
|
|
|
|
|
|
|
@ -158,6 +119,11 @@ 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[]
|
|
|
|
|
@ -181,8 +147,6 @@ const orgSelectTree = computed(() => {
|
|
|
|
|
const kept: OrgSelectNode[] = []
|
|
|
|
|
nodes.forEach((node) => {
|
|
|
|
|
const childNodes = Array.isArray(node.children) ? pruneAndAttach(node.children) : []
|
|
|
|
|
const hasValidEquipment = (node?.equipments ?? []).some((eq) => (eq?.parameters ?? []).length > 0)
|
|
|
|
|
if (!hasValidEquipment && !childNodes.length) return
|
|
|
|
|
kept.push({ id: node.id, name: node.name, children: childNodes.length ? childNodes : undefined })
|
|
|
|
|
})
|
|
|
|
|
return kept
|
|
|
|
|
@ -206,27 +170,100 @@ const treeSelectProps = {
|
|
|
|
|
|
|
|
|
|
const operatorOptions = ['+', '-', '*', '/']
|
|
|
|
|
|
|
|
|
|
const currentOrgNode = computed(() => {
|
|
|
|
|
return analysisList.value.find((o) => o.id === formData.value.orgId)
|
|
|
|
|
})
|
|
|
|
|
const isSameId = (a: any, b: any) => {
|
|
|
|
|
return String(a ?? '') === String(b ?? '')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const equipmentTree = computed(() => {
|
|
|
|
|
const equipments = currentOrgNode.value?.equipments ?? []
|
|
|
|
|
return equipments
|
|
|
|
|
.map((eq) => {
|
|
|
|
|
const params = eq.parameters ?? []
|
|
|
|
|
if (!params.length) return null
|
|
|
|
|
return {
|
|
|
|
|
id: `device:${eq.id}`,
|
|
|
|
|
name: eq.name,
|
|
|
|
|
disabled: true,
|
|
|
|
|
children: params.map((p) => ({
|
|
|
|
|
id: `${eq.id}:${p.id}`,
|
|
|
|
|
name: `${eq.name}: ${p.name}`
|
|
|
|
|
}))
|
|
|
|
|
const findOrgNode = (nodes: OrgAnalysisNode[], id: any): OrgAnalysisNode | undefined => {
|
|
|
|
|
for (const node of nodes) {
|
|
|
|
|
if (isSameId(node.id, id)) return node
|
|
|
|
|
const children = Array.isArray(node.children) ? node.children : []
|
|
|
|
|
if (children.length) {
|
|
|
|
|
const found = findOrgNode(children, id)
|
|
|
|
|
if (found) return found
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return undefined
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const devicePointLoading = ref(false)
|
|
|
|
|
const devicePointTree = ref<any[]>([])
|
|
|
|
|
|
|
|
|
|
const loadDevicePointTree = async () => {
|
|
|
|
|
if (devicePointTree.value.length > 0) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
devicePointLoading.value = true
|
|
|
|
|
try {
|
|
|
|
|
const res: any = await DeviceApi.devicePointList()
|
|
|
|
|
const list = Array.isArray(res) ? res : Array.isArray(res?.data) ? res.data : []
|
|
|
|
|
|
|
|
|
|
const normalizeToString = (v: any) => String(v ?? '')
|
|
|
|
|
const isNonEmptyArray = (v: any) => Array.isArray(v) && v.length > 0
|
|
|
|
|
|
|
|
|
|
const deviceGroups = new Map<string, { deviceId: string; deviceName: string; points: any[] }>()
|
|
|
|
|
|
|
|
|
|
const pushPoint = (deviceId: any, deviceName: any, pointId: any, pointName: any) => {
|
|
|
|
|
const dId = normalizeToString(deviceId)
|
|
|
|
|
if (!dId) return
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.filter(Boolean) as any[]
|
|
|
|
|
group.points.push({ id: `${dId}:${pId}`, name: `${group.deviceName}: ${String(pointName ?? '')}`.trim() })
|
|
|
|
|
deviceGroups.set(dId, group)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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 =
|
|
|
|
|
row?.contactModelDOList ??
|
|
|
|
|
row?.points ??
|
|
|
|
|
row?.pointList ??
|
|
|
|
|
row?.devicePoints ??
|
|
|
|
|
row?.devicePointList ??
|
|
|
|
|
row?.parameters
|
|
|
|
|
if (Array.isArray(points)) {
|
|
|
|
|
points.forEach((p) => {
|
|
|
|
|
const pointId = p?.pointId ?? p?.id
|
|
|
|
|
const pointName = p?.attributeName ?? p?.pointName ?? p?.name
|
|
|
|
|
pushPoint(deviceId, deviceName, pointId, pointName)
|
|
|
|
|
})
|
|
|
|
|
} else {
|
|
|
|
|
const pointId = row?.pointId ?? row?.attributeId ?? row?.devicePointId
|
|
|
|
|
const pointName = row?.attributeName ?? row?.pointName ?? row?.attributeName
|
|
|
|
|
pushPoint(deviceId, deviceName, pointId, pointName)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
} else if (isNonEmptyArray(list)) {
|
|
|
|
|
; (list as any[]).forEach((row) => {
|
|
|
|
|
const deviceId = row?.deviceId ?? row?.devId
|
|
|
|
|
const deviceName = row?.deviceName ?? row?.devName
|
|
|
|
|
const pointId = row?.pointId ?? row?.id
|
|
|
|
|
const pointName = row?.attributeName ?? row?.pointName ?? row?.name
|
|
|
|
|
pushPoint(deviceId, deviceName, pointId, pointName)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
devicePointTree.value = Array.from(deviceGroups.values())
|
|
|
|
|
.map((g) => {
|
|
|
|
|
const children = (g.points ?? []).filter((p) => p?.id)
|
|
|
|
|
if (!children.length) return null
|
|
|
|
|
return { id: `device:${g.deviceId}`, name: g.deviceName, disabled: true, children }
|
|
|
|
|
})
|
|
|
|
|
.filter(Boolean) as any[]
|
|
|
|
|
} finally {
|
|
|
|
|
devicePointLoading.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const equipmentTree = computed(() => {
|
|
|
|
|
return devicePointTree.value
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
/** 打开弹窗 */
|
|
|
|
|
@ -237,13 +274,14 @@ const open = async (type: string, id?: number) => {
|
|
|
|
|
resetForm()
|
|
|
|
|
|
|
|
|
|
await loadAnalysis()
|
|
|
|
|
await loadDevicePointTree()
|
|
|
|
|
// 修改时,设置数据
|
|
|
|
|
if (id) {
|
|
|
|
|
formLoading.value = true
|
|
|
|
|
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 }
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
@ -263,21 +301,23 @@ const submitForm = async () => {
|
|
|
|
|
// 提交请求
|
|
|
|
|
formLoading.value = true
|
|
|
|
|
try {
|
|
|
|
|
const data = formData.value as unknown as EnergyDeviceVO
|
|
|
|
|
data.deviceTypeName = typeList.value.find(item => item.id === data.deviceTypeId)?.name
|
|
|
|
|
const base = formData.value as unknown as EnergyDeviceVO
|
|
|
|
|
const payload: any = {
|
|
|
|
|
...(base as any),
|
|
|
|
|
deviceTypeName: typeList.value.find(item => item.id === base.deviceTypeId)?.name,
|
|
|
|
|
operationRulesVOList: buildOperationRulesPayload()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const org = analysisList.value.find((o) => o.id === data.orgId)
|
|
|
|
|
const org = findOrgNode(analysisTree.value ?? [], base.orgId)
|
|
|
|
|
if (org) {
|
|
|
|
|
;(data as any).orgName = org.name
|
|
|
|
|
payload.orgName = org.name
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
;(data as any).operationRulesVOList = buildOperationRulesPayload()
|
|
|
|
|
|
|
|
|
|
if (formType.value === 'create') {
|
|
|
|
|
await EnergyDeviceApi.createEnergyDevice(data)
|
|
|
|
|
await EnergyDeviceApi.createEnergyDevice(payload)
|
|
|
|
|
message.success(t('common.createSuccess'))
|
|
|
|
|
} else {
|
|
|
|
|
await EnergyDeviceApi.updateEnergyDevice(data)
|
|
|
|
|
await EnergyDeviceApi.updateEnergyDevice(payload)
|
|
|
|
|
message.success(t('common.updateSuccess'))
|
|
|
|
|
}
|
|
|
|
|
dialogVisible.value = false
|
|
|
|
|
@ -321,7 +361,9 @@ const loadAnalysis = async () => {
|
|
|
|
|
}
|
|
|
|
|
analysisLoading.value = true
|
|
|
|
|
try {
|
|
|
|
|
analysisList.value = await OrganizationApi.deviceParameterAnalysis({})
|
|
|
|
|
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[]
|
|
|
|
|
} finally {
|
|
|
|
|
analysisLoading.value = false
|
|
|
|
|
}
|
|
|
|
|
@ -332,11 +374,8 @@ const handleDeviceTypeChange = () => {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const handleOrgChange = () => {
|
|
|
|
|
const org = analysisList.value.find((o) => o.id === formData.value.orgId)
|
|
|
|
|
const org = findOrgNode(analysisTree.value ?? [], formData.value.orgId)
|
|
|
|
|
formData.value.orgName = org?.name
|
|
|
|
|
formData.value.operationRulesVOList = [
|
|
|
|
|
{ deviceId: undefined, pointId: undefined, operator: undefined, pointValue: undefined }
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const handlePointSelected = (index: number, val: any) => {
|
|
|
|
|
|