mirror of
https://github.com/yudaocode/yudao-ui-admin-vue3.git
synced 2026-03-30 00:44:31 +00:00
feat:【iot】modbus-tcp 协议接入 40%:优化部分表单的实现,基于 dreamy-stirring-kazoo.md
This commit is contained in:
@@ -39,6 +39,7 @@
|
||||
<span class="text-lg font-medium">点位配置</span>
|
||||
<el-button type="primary" @click="handleAddPoint">
|
||||
<Icon icon="ep:plus" class="mr-1" />
|
||||
<!-- TODO @AI:权限,需要和后端接口对齐 -->
|
||||
新增点位
|
||||
</el-button>
|
||||
</div>
|
||||
@@ -106,16 +107,16 @@
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" align="center" prop="status" min-width="80">
|
||||
<template #default="scope">
|
||||
<!-- TODO @AI dict-tag -->
|
||||
<el-tag :type="scope.row.status === 0 ? 'success' : 'danger'" size="small">
|
||||
{{ scope.row.status === 0 ? '启用' : '禁用' }}
|
||||
</el-tag>
|
||||
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
|
||||
</template>
|
||||
</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)">删除</el-button>
|
||||
<el-button link type="danger" @click="handleDeletePoint(scope.row.id, scope.row.name)">
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -149,6 +150,7 @@ import { ThingModelData } from '@/api/iot/thingmodel'
|
||||
import { DeviceModbusConfigApi, DeviceModbusConfigVO } from '@/api/iot/device/modbus/config'
|
||||
import { DeviceModbusPointApi, DeviceModbusPointVO } from '@/api/iot/device/modbus/point'
|
||||
import { ModbusFunctionCodeOptions } from '@/views/iot/utils/constants'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import DeviceModbusConfigForm from './DeviceModbusConfigForm.vue'
|
||||
import DeviceModbusPointForm from './DeviceModbusPointForm.vue'
|
||||
|
||||
@@ -163,16 +165,7 @@ const props = defineProps<{
|
||||
const message = useMessage()
|
||||
|
||||
// ======================= 连接配置 =======================
|
||||
// TODO @AI:默认应该都是空的
|
||||
const modbusConfig = ref<DeviceModbusConfigVO>({
|
||||
deviceId: props.device.id,
|
||||
ip: '',
|
||||
port: 502,
|
||||
slaveId: 1,
|
||||
timeout: 3000,
|
||||
retryInterval: 1000,
|
||||
status: 0 // TODO @AI:使用 CommonStatus;
|
||||
})
|
||||
const modbusConfig = ref<DeviceModbusConfigVO>({} as DeviceModbusConfigVO)
|
||||
|
||||
/** 获取连接配置 */
|
||||
const getModbusConfig = async () => {
|
||||
@@ -245,13 +238,16 @@ const handleEditPoint = (row: DeviceModbusPointVO) => {
|
||||
}
|
||||
|
||||
/** 删除点位 */
|
||||
const handleDeletePoint = async (id: number) => {
|
||||
// TODO @AI:最好点位的名字带上。参考别的模块;
|
||||
// TODO @AI:参考别的注释。
|
||||
await message.confirm('确定要删除该点位配置吗?')
|
||||
await DeviceModbusPointApi.deleteModbusPoint(id)
|
||||
message.success('删除成功')
|
||||
await getPointPage()
|
||||
const handleDeletePoint = async (id: number, name: string) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm('确定要删除点位【' + name + '】吗?')
|
||||
// 发起删除
|
||||
await DeviceModbusPointApi.deleteModbusPoint(id)
|
||||
message.success('删除成功')
|
||||
// 刷新列表
|
||||
await getPointPage()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
|
||||
@@ -11,9 +11,10 @@
|
||||
<el-form-item label="IP 地址" prop="ip">
|
||||
<el-input v-model="formData.ip" placeholder="请输入 Modbus 服务器 IP 地址" />
|
||||
</el-form-item>
|
||||
<el-form-item label="请输入 Modbus 端口" prop="port">
|
||||
<el-form-item label="端口" prop="port">
|
||||
<el-input-number
|
||||
v-model="formData.port"
|
||||
placeholder="请输入端口"
|
||||
:min="1"
|
||||
:max="65535"
|
||||
controls-position="right"
|
||||
@@ -26,42 +27,36 @@
|
||||
:min="1"
|
||||
:max="247"
|
||||
controls-position="right"
|
||||
placeholder="请输入从站地址,范围 1-247"
|
||||
class="!w-full"
|
||||
/>
|
||||
<!-- TODO @AI:placehoder 需要写下 -->
|
||||
</el-form-item>
|
||||
<el-form-item label="连接超时" prop="timeout">
|
||||
<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"
|
||||
/>
|
||||
<!-- TODO @AI:placehoder 需要写下 -->
|
||||
<div class="text-xs text-gray-400 mt-1">单位:毫秒</div>
|
||||
<!-- TODO @AI:上面的毫秒,可以去掉 -->
|
||||
</el-form-item>
|
||||
<el-form-item label="重试间隔" prop="retryInterval">
|
||||
<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"
|
||||
/>
|
||||
<!-- TODO @AI:placehoder 需要写下 -->
|
||||
<div class="text-xs text-gray-400 mt-1">单位:毫秒</div>
|
||||
<!-- TODO @AI:上面的毫秒,可以去掉 -->
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<!-- TODO @AI:看看别的禁用,应该是怎么样的 -->
|
||||
<!-- TODO @AI:参考别的模块,不要使用这个 -->
|
||||
<el-switch
|
||||
v-model="formData.status"
|
||||
:active-value="0"
|
||||
:inactive-value="1"
|
||||
active-text="启用"
|
||||
inactive-text="禁用"
|
||||
:active-value="CommonStatusEnum.ENABLE"
|
||||
:inactive-value="CommonStatusEnum.DISABLE"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
@@ -74,6 +69,7 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { DeviceModbusConfigApi, DeviceModbusConfigVO } from '@/api/iot/device/modbus/config'
|
||||
import { CommonStatusEnum } from '@/utils/constants'
|
||||
|
||||
defineOptions({ name: 'DeviceModbusConfigForm' })
|
||||
|
||||
@@ -92,8 +88,6 @@ const dialogTitle = ref('编辑 Modbus 连接配置')
|
||||
const formLoading = ref(false)
|
||||
const formRef = ref()
|
||||
|
||||
/** 表单数据 */
|
||||
// TODO @AI;
|
||||
const formData = ref<DeviceModbusConfigVO>({
|
||||
deviceId: props.deviceId,
|
||||
ip: '',
|
||||
@@ -101,7 +95,7 @@ const formData = ref<DeviceModbusConfigVO>({
|
||||
slaveId: 1,
|
||||
timeout: 3000,
|
||||
retryInterval: 10000,
|
||||
status: 0 // TODO @AI:commonstatusenum;
|
||||
status: CommonStatusEnum.ENABLE
|
||||
})
|
||||
|
||||
/** 表单校验规则 */
|
||||
@@ -132,22 +126,23 @@ const resetForm = () => {
|
||||
slaveId: 1,
|
||||
timeout: 3000,
|
||||
retryInterval: 1000,
|
||||
status: 0
|
||||
status: CommonStatusEnum.ENABLE
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
|
||||
/** 提交表单 */
|
||||
const submitForm = async () => {
|
||||
// TODO @AI:注释,需要补充下;参考别的模块
|
||||
// 校验表单
|
||||
if (!formRef.value) return
|
||||
const valid = await formRef.value.validate()
|
||||
if (!valid) return
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
await formRef.value?.validate()
|
||||
formLoading.value = true
|
||||
|
||||
formData.value.deviceId = props.deviceId
|
||||
await DeviceModbusConfigApi.saveModbusConfig(formData.value)
|
||||
message.success('保存成功')
|
||||
|
||||
dialogVisible.value = false
|
||||
emit('success')
|
||||
} finally {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<!-- Modbus 点位表单弹窗 -->
|
||||
<template>
|
||||
<!-- TODO @AI:placeholder 都提供下 -->
|
||||
<Dialog :title="dialogTitle" v-model="dialogVisible" width="600px">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
@@ -17,41 +16,33 @@
|
||||
class="!w-full"
|
||||
@change="handleThingModelChange"
|
||||
>
|
||||
<!-- TODO @AI:增加 option 里的告警; -->
|
||||
<el-option
|
||||
v-for="item in propertyList"
|
||||
:key="item.id"
|
||||
:key="item.id!"
|
||||
:label="`${item.name} (${item.identifier})`"
|
||||
:value="item.id"
|
||||
:value="item.id!"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="功能码" prop="functionCode">
|
||||
<!-- TODO @AI:select -->
|
||||
<el-radio-group v-model="formData.functionCode">
|
||||
<el-radio
|
||||
<el-select v-model="formData.functionCode" placeholder="请选择功能码" class="!w-full">
|
||||
<el-option
|
||||
v-for="item in ModbusFunctionCodeOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
class="!mr-4"
|
||||
>
|
||||
{{ item.label }}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<!-- TODO @AI:不要转换,直接输入 -->
|
||||
<el-form-item label="寄存器地址" prop="registerAddress">
|
||||
<el-input
|
||||
v-model="registerAddressInput"
|
||||
placeholder="请输入寄存器地址(支持十进制或十六进制如 0x0000)"
|
||||
@blur="handleRegisterAddressBlur"
|
||||
>
|
||||
<template #append>
|
||||
<span class="text-gray-500">
|
||||
= {{ formatRegisterAddress(formData.registerAddress) }}
|
||||
</span>
|
||||
</template>
|
||||
</el-input>
|
||||
<el-input-number
|
||||
v-model="formData.registerAddress"
|
||||
:min="0"
|
||||
:max="65535"
|
||||
controls-position="right"
|
||||
placeholder="请输入寄存器地址"
|
||||
class="!w-full"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="寄存器数量" prop="registerCount">
|
||||
<el-input-number
|
||||
@@ -59,6 +50,7 @@
|
||||
:min="1"
|
||||
:max="125"
|
||||
controls-position="right"
|
||||
placeholder="请输入寄存器数量"
|
||||
class="!w-full"
|
||||
/>
|
||||
</el-form-item>
|
||||
@@ -93,30 +85,25 @@
|
||||
:precision="6"
|
||||
:step="0.1"
|
||||
controls-position="right"
|
||||
placeholder="请输入缩放因子"
|
||||
class="!w-full"
|
||||
/>
|
||||
<!-- TODO @AI:tip,使用默认的 el-input-number 里的 -->
|
||||
<div class="text-xs text-gray-400 mt-1">
|
||||
读取时:实际值 = 原始值 × 缩放因子;写入时:原始值 = 实际值 ÷ 缩放因子
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="轮询间隔" prop="pollInterval">
|
||||
<el-form-item label="轮询间隔(ms)" prop="pollInterval">
|
||||
<el-input-number
|
||||
v-model="formData.pollInterval"
|
||||
:min="100"
|
||||
:step="1000"
|
||||
controls-position="right"
|
||||
placeholder="请输入轮询间隔"
|
||||
class="!w-full"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<!-- TODO @AI:参考下别的模块,开关 -->
|
||||
<el-switch
|
||||
v-model="formData.status"
|
||||
:active-value="0"
|
||||
:inactive-value="1"
|
||||
active-text="启用"
|
||||
inactive-text="禁用"
|
||||
:active-value="CommonStatusEnum.ENABLE"
|
||||
:inactive-value="CommonStatusEnum.DISABLE"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
@@ -135,6 +122,7 @@ import {
|
||||
ModbusRawDataTypeOptions,
|
||||
getByteOrderOptions
|
||||
} from '@/views/iot/utils/constants'
|
||||
import { CommonStatusEnum } from '@/utils/constants'
|
||||
|
||||
defineOptions({ name: 'DeviceModbusPointForm' })
|
||||
|
||||
@@ -149,19 +137,13 @@ const emit = defineEmits<{
|
||||
|
||||
const message = useMessage()
|
||||
|
||||
const dialogVisible = ref(false)
|
||||
const dialogTitle = ref('')
|
||||
const formLoading = ref(false)
|
||||
const formType = ref<'create' | 'update'>('create')
|
||||
const formRef = ref()
|
||||
|
||||
// TODO @AI:注释的风格,你看看是不是有些地方要尾注释(和别的模块保持一样的风格)
|
||||
|
||||
/** 表单数据 */
|
||||
// TODO @AI:里面的枚举类型,改成直接用枚举
|
||||
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,
|
||||
thingModelId: 0, // TODO @AI:不要有默认值;
|
||||
identifier: '',
|
||||
name: '',
|
||||
functionCode: 3,
|
||||
@@ -171,13 +153,8 @@ const formData = ref<DeviceModbusPointVO>({
|
||||
rawDataType: 'INT16',
|
||||
scale: 1,
|
||||
pollInterval: 5000,
|
||||
status: 0
|
||||
})
|
||||
|
||||
/** 寄存器地址输入框(支持十六进制) */
|
||||
const registerAddressInput = ref('0')
|
||||
|
||||
/** 表单校验规则 */
|
||||
status: CommonStatusEnum.ENABLE
|
||||
}) // TODO @AI:registerAddress、registerAddress、registerCount、rawDataType 不要有默认值;
|
||||
const formRules = {
|
||||
thingModelId: [{ required: true, message: '请选择物模型属性', trigger: 'change' }],
|
||||
functionCode: [{ required: true, message: '请选择功能码', trigger: 'change' }],
|
||||
@@ -186,10 +163,12 @@ const formRules = {
|
||||
rawDataType: [{ required: true, message: '请选择数据类型', trigger: 'change' }],
|
||||
pollInterval: [{ required: true, message: '请输入轮询间隔', trigger: 'blur' }]
|
||||
}
|
||||
const formRef = ref() // 表单 Ref
|
||||
|
||||
/** 筛选属性类型的物模型 */
|
||||
const propertyList = computed(() => {
|
||||
return props.thingModelList.filter((item) => item.type === 1) // type=1 为属性
|
||||
// TODO @AI:1 使用枚举代替魔法数字
|
||||
return props.thingModelList.filter((item) => item.type === 1)
|
||||
})
|
||||
|
||||
/** 当前字节序选项(根据数据类型动态变化) */
|
||||
@@ -197,53 +176,12 @@ const currentByteOrderOptions = computed(() => {
|
||||
return getByteOrderOptions(formData.value.rawDataType)
|
||||
})
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: 'create' | 'update', id?: number) => {
|
||||
dialogVisible.value = true
|
||||
formType.value = type
|
||||
// TODO @AI:参考别的模块,写法;
|
||||
dialogTitle.value = type === 'create' ? '新增 Modbus 点位' : '编辑 Modbus 点位'
|
||||
resetForm()
|
||||
|
||||
if (type === 'update' && id) {
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = await DeviceModbusPointApi.getModbusPoint(id)
|
||||
formData.value = data
|
||||
registerAddressInput.value = formatRegisterAddress(data.registerAddress)
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
deviceId: props.deviceId,
|
||||
thingModelId: 0,
|
||||
identifier: '',
|
||||
name: '',
|
||||
functionCode: 3,
|
||||
registerAddress: 0,
|
||||
registerCount: 1,
|
||||
byteOrder: 'AB',
|
||||
rawDataType: 'INT16',
|
||||
scale: 1,
|
||||
pollInterval: 5000,
|
||||
status: 0
|
||||
}
|
||||
registerAddressInput.value = '0'
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
|
||||
/** 物模型属性变化 */
|
||||
const handleThingModelChange = (thingModelId: number) => {
|
||||
const thingModel = props.thingModelList.find((item) => item.id === thingModelId)
|
||||
// TODO @AI:这里有 linter 告警,可以看看。
|
||||
if (thingModel) {
|
||||
formData.value.identifier = thingModel.identifier
|
||||
formData.value.name = thingModel.name
|
||||
formData.value.identifier = thingModel.identifier!
|
||||
formData.value.name = thingModel.name!
|
||||
}
|
||||
}
|
||||
|
||||
@@ -262,39 +200,33 @@ const handleRawDataTypeChange = (rawDataType: string) => {
|
||||
}
|
||||
}
|
||||
|
||||
/** 寄存器地址输入框失焦 */
|
||||
const handleRegisterAddressBlur = () => {
|
||||
const input = registerAddressInput.value.trim()
|
||||
let address = 0
|
||||
|
||||
if (input.toLowerCase().startsWith('0x')) {
|
||||
// 十六进制
|
||||
address = parseInt(input, 16)
|
||||
} else {
|
||||
// 十进制
|
||||
address = parseInt(input, 10)
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: 'create' | 'update', id?: number) => {
|
||||
dialogVisible.value = true
|
||||
formType.value = type
|
||||
// TODO @AI:dialogTitle.value = t('action.' + type)
|
||||
dialogTitle.value = type === 'create' ? '新增 Modbus 点位' : '编辑 Modbus 点位'
|
||||
resetForm()
|
||||
// 修改时,设置数据
|
||||
if (type === 'update' && id) {
|
||||
formLoading.value = true
|
||||
try {
|
||||
formData.value = await DeviceModbusPointApi.getModbusPoint(id)
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
if (isNaN(address) || address < 0) {
|
||||
address = 0
|
||||
}
|
||||
|
||||
formData.value.registerAddress = address
|
||||
registerAddressInput.value = formatRegisterAddress(address)
|
||||
}
|
||||
|
||||
/** 格式化寄存器地址为十六进制 */
|
||||
const formatRegisterAddress = (address: number) => {
|
||||
return '0x' + address.toString(16).toUpperCase().padStart(4, '0')
|
||||
}
|
||||
|
||||
/** 提交表单 */
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
if (!formRef.value) return
|
||||
const valid = await formRef.value.validate()
|
||||
if (!valid) return
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
await formRef.value?.validate()
|
||||
formLoading.value = true
|
||||
|
||||
// TODO @AI:这里的注释风格,可以看看。
|
||||
if (formType.value === 'create') {
|
||||
await DeviceModbusPointApi.createModbusPoint(formData.value)
|
||||
message.success('创建成功')
|
||||
@@ -302,14 +234,34 @@ const submitForm = async () => {
|
||||
await DeviceModbusPointApi.updateModbusPoint(formData.value)
|
||||
message.success('更新成功')
|
||||
}
|
||||
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
deviceId: props.deviceId,
|
||||
thingModelId: 0,
|
||||
identifier: '',
|
||||
name: '',
|
||||
// TODO @AI:3、0、1、AB/INT16 使用枚举;
|
||||
functionCode: 3,
|
||||
registerAddress: 0,
|
||||
registerCount: 1,
|
||||
byteOrder: 'AB',
|
||||
rawDataType: 'INT16',
|
||||
scale: 1,
|
||||
pollInterval: 5000,
|
||||
status: CommonStatusEnum.ENABLE
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
|
||||
/** 暴露方法 */
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user