|
|
|
|
@ -14,7 +14,7 @@
|
|
|
|
|
|
|
|
|
|
<el-tree
|
|
|
|
|
ref="treeRef"
|
|
|
|
|
node-key="id"
|
|
|
|
|
node-key="nodeKey"
|
|
|
|
|
:data="treeData"
|
|
|
|
|
:props="treeProps"
|
|
|
|
|
highlight-current
|
|
|
|
|
@ -27,12 +27,9 @@
|
|
|
|
|
<div class="device-mgmt__treeNode">
|
|
|
|
|
<div class="device-mgmt__treeNodeName">
|
|
|
|
|
<span class="device-mgmt__treeNodeText">{{ data.name }}</span>
|
|
|
|
|
<template v-if="data.type === 'device'">
|
|
|
|
|
<span
|
|
|
|
|
class="device-mgmt__dot"
|
|
|
|
|
:class="data.online ? 'device-mgmt__dot--ok' : 'device-mgmt__dot--muted'"
|
|
|
|
|
></span>
|
|
|
|
|
</template>
|
|
|
|
|
<span class="device-mgmt__treeNodeType" :class="getNodeTypeClass(data.nodeType)">
|
|
|
|
|
{{ getNodeTypeLabel(data.nodeType) }}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
<template v-if="data.type === 'device'">
|
|
|
|
|
<span class="device-mgmt__treeNodeMeta">{{ data.code }}</span>
|
|
|
|
|
@ -98,10 +95,12 @@
|
|
|
|
|
<el-table-column label="协议" prop="protocol" width="120" />
|
|
|
|
|
<el-table-column label="启用" prop="isEnable" width="90">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
{{ row.isEnable ? '是' : '否' }}
|
|
|
|
|
<el-switch
|
|
|
|
|
v-model="row.isEnable"
|
|
|
|
|
@change="(val) => handleDeviceEnableChange(row, val as boolean)"
|
|
|
|
|
/>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="采集时间" prop="collectionTime" width="180" />
|
|
|
|
|
<el-table-column label="操作" fixed="right" width="190">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
<el-button link type="primary" @click="openForm('setting', row.id)"
|
|
|
|
|
@ -122,12 +121,19 @@
|
|
|
|
|
<span
|
|
|
|
|
class="device-mgmt__dot device-mgmt__dot--lg"
|
|
|
|
|
:class="
|
|
|
|
|
selectedDevice?.online ? 'device-mgmt__dot--ok' : 'device-mgmt__dot--muted'
|
|
|
|
|
String(selectedDeviceDetail?.status) === '1' ||
|
|
|
|
|
selectedDeviceDetail?.status === 'online'
|
|
|
|
|
? 'device-mgmt__dot--ok'
|
|
|
|
|
: 'device-mgmt__dot--muted'
|
|
|
|
|
"
|
|
|
|
|
></span>
|
|
|
|
|
<div class="device-mgmt__titleText">
|
|
|
|
|
<div class="device-mgmt__title">{{ selectedDevice?.name || '-' }}</div>
|
|
|
|
|
<div class="device-mgmt__subtitle">{{ selectedDevice?.code || '-' }}</div>
|
|
|
|
|
<div class="device-mgmt__title">{{
|
|
|
|
|
selectedDeviceDetail?.deviceName || '-'
|
|
|
|
|
}}</div>
|
|
|
|
|
<div class="device-mgmt__subtitle">{{
|
|
|
|
|
selectedDeviceDetail?.deviceCode || '-'
|
|
|
|
|
}}</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
@ -148,47 +154,58 @@
|
|
|
|
|
<div class="device-mgmt__baseBlockTitle">设备信息</div>
|
|
|
|
|
<div class="device-mgmt__baseGrid">
|
|
|
|
|
<div class="device-mgmt__baseItem">
|
|
|
|
|
<div class="device-mgmt__baseLabel">设备编码</div>
|
|
|
|
|
<div class="device-mgmt__baseValue">{{ selectedDevice?.code || '-' }}</div>
|
|
|
|
|
<div class="device-mgmt__baseLabel">设备编号</div>
|
|
|
|
|
<div class="device-mgmt__baseValue">{{
|
|
|
|
|
selectedDeviceDetail?.deviceCode || '-'
|
|
|
|
|
}}</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="device-mgmt__baseItem">
|
|
|
|
|
<div class="device-mgmt__baseLabel">设备名称</div>
|
|
|
|
|
<div class="device-mgmt__baseValue">{{ selectedDevice?.name || '-' }}</div>
|
|
|
|
|
<div class="device-mgmt__baseValue">{{
|
|
|
|
|
selectedDeviceDetail?.deviceName || '-'
|
|
|
|
|
}}</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="device-mgmt__baseItem">
|
|
|
|
|
<div class="device-mgmt__baseLabel">设备型号</div>
|
|
|
|
|
<div class="device-mgmt__baseValue">{{ selectedDevice?.model || '-' }}</div>
|
|
|
|
|
<div class="device-mgmt__baseLabel">设备类型</div>
|
|
|
|
|
<div class="device-mgmt__baseValue">{{
|
|
|
|
|
selectedDeviceDetail?.deviceType || '-'
|
|
|
|
|
}}</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="device-mgmt__baseItem">
|
|
|
|
|
<div class="device-mgmt__baseLabel">制造商</div>
|
|
|
|
|
<div class="device-mgmt__baseLabel">关联设备模型</div>
|
|
|
|
|
<div class="device-mgmt__baseValue">{{
|
|
|
|
|
selectedDevice?.manufacturer || '-'
|
|
|
|
|
selectedDeviceDetail?.deviceModelId || '-'
|
|
|
|
|
}}</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="device-mgmt__baseItem">
|
|
|
|
|
<div class="device-mgmt__baseLabel">所属客户</div>
|
|
|
|
|
<div class="device-mgmt__baseLabel">状态</div>
|
|
|
|
|
<div class="device-mgmt__baseValue">{{
|
|
|
|
|
selectedDevice?.customerName || '-'
|
|
|
|
|
selectedDeviceDetail?.status || '-'
|
|
|
|
|
}}</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="device-mgmt__baseItem">
|
|
|
|
|
<div class="device-mgmt__baseLabel">所属产线</div>
|
|
|
|
|
<div class="device-mgmt__baseLabel">运行状态</div>
|
|
|
|
|
<div class="device-mgmt__baseValue">{{
|
|
|
|
|
selectedDevice?.lineName || '-'
|
|
|
|
|
selectedDeviceDetail?.operatingStatus || '-'
|
|
|
|
|
}}</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="device-mgmt__baseItem">
|
|
|
|
|
<div class="device-mgmt__baseLabel">安装位置</div>
|
|
|
|
|
<div class="device-mgmt__baseValue">{{ selectedDevice?.address || '-' }}</div>
|
|
|
|
|
<div class="device-mgmt__baseLabel">关联组织</div>
|
|
|
|
|
<div class="device-mgmt__baseValue">{{
|
|
|
|
|
selectedDeviceDetail?.org || '-'
|
|
|
|
|
}}</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="device-mgmt__baseItem">
|
|
|
|
|
<div class="device-mgmt__baseLabel">在线状态</div>
|
|
|
|
|
<div
|
|
|
|
|
class="device-mgmt__baseValue"
|
|
|
|
|
:class="selectedDevice?.online ? 'device-mgmt__baseValue--success' : ''"
|
|
|
|
|
>
|
|
|
|
|
{{ selectedDevice?.online ? '在线' : '离线' }}
|
|
|
|
|
</div>
|
|
|
|
|
<div class="device-mgmt__baseLabel">客户ID</div>
|
|
|
|
|
<div class="device-mgmt__baseValue">{{
|
|
|
|
|
selectedDeviceDetail?.customerId || '-'
|
|
|
|
|
}}</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="device-mgmt__baseItem">
|
|
|
|
|
<div class="device-mgmt__baseLabel">组织节点ID</div>
|
|
|
|
|
<div class="device-mgmt__baseValue">{{
|
|
|
|
|
selectedDeviceDetail?.orgNodeId || '-'
|
|
|
|
|
}}</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
@ -198,15 +215,57 @@
|
|
|
|
|
<div class="device-mgmt__baseBlockTitle">通讯配置</div>
|
|
|
|
|
<div class="device-mgmt__baseGrid">
|
|
|
|
|
<div class="device-mgmt__baseItem">
|
|
|
|
|
<div class="device-mgmt__baseLabel">MQTT主题</div>
|
|
|
|
|
<div class="device-mgmt__baseLabel">通讯协议</div>
|
|
|
|
|
<div class="device-mgmt__baseValue">{{
|
|
|
|
|
selectedDeviceDetail?.protocol || '-'
|
|
|
|
|
}}</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="device-mgmt__baseItem">
|
|
|
|
|
<div class="device-mgmt__baseLabel">端点url</div>
|
|
|
|
|
<div class="device-mgmt__baseValue">{{
|
|
|
|
|
selectedDeviceDetail?.url || '-'
|
|
|
|
|
}}</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="device-mgmt__baseItem">
|
|
|
|
|
<div class="device-mgmt__baseLabel">采集周期</div>
|
|
|
|
|
<div class="device-mgmt__baseValue">{{
|
|
|
|
|
selectedDeviceDetail?.sampleCycle || '-'
|
|
|
|
|
}}</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="device-mgmt__baseItem">
|
|
|
|
|
<div class="device-mgmt__baseLabel">用户名</div>
|
|
|
|
|
<div class="device-mgmt__baseValue">{{
|
|
|
|
|
selectedDeviceDetail?.username || '-'
|
|
|
|
|
}}</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="device-mgmt__baseItem">
|
|
|
|
|
<div class="device-mgmt__baseLabel">密码</div>
|
|
|
|
|
<div class="device-mgmt__baseValue">{{
|
|
|
|
|
selectedDeviceDetail?.password ? '******' : '-'
|
|
|
|
|
}}</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="device-mgmt__baseItem">
|
|
|
|
|
<div class="device-mgmt__baseLabel">网关id</div>
|
|
|
|
|
<div class="device-mgmt__baseValue">{{
|
|
|
|
|
selectedDeviceDetail?.gatewayId || '-'
|
|
|
|
|
}}</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="device-mgmt__baseItem">
|
|
|
|
|
<div class="device-mgmt__baseLabel">读主题</div>
|
|
|
|
|
<div class="device-mgmt__baseValue device-mgmt__baseValue--bg">{{
|
|
|
|
|
selectedDeviceDetail?.readTopic || '-'
|
|
|
|
|
}}</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="device-mgmt__baseItem">
|
|
|
|
|
<div class="device-mgmt__baseLabel">写主题</div>
|
|
|
|
|
<div class="device-mgmt__baseValue device-mgmt__baseValue--bg">{{
|
|
|
|
|
selectedDevice?.mqttTopic || '-'
|
|
|
|
|
selectedDeviceDetail?.writeTopic || '-'
|
|
|
|
|
}}</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="device-mgmt__baseItem">
|
|
|
|
|
<div class="device-mgmt__baseLabel">客户端ID</div>
|
|
|
|
|
<div class="device-mgmt__baseLabel">mqtt订阅主题</div>
|
|
|
|
|
<div class="device-mgmt__baseValue device-mgmt__baseValue--bg">{{
|
|
|
|
|
selectedDevice?.mqttClientId || '-'
|
|
|
|
|
selectedDeviceDetail?.topic || '-'
|
|
|
|
|
}}</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
@ -216,21 +275,53 @@
|
|
|
|
|
<div class="device-mgmt__baseBlock">
|
|
|
|
|
<div class="device-mgmt__baseBlockTitle">其他信息</div>
|
|
|
|
|
<div class="device-mgmt__baseGrid">
|
|
|
|
|
<div class="device-mgmt__baseItem">
|
|
|
|
|
<div class="device-mgmt__baseLabel">是否启用</div>
|
|
|
|
|
<div class="device-mgmt__baseValue">{{
|
|
|
|
|
selectedDeviceDetail?.isEnable ? '是' : '否'
|
|
|
|
|
}}</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="device-mgmt__baseItem">
|
|
|
|
|
<div class="device-mgmt__baseLabel">创建时间</div>
|
|
|
|
|
<div class="device-mgmt__baseValue">{{
|
|
|
|
|
selectedDevice?.createdAt || '-'
|
|
|
|
|
selectedDeviceDetail?.createTime
|
|
|
|
|
? formatDate(selectedDeviceDetail.createTime)
|
|
|
|
|
: '-'
|
|
|
|
|
}}</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="device-mgmt__baseItem">
|
|
|
|
|
<div class="device-mgmt__baseLabel">采集时间</div>
|
|
|
|
|
<div class="device-mgmt__baseValue">{{
|
|
|
|
|
selectedDeviceDetail?.collectionTime
|
|
|
|
|
? formatDate(selectedDeviceDetail.collectionTime)
|
|
|
|
|
: '-'
|
|
|
|
|
}}</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="device-mgmt__baseItem">
|
|
|
|
|
<div class="device-mgmt__baseLabel">离线间隔</div>
|
|
|
|
|
<div class="device-mgmt__baseValue">{{
|
|
|
|
|
selectedDeviceDetail?.offLineDuration || '-'
|
|
|
|
|
}}</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="device-mgmt__baseItem">
|
|
|
|
|
<div class="device-mgmt__baseLabel">最后上线时间</div>
|
|
|
|
|
<div class="device-mgmt__baseValue">{{
|
|
|
|
|
selectedDeviceDetail?.lastOnlineTime
|
|
|
|
|
? formatDate(selectedDeviceDetail.lastOnlineTime)
|
|
|
|
|
: '-'
|
|
|
|
|
}}</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="device-mgmt__baseItem">
|
|
|
|
|
<div class="device-mgmt__baseLabel">最后更新</div>
|
|
|
|
|
<div class="device-mgmt__baseLabel">设备品牌id</div>
|
|
|
|
|
<div class="device-mgmt__baseValue">{{
|
|
|
|
|
selectedDevice?.updatedAt || '-'
|
|
|
|
|
selectedDeviceDetail?.deviceBrandId || '-'
|
|
|
|
|
}}</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="device-mgmt__baseItem device-mgmt__baseItem--full">
|
|
|
|
|
<div class="device-mgmt__baseLabel">备注</div>
|
|
|
|
|
<div class="device-mgmt__baseValue">{{ selectedDevice?.remark || '-' }}</div>
|
|
|
|
|
<div class="device-mgmt__baseValue">{{
|
|
|
|
|
selectedDeviceDetail?.remark || '-'
|
|
|
|
|
}}</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
@ -238,138 +329,156 @@
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<template v-else-if="activeTab === 'realtime'">
|
|
|
|
|
<el-row :gutter="16">
|
|
|
|
|
<el-col v-for="m in realtimeMetrics" :key="m.key" :xs="24" :sm="12" :md="8" :lg="6">
|
|
|
|
|
<el-card shadow="never" class="device-mgmt__metricCard">
|
|
|
|
|
<div class="device-mgmt__metricTop">
|
|
|
|
|
<div class="device-mgmt__metricName">{{ m.name }}</div>
|
|
|
|
|
<el-tag :type="m.status === 'warn' ? 'warning' : 'success'" size="small">
|
|
|
|
|
{{ m.status === 'warn' ? '预警' : '正常' }}
|
|
|
|
|
</el-tag>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="device-mgmt__metricValue">
|
|
|
|
|
<span class="device-mgmt__metricNumber">{{ m.value }}</span>
|
|
|
|
|
<span class="device-mgmt__metricUnit">{{ m.unit }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
</el-card>
|
|
|
|
|
</el-col>
|
|
|
|
|
</el-row>
|
|
|
|
|
<el-card shadow="never" class="device-mgmt__panel mt-16px">
|
|
|
|
|
<div class="device-mgmt__hintRow">
|
|
|
|
|
<div class="device-mgmt__hintTitle">最近更新时间</div>
|
|
|
|
|
<div class="device-mgmt__hintValue">{{ lastRealtimeAt }}</div>
|
|
|
|
|
</div>
|
|
|
|
|
</el-card>
|
|
|
|
|
<div v-loading="realtimeLoading">
|
|
|
|
|
<el-row :gutter="16">
|
|
|
|
|
<el-col
|
|
|
|
|
v-for="m in realtimeList"
|
|
|
|
|
:key="m.id"
|
|
|
|
|
:xs="24"
|
|
|
|
|
:sm="12"
|
|
|
|
|
:md="8"
|
|
|
|
|
:lg="6"
|
|
|
|
|
class="mb-16px"
|
|
|
|
|
>
|
|
|
|
|
<el-card shadow="never" class="device-mgmt__metricCard">
|
|
|
|
|
<div class="device-mgmt__metricTop">
|
|
|
|
|
<div class="device-mgmt__metricName">{{ m.attributeName }}</div>
|
|
|
|
|
<el-tag :type="m.status === 'warn' ? 'warning' : 'success'" size="small">
|
|
|
|
|
{{ m.status === 'warn' ? '预警' : '正常' }}
|
|
|
|
|
</el-tag>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="device-mgmt__metricValue">
|
|
|
|
|
<span class="device-mgmt__metricNumber">{{ m.value || '-' }}</span>
|
|
|
|
|
<span class="device-mgmt__metricUnit">{{ m.dataUnit || '' }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
</el-card>
|
|
|
|
|
</el-col>
|
|
|
|
|
</el-row>
|
|
|
|
|
<Pagination
|
|
|
|
|
v-model:page="realtimePage"
|
|
|
|
|
v-model:limit="realtimePageSize"
|
|
|
|
|
:total="realtimeTotal"
|
|
|
|
|
@pagination="loadRealtimeData"
|
|
|
|
|
class="mt-16px"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<template v-else-if="activeTab === 'history'">
|
|
|
|
|
<el-card shadow="never" class="device-mgmt__panel">
|
|
|
|
|
<el-card shadow="never" class="device-mgmt__panel" v-loading="historyLoading">
|
|
|
|
|
<div class="device-mgmt__filters">
|
|
|
|
|
<div class="device-mgmt__filter">
|
|
|
|
|
<div class="device-mgmt__filterLabel">参数选择</div>
|
|
|
|
|
<el-radio-group v-model="historyMetricKey" size="small">
|
|
|
|
|
<el-radio-button
|
|
|
|
|
<el-select v-model="historyMetricKey" size="small" class="!w-160px" clearable>
|
|
|
|
|
<el-option
|
|
|
|
|
v-for="m in historyMetricOptions"
|
|
|
|
|
:key="m.key"
|
|
|
|
|
:label="m.key"
|
|
|
|
|
>
|
|
|
|
|
{{ m.name }}
|
|
|
|
|
</el-radio-button>
|
|
|
|
|
</el-radio-group>
|
|
|
|
|
:label="m.name"
|
|
|
|
|
:value="m.key"
|
|
|
|
|
/>
|
|
|
|
|
</el-select>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="device-mgmt__filter">
|
|
|
|
|
<div class="device-mgmt__filterLabel">时间范围</div>
|
|
|
|
|
<el-radio-group v-model="historyRange" size="small">
|
|
|
|
|
<el-radio-button label="today">今日</el-radio-button>
|
|
|
|
|
<el-radio-button label="week">本周</el-radio-button>
|
|
|
|
|
<el-radio-button label="month">本月</el-radio-button>
|
|
|
|
|
</el-radio-group>
|
|
|
|
|
<el-date-picker
|
|
|
|
|
v-model="historyTimeRange"
|
|
|
|
|
type="datetimerange"
|
|
|
|
|
size="small"
|
|
|
|
|
range-separator="-"
|
|
|
|
|
start-placeholder="开始时间"
|
|
|
|
|
end-placeholder="结束时间"
|
|
|
|
|
value-format="YYYY-MM-DD HH:mm:ss"
|
|
|
|
|
class="!w-320px"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="device-mgmt__filterActions">
|
|
|
|
|
<el-button type="primary">查询</el-button>
|
|
|
|
|
<el-button>导出</el-button>
|
|
|
|
|
<el-button type="primary" @click="loadHistoryData" size="small">查询</el-button>
|
|
|
|
|
<el-radio-group v-model="historyViewType" size="small" class="ml-16px">
|
|
|
|
|
<el-radio-button label="chart">曲线</el-radio-button>
|
|
|
|
|
<el-radio-button label="list">列表</el-radio-button>
|
|
|
|
|
</el-radio-group>
|
|
|
|
|
<el-button size="small" class="ml-16px">导出</el-button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<Echart :height="360" :options="historyChartOptions" />
|
|
|
|
|
|
|
|
|
|
<template v-if="historyViewType === 'chart'">
|
|
|
|
|
<Echart :height="400" :options="historyChartOptions" />
|
|
|
|
|
</template>
|
|
|
|
|
<template v-else>
|
|
|
|
|
<el-table :data="historyList" border height="400" class="mt-16px">
|
|
|
|
|
<el-table-column label="时间" prop="time" width="180" />
|
|
|
|
|
<el-table-column label="参数" prop="attributeName" />
|
|
|
|
|
<el-table-column label="数值" prop="value" />
|
|
|
|
|
<el-table-column label="单位" prop="unit" />
|
|
|
|
|
</el-table>
|
|
|
|
|
</template>
|
|
|
|
|
</el-card>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<template v-else>
|
|
|
|
|
<el-card shadow="never" class="device-mgmt__panel">
|
|
|
|
|
<el-card shadow="never" class="device-mgmt__panel" v-loading="alarmLoading">
|
|
|
|
|
<div class="device-mgmt__filters">
|
|
|
|
|
<div class="device-mgmt__filter">
|
|
|
|
|
<!-- <div class="device-mgmt__filter">
|
|
|
|
|
<div class="device-mgmt__filterLabel">报警类型</div>
|
|
|
|
|
<el-select v-model="alarmType" clearable placeholder="全部" class="!w-200px">
|
|
|
|
|
<el-option label="温度过高" value="temp_high" />
|
|
|
|
|
<el-option label="压力过高" value="pressure_high" />
|
|
|
|
|
<el-option label="电流过高" value="current_high" />
|
|
|
|
|
</el-select>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="device-mgmt__filter">
|
|
|
|
|
</div> -->
|
|
|
|
|
<!-- <div class="device-mgmt__filter">
|
|
|
|
|
<div class="device-mgmt__filterLabel">时间范围</div>
|
|
|
|
|
<el-radio-group v-model="alarmRange" size="small">
|
|
|
|
|
<el-radio-button label="today">今日</el-radio-button>
|
|
|
|
|
<el-radio-button label="week">本周</el-radio-button>
|
|
|
|
|
<el-radio-button label="month">本月</el-radio-button>
|
|
|
|
|
</el-radio-group>
|
|
|
|
|
</div>
|
|
|
|
|
</div> -->
|
|
|
|
|
<div class="device-mgmt__filterActions">
|
|
|
|
|
<el-button type="primary">查询</el-button>
|
|
|
|
|
<!-- <el-button type="primary" @click="loadAlarmData">查询</el-button> -->
|
|
|
|
|
<el-button type="success">导出</el-button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<el-row :gutter="16" class="mt-16px">
|
|
|
|
|
<el-col :xs="24" :sm="8">
|
|
|
|
|
<el-card shadow="never" class="device-mgmt__summaryCard">
|
|
|
|
|
<div class="device-mgmt__summaryValue">{{ alarmSummary.total }}</div>
|
|
|
|
|
<div class="device-mgmt__summaryLabel">总报警</div>
|
|
|
|
|
</el-card>
|
|
|
|
|
</el-col>
|
|
|
|
|
<el-col :xs="24" :sm="8">
|
|
|
|
|
<el-card
|
|
|
|
|
shadow="never"
|
|
|
|
|
class="device-mgmt__summaryCard device-mgmt__summaryCard--warn"
|
|
|
|
|
>
|
|
|
|
|
<div class="device-mgmt__summaryValue">{{ alarmSummary.unresolved }}</div>
|
|
|
|
|
<div class="device-mgmt__summaryLabel">未处理</div>
|
|
|
|
|
</el-card>
|
|
|
|
|
</el-col>
|
|
|
|
|
<el-col :xs="24" :sm="8">
|
|
|
|
|
<el-card
|
|
|
|
|
shadow="never"
|
|
|
|
|
class="device-mgmt__summaryCard device-mgmt__summaryCard--ok"
|
|
|
|
|
>
|
|
|
|
|
<div class="device-mgmt__summaryValue">{{ alarmSummary.resolved }}</div>
|
|
|
|
|
<div class="device-mgmt__summaryLabel">已处理</div>
|
|
|
|
|
</el-card>
|
|
|
|
|
</el-col>
|
|
|
|
|
</el-row>
|
|
|
|
|
|
|
|
|
|
<el-table :data="alarmList" class="mt-16px" border>
|
|
|
|
|
<el-table-column label="报警时间" prop="time" width="180" />
|
|
|
|
|
<el-table-column label="报警类型" prop="typeLabel" min-width="140" />
|
|
|
|
|
<el-table-column label="报警级别" prop="level" width="120">
|
|
|
|
|
<el-table-column label="报警时间" prop="createTime" width="180">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
{{ formatDate(row.createTime) }}
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="设备名称" prop="deviceName" min-width="140" />
|
|
|
|
|
<el-table-column label="模型名称" prop="modelName" min-width="120" />
|
|
|
|
|
<el-table-column label="规则名称" prop="ruleName" min-width="120" />
|
|
|
|
|
<el-table-column label="报警级别" prop="alarmLevel" width="120">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
<el-tag
|
|
|
|
|
:type="
|
|
|
|
|
row.level === '高' ? 'danger' : row.level === '中' ? 'warning' : 'info'
|
|
|
|
|
String(row.alarmLevel) === '1'
|
|
|
|
|
? 'danger'
|
|
|
|
|
: String(row.alarmLevel) === '2'
|
|
|
|
|
? 'warning'
|
|
|
|
|
: 'info'
|
|
|
|
|
"
|
|
|
|
|
>
|
|
|
|
|
{{ row.level }}
|
|
|
|
|
</el-tag>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="当前值" prop="current" width="120" />
|
|
|
|
|
<el-table-column label="阈值" prop="threshold" width="120" />
|
|
|
|
|
<el-table-column label="状态" prop="status" width="120">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
<el-tag :type="row.status === '已处理' ? 'success' : 'warning'">
|
|
|
|
|
{{ row.status }}
|
|
|
|
|
{{
|
|
|
|
|
String(row.alarmLevel) === '1'
|
|
|
|
|
? '高级'
|
|
|
|
|
: String(row.alarmLevel) === '2'
|
|
|
|
|
? '中级'
|
|
|
|
|
: '低级'
|
|
|
|
|
}}
|
|
|
|
|
</el-tag>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="当前值" prop="addressValue" width="120" />
|
|
|
|
|
</el-table>
|
|
|
|
|
<Pagination
|
|
|
|
|
v-model:page="alarmPage"
|
|
|
|
|
v-model:limit="alarmPageSize"
|
|
|
|
|
:total="alarmTotal"
|
|
|
|
|
@pagination="loadAlarmData"
|
|
|
|
|
class="mt-16px"
|
|
|
|
|
/>
|
|
|
|
|
</el-card>
|
|
|
|
|
</template>
|
|
|
|
|
</div>
|
|
|
|
|
@ -387,14 +496,16 @@
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
import { computed, ref, watchEffect } from 'vue'
|
|
|
|
|
import { computed, ref, watchEffect, watch } from 'vue'
|
|
|
|
|
import type { EChartsOption } from 'echarts'
|
|
|
|
|
import Echart from '@/components/Echart/src/Echart.vue'
|
|
|
|
|
import { DeviceApi } from '@/api/iot/device'
|
|
|
|
|
import { OrgNodeApi } from '@/api/iot/orgNode'
|
|
|
|
|
import { DeviceModelAttributeApi } from '@/api/iot/devicemodelattribute'
|
|
|
|
|
import { useRoute } from 'vue-router'
|
|
|
|
|
import NodeForm from './NodeForm.vue'
|
|
|
|
|
import DeviceForm from '../DeviceForm.vue'
|
|
|
|
|
import { formatDate } from '@/utils/formatTime'
|
|
|
|
|
|
|
|
|
|
defineOptions({ name: 'IoTDeviceManagement' })
|
|
|
|
|
|
|
|
|
|
@ -406,6 +517,8 @@ type HistoryRange = 'today' | 'week' | 'month'
|
|
|
|
|
type TreeNode = {
|
|
|
|
|
id: string
|
|
|
|
|
parentId?: string
|
|
|
|
|
parentKey?: string
|
|
|
|
|
nodeKey?: string
|
|
|
|
|
customerId?: string
|
|
|
|
|
nodeType?: number
|
|
|
|
|
deviceId?: string
|
|
|
|
|
@ -446,7 +559,7 @@ const message = useMessage()
|
|
|
|
|
const route = useRoute()
|
|
|
|
|
const treeData = ref<TreeNode[]>([])
|
|
|
|
|
const selectedNode = ref<TreeNode>()
|
|
|
|
|
const selectedDevice = ref<TreeNode>()
|
|
|
|
|
const selectedDeviceDetail = ref<Record<string, any>>({})
|
|
|
|
|
const customerId = ref<string | undefined>((route.query.customerId as string) || undefined)
|
|
|
|
|
const activeTab = ref<'base' | 'realtime' | 'history' | 'alarm'>('base')
|
|
|
|
|
const nodeFormRef = ref()
|
|
|
|
|
@ -492,6 +605,22 @@ const toNodeType = (nodeType?: number | string): TreeNodeType => {
|
|
|
|
|
return 'device'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const getNodeTypeLabel = (nodeType?: number | string) => {
|
|
|
|
|
const value = Number(nodeType)
|
|
|
|
|
if (value === 2) return '车间'
|
|
|
|
|
if (value === 3) return '产线'
|
|
|
|
|
if (value === 4) return '设备'
|
|
|
|
|
return '组织'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const getNodeTypeClass = (nodeType?: number | string) => {
|
|
|
|
|
const value = Number(nodeType)
|
|
|
|
|
if (value === 2) return 'device-mgmt__treeNodeType--workshop'
|
|
|
|
|
if (value === 3) return 'device-mgmt__treeNodeType--line'
|
|
|
|
|
if (value === 4) return 'device-mgmt__treeNodeType--device'
|
|
|
|
|
return 'device-mgmt__treeNodeType--default'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const toBooleanStatus = (raw: any): boolean => {
|
|
|
|
|
if (typeof raw.online === 'boolean') return raw.online
|
|
|
|
|
if (typeof raw.isOnline === 'boolean') return raw.isOnline
|
|
|
|
|
@ -505,6 +634,8 @@ const normalizeNode = (raw: any): TreeNode => {
|
|
|
|
|
return {
|
|
|
|
|
id,
|
|
|
|
|
parentId: raw.parentId !== undefined ? String(raw.parentId) : undefined,
|
|
|
|
|
parentKey: raw.parentKey !== undefined ? String(raw.parentKey) : undefined,
|
|
|
|
|
nodeKey: raw.nodeKey !== undefined ? String(raw.nodeKey) : undefined,
|
|
|
|
|
customerId: raw.customerId !== undefined ? String(raw.customerId) : undefined,
|
|
|
|
|
nodeType: Number(raw.nodeType ?? raw.type ?? 3),
|
|
|
|
|
deviceId: raw.deviceId !== undefined ? String(raw.deviceId) : undefined,
|
|
|
|
|
@ -516,28 +647,6 @@ const normalizeNode = (raw: any): TreeNode => {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const mapDeviceDetail = (raw: any): TreeNode => {
|
|
|
|
|
const status = raw.status ?? raw.isConnect ?? raw.online
|
|
|
|
|
const online = status === 1 || status === '1' || status === 'online' || status === '在线'
|
|
|
|
|
return {
|
|
|
|
|
id: String(raw.id ?? ''),
|
|
|
|
|
type: 'device',
|
|
|
|
|
name: raw.deviceName ?? '-',
|
|
|
|
|
code: raw.deviceCode ?? '-',
|
|
|
|
|
online,
|
|
|
|
|
model: raw.deviceModelName ?? raw.model ?? '-',
|
|
|
|
|
manufacturer: raw.manufacturer ?? raw.deviceBrandName ?? '-',
|
|
|
|
|
customerName: raw.customerName ?? '-',
|
|
|
|
|
lineName: raw.lineName ?? '-',
|
|
|
|
|
address: raw.address ?? raw.location ?? '-',
|
|
|
|
|
mqttTopic: raw.readTopic ?? '-',
|
|
|
|
|
mqttClientId: raw.clientId ?? raw.deviceCode ?? '-',
|
|
|
|
|
createdAt: raw.createTime ?? '-',
|
|
|
|
|
updatedAt: raw.updateTime ?? raw.lastOnlineTime ?? '-',
|
|
|
|
|
remark: raw.remark ?? '-'
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const getCustomerId = (node?: TreeNode) => {
|
|
|
|
|
return node?.customerId || customerId.value || treeData.value[0]?.customerId || ''
|
|
|
|
|
}
|
|
|
|
|
@ -570,7 +679,7 @@ const getFirstSelectableNode = (nodes: TreeNode[]): TreeNode | undefined => {
|
|
|
|
|
|
|
|
|
|
const loadDeviceDetail = async (deviceId: number) => {
|
|
|
|
|
const detail = await DeviceApi.getDevice(deviceId)
|
|
|
|
|
selectedDevice.value = mapDeviceDetail(detail)
|
|
|
|
|
selectedDeviceDetail.value = detail || {}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const getOperatingStatusType = (status?: string | number) => {
|
|
|
|
|
@ -600,6 +709,15 @@ const loadNodeDeviceList = async () => {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const handleDeviceEnableChange = async (row: DeviceListRow, enabled: boolean) => {
|
|
|
|
|
try {
|
|
|
|
|
await DeviceApi.updateDeviceEnabled({ id: row.id, enabled })
|
|
|
|
|
message.success(enabled ? '启用成功' : '停用成功')
|
|
|
|
|
} catch (error) {
|
|
|
|
|
row.isEnable = !enabled
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const handleTreeNodeClick = async (node: TreeNode) => {
|
|
|
|
|
selectedNode.value = node
|
|
|
|
|
rightMode.value = 'list'
|
|
|
|
|
@ -669,8 +787,17 @@ const handleNodeFormSuccess = async () => {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const removeNode = async (node: TreeNode) => {
|
|
|
|
|
if (Array.isArray(node.children) && node.children.length > 0) {
|
|
|
|
|
message.warning('当前节点存在子节点,无法删除')
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
await message.delConfirm(`确认删除节点「${node.name}」吗?`)
|
|
|
|
|
await OrgNodeApi.deleteOrgNode(node.id)
|
|
|
|
|
if (Number(node.nodeType) === 4) {
|
|
|
|
|
const deviceId = Number(node.deviceId || node.id)
|
|
|
|
|
await DeviceApi.deleteDevice(String(deviceId))
|
|
|
|
|
} else {
|
|
|
|
|
await OrgNodeApi.deleteOrgNode(node.id)
|
|
|
|
|
}
|
|
|
|
|
message.success('删除成功')
|
|
|
|
|
if (selectedNode.value?.id === node.id) {
|
|
|
|
|
selectedNode.value = undefined
|
|
|
|
|
@ -704,76 +831,109 @@ onMounted(async () => {
|
|
|
|
|
await getTreeData()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const buildRealtimeMetrics = (online?: boolean): Metric[] => {
|
|
|
|
|
const base: Metric[] = [
|
|
|
|
|
{ key: 'temp', name: '温度', value: 45.2, unit: '℃', status: 'ok' },
|
|
|
|
|
{ key: 'pressure', name: '压力', value: 0.85, unit: 'MPa', status: 'ok' },
|
|
|
|
|
{ key: 'power', name: '功率', value: 1200, unit: 'W', status: 'ok' },
|
|
|
|
|
{ key: 'current', name: '电流', value: 15.6, unit: 'A', status: 'warn' },
|
|
|
|
|
{ key: 'voltage', name: '电压', value: 380, unit: 'V', status: 'ok' },
|
|
|
|
|
{ key: 'flow', name: '水流', value: 8.5, unit: 'L/min', status: 'ok' },
|
|
|
|
|
{ key: 'runtime', name: '运行时间', value: 8.5, unit: 'h', status: 'ok' }
|
|
|
|
|
]
|
|
|
|
|
if (!online) {
|
|
|
|
|
return base.map((m) => ({ ...m, status: 'warn', value: Number((m.value * 0.6).toFixed(2)) }))
|
|
|
|
|
const realtimeList = ref<any[]>([])
|
|
|
|
|
const realtimeTotal = ref(0)
|
|
|
|
|
const realtimePage = ref(1)
|
|
|
|
|
const realtimePageSize = ref(16)
|
|
|
|
|
const realtimeLoading = ref(false)
|
|
|
|
|
|
|
|
|
|
const loadRealtimeData = async () => {
|
|
|
|
|
if (!selectedDeviceDetail.value?.id) return
|
|
|
|
|
realtimeLoading.value = true
|
|
|
|
|
try {
|
|
|
|
|
const res = await DeviceApi.getDeviceAttributePage({
|
|
|
|
|
pageNo: realtimePage.value,
|
|
|
|
|
pageSize: realtimePageSize.value,
|
|
|
|
|
deviceId: selectedDeviceDetail.value.id
|
|
|
|
|
})
|
|
|
|
|
realtimeList.value = res?.list || []
|
|
|
|
|
realtimeTotal.value = res?.total || 0
|
|
|
|
|
} finally {
|
|
|
|
|
realtimeLoading.value = false
|
|
|
|
|
}
|
|
|
|
|
return base
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const realtimeMetrics = computed(() => buildRealtimeMetrics(selectedDevice.value?.online))
|
|
|
|
|
const lastRealtimeAt = computed(() =>
|
|
|
|
|
selectedDevice.value?.updatedAt ? selectedDevice.value.updatedAt : '-'
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const historyMetricOptions = computed(() =>
|
|
|
|
|
realtimeMetrics.value
|
|
|
|
|
.filter((m) => m.key !== 'runtime')
|
|
|
|
|
.map((m) => ({ key: m.key, name: m.name }))
|
|
|
|
|
)
|
|
|
|
|
const historyMetricKey = ref<MetricKey>('temp')
|
|
|
|
|
const historyRange = ref<HistoryRange>('today')
|
|
|
|
|
|
|
|
|
|
watchEffect(() => {
|
|
|
|
|
if (
|
|
|
|
|
!historyMetricOptions.value.find((m) => m.key === historyMetricKey.value) &&
|
|
|
|
|
historyMetricOptions.value.length
|
|
|
|
|
) {
|
|
|
|
|
historyMetricKey.value = historyMetricOptions.value[0].key
|
|
|
|
|
watch([() => activeTab.value, () => selectedDeviceDetail.value.id], ([tab, deviceId]) => {
|
|
|
|
|
if (tab === 'realtime' && deviceId) {
|
|
|
|
|
realtimePage.value = 1
|
|
|
|
|
loadRealtimeData()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const buildHistory = (range: HistoryRange, base: number) => {
|
|
|
|
|
const points = range === 'today' ? 24 : range === 'week' ? 7 * 6 : 30 * 3
|
|
|
|
|
const labels: string[] = []
|
|
|
|
|
const values: number[] = []
|
|
|
|
|
for (let i = 0; i < points; i++) {
|
|
|
|
|
if (range === 'today') {
|
|
|
|
|
labels.push(String(i).padStart(2, '0') + ':00')
|
|
|
|
|
} else {
|
|
|
|
|
labels.push(`D${i + 1}`)
|
|
|
|
|
const historyMetricOptions = ref<any[]>([])
|
|
|
|
|
const historyMetricKey = ref<string | number>('')
|
|
|
|
|
const historyTimeRange = ref<string[]>([])
|
|
|
|
|
const historyViewType = ref<'chart' | 'list'>('chart')
|
|
|
|
|
const historyList = ref<any[]>([])
|
|
|
|
|
const historyLoading = ref(false)
|
|
|
|
|
|
|
|
|
|
const loadHistoryMetrics = async () => {
|
|
|
|
|
if (!selectedDeviceDetail.value?.id) return
|
|
|
|
|
try {
|
|
|
|
|
const list = await DeviceApi.getDeviceAttributeList(selectedDeviceDetail.value.id)
|
|
|
|
|
historyMetricOptions.value = Array.isArray(list) ? list : []
|
|
|
|
|
if (historyMetricOptions.value.length > 0 && !historyMetricKey.value) {
|
|
|
|
|
historyMetricKey.value =
|
|
|
|
|
historyMetricOptions.value[0].attributeCode || historyMetricOptions.value[0].attributeName
|
|
|
|
|
}
|
|
|
|
|
const noise = Math.sin(i / 2.3) * 0.9 + Math.cos(i / 5.2) * 0.6
|
|
|
|
|
const drift = (i / points) * 0.8
|
|
|
|
|
values.push(Number((base + noise + drift).toFixed(2)))
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.error(e)
|
|
|
|
|
}
|
|
|
|
|
return { labels, values }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const metricBaseValue = (key: MetricKey) => {
|
|
|
|
|
if (key === 'temp') return 42
|
|
|
|
|
if (key === 'pressure') return 0.8
|
|
|
|
|
if (key === 'power') return 1180
|
|
|
|
|
if (key === 'current') return 13
|
|
|
|
|
if (key === 'flow') return 8
|
|
|
|
|
if (key === 'voltage') return 380
|
|
|
|
|
if (key === 'runtime') return 8
|
|
|
|
|
return 1
|
|
|
|
|
const loadHistoryData = async () => {
|
|
|
|
|
if (!selectedDeviceDetail.value?.id) return
|
|
|
|
|
historyLoading.value = true
|
|
|
|
|
try {
|
|
|
|
|
const params: any = {
|
|
|
|
|
deviceId: selectedDeviceDetail.value.id,
|
|
|
|
|
modelId: selectedDeviceDetail.value.deviceModelId
|
|
|
|
|
}
|
|
|
|
|
if (historyTimeRange.value && historyTimeRange.value.length === 2) {
|
|
|
|
|
params.collectionStartTime = historyTimeRange.value[0]
|
|
|
|
|
params.collectionEndTime = historyTimeRange.value[1]
|
|
|
|
|
}
|
|
|
|
|
const res = await DeviceModelAttributeApi.operationAnalysisDetails(params)
|
|
|
|
|
let data = Array.isArray(res) ? res : res?.list || []
|
|
|
|
|
|
|
|
|
|
// Convert property names if necessary, assuming common names like createTime/time, addressValue/value, etc.
|
|
|
|
|
historyList.value = data.map((item: any) => ({
|
|
|
|
|
...item,
|
|
|
|
|
time: item.time || item.createTime || item.collectionTime,
|
|
|
|
|
value: item.value || item.addressValue || item.dataValue,
|
|
|
|
|
attributeName: item.attributeName || item.parameter || item.name,
|
|
|
|
|
unit: item.unit || item.dataUnit || ''
|
|
|
|
|
}))
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.error(e)
|
|
|
|
|
} finally {
|
|
|
|
|
historyLoading.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const historyChartOptions = computed<EChartsOption>(() => {
|
|
|
|
|
const metric = realtimeMetrics.value.find((m) => m.key === historyMetricKey.value)
|
|
|
|
|
const base = metricBaseValue(historyMetricKey.value)
|
|
|
|
|
const { labels, values } = buildHistory(historyRange.value, base)
|
|
|
|
|
let data = historyList.value
|
|
|
|
|
if (historyMetricKey.value) {
|
|
|
|
|
data = data.filter(
|
|
|
|
|
(item) =>
|
|
|
|
|
item.attributeCode === historyMetricKey.value ||
|
|
|
|
|
item.attributeName === historyMetricKey.value
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Sort by time ascending for chart
|
|
|
|
|
const sortedData = [...data].sort(
|
|
|
|
|
(a, b) => new Date(a.time).getTime() - new Date(b.time).getTime()
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const labels = sortedData.map((item) => formatDate(item.time))
|
|
|
|
|
const values = sortedData.map((item) => Number(item.value) || 0)
|
|
|
|
|
const metricName =
|
|
|
|
|
historyMetricOptions.value.find(
|
|
|
|
|
(m) =>
|
|
|
|
|
m.attributeCode === historyMetricKey.value || m.attributeName === historyMetricKey.value
|
|
|
|
|
)?.attributeName || '数据'
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
grid: { left: 20, right: 20, bottom: 20, top: 40, containLabel: true },
|
|
|
|
|
tooltip: { trigger: 'axis', axisPointer: { type: 'cross' }, padding: [6, 10] },
|
|
|
|
|
@ -786,7 +946,7 @@ const historyChartOptions = computed<EChartsOption>(() => {
|
|
|
|
|
yAxis: { type: 'value', axisTick: { show: false } },
|
|
|
|
|
series: [
|
|
|
|
|
{
|
|
|
|
|
name: metric?.name || '数据',
|
|
|
|
|
name: metricName,
|
|
|
|
|
type: 'line',
|
|
|
|
|
smooth: true,
|
|
|
|
|
showSymbol: false,
|
|
|
|
|
@ -797,88 +957,40 @@ const historyChartOptions = computed<EChartsOption>(() => {
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
type AlarmStatus = '已处理' | '未处理'
|
|
|
|
|
type AlarmLevel = '高' | '中' | '低'
|
|
|
|
|
type AlarmRow = {
|
|
|
|
|
id: string
|
|
|
|
|
time: string
|
|
|
|
|
type: string
|
|
|
|
|
typeLabel: string
|
|
|
|
|
level: AlarmLevel
|
|
|
|
|
current: string
|
|
|
|
|
threshold: string
|
|
|
|
|
status: AlarmStatus
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const alarmType = ref<string | undefined>(undefined)
|
|
|
|
|
const alarmRange = ref<HistoryRange>('today')
|
|
|
|
|
|
|
|
|
|
const mockAlarmAll = computed<AlarmRow[]>(() => {
|
|
|
|
|
const base = selectedDevice.value?.code || 'DEV'
|
|
|
|
|
return [
|
|
|
|
|
{
|
|
|
|
|
id: `${base}-a1`,
|
|
|
|
|
time: '2026-03-10 10:20:00',
|
|
|
|
|
type: 'temp_high',
|
|
|
|
|
typeLabel: '温度过高',
|
|
|
|
|
level: '高',
|
|
|
|
|
current: '85.6℃',
|
|
|
|
|
threshold: '80℃',
|
|
|
|
|
status: '未处理'
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: `${base}-a2`,
|
|
|
|
|
time: '2026-03-10 09:15:00',
|
|
|
|
|
type: 'pressure_high',
|
|
|
|
|
typeLabel: '压力过高',
|
|
|
|
|
level: '中',
|
|
|
|
|
current: '1.2MPa',
|
|
|
|
|
threshold: '1.0MPa',
|
|
|
|
|
status: '已处理'
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: `${base}-a3`,
|
|
|
|
|
time: '2026-03-09 18:30:00',
|
|
|
|
|
type: 'current_high',
|
|
|
|
|
typeLabel: '电流过高',
|
|
|
|
|
level: '中',
|
|
|
|
|
current: '16.8A',
|
|
|
|
|
threshold: '15A',
|
|
|
|
|
status: '已处理'
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: `${base}-a4`,
|
|
|
|
|
time: '2026-03-09 14:22:00',
|
|
|
|
|
type: 'temp_high',
|
|
|
|
|
typeLabel: '温度过高',
|
|
|
|
|
level: '低',
|
|
|
|
|
current: '78.2℃',
|
|
|
|
|
threshold: '75℃',
|
|
|
|
|
status: '已处理'
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: `${base}-a5`,
|
|
|
|
|
time: '2026-03-08 11:45:00',
|
|
|
|
|
type: 'pressure_high',
|
|
|
|
|
typeLabel: '压力波动',
|
|
|
|
|
level: '低',
|
|
|
|
|
current: '-',
|
|
|
|
|
threshold: '-',
|
|
|
|
|
status: '已处理'
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
watch([() => activeTab.value, () => selectedDeviceDetail.value.id], ([tab, deviceId]) => {
|
|
|
|
|
if (tab === 'history' && deviceId) {
|
|
|
|
|
loadHistoryMetrics()
|
|
|
|
|
loadHistoryData()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const alarmList = computed(() => {
|
|
|
|
|
const list = mockAlarmAll.value
|
|
|
|
|
if (!alarmType.value) return list
|
|
|
|
|
return list.filter((x) => x.type === alarmType.value)
|
|
|
|
|
})
|
|
|
|
|
const alarmList = ref<any[]>([])
|
|
|
|
|
const alarmTotal = ref(0)
|
|
|
|
|
const alarmPage = ref(1)
|
|
|
|
|
const alarmPageSize = ref(10)
|
|
|
|
|
const alarmLoading = ref(false)
|
|
|
|
|
|
|
|
|
|
const alarmSummary = computed(() => {
|
|
|
|
|
const total = alarmList.value.length
|
|
|
|
|
const unresolved = alarmList.value.filter((x) => x.status === '未处理').length
|
|
|
|
|
return { total, unresolved, resolved: total - unresolved }
|
|
|
|
|
const loadAlarmData = async () => {
|
|
|
|
|
if (!selectedDeviceDetail.value?.id) return
|
|
|
|
|
alarmLoading.value = true
|
|
|
|
|
try {
|
|
|
|
|
const res = await DeviceApi.getDeviceWarinningRecordPage({
|
|
|
|
|
pageNo: alarmPage.value,
|
|
|
|
|
pageSize: alarmPageSize.value,
|
|
|
|
|
deviceId: selectedDeviceDetail.value.id
|
|
|
|
|
})
|
|
|
|
|
alarmList.value = res?.list || []
|
|
|
|
|
alarmTotal.value = res?.total || 0
|
|
|
|
|
} finally {
|
|
|
|
|
alarmLoading.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
watch([() => activeTab.value, () => selectedDeviceDetail.value.id], ([tab, deviceId]) => {
|
|
|
|
|
if (tab === 'alarm' && deviceId) {
|
|
|
|
|
alarmPage.value = 1
|
|
|
|
|
loadAlarmData()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
@ -972,6 +1084,39 @@ const alarmSummary = computed(() => {
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.device-mgmt__treeNodeType {
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
padding: 0 6px;
|
|
|
|
|
line-height: 18px;
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
border: 1px solid transparent;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.device-mgmt__treeNodeType--workshop {
|
|
|
|
|
color: var(--el-color-warning);
|
|
|
|
|
background: var(--el-color-warning-light-9);
|
|
|
|
|
border-color: var(--el-color-warning-light-7);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.device-mgmt__treeNodeType--line {
|
|
|
|
|
color: var(--el-color-primary);
|
|
|
|
|
background: var(--el-color-primary-light-9);
|
|
|
|
|
border-color: var(--el-color-primary-light-7);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.device-mgmt__treeNodeType--device {
|
|
|
|
|
color: var(--el-color-success);
|
|
|
|
|
background: var(--el-color-success-light-9);
|
|
|
|
|
border-color: var(--el-color-success-light-7);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.device-mgmt__treeNodeType--default {
|
|
|
|
|
color: var(--el-text-color-secondary);
|
|
|
|
|
background: var(--el-fill-color-light);
|
|
|
|
|
border-color: var(--el-border-color-light);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.device-mgmt__treeNodeMeta {
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
color: var(--el-text-color-secondary);
|
|
|
|
|
@ -982,7 +1127,10 @@ const alarmSummary = computed(() => {
|
|
|
|
|
margin-left: auto;
|
|
|
|
|
display: none;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 4px;
|
|
|
|
|
|
|
|
|
|
.el-button {
|
|
|
|
|
margin: 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.device-mgmt__treeNode:hover .device-mgmt__treeNodeActions {
|
|
|
|
|
|