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 | }, | ... | ... |