fix(iot): 修复 6 处 bug(P1×5 + P3×1)

- product/product/data.ts: 网关子设备不显示联网方式
  show 条件由 != GATEWAY 改为 != GATEWAY_SUB(原写法漏判子设备)
- home/message-trend-card.vue: onMounted 兜底首次拉取
  ShortcutDateRangePicker 早期 emit 触发的请求落在 useEcharts
  isActiveRef=false 阶段会被静默丢弃;并加 isFirstMount guard 跳过
  子组件首次 emit,消除首次进入的双请求
- ota/firmware/data.ts: FileUpload accept 改回 ['bin','zip','pdf']
  并同步 helpText,对齐 vue3 源约定
- device/device/index.vue: 批量删除补 confirm 二次确认弹窗,
  与 system 模块批删风格一致,避免一键误删
- device/device/detail/modules/modbus-point-form.vue: 字节序仅在
  「为空 / 不属于新 rawDataType 合法选项」才重置,避免编辑回填时
  setValues 触发 handleValuesChange 把已保存字节序覆盖

antd + ele 两端同步。
This commit is contained in:
YunaiV
2026-05-23 22:04:08 +08:00
parent 152964395d
commit ef57c96b2f
10 changed files with 62 additions and 14 deletions

View File

@@ -246,10 +246,20 @@ const [Form, formApi] = useVbenForm({
if (option && option.registerCount > 0) {
await formApi.setFieldValue('registerCount', option.registerCount);
}
// 重置字节序为第一个选项
// 字节序:仅在当前值为空或不属于新 rawDataType 时才重置为第一个选项
// 编辑回填时 setValues 会触发本回调,无条件重置会覆盖已保存字节序
const byteOrderOptions = getByteOrderOptions(rawDataType);
if (byteOrderOptions.length > 0) {
await formApi.setFieldValue('byteOrder', byteOrderOptions[0]!.value);
const currentByteOrder = values.byteOrder;
const isCurrentValid =
!!currentByteOrder &&
byteOrderOptions.some((opt) => opt.value === currentByteOrder);
if (!isCurrentValid) {
await formApi.setFieldValue(
'byteOrder',
byteOrderOptions[0]!.value,
);
}
}
}
}

View File

@@ -9,7 +9,7 @@ import type { IotProductApi } from '#/api/iot/product/product';
import { nextTick, onMounted, ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { Page, useVbenModal } from '@vben/common-ui';
import { confirm, Page, useVbenModal } from '@vben/common-ui';
import { DICT_TYPE } from '@vben/constants';
import { getDictOptions } from '@vben/hooks';
import { IconifyIcon } from '@vben/icons';
@@ -169,6 +169,7 @@ async function handleDeleteBatch() {
message.warning('请选择要删除的设备');
return;
}
await confirm($t('ui.actionMessage.deleteBatchConfirm'));
const hideLoading = message.loading({
content: $t('ui.actionMessage.deletingBatch'),
duration: 0,

View File

@@ -3,7 +3,7 @@ import type { Dayjs } from 'dayjs';
import type { IotStatisticsApi } from '#/api/iot/statistics';
import { computed, nextTick, reactive, ref } from 'vue';
import { computed, nextTick, onMounted, reactive, ref } from 'vue';
import { DICT_TYPE } from '@vben/constants';
import { getDictOptions } from '@vben/hooks';
@@ -26,6 +26,7 @@ const loading = ref(false); // 加载状态
const messageData = ref<IotStatisticsApi.DeviceMessageSummaryByDateRespVO[]>(
[],
); // 消息趋势数据
const isFirstMount = ref(true); // 首次挂载标记,用于跳过子组件 mount 时的默认 emit
/** 时间范围(仅日期,不包含时分秒) */
const dateRange = ref<[string, string]>([
@@ -74,6 +75,11 @@ function handleDateRangeChange(times?: [Dayjs, Dayjs]) {
];
// 将选择的日期转换为带时分秒的格式(开始日期 00:00:00结束日期 23:59:59
queryParams.times = formatDateRangeWithTime(dateRange.value);
if (isFirstMount.value) {
// 子组件 ShortcutDateRangePicker mount 时会 emit 一次默认日期范围,跳过 fetch
// 首次请求统一由父组件 onMounted 发起,避免双请求
return;
}
handleQuery();
}
@@ -123,6 +129,13 @@ async function renderChartWhenReady() {
initChart();
}
// 父组件挂载后统一发起首次请求;
// 原因:子组件 ShortcutDateRangePicker 早期 emit 触发的请求落在 useEcharts isActiveRef = false 阶段,会被 renderEcharts 静默丢弃;
// 通过 handleDateRangeChange 在 isFirstMount=true 时跳过 fetch由这里统一发起一次避免双请求
onMounted(() => {
fetchMessageData();
isFirstMount.value = false;
});
</script>
<template>

View File

@@ -101,9 +101,9 @@ export function useFormSchema(): VbenFormSchema[] {
component: 'FileUpload',
componentProps: {
maxNumber: 1,
accept: ['bin', 'hex', 'zip'],
accept: ['bin', 'zip', 'pdf'],
maxSize: 50,
helpText: '支持上传 .bin、.hex、.zip 格式的固件文件,最大 50MB',
helpText: '支持上传 .bin、.zip、.pdf 格式的固件文件,最大 50MB',
},
rules: 'required',
dependencies: {

View File

@@ -131,7 +131,7 @@ export function useBasicFormSchema(
// 网关子设备走网关联网,不需要联网方式
dependencies: {
triggerFields: ['deviceType'],
show: (values) => values.deviceType !== DeviceTypeEnum.GATEWAY,
show: (values) => values.deviceType !== DeviceTypeEnum.GATEWAY_SUB,
},
rules: 'required',
},

View File

@@ -241,10 +241,20 @@ const [Form, formApi] = useVbenForm({
if (option && option.registerCount > 0) {
await formApi.setFieldValue('registerCount', option.registerCount);
}
// 重置字节序为第一个选项
// 字节序:仅在当前值为空或不属于新 rawDataType 时才重置为第一个选项
// 编辑回填时 setValues 会触发本回调,无条件重置会覆盖已保存字节序
const byteOrderOptions = getByteOrderOptions(rawDataType);
if (byteOrderOptions.length > 0) {
await formApi.setFieldValue('byteOrder', byteOrderOptions[0]!.value);
const currentByteOrder = values.byteOrder;
const isCurrentValid =
!!currentByteOrder &&
byteOrderOptions.some((opt) => opt.value === currentByteOrder);
if (!isCurrentValid) {
await formApi.setFieldValue(
'byteOrder',
byteOrderOptions[0]!.value,
);
}
}
}
}

View File

@@ -9,7 +9,7 @@ import type { IotProductApi } from '#/api/iot/product/product';
import { nextTick, onMounted, ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { Page, useVbenModal } from '@vben/common-ui';
import { confirm, Page, useVbenModal } from '@vben/common-ui';
import { DICT_TYPE } from '@vben/constants';
import { getDictOptions } from '@vben/hooks';
import { IconifyIcon } from '@vben/icons';
@@ -169,6 +169,7 @@ async function handleDeleteBatch() {
ElMessage.warning('请选择要删除的设备');
return;
}
await confirm($t('ui.actionMessage.deleteBatchConfirm'));
const loadingInstance = ElLoading.service({
text: $t('ui.actionMessage.deletingBatch'),
});

View File

@@ -3,7 +3,7 @@ import type { Dayjs } from 'dayjs';
import type { IotStatisticsApi } from '#/api/iot/statistics';
import { computed, nextTick, reactive, ref } from 'vue';
import { computed, nextTick, onMounted, reactive, ref } from 'vue';
import { DICT_TYPE } from '@vben/constants';
import { getDictOptions } from '@vben/hooks';
@@ -26,6 +26,7 @@ const loading = ref(false); // 加载状态
const messageData = ref<IotStatisticsApi.DeviceMessageSummaryByDateRespVO[]>(
[],
); // 消息趋势数据
const isFirstMount = ref(true); // 首次挂载标记,用于跳过子组件 mount 时的默认 emit
/** 时间范围(仅日期,不包含时分秒) */
const dateRange = ref<[string, string]>([
@@ -74,6 +75,11 @@ function handleDateRangeChange(times?: [Dayjs, Dayjs]) {
];
// 将选择的日期转换为带时分秒的格式(开始日期 00:00:00结束日期 23:59:59
queryParams.times = formatDateRangeWithTime(dateRange.value);
if (isFirstMount.value) {
// 子组件 ShortcutDateRangePicker mount 时会 emit 一次默认日期范围,跳过 fetch
// 首次请求统一由父组件 onMounted 发起,避免双请求
return;
}
handleQuery();
}
@@ -123,6 +129,13 @@ async function renderChartWhenReady() {
initChart();
}
// 父组件挂载后统一发起首次请求;
// 原因:子组件 ShortcutDateRangePicker 早期 emit 触发的请求落在 useEcharts isActiveRef = false 阶段,会被 renderEcharts 静默丢弃;
// 通过 handleDateRangeChange 在 isFirstMount=true 时跳过 fetch由这里统一发起一次避免双请求
onMounted(() => {
fetchMessageData();
isFirstMount.value = false;
});
</script>
<template>

View File

@@ -101,9 +101,9 @@ export function useFormSchema(): VbenFormSchema[] {
component: 'FileUpload',
componentProps: {
maxNumber: 1,
accept: ['bin', 'hex', 'zip'],
accept: ['bin', 'zip', 'pdf'],
maxSize: 50,
helpText: '支持上传 .bin、.hex、.zip 格式的固件文件,最大 50MB',
helpText: '支持上传 .bin、.zip、.pdf 格式的固件文件,最大 50MB',
},
rules: 'required',
dependencies: {

View File

@@ -128,7 +128,7 @@ export function useBasicFormSchema(
// 网关子设备走网关联网,不需要联网方式
dependencies: {
triggerFields: ['deviceType'],
show: (values) => values.deviceType !== DeviceTypeEnum.GATEWAY,
show: (values) => values.deviceType !== DeviceTypeEnum.GATEWAY_SUB,
},
rules: 'required',
},