diff --git a/apps/web-antd/src/views/iot/alert/record/data.ts b/apps/web-antd/src/views/iot/alert/record/data.ts index 3123c0188..34154ee9e 100644 --- a/apps/web-antd/src/views/iot/alert/record/data.ts +++ b/apps/web-antd/src/views/iot/alert/record/data.ts @@ -62,13 +62,27 @@ export function useGridFormSchema(): VbenFormSchema[] { label: '设备', component: 'ApiSelect', componentProps: { - api: getSimpleDeviceList, + api: (params?: { productId?: number }) => + getSimpleDeviceList(undefined, params?.productId), labelField: 'deviceName', valueField: 'id', placeholder: '请选择设备', allowClear: true, showSearch: true, }, + dependencies: { + triggerFields: ['productId'], + componentProps: (values) => { + return { + params: { productId: values.productId }, + }; + }, + trigger: (values, formApi) => { + if (values.deviceId !== undefined) { + void formApi.setFieldValue('deviceId', undefined); + } + }, + }, }, { fieldName: 'processStatus', diff --git a/apps/web-antd/src/views/iot/device/device/detail/modules/message.vue b/apps/web-antd/src/views/iot/device/device/detail/modules/message.vue index 965d5eb24..16efec5cf 100644 --- a/apps/web-antd/src/views/iot/device/device/detail/modules/message.vue +++ b/apps/web-antd/src/views/iot/device/device/detail/modules/message.vue @@ -31,6 +31,7 @@ const queryParams = reactive({ const autoRefresh = ref(false); // 自动刷新开关 let autoRefreshTimer: any = null; // 自动刷新定时器 +let refreshTimer: ReturnType | undefined; // 延迟刷新定时器 /** 消息方法选项 */ const methodOptions = computed(() => { @@ -150,6 +151,10 @@ onBeforeUnmount(() => { clearInterval(autoRefreshTimer); autoRefreshTimer = null; } + if (refreshTimer) { + clearTimeout(refreshTimer); + refreshTimer = undefined; + } }); /** 初始化 */ @@ -161,9 +166,14 @@ onMounted(() => { /** 刷新消息列表 */ function refresh(delay = 0) { + if (refreshTimer) { + clearTimeout(refreshTimer); + refreshTimer = undefined; + } if (delay > 0) { - setTimeout(() => { + refreshTimer = setTimeout(() => { gridApi.query(); + refreshTimer = undefined; }, delay); } else { gridApi.query(); diff --git a/apps/web-antd/src/views/iot/device/device/detail/modules/simulator.vue b/apps/web-antd/src/views/iot/device/device/detail/modules/simulator.vue index 3af6c6216..eefd303f8 100644 --- a/apps/web-antd/src/views/iot/device/device/detail/modules/simulator.vue +++ b/apps/web-antd/src/views/iot/device/device/detail/modules/simulator.vue @@ -6,11 +6,12 @@ import type { IotDeviceApi } from '#/api/iot/device/device'; import type { IotProductApi } from '#/api/iot/product/product'; import type { ThingModelApi } from '#/api/iot/thingmodel'; -import { computed, ref } from 'vue'; +import { computed, ref, watch } from 'vue'; import { ContentWrap } from '@vben/common-ui'; import { DeviceStateEnum, + IoTDataSpecsDataTypeEnum, IotDeviceMessageMethodEnum, IoTThingModelTypeEnum, } from '@vben/constants'; @@ -21,8 +22,10 @@ import { Card, Col, Input, + InputNumber, message, Row, + Select, Table, Tabs, Textarea, @@ -51,7 +54,7 @@ const debugCollapsed = ref(false); // 指令调试区域折叠状态 const messageCollapsed = ref(false); // 设备消息区域折叠状态 // 表单数据:存储用户输入的模拟值 -const formData = ref>({}); +const formData = ref>({}); // 根据类型过滤物模型数据 const getFilteredThingModelList = (type: number) => { @@ -184,21 +187,75 @@ const serviceColumns = [ // 获取表单值 function getFormValue(identifier: string) { - return formData.value[identifier] || ''; + return formData.value[identifier] ?? ''; } // 设置表单值 -function setFormValue(identifier: string, value: string) { +function setFormValue(identifier: string, value: any) { formData.value[identifier] = value; } +/** 获取属性数据类型 */ +function getPropertyDataType(row: ThingModelApi.ThingModel) { + return row.property?.dataType; +} + +/** 判断属性是否为数值类型 */ +function isNumberProperty(row: ThingModelApi.ThingModel) { + return [ + IoTDataSpecsDataTypeEnum.DOUBLE, + IoTDataSpecsDataTypeEnum.FLOAT, + IoTDataSpecsDataTypeEnum.INT, + ].includes(getPropertyDataType(row) as any); +} + +/** 判断属性是否使用下拉选项 */ +function isSelectProperty(row: ThingModelApi.ThingModel) { + return [ + IoTDataSpecsDataTypeEnum.BOOL, + IoTDataSpecsDataTypeEnum.ENUM, + ].includes(getPropertyDataType(row) as any); +} + +/** 获取属性选项 */ +function getPropertyOptions(row: ThingModelApi.ThingModel) { + const list = row.property?.dataSpecsList || []; + if (list.length > 0) { + return list.map((item: any) => ({ + label: item.name || item.label || String(item.value), + value: String(item.value), + })); + } + if (getPropertyDataType(row) === IoTDataSpecsDataTypeEnum.BOOL) { + return [ + { label: '真 (true)', value: 'true' }, + { label: '假 (false)', value: 'false' }, + ]; + } + return []; +} + +/** 按物模型数据类型转换属性值 */ +function normalizePropertyValue(row: ThingModelApi.ThingModel, value: any) { + if (value === undefined || value === null || value === '') { + return undefined; + } + if (isNumberProperty(row)) { + return Number(value); + } + return value; +} + // 属性上报 async function handlePropertyPost() { try { const params: Record = {}; propertyList.value.forEach((item) => { - const value = formData.value[item.identifier!]; - if (value) { + const value = normalizePropertyValue( + item, + formData.value[item.identifier!], + ); + if (value !== undefined) { params[item.identifier!] = value; } }); @@ -281,8 +338,11 @@ async function handlePropertySet() { try { const params: Record = {}; propertyList.value.forEach((item) => { - const value = formData.value[item.identifier!]; - if (value) { + const value = normalizePropertyValue( + item, + formData.value[item.identifier!], + ); + if (value !== undefined) { params[item.identifier!] = value; } }); @@ -320,6 +380,14 @@ async function handleServiceInvoke(row: ThingModelApi.ThingModel) { message.error('服务参数格式错误,请输入有效的JSON格式'); return; } + if ( + typeof inputParams !== 'object' || + inputParams === null || + Array.isArray(inputParams) + ) { + message.error('服务参数必须是 JSON 对象'); + return; + } } // 与后端 IotDeviceServiceInvokeReqDTO 对齐 :{ identifier, inputParams } @@ -340,6 +408,11 @@ async function handleServiceInvoke(row: ThingModelApi.ThingModel) { console.error(error); } } + +/** 切换调试方法时清空输入,避免不同方法之间串台提交 */ +watch([activeTab, upstreamTab, downstreamTab], () => { + formData.value = {}; +});