迁移主站对接
This commit is contained in:
parent
5ac3633606
commit
a10acc6613
@ -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
@ -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;
|
||||||
|
|||||||
@ -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 / 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(() => [
|
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>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user