- 新增 src/api/gateway.js(网关 CRUD + 测试连接 + options API + 枚举映射) - 新增 src/views/config/gateway/Index.vue(网关管理页面:列表/新增/编辑/测试连接/详情抽屉+下挂设备) - 修改 src/views/config/device/Index.vue(GatewayCode 文本输入改为 GatewayId 下拉选择,列表列显示 GatewayName) - 新增 src/api/product.js、src/api/thingPoint.js(产品/物模型点 API) - 新增 src/components/thing/(PointSendForm、ThingPointTable、ThingAlarmPanel 共享组件)
155 lines
5.2 KiB
Vue
155 lines
5.2 KiB
Vue
<template>
|
||
<div class="point-send-form">
|
||
<div class="ps-toolbar">
|
||
<span class="ps-count">
|
||
共 {{ points.length }} 个点,可下发 {{ writablePoints.length }} 个
|
||
<span v-if="points.length" class="ps-tip">(只读点不支持下发;Int32/Float 从地址起连写 2 个寄存器)</span>
|
||
</span>
|
||
<el-button link type="primary" size="small" :loading="loading" @click="fetchPoints">刷新</el-button>
|
||
</div>
|
||
|
||
<el-table :data="points" v-loading="loading" size="small" border stripe>
|
||
<el-table-column prop="Code" label="编码" width="120" />
|
||
<el-table-column prop="Name" label="名称" min-width="110" show-overflow-tooltip />
|
||
<el-table-column label="寄存器" width="90">
|
||
<template #default="{ row }">{{ registerLabel(row.RegisterType) }}</template>
|
||
</el-table-column>
|
||
<el-table-column label="地址" width="70" align="center">
|
||
<template #default="{ row }">0x{{ dec2hex(row.Address) }}</template>
|
||
</el-table-column>
|
||
<el-table-column label="读写" width="60" align="center">
|
||
<template #default="{ row }">{{ rwLabel(row.Rw) }}</template>
|
||
</el-table-column>
|
||
<el-table-column label="状态" width="84" align="center">
|
||
<template #default="{ row }">
|
||
<el-tag v-if="row.Rw === 1" size="small" type="info">只读</el-tag>
|
||
<el-tag v-else size="small" type="success">可下发</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="下发值" width="150">
|
||
<template #default="{ row }">
|
||
<el-switch
|
||
v-if="row.DataType === 1"
|
||
:model-value="row._sendValue === 1"
|
||
active-text="开"
|
||
@change="row._sendValue = $event ? 1 : 0"
|
||
/>
|
||
<el-input-number
|
||
v-else
|
||
v-model="row._sendValue"
|
||
:precision="row.DataType === 5 ? 1 : 0"
|
||
:step="1"
|
||
:controls="false"
|
||
size="small"
|
||
style="width: 100%"
|
||
/>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="单位" width="60" align="center">
|
||
<template #default="{ row }">{{ row.Unit || '-' }}</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作" width="70" fixed="right" align="center">
|
||
<template #default="{ row }">
|
||
<el-button
|
||
link type="primary"
|
||
:disabled="row.Rw === 1"
|
||
@click="onSend(row)"
|
||
>下发</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
|
||
<!-- 空状态区分:无点 / 点均不可下发 -->
|
||
<el-empty
|
||
v-if="!loading && points.length === 0"
|
||
description="该设备暂无物模型点,请先在「物模型点」标签页为该设备添加数据点"
|
||
:image-size="60"
|
||
/>
|
||
<el-alert
|
||
v-else-if="!loading && writablePoints.length === 0"
|
||
title="已添加的点均为只读,暂无可下发点(可将点读写属性改为「只写/读写」)"
|
||
type="info"
|
||
:closable="false"
|
||
class="ps-feedback"
|
||
/>
|
||
|
||
<!-- 下发反馈 -->
|
||
<el-alert v-if="feedback.text" :title="feedback.text" :type="feedback.type" :closable="false" class="ps-feedback" />
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, computed, watch, onMounted } from 'vue'
|
||
import { ElMessage } from 'element-plus'
|
||
import { getThingPoints, registerLabel, rwLabel } from '@/api/thingPoint'
|
||
import { sendDeviceCommand } from '@/api/device'
|
||
|
||
const props = defineProps({
|
||
deviceId: { type: String, required: true }
|
||
})
|
||
|
||
const loading = ref(false)
|
||
const points = ref([])
|
||
const feedback = ref({ text: '', type: 'success' })
|
||
|
||
// 只读(1)的点不能下发;只写/读写都可以(Int32/Float 走 FC16 连写 2 个寄存器,已支持)
|
||
const writablePoints = computed(() => points.value.filter(p => p.Rw !== 1))
|
||
|
||
function dec2hex(n) {
|
||
return (Number(n) || 0).toString(16).toUpperCase().padStart(4, '0')
|
||
}
|
||
|
||
async function fetchPoints() {
|
||
loading.value = true
|
||
try {
|
||
const pts = (await getThingPoints(2, props.deviceId)) || []
|
||
// 显示设备全部点(含只读),便于与「物模型点」标签页对照
|
||
points.value = pts.map(p => ({ ...p, _sendValue: p.DataType === 1 ? 1 : 0 }))
|
||
} catch (e) {
|
||
points.value = []
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
async function onSend(row) {
|
||
try {
|
||
await sendDeviceCommand(props.deviceId, {
|
||
Id: props.deviceId,
|
||
PointId: row.Id,
|
||
Value: row._sendValue ?? 0,
|
||
IsSimulated: false
|
||
})
|
||
// res 为后端返回的 Data(无数据时返回 undefined),消息提示走统一处理
|
||
feedback.value = { text: `已下发至「${row.Name || row.Code}」`, type: 'success' }
|
||
ElMessage.success(`指令已下发:${row.Name || row.Code}`)
|
||
} catch (e) {
|
||
feedback.value = { text: e?.message || '下发失败', type: 'error' }
|
||
}
|
||
}
|
||
|
||
watch(() => props.deviceId, (val) => { if (val) fetchPoints() })
|
||
onMounted(() => { if (props.deviceId) fetchPoints() })
|
||
|
||
defineExpose({ refresh: fetchPoints })
|
||
</script>
|
||
|
||
<style scoped>
|
||
.ps-toolbar {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 10px;
|
||
}
|
||
.ps-count {
|
||
font-size: 12px;
|
||
color: #909399;
|
||
}
|
||
.ps-tip {
|
||
color: #c0c4cc;
|
||
}
|
||
.ps-feedback {
|
||
margin-top: 12px;
|
||
}
|
||
</style>
|