迁移主站对接

This commit is contained in:
ChenYi 2026-07-03 14:46:27 +08:00
parent 5ac3633606
commit a10acc6613
4 changed files with 367 additions and 2 deletions

View File

@ -2231,6 +2231,35 @@ export const DeviceAuthenticationModeEnumSchema = {
description: '设备鉴权模式' description: '设备鉴权模式'
} as const; } as const;
export const DeviceBatchMasterSwitchInputSchema = {
required: ['addressList', 'ioTPlatform', 'ioTPlatformProductId', 'masterStationAddress'],
type: 'object',
properties: {
addressList: {
type: 'array',
items: {
type: 'string'
},
description: '设备地址列表'
},
ioTPlatform: {
'$ref': '#/components/schemas/IoTPlatformTypeEnum'
},
ioTPlatformProductId: {
minLength: 1,
type: 'string',
description: '物联网平台中对应的产品Id'
},
masterStationAddress: {
minLength: 1,
type: 'string',
description: '目标主站地址从主站下拉列表接口获取OneNET 静态地址或 EMQX 配置地址)'
}
},
additionalProperties: false,
description: '设备批量切换主站入参(只对同一平台、同一产品下的设备批量下发切换主站指令)'
} as const;
export const DeviceBatchUpgradeForApiInputSchema = { export const DeviceBatchUpgradeForApiInputSchema = {
required: ['addressList', 'ioTPlatform', 'ioTPlatformProductId', 'nowFirmwareVersionDataId'], required: ['addressList', 'ioTPlatform', 'ioTPlatformProductId', 'nowFirmwareVersionDataId'],
type: 'object', type: 'object',
@ -2922,7 +2951,7 @@ export const DeviceManagementInfoDtoPagedResultDtoHttpDataResultSchema = {
} as const; } as const;
export const DeviceMasterSwitchInputSchema = { export const DeviceMasterSwitchInputSchema = {
required: ['id', 'ioTPlatformProductId'], required: ['id', 'ioTPlatformProductId', 'masterStationAddress'],
type: 'object', type: 'object',
properties: { properties: {
id: { id: {
@ -2933,6 +2962,11 @@ export const DeviceMasterSwitchInputSchema = {
minLength: 1, minLength: 1,
type: 'string', type: 'string',
description: '物联网平台中对应的产品Id' description: '物联网平台中对应的产品Id'
},
masterStationAddress: {
minLength: 1,
type: 'string',
description: '目标主站地址从主站下拉列表接口获取OneNET 静态地址或 EMQX 配置地址)'
} }
}, },
additionalProperties: false, additionalProperties: false,
@ -7273,6 +7307,27 @@ export const LoginResultTypeSchema = {
type: 'string' type: 'string'
} as const; } as const;
export const MasterStationOutputSchema = {
type: 'object',
properties: {
masterName: {
type: 'string',
description: '主站显示名称',
nullable: true
},
masterStationAddress: {
type: 'string',
description: '主站地址(切换主站接口回传的目标地址)',
nullable: true
},
ioTPlatform: {
'$ref': '#/components/schemas/IoTPlatformTypeEnum'
}
},
additionalProperties: false,
description: '主站下拉项切换主站时可选的目标主站目前固定两项OneNET 静态地址、EMQX 配置地址)'
} as const;
export const MenuTypeSchema = { export const MenuTypeSchema = {
enum: ['Folder', 'Menu'], enum: ['Folder', 'Menu'],
type: 'string', type: 'string',

File diff suppressed because one or more lines are too long

View File

@ -1251,6 +1251,25 @@ export type DeleteTextTemplateInput = {
*/ */
export type DeviceAuthenticationModeEnum = 'Unspecified' | 'PerProductTypeSecret' | 'PerDeviceSecret'; export type DeviceAuthenticationModeEnum = 'Unspecified' | 'PerProductTypeSecret' | 'PerDeviceSecret';
/**
*
*/
export type DeviceBatchMasterSwitchInput = {
/**
*
*/
addressList: Array<(string)>;
ioTPlatform: IoTPlatformTypeEnum;
/**
* Id
*/
ioTPlatformProductId: string;
/**
* OneNET EMQX
*/
masterStationAddress: string;
};
/** /**
* *
*/ */
@ -1705,6 +1724,10 @@ export type DeviceMasterSwitchInput = {
* Id * Id
*/ */
ioTPlatformProductId: string; ioTPlatformProductId: string;
/**
* OneNET EMQX
*/
masterStationAddress: string;
}; };
/** /**
@ -3948,6 +3971,21 @@ export type LoginOutput = {
export type LoginResultType = 'Success' | 'InvalidUserNameOrPassword' | 'NotAllowed' | 'LockedOut' | 'RequiresTwoFactor'; export type LoginResultType = 'Success' | 'InvalidUserNameOrPassword' | 'NotAllowed' | 'LockedOut' | 'RequiresTwoFactor';
/**
* OneNET EMQX
*/
export type MasterStationOutput = {
/**
*
*/
masterName?: (string) | null;
/**
*
*/
masterStationAddress?: (string) | null;
ioTPlatform?: IoTPlatformTypeEnum;
};
/** /**
* *
*/ */
@ -7450,6 +7488,24 @@ export type PostAggregationDeviceDeviceMasterSwitchForApiAsyncResponse = (boolea
export type PostAggregationDeviceDeviceMasterSwitchForApiAsyncError = unknown; export type PostAggregationDeviceDeviceMasterSwitchForApiAsyncError = unknown;
export type PostAggregationDeviceDeviceBatchMasterSwitchForApiAsyncData = {
query?: {
input?: DeviceBatchMasterSwitchInput;
};
};
export type PostAggregationDeviceDeviceBatchMasterSwitchForApiAsyncResponse = (Array<(string)>);
export type PostAggregationDeviceDeviceBatchMasterSwitchForApiAsyncError = unknown;
export type GetAggregationDeviceGetMasterStationListForApiAsyncResponse = (Array<MasterStationOutput>);
export type GetAggregationDeviceGetMasterStationListForApiAsyncError = unknown;
export type PostAggregationDeviceGetMasterStationListForApiAsyncResponse = (Array<MasterStationOutput>);
export type PostAggregationDeviceGetMasterStationListForApiAsyncError = unknown;
export type PostFirmwareInfoCreateAsyncData = { export type PostFirmwareInfoCreateAsyncData = {
query?: { query?: {
input?: CreateDeviceFirmwareInfoInput; input?: CreateDeviceFirmwareInfoInput;

View File

@ -34,8 +34,10 @@ import {
postAggregationDeviceBatchSyncDeviceFromOneNetAsync, postAggregationDeviceBatchSyncDeviceFromOneNetAsync,
postAggregationDeviceBindingDeviceThingModel, postAggregationDeviceBindingDeviceThingModel,
postAggregationDeviceCallDeviceServiceForApiAsync, postAggregationDeviceCallDeviceServiceForApiAsync,
getAggregationDeviceGetMasterStationListForApiAsync,
postAggregationDeviceCreateAsync, postAggregationDeviceCreateAsync,
postAggregationDeviceDeleteAsync, postAggregationDeviceDeleteAsync,
postAggregationDeviceDeviceBatchMasterSwitchForApiAsync,
postAggregationDeviceDeviceBatchUpgradeForApiAsync, postAggregationDeviceDeviceBatchUpgradeForApiAsync,
postAggregationDeviceDeviceCommandForApiAsync, postAggregationDeviceDeviceCommandForApiAsync,
postAggregationDeviceDeviceUpgradeForApiAsync, postAggregationDeviceDeviceUpgradeForApiAsync,
@ -2995,6 +2997,182 @@ const openBatchBindModal = async () => {
} }
}; };
// ============ ============
// OneNET / EMQXCTWing=1OneNET=2EMQX=3
const MASTER_SWITCH_SUPPORTED_PLATFORMS = new Set([2, 3]);
const masterSwitchRows = ref<Array<Record<string, any>>>([]);
const masterStationOptions = ref<
Array<{ ioTPlatform?: number; label: string; value: string }>
>([]);
const masterStationLoading = ref(false);
const selectedMasterAddress = ref<string | undefined>(undefined);
//
function getGridCheckboxRecords(): any[] {
let checkboxRecords: any[] = [];
if (gridApi?.grid) {
const gridInstance = gridApi.grid as any;
if (typeof gridInstance.getCheckboxRecords === 'function') {
checkboxRecords = gridInstance.getCheckboxRecords();
} else if (gridInstance.checkboxRecords) {
checkboxRecords = gridInstance.checkboxRecords;
}
}
if (checkboxRecords.length === 0 && gridApi?.store) {
const store = gridApi.store as any;
if (store.checkboxRecords) {
checkboxRecords = store.checkboxRecords;
}
}
return checkboxRecords || [];
}
//
function toPlatformNumber(raw: unknown): number | undefined {
if (raw === undefined || raw === null || raw === '') {
return undefined;
}
const n = typeof raw === 'string' ? Number.parseInt(raw, 10) : Number(raw);
return Number.isNaN(n) ? undefined : n;
}
// OneNET EMQX
async function fetchMasterStationOptions() {
masterStationLoading.value = true;
try {
const { data } = await getAggregationDeviceGetMasterStationListForApiAsync();
const list = Array.isArray(data) ? data : [];
masterStationOptions.value = list
.filter((m) => m?.masterStationAddress)
.map((m) => ({
label: `${m.masterName ?? ''}${m.masterStationAddress}`,
value: String(m.masterStationAddress),
ioTPlatform: toPlatformNumber(m.ioTPlatform),
}));
} catch (error) {
console.error('获取主站下拉列表失败:', error);
Message.error('获取主站下拉列表失败');
masterStationOptions.value = [];
} finally {
masterStationLoading.value = false;
}
}
const [MasterSwitchModal, masterSwitchModalApi] = useVbenModal({
draggable: true,
onConfirm: submitBatchMasterSwitch,
onBeforeClose: () => {
masterSwitchRows.value = [];
selectedMasterAddress.value = undefined;
return true;
},
onOpenChange: async (isOpen: boolean) => {
if (isOpen) {
await fetchMasterStationOptions();
}
},
});
const masterSwitchModalState = masterSwitchModalApi.useStore();
//
function openBatchMasterSwitchModal() {
const checkboxRecords = getGridCheckboxRecords();
if (!checkboxRecords || checkboxRecords.length === 0) {
Message.warning(
'请先在表格中勾选要切换主站的设备(点击每行前面的复选框)',
);
return;
}
//
const productIds = checkboxRecords
.map((r) => r.ioTPlatformProductId)
.filter(Boolean);
if (productIds.length === 0) {
Message.error('选中的设备中没有有效的产品ID无法切换主站');
return;
}
if (new Set(productIds).size > 1) {
Message.error('选中的设备包含多种产品,请只选择同一产品的设备切换主站');
return;
}
// OneNET / EMQX
const platforms = checkboxRecords
.map((r) => toPlatformNumber(r.ioTPlatform))
.filter((p): p is number => p !== undefined);
const uniquePlatforms = [...new Set(platforms)];
if (uniquePlatforms.length > 1) {
Message.error('选中的设备包含多个物联网平台,请只选择同一平台的设备');
return;
}
if (
uniquePlatforms.length === 0 ||
!MASTER_SWITCH_SUPPORTED_PLATFORMS.has(uniquePlatforms[0]!)
) {
Message.warning('批量切换主站仅支持 OneNET / EMQX 平台的设备');
return;
}
masterSwitchRows.value = checkboxRecords;
selectedMasterAddress.value = undefined;
masterSwitchModalApi.open();
}
//
async function submitBatchMasterSwitch() {
const rows = masterSwitchRows.value;
if (!rows || rows.length === 0) {
Message.warning('没有选中的设备');
return;
}
if (!selectedMasterAddress.value) {
Message.warning('请选择目标主站');
return;
}
const addressList = rows.map((r) => r.deviceAddress).filter(Boolean);
if (addressList.length === 0) {
Message.error('选中的设备缺少设备地址,无法切换主站');
return;
}
const firstRow = rows[0]!;
const ioTPlatform = toPlatformNumber(firstRow.ioTPlatform);
try {
masterSwitchModalApi.setState({ loading: true, confirmLoading: true });
const { data } =
await postAggregationDeviceDeviceBatchMasterSwitchForApiAsync({
body: {
addressList,
ioTPlatform,
ioTPlatformProductId: String(firstRow.ioTPlatformProductId),
masterStationAddress: selectedMasterAddress.value,
},
});
const failedList = Array.isArray(data) ? data : [];
if (failedList.length === 0) {
Message.success(`批量切换主站指令已下发,共 ${addressList.length} 个设备`);
masterSwitchModalApi.close();
gridApi.reload();
} else {
Modal.warning({
title: '部分设备切换主站下发失败',
content: `成功 ${addressList.length - failedList.length} 个,失败 ${failedList.length} 个。失败设备地址:${failedList.join('、')}`,
});
}
} catch (error) {
console.error('批量切换主站失败:', error);
Message.error('批量切换主站失败');
} finally {
masterSwitchModalApi.setState({ loading: false, confirmLoading: false });
}
}
// //
const toolbarActions = computed(() => [ const toolbarActions = computed(() => [
{ {
@ -3035,6 +3213,13 @@ const toolbarActions = computed(() => [
onClick: openBatchUpgradeModal, onClick: openBatchUpgradeModal,
auth: ['AbpIdentity.Users.Create'], auth: ['AbpIdentity.Users.Create'],
}, },
{
label: '批量转主站',
type: 'default',
icon: 'ant-design:swap-outlined',
onClick: openBatchMasterSwitchModal,
auth: ['AbpIdentity.Users.Create'],
},
{ {
label: cacheRefreshLoading.value label: cacheRefreshLoading.value
? $t('common.loading') ? $t('common.loading')
@ -3543,5 +3728,44 @@ const [DeviceDataFlowModal, deviceDataFlowModalApi] = useVbenModal({
</div> </div>
</template> </template>
</BatchUpgradeModal> </BatchUpgradeModal>
<MasterSwitchModal title="批量切换主站" class="w-[560px]">
<div class="space-y-4 p-2">
<div class="text-sm text-gray-500">
已选设备<span class="font-medium text-gray-700">{{
masterSwitchRows.length
}}</span>
</div>
<div>
<div class="mb-2 text-sm">目标主站</div>
<Select
v-model:value="selectedMasterAddress"
class="w-full"
placeholder="请选择要切换到的目标主站"
:options="masterStationOptions"
:loading="masterStationLoading"
allow-clear
/>
</div>
<div class="text-xs text-gray-400">
切换主站指令将下发到所选设备指令下发成功不代表设备已完成切换最终以设备上报的主站切换事件为准
</div>
</div>
<template #footer>
<div class="flex w-full items-center justify-end gap-2">
<Button @click="masterSwitchModalApi.close()">
{{ $t('common.cancel') }}
</Button>
<Button
type="primary"
:loading="masterSwitchModalState?.confirmLoading"
:disabled="!selectedMasterAddress"
@click="submitBatchMasterSwitch"
>
{{ $t('common.confirm') }}
</Button>
</div>
</template>
</MasterSwitchModal>
</Page> </Page>
</template> </template>