设备管理前端对接设备密钥更新与启停功能

This commit is contained in:
ChenYi 2026-06-10 16:56:31 +08:00
parent b7b3620174
commit cf3de1855d
8 changed files with 324 additions and 16 deletions

View File

@ -25,7 +25,7 @@ VITE_ARCHIVER=true
VITE_REFRESH_ROLE = true
# 后端接口地址
VITE_APP_API_ADDRESS=http://192.168.111.221:28080/
VITE_APP_API_ADDRESS=http://192.168.111.174:28080/
# websocket地址
VITE_WEBSOCKET_URL=http://192.168.111.221:28080/signalr/notification
VITE_WEBSOCKET_URL=http://192.168.111.174:28080/signalr/notification

View File

@ -2347,6 +2347,13 @@ export const DeviceFirmwareInfoDtoPagedResultDtoSchema = {
additionalProperties: false
} as const;
export const DeviceLifeTypeEnumSchema = {
enum: ['Unspecified', 'Production', 'FactoryTest', 'AgingSource', 'FactoryDelivery', 'Running', 'Maintenance', 'ReturnToFactory', 'Deactivated'],
type: 'string',
description: `设备生命周期类型(业务运行态,与物模型上报 DataType、OneNET 连接生命周期无关)。
JiShe.ServicePro.Enums.DeviceLifeTypeEnum.FactoryTest`
} as const;
export const DeviceLoginModelSchema = {
required: ['clientid', 'password', 'username'],
type: 'object',
@ -2358,14 +2365,14 @@ export const DeviceLoginModelSchema = {
},
deviceAddress: {
type: 'string',
description: '原始的设备地址',
description: '原始的设备地址clientid 去掉 productId 前缀)',
nullable: true,
readOnly: true
},
username: {
minLength: 1,
type: 'string',
description: '登录用户名也就是OneNET平台的产品名称'
description: '登录用户名也就是OneNET平台的产品ID'
},
password: {
minLength: 1,
@ -2639,6 +2646,20 @@ export const DeviceManagementInfoDtoSchema = {
type: 'boolean',
description: '是否需要设备生产计划,默认不需要,1表示需要',
nullable: true
},
lifeInfo: {
'$ref': '#/components/schemas/DeviceLifeTypeEnum'
},
lifeInfoName: {
type: 'string',
description: '设备生命周期,JiShe.ServicePro.Enums.DeviceLifeTypeEnum',
nullable: true,
readOnly: true
},
finalMainStation: {
type: 'string',
description: '最后主站地址',
nullable: true
}
},
additionalProperties: false
@ -4125,6 +4146,23 @@ export const DownloadFileObjectInputSchema = {
additionalProperties: false
} as const;
export const EnableDeviceForApiInputSchema = {
required: ['id'],
type: 'object',
properties: {
id: {
type: 'string',
format: 'uuid'
},
enable: {
type: 'boolean',
description: '设备启停状态true-启用false-停用'
}
},
additionalProperties: false,
description: '设备启停入参'
} as const;
export const EnabledTwoFactorInputSchema = {
required: ['code', 'secret'],
type: 'object',
@ -11676,6 +11714,24 @@ export const UpdateDeviceFirmwareInfoInputSchema = {
description: '更新固件信息'
} as const;
export const UpdateDeviceSecKeyForApiInputSchema = {
required: ['id'],
type: 'object',
properties: {
id: {
type: 'string',
format: 'uuid'
},
secKey: {
type: 'string',
description: '设备接入鉴权密码明文长度不超过32个字符缺省时由服务端自动生成',
nullable: true
}
},
additionalProperties: false,
description: '更新设备密码入参'
} as const;
export const UpdateFeatureDtoSchema = {
type: 'object',
properties: {

File diff suppressed because one or more lines are too long

View File

@ -1313,6 +1313,12 @@ export type DeviceFirmwareInfoDtoPagedResultDto = {
totalCount?: number;
};
/**
* DataTypeOneNET
* JiShe.ServicePro.Enums.DeviceLifeTypeEnum.FactoryTest
*/
export type DeviceLifeTypeEnum = 'Unspecified' | 'Production' | 'FactoryTest' | 'AgingSource' | 'FactoryDelivery' | 'Running' | 'Maintenance' | 'ReturnToFactory' | 'Deactivated';
/**
*
*/
@ -1322,11 +1328,11 @@ export type DeviceLoginModel = {
*/
clientid: string;
/**
*
* clientid productId
*/
readonly deviceAddress?: (string) | null;
/**
* OneNET平台的产品名称
* OneNET平台的产品ID
*/
username: string;
/**
@ -1502,6 +1508,15 @@ export type DeviceManagementInfoDto = {
* ,,1
*/
needEquipmentProductionPlan?: (boolean) | null;
lifeInfo?: DeviceLifeTypeEnum;
/**
* ,JiShe.ServicePro.Enums.DeviceLifeTypeEnum
*/
readonly lifeInfoName?: (string) | null;
/**
*
*/
finalMainStation?: (string) | null;
};
export type DeviceManagementInfoDtoPagedResultDto = {
@ -2446,6 +2461,17 @@ export type DownloadFileObjectInput = {
id?: string;
};
/**
*
*/
export type EnableDeviceForApiInput = {
id: string;
/**
* true-false-
*/
enable?: boolean;
};
export type EnabledTwoFactorInput = {
/**
*
@ -6473,6 +6499,17 @@ export type UpdateDeviceFirmwareInfoInput = {
id: string;
};
/**
*
*/
export type UpdateDeviceSecKeyForApiInput = {
id: string;
/**
* 32
*/
secKey?: (string) | null;
};
export type UpdateFeatureDto = {
name?: (string) | null;
value?: (string) | null;
@ -7117,6 +7154,26 @@ export type PostAggregationDeviceRepushDeviceInfoToIoTplatformResponse = (Device
export type PostAggregationDeviceRepushDeviceInfoToIoTplatformError = unknown;
export type PostAggregationDeviceEnableDeviceForApiAsyncData = {
query?: {
input?: EnableDeviceForApiInput;
};
};
export type PostAggregationDeviceEnableDeviceForApiAsyncResponse = (boolean);
export type PostAggregationDeviceEnableDeviceForApiAsyncError = unknown;
export type PostAggregationDeviceUpdateDeviceSecKeyForApiAsyncData = {
query?: {
input?: UpdateDeviceSecKeyForApiInput;
};
};
export type PostAggregationDeviceUpdateDeviceSecKeyForApiAsyncResponse = (DeviceManagementInfoDto);
export type PostAggregationDeviceUpdateDeviceSecKeyForApiAsyncError = unknown;
export type PostAggregationDeviceDeviceCommandForApiAsyncData = {
query?: {
input?: DeviceCommandForApiInput;
@ -7306,12 +7363,7 @@ export type PostDeviceInfoCacheDeviceDataToRedisResponse = (boolean);
export type PostDeviceInfoCacheDeviceDataToRedisError = unknown;
export type PostDeviceInfoMqttDeviceLoginData = {
query?: {
/**
*
*/
loginModel?: DeviceLoginModel;
};
body?: DeviceLoginModel;
};
export type PostDeviceInfoMqttDeviceLoginResponse = (DeviceLoginResult);
@ -8237,6 +8289,10 @@ export type GetOneNetServiceCheckLocalMemroyResponse = (unknown);
export type GetOneNetServiceCheckLocalMemroyError = unknown;
export type PostOneNetServiceDataServerReceiveEmqxDataResponse = (unknown);
export type PostOneNetServiceDataServerReceiveEmqxDataError = unknown;
export type PostOrganizationUnitsTreeResponse = (Array<TreeOutput>);
export type PostOrganizationUnitsTreeError = (RemoteServiceErrorResponse);

View File

@ -222,7 +222,8 @@
"isNeedConfigDeviceModel": "IsNeedConfigDeviceModel",
"readingMode": "ReadingMode",
"deviceType": "DeviceType",
"deviceThingModelName": "ThingModelInfoManage"
"deviceThingModelName": "ThingModelInfoManage",
"lifeInfoName": "Life Cycle"
},
"thingModelInfos": {
"FiledType": "FiledType",

View File

@ -215,7 +215,8 @@
"isNeedConfigDeviceModel": "是否绑定设备模型",
"deviceType": "设备类型",
"readingMode": "抄读模式",
"deviceThingModelName": "设备物模型名称"
"deviceThingModelName": "设备物模型名称",
"lifeInfoName": "生命周期"
},
"thingModelInfos": {
"FiledType": "物模型类型",

View File

@ -36,8 +36,10 @@ import {
postAggregationDeviceDeviceBatchUpgradeForApiAsync,
postAggregationDeviceDeviceCommandForApiAsync,
postAggregationDeviceDeviceUpgradeForApiAsync,
postAggregationDeviceEnableDeviceForApiAsync,
postAggregationDeviceGetDevicePropertyValueForApiAsync,
postAggregationDeviceRepushDeviceInfoToIoTplatform,
postAggregationDeviceUpdateDeviceSecKeyForApiAsync,
postDeviceInfoCacheDeviceDataToRedis,
postDeviceInfoPage,
postIoTplatformThingModelInfoGetIoTplatformThingModelService,
@ -317,6 +319,17 @@ const pageLoading = ref(false);
const loadingTip = ref('缓存刷新中...');
const commandRow: Record<string, any> = ref({});
/** 设备生命周期8 表示停用模式 */
const DEVICE_STOPPED_LIFE_INFO = 8;
function isDeviceInStoppedState(row: Record<string, any>): boolean {
return Number(row?.lifeInfo) === DEVICE_STOPPED_LIFE_INFO;
}
function setDeviceLifeInfo(row: Record<string, any>, lifeInfo: number) {
row.lifeInfo = lifeInfo;
}
//
const upgradeRow: Record<string, any> = ref({});
@ -720,6 +733,119 @@ const [CommandModal, commandModalApi] = useVbenModal({
//
const commandModalState = commandModalApi.useStore();
const deviceEnableLoading = ref(false);
//
const handleDeviceEnable = () => {
if (!commandRow.value.id) {
Message.warning('设备信息无效');
return;
}
const isCurrentlyStopped = isDeviceInStoppedState(commandRow.value);
const nextEnable = isCurrentlyStopped;
const actionText = nextEnable ? '启用' : '停用';
const deviceLabel =
commandRow.value.deviceName || commandRow.value.deviceAddress || '';
Modal.confirm({
title: `确认${actionText}设备?`,
content: `设备:${deviceLabel}`,
okText: actionText,
cancelText: $t('common.cancel'),
onOk: async () => {
try {
deviceEnableLoading.value = true;
commandModalApi.setState({ loading: true });
const result = await postAggregationDeviceEnableDeviceForApiAsync({
body: {
id: commandRow.value.id,
enable: nextEnable,
},
});
if (result.data) {
setDeviceLifeInfo(
commandRow.value,
nextEnable ? 5 : DEVICE_STOPPED_LIFE_INFO,
);
Message.success(`设备已${actionText}`);
gridApi.reload();
} else {
Message.error(`${actionText}失败`);
}
} catch (error) {
console.error('设备启停失败:', error);
Message.error(`${actionText}失败`);
} finally {
deviceEnableLoading.value = false;
commandModalApi.setState({ loading: false });
}
},
});
};
//
const updatePasswordSecKey = ref('');
const submitUpdateDevicePassword = async () => {
if (!commandRow.value.id) {
Message.warning('设备信息无效');
return false;
}
const secKey = updatePasswordSecKey.value.trim();
if (secKey.length > 32) {
Message.warning('密码长度不能超过32个字符');
return false;
}
try {
updatePasswordModalApi.setState({ loading: true, confirmLoading: true });
const result = await postAggregationDeviceUpdateDeviceSecKeyForApiAsync({
body: {
id: commandRow.value.id,
secKey: secKey || undefined,
},
});
if (result.data) {
if (result.data.platformPassword) {
commandRow.value.platformPassword = result.data.platformPassword;
}
Message.success('密码更新成功');
updatePasswordModalApi.close();
gridApi.reload();
return true;
}
Message.error('密码更新失败');
return false;
} catch (error) {
console.error('更新设备密码失败:', error);
Message.error('密码更新失败');
return false;
} finally {
updatePasswordModalApi.setState({ loading: false, confirmLoading: false });
}
};
const [UpdatePasswordModal, updatePasswordModalApi] = useVbenModal({
draggable: true,
title: '更新设备密码',
onConfirm: submitUpdateDevicePassword,
onBeforeClose: () => {
updatePasswordSecKey.value = '';
return true;
},
});
const openUpdatePasswordModal = () => {
if (!commandRow.value.id) {
Message.warning('设备信息无效');
return;
}
updatePasswordSecKey.value = '';
updatePasswordModalApi.open();
};
// ---------- ----------
type ServiceParamField = { name: string; label: string };
@ -2865,6 +2991,12 @@ const toolbarActions = computed(() => [
)
" />
</template>
<template #lifeInfoName="{ row }">
<component :is="h(Tag, { color: Number(row.lifeInfo) === DEVICE_STOPPED_LIFE_INFO ? 'red' : 'green' }, () =>
row.lifeInfoName || '-',
)
" />
</template>
<template #action="{ row }">
<div style="display: flex; gap: 8px; align-items: center">
@ -2946,6 +3078,12 @@ const toolbarActions = computed(() => [
<Button @click="commandModalApi.close()">
{{ $t('common.cancel') }}
</Button>
<Button danger :loading="deviceEnableLoading" @click="handleDeviceEnable">
启停
</Button>
<Button danger @click="openUpdatePasswordModal">
更新密码
</Button>
<Button type="default" :loading="commandModalState?.loading" @click="submitBatchRead">
批量抄读
</Button>
@ -2955,6 +3093,26 @@ const toolbarActions = computed(() => [
</div>
</template>
</CommandModal>
<UpdatePasswordModal class="w-[480px]">
<div class="space-y-3">
<div class="text-sm text-gray-500">
设备{{ commandRow.deviceName || commandRow.deviceAddress || '-' }}
</div>
<div class="flex items-center gap-2">
<span class="w-20 flex-shrink-0 text-right text-sm text-gray-600">新密码</span>
<Input.Password
v-model:value="updatePasswordSecKey"
allow-clear
class="min-w-0 flex-1"
maxlength="32"
placeholder="留空则由服务端自动生成"
/>
</div>
<div class="text-xs text-gray-400">
设备接入鉴权密码明文长度不超过32个字符留空时由服务端自动生成
</div>
</div>
</UpdatePasswordModal>
<ServiceCallModal
:title="`服务调用 - ${serviceCallRow.deviceName || serviceCallRow.deviceAddress || ''}`"
class="w-[480px] max-w-[92vw]"

View File

@ -157,6 +157,12 @@ export const tableSchema: any = computed((): VxeGridProps['columns'] => [
minWidth: '150',
slots: { default: 'deviceOnlineStatusName' },
},
{
field: 'lifeInfoName',
title: $t('abp.deviceInfos.lifeInfoName'),
minWidth: '120',
slots: { default: 'lifeInfoName' },
},
{
field: 'lastOnlineTime',
title: $t('abp.deviceInfos.LastOnlineTime'),