平台端物模型导入导出

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;
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',
description: `设备生命周期类型(业务运行态,与物模型上报 DataType、OneNET 连接生命周期无关)。
JiShe.ServicePro.Enums.DeviceLifeTypeEnum.FactoryTest`
@ -2650,7 +2650,17 @@ export const DeviceManagementInfoDtoSchema = {
},
platformPassword: {
type: 'string',
description: '物联网平台设备密码',
description: '物联网平台设备密码(目标密钥:设备切到目标平台后应当使用的鉴权密钥)',
nullable: true
},
deviceEffectiveSecKey: {
type: 'string',
description: '设备当前实际持有的密钥(生效密钥),指令报文的 AES 加密与签名用它',
nullable: true
},
pendingSecKey: {
type: 'string',
description: '已下发、等待设备确认生效的目标密钥',
nullable: true
},
ioTPlatformProductId: {
@ -2801,6 +2811,14 @@ export const DeviceManagementInfoDtoSchema = {
description: '最后主站地址',
nullable: true
},
masterStationConfigs: {
type: 'array',
items: {
'$ref': '#/components/schemas/DeviceMasterStationEndpoint'
},
description: '本次下发的主站配置集合(主用 + 备用),记录每个端点的地址与所属平台。',
nullable: true
},
dataPushOverride: {
type: 'boolean',
description: '是否覆盖产品级推送配置true 用本设备的推送配置false 继承所属产品配置'
@ -2951,6 +2969,26 @@ export const DeviceManagementInfoDtoPagedResultDtoHttpDataResultSchema = {
description: 'HTTP 请求返回结果'
} 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 = {
required: ['backupMasterStation', 'id', 'ioTPlatformProductId', 'masterStation'],
type: 'object',
@ -4479,6 +4517,23 @@ export const EntityExtensionDtoSchema = {
additionalProperties: false
} 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 = {
type: 'object',
properties: {
@ -6381,6 +6436,43 @@ export const IdentityUserUpdateDtoSchema = {
additionalProperties: false
} 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 = {
type: 'object',
properties: {

File diff suppressed because one or more lines are too long

View File

@ -1410,7 +1410,7 @@ export type DeviceFirmwareInfoDtoPagedResultDto = {
* DataTypeOneNET
* 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
@ -1507,9 +1507,17 @@ export type DeviceManagementInfoDto = {
*/
ioTPlatformDeviceOpenInfo?: (string) | null;
/**
*
* 使
*/
platformPassword?: (string) | null;
/**
* AES
*/
deviceEffectiveSecKey?: (string) | null;
/**
*
*/
pendingSecKey?: (string) | null;
/**
* Id
*/
@ -1610,6 +1618,10 @@ export type DeviceManagementInfoDto = {
*
*/
finalMainStation?: (string) | null;
/**
* +
*/
masterStationConfigs?: Array<DeviceMasterStationEndpoint> | null;
/**
* true false
*/
@ -1713,6 +1725,21 @@ export type DeviceManagementInfoDtoPagedResultDtoHttpDataResult = {
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;
};
/**
*
*/
export type ExportPlatformThingModelInput = {
/**
* ID
*/
ioTPlatformProductId: string;
platform?: IoTPlatformTypeEnum;
};
export type ExtensionEnumDto = {
fields?: Array<ExtensionEnumFieldDto> | null;
localizationResource?: (string) | null;
@ -3381,6 +3419,36 @@ export type IdInput = {
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 = {
name?: (string) | null;
parametersOnMethod?: Array<MethodParameterApiDescriptionModel> | null;
@ -8187,6 +8255,42 @@ export type PostIoTplatformThingModelInfoCreateIoTplatformThingModelCommandRespo
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 PostLanguagesAllError = (RemoteServiceErrorResponse);

View File

@ -19,7 +19,9 @@ import {
postIoTplatformThingModelInfoCopyStandardThingModel,
postIoTplatformThingModelInfoCreateAsync,
postIoTplatformThingModelInfoDeleteAsync,
postIoTplatformThingModelInfoExportPlatformThingModel,
postIoTplatformThingModelInfoGetIoTplatformThingModelService,
postIoTplatformThingModelInfoImportPlatformThingModel,
postIoTplatformThingModelInfoPageAsync,
postIoTplatformThingModelInfoUpdateAsync,
} from '#/api-client';
@ -229,6 +231,11 @@ const editRow: Record<string, any> = ref({});
const hasData = ref(true);
// loading
const thingModelRefreshLoading = ref(false);
// / loading
const exportLoading = ref(false);
const importLoading = ref(false);
// input
const importFileInputRef = ref<HTMLInputElement | null>(null);
const [ThingModelModal, thingModelModalApi] = useVbenModal({
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 () => {
console.log('打开复制模态框,当前参数:', {
@ -1228,6 +1350,24 @@ async function onDel(record: any) {
disabled: !productId,
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>
@ -1300,6 +1440,15 @@ async function onDel(record: any) {
]" />
</template>
</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]">
<component :is="editRow.id ? EditForm : AddForm" />
</ThingModelModal>