迁移主站对接
This commit is contained in:
parent
5ac3633606
commit
a10acc6613
@ -2231,6 +2231,35 @@ export const DeviceAuthenticationModeEnumSchema = {
|
||||
description: '设备鉴权模式'
|
||||
} 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 = {
|
||||
required: ['addressList', 'ioTPlatform', 'ioTPlatformProductId', 'nowFirmwareVersionDataId'],
|
||||
type: 'object',
|
||||
@ -2922,7 +2951,7 @@ export const DeviceManagementInfoDtoPagedResultDtoHttpDataResultSchema = {
|
||||
} as const;
|
||||
|
||||
export const DeviceMasterSwitchInputSchema = {
|
||||
required: ['id', 'ioTPlatformProductId'],
|
||||
required: ['id', 'ioTPlatformProductId', 'masterStationAddress'],
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
@ -2933,6 +2962,11 @@ export const DeviceMasterSwitchInputSchema = {
|
||||
minLength: 1,
|
||||
type: 'string',
|
||||
description: '物联网平台中对应的产品Id'
|
||||
},
|
||||
masterStationAddress: {
|
||||
minLength: 1,
|
||||
type: 'string',
|
||||
description: '目标主站地址(从主站下拉列表接口获取:OneNET 静态地址或 EMQX 配置地址)'
|
||||
}
|
||||
},
|
||||
additionalProperties: false,
|
||||
@ -7273,6 +7307,27 @@ export const LoginResultTypeSchema = {
|
||||
type: 'string'
|
||||
} 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 = {
|
||||
enum: ['Folder', 'Menu'],
|
||||
type: 'string',
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -1251,6 +1251,25 @@ export type DeleteTextTemplateInput = {
|
||||
*/
|
||||
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
|
||||
*/
|
||||
ioTPlatformProductId: string;
|
||||
/**
|
||||
* 目标主站地址(从主站下拉列表接口获取:OneNET 静态地址或 EMQX 配置地址)
|
||||
*/
|
||||
masterStationAddress: string;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -3948,6 +3971,21 @@ export type LoginOutput = {
|
||||
|
||||
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 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 = {
|
||||
query?: {
|
||||
input?: CreateDeviceFirmwareInfoInput;
|
||||
|
||||
@ -34,8 +34,10 @@ import {
|
||||
postAggregationDeviceBatchSyncDeviceFromOneNetAsync,
|
||||
postAggregationDeviceBindingDeviceThingModel,
|
||||
postAggregationDeviceCallDeviceServiceForApiAsync,
|
||||
getAggregationDeviceGetMasterStationListForApiAsync,
|
||||
postAggregationDeviceCreateAsync,
|
||||
postAggregationDeviceDeleteAsync,
|
||||
postAggregationDeviceDeviceBatchMasterSwitchForApiAsync,
|
||||
postAggregationDeviceDeviceBatchUpgradeForApiAsync,
|
||||
postAggregationDeviceDeviceCommandForApiAsync,
|
||||
postAggregationDeviceDeviceUpgradeForApiAsync,
|
||||
@ -2995,6 +2997,182 @@ const openBatchBindModal = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
// ============ 批量转主站 ============
|
||||
// 转主站仅支持 OneNET / EMQX(CTWing=1,OneNET=2,EMQX=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(() => [
|
||||
{
|
||||
@ -3035,6 +3213,13 @@ const toolbarActions = computed(() => [
|
||||
onClick: openBatchUpgradeModal,
|
||||
auth: ['AbpIdentity.Users.Create'],
|
||||
},
|
||||
{
|
||||
label: '批量转主站',
|
||||
type: 'default',
|
||||
icon: 'ant-design:swap-outlined',
|
||||
onClick: openBatchMasterSwitchModal,
|
||||
auth: ['AbpIdentity.Users.Create'],
|
||||
},
|
||||
{
|
||||
label: cacheRefreshLoading.value
|
||||
? $t('common.loading')
|
||||
@ -3543,5 +3728,44 @@ const [DeviceDataFlowModal, deviceDataFlowModalApi] = useVbenModal({
|
||||
</div>
|
||||
</template>
|
||||
</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>
|
||||
</template>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user