设备管理前端对接设备密钥更新与启停功能
This commit is contained in:
parent
b7b3620174
commit
cf3de1855d
@ -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
|
||||
|
||||
@ -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
@ -1313,6 +1313,12 @@ export type DeviceFirmwareInfoDtoPagedResultDto = {
|
||||
totalCount?: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* 设备生命周期类型(业务运行态,与物模型上报 DataType、OneNET 连接生命周期无关)。
|
||||
* 软件侧可见起点为 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);
|
||||
|
||||
@ -222,7 +222,8 @@
|
||||
"isNeedConfigDeviceModel": "IsNeedConfigDeviceModel",
|
||||
"readingMode": "ReadingMode",
|
||||
"deviceType": "DeviceType",
|
||||
"deviceThingModelName": "ThingModelInfoManage"
|
||||
"deviceThingModelName": "ThingModelInfoManage",
|
||||
"lifeInfoName": "Life Cycle"
|
||||
},
|
||||
"thingModelInfos": {
|
||||
"FiledType": "FiledType",
|
||||
|
||||
@ -215,7 +215,8 @@
|
||||
"isNeedConfigDeviceModel": "是否绑定设备模型",
|
||||
"deviceType": "设备类型",
|
||||
"readingMode": "抄读模式",
|
||||
"deviceThingModelName": "设备物模型名称"
|
||||
"deviceThingModelName": "设备物模型名称",
|
||||
"lifeInfoName": "生命周期"
|
||||
},
|
||||
"thingModelInfos": {
|
||||
"FiledType": "物模型类型",
|
||||
|
||||
@ -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]"
|
||||
|
||||
@ -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'),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user