完善设备操作和设备端物模型管理

This commit is contained in:
ChenYi 2026-01-28 15:05:30 +08:00
parent 9197411c7b
commit 45055cdfef
9 changed files with 170 additions and 49 deletions

View File

@ -507,7 +507,7 @@ export const BindingDeviceThingModelInputSchema = {
description: '设备数据Id集合',
nullable: true
},
isNeedConfigDevicMdoel: {
isNeedConfigDeviceModel: {
type: 'boolean',
description: '是否需要配置设备模型默认false'
},
@ -2413,7 +2413,7 @@ export const DeviceManagementInfoDtoSchema = {
nullable: true,
readOnly: true
},
isNeedConfigDevicMdoel: {
isNeedConfigDeviceModel: {
type: 'boolean',
description: '是否需要配置设备模型默认false'
},
@ -2443,6 +2443,15 @@ export const DeviceManagementInfoDtoSchema = {
type: 'integer',
description: '子设备容量',
format: 'int32'
},
readingMode: {
'$ref': '#/components/schemas/DeviceReadingModeEnum'
},
readingModeName: {
type: 'string',
description: '抄读模式,JiShe.ServicePro.Core.DeviceReadingModeEnum',
nullable: true,
readOnly: true
}
},
additionalProperties: false
@ -2494,6 +2503,14 @@ export const DevicePropertyValueForApiInputSchema = {
description: '设备属性抄读'
} as const;
export const DeviceReadingModeEnumSchema = {
enum: [1, 2],
type: 'integer',
description: '设备抄读模式',
format: 'int32',
'说明:': '标准模式=1,特殊模式=2'
} as const;
export const DeviceSourceTypeEnumSchema = {
enum: [1, 2, 3, 4, 5, 6],
type: 'integer',
@ -2771,6 +2788,10 @@ export const DeviceThingModelCommandInfoDtoSchema = {
},
description: '指令设备端物模型的属性名称集合,JSON格式字符串数组一个指令的返回报文包含多个属性标识的数据',
nullable: true
},
isEnable: {
type: 'boolean',
description: '是否启用'
}
},
additionalProperties: false,
@ -3797,7 +3818,7 @@ export const DeviceUpgradeResultTypeEnumSchema = {
type: 'integer',
description: '设备升级结果枚举',
format: 'int32',
'说明:': 'Default=0,Success=1,InvalidUrlOrModuleUrlFetchFailed=1001,DownloadTimeout=2001,ParseHttpDlFileFailedOrDownloadIncomplete=2002,DownloadSizeMismatch=2003,ModuleReadTimeout=2004,FilePointerException=2005,ExternalFlashWriteFailed=3001,ExternalFlashCrc16Mismatch=3002,SignatureVerificationFailed=4001,Crc32Mismatch=5001,DiffDecompressOrPatchFailed=6001,RestoredFirmwareSizeExceededOrTargetAddressInBootArea=6002,IapPowerAbnormal=6003'
'说明:': '默认值=0,升级成功=1,URL 为空/长度非法、模块侧下载前取 URL 失败直接退出下载线程=1001,HTTP 下载未在超时时间内完成会放弃升级并退出下载状态=2001,解析 +MHTTPDLFILE 失败或下载未完成会失败并重试,最终失败会退出=2002,下载返回的文件总长度与期望不一致会退出=2003,本地从模组读取文件超时=2004,文件读写指针异常=2005,HTTP 分块写入外部存储失败时返回=3001,读/写外部 Flash 做 CRC16 校验,不一致返回=3002,对生成的签名和云端下发的签名进行验证,如果不同则直接退出=4001,下载完成后计算 CRC32 与升级包中的 CRC32 不一致退出=5001,差分解压/补丁失败、字典超限、压缩类型不支持时=6001,还原后固件大小超限或目标地址落入 BOOT 区会终止 IAP=6002,IAP 读写外部 Flash 或串口收发时检测到供电异常会复位退出升级=6003'
} as const;
export const DeviceUpgradeSourceTypeEnumSchema = {
@ -10883,11 +10904,11 @@ export const TenantDtoPagedResultDtoSchema = {
} as const;
export const ThingModelIdentifierTypeEnumSchema = {
enum: [1, 2],
enum: [1, 2, 3],
type: 'integer',
description: '设备物模型标识符类型枚举',
format: 'int32',
'说明:': '数据标识符=1,属性标识符=2'
'说明:': '唯一标识=1,数据标识符=2,属性标识符=3'
} as const;
export const TimeZoneSchema = {

File diff suppressed because one or more lines are too long

View File

@ -199,7 +199,7 @@ export type BindingDeviceThingModelInput = {
/**
* false
*/
isNeedConfigDevicMdoel?: boolean;
isNeedConfigDeviceModel?: boolean;
/**
* Id
*/
@ -1341,7 +1341,7 @@ export type DeviceManagementInfoDto = {
/**
* false
*/
isNeedConfigDevicMdoel?: boolean;
isNeedConfigDeviceModel?: boolean;
/**
* Id
*/
@ -1362,6 +1362,11 @@ export type DeviceManagementInfoDto = {
*
*/
subDeviceCapacity?: number;
readingMode?: DeviceReadingModeEnum;
/**
* ,JiShe.ServicePro.Core.DeviceReadingModeEnum
*/
readonly readingModeName?: (string) | null;
};
export type DeviceManagementInfoDtoPagedResultDto = {
@ -1385,6 +1390,11 @@ export type DevicePropertyValueForApiInput = {
propertyList: Array<(string)>;
};
/**
*
*/
export type DeviceReadingModeEnum = 1 | 2;
/**
*
*/
@ -1566,6 +1576,10 @@ export type DeviceThingModelCommandInfoDto = {
* ,JSON格式字符串数组
*/
propertyArray?: Array<(string)> | null;
/**
*
*/
isEnable?: boolean;
};
export type DeviceThingModelCommandInfoDtoPagedResultDto = {
@ -5964,7 +5978,7 @@ export type TenantDtoPagedResultDto = {
/**
*
*/
export type ThingModelIdentifierTypeEnum = 1 | 2;
export type ThingModelIdentifierTypeEnum = 1 | 2 | 3;
export type TimeZone = {
iana?: IanaTimeZone;
@ -6898,7 +6912,9 @@ export type PostDeviceThingModelManagementMessageAnalysisTestAsyncData = {
};
export type PostDeviceThingModelManagementMessageAnalysisTestAsyncResponse = ({
[key: string]: unknown;
[key: string]: {
[key: string]: unknown;
};
});
export type PostDeviceThingModelManagementMessageAnalysisTestAsyncError = unknown;
@ -7033,6 +7049,16 @@ export type PostDeviceThingModelManagementCommandFindByIdAsyncResponse = (Device
export type PostDeviceThingModelManagementCommandFindByIdAsyncError = unknown;
export type PostDeviceThingModelManagementUpdateCommandStatusByIdAsyncData = {
query?: {
input?: IdInput;
};
};
export type PostDeviceThingModelManagementUpdateCommandStatusByIdAsyncResponse = (boolean);
export type PostDeviceThingModelManagementUpdateCommandStatusByIdAsyncError = unknown;
export type PostDeviceThingModelManagementCommandPageAsyncData = {
query?: {
input?: DeviceThingModelCommandInfoPageInput;

View File

@ -218,7 +218,8 @@
"LastOfflineTime": "LastOfflineTime",
"deviceInfoManage": "DeviceInfoManage",
"thingModelInfoManage": "ThingModelInfoManage",
"isNeedConfigDevicMdoel": "IsNeedConfigDevicMdoel",
"isNeedConfigDeviceModel": "IsNeedConfigDeviceModel",
"readingMode": "ReadingMode",
"deviceThingModelName": "ThingModelInfoManage"
},
"thingModelInfos": {

View File

@ -211,7 +211,8 @@
"LastOfflineTime": "最后离线时间",
"deviceInfoManage": "设备管理",
"thingModelInfoManage": "物模型管理",
"isNeedConfigDevicMdoel": "是否绑定设备模型",
"isNeedConfigDeviceModel": "是否绑定设备模型",
"readingMode": "抄读模式",
"deviceThingModelName": "设备物模型名称"
},
"thingModelInfos": {

View File

@ -349,10 +349,10 @@ const commandTableColumns = computed(() => [
if (record.accessMode === 'r') {
return h('span', { style: { color: '#999' } }, '只读属性,无法设置指令');
}
const dataType = record.ioTPlatformRawFieldDataType;
const isNumeric = isNumericType(dataType);
return h(Input, {
value: record.commandValue,
placeholder: isNumeric ? '请输入数字' : '请输入指令内容',
@ -407,7 +407,7 @@ const commandTableColumns = computed(() => [
fixed: 'right' as const,
customRender: ({ record }: any) => {
const buttons = [];
// rww
if (record.accessMode === 'rw' || record.accessMode === 'w') {
buttons.push(
@ -423,7 +423,7 @@ const commandTableColumns = computed(() => [
),
);
}
// rrww
if (record.accessMode === 'r' || record.accessMode === 'rw') {
buttons.push(
@ -438,7 +438,7 @@ const commandTableColumns = computed(() => [
),
);
}
return h(
'div',
{
@ -794,8 +794,8 @@ const [BindForm, bindFormApi] = useVbenForm({
handleValuesChange: async (values, changedFields) => {
//
if (
changedFields.includes('isNeedConfigDevicMdoel') &&
values.isNeedConfigDevicMdoel
changedFields.includes('isNeedConfigDeviceModel') &&
values.isNeedConfigDeviceModel
) {
console.log('开关打开,准备触发设备物模型下拉框重新加载');
await nextTick();
@ -1491,19 +1491,19 @@ const openBindModal = async (
//
// 使
let isNeedConfigDevicMdoel = false;
let isNeedConfigDeviceModel = false;
let deviceThingModelDataId: string | undefined;
if (selectedRows.length === 1) {
const row = selectedRows[0] || {};
isNeedConfigDevicMdoel = !!row.isNeedConfigDevicMdoel;
isNeedConfigDeviceModel = !!row.isNeedConfigDeviceModel;
if (row.deviceThingModelDataId) {
deviceThingModelDataId = String(row.deviceThingModelDataId);
}
} else {
//
const allNeedConfig = selectedRows.every(
(row) => !!row.isNeedConfigDevicMdoel,
(row) => !!row.isNeedConfigDeviceModel,
);
const firstModelId = selectedRows[0]?.deviceThingModelDataId;
const allSameModel =
@ -1511,12 +1511,12 @@ const openBindModal = async (
!!firstModelId &&
selectedRows.every((row) => row.deviceThingModelDataId === firstModelId);
isNeedConfigDevicMdoel = allNeedConfig;
isNeedConfigDeviceModel = allNeedConfig;
deviceThingModelDataId = allSameModel ? String(firstModelId) : undefined;
}
await bindFormApi.setValues({
isNeedConfigDevicMdoel,
isNeedConfigDeviceModel,
deviceThingModelDataId,
_ioTPlatformProductId: productId ? String(productId) : undefined,
});
@ -1536,7 +1536,7 @@ const sendCommand = async (property: ThingModelProperty) => {
Message.warning('该属性为只读模式,无法发送指令');
return;
}
// wrw
if (!property.commandValue || property.commandValue.trim() === '') {
@ -1595,7 +1595,7 @@ const readData = async (property: ThingModelProperty) => {
Message.warning('该属性为只写模式,无法抄读数据');
return;
}
if (!property.ioTPlatformRawFieldName) {
Message.warning('该属性缺少平台字段名,无法抄读数据');
return;
@ -1635,9 +1635,23 @@ const readData = async (property: ThingModelProperty) => {
} else {
property.result = '抄读数据失败';
}
} catch (error) {
} catch (error: any) {
console.error('抄读数据失败:', error);
property.result = `抄读数据失败: ${(error as Error).message}`;
//
const backendMessage =
error?.response?.data?.error?.message ??
error?.data?.error?.message ??
error?.error?.message;
const finalMessage =
backendMessage && typeof backendMessage === 'string'
? backendMessage
: (error as Error).message || '抄读数据失败';
//
property.result = `抄读数据失败: ${finalMessage}`;
// Message.error
} finally {
property.loading = false;
}
@ -1670,7 +1684,7 @@ const submitBatchRead = async () => {
try {
commandModalApi.setState({ loading: true, confirmLoading: false });
//
selectedProperties.forEach((prop) => {
prop.loading = true;
@ -1727,7 +1741,7 @@ async function submitBindDeviceThingModel() {
const formValues = await bindFormApi.getValues();
//
const isNeedConfig = !!formValues.isNeedConfigDevicMdoel;
const isNeedConfig = !!formValues.isNeedConfigDeviceModel;
const deviceThingModelDataId = formValues.deviceThingModelDataId as
| string
| undefined;
@ -1755,7 +1769,7 @@ async function submitBindDeviceThingModel() {
const result = await postAggregationDeviceBindingDeviceThingModel({
body: {
devieDataIdList: deviceIds,
isNeedConfigDevicMdoel: isNeedConfig,
isNeedConfigDeviceModel: isNeedConfig,
deviceThingModelDataId: isNeedConfig ? deviceThingModelDataId : null,
},
});
@ -2302,7 +2316,7 @@ const toolbarActions = computed(() => [
</div>
<div class="ml-4 text-[11px] text-blue-700">
当前是否需要配置设备模型
{{ row.isNeedConfigDevicMdoel ? '是' : '否' }}
{{ row.isNeedConfigDeviceModel ? '是' : '否' }}
<span class="mx-1">|</span>
当前已绑定设备端物模型
{{ row.deviceThingModelName || '未绑定' }}

View File

@ -168,8 +168,8 @@ export const tableSchema: any = computed((): VxeGridProps['columns'] => [
},
},
{
field: 'isNeedConfigDevicMdoel',
title: $t('abp.deviceInfos.isNeedConfigDevicMdoel'),
field: 'isNeedConfigDeviceModel',
title: $t('abp.deviceInfos.isNeedConfigDeviceModel'),
minWidth: '150',
formatter: ({ cellValue }) => {
return cellValue ? '是' : '否';
@ -180,6 +180,11 @@ export const tableSchema: any = computed((): VxeGridProps['columns'] => [
title: $t('abp.deviceInfos.deviceThingModelName'),
minWidth: '150',
},
{
field: 'readingModeName',
title: $t('abp.deviceInfos.readingMode'),
minWidth: '150',
},
{
field: 'platformPassword',
title: $t('abp.deviceInfos.platformPassword'),
@ -393,7 +398,7 @@ export const bindDeviceThingModelFormSchema: any = computed(() => [
},
{
component: 'Switch',
fieldName: 'isNeedConfigDevicMdoel',
fieldName: 'isNeedConfigDeviceModel',
label: '是否需要配置设备模型',
defaultValue: false,
componentProps: {
@ -407,15 +412,15 @@ export const bindDeviceThingModelFormSchema: any = computed(() => [
// 仅在需要配置设备模型时显示
dependencies: {
show(values: any) {
const shouldShow = !!values.isNeedConfigDevicMdoel;
const shouldShow = !!values.isNeedConfigDeviceModel;
console.log('设备物模型下拉框显示检查:', {
isNeedConfigDevicMdoel: values.isNeedConfigDevicMdoel,
isNeedConfigDeviceModel: values.isNeedConfigDeviceModel,
shouldShow,
_ioTPlatformProductId: values._ioTPlatformProductId,
});
return shouldShow;
},
triggerFields: ['isNeedConfigDevicMdoel', '_ioTPlatformProductId'],
triggerFields: ['isNeedConfigDeviceModel', '_ioTPlatformProductId'],
},
// 选择设备端物模型
componentProps: (formValues: any) => {
@ -425,7 +430,7 @@ export const bindDeviceThingModelFormSchema: any = computed(() => [
console.log('设备物模型下拉框 componentProps 被调用:', {
formValues,
productId,
isNeedConfigDevicMdoel: formValues?.isNeedConfigDevicMdoel,
isNeedConfigDeviceModel: formValues?.isNeedConfigDeviceModel,
});
const config = {

View File

@ -230,13 +230,12 @@ const getUpgradeStatusColor = (status: string) => {
return statusMap[status] || 'default';
};
//
const getUpgradeResultColor = (result: string) => {
if (!result) return 'default';
if (result.includes('成功') || result.includes('完成')) return 'success';
if (result.includes('失败') || result.includes('错误')) return 'error';
if (result.includes('进行中') || result.includes('处理中')) return 'processing';
return 'default';
// upgradeResult 1 绿
const getUpgradeResultColor = (
upgradeResult: number | string | undefined | null,
) => {
if (upgradeResult == null || upgradeResult === '') return 'default';
return upgradeResult === 1 ? 'success' : 'error';
};
</script>
@ -264,7 +263,7 @@ const getUpgradeResultColor = (result: string) => {
:is="
h(
Tag,
{ color: getUpgradeResultColor(row.upgradeResultName || '') },
{ color: getUpgradeResultColor(row.upgradeResult) },
() => row.upgradeResultName || '-',
)
"

View File

@ -3,7 +3,7 @@ import { nextTick, ref } from 'vue';
import { useVbenModal, z } from '@vben/common-ui';
import { message as Message } from 'ant-design-vue';
import { message as Message, Tag } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
@ -14,6 +14,7 @@ import {
postDeviceThingModelManagementCommandPageAsync,
postDeviceThingModelManagementCommandUpdateAsync,
postDeviceThingModelManagementPropertyPageAsync,
postDeviceThingModelManagementUpdateCommandStatusByIdAsync,
} from '#/api-client';
import { TableAction } from '#/components/table-action';
import { $t } from '#/locales';
@ -70,10 +71,17 @@ const [Grid, gridApi] = useVbenVxeGrid({
return cellValue || '-';
},
},
{
field: 'isEnable',
title: '状态',
width: 90,
align: 'center',
slots: { default: 'isEnable' },
},
{
field: 'action',
title: $t('common.action'),
width: 200,
width: 190,
fixed: 'right',
slots: { default: 'action' },
},
@ -324,6 +332,30 @@ async function onDeleteCommand(record: any) {
}
}
// JSON { id, isEnable }
async function onUpdateCommandStatus(record: any) {
const nextEnable = !record.isEnable;
const action = nextEnable ? '启用' : '禁用';
try {
const resp =
await postDeviceThingModelManagementUpdateCommandStatusByIdAsync({
body: { id: record.id, isEnable: nextEnable } as any,
});
if (resp.data) {
Message.success(`${action}成功`);
await nextTick();
if (gridApi && gridApi.reload) {
await gridApi.reload();
}
} else {
Message.error(`${action}失败`);
}
} catch (error) {
console.error(`${action}指令状态失败:`, error);
Message.error(`${action}失败`);
}
}
const [Modal, modalApi] = useVbenModal({
onOpenChange(isOpen: boolean) {
if (isOpen) {
@ -356,8 +388,19 @@ const [Modal, modalApi] = useVbenModal({
]" />
</template>
<template #isEnable="{ row }">
<Tag v-if="row.isEnable" color="green">启用</Tag>
<Tag v-else color="red">禁用</Tag>
</template>
<template #action="{ row }">
<TableAction :actions="[
{
label: row.isEnable ? '禁用' : '启用',
type: 'link',
size: 'small',
style: row.isEnable ? { color: '#ff4d4f' } : { color: '#52c41a' },
onClick: onUpdateCommandStatus.bind(null, row),
},
{
label: $t('common.edit'),
type: 'link',
@ -370,6 +413,7 @@ const [Modal, modalApi] = useVbenModal({
icon: 'ant-design:delete-outlined',
type: 'link',
size: 'small',
style: { color: '#ff4d4f' },
auth: ['AbpIdentity.Users.Delete'],
popConfirm: {
title: $t('common.askConfirmDelete'),