feat:【iot】modbus-tcp 协议接入 100%:完整实现,修复 todo

This commit is contained in:
YunaiV
2026-01-18 00:07:23 +08:00
parent 56a25258ee
commit 972eb47610
4 changed files with 87 additions and 59 deletions

View File

@@ -4,14 +4,14 @@ import request from '@/config/axios'
export interface DeviceModbusPointVO {
id?: number // 主键
deviceId: number // 设备编号
thingModelId: number // 物模型属性编号
thingModelId?: number // 物模型属性编号
identifier: string // 属性标识符
name: string // 属性名称
functionCode: number // Modbus 功能码
registerAddress: number // 寄存器起始地址
registerCount: number // 寄存器数量
byteOrder: string // 字节序
rawDataType: string // 原始数据类型
functionCode?: number // Modbus 功能码
registerAddress?: number // 寄存器起始地址
registerCount?: number // 寄存器数量
byteOrder?: string // 字节序
rawDataType?: string // 原始数据类型
scale: number // 缩放因子
pollInterval: number // 轮询间隔,单位:毫秒
status: number // 状态

View File

@@ -5,7 +5,9 @@
<ContentWrap>
<div class="flex items-center justify-between mb-4">
<span class="text-lg font-medium">连接配置</span>
<el-button type="primary" @click="handleEditConfig">编辑</el-button>
<el-button type="primary" @click="handleEditConfig" v-hasPermi="['iot:device:create']">
编辑
</el-button>
</div>
<!-- 详情展示 -->
@@ -26,9 +28,7 @@
{{ modbusConfig.retryInterval ? `${modbusConfig.retryInterval} ms` : '-' }}
</el-descriptions-item>
<el-descriptions-item label="状态">
<el-tag :type="modbusConfig.status === 0 ? 'success' : 'danger'">
{{ modbusConfig.status === 0 ? '启用' : '禁用' }}
</el-tag>
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="modbusConfig.status" />
</el-descriptions-item>
</el-descriptions>
</ContentWrap>
@@ -37,9 +37,8 @@
<ContentWrap class="mt-4">
<div class="flex items-center justify-between mb-4">
<span class="text-lg font-medium">点位配置</span>
<el-button type="primary" @click="handleAddPoint">
<el-button type="primary" @click="handleAddPoint" v-hasPermi="['iot:device:create']">
<Icon icon="ep:plus" class="mr-1" />
<!-- TODO @AI权限需要和后端接口对齐 -->
新增点位
</el-button>
</div>
@@ -112,9 +111,20 @@
</el-table-column>
<el-table-column label="操作" align="center" fixed="right" width="120">
<template #default="scope">
<!-- TODO @AI权限需要和后端接口对齐 -->
<el-button link type="primary" @click="handleEditPoint(scope.row)">编辑</el-button>
<el-button link type="danger" @click="handleDeletePoint(scope.row.id, scope.row.name)">
<el-button
link
type="primary"
@click="handleEditPoint(scope.row)"
v-hasPermi="['iot:device:update']"
>
编辑
</el-button>
<el-button
link
type="danger"
@click="handleDeletePoint(scope.row.id, scope.row.name)"
v-hasPermi="['iot:device:delete']"
>
删除
</el-button>
</template>

View File

@@ -1,6 +1,6 @@
<!-- Modbus 连接配置弹窗 -->
<template>
<Dialog :title="dialogTitle" v-model="dialogVisible" width="600px">
<Dialog title="编辑 Modbus 连接配置" v-model="dialogVisible" width="600px">
<el-form
ref="formRef"
:model="formData"
@@ -52,12 +52,15 @@
/>
</el-form-item>
<el-form-item label="状态" prop="status">
<!-- TODO @AI参考别的模块不要使用这个 -->
<el-switch
v-model="formData.status"
:active-value="CommonStatusEnum.ENABLE"
:inactive-value="CommonStatusEnum.DISABLE"
/>
<el-radio-group v-model="formData.status">
<el-radio
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
:key="dict.value"
:label="dict.value"
>
{{ dict.label }}
</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<template #footer>
@@ -69,6 +72,7 @@
<script lang="ts" setup>
import { DeviceModbusConfigApi, DeviceModbusConfigVO } from '@/api/iot/device/modbus/config'
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
import { CommonStatusEnum } from '@/utils/constants'
defineOptions({ name: 'DeviceModbusConfigForm' })
@@ -82,12 +86,8 @@ const emit = defineEmits<{
}>()
const message = useMessage()
const dialogVisible = ref(false)
const dialogTitle = ref('编辑 Modbus 连接配置')
const formLoading = ref(false)
const formRef = ref()
const dialogVisible = ref(false) // 弹窗的是否展示
const formLoading = ref(false) // 表单提交 loading 状态
const formData = ref<DeviceModbusConfigVO>({
deviceId: props.deviceId,
ip: '',
@@ -97,8 +97,6 @@ const formData = ref<DeviceModbusConfigVO>({
retryInterval: 10000,
status: CommonStatusEnum.ENABLE
})
/** 表单校验规则 */
const formRules = {
ip: [{ required: true, message: '请输入 IP 地址', trigger: 'blur' }],
port: [{ required: true, message: '请输入端口', trigger: 'blur' }],
@@ -106,6 +104,7 @@ const formRules = {
timeout: [{ required: true, message: '请输入连接超时时间', trigger: 'blur' }],
retryInterval: [{ required: true, message: '请输入重试间隔', trigger: 'blur' }]
}
const formRef = ref() // 表单 Ref
/** 打开弹窗 */
const open = async (data?: DeviceModbusConfigVO) => {

View File

@@ -35,14 +35,18 @@
</el-select>
</el-form-item>
<el-form-item label="寄存器地址" prop="registerAddress">
<el-input-number
v-model="formData.registerAddress"
<el-input
v-model.number="formData.registerAddress"
type="number"
:min="0"
:max="65535"
controls-position="right"
placeholder="请输入寄存器地址"
class="!w-full"
/>
>
<template #suffix>
<span class="text-gray-400">{{ registerAddressHex }}</span>
</template>
</el-input>
</el-form-item>
<el-form-item label="寄存器数量" prop="registerCount">
<el-input-number
@@ -100,11 +104,15 @@
/>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-switch
v-model="formData.status"
:active-value="CommonStatusEnum.ENABLE"
:inactive-value="CommonStatusEnum.DISABLE"
/>
<el-radio-group v-model="formData.status">
<el-radio
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
:key="dict.value"
:label="dict.value"
>
{{ dict.label }}
</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<template #footer>
@@ -120,8 +128,10 @@ import { DeviceModbusPointApi, DeviceModbusPointVO } from '@/api/iot/device/modb
import {
ModbusFunctionCodeOptions,
ModbusRawDataTypeOptions,
getByteOrderOptions
getByteOrderOptions,
IoTThingModelTypeEnum
} from '@/views/iot/utils/constants'
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
import { CommonStatusEnum } from '@/utils/constants'
defineOptions({ name: 'DeviceModbusPointForm' })
@@ -135,26 +145,27 @@ const emit = defineEmits<{
(e: 'success'): void
}>()
const { t } = useI18n()
const message = useMessage()
const dialogVisible = ref(false) // 弹窗的是否展示
const dialogTitle = ref('') // 弹窗的标题
const formLoading = ref(false) // 表单的加载中1修改时的数据加载2提交的按钮禁用
const formType = ref('') // 表单的类型create - 新增update - 修改
const formData = ref<DeviceModbusPointVO>({
deviceId: props.deviceId,
thingModelId: 0, // TODO @AI不要有默认值
thingModelId: undefined,
identifier: '',
name: '',
functionCode: 3,
registerAddress: 0,
registerCount: 1,
byteOrder: 'AB',
rawDataType: 'INT16',
functionCode: undefined,
registerAddress: undefined,
registerCount: undefined,
byteOrder: undefined,
rawDataType: undefined,
scale: 1,
pollInterval: 5000,
status: CommonStatusEnum.ENABLE
}) // TODO @AIregisterAddress、registerAddress、registerCount、rawDataType 不要有默认值;
})
const formRules = {
thingModelId: [{ required: true, message: '请选择物模型属性', trigger: 'change' }],
functionCode: [{ required: true, message: '请选择功能码', trigger: 'change' }],
@@ -165,14 +176,24 @@ const formRules = {
}
const formRef = ref() // 表单 Ref
/** 寄存器地址十六进制显示 */
const registerAddressHex = computed(() => {
if (formData.value.registerAddress === undefined || formData.value.registerAddress === null) {
return ''
}
return '0x' + formData.value.registerAddress.toString(16).toUpperCase().padStart(4, '0')
})
/** 筛选属性类型的物模型 */
const propertyList = computed(() => {
// TODO @AI1 使用枚举代替魔法数字
return props.thingModelList.filter((item) => item.type === 1)
return props.thingModelList.filter((item) => item.type === IoTThingModelTypeEnum.PROPERTY)
})
/** 当前字节序选项(根据数据类型动态变化) */
const currentByteOrderOptions = computed(() => {
if (!formData.value.rawDataType) {
return []
}
return getByteOrderOptions(formData.value.rawDataType)
})
@@ -204,8 +225,7 @@ const handleRawDataTypeChange = (rawDataType: string) => {
const open = async (type: 'create' | 'update', id?: number) => {
dialogVisible.value = true
formType.value = type
// TODO @AIdialogTitle.value = t('action.' + type)
dialogTitle.value = type === 'create' ? '新增 Modbus 点位' : '编辑 Modbus 点位'
dialogTitle.value = t('action.' + type)
resetForm()
// 修改时,设置数据
if (type === 'update' && id) {
@@ -246,15 +266,14 @@ const submitForm = async () => {
const resetForm = () => {
formData.value = {
deviceId: props.deviceId,
thingModelId: 0,
thingModelId: undefined,
identifier: '',
name: '',
// TODO @AI3、0、1、AB/INT16 使用枚举;
functionCode: 3,
registerAddress: 0,
registerCount: 1,
byteOrder: 'AB',
rawDataType: 'INT16',
functionCode: undefined,
registerAddress: undefined,
registerCount: undefined,
byteOrder: undefined,
rawDataType: undefined,
scale: 1,
pollInterval: 5000,
status: CommonStatusEnum.ENABLE