Commit 66a595da8ba91b1ee121c854c7aff0927aa62be7
Merge branch 'feat/object-model' into 'main_dev'
feat:支持标准modbus_rtu解析 See merge request yunteng/thingskit-front!1209
Showing
56 changed files
with
2131 additions
and
1276 deletions
Too many changes to show.
To preserve performance only 56 of 58 files are displayed.
| 1 | -import { DataType } from '../../device/model/modelOfMatterModel'; | |
| 2 | -import { DataTypeEnum } from '/@/enums/objectModelEnum'; | |
| 1 | +import { RpcCommandType } from '../../device/model/deviceConfigModel'; | |
| 2 | +import { DataType, ExtensionDesc } from '../../device/model/modelOfMatterModel'; | |
| 3 | +import { DataTypeEnum, ObjectModelAccessModeEnum } from '/@/enums/objectModelEnum'; | |
| 3 | 4 | import { DataSource } from '/@/views/visual/palette/types'; |
| 4 | 5 | |
| 5 | 6 | export interface AddDataBoardParams { |
| ... | ... | @@ -154,12 +155,14 @@ export interface DeviceAttributeParams { |
| 154 | 155 | export interface DeviceAttributeRecord { |
| 155 | 156 | name: string; |
| 156 | 157 | identifier: string; |
| 158 | + accessMode: ObjectModelAccessModeEnum; | |
| 157 | 159 | detail: { dataType: DataType }; |
| 160 | + extensionDesc?: ExtensionDesc; | |
| 158 | 161 | } |
| 159 | 162 | |
| 160 | 163 | export interface SendCommandParams { |
| 161 | 164 | deviceId: string; |
| 162 | - value: any; | |
| 165 | + value: RpcCommandType; | |
| 163 | 166 | } |
| 164 | 167 | |
| 165 | 168 | export interface GetMeetTheConditionsDeviceParams { | ... | ... |
| 1 | +import { ProfileData } from './deviceModel'; | |
| 1 | 2 | import { BasicPageParams } from '/@/api/model/baseModel'; |
| 2 | 3 | import { CommandTypeEnum, RPCCommandMethodEnum } from '/@/enums/deviceEnum'; |
| 3 | 4 | |
| ... | ... | @@ -103,13 +104,6 @@ export interface ProvisionConfiguration { |
| 103 | 104 | provisionDeviceSecret?: any; |
| 104 | 105 | } |
| 105 | 106 | |
| 106 | -export interface ProfileData { | |
| 107 | - configuration: Configuration; | |
| 108 | - transportConfiguration: TransportConfiguration; | |
| 109 | - provisionConfiguration: ProvisionConfiguration; | |
| 110 | - alarms: any; | |
| 111 | -} | |
| 112 | - | |
| 113 | 107 | export interface ProfileRecord { |
| 114 | 108 | id: string; |
| 115 | 109 | creator: string; |
| ... | ... | @@ -168,22 +162,7 @@ export interface DeviceProfileDetail { |
| 168 | 162 | type: string; |
| 169 | 163 | deviceCount: number; |
| 170 | 164 | default: boolean; |
| 171 | -} | |
| 172 | - | |
| 173 | -export interface ProfileData { | |
| 174 | - configuration: Configuration; | |
| 175 | - transportConfiguration: TransportConfiguration; | |
| 176 | - provisionConfiguration: ProvisionConfiguration; | |
| 177 | - alarms: any; | |
| 178 | - thingsModel: any; | |
| 179 | -} | |
| 180 | - | |
| 181 | -export interface Configuration { | |
| 182 | - type: string; | |
| 183 | -} | |
| 184 | - | |
| 185 | -export interface TransportConfiguration { | |
| 186 | - type: string; | |
| 165 | + ifShowClass?: boolean; | |
| 187 | 166 | } |
| 188 | 167 | |
| 189 | 168 | export interface RpcCommandType { | ... | ... |
| 1 | -import { StructJSON } from './modelOfMatterModel'; | |
| 1 | +import { DataType, ExtensionDesc, ModelOfMatterParams } from './modelOfMatterModel'; | |
| 2 | 2 | import { BasicPageParams } from '/@/api/model/baseModel'; |
| 3 | 3 | import { AlarmStatus } from '/@/enums/alarmEnum'; |
| 4 | +import { TCPProtocolTypeEnum } from '/@/enums/deviceEnum'; | |
| 4 | 5 | import { DeviceStatusEnum } from '/@/views/rule/dataFlow/cpns/config'; |
| 5 | 6 | export enum DeviceState { |
| 6 | 7 | INACTIVE = 'INACTIVE', |
| ... | ... | @@ -60,6 +61,7 @@ export interface DeviceProfileModel { |
| 60 | 61 | transportType: string; |
| 61 | 62 | createTime: string; |
| 62 | 63 | description: string; |
| 64 | + profileData: ProfileData; | |
| 63 | 65 | } |
| 64 | 66 | |
| 65 | 67 | export type ChildDeviceParams = BasicPageParams & { |
| ... | ... | @@ -148,6 +150,7 @@ export interface TransportConfiguration { |
| 148 | 150 | |
| 149 | 151 | // TCP |
| 150 | 152 | scriptId: string; |
| 153 | + protocol?: TCPProtocolTypeEnum; | |
| 151 | 154 | } |
| 152 | 155 | |
| 153 | 156 | export interface ProvisionConfiguration { |
| ... | ... | @@ -160,6 +163,7 @@ export interface ProfileData { |
| 160 | 163 | transportConfiguration: TransportConfiguration; |
| 161 | 164 | provisionConfiguration: ProvisionConfiguration; |
| 162 | 165 | alarms?: any; |
| 166 | + thingsModel?: ModelOfMatterParams[]; | |
| 163 | 167 | } |
| 164 | 168 | |
| 165 | 169 | export interface DeviceRecord { |
| ... | ... | @@ -176,7 +180,6 @@ export interface DeviceRecord { |
| 176 | 180 | deviceCount: number; |
| 177 | 181 | tbDeviceId: string; |
| 178 | 182 | tbProfileId: string; |
| 179 | - profileData: ProfileData; | |
| 180 | 183 | defaultQueueName: string; |
| 181 | 184 | image: string; |
| 182 | 185 | type: string; |
| ... | ... | @@ -192,6 +195,7 @@ export interface DeviceRecord { |
| 192 | 195 | default: boolean; |
| 193 | 196 | name: string; |
| 194 | 197 | transportType: string; |
| 198 | + profileData: ProfileData; | |
| 195 | 199 | }; |
| 196 | 200 | customerAdditionalInfo?: { |
| 197 | 201 | isPublic?: boolean; |
| ... | ... | @@ -203,9 +207,10 @@ export interface DeviceModelOfMatterAttrs { |
| 203 | 207 | name: string; |
| 204 | 208 | identifier: string; |
| 205 | 209 | accessMode: string; |
| 206 | - detail: StructJSON; | |
| 207 | - deviceDetail?: DeviceRecord; | |
| 208 | - extensionDesc?: any; | |
| 210 | + detail: { | |
| 211 | + dataType: DataType; | |
| 212 | + }; | |
| 213 | + extensionDesc?: ExtensionDesc; | |
| 209 | 214 | } |
| 210 | 215 | |
| 211 | 216 | export interface DeviceStateLogModel { | ... | ... |
| 1 | -import { DataTypeEnum } from '/@/enums/objectModelEnum'; | |
| 1 | +import { | |
| 2 | + DataTypeEnum, | |
| 3 | + ExtendDescOperationTypeEnum, | |
| 4 | + OriginalDataTypeEnum, | |
| 5 | +} from '/@/enums/objectModelEnum'; | |
| 2 | 6 | import { FunctionTypeEnum } from '/@/enums/objectModelEnum'; |
| 3 | 7 | |
| 4 | 8 | export interface Specs { |
| ... | ... | @@ -27,10 +31,14 @@ export interface DataType { |
| 27 | 31 | } |
| 28 | 32 | |
| 29 | 33 | export interface ExtensionDesc { |
| 30 | - zoomFactor?: number; | |
| 31 | - actionType?: string; | |
| 32 | - dataType: string; | |
| 33 | - registerAddress: number; | |
| 34 | + writeOnly?: boolean; | |
| 35 | + bitMask?: number; | |
| 36 | + operationType: ExtendDescOperationTypeEnum; | |
| 37 | + originalDataType: OriginalDataTypeEnum; | |
| 38 | + registerAddress: string; | |
| 39 | + scaling?: number; | |
| 40 | + valueRange?: Record<'min' | 'max', number>; | |
| 41 | + registerCount?: number; | |
| 34 | 42 | } |
| 35 | 43 | |
| 36 | 44 | export interface StructJSON { |
| ... | ... | @@ -64,7 +72,7 @@ export interface ModelOfMatterParams { |
| 64 | 72 | } |
| 65 | 73 | |
| 66 | 74 | export interface GetModelTslParams { |
| 67 | - functionType: FunctionTypeEnum; | |
| 75 | + functionType?: FunctionTypeEnum; | |
| 68 | 76 | deviceProfileId: string; |
| 69 | 77 | ifShowClass?: string | Boolean; |
| 70 | 78 | } |
| ... | ... | @@ -108,7 +116,7 @@ export interface Tsl { |
| 108 | 116 | specs?: { |
| 109 | 117 | dataType: DataType; |
| 110 | 118 | }; |
| 111 | - extensionDesc: ExtensionDesc; | |
| 119 | + extensionDesc?: ExtensionDesc; | |
| 112 | 120 | eventType?: string; |
| 113 | 121 | outputData?: StructJSON[]; |
| 114 | 122 | callType?: string; | ... | ... |
| ... | ... | @@ -5,6 +5,7 @@ import { |
| 5 | 5 | ImportModelOfMatterType, |
| 6 | 6 | ModelOfMatterItemRecordType, |
| 7 | 7 | ModelOfMatterParams, |
| 8 | + Tsl, | |
| 8 | 9 | } from './model/modelOfMatterModel'; |
| 9 | 10 | import { FunctionTypeEnum } from '/@/enums/objectModelEnum'; |
| 10 | 11 | import { defHttp } from '/@/utils/http/axios'; |
| ... | ... | @@ -26,7 +27,7 @@ enum ModelOfMatter { |
| 26 | 27 | CATEGORY_EXPORT = '/things_model/categoryGetExport', |
| 27 | 28 | |
| 28 | 29 | IMPORT_CSV = '/things_model/csvImport', |
| 29 | - EXCEL_EXPORT = '/things_model/downloadTemplate', | |
| 30 | + EXCEL_EXPORT = '/things_model/download/template', | |
| 30 | 31 | |
| 31 | 32 | BATCH_GET_TSL_BY_DEVICE_PROFILES = '/things_model/batch/get_tsl', |
| 32 | 33 | } |
| ... | ... | @@ -47,8 +48,8 @@ export const getModelList = ( |
| 47 | 48 | }; |
| 48 | 49 | |
| 49 | 50 | export const getModelTsl = (params: GetModelTslParams) => { |
| 50 | - const { functionType, deviceProfileId } = params; | |
| 51 | - return defHttp.get({ | |
| 51 | + const { functionType = 'all', deviceProfileId } = params; | |
| 52 | + return defHttp.get<Tsl[]>({ | |
| 52 | 53 | url: `${ModelOfMatter.TSL}/${functionType}/${deviceProfileId}`, |
| 53 | 54 | }); |
| 54 | 55 | }; |
| ... | ... | @@ -149,11 +150,12 @@ export const importCsvCategory = (params: { |
| 149 | 150 | categoryId?: string; |
| 150 | 151 | deviceProfileId?: string; |
| 151 | 152 | file: FormData; |
| 153 | + type?: string; | |
| 152 | 154 | }) => { |
| 153 | - const { categoryId } = params || {}; | |
| 155 | + const { categoryId, type } = params || {}; | |
| 154 | 156 | |
| 155 | 157 | return defHttp.post<any>({ |
| 156 | - url: `${ModelOfMatter.IMPORT_CSV}?categoryId=${categoryId}`, | |
| 158 | + url: `${ModelOfMatter.IMPORT_CSV}?categoryId=${categoryId}&type=${type}`, | |
| 157 | 159 | params: params.file, |
| 158 | 160 | }); |
| 159 | 161 | }; |
| ... | ... | @@ -166,11 +168,12 @@ export const importCsvDeviceProfileId = (params: { |
| 166 | 168 | categoryId?: string; |
| 167 | 169 | deviceProfileId?: string; |
| 168 | 170 | file: FormData; |
| 171 | + type?: string; | |
| 169 | 172 | }) => { |
| 170 | - const { deviceProfileId } = params || {}; | |
| 173 | + const { deviceProfileId, type } = params || {}; | |
| 171 | 174 | |
| 172 | 175 | return defHttp.post<any>({ |
| 173 | - url: `${ModelOfMatter.IMPORT_CSV}?deviceProfileId=${deviceProfileId}`, | |
| 176 | + url: `${ModelOfMatter.IMPORT_CSV}?deviceProfileId=${deviceProfileId}&type=${type}`, | |
| 174 | 177 | params: params.file, |
| 175 | 178 | }); |
| 176 | 179 | }; |
| ... | ... | @@ -178,9 +181,10 @@ export const importCsvDeviceProfileId = (params: { |
| 178 | 181 | /** |
| 179 | 182 | * 物模型excel导出模板 |
| 180 | 183 | */ |
| 181 | -export const excelExport = () => { | |
| 184 | +export const excelExport = (type?: string) => { | |
| 182 | 185 | return defHttp.get({ |
| 183 | 186 | url: `${ModelOfMatter.EXCEL_EXPORT}`, |
| 187 | + params: { type }, | |
| 184 | 188 | responseType: 'blob', |
| 185 | 189 | }); |
| 186 | 190 | }; | ... | ... |
| ... | ... | @@ -2,6 +2,7 @@ |
| 2 | 2 | <div class="flex"> |
| 3 | 3 | <InputNumber |
| 4 | 4 | placeholder="最小值" |
| 5 | + v-bind="minInputProps" | |
| 5 | 6 | :disabled="$props.disabled" |
| 6 | 7 | :value="getValue.min!" |
| 7 | 8 | @change="(value) => emitChange(value, 'min')" |
| ... | ... | @@ -9,6 +10,7 @@ |
| 9 | 10 | <div class="text-center flex-shrink-0 w-6">~</div> |
| 10 | 11 | <InputNumber |
| 11 | 12 | placeholder="最大值" |
| 13 | + v-bind="maxInputProps" | |
| 12 | 14 | :disabled="$props.disabled" |
| 13 | 15 | :value="getValue.max!" |
| 14 | 16 | @change="(value) => emitChange(value, 'max')" |
| ... | ... | @@ -22,7 +24,7 @@ |
| 22 | 24 | </script> |
| 23 | 25 | <script lang="ts" setup> |
| 24 | 26 | import { computed } from 'vue'; |
| 25 | - import { InputNumber } from 'ant-design-vue'; | |
| 27 | + import { InputNumber, InputNumberProps } from 'ant-design-vue'; | |
| 26 | 28 | |
| 27 | 29 | const emit = defineEmits(['change', 'update:value']); |
| 28 | 30 | const props = withDefaults( |
| ... | ... | @@ -32,8 +34,12 @@ |
| 32 | 34 | max: Nullable<number>; |
| 33 | 35 | }; |
| 34 | 36 | disabled: boolean; |
| 37 | + maxInputProps?: InputNumberProps; | |
| 38 | + minInputProps?: InputNumberProps; | |
| 35 | 39 | }>(), |
| 36 | 40 | { |
| 41 | + maxInputProps: () => ({}), | |
| 42 | + minInputProps: () => ({}), | |
| 37 | 43 | value: () => ({ min: null, max: null }), |
| 38 | 44 | } |
| 39 | 45 | ); | ... | ... |
| ... | ... | @@ -48,3 +48,13 @@ export enum CommandTypeNameEnum { |
| 48 | 48 | export enum RPCCommandMethodEnum { |
| 49 | 49 | THINGSKIT = 'methodThingskit', |
| 50 | 50 | } |
| 51 | + | |
| 52 | +export enum TCPProtocolTypeEnum { | |
| 53 | + CUSTOM = 'CUSTOM', | |
| 54 | + MODBUS_RTU = 'MODBUS_RTU', | |
| 55 | +} | |
| 56 | + | |
| 57 | +export enum TCPProtocolTypeNameEnum { | |
| 58 | + CUSTOM = '自定义', | |
| 59 | + MODBUS_RTU = 'MODBUS_RTU', | |
| 60 | +} | ... | ... |
| ... | ... | @@ -31,31 +31,95 @@ export enum ObjectEventTypeNameEnum { |
| 31 | 31 | ERROR = '故障', |
| 32 | 32 | } |
| 33 | 33 | |
| 34 | -export enum RegisterDataTypeEnum { | |
| 35 | - UN_SHORT = 'unshort', | |
| 34 | +export enum ObjectModelAccessModeEnum { | |
| 35 | + READ = 'r', | |
| 36 | + READ_AND_WRITE = 'rw', | |
| 36 | 37 | } |
| 37 | 38 | |
| 38 | -export enum RegisterDataTypeNameEnum { | |
| 39 | - UN_SHORT = '16位有符号', | |
| 39 | +export enum ModbusCRCEnum { | |
| 40 | + CRC_16_LOWER = 'CRC_16_LOWER', | |
| 40 | 41 | } |
| 41 | 42 | |
| 42 | -export enum RegisterActionTypeEnum { | |
| 43 | - BOOL = '05', | |
| 44 | - INT = '06', | |
| 45 | - DOUBLE = '16', | |
| 43 | +export enum BuiltInIdentifierEnum { | |
| 44 | + SOURCE = 'source', | |
| 46 | 45 | } |
| 47 | 46 | |
| 48 | -export enum RegisterActionTypeNameEnum { | |
| 49 | - BOOL = '05写入单个线圈寄存器', | |
| 50 | - INT = '06写入单个保持寄存器', | |
| 51 | - DOUBLE = '16写入多个保持寄存器', | |
| 47 | +export enum ModbusMethodEnum { | |
| 48 | + WRITE_10 = '10', | |
| 52 | 49 | } |
| 53 | 50 | |
| 54 | -export enum ObjectModelAccessModeEnum { | |
| 55 | - READ = 'r', | |
| 56 | - READ_AND_WRITE = 'rw', | |
| 51 | +export enum OriginalDataTypeEnum { | |
| 52 | + INT16_AB = 'INT16_AB', | |
| 53 | + INT16_BA = 'INT16_BA', | |
| 54 | + UINT16_AB = 'UINT16_AB', | |
| 55 | + UINT16_BA = 'UINT16_BA', | |
| 56 | + INT32_AB_CD = 'INT32_AB_CD', | |
| 57 | + INT32_CD_AB = 'INT32_CD_AB', | |
| 58 | + INT32_BA_DC = 'INT32_BA_DC', | |
| 59 | + INT32_DC_BA = 'INT32_DC_BA', | |
| 60 | + UINT32_AB_CD = 'UINT32_AB_CD', | |
| 61 | + UINT32_CD_AB = 'UINT32_CD_AB', | |
| 62 | + UINT32_BA_DC = 'UINT32_BA_DC', | |
| 63 | + UINT32_DC_BA = 'UINT32_DC_BA', | |
| 64 | + FLOAT_AB_CD = 'FLOAT_AB_CD', | |
| 65 | + FLOAT_CD_AB = 'FLOAT_CD_AB', | |
| 66 | + FLOAT_BA_DC = 'FLOAT_BA_DC', | |
| 67 | + FLOAT_DC_BA = 'FLOAT_DC_BA', | |
| 68 | + DOUBLE = 'DOUBLE', | |
| 69 | + STRING = 'STRING', | |
| 70 | + BOOLEAN = 'BOOLEAN', | |
| 71 | + BITS = 'BITS', | |
| 57 | 72 | } |
| 58 | 73 | |
| 59 | -export enum ModbusCRCEnum { | |
| 60 | - CRC_16_LOWER = 'CRC_16_LOWER', | |
| 74 | +export enum OriginalDataTypeNameEnum { | |
| 75 | + INT16_AB = '16位有符号整数AB', | |
| 76 | + INT16_BA = '16位有符号整数BA', | |
| 77 | + UINT16_AB = '16位无符号整数AB', | |
| 78 | + UINT16_BA = '16位无符号整数BA', | |
| 79 | + INT32_AB_CD = '32位有符号整数AB_CD', | |
| 80 | + INT32_CD_AB = '32位有符号整数CD_AB', | |
| 81 | + INT32_BA_DC = '32位有符号整数BA_DC', | |
| 82 | + INT32_DC_BA = '32位有符号整数DC_BA', | |
| 83 | + UINT32_AB_CD = '32位无符号整数AB_CD', | |
| 84 | + UINT32_CD_AB = '32位无符号整数CD_AB', | |
| 85 | + UINT32_BA_DC = '32位无符号整数BA_DC', | |
| 86 | + UINT32_DC_BA = '32位无符号整数DC_BA', | |
| 87 | + FLOAT_AB_CD = '单精度浮点型AB_CD', | |
| 88 | + FLOAT_CD_AB = '单精度浮点型CD_AB', | |
| 89 | + FLOAT_BA_DC = '单精度浮点型BA_DC', | |
| 90 | + FLOAT_DC_BA = '单精度浮点型DC_BA', | |
| 91 | + DOUBLE = '双精度浮点型', | |
| 92 | + STRING = '字符串', | |
| 93 | + BOOLEAN = '布尔型', | |
| 94 | + BITS = '位', | |
| 95 | +} | |
| 96 | + | |
| 97 | +export enum ExtendDescOperationTypeEnum { | |
| 98 | + INPUT_STATUS_R_02 = 'inputStatus_r_02', | |
| 99 | + COIL_STATUS_R_01 = 'coilStatus_r_01', | |
| 100 | + COIL_STATUS_RW_01_05 = 'coilStatus_rw_01_05', | |
| 101 | + COIL_STATUS_RW_01_0F = 'coilStatus_rw_01_0F', | |
| 102 | + COIL_STATUS_W_05 = 'coilStatus_w_05', | |
| 103 | + COIL_STATUS_W_0F = 'coilStatus_w_0F', | |
| 104 | + HOLDING_REGISTER_R_03 = 'holdingRegister_r_03', | |
| 105 | + HOLDING_REGISTER_RW_03_06 = 'holdingRegister_rw_03_06', | |
| 106 | + HOLDING_REGISTER_RW_03_10 = 'holdingRegister_rw_03_10', | |
| 107 | + HOLDING_REGISTER_W_06 = 'holdingRegister_w_06', | |
| 108 | + HOLDING_REGISTER_W_10 = 'holdingRegister_w_10', | |
| 109 | + INPUT_REGISTER_R_04 = 'inputRegister_r_04', | |
| 110 | +} | |
| 111 | + | |
| 112 | +export enum ExtendDescOperationTypeNameEnum { | |
| 113 | + INPUT_STATUS_R_02 = '离散量输入(只读,0x02)', | |
| 114 | + COIL_STATUS_R_01 = '线圈状态(只读,0x01)', | |
| 115 | + COIL_STATUS_RW_01_05 = '线圈状态(读写,读取使用0x01,写入使用0x05)', | |
| 116 | + COIL_STATUS_RW_01_0F = '线圈状态(读写,读取使用0x01,写入使用0x0F)', | |
| 117 | + COIL_STATUS_W_05 = '线圈状态(只写,0x05)', | |
| 118 | + COIL_STATUS_W_0F = '线圈状态(只写,0x0F)', | |
| 119 | + HOLDING_REGISTER_R_03 = '保持寄存器(只读,0x03)', | |
| 120 | + HOLDING_REGISTER_RW_03_06 = '保持寄存器(读写,读取使用0x03,写入使用0x06)', | |
| 121 | + HOLDING_REGISTER_RW_03_10 = '保持寄存器(读写,读取使用0x03,写入使用0x10)', | |
| 122 | + HOLDING_REGISTER_W_06 = '保持寄存器(只写,0x06)', | |
| 123 | + HOLDING_REGISTER_W_10 = '保持寄存器(只写,0x10)', | |
| 124 | + INPUT_REGISTER_R_04 = '输入寄存器(只读,0x04)', | |
| 61 | 125 | } | ... | ... |
src/hooks/business/useBaseConversion.ts
0 → 100644
| 1 | +import { useParseOriginalDataType } from './useParseOriginalDataType'; | |
| 2 | +import { OriginalDataTypeEnum } from '/@/enums/objectModelEnum'; | |
| 3 | + | |
| 4 | +export function useBaseConversion() { | |
| 5 | + function DecTo32Float(number: number) { | |
| 6 | + const arr = new Uint8Array(4); | |
| 7 | + const view = new DataView(arr.buffer); | |
| 8 | + view.setFloat32(0, +number); | |
| 9 | + return arr; | |
| 10 | + } | |
| 11 | + | |
| 12 | + function DecTo64Double(number: number) { | |
| 13 | + const arr = new Uint8Array(8); | |
| 14 | + const view = new DataView(arr.buffer); | |
| 15 | + view.setFloat64(0, +number); | |
| 16 | + return arr; | |
| 17 | + } | |
| 18 | + | |
| 19 | + function arrToBase(toBase: number, arr: Uint8Array) { | |
| 20 | + let result = ''; | |
| 21 | + for (let i = 0; i < arr.length; i++) { | |
| 22 | + result += (256 + arr[i]).toString(toBase).substring(1).toUpperCase(); | |
| 23 | + } | |
| 24 | + | |
| 25 | + return result; | |
| 26 | + } | |
| 27 | + | |
| 28 | + function DecTo16Uint(number: number) { | |
| 29 | + const arr = new Uint8Array(2); | |
| 30 | + const view = new DataView(arr.buffer); | |
| 31 | + view.setUint16(0, +number); | |
| 32 | + return arr; | |
| 33 | + } | |
| 34 | + | |
| 35 | + function DecTo16Int(number: number) { | |
| 36 | + const arr = new Uint8Array(2); | |
| 37 | + const view = new DataView(arr.buffer); | |
| 38 | + view.setInt16(0, +number); | |
| 39 | + return arr; | |
| 40 | + } | |
| 41 | + | |
| 42 | + function DecTo32Uint(number: number) { | |
| 43 | + const arr = new Uint8Array(4); | |
| 44 | + const view = new DataView(arr.buffer); | |
| 45 | + view.setUint32(0, +number); | |
| 46 | + return arr; | |
| 47 | + } | |
| 48 | + | |
| 49 | + function DecTo32Int(number: number) { | |
| 50 | + const arr = new Uint8Array(4); | |
| 51 | + const view = new DataView(arr.buffer); | |
| 52 | + view.setInt32(0, +number); | |
| 53 | + return arr; | |
| 54 | + } | |
| 55 | + | |
| 56 | + function DecToBinaryByType(type: OriginalDataTypeEnum, number: number) { | |
| 57 | + switch (type) { | |
| 58 | + case OriginalDataTypeEnum.INT16_AB: | |
| 59 | + case OriginalDataTypeEnum.INT16_BA: | |
| 60 | + return arrToBase(2, DecTo16Int(number)); | |
| 61 | + | |
| 62 | + case OriginalDataTypeEnum.UINT16_AB: | |
| 63 | + case OriginalDataTypeEnum.UINT16_BA: | |
| 64 | + return arrToBase(2, DecTo16Uint(number)); | |
| 65 | + | |
| 66 | + case OriginalDataTypeEnum.INT32_AB_CD: | |
| 67 | + case OriginalDataTypeEnum.INT32_CD_AB: | |
| 68 | + case OriginalDataTypeEnum.INT32_BA_DC: | |
| 69 | + case OriginalDataTypeEnum.INT32_DC_BA: | |
| 70 | + return arrToBase(2, DecTo32Int(number)); | |
| 71 | + | |
| 72 | + case OriginalDataTypeEnum.UINT32_AB_CD: | |
| 73 | + case OriginalDataTypeEnum.UINT32_CD_AB: | |
| 74 | + case OriginalDataTypeEnum.UINT32_BA_DC: | |
| 75 | + case OriginalDataTypeEnum.UINT32_DC_BA: | |
| 76 | + return arrToBase(2, DecTo32Uint(number)); | |
| 77 | + | |
| 78 | + case OriginalDataTypeEnum.FLOAT_AB_CD: | |
| 79 | + case OriginalDataTypeEnum.FLOAT_CD_AB: | |
| 80 | + case OriginalDataTypeEnum.FLOAT_BA_DC: | |
| 81 | + case OriginalDataTypeEnum.FLOAT_DC_BA: | |
| 82 | + return arrToBase(2, DecTo32Float(number)); | |
| 83 | + | |
| 84 | + case OriginalDataTypeEnum.DOUBLE: | |
| 85 | + return arrToBase(2, DecTo64Double(number)); | |
| 86 | + } | |
| 87 | + } | |
| 88 | + | |
| 89 | + function SplitStringToGroupByItemLength( | |
| 90 | + value: string, | |
| 91 | + itemLength = 8, | |
| 92 | + ignoreLessThan = false | |
| 93 | + ): string[] { | |
| 94 | + const reg = new RegExp(`.{${ignoreLessThan ? '' : '1,'}${itemLength}}`, 'g'); | |
| 95 | + return value.match(reg) || []; | |
| 96 | + } | |
| 97 | + | |
| 98 | + function ExchangeByteOrder(binary: string, order: string) { | |
| 99 | + const group = SplitStringToGroupByItemLength(binary); | |
| 100 | + | |
| 101 | + const BASE_ORDER = { | |
| 102 | + A: 0, | |
| 103 | + B: 1, | |
| 104 | + C: 2, | |
| 105 | + D: 3, | |
| 106 | + }; | |
| 107 | + | |
| 108 | + const array: string[] = Array.from({ length: binary.length / 8 }); | |
| 109 | + | |
| 110 | + order | |
| 111 | + .split('') | |
| 112 | + .forEach((bytePosition, index) => (array[index] = group[BASE_ORDER[bytePosition]])); | |
| 113 | + | |
| 114 | + return array.join(''); | |
| 115 | + } | |
| 116 | + | |
| 117 | + function ByteToHex(binary: string) { | |
| 118 | + const group = SplitStringToGroupByItemLength(binary, 16); | |
| 119 | + return group.map((byte) => parseInt(byte, 2).toString(16)); | |
| 120 | + } | |
| 121 | + | |
| 122 | + function ByteToDec(binary: string) { | |
| 123 | + const group = SplitStringToGroupByItemLength(binary, 16); | |
| 124 | + return group.map((byte) => parseInt(byte, 2)); | |
| 125 | + } | |
| 126 | + | |
| 127 | + function StringToHEXBuffer(string: string | number) { | |
| 128 | + return string | |
| 129 | + .toString() | |
| 130 | + .split('') | |
| 131 | + .map((string) => string.charCodeAt(0).toString(16)) | |
| 132 | + .reverse(); | |
| 133 | + } | |
| 134 | + | |
| 135 | + function getRegisterValueByOriginalDataType( | |
| 136 | + value: number, | |
| 137 | + type: OriginalDataTypeEnum, | |
| 138 | + additional?: { bitMask?: number; registerNumber?: number } | |
| 139 | + ) { | |
| 140 | + const { exchangeSortFlag } = useParseOriginalDataType(type); | |
| 141 | + // eslint-disable-next-line no-console | |
| 142 | + console.groupCollapsed('Modbus Debug'); | |
| 143 | + // eslint-disable-next-line no-console | |
| 144 | + console.table({ input: value, sort: exchangeSortFlag, type }); | |
| 145 | + | |
| 146 | + let result: number[]; | |
| 147 | + | |
| 148 | + if (type === OriginalDataTypeEnum.BOOLEAN) { | |
| 149 | + result = [value]; | |
| 150 | + } else if (type === OriginalDataTypeEnum.STRING) { | |
| 151 | + let buffer = StringToHEXBuffer(value); | |
| 152 | + const { registerNumber = 0 } = additional || {}; | |
| 153 | + | |
| 154 | + if (buffer.length < registerNumber * 2) { | |
| 155 | + buffer = [ | |
| 156 | + ...Array.from({ length: registerNumber * 2 - buffer.length }, () => '00'), | |
| 157 | + ...buffer, | |
| 158 | + ]; | |
| 159 | + } | |
| 160 | + | |
| 161 | + result = SplitStringToGroupByItemLength(buffer.join(''), 4).map((hex) => parseInt(hex, 16)); | |
| 162 | + } else { | |
| 163 | + let binary = DecToBinaryByType(type, value)!; | |
| 164 | + // eslint-disable-next-line no-console | |
| 165 | + console.table({ beforeExchange: binary }); | |
| 166 | + | |
| 167 | + if (exchangeSortFlag) binary = ExchangeByteOrder(binary, exchangeSortFlag); | |
| 168 | + result = ByteToDec(binary); | |
| 169 | + | |
| 170 | + // eslint-disable-next-line no-console | |
| 171 | + console.table({ | |
| 172 | + afterEchange: binary, | |
| 173 | + dec: result.toString(), | |
| 174 | + hex: ByteToHex(binary).toString(), | |
| 175 | + }); | |
| 176 | + } | |
| 177 | + | |
| 178 | + // eslint-disable-next-line no-console | |
| 179 | + console.groupEnd(); | |
| 180 | + | |
| 181 | + return result; | |
| 182 | + } | |
| 183 | + | |
| 184 | + return { | |
| 185 | + DecToBinaryByType, | |
| 186 | + ByteToDec, | |
| 187 | + ByteToHex, | |
| 188 | + ExchangeByteOrder, | |
| 189 | + SplitStringToGroupByItemLength, | |
| 190 | + getRegisterValueByOriginalDataType, | |
| 191 | + StringToHEXBuffer, | |
| 192 | + }; | |
| 193 | +} | ... | ... |
src/hooks/business/useCommandDelivery.ts
0 → 100644
| 1 | +import { ref } from 'vue'; | |
| 2 | +import { useCoverModbusCommand } from './useCoverModbusCommand'; | |
| 3 | +import { commandIssuanceApi, getDeviceDetail } from '/@/api/device/deviceManager'; | |
| 4 | +import { RpcCommandType } from '/@/api/device/model/deviceConfigModel'; | |
| 5 | +import { DeviceProfileModel, DeviceRecord } from '/@/api/device/model/deviceModel'; | |
| 6 | +import { Tsl } from '/@/api/device/model/modelOfMatterModel'; | |
| 7 | +import { getModelTsl } from '/@/api/device/modelOfMatter'; | |
| 8 | +import { | |
| 9 | + CommandDeliveryWayEnum, | |
| 10 | + CommandTypeEnum, | |
| 11 | + RPCCommandMethodEnum, | |
| 12 | + ServiceCallTypeEnum, | |
| 13 | + TCPProtocolTypeEnum, | |
| 14 | + TransportTypeEnum, | |
| 15 | +} from '/@/enums/deviceEnum'; | |
| 16 | +import { FunctionTypeEnum } from '/@/enums/objectModelEnum'; | |
| 17 | +import { isFunction } from '/@/utils/is'; | |
| 18 | +import { getDeviceActiveTime } from '/@/api/alarm/position'; | |
| 19 | +import { useMessage } from '../web/useMessage'; | |
| 20 | + | |
| 21 | +interface SetupType { | |
| 22 | + entityId: string; | |
| 23 | + transportType: TransportTypeEnum; | |
| 24 | + isTCPModbus: boolean; | |
| 25 | + deviceCode: string | undefined; | |
| 26 | + deviceDetail: DeviceRecord; | |
| 27 | + objectModel: Tsl | undefined; | |
| 28 | + identifier: string; | |
| 29 | +} | |
| 30 | + | |
| 31 | +interface DoCommandDeliverParamsType { | |
| 32 | + value: any; | |
| 33 | + deviceDetail?: DeviceRecord; | |
| 34 | + deviceId?: string; | |
| 35 | + identifier?: string; | |
| 36 | + objectModel?: Tsl; | |
| 37 | + deviceProfileId?: string; | |
| 38 | + deviceProfileDetail?: DeviceProfileModel; | |
| 39 | + way?: CommandDeliveryWayEnum; | |
| 40 | + cmdType?: CommandTypeEnum; | |
| 41 | + transportType?: TransportTypeEnum; | |
| 42 | + beforeFetch?: ( | |
| 43 | + rpcCommand: RpcCommandType, | |
| 44 | + setup: SetupType | |
| 45 | + ) => | |
| 46 | + | { rpcCommand: RpcCommandType; way?: CommandDeliveryWayEnum } | |
| 47 | + | Promise<{ rpcCommand: RpcCommandType; way?: CommandDeliveryWayEnum }>; | |
| 48 | +} | |
| 49 | + | |
| 50 | +export function useCommandDelivery() { | |
| 51 | + const loading = ref(false); | |
| 52 | + async function doSetup(params: DoCommandDeliverParamsType) { | |
| 53 | + let { deviceDetail, identifier, deviceProfileId, objectModel, transportType } = params; | |
| 54 | + const { deviceId, deviceProfileDetail } = params; | |
| 55 | + | |
| 56 | + const entityId = deviceId || deviceDetail?.tbDeviceId; | |
| 57 | + if (!entityId) { | |
| 58 | + throw new Error('not found entityId'); | |
| 59 | + } | |
| 60 | + | |
| 61 | + identifier = identifier || objectModel?.identifier; | |
| 62 | + | |
| 63 | + if (!identifier) { | |
| 64 | + throw new Error('not found identifier'); | |
| 65 | + } | |
| 66 | + | |
| 67 | + transportType = transportType || (deviceDetail?.transportType as TransportTypeEnum); | |
| 68 | + | |
| 69 | + if ( | |
| 70 | + !transportType || | |
| 71 | + (transportType === TransportTypeEnum.TCP && !deviceDetail) || | |
| 72 | + !deviceDetail?.deviceProfile | |
| 73 | + ) { | |
| 74 | + deviceDetail = await getDeviceDetail(entityId); | |
| 75 | + transportType = deviceDetail.transportType as TransportTypeEnum; | |
| 76 | + } | |
| 77 | + | |
| 78 | + const isTCPModbus = | |
| 79 | + transportType === TransportTypeEnum.TCP && | |
| 80 | + deviceDetail?.deviceProfile?.profileData?.transportConfiguration?.protocol === | |
| 81 | + TCPProtocolTypeEnum.MODBUS_RTU; | |
| 82 | + | |
| 83 | + if (isTCPModbus && !objectModel?.extensionDesc) { | |
| 84 | + deviceProfileId = deviceDetail.deviceProfileId || deviceProfileId || deviceProfileDetail?.id; | |
| 85 | + if (!deviceProfileId) { | |
| 86 | + throw new Error('not found deviceProfile'); | |
| 87 | + } | |
| 88 | + | |
| 89 | + const objectModels = await getModelTsl({ deviceProfileId }); | |
| 90 | + objectModel = objectModels.find((item) => item.identifier === identifier); | |
| 91 | + } | |
| 92 | + | |
| 93 | + const deviceCode = deviceDetail.code; | |
| 94 | + | |
| 95 | + return { | |
| 96 | + entityId, | |
| 97 | + transportType, | |
| 98 | + isTCPModbus, | |
| 99 | + deviceCode, | |
| 100 | + deviceDetail, | |
| 101 | + objectModel, | |
| 102 | + identifier, | |
| 103 | + }; | |
| 104 | + } | |
| 105 | + | |
| 106 | + async function doCommandDelivery(params: DoCommandDeliverParamsType) { | |
| 107 | + try { | |
| 108 | + loading.value = true; | |
| 109 | + | |
| 110 | + const setupResult = await doSetup(params); | |
| 111 | + | |
| 112 | + const { entityId, transportType, isTCPModbus, deviceCode, objectModel, identifier } = | |
| 113 | + setupResult; | |
| 114 | + | |
| 115 | + let command = params.value; | |
| 116 | + | |
| 117 | + if (transportType === TransportTypeEnum.TCP) { | |
| 118 | + command = params.value; | |
| 119 | + if (isTCPModbus) { | |
| 120 | + const { doCoverCommand } = useCoverModbusCommand(); | |
| 121 | + command = await doCoverCommand(params.value, objectModel!, deviceCode, entityId); | |
| 122 | + } | |
| 123 | + } else { | |
| 124 | + command = { | |
| 125 | + [identifier]: command, | |
| 126 | + }; | |
| 127 | + } | |
| 128 | + | |
| 129 | + let rpcCommand: RpcCommandType = { | |
| 130 | + persistent: true, | |
| 131 | + method: RPCCommandMethodEnum.THINGSKIT, | |
| 132 | + additionalInfo: { | |
| 133 | + cmdType: params.cmdType ?? CommandTypeEnum.API, | |
| 134 | + }, | |
| 135 | + params: command, | |
| 136 | + }; | |
| 137 | + | |
| 138 | + let way = params.way ?? CommandDeliveryWayEnum.ONE_WAY; | |
| 139 | + | |
| 140 | + if (objectModel?.functionType === FunctionTypeEnum.SERVICE) { | |
| 141 | + rpcCommand.additionalInfo.cmdType = CommandTypeEnum.SERVICE; | |
| 142 | + way = | |
| 143 | + objectModel.callType === ServiceCallTypeEnum.ASYNC | |
| 144 | + ? CommandDeliveryWayEnum.ONE_WAY | |
| 145 | + : CommandDeliveryWayEnum.TWO_WAY; | |
| 146 | + } | |
| 147 | + | |
| 148 | + const sendApi = commandIssuanceApi; | |
| 149 | + | |
| 150 | + if (params.beforeFetch && isFunction(params.beforeFetch)) { | |
| 151 | + const { rpcCommand: _rpcCommand, way: _way } = await params.beforeFetch( | |
| 152 | + rpcCommand, | |
| 153 | + setupResult | |
| 154 | + ); | |
| 155 | + rpcCommand = _rpcCommand; | |
| 156 | + if (_way) way = _way; | |
| 157 | + } | |
| 158 | + | |
| 159 | + if (way === CommandDeliveryWayEnum.TWO_WAY) { | |
| 160 | + const result = await getDeviceActiveTime(entityId); | |
| 161 | + const [firsetItem] = result || []; | |
| 162 | + | |
| 163 | + if (!firsetItem.value) { | |
| 164 | + const { createMessage } = useMessage(); | |
| 165 | + const message = '当前设备不在线'; | |
| 166 | + createMessage.warning(message); | |
| 167 | + throw Error(message); | |
| 168 | + } | |
| 169 | + } | |
| 170 | + await sendApi(way, entityId, rpcCommand); | |
| 171 | + } finally { | |
| 172 | + loading.value = false; | |
| 173 | + } | |
| 174 | + } | |
| 175 | + | |
| 176 | + return { | |
| 177 | + loading, | |
| 178 | + doCommandDelivery, | |
| 179 | + }; | |
| 180 | +} | ... | ... |
src/hooks/business/useCoverModbusCommand.ts
0 → 100644
| 1 | +import { useParseOriginalDataType } from './useParseOriginalDataType'; | |
| 2 | +import { getDeviceHistoryInfo } from '/@/api/alarm/position'; | |
| 3 | +import { getDeviceDetail } from '/@/api/device/deviceManager'; | |
| 4 | +import { ExtensionDesc, Specs, Tsl } from '/@/api/device/model/modelOfMatterModel'; | |
| 5 | +import { genModbusCommand } from '/@/api/task'; | |
| 6 | +import { GenModbusCommandType } from '/@/api/task/model'; | |
| 7 | +import { ModbusCRCEnum, OriginalDataTypeEnum } from '/@/enums/objectModelEnum'; | |
| 8 | +import { useBaseConversion } from '/@/hooks/business/useBaseConversion'; | |
| 9 | +import { useMessage } from '/@/hooks/web/useMessage'; | |
| 10 | +import { isNullOrUnDef } from '/@/utils/is'; | |
| 11 | +import { | |
| 12 | + isFloatType, | |
| 13 | + isNumberType, | |
| 14 | + useParseOperationType, | |
| 15 | +} from '/@/views/device/profiles/components/ObjectModelForm/ExtendDesc/useParseOperationType'; | |
| 16 | + | |
| 17 | +const getFloatPart = (number: string | number) => { | |
| 18 | + const isLessZero = Number(number) < 0; | |
| 19 | + number = number.toString(); | |
| 20 | + const floatPartStartIndex = number.indexOf('.'); | |
| 21 | + const value = ~floatPartStartIndex | |
| 22 | + ? `${isLessZero ? '-' : ''}0.${number.substring(floatPartStartIndex + 1)}` | |
| 23 | + : '0'; | |
| 24 | + return Number(value); | |
| 25 | +}; | |
| 26 | + | |
| 27 | +function getValueFromValueRange(value: number, valueRange?: Record<'min' | 'max', number>) { | |
| 28 | + const { min, max } = valueRange || {}; | |
| 29 | + if (!isNullOrUnDef(min) && value < min) return min; | |
| 30 | + if (!isNullOrUnDef(max) && value > max) return max; | |
| 31 | + return value; | |
| 32 | +} | |
| 33 | + | |
| 34 | +async function getCurrentBitCommand(entityId: string, objectModel: Tsl, value: number) { | |
| 35 | + const deviceDetail = await getDeviceDetail(entityId); | |
| 36 | + const thingsModels = deviceDetail.deviceProfile.profileData.thingsModel; | |
| 37 | + | |
| 38 | + const { registerAddress } = objectModel.extensionDesc || {}; | |
| 39 | + | |
| 40 | + const bitsModel = thingsModels?.filter( | |
| 41 | + (item) => | |
| 42 | + item.extensionDesc?.originalDataType === OriginalDataTypeEnum.BITS && | |
| 43 | + item.extensionDesc.registerAddress === registerAddress | |
| 44 | + ); | |
| 45 | + | |
| 46 | + const valuePositionMap = | |
| 47 | + bitsModel?.reduce((prev, next) => { | |
| 48 | + return { ...prev, [next.identifier]: next.extensionDesc?.bitMask }; | |
| 49 | + }, {} as Record<string, number>) || {}; | |
| 50 | + | |
| 51 | + const attrKeys = Object.keys(valuePositionMap); | |
| 52 | + | |
| 53 | + const latestBitsValues = await getDeviceHistoryInfo({ entityId, keys: attrKeys.join(',') }); | |
| 54 | + | |
| 55 | + const binaryArr = Array.from({ length: 16 }, () => 0); | |
| 56 | + | |
| 57 | + for (const key of attrKeys) { | |
| 58 | + const index = valuePositionMap[key]; | |
| 59 | + | |
| 60 | + if (!isNullOrUnDef(index)) { | |
| 61 | + const [latest] = latestBitsValues[key]; | |
| 62 | + const { value } = latest; | |
| 63 | + binaryArr[index] = Number(value); | |
| 64 | + } | |
| 65 | + } | |
| 66 | + | |
| 67 | + if (objectModel.extensionDesc?.bitMask) { | |
| 68 | + binaryArr[objectModel.extensionDesc.bitMask] = value; | |
| 69 | + } | |
| 70 | + | |
| 71 | + return [parseInt(binaryArr.reverse().join(''), 2)]; | |
| 72 | +} | |
| 73 | + | |
| 74 | +export function useCoverModbusCommand() { | |
| 75 | + const { createMessage } = useMessage(); | |
| 76 | + | |
| 77 | + const doCoverCommand = async ( | |
| 78 | + value: number, | |
| 79 | + objectModel: Tsl, | |
| 80 | + deviceAddressCode?: string, | |
| 81 | + entityId?: string | |
| 82 | + ) => { | |
| 83 | + if (!deviceAddressCode) { | |
| 84 | + const message = '当前设备未绑定设备地址码'; | |
| 85 | + createMessage.warning(message); | |
| 86 | + throw new Error(message); | |
| 87 | + } | |
| 88 | + | |
| 89 | + const { | |
| 90 | + registerAddress, | |
| 91 | + operationType, | |
| 92 | + scaling, | |
| 93 | + originalDataType, | |
| 94 | + bitMask, | |
| 95 | + registerCount: registerNumber, | |
| 96 | + } = objectModel.extensionDesc as Required<ExtensionDesc>; | |
| 97 | + | |
| 98 | + const { writeRegisterAddress } = useParseOperationType(operationType); | |
| 99 | + const { unsigned, exchangeSortFlag, registerCount } = | |
| 100 | + useParseOriginalDataType(originalDataType); | |
| 101 | + | |
| 102 | + const params: GenModbusCommandType = { | |
| 103 | + crc: ModbusCRCEnum.CRC_16_LOWER, | |
| 104 | + registerNumber: registerCount || registerNumber, | |
| 105 | + deviceCode: deviceAddressCode, | |
| 106 | + registerAddress: parseInt(registerAddress, 16), | |
| 107 | + method: writeRegisterAddress!, | |
| 108 | + registerValues: [value], | |
| 109 | + }; | |
| 110 | + | |
| 111 | + if (exchangeSortFlag) params.hexByteOrderEnum = exchangeSortFlag; | |
| 112 | + | |
| 113 | + const { getRegisterValueByOriginalDataType } = useBaseConversion(); | |
| 114 | + | |
| 115 | + if (isNumberType(originalDataType)) { | |
| 116 | + let newValue = Math.trunc(value) * scaling + getFloatPart(value) * scaling; | |
| 117 | + | |
| 118 | + newValue = unsigned ? newValue : Math.abs(newValue); | |
| 119 | + | |
| 120 | + newValue = getValueFromValueRange( | |
| 121 | + newValue, | |
| 122 | + (objectModel.specs?.dataType.specs as Specs).valueRange | |
| 123 | + ); | |
| 124 | + | |
| 125 | + if (!isFloatType(originalDataType) && newValue % 1 !== 0) { | |
| 126 | + const message = `属性下发类型必须是整数,缩放因子为${scaling}`; | |
| 127 | + createMessage.warning(message); | |
| 128 | + throw Error(message); | |
| 129 | + } | |
| 130 | + | |
| 131 | + value = newValue; | |
| 132 | + } | |
| 133 | + | |
| 134 | + params.registerValues = | |
| 135 | + originalDataType === OriginalDataTypeEnum.BITS | |
| 136 | + ? await getCurrentBitCommand(entityId!, objectModel, value) | |
| 137 | + : getRegisterValueByOriginalDataType(value, originalDataType, { | |
| 138 | + bitMask, | |
| 139 | + registerNumber, | |
| 140 | + }); | |
| 141 | + | |
| 142 | + if (!params.method) { | |
| 143 | + const message = '物模型操作类型无法进行写入'; | |
| 144 | + createMessage.warning(message); | |
| 145 | + throw Error(message); | |
| 146 | + } | |
| 147 | + | |
| 148 | + return await genModbusCommand(params); | |
| 149 | + }; | |
| 150 | + | |
| 151 | + return { | |
| 152 | + doCoverCommand, | |
| 153 | + }; | |
| 154 | +} | ... | ... |
| 1 | +import { OriginalDataTypeEnum } from '/@/enums/objectModelEnum'; | |
| 2 | + | |
| 3 | +export type OriginalDataTypePrefixType<S = `${OriginalDataTypeEnum}`> = S extends string | |
| 4 | + ? S extends `${infer D}_${string}` | |
| 5 | + ? D | |
| 6 | + : S | |
| 7 | + : ''; | |
| 8 | + | |
| 9 | +function getRegisterCount(originalDataType: OriginalDataTypeEnum) { | |
| 10 | + switch (originalDataType) { | |
| 11 | + case OriginalDataTypeEnum.INT16_AB: | |
| 12 | + case OriginalDataTypeEnum.INT16_BA: | |
| 13 | + case OriginalDataTypeEnum.UINT16_AB: | |
| 14 | + case OriginalDataTypeEnum.UINT16_BA: | |
| 15 | + case OriginalDataTypeEnum.BITS: | |
| 16 | + case OriginalDataTypeEnum.BOOLEAN: | |
| 17 | + return 1; | |
| 18 | + | |
| 19 | + case OriginalDataTypeEnum.INT32_AB_CD: | |
| 20 | + case OriginalDataTypeEnum.INT32_BA_DC: | |
| 21 | + case OriginalDataTypeEnum.INT32_CD_AB: | |
| 22 | + case OriginalDataTypeEnum.INT32_DC_BA: | |
| 23 | + case OriginalDataTypeEnum.UINT32_AB_CD: | |
| 24 | + case OriginalDataTypeEnum.UINT32_BA_DC: | |
| 25 | + case OriginalDataTypeEnum.UINT32_CD_AB: | |
| 26 | + case OriginalDataTypeEnum.UINT32_DC_BA: | |
| 27 | + return 2; | |
| 28 | + | |
| 29 | + case OriginalDataTypeEnum.FLOAT_AB_CD: | |
| 30 | + case OriginalDataTypeEnum.FLOAT_BA_DC: | |
| 31 | + case OriginalDataTypeEnum.FLOAT_CD_AB: | |
| 32 | + case OriginalDataTypeEnum.FLOAT_DC_BA: | |
| 33 | + return 2; | |
| 34 | + | |
| 35 | + case OriginalDataTypeEnum.DOUBLE: | |
| 36 | + return 4; | |
| 37 | + } | |
| 38 | +} | |
| 39 | + | |
| 40 | +export function useParseOriginalDataType(originalDataType: OriginalDataTypeEnum) { | |
| 41 | + const signedMatchRef = /^UN/; | |
| 42 | + | |
| 43 | + const splitArray = originalDataType.split('_') as [OriginalDataTypePrefixType]; | |
| 44 | + | |
| 45 | + const [dataType] = splitArray; | |
| 46 | + | |
| 47 | + const exchangeSortFlag = splitArray.slice(1).join('_'); | |
| 48 | + | |
| 49 | + return { | |
| 50 | + registerCount: getRegisterCount(originalDataType), | |
| 51 | + unsigned: !signedMatchRef.test(originalDataType), | |
| 52 | + dataType, | |
| 53 | + exchangeSortFlag: exchangeSortFlag || null, | |
| 54 | + }; | |
| 55 | +} | ... | ... |
| ... | ... | @@ -5,17 +5,19 @@ import { JSONEditor } from '/@/components/CodeEditor'; |
| 5 | 5 | import { DeviceRecord, DeviceTypeEnum } from '/@/api/device/model/deviceModel'; |
| 6 | 6 | import { h } from 'vue'; |
| 7 | 7 | import { TaskTypeEnum } from '/@/views/task/center/config'; |
| 8 | -import { AddressTypeEnum } from '/@/views/task/center/components/PollCommandInput'; | |
| 9 | 8 | import { FileItem } from '/@/components/Form/src/components/ApiUpload.vue'; |
| 10 | 9 | import { createImgPreview } from '/@/components/Preview'; |
| 11 | 10 | import { uploadThumbnail } from '/@/api/configuration/center/configurationCenter'; |
| 12 | 11 | import LockControlGroup from '/@/components/Form/src/components/LockControlGroup.vue'; |
| 13 | 12 | import { OrgTreeSelect } from '/@/views/common/OrgTreeSelect'; |
| 14 | -import { TransportTypeEnum } from '/@/enums/deviceEnum'; | |
| 13 | +import { TCPProtocolTypeEnum, TransportTypeEnum } from '/@/enums/deviceEnum'; | |
| 14 | +import { HexInput, InputTypeEnum } from '../../profiles/components/ObjectModelForm/HexInput'; | |
| 15 | +import { DeviceProfileDetail } from '/@/api/device/model/deviceConfigModel'; | |
| 15 | 16 | |
| 16 | 17 | useComponentRegister('JSONEditor', JSONEditor); |
| 17 | 18 | useComponentRegister('LockControlGroup', LockControlGroup); |
| 18 | 19 | useComponentRegister('OrgTreeSelect', OrgTreeSelect); |
| 20 | +useComponentRegister('HexInput', HexInput); | |
| 19 | 21 | |
| 20 | 22 | export enum TypeEnum { |
| 21 | 23 | IS_GATEWAY = 'GATEWAY', |
| ... | ... | @@ -108,6 +110,12 @@ export const step1Schemas: FormSchema[] = [ |
| 108 | 110 | show: false, |
| 109 | 111 | }, |
| 110 | 112 | { |
| 113 | + field: 'tcpDeviceProtocol', | |
| 114 | + label: 'TCP设备协议类型', | |
| 115 | + component: 'Input', | |
| 116 | + show: false, | |
| 117 | + }, | |
| 118 | + { | |
| 111 | 119 | field: 'profileId', |
| 112 | 120 | label: '所属产品', |
| 113 | 121 | required: true, |
| ... | ... | @@ -127,30 +135,35 @@ export const step1Schemas: FormSchema[] = [ |
| 127 | 135 | const options = await queryDeviceProfileBy({ |
| 128 | 136 | deviceType: formModel?.isUpdate ? formModel?.deviceType : null, |
| 129 | 137 | }); |
| 130 | - const { profileId } = formModel; | |
| 131 | - if (profileId) { | |
| 132 | - const selectRecord = options.find((item) => item.tbProfileId === profileId); | |
| 133 | - selectRecord && setFieldsValue({ transportType: selectRecord!.transportType }); | |
| 134 | - } | |
| 138 | + | |
| 135 | 139 | return options; |
| 136 | 140 | }, |
| 137 | 141 | labelField: 'name', |
| 138 | 142 | valueField: 'tbProfileId', |
| 139 | - onChange( | |
| 140 | - _value: string, | |
| 141 | - option: { deviceType: string; transportType: string; id: string } | |
| 142 | - ) { | |
| 143 | + onChange(_value: string, option: DeviceProfileDetail) { | |
| 143 | 144 | const { deviceType, transportType, id } = option; |
| 144 | 145 | setFieldsValue({ |
| 145 | 146 | deviceType: deviceType, |
| 146 | 147 | transportType, |
| 147 | 148 | deviceProfileId: id, |
| 148 | 149 | gatewayId: null, |
| 149 | - codeType: transportType === TransportTypeEnum.TCP ? TaskTypeEnum.MODBUS_RTU : null, | |
| 150 | 150 | code: null, |
| 151 | 151 | addressCode: null, |
| 152 | + tcpDeviceProtocol: option?.profileData?.transportConfiguration?.protocol, | |
| 152 | 153 | }); |
| 153 | 154 | }, |
| 155 | + onOptionsChange(options: (DeviceProfileDetail & Record<'value', string>)[]) { | |
| 156 | + const { profileId } = formModel; | |
| 157 | + if (profileId) { | |
| 158 | + const selectRecord = options.find((item) => item.value === profileId); | |
| 159 | + | |
| 160 | + selectRecord && | |
| 161 | + setFieldsValue({ | |
| 162 | + transportType: selectRecord!.transportType, | |
| 163 | + tcpDeviceProtocol: selectRecord?.profileData?.transportConfiguration?.protocol, | |
| 164 | + }); | |
| 165 | + } | |
| 166 | + }, | |
| 154 | 167 | showSearch: true, |
| 155 | 168 | placeholder: '请选择产品', |
| 156 | 169 | filterOption: (inputValue: string, option: Record<'label' | 'value', string>) => |
| ... | ... | @@ -177,35 +190,6 @@ export const step1Schemas: FormSchema[] = [ |
| 177 | 190 | }, |
| 178 | 191 | }, |
| 179 | 192 | { |
| 180 | - field: 'codeType', | |
| 181 | - label: '标识符类型', | |
| 182 | - component: 'RadioGroup', | |
| 183 | - dynamicRules({ values }) { | |
| 184 | - return [ | |
| 185 | - { | |
| 186 | - required: values?.transportType === TransportTypeEnum.TCP, | |
| 187 | - message: '请输入设备标识符', | |
| 188 | - }, | |
| 189 | - ]; | |
| 190 | - }, | |
| 191 | - // ifShow: ({ values }) => | |
| 192 | - // values?.transportType === TransportTypeEnum.TCP && | |
| 193 | - // (values.deviceType === DeviceTypeEnum.SENSOR || values.deviceType === DeviceTypeEnum.GATEWAY), | |
| 194 | - ifShow: ({ values }) => values?.transportType === TransportTypeEnum.TCP, | |
| 195 | - componentProps: ({ formActionType }) => { | |
| 196 | - const { setFieldsValue } = formActionType; | |
| 197 | - return { | |
| 198 | - options: [ | |
| 199 | - { label: '自定义', value: TaskTypeEnum.CUSTOM }, | |
| 200 | - { label: 'ModBus', value: TaskTypeEnum.MODBUS_RTU }, | |
| 201 | - ], | |
| 202 | - onChange() { | |
| 203 | - setFieldsValue({ addressCode: null }); | |
| 204 | - }, | |
| 205 | - }; | |
| 206 | - }, | |
| 207 | - }, | |
| 208 | - { | |
| 209 | 193 | field: 'addressCode', |
| 210 | 194 | label: '地址码', |
| 211 | 195 | dynamicRules({ values }) { |
| ... | ... | @@ -213,32 +197,25 @@ export const step1Schemas: FormSchema[] = [ |
| 213 | 197 | { |
| 214 | 198 | required: |
| 215 | 199 | values?.transportType === TransportTypeEnum.TCP && |
| 216 | - values?.deviceType === DeviceTypeEnum.SENSOR, | |
| 217 | - message: '请输入设备地址码', | |
| 200 | + values?.tcpDeviceProtocol === TCPProtocolTypeEnum.MODBUS_RTU, | |
| 201 | + message: '地址码范围为00~FF', | |
| 202 | + pattern: /^[0-9A-Fa-f]{2}$/, | |
| 218 | 203 | }, |
| 219 | 204 | ]; |
| 220 | 205 | }, |
| 221 | - component: 'RegisterAddressInput', | |
| 206 | + helpMessage: ['地址码范围为00~FF'], | |
| 207 | + component: 'HexInput', | |
| 222 | 208 | changeEvent: 'update:value', |
| 223 | 209 | valueField: 'value', |
| 224 | 210 | componentProps: { |
| 225 | - type: AddressTypeEnum.DEC, | |
| 226 | - maxValue: 247, | |
| 227 | - minValue: 0, | |
| 228 | - disabledSwitch: true, | |
| 229 | - }, | |
| 230 | - // ifShow: ({ values }) => { | |
| 231 | - // return ( | |
| 232 | - // values?.transportType === TransportTypeEnum.TCP && | |
| 233 | - // (values.deviceType === DeviceTypeEnum.SENSOR || | |
| 234 | - // values.deviceType === DeviceTypeEnum.GATEWAY) && | |
| 235 | - // values?.codeType === TaskTypeEnum.MODBUS_RTU | |
| 236 | - // ); | |
| 237 | - // }, | |
| 211 | + type: InputTypeEnum.HEX, | |
| 212 | + maxValue: parseInt('FF', 16), | |
| 213 | + placeholder: '请输入寄存器地址', | |
| 214 | + }, | |
| 238 | 215 | ifShow: ({ values }) => { |
| 239 | 216 | return ( |
| 240 | 217 | values?.transportType === TransportTypeEnum.TCP && |
| 241 | - values?.codeType === TaskTypeEnum.MODBUS_RTU | |
| 218 | + values?.tcpDeviceProtocol === TCPProtocolTypeEnum.MODBUS_RTU | |
| 242 | 219 | ); |
| 243 | 220 | }, |
| 244 | 221 | }, |
| ... | ... | @@ -264,7 +241,8 @@ export const step1Schemas: FormSchema[] = [ |
| 264 | 241 | }, |
| 265 | 242 | ifShow: ({ values }) => { |
| 266 | 243 | return ( |
| 267 | - values?.transportType === TransportTypeEnum.TCP && values?.codeType === TaskTypeEnum.CUSTOM | |
| 244 | + values?.transportType === TransportTypeEnum.TCP && | |
| 245 | + values?.tcpDeviceProtocol === TaskTypeEnum.CUSTOM | |
| 268 | 246 | ); |
| 269 | 247 | }, |
| 270 | 248 | }, | ... | ... |
| ... | ... | @@ -88,12 +88,12 @@ |
| 88 | 88 | import { validatorLongitude, validatorLatitude } from '/@/utils/rules'; |
| 89 | 89 | import { getOrganizationList } from '/@/api/system/system'; |
| 90 | 90 | import { copyTransFun } from '/@/utils/fnUtils'; |
| 91 | - import { TaskTypeEnum } from '/@/views/task/center/config'; | |
| 92 | 91 | import { toRaw } from 'vue'; |
| 93 | 92 | import { FileItem } from '/@/components/Form/src/components/ApiUpload.vue'; |
| 94 | 93 | import { buildUUID } from '/@/utils/uuid'; |
| 95 | 94 | import { useMessage } from '/@/hooks/web/useMessage'; |
| 96 | 95 | import { computed } from 'vue'; |
| 96 | + import { TCPProtocolTypeEnum } from '/@/enums/deviceEnum'; | |
| 97 | 97 | |
| 98 | 98 | export default defineComponent({ |
| 99 | 99 | components: { |
| ... | ... | @@ -405,10 +405,11 @@ |
| 405 | 405 | icon: [{ uid: buildUUID(), name: 'name', url: deviceInfo.avatar } as FileItem], |
| 406 | 406 | }); |
| 407 | 407 | } |
| 408 | + | |
| 408 | 409 | setFieldsValue({ |
| 409 | 410 | ...data, |
| 410 | 411 | code: data?.code, |
| 411 | - addressCode: parseInt(data?.code || '', 16), | |
| 412 | + addressCode: data?.code, | |
| 412 | 413 | isUpdate: unref(isUpdate1), |
| 413 | 414 | }); |
| 414 | 415 | } |
| ... | ... | @@ -424,9 +425,9 @@ |
| 424 | 425 | ...(value?.code || value?.addressCode |
| 425 | 426 | ? { |
| 426 | 427 | code: |
| 427 | - value?.codeType === TaskTypeEnum.CUSTOM | |
| 428 | + value?.tcpDeviceProtocol === TCPProtocolTypeEnum.CUSTOM | |
| 428 | 429 | ? value?.code |
| 429 | - : (value?.addressCode || '').toString(16).padStart(2, '0').toUpperCase(), | |
| 430 | + : value?.addressCode || '', | |
| 430 | 431 | } |
| 431 | 432 | : {}), |
| 432 | 433 | }; | ... | ... |
| ... | ... | @@ -10,7 +10,9 @@ import { |
| 10 | 10 | ServiceCallTypeEnum, |
| 11 | 11 | CommandDeliveryWayEnum, |
| 12 | 12 | CommandDeliveryWayNameEnum, |
| 13 | + TCPProtocolTypeEnum, | |
| 13 | 14 | } from '/@/enums/deviceEnum'; |
| 15 | +import { DeviceRecord, DeviceTypeEnum } from '/@/api/device/model/deviceModel'; | |
| 14 | 16 | |
| 15 | 17 | export interface CommandDeliveryFormFieldType { |
| 16 | 18 | [CommandFieldsEnum.COMMAND_TYPE]: CommandTypeEnum; |
| ... | ... | @@ -36,10 +38,16 @@ export enum CommandFieldsEnum { |
| 36 | 38 | |
| 37 | 39 | useComponentRegister('JSONEditor', JSONEditor); |
| 38 | 40 | |
| 39 | -export const CommandSchemas = ( | |
| 40 | - transportType: TransportTypeEnum, | |
| 41 | - deviceProfileId: string | |
| 42 | -): FormSchema[] => { | |
| 41 | +export const CommandSchemas = (deviceRecord: DeviceRecord): FormSchema[] => { | |
| 42 | + const { transportType, deviceProfileId, deviceType } = deviceRecord; | |
| 43 | + | |
| 44 | + const isTCPTransport = transportType === TransportTypeEnum.TCP; | |
| 45 | + | |
| 46 | + const isTCPModbus = | |
| 47 | + isTCPTransport && | |
| 48 | + deviceRecord.deviceProfile?.profileData?.transportConfiguration?.protocol === | |
| 49 | + TCPProtocolTypeEnum.MODBUS_RTU; | |
| 50 | + | |
| 43 | 51 | return [ |
| 44 | 52 | { |
| 45 | 53 | field: CommandFieldsEnum.COMMAND_TYPE, |
| ... | ... | @@ -49,11 +57,19 @@ export const CommandSchemas = ( |
| 49 | 57 | required: true, |
| 50 | 58 | componentProps: ({ formActionType }) => { |
| 51 | 59 | const { setFieldsValue } = formActionType; |
| 60 | + | |
| 61 | + const getOptions = () => { | |
| 62 | + const options = [{ label: CommandTypeNameEnum.CUSTOM, value: CommandTypeEnum.CUSTOM }]; | |
| 63 | + | |
| 64 | + if (isTCPModbus || (isTCPTransport && deviceType === DeviceTypeEnum.SENSOR)) | |
| 65 | + return options; | |
| 66 | + | |
| 67 | + options.push({ label: CommandTypeNameEnum.SERVICE, value: CommandTypeEnum.SERVICE }); | |
| 68 | + | |
| 69 | + return options; | |
| 70 | + }; | |
| 52 | 71 | return { |
| 53 | - options: [ | |
| 54 | - { label: CommandTypeNameEnum.CUSTOM, value: CommandTypeEnum.CUSTOM }, | |
| 55 | - { label: CommandTypeNameEnum.SERVICE, value: CommandTypeEnum.SERVICE }, | |
| 56 | - ], | |
| 72 | + options: getOptions(), | |
| 57 | 73 | onChange() { |
| 58 | 74 | setFieldsValue({ |
| 59 | 75 | [CommandFieldsEnum.SERVICE]: null, | ... | ... |
| ... | ... | @@ -37,14 +37,15 @@ |
| 37 | 37 | defineEmits(['register']); |
| 38 | 38 | |
| 39 | 39 | const thingsModelFormRef = ref<InstanceType<typeof ThingsModelForm>>(); |
| 40 | - const deviceDetail = ref<DeviceRecord>(); | |
| 40 | + const props = defineProps<{ | |
| 41 | + deviceDetail: DeviceRecord; | |
| 42 | + }>(); | |
| 41 | 43 | |
| 42 | 44 | const [registerModal, { setModalProps }] = useModalInner( |
| 43 | 45 | (params: ModalParamsType<DeviceRecord>) => { |
| 44 | 46 | const { record } = params; |
| 45 | - deviceDetail.value = record; | |
| 46 | 47 | setProps({ |
| 47 | - schemas: CommandSchemas(record.transportType as TransportTypeEnum, record.deviceProfileId), | |
| 48 | + schemas: CommandSchemas(record), | |
| 48 | 49 | }); |
| 49 | 50 | } |
| 50 | 51 | ); |
| ... | ... | @@ -72,7 +73,7 @@ |
| 72 | 73 | }; |
| 73 | 74 | |
| 74 | 75 | const handleValidateDeviceActive = async (): Promise<boolean> => { |
| 75 | - const result = await getDeviceActiveTime(unref(deviceDetail)!.tbDeviceId); | |
| 76 | + const result = await getDeviceActiveTime(unref(props.deviceDetail)!.tbDeviceId); | |
| 76 | 77 | const [firstItem] = result; |
| 77 | 78 | return !!firstItem.value; |
| 78 | 79 | }; |
| ... | ... | @@ -83,7 +84,7 @@ |
| 83 | 84 | ) => { |
| 84 | 85 | const { commandType, service } = values; |
| 85 | 86 | |
| 86 | - const isTcpDevice = unref(deviceDetail)?.transportType === TransportTypeEnum.TCP; | |
| 87 | + const isTcpDevice = unref(props.deviceDetail)?.transportType === TransportTypeEnum.TCP; | |
| 87 | 88 | if (commandType === CommandTypeEnum.CUSTOM) { |
| 88 | 89 | if (isTcpDevice) { |
| 89 | 90 | return values.tcpCommandValue; |
| ... | ... | @@ -124,7 +125,7 @@ |
| 124 | 125 | params: handleCommandParams(values, serviceCommand), |
| 125 | 126 | }; |
| 126 | 127 | |
| 127 | - await commandIssuanceApi(callType, unref(deviceDetail)!.tbDeviceId, rpcCommands); | |
| 128 | + await commandIssuanceApi(callType, unref(props.deviceDetail)!.tbDeviceId, rpcCommands); | |
| 128 | 129 | |
| 129 | 130 | createMessage.success('命令下发成功'); |
| 130 | 131 | } finally { | ... | ... |
| 1 | -import { DeviceModelOfMatterAttrs } from '/@/api/device/model/deviceModel'; | |
| 1 | +import { DeviceModelOfMatterAttrs, DeviceRecord } from '/@/api/device/model/deviceModel'; | |
| 2 | 2 | import { DataType, Specs } from '/@/api/device/model/modelOfMatterModel'; |
| 3 | +import { TCPProtocolTypeEnum, TransportTypeEnum } from '/@/enums/deviceEnum'; | |
| 3 | 4 | import { DataTypeEnum } from '/@/enums/objectModelEnum'; |
| 4 | 5 | import { isArray } from '/@/utils/is'; |
| 5 | 6 | |
| ... | ... | @@ -31,18 +32,26 @@ export interface SocketInfoDataSourceItemType extends BaseAdditionalInfo { |
| 31 | 32 | } |
| 32 | 33 | |
| 33 | 34 | export function buildTableDataSourceByObjectModel( |
| 34 | - models: DeviceModelOfMatterAttrs[] | |
| 35 | + models: DeviceModelOfMatterAttrs[], | |
| 36 | + deviceDetail: DeviceRecord | |
| 35 | 37 | ): SocketInfoDataSourceItemType[] { |
| 38 | + const isTCPTransportType = deviceDetail.transportType === TransportTypeEnum.TCP; | |
| 39 | + | |
| 40 | + const isModbusDevice = | |
| 41 | + isTCPTransportType && | |
| 42 | + deviceDetail?.deviceProfile?.profileData?.transportConfiguration?.protocol === | |
| 43 | + TCPProtocolTypeEnum.MODBUS_RTU; | |
| 44 | + | |
| 36 | 45 | function getAdditionalInfoByDataType(dataType?: DataType) { |
| 37 | 46 | const { specs, specsList, type } = dataType || {}; |
| 38 | 47 | if (isArray(specs)) return {}; |
| 39 | 48 | const { unit, boolClose, boolOpen, unitName } = (specs as Partial<Specs>) || {}; |
| 40 | 49 | const result = { unit, boolClose, boolOpen, unitName }; |
| 41 | - if (type == DataTypeEnum.ENUM && specsList && specsList.length) { | |
| 50 | + if ((type == DataTypeEnum.ENUM && specsList && specsList.length) || isModbusDevice) { | |
| 42 | 51 | Reflect.set( |
| 43 | 52 | result, |
| 44 | 53 | 'enum', |
| 45 | - specsList.reduce((prev, next) => ({ ...prev, [next.value!]: next.name }), {}) | |
| 54 | + (specsList || []).reduce((prev, next) => ({ ...prev, [next.value!]: next.name }), {}) | |
| 46 | 55 | ); |
| 47 | 56 | } |
| 48 | 57 | ... | ... |
| ... | ... | @@ -19,7 +19,7 @@ |
| 19 | 19 | import { ModeSwitchButton, EnumTableCardMode } from '/@/components/Widget'; |
| 20 | 20 | import { toRaw } from 'vue'; |
| 21 | 21 | import { DataActionModeEnum } from '/@/enums/toolEnum'; |
| 22 | - import { ReadAndWriteEnum } from '/@/enums/deviceEnum'; | |
| 22 | + import { ReadAndWriteEnum, TCPProtocolTypeEnum, TransportTypeEnum } from '/@/enums/deviceEnum'; | |
| 23 | 23 | import { ObjectModelCommandDeliveryModal } from './ObjectModelCommandDeliveryModal'; |
| 24 | 24 | import { ModalParamsType } from '/#/utils'; |
| 25 | 25 | import { AreaChartOutlined } from '@ant-design/icons-vue'; |
| ... | ... | @@ -275,17 +275,30 @@ |
| 275 | 275 | openModal(true); |
| 276 | 276 | }; |
| 277 | 277 | |
| 278 | + const getIsModbusDevice = computed( | |
| 279 | + () => | |
| 280 | + props.deviceDetail.transportType === TransportTypeEnum.TCP && | |
| 281 | + props.deviceDetail?.deviceProfile?.profileData?.transportConfiguration?.protocol === | |
| 282 | + TCPProtocolTypeEnum.MODBUS_RTU | |
| 283 | + ); | |
| 284 | + | |
| 278 | 285 | onMounted(async () => { |
| 279 | 286 | const { deviceProfileId } = props.deviceDetail; |
| 280 | 287 | const value = await getDeviceAttrs({ deviceProfileId }); |
| 281 | 288 | socketInfo.attrKeys = isArray(value) ? value.map((item) => item.identifier) : []; |
| 282 | - socketInfo.rawDataSource = buildTableDataSourceByObjectModel(value); | |
| 289 | + socketInfo.rawDataSource = buildTableDataSourceByObjectModel(value, props.deviceDetail); | |
| 283 | 290 | setDataSource(); |
| 284 | 291 | open(); |
| 285 | 292 | }); |
| 286 | 293 | |
| 287 | 294 | const formatValue = (item: SocketInfoDataSourceItemType) => { |
| 288 | - if (isNullOrUnDef(item)) return '--'; | |
| 295 | + if (isNullOrUnDef(item) || isNullOrUnDef(item.value)) return '--'; | |
| 296 | + | |
| 297 | + if (unref(getIsModbusDevice) && item.type === DataTypeEnum.BOOL) { | |
| 298 | + const _result = Reflect.get(item.enum || {}, item.value as string); | |
| 299 | + return isNullOrUnDef(_result) ? item.value : _result; | |
| 300 | + } | |
| 301 | + | |
| 289 | 302 | switch (item.type) { |
| 290 | 303 | case DataTypeEnum.BOOL: |
| 291 | 304 | return !!Number(item.value) ? item.boolOpen : item.boolClose; |
| ... | ... | @@ -300,7 +313,7 @@ |
| 300 | 313 | const handleSendCommandModal = (data: SocketInfoDataSourceItemType) => { |
| 301 | 314 | openSendCommandModal(true, { |
| 302 | 315 | mode: DataActionModeEnum.READ, |
| 303 | - record: { ...toRaw(data.detail), deviceDetail: props.deviceDetail as any }, | |
| 316 | + record: { objectModel: toRaw(unref(data.detail)), deviceDetail: props.deviceDetail as any }, | |
| 304 | 317 | } as ModalParamsType); |
| 305 | 318 | }; |
| 306 | 319 | ... | ... |
src/views/device/list/cpns/tabs/ObjectModelCommandDeliveryModal/config.ts
deleted
100644 → 0
| 1 | -import { StructJSON } from '/@/api/device/model/modelOfMatterModel'; | |
| 2 | -import { FormSchema } from '/@/components/Form'; | |
| 3 | -import { validateTCPCustomCommand } from '/@/components/Form/src/components/ThingsModelForm'; | |
| 4 | -import { DataTypeEnum } from '/@/enums/objectModelEnum'; | |
| 5 | - | |
| 6 | -const InsertString = (t, c, n) => { | |
| 7 | - const r: string | number[] = []; | |
| 8 | - | |
| 9 | - for (let i = 0; i * 2 < t.length; i++) { | |
| 10 | - r.push(t.substr(i * 2, n)); | |
| 11 | - } | |
| 12 | - return r.join(c); | |
| 13 | -}; | |
| 14 | -const FillString = (t, c, n, b) => { | |
| 15 | - if (t == '' || c.length != 1 || n <= t.length) { | |
| 16 | - return t; | |
| 17 | - } | |
| 18 | - const l = t.length; | |
| 19 | - | |
| 20 | - for (let i = 0; i < n - l; i++) { | |
| 21 | - if (b == true) { | |
| 22 | - t = c + t; | |
| 23 | - } else { | |
| 24 | - t += c; | |
| 25 | - } | |
| 26 | - } | |
| 27 | - return t; | |
| 28 | -}; | |
| 29 | -const SingleToHex = (t) => { | |
| 30 | - if (t == '') { | |
| 31 | - return ''; | |
| 32 | - } | |
| 33 | - t = parseFloat(t); | |
| 34 | - | |
| 35 | - if (isNaN(t) == true) { | |
| 36 | - return 'Error'; | |
| 37 | - } | |
| 38 | - if (t == 0) { | |
| 39 | - return '00000000'; | |
| 40 | - } | |
| 41 | - let s, e, m; | |
| 42 | - | |
| 43 | - if (t > 0) { | |
| 44 | - s = 0; | |
| 45 | - } else { | |
| 46 | - s = 1; | |
| 47 | - | |
| 48 | - t = 0 - t; | |
| 49 | - } | |
| 50 | - m = t.toString(2); | |
| 51 | - | |
| 52 | - if (m >= 1) { | |
| 53 | - if (m.indexOf('.') == -1) { | |
| 54 | - m = m + '.0'; | |
| 55 | - } | |
| 56 | - e = m.indexOf('.') - 1; | |
| 57 | - } else { | |
| 58 | - e = 1 - m.indexOf('1'); | |
| 59 | - } | |
| 60 | - if (e >= 0) { | |
| 61 | - m = m.replace('.', ''); | |
| 62 | - } else { | |
| 63 | - m = m.substring(m.indexOf('1')); | |
| 64 | - } | |
| 65 | - if (m.length > 24) { | |
| 66 | - m = m.substr(0, 24); | |
| 67 | - } else { | |
| 68 | - m = FillString(m, '0', 24, false); | |
| 69 | - } | |
| 70 | - m = m.substring(1); | |
| 71 | - | |
| 72 | - e = (e + 127).toString(2); | |
| 73 | - | |
| 74 | - e = FillString(e, '0', 8, true); | |
| 75 | - | |
| 76 | - let r = parseInt(s + e + m, 2).toString(16); | |
| 77 | - | |
| 78 | - r = FillString(r, '0', 8, true); | |
| 79 | - | |
| 80 | - return InsertString(r, ' ', 2).toUpperCase(); | |
| 81 | -}; | |
| 82 | - | |
| 83 | -const FormatHex = (t, n, ie) => { | |
| 84 | - const r: string[] = []; | |
| 85 | - | |
| 86 | - let s = ''; | |
| 87 | - | |
| 88 | - let c = 0; | |
| 89 | - | |
| 90 | - for (let i = 0; i < t.length; i++) { | |
| 91 | - if (t.charAt(i) != ' ') { | |
| 92 | - s += t.charAt(i); | |
| 93 | - | |
| 94 | - c += 1; | |
| 95 | - | |
| 96 | - if (c == n) { | |
| 97 | - r.push(s); | |
| 98 | - | |
| 99 | - s = ''; | |
| 100 | - | |
| 101 | - c = 0; | |
| 102 | - } | |
| 103 | - } | |
| 104 | - if (ie == false) { | |
| 105 | - if (i == t.length - 1 && s != '') { | |
| 106 | - r.push(s); | |
| 107 | - } | |
| 108 | - } | |
| 109 | - } | |
| 110 | - return r.join('\n'); | |
| 111 | -}; | |
| 112 | -const FormatHexBatch = (t, n, ie) => { | |
| 113 | - const a = t.split('\n'); | |
| 114 | - | |
| 115 | - const r: string[] = []; | |
| 116 | - | |
| 117 | - for (let i = 0; i < a.length; i++) { | |
| 118 | - r[i] = FormatHex(a[i], n, ie); | |
| 119 | - } | |
| 120 | - return r.join('\n'); | |
| 121 | -}; | |
| 122 | -const SingleToHexBatch = (t) => { | |
| 123 | - const a = t.split('\n'); | |
| 124 | - | |
| 125 | - const r: string[] = []; | |
| 126 | - | |
| 127 | - for (let i = 0; i < a.length; i++) { | |
| 128 | - r[i] = SingleToHex(a[i]); | |
| 129 | - } | |
| 130 | - return r.join('\r\n'); | |
| 131 | -}; | |
| 132 | - | |
| 133 | -const formSchemasConfig = (schemas: StructJSON, actionType: string): FormSchema[] => { | |
| 134 | - const { identifier, functionName, dataType } = schemas; | |
| 135 | - | |
| 136 | - if (dataType?.type === DataTypeEnum.STRING) { | |
| 137 | - return [ | |
| 138 | - { | |
| 139 | - field: identifier, | |
| 140 | - label: functionName!, | |
| 141 | - component: 'Input', | |
| 142 | - rules: [{ required: true, validator: validateTCPCustomCommand }], | |
| 143 | - componentProps: { | |
| 144 | - placeholder: `请输入${functionName}`, | |
| 145 | - }, | |
| 146 | - }, | |
| 147 | - ]; | |
| 148 | - } | |
| 149 | - | |
| 150 | - if (actionType == '06') { | |
| 151 | - return [ | |
| 152 | - { | |
| 153 | - field: identifier, | |
| 154 | - label: functionName!, | |
| 155 | - component: 'InputNumber', | |
| 156 | - rules: [{ required: true, message: '请输入正数' }], | |
| 157 | - componentProps: { | |
| 158 | - min: 0, | |
| 159 | - precision: 2, | |
| 160 | - placeholder: `请输入正数`, | |
| 161 | - }, | |
| 162 | - }, | |
| 163 | - ]; | |
| 164 | - } else if (actionType == '05') { | |
| 165 | - return [ | |
| 166 | - { | |
| 167 | - field: identifier, | |
| 168 | - label: functionName!, | |
| 169 | - component: 'InputNumber', | |
| 170 | - rules: [{ required: true, message: '请输入值' }], | |
| 171 | - componentProps: { | |
| 172 | - min: 0, | |
| 173 | - max: 1, | |
| 174 | - precision: 0, | |
| 175 | - placeholder: `请输入0或1`, | |
| 176 | - }, | |
| 177 | - }, | |
| 178 | - ]; | |
| 179 | - } else { | |
| 180 | - return [ | |
| 181 | - { | |
| 182 | - field: identifier, | |
| 183 | - label: functionName!, | |
| 184 | - component: 'InputNumber', | |
| 185 | - rules: [{ required: true, message: '请输入值' }], | |
| 186 | - componentProps: { | |
| 187 | - placeholder: `请输入数字`, | |
| 188 | - precision: 2, | |
| 189 | - }, | |
| 190 | - }, | |
| 191 | - ]; | |
| 192 | - } | |
| 193 | -}; | |
| 194 | - | |
| 195 | -export { | |
| 196 | - InsertString, | |
| 197 | - FillString, | |
| 198 | - SingleToHex, | |
| 199 | - FormatHex, | |
| 200 | - FormatHexBatch, | |
| 201 | - SingleToHexBatch, | |
| 202 | - formSchemasConfig, | |
| 203 | -}; |
| 1 | 1 | <script lang="ts" setup> |
| 2 | - import { ref } from 'vue'; | |
| 3 | - import { useGenDynamicForm } from './useGenDynamicForm'; | |
| 2 | + import { ref, unref } from 'vue'; | |
| 3 | + import { useGenerateFormSchemasByObjectModel } from './useGenerateFormSchemasByObjectModel'; | |
| 4 | 4 | import { ModalParamsType } from '/#/utils'; |
| 5 | - import { DeviceModelOfMatterAttrs } from '/@/api/device/model/deviceModel'; | |
| 6 | - import { StructJSON } from '/@/api/device/model/modelOfMatterModel'; | |
| 5 | + import { DeviceModelOfMatterAttrs, DeviceRecord } from '/@/api/device/model/deviceModel'; | |
| 7 | 6 | import { BasicForm, useForm } from '/@/components/Form'; |
| 8 | 7 | import { BasicModal, useModalInner } from '/@/components/Modal'; |
| 9 | - import { sendCommandOneway } from '/@/api/dataBoard'; | |
| 8 | + import { CommandTypeEnum } from '/@/enums/deviceEnum'; | |
| 9 | + import { DataTypeEnum, FunctionTypeEnum } from '/@/enums/objectModelEnum'; | |
| 10 | + import { useCommandDelivery } from '/@/hooks/business/useCommandDelivery'; | |
| 10 | 11 | import { useMessage } from '/@/hooks/web/useMessage'; |
| 11 | - import { unref } from 'vue'; | |
| 12 | - import { genModbusCommand } from '/@/api/task'; | |
| 13 | - import { TaskTypeEnum } from '/@/views/task/center/config'; | |
| 14 | - import { SingleToHex, formSchemasConfig } from './config'; | |
| 15 | - import { DataTypeEnum } from '/@/enums/objectModelEnum'; | |
| 16 | - import { TransportTypeEnum } from '/@/enums/deviceEnum'; | |
| 17 | 12 | |
| 18 | 13 | defineEmits(['register']); |
| 19 | 14 | const props = defineProps<{ deviceId: string; deviceName: string }>(); |
| ... | ... | @@ -24,110 +19,25 @@ |
| 24 | 19 | layout: 'vertical', |
| 25 | 20 | }); |
| 26 | 21 | |
| 27 | - const { genForm, transformValue } = useGenDynamicForm(); | |
| 28 | - | |
| 29 | - const keys = ref<string[]>([]); | |
| 30 | - | |
| 31 | - const modBUSForm = ref<any>({}); | |
| 32 | - const isShowModBUS = ref<Boolean>(false); //用于判断标识符类型是否时自定义还是modBUS | |
| 33 | - const isShowActionType = ref<Boolean>(true); //判断设备属性标识符为modBus时没有填写扩展描述 | |
| 34 | - const formField = ref(''); //存一个表单取值的field | |
| 35 | - const zoomFactorValue = ref<number>(1); //缩放因子 | |
| 36 | - const isShowMultiply = ref<Boolean>(false); // 只有tcp --> int和double类型才相乘缩放因子 | |
| 37 | - const deviceTransportType = ref<string>(); | |
| 38 | - const objectDataType = ref<DataTypeEnum>(); | |
| 39 | - | |
| 40 | - const [register] = useModalInner(async (params: ModalParamsType<DeviceModelOfMatterAttrs>) => { | |
| 41 | - const { record } = params; | |
| 42 | - const { name, detail, identifier, deviceDetail, extensionDesc } = record; | |
| 43 | - const { dataType } = detail; | |
| 44 | - const { type } = dataType || {}; | |
| 45 | - const { codeType, deviceProfile, code } = deviceDetail || {}; | |
| 46 | - const { transportType } = deviceProfile || {}; | |
| 47 | - const { registerAddress, actionType, zoomFactor } = extensionDesc || {}; //获取扩展描述内容 | |
| 48 | - formField.value = identifier; | |
| 49 | - zoomFactorValue.value = zoomFactor ? Number(zoomFactor) : 1; | |
| 50 | - isShowMultiply.value = type == 'INT' || type == 'DOUBLE' ? true : false; | |
| 51 | - deviceTransportType.value = transportType; | |
| 52 | - objectDataType.value = type; | |
| 53 | - | |
| 54 | - let schemas = [{ dataType: dataType, identifier, functionName: name } as StructJSON]; | |
| 55 | - | |
| 56 | - if (type === DataTypeEnum.STRUCT) { | |
| 57 | - schemas = dataType?.specs as StructJSON[]; | |
| 58 | - } | |
| 59 | - | |
| 60 | - keys.value = schemas.map((item) => { | |
| 61 | - return item.identifier!; | |
| 62 | - }); | |
| 63 | - isShowActionType.value = actionType ? true : false; //判断modBUS类型时 物模型是否填写扩展描述 | |
| 64 | - | |
| 65 | - //是modBUS类型的就用另外的表单 | |
| 66 | - //判断是否是TCP ==> modBus的下发命令 | |
| 67 | - if (codeType === TaskTypeEnum.MODBUS_RTU && transportType == TransportTypeEnum.TCP) { | |
| 68 | - isShowModBUS.value = true; | |
| 69 | - modBUSForm.value = { | |
| 70 | - crc: 'CRC_16_LOWER', | |
| 71 | - deviceCode: code, | |
| 72 | - method: actionType == '16' ? '10' : actionType, | |
| 73 | - registerAddress, | |
| 74 | - registerNumber: 1, | |
| 75 | - registerValues: [], | |
| 76 | - }; | |
| 77 | - setProps({ schemas: formSchemasConfig(schemas[0], actionType) }); | |
| 78 | - } else { | |
| 79 | - isShowModBUS.value = false; | |
| 80 | - if (transportType === TransportTypeEnum.TCP) { | |
| 81 | - setProps({ | |
| 82 | - schemas: [ | |
| 83 | - { | |
| 84 | - field: 'command', | |
| 85 | - label: name, | |
| 86 | - component: 'Input', | |
| 87 | - required: true, | |
| 88 | - rules: [ | |
| 89 | - { | |
| 90 | - pattern: /^[\s0-9a-fA-F]+$/, | |
| 91 | - required: true, | |
| 92 | - message: '请输入ASCII或HEX服务命令(0~9/A~F)', | |
| 93 | - }, | |
| 94 | - ], | |
| 95 | - componentProps: { | |
| 96 | - placeholder: `请输入${name}`, | |
| 97 | - }, | |
| 98 | - }, | |
| 99 | - ], | |
| 100 | - }); | |
| 101 | - } else { | |
| 102 | - const formSchemas = genForm(schemas); | |
| 103 | - setProps({ schemas: formSchemas }); | |
| 104 | - } | |
| 105 | - } | |
| 106 | - | |
| 107 | - resetFields(); | |
| 108 | - }); | |
| 109 | - | |
| 110 | - const getArray = (values) => { | |
| 111 | - const str = values.replace(/\s+/g, ''); | |
| 112 | - const array: any = []; | |
| 113 | - | |
| 114 | - for (let i = 0; i < str.length; i += 4) { | |
| 115 | - const chunk = parseInt(str.substring(i, i + 4), 16); | |
| 116 | - array.push(chunk); | |
| 22 | + const { getFormByObjectModel } = useGenerateFormSchemasByObjectModel(); | |
| 23 | + | |
| 24 | + const currentParams = ref<{ | |
| 25 | + deviceDetail: DeviceRecord; | |
| 26 | + objectModel: DeviceModelOfMatterAttrs; | |
| 27 | + }>(); | |
| 28 | + | |
| 29 | + const [register] = useModalInner( | |
| 30 | + async ( | |
| 31 | + params: ModalParamsType<{ deviceDetail: DeviceRecord; objectModel: DeviceModelOfMatterAttrs }> | |
| 32 | + ) => { | |
| 33 | + const { record } = params; | |
| 34 | + currentParams.value = record; | |
| 35 | + const { objectModel, deviceDetail } = record; | |
| 36 | + const schemas = getFormByObjectModel(objectModel, deviceDetail); | |
| 37 | + setProps({ schemas }); | |
| 38 | + resetFields(); | |
| 117 | 39 | } |
| 118 | - return array; | |
| 119 | - }; | |
| 120 | - | |
| 121 | - // 获取小数 | |
| 122 | - const getFloatPart = (number: string | number) => { | |
| 123 | - const isLessZero = Number(number) < 0; | |
| 124 | - number = number.toString(); | |
| 125 | - const floatPartStartIndex = number.indexOf('.'); | |
| 126 | - const value = ~floatPartStartIndex | |
| 127 | - ? `${isLessZero ? '-' : ''}0.${number.substring(floatPartStartIndex + 1)}` | |
| 128 | - : '0'; | |
| 129 | - return Number(value); | |
| 130 | - }; | |
| 40 | + ); | |
| 131 | 41 | |
| 132 | 42 | const { createMessage } = useMessage(); |
| 133 | 43 | const loading = ref(false); |
| ... | ... | @@ -136,82 +46,29 @@ |
| 136 | 46 | loading.value = true; |
| 137 | 47 | if (!props.deviceId) return; |
| 138 | 48 | |
| 139 | - const sendValue = ref({}); | |
| 140 | - //判断tcp类型 标识符是自定义还是ModBus | |
| 141 | - if (unref(objectDataType) === DataTypeEnum.STRING) { | |
| 142 | - const flag = await validate(); | |
| 143 | - if (!flag) return; | |
| 144 | - const value = getFieldsValue()[unref(formField)]; | |
| 145 | - sendValue.value = value; | |
| 146 | - } else if (unref(isShowModBUS)) { | |
| 147 | - if (!unref(isShowActionType)) { | |
| 148 | - createMessage.warning('当前物模型扩展描述没有填写'); | |
| 149 | - return; | |
| 150 | - } | |
| 151 | - const flag = await validate(); | |
| 152 | - if (!flag) return; | |
| 153 | - | |
| 154 | - const oldValue = getFieldsValue()[unref(formField)]; | |
| 155 | - modBUSForm.value.registerNumber = 1; | |
| 156 | - modBUSForm.value.registerValues = [oldValue]; | |
| 157 | - | |
| 158 | - if (unref(isShowMultiply) && unref(modBUSForm).method == '06') { | |
| 159 | - const newValue = | |
| 160 | - Math.trunc(oldValue) * unref(zoomFactorValue) + | |
| 161 | - getFloatPart(oldValue) * unref(zoomFactorValue); | |
| 162 | - if (newValue % 1 != 0) { | |
| 163 | - createMessage.warning(`属性下发类型必须是整数,缩放因子为${unref(zoomFactorValue)}`); | |
| 164 | - return; | |
| 165 | - } | |
| 166 | - | |
| 167 | - if (oldValue * unref(zoomFactorValue) > 65535) { | |
| 168 | - createMessage.warning(`属性下发值不能超过65535,缩放因子是${unref(zoomFactorValue)}`); | |
| 169 | - return; | |
| 170 | - } | |
| 171 | - //bool类型的就不用去乘缩放因子了 | |
| 172 | - modBUSForm.value.registerValues = [newValue]; | |
| 173 | - } | |
| 49 | + await validate(); | |
| 50 | + const { deviceDetail, objectModel } = unref(currentParams) || {}; | |
| 174 | 51 | |
| 175 | - if (unref(modBUSForm).method == '16' || unref(modBUSForm).method == '10') { | |
| 176 | - const regex = /^-?\d+(\.\d{0,2})?$/; | |
| 177 | - const values = | |
| 178 | - Math.trunc(oldValue) * unref(zoomFactorValue) + | |
| 179 | - getFloatPart(oldValue) * unref(zoomFactorValue); | |
| 180 | - | |
| 181 | - if (!regex.test(values as any)) { | |
| 182 | - createMessage.warning(`属性下发值精确到两位小数,缩放因子是${unref(zoomFactorValue)}`); | |
| 183 | - return; | |
| 184 | - } | |
| 185 | - | |
| 186 | - const newValue = | |
| 187 | - values == 0 ? [0, 0] : getArray(SingleToHex(unref(isShowMultiply) ? values : oldValue)); | |
| 188 | - modBUSForm.value.registerValues = newValue; | |
| 189 | - modBUSForm.value.registerNumber = 2; | |
| 190 | - modBUSForm.value.method = '10'; | |
| 191 | - } | |
| 192 | - | |
| 193 | - sendValue.value = await genModbusCommand(unref(modBUSForm)); | |
| 194 | - } else { | |
| 195 | - await validate(); | |
| 196 | - const _value = transformValue(getFieldsValue()); | |
| 197 | - sendValue.value = unref(keys).reduce((prev, next) => { | |
| 198 | - return { ...prev, [next]: _value[next] }; | |
| 199 | - }, {}); | |
| 200 | - | |
| 201 | - // tcp 设备下发字符串 | |
| 202 | - if (unref(deviceTransportType) === TransportTypeEnum.TCP) { | |
| 203 | - sendValue.value = Object.values(_value).join('').replaceAll(/\s/g, ''); | |
| 204 | - } | |
| 52 | + let value = getFieldsValue(); | |
| 53 | + if (objectModel?.detail.dataType.type !== DataTypeEnum.STRUCT) { | |
| 54 | + value = value[objectModel!.identifier]; | |
| 205 | 55 | } |
| 206 | 56 | |
| 207 | - await sendCommandOneway({ | |
| 208 | - deviceId: props.deviceId, | |
| 209 | - value: { | |
| 210 | - persistent: true, | |
| 211 | - method: 'methodThingskit', | |
| 212 | - params: unref(sendValue), | |
| 57 | + const { doCommandDelivery } = useCommandDelivery(); | |
| 58 | + | |
| 59 | + await doCommandDelivery({ | |
| 60 | + deviceDetail, | |
| 61 | + objectModel: { | |
| 62 | + ...(objectModel || {}), | |
| 63 | + functionName: objectModel!.name, | |
| 64 | + identifier: objectModel!.identifier, | |
| 65 | + functionType: FunctionTypeEnum.PROPERTIES, | |
| 66 | + specs: objectModel?.detail, | |
| 213 | 67 | }, |
| 68 | + cmdType: CommandTypeEnum.ATTRIBUTE, | |
| 69 | + value, | |
| 214 | 70 | }); |
| 71 | + | |
| 215 | 72 | createMessage.success('属性下发成功'); |
| 216 | 73 | } catch (error) { |
| 217 | 74 | throw error; | ... | ... |
src/views/device/list/cpns/tabs/ObjectModelCommandDeliveryModal/useGenerateFormSchemasByObjectModel.ts
renamed from
src/views/device/list/cpns/tabs/ObjectModelCommandDeliveryModal/useGenDynamicForm.ts
| 1 | +import { unref } from 'vue'; | |
| 2 | +import { DeviceModelOfMatterAttrs, DeviceRecord } from '/@/api/device/model/deviceModel'; | |
| 1 | 3 | import { DataType, Specs, StructJSON } from '/@/api/device/model/modelOfMatterModel'; |
| 2 | -import { JSONEditor } from '/@/components/CodeEditor'; | |
| 3 | -import { FormSchema, useComponentRegister } from '/@/components/Form'; | |
| 4 | -import { DataTypeEnum } from '/@/enums/objectModelEnum'; | |
| 5 | -import { useJsonParse } from '/@/hooks/business/useJsonParse'; | |
| 4 | +import { FormSchema } from '/@/components/Form'; | |
| 5 | +import { DataTypeEnum, OriginalDataTypeEnum } from '/@/enums/objectModelEnum'; | |
| 6 | +import { TCPProtocolTypeEnum } from '/@/enums/deviceEnum'; | |
| 6 | 7 | |
| 7 | 8 | export interface BasicCreateFormParams { |
| 8 | 9 | identifier: string; |
| ... | ... | @@ -10,11 +11,9 @@ export interface BasicCreateFormParams { |
| 10 | 11 | dataType: DataType; |
| 11 | 12 | } |
| 12 | 13 | |
| 13 | -useComponentRegister('JSONEditor', JSONEditor); | |
| 14 | - | |
| 15 | 14 | const validateDouble = (value: number, min?: number | string, max?: number | string) => { |
| 16 | - min = Number(min) || Number.MIN_SAFE_INTEGER; | |
| 17 | - max = Number(max) || Number.MAX_SAFE_INTEGER; | |
| 15 | + min = Number(min) ?? Number.MIN_SAFE_INTEGER; | |
| 16 | + max = Number(max) ?? Number.MAX_SAFE_INTEGER; | |
| 18 | 17 | |
| 19 | 18 | return { |
| 20 | 19 | flag: value < min || value > max, |
| ... | ... | @@ -22,7 +21,7 @@ const validateDouble = (value: number, min?: number | string, max?: number | str |
| 22 | 21 | }; |
| 23 | 22 | }; |
| 24 | 23 | |
| 25 | -export const useGenDynamicForm = () => { | |
| 24 | +export const useGenerateFormSchemasByObjectModel = () => { | |
| 26 | 25 | const createInputNumber = ({ |
| 27 | 26 | identifier, |
| 28 | 27 | functionName, |
| ... | ... | @@ -128,24 +127,39 @@ export const useGenDynamicForm = () => { |
| 128 | 127 | }; |
| 129 | 128 | }; |
| 130 | 129 | |
| 131 | - const createInputJson = ({ identifier, functionName }: BasicCreateFormParams): FormSchema => { | |
| 130 | + const createModbusValueInput = (objectModel: DeviceModelOfMatterAttrs): FormSchema => { | |
| 131 | + const { identifier, name, detail, extensionDesc } = objectModel; | |
| 132 | + | |
| 133 | + const { dataType } = detail || {}; | |
| 134 | + const { specs } = dataType || {}; | |
| 135 | + const { valueRange } = specs as Specs; | |
| 136 | + const { max, min } = valueRange || {}; | |
| 137 | + | |
| 138 | + const isStringType = extensionDesc?.originalDataType === OriginalDataTypeEnum.STRING; | |
| 132 | 139 | return { |
| 133 | 140 | field: identifier, |
| 134 | - label: functionName, | |
| 135 | - component: 'JSONEditor', | |
| 136 | - valueField: 'value', | |
| 137 | - changeEvent: 'update:value', | |
| 138 | - rules: [ | |
| 139 | - { | |
| 140 | - validator: (_rule, value: any) => { | |
| 141 | - if (value) { | |
| 142 | - const { flag } = useJsonParse(value); | |
| 143 | - if (!flag) return Promise.reject(`${functionName} 不是一个有效的JSON对象`); | |
| 144 | - } | |
| 145 | - return Promise.resolve(); | |
| 146 | - }, | |
| 147 | - }, | |
| 148 | - ], | |
| 141 | + label: name, | |
| 142 | + component: isStringType ? 'Input' : 'InputNumber', | |
| 143 | + rules: isStringType | |
| 144 | + ? [] | |
| 145 | + : [ | |
| 146 | + { | |
| 147 | + type: 'number', | |
| 148 | + validator: (_rule, value) => { | |
| 149 | + const { flag, message } = validateDouble(value, min, max); | |
| 150 | + if (flag) { | |
| 151 | + return Promise.reject(`${name}${message}`); | |
| 152 | + } | |
| 153 | + return Promise.resolve(value); | |
| 154 | + }, | |
| 155 | + }, | |
| 156 | + ], | |
| 157 | + componentProps: { | |
| 158 | + max: max ?? Number.MAX_SAFE_INTEGER, | |
| 159 | + min: min ?? Number.MIN_SAFE_INTEGER, | |
| 160 | + placeholder: `请输入${name}`, | |
| 161 | + // precision: floatType.includes(extensionDesc!.originalDataType) ? 2 : 0, | |
| 162 | + }, | |
| 149 | 163 | }; |
| 150 | 164 | }; |
| 151 | 165 | |
| ... | ... | @@ -154,48 +168,38 @@ export const useGenDynamicForm = () => { |
| 154 | 168 | [DataTypeEnum.NUMBER_DOUBLE]: createInputNumber, |
| 155 | 169 | [DataTypeEnum.NUMBER_INT]: createInputNumber, |
| 156 | 170 | [DataTypeEnum.STRING]: createInput, |
| 157 | - [DataTypeEnum.STRUCT]: createInputJson, | |
| 158 | 171 | [DataTypeEnum.ENUM]: createEnumSelect, |
| 159 | 172 | }; |
| 160 | 173 | |
| 161 | - const fieldTypeMap = new Map<string, DataTypeEnum>(); | |
| 162 | - const genForm = (schemas: StructJSON[]) => { | |
| 163 | - fieldTypeMap.clear(); | |
| 164 | - const formSchema = schemas.map((item) => { | |
| 165 | - const { functionName, identifier, dataType } = item; | |
| 174 | + const getFormByObjectModel = ( | |
| 175 | + objectModel: DeviceModelOfMatterAttrs, | |
| 176 | + deviceDetail: DeviceRecord | |
| 177 | + ): FormSchema[] => { | |
| 178 | + const { name, identifier, detail } = objectModel; | |
| 166 | 179 | |
| 167 | - const { type } = dataType || {}; | |
| 180 | + const isTCPModbusProduct = | |
| 181 | + unref(deviceDetail).deviceProfile?.profileData?.transportConfiguration?.protocol === | |
| 182 | + TCPProtocolTypeEnum.MODBUS_RTU; | |
| 168 | 183 | |
| 169 | - fieldTypeMap.set(identifier!, dataType!.type); | |
| 170 | - const method = schemaMethod[type!]; | |
| 184 | + const { dataType } = detail; | |
| 185 | + const { type } = dataType || {}; | |
| 171 | 186 | |
| 172 | - const formSchema = method?.({ | |
| 173 | - identifier: identifier!, | |
| 174 | - functionName: functionName!, | |
| 175 | - dataType: dataType!, | |
| 176 | - }); | |
| 187 | + if (isTCPModbusProduct) { | |
| 188 | + return [createModbusValueInput(objectModel)]; | |
| 189 | + } | |
| 177 | 190 | |
| 178 | - return formSchema; | |
| 179 | - }); | |
| 191 | + if (type === DataTypeEnum.STRUCT) { | |
| 192 | + return (dataType?.specs as StructJSON[]).map((item) => { | |
| 193 | + const { functionName, identifier, dataType } = item; | |
| 194 | + const { type } = dataType || {}; | |
| 180 | 195 | |
| 181 | - return formSchema.filter(Boolean); | |
| 182 | - }; | |
| 196 | + return schemaMethod[type!]?.({ identifier, functionName, dataType }); | |
| 197 | + }); | |
| 198 | + } | |
| 183 | 199 | |
| 184 | - const transformValue = (value: Recordable) => { | |
| 185 | - return Object.keys(value || {}).reduce((prev, next) => { | |
| 186 | - const dataType = fieldTypeMap.get(next)!; | |
| 187 | - | |
| 188 | - let itemValue = value[next]; | |
| 189 | - if (dataType === DataTypeEnum.STRUCT) { | |
| 190 | - const { value } = useJsonParse(itemValue); | |
| 191 | - itemValue = value; | |
| 192 | - } | |
| 193 | - return { | |
| 194 | - ...prev, | |
| 195 | - [next]: itemValue, | |
| 196 | - }; | |
| 197 | - }, {} as Recordable); | |
| 200 | + const result = schemaMethod[type!]?.({ identifier, functionName: name, dataType: dataType! }); | |
| 201 | + return result ? [result] : []; | |
| 198 | 202 | }; |
| 199 | 203 | |
| 200 | - return { genForm, transformValue }; | |
| 204 | + return { getFormByObjectModel }; | |
| 201 | 205 | }; | ... | ... |
| ... | ... | @@ -39,45 +39,37 @@ export const validateValueRange: ValidatorRule['validator'] = ( |
| 39 | 39 | return Promise.resolve(); |
| 40 | 40 | }; |
| 41 | 41 | |
| 42 | -export const validateFunctionName: ValidatorRule['validator'] = (_rule, value: any) => { | |
| 43 | - if (/^[a-zA-Z0-9_\-\u4e00-\u9fa5]+$/.test(value)) return Promise.resolve(); | |
| 44 | - return Promise.reject('支持中文、大小写字母、数字、短划线、下划线.'); | |
| 45 | -}; | |
| 46 | - | |
| 47 | -export const validateIdentifier: ValidatorRule['validator'] = (_rule, value: any) => { | |
| 48 | - if (/^[a-zA-Z0-9_]+$/.test(value)) { | |
| 49 | - return Promise.resolve(); | |
| 50 | - } | |
| 51 | - return Promise.reject('支持大小写字母、数字和下划线.'); | |
| 52 | -}; | |
| 53 | - | |
| 54 | 42 | export const createFunctionNameFormItem = (params: Partial<FormSchema> = {}): FormSchema => { |
| 43 | + const helpMessage = '支持中文、大小写字母、数字、短划线、下划线。'; | |
| 55 | 44 | return { |
| 56 | 45 | field: FormFieldsEnum.FUNCTION_NAME, |
| 57 | 46 | label: FormFieldsNameEnum.FUNCTION_NAME, |
| 58 | 47 | component: 'Input', |
| 59 | 48 | required: true, |
| 60 | - helpMessage: '支持中文、大小写字母、数字、短划线、下划线。', | |
| 49 | + helpMessage, | |
| 61 | 50 | rules: [ |
| 62 | 51 | { |
| 63 | 52 | required: true, |
| 64 | - validator: validateFunctionName, | |
| 53 | + message: helpMessage, | |
| 54 | + pattern: /^[a-zA-Z0-9_\-\u4e00-\u9fa5\(\)]+$/, | |
| 65 | 55 | }, |
| 66 | 56 | ], |
| 67 | 57 | componentProps: { |
| 68 | 58 | placeholder: `请输入${FormFieldsNameEnum.FUNCTION_NAME}`, |
| 59 | + maxlength: 32, | |
| 69 | 60 | }, |
| 70 | 61 | ...params, |
| 71 | 62 | }; |
| 72 | 63 | }; |
| 73 | 64 | |
| 74 | 65 | export const createIdentifierFormItem = (params: Partial<FormSchema> = {}): FormSchema => { |
| 66 | + const helpMessage = '支持大小写字母、数字和下划线.'; | |
| 75 | 67 | return { |
| 76 | 68 | field: FormFieldsEnum.IDENTIFIER, |
| 77 | 69 | label: FormFieldsNameEnum.IDENTIFIER, |
| 78 | 70 | required: true, |
| 79 | 71 | component: 'Input', |
| 80 | - helpMessage: '支持大小写字母、数字和下划线.', | |
| 72 | + helpMessage, | |
| 81 | 73 | componentProps: { |
| 82 | 74 | maxLength: 128, |
| 83 | 75 | placeholder: '请输入标识符', |
| ... | ... | @@ -85,7 +77,8 @@ export const createIdentifierFormItem = (params: Partial<FormSchema> = {}): Form |
| 85 | 77 | rules: [ |
| 86 | 78 | { |
| 87 | 79 | required: true, |
| 88 | - validator: validateIdentifier, | |
| 80 | + message: helpMessage, | |
| 81 | + pattern: /^[a-zA-Z0-9_\-\u4e00-\u9fa5\(\)]+$/, | |
| 89 | 82 | }, |
| 90 | 83 | ], |
| 91 | 84 | ...params, |
| ... | ... | @@ -330,7 +323,6 @@ export const getFormSchemas = (dataType: DataTypeEnum[], showRemark: boolean): F |
| 330 | 323 | model[FormFieldsEnum.FUNCTION_TYPE] |
| 331 | 324 | ), |
| 332 | 325 | }, |
| 333 | - | |
| 334 | 326 | { |
| 335 | 327 | field: FormFieldsEnum.REMARK, |
| 336 | 328 | label: FormFieldsNameEnum.REMARK, | ... | ... |
| ... | ... | @@ -20,7 +20,7 @@ |
| 20 | 20 | } |
| 21 | 21 | ); |
| 22 | 22 | |
| 23 | - defineEmits<{ | |
| 23 | + const emits = defineEmits<{ | |
| 24 | 24 | (event: 'field-value-change', field: string, value: any): void; |
| 25 | 25 | }>(); |
| 26 | 26 | |
| ... | ... | @@ -37,6 +37,9 @@ |
| 37 | 37 | enumListRef, |
| 38 | 38 | }); |
| 39 | 39 | |
| 40 | + const handleFieldValueChang = (field: string, value: any) => | |
| 41 | + emits('field-value-change', field, value); | |
| 42 | + | |
| 40 | 43 | defineExpose({ getFieldsValue, setFieldsValue, validate, resetFieldsValue }); |
| 41 | 44 | </script> |
| 42 | 45 | |
| ... | ... | @@ -45,7 +48,7 @@ |
| 45 | 48 | @register="register" |
| 46 | 49 | class="data-type-form" |
| 47 | 50 | :disabled="mode === DataActionModeEnum.READ" |
| 48 | - @field-value-change="(field, value) => $emit('field-value-change', field, value)" | |
| 51 | + @field-value-change="handleFieldValueChang" | |
| 49 | 52 | > |
| 50 | 53 | <template #enumsData="{ model, field }"> |
| 51 | 54 | <EnumList |
| ... | ... | @@ -77,4 +80,3 @@ |
| 77 | 80 | } |
| 78 | 81 | } |
| 79 | 82 | </style> |
| 80 | -./config | ... | ... |
| 1 | 1 | import { Ref, unref } from 'vue'; |
| 2 | 2 | import { FormActionType } from '/@/components/Form'; |
| 3 | -import EnumList from './EnumList.vue'; | |
| 3 | +import { EnumList } from '../EnumList'; | |
| 4 | 4 | import { DefineComponentsBasicExpose } from '/#/utils'; |
| 5 | 5 | import { DataTypeFormGetFieldsValueType } from './config'; |
| 6 | 6 | import { DataType, Specs, StructJSON } from '/@/api/device/model/modelOfMatterModel'; | ... | ... |
| ... | ... | @@ -6,15 +6,21 @@ export enum FormFieldsEnum { |
| 6 | 6 | DATA_TYPE = 'dataType', |
| 7 | 7 | } |
| 8 | 8 | |
| 9 | -export const getFormSchemas = (): FormSchema[] => { | |
| 9 | +export const getFormSchemas = (required: boolean): FormSchema[] => { | |
| 10 | 10 | return [ |
| 11 | 11 | { |
| 12 | 12 | field: FormFieldsEnum.VALUE, |
| 13 | 13 | label: '', |
| 14 | 14 | component: 'InputNumber', |
| 15 | - rules: [ | |
| 16 | - { required: true, message: `支持整型,取值范围:-2147483648 ~ 2147483647`, type: 'number' }, | |
| 17 | - ], | |
| 15 | + dynamicRules: ({ model }) => { | |
| 16 | + return [ | |
| 17 | + { | |
| 18 | + required: required || model[FormFieldsEnum.NAME], | |
| 19 | + message: `支持整型,取值范围:-2147483648 ~ 2147483647`, | |
| 20 | + type: 'number', | |
| 21 | + }, | |
| 22 | + ]; | |
| 23 | + }, | |
| 18 | 24 | componentProps: () => { |
| 19 | 25 | return { |
| 20 | 26 | placeholder: '编号如"0"', |
| ... | ... | @@ -41,7 +47,15 @@ export const getFormSchemas = (): FormSchema[] => { |
| 41 | 47 | field: FormFieldsEnum.NAME, |
| 42 | 48 | label: '', |
| 43 | 49 | component: 'Input', |
| 44 | - rules: [{ required: true, message: `参数描述不能为空`, type: 'string' }], | |
| 50 | + dynamicRules: ({ model }) => { | |
| 51 | + return [ | |
| 52 | + { | |
| 53 | + required: required || model[FormFieldsEnum.VALUE], | |
| 54 | + message: `参数描述不能为空`, | |
| 55 | + type: 'string', | |
| 56 | + }, | |
| 57 | + ]; | |
| 58 | + }, | |
| 45 | 59 | componentProps: () => { |
| 46 | 60 | return { |
| 47 | 61 | placeholder: '对该枚举项的描述', | ... | ... |
| ... | ... | @@ -9,7 +9,18 @@ |
| 9 | 9 | import { DataTypeEnum } from '/@/enums/objectModelEnum'; |
| 10 | 10 | import { isNullOrUnDef } from '/@/utils/is'; |
| 11 | 11 | |
| 12 | - const props = defineProps<{ disabled?: boolean; value?: Specs[] }>(); | |
| 12 | + const props = withDefaults( | |
| 13 | + defineProps<{ | |
| 14 | + disabled?: boolean; | |
| 15 | + value?: Specs[]; | |
| 16 | + addButtonName?: string; | |
| 17 | + required?: boolean; | |
| 18 | + }>(), | |
| 19 | + { | |
| 20 | + addButtonName: '+添加枚举项', | |
| 21 | + required: true, | |
| 22 | + } | |
| 23 | + ); | |
| 13 | 24 | |
| 14 | 25 | interface EnumElItemType { |
| 15 | 26 | uuid: string; |
| ... | ... | @@ -18,7 +29,7 @@ |
| 18 | 29 | } |
| 19 | 30 | |
| 20 | 31 | const [registerForm] = useForm({ |
| 21 | - schemas: getFormSchemas(), | |
| 32 | + schemas: getFormSchemas(props.required), | |
| 22 | 33 | showActionButtonGroup: false, |
| 23 | 34 | layout: 'inline', |
| 24 | 35 | }); |
| ... | ... | @@ -75,6 +86,8 @@ |
| 75 | 86 | const index = unref(enumsListElRef).findIndex((temp) => item.uuid === temp.uuid); |
| 76 | 87 | |
| 77 | 88 | ~index && enumsListElRef.value.splice(index, 1); |
| 89 | + | |
| 90 | + validateSameEnum(); | |
| 78 | 91 | }; |
| 79 | 92 | |
| 80 | 93 | const handleAddEnums = () => { |
| ... | ... | @@ -102,14 +115,14 @@ |
| 102 | 115 | <section class="w-full"> |
| 103 | 116 | <header class="flex h-8 items-center"> |
| 104 | 117 | <div class="w-1/2"> |
| 105 | - <span class="mr-1 text-red-400">*</span> | |
| 118 | + <span v-if="required" class="mr-1 text-red-400">*</span> | |
| 106 | 119 | <span> 参考值 </span> |
| 107 | 120 | <Tooltip title="支持整型,取值范围:-2147483648 ~ 2147483647"> |
| 108 | 121 | <Icon icon="ant-design:question-circle-outlined" class="cursor-pointer ml-1" /> |
| 109 | 122 | </Tooltip> |
| 110 | 123 | </div> |
| 111 | 124 | <div class="w-1/2"> |
| 112 | - <span class="mr-1 text-red-400">*</span> | |
| 125 | + <span v-if="required" class="mr-1 text-red-400">*</span> | |
| 113 | 126 | <span> 参考描述 </span> |
| 114 | 127 | <Tooltip |
| 115 | 128 | title="支持中文、英文大小写、日文、数字、下划线和短划线,必须以中文、英文或数字开头,不超过20个字符" |
| ... | ... | @@ -144,7 +157,7 @@ |
| 144 | 157 | <div v-if="hasSameEnum" class="text-red-400">枚举项中存在相同的参数值或参数描述</div> |
| 145 | 158 | <Tooltip title="枚举项最多创建 100 个"> |
| 146 | 159 | <Button type="link" @click="handleAddEnums" :disabled="disabled || getEnumsLimit"> |
| 147 | - +添加枚举项 | |
| 160 | + {{ addButtonName }} | |
| 148 | 161 | </Button> |
| 149 | 162 | </Tooltip> |
| 150 | 163 | </section> | ... | ... |
| 1 | -import { unref } from 'vue'; | |
| 2 | -import { useObjectModelFormContext } from '../useObjectModelFormContext'; | |
| 3 | -import { findDictItemByCode } from '/@/api/system/dict'; | |
| 4 | 1 | import { FormSchema } from '/@/components/Table'; |
| 5 | -import { DictEnum } from '/@/enums/dictEnum'; | |
| 6 | 2 | import { |
| 7 | - DataTypeEnum, | |
| 8 | - RegisterActionTypeEnum, | |
| 9 | - RegisterActionTypeNameEnum, | |
| 10 | - RegisterDataTypeEnum, | |
| 3 | + ExtendDescOperationTypeEnum, | |
| 4 | + ExtendDescOperationTypeNameEnum, | |
| 5 | + OriginalDataTypeEnum, | |
| 6 | + OriginalDataTypeNameEnum, | |
| 11 | 7 | } from '/@/enums/objectModelEnum'; |
| 8 | +import { validateValueRange } from '../DataTypeForm/config'; | |
| 9 | +import { useComponentRegister } from '/@/components/Form'; | |
| 10 | +import { HexInput, InputTypeEnum } from '../HexInput'; | |
| 11 | +import { isFloatType } from './useParseOperationType'; | |
| 12 | +import { useObjectModelFormContext } from '../useObjectModelFormContext'; | |
| 13 | +import { unref } from 'vue'; | |
| 14 | +import { DataActionModeEnum } from '/@/enums/toolEnum'; | |
| 15 | + | |
| 16 | +useComponentRegister('HexInput', HexInput); | |
| 12 | 17 | |
| 13 | 18 | export enum FormFieldsEnum { |
| 14 | - REGISTER_ADDRESS = 'registerAddress', | |
| 15 | - DATA_TYPE = 'dataType', | |
| 16 | 19 | ACTION_TYPE = 'actionType', |
| 17 | 20 | ZOOM_FACTOR = 'zoomFactor', |
| 18 | 21 | |
| 19 | 22 | OBJECT_MODEL_TYPE = 'objectModelType', |
| 23 | + | |
| 24 | + ORIGINAL_DATA_TYPE = 'originalDataType', | |
| 25 | + REGISTER_ADDRESS = 'registerAddress', | |
| 26 | + OPERATION_TYPE = 'operationType', | |
| 27 | + VALUE_RANGE = 'valueRange', | |
| 28 | + SCALING = 'scaling', | |
| 29 | + BIT_MASK = 'bitMask', | |
| 30 | + | |
| 31 | + REGISTER_COUNT = 'registerCount', | |
| 32 | + | |
| 33 | + VALUE_MAPPING = 'valueMapping', | |
| 20 | 34 | } |
| 21 | 35 | |
| 22 | -function getActionTypeByObjectModelType(dataType: DataTypeEnum) { | |
| 23 | - const list: Record<'label' | 'value', string>[] = []; | |
| 24 | - if (dataType === DataTypeEnum.BOOL) { | |
| 25 | - list.push({ label: RegisterActionTypeNameEnum.BOOL, value: RegisterActionTypeEnum.BOOL }); | |
| 26 | - } else if (dataType === DataTypeEnum.NUMBER_INT) { | |
| 27 | - list.push({ label: RegisterActionTypeNameEnum.INT, value: RegisterActionTypeEnum.INT }); | |
| 28 | - } else if (dataType === DataTypeEnum.NUMBER_DOUBLE) { | |
| 29 | - list.push({ label: RegisterActionTypeNameEnum.DOUBLE, value: RegisterActionTypeEnum.DOUBLE }); | |
| 30 | - } else { | |
| 31 | - list.push( | |
| 32 | - ...Object.keys(RegisterActionTypeEnum).map((key) => ({ | |
| 33 | - label: RegisterActionTypeNameEnum[key], | |
| 34 | - value: RegisterActionTypeEnum[key], | |
| 35 | - })) | |
| 36 | - ); | |
| 36 | +export enum FormFieldsNameEnum { | |
| 37 | + ORIGINAL_DATA_TYPE = '数据类型', | |
| 38 | + REGISTER_ADDRESS = '寄存器地址', | |
| 39 | + OPERATION_TYPE = '操作类型', | |
| 40 | + VALUE_RANGE = '取值范围', | |
| 41 | + SCALING = '缩放因子', | |
| 42 | + BIT_MASK = '比特位置', | |
| 43 | + | |
| 44 | + REGISTER_COUNT = '寄存器个数', | |
| 45 | + VALUE_MAPPING = '值映射', | |
| 46 | +} | |
| 47 | + | |
| 48 | +export const BOOL_DEFAULT_VALUE_RANGE = { min: 0, max: 1 }; | |
| 49 | + | |
| 50 | +export const INT16_VALUE_RANGE = { | |
| 51 | + min: -(2 ** 15), | |
| 52 | + max: 2 ** 15 - 1, | |
| 53 | +}; | |
| 54 | + | |
| 55 | +export const UINT16_VALUE_RANGE = { | |
| 56 | + min: 0, | |
| 57 | + max: 2 ** 16 - 1, | |
| 58 | +}; | |
| 59 | + | |
| 60 | +export const INT32_VALUE_RANGE = { | |
| 61 | + min: -(2 ** 31), | |
| 62 | + max: 2 ** 31 - 1, | |
| 63 | +}; | |
| 64 | + | |
| 65 | +export const UINT32_VALUE_RANGE = { | |
| 66 | + min: 0, | |
| 67 | + max: 2 ** 32 - 1, | |
| 68 | +}; | |
| 69 | + | |
| 70 | +function getValueRangeFromOriginDataType( | |
| 71 | + originalDataType?: OriginalDataTypeEnum | |
| 72 | +): Record<'min' | 'max', number> { | |
| 73 | + switch (originalDataType) { | |
| 74 | + case OriginalDataTypeEnum.BOOLEAN: | |
| 75 | + case OriginalDataTypeEnum.BITS: | |
| 76 | + return BOOL_DEFAULT_VALUE_RANGE; | |
| 77 | + | |
| 78 | + case OriginalDataTypeEnum.INT16_AB: | |
| 79 | + case OriginalDataTypeEnum.INT16_BA: | |
| 80 | + return INT16_VALUE_RANGE; | |
| 81 | + | |
| 82 | + case OriginalDataTypeEnum.UINT16_AB: | |
| 83 | + case OriginalDataTypeEnum.UINT16_BA: | |
| 84 | + return UINT16_VALUE_RANGE; | |
| 85 | + | |
| 86 | + case OriginalDataTypeEnum.INT32_AB_CD: | |
| 87 | + case OriginalDataTypeEnum.INT32_BA_DC: | |
| 88 | + case OriginalDataTypeEnum.INT32_CD_AB: | |
| 89 | + case OriginalDataTypeEnum.INT32_DC_BA: | |
| 90 | + return INT32_VALUE_RANGE; | |
| 91 | + | |
| 92 | + case OriginalDataTypeEnum.UINT32_AB_CD: | |
| 93 | + case OriginalDataTypeEnum.UINT32_BA_DC: | |
| 94 | + case OriginalDataTypeEnum.UINT32_CD_AB: | |
| 95 | + case OriginalDataTypeEnum.UINT32_DC_BA: | |
| 96 | + return UINT32_VALUE_RANGE; | |
| 97 | + | |
| 98 | + default: | |
| 99 | + return INT32_VALUE_RANGE; | |
| 37 | 100 | } |
| 101 | +} | |
| 38 | 102 | |
| 39 | - return list; | |
| 103 | +const BOOL_OPERATION_TYPE = [ | |
| 104 | + ExtendDescOperationTypeEnum.INPUT_STATUS_R_02, | |
| 105 | + ExtendDescOperationTypeEnum.COIL_STATUS_R_01, | |
| 106 | + ExtendDescOperationTypeEnum.COIL_STATUS_RW_01_05, | |
| 107 | + ExtendDescOperationTypeEnum.COIL_STATUS_RW_01_0F, | |
| 108 | + ExtendDescOperationTypeEnum.COIL_STATUS_W_05, | |
| 109 | + ExtendDescOperationTypeEnum.COIL_STATUS_W_0F, | |
| 110 | +]; | |
| 111 | + | |
| 112 | +function getOriginalDataTypeByOperationType( | |
| 113 | + operationType: ExtendDescOperationTypeEnum | |
| 114 | +): Record<'label' | 'value', string>[] { | |
| 115 | + switch (operationType) { | |
| 116 | + case ExtendDescOperationTypeEnum.INPUT_STATUS_R_02: | |
| 117 | + case ExtendDescOperationTypeEnum.COIL_STATUS_R_01: | |
| 118 | + case ExtendDescOperationTypeEnum.COIL_STATUS_RW_01_05: | |
| 119 | + case ExtendDescOperationTypeEnum.COIL_STATUS_RW_01_0F: | |
| 120 | + case ExtendDescOperationTypeEnum.COIL_STATUS_W_05: | |
| 121 | + case ExtendDescOperationTypeEnum.COIL_STATUS_W_0F: | |
| 122 | + return [{ label: OriginalDataTypeNameEnum.BOOLEAN, value: OriginalDataTypeEnum.BOOLEAN }]; | |
| 123 | + | |
| 124 | + case ExtendDescOperationTypeEnum.HOLDING_REGISTER_RW_03_06: | |
| 125 | + case ExtendDescOperationTypeEnum.HOLDING_REGISTER_W_06: | |
| 126 | + return [ | |
| 127 | + { label: OriginalDataTypeNameEnum.INT16_AB, value: OriginalDataTypeEnum.INT16_AB }, | |
| 128 | + { label: OriginalDataTypeNameEnum.INT16_BA, value: OriginalDataTypeEnum.INT16_BA }, | |
| 129 | + { label: OriginalDataTypeNameEnum.UINT16_AB, value: OriginalDataTypeEnum.UINT16_AB }, | |
| 130 | + { label: OriginalDataTypeNameEnum.UINT16_BA, value: OriginalDataTypeEnum.UINT16_BA }, | |
| 131 | + { label: OriginalDataTypeNameEnum.BITS, value: OriginalDataTypeEnum.BITS }, | |
| 132 | + { label: OriginalDataTypeNameEnum.BOOLEAN, value: OriginalDataTypeEnum.BOOLEAN }, | |
| 133 | + ]; | |
| 134 | + | |
| 135 | + case ExtendDescOperationTypeEnum.HOLDING_REGISTER_RW_03_10: | |
| 136 | + case ExtendDescOperationTypeEnum.HOLDING_REGISTER_W_10: | |
| 137 | + case ExtendDescOperationTypeEnum.HOLDING_REGISTER_R_03: | |
| 138 | + case ExtendDescOperationTypeEnum.INPUT_REGISTER_R_04: | |
| 139 | + return Object.keys(OriginalDataTypeEnum).map((key) => ({ | |
| 140 | + label: OriginalDataTypeNameEnum[key], | |
| 141 | + value: OriginalDataTypeEnum[key], | |
| 142 | + })); | |
| 143 | + } | |
| 40 | 144 | } |
| 41 | 145 | |
| 42 | -export const getFormSchemas = (): FormSchema[] => { | |
| 43 | - const { getDataType } = useObjectModelFormContext(); | |
| 146 | +export const getExtendDescFormSchemas = (): FormSchema[] => { | |
| 147 | + const DEFAULT_OPERATION_TYPE = ExtendDescOperationTypeEnum.INPUT_STATUS_R_02; | |
| 148 | + const DEFAULT_ORIGINAL_TYPE = OriginalDataTypeEnum.BOOLEAN; | |
| 44 | 149 | return [ |
| 45 | 150 | { |
| 151 | + field: FormFieldsEnum.OPERATION_TYPE, | |
| 152 | + label: FormFieldsNameEnum.OPERATION_TYPE, | |
| 153 | + component: 'Select', | |
| 154 | + required: true, | |
| 155 | + defaultValue: DEFAULT_OPERATION_TYPE, | |
| 156 | + componentProps: ({ formActionType }) => { | |
| 157 | + return { | |
| 158 | + options: Object.keys(ExtendDescOperationTypeEnum).map((key) => ({ | |
| 159 | + label: ExtendDescOperationTypeNameEnum[key], | |
| 160 | + value: ExtendDescOperationTypeEnum[key], | |
| 161 | + })), | |
| 162 | + labelField: 'itemText', | |
| 163 | + valueField: 'itemValue', | |
| 164 | + getPopupContainer: () => document.body, | |
| 165 | + placeholder: '请选择操作类型', | |
| 166 | + allowClear: false, | |
| 167 | + onChange(value: ExtendDescOperationTypeEnum) { | |
| 168 | + const isBoolTypeOperationType = BOOL_OPERATION_TYPE.includes(value); | |
| 169 | + const originalDataType = isBoolTypeOperationType | |
| 170 | + ? OriginalDataTypeEnum.BOOLEAN | |
| 171 | + : OriginalDataTypeEnum.INT16_AB; | |
| 172 | + | |
| 173 | + formActionType.setFieldsValue({ | |
| 174 | + [FormFieldsEnum.ORIGINAL_DATA_TYPE]: originalDataType, | |
| 175 | + [FormFieldsEnum.VALUE_RANGE]: isBoolTypeOperationType | |
| 176 | + ? BOOL_DEFAULT_VALUE_RANGE | |
| 177 | + : INT16_VALUE_RANGE, | |
| 178 | + }); | |
| 179 | + }, | |
| 180 | + }; | |
| 181 | + }, | |
| 182 | + }, | |
| 183 | + { | |
| 46 | 184 | field: FormFieldsEnum.REGISTER_ADDRESS, |
| 47 | - component: 'RegisterAddressInput', | |
| 48 | - label: '寄存器地址', | |
| 185 | + label: FormFieldsNameEnum.REGISTER_ADDRESS, | |
| 186 | + component: 'HexInput', | |
| 49 | 187 | changeEvent: 'update:value', |
| 50 | 188 | valueField: 'value', |
| 51 | - rules: [{ message: '请输入寄存器地址', required: true, type: 'number' }], | |
| 189 | + helpMessage: ['范围在 0x0-0xFFFF'], | |
| 190 | + rules: [{ message: '请输入寄存器地址', required: true }], | |
| 52 | 191 | componentProps: { |
| 53 | 192 | placeholder: '请输入寄存器地址', |
| 193 | + type: InputTypeEnum.HEX, | |
| 194 | + maxValue: parseInt('0xFFFF', 16), | |
| 54 | 195 | }, |
| 55 | 196 | }, |
| 56 | 197 | { |
| 57 | - field: FormFieldsEnum.DATA_TYPE, | |
| 58 | - component: 'ApiSelect', | |
| 59 | - label: '数据格式', | |
| 60 | - rules: [{ message: '请选择数据格式', required: true }], | |
| 61 | - defaultValue: RegisterDataTypeEnum.UN_SHORT, | |
| 62 | - componentProps: { | |
| 63 | - api: findDictItemByCode, | |
| 64 | - params: { | |
| 65 | - dictCode: DictEnum.REGISTER_DATA_FORMAT, | |
| 66 | - }, | |
| 67 | - labelField: 'itemText', | |
| 68 | - valueField: 'itemValue', | |
| 69 | - placeholder: '请选择数据格式', | |
| 70 | - getPopupContainer: () => document.body, | |
| 198 | + field: FormFieldsEnum.ORIGINAL_DATA_TYPE, | |
| 199 | + label: FormFieldsNameEnum.ORIGINAL_DATA_TYPE, | |
| 200 | + component: 'Select', | |
| 201 | + required: true, | |
| 202 | + defaultValue: DEFAULT_ORIGINAL_TYPE, | |
| 203 | + dynamicDisabled: ({ model }) => | |
| 204 | + BOOL_OPERATION_TYPE.includes(model[FormFieldsEnum.OPERATION_TYPE]) || | |
| 205 | + unref(useObjectModelFormContext().getModalMode) === DataActionModeEnum.READ, | |
| 206 | + componentProps: ({ formActionType, formModel }) => { | |
| 207 | + const operationType = formModel[FormFieldsEnum.OPERATION_TYPE]; | |
| 208 | + | |
| 209 | + return { | |
| 210 | + options: getOriginalDataTypeByOperationType(operationType), | |
| 211 | + labelField: 'itemText', | |
| 212 | + valueField: 'itemValue', | |
| 213 | + placeholder: '请选择数据类型', | |
| 214 | + getPopupContainer: () => document.body, | |
| 215 | + onChange(value: OriginalDataTypeEnum) { | |
| 216 | + const { setFieldsValue } = formActionType; | |
| 217 | + setFieldsValue({ | |
| 218 | + [FormFieldsEnum.VALUE_RANGE]: getValueRangeFromOriginDataType( | |
| 219 | + value as OriginalDataTypeEnum | |
| 220 | + ), | |
| 221 | + }); | |
| 222 | + }, | |
| 223 | + }; | |
| 71 | 224 | }, |
| 72 | 225 | }, |
| 73 | 226 | { |
| 74 | - field: FormFieldsEnum.ACTION_TYPE, | |
| 227 | + field: FormFieldsEnum.BIT_MASK, | |
| 228 | + label: FormFieldsNameEnum.BIT_MASK, | |
| 75 | 229 | component: 'Select', |
| 76 | - label: '操作类型', | |
| 77 | - rules: [{ message: '请选择操作类型', required: true }], | |
| 230 | + ifShow: ({ model }) => model[FormFieldsEnum.ORIGINAL_DATA_TYPE] === OriginalDataTypeEnum.BITS, | |
| 231 | + required: true, | |
| 232 | + defaultValue: 7, | |
| 78 | 233 | componentProps: () => { |
| 79 | 234 | return { |
| 80 | - options: getActionTypeByObjectModelType(unref(getDataType)), | |
| 81 | - placeholder: '请选择操作类型', | |
| 235 | + options: Array.from({ length: 16 }, (_, index) => ({ | |
| 236 | + label: `${index}`, | |
| 237 | + value: index, | |
| 238 | + })), | |
| 239 | + allowClear: false, | |
| 82 | 240 | getPopupContainer: () => document.body, |
| 83 | 241 | }; |
| 84 | 242 | }, |
| 85 | 243 | }, |
| 86 | 244 | { |
| 87 | - field: FormFieldsEnum.ZOOM_FACTOR, | |
| 245 | + field: FormFieldsEnum.VALUE_RANGE, | |
| 246 | + label: FormFieldsNameEnum.VALUE_RANGE, | |
| 247 | + component: 'CustomMinMaxInput', | |
| 248 | + rules: [{ required: true, validator: validateValueRange }], | |
| 249 | + changeEvent: 'update:value', | |
| 250 | + valueField: 'value', | |
| 251 | + defaultValue: BOOL_DEFAULT_VALUE_RANGE, | |
| 252 | + helpMessage: [ | |
| 253 | + '取值范围指的是原始数据经过缩放因子处理之后的取值范围,超出取值范围的数据会被丢弃。', | |
| 254 | + ], | |
| 255 | + ifShow: ({ model }) => | |
| 256 | + model[FormFieldsEnum.ORIGINAL_DATA_TYPE] !== OriginalDataTypeEnum.STRING, | |
| 257 | + componentProps: ({ formModel }) => { | |
| 258 | + const originalDataType = formModel[FormFieldsEnum.ORIGINAL_DATA_TYPE]; | |
| 259 | + | |
| 260 | + const { min, max } = getValueRangeFromOriginDataType( | |
| 261 | + originalDataType as OriginalDataTypeEnum | |
| 262 | + ); | |
| 263 | + | |
| 264 | + const flag = isFloatType(originalDataType); | |
| 265 | + | |
| 266 | + return { | |
| 267 | + minInputProps: { | |
| 268 | + precision: 0, | |
| 269 | + min: flag ? undefined : min, | |
| 270 | + max: flag ? undefined : max, | |
| 271 | + }, | |
| 272 | + maxInputProps: { | |
| 273 | + precision: 0, | |
| 274 | + min: flag ? undefined : min, | |
| 275 | + max: flag ? undefined : max, | |
| 276 | + }, | |
| 277 | + }; | |
| 278 | + }, | |
| 279 | + }, | |
| 280 | + | |
| 281 | + { | |
| 282 | + field: FormFieldsEnum.SCALING, | |
| 283 | + label: FormFieldsNameEnum.SCALING, | |
| 88 | 284 | component: 'InputNumber', |
| 89 | - label: '缩放因子', | |
| 90 | - helpMessage: ['缩放因子不能为0,默认为1'], | |
| 285 | + required: true, | |
| 91 | 286 | defaultValue: 1, |
| 287 | + helpMessage: ['缩放因子,不能为 0,默认为 1'], | |
| 92 | 288 | ifShow: ({ model }) => |
| 93 | - ![DataTypeEnum.BOOL, DataTypeEnum.STRING].includes(model[FormFieldsEnum.OBJECT_MODEL_TYPE]), | |
| 94 | - componentProps: { | |
| 95 | - min: 1, | |
| 96 | - placeholder: '请输入缩放因子', | |
| 289 | + ![OriginalDataTypeEnum.STRING, OriginalDataTypeEnum.BOOLEAN].includes( | |
| 290 | + model[FormFieldsEnum.ORIGINAL_DATA_TYPE] | |
| 291 | + ), | |
| 292 | + rules: [ | |
| 293 | + { | |
| 294 | + validator: (_rule, value: any) => { | |
| 295 | + if (Number(value) > 0) return Promise.resolve(); | |
| 296 | + return Promise.reject(Error('缩放因子不能为0')); | |
| 297 | + }, | |
| 298 | + }, | |
| 299 | + ], | |
| 300 | + componentProps: () => {}, | |
| 301 | + }, | |
| 302 | + { | |
| 303 | + field: FormFieldsEnum.REGISTER_COUNT, | |
| 304 | + label: FormFieldsNameEnum.REGISTER_COUNT, | |
| 305 | + component: 'InputNumber', | |
| 306 | + helpMessage: ['操作类型为保持寄存器/输入寄存器时限制为1~125'], | |
| 307 | + ifShow: ({ model }) => | |
| 308 | + model[FormFieldsEnum.ORIGINAL_DATA_TYPE] === OriginalDataTypeEnum.STRING, | |
| 309 | + required: true, | |
| 310 | + componentProps: () => { | |
| 311 | + return { | |
| 312 | + placeholder: '请输入寄存器个数', | |
| 313 | + min: 1, | |
| 314 | + max: 125, | |
| 315 | + precision: 0, | |
| 316 | + }; | |
| 97 | 317 | }, |
| 98 | 318 | }, |
| 319 | + { | |
| 320 | + field: FormFieldsEnum.VALUE_MAPPING, | |
| 321 | + component: 'Input', | |
| 322 | + label: FormFieldsNameEnum.VALUE_MAPPING, | |
| 323 | + slot: FormFieldsEnum.VALUE_MAPPING, | |
| 324 | + defaultValue: [ | |
| 325 | + { value: 0, name: '关' }, | |
| 326 | + { value: 1, name: '开' }, | |
| 327 | + ], | |
| 328 | + ifShow: ({ model }) => | |
| 329 | + [OriginalDataTypeEnum.BOOLEAN, OriginalDataTypeEnum.BITS].includes( | |
| 330 | + model[FormFieldsEnum.ORIGINAL_DATA_TYPE] | |
| 331 | + ), | |
| 332 | + }, | |
| 99 | 333 | ]; |
| 100 | 334 | }; | ... | ... |
| 1 | 1 | export { default as ExtendDesc } from './index.vue'; |
| 2 | + | |
| 3 | +import { FormFieldsEnum } from './config'; | |
| 4 | +import { Specs } from '/@/api/device/model/modelOfMatterModel'; | |
| 5 | +import { ExtendDescOperationTypeEnum, OriginalDataTypeEnum } from '/@/enums/objectModelEnum'; | |
| 6 | + | |
| 7 | +export interface ExtendDescFormFieldsValueType { | |
| 8 | + [FormFieldsEnum.BIT_MASK]?: number; | |
| 9 | + [FormFieldsEnum.OPERATION_TYPE]: ExtendDescOperationTypeEnum; | |
| 10 | + [FormFieldsEnum.ORIGINAL_DATA_TYPE]: OriginalDataTypeEnum; | |
| 11 | + [FormFieldsEnum.REGISTER_ADDRESS]: string; | |
| 12 | + [FormFieldsEnum.SCALING]?: number; | |
| 13 | + [FormFieldsEnum.VALUE_RANGE]?: Record<'min' | 'max', number>; | |
| 14 | + [FormFieldsEnum.REGISTER_COUNT]?: number; | |
| 15 | + [FormFieldsEnum.VALUE_MAPPING]?: Specs[]; | |
| 16 | +} | ... | ... |
| 1 | 1 | <script lang="ts" setup> |
| 2 | - import { Button } from 'ant-design-vue'; | |
| 3 | - import { nextTick, ref, watch } from 'vue'; | |
| 2 | + import { Button, Divider } from 'ant-design-vue'; | |
| 3 | + import { nextTick, ref, unref, watch } from 'vue'; | |
| 4 | 4 | import { BasicForm, useForm } from '/@/components/Form'; |
| 5 | - import { BasicModal } from '/@/components/Modal'; | |
| 6 | 5 | import { PlusCircleOutlined } from '@ant-design/icons-vue'; |
| 7 | - import { getFormSchemas } from './config'; | |
| 8 | - import { ExtensionDesc } from '/@/api/device/model/modelOfMatterModel'; | |
| 6 | + import { getExtendDescFormSchemas } from './config'; | |
| 7 | + import { ExtendDescFormFieldsValueType } from '.'; | |
| 8 | + import { BasicModal } from '/@/components/Modal'; | |
| 9 | + import { PlusSquareOutlined, MinusSquareOutlined } from '@ant-design/icons-vue'; | |
| 10 | + import { | |
| 11 | + ExtendDescOperationTypeEnum, | |
| 12 | + ExtendDescOperationTypeNameEnum, | |
| 13 | + OriginalDataTypeNameEnum, | |
| 14 | + } from '/@/enums/objectModelEnum'; | |
| 15 | + import { EnumList } from '../EnumList'; | |
| 9 | 16 | |
| 10 | 17 | const show = ref(false); |
| 11 | 18 | |
| 12 | 19 | const props = withDefaults( |
| 13 | 20 | defineProps<{ |
| 14 | - value?: ExtensionDesc; | |
| 21 | + value?: ExtendDescFormFieldsValueType; | |
| 15 | 22 | disabled?: boolean; |
| 16 | 23 | }>(), |
| 17 | 24 | { |
| 18 | - value: () => ({} as ExtensionDesc), | |
| 25 | + value: () => ({} as ExtendDescFormFieldsValueType), | |
| 19 | 26 | } |
| 20 | 27 | ); |
| 21 | 28 | |
| 22 | - const emit = defineEmits(['update:value']); | |
| 29 | + const emit = defineEmits(['update:value', 'change']); | |
| 23 | 30 | |
| 24 | 31 | const [registerForm, { setFieldsValue, getFieldsValue, setProps, validate, resetFields }] = |
| 25 | 32 | useForm({ |
| 26 | - schemas: getFormSchemas(), | |
| 33 | + schemas: getExtendDescFormSchemas(), | |
| 27 | 34 | showActionButtonGroup: false, |
| 28 | 35 | }); |
| 29 | 36 | |
| 37 | + const infoExpandFlag = ref(false); | |
| 38 | + | |
| 39 | + const enumListRef = ref<InstanceType<typeof EnumList>>(); | |
| 40 | + | |
| 30 | 41 | const handleClick = async () => { |
| 31 | 42 | show.value = true; |
| 32 | 43 | await nextTick(); |
| ... | ... | @@ -37,24 +48,111 @@ |
| 37 | 48 | |
| 38 | 49 | const handleSubmit = async () => { |
| 39 | 50 | await validate(); |
| 40 | - const value = getFieldsValue(); | |
| 51 | + await unref(enumListRef)?.validate(); | |
| 52 | + const value = getFieldsValue() as ExtendDescFormFieldsValueType; | |
| 53 | + const valueMapping = unref(enumListRef)?.getFieldsValue(); | |
| 54 | + value.valueMapping = valueMapping; | |
| 41 | 55 | emit('update:value', value); |
| 56 | + emit('change', value); | |
| 42 | 57 | show.value = false; |
| 43 | 58 | }; |
| 44 | 59 | |
| 60 | + const handleDelete = () => { | |
| 61 | + emit('update:value', {}); | |
| 62 | + emit('change', {}); | |
| 63 | + }; | |
| 64 | + | |
| 45 | 65 | watch(show, async (value) => { |
| 46 | 66 | if (value) { |
| 47 | 67 | await nextTick(); |
| 48 | 68 | setFieldsValue({ ...props.value }); |
| 49 | 69 | } |
| 50 | 70 | }); |
| 71 | + | |
| 72 | + const getOperationTypeName = (operationType: ExtendDescOperationTypeEnum) => { | |
| 73 | + const key = Object.keys(ExtendDescOperationTypeEnum).find( | |
| 74 | + (key) => ExtendDescOperationTypeEnum[key] === operationType | |
| 75 | + ); | |
| 76 | + | |
| 77 | + return key ? ExtendDescOperationTypeNameEnum[key] : key; | |
| 78 | + }; | |
| 51 | 79 | </script> |
| 52 | 80 | |
| 53 | 81 | <template> |
| 54 | 82 | <section> |
| 55 | - <Button type="link" @click="handleClick"><PlusCircleOutlined />新增扩展描述</Button> | |
| 56 | - <BasicModal title="扩展描述" v-model:visible="show" @ok="handleSubmit" :show-ok-btn="!disabled"> | |
| 57 | - <BasicForm class="extension-form" @register="registerForm" /> | |
| 83 | + <Button v-if="!value.operationType" type="link" @click="handleClick"> | |
| 84 | + <PlusCircleOutlined /> | |
| 85 | + 新增扩展描述 | |
| 86 | + </Button> | |
| 87 | + <main v-if="value.operationType" class="flex"> | |
| 88 | + <div class="flex-auto flex text-gray-400 bg-blue-50 text-xs p-2 border gap-2 border-gray-100"> | |
| 89 | + <div text-base> | |
| 90 | + <PlusSquareOutlined | |
| 91 | + v-if="!infoExpandFlag" | |
| 92 | + class="cursor-pointer" | |
| 93 | + @click="infoExpandFlag = !infoExpandFlag" | |
| 94 | + /> | |
| 95 | + <MinusSquareOutlined | |
| 96 | + v-else | |
| 97 | + class="cursor-pointer" | |
| 98 | + @click="infoExpandFlag = !infoExpandFlag" | |
| 99 | + /> | |
| 100 | + </div> | |
| 101 | + <div> | |
| 102 | + <div class="mb-2"> | |
| 103 | + <span class="text-gray-400">操作类型</span> | |
| 104 | + <span class="ml-2">{{ getOperationTypeName(value.operationType) }}</span> | |
| 105 | + </div> | |
| 106 | + <section | |
| 107 | + class="flex flex-col gap-2 overflow-hidden transition" | |
| 108 | + :class="infoExpandFlag ? '' : 'h-0'" | |
| 109 | + > | |
| 110 | + <div> | |
| 111 | + <span>寄存器地址:</span> | |
| 112 | + <span class="ml-2">{{ value.registerAddress }}</span> | |
| 113 | + </div> | |
| 114 | + <div> | |
| 115 | + <span>原始数据类型:</span> | |
| 116 | + <span class="ml-2">{{ OriginalDataTypeNameEnum[value.originalDataType] }}</span> | |
| 117 | + </div> | |
| 118 | + <div v-if="value.scaling"> | |
| 119 | + <span>缩放因子:</span> | |
| 120 | + <span class="ml-2">{{ value.scaling }}</span> | |
| 121 | + </div> | |
| 122 | + <div v-if="value.valueRange"> | |
| 123 | + <span>取值范围:</span> | |
| 124 | + <span class="ml-2">{{ value.valueRange?.min }} ~ {{ value.valueRange?.max }}</span> | |
| 125 | + </div> | |
| 126 | + </section> | |
| 127 | + </div> | |
| 128 | + </div> | |
| 129 | + <div class="ml-2"> | |
| 130 | + <Button class="!px-0" @click="handleClick" type="link"> | |
| 131 | + {{ disabled ? '查看' : '编辑' }} | |
| 132 | + </Button> | |
| 133 | + <Divider v-if="!disabled" type="vertical" /> | |
| 134 | + <Button class="!px-0" @click="handleDelete" type="link" v-if="!disabled"> 删除 </Button> | |
| 135 | + </div> | |
| 136 | + </main> | |
| 137 | + | |
| 138 | + <BasicModal | |
| 139 | + title="扩展描述" | |
| 140 | + v-model:visible="show" | |
| 141 | + @ok="handleSubmit" | |
| 142 | + :show-ok-btn="!disabled" | |
| 143 | + :width="480" | |
| 144 | + > | |
| 145 | + <BasicForm class="extension-form" @register="registerForm"> | |
| 146 | + <template #valueMapping="{ model, field }"> | |
| 147 | + <EnumList | |
| 148 | + ref="enumListRef" | |
| 149 | + v-model:value="model[field]" | |
| 150 | + :disabled="disabled" | |
| 151 | + :required="false" | |
| 152 | + add-button-name="+添加值映射" | |
| 153 | + /> | |
| 154 | + </template> | |
| 155 | + </BasicForm> | |
| 58 | 156 | </BasicModal> |
| 59 | 157 | </section> |
| 60 | 158 | </template> | ... | ... |
| 1 | +import { | |
| 2 | + DataTypeEnum, | |
| 3 | + ExtendDescOperationTypeEnum, | |
| 4 | + ObjectModelAccessModeEnum, | |
| 5 | + OriginalDataTypeEnum, | |
| 6 | +} from '/@/enums/objectModelEnum'; | |
| 7 | + | |
| 8 | +export function isNumberType(originalDataType: OriginalDataTypeEnum) { | |
| 9 | + return ![ | |
| 10 | + OriginalDataTypeEnum.BITS, | |
| 11 | + OriginalDataTypeEnum.BOOLEAN, | |
| 12 | + OriginalDataTypeEnum.STRING, | |
| 13 | + ].includes(originalDataType); | |
| 14 | +} | |
| 15 | + | |
| 16 | +export function isFloatType(originalDataType: OriginalDataTypeEnum) { | |
| 17 | + return [ | |
| 18 | + OriginalDataTypeEnum.FLOAT_AB_CD, | |
| 19 | + OriginalDataTypeEnum.FLOAT_BA_DC, | |
| 20 | + OriginalDataTypeEnum.FLOAT_CD_AB, | |
| 21 | + OriginalDataTypeEnum.FLOAT_DC_BA, | |
| 22 | + OriginalDataTypeEnum.DOUBLE, | |
| 23 | + ].includes(originalDataType); | |
| 24 | +} | |
| 25 | + | |
| 26 | +export function getDataTypeByOriginalDataType(originalDataType: OriginalDataTypeEnum) { | |
| 27 | + if (originalDataType === OriginalDataTypeEnum.STRING) return DataTypeEnum.STRING; | |
| 28 | + else if (originalDataType === OriginalDataTypeEnum.BOOLEAN) return DataTypeEnum.BOOL; | |
| 29 | + else if (isFloatType(originalDataType)) return DataTypeEnum.NUMBER_DOUBLE; | |
| 30 | + else return DataTypeEnum.NUMBER_INT; | |
| 31 | +} | |
| 32 | + | |
| 33 | +export function useParseOperationType(actionType: ExtendDescOperationTypeEnum) { | |
| 34 | + const [_, accessMode, inputOrOutput, output = undefined] = (actionType || '').split('_'); | |
| 35 | + | |
| 36 | + const _accessMode = | |
| 37 | + accessMode === ObjectModelAccessModeEnum.READ | |
| 38 | + ? ObjectModelAccessModeEnum.READ | |
| 39 | + : ObjectModelAccessModeEnum.READ_AND_WRITE; | |
| 40 | + | |
| 41 | + const writeOnly = | |
| 42 | + _accessMode === ObjectModelAccessModeEnum.READ_AND_WRITE && | |
| 43 | + accessMode !== ObjectModelAccessModeEnum.READ_AND_WRITE; | |
| 44 | + | |
| 45 | + return { | |
| 46 | + accessMode: _accessMode, | |
| 47 | + originAccessMode: accessMode, | |
| 48 | + writeOnly, | |
| 49 | + readRegisterAddress: writeOnly ? undefined : inputOrOutput, | |
| 50 | + writeRegisterAddress: writeOnly ? inputOrOutput : output, | |
| 51 | + }; | |
| 52 | +} | ... | ... |
| 1 | +<script setup lang="ts"> | |
| 2 | + import { InputGroup, Select, Input } from 'ant-design-vue'; | |
| 3 | + import { computed, ref, unref } from 'vue'; | |
| 4 | + import { InputTypeEnum } from './config'; | |
| 5 | + import { isNullOrUnDef } from '/@/utils/is'; | |
| 6 | + | |
| 7 | + const props = withDefaults( | |
| 8 | + defineProps<{ | |
| 9 | + type?: InputTypeEnum; | |
| 10 | + value?: number | string; | |
| 11 | + hexWithPrefix?: boolean; | |
| 12 | + hexUpperCase?: boolean; | |
| 13 | + disabled?: boolean; | |
| 14 | + maxValue?: number; | |
| 15 | + showScaleSelect?: boolean; | |
| 16 | + }>(), | |
| 17 | + { | |
| 18 | + type: InputTypeEnum.HEX, | |
| 19 | + hexWithPrefix: false, | |
| 20 | + hexUpperCase: true, | |
| 21 | + maxValue: Number.MAX_SAFE_INTEGER, | |
| 22 | + showScaleSelect: true, | |
| 23 | + } | |
| 24 | + ); | |
| 25 | + | |
| 26 | + const emits = defineEmits(['update:value', 'change']); | |
| 27 | + | |
| 28 | + const typOptions = Object.values(InputTypeEnum).map((value) => ({ label: value, value })); | |
| 29 | + | |
| 30 | + const inputType = ref(props.type); | |
| 31 | + | |
| 32 | + const validateDECReg = /^[\d]+$/; | |
| 33 | + | |
| 34 | + const validateHEXReg = /^(0x)?[\da-fA-F]+$/; | |
| 35 | + | |
| 36 | + const getMaxValue = computed(() => { | |
| 37 | + const { maxValue } = props; | |
| 38 | + return maxValue; | |
| 39 | + }); | |
| 40 | + | |
| 41 | + const getDecToHex = (value: number | string) => { | |
| 42 | + const { hexUpperCase, hexWithPrefix } = props; | |
| 43 | + let hex = Number(value).toString(16); | |
| 44 | + hex = hexUpperCase ? hex.toUpperCase() : hex; | |
| 45 | + return `${hexWithPrefix ? '0x' : ''}${hex}`; | |
| 46 | + }; | |
| 47 | + | |
| 48 | + const getHexToDec = (value: number | string) => { | |
| 49 | + return parseInt(value, 16); | |
| 50 | + }; | |
| 51 | + | |
| 52 | + const getDECValue = (value: number | string) => { | |
| 53 | + return parseInt(value, unref(inputType) === InputTypeEnum.HEX ? 16 : 10); | |
| 54 | + }; | |
| 55 | + | |
| 56 | + const getFormatValue = (value: number | string) => { | |
| 57 | + if (unref(inputType) === InputTypeEnum.HEX) { | |
| 58 | + const { hexUpperCase, hexWithPrefix } = props; | |
| 59 | + if (hexUpperCase) value = value.toString().toUpperCase(); | |
| 60 | + if (hexWithPrefix) value = `0x${value}`; | |
| 61 | + return value; | |
| 62 | + } | |
| 63 | + | |
| 64 | + return value; | |
| 65 | + }; | |
| 66 | + | |
| 67 | + const getInputValue = computed({ | |
| 68 | + get() { | |
| 69 | + const { type, value } = props; | |
| 70 | + | |
| 71 | + if (isNullOrUnDef(value)) return; | |
| 72 | + | |
| 73 | + if (type === unref(inputType)) return value; | |
| 74 | + | |
| 75 | + if (type === InputTypeEnum.HEX && unref(inputType) === InputTypeEnum.DEC) { | |
| 76 | + return getHexToDec(value); | |
| 77 | + } else { | |
| 78 | + return getDecToHex(value); | |
| 79 | + } | |
| 80 | + }, | |
| 81 | + set(value: string) { | |
| 82 | + const { type } = props; | |
| 83 | + | |
| 84 | + if (unref(inputType) === InputTypeEnum.HEX) value = value.replace('0x', ''); | |
| 85 | + | |
| 86 | + if (!value) { | |
| 87 | + emits('update:value', undefined); | |
| 88 | + return; | |
| 89 | + } | |
| 90 | + | |
| 91 | + if (unref(inputType) === InputTypeEnum.HEX && !validateHEXReg.test(value)) return; | |
| 92 | + | |
| 93 | + if (unref(inputType) === InputTypeEnum.DEC && !validateDECReg.test(value)) return; | |
| 94 | + | |
| 95 | + if (getDECValue(value) > unref(getMaxValue)) return; | |
| 96 | + | |
| 97 | + if (type === unref(inputType)) { | |
| 98 | + emits('update:value', getFormatValue(value)); | |
| 99 | + return; | |
| 100 | + } | |
| 101 | + | |
| 102 | + if (type === InputTypeEnum.HEX && unref(inputType) === InputTypeEnum.DEC) { | |
| 103 | + emits('update:value', getDecToHex(value)); | |
| 104 | + } else { | |
| 105 | + emits('update:value', getHexToDec(value)); | |
| 106 | + } | |
| 107 | + }, | |
| 108 | + }); | |
| 109 | + | |
| 110 | + const getNegationValue = computed(() => { | |
| 111 | + const { hexWithPrefix } = props; | |
| 112 | + if (isNullOrUnDef(unref(getInputValue))) return 0; | |
| 113 | + return unref(inputType) === InputTypeEnum.HEX | |
| 114 | + ? getHexToDec(unref(getInputValue)!) | |
| 115 | + : `${hexWithPrefix ? '0x' : ''}${Number(unref(getInputValue)).toString(16).toUpperCase()}`; | |
| 116 | + }); | |
| 117 | +</script> | |
| 118 | + | |
| 119 | +<template> | |
| 120 | + <InputGroup class="!flex w-full h-full justify-center items-center" compact> | |
| 121 | + <Select | |
| 122 | + v-if="showScaleSelect" | |
| 123 | + v-model:value="inputType" | |
| 124 | + class="min-w-20" | |
| 125 | + :options="typOptions" | |
| 126 | + :disabled="disabled" | |
| 127 | + /> | |
| 128 | + <Input v-bind="$attrs" v-model:value="getInputValue" class="!w-full" :disabled="disabled" /> | |
| 129 | + <div class="min-w-14 h-full px-2 flex-grow flex-shrink-0 text-center leading-8 bg-gray-200"> | |
| 130 | + {{ getNegationValue }} | |
| 131 | + </div> | |
| 132 | + </InputGroup> | |
| 133 | +</template> | ... | ... |
| 1 | -import { FormFieldsEnum, FormFieldsNameEnum } from '../config'; | |
| 2 | -import { findDictItemByCode } from '/@/api/system/dict'; | |
| 3 | -import { FormSchema } from '/@/components/Form'; | |
| 4 | -import { DictEnum } from '/@/enums/dictEnum'; | |
| 5 | -import { DataTypeEnum, FunctionTypeEnum, FunctionTypeNameEnum } from '/@/enums/objectModelEnum'; | |
| 6 | -import { isNullOrUnDef } from '/@/utils/is'; | |
| 7 | - | |
| 8 | -export const validateValueRange = (_rule, value: Record<'min' | 'max', number>, _callback) => { | |
| 9 | - value = value || {}; | |
| 10 | - const { min, max } = value; | |
| 11 | - if (min > max) { | |
| 12 | - return Promise.reject('最大值小于最小值'); | |
| 13 | - } | |
| 14 | - return Promise.resolve(); | |
| 15 | -}; | |
| 16 | - | |
| 17 | -export const getFormSchemas = (): FormSchema[] => { | |
| 18 | - return [ | |
| 19 | - { | |
| 20 | - field: FormFieldsEnum.FUNCTION_TYPE, | |
| 21 | - label: FormFieldsNameEnum.FUNCTION_TYPE, | |
| 22 | - component: 'Segmented', | |
| 23 | - defaultValue: FunctionTypeEnum.PROPERTIES, | |
| 24 | - required: true, | |
| 25 | - componentProps: ({ formActionType }) => { | |
| 26 | - return { | |
| 27 | - options: Object.keys(FunctionTypeEnum).map((key) => ({ | |
| 28 | - title: FunctionTypeNameEnum[key], | |
| 29 | - value: FunctionTypeEnum[key], | |
| 30 | - })), | |
| 31 | - onChange() { | |
| 32 | - const { setFieldsValue, clearValidate } = formActionType; | |
| 33 | - setFieldsValue({ | |
| 34 | - [FormFieldsEnum.INPUT_DATA]: [], | |
| 35 | - [FormFieldsEnum.OUTPUT_DATA]: [], | |
| 36 | - [FormFieldsEnum.STRUCT_DATA]: [], | |
| 37 | - [FormFieldsEnum.SERVICE_COMMAND]: null, | |
| 38 | - }); | |
| 39 | - clearValidate(); | |
| 40 | - }, | |
| 41 | - }; | |
| 42 | - }, | |
| 43 | - }, | |
| 44 | - { | |
| 45 | - field: FormFieldsEnum.FUNCTION_NAME, | |
| 46 | - label: FormFieldsNameEnum.FUNCTION_NAME, | |
| 47 | - component: 'Input', | |
| 48 | - required: true, | |
| 49 | - dynamicRules: ({ values }) => { | |
| 50 | - return [ | |
| 51 | - { required: true, message: '请输入功能名称' }, | |
| 52 | - { | |
| 53 | - validator: () => { | |
| 54 | - const reg = /[,,]+/; | |
| 55 | - if (reg.test(values?.[FormFieldsEnum.FUNCTION_NAME])) { | |
| 56 | - return Promise.reject(`${FormFieldsNameEnum.FUNCTION_NAME}不能包含逗号`); | |
| 57 | - } | |
| 58 | - return Promise.resolve(); | |
| 59 | - }, | |
| 60 | - }, | |
| 61 | - ]; | |
| 62 | - }, | |
| 63 | - componentProps: { | |
| 64 | - placeholder: `请输入${FormFieldsNameEnum.FUNCTION_NAME}`, | |
| 65 | - }, | |
| 66 | - }, | |
| 67 | - { | |
| 68 | - field: FormFieldsEnum.IDENTIFIER, | |
| 69 | - label: FormFieldsNameEnum.IDENTIFIER, | |
| 70 | - required: true, | |
| 71 | - component: 'Input', | |
| 72 | - componentProps: { | |
| 73 | - maxLength: 128, | |
| 74 | - placeholder: '请输入标识符', | |
| 75 | - }, | |
| 76 | - dynamicRules: ({ values }) => { | |
| 77 | - return [ | |
| 78 | - { required: true, message: '请输入标识符' }, | |
| 79 | - { | |
| 80 | - validator: () => { | |
| 81 | - const reg = /[,,]+/; | |
| 82 | - if (reg.test(values?.[FormFieldsEnum.IDENTIFIER])) { | |
| 83 | - return Promise.reject(`${FormFieldsNameEnum.IDENTIFIER}不能包含逗号`); | |
| 84 | - } | |
| 85 | - return Promise.resolve(); | |
| 86 | - }, | |
| 87 | - }, | |
| 88 | - ]; | |
| 89 | - }, | |
| 90 | - }, | |
| 91 | - { | |
| 92 | - field: FormFieldsEnum.DATA_TYPE, | |
| 93 | - label: FormFieldsNameEnum.DATA_TYPE, | |
| 94 | - required: true, | |
| 95 | - component: 'ApiSelect', | |
| 96 | - defaultValue: DataTypeEnum.NUMBER_INT, | |
| 97 | - ifShow: ({ model }) => | |
| 98 | - ![FunctionTypeEnum.SERVICE, FunctionTypeEnum.EVENTS].includes( | |
| 99 | - model[FormFieldsEnum.FUNCTION_TYPE] | |
| 100 | - ), | |
| 101 | - componentProps: () => { | |
| 102 | - return { | |
| 103 | - placeholder: '请选择数据类型', | |
| 104 | - api: async (params: Recordable) => { | |
| 105 | - const result = await findDictItemByCode(params); | |
| 106 | - return result; | |
| 107 | - }, | |
| 108 | - params: { | |
| 109 | - dictCode: DictEnum.DATA_TYPE, | |
| 110 | - }, | |
| 111 | - labelField: 'itemText', | |
| 112 | - valueField: 'itemValue', | |
| 113 | - getPopupContainer: () => document.body, | |
| 114 | - }; | |
| 115 | - }, | |
| 116 | - }, | |
| 117 | - { | |
| 118 | - field: FormFieldsEnum.VALUE_RANGE, | |
| 119 | - label: FormFieldsNameEnum.VALUE_RANGE, | |
| 120 | - component: 'CustomMinMaxInput', | |
| 121 | - valueField: 'value', | |
| 122 | - changeEvent: 'update:value', | |
| 123 | - ifShow: ({ model }) => | |
| 124 | - ![FunctionTypeEnum.SERVICE, FunctionTypeEnum.EVENTS].includes( | |
| 125 | - model[FormFieldsEnum.FUNCTION_TYPE] | |
| 126 | - ) && | |
| 127 | - (model[FormFieldsEnum.DATA_TYPE] === DataTypeEnum.NUMBER_INT || | |
| 128 | - model[FormFieldsEnum.DATA_TYPE] === DataTypeEnum.NUMBER_DOUBLE), | |
| 129 | - rules: [{ validator: validateValueRange }], | |
| 130 | - }, | |
| 131 | - { | |
| 132 | - field: FormFieldsEnum.STEP, | |
| 133 | - label: FormFieldsNameEnum.STEP, | |
| 134 | - component: 'InputNumber', | |
| 135 | - componentProps: { | |
| 136 | - maxLength: 255, | |
| 137 | - placeholder: '请输入步长', | |
| 138 | - min: 1, | |
| 139 | - formatter: (value: number | string) => { | |
| 140 | - return value ? Math.floor(Number(value)) : value; | |
| 141 | - }, | |
| 142 | - }, | |
| 143 | - ifShow: ({ model }) => | |
| 144 | - ![FunctionTypeEnum.SERVICE, FunctionTypeEnum.EVENTS].includes( | |
| 145 | - model[FormFieldsEnum.FUNCTION_TYPE] | |
| 146 | - ) && | |
| 147 | - (model[FormFieldsEnum.DATA_TYPE] === DataTypeEnum.NUMBER_INT || | |
| 148 | - model[FormFieldsEnum.DATA_TYPE] === DataTypeEnum.NUMBER_DOUBLE), | |
| 149 | - dynamicRules: ({ model }) => { | |
| 150 | - const valueRange = model[FormFieldsEnum.VALUE_RANGE] || {}; | |
| 151 | - const { min, max } = valueRange; | |
| 152 | - const step = model[FormFieldsEnum.STEP]; | |
| 153 | - return [ | |
| 154 | - { | |
| 155 | - validator: () => { | |
| 156 | - if ([min, max].every(isNullOrUnDef)) return Promise.resolve(); | |
| 157 | - if (step > max - min) { | |
| 158 | - return Promise.reject('步长不能大于取值范围的差值'); | |
| 159 | - } | |
| 160 | - return Promise.resolve(); | |
| 161 | - }, | |
| 162 | - }, | |
| 163 | - ]; | |
| 164 | - }, | |
| 165 | - }, | |
| 166 | - { | |
| 167 | - field: FormFieldsEnum.UNIT_NAME, | |
| 168 | - label: FormFieldsNameEnum.UNIT_NAME, | |
| 169 | - component: 'Input', | |
| 170 | - show: false, | |
| 171 | - }, | |
| 172 | - { | |
| 173 | - field: FormFieldsEnum.UNIT, | |
| 174 | - label: FormFieldsNameEnum.UNIT, | |
| 175 | - component: 'ApiSelect', | |
| 176 | - componentProps: ({ formActionType }) => { | |
| 177 | - const { setFieldsValue } = formActionType; | |
| 178 | - return { | |
| 179 | - placeholder: '请选择单位', | |
| 180 | - api: async (params: Recordable) => { | |
| 181 | - const list = await findDictItemByCode(params); | |
| 182 | - list.map((item) => (item.itemText = `${item.itemText} / ${item.itemValue}`)); | |
| 183 | - return list; | |
| 184 | - }, | |
| 185 | - params: { | |
| 186 | - dictCode: DictEnum.ATTRIBUTE_UNIT, | |
| 187 | - }, | |
| 188 | - labelInValue: true, | |
| 189 | - labelField: 'itemText', | |
| 190 | - valueField: 'itemValue', | |
| 191 | - onChange(_, record: Record<'label' | 'value', string>) { | |
| 192 | - if (record) { | |
| 193 | - const { label } = record; | |
| 194 | - setFieldsValue({ [FormFieldsEnum.UNIT_NAME]: label }); | |
| 195 | - } | |
| 196 | - }, | |
| 197 | - getPopupContainer: () => document.body, | |
| 198 | - showSearch: true, | |
| 199 | - filterOption: (inputValue: string, option: Record<'label' | 'value', string>) => { | |
| 200 | - let { label, value } = option; | |
| 201 | - label = label.toLowerCase(); | |
| 202 | - value = value.toLowerCase(); | |
| 203 | - inputValue = inputValue.toLowerCase(); | |
| 204 | - return label.includes(inputValue) || value.includes(inputValue); | |
| 205 | - }, | |
| 206 | - }; | |
| 207 | - }, | |
| 208 | - ifShow: ({ model }) => | |
| 209 | - ![FunctionTypeEnum.SERVICE, FunctionTypeEnum.EVENTS].includes( | |
| 210 | - model[FormFieldsEnum.FUNCTION_TYPE] | |
| 211 | - ) && | |
| 212 | - (model[FormFieldsEnum.DATA_TYPE] === DataTypeEnum.NUMBER_INT || | |
| 213 | - model[FormFieldsEnum.DATA_TYPE] === DataTypeEnum.NUMBER_DOUBLE), | |
| 214 | - }, | |
| 215 | - { | |
| 216 | - field: FormFieldsEnum.BOOL_CLOSE, | |
| 217 | - component: 'Input', | |
| 218 | - required: true, | |
| 219 | - label: FormFieldsNameEnum.BOOL_CLOSE, | |
| 220 | - componentProps: { | |
| 221 | - placeholder: '如:关', | |
| 222 | - }, | |
| 223 | - defaultValue: '关', | |
| 224 | - ifShow: ({ model }) => | |
| 225 | - ![FunctionTypeEnum.SERVICE, FunctionTypeEnum.EVENTS].includes( | |
| 226 | - model[FormFieldsEnum.FUNCTION_TYPE] | |
| 227 | - ) && model[FormFieldsEnum.DATA_TYPE] === DataTypeEnum.BOOL, | |
| 228 | - dynamicRules: ({ model }) => { | |
| 229 | - const close = model[FormFieldsEnum.BOOL_CLOSE]; | |
| 230 | - const open = model[FormFieldsEnum.BOOL_OPEN]; | |
| 231 | - return [ | |
| 232 | - { | |
| 233 | - required: true, | |
| 234 | - message: `布尔值不能为空`, | |
| 235 | - }, | |
| 236 | - { | |
| 237 | - validator() { | |
| 238 | - if (open === close) return Promise.reject('布尔值不能相同'); | |
| 239 | - return Promise.resolve(); | |
| 240 | - }, | |
| 241 | - }, | |
| 242 | - ]; | |
| 243 | - }, | |
| 244 | - }, | |
| 245 | - { | |
| 246 | - field: FormFieldsEnum.BOOL_OPEN, | |
| 247 | - component: 'Input', | |
| 248 | - required: true, | |
| 249 | - label: FormFieldsNameEnum.BOOL_OPEN, | |
| 250 | - componentProps: { | |
| 251 | - placeholder: '如:开', | |
| 252 | - }, | |
| 253 | - defaultValue: '开', | |
| 254 | - ifShow: ({ model }) => | |
| 255 | - ![FunctionTypeEnum.SERVICE, FunctionTypeEnum.EVENTS].includes( | |
| 256 | - model[FormFieldsEnum.FUNCTION_TYPE] | |
| 257 | - ) && model[FormFieldsEnum.DATA_TYPE] === DataTypeEnum.BOOL, | |
| 258 | - dynamicRules: ({ model }) => { | |
| 259 | - const close = model[FormFieldsEnum.BOOL_CLOSE]; | |
| 260 | - const open = model[FormFieldsEnum.BOOL_OPEN]; | |
| 261 | - return [ | |
| 262 | - { | |
| 263 | - required: true, | |
| 264 | - message: `布尔值不能为空`, | |
| 265 | - }, | |
| 266 | - { | |
| 267 | - validator() { | |
| 268 | - if (open === close) return Promise.reject('布尔值不能相同'); | |
| 269 | - return Promise.resolve(); | |
| 270 | - }, | |
| 271 | - }, | |
| 272 | - ]; | |
| 273 | - }, | |
| 274 | - }, | |
| 275 | - { | |
| 276 | - field: FormFieldsEnum.LENGTH, | |
| 277 | - component: 'Input', | |
| 278 | - required: true, | |
| 279 | - label: FormFieldsNameEnum.LENGTH, | |
| 280 | - defaultValue: '10240', | |
| 281 | - colProps: { | |
| 282 | - span: 8, | |
| 283 | - }, | |
| 284 | - componentProps: { | |
| 285 | - placeholder: '请输入数据长度', | |
| 286 | - }, | |
| 287 | - renderComponentContent: () => { | |
| 288 | - return { | |
| 289 | - suffix: () => '字节', | |
| 290 | - }; | |
| 291 | - }, | |
| 292 | - ifShow: ({ model }) => | |
| 293 | - ![FunctionTypeEnum.SERVICE, FunctionTypeEnum.EVENTS].includes( | |
| 294 | - model[FormFieldsEnum.FUNCTION_TYPE] | |
| 295 | - ) && model[FormFieldsEnum.DATA_TYPE] === DataTypeEnum.STRING, | |
| 296 | - }, | |
| 297 | - { | |
| 298 | - field: FormFieldsEnum.ENUMS_DATA, | |
| 299 | - label: FormFieldsNameEnum.ENUMS_DATA, | |
| 300 | - component: 'Input', | |
| 301 | - slot: FormFieldsEnum.ENUMS_DATA, | |
| 302 | - changeEvent: 'update:value', | |
| 303 | - valueField: 'value', | |
| 304 | - ifShow: ({ model }) => | |
| 305 | - ![FunctionTypeEnum.SERVICE, FunctionTypeEnum.EVENTS].includes( | |
| 306 | - model[FormFieldsEnum.FUNCTION_TYPE] | |
| 307 | - ) && model[FormFieldsEnum.DATA_TYPE] === DataTypeEnum.ENUM, | |
| 308 | - }, | |
| 309 | - { | |
| 310 | - field: FormFieldsEnum.STRUCT_DATA, | |
| 311 | - label: FormFieldsNameEnum.STRUCT_DATA, | |
| 312 | - component: 'Input', | |
| 313 | - slot: FormFieldsEnum.STRUCT_DATA, | |
| 314 | - changeEvent: 'update:value', | |
| 315 | - valueField: 'value', | |
| 316 | - required: true, | |
| 317 | - ifShow: ({ model }) => | |
| 318 | - model[FormFieldsEnum.DATA_TYPE] === DataTypeEnum.STRUCT && | |
| 319 | - ![FunctionTypeEnum.SERVICE, FunctionTypeEnum.EVENTS].includes( | |
| 320 | - model[FormFieldsEnum.FUNCTION_TYPE] | |
| 321 | - ), | |
| 322 | - }, | |
| 323 | - ]; | |
| 324 | -}; |
| ... | ... | @@ -7,6 +7,7 @@ import { FormSchema } from '/@/components/Form'; |
| 7 | 7 | import { |
| 8 | 8 | ServiceCallTypeEnum, |
| 9 | 9 | ServiceCallTypeNameEnum, |
| 10 | + TCPProtocolTypeEnum, | |
| 10 | 11 | TransportTypeEnum, |
| 11 | 12 | } from '/@/enums/deviceEnum'; |
| 12 | 13 | import { DictEnum } from '/@/enums/dictEnum'; |
| ... | ... | @@ -15,8 +16,11 @@ import { |
| 15 | 16 | FunctionTypeEnum, |
| 16 | 17 | FunctionTypeNameEnum, |
| 17 | 18 | ObjectEventTypeEnum, |
| 19 | + BuiltInIdentifierEnum, | |
| 18 | 20 | } from '/@/enums/objectModelEnum'; |
| 19 | 21 | import { DataActionModeEnum } from '/@/enums/toolEnum'; |
| 22 | +import { DeviceTypeEnum } from '/@/api/device/model/deviceModel'; | |
| 23 | +import { DeviceProfileDetail } from '/@/api/device/model/deviceConfigModel'; | |
| 20 | 24 | |
| 21 | 25 | export enum FormFieldsEnum { |
| 22 | 26 | FUNCTION_TYPE = 'functionType', |
| ... | ... | @@ -74,10 +78,18 @@ export enum FormFieldsNameEnum { |
| 74 | 78 | |
| 75 | 79 | export const getFormSchemas = ({ |
| 76 | 80 | transportType, |
| 81 | + deviceRecord, | |
| 77 | 82 | }: { |
| 78 | 83 | transportType?: string; |
| 79 | 84 | mode?: DataActionModeEnum; |
| 85 | + deviceRecord?: DeviceProfileDetail; | |
| 80 | 86 | }): FormSchema[] => { |
| 87 | + const isTCPTransport = deviceRecord?.transportType === TransportTypeEnum.TCP; | |
| 88 | + | |
| 89 | + const isTCPModbusProduct = | |
| 90 | + isTCPTransport && | |
| 91 | + deviceRecord?.profileData.transportConfiguration.protocol === TCPProtocolTypeEnum.MODBUS_RTU; | |
| 92 | + | |
| 81 | 93 | return [ |
| 82 | 94 | { |
| 83 | 95 | field: FormFieldsEnum.FUNCTION_TYPE, |
| ... | ... | @@ -85,16 +97,29 @@ export const getFormSchemas = ({ |
| 85 | 97 | component: 'Segmented', |
| 86 | 98 | defaultValue: FunctionTypeEnum.PROPERTIES, |
| 87 | 99 | required: true, |
| 100 | + helpMessage: [ | |
| 101 | + '属性一般是指设备的运行状态,如当前温度等;服务是指设备可被调用的方法,支持定义参数,如执行某项任务;事件则是指设备上报的通知,如告警,需要被及时处理。', | |
| 102 | + ], | |
| 88 | 103 | dynamicDisabled: () => { |
| 89 | 104 | const { getModalMode } = useObjectModelFormContext(); |
| 90 | 105 | return unref(getModalMode) !== DataActionModeEnum.CREATE; |
| 91 | 106 | }, |
| 92 | 107 | componentProps: ({ formActionType }) => { |
| 108 | + const isSensor = deviceRecord?.deviceType === DeviceTypeEnum.SENSOR; | |
| 93 | 109 | return { |
| 94 | - options: Object.keys(FunctionTypeEnum).map((key) => ({ | |
| 95 | - title: FunctionTypeNameEnum[key], | |
| 96 | - value: FunctionTypeEnum[key], | |
| 97 | - })), | |
| 110 | + options: [ | |
| 111 | + { title: FunctionTypeNameEnum.PROPERTIES, value: FunctionTypeEnum.PROPERTIES }, | |
| 112 | + { | |
| 113 | + title: FunctionTypeNameEnum.SERVICE, | |
| 114 | + value: FunctionTypeEnum.SERVICE, | |
| 115 | + disabled: (isTCPTransport && isSensor) || isTCPModbusProduct, | |
| 116 | + }, | |
| 117 | + { | |
| 118 | + title: FunctionTypeNameEnum.EVENTS, | |
| 119 | + value: FunctionTypeEnum.EVENTS, | |
| 120 | + disabled: isTCPTransport, | |
| 121 | + }, | |
| 122 | + ], | |
| 98 | 123 | onChange() { |
| 99 | 124 | const { setFieldsValue, clearValidate } = formActionType; |
| 100 | 125 | setFieldsValue({ |
| ... | ... | @@ -112,26 +137,26 @@ export const getFormSchemas = ({ |
| 112 | 137 | field: FormFieldsEnum.DATA_TYPE_FORM, |
| 113 | 138 | component: 'Input', |
| 114 | 139 | label: '', |
| 115 | - ifShow: ({ model }) => model[FormFieldsEnum.FUNCTION_TYPE] === FunctionTypeEnum.PROPERTIES, | |
| 140 | + ifShow: ({ model }) => | |
| 141 | + model[FormFieldsEnum.FUNCTION_TYPE] === FunctionTypeEnum.PROPERTIES && !isTCPModbusProduct, | |
| 116 | 142 | colSlot: FormFieldsEnum.DATA_TYPE_FORM, |
| 117 | 143 | }, |
| 118 | 144 | createFunctionNameFormItem({ |
| 119 | - ifShow: ({ model }) => model[FormFieldsEnum.FUNCTION_TYPE] !== FunctionTypeEnum.PROPERTIES, | |
| 145 | + ifShow: ({ model }) => | |
| 146 | + model[FormFieldsEnum.FUNCTION_TYPE] !== FunctionTypeEnum.PROPERTIES || isTCPModbusProduct, | |
| 120 | 147 | }), |
| 121 | 148 | createIdentifierFormItem({ |
| 122 | - ifShow: ({ model }) => model[FormFieldsEnum.FUNCTION_TYPE] !== FunctionTypeEnum.PROPERTIES, | |
| 123 | - }), | |
| 124 | - { | |
| 125 | - field: FormFieldsEnum.EXTENSION_DESC, | |
| 126 | - component: 'Input', | |
| 127 | - label: FormFieldsNameEnum.EXTENSION_DESC, | |
| 128 | - slot: FormFieldsEnum.EXTENSION_DESC, | |
| 149 | + helpMessage: ['支持大小写字母、数字和下划线', '如要支持原始数据留存,请使用标识符 "source"'], | |
| 129 | 150 | ifShow: ({ model }) => |
| 130 | - transportType === TransportTypeEnum.TCP && | |
| 131 | - ![FunctionTypeEnum.SERVICE, FunctionTypeEnum.EVENTS].includes( | |
| 132 | - model[FormFieldsEnum.FUNCTION_TYPE] | |
| 133 | - ), | |
| 134 | - }, | |
| 151 | + model[FormFieldsEnum.FUNCTION_TYPE] !== FunctionTypeEnum.PROPERTIES || isTCPModbusProduct, | |
| 152 | + component: 'AutoComplete', | |
| 153 | + defaultValue: '', | |
| 154 | + componentProps: { | |
| 155 | + options: isTCPModbusProduct ? [{ value: BuiltInIdentifierEnum.SOURCE }] : [], | |
| 156 | + placeholder: '请输入功能标识符', | |
| 157 | + autocomplete: false, | |
| 158 | + }, | |
| 159 | + }), | |
| 135 | 160 | { |
| 136 | 161 | field: FormFieldsEnum.ACCESS_MODE, |
| 137 | 162 | component: 'ApiRadioGroup', |
| ... | ... | @@ -140,7 +165,7 @@ export const getFormSchemas = ({ |
| 140 | 165 | ifShow: ({ model }) => |
| 141 | 166 | ![FunctionTypeEnum.SERVICE, FunctionTypeEnum.EVENTS].includes( |
| 142 | 167 | model[FormFieldsEnum.FUNCTION_TYPE] |
| 143 | - ), | |
| 168 | + ) && !isTCPModbusProduct, | |
| 144 | 169 | defaultValue: ObjectModelAccessModeEnum.READ_AND_WRITE, |
| 145 | 170 | componentProps: { |
| 146 | 171 | placeholder: '请选择读写类型', |
| ... | ... | @@ -232,6 +257,70 @@ export const getFormSchemas = ({ |
| 232 | 257 | ), |
| 233 | 258 | }, |
| 234 | 259 | |
| 260 | + // TCP Modbus | |
| 261 | + { | |
| 262 | + field: FormFieldsEnum.UNIT_NAME, | |
| 263 | + label: FormFieldsNameEnum.UNIT_NAME, | |
| 264 | + component: 'Input', | |
| 265 | + show: false, | |
| 266 | + }, | |
| 267 | + { | |
| 268 | + field: FormFieldsEnum.UNIT, | |
| 269 | + label: FormFieldsNameEnum.UNIT, | |
| 270 | + component: 'ApiSelect', | |
| 271 | + ifShow: () => isTCPModbusProduct, | |
| 272 | + componentProps: ({ formActionType }) => { | |
| 273 | + const { setFieldsValue } = formActionType; | |
| 274 | + return { | |
| 275 | + placeholder: '请选择单位', | |
| 276 | + api: async (params: Recordable) => { | |
| 277 | + const list = await findDictItemByCode(params); | |
| 278 | + list.map((item) => (item.itemText = `${item.itemText} / ${item.itemValue}`)); | |
| 279 | + return list; | |
| 280 | + }, | |
| 281 | + params: { | |
| 282 | + dictCode: DictEnum.ATTRIBUTE_UNIT, | |
| 283 | + }, | |
| 284 | + labelField: 'itemText', | |
| 285 | + valueField: 'itemValue', | |
| 286 | + onChange(_, record: Record<'label' | 'value', string>) { | |
| 287 | + if (record) { | |
| 288 | + const { label } = record; | |
| 289 | + setFieldsValue({ [FormFieldsEnum.UNIT_NAME]: label }); | |
| 290 | + } | |
| 291 | + }, | |
| 292 | + getPopupContainer: () => document.body, | |
| 293 | + showSearch: true, | |
| 294 | + filterOption: (inputValue: string, option: Record<'label' | 'value', string>) => { | |
| 295 | + let { label, value } = option; | |
| 296 | + label = label.toLowerCase(); | |
| 297 | + value = value.toLowerCase(); | |
| 298 | + inputValue = inputValue.toLowerCase(); | |
| 299 | + return label.includes(inputValue) || value.includes(inputValue); | |
| 300 | + }, | |
| 301 | + }; | |
| 302 | + }, | |
| 303 | + }, | |
| 304 | + { | |
| 305 | + field: FormFieldsEnum.EXTENSION_DESC, | |
| 306 | + component: 'Input', | |
| 307 | + label: FormFieldsNameEnum.EXTENSION_DESC, | |
| 308 | + slot: FormFieldsEnum.EXTENSION_DESC, | |
| 309 | + helpMessage: ['扩展描述用于配置设备接入网关协议和物模型定义之间的映射关系。'], | |
| 310 | + rules: [ | |
| 311 | + { | |
| 312 | + required: true, | |
| 313 | + validator(_rule, value: any) { | |
| 314 | + return Object.values(value || {}).length | |
| 315 | + ? Promise.resolve() | |
| 316 | + : Promise.reject('请填写拓展描述符'); | |
| 317 | + }, | |
| 318 | + }, | |
| 319 | + ], | |
| 320 | + ifShow: ({ model }) => | |
| 321 | + isTCPModbusProduct && model[FormFieldsEnum.IDENTIFIER] !== BuiltInIdentifierEnum.SOURCE, | |
| 322 | + }, | |
| 323 | + | |
| 235 | 324 | { |
| 236 | 325 | field: FormFieldsEnum.REMARK, |
| 237 | 326 | label: FormFieldsNameEnum.REMARK, | ... | ... |
| ... | ... | @@ -3,18 +3,14 @@ |
| 3 | 3 | import { getFormSchemas, FormFieldsEnum } from './config'; |
| 4 | 4 | import { StructFormItem } from './StructFormItem'; |
| 5 | 5 | import { useObjectFormData } from './useObjectFormData'; |
| 6 | - import { computed, ref, unref } from 'vue'; | |
| 7 | - import { TransportTypeEnum } from '/@/enums/deviceEnum'; | |
| 6 | + import { computed, ref } from 'vue'; | |
| 8 | 7 | import { DataTypeForm } from './DataTypeForm'; |
| 9 | 8 | import { DataActionModeEnum } from '/@/enums/toolEnum'; |
| 10 | 9 | import { ExtendDesc } from './ExtendDesc'; |
| 11 | 10 | import { createObjectModelFormContext } from './useObjectModelFormContext'; |
| 12 | - import { DataTypeEnum } from '/@/enums/objectModelEnum'; | |
| 11 | + import { ObjectModelFormPropsType } from './types'; | |
| 13 | 12 | |
| 14 | - const props = defineProps<{ | |
| 15 | - transportType?: TransportTypeEnum; | |
| 16 | - mode?: DataActionModeEnum; | |
| 17 | - }>(); | |
| 13 | + const props = defineProps<ObjectModelFormPropsType>(); | |
| 18 | 14 | |
| 19 | 15 | const dataTypeFormRef = ref<InstanceType<typeof DataTypeForm>>(); |
| 20 | 16 | |
| ... | ... | @@ -23,27 +19,20 @@ |
| 23 | 19 | layout: 'vertical', |
| 24 | 20 | showActionButtonGroup: false, |
| 25 | 21 | }); |
| 26 | - | |
| 27 | 22 | const { getFieldsValue, setFieldsValue, validate, resetFieldsValue } = useObjectFormData({ |
| 28 | 23 | formActionType, |
| 29 | 24 | dataTypeFormRef, |
| 30 | - transportType: props.transportType!, | |
| 25 | + propsRef: props, | |
| 31 | 26 | }); |
| 32 | 27 | |
| 33 | - const objectModelType = ref(DataTypeEnum.NUMBER_INT); | |
| 34 | - | |
| 35 | 28 | createObjectModelFormContext({ |
| 36 | 29 | getTransportType: computed(() => props.transportType), |
| 37 | - getDataType: computed(() => unref(objectModelType)), | |
| 38 | 30 | getModalMode: computed(() => props.mode), |
| 39 | 31 | }); |
| 40 | 32 | |
| 41 | - const handleDataTypeFormChange = (field: string, value: any) => { | |
| 42 | - if (field === FormFieldsEnum.DATA_TYPE) { | |
| 43 | - objectModelType.value = value; | |
| 44 | - } | |
| 33 | + const handleValidateExtendDesc = () => { | |
| 34 | + formActionType.clearValidate(FormFieldsEnum.EXTENSION_DESC); | |
| 45 | 35 | }; |
| 46 | - | |
| 47 | 36 | defineExpose({ |
| 48 | 37 | getFieldsValue, |
| 49 | 38 | setFieldsValue, |
| ... | ... | @@ -55,11 +44,7 @@ |
| 55 | 44 | <template> |
| 56 | 45 | <BasicForm @register="register" :disabled="mode === DataActionModeEnum.READ"> |
| 57 | 46 | <template #dataTypeForm> |
| 58 | - <DataTypeForm | |
| 59 | - ref="dataTypeFormRef" | |
| 60 | - :mode="mode" | |
| 61 | - @field-value-change="handleDataTypeFormChange" | |
| 62 | - /> | |
| 47 | + <DataTypeForm ref="dataTypeFormRef" :mode="mode" /> | |
| 63 | 48 | </template> |
| 64 | 49 | <template #inputData="{ model, field }"> |
| 65 | 50 | <StructFormItem v-model:value="model[field]" buttonName="+ 增加参数" :mode="mode" /> |
| ... | ... | @@ -68,7 +53,11 @@ |
| 68 | 53 | <StructFormItem v-model:value="model[field]" buttonName="+ 增加参数" :mode="mode" /> |
| 69 | 54 | </template> |
| 70 | 55 | <template #extensionDesc="{ model, field }"> |
| 71 | - <ExtendDesc v-model:value="model[field]" :disabled="mode === DataActionModeEnum.READ" /> | |
| 56 | + <ExtendDesc | |
| 57 | + v-model:value="model[field]" | |
| 58 | + :disabled="mode === DataActionModeEnum.READ" | |
| 59 | + @change="handleValidateExtendDesc" | |
| 60 | + /> | |
| 72 | 61 | </template> |
| 73 | 62 | </BasicForm> |
| 74 | 63 | </template> | ... | ... |
| 1 | +import { ExtendDescFormFieldsValueType } from './ExtendDesc'; | |
| 1 | 2 | import { FormFieldsEnum } from './config'; |
| 2 | -import { ExtensionDesc, StructJSON } from '/@/api/device/model/modelOfMatterModel'; | |
| 3 | -import { ServiceCallTypeEnum } from '/@/enums/deviceEnum'; | |
| 3 | +import { DeviceProfileDetail } from '/@/api/device/model/deviceConfigModel'; | |
| 4 | +import { StructJSON } from '/@/api/device/model/modelOfMatterModel'; | |
| 5 | +import { ServiceCallTypeEnum, TransportTypeEnum } from '/@/enums/deviceEnum'; | |
| 4 | 6 | import { |
| 5 | 7 | FunctionTypeEnum, |
| 6 | 8 | ObjectEventTypeEnum, |
| 7 | 9 | ObjectModelAccessModeEnum, |
| 8 | 10 | } from '/@/enums/objectModelEnum'; |
| 11 | +import { DataActionModeEnum } from '/@/enums/toolEnum'; | |
| 12 | + | |
| 13 | +export interface ObjectModelFormPropsType { | |
| 14 | + transportType?: TransportTypeEnum; | |
| 15 | + mode?: DataActionModeEnum; | |
| 16 | + deviceRecord?: DeviceProfileDetail; | |
| 17 | +} | |
| 9 | 18 | |
| 10 | 19 | export interface ObjectModelFormGetFieldsValueType { |
| 11 | 20 | [FormFieldsEnum.FUNCTION_TYPE]: FunctionTypeEnum; |
| ... | ... | @@ -14,16 +23,13 @@ export interface ObjectModelFormGetFieldsValueType { |
| 14 | 23 | [FormFieldsEnum.FUNCTION_NAME]: string; |
| 15 | 24 | [FormFieldsEnum.IDENTIFIER]: string; |
| 16 | 25 | [FormFieldsEnum.EVENT_TYPE]?: ObjectEventTypeEnum; |
| 26 | + [FormFieldsEnum.UNIT]?: string; | |
| 27 | + [FormFieldsEnum.UNIT_NAME]: string; | |
| 17 | 28 | |
| 18 | 29 | [FormFieldsEnum.CALL_TYPE]?: ServiceCallTypeEnum; |
| 19 | 30 | [FormFieldsEnum.INPUT_DATA]?: StructJSON[]; |
| 20 | 31 | [FormFieldsEnum.OUTPUT_DATA]?: StructJSON[]; |
| 21 | 32 | |
| 22 | 33 | [FormFieldsEnum.SERVICE_COMMAND]?: string; |
| 23 | - [FormFieldsEnum.EXTENSION_DESC]?: ExtensionDesc; | |
| 24 | - | |
| 25 | - // EXTENSION_DESC = 'extensionDesc', | |
| 26 | - // STRUCT_DATA = 'structData', | |
| 27 | - // INPUT_DATA = 'inputData', | |
| 28 | - // OUTPUT_DATA = 'outputData', | |
| 34 | + [FormFieldsEnum.EXTENSION_DESC]?: ExtendDescFormFieldsValueType; | |
| 29 | 35 | } | ... | ... |
| 1 | 1 | import { DataTypeForm } from './DataTypeForm'; |
| 2 | +import { ExtendDescFormFieldsValueType } from './ExtendDesc'; | |
| 3 | +import { isFloatType, useParseOperationType } from './ExtendDesc/useParseOperationType'; | |
| 2 | 4 | import { FormFieldsEnum } from './config'; |
| 3 | -import { ObjectModelFormGetFieldsValueType } from './types'; | |
| 5 | +import { ObjectModelFormGetFieldsValueType, ObjectModelFormPropsType } from './types'; | |
| 4 | 6 | import { DefineComponentsBasicExpose } from '/#/utils'; |
| 5 | -import { ModelOfMatterParams, StructJSON } from '/@/api/device/model/modelOfMatterModel'; | |
| 7 | +import { ModelOfMatterParams, Specs, StructJSON } from '/@/api/device/model/modelOfMatterModel'; | |
| 6 | 8 | import { FormActionType } from '/@/components/Form'; |
| 7 | -import { TransportTypeEnum } from '/@/enums/deviceEnum'; | |
| 8 | -import { FunctionTypeEnum } from '/@/enums/objectModelEnum'; | |
| 9 | -import { Ref, toRaw, unref } from 'vue'; | |
| 9 | +import { TCPProtocolTypeEnum, TransportTypeEnum } from '/@/enums/deviceEnum'; | |
| 10 | +import { | |
| 11 | + BuiltInIdentifierEnum, | |
| 12 | + DataTypeEnum, | |
| 13 | + FunctionTypeEnum, | |
| 14 | + ObjectModelAccessModeEnum, | |
| 15 | + OriginalDataTypeEnum, | |
| 16 | +} from '/@/enums/objectModelEnum'; | |
| 17 | +import { Ref, computed, toRaw, unref } from 'vue'; | |
| 10 | 18 | |
| 11 | 19 | interface UseObjectFormDataParamsType { |
| 12 | 20 | formActionType: FormActionType; |
| 13 | 21 | dataTypeFormRef: Ref<InstanceType<typeof DataTypeForm> | undefined>; |
| 14 | - transportType: TransportTypeEnum; | |
| 22 | + propsRef?: ObjectModelFormPropsType; | |
| 23 | +} | |
| 24 | + | |
| 25 | +export function getDataTypeByOriginalDataType( | |
| 26 | + originalDataType: OriginalDataTypeEnum, | |
| 27 | + identifier: string | |
| 28 | +) { | |
| 29 | + if (identifier === BuiltInIdentifierEnum.SOURCE) return DataTypeEnum.STRING; | |
| 30 | + | |
| 31 | + if (isFloatType(originalDataType)) return DataTypeEnum.NUMBER_DOUBLE; | |
| 32 | + else if (originalDataType === OriginalDataTypeEnum.STRING) return DataTypeEnum.STRING; | |
| 33 | + else if ([OriginalDataTypeEnum.BOOLEAN, OriginalDataTypeEnum.BITS].includes(originalDataType)) | |
| 34 | + return DataTypeEnum.BOOL; | |
| 35 | + else return DataTypeEnum.NUMBER_INT; | |
| 36 | +} | |
| 37 | + | |
| 38 | +function handleTCPModbusProductPropertiesType( | |
| 39 | + value: ObjectModelFormGetFieldsValueType | |
| 40 | +): ModelOfMatterParams { | |
| 41 | + const { functionName, identifier, functionType, remark, extensionDesc, unit, unitName } = value; | |
| 42 | + | |
| 43 | + const { | |
| 44 | + operationType, | |
| 45 | + originalDataType, | |
| 46 | + bitMask, | |
| 47 | + valueRange, | |
| 48 | + registerAddress, | |
| 49 | + scaling, | |
| 50 | + registerCount, | |
| 51 | + valueMapping, | |
| 52 | + } = extensionDesc! || {}; | |
| 53 | + | |
| 54 | + const { writeOnly, accessMode } = useParseOperationType(operationType); | |
| 55 | + | |
| 56 | + const dataType = getDataTypeByOriginalDataType(originalDataType, identifier); | |
| 57 | + | |
| 58 | + return { | |
| 59 | + functionType, | |
| 60 | + functionName, | |
| 61 | + identifier, | |
| 62 | + functionJson: { | |
| 63 | + dataType: { | |
| 64 | + type: dataType, | |
| 65 | + specs: { | |
| 66 | + unit, | |
| 67 | + unitName, | |
| 68 | + valueRange, | |
| 69 | + }, | |
| 70 | + specsList: valueMapping || [], | |
| 71 | + }, | |
| 72 | + }, | |
| 73 | + accessMode: | |
| 74 | + identifier === BuiltInIdentifierEnum.SOURCE ? ObjectModelAccessModeEnum.READ : accessMode, | |
| 75 | + remark, | |
| 76 | + extensionDesc: { | |
| 77 | + writeOnly, | |
| 78 | + bitMask, | |
| 79 | + operationType, | |
| 80 | + originalDataType, | |
| 81 | + registerAddress, | |
| 82 | + scaling, | |
| 83 | + registerCount, | |
| 84 | + }, | |
| 85 | + }; | |
| 15 | 86 | } |
| 16 | 87 | |
| 17 | 88 | function handlePropertiesType( |
| 18 | 89 | value: ObjectModelFormGetFieldsValueType, |
| 19 | - dataTypeFormValue: StructJSON, | |
| 20 | - transportType?: TransportTypeEnum | |
| 90 | + dataTypeFormValue: StructJSON | |
| 21 | 91 | ): ModelOfMatterParams { |
| 22 | - const { functionType, accessMode, remark, extensionDesc } = value; | |
| 23 | - const { functionName, identifier, dataType } = dataTypeFormValue; | |
| 92 | + const { functionType, accessMode, remark } = value; | |
| 93 | + const { functionName, identifier, dataType } = dataTypeFormValue || {}; | |
| 24 | 94 | |
| 25 | 95 | const result: ModelOfMatterParams = { |
| 26 | 96 | functionType, |
| ... | ... | @@ -33,15 +103,12 @@ function handlePropertiesType( |
| 33 | 103 | remark, |
| 34 | 104 | }; |
| 35 | 105 | |
| 36 | - if (transportType === TransportTypeEnum.TCP && extensionDesc) | |
| 37 | - result.extensionDesc = extensionDesc; | |
| 38 | - | |
| 39 | 106 | return result; |
| 40 | 107 | } |
| 41 | 108 | |
| 42 | 109 | function handleServiceType( |
| 43 | 110 | value: ObjectModelFormGetFieldsValueType, |
| 44 | - transportType: TransportTypeEnum | |
| 111 | + transportType?: TransportTypeEnum | |
| 45 | 112 | ): ModelOfMatterParams { |
| 46 | 113 | const { |
| 47 | 114 | functionName, |
| ... | ... | @@ -51,9 +118,11 @@ function handleServiceType( |
| 51 | 118 | inputData = [], |
| 52 | 119 | outputData = [], |
| 53 | 120 | serviceCommand, |
| 121 | + callType, | |
| 54 | 122 | } = value; |
| 55 | 123 | |
| 56 | 124 | return { |
| 125 | + callType, | |
| 57 | 126 | functionType, |
| 58 | 127 | functionName, |
| 59 | 128 | identifier, |
| ... | ... | @@ -86,26 +155,37 @@ function handleEventType(value: ObjectModelFormGetFieldsValueType): ModelOfMatte |
| 86 | 155 | export const useObjectFormData = ({ |
| 87 | 156 | formActionType, |
| 88 | 157 | dataTypeFormRef, |
| 89 | - transportType, | |
| 158 | + propsRef, | |
| 90 | 159 | }: UseObjectFormDataParamsType): DefineComponentsBasicExpose<ModelOfMatterParams> => { |
| 160 | + const getTCPModbusProductFlag = computed( | |
| 161 | + () => | |
| 162 | + propsRef?.deviceRecord?.profileData.transportConfiguration.protocol === | |
| 163 | + TCPProtocolTypeEnum.MODBUS_RTU | |
| 164 | + ); | |
| 165 | + | |
| 166 | + const getTransportType = computed( | |
| 167 | + () => propsRef?.deviceRecord?.transportType as TransportTypeEnum | |
| 168 | + ); | |
| 169 | + | |
| 91 | 170 | const getFieldsValue = (): ModelOfMatterParams => { |
| 92 | 171 | const value = formActionType.getFieldsValue() as ObjectModelFormGetFieldsValueType; |
| 93 | 172 | const { functionType } = value; |
| 94 | - | |
| 95 | 173 | if (functionType === FunctionTypeEnum.PROPERTIES) { |
| 96 | 174 | const dataTypeFormValue = unref(dataTypeFormRef)?.getFieldsValue?.(); |
| 97 | - return handlePropertiesType(value, dataTypeFormValue!, transportType); | |
| 175 | + if (unref(getTCPModbusProductFlag)) return handleTCPModbusProductPropertiesType(value); | |
| 176 | + return handlePropertiesType(value, dataTypeFormValue!); | |
| 98 | 177 | } |
| 99 | 178 | |
| 100 | 179 | if (functionType === FunctionTypeEnum.EVENTS) { |
| 101 | 180 | return handleEventType(value); |
| 102 | - } else { | |
| 103 | - return handleServiceType(value, transportType); | |
| 104 | 181 | } |
| 182 | + | |
| 183 | + return handleServiceType(value, unref(getTransportType)); | |
| 105 | 184 | }; |
| 106 | 185 | |
| 107 | 186 | const setFieldsValue = (values: ModelOfMatterParams) => { |
| 108 | 187 | values = toRaw(unref(values)); |
| 188 | + const transportType = unref(getTransportType); | |
| 109 | 189 | const { |
| 110 | 190 | functionName, |
| 111 | 191 | identifier, |
| ... | ... | @@ -115,10 +195,11 @@ export const useObjectFormData = ({ |
| 115 | 195 | callType, |
| 116 | 196 | remark, |
| 117 | 197 | extensionDesc, |
| 198 | + eventType, | |
| 118 | 199 | } = values; |
| 119 | 200 | const { dataType, inputData = [], outputData = [] } = functionJson || {}; |
| 120 | 201 | |
| 121 | - formActionType.setFieldsValue({ | |
| 202 | + const setValue = { | |
| 122 | 203 | identifier, |
| 123 | 204 | functionName, |
| 124 | 205 | functionType, |
| ... | ... | @@ -127,9 +208,23 @@ export const useObjectFormData = ({ |
| 127 | 208 | inputData, |
| 128 | 209 | outputData, |
| 129 | 210 | remark, |
| 211 | + eventType, | |
| 130 | 212 | serviceCommand: transportType === TransportTypeEnum.TCP ? inputData?.[0]?.serviceCommand : '', |
| 131 | 213 | extensionDesc, |
| 132 | - } as ObjectModelFormGetFieldsValueType); | |
| 214 | + } as ObjectModelFormGetFieldsValueType; | |
| 215 | + | |
| 216 | + if (unref(getTCPModbusProductFlag)) { | |
| 217 | + const { specs, type, specsList } = dataType || {}; | |
| 218 | + const { unit, unitName, valueRange } = (specs as Specs) || {}; | |
| 219 | + Object.assign(setValue, { unit, unitName } as ObjectModelFormGetFieldsValueType); | |
| 220 | + Object.assign(setValue.extensionDesc || {}, { | |
| 221 | + valueRange, | |
| 222 | + dataType: type, | |
| 223 | + valueMapping: specsList, | |
| 224 | + } as Partial<ExtendDescFormFieldsValueType>); | |
| 225 | + } | |
| 226 | + | |
| 227 | + formActionType.setFieldsValue(setValue); | |
| 133 | 228 | |
| 134 | 229 | if (functionType === FunctionTypeEnum.PROPERTIES) { |
| 135 | 230 | unref(dataTypeFormRef)?.setFieldsValue({ |
| ... | ... | @@ -145,7 +240,7 @@ export const useObjectFormData = ({ |
| 145 | 240 | await unref(dataTypeFormRef)?.validate?.(); |
| 146 | 241 | }; |
| 147 | 242 | |
| 148 | - const resetFieldsValue = () => { | |
| 243 | + const resetFieldsValue = async () => { | |
| 149 | 244 | formActionType.resetFields(); |
| 150 | 245 | unref(dataTypeFormRef)?.resetFieldsValue?.(); |
| 151 | 246 | }; | ... | ... |
| 1 | 1 | import type { InjectionKey, ComputedRef } from 'vue'; |
| 2 | 2 | import { createContext, useContext } from '/@/hooks/core/useContext'; |
| 3 | 3 | import { TransportTypeEnum } from '/@/enums/deviceEnum'; |
| 4 | -import { DataTypeEnum } from '/@/enums/objectModelEnum'; | |
| 5 | 4 | import { DataActionModeEnum } from '/@/enums/toolEnum'; |
| 6 | 5 | |
| 7 | 6 | export interface ObjectModelFormContextProps { |
| 8 | 7 | getTransportType: ComputedRef<TransportTypeEnum | undefined>; |
| 9 | - getDataType: ComputedRef<DataTypeEnum>; | |
| 10 | 8 | getModalMode: ComputedRef<DataActionModeEnum | undefined>; |
| 11 | 9 | } |
| 12 | 10 | ... | ... |
| ... | ... | @@ -28,10 +28,9 @@ |
| 28 | 28 | </BasicModal> |
| 29 | 29 | </template> |
| 30 | 30 | <script lang="ts" setup> |
| 31 | - import { ref, unref } from 'vue'; | |
| 31 | + import { computed, ref, unref } from 'vue'; | |
| 32 | 32 | import { Button, Radio, RadioGroup } from 'ant-design-vue'; |
| 33 | 33 | import { BasicModal, useModalInner } from '/@/components/Modal'; |
| 34 | - import { DeviceRecord } from '/@/api/device/model/deviceModel'; | |
| 35 | 34 | import { useMessage } from '/@/hooks/web/useMessage'; |
| 36 | 35 | import { isObject, isString } from '/@/utils/is'; |
| 37 | 36 | import { |
| ... | ... | @@ -45,11 +44,13 @@ |
| 45 | 44 | import { BasicForm, useForm } from '/@/components/Form'; |
| 46 | 45 | import { Authority } from '/@/components/Authority'; |
| 47 | 46 | import { usePermission } from '/@/hooks/web/usePermission'; |
| 47 | + import { TCPProtocolTypeEnum, TransportTypeEnum } from '/@/enums/deviceEnum'; | |
| 48 | + import { DeviceProfileDetail } from '/@/api/device/model/deviceConfigModel'; | |
| 48 | 49 | |
| 49 | 50 | const emits = defineEmits(['register', 'handleImportCSV', 'handleReload']); |
| 50 | 51 | |
| 51 | - defineProps<{ | |
| 52 | - record: DeviceRecord; | |
| 52 | + const props = defineProps<{ | |
| 53 | + record: DeviceProfileDetail; | |
| 53 | 54 | }>(); |
| 54 | 55 | |
| 55 | 56 | const { createMessage } = useMessage(); |
| ... | ... | @@ -196,10 +197,14 @@ |
| 196 | 197 | const formData = new FormData(); |
| 197 | 198 | formData.set('file', file); |
| 198 | 199 | const flag = isCateGory |
| 199 | - ? await importCsvCategory({ categoryId: id, deviceProfileId: undefined, file: formData }) | |
| 200 | + ? await importCsvCategory({ | |
| 201 | + categoryId: id, | |
| 202 | + type: unref(getIsModbusDeviceFlag) ? 'modbus' : '', | |
| 203 | + file: formData, | |
| 204 | + }) | |
| 200 | 205 | : await importCsvDeviceProfileId({ |
| 201 | - categoryId: undefined, | |
| 202 | 206 | deviceProfileId: id, |
| 207 | + type: unref(getIsModbusDeviceFlag) ? 'modbus' : '', | |
| 203 | 208 | file: formData, |
| 204 | 209 | }); |
| 205 | 210 | flag && createMessage.info(flag?.message); |
| ... | ... | @@ -224,10 +229,18 @@ |
| 224 | 229 | URL.revokeObjectURL(objectURL); |
| 225 | 230 | }; |
| 226 | 231 | |
| 232 | + const getIsModbusDeviceFlag = computed(() => { | |
| 233 | + const isTCPProtocol = props.record.transportType === TransportTypeEnum.TCP; | |
| 234 | + return ( | |
| 235 | + isTCPProtocol && | |
| 236 | + props.record?.profileData?.transportConfiguration?.protocol === TCPProtocolTypeEnum.MODBUS_RTU | |
| 237 | + ); | |
| 238 | + }); | |
| 239 | + | |
| 227 | 240 | // 模板下载 |
| 228 | 241 | const handleTemplateDownload = async () => { |
| 229 | - const res = await excelExport(); | |
| 230 | - downloadFile(res, '物模型属性导入模板', 'xls'); | |
| 242 | + const res = await excelExport(unref(getIsModbusDeviceFlag) ? 'modbus' : ''); | |
| 243 | + downloadFile(res, `${unref(getIsModbusDeviceFlag) ? 'Modbus' : ''}物模型属性导入模板`, 'xls'); | |
| 231 | 244 | }; |
| 232 | 245 | |
| 233 | 246 | const handleCancel = () => { | ... | ... |
| ... | ... | @@ -140,7 +140,6 @@ |
| 140 | 140 | import PhysicalModelTsl from './cpns/physical/PhysicalModelTsl.vue'; |
| 141 | 141 | import { Popconfirm, Button, Alert } from 'ant-design-vue'; |
| 142 | 142 | import { useMessage } from '/@/hooks/web/useMessage'; |
| 143 | - import { DeviceRecord } from '/@/api/device/model/deviceModel'; | |
| 144 | 143 | import { |
| 145 | 144 | deleteModel, |
| 146 | 145 | deleteModelCategory, |
| ... | ... | @@ -158,12 +157,13 @@ |
| 158 | 157 | import { FunctionTypeEnum } from '/@/enums/objectModelEnum'; |
| 159 | 158 | import SelectImport from '../components/SelectImport.vue'; |
| 160 | 159 | import { DataActionModeEnum } from '/@/enums/toolEnum'; |
| 160 | + import { DeviceProfileDetail } from '/@/api/device/model/deviceConfigModel'; | |
| 161 | 161 | |
| 162 | 162 | const { isPlatformAdmin, isSysadmin } = useRole(); |
| 163 | 163 | defineEmits(['register']); |
| 164 | 164 | |
| 165 | 165 | const props = defineProps<{ |
| 166 | - record: DeviceRecord; | |
| 166 | + record: DeviceProfileDetail; | |
| 167 | 167 | }>(); |
| 168 | 168 | |
| 169 | 169 | const { createMessage } = useMessage(); | ... | ... |
| ... | ... | @@ -49,7 +49,7 @@ |
| 49 | 49 | </div> |
| 50 | 50 | </template> |
| 51 | 51 | <script lang="ts" setup> |
| 52 | - import { reactive, ref, onUnmounted, nextTick } from 'vue'; | |
| 52 | + import { reactive, ref, onUnmounted, nextTick, unref } from 'vue'; | |
| 53 | 53 | import { BasicForm, useForm } from '/@/components/Form'; |
| 54 | 54 | import { step2Schemas } from '../device.profile.data'; |
| 55 | 55 | import { Button } from '/@/components/Button'; |
| ... | ... | @@ -183,6 +183,7 @@ |
| 183 | 183 | }, |
| 184 | 184 | }, |
| 185 | 185 | }); |
| 186 | + unref(tcpRef)?.setProtocolStatus(status); | |
| 186 | 187 | }; |
| 187 | 188 | const updateDisabled = async () => { |
| 188 | 189 | await nextTick(); | ... | ... |
| ... | ... | @@ -8,6 +8,7 @@ |
| 8 | 8 | <ObjectModelForm |
| 9 | 9 | ref="objectModelElRef" |
| 10 | 10 | :mode="openModalMode" |
| 11 | + :device-record="record" | |
| 11 | 12 | :transport-type="(record.transportType as TransportTypeEnum)" |
| 12 | 13 | /> |
| 13 | 14 | </BasicModal> |
| ... | ... | @@ -23,12 +24,12 @@ |
| 23 | 24 | createModelCategory, |
| 24 | 25 | updateModelCategory, |
| 25 | 26 | } from '/@/api/device/modelOfMatter'; |
| 26 | - import { DeviceRecord } from '/@/api/device/model/deviceModel'; | |
| 27 | 27 | import { useMessage } from '/@/hooks/web/useMessage'; |
| 28 | 28 | import { useRole } from '/@/hooks/business/useRole'; |
| 29 | 29 | import { ObjectModelForm } from '../../../components/ObjectModelForm'; |
| 30 | 30 | import { TransportTypeEnum } from '/@/enums/deviceEnum'; |
| 31 | 31 | import { DataActionModeEnum, DataActionModeNameEnum } from '/@/enums/toolEnum'; |
| 32 | + import { DeviceProfileDetail } from '/@/api/device/model/deviceConfigModel'; | |
| 32 | 33 | |
| 33 | 34 | const { isPlatformAdmin, isSysadmin } = useRole(); |
| 34 | 35 | |
| ... | ... | @@ -37,7 +38,7 @@ |
| 37 | 38 | const objectModelElRef = ref<InstanceType<typeof ObjectModelForm>>(); |
| 38 | 39 | |
| 39 | 40 | const props = defineProps<{ |
| 40 | - record: DeviceRecord; | |
| 41 | + record: DeviceProfileDetail; | |
| 41 | 42 | }>(); |
| 42 | 43 | |
| 43 | 44 | const blockContent = `属性一般是指设备的运行状态,如当前温度等;服务是指设备可被调用的方法,支持定义参数,如执行某项任务;事件则是指设备上报的通知,如告警,需要被及时处理。`; |
| ... | ... | @@ -55,13 +56,14 @@ |
| 55 | 56 | if (record) { |
| 56 | 57 | await nextTick(); |
| 57 | 58 | } |
| 58 | - unref(objectModelElRef)?.resetFieldsValue?.(); | |
| 59 | + | |
| 59 | 60 | const title = `${DataActionModeNameEnum[mode]}物模型`; |
| 60 | 61 | setModalProps({ |
| 61 | 62 | showOkBtn: mode !== DataActionModeEnum.READ, |
| 62 | 63 | title, |
| 63 | 64 | }); |
| 64 | 65 | |
| 66 | + unref(objectModelElRef)?.resetFieldsValue?.(); | |
| 65 | 67 | if (mode !== DataActionModeEnum.CREATE && record) { |
| 66 | 68 | currentActionModel.value = record; |
| 67 | 69 | unref(objectModelElRef)?.setFieldsValue(record || {}); | ... | ... |
| 1 | -<template> | |
| 2 | - <div> | |
| 3 | - <a-form | |
| 4 | - ref="formRef" | |
| 5 | - :model="scriptForm" | |
| 6 | - name="basic" | |
| 7 | - :label-col="{ span: 4 }" | |
| 8 | - :wrapper-col="{ span: 16 }" | |
| 9 | - autocomplete="off" | |
| 10 | - style="margin-left: 2.4rem" | |
| 11 | - > | |
| 12 | - <a-form-item | |
| 13 | - v-if="deviceTypeStr !== TypeEnum.SENSOR" | |
| 14 | - label="鉴权脚本" | |
| 15 | - name="authScriptId" | |
| 16 | - :rules="[{ required: true, message: '请选择鉴权脚本' }]" | |
| 17 | - > | |
| 18 | - <ScriptSelectItem | |
| 19 | - ref="scriptSelectItemAuthRef" | |
| 20 | - v-model:value="scriptForm.authScriptId" | |
| 21 | - :scriptType="ScriptTypeEnum.TRANSPORT_TCP_AUTH" | |
| 22 | - /> | |
| 23 | - </a-form-item> | |
| 24 | - <a-form-item | |
| 25 | - label="上行脚本" | |
| 26 | - name="upScriptId" | |
| 27 | - :rules="[{ required: true, message: '请选择上行脚本' }]" | |
| 28 | - > | |
| 29 | - <ScriptSelectItem | |
| 30 | - ref="scriptSelectItemUpRef" | |
| 31 | - v-model:value="scriptForm.upScriptId" | |
| 32 | - :scriptType="ScriptTypeEnum.TRANSPORT_TCP_UP" | |
| 33 | - /> | |
| 34 | - </a-form-item> | |
| 35 | - </a-form> | |
| 36 | - </div> | |
| 37 | -</template> | |
| 38 | -<script lang="ts" setup name="index"> | |
| 39 | - import { reactive, ref } from 'vue'; | |
| 40 | - import { useMessage } from '/@/hooks/web/useMessage'; | |
| 41 | - import { ScriptSelectItem } from './components'; | |
| 42 | - import { ScriptTypeEnum } from '/@/views/rule/script/TcpConversionScript/config'; | |
| 43 | - import { TypeEnum } from '/@/views/device/list/config/data'; | |
| 44 | - | |
| 45 | - const props = defineProps({ | |
| 46 | - deviceTypeStr: { type: String, default: '' }, | |
| 47 | - }); | |
| 48 | - | |
| 49 | - const scriptForm = reactive({ | |
| 50 | - authScriptId: '', | |
| 51 | - upScriptId: '', | |
| 52 | - }); | |
| 53 | - | |
| 54 | - const { createMessage } = useMessage(); | |
| 55 | - | |
| 56 | - const scriptSelectItemAuthRef = ref<InstanceType<typeof ScriptSelectItem>>(); | |
| 57 | - | |
| 58 | - const scriptSelectItemUpRef = ref<InstanceType<typeof ScriptSelectItem>>(); | |
| 59 | - | |
| 60 | - const getFormData = async () => { | |
| 61 | - scriptForm.authScriptId = scriptSelectItemAuthRef.value?.getValue(); | |
| 62 | - scriptForm.upScriptId = scriptSelectItemUpRef.value?.getValue(); | |
| 63 | - //业务 网关子设备没有鉴权脚本 | |
| 64 | - if (props.deviceTypeStr === TypeEnum.SENSOR) Reflect.deleteProperty(scriptForm, 'authScriptId'); | |
| 65 | - if (Object.values(scriptForm).some((item) => !item)) { | |
| 66 | - createMessage.error('请先选择对应脚本'); | |
| 67 | - throw new Error('请先选择对应脚本'); | |
| 68 | - } | |
| 69 | - return { | |
| 70 | - ...scriptForm, | |
| 71 | - type: 'TCP', | |
| 72 | - }; | |
| 73 | - }; | |
| 74 | - | |
| 75 | - const resetFormData = () => {}; | |
| 76 | - | |
| 77 | - const setFormData = (data) => { | |
| 78 | - scriptForm.authScriptId = data?.authScriptId; | |
| 79 | - scriptForm.upScriptId = data?.upScriptId; | |
| 80 | - scriptSelectItemAuthRef.value?.setValue(data?.authScriptId); | |
| 81 | - scriptSelectItemUpRef.value?.setValue(data?.upScriptId); | |
| 82 | - }; | |
| 83 | - defineExpose({ | |
| 84 | - getFormData, | |
| 85 | - resetFormData, | |
| 86 | - setFormData, | |
| 87 | - }); | |
| 88 | -</script> | |
| 89 | -<style lang="less" scoped></style> | |
| 1 | +<template> | |
| 2 | + <div> | |
| 3 | + <Form | |
| 4 | + ref="formRef" | |
| 5 | + :model="scriptForm" | |
| 6 | + name="basic" | |
| 7 | + :label-col="{ span: 4 }" | |
| 8 | + :wrapper-col="{ span: 16 }" | |
| 9 | + autocomplete="off" | |
| 10 | + style="margin-left: 2.4rem" | |
| 11 | + > | |
| 12 | + <Form.Item | |
| 13 | + label="类型" | |
| 14 | + :rules="[{ required: true, message: '请选择鉴权脚本' }]" | |
| 15 | + :wrapper-col="{ span: 10 }" | |
| 16 | + > | |
| 17 | + <Select | |
| 18 | + v-model:value="scriptForm.protocol" | |
| 19 | + :options="typeOptions" | |
| 20 | + :disabled="protocolStatus" | |
| 21 | + /> | |
| 22 | + </Form.Item> | |
| 23 | + <Form.Item | |
| 24 | + v-if=" | |
| 25 | + scriptForm.protocol === TCPProtocolTypeEnum.CUSTOM && deviceTypeStr !== TypeEnum.SENSOR | |
| 26 | + " | |
| 27 | + label="鉴权脚本" | |
| 28 | + name="authScriptId" | |
| 29 | + :rules="[{ required: true, message: '请选择鉴权脚本' }]" | |
| 30 | + > | |
| 31 | + <ScriptSelectItem | |
| 32 | + ref="scriptSelectItemAuthRef" | |
| 33 | + v-model:value="scriptForm.authScriptId" | |
| 34 | + :scriptType="ScriptTypeEnum.TRANSPORT_TCP_AUTH" | |
| 35 | + /> | |
| 36 | + </Form.Item> | |
| 37 | + <Form.Item | |
| 38 | + v-if="scriptForm.protocol === TCPProtocolTypeEnum.CUSTOM" | |
| 39 | + label="上行脚本" | |
| 40 | + name="upScriptId" | |
| 41 | + :rules="[{ required: true, message: '请选择上行脚本' }]" | |
| 42 | + > | |
| 43 | + <ScriptSelectItem | |
| 44 | + ref="scriptSelectItemUpRef" | |
| 45 | + v-model:value="scriptForm.upScriptId" | |
| 46 | + :scriptType="ScriptTypeEnum.TRANSPORT_TCP_UP" | |
| 47 | + /> | |
| 48 | + </Form.Item> | |
| 49 | + </Form> | |
| 50 | + </div> | |
| 51 | +</template> | |
| 52 | +<script lang="ts" setup name="index"> | |
| 53 | + import { reactive, ref } from 'vue'; | |
| 54 | + import { Form, Select } from 'ant-design-vue'; | |
| 55 | + import { useMessage } from '/@/hooks/web/useMessage'; | |
| 56 | + import { ScriptSelectItem } from './components'; | |
| 57 | + import { ScriptTypeEnum } from '/@/views/rule/script/TcpConversionScript/config'; | |
| 58 | + import { TypeEnum } from '/@/views/device/list/config/data'; | |
| 59 | + import { TCPProtocolTypeEnum, TCPProtocolTypeNameEnum } from '/@/enums/deviceEnum'; | |
| 60 | + | |
| 61 | + const props = defineProps({ | |
| 62 | + deviceTypeStr: { type: String, default: '' }, | |
| 63 | + }); | |
| 64 | + | |
| 65 | + const typeOptions = Object.keys(TCPProtocolTypeEnum).map((key) => ({ | |
| 66 | + label: TCPProtocolTypeNameEnum[key], | |
| 67 | + value: TCPProtocolTypeEnum[key], | |
| 68 | + })); | |
| 69 | + | |
| 70 | + const scriptForm = reactive({ | |
| 71 | + authScriptId: '', | |
| 72 | + upScriptId: '', | |
| 73 | + protocol: TCPProtocolTypeEnum.MODBUS_RTU, | |
| 74 | + }); | |
| 75 | + | |
| 76 | + const { createMessage } = useMessage(); | |
| 77 | + | |
| 78 | + const scriptSelectItemAuthRef = ref<InstanceType<typeof ScriptSelectItem>>(); | |
| 79 | + | |
| 80 | + const scriptSelectItemUpRef = ref<InstanceType<typeof ScriptSelectItem>>(); | |
| 81 | + | |
| 82 | + const getFormData = async () => { | |
| 83 | + scriptForm.authScriptId = scriptSelectItemAuthRef.value?.getValue(); | |
| 84 | + scriptForm.upScriptId = scriptSelectItemUpRef.value?.getValue(); | |
| 85 | + //业务 网关子设备没有鉴权脚本 | |
| 86 | + if (scriptForm.protocol === TCPProtocolTypeEnum.CUSTOM) { | |
| 87 | + if (props.deviceTypeStr === TypeEnum.SENSOR) | |
| 88 | + Reflect.deleteProperty(scriptForm, 'authScriptId'); | |
| 89 | + if (Object.values(scriptForm).some((item) => !item)) { | |
| 90 | + createMessage.error('请先选择对应脚本'); | |
| 91 | + throw new Error('请先选择对应脚本'); | |
| 92 | + } | |
| 93 | + } | |
| 94 | + | |
| 95 | + return { | |
| 96 | + ...scriptForm, | |
| 97 | + type: 'TCP', | |
| 98 | + }; | |
| 99 | + }; | |
| 100 | + | |
| 101 | + const resetFormData = () => {}; | |
| 102 | + | |
| 103 | + const setFormData = (data) => { | |
| 104 | + scriptForm.protocol = data?.protocol; | |
| 105 | + scriptForm.authScriptId = data?.authScriptId; | |
| 106 | + scriptForm.upScriptId = data?.upScriptId; | |
| 107 | + scriptSelectItemAuthRef.value?.setValue(data?.authScriptId); | |
| 108 | + scriptSelectItemUpRef.value?.setValue(data?.upScriptId); | |
| 109 | + }; | |
| 110 | + | |
| 111 | + const protocolStatus = ref(false); | |
| 112 | + | |
| 113 | + const setProtocolStatus = (flag: boolean) => { | |
| 114 | + protocolStatus.value = flag; | |
| 115 | + }; | |
| 116 | + | |
| 117 | + defineExpose({ | |
| 118 | + getFormData, | |
| 119 | + resetFormData, | |
| 120 | + setFormData, | |
| 121 | + setProtocolStatus, | |
| 122 | + }); | |
| 123 | +</script> | |
| 124 | +<style lang="less" scoped></style> | ... | ... |
| ... | ... | @@ -5,7 +5,7 @@ import { |
| 5 | 5 | TriggerEntityTypeEnum, |
| 6 | 6 | TriggerEntityTypeNameEnum, |
| 7 | 7 | } from '/@/enums/linkedgeEnum'; |
| 8 | -import { CommandTypeEnum, CommandTypeNameEnum } from '/@/enums/deviceEnum'; | |
| 8 | +import { CommandTypeEnum, CommandTypeNameEnum, TCPProtocolTypeEnum } from '/@/enums/deviceEnum'; | |
| 9 | 9 | import AlarmProfileSelect from './AlarmProfileSelect.vue'; |
| 10 | 10 | import { |
| 11 | 11 | byOrganizationIdGetMasterDevice, |
| ... | ... | @@ -50,6 +50,8 @@ export enum FormFieldsEnum { |
| 50 | 50 | TRANSPORT_TYPE = 'transportType', |
| 51 | 51 | ENABLE_CLEAR_RULE = 'enableClearRule', |
| 52 | 52 | CLEAR_RULE = 'clearRule', |
| 53 | + | |
| 54 | + IS_TCP_MODBUS_PRODUCT = 'isTCPModbusProduct', | |
| 53 | 55 | } |
| 54 | 56 | |
| 55 | 57 | export enum FormFieldsNameEnum { |
| ... | ... | @@ -199,6 +201,12 @@ export const getFormSchemas = (hasAlarmNotify: Ref<boolean>): FormSchema[] => { |
| 199 | 201 | ifShow: false, |
| 200 | 202 | }, |
| 201 | 203 | { |
| 204 | + field: FormFieldsEnum.IS_TCP_MODBUS_PRODUCT, | |
| 205 | + label: 'TCPModbus产品', | |
| 206 | + component: 'Input', | |
| 207 | + ifShow: false, | |
| 208 | + }, | |
| 209 | + { | |
| 202 | 210 | field: FormFieldsEnum.DEVICE_PROFILE_ID, |
| 203 | 211 | label: '', |
| 204 | 212 | component: 'ApiSelect', |
| ... | ... | @@ -216,22 +224,33 @@ export const getFormSchemas = (hasAlarmNotify: Ref<boolean>): FormSchema[] => { |
| 216 | 224 | placeholder: `请选择${FormFieldsNameEnum.DEVICE_PROFILE_ID}`, |
| 217 | 225 | ...createPickerSearch(), |
| 218 | 226 | onChange(_value: string, option: DeviceProfileModel) { |
| 227 | + const transportType = option?.transportType; | |
| 219 | 228 | setFieldsValue({ |
| 220 | 229 | [FormFieldsEnum.ENTITY_ID]: [], |
| 221 | - [FormFieldsEnum.TRANSPORT_TYPE]: option?.transportType, | |
| 230 | + [FormFieldsEnum.TRANSPORT_TYPE]: transportType, | |
| 222 | 231 | [FormFieldsEnum.SERVICE_ID]: null, |
| 232 | + [FormFieldsEnum.COMMAND_TYPE]: CommandTypeEnum.CUSTOM, | |
| 223 | 233 | [FormFieldsEnum.CALL_SERVICE]: null, |
| 224 | 234 | [FormFieldsEnum.CALL_SERVICE_IDENTIFIER]: null, |
| 225 | 235 | [FormFieldsEnum.SERVICE_COMMAND]: null, |
| 236 | + [FormFieldsEnum.IS_TCP_MODBUS_PRODUCT]: | |
| 237 | + transportType === TransportTypeEnum.TCP && | |
| 238 | + option?.profileData?.transportConfiguration?.protocol === | |
| 239 | + TCPProtocolTypeEnum.MODBUS_RTU, | |
| 226 | 240 | }); |
| 227 | 241 | }, |
| 228 | 242 | onOptionsChange(options: (DeviceProfileModel & Record<'label' | 'value', string>)[]) { |
| 229 | 243 | const deviceProfileId = formModel[FormFieldsEnum.DEVICE_PROFILE_ID]; |
| 230 | 244 | const res = options.find((item) => item.value === deviceProfileId); |
| 231 | - res && | |
| 232 | - setFieldsValue({ | |
| 233 | - [FormFieldsEnum.TRANSPORT_TYPE]: res.transportType, | |
| 234 | - }); | |
| 245 | + if (!res) return; | |
| 246 | + const transportType = res.transportType; | |
| 247 | + setFieldsValue({ | |
| 248 | + [FormFieldsEnum.TRANSPORT_TYPE]: transportType, | |
| 249 | + [FormFieldsEnum.IS_TCP_MODBUS_PRODUCT]: | |
| 250 | + transportType === TransportTypeEnum.TCP && | |
| 251 | + res?.profileData?.transportConfiguration?.protocol === | |
| 252 | + TCPProtocolTypeEnum.MODBUS_RTU, | |
| 253 | + }); | |
| 235 | 254 | }, |
| 236 | 255 | }; |
| 237 | 256 | }, |
| ... | ... | @@ -295,12 +314,23 @@ export const getFormSchemas = (hasAlarmNotify: Ref<boolean>): FormSchema[] => { |
| 295 | 314 | ], |
| 296 | 315 | defaultValue: CommandTypeEnum.CUSTOM, |
| 297 | 316 | ifShow: ({ model }) => model[FormFieldsEnum.OUT_TARGET] === ExecutionActionEnum.DEVICE_OUT, |
| 298 | - componentProps: ({ formActionType }) => { | |
| 317 | + componentProps: ({ formModel, formActionType }) => { | |
| 318 | + const getOptions = () => { | |
| 319 | + const options = [{ label: CommandTypeNameEnum.CUSTOM, value: CommandTypeEnum.CUSTOM }]; | |
| 320 | + | |
| 321 | + const isTCPModbusProduct = formModel[FormFieldsEnum.IS_TCP_MODBUS_PRODUCT]; | |
| 322 | + const isTCP = formModel[FormFieldsEnum.TRANSPORT_TYPE] === TransportTypeEnum.TCP; | |
| 323 | + const deviceType = formModel[FormFieldsEnum.DEVICE_TYPE]; | |
| 324 | + | |
| 325 | + // TCPModbus设备 TCP自定义网关子设备 | |
| 326 | + if (isTCPModbusProduct || (isTCP && deviceType === DeviceTypeEnum.SENSOR)) return options; | |
| 327 | + | |
| 328 | + options.push({ label: CommandTypeNameEnum.SERVICE, value: CommandTypeEnum.SERVICE }); | |
| 329 | + | |
| 330 | + return options; | |
| 331 | + }; | |
| 299 | 332 | return { |
| 300 | - options: [ | |
| 301 | - { label: CommandTypeNameEnum.CUSTOM, value: CommandTypeEnum.CUSTOM }, | |
| 302 | - { label: CommandTypeNameEnum.SERVICE, value: CommandTypeEnum.SERVICE }, | |
| 303 | - ], | |
| 333 | + options: getOptions(), | |
| 304 | 334 | placeholder: `请选择${FormFieldsNameEnum.COMMAND_TYPE}`, |
| 305 | 335 | onChange() { |
| 306 | 336 | const { setFieldsValue } = formActionType; | ... | ... |
| ... | ... | @@ -144,6 +144,7 @@ |
| 144 | 144 | [oldCategory, category].some((item) => item === PackagesCategoryEnum.CONTROL) && |
| 145 | 145 | oldCategory !== category && |
| 146 | 146 | firstEnter; |
| 147 | + | |
| 147 | 148 | dataSource.value = unref(dataSource).map((item) => ({ |
| 148 | 149 | ...item, |
| 149 | 150 | ...(needReset ? { attribute: null } : {}), | ... | ... |
| ... | ... | @@ -9,8 +9,8 @@ |
| 9 | 9 | import { useDataFetch } from '../../../hook/socket/useSocket'; |
| 10 | 10 | import { useModal } from '/@/components/Modal'; |
| 11 | 11 | import PasswordModal from '../component/PasswordModal.vue'; |
| 12 | - import { useCommandDelivery } from '../../../hook/useCommandDelivery'; | |
| 13 | 12 | import { useDeviceProfileQueryContext } from '/@/views/visual/palette/hooks/useDeviceProfileQueryContext'; |
| 13 | + import { useControlComand } from '../../../hook/useControlCommand'; | |
| 14 | 14 | |
| 15 | 15 | const props = defineProps<{ |
| 16 | 16 | config: ComponentPropsConfigType<typeof option>; |
| ... | ... | @@ -38,12 +38,12 @@ |
| 38 | 38 | }; |
| 39 | 39 | }); |
| 40 | 40 | |
| 41 | - const { doCommandDeliver, loading } = useCommandDelivery(); | |
| 41 | + const { loading, doControlSendCommand } = useControlComand(); | |
| 42 | 42 | |
| 43 | 43 | const handleSendCommand = async () => { |
| 44 | 44 | if (props.config.option.mode === ComponentMode.SELECT_PREVIEW) return; |
| 45 | 45 | const { option } = props.config || {}; |
| 46 | - const result = await doCommandDeliver(option, Number(!unref(currentValue))); | |
| 46 | + const result = await doControlSendCommand(option, Number(!unref(currentValue))); | |
| 47 | 47 | currentValue.value = result ? !unref(currentValue) : unref(currentValue); |
| 48 | 48 | }; |
| 49 | 49 | ... | ... |
| ... | ... | @@ -11,8 +11,8 @@ |
| 11 | 11 | import { useDataFetch } from '../../../hook/socket/useSocket'; |
| 12 | 12 | import { useModal } from '/@/components/Modal'; |
| 13 | 13 | import PasswordModal from '../component/PasswordModal.vue'; |
| 14 | - import { useCommandDelivery } from '../../../hook/useCommandDelivery'; | |
| 15 | 14 | import { useDeviceProfileQueryContext } from '/@/views/visual/palette/hooks/useDeviceProfileQueryContext'; |
| 15 | + import { useControlComand } from '../../../hook/useControlCommand'; | |
| 16 | 16 | |
| 17 | 17 | const props = defineProps<{ |
| 18 | 18 | config: ComponentPropsConfigType<typeof option>; |
| ... | ... | @@ -44,7 +44,7 @@ |
| 44 | 44 | }; |
| 45 | 45 | }); |
| 46 | 46 | |
| 47 | - const { loading, doCommandDeliver } = useCommandDelivery(); | |
| 47 | + const { loading, doControlSendCommand } = useControlComand(); | |
| 48 | 48 | |
| 49 | 49 | const handleChange = async () => { |
| 50 | 50 | if (unref(getDesign).password) { |
| ... | ... | @@ -58,7 +58,7 @@ |
| 58 | 58 | const handleSendCommand = async () => { |
| 59 | 59 | if (props.config.option.mode === ComponentMode.SELECT_PREVIEW) return; |
| 60 | 60 | const { option } = props.config || {}; |
| 61 | - const result = await doCommandDeliver(option, Number(unref(checked))); | |
| 61 | + const result = await doControlSendCommand(option, Number(unref(checked))); | |
| 62 | 62 | if (!result) checked.value = !unref(checked); |
| 63 | 63 | }; |
| 64 | 64 | ... | ... |
| ... | ... | @@ -9,8 +9,8 @@ |
| 9 | 9 | import { useDataFetch } from '../../../hook/socket/useSocket'; |
| 10 | 10 | import PasswordModal from '../component/PasswordModal.vue'; |
| 11 | 11 | import { useModal } from '/@/components/Modal'; |
| 12 | - import { useCommandDelivery } from '../../../hook/useCommandDelivery'; | |
| 13 | 12 | import { useDeviceProfileQueryContext } from '/@/views/visual/palette/hooks/useDeviceProfileQueryContext'; |
| 13 | + import { useControlComand } from '../../../hook/useControlCommand'; | |
| 14 | 14 | |
| 15 | 15 | const props = defineProps<{ |
| 16 | 16 | config: ComponentPropsConfigType<typeof option>; |
| ... | ... | @@ -37,12 +37,13 @@ |
| 37 | 37 | fontSize: fontSize || persetFontSize || 14, |
| 38 | 38 | }; |
| 39 | 39 | }); |
| 40 | - const { doCommandDeliver, loading } = useCommandDelivery(); | |
| 40 | + | |
| 41 | + const { loading, doControlSendCommand } = useControlComand(); | |
| 41 | 42 | |
| 42 | 43 | const handleSendCommand = async () => { |
| 43 | 44 | if (props.config.option.mode === ComponentMode.SELECT_PREVIEW) return; |
| 44 | 45 | const { option } = props.config || {}; |
| 45 | - const result = await doCommandDeliver(option, Number(!unref(currentValue))); | |
| 46 | + const result = await doControlSendCommand(option, Number(!unref(currentValue))); | |
| 46 | 47 | currentValue.value = result ? !unref(currentValue) : unref(currentValue); |
| 47 | 48 | }; |
| 48 | 49 | const handleChange = async (event: Event) => { | ... | ... |
| ... | ... | @@ -10,8 +10,8 @@ |
| 10 | 10 | import { useDataFetch } from '../../../hook/socket/useSocket'; |
| 11 | 11 | import PasswordModal from '../component/PasswordModal.vue'; |
| 12 | 12 | import { useModal } from '/@/components/Modal'; |
| 13 | - import { useCommandDelivery } from '../../../hook/useCommandDelivery'; | |
| 14 | 13 | import { useDeviceProfileQueryContext } from '/@/views/visual/palette/hooks/useDeviceProfileQueryContext'; |
| 14 | + import { useControlComand } from '../../../hook/useControlCommand'; | |
| 15 | 15 | |
| 16 | 16 | const props = defineProps<{ |
| 17 | 17 | config: ComponentPropsConfigType<typeof option>; |
| ... | ... | @@ -67,7 +67,7 @@ |
| 67 | 67 | sendValue.value = value; |
| 68 | 68 | }; |
| 69 | 69 | |
| 70 | - const { loading, doCommandDeliver } = useCommandDelivery(); | |
| 70 | + const { loading, doControlSendCommand } = useControlComand(); | |
| 71 | 71 | |
| 72 | 72 | const handleAfterChange = () => { |
| 73 | 73 | if (unref(getDesign).password) { |
| ... | ... | @@ -82,7 +82,7 @@ |
| 82 | 82 | if (props.config.option.mode === ComponentMode.SELECT_PREVIEW) return; |
| 83 | 83 | const value = unref(sendValue); |
| 84 | 84 | const { option } = props.config || {}; |
| 85 | - const result = await doCommandDeliver(option, value); | |
| 85 | + const result = await doControlSendCommand(option, value); | |
| 86 | 86 | unref(sliderElRef)?.blur(); |
| 87 | 87 | sliderValue.value = result ? value : unref(sliderValue); |
| 88 | 88 | }; | ... | ... |
| ... | ... | @@ -13,11 +13,11 @@ |
| 13 | 13 | import { useReceiveValue } from '../../../hook/useReceiveValue'; |
| 14 | 14 | import { useModal } from '/@/components/Modal'; |
| 15 | 15 | import PasswordModal from '../component/PasswordModal.vue'; |
| 16 | + import { useDeviceProfileQueryContext } from '/@/views/visual/palette/hooks/useDeviceProfileQueryContext'; | |
| 16 | 17 | import { |
| 17 | 18 | DoCommandDeliverDataSourceType, |
| 18 | - useCommandDelivery, | |
| 19 | - } from '../../../hook/useCommandDelivery'; | |
| 20 | - import { useDeviceProfileQueryContext } from '/@/views/visual/palette/hooks/useDeviceProfileQueryContext'; | |
| 19 | + useControlComand, | |
| 20 | + } from '../../../hook/useControlCommand'; | |
| 21 | 21 | |
| 22 | 22 | const props = defineProps<{ |
| 23 | 23 | config: ComponentPropsConfigType<typeof option>; |
| ... | ... | @@ -96,10 +96,10 @@ |
| 96 | 96 | props.config.option.dataSource ? unref(getDesign).dataSource : DEFAULT_VALUE |
| 97 | 97 | ); |
| 98 | 98 | |
| 99 | - const { loading, doCommandDeliver } = useCommandDelivery(); | |
| 99 | + const { loading, doControlSendCommand } = useControlComand(); | |
| 100 | 100 | const handleSendCommand = async (modalData: SwitchItemType) => { |
| 101 | 101 | if (props.config.option.mode === ComponentMode.SELECT_PREVIEW) return; |
| 102 | - await doCommandDeliver( | |
| 102 | + await doControlSendCommand( | |
| 103 | 103 | toRaw(unref(modalData)) as DoCommandDeliverDataSourceType, |
| 104 | 104 | Number(unref(modalData).checked) |
| 105 | 105 | ); | ... | ... |
| ... | ... | @@ -9,12 +9,11 @@ import { findDictItemByCode } from '/@/api/system/dict'; |
| 9 | 9 | import { ApiCascader, FormSchema, useComponentRegister } from '/@/components/Form'; |
| 10 | 10 | import { OrgTreeSelect } from '/@/views/common/OrgTreeSelect'; |
| 11 | 11 | import { DataActionModeEnum } from '/@/enums/toolEnum'; |
| 12 | -import { TaskTypeEnum } from '/@/views/task/center/config'; | |
| 13 | 12 | import { createPickerSearch } from '/@/utils/pickerSearch'; |
| 14 | -import { DataTypeEnum } from '/@/enums/objectModelEnum'; | |
| 15 | -import { CommandTypeEnum, CommandTypeNameEnum } from '/@/enums/deviceEnum'; | |
| 13 | +import { DataTypeEnum, ObjectModelAccessModeEnum } from '/@/enums/objectModelEnum'; | |
| 14 | +import { CommandTypeEnum, CommandTypeNameEnum, TCPProtocolTypeEnum } from '/@/enums/deviceEnum'; | |
| 16 | 15 | import { TransportTypeEnum } from '/@/enums/deviceEnum'; |
| 17 | -import { DeviceTypeEnum } from '/@/api/device/model/deviceModel'; | |
| 16 | +import { DeviceProfileModel, DeviceTypeEnum } from '/@/api/device/model/deviceModel'; | |
| 18 | 17 | import { ControlComponentEnum } from '../components/Control'; |
| 19 | 18 | import { isNullOrUnDef } from '/@/utils/is'; |
| 20 | 19 | import { validateTCPCustomCommand } from '/@/components/Form/src/components/ThingsModelForm'; |
| ... | ... | @@ -35,20 +34,16 @@ export interface CommonDataSourceBindValueType extends Record<DataSourceField, s |
| 35 | 34 | } |
| 36 | 35 | |
| 37 | 36 | export enum DataSourceField { |
| 38 | - // IS_GATEWAY_DEVICE = 'gatewayDevice', | |
| 39 | 37 | DEVICE_TYPE = 'deviceType', |
| 40 | 38 | TRANSPORT_TYPE = 'transportType', |
| 41 | 39 | ORIGINATION_ID = 'organizationId', |
| 42 | 40 | DEVICE_ID = 'deviceId', |
| 43 | - // DEVICE_CODE = 'deviceCode', //设备地址码 | |
| 44 | 41 | DEVICE_PROFILE_ID = 'deviceProfileId', |
| 45 | 42 | ATTRIBUTE = 'attribute', |
| 46 | - // ATTRIBUTE_NAME = 'attributeName', | |
| 47 | 43 | ATTRIBUTE_RENAME = 'attributeRename', |
| 48 | 44 | DEVICE_NAME = 'deviceName', |
| 49 | 45 | DEVICE_RENAME = 'deviceRename', |
| 50 | 46 | |
| 51 | - CODE_TYPE = 'codeType', | |
| 52 | 47 | // COMMAND = 'command', |
| 53 | 48 | |
| 54 | 49 | OPEN_COMMAND = 'openCommand', |
| ... | ... | @@ -78,8 +73,7 @@ export enum DataSourceField { |
| 78 | 73 | */ |
| 79 | 74 | LATITUDE_IDENTIFIER = 'latitudeIdentifier', |
| 80 | 75 | |
| 81 | - // EXTENSION_DESC = 'extensionDesc', | |
| 82 | - // CALL_TYPE = 'callType', | |
| 76 | + IS_TCP_MODBUS_PROFILE = 'isTcpModbusProfile', | |
| 83 | 77 | } |
| 84 | 78 | |
| 85 | 79 | const isTcpProfile = (transportType: string) => transportType === TransportTypeEnum.TCP; |
| ... | ... | @@ -131,6 +125,7 @@ export const commonDataSourceSchemas = (): FormSchema[] => { |
| 131 | 125 | [DataSourceField.DEVICE_ID]: null, |
| 132 | 126 | [DataSourceField.ATTRIBUTE]: null, |
| 133 | 127 | [DataSourceField.TRANSPORT_TYPE]: null, |
| 128 | + [DataSourceField.COMMAND_TYPE]: null, | |
| 134 | 129 | }); |
| 135 | 130 | }, |
| 136 | 131 | getPopupContainer: () => document.body, |
| ... | ... | @@ -144,6 +139,12 @@ export const commonDataSourceSchemas = (): FormSchema[] => { |
| 144 | 139 | ifShow: false, |
| 145 | 140 | }, |
| 146 | 141 | { |
| 142 | + field: DataSourceField.IS_TCP_MODBUS_PROFILE, | |
| 143 | + component: 'Input', | |
| 144 | + label: 'TCP Modbus产品', | |
| 145 | + ifShow: false, | |
| 146 | + }, | |
| 147 | + { | |
| 147 | 148 | field: DataSourceField.DEVICE_PROFILE_ID, |
| 148 | 149 | component: 'ApiSelect', |
| 149 | 150 | label: '产品', |
| ... | ... | @@ -166,14 +167,35 @@ export const commonDataSourceSchemas = (): FormSchema[] => { |
| 166 | 167 | labelField: 'name', |
| 167 | 168 | valueField: 'id', |
| 168 | 169 | placeholder: '请选择产品', |
| 169 | - onChange: (_, option: Record<'transportType', string>) => { | |
| 170 | + onChange: (_, option: DeviceProfileModel) => { | |
| 171 | + const transportType = option?.[DataSourceField.TRANSPORT_TYPE]; | |
| 170 | 172 | setFieldsValue({ |
| 171 | 173 | [DataSourceField.DEVICE_ID]: null, |
| 172 | 174 | [DataSourceField.ATTRIBUTE]: null, |
| 173 | - [DataSourceField.TRANSPORT_TYPE]: option?.[DataSourceField.TRANSPORT_TYPE], | |
| 174 | 175 | [DataSourceField.COMMAND_TYPE]: null, |
| 176 | + [DataSourceField.TRANSPORT_TYPE]: transportType, | |
| 177 | + [DataSourceField.IS_TCP_MODBUS_PROFILE]: | |
| 178 | + transportType === TransportTypeEnum.TCP && | |
| 179 | + option?.profileData?.transportConfiguration?.protocol === | |
| 180 | + TCPProtocolTypeEnum.MODBUS_RTU, | |
| 175 | 181 | }); |
| 176 | 182 | }, |
| 183 | + onOptionsChange(options: (DeviceProfileModel & Record<'value' | 'label', string>)[]) { | |
| 184 | + const currentItem = options.find( | |
| 185 | + (item) => item.value === formModel[DataSourceField.DEVICE_PROFILE_ID] | |
| 186 | + ); | |
| 187 | + | |
| 188 | + if (currentItem) { | |
| 189 | + const transportType = currentItem?.[DataSourceField.TRANSPORT_TYPE]; | |
| 190 | + setFieldsValue({ | |
| 191 | + [DataSourceField.TRANSPORT_TYPE]: transportType, | |
| 192 | + [DataSourceField.IS_TCP_MODBUS_PROFILE]: | |
| 193 | + transportType === TransportTypeEnum.TCP && | |
| 194 | + currentItem?.profileData?.transportConfiguration?.protocol === | |
| 195 | + TCPProtocolTypeEnum.MODBUS_RTU, | |
| 196 | + }); | |
| 197 | + } | |
| 198 | + }, | |
| 177 | 199 | getPopupContainer: () => document.body, |
| 178 | 200 | }; |
| 179 | 201 | }, |
| ... | ... | @@ -236,13 +258,13 @@ export const commonDataSourceSchemas = (): FormSchema[] => { |
| 236 | 258 | deviceType: item.deviceType, |
| 237 | 259 | })); |
| 238 | 260 | |
| 239 | - if ( | |
| 240 | - unref(selectWidgetKeys).componentKey === | |
| 241 | - ControlComponentEnum.LATERAL_NUMERICAL_CONTROL && | |
| 242 | - isTcpProfile(formModel[DataSourceField.TRANSPORT_TYPE]) | |
| 243 | - ) { | |
| 244 | - return result.filter((item) => item.codeType === TaskTypeEnum.MODBUS_RTU); | |
| 245 | - } | |
| 261 | + // if ( | |
| 262 | + // unref(selectWidgetKeys).componentKey === | |
| 263 | + // ControlComponentEnum.LATERAL_NUMERICAL_CONTROL && | |
| 264 | + // isTcpProfile(formModel[DataSourceField.TRANSPORT_TYPE]) | |
| 265 | + // ) { | |
| 266 | + // return result.filter((item) => item.codeType === TaskTypeEnum.MODBUS_RTU); | |
| 267 | + // } | |
| 246 | 268 | |
| 247 | 269 | return result; |
| 248 | 270 | } |
| ... | ... | @@ -254,7 +276,6 @@ export const commonDataSourceSchemas = (): FormSchema[] => { |
| 254 | 276 | setFieldsValue({ |
| 255 | 277 | [DataSourceField.COMMAND_TYPE]: null, |
| 256 | 278 | [DataSourceField.DEVICE_NAME]: options?.label, |
| 257 | - [DataSourceField.CODE_TYPE]: options?.codeType, | |
| 258 | 279 | }); |
| 259 | 280 | }, |
| 260 | 281 | placeholder: '请选择设备', |
| ... | ... | @@ -263,26 +284,19 @@ export const commonDataSourceSchemas = (): FormSchema[] => { |
| 263 | 284 | }, |
| 264 | 285 | }, |
| 265 | 286 | { |
| 266 | - field: DataSourceField.CODE_TYPE, | |
| 267 | - label: '设备标识符类型', | |
| 268 | - component: 'Input', | |
| 269 | - ifShow: false, | |
| 270 | - }, | |
| 271 | - { | |
| 272 | 287 | field: DataSourceField.ATTRIBUTE, |
| 273 | 288 | component: 'ApiSelect', |
| 274 | 289 | label: '属性', |
| 275 | 290 | colProps: { span: 8 }, |
| 276 | 291 | rules: [{ required: true, message: '请选择属性' }], |
| 277 | 292 | ifShow: () => category !== CategoryEnum.MAP, |
| 278 | - componentProps({ formModel, formActionType }) { | |
| 279 | - const { setFieldsValue } = formActionType; | |
| 293 | + componentProps({ formModel }) { | |
| 280 | 294 | const deviceProfileId = formModel[DataSourceField.DEVICE_PROFILE_ID]; |
| 281 | 295 | return { |
| 282 | 296 | api: async () => { |
| 283 | 297 | try { |
| 284 | 298 | if (deviceProfileId) { |
| 285 | - const option = await getDeviceAttribute({ | |
| 299 | + let option = await getDeviceAttribute({ | |
| 286 | 300 | deviceProfileId, |
| 287 | 301 | dataType: |
| 288 | 302 | (isControlComponent(category!) && |
| ... | ... | @@ -292,21 +306,26 @@ export const commonDataSourceSchemas = (): FormSchema[] => { |
| 292 | 306 | ? DataTypeEnum.BOOL |
| 293 | 307 | : undefined, |
| 294 | 308 | }); |
| 309 | + // 过滤只读属性 | |
| 310 | + option = option.filter( | |
| 311 | + (item) => item.accessMode !== ObjectModelAccessModeEnum.READ | |
| 312 | + ); | |
| 295 | 313 | |
| 296 | 314 | // 选择控制组件4的时候只能选择属性且是(int double类型) |
| 297 | 315 | if ( |
| 298 | 316 | unref(selectWidgetKeys).componentKey === |
| 299 | 317 | ControlComponentEnum.LATERAL_NUMERICAL_CONTROL |
| 300 | 318 | ) { |
| 301 | - setFieldsValue({ | |
| 302 | - [DataSourceField.COMMAND_TYPE]: CommandTypeEnum.ATTRIBUTE.toString(), | |
| 303 | - }); | |
| 319 | + // setFieldsValue({ | |
| 320 | + // [DataSourceField.COMMAND_TYPE]: CommandTypeEnum.ATTRIBUTE.toString(), | |
| 321 | + // }); | |
| 304 | 322 | return option.filter( |
| 305 | 323 | (item) => |
| 306 | 324 | item.detail.dataType.type === DataTypeEnum.NUMBER_INT || |
| 307 | 325 | item.detail.dataType.type == DataTypeEnum.NUMBER_DOUBLE |
| 308 | 326 | ); |
| 309 | 327 | } |
| 328 | + | |
| 310 | 329 | return option; |
| 311 | 330 | } |
| 312 | 331 | } catch (error) {} |
| ... | ... | @@ -412,18 +431,19 @@ export const commonDataSourceSchemas = (): FormSchema[] => { |
| 412 | 431 | isControlComponent(category!) && |
| 413 | 432 | unref(selectWidgetKeys).componentKey !== ControlComponentEnum.LATERAL_NUMERICAL_CONTROL && |
| 414 | 433 | isTcpProfile(model[DataSourceField.TRANSPORT_TYPE]) && |
| 415 | - model[DataSourceField.CODE_TYPE] === TaskTypeEnum.CUSTOM | |
| 434 | + !model[DataSourceField.IS_TCP_MODBUS_PROFILE] | |
| 416 | 435 | ); |
| 417 | 436 | }, |
| 418 | 437 | componentProps: ({ formActionType, formModel }) => { |
| 419 | 438 | const { setFieldsValue } = formActionType; |
| 420 | 439 | const deviceType = formModel[DataSourceField.DEVICE_TYPE]; |
| 440 | + const isTCPModbusProfile = formModel[DataSourceField.IS_TCP_MODBUS_PROFILE]; | |
| 421 | 441 | const options: Record<'label' | 'value', string | number>[] = [ |
| 422 | 442 | { label: CommandTypeNameEnum.CUSTOM, value: CommandTypeEnum.CUSTOM }, |
| 423 | 443 | ]; |
| 424 | 444 | |
| 425 | - // 网关子设备无服务 | |
| 426 | - if (deviceType !== DeviceTypeEnum.SENSOR) | |
| 445 | + // TCP网关子设备无服务 TCP Modbus类型设备无服务 | |
| 446 | + if (deviceType !== DeviceTypeEnum.SENSOR && !isTCPModbusProfile) | |
| 427 | 447 | options.push({ label: CommandTypeNameEnum.SERVICE, value: CommandTypeEnum.SERVICE }); |
| 428 | 448 | |
| 429 | 449 | return { |
| ... | ... | @@ -448,9 +468,8 @@ export const commonDataSourceSchemas = (): FormSchema[] => { |
| 448 | 468 | colProps: { span: 8 }, |
| 449 | 469 | rules: [{ required: true, message: '请选择开服务' }], |
| 450 | 470 | ifShow: ({ model }) => |
| 451 | - isControlComponent(category!) && | |
| 452 | - model[DataSourceField.COMMAND_TYPE] === CommandTypeEnum.SERVICE && | |
| 453 | - isTcpProfile(model[DataSourceField.TRANSPORT_TYPE]), | |
| 471 | + isTcpProfile(model[DataSourceField.TRANSPORT_TYPE]) && | |
| 472 | + model[DataSourceField.COMMAND_TYPE] === CommandTypeEnum.SERVICE, | |
| 454 | 473 | componentProps({ formModel }) { |
| 455 | 474 | const deviceProfileId = formModel[DataSourceField.DEVICE_PROFILE_ID]; |
| 456 | 475 | return { |
| ... | ... | @@ -472,9 +491,8 @@ export const commonDataSourceSchemas = (): FormSchema[] => { |
| 472 | 491 | colProps: { span: 8 }, |
| 473 | 492 | rules: [{ required: true, message: '请选择关服务' }], |
| 474 | 493 | ifShow: ({ model }) => |
| 475 | - isControlComponent(category!) && | |
| 476 | - model[DataSourceField.COMMAND_TYPE] === CommandTypeEnum.SERVICE && | |
| 477 | - isTcpProfile(model[DataSourceField.TRANSPORT_TYPE]), | |
| 494 | + isTcpProfile(model[DataSourceField.TRANSPORT_TYPE]) && | |
| 495 | + model[DataSourceField.COMMAND_TYPE] === CommandTypeEnum.SERVICE, | |
| 478 | 496 | componentProps({ formModel }) { |
| 479 | 497 | const deviceProfileId = formModel[DataSourceField.DEVICE_PROFILE_ID]; |
| 480 | 498 | return { |
| ... | ... | @@ -497,10 +515,8 @@ export const commonDataSourceSchemas = (): FormSchema[] => { |
| 497 | 515 | rules: [{ validator: validateTCPCustomCommand }], |
| 498 | 516 | // 是控制组件 && 自定义命令 && 传输协议为TCP |
| 499 | 517 | ifShow: ({ model }) => |
| 500 | - isControlComponent(category!) && | |
| 501 | - model[DataSourceField.COMMAND_TYPE] === CommandTypeEnum.CUSTOM && | |
| 502 | - model[DataSourceField.TRANSPORT_TYPE] && | |
| 503 | - isTcpProfile(model[DataSourceField.TRANSPORT_TYPE]), | |
| 518 | + isTcpProfile(model[DataSourceField.TRANSPORT_TYPE]) && | |
| 519 | + model[DataSourceField.COMMAND_TYPE] === CommandTypeEnum.CUSTOM, | |
| 504 | 520 | componentProps: { |
| 505 | 521 | placeholder: '请输入开下发命令', |
| 506 | 522 | }, |
| ... | ... | @@ -513,10 +529,8 @@ export const commonDataSourceSchemas = (): FormSchema[] => { |
| 513 | 529 | rules: [{ validator: validateTCPCustomCommand }], |
| 514 | 530 | // 是控制组件 && 自定义命令 && 传输协议为TCP |
| 515 | 531 | ifShow: ({ model }) => |
| 516 | - isControlComponent(category!) && | |
| 517 | - model[DataSourceField.COMMAND_TYPE] === CommandTypeEnum.CUSTOM && | |
| 518 | - model[DataSourceField.TRANSPORT_TYPE] && | |
| 519 | - isTcpProfile(model[DataSourceField.TRANSPORT_TYPE]), | |
| 532 | + isTcpProfile(model[DataSourceField.TRANSPORT_TYPE]) && | |
| 533 | + model[DataSourceField.COMMAND_TYPE] === CommandTypeEnum.CUSTOM, | |
| 520 | 534 | componentProps: { |
| 521 | 535 | placeholder: '请输入关下发命令', |
| 522 | 536 | }, | ... | ... |