admin-vue3/src/views/iot/thingmodel/ThingModelProperty.vue

383 lines
13 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<!-- 产品的物模型表单property -->
<template>
<el-form-item
:rules="[{ required: true, message: '请选择数据类型', trigger: 'change' }]"
label="数据类型"
prop="property.dataType"
>
<el-select v-model="property.dataType" placeholder="请选择数据类型" @change="handleChange">
<!-- ARRAY 和 STRUCT 类型数据相互嵌套时,最多支持递归嵌套 2 层(父和子) -->
<el-option
v-for="option in getDataTypeOptions2"
:key="option.value"
:label="`${option.value}(${option.label})`"
:value="option.value"
/>
</el-select>
</el-form-item>
<!-- 数值型配置 -->
<ThingModelNumberDataSpecs
v-if="
[
IoTDataSpecsDataTypeEnum.INT,
IoTDataSpecsDataTypeEnum.DOUBLE,
IoTDataSpecsDataTypeEnum.FLOAT
].includes(property.dataType || '')
"
v-model="property.dataSpecs"
/>
<!-- 枚举型配置 -->
<ThingModelEnumDataSpecs
v-if="property.dataType === IoTDataSpecsDataTypeEnum.ENUM"
v-model="property.dataSpecsList"
/>
<!-- 布尔型配置 -->
<el-form-item v-if="property.dataType === IoTDataSpecsDataTypeEnum.BOOL" label="布尔值">
<template v-for="(item, index) in property.dataSpecsList" :key="item.value">
<div class="flex items-center justify-start w-1/1 mb-5px">
<span>{{ item.value }}</span>
<span class="mx-2">-</span>
<el-form-item
:prop="`property.dataSpecsList[${index}].name`"
:rules="[
{ required: true, message: '枚举描述不能为空' },
{ validator: validateBoolName, trigger: 'blur' }
]"
class="flex-1 mb-0"
>
<el-input
v-model="item.name"
:placeholder="`如:${item.value === 0 ? '关' : '开'}`"
class="w-255px!"
/>
</el-form-item>
</div>
</template>
</el-form-item>
<!-- 文本型配置 -->
<el-form-item
v-if="property.dataType === IoTDataSpecsDataTypeEnum.TEXT"
label="数据长度"
prop="property.dataSpecs.length"
>
<el-input v-model="property.dataSpecs.length" class="w-255px!" placeholder="请输入文本字节长度">
<template #append>字节</template>
</el-input>
</el-form-item>
<!-- 时间型配置 -->
<el-form-item
v-if="property.dataType === IoTDataSpecsDataTypeEnum.DATE"
label="时间格式"
prop="date"
>
<el-input class="w-255px!" disabled placeholder="String 类型的 UTC 时间戳(毫秒)" />
</el-form-item>
<!-- 数组型配置-->
<ThingModelArrayDataSpecs
v-if="property.dataType === IoTDataSpecsDataTypeEnum.ARRAY"
v-model="property.dataSpecs"
/>
<!-- Struct 型配置-->
<ThingModelStructDataSpecs
v-if="property.dataType === IoTDataSpecsDataTypeEnum.STRUCT"
v-model="property.dataSpecsList"
/>
<el-form-item v-if="!isStructDataSpecs && !isParams" label="读写类型" prop="property.accessMode">
<el-radio-group v-model="property.accessMode">
<el-radio
v-for="accessMode in Object.values(IoTThingModelAccessModeEnum)"
:key="accessMode.value"
:label="accessMode.value"
>
{{ accessMode.label }}
</el-radio>
</el-radio-group>
</el-form-item>
<!-- Modbus 配置区域 -->
<div v-if="isModbusProduct && !isStructDataSpecs && !isParams" class="modbus-config-section">
<el-divider content-position="left">Modbus 配置</el-divider>
<!-- 数据采集模式 -->
<el-form-item label="数据采集模式" prop="property.modbusConfig.collectionMode">
<el-radio-group v-model="property.modbusConfig.collectionMode" @change="handleCollectionModeChange">
<el-radio
v-for="mode in Object.values(IoTModbusCollectionModeEnum)"
:key="mode.value"
:label="mode.value"
>
{{ mode.label }}
</el-radio>
</el-radio-group>
</el-form-item>
<!-- 采集频率(仅在定时轮询模式下显示) -->
<el-form-item
v-if="property.modbusConfig.collectionMode === IoTModbusCollectionModeEnum.POLLING.value"
label="采集频率"
prop="property.modbusConfig.pollingInterval"
>
<el-input-number
v-model="property.modbusConfig.pollingInterval"
:min="10"
:max="86400"
placeholder="请输入采集频率10-86400秒"
class="w-full"
/>
</el-form-item>
<!-- 功能码 -->
<el-form-item label="功能码" prop="property.modbusConfig.functionCode">
<el-input-number
v-model="property.modbusConfig.functionCode"
:min="1"
:max="255"
placeholder="请输入功能码1-255"
@change="handleFunctionCodeChange"
class="w-full"
/>
</el-form-item>
<!-- 寄存器地址 -->
<el-form-item label="寄存器地址" prop="property.modbusConfig.registerAddress">
<el-input-number
v-model="property.modbusConfig.registerAddress"
:min="0"
:max="65535"
placeholder="请输入寄存器地址0-65535"
@change="handleRegisterAddressChange"
class="w-full"
/>
</el-form-item>
<!-- 寄存器数量 / 线圈数量 -->
<el-form-item label="寄存器数量" prop="property.modbusConfig.registerCount">
<el-input-number
v-model="property.modbusConfig.registerCount"
:min="1"
:max="255"
placeholder="请输入寄存器数量1-255"
@change="handleRegisterCountChange"
class="w-full"
/>
</el-form-item>
<!-- 字节序 -->
<el-form-item label="字节序" prop="property.modbusConfig.byteOrder">
<el-radio-group v-model="property.modbusConfig.byteOrder">
<el-radio
v-for="order in Object.values(IoTModbusByteOrderEnum)"
:key="order.value"
:label="order.value"
>
{{ order.label }}
</el-radio>
</el-radio-group>
</el-form-item>
<!-- 自动生成的命令(只读显示) -->
<el-form-item label="完整数据帧" v-if="property.modbusConfig?.fullFrame">
<el-input
v-model="property.modbusConfig.fullFrame"
readonly
placeholder="自动生成"
/>
</el-form-item>
<el-form-item label="CRC校验码" v-if="property.modbusConfig?.crcHex">
<el-input
v-model="property.modbusConfig.crcHex"
readonly
placeholder="自动生成"
/>
</el-form-item>
</div>
</template>
<script lang="ts" setup>
import { useVModel } from '@vueuse/core'
import {
ThingModelArrayDataSpecs,
ThingModelEnumDataSpecs,
ThingModelNumberDataSpecs,
ThingModelStructDataSpecs
} from './dataSpecs'
import { ThingModelApi, ThingModelProperty, validateBoolName } from '@/api/iot/thingmodel'
import { isEmpty } from '@/utils/is'
import {
getDataTypeOptions,
IoTDataSpecsDataTypeEnum,
IoTThingModelAccessModeEnum,
IoTModbusCollectionModeEnum,
IoTModbusByteOrderEnum,
IOT_PROVIDE_KEY
} from '@/views/iot/utils/constants'
import { ProductVO } from '@/api/iot/product/product'
import { inject } from 'vue'
import { useMessage } from '@/hooks/web/useMessage'
/** IoT 物模型属性 */
defineOptions({ name: 'ThingModelProperty' })
const props = defineProps<{ modelValue: any; isStructDataSpecs?: boolean; isParams?: boolean }>()
const emits = defineEmits(['update:modelValue'])
const property = useVModel(props, 'modelValue', emits) as Ref<ThingModelProperty>
const product = inject<Ref<ProductVO>>(IOT_PROVIDE_KEY.PRODUCT) // 注入产品信息
const message = useMessage()
// 计算属性:是否为 Modbus 产品
const isModbusProduct = computed(() => {
if (!product?.value) return false
return product.value.protocolType === 'MODBUS_RTU' || product.value.protocolType === 'MODBUS_TCP'
})
const getDataTypeOptions2 = computed(() => {
if (!props.isStructDataSpecs) {
return getDataTypeOptions()
}
const excludedTypes = [IoTDataSpecsDataTypeEnum.STRUCT, IoTDataSpecsDataTypeEnum.ARRAY]
return getDataTypeOptions().filter((item: any) => !excludedTypes.includes(item.value))
}) // 获得数据类型列表
/** 属性值的数据类型切换时初始化相关数据 */
const handleChange = (dataType: any) => {
property.value.dataSpecs = {}
property.value.dataSpecsList = []
// 不是列表型数据才设置 dataSpecs.dataType
![
IoTDataSpecsDataTypeEnum.ENUM,
IoTDataSpecsDataTypeEnum.BOOL,
IoTDataSpecsDataTypeEnum.STRUCT
].includes(dataType) && (property.value.dataSpecs.dataType = dataType)
switch (dataType) {
case IoTDataSpecsDataTypeEnum.ENUM:
property.value.dataSpecsList.push({
dataType: IoTDataSpecsDataTypeEnum.ENUM,
name: '', // 枚举项的名称
value: undefined // 枚举值
})
break
case IoTDataSpecsDataTypeEnum.BOOL:
for (let i = 0; i < 2; i++) {
property.value.dataSpecsList.push({
dataType: IoTDataSpecsDataTypeEnum.BOOL,
name: '', // 布尔值的名称
value: i // 布尔值
})
}
break
}
}
/** 默认选中读写 */
watch(
() => property.value.accessMode,
(val: string) => {
if (props.isStructDataSpecs || props.isParams) {
return
}
isEmpty(val) && (property.value.accessMode = IoTThingModelAccessModeEnum.READ_WRITE.value)
},
{ immediate: true }
)
/** 初始化 Modbus 配置 */
watch(
() => isModbusProduct.value,
(isModbus) => {
if (isModbus && !property.value.modbusConfig) {
property.value.modbusConfig = {
collectionMode: IoTModbusCollectionModeEnum.POLLING.value, // 默认为定时轮询模式
functionCode: undefined, // 功能码由用户输入,不设置默认值
registerAddress: undefined,
registerCount: 1, // 默认读取 1 个寄存器
pollingInterval: 300, // 默认采集频率 300 秒5分钟
fullFrame: '',
crcHex: ''
}
} else if (isModbus && property.value.modbusConfig && !property.value.modbusConfig.collectionMode) {
// 向下兼容:如果没有 collectionMode默认设置为定时轮询
property.value.modbusConfig.collectionMode = IoTModbusCollectionModeEnum.POLLING.value
}
// 向下兼容:如果没有 registerCount默认设置为 1
if (isModbus && property.value.modbusConfig && !property.value.modbusConfig.registerCount) {
property.value.modbusConfig.registerCount = 1
}
// 向下兼容:如果没有 pollingInterval默认设置为 300
if (isModbus && property.value.modbusConfig && !property.value.modbusConfig.pollingInterval) {
property.value.modbusConfig.pollingInterval = 300
}
// 向下兼容:如果没有 byteOrder默认设置为大端模式
if (isModbus && property.value.modbusConfig && !property.value.modbusConfig.byteOrder) {
property.value.modbusConfig.byteOrder = IoTModbusByteOrderEnum.BIG_ENDIAN.value
}
},
{ immediate: true }
)
/** 寄存器地址变化时自动计算命令 */
const handleRegisterAddressChange = async (registerAddress: number | undefined) => {
if (!registerAddress || !product?.value?.id) {
return
}
try {
const { data } = await ThingModelApi.calculateModbusCommand({
productId: product.value.id,
registerAddress: registerAddress,
functionCode: property.value.modbusConfig?.functionCode || 3,
registerCount: property.value.modbusConfig?.registerCount || 1,
byteOrder: property.value.modbusConfig?.byteOrder || 2 // 默认小端模式
})
// 更新表单数据
if (!property.value.modbusConfig) {
property.value.modbusConfig = {}
}
property.value.modbusConfig.fullFrame = data.fullFrame
property.value.modbusConfig.crcHex = data.crcHex
message.success('Modbus 命令已自动生成')
} catch (error: any) {
console.error('生成 Modbus 命令失败', error)
message.error('生成命令失败:' + (error.message || '未知错误'))
}
}
/** 功能码变化时重新计算命令 */
const handleFunctionCodeChange = async (functionCode: number | undefined) => {
// 如果寄存器地址已设置,则重新生成命令
if (property.value.modbusConfig?.registerAddress) {
await handleRegisterAddressChange(property.value.modbusConfig.registerAddress)
}
}
/** 寄存器数量变化时重新计算命令 */
const handleRegisterCountChange = async (registerCount: number | undefined) => {
// 如果寄存器地址已设置,则重新生成命令
if (property.value.modbusConfig?.registerAddress) {
await handleRegisterAddressChange(property.value.modbusConfig.registerAddress)
}
}
/** 采集模式变化时的处理 */
const handleCollectionModeChange = (mode: number) => {
// 如果切换到定时轮询模式,且没有设置采集频率,则设置默认值
if (mode === IoTModbusCollectionModeEnum.POLLING.value) {
if (property.value.modbusConfig && !property.value.modbusConfig.pollingInterval) {
property.value.modbusConfig.pollingInterval = 300
}
}
}
</script>
<style lang="scss" scoped>
:deep(.el-form-item) {
.el-form-item {
margin-bottom: 0;
}
}
.modbus-config-section {
margin-top: 20px;
}
.modbus-config-section .el-divider {
margin-top: 0;
}
</style>