重构设备操作界面

This commit is contained in:
ChenYi 2025-12-25 11:26:14 +08:00
parent f208976dd8
commit 2ff8a447cf
4 changed files with 735 additions and 359 deletions

View File

@ -750,6 +750,10 @@ export const CTWingAccountListInputSchema = {
</example>`,
nullable: true
},
isPage: {
type: 'boolean',
description: '是否分页'
},
phoneNumber: {
type: 'string',
description: '手机号码',
@ -1091,6 +1095,10 @@ export const CTWingPrivateProductInfoListInputSchema = {
</example>`,
nullable: true
},
isPage: {
type: 'boolean',
description: '是否分页'
},
ctWingAccountId: {
type: 'string',
description: 'CTWing账户Id',
@ -2238,6 +2246,10 @@ export const DeviceTableModelDataInfoPageInputSchema = {
</example>`,
nullable: true
},
isPage: {
type: 'boolean',
description: '是否分页'
},
ioTDataType: {
type: 'string',
description: `数据类型,用于构建存储路径
@ -2528,6 +2540,10 @@ export const DeviceThingModelCommandInfoPageInputSchema = {
</example>`,
nullable: true
},
isPage: {
type: 'boolean',
description: '是否分页'
},
deviceThingModelId: {
type: 'string',
description: '设备端物模型Id',
@ -2550,10 +2566,6 @@ export const DeviceThingModelCommandInfoPageInputSchema = {
type: 'string',
description: '物联网平台中对应产品物模型属性或者事件类型 JiShe.ServicePro.Core.DataDictionaryTypeConst',
nullable: true
},
isPage: {
type: 'boolean',
description: '是否分页'
}
},
additionalProperties: false,
@ -2793,6 +2805,10 @@ export const DeviceThingModelPageInputSchema = {
</example>`,
nullable: true
},
isPage: {
type: 'boolean',
description: '是否分页'
},
ioTPlatform: {
'$ref': '#/components/schemas/IoTPlatformTypeEnum'
},
@ -2810,10 +2826,6 @@ export const DeviceThingModelPageInputSchema = {
type: 'string',
description: '物联网平台中对应产品物模型属性或者事件类型 JiShe.ServicePro.Core.DataDictionaryTypeConst',
nullable: true
},
isPage: {
type: 'boolean',
description: '是否分页'
}
},
additionalProperties: false
@ -3070,6 +3082,10 @@ export const DeviceThingModelPropertyPageInputSchema = {
</example>`,
nullable: true
},
isPage: {
type: 'boolean',
description: '是否分页'
},
deviceThingModelId: {
type: 'string',
description: '设备端物模型Id',
@ -3092,10 +3108,6 @@ export const DeviceThingModelPropertyPageInputSchema = {
type: 'string',
description: '物联网平台中对应产品物模型属性或者事件类型 JiShe.ServicePro.Core.DataDictionaryTypeConst',
nullable: true
},
isPage: {
type: 'boolean',
description: '是否分页'
}
},
additionalProperties: false,
@ -3236,6 +3248,10 @@ export const DeviceTreeModelDataInfoInputSchema = {
</example>`,
nullable: true
},
isPage: {
type: 'boolean',
description: '是否分页'
},
ioTDataType: {
type: 'string',
description: `数据类型,用于构建存储路径
@ -4431,6 +4447,10 @@ export const GetOrganizationUnitRoleInputSchema = {
</example>`,
nullable: true
},
isPage: {
type: 'boolean',
description: '是否分页'
},
organizationUnitId: {
type: 'string',
format: 'uuid'
@ -4499,6 +4519,10 @@ export const GetOrganizationUnitUserInputSchema = {
</example>`,
nullable: true
},
isPage: {
type: 'boolean',
description: '是否分页'
},
organizationUnitId: {
type: 'string',
format: 'uuid'
@ -4608,6 +4632,10 @@ export const GetUnAddRoleInputSchema = {
</example>`,
nullable: true
},
isPage: {
type: 'boolean',
description: '是否分页'
},
organizationUnitId: {
type: 'string',
format: 'uuid'
@ -4680,6 +4708,10 @@ export const GetUnAddUserInputSchema = {
</example>`,
nullable: true
},
isPage: {
type: 'boolean',
description: '是否分页'
},
organizationUnitId: {
type: 'string',
format: 'uuid'
@ -5576,6 +5608,10 @@ export const IoTPlatformThingModelPageInputSchema = {
</example>`,
nullable: true
},
isPage: {
type: 'boolean',
description: '是否分页'
},
ioTPlatform: {
'$ref': '#/components/schemas/IoTPlatformTypeEnum'
},
@ -5899,6 +5935,10 @@ export const MeterReadingPacketInfoPageInputSchema = {
</example>`,
nullable: true
},
isPage: {
type: 'boolean',
description: '是否分页'
},
ioTDataType: {
type: 'string',
description: `数据类型,用于构建存储路径
@ -6704,6 +6744,10 @@ export const OneNetAccountListInputSchema = {
</example>`,
nullable: true
},
isPage: {
type: 'boolean',
description: '是否分页'
},
phoneNumber: {
type: 'string',
description: '手机号码',
@ -6817,6 +6861,10 @@ export const OneNetProductInfoListInputSchema = {
</example>`,
nullable: true
},
isPage: {
type: 'boolean',
description: '是否分页'
},
oneNETAccountId: {
type: 'string',
description: 'OneNET账户Id',
@ -6948,6 +6996,10 @@ export const PageDeviceInputSchema = {
</example>`,
nullable: true
},
isPage: {
type: 'boolean',
description: '是否分页'
},
deviceAddress: {
type: 'string',
description: '设备地址',
@ -7002,6 +7054,10 @@ export const PageFileObjectInputSchema = {
</example>`,
nullable: true
},
isPage: {
type: 'boolean',
description: '是否分页'
},
startCreationTime: {
type: 'string',
description: '开始创建时间',
@ -7237,6 +7293,10 @@ export const PageLanguageInputSchema = {
</example>`,
nullable: true
},
isPage: {
type: 'boolean',
description: '是否分页'
},
filter: {
type: 'string',
nullable: true
@ -7337,6 +7397,10 @@ export const PageLanguageTextInputSchema = {
</example>`,
nullable: true
},
isPage: {
type: 'boolean',
description: '是否分页'
},
cultureName: {
type: 'string',
description: '语言',
@ -7425,6 +7489,10 @@ export const PageMenuInputSchema = {
</example>`,
nullable: true
},
isPage: {
type: 'boolean',
description: '是否分页'
},
startCreationTime: {
type: 'string',
description: '开始创建时间',
@ -7650,6 +7718,10 @@ export const PageTextTemplateInputSchema = {
</example>`,
nullable: true
},
isPage: {
type: 'boolean',
description: '是否分页'
},
name: {
type: 'string',
description: '名称',
@ -7810,6 +7882,10 @@ export const PagingAuditLogInputSchema = {
</example>`,
nullable: true
},
isPage: {
type: 'boolean',
description: '是否分页'
},
startTime: {
type: 'string',
description: '开始时间',
@ -8029,6 +8105,10 @@ export const PagingDataDictionaryDetailInputSchema = {
</example>`,
nullable: true
},
isPage: {
type: 'boolean',
description: '是否分页'
},
dataDictionaryId: {
type: 'string',
format: 'uuid'
@ -8131,6 +8211,10 @@ export const PagingDataDictionaryInputSchema = {
</example>`,
nullable: true
},
isPage: {
type: 'boolean',
description: '是否分页'
},
filter: {
type: 'string',
nullable: true
@ -8294,6 +8378,10 @@ export const PagingIdentitySecurityLogInputSchema = {
format: 'int32',
readOnly: true
},
isPage: {
type: 'boolean',
description: '是否分页'
},
sorting: {
type: 'string',
description: '排序',
@ -8456,6 +8544,10 @@ export const PagingNotificationInputSchema = {
</example>`,
nullable: true
},
isPage: {
type: 'boolean',
description: '是否分页'
},
title: {
type: 'string',
description: '标题',
@ -8640,6 +8732,10 @@ export const PagingNotificationSubscriptionInputSchema = {
</example>`,
nullable: true
},
isPage: {
type: 'boolean',
description: '是否分页'
},
notificationId: {
type: 'string',
format: 'uuid'
@ -8798,6 +8894,10 @@ export const PagingRoleListInputSchema = {
</example>`,
nullable: true
},
isPage: {
type: 'boolean',
description: '是否分页'
},
filter: {
type: 'string',
nullable: true
@ -8833,6 +8933,10 @@ export const PagingTenantInputSchema = {
</example>`,
nullable: true
},
isPage: {
type: 'boolean',
description: '是否分页'
},
filter: {
type: 'string',
nullable: true
@ -8868,6 +8972,10 @@ export const PagingUserListInputSchema = {
</example>`,
nullable: true
},
isPage: {
type: 'boolean',
description: '是否分页'
},
filter: {
type: 'string',
description: '关键字',
@ -9082,6 +9190,10 @@ export const QueryCTWingAepReceiveMessageInputSchema = {
</example>`,
nullable: true
},
isPage: {
type: 'boolean',
description: '是否分页'
},
ioTDataType: {
type: 'string',
description: `数据类型,用于构建存储路径
@ -9295,6 +9407,10 @@ export const QueryOneNETReceiveMessageInputSchema = {
</example>`,
nullable: true
},
isPage: {
type: 'boolean',
description: '是否分页'
},
ioTDataType: {
type: 'string',
description: `数据类型,用于构建存储路径
@ -9696,6 +9812,10 @@ export const SelectDataDictionaryDetailInputSchema = {
</example>`,
nullable: true
},
isPage: {
type: 'boolean',
description: '是否分页'
},
dataDictionaryId: {
type: 'string',
format: 'uuid'

View File

@ -622,6 +622,10 @@ export type CTWingAccountListInput = {
* </example>
*/
sorting?: (string) | null;
/**
*
*/
isPage?: boolean;
/**
*
*/
@ -848,6 +852,10 @@ export type CTWingPrivateProductInfoListInput = {
* </example>
*/
sorting?: (string) | null;
/**
*
*/
isPage?: boolean;
/**
* CTWing账户Id
*/
@ -1238,6 +1246,10 @@ export type DeviceTableModelDataInfoPageInput = {
* </example>
*/
sorting?: (string) | null;
/**
*
*/
isPage?: boolean;
/**
*
* JiShe.ServicePro.Consts.IoTDBDataTypeConst
@ -1417,6 +1429,10 @@ export type DeviceThingModelCommandInfoPageInput = {
* </example>
*/
sorting?: (string) | null;
/**
*
*/
isPage?: boolean;
/**
* Id
*/
@ -1434,10 +1450,6 @@ export type DeviceThingModelCommandInfoPageInput = {
* JiShe.ServicePro.Core.DataDictionaryTypeConst
*/
filedType?: (string) | null;
/**
*
*/
isPage?: boolean;
};
/**
@ -1577,6 +1589,10 @@ export type DeviceThingModelPageInput = {
* </example>
*/
sorting?: (string) | null;
/**
*
*/
isPage?: boolean;
ioTPlatform?: IoTPlatformTypeEnum;
/**
*
@ -1590,10 +1606,6 @@ export type DeviceThingModelPageInput = {
* JiShe.ServicePro.Core.DataDictionaryTypeConst
*/
filedType?: (string) | null;
/**
*
*/
isPage?: boolean;
};
/**
@ -1757,6 +1769,10 @@ export type DeviceThingModelPropertyPageInput = {
* </example>
*/
sorting?: (string) | null;
/**
*
*/
isPage?: boolean;
/**
* Id
*/
@ -1774,10 +1790,6 @@ export type DeviceThingModelPropertyPageInput = {
* JiShe.ServicePro.Core.DataDictionaryTypeConst
*/
filedType?: (string) | null;
/**
*
*/
isPage?: boolean;
};
/**
@ -1876,6 +1888,10 @@ export type DeviceTreeModelDataInfoInput = {
* </example>
*/
sorting?: (string) | null;
/**
*
*/
isPage?: boolean;
/**
*
* JiShe.ServicePro.Consts.IoTDBDataTypeConst
@ -2321,6 +2337,10 @@ export type GetOrganizationUnitRoleInput = {
* </example>
*/
sorting?: (string) | null;
/**
*
*/
isPage?: boolean;
organizationUnitId?: string;
};
@ -2354,6 +2374,10 @@ export type GetOrganizationUnitUserInput = {
* </example>
*/
sorting?: (string) | null;
/**
*
*/
isPage?: boolean;
organizationUnitId?: string;
filter?: (string) | null;
};
@ -2405,6 +2429,10 @@ export type GetUnAddRoleInput = {
* </example>
*/
sorting?: (string) | null;
/**
*
*/
isPage?: boolean;
organizationUnitId?: string;
filter?: (string) | null;
};
@ -2439,6 +2467,10 @@ export type GetUnAddUserInput = {
* </example>
*/
sorting?: (string) | null;
/**
*
*/
isPage?: boolean;
organizationUnitId?: string;
filter?: (string) | null;
};
@ -2828,6 +2860,10 @@ export type IoTPlatformThingModelPageInput = {
* </example>
*/
sorting?: (string) | null;
/**
*
*/
isPage?: boolean;
ioTPlatform?: IoTPlatformTypeEnum;
/**
*
@ -3040,6 +3076,10 @@ export type MeterReadingPacketInfoPageInput = {
* </example>
*/
sorting?: (string) | null;
/**
*
*/
isPage?: boolean;
/**
*
* JiShe.ServicePro.Consts.IoTDBDataTypeConst
@ -3369,6 +3409,10 @@ export type OneNetAccountListInput = {
* </example>
*/
sorting?: (string) | null;
/**
*
*/
isPage?: boolean;
/**
*
*/
@ -3589,6 +3633,10 @@ export type OneNetProductInfoListInput = {
* </example>
*/
sorting?: (string) | null;
/**
*
*/
isPage?: boolean;
/**
* OneNET账户Id
*/
@ -3688,6 +3736,10 @@ export type PageDeviceInput = {
* </example>
*/
sorting?: (string) | null;
/**
*
*/
isPage?: boolean;
/**
*
*/
@ -3730,6 +3782,10 @@ export type PageFileObjectInput = {
* </example>
*/
sorting?: (string) | null;
/**
*
*/
isPage?: boolean;
/**
*
*/
@ -3837,6 +3893,10 @@ export type PageLanguageInput = {
* </example>
*/
sorting?: (string) | null;
/**
*
*/
isPage?: boolean;
filter?: (string) | null;
};
@ -3906,6 +3966,10 @@ export type PageLanguageTextInput = {
* </example>
*/
sorting?: (string) | null;
/**
*
*/
isPage?: boolean;
/**
*
*/
@ -3966,6 +4030,10 @@ export type PageMenuInput = {
* </example>
*/
sorting?: (string) | null;
/**
*
*/
isPage?: boolean;
/**
*
*/
@ -4110,6 +4178,10 @@ export type PageTextTemplateInput = {
* </example>
*/
sorting?: (string) | null;
/**
*
*/
isPage?: boolean;
/**
*
*/
@ -4201,6 +4273,10 @@ export type PagingAuditLogInput = {
* </example>
*/
sorting?: (string) | null;
/**
*
*/
isPage?: boolean;
/**
*
*/
@ -4301,6 +4377,10 @@ export type PagingDataDictionaryDetailInput = {
* </example>
*/
sorting?: (string) | null;
/**
*
*/
isPage?: boolean;
dataDictionaryId?: string;
filter?: (string) | null;
};
@ -4362,6 +4442,10 @@ export type PagingDataDictionaryInput = {
* </example>
*/
sorting?: (string) | null;
/**
*
*/
isPage?: boolean;
filter?: (string) | null;
};
@ -4425,6 +4509,10 @@ export type PagingIdentitySecurityLogInput = {
*
*/
readonly skipCount?: number;
/**
*
*/
isPage?: boolean;
/**
*
*/
@ -4505,6 +4593,10 @@ export type PagingNotificationInput = {
* </example>
*/
sorting?: (string) | null;
/**
*
*/
isPage?: boolean;
/**
*
*/
@ -4617,6 +4709,10 @@ export type PagingNotificationSubscriptionInput = {
* </example>
*/
sorting?: (string) | null;
/**
*
*/
isPage?: boolean;
notificationId?: string;
/**
* Id
@ -4713,6 +4809,10 @@ export type PagingRoleListInput = {
* </example>
*/
sorting?: (string) | null;
/**
*
*/
isPage?: boolean;
filter?: (string) | null;
};
@ -4736,6 +4836,10 @@ export type PagingTenantInput = {
* </example>
*/
sorting?: (string) | null;
/**
*
*/
isPage?: boolean;
filter?: (string) | null;
};
@ -4759,6 +4863,10 @@ export type PagingUserListInput = {
* </example>
*/
sorting?: (string) | null;
/**
*
*/
isPage?: boolean;
/**
*
*/
@ -4846,6 +4954,10 @@ export type QueryCTWingAepReceiveMessageInput = {
* </example>
*/
sorting?: (string) | null;
/**
*
*/
isPage?: boolean;
/**
*
* JiShe.ServicePro.Consts.IoTDBDataTypeConst
@ -5002,6 +5114,10 @@ export type QueryOneNETReceiveMessageInput = {
* </example>
*/
sorting?: (string) | null;
/**
*
*/
isPage?: boolean;
/**
*
* JiShe.ServicePro.Consts.IoTDBDataTypeConst
@ -5213,6 +5329,10 @@ export type SelectDataDictionaryDetailInput = {
* </example>
*/
sorting?: (string) | null;
/**
*
*/
isPage?: boolean;
dataDictionaryId?: string;
filter?: (string) | null;
typeCode?: (string) | null;

View File

@ -3,16 +3,18 @@ import type { VbenFormProps } from '#/adapter/form';
import type { VxeGridProps } from '#/adapter/vxe-table';
import { computed, h, nextTick, ref, watch } from 'vue';
import { useRouter, useRoute } from 'vue-router';
import { useRoute, useRouter } from 'vue-router';
import { Page, useVbenModal } from '@vben/common-ui';
import {
Button,
Checkbox,
Input,
message as Message,
Modal,
Popover,
Table,
Tag,
} from 'ant-design-vue';
@ -23,23 +25,24 @@ import {
postAggregationDeviceCreateAsync,
postAggregationDeviceDeleteAsync,
postAggregationDeviceDeviceCommandForApiAsync,
postAggregationDeviceGetDevicePropertyValueForApiAsync,
postAggregationDeviceRepushDeviceInfoToIoTplatform,
postDeviceInfoCacheDeviceDataToRedis,
postDeviceInfoBindingDeviceThingModel,
postDeviceInfoCacheDeviceDataToRedis,
postDeviceInfoPage,
postIoTplatformThingModelInfoPageAsync,
} from '#/api-client';
import { Icon } from '#/components/icon';
import { Loading } from '#/components/Loading';
import { TableAction } from '#/components/table-action';
import { $t } from '#/locales';
import IoTPlatformThingModelDataSelect from '#/views/thingmodelinfo/IoTPlatformThingModelDataSelect.vue';
import {
addDeviceFormSchema,
batchAddDeviceFormSchema,
bindDeviceThingModelFormSchema,
commandFormSchema,
editDeviceFormSchemaEdit,
bindDeviceThingModelFormSchema,
querySchema,
tableSchema,
} from './schema';
@ -94,7 +97,7 @@ const gridOptions: VxeGridProps<any> = {
: formValues || {};
// 使API使formValues
const finalFormValues = { ...(formValues || {}), ...currentFormValues };
const finalFormValues = { ...formValues, ...currentFormValues };
const { data } = await postDeviceInfoPage({
body: {
@ -131,9 +134,15 @@ const gridOptions: VxeGridProps<any> = {
const currentProductId = row?.ioTPlatformProductId;
// ID
if (firstProductId && currentProductId && firstProductId !== currentProductId) {
if (
firstProductId &&
currentProductId &&
firstProductId !== currentProductId
) {
// ID
Message.warning('只能选择同一种产品的设备进行批量绑定,当前设备的产品与已选设备的产品不一致');
Message.warning(
'只能选择同一种产品的设备进行批量绑定,当前设备的产品与已选设备的产品不一致',
);
//
setTimeout(() => {
@ -146,7 +155,6 @@ const gridOptions: VxeGridProps<any> = {
}
}
}, 100);
return;
}
}
},
@ -160,13 +168,15 @@ const gridOptions: VxeGridProps<any> = {
// ID
const productIds = records
.map((r: any) => r.ioTPlatformProductId)
.filter((id: any) => id);
.filter(Boolean);
if (productIds.length > 0) {
const uniqueProductIds = [...new Set(productIds)];
if (uniqueProductIds.length > 1) {
// ID
Message.warning('只能选择同一种产品的设备进行批量绑定,当前页面包含多种产品');
Message.warning(
'只能选择同一种产品的设备进行批量绑定,当前页面包含多种产品',
);
//
setTimeout(() => {
@ -179,7 +189,6 @@ const gridOptions: VxeGridProps<any> = {
}
}
}, 100);
return;
}
}
},
@ -206,9 +215,11 @@ watch(
if (newQuery.ioTPlatformDeviceOpenInfo) {
// ID
filterValues.ioTPlatformProductId = newQuery.ioTPlatformDeviceOpenInfo;
filterValues.ioTPlatformProductId =
newQuery.ioTPlatformDeviceOpenInfo;
//
filterValues.ioTPlatformDeviceOpenInfo = newQuery.ioTPlatformDeviceOpenInfo;
filterValues.ioTPlatformDeviceOpenInfo =
newQuery.ioTPlatformDeviceOpenInfo;
}
//
@ -224,7 +235,7 @@ watch(
}, 200); // ApiSelect
}
},
{ immediate: true }
{ immediate: true },
);
// ioTPlatformDeviceOpenInfo
@ -237,7 +248,10 @@ watch(
// ioTPlatformDeviceOpenInfo
if (ioTPlatformProductId) {
console.log('检测到产品选择变化:', ioTPlatformProductId);
gridApi?.formApi?.setFieldValue('ioTPlatformDeviceOpenInfo', ioTPlatformProductId);
gridApi?.formApi?.setFieldValue(
'ioTPlatformDeviceOpenInfo',
ioTPlatformProductId,
);
console.log('已设置ioTPlatformDeviceOpenInfo为:', ioTPlatformProductId);
} else {
console.log('清空产品选择');
@ -245,7 +259,7 @@ watch(
}
}
},
{ deep: true }
{ deep: true },
);
const cacheRefreshLoading = ref(false);
@ -253,71 +267,174 @@ const pageLoading = ref(false);
const loadingTip = ref('缓存刷新中...');
const commandRow: Record<string, any> = ref({});
//
const bindRows: Array<Record<string, any>> = ref([]);
// -
interface CommandProperty {
id: string; //
propertyId: string; // ID
propertyName: string; //
platformFieldName: string; //
standardFieldName: string; //
value: string; //
}
const commandProperties = ref<CommandProperty[]>([
//
const commandTableColumns = computed(() => [
{
id: `prop-${Date.now()}`,
propertyId: '',
propertyName: '',
platformFieldName: '',
standardFieldName: '',
value: '',
title: '',
key: 'selected',
width: 50,
fixed: 'left' as const,
customRender: ({ record }: any) => {
return h(Checkbox, {
checked: record.selected,
'onUpdate:checked': (checked: boolean) => {
record.selected = checked;
},
});
},
},
{
title: '平台物模型属性标识符',
dataIndex: 'ioTPlatformRawFieldName',
key: 'ioTPlatformRawFieldName',
width: 200,
},
{
title: '标准物模型属性名称',
dataIndex: 'standardFieldDisplayName',
key: 'standardFieldDisplayName',
width: 200,
},
{
title: '指令内容',
dataIndex: 'commandValue',
key: 'commandValue',
width: 200,
customRender: ({ record }: any) => {
return h(Input, {
value: record.commandValue,
placeholder: '请输入指令内容',
'onUpdate:value': (val: string) => {
record.commandValue = val;
},
});
},
},
{
title: '返回结果',
dataIndex: 'result',
key: 'result',
width: 250,
customRender: ({ record }: any) => {
// result
const resultText = record.result
? (typeof record.result === 'string'
? record.result
: String(record.result))
: '-';
const resultStr = String(resultText);
const isError = resultStr.includes('失败') || resultStr.includes('失败:');
return h(
'div',
{
style: {
color:
resultText === '-' ? '#666' : isError ? '#ff4d4f' : '#52c41a',
wordBreak: 'break-all',
},
},
resultStr,
);
},
},
{
title: '操作',
key: 'action',
width: 200,
fixed: 'right' as const,
customRender: ({ record }: any) => {
return h(
'div',
{
style: {
display: 'flex',
gap: '8px',
},
},
[
h(
Button,
{
type: 'primary',
size: 'small',
loading: record.loading,
onClick: () => sendCommand(record),
},
'发送指令',
),
h(
Button,
{
size: 'small',
loading: record.loading,
onClick: () => readData(record),
},
'抄读数据',
),
],
);
},
},
]);
// -
const addCommandProperty = () => {
commandProperties.value.push({
id: `prop-${Date.now()}-${Math.random()}`,
propertyId: '',
propertyName: '',
platformFieldName: '',
standardFieldName: '',
value: '',
//
const bindRows: Array<Record<string, any>> = ref([]);
//
interface ThingModelProperty {
id: string; // ID
ioTPlatformRawFieldName: string; //
standardFieldDisplayName: string; //
commandValue: string; //
result: string; //
loading?: boolean; //
selected?: boolean; //
}
const thingModelProperties = ref<ThingModelProperty[]>([]);
const thingModelLoading = ref(false);
//
const fetchThingModelProperties = async (row: Record<string, any>) => {
if (!row.ioTPlatform || !row.ioTPlatformProductId) {
Message.warning('设备缺少平台或产品信息,无法获取物模型属性');
return;
}
thingModelLoading.value = true;
try {
const requestBody: any = {
pageIndex: 1,
pageSize: 1000, //
IsPage: false, //
ioTPlatform:
typeof row.ioTPlatform === 'string'
? Number.parseInt(row.ioTPlatform)
: row.ioTPlatform,
ioTPlatformProductId: String(row.ioTPlatformProductId),
};
const { data } = await postIoTplatformThingModelInfoPageAsync({
body: requestBody,
});
};
// -
const removeCommandProperty = (id: string) => {
const index = commandProperties.value.findIndex((p) => p.id === id);
if (index > -1) {
commandProperties.value.splice(index, 1);
}
//
if (commandProperties.value.length === 0) {
addCommandProperty();
}
};
//
const handlePropertyChange = (item: any, property: CommandProperty) => {
if (item) {
property.propertyId = item.id || item.value || '';
property.propertyName =
item.standardFieldDisplayName ||
item.ioTPlatformRawFieldName ||
item.label ||
'';
// 使使
property.platformFieldName = item.ioTPlatformRawFieldName || item.standardFieldName || '';
property.standardFieldName = item.standardFieldName || '';
} else {
property.propertyId = '';
property.propertyName = '';
property.platformFieldName = '';
property.standardFieldName = '';
const items = Array.isArray(data?.items) ? data!.items : [];
thingModelProperties.value = items.map((item: any) => ({
id: item.id || '',
ioTPlatformRawFieldName: item.ioTPlatformRawFieldName || '',
standardFieldDisplayName: item.standardFieldDisplayName || '',
commandValue: '',
result: '',
loading: false,
selected: false,
}));
} catch (error) {
console.error('获取平台物模型属性失败:', error);
Message.error('获取平台物模型属性失败');
thingModelProperties.value = [];
} finally {
thingModelLoading.value = false;
}
};
@ -330,30 +447,79 @@ const [UserModal, userModalApi] = useVbenModal({
},
});
//
const submitBatchCommand = async () => {
//
const selectedProperties = thingModelProperties.value.filter(
(prop) =>
prop.selected &&
prop.ioTPlatformRawFieldName &&
prop.commandValue &&
prop.commandValue.trim() !== '',
);
if (selectedProperties.length === 0) {
Message.warning('请至少选择一个已填写指令内容的属性');
return;
}
//
const commandContent: Record<string, any> = {};
selectedProperties.forEach((prop) => {
commandContent[prop.ioTPlatformRawFieldName] = prop.commandValue.trim();
});
try {
commandModalApi.setState({ loading: true, confirmLoading: true });
//
const result = await postAggregationDeviceDeviceCommandForApiAsync({
body: {
id: commandRow.value.id,
commandContent,
},
});
if (result.data) {
//
selectedProperties.forEach((prop) => {
prop.result =
typeof result.data === 'string'
? result.data
: (result.data
? JSON.stringify(result.data)
: '指令下发成功');
});
Message.success(`成功发送 ${selectedProperties.length} 个指令`);
} else {
selectedProperties.forEach((prop) => {
prop.result = '指令下发失败';
});
Message.error('指令下发失败');
}
} catch (error) {
console.error('批量指令发送失败:', error);
selectedProperties.forEach((prop) => {
prop.result = `指令下发失败: ${(error as Error).message}`;
});
Message.error('指令下发失败');
} finally {
commandModalApi.setState({ loading: false, confirmLoading: false });
}
};
const [CommandModal, commandModalApi] = useVbenModal({
draggable: true,
onConfirm: submitCommand,
onConfirm: submitBatchCommand,
onBeforeClose: () => {
commandRow.value = {};
//
commandProperties.value = [
{
id: `prop-${Date.now()}`,
propertyId: '',
propertyName: '',
platformFieldName: '',
standardFieldName: '',
value: '',
},
];
thingModelProperties.value = [];
return true;
},
onOpenChange: (isOpen: boolean) => {
onOpenChange: async (isOpen: boolean) => {
if (isOpen && commandRow.value) {
// -
if (commandProperties.value.length === 0) {
addCommandProperty();
}
//
await fetchThingModelProperties(commandRow.value);
}
},
});
@ -455,15 +621,21 @@ const [BindForm, bindFormApi] = useVbenForm({
wrapperClass: 'grid-cols-1',
handleValuesChange: async (values, changedFields) => {
//
if (changedFields.includes('isNeedConfigDevicMdoel') && values.isNeedConfigDevicMdoel) {
if (
changedFields.includes('isNeedConfigDevicMdoel') &&
values.isNeedConfigDevicMdoel
) {
console.log('开关打开,准备触发设备物模型下拉框重新加载');
await nextTick();
setTimeout(async () => {
try {
const productId = values._ioTPlatformProductId || values.ioTPlatformProductId;
const productId =
values._ioTPlatformProductId || values.ioTPlatformProductId;
console.log('手动触发下拉框重新加载产品ID:', productId);
if (productId) {
const fieldRef = bindFormApi.getFieldComponentRef('deviceThingModelDataId');
const fieldRef = bindFormApi.getFieldComponentRef(
'deviceThingModelDataId',
);
if (fieldRef && typeof fieldRef.updateParam === 'function') {
fieldRef.updateParam({
query: {
@ -682,24 +854,16 @@ const openAddModal = async () => {
userModalApi.open();
};
const openCommandModal = (row: Record<string, any>) => {
const openCommandModal = async (row: Record<string, any>) => {
commandRow.value = row;
//
commandProperties.value = [
{
id: `prop-${Date.now()}`,
propertyId: '',
propertyName: '',
platformFieldName: '',
standardFieldName: '',
value: '',
},
];
thingModelProperties.value = [];
commandModalApi.open();
};
//
const openBindModal = async (rowOrRows?: Record<string, any> | Array<Record<string, any>>) => {
const openBindModal = async (
rowOrRows?: Array<Record<string, any>> | Record<string, any>,
) => {
//
let selectedRows: Array<Record<string, any>> = [];
@ -749,7 +913,7 @@ const openBindModal = async (rowOrRows?: Record<string, any> | Array<Record<stri
if (selectedRows.length > 1) {
const productIds = selectedRows
.map((row) => row.ioTPlatformProductId)
.filter((id) => id); //
.filter(Boolean); //
if (productIds.length === 0) {
Message.error('选中的设备中没有有效的产品ID无法进行绑定');
@ -759,7 +923,9 @@ const openBindModal = async (rowOrRows?: Record<string, any> | Array<Record<stri
// ID
const uniqueProductIds = [...new Set(productIds)];
if (uniqueProductIds.length > 1) {
Message.error(`选中的设备包含 ${uniqueProductIds.length} 种不同的产品,请只选择同一种产品的设备进行批量绑定`);
Message.error(
`选中的设备包含 ${uniqueProductIds.length} 种不同的产品,请只选择同一种产品的设备进行批量绑定`,
);
return;
}
}
@ -773,7 +939,9 @@ const openBindModal = async (rowOrRows?: Record<string, any> | Array<Record<stri
console.log('设置表单值产品ID:', productId, '类型:', typeof productId);
if (!productId) {
console.warn('警告:设备行数据中没有 ioTPlatformProductId 字段,请检查表格数据是否包含该字段');
console.warn(
'警告:设备行数据中没有 ioTPlatformProductId 字段,请检查表格数据是否包含该字段',
);
}
bindModalApi.open();
@ -787,83 +955,107 @@ const openBindModal = async (rowOrRows?: Record<string, any> | Array<Record<stri
const formValues = await bindFormApi.getValues();
console.log('表单值设置完成,当前表单值:', formValues);
console.log('表单值中的 _ioTPlatformProductId:', formValues._ioTPlatformProductId);
console.log(
'表单值中的 _ioTPlatformProductId:',
formValues._ioTPlatformProductId,
);
};
//
async function submitCommand() {
// -
const invalidProperties = commandProperties.value.filter(
(p) => !p.propertyId || !p.value || p.value.trim() === '',
);
if (invalidProperties.length > 0) {
Message.error('请完整填写所有属性名称和指令值');
//
const sendCommand = async (property: ThingModelProperty) => {
if (!property.commandValue || property.commandValue.trim() === '') {
Message.warning('请输入指令内容');
return;
}
// -
const validProperties = commandProperties.value.filter(
(p) => {
const key = p.platformFieldName || p.standardFieldName || p.propertyName;
return key && p.value && p.value.trim() !== '';
},
);
if (validProperties.length === 0) {
Message.error('请至少添加一个有效的属性-值对');
if (!property.ioTPlatformRawFieldName) {
Message.warning('该属性缺少平台字段名,无法发送指令');
return;
}
property.loading = true;
try {
commandModalApi.setState({ loading: true, confirmLoading: true });
// -
const commandContent: Record<string, any> = {};
commandProperties.value.forEach((prop) => {
if (prop.value && prop.value.trim() !== '') {
// 使使使
const key = prop.platformFieldName || prop.standardFieldName || prop.propertyName;
if (key) {
commandContent[key] = prop.value;
}
}
});
commandContent[property.ioTPlatformRawFieldName] = property.commandValue;
//
const result = await postAggregationDeviceDeviceCommandForApiAsync({
body: {
id: commandRow.value.id,
commandContent: commandContent,
commandContent,
},
});
if (result.data) {
// result
property.result =
typeof result.data === 'string'
? result.data
: (result.data
? JSON.stringify(result.data)
: '指令下发成功');
Message.success('指令下发成功');
commandModalApi.close();
commandRow.value = {};
//
commandProperties.value = [
{
id: `prop-${Date.now()}`,
propertyId: '',
propertyName: '',
platformFieldName: '',
standardFieldName: '',
value: '',
},
];
} else {
property.result = '指令下发失败';
Message.error('指令下发失败');
}
} catch (error) {
console.error('指令发送失败:', error);
property.result = `指令下发失败: ${(error as Error).message}`;
Message.error('指令下发失败');
} finally {
commandModalApi.setState({ loading: false, confirmLoading: false });
property.loading = false;
}
};
//
const readData = async (property: ThingModelProperty) => {
if (!property.ioTPlatformRawFieldName) {
Message.warning('该属性缺少平台字段名,无法抄读数据');
return;
}
if (!commandRow.value.id) {
Message.warning('设备ID不存在无法抄读数据');
return;
}
property.loading = true;
try {
const result = await postAggregationDeviceGetDevicePropertyValueForApiAsync(
{
body: {
id: String(commandRow.value.id),
propertyList: [property.ioTPlatformRawFieldName],
},
},
);
if (result.data !== undefined && result.data !== null) {
//
const propertyValue = (result.data as any)[
property.ioTPlatformRawFieldName
];
if (propertyValue !== undefined && propertyValue !== null) {
property.result =
typeof propertyValue === 'string'
? propertyValue
: JSON.stringify(propertyValue);
Message.success('抄读数据成功');
} else {
property.result = '未获取到数据';
Message.warning('未获取到该属性的数据');
}
} else {
property.result = '抄读数据失败';
}
} catch (error) {
console.error('抄读数据失败:', error);
property.result = `抄读数据失败: ${(error as Error).message}`;
} finally {
property.loading = false;
}
};
//
async function submitBindDeviceThingModel() {
const formValues = await bindFormApi.getValues();
@ -885,9 +1077,7 @@ async function submitBindDeviceThingModel() {
}
// ID
const deviceIds = bindRows.value
.map((row) => row.id)
.filter((id) => id); //
const deviceIds = bindRows.value.map((row) => row.id).filter(Boolean); //
if (deviceIds.length === 0) {
Message.error('选中的设备中没有有效的设备ID');
@ -914,7 +1104,6 @@ async function submitBindDeviceThingModel() {
}
} catch (error) {
console.error('绑定设备端物模型失败:', error);
Message.error('绑定设备端物模型失败');
} finally {
bindModalApi.setState({ loading: false, confirmLoading: false });
}
@ -1168,7 +1357,10 @@ const openBatchBindModal = async () => {
console.log('通过 grid.getCheckboxRecords 获取:', checkboxRecords);
} else if (typeof gridInstance.getCheckboxReserveRecords === 'function') {
checkboxRecords = gridInstance.getCheckboxReserveRecords();
console.log('通过 grid.getCheckboxReserveRecords 获取:', checkboxRecords);
console.log(
'通过 grid.getCheckboxReserveRecords 获取:',
checkboxRecords,
);
}
// 访
@ -1182,7 +1374,10 @@ const openBatchBindModal = async () => {
const store = gridInstance.$store;
if (store.checkboxRecords) {
checkboxRecords = store.checkboxRecords;
console.log('通过 grid.$store.checkboxRecords 获取:', checkboxRecords);
console.log(
'通过 grid.$store.checkboxRecords 获取:',
checkboxRecords,
);
}
}
}
@ -1192,12 +1387,18 @@ const openBatchBindModal = async () => {
const store = gridApi.store as any;
if (store.checkboxRecords) {
checkboxRecords = store.checkboxRecords;
console.log('通过 gridApi.store.checkboxRecords 获取:', checkboxRecords);
console.log(
'通过 gridApi.store.checkboxRecords 获取:',
checkboxRecords,
);
}
}
// 3 gridApi
if (checkboxRecords.length === 0 && typeof (gridApi as any).getCheckboxRecords === 'function') {
if (
checkboxRecords.length === 0 &&
typeof (gridApi as any).getCheckboxRecords === 'function'
) {
checkboxRecords = (gridApi as any).getCheckboxRecords();
console.log('通过 gridApi.getCheckboxRecords 获取:', checkboxRecords);
}
@ -1213,7 +1414,7 @@ const openBatchBindModal = async () => {
//
const productIds = checkboxRecords
.map((row) => row.ioTPlatformProductId)
.filter((id) => id); //
.filter(Boolean); //
if (productIds.length === 0) {
Message.error('选中的设备中没有有效的产品ID无法进行绑定');
@ -1223,7 +1424,9 @@ const openBatchBindModal = async () => {
// ID
const uniqueProductIds = [...new Set(productIds)];
if (uniqueProductIds.length > 1) {
Message.error(`选中的设备包含 ${uniqueProductIds.length} 种不同的产品,请只选择同一种产品的设备进行批量绑定`);
Message.error(
`选中的设备包含 ${uniqueProductIds.length} 种不同的产品,请只选择同一种产品的设备进行批量绑定`,
);
return;
}
@ -1338,10 +1541,10 @@ const toolbarActions = computed(() => [
{{ $t('abp.deviceInfos.viewData') }}
</Button>
<Button size="small" type="link" @click="openCommandModal.bind(null, row)()">
{{ $t('abp.IoTDBBase.Command') }}
设备操作
</Button>
<Button size="small" type="link" @click="openBindModal.bind(null, row)()">
绑定设备端物模型
绑定物模型
</Button>
<Popover trigger="hover" placement="bottomRight" :overlay-style="{ minWidth: '120px' }">
<template #content>
@ -1376,93 +1579,20 @@ const toolbarActions = computed(() => [
<UserModal :title="editRow.id ? $t('common.edit') : $t('common.add')" class="w-[800px]">
<component :is="editRow.id ? EditForm : AddForm" />
</UserModal>
<CommandModal :title="$t('abp.IoTDBBase.Command')" class="w-[800px]">
<div class="command-form-container">
<div
v-for="(property, index) in commandProperties"
:key="property.id"
class="command-property-item"
style="margin-bottom: 16px; padding: 16px; border: 1px solid #d9d9d9; border-radius: 4px; background: #fafafa"
>
<div class="flex items-center justify-between mb-2">
<span class="text-sm font-medium" style="color: #666">
属性-值对 {{ index + 1 }}
</span>
<Button
v-if="commandProperties.length > 1"
type="text"
danger
size="small"
@click="removeCommandProperty(property.id)"
>
<template #icon>
<Icon icon="ant-design:delete-outlined" />
</template>
删除
</Button>
</div>
<div class="flex gap-4" style="align-items: flex-start">
<div style="flex: 1; min-width: 0; max-width: 50%">
<label class="block mb-2 text-sm" style="color: #333">
平台物模型属性 <span style="color: #ff4d4f">*</span>
</label>
<IoTPlatformThingModelDataSelect
:value="property.propertyId"
:ioTPlatform="
commandRow.ioTPlatform
? Number.parseInt(String(commandRow.ioTPlatform))
: null
"
:ioTPlatformProductId="commandRow.ioTPlatformProductId || null"
placeholder="请选择物模型属性"
style="width: 100%"
@update:value="
(val) => {
property.propertyId = val || '';
}
"
@item-change="(item) => handlePropertyChange(item, property)"
/>
</div>
<div style="flex: 1; min-width: 0">
<label class="block mb-2 text-sm" style="color: #333">
指令值 <span style="color: #ff4d4f">*</span>
</label>
<Input
v-model:value="property.value"
placeholder="请输入指令值"
style="width: 100%"
/>
</div>
</div>
<div v-if="property.propertyName" class="mt-2 text-xs" style="color: #999">
已选择属性: {{ property.propertyName }}
<span v-if="property.platformFieldName" style="margin-left: 8px; color: #666">
(平台字段: {{ property.platformFieldName }})
</span>
</div>
</div>
<div class="mt-4">
<Button type="dashed" block @click="addCommandProperty">
<template #icon>
<Icon icon="ant-design:plus-outlined" />
</template>
新增属性-值对
</Button>
</div>
<CommandModal title="设备操作" class="w-[1200px]">
<div v-if="thingModelLoading" style="padding: 40px; text-align: center">
<Loading :loading="true" tip="加载物模型属性中..." />
</div>
<Table v-else :columns="commandTableColumns" :data-source="thingModelProperties" :pagination="false"
:scroll="{ x: 1000, y: 500 }" row-key="id" size="small" />
</CommandModal>
<BindModal :title="`绑定设备端物模型${bindRows.length > 0 ? ` (已选择 ${bindRows.length} 个设备)` : ''}`" class="w-[600px]">
<div v-if="bindRows.length > 0" class="mb-4 p-3 bg-blue-50 rounded border border-blue-200">
<div class="text-sm font-medium text-blue-800 mb-2">
<div v-if="bindRows.length > 0" class="mb-4 rounded border border-blue-200 bg-blue-50 p-3">
<div class="mb-2 text-sm font-medium text-blue-800">
已选择 {{ bindRows.length }} 个设备
</div>
<div class="text-xs text-blue-600 max-h-32 overflow-y-auto">
<div
v-for="(row, index) in bindRows"
:key="row.id || index"
class="mb-1"
>
<div class="max-h-32 overflow-y-auto text-xs text-blue-600">
<div v-for="(row, index) in bindRows" :key="row.id || index" class="mb-1">
{{ index + 1 }}. {{ row.deviceName || row.deviceAddress || row.id }}
</div>
</div>

View File

@ -193,7 +193,7 @@ export const tableSchema: any = computed((): VxeGridProps['columns'] => [
title: $t('common.action'),
field: 'action',
fixed: 'right',
width: '280',
width: '260',
slots: { default: 'action' },
},
]);
@ -330,10 +330,12 @@ export const addDeviceFormSchema: any = computed(() => [
const accountId = formValues?.ioTPlatformAccountId;
return {
api: platform && accountId
api:
platform && accountId
? postAggregationIoTplatformGetIoTplatformProductInfoAsync
: null,
params: platform && accountId
params:
platform && accountId
? {
body: {
ioTPlatformType:
@ -586,10 +588,12 @@ export const editDeviceFormSchemaEdit: any = computed(() => [
const accountId = formValues?.ioTPlatformAccountId;
return {
api: platform && accountId
api:
platform && accountId
? postAggregationIoTplatformGetIoTplatformProductInfoAsync
: null,
params: platform && accountId
params:
platform && accountId
? {
body: {
ioTPlatformType:
@ -784,10 +788,12 @@ export const batchAddDeviceFormSchema: any = computed(() => [
const accountId = formValues?.ioTPlatformAccountId;
return {
api: platform && accountId
api:
platform && accountId
? postAggregationIoTplatformGetIoTplatformProductInfoAsync
: null,
params: platform && accountId
params:
platform && accountId
? {
body: {
ioTPlatformType: