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 两端同步。pull/348/head
parent
152964395d
commit
ef57c96b2f
|
|
@ -246,10 +246,20 @@ const [Form, formApi] = useVbenForm({
|
||||||
if (option && option.registerCount > 0) {
|
if (option && option.registerCount > 0) {
|
||||||
await formApi.setFieldValue('registerCount', option.registerCount);
|
await formApi.setFieldValue('registerCount', option.registerCount);
|
||||||
}
|
}
|
||||||
// 重置字节序为第一个选项
|
// 字节序:仅在当前值为空或不属于新 rawDataType 时才重置为第一个选项;
|
||||||
|
// 编辑回填时 setValues 会触发本回调,无条件重置会覆盖已保存字节序
|
||||||
const byteOrderOptions = getByteOrderOptions(rawDataType);
|
const byteOrderOptions = getByteOrderOptions(rawDataType);
|
||||||
if (byteOrderOptions.length > 0) {
|
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,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import type { IotProductApi } from '#/api/iot/product/product';
|
||||||
import { nextTick, onMounted, ref } from 'vue';
|
import { nextTick, onMounted, ref } from 'vue';
|
||||||
import { useRoute, useRouter } from 'vue-router';
|
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 { DICT_TYPE } from '@vben/constants';
|
||||||
import { getDictOptions } from '@vben/hooks';
|
import { getDictOptions } from '@vben/hooks';
|
||||||
import { IconifyIcon } from '@vben/icons';
|
import { IconifyIcon } from '@vben/icons';
|
||||||
|
|
@ -169,6 +169,7 @@ async function handleDeleteBatch() {
|
||||||
message.warning('请选择要删除的设备');
|
message.warning('请选择要删除的设备');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
await confirm($t('ui.actionMessage.deleteBatchConfirm'));
|
||||||
const hideLoading = message.loading({
|
const hideLoading = message.loading({
|
||||||
content: $t('ui.actionMessage.deletingBatch'),
|
content: $t('ui.actionMessage.deletingBatch'),
|
||||||
duration: 0,
|
duration: 0,
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import type { Dayjs } from 'dayjs';
|
||||||
|
|
||||||
import type { IotStatisticsApi } from '#/api/iot/statistics';
|
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 { DICT_TYPE } from '@vben/constants';
|
||||||
import { getDictOptions } from '@vben/hooks';
|
import { getDictOptions } from '@vben/hooks';
|
||||||
|
|
@ -26,6 +26,7 @@ const loading = ref(false); // 加载状态
|
||||||
const messageData = ref<IotStatisticsApi.DeviceMessageSummaryByDateRespVO[]>(
|
const messageData = ref<IotStatisticsApi.DeviceMessageSummaryByDateRespVO[]>(
|
||||||
[],
|
[],
|
||||||
); // 消息趋势数据
|
); // 消息趋势数据
|
||||||
|
const isFirstMount = ref(true); // 首次挂载标记,用于跳过子组件 mount 时的默认 emit
|
||||||
|
|
||||||
/** 时间范围(仅日期,不包含时分秒) */
|
/** 时间范围(仅日期,不包含时分秒) */
|
||||||
const dateRange = ref<[string, string]>([
|
const dateRange = ref<[string, string]>([
|
||||||
|
|
@ -74,6 +75,11 @@ function handleDateRangeChange(times?: [Dayjs, Dayjs]) {
|
||||||
];
|
];
|
||||||
// 将选择的日期转换为带时分秒的格式(开始日期 00:00:00,结束日期 23:59:59)
|
// 将选择的日期转换为带时分秒的格式(开始日期 00:00:00,结束日期 23:59:59)
|
||||||
queryParams.times = formatDateRangeWithTime(dateRange.value);
|
queryParams.times = formatDateRangeWithTime(dateRange.value);
|
||||||
|
if (isFirstMount.value) {
|
||||||
|
// 子组件 ShortcutDateRangePicker mount 时会 emit 一次默认日期范围,跳过 fetch;
|
||||||
|
// 首次请求统一由父组件 onMounted 发起,避免双请求
|
||||||
|
return;
|
||||||
|
}
|
||||||
handleQuery();
|
handleQuery();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -123,6 +129,13 @@ async function renderChartWhenReady() {
|
||||||
initChart();
|
initChart();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 父组件挂载后统一发起首次请求;
|
||||||
|
// 原因:子组件 ShortcutDateRangePicker 早期 emit 触发的请求落在 useEcharts isActiveRef = false 阶段,会被 renderEcharts 静默丢弃;
|
||||||
|
// 通过 handleDateRangeChange 在 isFirstMount=true 时跳过 fetch,由这里统一发起一次,避免双请求
|
||||||
|
onMounted(() => {
|
||||||
|
fetchMessageData();
|
||||||
|
isFirstMount.value = false;
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
||||||
|
|
@ -101,9 +101,9 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||||
component: 'FileUpload',
|
component: 'FileUpload',
|
||||||
componentProps: {
|
componentProps: {
|
||||||
maxNumber: 1,
|
maxNumber: 1,
|
||||||
accept: ['bin', 'hex', 'zip'],
|
accept: ['bin', 'zip', 'pdf'],
|
||||||
maxSize: 50,
|
maxSize: 50,
|
||||||
helpText: '支持上传 .bin、.hex、.zip 格式的固件文件,最大 50MB',
|
helpText: '支持上传 .bin、.zip、.pdf 格式的固件文件,最大 50MB',
|
||||||
},
|
},
|
||||||
rules: 'required',
|
rules: 'required',
|
||||||
dependencies: {
|
dependencies: {
|
||||||
|
|
|
||||||
|
|
@ -131,7 +131,7 @@ export function useBasicFormSchema(
|
||||||
// 网关子设备走网关联网,不需要联网方式
|
// 网关子设备走网关联网,不需要联网方式
|
||||||
dependencies: {
|
dependencies: {
|
||||||
triggerFields: ['deviceType'],
|
triggerFields: ['deviceType'],
|
||||||
show: (values) => values.deviceType !== DeviceTypeEnum.GATEWAY,
|
show: (values) => values.deviceType !== DeviceTypeEnum.GATEWAY_SUB,
|
||||||
},
|
},
|
||||||
rules: 'required',
|
rules: 'required',
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -241,10 +241,20 @@ const [Form, formApi] = useVbenForm({
|
||||||
if (option && option.registerCount > 0) {
|
if (option && option.registerCount > 0) {
|
||||||
await formApi.setFieldValue('registerCount', option.registerCount);
|
await formApi.setFieldValue('registerCount', option.registerCount);
|
||||||
}
|
}
|
||||||
// 重置字节序为第一个选项
|
// 字节序:仅在当前值为空或不属于新 rawDataType 时才重置为第一个选项;
|
||||||
|
// 编辑回填时 setValues 会触发本回调,无条件重置会覆盖已保存字节序
|
||||||
const byteOrderOptions = getByteOrderOptions(rawDataType);
|
const byteOrderOptions = getByteOrderOptions(rawDataType);
|
||||||
if (byteOrderOptions.length > 0) {
|
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,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import type { IotProductApi } from '#/api/iot/product/product';
|
||||||
import { nextTick, onMounted, ref } from 'vue';
|
import { nextTick, onMounted, ref } from 'vue';
|
||||||
import { useRoute, useRouter } from 'vue-router';
|
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 { DICT_TYPE } from '@vben/constants';
|
||||||
import { getDictOptions } from '@vben/hooks';
|
import { getDictOptions } from '@vben/hooks';
|
||||||
import { IconifyIcon } from '@vben/icons';
|
import { IconifyIcon } from '@vben/icons';
|
||||||
|
|
@ -169,6 +169,7 @@ async function handleDeleteBatch() {
|
||||||
ElMessage.warning('请选择要删除的设备');
|
ElMessage.warning('请选择要删除的设备');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
await confirm($t('ui.actionMessage.deleteBatchConfirm'));
|
||||||
const loadingInstance = ElLoading.service({
|
const loadingInstance = ElLoading.service({
|
||||||
text: $t('ui.actionMessage.deletingBatch'),
|
text: $t('ui.actionMessage.deletingBatch'),
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import type { Dayjs } from 'dayjs';
|
||||||
|
|
||||||
import type { IotStatisticsApi } from '#/api/iot/statistics';
|
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 { DICT_TYPE } from '@vben/constants';
|
||||||
import { getDictOptions } from '@vben/hooks';
|
import { getDictOptions } from '@vben/hooks';
|
||||||
|
|
@ -26,6 +26,7 @@ const loading = ref(false); // 加载状态
|
||||||
const messageData = ref<IotStatisticsApi.DeviceMessageSummaryByDateRespVO[]>(
|
const messageData = ref<IotStatisticsApi.DeviceMessageSummaryByDateRespVO[]>(
|
||||||
[],
|
[],
|
||||||
); // 消息趋势数据
|
); // 消息趋势数据
|
||||||
|
const isFirstMount = ref(true); // 首次挂载标记,用于跳过子组件 mount 时的默认 emit
|
||||||
|
|
||||||
/** 时间范围(仅日期,不包含时分秒) */
|
/** 时间范围(仅日期,不包含时分秒) */
|
||||||
const dateRange = ref<[string, string]>([
|
const dateRange = ref<[string, string]>([
|
||||||
|
|
@ -74,6 +75,11 @@ function handleDateRangeChange(times?: [Dayjs, Dayjs]) {
|
||||||
];
|
];
|
||||||
// 将选择的日期转换为带时分秒的格式(开始日期 00:00:00,结束日期 23:59:59)
|
// 将选择的日期转换为带时分秒的格式(开始日期 00:00:00,结束日期 23:59:59)
|
||||||
queryParams.times = formatDateRangeWithTime(dateRange.value);
|
queryParams.times = formatDateRangeWithTime(dateRange.value);
|
||||||
|
if (isFirstMount.value) {
|
||||||
|
// 子组件 ShortcutDateRangePicker mount 时会 emit 一次默认日期范围,跳过 fetch;
|
||||||
|
// 首次请求统一由父组件 onMounted 发起,避免双请求
|
||||||
|
return;
|
||||||
|
}
|
||||||
handleQuery();
|
handleQuery();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -123,6 +129,13 @@ async function renderChartWhenReady() {
|
||||||
initChart();
|
initChart();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 父组件挂载后统一发起首次请求;
|
||||||
|
// 原因:子组件 ShortcutDateRangePicker 早期 emit 触发的请求落在 useEcharts isActiveRef = false 阶段,会被 renderEcharts 静默丢弃;
|
||||||
|
// 通过 handleDateRangeChange 在 isFirstMount=true 时跳过 fetch,由这里统一发起一次,避免双请求
|
||||||
|
onMounted(() => {
|
||||||
|
fetchMessageData();
|
||||||
|
isFirstMount.value = false;
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
||||||
|
|
@ -101,9 +101,9 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||||
component: 'FileUpload',
|
component: 'FileUpload',
|
||||||
componentProps: {
|
componentProps: {
|
||||||
maxNumber: 1,
|
maxNumber: 1,
|
||||||
accept: ['bin', 'hex', 'zip'],
|
accept: ['bin', 'zip', 'pdf'],
|
||||||
maxSize: 50,
|
maxSize: 50,
|
||||||
helpText: '支持上传 .bin、.hex、.zip 格式的固件文件,最大 50MB',
|
helpText: '支持上传 .bin、.zip、.pdf 格式的固件文件,最大 50MB',
|
||||||
},
|
},
|
||||||
rules: 'required',
|
rules: 'required',
|
||||||
dependencies: {
|
dependencies: {
|
||||||
|
|
|
||||||
|
|
@ -128,7 +128,7 @@ export function useBasicFormSchema(
|
||||||
// 网关子设备走网关联网,不需要联网方式
|
// 网关子设备走网关联网,不需要联网方式
|
||||||
dependencies: {
|
dependencies: {
|
||||||
triggerFields: ['deviceType'],
|
triggerFields: ['deviceType'],
|
||||||
show: (values) => values.deviceType !== DeviceTypeEnum.GATEWAY,
|
show: (values) => values.deviceType !== DeviceTypeEnum.GATEWAY_SUB,
|
||||||
},
|
},
|
||||||
rules: 'required',
|
rules: 'required',
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue