|
|
|
@ -10,24 +10,6 @@
|
|
|
|
placeholder="搜索设备/产线/公司"
|
|
|
|
placeholder="搜索设备/产线/公司"
|
|
|
|
class="device-mgmt__search"
|
|
|
|
class="device-mgmt__search"
|
|
|
|
/>
|
|
|
|
/>
|
|
|
|
<div class="device-mgmt__stats">
|
|
|
|
|
|
|
|
<div class="device-mgmt__stat">
|
|
|
|
|
|
|
|
<div class="device-mgmt__statLabel">设备</div>
|
|
|
|
|
|
|
|
<div class="device-mgmt__statValue">{{ deviceTotal }}</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="device-mgmt__stat">
|
|
|
|
|
|
|
|
<div class="device-mgmt__statLabel">在线</div>
|
|
|
|
|
|
|
|
<div class="device-mgmt__statValue device-mgmt__statValue--ok">{{
|
|
|
|
|
|
|
|
deviceOnline
|
|
|
|
|
|
|
|
}}</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="device-mgmt__stat">
|
|
|
|
|
|
|
|
<div class="device-mgmt__statLabel">离线</div>
|
|
|
|
|
|
|
|
<div class="device-mgmt__statValue device-mgmt__statValue--muted">
|
|
|
|
|
|
|
|
{{ deviceTotal - deviceOnline }}
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<el-tree
|
|
|
|
<el-tree
|
|
|
|
@ -55,6 +37,31 @@
|
|
|
|
<template v-if="data.type === 'device'">
|
|
|
|
<template v-if="data.type === 'device'">
|
|
|
|
<span class="device-mgmt__treeNodeMeta">{{ data.code }}</span>
|
|
|
|
<span class="device-mgmt__treeNodeMeta">{{ data.code }}</span>
|
|
|
|
</template>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<div class="device-mgmt__treeNodeActions">
|
|
|
|
|
|
|
|
<el-button
|
|
|
|
|
|
|
|
v-if="canCreateChild(data)"
|
|
|
|
|
|
|
|
link
|
|
|
|
|
|
|
|
type="primary"
|
|
|
|
|
|
|
|
@click.stop="openCreateNodeForm(data)"
|
|
|
|
|
|
|
|
>
|
|
|
|
|
|
|
|
新增
|
|
|
|
|
|
|
|
</el-button>
|
|
|
|
|
|
|
|
<el-button
|
|
|
|
|
|
|
|
v-if="Number(data.nodeType) !== 1"
|
|
|
|
|
|
|
|
link
|
|
|
|
|
|
|
|
type="warning"
|
|
|
|
|
|
|
|
@click.stop="openUpdateNodeForm(data)"
|
|
|
|
|
|
|
|
>编辑</el-button
|
|
|
|
|
|
|
|
>
|
|
|
|
|
|
|
|
<el-button
|
|
|
|
|
|
|
|
v-if="Number(data.nodeType) !== 1"
|
|
|
|
|
|
|
|
link
|
|
|
|
|
|
|
|
type="danger"
|
|
|
|
|
|
|
|
@click.stop="removeNode(data)"
|
|
|
|
|
|
|
|
>
|
|
|
|
|
|
|
|
删除
|
|
|
|
|
|
|
|
</el-button>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
</template>
|
|
|
|
</el-tree>
|
|
|
|
</el-tree>
|
|
|
|
@ -62,12 +69,61 @@
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="device-mgmt__right">
|
|
|
|
<div class="device-mgmt__right">
|
|
|
|
|
|
|
|
<div class="device-mgmt__content" v-if="rightMode === 'list'">
|
|
|
|
|
|
|
|
<el-card shadow="never" class="device-mgmt__panel">
|
|
|
|
|
|
|
|
<template #header>
|
|
|
|
|
|
|
|
<div class="device-mgmt__listHeader">
|
|
|
|
|
|
|
|
<div class="device-mgmt__listTitle">
|
|
|
|
|
|
|
|
{{
|
|
|
|
|
|
|
|
selectedNode
|
|
|
|
|
|
|
|
? `${selectedNode.name}(${Number(selectedNode.nodeType) === 4 ? '设备节点' : '组织节点'})`
|
|
|
|
|
|
|
|
: '设备列表'
|
|
|
|
|
|
|
|
}}
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<!-- <div class="device-mgmt__listActions">
|
|
|
|
|
|
|
|
<el-button @click="loadNodeDeviceList">刷新</el-button>
|
|
|
|
|
|
|
|
</div> -->
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<el-table v-loading="nodeDeviceLoading" :data="nodeDeviceList" row-key="id" border>
|
|
|
|
|
|
|
|
<el-table-column label="设备编码" prop="deviceCode" min-width="160" />
|
|
|
|
|
|
|
|
<el-table-column label="设备名称" prop="deviceName" min-width="180" />
|
|
|
|
|
|
|
|
<el-table-column label="运行状态" prop="operatingStatus" width="120">
|
|
|
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
|
|
|
<el-tag :type="getOperatingStatusType(row.operatingStatus)">
|
|
|
|
|
|
|
|
{{ getOperatingStatusLabel(row.operatingStatus) }}
|
|
|
|
|
|
|
|
</el-tag>
|
|
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
|
|
<el-table-column label="协议" prop="protocol" width="120" />
|
|
|
|
|
|
|
|
<el-table-column label="启用" prop="isEnable" width="90">
|
|
|
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
|
|
|
{{ row.isEnable ? '是' : '否' }}
|
|
|
|
|
|
|
|
</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)"
|
|
|
|
|
|
|
|
>设备设置</el-button
|
|
|
|
|
|
|
|
>
|
|
|
|
|
|
|
|
<el-button link type="primary" @click="openDeviceDetail(row)">详情</el-button>
|
|
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
|
|
</el-table>
|
|
|
|
|
|
|
|
</el-card>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<template v-else>
|
|
|
|
<el-card shadow="never" class="device-mgmt__panel device-mgmt__header">
|
|
|
|
<el-card shadow="never" class="device-mgmt__panel device-mgmt__header">
|
|
|
|
<div class="device-mgmt__headerMain">
|
|
|
|
<div class="device-mgmt__headerMain">
|
|
|
|
<div class="device-mgmt__titleRow">
|
|
|
|
<div class="device-mgmt__titleRow">
|
|
|
|
|
|
|
|
<el-button link type="primary" @click="backToList">返回列表</el-button>
|
|
|
|
<span
|
|
|
|
<span
|
|
|
|
class="device-mgmt__dot device-mgmt__dot--lg"
|
|
|
|
class="device-mgmt__dot device-mgmt__dot--lg"
|
|
|
|
:class="selectedDevice?.online ? 'device-mgmt__dot--ok' : 'device-mgmt__dot--muted'"
|
|
|
|
:class="
|
|
|
|
|
|
|
|
selectedDevice?.online ? 'device-mgmt__dot--ok' : 'device-mgmt__dot--muted'
|
|
|
|
|
|
|
|
"
|
|
|
|
></span>
|
|
|
|
></span>
|
|
|
|
<div class="device-mgmt__titleText">
|
|
|
|
<div class="device-mgmt__titleText">
|
|
|
|
<div class="device-mgmt__title">{{ selectedDevice?.name || '-' }}</div>
|
|
|
|
<div class="device-mgmt__title">{{ selectedDevice?.name || '-' }}</div>
|
|
|
|
@ -117,7 +173,9 @@
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="device-mgmt__baseItem">
|
|
|
|
<div class="device-mgmt__baseItem">
|
|
|
|
<div class="device-mgmt__baseLabel">所属产线</div>
|
|
|
|
<div class="device-mgmt__baseLabel">所属产线</div>
|
|
|
|
<div class="device-mgmt__baseValue">{{ selectedDevice?.lineName || '-' }}</div>
|
|
|
|
<div class="device-mgmt__baseValue">{{
|
|
|
|
|
|
|
|
selectedDevice?.lineName || '-'
|
|
|
|
|
|
|
|
}}</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="device-mgmt__baseItem">
|
|
|
|
<div class="device-mgmt__baseItem">
|
|
|
|
<div class="device-mgmt__baseLabel">安装位置</div>
|
|
|
|
<div class="device-mgmt__baseLabel">安装位置</div>
|
|
|
|
@ -160,11 +218,15 @@
|
|
|
|
<div class="device-mgmt__baseGrid">
|
|
|
|
<div class="device-mgmt__baseGrid">
|
|
|
|
<div class="device-mgmt__baseItem">
|
|
|
|
<div class="device-mgmt__baseItem">
|
|
|
|
<div class="device-mgmt__baseLabel">创建时间</div>
|
|
|
|
<div class="device-mgmt__baseLabel">创建时间</div>
|
|
|
|
<div class="device-mgmt__baseValue">{{ selectedDevice?.createdAt || '-' }}</div>
|
|
|
|
<div class="device-mgmt__baseValue">{{
|
|
|
|
|
|
|
|
selectedDevice?.createdAt || '-'
|
|
|
|
|
|
|
|
}}</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="device-mgmt__baseItem">
|
|
|
|
<div class="device-mgmt__baseItem">
|
|
|
|
<div class="device-mgmt__baseLabel">最后更新</div>
|
|
|
|
<div class="device-mgmt__baseLabel">最后更新</div>
|
|
|
|
<div class="device-mgmt__baseValue">{{ selectedDevice?.updatedAt || '-' }}</div>
|
|
|
|
<div class="device-mgmt__baseValue">{{
|
|
|
|
|
|
|
|
selectedDevice?.updatedAt || '-'
|
|
|
|
|
|
|
|
}}</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="device-mgmt__baseItem device-mgmt__baseItem--full">
|
|
|
|
<div class="device-mgmt__baseItem device-mgmt__baseItem--full">
|
|
|
|
<div class="device-mgmt__baseLabel">备注</div>
|
|
|
|
<div class="device-mgmt__baseLabel">备注</div>
|
|
|
|
@ -206,7 +268,11 @@
|
|
|
|
<div class="device-mgmt__filter">
|
|
|
|
<div class="device-mgmt__filter">
|
|
|
|
<div class="device-mgmt__filterLabel">参数选择</div>
|
|
|
|
<div class="device-mgmt__filterLabel">参数选择</div>
|
|
|
|
<el-radio-group v-model="historyMetricKey" size="small">
|
|
|
|
<el-radio-group v-model="historyMetricKey" size="small">
|
|
|
|
<el-radio-button v-for="m in historyMetricOptions" :key="m.key" :label="m.key">
|
|
|
|
<el-radio-button
|
|
|
|
|
|
|
|
v-for="m in historyMetricOptions"
|
|
|
|
|
|
|
|
:key="m.key"
|
|
|
|
|
|
|
|
:label="m.key"
|
|
|
|
|
|
|
|
>
|
|
|
|
{{ m.name }}
|
|
|
|
{{ m.name }}
|
|
|
|
</el-radio-button>
|
|
|
|
</el-radio-button>
|
|
|
|
</el-radio-group>
|
|
|
|
</el-radio-group>
|
|
|
|
@ -307,15 +373,28 @@
|
|
|
|
</el-card>
|
|
|
|
</el-card>
|
|
|
|
</template>
|
|
|
|
</template>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</template>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</ContentWrap>
|
|
|
|
</ContentWrap>
|
|
|
|
|
|
|
|
<NodeForm
|
|
|
|
|
|
|
|
ref="nodeFormRef"
|
|
|
|
|
|
|
|
:tree-data="treeData"
|
|
|
|
|
|
|
|
:customer-id="customerId"
|
|
|
|
|
|
|
|
@success="handleNodeFormSuccess"
|
|
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
<DeviceForm ref="deviceFormRef" @success="onDeviceFormSuccess" />
|
|
|
|
</template>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
|
<script setup lang="ts">
|
|
|
|
import { computed, ref, watchEffect } from 'vue'
|
|
|
|
import { computed, ref, watchEffect } from 'vue'
|
|
|
|
import type { EChartsOption } from 'echarts'
|
|
|
|
import type { EChartsOption } from 'echarts'
|
|
|
|
import Echart from '@/components/Echart/src/Echart.vue'
|
|
|
|
import Echart from '@/components/Echart/src/Echart.vue'
|
|
|
|
|
|
|
|
import { DeviceApi } from '@/api/iot/device'
|
|
|
|
|
|
|
|
import { OrgNodeApi } from '@/api/iot/orgNode'
|
|
|
|
|
|
|
|
import { useRoute } from 'vue-router'
|
|
|
|
|
|
|
|
import NodeForm from './NodeForm.vue'
|
|
|
|
|
|
|
|
import DeviceForm from '../DeviceForm.vue'
|
|
|
|
|
|
|
|
|
|
|
|
defineOptions({ name: 'IoTDeviceManagement' })
|
|
|
|
defineOptions({ name: 'IoTDeviceManagement' })
|
|
|
|
|
|
|
|
|
|
|
|
@ -326,6 +405,10 @@ type HistoryRange = 'today' | 'week' | 'month'
|
|
|
|
|
|
|
|
|
|
|
|
type TreeNode = {
|
|
|
|
type TreeNode = {
|
|
|
|
id: string
|
|
|
|
id: string
|
|
|
|
|
|
|
|
parentId?: string
|
|
|
|
|
|
|
|
customerId?: string
|
|
|
|
|
|
|
|
nodeType?: number
|
|
|
|
|
|
|
|
deviceId?: string
|
|
|
|
type: TreeNodeType
|
|
|
|
type: TreeNodeType
|
|
|
|
name: string
|
|
|
|
name: string
|
|
|
|
code?: string
|
|
|
|
code?: string
|
|
|
|
@ -346,128 +429,31 @@ type TreeNode = {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
type Metric = { key: MetricKey; name: string; value: number; unit: string; status: MetricStatus }
|
|
|
|
type Metric = { key: MetricKey; name: string; value: number; unit: string; status: MetricStatus }
|
|
|
|
|
|
|
|
type DeviceListRow = {
|
|
|
|
|
|
|
|
id: number
|
|
|
|
|
|
|
|
deviceCode?: string
|
|
|
|
|
|
|
|
deviceName?: string
|
|
|
|
|
|
|
|
operatingStatus?: string | number
|
|
|
|
|
|
|
|
protocol?: string
|
|
|
|
|
|
|
|
isEnable?: boolean
|
|
|
|
|
|
|
|
collectionTime?: string
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const treeProps = { children: 'children', label: 'name' }
|
|
|
|
const treeProps = { children: 'children', label: 'name' }
|
|
|
|
const treeRef = ref()
|
|
|
|
const treeRef = ref()
|
|
|
|
const treeKeyword = ref('')
|
|
|
|
const treeKeyword = ref('')
|
|
|
|
|
|
|
|
const message = useMessage()
|
|
|
|
const treeData = ref<TreeNode[]>([
|
|
|
|
const route = useRoute()
|
|
|
|
{
|
|
|
|
const treeData = ref<TreeNode[]>([])
|
|
|
|
id: 'c1',
|
|
|
|
const selectedNode = ref<TreeNode>()
|
|
|
|
type: 'company',
|
|
|
|
const selectedDevice = ref<TreeNode>()
|
|
|
|
name: '北京科技有限公司',
|
|
|
|
const customerId = ref<string | undefined>((route.query.customerId as string) || undefined)
|
|
|
|
children: [
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
id: 'c1-l1',
|
|
|
|
|
|
|
|
type: 'line',
|
|
|
|
|
|
|
|
name: 'SMT产线A',
|
|
|
|
|
|
|
|
children: [
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
id: 'd-1001',
|
|
|
|
|
|
|
|
type: 'device',
|
|
|
|
|
|
|
|
name: '贴片机#1',
|
|
|
|
|
|
|
|
code: 'BJ-PL01-DEV01',
|
|
|
|
|
|
|
|
online: true,
|
|
|
|
|
|
|
|
companyName: '北京科技有限公司',
|
|
|
|
|
|
|
|
customerName: '北京科技有限公司',
|
|
|
|
|
|
|
|
lineName: 'SMT产线A',
|
|
|
|
|
|
|
|
model: 'SM471',
|
|
|
|
|
|
|
|
manufacturer: '三星',
|
|
|
|
|
|
|
|
address: '车间A区-01工位',
|
|
|
|
|
|
|
|
mqttTopic: 'device/bj/pl01/dev01',
|
|
|
|
|
|
|
|
mqttClientId: 'client_bj_dev01',
|
|
|
|
|
|
|
|
createdAt: '2026-01-15 09:00:00',
|
|
|
|
|
|
|
|
updatedAt: '2026-03-10 10:30:00',
|
|
|
|
|
|
|
|
remark: '位于车间A区,主贴片工位'
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
id: 'd-1002',
|
|
|
|
|
|
|
|
type: 'device',
|
|
|
|
|
|
|
|
name: '注塑机#2',
|
|
|
|
|
|
|
|
code: 'SH-P01-DEV02',
|
|
|
|
|
|
|
|
online: false,
|
|
|
|
|
|
|
|
companyName: '北京科技有限公司',
|
|
|
|
|
|
|
|
customerName: '北京科技有限公司',
|
|
|
|
|
|
|
|
lineName: 'SMT产线A',
|
|
|
|
|
|
|
|
model: 'MA3200',
|
|
|
|
|
|
|
|
productKey: 'INJ-3200',
|
|
|
|
|
|
|
|
address: '一号车间 02 工位',
|
|
|
|
|
|
|
|
mqttTopic: 'dev/iot/sh_p01/dev02',
|
|
|
|
|
|
|
|
mqttClientId: 'client_sh_p01_dev02',
|
|
|
|
|
|
|
|
createdAt: '2026-02-02 09:20:00',
|
|
|
|
|
|
|
|
updatedAt: '2026-03-09 18:30:00',
|
|
|
|
|
|
|
|
remark: '离线待检修'
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
id: 'c2',
|
|
|
|
|
|
|
|
type: 'company',
|
|
|
|
|
|
|
|
name: '上海智联科技股份公司',
|
|
|
|
|
|
|
|
children: [
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
id: 'c2-l1',
|
|
|
|
|
|
|
|
type: 'line',
|
|
|
|
|
|
|
|
name: '装配产线',
|
|
|
|
|
|
|
|
children: [
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
id: 'd-2001',
|
|
|
|
|
|
|
|
type: 'device',
|
|
|
|
|
|
|
|
name: '装配线#1',
|
|
|
|
|
|
|
|
code: 'SH-A01-DEV01',
|
|
|
|
|
|
|
|
online: true,
|
|
|
|
|
|
|
|
companyName: '上海智联科技股份公司',
|
|
|
|
|
|
|
|
lineName: '装配产线',
|
|
|
|
|
|
|
|
model: 'ASSY-X1',
|
|
|
|
|
|
|
|
productKey: 'ASSY-X1',
|
|
|
|
|
|
|
|
address: '二号车间 A 区',
|
|
|
|
|
|
|
|
mqttTopic: 'dev/iot/sh_a01/dev01',
|
|
|
|
|
|
|
|
mqttClientId: 'client_sh_a01_dev01',
|
|
|
|
|
|
|
|
createdAt: '2025-12-01 12:00:00',
|
|
|
|
|
|
|
|
updatedAt: '2026-03-10 10:28:10',
|
|
|
|
|
|
|
|
remark: '装配主线设备'
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
id: 'c3',
|
|
|
|
|
|
|
|
type: 'company',
|
|
|
|
|
|
|
|
name: '杭州云计算中心',
|
|
|
|
|
|
|
|
children: [
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
id: 'c3-l1',
|
|
|
|
|
|
|
|
type: 'line',
|
|
|
|
|
|
|
|
name: '测试产线',
|
|
|
|
|
|
|
|
children: [
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
id: 'd-3001',
|
|
|
|
|
|
|
|
type: 'device',
|
|
|
|
|
|
|
|
name: '测试机#1',
|
|
|
|
|
|
|
|
code: 'HZ-T01-DEV01',
|
|
|
|
|
|
|
|
online: true,
|
|
|
|
|
|
|
|
companyName: '杭州云计算中心',
|
|
|
|
|
|
|
|
lineName: '测试产线',
|
|
|
|
|
|
|
|
model: 'TEST-PRO',
|
|
|
|
|
|
|
|
productKey: 'TEST-PRO',
|
|
|
|
|
|
|
|
address: '三号车间 03 工位',
|
|
|
|
|
|
|
|
mqttTopic: 'dev/iot/hz_t01/dev01',
|
|
|
|
|
|
|
|
mqttClientId: 'client_hz_t01_dev01',
|
|
|
|
|
|
|
|
createdAt: '2026-01-02 11:10:00',
|
|
|
|
|
|
|
|
updatedAt: '2026-03-10 10:15:00',
|
|
|
|
|
|
|
|
remark: '用于产线测试'
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const selectedDeviceId = ref<string>('')
|
|
|
|
|
|
|
|
const activeTab = ref<'base' | 'realtime' | 'history' | 'alarm'>('base')
|
|
|
|
const activeTab = ref<'base' | 'realtime' | 'history' | 'alarm'>('base')
|
|
|
|
|
|
|
|
const nodeFormRef = ref()
|
|
|
|
|
|
|
|
const deviceFormRef = ref()
|
|
|
|
|
|
|
|
const rightMode = ref<'list' | 'detail'>('list')
|
|
|
|
|
|
|
|
const nodeDeviceLoading = ref(false)
|
|
|
|
|
|
|
|
const nodeDeviceList = ref<DeviceListRow[]>([])
|
|
|
|
|
|
|
|
|
|
|
|
const flattenDevices = (nodes: TreeNode[]) => {
|
|
|
|
const flattenDevices = (nodes: TreeNode[]) => {
|
|
|
|
const devices: TreeNode[] = []
|
|
|
|
const devices: TreeNode[] = []
|
|
|
|
@ -485,24 +471,6 @@ const deviceList = computed(() => flattenDevices(treeData.value))
|
|
|
|
const deviceTotal = computed(() => deviceList.value.length)
|
|
|
|
const deviceTotal = computed(() => deviceList.value.length)
|
|
|
|
const deviceOnline = computed(() => deviceList.value.filter((d) => !!d.online).length)
|
|
|
|
const deviceOnline = computed(() => deviceList.value.filter((d) => !!d.online).length)
|
|
|
|
|
|
|
|
|
|
|
|
const selectedDevice = computed(() => deviceList.value.find((d) => d.id === selectedDeviceId.value))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const getFirstDeviceId = (node: TreeNode): string | undefined => {
|
|
|
|
|
|
|
|
if (node.type === 'device') return node.id
|
|
|
|
|
|
|
|
const stack = [...(node.children || [])]
|
|
|
|
|
|
|
|
while (stack.length) {
|
|
|
|
|
|
|
|
const cur = stack.shift()!
|
|
|
|
|
|
|
|
if (cur.type === 'device') return cur.id
|
|
|
|
|
|
|
|
if (cur.children?.length) stack.unshift(...cur.children)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return undefined
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const handleTreeNodeClick = (node: TreeNode) => {
|
|
|
|
|
|
|
|
const nextId = getFirstDeviceId(node)
|
|
|
|
|
|
|
|
if (nextId) selectedDeviceId.value = nextId
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const filterTreeNode = (value: string, data: TreeNode) => {
|
|
|
|
const filterTreeNode = (value: string, data: TreeNode) => {
|
|
|
|
if (!value) return true
|
|
|
|
if (!value) return true
|
|
|
|
const v = value.trim()
|
|
|
|
const v = value.trim()
|
|
|
|
@ -517,10 +485,223 @@ watchEffect(() => {
|
|
|
|
treeRef.value?.filter?.(treeKeyword.value)
|
|
|
|
treeRef.value?.filter?.(treeKeyword.value)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
watchEffect(() => {
|
|
|
|
const toNodeType = (nodeType?: number | string): TreeNodeType => {
|
|
|
|
if (!selectedDeviceId.value && deviceList.value.length) {
|
|
|
|
const value = Number(nodeType)
|
|
|
|
selectedDeviceId.value = deviceList.value[0].id
|
|
|
|
if (value === 1) return 'company'
|
|
|
|
|
|
|
|
if (value === 2) return 'line'
|
|
|
|
|
|
|
|
return 'device'
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const toBooleanStatus = (raw: any): boolean => {
|
|
|
|
|
|
|
|
if (typeof raw.online === 'boolean') return raw.online
|
|
|
|
|
|
|
|
if (typeof raw.isOnline === 'boolean') return raw.isOnline
|
|
|
|
|
|
|
|
if (typeof raw.status === 'number') return raw.status === 1
|
|
|
|
|
|
|
|
if (typeof raw.status === 'string') return ['online', '1', 'true', '在线'].includes(raw.status)
|
|
|
|
|
|
|
|
return false
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const normalizeNode = (raw: any): TreeNode => {
|
|
|
|
|
|
|
|
const id = String(raw.id ?? raw.nodeId ?? '')
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
|
|
id,
|
|
|
|
|
|
|
|
parentId: raw.parentId !== undefined ? String(raw.parentId) : undefined,
|
|
|
|
|
|
|
|
customerId: raw.customerId !== undefined ? String(raw.customerId) : undefined,
|
|
|
|
|
|
|
|
nodeType: Number(raw.nodeType ?? raw.type ?? 3),
|
|
|
|
|
|
|
|
deviceId: raw.deviceId !== undefined ? String(raw.deviceId) : undefined,
|
|
|
|
|
|
|
|
type: toNodeType(raw.nodeType ?? raw.type),
|
|
|
|
|
|
|
|
name: raw.name ?? raw.nodeName ?? '-',
|
|
|
|
|
|
|
|
code: raw.code ?? raw.deviceCode,
|
|
|
|
|
|
|
|
online: toBooleanStatus(raw),
|
|
|
|
|
|
|
|
children: Array.isArray(raw.children) ? raw.children.map((child) => normalizeNode(child)) : []
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 || ''
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const getNodeDeviceId = (node: TreeNode): number | undefined => {
|
|
|
|
|
|
|
|
const direct = Number(node.deviceId)
|
|
|
|
|
|
|
|
if (Number.isFinite(direct) && direct > 0) return direct
|
|
|
|
|
|
|
|
if (node.type === 'device') {
|
|
|
|
|
|
|
|
const selfId = Number(node.id)
|
|
|
|
|
|
|
|
if (Number.isFinite(selfId) && selfId > 0) return selfId
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!node.children?.length) return undefined
|
|
|
|
|
|
|
|
for (const child of node.children) {
|
|
|
|
|
|
|
|
const childId = getNodeDeviceId(child)
|
|
|
|
|
|
|
|
if (childId) return childId
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return undefined
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const getFirstSelectableNode = (nodes: TreeNode[]): TreeNode | undefined => {
|
|
|
|
|
|
|
|
for (const node of nodes) {
|
|
|
|
|
|
|
|
if (getNodeDeviceId(node)) return node
|
|
|
|
|
|
|
|
if (node.children?.length) {
|
|
|
|
|
|
|
|
const child = getFirstSelectableNode(node.children)
|
|
|
|
|
|
|
|
if (child) return child
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return nodes[0]
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const loadDeviceDetail = async (deviceId: number) => {
|
|
|
|
|
|
|
|
const detail = await DeviceApi.getDevice(deviceId)
|
|
|
|
|
|
|
|
selectedDevice.value = mapDeviceDetail(detail)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const getOperatingStatusType = (status?: string | number) => {
|
|
|
|
|
|
|
|
const statusText = String(status ?? '')
|
|
|
|
|
|
|
|
if (statusText === '运行' || statusText === 'running' || statusText === '1') return 'success'
|
|
|
|
|
|
|
|
if (statusText === '故障中' || statusText === 'fault') return 'danger'
|
|
|
|
|
|
|
|
if (statusText === '报警中' || statusText === 'alarm') return 'warning'
|
|
|
|
|
|
|
|
return 'info'
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const getOperatingStatusLabel = (status?: string | number) => {
|
|
|
|
|
|
|
|
if (status === undefined || status === null || status === '') return '-'
|
|
|
|
|
|
|
|
return String(status)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const loadNodeDeviceList = async () => {
|
|
|
|
|
|
|
|
if (!selectedNode.value) return
|
|
|
|
|
|
|
|
nodeDeviceLoading.value = true
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
const data = await OrgNodeApi.getDeviceListByNode({
|
|
|
|
|
|
|
|
nodeId: Number(selectedNode.value.id),
|
|
|
|
|
|
|
|
nodeType: Number(selectedNode.value.nodeType || 4)
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
nodeDeviceList.value = Array.isArray(data) ? data : []
|
|
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
|
|
nodeDeviceLoading.value = false
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const handleTreeNodeClick = async (node: TreeNode) => {
|
|
|
|
|
|
|
|
selectedNode.value = node
|
|
|
|
|
|
|
|
rightMode.value = 'list'
|
|
|
|
|
|
|
|
await loadNodeDeviceList()
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const getTreeData = async () => {
|
|
|
|
|
|
|
|
const params = getCustomerId() ? { customerId: getCustomerId() } : {}
|
|
|
|
|
|
|
|
const data = await OrgNodeApi.getOrgNodeTree(params)
|
|
|
|
|
|
|
|
treeData.value = Array.isArray(data) ? data.map((item) => normalizeNode(item)) : []
|
|
|
|
|
|
|
|
if (!selectedNode.value) {
|
|
|
|
|
|
|
|
const firstNode = getFirstSelectableNode(treeData.value)
|
|
|
|
|
|
|
|
if (firstNode) await handleTreeNodeClick(firstNode)
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
const stack = [...treeData.value]
|
|
|
|
|
|
|
|
while (stack.length) {
|
|
|
|
|
|
|
|
const node = stack.shift()!
|
|
|
|
|
|
|
|
if (node.id === selectedNode.value.id) {
|
|
|
|
|
|
|
|
selectedNode.value = node
|
|
|
|
|
|
|
|
await loadNodeDeviceList()
|
|
|
|
|
|
|
|
break
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (node.children?.length) stack.push(...node.children)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const getDefaultParentNode = () => {
|
|
|
|
|
|
|
|
if (selectedNode.value && Number(selectedNode.value.nodeType) !== 4) return selectedNode.value
|
|
|
|
|
|
|
|
const device =
|
|
|
|
|
|
|
|
selectedNode.value && Number(selectedNode.value.nodeType) === 4 ? selectedNode.value : undefined
|
|
|
|
|
|
|
|
if (device?.parentId) {
|
|
|
|
|
|
|
|
const stack = [...treeData.value]
|
|
|
|
|
|
|
|
while (stack.length) {
|
|
|
|
|
|
|
|
const node = stack.shift()!
|
|
|
|
|
|
|
|
if (node.id === device.parentId) return node
|
|
|
|
|
|
|
|
if (node.children?.length) stack.push(...node.children)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return undefined
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const canCreateChild = (node: TreeNode) => {
|
|
|
|
|
|
|
|
const nodeType = Number(node.nodeType)
|
|
|
|
|
|
|
|
return nodeType === 1 || nodeType === 2 || nodeType === 3
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const openCreateNodeForm = (node?: TreeNode) => {
|
|
|
|
|
|
|
|
const parentNode = node || getDefaultParentNode()
|
|
|
|
|
|
|
|
if (!parentNode) {
|
|
|
|
|
|
|
|
message.warning('请先在左侧选择父组织后再新增子节点')
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!canCreateChild(parentNode)) {
|
|
|
|
|
|
|
|
message.warning('当前节点不支持新增子节点')
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
nodeFormRef.value?.open('create', parentNode, parentNode.id)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const openUpdateNodeForm = (node: TreeNode) => {
|
|
|
|
|
|
|
|
nodeFormRef.value?.open('update', node, node.parentId)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const handleNodeFormSuccess = async () => {
|
|
|
|
|
|
|
|
await getTreeData()
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const removeNode = async (node: TreeNode) => {
|
|
|
|
|
|
|
|
await message.delConfirm(`确认删除节点「${node.name}」吗?`)
|
|
|
|
|
|
|
|
await OrgNodeApi.deleteOrgNode(node.id)
|
|
|
|
|
|
|
|
message.success('删除成功')
|
|
|
|
|
|
|
|
if (selectedNode.value?.id === node.id) {
|
|
|
|
|
|
|
|
selectedNode.value = undefined
|
|
|
|
|
|
|
|
selectedDevice.value = undefined
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
await getTreeData()
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const openForm = (type: string, id?: number) => {
|
|
|
|
|
|
|
|
deviceFormRef.value?.open(type, id)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const openDeviceDetail = async (row: DeviceListRow) => {
|
|
|
|
|
|
|
|
if (!row.id) return
|
|
|
|
|
|
|
|
await loadDeviceDetail(row.id)
|
|
|
|
|
|
|
|
rightMode.value = 'detail'
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const backToList = () => {
|
|
|
|
|
|
|
|
rightMode.value = 'list'
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const onDeviceFormSuccess = async () => {
|
|
|
|
|
|
|
|
await loadNodeDeviceList()
|
|
|
|
|
|
|
|
if (rightMode.value === 'detail' && selectedDevice.value?.id) {
|
|
|
|
|
|
|
|
await loadDeviceDetail(Number(selectedDevice.value.id))
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
onMounted(async () => {
|
|
|
|
|
|
|
|
await getTreeData()
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
const buildRealtimeMetrics = (online?: boolean): Metric[] => {
|
|
|
|
const buildRealtimeMetrics = (online?: boolean): Metric[] => {
|
|
|
|
@ -733,6 +914,11 @@ const alarmSummary = computed(() => {
|
|
|
|
gap: 10px;
|
|
|
|
gap: 10px;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.device-mgmt__treeToolbar {
|
|
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
|
|
gap: 10px;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.device-mgmt__stat {
|
|
|
|
.device-mgmt__stat {
|
|
|
|
padding: 10px 12px;
|
|
|
|
padding: 10px 12px;
|
|
|
|
border-radius: 8px;
|
|
|
|
border-radius: 8px;
|
|
|
|
@ -769,7 +955,7 @@ const alarmSummary = computed(() => {
|
|
|
|
display: flex;
|
|
|
|
display: flex;
|
|
|
|
width: 100%;
|
|
|
|
width: 100%;
|
|
|
|
align-items: center;
|
|
|
|
align-items: center;
|
|
|
|
justify-content: space-between;
|
|
|
|
justify-content: flex-start;
|
|
|
|
gap: 12px;
|
|
|
|
gap: 12px;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@ -792,6 +978,17 @@ const alarmSummary = computed(() => {
|
|
|
|
white-space: nowrap;
|
|
|
|
white-space: nowrap;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.device-mgmt__treeNodeActions {
|
|
|
|
|
|
|
|
margin-left: auto;
|
|
|
|
|
|
|
|
display: none;
|
|
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
|
|
gap: 4px;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.device-mgmt__treeNode:hover .device-mgmt__treeNodeActions {
|
|
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.device-mgmt__dot {
|
|
|
|
.device-mgmt__dot {
|
|
|
|
width: 8px;
|
|
|
|
width: 8px;
|
|
|
|
height: 8px;
|
|
|
|
height: 8px;
|
|
|
|
|