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

View File

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

View File

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

View File

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