fix: 修复 IoT 迁移页面多处交互与契约问题
- 修复告警记录产品/设备筛选联动 - 清理设备详情延迟刷新 timer,避免卸载后触发查询 - 优化 OTA 固件编辑态只读展示与任务列表分页重置 - 修复场景联动值输入回显和布尔值类型 - 修复设备模拟器输入串台、数值类型提交和服务参数校验 - 更新 IoT bug 归档与迁移说明pull/348/head
parent
eb0f2a5ff2
commit
48547bc53b
|
|
@ -62,13 +62,27 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||||
label: '设备',
|
label: '设备',
|
||||||
component: 'ApiSelect',
|
component: 'ApiSelect',
|
||||||
componentProps: {
|
componentProps: {
|
||||||
api: getSimpleDeviceList,
|
api: (params?: { productId?: number }) =>
|
||||||
|
getSimpleDeviceList(undefined, params?.productId),
|
||||||
labelField: 'deviceName',
|
labelField: 'deviceName',
|
||||||
valueField: 'id',
|
valueField: 'id',
|
||||||
placeholder: '请选择设备',
|
placeholder: '请选择设备',
|
||||||
allowClear: true,
|
allowClear: true,
|
||||||
showSearch: 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',
|
fieldName: 'processStatus',
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ const queryParams = reactive({
|
||||||
|
|
||||||
const autoRefresh = ref(false); // 自动刷新开关
|
const autoRefresh = ref(false); // 自动刷新开关
|
||||||
let autoRefreshTimer: any = null; // 自动刷新定时器
|
let autoRefreshTimer: any = null; // 自动刷新定时器
|
||||||
|
let refreshTimer: ReturnType<typeof setTimeout> | undefined; // 延迟刷新定时器
|
||||||
|
|
||||||
/** 消息方法选项 */
|
/** 消息方法选项 */
|
||||||
const methodOptions = computed(() => {
|
const methodOptions = computed(() => {
|
||||||
|
|
@ -150,6 +151,10 @@ onBeforeUnmount(() => {
|
||||||
clearInterval(autoRefreshTimer);
|
clearInterval(autoRefreshTimer);
|
||||||
autoRefreshTimer = null;
|
autoRefreshTimer = null;
|
||||||
}
|
}
|
||||||
|
if (refreshTimer) {
|
||||||
|
clearTimeout(refreshTimer);
|
||||||
|
refreshTimer = undefined;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/** 初始化 */
|
/** 初始化 */
|
||||||
|
|
@ -161,9 +166,14 @@ onMounted(() => {
|
||||||
|
|
||||||
/** 刷新消息列表 */
|
/** 刷新消息列表 */
|
||||||
function refresh(delay = 0) {
|
function refresh(delay = 0) {
|
||||||
|
if (refreshTimer) {
|
||||||
|
clearTimeout(refreshTimer);
|
||||||
|
refreshTimer = undefined;
|
||||||
|
}
|
||||||
if (delay > 0) {
|
if (delay > 0) {
|
||||||
setTimeout(() => {
|
refreshTimer = setTimeout(() => {
|
||||||
gridApi.query();
|
gridApi.query();
|
||||||
|
refreshTimer = undefined;
|
||||||
}, delay);
|
}, delay);
|
||||||
} else {
|
} else {
|
||||||
gridApi.query();
|
gridApi.query();
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,12 @@ import type { IotDeviceApi } from '#/api/iot/device/device';
|
||||||
import type { IotProductApi } from '#/api/iot/product/product';
|
import type { IotProductApi } from '#/api/iot/product/product';
|
||||||
import type { ThingModelApi } from '#/api/iot/thingmodel';
|
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 { ContentWrap } from '@vben/common-ui';
|
||||||
import {
|
import {
|
||||||
DeviceStateEnum,
|
DeviceStateEnum,
|
||||||
|
IoTDataSpecsDataTypeEnum,
|
||||||
IotDeviceMessageMethodEnum,
|
IotDeviceMessageMethodEnum,
|
||||||
IoTThingModelTypeEnum,
|
IoTThingModelTypeEnum,
|
||||||
} from '@vben/constants';
|
} from '@vben/constants';
|
||||||
|
|
@ -21,8 +22,10 @@ import {
|
||||||
Card,
|
Card,
|
||||||
Col,
|
Col,
|
||||||
Input,
|
Input,
|
||||||
|
InputNumber,
|
||||||
message,
|
message,
|
||||||
Row,
|
Row,
|
||||||
|
Select,
|
||||||
Table,
|
Table,
|
||||||
Tabs,
|
Tabs,
|
||||||
Textarea,
|
Textarea,
|
||||||
|
|
@ -51,7 +54,7 @@ const debugCollapsed = ref(false); // 指令调试区域折叠状态
|
||||||
const messageCollapsed = ref(false); // 设备消息区域折叠状态
|
const messageCollapsed = ref(false); // 设备消息区域折叠状态
|
||||||
|
|
||||||
// 表单数据:存储用户输入的模拟值
|
// 表单数据:存储用户输入的模拟值
|
||||||
const formData = ref<Record<string, string>>({});
|
const formData = ref<Record<string, any>>({});
|
||||||
|
|
||||||
// 根据类型过滤物模型数据
|
// 根据类型过滤物模型数据
|
||||||
const getFilteredThingModelList = (type: number) => {
|
const getFilteredThingModelList = (type: number) => {
|
||||||
|
|
@ -184,21 +187,75 @@ const serviceColumns = [
|
||||||
|
|
||||||
// 获取表单值
|
// 获取表单值
|
||||||
function getFormValue(identifier: string) {
|
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;
|
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() {
|
async function handlePropertyPost() {
|
||||||
try {
|
try {
|
||||||
const params: Record<string, any> = {};
|
const params: Record<string, any> = {};
|
||||||
propertyList.value.forEach((item) => {
|
propertyList.value.forEach((item) => {
|
||||||
const value = formData.value[item.identifier!];
|
const value = normalizePropertyValue(
|
||||||
if (value) {
|
item,
|
||||||
|
formData.value[item.identifier!],
|
||||||
|
);
|
||||||
|
if (value !== undefined) {
|
||||||
params[item.identifier!] = value;
|
params[item.identifier!] = value;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -281,8 +338,11 @@ async function handlePropertySet() {
|
||||||
try {
|
try {
|
||||||
const params: Record<string, any> = {};
|
const params: Record<string, any> = {};
|
||||||
propertyList.value.forEach((item) => {
|
propertyList.value.forEach((item) => {
|
||||||
const value = formData.value[item.identifier!];
|
const value = normalizePropertyValue(
|
||||||
if (value) {
|
item,
|
||||||
|
formData.value[item.identifier!],
|
||||||
|
);
|
||||||
|
if (value !== undefined) {
|
||||||
params[item.identifier!] = value;
|
params[item.identifier!] = value;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -320,6 +380,14 @@ async function handleServiceInvoke(row: ThingModelApi.ThingModel) {
|
||||||
message.error('服务参数格式错误,请输入有效的JSON格式');
|
message.error('服务参数格式错误,请输入有效的JSON格式');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
typeof inputParams !== 'object' ||
|
||||||
|
inputParams === null ||
|
||||||
|
Array.isArray(inputParams)
|
||||||
|
) {
|
||||||
|
message.error('服务参数必须是 JSON 对象');
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 与后端 IotDeviceServiceInvokeReqDTO 对齐 :{ identifier, inputParams }
|
// 与后端 IotDeviceServiceInvokeReqDTO 对齐 :{ identifier, inputParams }
|
||||||
|
|
@ -340,6 +408,11 @@ async function handleServiceInvoke(row: ThingModelApi.ThingModel) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 切换调试方法时清空输入,避免不同方法之间串台提交 */
|
||||||
|
watch([activeTab, upstreamTab, downstreamTab], () => {
|
||||||
|
formData.value = {};
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -392,7 +465,29 @@ async function handleServiceInvoke(row: ThingModelApi.ThingModel) {
|
||||||
<DataDefinition :data="record" />
|
<DataDefinition :data="record" />
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="column.key === 'value'">
|
<template v-else-if="column.key === 'value'">
|
||||||
|
<InputNumber
|
||||||
|
v-if="isNumberProperty(record)"
|
||||||
|
:value="getFormValue(record.identifier)"
|
||||||
|
placeholder="输入值"
|
||||||
|
size="small"
|
||||||
|
class="w-full"
|
||||||
|
@update:value="
|
||||||
|
setFormValue(record.identifier, $event)
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
<Select
|
||||||
|
v-else-if="isSelectProperty(record)"
|
||||||
|
:value="getFormValue(record.identifier)"
|
||||||
|
:options="getPropertyOptions(record)"
|
||||||
|
placeholder="请选择值"
|
||||||
|
size="small"
|
||||||
|
class="w-full"
|
||||||
|
@update:value="
|
||||||
|
setFormValue(record.identifier, $event)
|
||||||
|
"
|
||||||
|
/>
|
||||||
<Input
|
<Input
|
||||||
|
v-else
|
||||||
:value="getFormValue(record.identifier)"
|
:value="getFormValue(record.identifier)"
|
||||||
placeholder="输入值"
|
placeholder="输入值"
|
||||||
size="small"
|
size="small"
|
||||||
|
|
@ -514,7 +609,29 @@ async function handleServiceInvoke(row: ThingModelApi.ThingModel) {
|
||||||
<DataDefinition :data="record" />
|
<DataDefinition :data="record" />
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="column.key === 'value'">
|
<template v-else-if="column.key === 'value'">
|
||||||
|
<InputNumber
|
||||||
|
v-if="isNumberProperty(record)"
|
||||||
|
:value="getFormValue(record.identifier)"
|
||||||
|
placeholder="输入值"
|
||||||
|
size="small"
|
||||||
|
class="w-full"
|
||||||
|
@update:value="
|
||||||
|
setFormValue(record.identifier, $event)
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
<Select
|
||||||
|
v-else-if="isSelectProperty(record)"
|
||||||
|
:value="getFormValue(record.identifier)"
|
||||||
|
:options="getPropertyOptions(record)"
|
||||||
|
placeholder="请选择值"
|
||||||
|
size="small"
|
||||||
|
class="w-full"
|
||||||
|
@update:value="
|
||||||
|
setFormValue(record.identifier, $event)
|
||||||
|
"
|
||||||
|
/>
|
||||||
<Input
|
<Input
|
||||||
|
v-else
|
||||||
:value="getFormValue(record.identifier)"
|
:value="getFormValue(record.identifier)"
|
||||||
placeholder="输入值"
|
placeholder="输入值"
|
||||||
size="small"
|
size="small"
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
import type { ThingModelApi } from '#/api/iot/thingmodel';
|
import type { ThingModelApi } from '#/api/iot/thingmodel';
|
||||||
|
|
||||||
import { computed, onMounted, reactive, watch } from 'vue';
|
import { computed, onBeforeUnmount, onMounted, reactive, watch } from 'vue';
|
||||||
|
|
||||||
import { Page } from '@vben/common-ui';
|
import { Page } from '@vben/common-ui';
|
||||||
import {
|
import {
|
||||||
|
|
@ -29,6 +29,7 @@ const queryParams = reactive({
|
||||||
identifier: '',
|
identifier: '',
|
||||||
times: undefined as [string, string] | undefined,
|
times: undefined as [string, string] | undefined,
|
||||||
});
|
});
|
||||||
|
let refreshTimer: ReturnType<typeof setTimeout> | undefined; // 延迟刷新定时器
|
||||||
|
|
||||||
/** 事件类型的物模型数据 */
|
/** 事件类型的物模型数据 */
|
||||||
const eventThingModels = computed(() => {
|
const eventThingModels = computed(() => {
|
||||||
|
|
@ -153,8 +154,15 @@ function parseParams(params: string) {
|
||||||
|
|
||||||
/** 刷新列表 */
|
/** 刷新列表 */
|
||||||
function refresh(delay = 0) {
|
function refresh(delay = 0) {
|
||||||
|
if (refreshTimer) {
|
||||||
|
clearTimeout(refreshTimer);
|
||||||
|
refreshTimer = undefined;
|
||||||
|
}
|
||||||
if (delay > 0) {
|
if (delay > 0) {
|
||||||
setTimeout(() => gridApi.query(), delay);
|
refreshTimer = setTimeout(() => {
|
||||||
|
gridApi.query();
|
||||||
|
refreshTimer = undefined;
|
||||||
|
}, delay);
|
||||||
} else {
|
} else {
|
||||||
gridApi.query();
|
gridApi.query();
|
||||||
}
|
}
|
||||||
|
|
@ -177,6 +185,14 @@ onMounted(() => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/** 组件卸载时清除延迟刷新定时器 */
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
if (refreshTimer) {
|
||||||
|
clearTimeout(refreshTimer);
|
||||||
|
refreshTimer = undefined;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
/** 暴露方法给父组件 */
|
/** 暴露方法给父组件 */
|
||||||
defineExpose({
|
defineExpose({
|
||||||
refresh,
|
refresh,
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
import type { ThingModelApi } from '#/api/iot/thingmodel';
|
import type { ThingModelApi } from '#/api/iot/thingmodel';
|
||||||
|
|
||||||
import { computed, onMounted, reactive, watch } from 'vue';
|
import { computed, onBeforeUnmount, onMounted, reactive, watch } from 'vue';
|
||||||
|
|
||||||
import { Page } from '@vben/common-ui';
|
import { Page } from '@vben/common-ui';
|
||||||
import {
|
import {
|
||||||
|
|
@ -29,6 +29,7 @@ const queryParams = reactive({
|
||||||
identifier: '',
|
identifier: '',
|
||||||
times: undefined as [string, string] | undefined,
|
times: undefined as [string, string] | undefined,
|
||||||
});
|
});
|
||||||
|
let refreshTimer: ReturnType<typeof setTimeout> | undefined; // 延迟刷新定时器
|
||||||
|
|
||||||
/** 服务类型的物模型数据 */
|
/** 服务类型的物模型数据 */
|
||||||
const serviceThingModels = computed(() => {
|
const serviceThingModels = computed(() => {
|
||||||
|
|
@ -167,8 +168,15 @@ function parseParams(params: string) {
|
||||||
|
|
||||||
/** 刷新列表 */
|
/** 刷新列表 */
|
||||||
function refresh(delay = 0) {
|
function refresh(delay = 0) {
|
||||||
|
if (refreshTimer) {
|
||||||
|
clearTimeout(refreshTimer);
|
||||||
|
refreshTimer = undefined;
|
||||||
|
}
|
||||||
if (delay > 0) {
|
if (delay > 0) {
|
||||||
setTimeout(() => gridApi.query(), delay);
|
refreshTimer = setTimeout(() => {
|
||||||
|
gridApi.query();
|
||||||
|
refreshTimer = undefined;
|
||||||
|
}, delay);
|
||||||
} else {
|
} else {
|
||||||
gridApi.query();
|
gridApi.query();
|
||||||
}
|
}
|
||||||
|
|
@ -191,6 +199,14 @@ onMounted(() => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/** 组件卸载时清除延迟刷新定时器 */
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
if (refreshTimer) {
|
||||||
|
clearTimeout(refreshTimer);
|
||||||
|
refreshTimer = undefined;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
/** 暴露方法给父组件 */
|
/** 暴露方法给父组件 */
|
||||||
defineExpose({
|
defineExpose({
|
||||||
refresh,
|
refresh,
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,11 @@ export function getProductName(productId?: number): string {
|
||||||
export function useDetailSchema(): DescriptionItemSchema[] {
|
export function useDetailSchema(): DescriptionItemSchema[] {
|
||||||
return [
|
return [
|
||||||
{ field: 'name', label: '固件名称' },
|
{ field: 'name', label: '固件名称' },
|
||||||
{ field: 'productName', label: '所属产品' },
|
{
|
||||||
|
field: 'productName',
|
||||||
|
label: '所属产品',
|
||||||
|
render: (val) => val || '-',
|
||||||
|
},
|
||||||
{ field: 'version', label: '固件版本' },
|
{ field: 'version', label: '固件版本' },
|
||||||
{
|
{
|
||||||
field: 'createTime',
|
field: 'createTime',
|
||||||
|
|
@ -67,10 +71,12 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||||
valueField: 'id',
|
valueField: 'id',
|
||||||
placeholder: '请选择产品',
|
placeholder: '请选择产品',
|
||||||
},
|
},
|
||||||
rules: 'required',
|
|
||||||
dependencies: {
|
dependencies: {
|
||||||
triggerFields: ['id'],
|
triggerFields: ['id'],
|
||||||
show: (values) => !values.id,
|
componentProps: (values) => ({
|
||||||
|
disabled: !!values.id,
|
||||||
|
}),
|
||||||
|
rules: (values) => (values.id ? null : 'required'),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -80,10 +86,12 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||||
componentProps: {
|
componentProps: {
|
||||||
placeholder: '请输入版本号',
|
placeholder: '请输入版本号',
|
||||||
},
|
},
|
||||||
rules: 'required',
|
|
||||||
dependencies: {
|
dependencies: {
|
||||||
triggerFields: ['id'],
|
triggerFields: ['id'],
|
||||||
show: (values) => !values.id,
|
componentProps: (values) => ({
|
||||||
|
disabled: !!values.id,
|
||||||
|
}),
|
||||||
|
rules: (values) => (values.id ? null : 'required'),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -105,10 +113,12 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||||
maxSize: 50,
|
maxSize: 50,
|
||||||
helpText: '支持上传 .bin、.zip、.pdf 格式的固件文件,最大 50MB',
|
helpText: '支持上传 .bin、.zip、.pdf 格式的固件文件,最大 50MB',
|
||||||
},
|
},
|
||||||
rules: 'required',
|
|
||||||
dependencies: {
|
dependencies: {
|
||||||
triggerFields: ['id'],
|
triggerFields: ['id'],
|
||||||
show: (values) => !values.id,
|
componentProps: (values) => ({
|
||||||
|
disabled: !!values.id,
|
||||||
|
}),
|
||||||
|
rules: (values) => (values.id ? null : 'required'),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ async function handleRefresh() {
|
||||||
|
|
||||||
/** 按任务名称搜索 */
|
/** 按任务名称搜索 */
|
||||||
async function handleSearch() {
|
async function handleSearch() {
|
||||||
await gridApi.query();
|
await gridApi.reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 新增任务 */
|
/** 新增任务 */
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ const statusTabs = computed(() => {
|
||||||
/** 切换标签 */
|
/** 切换标签 */
|
||||||
async function handleTabChange(tabKey: number | string) {
|
async function handleTabChange(tabKey: number | string) {
|
||||||
activeTab.value = String(tabKey);
|
activeTab.value = String(tabKey);
|
||||||
await gridApi.query();
|
await gridApi.reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 取消单条记录的升级 */
|
/** 取消单条记录的升级 */
|
||||||
|
|
@ -89,7 +89,7 @@ watch(
|
||||||
async (val) => {
|
async (val) => {
|
||||||
if (val) {
|
if (val) {
|
||||||
activeTab.value = '';
|
activeTab.value = '';
|
||||||
await gridApi.query();
|
await gridApi.reload();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -137,10 +137,46 @@ function handleNumberChange(value: any) {
|
||||||
localValue.value = value == null ? '' : String(value);
|
localValue.value = value == null ? '' : String(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 根据外部值同步内部输入态 */
|
||||||
|
function syncInternalValue(value = '') {
|
||||||
|
const normalized = value;
|
||||||
|
if (
|
||||||
|
props.operator ===
|
||||||
|
IotRuleSceneTriggerConditionParameterOperatorEnum.BETWEEN.value
|
||||||
|
) {
|
||||||
|
const [start = '', end = ''] = normalized.split(',');
|
||||||
|
rangeStart.value = start;
|
||||||
|
rangeEnd.value = end;
|
||||||
|
numberValue.value = undefined;
|
||||||
|
dateValue.value = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
rangeStart.value = '';
|
||||||
|
rangeEnd.value = '';
|
||||||
|
if (props.propertyType === IoTDataSpecsDataTypeEnum.DATE) {
|
||||||
|
dateValue.value = normalized;
|
||||||
|
numberValue.value = undefined;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isNumericType()) {
|
||||||
|
const parsed = Number(normalized);
|
||||||
|
numberValue.value =
|
||||||
|
normalized === '' || Number.isNaN(parsed) ? undefined : parsed;
|
||||||
|
dateValue.value = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
numberValue.value = undefined;
|
||||||
|
dateValue.value = '';
|
||||||
|
}
|
||||||
|
|
||||||
/** 监听操作符变化 */
|
/** 监听操作符变化 */
|
||||||
watch(
|
watch(
|
||||||
() => props.operator,
|
() => props.operator,
|
||||||
() => {
|
(_operator, oldOperator) => {
|
||||||
|
if (oldOperator === undefined) {
|
||||||
|
syncInternalValue(props.modelValue);
|
||||||
|
return;
|
||||||
|
}
|
||||||
localValue.value = '';
|
localValue.value = '';
|
||||||
rangeStart.value = '';
|
rangeStart.value = '';
|
||||||
rangeEnd.value = '';
|
rangeEnd.value = '';
|
||||||
|
|
@ -148,6 +184,12 @@ watch(
|
||||||
numberValue.value = undefined;
|
numberValue.value = undefined;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => [props.modelValue, props.propertyType] as const,
|
||||||
|
([value]) => syncInternalValue(value),
|
||||||
|
{ immediate: true },
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -159,8 +201,8 @@ watch(
|
||||||
placeholder="请选择布尔值"
|
placeholder="请选择布尔值"
|
||||||
class="w-full!"
|
class="w-full!"
|
||||||
>
|
>
|
||||||
<Select.Option :value="true">真 (true)</Select.Option>
|
<Select.Option value="true">真 (true)</Select.Option>
|
||||||
<Select.Option :value="false">假 (false)</Select.Option>
|
<Select.Option value="false">假 (false)</Select.Option>
|
||||||
</Select>
|
</Select>
|
||||||
|
|
||||||
<!-- 枚举值选择 -->
|
<!-- 枚举值选择 -->
|
||||||
|
|
|
||||||
|
|
@ -33,10 +33,7 @@ async function getList() {
|
||||||
function handleChange(value: SelectValue) {
|
function handleChange(value: SelectValue) {
|
||||||
const toolId = typeof value === 'number' ? value : undefined;
|
const toolId = typeof value === 'number' ? value : undefined;
|
||||||
emit('update:modelValue', toolId);
|
emit('update:modelValue', toolId);
|
||||||
emit(
|
emit('change', list.value.find((item) => item.id === toolId));
|
||||||
'change',
|
|
||||||
list.value.find((item) => item.id === toolId),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(getList);
|
onMounted(getList);
|
||||||
|
|
|
||||||
|
|
@ -62,13 +62,27 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||||
label: '设备',
|
label: '设备',
|
||||||
component: 'ApiSelect',
|
component: 'ApiSelect',
|
||||||
componentProps: {
|
componentProps: {
|
||||||
api: getSimpleDeviceList,
|
api: (params?: { productId?: number }) =>
|
||||||
|
getSimpleDeviceList(undefined, params?.productId),
|
||||||
labelField: 'deviceName',
|
labelField: 'deviceName',
|
||||||
valueField: 'id',
|
valueField: 'id',
|
||||||
placeholder: '请选择设备',
|
placeholder: '请选择设备',
|
||||||
clearable: true,
|
clearable: true,
|
||||||
filterable: true,
|
filterable: 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',
|
fieldName: 'processStatus',
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ const queryParams = reactive({
|
||||||
|
|
||||||
const autoRefresh = ref(false); // 自动刷新开关
|
const autoRefresh = ref(false); // 自动刷新开关
|
||||||
let autoRefreshTimer: any = null; // 自动刷新定时器
|
let autoRefreshTimer: any = null; // 自动刷新定时器
|
||||||
|
let refreshTimer: ReturnType<typeof setTimeout> | undefined; // 延迟刷新定时器
|
||||||
|
|
||||||
/** 消息方法选项 */
|
/** 消息方法选项 */
|
||||||
const methodOptions = computed(() => {
|
const methodOptions = computed(() => {
|
||||||
|
|
@ -156,6 +157,10 @@ onBeforeUnmount(() => {
|
||||||
clearInterval(autoRefreshTimer);
|
clearInterval(autoRefreshTimer);
|
||||||
autoRefreshTimer = null;
|
autoRefreshTimer = null;
|
||||||
}
|
}
|
||||||
|
if (refreshTimer) {
|
||||||
|
clearTimeout(refreshTimer);
|
||||||
|
refreshTimer = undefined;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/** 初始化 */
|
/** 初始化 */
|
||||||
|
|
@ -167,9 +172,14 @@ onMounted(() => {
|
||||||
|
|
||||||
/** 刷新消息列表 */
|
/** 刷新消息列表 */
|
||||||
function refresh(delay = 0) {
|
function refresh(delay = 0) {
|
||||||
|
if (refreshTimer) {
|
||||||
|
clearTimeout(refreshTimer);
|
||||||
|
refreshTimer = undefined;
|
||||||
|
}
|
||||||
if (delay > 0) {
|
if (delay > 0) {
|
||||||
setTimeout(() => {
|
refreshTimer = setTimeout(() => {
|
||||||
gridApi.query();
|
gridApi.query();
|
||||||
|
refreshTimer = undefined;
|
||||||
}, delay);
|
}, delay);
|
||||||
} else {
|
} else {
|
||||||
gridApi.query();
|
gridApi.query();
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,12 @@ import type { IotDeviceApi } from '#/api/iot/device/device';
|
||||||
import type { IotProductApi } from '#/api/iot/product/product';
|
import type { IotProductApi } from '#/api/iot/product/product';
|
||||||
import type { ThingModelApi } from '#/api/iot/thingmodel';
|
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 { ContentWrap } from '@vben/common-ui';
|
||||||
import {
|
import {
|
||||||
DeviceStateEnum,
|
DeviceStateEnum,
|
||||||
|
IoTDataSpecsDataTypeEnum,
|
||||||
IotDeviceMessageMethodEnum,
|
IotDeviceMessageMethodEnum,
|
||||||
IoTThingModelTypeEnum,
|
IoTThingModelTypeEnum,
|
||||||
} from '@vben/constants';
|
} from '@vben/constants';
|
||||||
|
|
@ -19,8 +20,11 @@ import {
|
||||||
ElCard,
|
ElCard,
|
||||||
ElCol,
|
ElCol,
|
||||||
ElInput,
|
ElInput,
|
||||||
|
ElInputNumber,
|
||||||
ElMessage,
|
ElMessage,
|
||||||
|
ElOption,
|
||||||
ElRow,
|
ElRow,
|
||||||
|
ElSelect,
|
||||||
ElTable,
|
ElTable,
|
||||||
ElTableColumn,
|
ElTableColumn,
|
||||||
ElTabPane,
|
ElTabPane,
|
||||||
|
|
@ -50,7 +54,7 @@ const debugCollapsed = ref(false); // 指令调试区域折叠状态
|
||||||
const messageCollapsed = ref(false); // 设备消息区域折叠状态
|
const messageCollapsed = ref(false); // 设备消息区域折叠状态
|
||||||
|
|
||||||
// 表单数据:存储用户输入的模拟值
|
// 表单数据:存储用户输入的模拟值
|
||||||
const formData = ref<Record<string, string>>({});
|
const formData = ref<Record<string, any>>({});
|
||||||
|
|
||||||
// 根据类型过滤物模型数据
|
// 根据类型过滤物模型数据
|
||||||
const getFilteredThingModelList = (type: number) => {
|
const getFilteredThingModelList = (type: number) => {
|
||||||
|
|
@ -76,21 +80,72 @@ const serviceList = computed(() =>
|
||||||
|
|
||||||
// 获取表单值
|
// 获取表单值
|
||||||
function getFormValue(identifier: string) {
|
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;
|
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() {
|
async function handlePropertyPost() {
|
||||||
try {
|
try {
|
||||||
const params: Record<string, any> = {};
|
const params: Record<string, any> = {};
|
||||||
propertyList.value.forEach((item) => {
|
propertyList.value.forEach((item) => {
|
||||||
const value = formData.value[item.identifier!];
|
const value = normalizePropertyValue(item, formData.value[item.identifier!]);
|
||||||
if (value) {
|
if (value !== undefined) {
|
||||||
params[item.identifier!] = value;
|
params[item.identifier!] = value;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -173,8 +228,8 @@ async function handlePropertySet() {
|
||||||
try {
|
try {
|
||||||
const params: Record<string, any> = {};
|
const params: Record<string, any> = {};
|
||||||
propertyList.value.forEach((item) => {
|
propertyList.value.forEach((item) => {
|
||||||
const value = formData.value[item.identifier!];
|
const value = normalizePropertyValue(item, formData.value[item.identifier!]);
|
||||||
if (value) {
|
if (value !== undefined) {
|
||||||
params[item.identifier!] = value;
|
params[item.identifier!] = value;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -212,6 +267,14 @@ async function handleServiceInvoke(row: ThingModelApi.ThingModel) {
|
||||||
ElMessage.error('服务参数格式错误,请输入有效的JSON格式');
|
ElMessage.error('服务参数格式错误,请输入有效的JSON格式');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
typeof inputParams !== 'object' ||
|
||||||
|
inputParams === null ||
|
||||||
|
Array.isArray(inputParams)
|
||||||
|
) {
|
||||||
|
ElMessage.error('服务参数必须是 JSON 对象');
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 与后端 IotDeviceServiceInvokeReqDTO 对齐 :{ identifier, inputParams }
|
// 与后端 IotDeviceServiceInvokeReqDTO 对齐 :{ identifier, inputParams }
|
||||||
|
|
@ -232,6 +295,11 @@ async function handleServiceInvoke(row: ThingModelApi.ThingModel) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 切换调试方法时清空输入,避免不同方法之间串台提交 */
|
||||||
|
watch([activeTab, upstreamTab, downstreamTab], () => {
|
||||||
|
formData.value = {};
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -297,7 +365,35 @@ async function handleServiceInvoke(row: ThingModelApi.ThingModel) {
|
||||||
</ElTableColumn>
|
</ElTableColumn>
|
||||||
<ElTableColumn label="值" width="180" fixed="right">
|
<ElTableColumn label="值" width="180" fixed="right">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
|
<ElInputNumber
|
||||||
|
v-if="isNumberProperty(row)"
|
||||||
|
:model-value="getFormValue(row.identifier)"
|
||||||
|
placeholder="输入值"
|
||||||
|
size="small"
|
||||||
|
class="w-full"
|
||||||
|
@update:model-value="
|
||||||
|
setFormValue(row.identifier, $event)
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
<ElSelect
|
||||||
|
v-else-if="isSelectProperty(row)"
|
||||||
|
:model-value="getFormValue(row.identifier)"
|
||||||
|
placeholder="请选择值"
|
||||||
|
size="small"
|
||||||
|
class="w-full"
|
||||||
|
@update:model-value="
|
||||||
|
setFormValue(row.identifier, $event)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<ElOption
|
||||||
|
v-for="option in getPropertyOptions(row)"
|
||||||
|
:key="option.value"
|
||||||
|
:label="option.label"
|
||||||
|
:value="option.value"
|
||||||
|
/>
|
||||||
|
</ElSelect>
|
||||||
<ElInput
|
<ElInput
|
||||||
|
v-else
|
||||||
:model-value="getFormValue(row.identifier)"
|
:model-value="getFormValue(row.identifier)"
|
||||||
placeholder="输入值"
|
placeholder="输入值"
|
||||||
size="small"
|
size="small"
|
||||||
|
|
@ -448,7 +544,35 @@ async function handleServiceInvoke(row: ThingModelApi.ThingModel) {
|
||||||
</ElTableColumn>
|
</ElTableColumn>
|
||||||
<ElTableColumn label="值" width="180" fixed="right">
|
<ElTableColumn label="值" width="180" fixed="right">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
|
<ElInputNumber
|
||||||
|
v-if="isNumberProperty(row)"
|
||||||
|
:model-value="getFormValue(row.identifier)"
|
||||||
|
placeholder="输入值"
|
||||||
|
size="small"
|
||||||
|
class="w-full"
|
||||||
|
@update:model-value="
|
||||||
|
setFormValue(row.identifier, $event)
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
<ElSelect
|
||||||
|
v-else-if="isSelectProperty(row)"
|
||||||
|
:model-value="getFormValue(row.identifier)"
|
||||||
|
placeholder="请选择值"
|
||||||
|
size="small"
|
||||||
|
class="w-full"
|
||||||
|
@update:model-value="
|
||||||
|
setFormValue(row.identifier, $event)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<ElOption
|
||||||
|
v-for="option in getPropertyOptions(row)"
|
||||||
|
:key="option.value"
|
||||||
|
:label="option.label"
|
||||||
|
:value="option.value"
|
||||||
|
/>
|
||||||
|
</ElSelect>
|
||||||
<ElInput
|
<ElInput
|
||||||
|
v-else
|
||||||
:model-value="getFormValue(row.identifier)"
|
:model-value="getFormValue(row.identifier)"
|
||||||
placeholder="输入值"
|
placeholder="输入值"
|
||||||
size="small"
|
size="small"
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
import type { ThingModelApi } from '#/api/iot/thingmodel';
|
import type { ThingModelApi } from '#/api/iot/thingmodel';
|
||||||
|
|
||||||
import { computed, onMounted, reactive, watch } from 'vue';
|
import { computed, onBeforeUnmount, onMounted, reactive, watch } from 'vue';
|
||||||
|
|
||||||
import { Page } from '@vben/common-ui';
|
import { Page } from '@vben/common-ui';
|
||||||
import {
|
import {
|
||||||
|
|
@ -35,6 +35,7 @@ const queryParams = reactive({
|
||||||
identifier: '',
|
identifier: '',
|
||||||
times: undefined as [string, string] | undefined,
|
times: undefined as [string, string] | undefined,
|
||||||
});
|
});
|
||||||
|
let refreshTimer: ReturnType<typeof setTimeout> | undefined; // 延迟刷新定时器
|
||||||
|
|
||||||
/** 事件类型的物模型数据 */
|
/** 事件类型的物模型数据 */
|
||||||
const eventThingModels = computed(() => {
|
const eventThingModels = computed(() => {
|
||||||
|
|
@ -159,8 +160,15 @@ function parseParams(params: string) {
|
||||||
|
|
||||||
/** 刷新列表 */
|
/** 刷新列表 */
|
||||||
function refresh(delay = 0) {
|
function refresh(delay = 0) {
|
||||||
|
if (refreshTimer) {
|
||||||
|
clearTimeout(refreshTimer);
|
||||||
|
refreshTimer = undefined;
|
||||||
|
}
|
||||||
if (delay > 0) {
|
if (delay > 0) {
|
||||||
setTimeout(() => gridApi.query(), delay);
|
refreshTimer = setTimeout(() => {
|
||||||
|
gridApi.query();
|
||||||
|
refreshTimer = undefined;
|
||||||
|
}, delay);
|
||||||
} else {
|
} else {
|
||||||
gridApi.query();
|
gridApi.query();
|
||||||
}
|
}
|
||||||
|
|
@ -183,6 +191,14 @@ onMounted(() => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/** 组件卸载时清除延迟刷新定时器 */
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
if (refreshTimer) {
|
||||||
|
clearTimeout(refreshTimer);
|
||||||
|
refreshTimer = undefined;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
/** 暴露方法给父组件 */
|
/** 暴露方法给父组件 */
|
||||||
defineExpose({
|
defineExpose({
|
||||||
refresh,
|
refresh,
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
import type { ThingModelApi } from '#/api/iot/thingmodel';
|
import type { ThingModelApi } from '#/api/iot/thingmodel';
|
||||||
|
|
||||||
import { computed, onMounted, reactive, watch } from 'vue';
|
import { computed, onBeforeUnmount, onMounted, reactive, watch } from 'vue';
|
||||||
|
|
||||||
import { Page } from '@vben/common-ui';
|
import { Page } from '@vben/common-ui';
|
||||||
import {
|
import {
|
||||||
|
|
@ -35,6 +35,7 @@ const queryParams = reactive({
|
||||||
identifier: '',
|
identifier: '',
|
||||||
times: undefined as [string, string] | undefined,
|
times: undefined as [string, string] | undefined,
|
||||||
});
|
});
|
||||||
|
let refreshTimer: ReturnType<typeof setTimeout> | undefined; // 延迟刷新定时器
|
||||||
|
|
||||||
/** 服务类型的物模型数据 */
|
/** 服务类型的物模型数据 */
|
||||||
const serviceThingModels = computed(() => {
|
const serviceThingModels = computed(() => {
|
||||||
|
|
@ -173,8 +174,15 @@ function parseParams(params: string) {
|
||||||
|
|
||||||
/** 刷新列表 */
|
/** 刷新列表 */
|
||||||
function refresh(delay = 0) {
|
function refresh(delay = 0) {
|
||||||
|
if (refreshTimer) {
|
||||||
|
clearTimeout(refreshTimer);
|
||||||
|
refreshTimer = undefined;
|
||||||
|
}
|
||||||
if (delay > 0) {
|
if (delay > 0) {
|
||||||
setTimeout(() => gridApi.query(), delay);
|
refreshTimer = setTimeout(() => {
|
||||||
|
gridApi.query();
|
||||||
|
refreshTimer = undefined;
|
||||||
|
}, delay);
|
||||||
} else {
|
} else {
|
||||||
gridApi.query();
|
gridApi.query();
|
||||||
}
|
}
|
||||||
|
|
@ -197,6 +205,14 @@ onMounted(() => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/** 组件卸载时清除延迟刷新定时器 */
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
if (refreshTimer) {
|
||||||
|
clearTimeout(refreshTimer);
|
||||||
|
refreshTimer = undefined;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
/** 暴露方法给父组件 */
|
/** 暴露方法给父组件 */
|
||||||
defineExpose({
|
defineExpose({
|
||||||
refresh,
|
refresh,
|
||||||
|
|
|
||||||
|
|
@ -67,10 +67,12 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||||
valueField: 'id',
|
valueField: 'id',
|
||||||
placeholder: '请选择产品',
|
placeholder: '请选择产品',
|
||||||
},
|
},
|
||||||
rules: 'required',
|
|
||||||
dependencies: {
|
dependencies: {
|
||||||
triggerFields: ['id'],
|
triggerFields: ['id'],
|
||||||
show: (values) => !values.id,
|
componentProps: (values) => ({
|
||||||
|
disabled: !!values.id,
|
||||||
|
}),
|
||||||
|
rules: (values) => (values.id ? null : 'required'),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -80,10 +82,12 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||||
componentProps: {
|
componentProps: {
|
||||||
placeholder: '请输入版本号',
|
placeholder: '请输入版本号',
|
||||||
},
|
},
|
||||||
rules: 'required',
|
|
||||||
dependencies: {
|
dependencies: {
|
||||||
triggerFields: ['id'],
|
triggerFields: ['id'],
|
||||||
show: (values) => !values.id,
|
componentProps: (values) => ({
|
||||||
|
disabled: !!values.id,
|
||||||
|
}),
|
||||||
|
rules: (values) => (values.id ? null : 'required'),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -105,10 +109,12 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||||
maxSize: 50,
|
maxSize: 50,
|
||||||
helpText: '支持上传 .bin、.zip、.pdf 格式的固件文件,最大 50MB',
|
helpText: '支持上传 .bin、.zip、.pdf 格式的固件文件,最大 50MB',
|
||||||
},
|
},
|
||||||
rules: 'required',
|
|
||||||
dependencies: {
|
dependencies: {
|
||||||
triggerFields: ['id'],
|
triggerFields: ['id'],
|
||||||
show: (values) => !values.id,
|
componentProps: (values) => ({
|
||||||
|
disabled: !!values.id,
|
||||||
|
}),
|
||||||
|
rules: (values) => (values.id ? null : 'required'),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ async function handleRefresh() {
|
||||||
|
|
||||||
/** 按任务名称搜索 */
|
/** 按任务名称搜索 */
|
||||||
async function handleSearch() {
|
async function handleSearch() {
|
||||||
await gridApi.query();
|
await gridApi.reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 新增任务 */
|
/** 新增任务 */
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ const statusTabs = computed(() => {
|
||||||
/** 切换标签 */
|
/** 切换标签 */
|
||||||
async function handleTabChange(tabKey: number | string) {
|
async function handleTabChange(tabKey: number | string) {
|
||||||
activeTab.value = String(tabKey);
|
activeTab.value = String(tabKey);
|
||||||
await gridApi.query();
|
await gridApi.reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 取消单条记录的升级 */
|
/** 取消单条记录的升级 */
|
||||||
|
|
@ -89,7 +89,7 @@ watch(
|
||||||
async (val) => {
|
async (val) => {
|
||||||
if (val) {
|
if (val) {
|
||||||
activeTab.value = '';
|
activeTab.value = '';
|
||||||
await gridApi.query();
|
await gridApi.reload();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -146,10 +146,46 @@ function handleNumberChange(value: number | undefined) {
|
||||||
localValue.value = value?.toString() || '';
|
localValue.value = value?.toString() || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 根据外部值同步内部输入态 */
|
||||||
|
function syncInternalValue(value = '') {
|
||||||
|
const normalized = value;
|
||||||
|
if (
|
||||||
|
props.operator ===
|
||||||
|
IotRuleSceneTriggerConditionParameterOperatorEnum.BETWEEN.value
|
||||||
|
) {
|
||||||
|
const [start = '', end = ''] = normalized.split(',');
|
||||||
|
rangeStart.value = start;
|
||||||
|
rangeEnd.value = end;
|
||||||
|
numberValue.value = undefined;
|
||||||
|
dateValue.value = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
rangeStart.value = '';
|
||||||
|
rangeEnd.value = '';
|
||||||
|
if (props.propertyType === IoTDataSpecsDataTypeEnum.DATE) {
|
||||||
|
dateValue.value = normalized;
|
||||||
|
numberValue.value = undefined;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isNumericType()) {
|
||||||
|
const parsed = Number(normalized);
|
||||||
|
numberValue.value =
|
||||||
|
normalized === '' || Number.isNaN(parsed) ? undefined : parsed;
|
||||||
|
dateValue.value = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
numberValue.value = undefined;
|
||||||
|
dateValue.value = '';
|
||||||
|
}
|
||||||
|
|
||||||
/** 监听操作符变化 */
|
/** 监听操作符变化 */
|
||||||
watch(
|
watch(
|
||||||
() => props.operator,
|
() => props.operator,
|
||||||
() => {
|
(_operator, oldOperator) => {
|
||||||
|
if (oldOperator === undefined) {
|
||||||
|
syncInternalValue(props.modelValue);
|
||||||
|
return;
|
||||||
|
}
|
||||||
localValue.value = '';
|
localValue.value = '';
|
||||||
rangeStart.value = '';
|
rangeStart.value = '';
|
||||||
rangeEnd.value = '';
|
rangeEnd.value = '';
|
||||||
|
|
@ -157,6 +193,12 @@ watch(
|
||||||
numberValue.value = undefined;
|
numberValue.value = undefined;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => [props.modelValue, props.propertyType] as const,
|
||||||
|
([value]) => syncInternalValue(value),
|
||||||
|
{ immediate: true },
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -168,8 +210,8 @@ watch(
|
||||||
placeholder="请选择布尔值"
|
placeholder="请选择布尔值"
|
||||||
class="w-full!"
|
class="w-full!"
|
||||||
>
|
>
|
||||||
<ElOption label="真 (true)" :value="true" />
|
<ElOption label="真 (true)" value="true" />
|
||||||
<ElOption label="假 (false)" :value="false" />
|
<ElOption label="假 (false)" value="false" />
|
||||||
</ElSelect>
|
</ElSelect>
|
||||||
|
|
||||||
<!-- 枚举值选择 -->
|
<!-- 枚举值选择 -->
|
||||||
|
|
|
||||||
|
|
@ -31,10 +31,7 @@ async function getList() {
|
||||||
function handleChange(value: number | string | undefined) {
|
function handleChange(value: number | string | undefined) {
|
||||||
const toolId = typeof value === 'number' ? value : undefined;
|
const toolId = typeof value === 'number' ? value : undefined;
|
||||||
emit('update:modelValue', toolId);
|
emit('update:modelValue', toolId);
|
||||||
emit(
|
emit('change', list.value.find((item) => item.id === toolId));
|
||||||
'change',
|
|
||||||
list.value.find((item) => item.id === toolId),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(getList);
|
onMounted(getList);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue