mirror of
https://github.com/yudaocode/yudao-ui-admin-vue3.git
synced 2026-03-29 23:25:52 +00:00
feat(iot):Modbus 支持 Master/Slave 双模式,配置表单和详情按协议类型区分展示
1. ProtocolTypeEnum 拆分:MODBUS_TCP → MODBUS_TCP_MASTER + MODBUS_TCP_SLAVE 2. Slave 模式新增 mode(工作模式)、frameFormat(帧格式)字段,使用字典管理 3. 配置表单和详情页按 Master/Slave 模式条件展示不同字段,表单校验规则动态适配 4. 新增 DICT_TYPE:IOT_MODBUS_MODE、IOT_MODBUS_FRAME_FORMAT 5. 修复设备卡片 deviceName 过长溢出问题,添加文本截断 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,8 @@ export interface DeviceModbusConfigVO {
|
||||
slaveId: number // 从站地址
|
||||
timeout: number // 连接超时时间,单位:毫秒
|
||||
retryInterval: number // 重试间隔,单位:毫秒
|
||||
mode: number // 模式
|
||||
frameFormat: number // 帧格式
|
||||
status: number // 状态
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,8 @@ export enum ProtocolTypeEnum {
|
||||
MQTT = 'mqtt',
|
||||
EMQX = 'emqx',
|
||||
COAP = 'coap',
|
||||
MODBUS_TCP = 'modbus_tcp'
|
||||
MODBUS_TCP_MASTER = 'modbus_tcp_master',
|
||||
MODBUS_TCP_SLAVE = 'modbus_tcp_slave'
|
||||
}
|
||||
|
||||
// IoT 序列化类型枚举
|
||||
|
||||
@@ -248,5 +248,7 @@ export enum DICT_TYPE {
|
||||
IOT_ALERT_RECEIVE_TYPE = 'iot_alert_receive_type', // IoT 告警接收类型
|
||||
IOT_OTA_TASK_DEVICE_SCOPE = 'iot_ota_task_device_scope', // IoT OTA任务设备范围
|
||||
IOT_OTA_TASK_STATUS = 'iot_ota_task_status', // IoT OTA 任务状态
|
||||
IOT_OTA_TASK_RECORD_STATUS = 'iot_ota_task_record_status' // IoT OTA 记录状态
|
||||
IOT_OTA_TASK_RECORD_STATUS = 'iot_ota_task_record_status', // IoT OTA 记录状态
|
||||
IOT_MODBUS_MODE = 'iot_modbus_mode', // IoT Modbus 工作模式
|
||||
IOT_MODBUS_FRAME_FORMAT = 'iot_modbus_frame_format' // IoT Modbus 帧格式
|
||||
}
|
||||
|
||||
@@ -12,21 +12,38 @@
|
||||
|
||||
<!-- 详情展示 -->
|
||||
<el-descriptions :column="3" border direction="horizontal">
|
||||
<el-descriptions-item label="IP 地址">
|
||||
{{ modbusConfig.ip || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="端口">
|
||||
{{ modbusConfig.port || '-' }}
|
||||
</el-descriptions-item>
|
||||
<!-- Master 模式专有字段 -->
|
||||
<template v-if="isMaster">
|
||||
<el-descriptions-item label="IP 地址">
|
||||
{{ modbusConfig.ip || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="端口">
|
||||
{{ modbusConfig.port || '-' }}
|
||||
</el-descriptions-item>
|
||||
</template>
|
||||
<!-- 公共字段 -->
|
||||
<el-descriptions-item label="从站地址">
|
||||
{{ modbusConfig.slaveId || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="连接超时">
|
||||
{{ modbusConfig.timeout ? `${modbusConfig.timeout} ms` : '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="重试间隔">
|
||||
{{ modbusConfig.retryInterval ? `${modbusConfig.retryInterval} ms` : '-' }}
|
||||
</el-descriptions-item>
|
||||
<!-- Master 模式专有字段 -->
|
||||
<template v-if="isMaster">
|
||||
<el-descriptions-item label="连接超时">
|
||||
{{ modbusConfig.timeout ? `${modbusConfig.timeout} ms` : '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="重试间隔">
|
||||
{{ modbusConfig.retryInterval ? `${modbusConfig.retryInterval} ms` : '-' }}
|
||||
</el-descriptions-item>
|
||||
</template>
|
||||
<!-- Slave 模式专有字段 -->
|
||||
<template v-if="isSlave">
|
||||
<el-descriptions-item label="工作模式">
|
||||
<dict-tag :type="DICT_TYPE.IOT_MODBUS_MODE" :value="modbusConfig.mode" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="帧格式">
|
||||
<dict-tag :type="DICT_TYPE.IOT_MODBUS_FRAME_FORMAT" :value="modbusConfig.frameFormat" />
|
||||
</el-descriptions-item>
|
||||
</template>
|
||||
<!-- 公共字段 -->
|
||||
<el-descriptions-item label="状态">
|
||||
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="modbusConfig.status" />
|
||||
</el-descriptions-item>
|
||||
@@ -141,7 +158,12 @@
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 连接配置弹窗 -->
|
||||
<DeviceModbusConfigForm ref="configFormRef" :device-id="device.id" @success="getModbusConfig" />
|
||||
<DeviceModbusConfigForm
|
||||
ref="configFormRef"
|
||||
:device-id="device.id"
|
||||
:protocol-type="product.protocolType"
|
||||
@success="getModbusConfig"
|
||||
/>
|
||||
|
||||
<!-- 点位表单弹窗 -->
|
||||
<DeviceModbusPointForm
|
||||
@@ -155,7 +177,7 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { DeviceVO } from '@/api/iot/device/device'
|
||||
import { ProductVO } from '@/api/iot/product/product'
|
||||
import { ProductVO, ProtocolTypeEnum } from '@/api/iot/product/product'
|
||||
import { ThingModelData } from '@/api/iot/thingmodel'
|
||||
import { DeviceModbusConfigApi, DeviceModbusConfigVO } from '@/api/iot/device/modbus/config'
|
||||
import { DeviceModbusPointApi, DeviceModbusPointVO } from '@/api/iot/device/modbus/point'
|
||||
@@ -175,6 +197,8 @@ const props = defineProps<{
|
||||
const message = useMessage()
|
||||
|
||||
// ======================= 连接配置 =======================
|
||||
const isMaster = computed(() => props.product.protocolType === ProtocolTypeEnum.MODBUS_TCP_MASTER) // 是否为 Master 模式
|
||||
const isSlave = computed(() => props.product.protocolType === ProtocolTypeEnum.MODBUS_TCP_SLAVE) // 是否为 Slave 模式
|
||||
const modbusConfig = ref<DeviceModbusConfigVO>({} as DeviceModbusConfigVO)
|
||||
|
||||
/** 获取连接配置 */
|
||||
|
||||
@@ -8,19 +8,23 @@
|
||||
label-width="120px"
|
||||
v-loading="formLoading"
|
||||
>
|
||||
<el-form-item label="IP 地址" prop="ip">
|
||||
<el-input v-model="formData.ip" placeholder="请输入 Modbus 服务器 IP 地址" />
|
||||
</el-form-item>
|
||||
<el-form-item label="端口" prop="port">
|
||||
<el-input-number
|
||||
v-model="formData.port"
|
||||
placeholder="请输入端口"
|
||||
:min="1"
|
||||
:max="65535"
|
||||
controls-position="right"
|
||||
class="!w-full"
|
||||
/>
|
||||
</el-form-item>
|
||||
<!-- Master 模式专有字段:IP、端口、超时、重试 -->
|
||||
<template v-if="isMaster">
|
||||
<el-form-item label="IP 地址" prop="ip">
|
||||
<el-input v-model="formData.ip" placeholder="请输入 Modbus 服务器 IP 地址" />
|
||||
</el-form-item>
|
||||
<el-form-item label="端口" prop="port">
|
||||
<el-input-number
|
||||
v-model="formData.port"
|
||||
placeholder="请输入端口"
|
||||
:min="1"
|
||||
:max="65535"
|
||||
controls-position="right"
|
||||
class="!w-full"
|
||||
/>
|
||||
</el-form-item>
|
||||
</template>
|
||||
<!-- 公共字段:从站地址 -->
|
||||
<el-form-item label="从站地址" prop="slaveId">
|
||||
<el-input-number
|
||||
v-model="formData.slaveId"
|
||||
@@ -31,26 +35,55 @@
|
||||
class="!w-full"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="连接超时(ms)" prop="timeout">
|
||||
<el-input-number
|
||||
v-model="formData.timeout"
|
||||
:min="1000"
|
||||
:step="1000"
|
||||
controls-position="right"
|
||||
placeholder="请输入连接超时时间"
|
||||
class="!w-full"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="重试间隔(ms)" prop="retryInterval">
|
||||
<el-input-number
|
||||
v-model="formData.retryInterval"
|
||||
:min="1000"
|
||||
:step="1000"
|
||||
controls-position="right"
|
||||
placeholder="请输入重试间隔"
|
||||
class="!w-full"
|
||||
/>
|
||||
</el-form-item>
|
||||
<!-- Master 模式专有字段:超时、重试 -->
|
||||
<template v-if="isMaster">
|
||||
<el-form-item label="连接超时(ms)" prop="timeout">
|
||||
<el-input-number
|
||||
v-model="formData.timeout"
|
||||
:min="1000"
|
||||
:step="1000"
|
||||
controls-position="right"
|
||||
placeholder="请输入连接超时时间"
|
||||
class="!w-full"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="重试间隔(ms)" prop="retryInterval">
|
||||
<el-input-number
|
||||
v-model="formData.retryInterval"
|
||||
:min="1000"
|
||||
:step="1000"
|
||||
controls-position="right"
|
||||
placeholder="请输入重试间隔"
|
||||
class="!w-full"
|
||||
/>
|
||||
</el-form-item>
|
||||
</template>
|
||||
<!-- Slave 模式专有字段:模式、帧格式 -->
|
||||
<template v-if="isSlave">
|
||||
<el-form-item label="工作模式" prop="mode">
|
||||
<el-radio-group v-model="formData.mode">
|
||||
<el-radio
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.IOT_MODBUS_MODE)"
|
||||
:key="dict.value"
|
||||
:label="dict.value"
|
||||
>
|
||||
{{ dict.label }}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="帧格式" prop="frameFormat">
|
||||
<el-radio-group v-model="formData.frameFormat">
|
||||
<el-radio
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.IOT_MODBUS_FRAME_FORMAT)"
|
||||
:key="dict.value"
|
||||
:label="dict.value"
|
||||
>
|
||||
{{ dict.label }}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</template>
|
||||
<!-- 公共字段:状态 -->
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-radio-group v-model="formData.status">
|
||||
<el-radio
|
||||
@@ -72,13 +105,16 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { DeviceModbusConfigApi, DeviceModbusConfigVO } from '@/api/iot/device/modbus/config'
|
||||
import { ProtocolTypeEnum } from '@/api/iot/product/product'
|
||||
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
|
||||
import { CommonStatusEnum } from '@/utils/constants'
|
||||
import { ModbusModeEnum, ModbusFrameFormatEnum } from '@/views/iot/utils/constants'
|
||||
|
||||
defineOptions({ name: 'DeviceModbusConfigForm' })
|
||||
|
||||
const props = defineProps<{
|
||||
deviceId: number
|
||||
protocolType: string
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
@@ -88,6 +124,8 @@ const emit = defineEmits<{
|
||||
const message = useMessage()
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const formLoading = ref(false) // 表单提交 loading 状态
|
||||
const isMaster = computed(() => props.protocolType === ProtocolTypeEnum.MODBUS_TCP_MASTER) // 是否为 Master 模式
|
||||
const isSlave = computed(() => props.protocolType === ProtocolTypeEnum.MODBUS_TCP_SLAVE) // 是否为 Slave 模式
|
||||
const formData = ref<DeviceModbusConfigVO>({
|
||||
deviceId: props.deviceId,
|
||||
ip: '',
|
||||
@@ -95,15 +133,26 @@ const formData = ref<DeviceModbusConfigVO>({
|
||||
slaveId: 1,
|
||||
timeout: 3000,
|
||||
retryInterval: 10000,
|
||||
mode: ModbusModeEnum.POLLING,
|
||||
frameFormat: ModbusFrameFormatEnum.MODBUS_TCP,
|
||||
status: CommonStatusEnum.ENABLE
|
||||
})
|
||||
const formRules = {
|
||||
ip: [{ required: true, message: '请输入 IP 地址', trigger: 'blur' }],
|
||||
port: [{ required: true, message: '请输入端口', trigger: 'blur' }],
|
||||
slaveId: [{ required: true, message: '请输入从站地址', trigger: 'blur' }],
|
||||
timeout: [{ required: true, message: '请输入连接超时时间', trigger: 'blur' }],
|
||||
retryInterval: [{ required: true, message: '请输入重试间隔', trigger: 'blur' }]
|
||||
}
|
||||
const formRules = computed(() => {
|
||||
const rules: Record<string, any[]> = {
|
||||
slaveId: [{ required: true, message: '请输入从站地址', trigger: 'blur' }]
|
||||
}
|
||||
if (isMaster.value) {
|
||||
rules.ip = [{ required: true, message: '请输入 IP 地址', trigger: 'blur' }]
|
||||
rules.port = [{ required: true, message: '请输入端口', trigger: 'blur' }]
|
||||
rules.timeout = [{ required: true, message: '请输入连接超时时间', trigger: 'blur' }]
|
||||
rules.retryInterval = [{ required: true, message: '请输入重试间隔', trigger: 'blur' }]
|
||||
}
|
||||
if (isSlave.value) {
|
||||
rules.mode = [{ required: true, message: '请选择工作模式', trigger: 'change' }]
|
||||
rules.frameFormat = [{ required: true, message: '请选择帧格式', trigger: 'change' }]
|
||||
}
|
||||
return rules
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
|
||||
/** 打开弹窗 */
|
||||
@@ -124,7 +173,9 @@ const resetForm = () => {
|
||||
port: 502,
|
||||
slaveId: 1,
|
||||
timeout: 3000,
|
||||
retryInterval: 1000,
|
||||
retryInterval: 10000,
|
||||
mode: ModbusModeEnum.POLLING,
|
||||
frameFormat: ModbusFrameFormatEnum.MODBUS_TCP,
|
||||
status: CommonStatusEnum.ENABLE
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
|
||||
@@ -45,7 +45,11 @@
|
||||
<el-tab-pane
|
||||
label="Modbus 配置"
|
||||
name="modbus"
|
||||
v-if="product.codecType === 'ModbusTcp'"
|
||||
v-if="
|
||||
[ProtocolTypeEnum.MODBUS_TCP_MASTER, ProtocolTypeEnum.MODBUS_TCP_SLAVE].includes(
|
||||
product.protocolType as ProtocolTypeEnum
|
||||
)
|
||||
"
|
||||
>
|
||||
<DeviceModbusConfig
|
||||
v-if="activeTab === 'modbus'"
|
||||
@@ -60,7 +64,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { useTagsViewStore } from '@/store/modules/tagsView'
|
||||
import { DeviceApi, DeviceVO } from '@/api/iot/device/device'
|
||||
import { DeviceTypeEnum, ProductApi, ProductVO } from '@/api/iot/product/product'
|
||||
import { DeviceTypeEnum, ProductApi, ProductVO, ProtocolTypeEnum } from '@/api/iot/product/product'
|
||||
import { ThingModelApi, ThingModelData } from '@/api/iot/thingmodel'
|
||||
import DeviceDetailsHeader from './DeviceDetailsHeader.vue'
|
||||
import DeviceDetailsInfo from './DeviceDetailsInfo.vue'
|
||||
|
||||
@@ -173,7 +173,7 @@
|
||||
<div class="mr-2.5 flex items-center">
|
||||
<el-image :src="defaultIconUrl" class="w-[18px] h-[18px]" />
|
||||
</div>
|
||||
<div class="text-[16px] font-600 flex-1">{{ item.deviceName }}</div>
|
||||
<div class="text-[16px] font-600 flex-1 overflow-hidden text-ellipsis whitespace-nowrap">{{ item.deviceName }}</div>
|
||||
<!-- 添加设备状态标签 -->
|
||||
<div class="inline-flex items-center">
|
||||
<div
|
||||
|
||||
@@ -600,7 +600,19 @@ export const JSON_PARAMS_EXAMPLE_VALUES = {
|
||||
DEFAULT: { display: '""', value: '' }
|
||||
} as const
|
||||
|
||||
// ========== Modbus 相关常量 ==========
|
||||
// ========== Modbus 通用常量 ==========
|
||||
|
||||
/** Modbus 模式枚举 */
|
||||
export const ModbusModeEnum = {
|
||||
POLLING: 1, // 云端轮询
|
||||
ACTIVE_REPORT: 2 // 主动上报
|
||||
} as const
|
||||
|
||||
/** Modbus 帧格式枚举 */
|
||||
export const ModbusFrameFormatEnum = {
|
||||
MODBUS_TCP: 1, // Modbus TCP
|
||||
MODBUS_RTU: 2 // Modbus RTU
|
||||
} as const
|
||||
|
||||
/** Modbus 功能码枚举 */
|
||||
export const ModbusFunctionCodeEnum = {
|
||||
@@ -614,8 +626,8 @@ export const ModbusFunctionCodeEnum = {
|
||||
export const ModbusFunctionCodeOptions = [
|
||||
{ value: 1, label: '01 - 读线圈 (Coils)', description: '可读写布尔值' },
|
||||
{ value: 2, label: '02 - 读离散输入 (Discrete Inputs)', description: '只读布尔值' },
|
||||
{ value: 3, label: '03 - 读保持寄存器 (Holding Registers)', description: '可读写16位数据' },
|
||||
{ value: 4, label: '04 - 读输入寄存器 (Input Registers)', description: '只读16位数据' }
|
||||
{ value: 3, label: '03 - 读保持寄存器 (Holding Registers)', description: '可读写 16 位数据' },
|
||||
{ value: 4, label: '04 - 读输入寄存器 (Input Registers)', description: '只读 16 位数据' }
|
||||
]
|
||||
|
||||
/** Modbus 原始数据类型枚举 */
|
||||
@@ -662,7 +674,7 @@ export const getByteOrderOptions = (rawDataType: string) => {
|
||||
return ModbusByteOrder32Options
|
||||
}
|
||||
if (rawDataType === 'DOUBLE') {
|
||||
// 64位暂时复用32位字节序
|
||||
// 64 位暂时复用 32 位字节序
|
||||
return ModbusByteOrder32Options
|
||||
}
|
||||
return ModbusByteOrder16Options
|
||||
|
||||
Reference in New Issue
Block a user