平台端物模型导入导出

This commit is contained in:
ChenYi 2026-07-17 10:04:43 +08:00
parent 15cc3c7b14
commit 7d2fd92c32
4 changed files with 375 additions and 5 deletions

View File

@ -2489,7 +2489,7 @@ export const DeviceFirmwareInfoDtoPagedResultDtoSchema = {
} as const; } as const;
export const DeviceLifeTypeEnumSchema = { export const DeviceLifeTypeEnumSchema = {
enum: ['Unspecified', 'Production', 'FactoryTest', 'AgingSource', 'FactoryDelivery', 'Running', 'Maintenance', 'ReturnToFactory', 'Deactivated'], enum: ['Unspecified', 'Production', 'FactoryTest', 'AgingSource', 'FactoryDelivery', 'Running', 'Maintenance', 'ReturnToFactory', 'Deactivated', 'Banned'],
type: 'string', type: 'string',
description: `设备生命周期类型(业务运行态,与物模型上报 DataType、OneNET 连接生命周期无关)。 description: `设备生命周期类型(业务运行态,与物模型上报 DataType、OneNET 连接生命周期无关)。
JiShe.ServicePro.Enums.DeviceLifeTypeEnum.FactoryTest` JiShe.ServicePro.Enums.DeviceLifeTypeEnum.FactoryTest`
@ -2650,7 +2650,17 @@ export const DeviceManagementInfoDtoSchema = {
}, },
platformPassword: { platformPassword: {
type: 'string', type: 'string',
description: '物联网平台设备密码', description: '物联网平台设备密码(目标密钥:设备切到目标平台后应当使用的鉴权密钥)',
nullable: true
},
deviceEffectiveSecKey: {
type: 'string',
description: '设备当前实际持有的密钥(生效密钥),指令报文的 AES 加密与签名用它',
nullable: true
},
pendingSecKey: {
type: 'string',
description: '已下发、等待设备确认生效的目标密钥',
nullable: true nullable: true
}, },
ioTPlatformProductId: { ioTPlatformProductId: {
@ -2801,6 +2811,14 @@ export const DeviceManagementInfoDtoSchema = {
description: '最后主站地址', description: '最后主站地址',
nullable: true nullable: true
}, },
masterStationConfigs: {
type: 'array',
items: {
'$ref': '#/components/schemas/DeviceMasterStationEndpoint'
},
description: '本次下发的主站配置集合(主用 + 备用),记录每个端点的地址与所属平台。',
nullable: true
},
dataPushOverride: { dataPushOverride: {
type: 'boolean', type: 'boolean',
description: '是否覆盖产品级推送配置true 用本设备的推送配置false 继承所属产品配置' description: '是否覆盖产品级推送配置true 用本设备的推送配置false 继承所属产品配置'
@ -2951,6 +2969,26 @@ export const DeviceManagementInfoDtoPagedResultDtoHttpDataResultSchema = {
description: 'HTTP 请求返回结果' description: 'HTTP 请求返回结果'
} as const; } as const;
export const DeviceMasterStationEndpointSchema = {
type: 'object',
properties: {
masterStationAddress: {
type: 'string',
description: '主站地址host:port',
nullable: true
},
ioTPlatform: {
'$ref': '#/components/schemas/IoTPlatformTypeEnum'
}
},
additionalProperties: false,
description: `设备主站端点:一个主站的地址 + 所属物联网平台。
+
"主或备是否含 OneNET" OneNET
OneNET OneNET
JSON jsonb / DTO / `
} as const;
export const DeviceMasterSwitchInputSchema = { export const DeviceMasterSwitchInputSchema = {
required: ['backupMasterStation', 'id', 'ioTPlatformProductId', 'masterStation'], required: ['backupMasterStation', 'id', 'ioTPlatformProductId', 'masterStation'],
type: 'object', type: 'object',
@ -4479,6 +4517,23 @@ export const EntityExtensionDtoSchema = {
additionalProperties: false additionalProperties: false
} as const; } as const;
export const ExportPlatformThingModelInputSchema = {
required: ['ioTPlatformProductId'],
type: 'object',
properties: {
ioTPlatformProductId: {
minLength: 1,
type: 'string',
description: '平台产品ID'
},
platform: {
'$ref': '#/components/schemas/IoTPlatformTypeEnum'
}
},
additionalProperties: false,
description: '导出平台端物模型入参(单个产品)'
} as const;
export const ExtensionEnumDtoSchema = { export const ExtensionEnumDtoSchema = {
type: 'object', type: 'object',
properties: { properties: {
@ -6381,6 +6436,43 @@ export const IdentityUserUpdateDtoSchema = {
additionalProperties: false additionalProperties: false
} as const; } as const;
export const ImportPlatformThingModelResultDtoSchema = {
type: 'object',
properties: {
ioTPlatformProductId: {
type: 'string',
description: '导入目标产品ID',
nullable: true
},
sourceProductId: {
type: 'string',
description: '导入文件中记录的源产品ID',
nullable: true
},
thingModelCount: {
type: 'integer',
description: '导入的物模型标识符条数',
format: 'int32'
},
extensionCount: {
type: 'integer',
description: '导入的扩展子字段条数',
format: 'int32'
},
commandCount: {
type: 'integer',
description: '导入的默认操作指令条数',
format: 'int32'
},
success: {
type: 'boolean',
description: '是否导入成功(已覆盖数据库并刷新 Redis 缓存)'
}
},
additionalProperties: false,
description: '平台端物模型导入结果(单个产品)'
} as const;
export const InterfaceMethodApiDescriptionModelSchema = { export const InterfaceMethodApiDescriptionModelSchema = {
type: 'object', type: 'object',
properties: { properties: {

File diff suppressed because one or more lines are too long

View File

@ -1410,7 +1410,7 @@ export type DeviceFirmwareInfoDtoPagedResultDto = {
* DataTypeOneNET * DataTypeOneNET
* JiShe.ServicePro.Enums.DeviceLifeTypeEnum.FactoryTest * JiShe.ServicePro.Enums.DeviceLifeTypeEnum.FactoryTest
*/ */
export type DeviceLifeTypeEnum = 'Unspecified' | 'Production' | 'FactoryTest' | 'AgingSource' | 'FactoryDelivery' | 'Running' | 'Maintenance' | 'ReturnToFactory' | 'Deactivated'; export type DeviceLifeTypeEnum = 'Unspecified' | 'Production' | 'FactoryTest' | 'AgingSource' | 'FactoryDelivery' | 'Running' | 'Maintenance' | 'ReturnToFactory' | 'Deactivated' | 'Banned';
/** /**
* EMQX HTTP * EMQX HTTP
@ -1507,9 +1507,17 @@ export type DeviceManagementInfoDto = {
*/ */
ioTPlatformDeviceOpenInfo?: (string) | null; ioTPlatformDeviceOpenInfo?: (string) | null;
/** /**
* * 使
*/ */
platformPassword?: (string) | null; platformPassword?: (string) | null;
/**
* AES
*/
deviceEffectiveSecKey?: (string) | null;
/**
*
*/
pendingSecKey?: (string) | null;
/** /**
* Id * Id
*/ */
@ -1610,6 +1618,10 @@ export type DeviceManagementInfoDto = {
* *
*/ */
finalMainStation?: (string) | null; finalMainStation?: (string) | null;
/**
* +
*/
masterStationConfigs?: Array<DeviceMasterStationEndpoint> | null;
/** /**
* true false * true false
*/ */
@ -1713,6 +1725,21 @@ export type DeviceManagementInfoDtoPagedResultDtoHttpDataResult = {
total?: number; total?: number;
}; };
/**
* +
* +
* "主或备是否含 OneNET" OneNET
* OneNET OneNET
* JSON jsonb / DTO /
*/
export type DeviceMasterStationEndpoint = {
/**
* host:port
*/
masterStationAddress?: (string) | null;
ioTPlatform?: IoTPlatformTypeEnum;
};
/** /**
* *
*/ */
@ -2685,6 +2712,17 @@ export type EntityExtensionDto = {
} | null; } | null;
}; };
/**
*
*/
export type ExportPlatformThingModelInput = {
/**
* ID
*/
ioTPlatformProductId: string;
platform?: IoTPlatformTypeEnum;
};
export type ExtensionEnumDto = { export type ExtensionEnumDto = {
fields?: Array<ExtensionEnumFieldDto> | null; fields?: Array<ExtensionEnumFieldDto> | null;
localizationResource?: (string) | null; localizationResource?: (string) | null;
@ -3381,6 +3419,36 @@ export type IdInput = {
id: string; id: string;
}; };
/**
*
*/
export type ImportPlatformThingModelResultDto = {
/**
* ID
*/
ioTPlatformProductId?: (string) | null;
/**
* ID
*/
sourceProductId?: (string) | null;
/**
*
*/
thingModelCount?: number;
/**
*
*/
extensionCount?: number;
/**
*
*/
commandCount?: number;
/**
* Redis
*/
success?: boolean;
};
export type InterfaceMethodApiDescriptionModel = { export type InterfaceMethodApiDescriptionModel = {
name?: (string) | null; name?: (string) | null;
parametersOnMethod?: Array<MethodParameterApiDescriptionModel> | null; parametersOnMethod?: Array<MethodParameterApiDescriptionModel> | null;
@ -8187,6 +8255,42 @@ export type PostIoTplatformThingModelInfoCreateIoTplatformThingModelCommandRespo
export type PostIoTplatformThingModelInfoCreateIoTplatformThingModelCommandError = unknown; export type PostIoTplatformThingModelInfoCreateIoTplatformThingModelCommandError = unknown;
export type PostIoTplatformThingModelInfoExportPlatformThingModelData = {
query?: {
/**
* ID OneNET
*/
input?: ExportPlatformThingModelInput;
};
};
export type PostIoTplatformThingModelInfoExportPlatformThingModelResponse = ((Blob | File));
export type PostIoTplatformThingModelInfoExportPlatformThingModelError = unknown;
export type PostIoTplatformThingModelInfoImportPlatformThingModelData = {
body?: {
/**
* JSON
*/
file?: (Blob | File);
};
query?: {
/**
* ID
*/
ioTPlatformProductId?: string;
/**
* OneNET
*/
platform?: IoTPlatformTypeEnum;
};
};
export type PostIoTplatformThingModelInfoImportPlatformThingModelResponse = (ImportPlatformThingModelResultDto);
export type PostIoTplatformThingModelInfoImportPlatformThingModelError = unknown;
export type PostLanguagesAllResponse = (Array<PageLanguageOutput>); export type PostLanguagesAllResponse = (Array<PageLanguageOutput>);
export type PostLanguagesAllError = (RemoteServiceErrorResponse); export type PostLanguagesAllError = (RemoteServiceErrorResponse);

View File

@ -19,7 +19,9 @@ import {
postIoTplatformThingModelInfoCopyStandardThingModel, postIoTplatformThingModelInfoCopyStandardThingModel,
postIoTplatformThingModelInfoCreateAsync, postIoTplatformThingModelInfoCreateAsync,
postIoTplatformThingModelInfoDeleteAsync, postIoTplatformThingModelInfoDeleteAsync,
postIoTplatformThingModelInfoExportPlatformThingModel,
postIoTplatformThingModelInfoGetIoTplatformThingModelService, postIoTplatformThingModelInfoGetIoTplatformThingModelService,
postIoTplatformThingModelInfoImportPlatformThingModel,
postIoTplatformThingModelInfoPageAsync, postIoTplatformThingModelInfoPageAsync,
postIoTplatformThingModelInfoUpdateAsync, postIoTplatformThingModelInfoUpdateAsync,
} from '#/api-client'; } from '#/api-client';
@ -229,6 +231,11 @@ const editRow: Record<string, any> = ref({});
const hasData = ref(true); const hasData = ref(true);
// loading // loading
const thingModelRefreshLoading = ref(false); const thingModelRefreshLoading = ref(false);
// / loading
const exportLoading = ref(false);
const importLoading = ref(false);
// input
const importFileInputRef = ref<HTMLInputElement | null>(null);
const [ThingModelModal, thingModelModalApi] = useVbenModal({ const [ThingModelModal, thingModelModalApi] = useVbenModal({
draggable: true, draggable: true,
@ -1063,6 +1070,121 @@ async function onThingModelRefresh() {
} }
} }
// /
async function getCurrentPlatformAndProduct() {
const formValues = gridApi?.formApi ? await gridApi.formApi.getValues() : {};
const currentProductId = String(
formValues.ioTPlatformProductId || productId.value || '',
);
const platformNum =
Number.parseInt(
String(formValues.ioTPlatform || ioTPlatform.value || '2'),
10,
) || 2;
return { currentProductId, platformNum };
}
// JSON
async function onExportThingModel() {
const { currentProductId, platformNum } =
await getCurrentPlatformAndProduct();
if (!currentProductId) {
Message.error('请先选择产品再导出');
return;
}
if (!hasData.value) {
Message.error('当前产品暂无可导出的平台端物模型');
return;
}
exportLoading.value = true;
try {
const { data } = await postIoTplatformThingModelInfoExportPlatformThingModel(
{
// body query.input nswag
body: {
ioTPlatformProductId: currentProductId,
platform: platformNum,
},
responseType: 'blob',
},
);
const url = window.URL.createObjectURL(new Blob([data as Blob]));
const link = document.createElement('a');
link.href = url;
const now = new Date();
const pad = (n: number) => String(n).padStart(2, '0');
const stamp = `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
link.setAttribute(
'download',
`平台端物模型_${currentProductId}_${stamp}.json`,
);
document.body.append(link);
link.click();
link.remove();
window.URL.revokeObjectURL(url);
Message.success('导出成功');
} catch (error) {
console.error('导出平台端物模型失败:', error);
Message.error('导出失败,请确认该产品已配置平台端物模型');
} finally {
exportLoading.value = false;
}
}
//
function triggerImportThingModel() {
if (!productId.value) {
Message.error('请先选择导入目标产品');
return;
}
importFileInputRef.value?.click();
}
//
async function onImportThingModelFileChange(e: Event) {
const input = e.target as HTMLInputElement;
const file = input.files?.[0];
// input change
input.value = '';
if (!file) {
return;
}
const { currentProductId, platformNum } =
await getCurrentPlatformAndProduct();
if (!currentProductId) {
Message.error('请先选择导入目标产品');
return;
}
importLoading.value = true;
try {
const { data } = await postIoTplatformThingModelInfoImportPlatformThingModel(
{
query: {
ioTPlatformProductId: currentProductId,
platform: platformNum as any,
},
body: { file },
},
);
if (data?.success) {
Message.success(
`导入成功:物模型 ${data.thingModelCount ?? 0} 条、扩展 ${data.extensionCount ?? 0} 条、指令 ${data.commandCount ?? 0}`,
);
await gridApi.reload();
} else {
Message.error('导入失败');
}
} catch (error) {
//
console.error('导入平台端物模型失败:', error);
} finally {
importLoading.value = false;
}
}
// //
const openCopyAnotherThingModelModal = async () => { const openCopyAnotherThingModelModal = async () => {
console.log('打开复制模态框,当前参数:', { console.log('打开复制模态框,当前参数:', {
@ -1228,6 +1350,24 @@ async function onDel(record: any) {
disabled: !productId, disabled: !productId,
loading: thingModelRefreshLoading, loading: thingModelRefreshLoading,
}, },
{
label: '导出JSON',
type: 'default',
icon: 'ant-design:download-outlined',
onClick: onExportThingModel,
auth: ['AbpIdentity.Users.Create'],
disabled: !productId,
loading: exportLoading,
},
{
label: '导入JSON',
type: 'default',
icon: 'ant-design:upload-outlined',
onClick: triggerImportThingModel,
auth: ['AbpIdentity.Users.Create'],
disabled: !productId,
loading: importLoading,
},
]" /> ]" />
</template> </template>
@ -1300,6 +1440,15 @@ async function onDel(record: any) {
]" /> ]" />
</template> </template>
</Grid> </Grid>
<!-- 隐藏的导入文件选择框 -->
<input
ref="importFileInputRef"
type="file"
accept=".json,application/json"
style="display: none"
@change="onImportThingModelFileChange"
/>
<ThingModelModal :title="editRow.id ? $t('common.edit') : $t('common.add')" class="w-[800px]"> <ThingModelModal :title="editRow.id ? $t('common.edit') : $t('common.add')" class="w-[800px]">
<component :is="editRow.id ? EditForm : AddForm" /> <component :is="editRow.id ? EditForm : AddForm" />
</ThingModelModal> </ThingModelModal>