Commit b2bff1c5a02bdcd83240baf8c86094679b46587b
Merge branch 'main_dev' into 'main'
Main dev See merge request yunteng/thingskit-scada!224
Showing
71 changed files
with
2545 additions
and
1353 deletions
| 1 | -import type { DeviceActiveType, DeviceAttributeItemType, DeviceItemType, DeviceProfileItemType, OrganizationItemType, RpcCommandType, SendValue, ThingsModel, ThingsModelItemType } from './model' | |
| 1 | +import type { DeviceActiveType, DeviceAttributeItemType, DeviceItemType, DeviceProfileItemType, OrganizationItemType, ProductsDetailWithThingsModelType, RpcCommandType, SendValue, ThingsModel, ThingsModelItemType } from './model' | |
| 2 | 2 | import { CommandWayEnum } from '@/enums/commandEnum' |
| 3 | -import type { DeviceTypeEnum } from '@/enums/datasource' | |
| 3 | +import type { DeviceTypeEnum } from '@/enums/deviceEnum' | |
| 4 | +import { FunctionTypeEnum } from '@/enums/objectModelEnum' | |
| 4 | 5 | import { isShareMode } from '@/utils/env' |
| 5 | 6 | import { defHttp } from '@/utils/http' |
| 6 | 7 | |
| ... | ... | @@ -13,7 +14,7 @@ enum Api { |
| 13 | 14 | GET_THINGS_MODEL_SERVICES = '/things_model/get_services/', |
| 14 | 15 | |
| 15 | 16 | RPC_COMMAND = '/rpc/', |
| 16 | - GET_DEVICE_ACTIVE = 'plugins/telemetry/DEVICE/', | |
| 17 | + GET_DEVICE_ACTIVE = '/plugins/telemetry/DEVICE/', | |
| 17 | 18 | RPC_ONEWAY = '/rpc/oneway', |
| 18 | 19 | RPC_TWOWAY = '/rpc/twoway', |
| 19 | 20 | |
| ... | ... | @@ -21,6 +22,9 @@ enum Api { |
| 21 | 22 | GET_DEVICE_DETAIL = '/device/', // 获取设备详情 |
| 22 | 23 | |
| 23 | 24 | GET_LIST_BY_CONFIGURATION_ID = '/configuration/center/getListByConfigurationId', |
| 25 | + | |
| 26 | + GET_PRODUCTS_DETAIL_WITH_THINGS_MODEL = '/things_model/batch/get_tsl', | |
| 27 | + | |
| 24 | 28 | } |
| 25 | 29 | |
| 26 | 30 | export interface GenModbusCommandType { |
| ... | ... | @@ -30,6 +34,7 @@ export interface GenModbusCommandType { |
| 30 | 34 | registerAddress: number |
| 31 | 35 | registerNumber?: number |
| 32 | 36 | registerValues?: number[] |
| 37 | + hexByteOrderEnum?: string | |
| 33 | 38 | } |
| 34 | 39 | |
| 35 | 40 | export const getDeviceProfile = (deviceType?: DeviceTypeEnum) => { |
| ... | ... | @@ -125,6 +130,17 @@ export const getDeviceActiveTime = (entityId: string) => { |
| 125 | 130 | ) |
| 126 | 131 | } |
| 127 | 132 | |
| 133 | +export const getDeviceTelemetryValue = (params: { entityId: string; keys: string }) => { | |
| 134 | + return defHttp.get({ | |
| 135 | + url: `${Api.GET_DEVICE_ACTIVE}${params.entityId}/values/timeseries`, | |
| 136 | + params: { | |
| 137 | + keys: params.keys, | |
| 138 | + }, | |
| 139 | + }, { | |
| 140 | + joinPrefix: false, | |
| 141 | + }) | |
| 142 | +} | |
| 143 | + | |
| 128 | 144 | // 获取设备详情 |
| 129 | 145 | export const getDeviceInfo = (deviceId: string) => { |
| 130 | 146 | return defHttp.get<DeviceItemType>( |
| ... | ... | @@ -139,3 +155,13 @@ export const getListByConfigurationId = (configurationId: string) => { |
| 139 | 155 | url: `${Api.GET_LIST_BY_CONFIGURATION_ID}?configurationId=${configurationId}`, |
| 140 | 156 | }) |
| 141 | 157 | } |
| 158 | + | |
| 159 | +export const getProductsDetailWithThingsModel = ({ deviceProfileIds, functionTypeEnum }: { deviceProfileIds: string[]; functionTypeEnum?: FunctionTypeEnum | 'all' }) => { | |
| 160 | + return defHttp.post<ProductsDetailWithThingsModelType[]>({ | |
| 161 | + url: Api.GET_PRODUCTS_DETAIL_WITH_THINGS_MODEL, | |
| 162 | + data: { | |
| 163 | + deviceProfileIds, | |
| 164 | + functionTypeEnum: functionTypeEnum || FunctionTypeEnum.PROPERTIES, | |
| 165 | + }, | |
| 166 | + }) | |
| 167 | +} | ... | ... |
| 1 | -import type { DataTypeEnum, FunctionType, TransportTypeEnum } from '@/enums/datasource' | |
| 1 | +import type { CommandCallWayEnum } from '@/enums/commandEnum' | |
| 2 | +import type { TCPProtocolTypeEnum, TransportTypeEnum } from '@/enums/deviceEnum' | |
| 3 | +import type { DataTypeEnum, ExtendDescOperationTypeEnum, FunctionTypeEnum, OriginalDataTypeEnum } from '@/enums/objectModelEnum' | |
| 2 | 4 | |
| 3 | 5 | export interface DeviceProfileItemType { |
| 4 | 6 | id: string |
| ... | ... | @@ -27,6 +29,7 @@ export interface ProfileData { |
| 27 | 29 | interface AdditionalInfo { |
| 28 | 30 | cmdType: string |
| 29 | 31 | } |
| 32 | + | |
| 30 | 33 | export interface SendValue { |
| 31 | 34 | additionalInfo: AdditionalInfo |
| 32 | 35 | method: string |
| ... | ... | @@ -40,6 +43,7 @@ export interface Configuration { |
| 40 | 43 | |
| 41 | 44 | export interface TransportConfiguration { |
| 42 | 45 | type: string |
| 46 | + protocol: TCPProtocolTypeEnum | |
| 43 | 47 | } |
| 44 | 48 | |
| 45 | 49 | export interface ProvisionConfiguration { |
| ... | ... | @@ -57,7 +61,7 @@ export interface StructJSON { |
| 57 | 61 | } |
| 58 | 62 | |
| 59 | 63 | export interface ThingsModel { |
| 60 | - functionType: FunctionType | |
| 64 | + functionType: FunctionTypeEnum | |
| 61 | 65 | functionName: string |
| 62 | 66 | identifier: string |
| 63 | 67 | callType: any |
| ... | ... | @@ -71,7 +75,7 @@ export interface ThingsModel { |
| 71 | 75 | } |
| 72 | 76 | |
| 73 | 77 | export interface FunctionJson { |
| 74 | - dataType: DataType | DataType[] | |
| 78 | + dataType: DataType | |
| 75 | 79 | inputData?: StructJSON[] |
| 76 | 80 | outputData?: StructJSON[] |
| 77 | 81 | serviceCommand?: string |
| ... | ... | @@ -115,6 +119,10 @@ export interface DeviceItemType { |
| 115 | 119 | deviceType: string |
| 116 | 120 | alarmStatus: number |
| 117 | 121 | enable: boolean |
| 122 | + deviceProfile: { | |
| 123 | + transportType: TransportTypeEnum | |
| 124 | + profileData: ProfileData | |
| 125 | + } | |
| 118 | 126 | |
| 119 | 127 | transportType: TransportTypeEnum |
| 120 | 128 | } |
| ... | ... | @@ -134,10 +142,14 @@ export interface ThingsModelItemType { |
| 134 | 142 | } |
| 135 | 143 | |
| 136 | 144 | export interface ExtensionDesc { |
| 137 | - zoomFactor?: number | |
| 138 | - actionType?: string | |
| 139 | - dataType: string | |
| 140 | - registerAddress: number | |
| 145 | + writeOnly?: boolean | |
| 146 | + bitMask?: number | |
| 147 | + operationType: ExtendDescOperationTypeEnum | |
| 148 | + originalDataType: OriginalDataTypeEnum | |
| 149 | + registerAddress: string | |
| 150 | + scaling?: number | |
| 151 | + valueRange?: Record<'min' | 'max', number> | |
| 152 | + registerCount?: number | |
| 141 | 153 | } |
| 142 | 154 | |
| 143 | 155 | export interface Detail { |
| ... | ... | @@ -145,7 +157,7 @@ export interface Detail { |
| 145 | 157 | } |
| 146 | 158 | |
| 147 | 159 | export interface DataType { |
| 148 | - type: string | |
| 160 | + type: DataTypeEnum | |
| 149 | 161 | specs: Specs | StructJSON[] |
| 150 | 162 | specsList: Specs[] |
| 151 | 163 | } |
| ... | ... | @@ -160,8 +172,9 @@ export interface Specs { |
| 160 | 172 | min: string |
| 161 | 173 | max: string |
| 162 | 174 | name?: string |
| 163 | - value?: string | |
| 175 | + value?: any | |
| 164 | 176 | dataType?: DataTypeEnum |
| 177 | + step?: number | |
| 165 | 178 | } |
| 166 | 179 | |
| 167 | 180 | export interface ValueRange { |
| ... | ... | @@ -193,3 +206,25 @@ export interface DeviceActiveType { |
| 193 | 206 | lastUpdateTs: number |
| 194 | 207 | value: boolean |
| 195 | 208 | } |
| 209 | + | |
| 210 | +export interface ProductsDetailWithThingsModelType { | |
| 211 | + id: string | |
| 212 | + name: string | |
| 213 | + transportType: string | |
| 214 | + deviceType: string | |
| 215 | + tsl: Tsl[] | |
| 216 | +} | |
| 217 | + | |
| 218 | +export interface Tsl { | |
| 219 | + functionName: string | |
| 220 | + identifier: string | |
| 221 | + accessMode: string | |
| 222 | + functionType: FunctionTypeEnum | |
| 223 | + callType: CommandCallWayEnum | |
| 224 | + specs: { | |
| 225 | + dataType: DataType | |
| 226 | + } | |
| 227 | + extensionDesc?: ExtensionDesc | |
| 228 | + inputData?: StructJSON[] | |
| 229 | + outputData?: StructJSON[] | |
| 230 | +} | ... | ... |
| 1 | 1 | import type { ThingsModelItemType } from '@/api/device/model' |
| 2 | 2 | import type { ImageSelectorDataType } from '@/core/Library/components/ImageSelector' |
| 3 | -import type { CommandWayEnum } from '@/enums/commandEnum' | |
| 4 | -import type { ActRangListItemTypeEnum, ActTypeEnum, AggregateTypeEnum, CommandDeliveryWayEnum, DeviceTypeEnum, EventActionTypeEnum, EventTypeEnum, SocketSubscriberEnum, TransportTypeEnum } from '@/enums/datasource' | |
| 3 | +import type { CommandDeliveryWayEnum, CommandWayEnum } from '@/enums/commandEnum' | |
| 4 | +import type { ActRangListItemTypeEnum, ActTypeEnum, AggregateTypeEnum, EventActionTypeEnum, EventTypeEnum, SocketSubscriberEnum } from '@/enums/datasource' | |
| 5 | +import type { DeviceTypeEnum } from '@/enums/deviceEnum' | |
| 5 | 6 | |
| 6 | 7 | export enum DeleteNodeDataTypeEnum { |
| 7 | 8 | DATASOURCE = 'DATASOURCE', |
| ... | ... | @@ -67,6 +68,8 @@ export interface VideoOptionType { |
| 67 | 68 | orgId: string |
| 68 | 69 | videoComponentFlag: boolean |
| 69 | 70 | videoUrl: string |
| 71 | + channelId?: string | |
| 72 | + deviceId?: string | |
| 70 | 73 | } |
| 71 | 74 | |
| 72 | 75 | export interface AlarmListOptionType { |
| ... | ... | @@ -92,19 +95,15 @@ export interface NodeDataDataSourceJsonType { |
| 92 | 95 | deviceId: string |
| 93 | 96 | deviceProfileId: string |
| 94 | 97 | deviceProfileTemplateId?: string |
| 95 | - attr: string | |
| 96 | - attrInfo: ThingsModelItemType | |
| 97 | - | |
| 98 | - deviceType?: DeviceTypeEnum | |
| 99 | - transportType?: TransportTypeEnum | |
| 98 | + attr: string | string[] | |
| 99 | + deviceName?: string | |
| 100 | 100 | |
| 101 | 101 | enable: boolean |
| 102 | 102 | chartOption?: ChartOptionType |
| 103 | 103 | videoOption?: VideoOptionType |
| 104 | 104 | alarmListOption?: AlarmListOptionType |
| 105 | - deviceInfo?: DeviceInfoType | |
| 106 | - circularFlowMeterOption?: FlowMeterColorItemType[] // 圆形水球图数据暂定any | |
| 107 | - rectFlowMeterOption?: FlowMeterColorItemType[] // 方形水球图颜色配置数据暂定any | |
| 105 | + circularFlowMeterOption?: FlowMeterColorItemType[] | |
| 106 | + rectFlowMeterOption?: FlowMeterColorItemType[] | |
| 108 | 107 | |
| 109 | 108 | } |
| 110 | 109 | |
| ... | ... | @@ -128,17 +127,12 @@ export interface DoubleClickEventDataType { |
| 128 | 127 | openPage?: string |
| 129 | 128 | enable: boolean |
| 130 | 129 | service?: string |
| 130 | + | |
| 131 | 131 | serviceCommand?: Recordable |
| 132 | 132 | way?: CommandWayEnum |
| 133 | 133 | customCommand?: string |
| 134 | - deviceInfo?: DeviceInfoType | |
| 135 | 134 | commandWay?: CommandDeliveryWayEnum |
| 136 | 135 | callType?: string |
| 137 | - operationPassword?: { | |
| 138 | - label: string | |
| 139 | - value: string | number | |
| 140 | - checked: boolean | |
| 141 | - } | |
| 142 | 136 | } |
| 143 | 137 | |
| 144 | 138 | export interface SingleClickEventDataType extends DoubleClickEventDataType { } |
| ... | ... | @@ -155,7 +149,7 @@ export interface NodeDataEventJsonType { |
| 155 | 149 | [EventTypeEnum.SINGLE]: SingleClickEventDataType |
| 156 | 150 | [EventTypeEnum.DOWN]: MouseDownEventDataType |
| 157 | 151 | [EventTypeEnum.UP]: MouseUpEventDataType |
| 158 | - [EventTypeEnum.OPERATION_PASSWORD]: OperationPasswordDataType | |
| 152 | + [EventTypeEnum.OPERATION_PASSWORD]?: OperationPasswordDataType | |
| 159 | 153 | } |
| 160 | 154 | |
| 161 | 155 | export interface RangeItemType { | ... | ... |
| 1 | +import type { VideoChannelPlayAddressType, VideoItemRecordType } from './model' | |
| 1 | 2 | import { defHttp } from '@/utils/http' |
| 2 | 3 | |
| 3 | 4 | enum Api { |
| 4 | 5 | GET_VIDEO_LIST = '/video', |
| 5 | 6 | STREAMING_PLAY_GET_URL = '/video/url', |
| 7 | + GET_VIDEO_ALL_LIST = '/video/list', | |
| 6 | 8 | RTSP_CLOSEFLV = '/rtsp.closeFlv', |
| 9 | + GET_VIDEO_CONTROL_START = '/video/control/start', | |
| 10 | + GET_VIDEO_CONTROL_STOP = '/video/control/stop', | |
| 7 | 11 | } |
| 8 | 12 | |
| 9 | 13 | // 获取视频组件--->频流 |
| 10 | 14 | export const getVideoList = (organizationId?: string) => { |
| 11 | - return defHttp.get({ | |
| 15 | + return defHttp.get<{ items: VideoItemRecordType[] }>({ | |
| 12 | 16 | url: Api.GET_VIDEO_LIST, |
| 13 | 17 | params: { |
| 14 | 18 | organizationId, |
| ... | ... | @@ -35,3 +39,21 @@ export const closeFlvPlay = (url: string, browserId: string) => { |
| 35 | 39 | }) |
| 36 | 40 | } |
| 37 | 41 | |
| 42 | +export const getVideoControlStart = ({ | |
| 43 | + deviceId, | |
| 44 | + channelId, | |
| 45 | +}: Record<'deviceId' | 'channelId', string>) => { | |
| 46 | + return defHttp.get<VideoChannelPlayAddressType>( | |
| 47 | + { | |
| 48 | + url: `${Api.GET_VIDEO_CONTROL_START}/${deviceId}/${channelId}`, | |
| 49 | + timeout: 30 * 1000, | |
| 50 | + }, | |
| 51 | + {}, | |
| 52 | + ) | |
| 53 | +} | |
| 54 | +export const getCameraList = (params: Record<'organizationId', string>) => { | |
| 55 | + return defHttp.get<{ data: VideoItemRecordType[] }>({ | |
| 56 | + url: Api.GET_VIDEO_ALL_LIST, | |
| 57 | + params, | |
| 58 | + }) | |
| 59 | +} | ... | ... |
| 1 | +export interface VideoItemRecordType { | |
| 2 | + id: string | |
| 3 | + creator: string | |
| 4 | + createTime: string | |
| 5 | + name: string | |
| 6 | + enabled: boolean | |
| 7 | + tenantId: string | |
| 8 | + sn: string | |
| 9 | + organizationId: string | |
| 10 | + organizationName: string | |
| 11 | + status: boolean | |
| 12 | + accessMode: number | |
| 13 | + playProtocol: number | |
| 14 | + params: Params | |
| 15 | + videoUrl: string | |
| 16 | +} | |
| 17 | + | |
| 18 | +export interface Params { | |
| 19 | + channelNo: string | |
| 20 | + deviceId: string | |
| 21 | +} | |
| 22 | + | |
| 23 | +export interface VideoChannelPlayAddressType { | |
| 24 | + code: number | |
| 25 | + message: string | |
| 26 | + data: Data | |
| 27 | +} | |
| 28 | + | |
| 29 | +export interface Data { | |
| 30 | + app: string | |
| 31 | + stream: string | |
| 32 | + ip: any | |
| 33 | + flv: string | |
| 34 | + https_flv: string | |
| 35 | + ws_flv: string | |
| 36 | + wss_flv: string | |
| 37 | + fmp4: string | |
| 38 | + https_fmp4: string | |
| 39 | + ws_fmp4: string | |
| 40 | + wss_fmp4: string | |
| 41 | + hls: string | |
| 42 | + https_hls: string | |
| 43 | + ws_hls: string | |
| 44 | + wss_hls: string | |
| 45 | + ts: string | |
| 46 | + https_ts: string | |
| 47 | + ws_ts: string | |
| 48 | + wss_ts: any | |
| 49 | + rtmp: string | |
| 50 | + rtmps: string | |
| 51 | + rtsp: string | |
| 52 | + rtsps: string | |
| 53 | + rtc: string | |
| 54 | + rtcs: string | |
| 55 | + mediaServerId: string | |
| 56 | + tracks: Track[] | |
| 57 | + startTime: any | |
| 58 | + endTime: any | |
| 59 | + progress: number | |
| 60 | +} | |
| 61 | + | |
| 62 | +export interface Track { | |
| 63 | + channels: number | |
| 64 | + codecId: number | |
| 65 | + codecIdName: any | |
| 66 | + codecType: number | |
| 67 | + ready: boolean | |
| 68 | + sampleBit: number | |
| 69 | + sampleRate: number | |
| 70 | + fps: number | |
| 71 | + height: number | |
| 72 | + width: number | |
| 73 | +} | ... | ... |
| ... | ... | @@ -2,7 +2,9 @@ import type { FormSchema } from '@/components/Form' |
| 2 | 2 | import { ComponentEnum } from '@/components/Form/src/enum' |
| 3 | 3 | import type { BasicColumn } from '@/components/Table' |
| 4 | 4 | import type { CommandWayEnum } from '@/enums/commandEnum' |
| 5 | -import { ActTypeEnum, type CodeTypeEnum, type ContentDataFieldsEnum, type DeviceTypeEnum } from '@/enums/datasource' | |
| 5 | +import { ActTypeEnum, type ContentDataFieldsEnum } from '@/enums/datasource' | |
| 6 | +import type { DeviceTypeEnum, TCPProtocolTypeEnum } from '@/enums/deviceEnum' | |
| 7 | + | |
| 6 | 8 | export enum TableColumnFieldEnum { |
| 7 | 9 | DEVICE_ID = 'deviceId', |
| 8 | 10 | WAY = 'way', |
| ... | ... | @@ -15,7 +17,7 @@ export interface TableRecordItemType { |
| 15 | 17 | [TableColumnFieldEnum.WAY]?: Nullable<CommandWayEnum> |
| 16 | 18 | [TableColumnFieldEnum.COMMAND]?: Nullable<string | Recordable> |
| 17 | 19 | [ContentDataFieldsEnum.DEVICE_TYPE]?: Nullable<DeviceTypeEnum> |
| 18 | - [ContentDataFieldsEnum.CODE_TYPE]?: Nullable<CodeTypeEnum> | |
| 20 | + [ContentDataFieldsEnum.CODE_TYPE]?: Nullable<TCPProtocolTypeEnum> | |
| 19 | 21 | } |
| 20 | 22 | |
| 21 | 23 | export const getFormSchemas = (): FormSchema[] => { | ... | ... |
| 1 | -import type { Specs, StructJSON } from '@/api/device/model' | |
| 2 | -import type { NodeDataDataSourceJsonType } from '@/api/node/model' | |
| 1 | +import type { DeviceItemType, Specs, StructJSON, Tsl } from '@/api/device/model' | |
| 2 | +import type { NodeDataDataSourceJsonType, OperationPasswordDataType, SingleClickEventDataType } from '@/api/node/model' | |
| 3 | 3 | import { type FormSchema, useComponentRegister } from '@/components/Form' |
| 4 | 4 | import { ComponentEnum } from '@/components/Form/src/enum' |
| 5 | -import { ThingsModelForm, validateTCPCustomCommand } from '@/core/Library/components/ThingsModelForm' | |
| 5 | +import { ThingsModelForm } from '@/core/Library/components/ThingsModelForm' | |
| 6 | 6 | import { getFormSchemas } from '@/core/Library/components/ThingsModelForm/config' |
| 7 | -import { CodeTypeEnum, DataTypeEnum, TransportTypeEnum } from '@/enums/datasource' | |
| 8 | -import { TCPObjectModelActionTypeEnum } from '@/enums/objectModelEnum' | |
| 7 | +import { TCPProtocolTypeEnum, TransportTypeEnum } from '@/enums/deviceEnum' | |
| 8 | +import { DataTypeEnum, OriginalDataTypeEnum } from '@/enums/objectModelEnum' | |
| 9 | 9 | |
| 10 | 10 | useComponentRegister(ComponentEnum.THINGS_MODEL_FORM, ThingsModelForm) |
| 11 | 11 | |
| ... | ... | @@ -15,83 +15,106 @@ export enum FormFieldsEnum { |
| 15 | 15 | } |
| 16 | 16 | |
| 17 | 17 | export interface AttributeDeliverModalOpenParamsType { |
| 18 | - title?: string | |
| 19 | - operationPassword?: string | |
| 20 | - operationPasswordEnable?: boolean | |
| 18 | + operationPasswordInfo?: OperationPasswordDataType | |
| 21 | 19 | dataSourceJson: NodeDataDataSourceJsonType |
| 20 | + eventBindData: SingleClickEventDataType | |
| 22 | 21 | } |
| 23 | 22 | |
| 24 | -function getStructJsonFromDataSourceJson(dataSourceJson: NodeDataDataSourceJsonType): StructJSON { | |
| 25 | - const { attrInfo } = dataSourceJson | |
| 26 | - const { identifier, name, detail } = attrInfo || {} | |
| 23 | +export interface CreateFormSchemasParamsType { | |
| 24 | + deviceInfo: DeviceItemType | |
| 25 | + objectModelTsl: Tsl | |
| 26 | + operationPasswordInfo?: OperationPasswordDataType | |
| 27 | +} | |
| 28 | + | |
| 29 | +function getStructJsonFromDataSourceJson(objectModelTsl: Tsl): StructJSON { | |
| 30 | + const { identifier, functionName, specs } = objectModelTsl || {} | |
| 27 | 31 | return { |
| 28 | - functionName: name, | |
| 32 | + functionName, | |
| 29 | 33 | identifier, |
| 30 | - dataType: detail.dataType, | |
| 34 | + dataType: specs.dataType, | |
| 31 | 35 | } |
| 32 | 36 | } |
| 33 | 37 | |
| 34 | -function getTCPModbusSchemas({ structJson, required, actionType }: { structJson: StructJSON; required?: boolean; actionType: string }): FormSchema { | |
| 35 | - const { dataType } = structJson | |
| 36 | - const { specs, type } = dataType || {} | |
| 37 | - const { valueRange, length = 10240 } = specs! as Specs | |
| 38 | - const { max = Number.MAX_SAFE_INTEGER, min = Number.MIN_SAFE_INTEGER } = valueRange || {} | |
| 38 | +const validateDouble = (value: number, min?: number | string, max?: number | string) => { | |
| 39 | + min = Number(min) ?? Number.MIN_SAFE_INTEGER | |
| 40 | + max = Number(max) ?? Number.MAX_SAFE_INTEGER | |
| 39 | 41 | |
| 40 | - function createInputNumber({ | |
| 41 | - identifier, | |
| 42 | - functionName, | |
| 43 | - }: StructJSON): FormSchema { | |
| 44 | - return { | |
| 45 | - field: identifier, | |
| 46 | - label: functionName, | |
| 47 | - component: ComponentEnum.INPUT_NUMBER, | |
| 48 | - required, | |
| 49 | - componentProps: { | |
| 50 | - max: actionType === TCPObjectModelActionTypeEnum.BOOL ? 1 : max, | |
| 51 | - min: actionType === TCPObjectModelActionTypeEnum.BOOL ? 0 : min, | |
| 52 | - precision: actionType === TCPObjectModelActionTypeEnum.BOOL ? 0 : 2, | |
| 53 | - }, | |
| 54 | - } | |
| 42 | + return { | |
| 43 | + flag: value < min || value > max, | |
| 44 | + message: `取值范围在${min}~${max}之间`, | |
| 55 | 45 | } |
| 46 | +} | |
| 47 | + | |
| 48 | +export const createModbusValueInput = (objectModel: Tsl): FormSchema => { | |
| 49 | + const { identifier, functionName, extensionDesc } = objectModel | |
| 50 | + | |
| 51 | + const { dataType } = objectModel.specs || {} | |
| 52 | + const { specs } = dataType || {} | |
| 53 | + const { valueRange } = specs as Specs | |
| 54 | + const { max, min } = valueRange || {} | |
| 55 | + | |
| 56 | + if (extensionDesc?.originalDataType === OriginalDataTypeEnum.BOOLEAN) { | |
| 57 | + const options = [ | |
| 58 | + { label: '闭合', value: parseInt('FF00', 16) }, | |
| 59 | + { label: '断开', value: parseInt('0000', 16) }, | |
| 60 | + ] | |
| 56 | 61 | |
| 57 | - const createInput = ({ identifier, functionName }: StructJSON): FormSchema => { | |
| 58 | 62 | return { |
| 59 | 63 | field: identifier, |
| 60 | 64 | label: functionName, |
| 61 | - component: ComponentEnum.INPUT, | |
| 62 | - rules: [ | |
| 63 | - { | |
| 64 | - required, | |
| 65 | - message: `${functionName}是必填项`, | |
| 66 | - }, | |
| 67 | - { | |
| 68 | - validator: validateTCPCustomCommand, | |
| 69 | - }, | |
| 70 | - ], | |
| 71 | - componentProps: { | |
| 72 | - maxLength: length, | |
| 65 | + component: ComponentEnum.SELECT, | |
| 66 | + componentProps: () => { | |
| 67 | + return { | |
| 68 | + options, | |
| 69 | + placeholder: `请选择${functionName}`, | |
| 70 | + getPopupContainer: () => document.body, | |
| 71 | + } | |
| 73 | 72 | }, |
| 74 | - } as FormSchema | |
| 73 | + } | |
| 75 | 74 | } |
| 76 | 75 | |
| 77 | - return type === DataTypeEnum.STRING ? createInput(structJson) : createInputNumber(structJson) | |
| 76 | + const isStringType = extensionDesc?.originalDataType === OriginalDataTypeEnum.STRING | |
| 77 | + return { | |
| 78 | + field: identifier, | |
| 79 | + label: functionName, | |
| 80 | + component: isStringType ? ComponentEnum.INPUT : ComponentEnum.INPUT_NUMBER, | |
| 81 | + rules: isStringType | |
| 82 | + ? [] | |
| 83 | + : [ | |
| 84 | + { | |
| 85 | + type: 'number', | |
| 86 | + validator: (_rule, value) => { | |
| 87 | + const { flag, message } = validateDouble(value, min, max) | |
| 88 | + if (flag) | |
| 89 | + return Promise.reject(Error(`${functionName}${message}`)) | |
| 90 | + | |
| 91 | + return Promise.resolve(value) | |
| 92 | + }, | |
| 93 | + }, | |
| 94 | + ], | |
| 95 | + componentProps: { | |
| 96 | + // max: max ?? Number.MAX_SAFE_INTEGER, | |
| 97 | + // min: min ?? Number.MIN_SAFE_INTEGER, | |
| 98 | + placeholder: `请输入${functionName}`, | |
| 99 | + precision: 0, | |
| 100 | + }, | |
| 101 | + } | |
| 78 | 102 | } |
| 79 | 103 | |
| 80 | -export const createFormSchemas = ({ operationPassword, operationPasswordEnable, dataSourceJson }: AttributeDeliverModalOpenParamsType): FormSchema[] => { | |
| 104 | +export const createFormSchemas = ({ operationPasswordInfo, objectModelTsl, deviceInfo }: CreateFormSchemasParamsType): FormSchema[] => { | |
| 81 | 105 | const schemas: FormSchema[] = [] |
| 82 | 106 | |
| 83 | - const { deviceInfo } = dataSourceJson | |
| 84 | - if (deviceInfo?.transportType === TransportTypeEnum.TCP && deviceInfo.codeType === CodeTypeEnum.MODBUS_RTU && dataSourceJson.deviceInfo?.codeType) { | |
| 85 | - schemas.push(getTCPModbusSchemas({ required: true, structJson: getStructJsonFromDataSourceJson(dataSourceJson), actionType: dataSourceJson.deviceInfo?.codeType })) | |
| 107 | + if (deviceInfo?.transportType === TransportTypeEnum.TCP && deviceInfo.deviceProfile?.profileData?.transportConfiguration?.protocol === TCPProtocolTypeEnum.MODBUS_RTU) { | |
| 108 | + schemas.push(createModbusValueInput(objectModelTsl)) | |
| 86 | 109 | } |
| 87 | 110 | else { |
| 88 | - const isStructType = dataSourceJson.attrInfo?.detail?.dataType?.type === DataTypeEnum.STRUCT | |
| 111 | + const isStructType = objectModelTsl.specs.dataType.type === DataTypeEnum.STRUCT | |
| 89 | 112 | schemas.push( |
| 90 | - ...getFormSchemas({ structJSON: isStructType ? dataSourceJson?.attrInfo?.detail?.dataType?.specs as StructJSON[] || [] : [getStructJsonFromDataSourceJson(dataSourceJson)], required: !isStructType }), | |
| 113 | + ...getFormSchemas({ structJSON: isStructType ? objectModelTsl.specs.dataType.specs as StructJSON[] || [] : [getStructJsonFromDataSourceJson(objectModelTsl)], required: !isStructType }), | |
| 91 | 114 | ) |
| 92 | 115 | } |
| 93 | 116 | |
| 94 | - if (operationPassword && operationPasswordEnable) { | |
| 117 | + if (operationPasswordInfo?.value && operationPasswordInfo.checked) { | |
| 95 | 118 | schemas.unshift({ |
| 96 | 119 | field: FormFieldsEnum.PASSWORD, |
| 97 | 120 | label: '操作密码', |
| ... | ... | @@ -100,7 +123,7 @@ export const createFormSchemas = ({ operationPassword, operationPasswordEnable, |
| 100 | 123 | rules: [ |
| 101 | 124 | { |
| 102 | 125 | validator(_rule, value) { |
| 103 | - if (value && value !== operationPassword) return Promise.reject(new Error('操作密码不正确')) | |
| 126 | + if (value && value !== operationPasswordInfo.value) return Promise.reject(new Error('操作密码不正确')) | |
| 104 | 127 | return Promise.resolve() |
| 105 | 128 | }, |
| 106 | 129 | }, | ... | ... |
| 1 | 1 | <script setup lang="ts"> |
| 2 | 2 | import { Modal } from 'ant-design-vue' |
| 3 | -import { nextTick, ref, unref } from 'vue' | |
| 4 | -import type { AttributeDeliverModalOpenParamsType } from './AttributeDeliverModal.config' | |
| 5 | -import { createFormSchemas } from './AttributeDeliverModal.config' | |
| 6 | -import { useGetModbusCommand } from './useGetModbusCommand' | |
| 3 | +import { nextTick, ref, toRaw, unref } from 'vue' | |
| 4 | +import { type AttributeDeliverModalOpenParamsType, createFormSchemas } from './AttributeDeliverModal.config' | |
| 7 | 5 | import { BasicForm, useForm } from '@/components/Form' |
| 8 | 6 | import { FormLayoutEnum } from '@/components/Form/src/enum' |
| 9 | -import type { NodeDataDataSourceJsonType } from '@/api/node/model' | |
| 10 | -import { CodeTypeEnum, DataTypeEnum, TransportTypeEnum } from '@/enums/datasource' | |
| 11 | - | |
| 12 | -const resolveFn = ref<Fn>() | |
| 7 | +import type { DeviceItemType, Tsl } from '@/api/device/model' | |
| 8 | +import { useProductsStore } from '@/store/modules/products' | |
| 9 | +import { getDeviceInfo } from '@/api/device' | |
| 10 | +import type { CommandWayEnum } from '@/enums/commandEnum' | |
| 11 | +import { useCommandDelivery } from '@/hooks/business/useCommandDelivery' | |
| 12 | +import { DataTypeEnum } from '@/enums/objectModelEnum' | |
| 13 | +import { useMessage } from '@/hooks/web/useMessage' | |
| 13 | 14 | |
| 14 | 15 | const visible = ref(false) |
| 15 | 16 | |
| 16 | -const password = ref() | |
| 17 | - | |
| 18 | -const currentDataSourceJson = ref<NodeDataDataSourceJsonType>() | |
| 19 | - | |
| 20 | 17 | const [register, { getFieldsValue, resetFields, validate, setProps, clearValidate }] = useForm({ |
| 21 | 18 | layout: FormLayoutEnum.VERTICAL, |
| 22 | 19 | showActionButtonGroup: false, |
| 23 | 20 | }) |
| 24 | 21 | |
| 25 | -const open = async ({ title, operationPassword, operationPasswordEnable, dataSourceJson }: AttributeDeliverModalOpenParamsType) => { | |
| 22 | +const deviceInfo = ref<DeviceItemType>() | |
| 23 | +const objectModelTsl = ref<Tsl>() | |
| 24 | +const productsStore = useProductsStore() | |
| 25 | +const commandWay = ref<CommandWayEnum>() | |
| 26 | + | |
| 27 | +const open = async ({ operationPasswordInfo, dataSourceJson, eventBindData }: AttributeDeliverModalOpenParamsType) => { | |
| 26 | 28 | visible.value = true |
| 27 | - password.value = operationPassword | |
| 28 | - currentDataSourceJson.value = dataSourceJson | |
| 29 | - return new Promise((resolve) => { | |
| 30 | - resolveFn.value = resolve | |
| 31 | - nextTick(() => { | |
| 32 | - setProps({ schemas: createFormSchemas({ title, operationPassword, operationPasswordEnable, dataSourceJson }) }) | |
| 33 | - }) | |
| 29 | + const { deviceId, deviceProfileId, attr } = dataSourceJson | |
| 30 | + const { way } = eventBindData | |
| 31 | + commandWay.value = way | |
| 32 | + objectModelTsl.value = productsStore.getObjectModelByIdWithIdentifier(deviceProfileId, attr as string)! | |
| 33 | + deviceInfo.value = await getDeviceInfo(deviceId) | |
| 34 | + | |
| 35 | + nextTick(() => { | |
| 36 | + const schemas = createFormSchemas({ operationPasswordInfo, deviceInfo: toRaw(unref(deviceInfo)!), objectModelTsl: toRaw(unref(objectModelTsl)!) }) | |
| 37 | + setProps({ schemas }) | |
| 34 | 38 | }) |
| 35 | 39 | } |
| 36 | 40 | |
| 37 | 41 | async function getResult() { |
| 38 | - const result = getFieldsValue() | |
| 39 | - const isTCPModbusDevice = unref(currentDataSourceJson)?.deviceInfo?.transportType === TransportTypeEnum.TCP && unref(currentDataSourceJson)?.deviceInfo?.codeType === CodeTypeEnum.MODBUS_RTU | |
| 40 | - const isStructJSON = unref(currentDataSourceJson)?.attrInfo?.detail?.dataType?.type === DataTypeEnum.STRUCT | |
| 41 | - const attrKey = unref(currentDataSourceJson)!.attr | |
| 42 | - if (!isTCPModbusDevice) { return isStructJSON ? result : result[attrKey] } | |
| 43 | - else { | |
| 44 | - const value = result[attrKey] | |
| 45 | - const isString = unref(currentDataSourceJson)?.attrInfo?.detail?.dataType?.type === DataTypeEnum.STRING | |
| 42 | + let value = getFieldsValue() | |
| 43 | + const { doCommandDelivery } = useCommandDelivery() | |
| 46 | 44 | |
| 47 | - if (isString) return value | |
| 45 | + const isStructJSON = unref(objectModelTsl)?.specs.dataType.type === DataTypeEnum.STRUCT | |
| 46 | + const identifier = unref(objectModelTsl)!.identifier | |
| 47 | + value = isStructJSON ? value : value[identifier] | |
| 48 | 48 | |
| 49 | - const { getModbusCommand, validateCanGetCommand } = useGetModbusCommand() | |
| 50 | - if (!validateCanGetCommand(unref(currentDataSourceJson)!.attrInfo.extensionDesc, unref(currentDataSourceJson)!.deviceInfo?.code).flag) return | |
| 49 | + await doCommandDelivery({ | |
| 50 | + objectModel: unref(objectModelTsl), | |
| 51 | + deviceDetail: unref(deviceInfo), | |
| 52 | + value, | |
| 53 | + }) | |
| 51 | 54 | |
| 52 | - const res = await getModbusCommand(value as unknown as number, unref(currentDataSourceJson)!.attrInfo.extensionDesc!, unref(currentDataSourceJson)!.deviceInfo!.code!) | |
| 53 | - return res | |
| 54 | - } | |
| 55 | + const { createMessage } = useMessage() | |
| 56 | + createMessage.success('命令下发成功') | |
| 55 | 57 | } |
| 56 | 58 | |
| 57 | 59 | const handleOk = async () => { |
| 58 | 60 | await validate() |
| 59 | - const result = await getResult() | |
| 60 | - if (!result) return | |
| 61 | - unref(resolveFn)?.(result) | |
| 61 | + await getResult() | |
| 62 | 62 | visible.value = false |
| 63 | 63 | resetFields() |
| 64 | 64 | } |
| ... | ... | @@ -73,7 +73,7 @@ defineExpose({ open }) |
| 73 | 73 | |
| 74 | 74 | <template> |
| 75 | 75 | <Modal |
| 76 | - v-model:open="visible" title="属性下发" ok-text="确认" :width="400" cancel-text="取消" @cancel="handleCancel" | |
| 76 | + v-model:open="visible" :title="`属性下发 / ${deviceInfo?.alias || deviceInfo?.name}`" ok-text="确认" :width="400" cancel-text="取消" @cancel="handleCancel" | |
| 77 | 77 | @ok="handleOk" |
| 78 | 78 | > |
| 79 | 79 | <BasicForm @register="register" /> | ... | ... |
src/core/Library/components/PublicForm/components/DataEvents/CommandDeliveryModal/CommandDeliveryConfirmModal.vue
deleted
100644 → 0
| 1 | -<script setup lang="ts"> | |
| 2 | -import { Modal } from 'ant-design-vue' | |
| 3 | -import { nextTick, ref, unref } from 'vue' | |
| 4 | -import { BasicForm, type FormSchema, useForm } from '@/components/Form' | |
| 5 | -import { ComponentEnum, FormLayoutEnum } from '@/components/Form/src/enum' | |
| 6 | - | |
| 7 | -const resolveFn = ref<Fn>() | |
| 8 | - | |
| 9 | -const visible = ref(false) | |
| 10 | - | |
| 11 | -const [register, { resetFields, validate, setProps }] = useForm({ | |
| 12 | - showActionButtonGroup: false, | |
| 13 | - layout: FormLayoutEnum.VERTICAL, | |
| 14 | -}) | |
| 15 | - | |
| 16 | -const createFormSchemas = (password: string): FormSchema[] => { | |
| 17 | - return [ | |
| 18 | - { | |
| 19 | - field: 'password', | |
| 20 | - component: ComponentEnum.INPUT_PAWSSWORD, | |
| 21 | - label: '', | |
| 22 | - required: true, | |
| 23 | - rules: [{ | |
| 24 | - validator(_rule, value) { | |
| 25 | - if (value && value !== password) return Promise.reject(new Error('操作密码不正确')) | |
| 26 | - return Promise.resolve() | |
| 27 | - }, | |
| 28 | - }], | |
| 29 | - componentProps: { | |
| 30 | - placeholder: '请输入操作密码', | |
| 31 | - }, | |
| 32 | - }, | |
| 33 | - ] | |
| 34 | -} | |
| 35 | - | |
| 36 | -const open = async (pwd: string) => { | |
| 37 | - visible.value = true | |
| 38 | - return new Promise((resolve) => { | |
| 39 | - resolveFn.value = resolve | |
| 40 | - | |
| 41 | - nextTick(() => { | |
| 42 | - setProps({ schemas: createFormSchemas(pwd) }) | |
| 43 | - }) | |
| 44 | - }) | |
| 45 | -} | |
| 46 | - | |
| 47 | -const handlerOk = async () => { | |
| 48 | - await validate() | |
| 49 | - unref(resolveFn)?.(true) | |
| 50 | - resetFields() | |
| 51 | - visible.value = false | |
| 52 | -} | |
| 53 | - | |
| 54 | -defineExpose({ open }) | |
| 55 | -</script> | |
| 56 | - | |
| 57 | -<template> | |
| 58 | - <Modal | |
| 59 | - v-model:open="visible" :width="300" ok-text="确认" cancel-text="取消" | |
| 60 | - title="操作密码" | |
| 61 | - @ok="handlerOk" | |
| 62 | - > | |
| 63 | - <BasicForm @register="register" /> | |
| 64 | - </Modal> | |
| 65 | -</template> |
src/core/Library/components/PublicForm/components/DataEvents/CommandDeliveryModal/ConfirmModal.vue
deleted
100644 → 0
| 1 | -<script setup lang="ts"> | |
| 2 | -import { Icon } from '@iconify/vue' | |
| 3 | -import { Modal } from 'ant-design-vue' | |
| 4 | -import { ref, unref } from 'vue' | |
| 5 | - | |
| 6 | -const visible = ref(false) | |
| 7 | - | |
| 8 | -const resolveFn = ref<Fn>() | |
| 9 | - | |
| 10 | -const rejectFn = ref<Fn>() | |
| 11 | - | |
| 12 | -const open = async () => { | |
| 13 | - visible.value = true | |
| 14 | - return new Promise((resolve, reject) => { | |
| 15 | - resolveFn.value = resolve | |
| 16 | - rejectFn.value = reject | |
| 17 | - }) | |
| 18 | -} | |
| 19 | - | |
| 20 | -const handleOk = () => { | |
| 21 | - unref(resolveFn)?.() | |
| 22 | - visible.value = false | |
| 23 | -} | |
| 24 | - | |
| 25 | -const handleCancel = () => { | |
| 26 | - unref(rejectFn)?.() | |
| 27 | - visible.value = false | |
| 28 | -} | |
| 29 | - | |
| 30 | -defineExpose({ open }) | |
| 31 | -</script> | |
| 32 | - | |
| 33 | -<template> | |
| 34 | - <Modal v-model:open="visible" :width="400" ok-text="确认" cancel-text="取消" @ok="handleOk" @cancel="handleCancel"> | |
| 35 | - <template #title> | |
| 36 | - <div class="flex"> | |
| 37 | - <span><Icon icon="ant-design:exclamation-circle-filled" class="text-yellow-300 text-xl" /></span> | |
| 38 | - <span class="font-bold ml-2">提示</span> | |
| 39 | - </div> | |
| 40 | - </template> | |
| 41 | - <div class="ml-5"> | |
| 42 | - 是否确认此操作? | |
| 43 | - </div> | |
| 44 | - </Modal> | |
| 45 | -</template> |
| 1 | +<script setup lang="ts"> | |
| 2 | +import { Modal } from 'ant-design-vue' | |
| 3 | +import { computed, nextTick, ref, unref } from 'vue' | |
| 4 | +import { Icon } from '@iconify/vue' | |
| 5 | +import { BasicForm, type FormSchema, useForm } from '@/components/Form' | |
| 6 | +import { ComponentEnum, FormLayoutEnum } from '@/components/Form/src/enum' | |
| 7 | +import type { NodeDataDataSourceJsonType, OperationPasswordDataType, SingleClickEventDataType } from '@/api/node/model' | |
| 8 | +import type { DoCommandDeliverParamsType } from '@/hooks/business/useCommandDelivery' | |
| 9 | +import { useCommandDelivery } from '@/hooks/business/useCommandDelivery' | |
| 10 | +import { CommandDeliveryWayEnum } from '@/enums/commandEnum' | |
| 11 | +import { useProductsStore } from '@/store/modules/products' | |
| 12 | +import { getDeviceInfo } from '@/api/device' | |
| 13 | +import { TransportTypeEnum } from '@/enums/deviceEnum' | |
| 14 | +import { useMessage } from '@/hooks/web/useMessage' | |
| 15 | + | |
| 16 | +interface SwitchCommandDeliveryModalParamsType { | |
| 17 | + operationPasswordInfo?: OperationPasswordDataType | |
| 18 | + dataSourceJson: NodeDataDataSourceJsonType | |
| 19 | + eventBindData: SingleClickEventDataType | |
| 20 | + sendValue: any | |
| 21 | +} | |
| 22 | + | |
| 23 | +const visible = ref(false) | |
| 24 | + | |
| 25 | +const currentOperationPasswordInfo = ref<OperationPasswordDataType>() | |
| 26 | + | |
| 27 | +const [register, { resetFields, validate, setProps }] = useForm({ | |
| 28 | + showActionButtonGroup: false, | |
| 29 | + layout: FormLayoutEnum.VERTICAL, | |
| 30 | +}) | |
| 31 | + | |
| 32 | +const createFormSchemas = (password: string): FormSchema[] => { | |
| 33 | + return [ | |
| 34 | + { | |
| 35 | + field: 'password', | |
| 36 | + component: ComponentEnum.INPUT_PAWSSWORD, | |
| 37 | + label: '操作密码', | |
| 38 | + required: true, | |
| 39 | + rules: [{ | |
| 40 | + validator(_rule, value) { | |
| 41 | + if (value && value !== password) return Promise.reject(new Error('操作密码不正确')) | |
| 42 | + return Promise.resolve() | |
| 43 | + }, | |
| 44 | + }], | |
| 45 | + componentProps: { | |
| 46 | + placeholder: '请输入操作密码', | |
| 47 | + }, | |
| 48 | + }, | |
| 49 | + ] | |
| 50 | +} | |
| 51 | + | |
| 52 | +const productsStore = useProductsStore() | |
| 53 | + | |
| 54 | +const getHasEnableOperationPassword = computed(() => !!(unref(currentOperationPasswordInfo)?.checked && unref(currentOperationPasswordInfo)?.value)) | |
| 55 | + | |
| 56 | +const currentParams = ref<SwitchCommandDeliveryModalParamsType>() | |
| 57 | +const open = async (params: SwitchCommandDeliveryModalParamsType) => { | |
| 58 | + const { operationPasswordInfo } = params | |
| 59 | + visible.value = true | |
| 60 | + currentParams.value = params | |
| 61 | + currentOperationPasswordInfo.value = operationPasswordInfo | |
| 62 | + | |
| 63 | + nextTick(() => { | |
| 64 | + unref(getHasEnableOperationPassword) && setProps({ schemas: createFormSchemas(operationPasswordInfo!.value!) }) | |
| 65 | + }) | |
| 66 | +} | |
| 67 | + | |
| 68 | +const handlerOk = async () => { | |
| 69 | + if (unref(getHasEnableOperationPassword)) | |
| 70 | + await validate() | |
| 71 | + const { doCommandDelivery } = useCommandDelivery() | |
| 72 | + | |
| 73 | + const { sendValue, eventBindData, dataSourceJson } = unref(currentParams)! | |
| 74 | + const { way, commandWay } = eventBindData | |
| 75 | + const { deviceProfileId, attr, deviceId } = dataSourceJson | |
| 76 | + | |
| 77 | + const deviceDetail = await getDeviceInfo(deviceId) | |
| 78 | + | |
| 79 | + const doCommandParams: DoCommandDeliverParamsType = { | |
| 80 | + value: sendValue, | |
| 81 | + identifier: attr as string, | |
| 82 | + deviceDetail, | |
| 83 | + deviceProfileId, | |
| 84 | + way, | |
| 85 | + } | |
| 86 | + | |
| 87 | + if (commandWay === CommandDeliveryWayEnum.CUSTOM) { | |
| 88 | + doCommandParams.penetration = true | |
| 89 | + } | |
| 90 | + else if (commandWay === CommandDeliveryWayEnum.SERVICE) { | |
| 91 | + const { service } = eventBindData | |
| 92 | + doCommandParams.objectModel = productsStore.getObjectModelByIdWithIdentifier(deviceProfileId, service!)! | |
| 93 | + doCommandParams.value = deviceDetail?.transportType === TransportTypeEnum.TCP ? doCommandParams.value : { [doCommandParams.objectModel.identifier]: doCommandParams.value } | |
| 94 | + } | |
| 95 | + | |
| 96 | + await doCommandDelivery(doCommandParams) | |
| 97 | + | |
| 98 | + const { createMessage } = useMessage() | |
| 99 | + createMessage.success('命令下发成功') | |
| 100 | + | |
| 101 | + if (unref(getHasEnableOperationPassword)) resetFields() | |
| 102 | + visible.value = false | |
| 103 | +} | |
| 104 | + | |
| 105 | +defineExpose({ open }) | |
| 106 | +</script> | |
| 107 | + | |
| 108 | +<template> | |
| 109 | + <Modal v-model:open="visible" :width="300" ok-text="确认" cancel-text="取消" destroy-on-close @ok="handlerOk"> | |
| 110 | + <BasicForm v-if="getHasEnableOperationPassword" @register="register" /> | |
| 111 | + <div v-else class="flex items-center"> | |
| 112 | + <Icon icon="ant-design:exclamation-circle-filled" class="text-yellow-300 text-xl" /> | |
| 113 | + <span class="ml-2"> | |
| 114 | + 是否确认此操作? | |
| 115 | + </span> | |
| 116 | + </div> | |
| 117 | + </Modal> | |
| 118 | +</template> | ... | ... |
| 1 | 1 | import { ComponentEnum } from '@/components/Form/src/enum' |
| 2 | 2 | import type { FormSchema } from '@/components/Form' |
| 3 | -import type { StructJSON } from '@/api/device/model' | |
| 3 | +import type { OperationPasswordDataType } from '@/api/node/model' | |
| 4 | +import { validateTCPCustomCommand } from '@/core/Library/components/ThingsModelForm' | |
| 5 | +import { JSONEditorValidator } from '@/components/CodeEditor/src/JSONEditor' | |
| 6 | + | |
| 7 | +export enum FormFieldsEnum { | |
| 8 | + CUSTOM_COMMAND = 'customCommand', | |
| 9 | + OPERATION_PASSWORD = 'password', | |
| 10 | + | |
| 11 | + SERVICE_COMMAND = 'serviceCommand', | |
| 12 | +} | |
| 13 | + | |
| 4 | 14 | export const formSchemas: FormSchema[] = [ |
| 5 | 15 | { |
| 6 | 16 | field: 'sendValue', |
| ... | ... | @@ -10,219 +20,53 @@ export const formSchemas: FormSchema[] = [ |
| 10 | 20 | }, |
| 11 | 21 | ] |
| 12 | 22 | |
| 13 | -const InsertString = (t: any, c: any, n: any) => { | |
| 14 | - const r: string | number[] = [] | |
| 15 | - | |
| 16 | - for (let i = 0; i * 2 < t.length; i++) | |
| 17 | - r.push(t.substr(i * 2, n)) | |
| 18 | - | |
| 19 | - return r.join(c) | |
| 20 | -} | |
| 21 | -const FillString = (t: any, c: any, n: any, b: any) => { | |
| 22 | - if (t === '' || c.length !== 1 || n <= t.length) | |
| 23 | - return t | |
| 24 | - | |
| 25 | - const l = t.length | |
| 26 | - | |
| 27 | - for (let i = 0; i < n - l; i++) { | |
| 28 | - if (b === true) | |
| 29 | - t = c + t | |
| 30 | - | |
| 31 | - else | |
| 32 | - t += c | |
| 33 | - } | |
| 34 | - return t | |
| 35 | -} | |
| 36 | -const SingleToHex = (t: any) => { | |
| 37 | - if (t === '') | |
| 38 | - return '' | |
| 39 | - | |
| 40 | - t = parseFloat(t) | |
| 41 | - | |
| 42 | - if (isNaN(t) === true) | |
| 43 | - return 'Error' | |
| 44 | - | |
| 45 | - if (t === 0) | |
| 46 | - return '00000000' | |
| 47 | - | |
| 48 | - let s, e, m | |
| 49 | - | |
| 50 | - if (t > 0) { | |
| 51 | - s = 0 | |
| 52 | - } | |
| 53 | - else { | |
| 54 | - s = 1 | |
| 55 | - | |
| 56 | - t = 0 - t | |
| 57 | - } | |
| 58 | - m = t.toString(2) | |
| 59 | - | |
| 60 | - if (m >= 1) { | |
| 61 | - if (m.indexOf('.') === -1) | |
| 62 | - m = `${m}.0` | |
| 63 | - | |
| 64 | - e = m.indexOf('.') - 1 | |
| 65 | - } | |
| 66 | - else { | |
| 67 | - e = 1 - m.indexOf('1') | |
| 68 | - } | |
| 69 | - if (e >= 0) | |
| 70 | - m = m.replace('.', '') | |
| 71 | - | |
| 72 | - else | |
| 73 | - m = m.substring(m.indexOf('1')) | |
| 74 | - | |
| 75 | - if (m.length > 24) | |
| 76 | - m = m.substr(0, 24) | |
| 77 | - | |
| 78 | - else | |
| 79 | - m = FillString(m, '0', 24, false) | |
| 80 | - | |
| 81 | - m = m.substring(1) | |
| 82 | - | |
| 83 | - e = (e + 127).toString(2) | |
| 84 | - | |
| 85 | - e = FillString(e, '0', 8, true) | |
| 86 | - | |
| 87 | - let r = parseInt(s + e + m, 2).toString(16) | |
| 88 | - | |
| 89 | - r = FillString(r, '0', 8, true) | |
| 90 | - | |
| 91 | - return InsertString(r, ' ', 2).toUpperCase() | |
| 92 | -} | |
| 93 | - | |
| 94 | -const FormatHex = (t: any, n: any, ie: any) => { | |
| 95 | - const r: string[] = [] | |
| 96 | - | |
| 97 | - let s = '' | |
| 98 | - | |
| 99 | - let c = 0 | |
| 100 | - | |
| 101 | - for (let i = 0; i < t.length; i++) { | |
| 102 | - if (t.charAt(i) !== ' ') { | |
| 103 | - s += t.charAt(i) | |
| 104 | - | |
| 105 | - c += 1 | |
| 106 | - | |
| 107 | - if (c === n) { | |
| 108 | - r.push(s) | |
| 109 | - | |
| 110 | - s = '' | |
| 111 | - | |
| 112 | - c = 0 | |
| 113 | - } | |
| 114 | - } | |
| 115 | - if (ie === false) { | |
| 116 | - if (i === t.length - 1 && s !== '') | |
| 117 | - r.push(s) | |
| 118 | - } | |
| 119 | - } | |
| 120 | - return r.join('\n') | |
| 121 | -} | |
| 122 | -const FormatHexBatch = (t: any, n: any, ie: any) => { | |
| 123 | - const a = t.split('\n') | |
| 124 | - | |
| 125 | - const r: string[] = [] | |
| 126 | - | |
| 127 | - for (let i = 0; i < a.length; i++) | |
| 128 | - r[i] = FormatHex(a[i], n, ie) | |
| 129 | - | |
| 130 | - return r.join('\n') | |
| 131 | -} | |
| 132 | -const SingleToHexBatch = (t: any) => { | |
| 133 | - const a = t.split('\n') | |
| 134 | - | |
| 135 | - const r: string[] = [] | |
| 136 | - | |
| 137 | - for (let i = 0; i < a.length; i++) | |
| 138 | - r[i] = SingleToHex(a[i]) | |
| 139 | - | |
| 140 | - return r.join('\r\n') | |
| 141 | -} | |
| 142 | - | |
| 143 | -const formSchemasConfig = (schemas: StructJSON, actionType: any): FormSchema[] => { | |
| 144 | - const { identifier, functionName } = schemas | |
| 145 | - if (actionType === '06') { | |
| 146 | - return [ | |
| 147 | - { | |
| 148 | - field: identifier, | |
| 149 | - label: functionName, | |
| 150 | - component: ComponentEnum.INPUT_NUMBER, | |
| 151 | - rules: [{ required: true, message: '请输入正数' }], | |
| 152 | - componentProps: { | |
| 153 | - min: 0, | |
| 154 | - formatter: (e: any) => { | |
| 155 | - const value = `${e}`.replace('-', '').replace(/^(-)*(\d+)\.(\d\d).*$/, '$1$2.$3') | |
| 156 | - return value | |
| 23 | +export const getOperationPasswordSchemas = (operationPasswordInfo: OperationPasswordDataType): FormSchema[] => { | |
| 24 | + return [ | |
| 25 | + { | |
| 26 | + field: FormFieldsEnum.OPERATION_PASSWORD, | |
| 27 | + label: '操作密码', | |
| 28 | + component: ComponentEnum.INPUT_PAWSSWORD, | |
| 29 | + required: true, | |
| 30 | + rules: [ | |
| 31 | + { | |
| 32 | + validator(_rule, value: string) { | |
| 33 | + if (value !== operationPasswordInfo.value) | |
| 34 | + return Promise.reject(Error('操作密码不正确')) | |
| 35 | + return Promise.resolve() | |
| 157 | 36 | }, |
| 158 | - placeholder: '请输入正数', | |
| 159 | 37 | }, |
| 38 | + ], | |
| 39 | + componentProps: { | |
| 40 | + placeholder: '请输入操作密码', | |
| 41 | + max: 120, | |
| 42 | + autocomplete: 'cc-number', | |
| 160 | 43 | }, |
| 161 | - ] | |
| 162 | - } | |
| 163 | - else if (actionType === '05') { | |
| 164 | - return [ | |
| 165 | - { | |
| 166 | - field: identifier, | |
| 167 | - label: functionName, | |
| 168 | - component: ComponentEnum.INPUT_NUMBER, | |
| 169 | - rules: [{ required: true, message: '请输入值' }], | |
| 170 | - componentProps: { | |
| 171 | - min: 0, | |
| 172 | - max: 1, | |
| 173 | - precision: 0, | |
| 174 | - placeholder: '请输入0或1', | |
| 175 | - }, | |
| 176 | - }, | |
| 177 | - ] | |
| 178 | - } | |
| 179 | - else { | |
| 180 | - return [ | |
| 181 | - { | |
| 182 | - field: identifier, | |
| 183 | - label: functionName, | |
| 184 | - component: ComponentEnum.INPUT_NUMBER, | |
| 185 | - rules: [{ required: true, message: '请输入值' }], | |
| 186 | - componentProps: { | |
| 187 | - placeholder: '请输入数字', | |
| 188 | - formatter: (e: any) => | |
| 189 | - `${e}`.replace(/\B(?=(\d{3})+(?!\d))/g, '').replace(/^(-)*(\d+)\.(\d\d).*$/, '$1$2.$3'), | |
| 190 | - }, | |
| 191 | - }, | |
| 192 | - ] | |
| 193 | - } | |
| 44 | + }, | |
| 45 | + ] | |
| 194 | 46 | } |
| 195 | 47 | |
| 196 | -const getArray = (values: any) => { | |
| 197 | - const str = values.replace(/\s+/g, '') | |
| 198 | - const array: any = [] | |
| 199 | - | |
| 200 | - for (let i = 0; i < str.length; i += 4) { | |
| 201 | - const chunk = parseInt(str.substring(i, i + 4), 16) | |
| 202 | - array.push(chunk) | |
| 203 | - } | |
| 204 | - return array | |
| 48 | +export const getTcpCustomCommandSchemas = (): FormSchema[] => { | |
| 49 | + return [ | |
| 50 | + { | |
| 51 | + field: FormFieldsEnum.CUSTOM_COMMAND, | |
| 52 | + label: '自定义命令', | |
| 53 | + component: ComponentEnum.INPUT, | |
| 54 | + required: true, | |
| 55 | + rules: [{ validator: validateTCPCustomCommand }], | |
| 56 | + }, | |
| 57 | + ] | |
| 205 | 58 | } |
| 206 | 59 | |
| 207 | -// 获取小数 | |
| 208 | -const getFloatPart = (number: string | number) => { | |
| 209 | - const isLessZero = Number(number) < 0 | |
| 210 | - number = number.toString() | |
| 211 | - const floatPartStartIndex = number.indexOf('.') | |
| 212 | - const value = ~floatPartStartIndex | |
| 213 | - ? `${isLessZero ? '-' : ''}0.${number.substring(floatPartStartIndex + 1)}` | |
| 214 | - : '0' | |
| 215 | - return Number(value) | |
| 60 | +export const getJSONCommandSchemas = (): FormSchema[] => { | |
| 61 | + return [ | |
| 62 | + { | |
| 63 | + field: FormFieldsEnum.CUSTOM_COMMAND, | |
| 64 | + label: '自定义命令', | |
| 65 | + component: ComponentEnum.JSON_EDITOR, | |
| 66 | + required: true, | |
| 67 | + changeEvent: 'update:value', | |
| 68 | + rules: JSONEditorValidator(), | |
| 69 | + }, | |
| 70 | + ] | |
| 216 | 71 | } |
| 217 | 72 | |
| 218 | -export { | |
| 219 | - InsertString, | |
| 220 | - FillString, | |
| 221 | - SingleToHex, | |
| 222 | - FormatHex, | |
| 223 | - FormatHexBatch, | |
| 224 | - SingleToHexBatch, | |
| 225 | - formSchemasConfig, | |
| 226 | - getArray, | |
| 227 | - getFloatPart, | |
| 228 | -} | ... | ... |
| 1 | 1 | export { default as CommandDeliveryModal } from './index.vue' |
| 2 | -export { default as CommandDeliveryConfirmModal } from './CommandDeliveryConfirmModal.vue' | |
| 3 | -export { default as ConfirmModal } from './ConfirmModal.vue' | |
| 2 | +export { default as SwitchCommandDeliveryModal } from './SwitchCommandDeliveryModal.vue' | |
| 4 | 3 | export { default as AttributeDeliverModal } from './AttributeDeliverModal.vue' | ... | ... |
| 1 | 1 | <script setup lang="ts"> |
| 2 | -import { nextTick, reactive, ref, unref } from 'vue' | |
| 2 | +import { nextTick, reactive, ref, toRaw, unref } from 'vue' | |
| 3 | 3 | import { Button } from 'ant-design-vue' |
| 4 | -import { SingleToHex, formSchemas, formSchemasConfig, getArray, getFloatPart } from './config.ts' | |
| 5 | -import { BasicModal } from '@/components/Modal' | |
| 6 | -import type { SingleClickEventDataType } from '@/api/node/model' | |
| 4 | +import { FormFieldsEnum, formSchemas, getJSONCommandSchemas, getOperationPasswordSchemas, getTcpCustomCommandSchemas } from './config.ts' | |
| 5 | +import { createModbusValueInput } from './AttributeDeliverModal.config' | |
| 6 | +import { BasicModal, useModalInner } from '@/components/Modal' | |
| 7 | +import type { NodeDataDataSourceJsonType, OperationPasswordDataType, SingleClickEventDataType } from '@/api/node/model' | |
| 7 | 8 | import { BasicForm, useForm } from '@/components/Form' |
| 8 | -import { ComponentEnum, FormLayoutEnum } from '@/components/Form/src/enum' | |
| 9 | -import { CommandDeliveryWayEnum, CommandTypeEnum, TransportTypeEnum } from '@/enums/datasource' | |
| 10 | -import { genModbusCommand, getDeviceActiveTime, getDeviceInfo, getThingsModelServices, sendRpcOneway } from '@/api/device' | |
| 9 | +import { FormLayoutEnum } from '@/components/Form/src/enum' | |
| 11 | 10 | import { ThingsModelForm } from '@/core/Library/components/ThingsModelForm' |
| 11 | +import type { DeviceItemType, Tsl } from '@/api/device/model' | |
| 12 | +import { useProductsStore, useProductsStoreWithOut } from '@/store/modules/products' | |
| 13 | +import { useJsonParse } from '@/hooks/business/useJSONParse' | |
| 14 | +import { CommandDeliveryWayEnum } from '@/enums/commandEnum' | |
| 15 | +import { TransportTypeEnum } from '@/enums/deviceEnum' | |
| 16 | +import { getDeviceInfo } from '@/api/device' | |
| 17 | +import type { DoCommandDeliverParamsType } from '@/hooks/business/useCommandDelivery' | |
| 18 | +import { useCommandDelivery } from '@/hooks/business/useCommandDelivery' | |
| 12 | 19 | import { useMessage } from '@/hooks/web/useMessage' |
| 13 | -import type { StructJSON } from '@/api/device/model' | |
| 14 | - | |
| 15 | -interface ServiceInfo { | |
| 16 | - title?: string | |
| 17 | - serviceCommand?: Object | |
| 18 | - inputData?: [] | |
| 19 | - transportType?: string | |
| 20 | - callType?: string | |
| 21 | - code?: string | |
| 22 | - way?: string | |
| 23 | -} | |
| 24 | - | |
| 25 | -const visible = ref<boolean>(false) | |
| 26 | 20 | |
| 27 | -const { createMessage } = useMessage() | |
| 28 | -const loading = ref<boolean>(false) | |
| 29 | - | |
| 30 | -const dataSourceJson = ref<any>({}) | |
| 31 | -const thingsModelElRef = ref() | |
| 21 | +interface OpenParamsType { | |
| 22 | + dataSource: NodeDataDataSourceJsonType | |
| 23 | + eventBindData: SingleClickEventDataType | |
| 24 | + operationPasswordInfo?: OperationPasswordDataType | |
| 25 | +} | |
| 32 | 26 | |
| 33 | -const isInputData = ref<boolean>(false)// 判断使用那个表单 true: 用封装的表单 false:用自己写的表单 | |
| 27 | +const thingsModelElRef = ref<InstanceType<typeof ThingsModelForm>>() | |
| 34 | 28 | |
| 35 | -const zoomFactorValue = ref<number>(1) // 缩放因子 | |
| 36 | -const isShowMultiply = ref<boolean>(false) // 只有tcp --> int和double类型才相乘缩放因子 | |
| 37 | -const isShowActionType = ref<boolean>(true) // 判断设备属性标识符为modBus时没有填写扩展描述 | |
| 38 | -const isShowModBUS = ref<boolean>(false) // 用于判断标识符类型是否时自定义还是modBUS | |
| 39 | -const modBUSForm = ref<any>({}) | |
| 40 | -const formField = ref('') // 存一个表单取值的field | |
| 41 | -const isCommandWay = ref<undefined | string>('') // 存取命令下发方式的值 | |
| 42 | -const deviceTitle = ref<string | undefined>()// 设备名称 | |
| 29 | +const getDefaultConfiguration = () => ({ | |
| 30 | + modalVisible: false, | |
| 31 | + commandDeliveryWay: CommandDeliveryWayEnum.CUSTOM, | |
| 32 | + operationPasswordEnable: false, | |
| 33 | +}) | |
| 43 | 34 | |
| 44 | -const isPasswordInfo = ref<{ checked?: boolean; value?: string | number; label?: string }>({})// 是否需要操作密码才进行命令下发 | |
| 35 | +const configuration = reactive(getDefaultConfiguration()) | |
| 45 | 36 | |
| 46 | -const serviceInfo = reactive<ServiceInfo>({ | |
| 47 | - title: '', | |
| 48 | - serviceCommand: {}, | |
| 49 | - inputData: [], | |
| 50 | - transportType: '', | |
| 51 | - callType: '', | |
| 52 | - code: '', | |
| 53 | - way: '', // 单向还是双向 | |
| 54 | -}) | |
| 37 | +const [registerModal, { setModalProps, closeModal }] = useModalInner() | |
| 55 | 38 | |
| 56 | -const [register, { getFieldsValue, validate, setFieldsValue, updateSchema, setProps }] = useForm({ // 自定义下发值 | |
| 57 | - schemas: formSchemas, | |
| 39 | +// 操作密码 | |
| 40 | +const [registerPassword, operationPasswordFormAction] = useForm({ | |
| 58 | 41 | showActionButtonGroup: false, |
| 59 | 42 | layout: FormLayoutEnum.VERTICAL, |
| 60 | 43 | labelWidth: 110, |
| 61 | 44 | }) |
| 62 | 45 | |
| 63 | -const [registerPassword, { getFieldsValue: getPasswordValue, validate: validatePassword }] = useForm({ // 操作密码 | |
| 64 | - schemas: [{ | |
| 65 | - field: 'password', | |
| 66 | - label: '操作密码', | |
| 67 | - component: ComponentEnum.INPUT_PAWSSWORD, | |
| 68 | - required: true, | |
| 69 | - componentProps: { | |
| 70 | - placeholder: '请输入操作密码', | |
| 71 | - max: 120, | |
| 72 | - }, | |
| 73 | - }], | |
| 46 | +// 自定义下发值 | |
| 47 | +const [register, customCommandFormAction] = useForm({ | |
| 48 | + schemas: formSchemas, | |
| 74 | 49 | showActionButtonGroup: false, |
| 75 | 50 | layout: FormLayoutEnum.VERTICAL, |
| 76 | - labelWidth: 110, | |
| 77 | 51 | }) |
| 78 | 52 | |
| 79 | -const getServiceInfo = async (deviceProfileId: string, service?: string, serviceCommand?: Recordable) => { | |
| 80 | - isInputData.value = true | |
| 81 | - const functionJson: any = await getThingsModelServices(deviceProfileId) | |
| 82 | - serviceInfo.inputData = functionJson.filter((item: any) => item.identifier === service)?.[0].functionJson.inputData | |
| 83 | - serviceInfo.title = functionJson[0].functionName | |
| 84 | - serviceInfo.serviceCommand = serviceCommand | |
| 53 | +function handleHasOperationPassword(operationPasswordInfo: OperationPasswordDataType) { | |
| 54 | + configuration.operationPasswordEnable = true | |
| 55 | + nextTick(() => { | |
| 56 | + operationPasswordFormAction.setProps({ | |
| 57 | + schemas: getOperationPasswordSchemas(operationPasswordInfo), | |
| 58 | + }) | |
| 59 | + }) | |
| 85 | 60 | } |
| 86 | 61 | |
| 87 | -// 获取设备信息 | |
| 88 | -const getDeviceDetail = async (deviceId: string) => { | |
| 89 | - return await getDeviceInfo(deviceId) | |
| 62 | +function handleOpenCustomCommandModal(deviceInfo: DeviceItemType, eventBindData: SingleClickEventDataType) { | |
| 63 | + const { transportType } = deviceInfo | |
| 64 | + | |
| 65 | + const { setProps, setFieldsValue } = customCommandFormAction | |
| 66 | + if (transportType === TransportTypeEnum.TCP) | |
| 67 | + setProps({ schemas: getTcpCustomCommandSchemas() }) | |
| 68 | + else | |
| 69 | + setProps({ schemas: getJSONCommandSchemas() }) | |
| 70 | + | |
| 71 | + setFieldsValue({ [FormFieldsEnum.CUSTOM_COMMAND]: eventBindData.customCommand }) | |
| 90 | 72 | } |
| 91 | 73 | |
| 92 | -const open = async (_data: SingleClickEventDataType) => { | |
| 93 | - const { operationPassword } = _data || {} | |
| 94 | - dataSourceJson.value = _data.deviceInfo | |
| 95 | - isPasswordInfo.value = operationPassword || {} | |
| 96 | - // commandWay-->命令下发方式 | |
| 97 | - const { commandWay, customCommand, serviceCommand, service, callType, way } = _data || {} | |
| 98 | - isCommandWay.value = commandWay | |
| 99 | - const { attrInfo, deviceId } = unref(dataSourceJson) | |
| 100 | - const { identifier, extensionDesc, detail, name } = attrInfo || {}// 属性信息 | |
| 101 | - const { dataType } = detail || {} | |
| 102 | - const { type } = dataType || {} | |
| 103 | - | |
| 104 | - const { code, transportType, deviceProfileId, alias, name: deviceName } = await getDeviceDetail(deviceId) || {}// 设备信息 | |
| 105 | - deviceTitle.value = alias || deviceName// 产品名称 | |
| 106 | - const { registerAddress, actionType, zoomFactor } = extensionDesc || {} // 获取扩展描述内容 | |
| 107 | - | |
| 108 | - const schemas = [{ dataType, identifier, functionName: name } as StructJSON] | |
| 109 | - | |
| 110 | - zoomFactorValue.value = zoomFactor ? Number(zoomFactor) : 1 | |
| 111 | - isShowMultiply.value = !!(type === 'INT' || type === 'DOUBLE') | |
| 112 | - formField.value = identifier | |
| 113 | - isShowActionType.value = !!actionType // 判断modBUS类型时 物模型是否填写扩展描述 | |
| 114 | - serviceInfo.callType = callType // 服务命令调用方式 是同步还是异步 | |
| 115 | - serviceInfo.way = way || 'oneway' // 命令下发是双向还是单向 | |
| 116 | - | |
| 117 | - serviceInfo.transportType = transportType | |
| 118 | - serviceInfo.code = code | |
| 119 | - | |
| 120 | - visible.value = true | |
| 121 | - | |
| 122 | - if (transportType === TransportTypeEnum.TCP) { | |
| 123 | - if (commandWay === CommandDeliveryWayEnum.SERVICE) { // 判断命令下发方式是服务或者自定义调用 服务调用用封装的组件 自定义和modbus就用输入框 | |
| 124 | - getServiceInfo(deviceProfileId, service, serviceCommand) | |
| 125 | - } | |
| 126 | - else if (commandWay === CommandDeliveryWayEnum.CUSTOM) { // 自定义 | |
| 127 | - await nextTick() | |
| 128 | - updateSchema([ | |
| 129 | - { | |
| 130 | - field: 'sendValue', | |
| 131 | - required: true, | |
| 132 | - label: '自定义下发值', | |
| 133 | - component: ComponentEnum.INPUT, | |
| 134 | - }, | |
| 135 | - ]) | |
| 136 | - setFieldsValue({ sendValue: customCommand }) | |
| 137 | - } | |
| 138 | - else { // modBus调用 | |
| 139 | - isShowModBUS.value = true | |
| 140 | - modBUSForm.value = { | |
| 141 | - crc: 'CRC_16_LOWER', | |
| 142 | - deviceCode: code, | |
| 143 | - method: actionType === '16' ? '10' : actionType, | |
| 144 | - registerAddress, | |
| 145 | - registerNumber: 1, | |
| 146 | - registerValues: [], | |
| 147 | - } | |
| 148 | - await nextTick() | |
| 149 | - setProps({ schemas: formSchemasConfig(schemas[0], actionType) }) | |
| 150 | - } | |
| 151 | - } | |
| 152 | - else { | |
| 153 | - isShowModBUS.value = false | |
| 154 | - | |
| 155 | - // 命令下发方式是服务或则自定义调用 | |
| 156 | - if (commandWay === CommandDeliveryWayEnum.SERVICE) { getServiceInfo(deviceProfileId, service, serviceCommand) } | |
| 157 | - | |
| 158 | - else if (commandWay === CommandDeliveryWayEnum.CUSTOM) { | |
| 159 | - await nextTick() | |
| 160 | - updateSchema([ | |
| 161 | - { | |
| 162 | - field: 'sendValue', | |
| 163 | - component: ComponentEnum.JSON_EDITOR, | |
| 164 | - changeEvent: 'update:value', | |
| 165 | - label: '自定义下发值', | |
| 166 | - required: true, | |
| 167 | - }, | |
| 168 | - ]) | |
| 169 | - setFieldsValue({ sendValue: customCommand }) | |
| 170 | - } | |
| 171 | - } | |
| 74 | +function handleOpenServiceCommandModal(deviceInfo: DeviceItemType, eventBindData: SingleClickEventDataType) { | |
| 75 | + const { service, serviceCommand = {} } = eventBindData | |
| 76 | + | |
| 77 | + const productsStore = useProductsStoreWithOut() | |
| 78 | + | |
| 79 | + const tsl = productsStore.getObjectModelByIdWithIdentifier(deviceInfo.deviceProfileId, service!) | |
| 80 | + unref(thingsModelElRef)?.setProps({ inputData: tsl?.inputData, transportType: deviceInfo.transportType, title: tsl?.functionName }) | |
| 81 | + | |
| 82 | + nextTick(() => { | |
| 83 | + unref(thingsModelElRef)?.setFieldsValue(serviceCommand) | |
| 84 | + }) | |
| 85 | +} | |
| 86 | + | |
| 87 | +function handleOpenModbusCommandModal(objectModelTsl: Tsl) { | |
| 88 | + const { setProps } = customCommandFormAction | |
| 89 | + setProps({ schemas: [createModbusValueInput(objectModelTsl)] }) | |
| 172 | 90 | } |
| 173 | 91 | |
| 174 | -const error = () => { | |
| 175 | - // createMessage.error('下发指令失败') | |
| 176 | - return false | |
| 92 | +const deviceInfo = ref<DeviceItemType>() | |
| 93 | +const objectModelTsl = ref<Tsl>() | |
| 94 | +const currentParams = ref<OpenParamsType>() | |
| 95 | +const productsStore = useProductsStore() | |
| 96 | + | |
| 97 | +const open = async (params: OpenParamsType) => { | |
| 98 | + const { eventBindData, operationPasswordInfo, dataSource } = params | |
| 99 | + const { deviceId, deviceProfileId, attr } = dataSource | |
| 100 | + const { commandWay } = eventBindData | |
| 101 | + const { checked, value: operationPassword } = operationPasswordInfo || {} | |
| 102 | + configuration.commandDeliveryWay = commandWay! | |
| 103 | + currentParams.value = params | |
| 104 | + deviceInfo.value = await getDeviceInfo(deviceId) | |
| 105 | + objectModelTsl.value = productsStore.getObjectModelByIdWithIdentifier(deviceProfileId, attr as string)! | |
| 106 | + setModalProps({ | |
| 107 | + open: true, | |
| 108 | + loading: true, | |
| 109 | + }) | |
| 110 | + | |
| 111 | + if (checked && operationPassword) | |
| 112 | + handleHasOperationPassword(operationPasswordInfo!) | |
| 113 | + | |
| 114 | + await nextTick() | |
| 115 | + | |
| 116 | + if (commandWay === CommandDeliveryWayEnum.CUSTOM) | |
| 117 | + handleOpenCustomCommandModal(toRaw(unref(deviceInfo)!), eventBindData) | |
| 118 | + else if (commandWay === CommandDeliveryWayEnum.SERVICE) | |
| 119 | + handleOpenServiceCommandModal(toRaw(unref(deviceInfo)!), eventBindData) | |
| 120 | + else if (commandWay === CommandDeliveryWayEnum.MODBUS) | |
| 121 | + handleOpenModbusCommandModal(toRaw(unref(objectModelTsl)!)) | |
| 122 | + | |
| 123 | + setModalProps({ | |
| 124 | + loading: false, | |
| 125 | + title: `参数设置 / ${unref(deviceInfo)?.alias || unref(deviceInfo)?.name}`, | |
| 126 | + }) | |
| 127 | +} | |
| 128 | + | |
| 129 | +function handleGetCustomCommand() { | |
| 130 | + const res = customCommandFormAction.getFieldsValue() | |
| 131 | + const command = res[FormFieldsEnum.CUSTOM_COMMAND] | |
| 132 | + return unref(deviceInfo)?.transportType === TransportTypeEnum.TCP ? command : useJsonParse(command).value | |
| 133 | +} | |
| 134 | + | |
| 135 | +function handleGetServiceCommand() { | |
| 136 | + const res = unref(thingsModelElRef)!.getFieldsValue() | |
| 137 | + return unref(deviceInfo)?.transportType === TransportTypeEnum.TCP ? res[FormFieldsEnum.SERVICE_COMMAND] : res | |
| 138 | +} | |
| 139 | + | |
| 140 | +function handleGetModbusCommand() { | |
| 141 | + const res = customCommandFormAction.getFieldsValue() | |
| 142 | + const identifier = unref(objectModelTsl)!.identifier | |
| 143 | + return res[identifier] | |
| 177 | 144 | } |
| 178 | 145 | |
| 179 | 146 | const handleSubmit = async () => { |
| 180 | - unref(isPasswordInfo)?.checked && await validatePassword()// 操作密码 | |
| 181 | - !unref(isInputData) && await validate() | |
| 182 | - unref(isInputData) && await unref(thingsModelElRef).validate() | |
| 183 | - | |
| 184 | - if (unref(isPasswordInfo)?.checked) { | |
| 185 | - const { password } = getPasswordValue() | |
| 186 | - if (unref(isPasswordInfo)?.value !== password) { | |
| 187 | - createMessage.warning('操作密码不正确') | |
| 188 | - return | |
| 189 | - } | |
| 190 | - } | |
| 191 | - if (serviceInfo.callType === 'SYNC') { // 服务命令调用方式 是同步 需要调用设备是否在线才能下发 | |
| 192 | - const res = await getDeviceActiveTime(dataSourceJson.value.deviceId) | |
| 193 | - const { value } = res?.[0] || {} | |
| 194 | - if (!value) { | |
| 195 | - createMessage.error('当前设备不在线') | |
| 196 | - return | |
| 197 | - } | |
| 147 | + if (configuration.operationPasswordEnable) await operationPasswordFormAction.validate() | |
| 148 | + configuration.commandDeliveryWay === CommandDeliveryWayEnum.SERVICE ? await unref(thingsModelElRef)?.validate() : await customCommandFormAction.validate() | |
| 149 | + | |
| 150 | + const res | |
| 151 | + = configuration.commandDeliveryWay === CommandDeliveryWayEnum.CUSTOM | |
| 152 | + ? handleGetCustomCommand() | |
| 153 | + : configuration.commandDeliveryWay === CommandDeliveryWayEnum.MODBUS | |
| 154 | + ? handleGetModbusCommand() | |
| 155 | + : handleGetServiceCommand() | |
| 156 | + | |
| 157 | + const { doCommandDelivery } = useCommandDelivery() | |
| 158 | + | |
| 159 | + const { eventBindData, dataSource } = unref(currentParams)! | |
| 160 | + const { deviceProfileId } = dataSource | |
| 161 | + const { commandWay, way } = eventBindData | |
| 162 | + | |
| 163 | + const params: DoCommandDeliverParamsType = { | |
| 164 | + value: res, | |
| 165 | + deviceDetail: unref(deviceInfo), | |
| 166 | + objectModel: unref(objectModelTsl), | |
| 167 | + way, | |
| 198 | 168 | } |
| 199 | 169 | |
| 200 | - const sendValue = ref({}) | |
| 201 | - try { | |
| 202 | - loading.value = true | |
| 203 | - if (unref(isShowModBUS)) { | |
| 204 | - if (!unref(isShowActionType)) { | |
| 205 | - createMessage.warning('当前物模型扩展描述没有填写') | |
| 206 | - return | |
| 207 | - } | |
| 208 | - | |
| 209 | - if (!serviceInfo.code) { | |
| 210 | - createMessage.error('当前缺少设备地址码') | |
| 211 | - return | |
| 212 | - } | |
| 213 | - const oldValue = getFieldsValue()[unref(formField)] | |
| 214 | - modBUSForm.value.registerNumber = 1 | |
| 215 | - modBUSForm.value.registerValues = [oldValue] | |
| 216 | - | |
| 217 | - if (unref(isShowMultiply) && unref(modBUSForm).method === '06') { | |
| 218 | - const newValue | |
| 219 | - = Math.trunc(oldValue) * unref(zoomFactorValue) | |
| 220 | - + getFloatPart(oldValue) * unref(zoomFactorValue) | |
| 221 | - if (newValue % 1 !== 0) { | |
| 222 | - createMessage.warning(`属性下发类型必须是整数,缩放因子为${unref(zoomFactorValue)}`) | |
| 223 | - return | |
| 224 | - } | |
| 225 | - | |
| 226 | - if (oldValue * unref(zoomFactorValue) > 65535) { | |
| 227 | - createMessage.warning(`属性下发值不能超过65535,缩放因子是${unref(zoomFactorValue)}`) | |
| 228 | - return | |
| 229 | - } | |
| 230 | - // bool类型的就不用去乘缩放因子了 | |
| 231 | - modBUSForm.value.registerValues = [newValue] | |
| 232 | - } | |
| 233 | - | |
| 234 | - if (unref(modBUSForm).method === '16' || unref(modBUSForm).method === '10') { | |
| 235 | - const regex = /^-?\d+(\.\d{0,2})?$/ | |
| 236 | - const values | |
| 237 | - = Math.trunc(oldValue) * unref(zoomFactorValue) | |
| 238 | - + getFloatPart(oldValue) * unref(zoomFactorValue) | |
| 239 | - | |
| 240 | - if (!regex.test(values as any)) { | |
| 241 | - createMessage.warning(`属性下发值精确到两位小数,缩放因子是${unref(zoomFactorValue)}`) | |
| 242 | - return | |
| 243 | - } | |
| 244 | - | |
| 245 | - const newValue | |
| 246 | - = values === 0 ? [0, 0] : getArray(SingleToHex(unref(isShowMultiply) ? values : oldValue)) | |
| 247 | - modBUSForm.value.registerValues = newValue | |
| 248 | - modBUSForm.value.registerNumber = 2 | |
| 249 | - modBUSForm.value.method = '10' | |
| 250 | - } | |
| 251 | - sendValue.value = await genModbusCommand(unref(modBUSForm)) | |
| 252 | - } | |
| 253 | - else { | |
| 254 | - if (unref(isInputData)) { | |
| 255 | - const values = serviceInfo.transportType === TransportTypeEnum.TCP ? Object.values(unref(thingsModelElRef)?.getFieldsValue()).join('').replace(/\s/g, '') : unref(thingsModelElRef)?.getFieldsValue() | |
| 256 | - sendValue.value = values | |
| 257 | - } | |
| 258 | - else { | |
| 259 | - const values = serviceInfo.transportType === TransportTypeEnum.TCP ? getFieldsValue().sendValue : JSON.parse(getFieldsValue().sendValue) || {} | |
| 260 | - sendValue.value = values | |
| 261 | - } | |
| 262 | - } | |
| 263 | - await sendRpcOneway({ | |
| 264 | - additionalInfo: { | |
| 265 | - cmdType: | |
| 266 | - unref(isCommandWay) === CommandDeliveryWayEnum.SERVICE | |
| 267 | - ? CommandTypeEnum.SERVICE | |
| 268 | - : CommandTypeEnum.API, | |
| 269 | - }, | |
| 270 | - persistent: true, | |
| 271 | - method: 'methodThingskit', | |
| 272 | - params: serviceInfo.transportType !== TransportTypeEnum.TCP && unref(isCommandWay) === CommandDeliveryWayEnum.SERVICE ? { service: unref(sendValue) } : unref(sendValue), | |
| 273 | - }, dataSourceJson.value.deviceId, serviceInfo.callType ? serviceInfo.callType === 'SYNC' ? 'twoway' : 'oneway' : serviceInfo.way) | |
| 274 | - createMessage.success('命令下发成功') | |
| 275 | - visible.value = false | |
| 276 | - isInputData.value = false | |
| 277 | - } | |
| 278 | - catch (msg) { | |
| 279 | - console.error(msg) | |
| 280 | - return error() | |
| 170 | + if (commandWay === CommandDeliveryWayEnum.CUSTOM) { | |
| 171 | + params.penetration = true | |
| 281 | 172 | } |
| 282 | - finally { | |
| 283 | - loading.value = false | |
| 173 | + else if (commandWay === CommandDeliveryWayEnum.SERVICE) { | |
| 174 | + const { service } = eventBindData | |
| 175 | + params.objectModel = productsStore.getObjectModelByIdWithIdentifier(deviceProfileId, service!)! | |
| 176 | + params.value = unref(deviceInfo)?.transportType === TransportTypeEnum.TCP ? params.value : { [params.objectModel.identifier]: params.value } | |
| 284 | 177 | } |
| 178 | + | |
| 179 | + await doCommandDelivery(params) | |
| 180 | + const { createMessage } = useMessage() | |
| 181 | + createMessage.success('命令下发成功') | |
| 182 | + closeModal() | |
| 285 | 183 | } |
| 286 | 184 | |
| 287 | 185 | const handleCancel = () => { |
| 288 | - visible.value = false | |
| 289 | - isInputData.value = false | |
| 186 | + closeModal() | |
| 187 | + Object.assign(configuration, getDefaultConfiguration()) | |
| 290 | 188 | } |
| 291 | 189 | |
| 292 | 190 | defineExpose({ |
| ... | ... | @@ -295,28 +193,15 @@ defineExpose({ |
| 295 | 193 | </script> |
| 296 | 194 | |
| 297 | 195 | <template> |
| 298 | - <BasicModal | |
| 299 | - v-model:open="visible" :title="`参数设置-${deviceTitle}`" :destroy-on-close="true" :confirm-loading="loading" | |
| 300 | - :ok-button-props="{ type: 'primary' }" @ok="handleSubmit" @cancel="handleCancel" | |
| 301 | - > | |
| 302 | - <BasicForm v-if="isPasswordInfo?.checked" @register="registerPassword" /> | |
| 303 | - <BasicForm v-if="!isInputData" @register="register" /> | |
| 304 | - <ThingsModelForm | |
| 305 | - v-else ref="thingsModelElRef" v-model:value="serviceInfo.serviceCommand" :title="serviceInfo.title" | |
| 306 | - :input-data="serviceInfo.inputData || []" :transport-type="serviceInfo.transportType" | |
| 307 | - /> | |
| 196 | + <BasicModal :destroy-on-close="true" @register="registerModal" @ok="handleSubmit" @cancel="handleCancel"> | |
| 197 | + <BasicForm v-if="configuration.operationPasswordEnable" @register="registerPassword" /> | |
| 198 | + <BasicForm v-if="configuration.commandDeliveryWay !== CommandDeliveryWayEnum.SERVICE" @register="register" /> | |
| 199 | + <ThingsModelForm v-if="configuration.commandDeliveryWay === CommandDeliveryWayEnum.SERVICE" ref="thingsModelElRef" /> | |
| 308 | 200 | <template #footer> |
| 309 | - <Button | |
| 310 | - style="background-color: #ffffff; | |
| 311 | - border-color: #d9d9d9; | |
| 312 | - box-shadow: 0 2px 0 rgba(0, 0, 0, 0.02);" @click="handleCancel" | |
| 313 | - > | |
| 201 | + <Button @click="handleCancel"> | |
| 314 | 202 | 取消 |
| 315 | 203 | </Button> |
| 316 | - <Button | |
| 317 | - type="primary" :loading="loading" | |
| 318 | - style="background: #1677ff !important; box-shadow: 0 2px 0 rgba(5, 145, 255, 0.1)" @click="handleSubmit" | |
| 319 | - > | |
| 204 | + <Button type="primary" @click="handleSubmit"> | |
| 320 | 205 | 确定 |
| 321 | 206 | </Button> |
| 322 | 207 | </template> | ... | ... |
| 1 | +import { unref } from 'vue' | |
| 2 | +import { DataTypeEnum, OriginalDataTypeEnum } from '@/enums/objectModelEnum' | |
| 3 | +import type { FormSchema } from '@/components/Form' | |
| 4 | +import type { DataType, DeviceItemType, Specs, StructJSON, Tsl } from '@/api/device/model' | |
| 5 | +import { ComponentEnum } from '@/components/Form/src/enum' | |
| 6 | +import { TCPProtocolTypeEnum } from '@/enums/deviceEnum' | |
| 7 | + | |
| 8 | +export interface BasicCreateFormParams { | |
| 9 | + identifier: string | |
| 10 | + functionName: string | |
| 11 | + dataType: DataType | |
| 12 | +} | |
| 13 | + | |
| 14 | +const validateDouble = (value: number, min?: number | string, max?: number | string) => { | |
| 15 | + min = Number(min) ?? Number.MIN_SAFE_INTEGER | |
| 16 | + max = Number(max) ?? Number.MAX_SAFE_INTEGER | |
| 17 | + | |
| 18 | + return { | |
| 19 | + flag: value < min || value > max, | |
| 20 | + message: `取值范围在${min}~${max}之间`, | |
| 21 | + } | |
| 22 | +} | |
| 23 | + | |
| 24 | +export const useGenerateFormSchemasByObjectModel = () => { | |
| 25 | + const createInputNumber = ({ | |
| 26 | + identifier, | |
| 27 | + functionName, | |
| 28 | + dataType, | |
| 29 | + }: BasicCreateFormParams): FormSchema => { | |
| 30 | + const { specs, type } = dataType | |
| 31 | + const { valueRange, step } = specs! as Partial<Specs> | |
| 32 | + const { max, min } = valueRange || {} | |
| 33 | + return { | |
| 34 | + field: identifier, | |
| 35 | + label: functionName, | |
| 36 | + component: 'InputNumber', | |
| 37 | + rules: [ | |
| 38 | + { | |
| 39 | + type: 'number', | |
| 40 | + trigger: 'change', | |
| 41 | + validator: (_rule, value) => { | |
| 42 | + const { flag, message } = validateDouble(value, min, max) | |
| 43 | + if (flag) | |
| 44 | + return Promise.reject(Error(`${functionName}${message}`)) | |
| 45 | + | |
| 46 | + return Promise.resolve(value) | |
| 47 | + }, | |
| 48 | + }, | |
| 49 | + ], | |
| 50 | + componentProps: { | |
| 51 | + max: max ?? Number.MAX_SAFE_INTEGER, | |
| 52 | + min: min ?? Number.MIN_SAFE_INTEGER, | |
| 53 | + step, | |
| 54 | + placeholder: `请输入${functionName}`, | |
| 55 | + precision: type === DataTypeEnum.NUMBER_INT ? 0 : 2, | |
| 56 | + }, | |
| 57 | + } as FormSchema | |
| 58 | + } | |
| 59 | + | |
| 60 | + const createInput = ({ | |
| 61 | + identifier, | |
| 62 | + functionName, | |
| 63 | + dataType, | |
| 64 | + }: BasicCreateFormParams): FormSchema => { | |
| 65 | + const { specs } = dataType | |
| 66 | + const { length = 10240 } = specs! as Partial<Specs> | |
| 67 | + return { | |
| 68 | + field: identifier, | |
| 69 | + label: functionName, | |
| 70 | + component: 'Input', | |
| 71 | + rules: [ | |
| 72 | + { | |
| 73 | + type: 'string', | |
| 74 | + trigger: 'change', | |
| 75 | + validator: (_rule, value) => { | |
| 76 | + if (value?.length > length) | |
| 77 | + return Promise.reject(Error(`${functionName}数据长度应该小于${length}`)) | |
| 78 | + | |
| 79 | + return Promise.resolve(value) | |
| 80 | + }, | |
| 81 | + }, | |
| 82 | + ], | |
| 83 | + componentProps: { | |
| 84 | + maxLength: length, | |
| 85 | + placeholder: `请输入${functionName}`, | |
| 86 | + }, | |
| 87 | + } as FormSchema | |
| 88 | + } | |
| 89 | + | |
| 90 | + const createSelect = ({ | |
| 91 | + identifier, | |
| 92 | + functionName, | |
| 93 | + dataType, | |
| 94 | + }: BasicCreateFormParams): FormSchema => { | |
| 95 | + const { specs } = dataType | |
| 96 | + const { boolClose, boolOpen } = specs! as Partial<Specs> | |
| 97 | + return { | |
| 98 | + field: identifier, | |
| 99 | + label: functionName, | |
| 100 | + component: ComponentEnum.SELECT, | |
| 101 | + componentProps: { | |
| 102 | + options: [ | |
| 103 | + { label: `${boolClose}-0`, value: 0 }, | |
| 104 | + { label: `${boolOpen}-1`, value: 1 }, | |
| 105 | + ], | |
| 106 | + placeholder: `请选择${functionName}`, | |
| 107 | + getPopupContainer: () => document.body, | |
| 108 | + }, | |
| 109 | + } | |
| 110 | + } | |
| 111 | + | |
| 112 | + const createEnumSelect = ({ | |
| 113 | + identifier, | |
| 114 | + functionName, | |
| 115 | + dataType, | |
| 116 | + }: BasicCreateFormParams): FormSchema => { | |
| 117 | + const { specsList } = dataType | |
| 118 | + return { | |
| 119 | + field: identifier, | |
| 120 | + label: functionName, | |
| 121 | + component: ComponentEnum.SELECT, | |
| 122 | + componentProps: { | |
| 123 | + options: specsList?.map(item => ({ label: item.name, value: item.value })), | |
| 124 | + placeholder: `请选择${functionName}`, | |
| 125 | + getPopupContainer: () => document.body, | |
| 126 | + }, | |
| 127 | + } | |
| 128 | + } | |
| 129 | + | |
| 130 | + const createModbusValueInput = (objectModel: Tsl): FormSchema => { | |
| 131 | + const { identifier, functionName, extensionDesc } = objectModel | |
| 132 | + | |
| 133 | + const { dataType } = objectModel.specs || {} | |
| 134 | + const { specs } = dataType || {} | |
| 135 | + const { valueRange } = specs as Specs | |
| 136 | + const { max, min } = valueRange || {} | |
| 137 | + | |
| 138 | + const isStringType = extensionDesc?.originalDataType === OriginalDataTypeEnum.STRING | |
| 139 | + return { | |
| 140 | + field: identifier, | |
| 141 | + label: functionName, | |
| 142 | + component: isStringType ? ComponentEnum.INPUT : ComponentEnum.INPUT_NUMBER, | |
| 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(Error(`${functionName}${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: `请输入${functionName}`, | |
| 161 | + precision: 0, | |
| 162 | + }, | |
| 163 | + } | |
| 164 | + } | |
| 165 | + | |
| 166 | + const schemaMethod: Partial<Record<DataTypeEnum, (...args: any[]) => FormSchema>> = { | |
| 167 | + [DataTypeEnum.BOOL]: createSelect, | |
| 168 | + [DataTypeEnum.NUMBER_DOUBLE]: createInputNumber, | |
| 169 | + [DataTypeEnum.NUMBER_INT]: createInputNumber, | |
| 170 | + [DataTypeEnum.STRING]: createInput, | |
| 171 | + [DataTypeEnum.ENUM]: createEnumSelect, | |
| 172 | + } | |
| 173 | + | |
| 174 | + const getFormByObjectModel = ( | |
| 175 | + objectModel: Tsl, | |
| 176 | + deviceDetail: DeviceItemType, | |
| 177 | + ): FormSchema[] => { | |
| 178 | + const { functionName, identifier, specs } = objectModel | |
| 179 | + | |
| 180 | + const isTCPModbusProduct | |
| 181 | + = unref(deviceDetail).deviceProfile?.profileData?.transportConfiguration?.protocol | |
| 182 | + === TCPProtocolTypeEnum.MODBUS_RTU | |
| 183 | + | |
| 184 | + const { dataType } = specs | |
| 185 | + const { type } = dataType || {} | |
| 186 | + | |
| 187 | + if (isTCPModbusProduct) | |
| 188 | + return [createModbusValueInput(objectModel)] | |
| 189 | + | |
| 190 | + if (type === DataTypeEnum.STRUCT) { | |
| 191 | + return (dataType?.specs as StructJSON[]).map((item) => { | |
| 192 | + const { functionName, identifier, dataType } = item | |
| 193 | + const { type } = dataType || {} | |
| 194 | + | |
| 195 | + return schemaMethod[type!]?.({ identifier, functionName, dataType }) | |
| 196 | + }) as FormSchema[] | |
| 197 | + } | |
| 198 | + | |
| 199 | + const result = schemaMethod[type!]?.({ identifier, functionName, dataType: dataType! }) | |
| 200 | + return result ? [result] : [] | |
| 201 | + } | |
| 202 | + | |
| 203 | + return { getFormByObjectModel } | |
| 204 | +} | ... | ... |
src/core/Library/components/PublicForm/components/DataEvents/CommandDeliveryModal/useGetModbusCommand.ts
deleted
100644 → 0
| 1 | -import { SingleToHex, getArray } from './config' | |
| 2 | -import { type GenModbusCommandType, genModbusCommand } from '@/api/device' | |
| 3 | -import type { ExtensionDesc } from '@/api/device/model' | |
| 4 | -import { TCPObjectModelActionTypeEnum } from '@/enums/objectModelEnum' | |
| 5 | -import { useMessage } from '@/hooks/web/useMessage' | |
| 6 | - | |
| 7 | -const getFloatPart = (number: string | number) => { | |
| 8 | - const isLessZero = Number(number) < 0 | |
| 9 | - number = number.toString() | |
| 10 | - const floatPartStartIndex = number.indexOf('.') | |
| 11 | - const value = ~floatPartStartIndex | |
| 12 | - ? `${isLessZero ? '-' : ''}0.${number.substring(floatPartStartIndex + 1)}` | |
| 13 | - : '0' | |
| 14 | - return Number(value) | |
| 15 | -} | |
| 16 | - | |
| 17 | -const REGISTER_MAX_VALUE = Number(0xffff) | |
| 18 | - | |
| 19 | -export function useGetModbusCommand() { | |
| 20 | - const { createMessage } = useMessage() | |
| 21 | - | |
| 22 | - const getModbusCommand = async (value: number, extensionDesc: ExtensionDesc, deviceAddressCode: string) => { | |
| 23 | - const { registerAddress, actionType, zoomFactor } = extensionDesc as Required<ExtensionDesc> | |
| 24 | - const params: GenModbusCommandType = { | |
| 25 | - crc: 'CRC_16_LOWER', | |
| 26 | - registerNumber: 1, | |
| 27 | - deviceCode: deviceAddressCode, | |
| 28 | - registerAddress, | |
| 29 | - method: actionType, | |
| 30 | - registerValues: [value], | |
| 31 | - } | |
| 32 | - | |
| 33 | - if (actionType === TCPObjectModelActionTypeEnum.INT) { | |
| 34 | - const newValue | |
| 35 | - = Math.trunc(value) * zoomFactor | |
| 36 | - + getFloatPart(value) * zoomFactor | |
| 37 | - | |
| 38 | - if (newValue % 1 !== 0) { | |
| 39 | - createMessage.warning(`属性下发类型必须是整数,缩放因子为${zoomFactor}`) | |
| 40 | - return | |
| 41 | - } | |
| 42 | - | |
| 43 | - if (value * zoomFactor > REGISTER_MAX_VALUE) { | |
| 44 | - createMessage.warning(`属性下发值不能超过${REGISTER_MAX_VALUE},缩放因子是${zoomFactor}`) | |
| 45 | - return | |
| 46 | - } | |
| 47 | - | |
| 48 | - params.registerValues = [newValue] | |
| 49 | - } | |
| 50 | - else if (actionType === TCPObjectModelActionTypeEnum.DOUBLE) { | |
| 51 | - const regex = /^-?\d+(\.\d{0,2})?$/ | |
| 52 | - const values | |
| 53 | - = Math.trunc(value) * zoomFactor | |
| 54 | - + getFloatPart(value) * zoomFactor | |
| 55 | - | |
| 56 | - if (!regex.test(values.toString())) { | |
| 57 | - createMessage.warning(`属性下发值精确到两位小数,缩放因子是${zoomFactor}`) | |
| 58 | - return | |
| 59 | - } | |
| 60 | - | |
| 61 | - const newValue | |
| 62 | - = values === 0 ? [0, 0] : getArray(SingleToHex(values)) | |
| 63 | - params.registerValues = newValue | |
| 64 | - params.registerNumber = 2 | |
| 65 | - params.method = '10' | |
| 66 | - } | |
| 67 | - | |
| 68 | - return await genModbusCommand(params) | |
| 69 | - } | |
| 70 | - | |
| 71 | - /** | |
| 72 | - * | |
| 73 | - * @param extensionDesc 物模型拓展描述符 | |
| 74 | - * @param deviceAddressCode 设备地址码 | |
| 75 | - */ | |
| 76 | - const validateCanGetCommand = (extensionDesc?: ExtensionDesc, deviceAddressCode?: string, createValidateMessage = true) => { | |
| 77 | - const result = { flag: true, message: '' } | |
| 78 | - if (!extensionDesc) { | |
| 79 | - result.flag = false | |
| 80 | - result.message = '当前物模型扩展描述没有填写' | |
| 81 | - } | |
| 82 | - | |
| 83 | - if (!deviceAddressCode) { | |
| 84 | - result.flag = false | |
| 85 | - result.message = '当前设备未绑定设备地址码' | |
| 86 | - } | |
| 87 | - | |
| 88 | - if (result.message && createValidateMessage) | |
| 89 | - createMessage.warning(result.message) | |
| 90 | - | |
| 91 | - return result | |
| 92 | - } | |
| 93 | - | |
| 94 | - return { | |
| 95 | - getModbusCommand, | |
| 96 | - validateCanGetCommand, | |
| 97 | - } | |
| 98 | -} |
| ... | ... | @@ -6,7 +6,7 @@ import { JSONEditor } from '@/components/CodeEditor/src/JSONEditor' |
| 6 | 6 | import { BasicModal, useModalInner } from '@/components/Modal' |
| 7 | 7 | import { useMessage } from '@/hooks/web/useMessage' |
| 8 | 8 | import { useJsonParse } from '@/hooks/business/useJSONParse' |
| 9 | -import { TransportTypeEnum } from '@/enums/datasource' | |
| 9 | +import { TransportTypeEnum } from '@/enums/deviceEnum' | |
| 10 | 10 | |
| 11 | 11 | const emit = defineEmits(['register', 'editComplete']) |
| 12 | 12 | ... | ... |
| ... | ... | @@ -2,8 +2,9 @@ import type { FormSchema } from '@/components/Form' |
| 2 | 2 | import { ComponentEnum } from '@/components/Form/src/enum' |
| 3 | 3 | import type { BasicColumn } from '@/components/Table' |
| 4 | 4 | import type { CommandWayEnum } from '@/enums/commandEnum' |
| 5 | -import type { CodeTypeEnum, ContentDataFieldsEnum, DeviceTypeEnum, TransportTypeEnum } from '@/enums/datasource' | |
| 5 | +import type { ContentDataFieldsEnum } from '@/enums/datasource' | |
| 6 | 6 | import { EventActionTypeEnum, EventActionTypeNameEnum, EventTypeEnum, EventTypeNameEnum } from '@/enums/datasource' |
| 7 | +import type { DeviceTypeEnum, TCPProtocolTypeEnum } from '@/enums/deviceEnum' | |
| 7 | 8 | |
| 8 | 9 | export enum TableColumnFieldEnum { |
| 9 | 10 | DEVICE_ID = 'deviceId', |
| ... | ... | @@ -18,9 +19,8 @@ export interface TableRecordItemType { |
| 18 | 19 | [TableColumnFieldEnum.WAY]?: Nullable<CommandWayEnum> |
| 19 | 20 | [TableColumnFieldEnum.COMMAND]?: Nullable<string | Recordable> |
| 20 | 21 | [ContentDataFieldsEnum.DEVICE_TYPE]?: Nullable<DeviceTypeEnum> |
| 21 | - [ContentDataFieldsEnum.CODE_TYPE]?: Nullable<CodeTypeEnum> | |
| 22 | + [ContentDataFieldsEnum.CODE_TYPE]?: Nullable<TCPProtocolTypeEnum> | |
| 22 | 23 | [ContentDataFieldsEnum.DEVICE_PROFILE_ID]: Nullable<string> |
| 23 | - [ContentDataFieldsEnum.TRANSPORT_TYPE]?: Nullable<TransportTypeEnum> | |
| 24 | 24 | } |
| 25 | 25 | |
| 26 | 26 | export const getFormSchemas = (event: EventTypeEnum): FormSchema[] => { | ... | ... |
| ... | ... | @@ -4,11 +4,14 @@ import { usePublicFormContext } from '../../../usePublicFormContext' |
| 4 | 4 | import { getThingsModelServices } from '@/api/device' |
| 5 | 5 | import { type FormSchema } from '@/components/Form' |
| 6 | 6 | import { ComponentEnum } from '@/components/Form/src/enum' |
| 7 | -import { CommandWayEnum, CommandWayNameEnum } from '@/enums/commandEnum' | |
| 8 | -import { CommandDeliveryWayEnum, CommandDeliveryWayNameEnum, DeviceTypeEnum, EventActionTypeEnum, EventActionTypeNameEnum, EventTypeEnum, EventTypeNameEnum, TransportTypeEnum } from '@/enums/datasource' | |
| 7 | +import { CommandDeliveryWayEnum, CommandDeliveryWayNameEnum, CommandWayEnum, CommandWayNameEnum } from '@/enums/commandEnum' | |
| 8 | +import { EventActionTypeEnum, EventActionTypeNameEnum, EventTypeEnum, EventTypeNameEnum } from '@/enums/datasource' | |
| 9 | 9 | import type { ThingsModel } from '@/api/device/model' |
| 10 | 10 | import { PackageCategoryEnum } from '@/core/Library/enum' |
| 11 | -import { useContentDataStoreWithOut } from '@/store/modules/contentData' | |
| 11 | +import { validateTCPCustomCommand } from '@/core/Library/components/ThingsModelForm' | |
| 12 | +import { JSONEditorValidator } from '@/components/CodeEditor/src/JSONEditor' | |
| 13 | +import { DeviceTypeEnum, TCPProtocolTypeEnum, TransportTypeEnum } from '@/enums/deviceEnum' | |
| 14 | +import { useProductsStoreWithOut } from '@/store/modules/products' | |
| 12 | 15 | |
| 13 | 16 | export enum FormFieldsEnum { |
| 14 | 17 | ACTION_TYPE = 'actionType', |
| ... | ... | @@ -33,17 +36,14 @@ export enum FormFieldsNameEnum { |
| 33 | 36 | SERVICE = '服务', |
| 34 | 37 | } |
| 35 | 38 | |
| 36 | -const contentDataStore = useContentDataStoreWithOut() | |
| 37 | - | |
| 38 | 39 | export const getFormSchemas = (event: EventTypeEnum): FormSchema[] => { |
| 39 | - const { getNodeData, getCellInfo } = usePublicFormContext() | |
| 40 | + const { getNodeData, getCellInfo, getDeviceInfo } = usePublicFormContext() | |
| 40 | 41 | const { dataSourceJson } = unref(getNodeData) || {} |
| 41 | - const { deviceProfileId, deviceInfo, deviceId } = dataSourceJson || {} | |
| 42 | - // transportType:判断是什么类型的设备 code:设备地址码 deviceType:设备类型 | |
| 43 | - let codeType: string | null = '' | |
| 44 | - const { transportType, deviceType, codeType: deviceCodeType } = deviceInfo || {} | |
| 45 | - codeType = deviceCodeType || (deviceId ? contentDataStore.diveceDetailMap?.[deviceId]?.codeType : null) | |
| 46 | - const isTemplate = contentDataStore.isTemplate // 判断是否是模板 | |
| 42 | + const { deviceProfileId } = dataSourceJson || {} | |
| 43 | + const productsStore = useProductsStoreWithOut() | |
| 44 | + const detail = productsStore.getProductDetailById(deviceProfileId!) | |
| 45 | + const { transportType } = unref(getDeviceInfo) || detail || {} | |
| 46 | + | |
| 47 | 47 | return [ |
| 48 | 48 | { |
| 49 | 49 | field: 'eventName', |
| ... | ... | @@ -68,7 +68,6 @@ export const getFormSchemas = (event: EventTypeEnum): FormSchema[] => { |
| 68 | 68 | const options: DefaultOptionType[] = [ |
| 69 | 69 | { label: EventActionTypeNameEnum.OPEN_LINK, value: EventActionTypeEnum.OPEN_LINK }, |
| 70 | 70 | { label: EventActionTypeNameEnum.OPEN_PAGE, value: EventActionTypeEnum.OPEN_PAGE }, |
| 71 | - | |
| 72 | 71 | ] |
| 73 | 72 | |
| 74 | 73 | if (unref(getCellInfo).category.toUpperCase() === PackageCategoryEnum.CONTROL) |
| ... | ... | @@ -93,7 +92,6 @@ export const getFormSchemas = (event: EventTypeEnum): FormSchema[] => { |
| 93 | 92 | field: FormFieldsEnum.OPEN_LINK, |
| 94 | 93 | label: '链接', |
| 95 | 94 | component: ComponentEnum.INPUT, |
| 96 | - // dynamicDisabled: () => !deviceProfileId, | |
| 97 | 95 | required: true, |
| 98 | 96 | ifShow: ({ model }) => model[FormFieldsEnum.ACTION_TYPE] === EventActionTypeEnum.OPEN_LINK, |
| 99 | 97 | }, |
| ... | ... | @@ -115,39 +113,30 @@ export const getFormSchemas = (event: EventTypeEnum): FormSchema[] => { |
| 115 | 113 | component: ComponentEnum.SELECT, |
| 116 | 114 | required: true, |
| 117 | 115 | ifShow: ({ model }) => model[FormFieldsEnum.ACTION_TYPE] === EventActionTypeEnum.PARAMS_SETTING, |
| 116 | + defaultValue: CommandDeliveryWayEnum.CUSTOM, | |
| 118 | 117 | componentProps: ({ formActionType }) => { |
| 119 | 118 | function getOptions() { |
| 120 | 119 | const options = [ |
| 121 | 120 | { label: CommandDeliveryWayNameEnum.CUSTOM, value: CommandDeliveryWayEnum.CUSTOM }, |
| 122 | 121 | ] |
| 123 | 122 | |
| 124 | - const serviceOption = { label: CommandDeliveryWayNameEnum.SERVICE, value: CommandDeliveryWayEnum.SERVICE } | |
| 125 | - const modbusOption = { label: CommandDeliveryWayNameEnum.MODBUS, value: CommandDeliveryWayEnum.MODBUS } | |
| 126 | - | |
| 127 | - function setOptions() { | |
| 128 | - // 是模板的话选择不到设备标识符类型所以就判断为放开自定义命令 | |
| 129 | - if (isTemplate) { | |
| 130 | - if (transportType !== TransportTypeEnum.TCP) | |
| 131 | - options.push(serviceOption) | |
| 132 | - return | |
| 133 | - } | |
| 134 | - | |
| 135 | - if (transportType !== TransportTypeEnum.TCP && deviceType === DeviceTypeEnum.SENSOR) { | |
| 136 | - // 判断不是TCP但是是网关子 | |
| 137 | - options.push(serviceOption) | |
| 138 | - return | |
| 139 | - } | |
| 123 | + const isTCPDevice = unref(getDeviceInfo)?.transportType === TransportTypeEnum.TCP | |
| 124 | + const isTCPModbusDevice = isTCPDevice && unref(getDeviceInfo)?.deviceProfile?.profileData?.transportConfiguration?.protocol === TCPProtocolTypeEnum.MODBUS_RTU | |
| 140 | 125 | |
| 141 | - if (deviceType !== DeviceTypeEnum.SENSOR) | |
| 142 | - options.push(serviceOption) | |
| 126 | + // TCP 自定义设备 自定义 | |
| 127 | + if (isTCPDevice && !isTCPModbusDevice && unref(getDeviceInfo)?.deviceType === DeviceTypeEnum.SENSOR) | |
| 128 | + return options | |
| 143 | 129 | |
| 144 | - if (transportType !== TransportTypeEnum.TCP) return | |
| 145 | - | |
| 146 | - if (codeType !== CommandDeliveryWayEnum.CUSTOM) | |
| 147 | - options.push(modbusOption) | |
| 130 | + // TCP Modbus设备 自定义&Modbus | |
| 131 | + if (isTCPModbusDevice) { | |
| 132 | + const modbusOption = { label: CommandDeliveryWayNameEnum.MODBUS, value: CommandDeliveryWayEnum.MODBUS } | |
| 133 | + options.push(modbusOption) | |
| 134 | + return options | |
| 148 | 135 | } |
| 149 | 136 | |
| 150 | - setOptions() | |
| 137 | + // 其他 自定义&服务 | |
| 138 | + options.push({ label: CommandDeliveryWayNameEnum.SERVICE, value: CommandDeliveryWayEnum.SERVICE }) | |
| 139 | + | |
| 151 | 140 | return options |
| 152 | 141 | } |
| 153 | 142 | |
| ... | ... | @@ -187,20 +176,16 @@ export const getFormSchemas = (event: EventTypeEnum): FormSchema[] => { |
| 187 | 176 | component: ComponentEnum.JSON_EDITOR, |
| 188 | 177 | changeEvent: 'update:value', |
| 189 | 178 | required: true, |
| 179 | + rules: JSONEditorValidator(), | |
| 190 | 180 | ifShow: ({ model }) => transportType !== TransportTypeEnum.TCP && model[FormFieldsEnum.ACTION_TYPE] === EventActionTypeEnum.PARAMS_SETTING && model[FormFieldsEnum.COMMAND_WAY] === CommandDeliveryWayEnum.CUSTOM, |
| 191 | - componentProps: () => { | |
| 192 | - return {} | |
| 193 | - }, | |
| 194 | 181 | }, |
| 195 | 182 | { |
| 196 | 183 | field: FormFieldsEnum.CUSTOM_COMMAND, |
| 197 | 184 | label: FormFieldsNameEnum.COMMAND, |
| 198 | 185 | component: ComponentEnum.INPUT, |
| 199 | 186 | required: true, |
| 187 | + rules: [{ validator: validateTCPCustomCommand }], | |
| 200 | 188 | ifShow: ({ model }) => transportType === TransportTypeEnum.TCP && model[FormFieldsEnum.ACTION_TYPE] === EventActionTypeEnum.PARAMS_SETTING && model[FormFieldsEnum.COMMAND_WAY] === CommandDeliveryWayEnum.CUSTOM, |
| 201 | - componentProps: () => { | |
| 202 | - return {} | |
| 203 | - }, | |
| 204 | 189 | }, |
| 205 | 190 | { |
| 206 | 191 | field: FormFieldsEnum.SERVICE, |
| ... | ... | @@ -224,7 +209,6 @@ export const getFormSchemas = (event: EventTypeEnum): FormSchema[] => { |
| 224 | 209 | const service = formModel[FormFieldsEnum.SERVICE] |
| 225 | 210 | if (service) { |
| 226 | 211 | const thingsModel = unref(options).find(item => item.identifier === service) || null |
| 227 | - // if (thingsModel) | |
| 228 | 212 | formModel[FormFieldsEnum.THINGS_MODEL] = thingsModel |
| 229 | 213 | } |
| 230 | 214 | }, |
| ... | ... | @@ -243,15 +227,8 @@ export const getFormSchemas = (event: EventTypeEnum): FormSchema[] => { |
| 243 | 227 | component: ComponentEnum.THINGS_MODEL_FORM, |
| 244 | 228 | ifShow: ({ model }) => model[FormFieldsEnum.THINGS_MODEL] && model[FormFieldsEnum.COMMAND_WAY] === CommandDeliveryWayEnum.SERVICE, |
| 245 | 229 | changeEvent: 'update:value', |
| 246 | - colSlot: 'serviceCommand', | |
| 230 | + slot: 'serviceCommand', | |
| 247 | 231 | }, |
| 248 | - // { | |
| 249 | - // field: 'deviceInfo', | |
| 250 | - // label: '设备属性数据', | |
| 251 | - // component: ComponentEnum.INPUT, | |
| 252 | - // defaultValue: dataSourceJson, | |
| 253 | - // ifShow: false, | |
| 254 | - // }, | |
| 255 | 232 | { |
| 256 | 233 | field: 'transportType', |
| 257 | 234 | label: '', | ... | ... |
| 1 | -import { toRaw, unref } from 'vue' | |
| 1 | +import { unref } from 'vue' | |
| 2 | 2 | import { getDeviceAttributes, getListByConfigurationId, getListByDeviceProfileIds } from '@/api/device' |
| 3 | 3 | import type { DeviceItemType, ThingsModelItemType } from '@/api/device/model' |
| 4 | 4 | import type { FormSchema } from '@/components/Form' |
| 5 | 5 | import { ComponentEnum } from '@/components/Form/src/enum' |
| 6 | -import { ContentDataFieldsEnum, ContentDataFieldsNameEnum, DataTypeEnum } from '@/enums/datasource' | |
| 6 | +import { ContentDataFieldsEnum, ContentDataFieldsNameEnum } from '@/enums/datasource' | |
| 7 | 7 | import { useContentDataStoreWithOut } from '@/store/modules/contentData' |
| 8 | 8 | import type { ProductAndDevice } from '@/api/content/model' |
| 9 | 9 | import { ControlComponentEnum } from '@/core/Library/packages/Control' |
| 10 | 10 | import { useParseParams } from '@/core/LoadData' |
| 11 | +import { DataTypeEnum } from '@/enums/objectModelEnum' | |
| 11 | 12 | |
| 12 | 13 | const contentDataStore = useContentDataStoreWithOut() |
| 13 | 14 | export const formSchemas = (componentKey?: string): FormSchema[] => { |
| ... | ... | @@ -32,9 +33,8 @@ export const formSchemas = (componentKey?: string): FormSchema[] => { |
| 32 | 33 | return { |
| 33 | 34 | options: (unref(contentDataStore.getProductAndDevice) || []).map((item: ProductAndDevice) => ({ label: item.profileName || item.name, value: item.profileId, transportType: item?.transportType, deviceType: item?.deviceType })), |
| 34 | 35 | placeholder: '请选择产品', |
| 35 | - onSelect(value: string, option: any) { | |
| 36 | + onSelect(value: string) { | |
| 36 | 37 | formModel[ContentDataFieldsEnum.DEVICE_PROFILE_ID] = value |
| 37 | - formModel[ContentDataFieldsEnum.DEVICE_INFO] = value && option ? { transportType: option.transportType, deviceType: option.deviceType, codeType: option?.codeType, deviceName: null } : null | |
| 38 | 38 | }, |
| 39 | 39 | getPopupContainer: () => document.body, |
| 40 | 40 | } |
| ... | ... | @@ -63,8 +63,8 @@ export const formSchemas = (componentKey?: string): FormSchema[] => { |
| 63 | 63 | fieldNames: { label: 'name', value: 'tbDeviceId' }, |
| 64 | 64 | onSelect(value: string, option: DeviceItemType) { |
| 65 | 65 | formModel[ContentDataFieldsEnum.DEVICE_PROFILE_ID] = value ? option.deviceProfileId : null |
| 66 | - formModel[ContentDataFieldsEnum.DEVICE_INFO] = value && option ? { code: option.code, transportType: option.transportType, deviceType: option.deviceType, deviceProfileId: option.deviceProfileId, deviceName: option.alias || option.name || null, codeType: option?.codeType } : null | |
| 67 | 66 | formModel[ContentDataFieldsEnum.ATTR] = null |
| 67 | + formModel[ContentDataFieldsEnum.DEVICE_NAME] = value ? option.alias || option.name : '' | |
| 68 | 68 | }, |
| 69 | 69 | filterOption: (inputValue: string, option: DeviceItemType) => { |
| 70 | 70 | return option.alias?.includes?.(inputValue) || option.name?.includes?.(inputValue) |
| ... | ... | @@ -73,6 +73,12 @@ export const formSchemas = (componentKey?: string): FormSchema[] => { |
| 73 | 73 | }, |
| 74 | 74 | }, |
| 75 | 75 | { |
| 76 | + field: ContentDataFieldsEnum.DEVICE_NAME, | |
| 77 | + label: ContentDataFieldsNameEnum.deviceName, | |
| 78 | + component: ComponentEnum.INPUT, | |
| 79 | + ifShow: false, | |
| 80 | + }, | |
| 81 | + { | |
| 76 | 82 | field: ContentDataFieldsEnum.ATTR, |
| 77 | 83 | label: ContentDataFieldsNameEnum.ATTR, |
| 78 | 84 | component: ComponentEnum.API_SELECT, |
| ... | ... | @@ -83,36 +89,22 @@ export const formSchemas = (componentKey?: string): FormSchema[] => { |
| 83 | 89 | showSearch: true, |
| 84 | 90 | api: async (params: string) => { |
| 85 | 91 | if (!deviceProfileId) return [] |
| 86 | - const options = await getDeviceAttributes(params) | |
| 92 | + let options = await getDeviceAttributes(params) | |
| 87 | 93 | if (componentKey === ControlComponentEnum.SWITCH) { // 开关只返回bool |
| 88 | - return options.filter((item) => { | |
| 94 | + options = options.filter((item) => { | |
| 89 | 95 | return item.detail.dataType.type === DataTypeEnum.BOOL |
| 90 | 96 | }) |
| 91 | 97 | } |
| 98 | + | |
| 92 | 99 | return options |
| 93 | 100 | }, |
| 94 | 101 | params: deviceProfileId, |
| 95 | 102 | fieldNames: { label: 'name', value: 'identifier' }, |
| 96 | - onSelect(value: string, option: ThingsModelItemType) { | |
| 97 | - formModel[ContentDataFieldsEnum.ATTR_INFO] = value && option ? toRaw(unref(option)) : null | |
| 98 | - }, | |
| 99 | 103 | filterOption: (inputValue: string, option: ThingsModelItemType) => { |
| 100 | 104 | return option.name.includes(inputValue) |
| 101 | 105 | }, |
| 102 | 106 | } |
| 103 | 107 | }, |
| 104 | 108 | }, |
| 105 | - { | |
| 106 | - field: ContentDataFieldsEnum.ATTR_INFO, | |
| 107 | - label: ContentDataFieldsNameEnum.ATTR_INFO, | |
| 108 | - component: ComponentEnum.INPUT, | |
| 109 | - ifShow: false, | |
| 110 | - }, | |
| 111 | - { | |
| 112 | - field: ContentDataFieldsEnum.DEVICE_INFO, | |
| 113 | - label: ContentDataFieldsNameEnum.DEVICE_INFO, | |
| 114 | - component: ComponentEnum.INPUT, | |
| 115 | - ifShow: false, | |
| 116 | - }, | |
| 117 | 109 | ] |
| 118 | 110 | } | ... | ... |
| ... | ... | @@ -58,7 +58,7 @@ const getEventJson = (): NodeDataEventJsonType => { |
| 58 | 58 | |
| 59 | 59 | Object.keys(status).forEach((key) => { |
| 60 | 60 | if (eventJson[key as EventTypeEnum]) |
| 61 | - eventJson[key as EventTypeEnum].enable = status[key as EventTypeEnum] | |
| 61 | + eventJson[key as EventTypeEnum]!.enable = status[key as EventTypeEnum] | |
| 62 | 62 | }) |
| 63 | 63 | |
| 64 | 64 | return eventJson |
| ... | ... | @@ -111,7 +111,7 @@ const handleSave = async () => { |
| 111 | 111 | if (operationPassword.checked) |
| 112 | 112 | await validatePassword() |
| 113 | 113 | if (contentDataStore.getIsTemplate)// 判断组态是不是模板 |
| 114 | - dataSourceJson = { ...value, deviceProfileId: value?.deviceProfileTemplateId, deviceId: null } | |
| 114 | + dataSourceJson = { ...value, deviceProfileId: value?.deviceProfileTemplateId, deviceId: null, deviceName: null } | |
| 115 | 115 | await saveNodeAllData({ dataSourceJson, eventJson: { ...eventJson, OPERATION_PASSWORD: unref(getCellInfo).category === PackageCategoryEnum.CONTROL ? operationPassword : undefined }, actJson }) |
| 116 | 116 | createMessage.success('操作成功') |
| 117 | 117 | savePageContent() |
| ... | ... | @@ -190,7 +190,7 @@ createPublicFormContext(nodeDataActinType) |
| 190 | 190 | </div> |
| 191 | 191 | </Checkbox> |
| 192 | 192 | <Form> |
| 193 | - <FormItem :validate-status="getValidateStatus(operationPassword.value)"> | |
| 193 | + <FormItem :validate-status="getValidateStatus(operationPassword.value!)"> | |
| 194 | 194 | <InputPassword |
| 195 | 195 | v-model:value="operationPassword.value" class="mt-1" :disabled="!operationPassword.checked" |
| 196 | 196 | placeholder="请输入操作密码" | ... | ... |
| 1 | +import { validateTCPCustomCommand } from '.' | |
| 1 | 2 | import type { Specs, StructJSON } from '@/api/device/model' |
| 2 | 3 | import { type FormSchema } from '@/components/Form' |
| 3 | 4 | import { ComponentEnum } from '@/components/Form/src/enum' |
| 4 | -import { DataTypeEnum } from '@/enums/datasource' | |
| 5 | +import { TransportTypeEnum } from '@/enums/deviceEnum' | |
| 6 | +import { DataTypeEnum } from '@/enums/objectModelEnum' | |
| 7 | + | |
| 8 | +export enum FormFieldsEnum { | |
| 9 | + SERVICE_COMMAND = 'serviceCommand', | |
| 10 | +} | |
| 5 | 11 | |
| 6 | 12 | export const getFormSchemas = ({ structJSON: structJson, required, transportType }: { structJSON: StructJSON[]; required?: boolean; transportType?: string }): FormSchema[] => { |
| 7 | 13 | const createInputNumber = ({ |
| ... | ... | @@ -120,10 +126,11 @@ export const getFormSchemas = ({ structJSON: structJson, required, transportType |
| 120 | 126 | |
| 121 | 127 | const createTCPServiceCommandInput = ({ serviceCommand }: StructJSON): FormSchema => { |
| 122 | 128 | return { |
| 123 | - field: 'serviceCommand', | |
| 129 | + field: FormFieldsEnum.SERVICE_COMMAND, | |
| 124 | 130 | label: '服务命令', |
| 125 | 131 | component: ComponentEnum.INPUT, |
| 126 | 132 | required, |
| 133 | + rules: [{ validator: validateTCPCustomCommand }], | |
| 127 | 134 | componentProps: { |
| 128 | 135 | defaultValue: serviceCommand, |
| 129 | 136 | }, |
| ... | ... | @@ -134,8 +141,12 @@ export const getFormSchemas = ({ structJSON: structJson, required, transportType |
| 134 | 141 | for (const item of structJson) { |
| 135 | 142 | const { dataType } = item |
| 136 | 143 | const { type } = dataType || {} |
| 137 | - if (transportType === 'TCP') | |
| 138 | - schemas.push(createTCPServiceCommandInput(item)) | |
| 144 | + | |
| 145 | + if (transportType === TransportTypeEnum.TCP) { | |
| 146 | + item.serviceCommand && schemas.push(createTCPServiceCommandInput(item)) | |
| 147 | + break | |
| 148 | + } | |
| 149 | + | |
| 139 | 150 | if (type === DataTypeEnum.BOOL) |
| 140 | 151 | schemas.push(createSelect(item)) |
| 141 | 152 | else if (type === DataTypeEnum.ENUM) | ... | ... |
| 1 | 1 | <script setup lang="ts"> |
| 2 | 2 | import { Card } from 'ant-design-vue' |
| 3 | -import { computed, nextTick, reactive, unref, watch } from 'vue' | |
| 4 | -import type { ComponentExposeType } from '../PublicForm' | |
| 3 | +import { computed, nextTick, reactive, ref, unref, watch } from 'vue' | |
| 5 | 4 | import { getFormSchemas } from './config' |
| 6 | 5 | import { ThingsModelForm } from '.' |
| 7 | 6 | import type { StructJSON } from '@/api/device/model' |
| 8 | 7 | import { BasicForm, useForm } from '@/components/Form' |
| 9 | -import { DataTypeEnum } from '@/enums/datasource' | |
| 10 | 8 | import { FormLabelAlignEnum, FormLayoutEnum } from '@/components/Form/src/enum' |
| 9 | +import { deepMerge } from '@/utils' | |
| 10 | +import { DataTypeEnum } from '@/enums/objectModelEnum' | |
| 11 | 11 | |
| 12 | -const props = withDefaults(defineProps<{ | |
| 13 | - value: Recordable | |
| 12 | +interface ThingsModelFormPropsType { | |
| 13 | + value?: Recordable | |
| 14 | 14 | inputData?: StructJSON[] |
| 15 | 15 | required?: boolean |
| 16 | 16 | title?: string |
| 17 | 17 | transportType?: string |
| 18 | -}>(), { | |
| 18 | +} | |
| 19 | + | |
| 20 | +const props = withDefaults(defineProps<ThingsModelFormPropsType>(), { | |
| 19 | 21 | inputData: () => [], |
| 20 | 22 | required: true, |
| 21 | 23 | }) |
| 22 | 24 | |
| 25 | +const propsRef = ref<Partial<ThingsModelFormPropsType>>({}) | |
| 26 | + | |
| 27 | +const getProps = computed<ThingsModelFormPropsType>(() => ({ ...props, ...unref(propsRef) })) | |
| 28 | + | |
| 23 | 29 | const thingsModelFormListElMap = reactive<Record<string, { el: InstanceType<typeof ThingsModelForm>; structJSON: StructJSON }>>({}) |
| 24 | 30 | |
| 25 | 31 | const [register, formActionType] = useForm({ |
| 26 | - schemas: getFormSchemas({ structJSON: props.inputData || [], required: props.required, transportType: props.transportType }), | |
| 32 | + schemas: getFormSchemasByProps(), | |
| 27 | 33 | showActionButtonGroup: false, |
| 34 | + name: Math.random().toString(16).substring(2), | |
| 28 | 35 | labelWidth: 100, |
| 29 | 36 | labelAlign: FormLabelAlignEnum.RIGHT, |
| 30 | 37 | layout: FormLayoutEnum.HORIZONTAL, |
| 31 | 38 | }) |
| 32 | 39 | |
| 33 | 40 | const getStructFormItem = computed(() => { |
| 34 | - const { inputData } = props | |
| 35 | - return inputData.filter(item => item.dataType?.type === DataTypeEnum.STRUCT) | |
| 41 | + const { inputData } = unref(getProps) | |
| 42 | + return (inputData || []).filter(item => item.dataType?.type === DataTypeEnum.STRUCT) | |
| 36 | 43 | }) |
| 37 | 44 | |
| 38 | 45 | const setFormElRef = (el: InstanceType<typeof ThingsModelForm>, structJSON: StructJSON) => { |
| ... | ... | @@ -68,11 +75,7 @@ const validate = async () => { |
| 68 | 75 | await thingsModelFormListElMap[key].el.validate() |
| 69 | 76 | } |
| 70 | 77 | |
| 71 | -const updateSchema = (value: Recordable) => { | |
| 72 | - return formActionType.updateSchema(value) | |
| 73 | -} | |
| 74 | - | |
| 75 | -watch(() => props.value, | |
| 78 | +watch(() => unref(getProps).value, | |
| 76 | 79 | async (value) => { |
| 77 | 80 | await nextTick() |
| 78 | 81 | formActionType.setFieldsValue(value || {}) |
| ... | ... | @@ -80,25 +83,31 @@ watch(() => props.value, |
| 80 | 83 | { immediate: true }, |
| 81 | 84 | ) |
| 82 | 85 | |
| 86 | +function getFormSchemasByProps() { | |
| 87 | + return getFormSchemas({ structJSON: unref(getProps).inputData || [], required: unref(getProps).required, transportType: unref(getProps).transportType }) | |
| 88 | +} | |
| 89 | + | |
| 83 | 90 | watch( |
| 84 | - () => props.inputData, | |
| 91 | + () => unref(getProps).inputData, | |
| 85 | 92 | (value) => { |
| 86 | - if (value && value.length) { | |
| 87 | - const schemas = getFormSchemas({ structJSON: props.inputData || [], required: props.required, transportType: props.transportType }) | |
| 88 | - formActionType.setProps({ schemas }) | |
| 89 | - } | |
| 93 | + if (value && value.length) | |
| 94 | + formActionType.setProps({ schemas: getFormSchemasByProps() }) | |
| 90 | 95 | }) |
| 91 | 96 | |
| 92 | -defineExpose<ComponentExposeType>({ | |
| 97 | +function setProps(props: Partial<ThingsModelFormPropsType>) { | |
| 98 | + propsRef.value = deepMerge(unref(propsRef) || {}, props) as any | |
| 99 | +} | |
| 100 | + | |
| 101 | +defineExpose({ | |
| 93 | 102 | getFieldsValue, |
| 94 | 103 | setFieldsValue, |
| 95 | 104 | validate, |
| 96 | - updateSchema, | |
| 105 | + setProps, | |
| 97 | 106 | }) |
| 98 | 107 | </script> |
| 99 | 108 | |
| 100 | 109 | <template> |
| 101 | - <Card class="!border-2 !border-dashed" :title="title"> | |
| 110 | + <Card class="!border-2 !border-dashed" :title="getProps.title"> | |
| 102 | 111 | <BasicForm class="things-model-form" @register="register"> |
| 103 | 112 | <template v-for="item in getStructFormItem" #[item.identifier]="{ model, field }" :key="item.identifier"> |
| 104 | 113 | <ThingsModelForm | ... | ... |
| ... | ... | @@ -6,3 +6,10 @@ export function useLatestMessageValue(message: SubscriptionData, attr: string) { |
| 6 | 6 | const [ts, latestValue = null] = lateset || [] |
| 7 | 7 | return { ts, latestValue } |
| 8 | 8 | } |
| 9 | + | |
| 10 | +export function useLatestMultipleMessageValue(message: SubscriptionData, attr: string[], Fn: (attribute: string, value: any, timespan: number) => void) { | |
| 11 | + attr.forEach((item) => { | |
| 12 | + const [ts = null, latestValue = null] = message[item][0] || [] | |
| 13 | + Fn(item, ts, latestValue) | |
| 14 | + }) | |
| 15 | +} | ... | ... |
| ... | ... | @@ -7,6 +7,8 @@ import { CellAttributeKeyEnum } from '@/enums/cellAttributeEnum' |
| 7 | 7 | import { useContentDataStoreWithOut } from '@/store/modules/contentData' |
| 8 | 8 | import { useMessage } from '@/hooks/web/useMessage' |
| 9 | 9 | import { MessageEnum } from '@/enums/messageEnum' |
| 10 | +import type { DeviceItemType } from '@/api/device/model' | |
| 11 | +import { getDeviceInfo } from '@/api/device' | |
| 10 | 12 | |
| 11 | 13 | interface UseNodeDataParamsType { |
| 12 | 14 | cell: MxCell |
| ... | ... | @@ -35,9 +37,9 @@ export function useNodeData({ cell, immediate = true }: UseNodeDataParamsType) { |
| 35 | 37 | const { createMessage } = useMessage() |
| 36 | 38 | |
| 37 | 39 | const nodeData = ref<NodeDataType>() |
| 40 | + const deviceInfo = ref<DeviceItemType>() | |
| 38 | 41 | |
| 39 | 42 | const contentDataStore = useContentDataStoreWithOut() |
| 40 | - // const contentId = window.DrawApp.configurationContentId! | |
| 41 | 43 | const { configurationId } = useParseParams() |
| 42 | 44 | |
| 43 | 45 | const getCellID = () => { |
| ... | ... | @@ -64,9 +66,15 @@ export function useNodeData({ cell, immediate = true }: UseNodeDataParamsType) { |
| 64 | 66 | } |
| 65 | 67 | }) |
| 66 | 68 | |
| 69 | + const getDeviceDetail = async () => { | |
| 70 | + if (!unref(nodeData)?.dataSourceJson?.deviceId) return | |
| 71 | + deviceInfo.value = await getDeviceInfo(unref(nodeData)!.dataSourceJson.deviceId) | |
| 72 | + } | |
| 73 | + | |
| 67 | 74 | const getNodeAllData = async () => { |
| 68 | 75 | const result = await doGetNodeBindData(basicNodeBindData(ActionType.GET)) |
| 69 | 76 | nodeData.value = result || getDefaultNodeData() |
| 77 | + await getDeviceDetail() | |
| 70 | 78 | return result |
| 71 | 79 | } |
| 72 | 80 | |
| ... | ... | @@ -79,6 +87,8 @@ export function useNodeData({ cell, immediate = true }: UseNodeDataParamsType) { |
| 79 | 87 | const result = await doSaveNodeAllData(Object.assign(data, basicNodeBindData(ActionType.SAVE))) |
| 80 | 88 | removeCellSourceAttribute(cell) |
| 81 | 89 | nodeData.value = result || getDefaultNodeData() |
| 90 | + | |
| 91 | + await getDeviceDetail() | |
| 82 | 92 | return result |
| 83 | 93 | } |
| 84 | 94 | |
| ... | ... | @@ -123,6 +133,7 @@ export function useNodeData({ cell, immediate = true }: UseNodeDataParamsType) { |
| 123 | 133 | }) |
| 124 | 134 | |
| 125 | 135 | return { |
| 136 | + getDeviceInfo: computed(() => unref(deviceInfo)), | |
| 126 | 137 | getNodeData: computed(() => unref(nodeData)), |
| 127 | 138 | getCellInfo, |
| 128 | 139 | getNodeAllData, | ... | ... |
| 1 | -import { h, render, unref } from 'vue' | |
| 1 | +import { h, render, toRaw, unref } from 'vue' | |
| 2 | 2 | import { ControlComponentEnum } from '../packages/Control' |
| 3 | -import { useGetModbusCommand } from '../components/PublicForm/components/DataEvents/CommandDeliveryModal/useGetModbusCommand' | |
| 4 | -import { doCommandDelivery, getDeviceActive, getDeviceInfo } from '@/api/device' | |
| 3 | +import { doCommandDelivery, getDeviceActive } from '@/api/device' | |
| 5 | 4 | import type { MouseUpEventDataType, NodeDataDataSourceJsonType, NodeDataEventJsonType, SingleClickEventDataType } from '@/api/node/model' |
| 6 | -import { CommandWayEnum } from '@/enums/commandEnum' | |
| 7 | -import { ActRangListItemTypeEnum, CodeTypeEnum, EventActionTypeEnum, TransportTypeEnum } from '@/enums/datasource' | |
| 5 | +import { CommandMethodEnum, CommandTypeEnum, CommandWayEnum } from '@/enums/commandEnum' | |
| 6 | +import { ActRangListItemTypeEnum, EventActionTypeEnum } from '@/enums/datasource' | |
| 8 | 7 | import { useMessage } from '@/hooks/web/useMessage' |
| 9 | -import { AttributeDeliverModal, CommandDeliveryConfirmModal, CommandDeliveryModal, ConfirmModal } from '@/core/Library/components/PublicForm/components/DataEvents/CommandDeliveryModal' | |
| 8 | +import { AttributeDeliverModal, CommandDeliveryModal, SwitchCommandDeliveryModal } from '@/core/Library/components/PublicForm/components/DataEvents/CommandDeliveryModal' | |
| 10 | 9 | import type { MxCell } from '@/fitCore/types' |
| 11 | 10 | import { NodeUtils } from '@/hooks/business/useNodeUtils' |
| 12 | 11 | import { useContentDataStoreWithOut } from '@/store/modules/contentData' |
| 13 | -import type { RpcCommandType } from '@/api/device/model' | |
| 14 | 12 | |
| 15 | 13 | export function useNodeEvent(eventJson: NodeDataEventJsonType, dataSourceJson: NodeDataDataSourceJsonType, cell: MxCell) { |
| 16 | 14 | const { createMessage } = useMessage() |
| 17 | 15 | |
| 16 | + /** | |
| 17 | + * @description 多命令下发 | |
| 18 | + * @param data | |
| 19 | + * @returns | |
| 20 | + */ | |
| 18 | 21 | const handlerCommandDelivery = async (data: MouseUpEventDataType) => { |
| 19 | 22 | try { |
| 20 | 23 | const promiseList: (() => Promise<any>)[] = [] |
| ... | ... | @@ -34,8 +37,8 @@ export function useNodeEvent(eventJson: NodeDataEventJsonType, dataSourceJson: N |
| 34 | 37 | way, |
| 35 | 38 | deviceId, |
| 36 | 39 | command: { |
| 37 | - additionalInfo: { cmdType: 'API' }, | |
| 38 | - method: 'methodThingskit', | |
| 40 | + additionalInfo: { cmdType: CommandTypeEnum.API }, | |
| 41 | + method: CommandMethodEnum.THINGSKIT, | |
| 39 | 42 | params: command, |
| 40 | 43 | persistent: true, |
| 41 | 44 | }, |
| ... | ... | @@ -51,11 +54,19 @@ export function useNodeEvent(eventJson: NodeDataEventJsonType, dataSourceJson: N |
| 51 | 54 | } |
| 52 | 55 | } |
| 53 | 56 | |
| 57 | + /** | |
| 58 | + * @description 打开链接 | |
| 59 | + * @param data | |
| 60 | + */ | |
| 54 | 61 | const handleOpenLink = (data: SingleClickEventDataType) => { |
| 55 | 62 | const { openLink } = data |
| 56 | 63 | window.open(openLink) |
| 57 | 64 | } |
| 58 | 65 | |
| 66 | + /** | |
| 67 | + * @description 跳转页面 | |
| 68 | + * @param data | |
| 69 | + */ | |
| 59 | 70 | const handleSwitchPage = (data: SingleClickEventDataType) => { |
| 60 | 71 | const { openPage } = data |
| 61 | 72 | const pages = window.DrawApp.pages |
| ... | ... | @@ -64,75 +75,36 @@ export function useNodeEvent(eventJson: NodeDataEventJsonType, dataSourceJson: N |
| 64 | 75 | window.DrawApp.selectPage(openPageFile) |
| 65 | 76 | } |
| 66 | 77 | |
| 67 | - const handleParamsSetting = async (_data: SingleClickEventDataType) => { | |
| 68 | - const deviceInfo = dataSourceJson | |
| 69 | - | |
| 78 | + const handleParamsSetting = async (data: SingleClickEventDataType) => { | |
| 70 | 79 | const instance = h(CommandDeliveryModal) |
| 71 | 80 | render(instance, document.body); |
| 72 | - (instance.component?.exposed as InstanceType<typeof CommandDeliveryModal>)?.open({ ..._data, deviceInfo, operationPassword: eventJson?.OPERATION_PASSWORD }) | |
| 81 | + (instance.component?.exposed as InstanceType<typeof CommandDeliveryModal>)?.open({ dataSource: toRaw(unref(dataSourceJson)), eventBindData: data, operationPasswordInfo: eventJson.OPERATION_PASSWORD }) | |
| 73 | 82 | } |
| 74 | 83 | |
| 75 | 84 | const handleAttributeDelivery = async (data: SingleClickEventDataType) => { |
| 76 | 85 | try { |
| 77 | 86 | const nodeUtils = new NodeUtils() |
| 78 | - const { way = CommandWayEnum.ONE_WAY } = data | |
| 79 | - const { attr, deviceId, attrInfo } = dataSourceJson | |
| 80 | - const { transportType, alias, name: deviceName, code, codeType } = await getDeviceInfo(dataSourceJson.deviceId) || {}// 设备信息 | |
| 81 | - const contentDataStore = useContentDataStoreWithOut() | |
| 82 | - const currentData = contentDataStore.contentData.find(item => item.id === cell.getId()) | |
| 83 | - if (!currentData) return | |
| 84 | - const { actJson, eventJson } = currentData | |
| 85 | - const { value: operationPassword, checked: operationPasswordEnable } = eventJson.OPERATION_PASSWORD | |
| 87 | + const operationPasswordInfo = toRaw(unref(eventJson.OPERATION_PASSWORD)) | |
| 86 | 88 | |
| 87 | - const command: RpcCommandType = { | |
| 88 | - additionalInfo: { cmdType: 'API' }, | |
| 89 | - method: 'methodThingskit', | |
| 90 | - params: {}, | |
| 91 | - persistent: true, | |
| 92 | - } | |
| 93 | 89 | if (nodeUtils.getNodeComponentKey(cell) === ControlComponentEnum.SWITCH) { |
| 90 | + const contentDataStore = useContentDataStoreWithOut() | |
| 91 | + const currentData = contentDataStore.contentData.find(item => item.id === cell.getId()) | |
| 92 | + if (!currentData) return | |
| 93 | + const { actJson } = currentData | |
| 94 | 94 | const { rangeList } = actJson.STATUS_SETTING |
| 95 | 95 | const status = cell.getAttribute('SWITCH_STATUS') |
| 96 | 96 | const res = rangeList.find(item => item.type === (status === ActRangListItemTypeEnum.OPEN ? ActRangListItemTypeEnum.CLOSE : ActRangListItemTypeEnum.OPEN)) |
| 97 | 97 | if (!res) return |
| 98 | 98 | const { statusValue } = res |
| 99 | 99 | |
| 100 | - command.params = transportType === TransportTypeEnum.TCP | |
| 101 | - ? statusValue! | |
| 102 | - : { | |
| 103 | - [attr]: statusValue, | |
| 104 | - } | |
| 105 | - | |
| 106 | - if (operationPasswordEnable) { | |
| 107 | - const instance = h(CommandDeliveryConfirmModal) | |
| 108 | - render(instance, document.body) | |
| 109 | - await (instance.component?.exposed as InstanceType<typeof CommandDeliveryConfirmModal>)?.open(operationPassword!) | |
| 110 | - } | |
| 111 | - else { | |
| 112 | - const instance = h(ConfirmModal) | |
| 113 | - render(instance, document.body) | |
| 114 | - await (instance.component?.exposed as InstanceType<typeof ConfirmModal>)?.open() | |
| 115 | - } | |
| 116 | - | |
| 117 | - if (transportType === TransportTypeEnum.TCP && codeType === CodeTypeEnum.MODBUS_RTU) { | |
| 118 | - const { validateCanGetCommand, getModbusCommand } = useGetModbusCommand() | |
| 119 | - if (!validateCanGetCommand(unref(dataSourceJson).attrInfo.extensionDesc, code).flag) return | |
| 120 | - const res = await getModbusCommand(statusValue!, unref(dataSourceJson).attrInfo.extensionDesc!, code) | |
| 121 | - if (!res) return | |
| 122 | - command.params = res | |
| 123 | - } | |
| 124 | - | |
| 125 | - await doCommandDelivery({ way, command, deviceId }) | |
| 126 | - createMessage.success('命令下发成功') | |
| 100 | + const instance = h(SwitchCommandDeliveryModal) | |
| 101 | + render(instance, document.body) | |
| 102 | + await (instance.component?.exposed as InstanceType<typeof SwitchCommandDeliveryModal>)?.open({ operationPasswordInfo, eventBindData: toRaw(unref(data)), dataSourceJson: toRaw(unref(dataSourceJson)), sendValue: statusValue }!) | |
| 127 | 103 | } |
| 128 | 104 | else { |
| 129 | 105 | const instance = h(AttributeDeliverModal) |
| 130 | - render(instance, document.body) | |
| 131 | - const value = await (instance.component?.exposed as InstanceType<typeof AttributeDeliverModal>)?.open({ title: `${alias || deviceName}-${attrInfo.name}`, operationPassword, operationPasswordEnable, dataSourceJson }) as string | |
| 132 | - command.params = transportType === TransportTypeEnum.TCP ? value : { [attr]: value } | |
| 133 | - if (!command.params) return | |
| 134 | - await doCommandDelivery({ way, command, deviceId }) | |
| 135 | - createMessage.success('命令下发成功') | |
| 106 | + render(instance, document.body); | |
| 107 | + (instance.component?.exposed as InstanceType<typeof AttributeDeliverModal>)?.open({ dataSourceJson, operationPasswordInfo, eventBindData: data }) | |
| 136 | 108 | } |
| 137 | 109 | } |
| 138 | 110 | catch (error) { | ... | ... |
src/core/Library/hook/useProduct.ts
0 → 100644
| 1 | +import type { ConfigurationContentType } from '@/api/content/model' | |
| 2 | +import { getProductsDetailWithThingsModel } from '@/api/device' | |
| 3 | +import { useProductsStoreWithOut } from '@/store/modules/products' | |
| 4 | + | |
| 5 | +export function useProduct() { | |
| 6 | + const getAllDeviceProfileIds = async (contentData: ConfigurationContentType) => { | |
| 7 | + const deviceProfileIds = contentData.productAndDevice?.map(item => item.profileId) || [] | |
| 8 | + const result = await getProductsDetailWithThingsModel({ deviceProfileIds, functionTypeEnum: 'all' }) | |
| 9 | + const productsStore = useProductsStoreWithOut() | |
| 10 | + productsStore.setProducts(result.reduce((prev, next) => ({ ...prev, [next.id]: next }), {})) | |
| 11 | + } | |
| 12 | + | |
| 13 | + return { | |
| 14 | + getAllDeviceProfileIds, | |
| 15 | + } | |
| 16 | +} | ... | ... |
| ... | ... | @@ -8,8 +8,10 @@ import { ComponentEnum } from '@/components/Form/src/enum' |
| 8 | 8 | import { useContentDataStoreWithOut } from '@/store/modules/contentData' |
| 9 | 9 | import { DateFormatEnum } from '@/enums/timeEnum' |
| 10 | 10 | import type { BasicColumn } from '@/components/Table' |
| 11 | -import { AlarmStatusEnum, type CodeTypeEnum, type ContentDataFieldsEnum } from '@/enums/datasource' | |
| 11 | +import { type ContentDataFieldsEnum } from '@/enums/datasource' | |
| 12 | 12 | import { useParseParams } from '@/core/LoadData' |
| 13 | +import type { TCPProtocolTypeEnum } from '@/enums/deviceEnum' | |
| 14 | +import { AlarmStatusEnum } from '@/enums/alarmEnum' | |
| 13 | 15 | |
| 14 | 16 | export const tableColumns: BasicColumn[] = [ |
| 15 | 17 | { |
| ... | ... | @@ -27,7 +29,7 @@ export const tableColumns: BasicColumn[] = [ |
| 27 | 29 | export interface TableRecordItemType { |
| 28 | 30 | uuid: string |
| 29 | 31 | [TableColumnFieldEnum.DEVICE_ID]?: Nullable<string> |
| 30 | - [ContentDataFieldsEnum.CODE_TYPE]?: Nullable<CodeTypeEnum> | |
| 32 | + [ContentDataFieldsEnum.CODE_TYPE]?: Nullable<TCPProtocolTypeEnum> | |
| 31 | 33 | } |
| 32 | 34 | |
| 33 | 35 | export const AlarmColorMap = { | ... | ... |
| ... | ... | @@ -8,8 +8,8 @@ import type { AlarmListItemType } from '@/api/alarm/model' |
| 8 | 8 | import type { CreateComponentType } from '@/core/Library/types' |
| 9 | 9 | import { useContentDataStore } from '@/store/modules/contentData' |
| 10 | 10 | import { isLightboxMode, isShareMode } from '@/utils/env' |
| 11 | -import { AlarmStatusColorEnum, AlarmStatusEnum, AlarmStatusNameEnum } from '@/enums/datasource' | |
| 12 | 11 | import { formatToDateTime } from '@/utils/dateUtil' |
| 12 | +import { AlarmStatusColorEnum, AlarmStatusNameEnum } from '@/enums/alarmEnum' | |
| 13 | 13 | |
| 14 | 14 | const props = defineProps<{ |
| 15 | 15 | config: CreateComponentType |
| ... | ... | @@ -67,7 +67,7 @@ onMounted(async () => { |
| 67 | 67 | useIntervalFn(initFetchAlarmList, (initOptions.polling || 30) * 1000) |
| 68 | 68 | } |
| 69 | 69 | else { |
| 70 | - const statusList = Object.values(AlarmStatusEnum) | |
| 70 | + const statusList = Object.values(AlarmStatusNameEnum) | |
| 71 | 71 | Object.assign(initOptions, { |
| 72 | 72 | scroll: false, |
| 73 | 73 | interval: 0, | ... | ... |
| ... | ... | @@ -75,6 +75,15 @@ const init = () => { |
| 75 | 75 | videoPlayInstance.value = videoJs(unref(videoPlayEl)!, unref(getOptions), () => { |
| 76 | 76 | emit('ready', unref(videoPlayInstance)) |
| 77 | 77 | }) |
| 78 | + | |
| 79 | + unref(videoPlayInstance)?.on('timeupdate', () => { | |
| 80 | + if (!unref(videoPlayInstance)) return | |
| 81 | + | |
| 82 | + const differTime = unref(videoPlayInstance)!.buffered().end(0) - unref(videoPlayInstance)!.currentTime() | |
| 83 | + | |
| 84 | + if (differTime > 10) | |
| 85 | + init() | |
| 86 | + }) | |
| 78 | 87 | } |
| 79 | 88 | |
| 80 | 89 | const customInit = (getOptionsFn: (optios: VideoJsPlayerOptions) => VideoJsPlayerOptions) => { | ... | ... |
| ... | ... | @@ -2,7 +2,7 @@ |
| 2 | 2 | import { Button, Divider } from 'ant-design-vue' |
| 3 | 3 | import { nextTick, onMounted, ref, unref } from 'vue' |
| 4 | 4 | import { getOrganization } from '@/api/device' |
| 5 | -import { getVideoList } from '@/api/video' | |
| 5 | +import { getCameraList } from '@/api/video' | |
| 6 | 6 | import { BasicForm, useForm } from '@/components/Form' |
| 7 | 7 | import { ComponentEnum, FormLayoutEnum } from '@/components/Form/src/enum' |
| 8 | 8 | import { ContentDataFieldsEnum, ContentDataFieldsNameEnum } from '@/enums/datasource' |
| ... | ... | @@ -13,6 +13,8 @@ import { createPublicFormContext } from '@/core/Library/components/PublicForm/us |
| 13 | 13 | import type { NodeDataDataSourceJsonType, VideoOptionType } from '@/api/node/model' |
| 14 | 14 | import { useMessage } from '@/hooks/web/useMessage' |
| 15 | 15 | import { useSavePageContent } from '@/core/Library/hook/useSavePageContent' |
| 16 | +import { VideoAccessModeEnum } from '@/enums/videoEnum' | |
| 17 | +import type { VideoItemRecordType } from '@/api/video/model' | |
| 16 | 18 | |
| 17 | 19 | const props = defineProps<ConfigComponentProps>() |
| 18 | 20 | const { organizationId } = useParseParams() |
| ... | ... | @@ -20,6 +22,23 @@ const nodeDataActinType = useNodeData({ cell: props.cell!, immediate: false }) |
| 20 | 22 | |
| 21 | 23 | const { getNodeData, getNodeAllData, saveNodeAllData } = nodeDataActinType |
| 22 | 24 | |
| 25 | +enum VideoFormFieldsEnum { | |
| 26 | + VIDEO_ID = 'id', | |
| 27 | + VIDEO_URL = 'videoUrl', | |
| 28 | + ACCESS_MODE = 'accessMode', | |
| 29 | + VIDEO_FLAG = 'videoComponentFlag', | |
| 30 | + CHANNEL_ID = 'channelId', | |
| 31 | + DEVICE_ID = 'deviceId', | |
| 32 | +} | |
| 33 | + | |
| 34 | +enum VideoFormFieldsNameEnum { | |
| 35 | + VIDEO_ID = '视频流', | |
| 36 | + ACCESS_MODE = 'ACCESS_MODE', | |
| 37 | + VIDEO_URL = '视频地址', | |
| 38 | + CHANNEL_ID = '通道号', | |
| 39 | + DEVICE_ID = '设备id', | |
| 40 | +} | |
| 41 | + | |
| 23 | 42 | const loading = ref(false) |
| 24 | 43 | |
| 25 | 44 | const [register, { getFieldsValue, validate, setFieldsValue }] = useForm({ |
| ... | ... | @@ -36,45 +55,67 @@ const [register, { getFieldsValue, validate, setFieldsValue }] = useForm({ |
| 36 | 55 | labelField: 'name', |
| 37 | 56 | valueField: 'id', |
| 38 | 57 | onChange() { |
| 39 | - formModel[ContentDataFieldsEnum.VIDEO_ID] = null | |
| 58 | + formModel[VideoFormFieldsEnum.VIDEO_ID] = null | |
| 59 | + formModel[VideoFormFieldsEnum.CHANNEL_ID] = null | |
| 60 | + formModel[VideoFormFieldsEnum.DEVICE_ID] = null | |
| 40 | 61 | }, |
| 41 | 62 | } |
| 42 | 63 | }, |
| 43 | 64 | }, |
| 44 | 65 | { |
| 45 | - field: ContentDataFieldsEnum.VIDEO_ID, | |
| 46 | - label: ContentDataFieldsNameEnum.VIDEO_ID, | |
| 66 | + field: VideoFormFieldsEnum.VIDEO_ID, | |
| 67 | + label: VideoFormFieldsNameEnum.VIDEO_ID, | |
| 47 | 68 | component: ComponentEnum.API_SELECT, |
| 69 | + required: true, | |
| 48 | 70 | componentProps: ({ formModel }) => { |
| 49 | 71 | return { |
| 50 | - api: getVideoList, | |
| 51 | - params: formModel[ContentDataFieldsEnum.ORG_ID], | |
| 52 | - | |
| 72 | + api: async (params: Record<'organizationId', string>) => { | |
| 73 | + if (!params.organizationId) return [] | |
| 74 | + return await getCameraList(params) | |
| 75 | + }, | |
| 76 | + params: { | |
| 77 | + organizationId: formModel[ContentDataFieldsEnum.ORG_ID], | |
| 78 | + }, | |
| 53 | 79 | fieldNames: { label: 'name', value: 'id' }, |
| 54 | - resultField: 'items', | |
| 55 | - onSelect(value: string, option: any) { | |
| 56 | - formModel[ContentDataFieldsEnum.ACCESS_MODE] = value && option ? option.accessMode : null | |
| 57 | - formModel[ContentDataFieldsEnum.VIDEO_URL] = value && option ? option.videoUrl : null | |
| 58 | - formModel[ContentDataFieldsEnum.VIDEO_FLAG] = true | |
| 80 | + resultField: 'data', | |
| 81 | + onSelect(_: string, option: VideoItemRecordType) { | |
| 82 | + const accessMode = option?.accessMode | |
| 83 | + formModel[VideoFormFieldsEnum.ACCESS_MODE] = accessMode | |
| 84 | + formModel[VideoFormFieldsEnum.VIDEO_URL] = option?.videoUrl | |
| 85 | + formModel[VideoFormFieldsEnum.CHANNEL_ID] = accessMode === VideoAccessModeEnum.GBT28181 ? option?.params?.channelNo : null | |
| 86 | + formModel[VideoFormFieldsEnum.DEVICE_ID] = accessMode === VideoAccessModeEnum.GBT28181 ? option?.params?.deviceId : null | |
| 87 | + formModel[VideoFormFieldsEnum.VIDEO_FLAG] = true | |
| 59 | 88 | }, |
| 60 | 89 | } |
| 61 | 90 | }, |
| 62 | 91 | }, |
| 63 | 92 | { |
| 64 | - field: ContentDataFieldsEnum.ACCESS_MODE, | |
| 65 | - label: ContentDataFieldsNameEnum.ACCESS_MODE, | |
| 93 | + field: VideoFormFieldsEnum.ACCESS_MODE, | |
| 94 | + label: VideoFormFieldsNameEnum.ACCESS_MODE, | |
| 95 | + component: ComponentEnum.INPUT, | |
| 96 | + ifShow: false, | |
| 97 | + }, | |
| 98 | + { | |
| 99 | + field: VideoFormFieldsEnum.DEVICE_ID, | |
| 100 | + label: VideoFormFieldsNameEnum.DEVICE_ID, | |
| 66 | 101 | component: ComponentEnum.INPUT, |
| 67 | 102 | ifShow: false, |
| 68 | 103 | }, |
| 69 | 104 | { |
| 70 | - field: ContentDataFieldsEnum.VIDEO_URL, | |
| 71 | - label: ContentDataFieldsNameEnum.VIDEO_URL, | |
| 105 | + field: VideoFormFieldsEnum.CHANNEL_ID, | |
| 106 | + label: VideoFormFieldsNameEnum.CHANNEL_ID, | |
| 72 | 107 | component: ComponentEnum.INPUT, |
| 73 | 108 | ifShow: false, |
| 74 | 109 | }, |
| 75 | 110 | { |
| 76 | - field: ContentDataFieldsEnum.VIDEO_FLAG, | |
| 77 | - label: ContentDataFieldsEnum.VIDEO_FLAG, | |
| 111 | + field: VideoFormFieldsEnum.VIDEO_URL, | |
| 112 | + label: VideoFormFieldsNameEnum.VIDEO_URL, | |
| 113 | + component: ComponentEnum.INPUT, | |
| 114 | + ifShow: false, | |
| 115 | + }, | |
| 116 | + { | |
| 117 | + field: VideoFormFieldsEnum.VIDEO_FLAG, | |
| 118 | + label: VideoFormFieldsEnum.VIDEO_FLAG, | |
| 78 | 119 | component: ComponentEnum.INPUT, |
| 79 | 120 | ifShow: false, |
| 80 | 121 | }, |
| ... | ... | @@ -130,9 +171,10 @@ createPublicFormContext(nodeDataActinType) |
| 130 | 171 | |
| 131 | 172 | <style lang="less" scoped> |
| 132 | 173 | .form-container { |
| 174 | + @apply text-sm; | |
| 175 | + | |
| 176 | + :deep(.ant-divider) { | |
| 133 | 177 | @apply text-sm; |
| 134 | - :deep(.ant-divider) { | |
| 135 | - @apply text-sm; | |
| 136 | - } | |
| 137 | 178 | } |
| 179 | +} | |
| 138 | 180 | </style> | ... | ... |
| 1 | 1 | <script setup lang="ts"> |
| 2 | +import { Spin } from 'ant-design-vue' | |
| 2 | 3 | import { computed, nextTick, onMounted, onUnmounted, ref, toRaw, unref } from 'vue' |
| 3 | 4 | import 'video.js/dist/video-js.css' |
| 4 | 5 | |
| 5 | 6 | import type { VideoJsPlayerOptions } from 'video.js' |
| 6 | 7 | import { BasicVideoPlay } from './component/index.ts' |
| 7 | -import { getVideoTypeByUrl, isRtspProtocol, useFingerprint } from './component/config' | |
| 8 | +import { VideoPlayerType, getVideoTypeByUrl, isRtspProtocol, useFingerprint } from './component/config' | |
| 8 | 9 | |
| 9 | 10 | import { getJwtToken, getShareJwtToken } from '@/utils/auth' |
| 10 | 11 | import { isLightboxMode, isShareMode } from '@/utils/env' |
| 11 | 12 | import type { CreateComponentType } from '@/core/Library/types' |
| 12 | -import { closeFlvPlay, getFlvPlayUrl, getStreamingPlayUrl } from '@/api/video' | |
| 13 | +import { closeFlvPlay, getFlvPlayUrl, getStreamingPlayUrl, getVideoControlStart } from '@/api/video' | |
| 13 | 14 | import { useContentDataStore } from '@/store/modules/contentData' |
| 15 | +import { VideoAccessModeEnum } from '@/enums/videoEnum' | |
| 14 | 16 | |
| 15 | 17 | const props = defineProps<{ |
| 16 | 18 | config: CreateComponentType |
| ... | ... | @@ -20,6 +22,7 @@ const props = defineProps<{ |
| 20 | 22 | // 获取节点数据 |
| 21 | 23 | const contentDataStore = useContentDataStore() |
| 22 | 24 | const { getResult } = useFingerprint() |
| 25 | +const loading = ref(false) | |
| 23 | 26 | |
| 24 | 27 | const getOptions = computed<VideoJsPlayerOptions>(() => { |
| 25 | 28 | const { config } = props || {} |
| ... | ... | @@ -46,45 +49,58 @@ const playSource = ref<Record<'src' | 'type', string>>( |
| 46 | 49 | |
| 47 | 50 | // 数据绑定获取的url |
| 48 | 51 | const handleGetVideoPlay = async () => { |
| 49 | - const { dataSourceJson } = unref(videoConfig)[0] || {} | |
| 50 | - const { videoOption } = dataSourceJson || {} | |
| 51 | - const { id, accessMode, videoUrl } = videoOption || {} | |
| 52 | - let type = getVideoTypeByUrl(videoUrl!) | |
| 53 | - let playUrl = videoUrl | |
| 54 | - | |
| 55 | - // 判断是否是流媒体播放 | |
| 56 | - if (accessMode === 1 && id) { | |
| 57 | - const { data: { url } = { url: '' } } = await getStreamingPlayUrl(id!) | |
| 58 | - playUrl = url | |
| 59 | - playUrl && (type = getVideoTypeByUrl(playUrl!)) | |
| 60 | - } | |
| 52 | + try { | |
| 53 | + loading.value = true | |
| 54 | + const { dataSourceJson } = unref(videoConfig)[0] || {} | |
| 55 | + const { videoOption } = dataSourceJson || {} | |
| 56 | + const { id, accessMode, videoUrl, deviceId, channelId } = videoOption || {} | |
| 57 | + let type = getVideoTypeByUrl(videoUrl!) | |
| 58 | + let playUrl = videoUrl | |
| 59 | + // 判断是否是流媒体播放 | |
| 60 | + if (accessMode === VideoAccessModeEnum.Streaming && id) { | |
| 61 | + const { data: { url } = { url: '' } } = await getStreamingPlayUrl(id!) | |
| 62 | + playUrl = url | |
| 63 | + playUrl && (type = getVideoTypeByUrl(playUrl!)) | |
| 64 | + } | |
| 65 | + else if (accessMode === VideoAccessModeEnum.GBT28181 && deviceId && channelId) { | |
| 66 | + const { | |
| 67 | + data: { flv }, | |
| 68 | + } = await getVideoControlStart({ channelId, deviceId }) | |
| 61 | 69 | |
| 62 | - // 判断是否是rtsp播放 | |
| 63 | - if (isRtspProtocol(videoUrl!)) { | |
| 64 | - const result = await getResult() | |
| 65 | - const { visitorId } = result | |
| 66 | - playUrl = getFlvPlayUrl(playUrl!, visitorId) | |
| 67 | - withToken.value = true | |
| 68 | - } | |
| 70 | + playUrl = flv | |
| 71 | + type = VideoPlayerType.flv | |
| 72 | + } | |
| 69 | 73 | |
| 70 | - playSource.value = { | |
| 71 | - src: playUrl!, | |
| 72 | - type, | |
| 73 | - } | |
| 74 | + // 判断是否是rtsp播放 | |
| 75 | + if (isRtspProtocol(videoUrl!)) { | |
| 76 | + const result = await getResult() | |
| 77 | + const { visitorId } = result | |
| 78 | + playUrl = getFlvPlayUrl(playUrl!, visitorId) | |
| 79 | + withToken.value = true | |
| 80 | + } | |
| 74 | 81 | |
| 75 | - const instance = unref(basicVideoPlayEl)?.customInit((options) => { | |
| 76 | - if (unref(withToken)) { | |
| 77 | - (options as any).flvjs.config.headers = { | |
| 78 | - 'X-Authorization': `Bearer ${isShareMode() ? getShareJwtToken() : getJwtToken()}`, | |
| 79 | - } | |
| 82 | + playSource.value = { | |
| 83 | + src: playUrl!, | |
| 84 | + type, | |
| 80 | 85 | } |
| 81 | - return { | |
| 82 | - ...options, | |
| 83 | - sources: [toRaw(unref(playSource))], | |
| 84 | - } as VideoJsPlayerOptions | |
| 85 | - }) | |
| 86 | 86 | |
| 87 | - instance?.play() | |
| 87 | + const instance = unref(basicVideoPlayEl)?.customInit((options) => { | |
| 88 | + if (unref(withToken)) { | |
| 89 | + (options as any).flvjs.config.headers = { | |
| 90 | + 'X-Authorization': `Bearer ${isShareMode() ? getShareJwtToken() : getJwtToken()}`, | |
| 91 | + } | |
| 92 | + } | |
| 93 | + return { | |
| 94 | + ...options, | |
| 95 | + sources: [toRaw(unref(playSource))], | |
| 96 | + } as VideoJsPlayerOptions | |
| 97 | + }) | |
| 98 | + | |
| 99 | + instance?.play() | |
| 100 | + } | |
| 101 | + finally { | |
| 102 | + loading.value = false | |
| 103 | + } | |
| 88 | 104 | } |
| 89 | 105 | |
| 90 | 106 | // 默认播放 |
| ... | ... | @@ -126,23 +142,21 @@ onUnmounted(async () => { |
| 126 | 142 | |
| 127 | 143 | <template> |
| 128 | 144 | <div class="w-full h-full flex justify-center items-center"> |
| 129 | - <!-- <Spin :spinning="loading" wrapper-class-name="video-spin" class="w-full h-full"> --> | |
| 130 | - <BasicVideoPlay | |
| 131 | - ref="basicVideoPlayEl" | |
| 132 | - :options="getOptions" | |
| 133 | - :with-token="withToken" | |
| 134 | - :immediate-init-on-mounted="false" | |
| 135 | - /> | |
| 136 | - <!-- </Spin> --> | |
| 145 | + <Spin :spinning="loading"> | |
| 146 | + <BasicVideoPlay | |
| 147 | + ref="basicVideoPlayEl" :options="getOptions" :with-token="withToken" | |
| 148 | + :immediate-init-on-mounted="false" | |
| 149 | + /> | |
| 150 | + </Spin> | |
| 137 | 151 | </div> |
| 138 | 152 | </template> |
| 139 | 153 | |
| 140 | 154 | <style lang="less" scoped> |
| 141 | 155 | .video-spin { |
| 142 | - @apply w-full h-full; | |
| 156 | + @apply w-full h-full; | |
| 143 | 157 | |
| 144 | - :deep(.ant-spin-container) { | |
| 145 | - @apply w-full h-full; | |
| 146 | - } | |
| 158 | + :deep(.ant-spin-container) { | |
| 159 | + @apply w-full h-full; | |
| 147 | 160 | } |
| 161 | +} | |
| 148 | 162 | </style> | ... | ... |
| ... | ... | @@ -34,7 +34,7 @@ const handleSubmit = async () => { |
| 34 | 34 | const formValues = unref(dataSourceFormRef)?.getFieldsValue() |
| 35 | 35 | let dataSourceJson = formValues |
| 36 | 36 | if (contentDataStore.getIsTemplate) |
| 37 | - dataSourceJson = { ...formValues, deviceProfileId: formValues?.deviceProfileTemplateId, deviceId: null } | |
| 37 | + dataSourceJson = { ...formValues, deviceProfileId: formValues?.deviceProfileTemplateId, deviceId: null, deviceName: null } | |
| 38 | 38 | |
| 39 | 39 | saveNodeAllData({ dataSourceJson: { ...dataSourceJson, circularFlowMeterOption: unref(colorConfig) } }) |
| 40 | 40 | createMessage.success('保存成功~') | ... | ... |
| ... | ... | @@ -9,6 +9,7 @@ import { useOnMessage } from '@/core/Library/hook/useOnMessage' |
| 9 | 9 | import type { NodeDataDataSourceJsonType } from '@/api/node/model' |
| 10 | 10 | import { useLatestMessageValue } from '@/core/Library/hook/useLatestMessageValue' |
| 11 | 11 | import { useContentDataStore } from '@/store/modules/contentData' |
| 12 | +import { useProductsStore } from '@/store/modules/products' | |
| 12 | 13 | |
| 13 | 14 | const props = defineProps<{ |
| 14 | 15 | config: CreateComponentType |
| ... | ... | @@ -24,6 +25,8 @@ const chartElRef = ref<Nullable<HTMLDivElement>>() |
| 24 | 25 | |
| 25 | 26 | const chartInstance = ref<Nullable<ECharts>>() |
| 26 | 27 | |
| 28 | +const productsStore = useProductsStore() | |
| 29 | + | |
| 27 | 30 | function initChartInstance() { |
| 28 | 31 | const { dataSourceJson } = unref(getCurrentNodeData) || {} |
| 29 | 32 | const { circularFlowMeterOption } = dataSourceJson || {} |
| ... | ... | @@ -34,13 +37,13 @@ function initChartInstance() { |
| 34 | 37 | const { onMessage } = useOnMessage({ |
| 35 | 38 | onReceiveDataSourceMessage(commandSource, message) { |
| 36 | 39 | const { data } = commandSource |
| 37 | - const { deviceInfo, attrInfo } = data || {} | |
| 38 | - const { deviceName } = deviceInfo || {} | |
| 39 | - const { attr } = data as NodeDataDataSourceJsonType | |
| 40 | - const { latestValue } = useLatestMessageValue(message.data, attr) | |
| 40 | + const { attr, deviceName, deviceProfileId } = data as NodeDataDataSourceJsonType | |
| 41 | + | |
| 42 | + const { functionName } = productsStore.getObjectModelByIdWithIdentifier(deviceProfileId, (attr) as string) || {} | |
| 43 | + const { latestValue } = useLatestMessageValue(message.data, (attr as string)) | |
| 41 | 44 | unref(chartInstance)?.setOption({ |
| 42 | 45 | title: { |
| 43 | - text: `${deviceName || ''}-${attrInfo.name || ''}`, | |
| 46 | + text: `${deviceName || ''} - ${functionName || ''}`, | |
| 44 | 47 | }, |
| 45 | 48 | series: [{ |
| 46 | 49 | data: getSetValue(Number(latestValue)), | ... | ... |
| ... | ... | @@ -3,7 +3,8 @@ import { Button, Divider } from 'ant-design-vue' |
| 3 | 3 | import { nextTick, onMounted, ref, unref } from 'vue' |
| 4 | 4 | import { formSchemas } from '../config' |
| 5 | 5 | import { ChartComponentEnum } from '..' |
| 6 | -import { DataSourceForm } from '@/core/Library/components/PublicForm/components/DataSourceForm' | |
| 6 | +import { DataSourceForm } from '../component/index' | |
| 7 | +// import { DataSourceForm } from '@/core/Library/components/PublicForm/components/DataSourceForm' | |
| 7 | 8 | |
| 8 | 9 | import { BasicForm, useForm } from '@/components/Form' |
| 9 | 10 | import { FormLayoutEnum } from '@/components/Form/src/enum' |
| ... | ... | @@ -44,9 +45,9 @@ const handleSubmit = async () => { |
| 44 | 45 | const values = getFieldsValue() |
| 45 | 46 | let dataSourceJson = value |
| 46 | 47 | if (contentDataStore.getIsTemplate) |
| 47 | - dataSourceJson = { ...value, deviceProfileId: value?.deviceProfileTemplateId, deviceId: null } | |
| 48 | + dataSourceJson = { ...value, deviceProfileId: value?.deviceProfileTemplateId, deviceId: null, deviceName: null } | |
| 48 | 49 | |
| 49 | - await saveNodeAllData({ dataSourceJson: { ...dataSourceJson, chartOption: { ...values } } }) | |
| 50 | + await saveNodeAllData({ dataSourceJson: { ...dataSourceJson, attr: typeof value.attr == 'string' ? [value.attr] : value.attr, chartOption: { ...values } } }) | |
| 50 | 51 | savePageContent() |
| 51 | 52 | createMessage.success('操作成功~') |
| 52 | 53 | } |
| ... | ... | @@ -57,9 +58,9 @@ const handleSubmit = async () => { |
| 57 | 58 | |
| 58 | 59 | const handleSetFormValues = async () => { |
| 59 | 60 | const { dataSourceJson } = unref(getNodeData) || {} |
| 60 | - const { deviceId, attr, chartOption, deviceProfileId, deviceProfileTemplateId, attrInfo, deviceInfo } = dataSourceJson || {} | |
| 61 | + const { deviceId, attr, chartOption, deviceProfileId, deviceProfileTemplateId, deviceName } = dataSourceJson || {} | |
| 61 | 62 | await nextTick() |
| 62 | - unref(dataSourceElRef)?.setFieldsValue({ deviceId, attr, deviceProfileId, attrInfo, deviceProfileTemplateId, deviceInfo }) | |
| 63 | + unref(dataSourceElRef)?.setFieldsValue({ deviceId, attr, deviceName, deviceProfileId, deviceProfileTemplateId }) | |
| 63 | 64 | setFieldsValue({ ...chartOption }) |
| 64 | 65 | } |
| 65 | 66 | ... | ... |
| 1 | +import type { EChartsOption } from 'echarts' | |
| 1 | 2 | import { isLightboxMode } from '@/utils/env' |
| 2 | - | |
| 3 | -const data = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] | |
| 4 | -const seriesList = [ | |
| 5 | - { value: 120, name: '123', itemStyle: { color: '#5470c6' } }, | |
| 6 | - { value: 150, name: '456', itemStyle: { color: '#5470c6' } }, | |
| 7 | - { value: 40, name: '789', itemStyle: { color: '#5470c6' } }, | |
| 8 | - { value: 139, name: '111', itemStyle: { color: '#5470c6' } }, | |
| 9 | - { value: 130, name: '222', itemStyle: { color: '#5470c6' } }, | |
| 10 | - { value: 125, name: '333', itemStyle: { color: '#5470c6' } }, | |
| 11 | - { value: 110, name: '444', itemStyle: { color: '#5470c6' } }, | |
| 12 | -] | |
| 13 | -export const defaultOption = { | |
| 14 | - color: ['#3398DB'], | |
| 15 | - tooltip: { | |
| 16 | - // 提示框 | |
| 17 | - trigger: 'item', | |
| 18 | - confine: true, | |
| 19 | - axisPointer: { | |
| 20 | - type: 'shadow', | |
| 3 | +export const defaultOption = (): EChartsOption => { | |
| 4 | + return { | |
| 5 | + tooltip: { | |
| 21 | 6 | }, |
| 22 | - }, | |
| 23 | - xAxis: { | |
| 24 | - type: 'category', | |
| 25 | - data: isLightboxMode() ? [] : data, | |
| 26 | - axisTick: { | |
| 27 | - alignWithLabel: true, | |
| 7 | + calculable: true, | |
| 8 | + xAxis: | |
| 9 | + { | |
| 10 | + type: 'category', | |
| 11 | + // prettier-ignore | |
| 12 | + data: isLightboxMode() ? [] : ['Jan', 'Feb', 'Mar', 'Apr', 'May'], | |
| 13 | + }, | |
| 14 | + | |
| 15 | + legend: { | |
| 16 | + top: '8%', | |
| 17 | + left: 'center', | |
| 18 | + data: [''], | |
| 28 | 19 | }, |
| 29 | - }, | |
| 30 | - grid: { | |
| 31 | - top: '10%', | |
| 32 | - left: '10%', | |
| 33 | - bottom: '10%', | |
| 34 | - }, | |
| 35 | - yAxis: { | |
| 36 | - type: 'value', | |
| 37 | - }, | |
| 38 | - series: [ | |
| 39 | - { | |
| 40 | - name: '', | |
| 41 | - type: 'bar', | |
| 42 | - barWidth: '60%', | |
| 43 | - data: isLightboxMode () ? [] : seriesList, | |
| 20 | + | |
| 21 | + grid: { | |
| 22 | + top: '25%', | |
| 23 | + left: '6%', | |
| 24 | + right: '10%', | |
| 25 | + bottom: '8%', | |
| 26 | + containLabel: true, | |
| 44 | 27 | }, |
| 45 | - ], | |
| 28 | + yAxis: | |
| 29 | + { | |
| 30 | + type: 'value', | |
| 31 | + }, | |
| 32 | + series: [ | |
| 33 | + { | |
| 34 | + name: 'Rainfall', | |
| 35 | + type: isLightboxMode() ? 'line' : 'bar', | |
| 36 | + data: isLightboxMode() | |
| 37 | + ? [] | |
| 38 | + : [ | |
| 39 | + 25.6, 76.7, 42.0, 27.0, 23.2, | |
| 40 | + ], | |
| 41 | + }, | |
| 42 | + { | |
| 43 | + name: 'Evaporation', | |
| 44 | + type: 'bar', | |
| 45 | + data: isLightboxMode() | |
| 46 | + ? [] | |
| 47 | + : [ | |
| 48 | + 28.7, 32.6, 70.7, 29.0, 26.4, | |
| 49 | + ], | |
| 50 | + }, | |
| 51 | + ], | |
| 52 | + } | |
| 46 | 53 | } | ... | ... |
| 1 | 1 | <script lang="ts" setup> |
| 2 | -import { computed, onMounted, onUnmounted, ref, unref } from 'vue' | |
| 2 | +import { computed, onMounted, onUnmounted, ref, toRaw, unref } from 'vue' | |
| 3 | 3 | import { init } from 'echarts' |
| 4 | 4 | import type { ECharts, EChartsOption } from 'echarts' |
| 5 | 5 | import { defaultOption } from './index.config' |
| ... | ... | @@ -10,6 +10,7 @@ import { SocketSubscriberEnum } from '@/enums/datasource' |
| 10 | 10 | import type { SubscriptionData } from '@/core/websocket/type/message' |
| 11 | 11 | import { formatToDateTime } from '@/utils/dateUtil' |
| 12 | 12 | import type { CommandSource } from '@/core/websocket/processor' |
| 13 | +import { useProductsStore } from '@/store/modules/products' | |
| 13 | 14 | |
| 14 | 15 | const props = defineProps<{ |
| 15 | 16 | config: CreateComponentType |
| ... | ... | @@ -21,40 +22,80 @@ const chartElRef = ref<Nullable<HTMLDivElement>>() |
| 21 | 22 | |
| 22 | 23 | const chartInstance = ref<Nullable<ECharts>>() |
| 23 | 24 | |
| 25 | +const seriesData = ref<any>([]) | |
| 26 | +const XAxisData = ref<any>([]) | |
| 27 | +const titleATTR = ref<string[] | any>([]) | |
| 28 | + | |
| 24 | 29 | function initChartInstance() { |
| 25 | 30 | chartInstance.value = init(unref(chartElRef)) |
| 26 | - chartInstance.value.setOption(defaultOption) | |
| 31 | + chartInstance.value.setOption(defaultOption()) | |
| 27 | 32 | } |
| 28 | 33 | |
| 34 | +const productsStore = useProductsStore() | |
| 35 | + | |
| 29 | 36 | const handleHistoryData = (commandSource: CommandSource, message: SubscriptionData) => { |
| 30 | 37 | const { data } = commandSource |
| 31 | - const { attrInfo, deviceInfo, attr } = data as NodeDataDataSourceJsonType | |
| 32 | - const { deviceName } = deviceInfo || {} | |
| 38 | + const { attr, deviceName } = data as NodeDataDataSourceJsonType as any | |
| 33 | 39 | |
| 34 | - const historyData = message[attr] | |
| 35 | - const xAxisData: string[] = [] | |
| 36 | - const seriesData: number[] = [] | |
| 40 | + attr.forEach((item: any) => { // 得到所有时间 | |
| 41 | + XAxisData.value.push(...message[item].map((item1) => { | |
| 42 | + const [ts] = item1 | |
| 43 | + return ts | |
| 44 | + })) | |
| 45 | + }) | |
| 46 | + XAxisData.value = Array.from(new Set(XAxisData.value)).sort((a: any, b: any) => a - b)// 去重获取到的时间 | |
| 47 | + // 初始化有几个柱 | |
| 48 | + seriesData.value.forEach((item: any) => { | |
| 49 | + item.data = unref(XAxisData).map((time: any) => ({ name: time, value: '' })) | |
| 50 | + }) | |
| 37 | 51 | |
| 38 | - for (const item of historyData) { | |
| 39 | - const [ts, value] = item | |
| 40 | - xAxisData.push(formatToDateTime(ts)) | |
| 41 | - seriesData.push(value) | |
| 42 | - } | |
| 52 | + // 跟每个柱添加数据 | |
| 53 | + seriesData.value.forEach((item: any) => { | |
| 54 | + item.data.forEach((item1: any) => { | |
| 55 | + message[item.attribute].forEach((messageItem) => { | |
| 56 | + const [ts, value] = messageItem | |
| 57 | + if (item1.name === ts) | |
| 58 | + item1.value = value || '' | |
| 59 | + // item1.name = formatToDateTime(item1.name) | |
| 60 | + }) | |
| 61 | + }) | |
| 62 | + }) | |
| 43 | 63 | |
| 44 | 64 | unref(chartInstance)?.setOption({ |
| 45 | 65 | title: { |
| 46 | - text: `${deviceName || ''}-${attrInfo.name || ''}`, | |
| 66 | + text: `${deviceName || ''}`, | |
| 67 | + }, | |
| 68 | + xAxis: [ | |
| 69 | + { | |
| 70 | + type: 'category', | |
| 71 | + data: toRaw(unref(XAxisData.value.map((item: string | number) => formatToDateTime(item)))), | |
| 72 | + }, | |
| 73 | + ], | |
| 74 | + legend: { | |
| 75 | + data: unref(titleATTR), | |
| 47 | 76 | }, |
| 48 | - xAxis: { data: xAxisData }, | |
| 49 | - series: { data: seriesData }, | |
| 77 | + series: toRaw(unref(seriesData).map((item: { type: string; name: string; data: any; attribute: string }) => { | |
| 78 | + const { type, name, data } = item | |
| 79 | + return { | |
| 80 | + type, | |
| 81 | + name, | |
| 82 | + data: data.map((item: any) => item.value), | |
| 83 | + } | |
| 84 | + })), | |
| 50 | 85 | } as EChartsOption) |
| 51 | 86 | } |
| 52 | 87 | |
| 53 | 88 | const { onMessage } = useOnMessage({ |
| 54 | 89 | onReceiveDataSourceMessage(commandSource, message) { |
| 55 | 90 | const { data } = commandSource |
| 56 | - const { chartOption } = data as NodeDataDataSourceJsonType | |
| 91 | + const { chartOption, attr, deviceProfileId } = data as NodeDataDataSourceJsonType | |
| 57 | 92 | const { queryType } = chartOption || {} |
| 93 | + if (!seriesData.value.length) { | |
| 94 | + (attr as string[]).forEach((item: string) => { | |
| 95 | + titleATTR.value.push(productsStore.getObjectModelByIdWithIdentifier(deviceProfileId, item)?.functionName) | |
| 96 | + seriesData.value.push({ attribute: item, data: [], type: 'bar', name: productsStore.getObjectModelByIdWithIdentifier(deviceProfileId, item)?.functionName }) | |
| 97 | + }) | |
| 98 | + } | |
| 58 | 99 | |
| 59 | 100 | if (queryType === SocketSubscriberEnum.HISTORY_CMDS) |
| 60 | 101 | handleHistoryData(commandSource, message.data) | ... | ... |
| ... | ... | @@ -42,7 +42,7 @@ const handleSubmit = async () => { |
| 42 | 42 | const values = getFieldsValue() |
| 43 | 43 | let dataSourceJson = value |
| 44 | 44 | if (contentDataStore.getIsTemplate) |
| 45 | - dataSourceJson = { ...value, deviceProfileId: value?.deviceProfileTemplateId, deviceId: null } | |
| 45 | + dataSourceJson = { ...value, deviceProfileId: value?.deviceProfileTemplateId, deviceId: null, deviceName: null } | |
| 46 | 46 | |
| 47 | 47 | await saveNodeAllData({ dataSourceJson: { ...dataSourceJson, chartOption: values } }) |
| 48 | 48 | createMessage.success('操作成功~') |
| ... | ... | @@ -55,9 +55,9 @@ const handleSubmit = async () => { |
| 55 | 55 | |
| 56 | 56 | const handleSetFormValues = async () => { |
| 57 | 57 | const { dataSourceJson } = unref(getNodeData) || {} |
| 58 | - const { deviceId, attr, chartOption, deviceProfileId, attrInfo, deviceProfileTemplateId, deviceInfo } = dataSourceJson || {} | |
| 58 | + const { deviceId, attr, chartOption, deviceProfileId, deviceProfileTemplateId, deviceName } = dataSourceJson || {} | |
| 59 | 59 | await nextTick() |
| 60 | - unref(dataSourceElRef)?.setFieldsValue({ deviceId, attr, deviceProfileId, attrInfo, deviceInfo, deviceProfileTemplateId }) | |
| 60 | + unref(dataSourceElRef)?.setFieldsValue({ deviceId, attr, deviceProfileId, deviceName, deviceProfileTemplateId }) | |
| 61 | 61 | setFieldsValue({ ...chartOption }) |
| 62 | 62 | } |
| 63 | 63 | ... | ... |
| ... | ... | @@ -10,6 +10,7 @@ import type { CommandSource } from '@/core/websocket/processor' |
| 10 | 10 | import type { NodeDataDataSourceJsonType } from '@/api/node/model' |
| 11 | 11 | import type { SubscriptionUpdateMsg } from '@/core/websocket/type/message' |
| 12 | 12 | import { useContentDataStore } from '@/store/modules/contentData' |
| 13 | +import { useProductsStore } from '@/store/modules/products' | |
| 13 | 14 | |
| 14 | 15 | const props = defineProps<{ |
| 15 | 16 | config: CreateComponentType |
| ... | ... | @@ -23,6 +24,8 @@ const chartInstance = ref<Nullable<ECharts>>() |
| 23 | 24 | |
| 24 | 25 | const contentDataStore = useContentDataStore() |
| 25 | 26 | |
| 27 | +const productsStore = useProductsStore() | |
| 28 | + | |
| 26 | 29 | function initChartInstance() { |
| 27 | 30 | chartInstance.value = init(unref(chartElRef)) |
| 28 | 31 | const currentNodeData = unref(contentDataStore.getCurrentNodeDataById(props.config)) |
| ... | ... | @@ -36,17 +39,16 @@ onMounted(() => { |
| 36 | 39 | |
| 37 | 40 | const onReceiveDataSourceMessage = (commandSource: CommandSource, message: SubscriptionUpdateMsg) => { |
| 38 | 41 | const { data } = commandSource |
| 39 | - const { deviceInfo, attrInfo } = data || {} | |
| 40 | - const { deviceName } = deviceInfo || {} | |
| 41 | - const { attr } = data as NodeDataDataSourceJsonType | |
| 42 | - const { latestValue } = useLatestMessageValue(message.data, attr) | |
| 42 | + const { attr, deviceName, deviceProfileId } = data as NodeDataDataSourceJsonType | |
| 43 | + const { functionName } = productsStore.getObjectModelByIdWithIdentifier(deviceProfileId, (attr) as string) || {} | |
| 44 | + const { latestValue } = useLatestMessageValue(message.data, (attr as string)) | |
| 43 | 45 | unref(chartInstance)?.setOption({ |
| 44 | 46 | series: [{ |
| 45 | 47 | data: [{ value: latestValue }], |
| 46 | 48 | |
| 47 | 49 | }], |
| 48 | 50 | title: { |
| 49 | - text: `${deviceName || ''}-${attrInfo.name || ''}`, | |
| 51 | + text: `${deviceName || ''}- ${functionName || ''} `, | |
| 50 | 52 | }, |
| 51 | 53 | } as EChartsOption) |
| 52 | 54 | } | ... | ... |
| ... | ... | @@ -3,7 +3,9 @@ import { Button, Divider } from 'ant-design-vue' |
| 3 | 3 | import { nextTick, onMounted, ref, unref } from 'vue' |
| 4 | 4 | import { formSchemas } from '../config' |
| 5 | 5 | import { ChartComponentEnum } from '..' |
| 6 | -import { DataSourceForm } from '@/core/Library/components/PublicForm/components/DataSourceForm' | |
| 6 | + | |
| 7 | +import { DataSourceForm } from '../component/index' | |
| 8 | +// import { DataSourceForm } from '@/core/Library/components/PublicForm/components/DataSourceForm' | |
| 7 | 9 | |
| 8 | 10 | import { BasicForm, useForm } from '@/components/Form' |
| 9 | 11 | import { FormLayoutEnum } from '@/components/Form/src/enum' |
| ... | ... | @@ -33,9 +35,9 @@ const dataSourceElRef = ref<Nullable<InstanceType<typeof DataSourceForm>>>() |
| 33 | 35 | |
| 34 | 36 | const handleSetFormValues = async () => { |
| 35 | 37 | const { dataSourceJson } = unref(getNodeData) || {} |
| 36 | - const { deviceId, attr, chartOption, deviceProfileId, deviceProfileTemplateId, attrInfo, deviceInfo } = dataSourceJson || {} | |
| 38 | + const { deviceId, attr, chartOption, deviceProfileId, deviceProfileTemplateId, deviceName } = dataSourceJson || {} | |
| 37 | 39 | await nextTick() |
| 38 | - unref(dataSourceElRef)?.setFieldsValue({ deviceId, attr, deviceProfileId, deviceProfileTemplateId, attrInfo, deviceInfo }) | |
| 40 | + unref(dataSourceElRef)?.setFieldsValue({ deviceId, attr, deviceProfileId, deviceProfileTemplateId, deviceName }) | |
| 39 | 41 | setFieldsValue({ ...chartOption }) |
| 40 | 42 | } |
| 41 | 43 | |
| ... | ... | @@ -52,9 +54,9 @@ const handleSubmit = async () => { |
| 52 | 54 | const values = getFieldsValue() |
| 53 | 55 | let dataSourceJson = value |
| 54 | 56 | if (contentDataStore.getIsTemplate) |
| 55 | - dataSourceJson = { ...value, deviceProfileId: value?.deviceProfileTemplateId, deviceId: null } | |
| 57 | + dataSourceJson = { ...value, deviceProfileId: value?.deviceProfileTemplateId, deviceId: null, deviceName: null } | |
| 56 | 58 | |
| 57 | - saveNodeAllData({ dataSourceJson: { ...dataSourceJson, chartOption: { ...values } } }) | |
| 59 | + saveNodeAllData({ dataSourceJson: { ...dataSourceJson, attr: typeof value.attr == 'string' ? [value.attr] : value.attr, chartOption: { ...values } } }) | |
| 58 | 60 | createMessage.success('操作成功~') |
| 59 | 61 | savePageContent() |
| 60 | 62 | } | ... | ... |
| ... | ... | @@ -3,31 +3,38 @@ import { isLightboxMode } from '@/utils/env' |
| 3 | 3 | |
| 4 | 4 | export const getDefaultOption = (): EChartsOption => { |
| 5 | 5 | return { |
| 6 | + | |
| 6 | 7 | tooltip: { |
| 7 | - trigger: 'item', | |
| 8 | - confine: true, | |
| 8 | + }, | |
| 9 | + legend: { | |
| 10 | + top: '10%', | |
| 11 | + left: 'center', | |
| 12 | + data: [''], | |
| 9 | 13 | }, |
| 10 | 14 | grid: { |
| 11 | - left: '3%', | |
| 12 | - right: '4%', | |
| 13 | - bottom: '3%', | |
| 15 | + top: '30%', | |
| 16 | + left: '6%', | |
| 17 | + right: '10%', | |
| 18 | + bottom: '8%', | |
| 14 | 19 | containLabel: true, |
| 15 | 20 | }, |
| 16 | - dataset: { | |
| 17 | - source: isLightboxMode() | |
| 21 | + xAxis: { | |
| 22 | + type: 'category', | |
| 23 | + }, | |
| 24 | + yAxis: { | |
| 25 | + type: 'value', | |
| 26 | + boundaryGap: [0, '100%'], | |
| 27 | + }, | |
| 28 | + series: [{ | |
| 29 | + type: 'line', | |
| 30 | + name: '温度', | |
| 31 | + data: isLightboxMode() | |
| 18 | 32 | ? [] |
| 19 | 33 | : [ |
| 20 | - ['product', '2015', '2016', '2017'], | |
| 21 | - ['Matcha Latte', 43.3, 85.8, 93.7], | |
| 22 | - ['Milk Tea', 83.1, 73.4, 55.1], | |
| 23 | - ['Cheese Cocoa', 86.4, 65.2, 82.5], | |
| 24 | - ['Walnut Brownie', 72.4, 53.9, 39.1], | |
| 25 | - ], | |
| 26 | - }, | |
| 27 | - xAxis: { type: 'category' }, | |
| 28 | - yAxis: {}, | |
| 29 | - series: [ | |
| 30 | - { type: 'line' }, | |
| 31 | - ], | |
| 34 | + ['Matcha Latte', 43.3], | |
| 35 | + ['Milk Tea', 83.1], | |
| 36 | + ['Cheese Cocoa', 86.4], | |
| 37 | + ['Walnut Brownie', 72.4]], | |
| 38 | + }], | |
| 32 | 39 | } |
| 33 | 40 | } | ... | ... |
| 1 | 1 | <script lang="ts" setup> |
| 2 | -import { computed, onMounted, onUnmounted, ref, unref } from 'vue' | |
| 3 | -import type { DatasetComponentOption, ECharts, EChartsOption } from 'echarts' | |
| 2 | +import { computed, onMounted, onUnmounted, ref, toRaw, unref } from 'vue' | |
| 3 | +import type { ECharts, EChartsOption } from 'echarts' | |
| 4 | 4 | import { init } from 'echarts' |
| 5 | 5 | import { getDefaultOption } from './index.config' |
| 6 | 6 | import type { CreateComponentType, RenderComponentExposeType } from '@/core/Library/types' |
| ... | ... | @@ -9,8 +9,12 @@ import type { NodeDataDataSourceJsonType } from '@/api/node/model' |
| 9 | 9 | import type { SubscriptionData } from '@/core/websocket/type/message' |
| 10 | 10 | import { SocketSubscriberEnum } from '@/enums/datasource' |
| 11 | 11 | import { formatToDateTime } from '@/utils/dateUtil' |
| 12 | -import { useLatestMessageValue } from '@/core/Library/hook/useLatestMessageValue' | |
| 13 | 12 | import type { CommandSource } from '@/core/websocket/processor' |
| 13 | +import { useLatestMultipleMessageValue } from '@/core/Library/hook/useLatestMessageValue' | |
| 14 | +import { useProductsStore } from '@/store/modules/products' | |
| 15 | +interface IList { | |
| 16 | + time: string | number | |
| 17 | +} | |
| 14 | 18 | |
| 15 | 19 | const props = defineProps<{ |
| 16 | 20 | config: CreateComponentType |
| ... | ... | @@ -22,67 +26,131 @@ const chartElRef = ref<Nullable<HTMLDivElement>>() |
| 22 | 26 | |
| 23 | 27 | const chartInstance = ref<Nullable<ECharts>>() |
| 24 | 28 | |
| 29 | +const maxLength = ref<number>(20) | |
| 30 | + | |
| 31 | +const timeList = ref<number[] | string[] | any>([])// 存储XAxis的值 | |
| 32 | +const seriesData = ref<any>([])// 存储series的值 | |
| 33 | +const titleATTR = ref<string[] | any>([]) | |
| 34 | + | |
| 35 | +const productsStore = useProductsStore() | |
| 36 | + | |
| 25 | 37 | function initChartInstance() { |
| 26 | 38 | chartInstance.value = init(unref(chartElRef)) |
| 27 | 39 | chartInstance.value.setOption(getDefaultOption()) |
| 28 | 40 | } |
| 29 | 41 | |
| 30 | -const handleHistoryData = (message: SubscriptionData, attr: string) => { | |
| 31 | - const data = message[attr] | |
| 32 | - const xAxisData: string[] = [] | |
| 33 | - const seriesData: number[] = [] | |
| 42 | +const handleHistoryData = (commandSource: CommandSource, message: SubscriptionData, attr: string[] | string) => { | |
| 43 | + const { data } = commandSource || {} | |
| 44 | + const { deviceName } = data as NodeDataDataSourceJsonType | |
| 34 | 45 | |
| 35 | - for (const item of data) { | |
| 36 | - const [ts, value] = item | |
| 37 | - xAxisData.push(formatToDateTime(ts)) | |
| 38 | - seriesData.push(value) | |
| 39 | - } | |
| 46 | + (attr as string[]).forEach((item) => { | |
| 47 | + timeList.value.push(...message[item].map((item1) => { | |
| 48 | + const [ts] = item1 | |
| 49 | + return ts | |
| 50 | + })) | |
| 51 | + }) | |
| 52 | + timeList.value = Array.from(new Set(timeList.value))?.sort((a: any, b: any) => a - b)// 去重获取到的时间 | |
| 53 | + seriesData.value.forEach((item: any) => { | |
| 54 | + item.data = unref(timeList).map((time: any) => ({ name: time, value: null })) | |
| 55 | + }) | |
| 40 | 56 | |
| 57 | + seriesData.value.forEach((item: any) => { | |
| 58 | + item.data.forEach((item1: any) => { | |
| 59 | + message[item.attribute].forEach((messageItem) => { | |
| 60 | + const [ts, value] = messageItem | |
| 61 | + if (item1.name === ts) { | |
| 62 | + item1.value = value || undefined | |
| 63 | + item1.name = formatToDateTime(item1.name) | |
| 64 | + } | |
| 65 | + }) | |
| 66 | + }) | |
| 67 | + }) | |
| 41 | 68 | unref(chartInstance)?.setOption({ |
| 42 | - xAxis: { data: xAxisData }, | |
| 43 | - series: { data: seriesData }, | |
| 69 | + title: { | |
| 70 | + text: `${deviceName || ''}`, | |
| 71 | + }, | |
| 72 | + xAxis: { | |
| 73 | + data: toRaw(unref(timeList.value.map((item: string | number) => formatToDateTime(item)))), | |
| 74 | + }, | |
| 75 | + legend: { | |
| 76 | + data: unref(titleATTR), | |
| 77 | + } as any, | |
| 78 | + series: toRaw( | |
| 79 | + unref(seriesData).map((item: { type: string; name: string; data: any }) => { | |
| 80 | + const { type, name, data } = item | |
| 81 | + return { | |
| 82 | + type, | |
| 83 | + name, | |
| 84 | + data, | |
| 85 | + } | |
| 86 | + }), | |
| 87 | + ), | |
| 44 | 88 | } as EChartsOption) |
| 45 | 89 | } |
| 46 | 90 | |
| 47 | -function sliceData(data: any[], maxLength = 20) { | |
| 48 | - if (data.length > maxLength) | |
| 49 | - return data.slice(1) | |
| 50 | - return data | |
| 51 | -} | |
| 91 | +// const contentDataStore = useContentDataStoreWithOut() | |
| 52 | 92 | |
| 53 | -const handlerTimeSeriesData = (commandSource: CommandSource, message: SubscriptionData, attr: string) => { | |
| 93 | +const handlerTimeSeriesData = (commandSource: CommandSource, message: SubscriptionData, attr: string[] | string) => { | |
| 54 | 94 | const { data } = commandSource |
| 55 | - const { attrInfo, deviceInfo } = data as NodeDataDataSourceJsonType | |
| 56 | - const { deviceName } = deviceInfo || {} | |
| 57 | - const { ts, latestValue } = useLatestMessageValue(message, attr) | |
| 58 | - const option = unref(chartInstance)?.getOption() as EChartsOption | |
| 59 | - const oldDataset = (option.dataset as DatasetComponentOption[])?.[0].source as Recordable[] | |
| 60 | - oldDataset.push({ | |
| 61 | - ts: formatToDateTime(ts), | |
| 62 | - [attr]: latestValue, | |
| 95 | + const { deviceName } = data as NodeDataDataSourceJsonType | |
| 96 | + const list: IList | any = {}// 记录时间 | |
| 97 | + useLatestMultipleMessageValue(message, attr as any, (attribute, timespan, value) => { | |
| 98 | + list.time = timespan || list.time | |
| 99 | + seriesData.value.forEach((item: any) => { | |
| 100 | + if (item.attribute === attribute) { | |
| 101 | + item.data.push({ | |
| 102 | + name: formatToDateTime(list.time), | |
| 103 | + value: value || undefined, | |
| 104 | + }) | |
| 105 | + } | |
| 106 | + if (item.data.length > unref(maxLength)) | |
| 107 | + item.data.shift() | |
| 108 | + }) | |
| 63 | 109 | }) |
| 110 | + list.time && timeList.value.push(formatToDateTime(list.time)) | |
| 111 | + if (unref(timeList).length > unref(maxLength)) | |
| 112 | + timeList.value.shift() | |
| 64 | 113 | |
| 65 | 114 | unref(chartInstance)?.setOption({ |
| 66 | 115 | title: { |
| 67 | - text: `${deviceName || ''}-${attrInfo.name || ''}`, | |
| 116 | + text: `${deviceName || ''}`, | |
| 68 | 117 | }, |
| 69 | - dataset: { | |
| 70 | - dimensions: ['ts', attr], | |
| 71 | - source: sliceData(oldDataset), | |
| 118 | + xAxis: { | |
| 119 | + data: toRaw(unref(timeList)), | |
| 72 | 120 | }, |
| 121 | + legend: { | |
| 122 | + data: unref(titleATTR), | |
| 123 | + } as any, | |
| 124 | + series: toRaw( | |
| 125 | + unref(seriesData).map((item: { type: string; name: string; data: any }) => { | |
| 126 | + const { type, name, data } = item | |
| 127 | + return { | |
| 128 | + type, | |
| 129 | + name, | |
| 130 | + data, | |
| 131 | + } | |
| 132 | + }), | |
| 133 | + ), | |
| 73 | 134 | } as EChartsOption) |
| 74 | 135 | } |
| 75 | 136 | |
| 76 | 137 | const { onMessage } = useOnMessage({ |
| 77 | 138 | onReceiveDataSourceMessage(commandSource, message) { |
| 78 | 139 | const { data } = commandSource |
| 79 | - const { chartOption, attr } = data as NodeDataDataSourceJsonType | |
| 140 | + const { chartOption, attr, deviceProfileId } = data as NodeDataDataSourceJsonType | |
| 80 | 141 | const { queryType } = chartOption || {} |
| 142 | + if (!seriesData.value.length) { | |
| 143 | + (attr as string[]).forEach((item: string) => { | |
| 144 | + titleATTR.value.push(productsStore.getObjectModelByIdWithIdentifier(deviceProfileId, item)?.functionName) | |
| 145 | + | |
| 146 | + seriesData.value.push({ attribute: item, data: [], type: 'line', name: productsStore.getObjectModelByIdWithIdentifier(deviceProfileId, item)?.functionName }) | |
| 147 | + }) | |
| 148 | + } | |
| 81 | 149 | |
| 82 | 150 | if (queryType === SocketSubscriberEnum.TS_SUB_CMDS) |
| 83 | 151 | handlerTimeSeriesData(commandSource, message.data, attr) |
| 84 | 152 | else if (queryType === SocketSubscriberEnum.HISTORY_CMDS) |
| 85 | - handleHistoryData(message.data, attr) | |
| 153 | + handleHistoryData(commandSource, message.data, attr) | |
| 86 | 154 | }, |
| 87 | 155 | }) |
| 88 | 156 | ... | ... |
| ... | ... | @@ -34,7 +34,7 @@ const handleSubmit = async () => { |
| 34 | 34 | const formValues = unref(dataSourceFormRef)?.getFieldsValue() |
| 35 | 35 | let dataSourceJson = formValues |
| 36 | 36 | if (contentDataStore.getIsTemplate) |
| 37 | - dataSourceJson = { ...formValues, deviceProfileId: formValues?.deviceProfileTemplateId, deviceId: null } | |
| 37 | + dataSourceJson = { ...formValues, deviceProfileId: formValues?.deviceProfileTemplateId, deviceId: null, deviceName: null } | |
| 38 | 38 | |
| 39 | 39 | await saveNodeAllData({ dataSourceJson: { ...dataSourceJson, rectFlowMeterOption: unref(colorConfig) } }) |
| 40 | 40 | createMessage.success('保存成功') | ... | ... |
| ... | ... | @@ -9,6 +9,7 @@ import { useOnMessage } from '@/core/Library/hook/useOnMessage' |
| 9 | 9 | import type { NodeDataDataSourceJsonType } from '@/api/node/model' |
| 10 | 10 | import { useLatestMessageValue } from '@/core/Library/hook/useLatestMessageValue' |
| 11 | 11 | import { useContentDataStore } from '@/store/modules/contentData' |
| 12 | +import { useProductsStore } from '@/store/modules/products' | |
| 12 | 13 | |
| 13 | 14 | const props = defineProps<{ |
| 14 | 15 | config: CreateComponentType |
| ... | ... | @@ -22,6 +23,8 @@ const getCellBounds = computed(() => props.config.cellBounds || { width: 300, he |
| 22 | 23 | |
| 23 | 24 | const chartElRef = ref<Nullable<HTMLDivElement>>() |
| 24 | 25 | |
| 26 | +const productsStore = useProductsStore() | |
| 27 | + | |
| 25 | 28 | const chartInstance = ref<Nullable<ECharts>>() |
| 26 | 29 | |
| 27 | 30 | function initChartInstance() { |
| ... | ... | @@ -34,13 +37,12 @@ function initChartInstance() { |
| 34 | 37 | const { onMessage } = useOnMessage({ |
| 35 | 38 | onReceiveDataSourceMessage(commandSource, message) { |
| 36 | 39 | const { data } = commandSource |
| 37 | - const { deviceInfo, attrInfo } = data || {} | |
| 38 | - const { deviceName } = deviceInfo || {} | |
| 39 | - const { attr } = data as NodeDataDataSourceJsonType | |
| 40 | - const { latestValue } = useLatestMessageValue(message.data, attr) | |
| 40 | + const { attr, deviceName, deviceProfileId } = data as NodeDataDataSourceJsonType | |
| 41 | + const { functionName } = productsStore.getObjectModelByIdWithIdentifier(deviceProfileId, (attr as string)) || {} | |
| 42 | + const { latestValue } = useLatestMessageValue(message.data, (attr as string)) | |
| 41 | 43 | unref(chartInstance)?.setOption({ |
| 42 | 44 | title: { |
| 43 | - text: `${deviceName || ''}-${attrInfo.name || ''}`, | |
| 45 | + text: `${deviceName || ''} - ${functionName || ''}`, | |
| 44 | 46 | }, |
| 45 | 47 | series: [{ |
| 46 | 48 | data: getSetValue(Number(latestValue)), | ... | ... |
| ... | ... | @@ -7,6 +7,7 @@ import type { CreateComponentType, RenderComponentExposeType } from '@/core/Libr |
| 7 | 7 | import type { NodeDataDataSourceJsonType } from '@/api/node/model' |
| 8 | 8 | import { useLatestMessageValue } from '@/core/Library/hook/useLatestMessageValue' |
| 9 | 9 | import { useOnMessage } from '@/core/Library/hook/useOnMessage' |
| 10 | +import { useProductsStore } from '@/store/modules/products' | |
| 10 | 11 | |
| 11 | 12 | const props = defineProps<{ |
| 12 | 13 | config: CreateComponentType |
| ... | ... | @@ -18,6 +19,8 @@ const chartElRef = ref<Nullable<HTMLDivElement>>() |
| 18 | 19 | |
| 19 | 20 | const chartInstance = ref<Nullable<ECharts>>() |
| 20 | 21 | |
| 22 | +const productsStore = useProductsStore() | |
| 23 | + | |
| 21 | 24 | function initChartInstance() { |
| 22 | 25 | chartInstance.value = init(unref(chartElRef)) |
| 23 | 26 | chartInstance.value.setOption(getDefaultOption) |
| ... | ... | @@ -26,13 +29,12 @@ function initChartInstance() { |
| 26 | 29 | const { onMessage } = useOnMessage({ |
| 27 | 30 | onReceiveDataSourceMessage(commandSource, message) { |
| 28 | 31 | const { data } = commandSource |
| 29 | - const { deviceInfo, attrInfo } = (data || {}) as NodeDataDataSourceJsonType | |
| 30 | - const { deviceName } = deviceInfo || {} | |
| 31 | - const { attr } = data as NodeDataDataSourceJsonType | |
| 32 | - const { latestValue } = useLatestMessageValue(message.data, attr) | |
| 32 | + const { attr, deviceName, deviceProfileId } = data as NodeDataDataSourceJsonType | |
| 33 | + const { functionName } = productsStore.getObjectModelByIdWithIdentifier(deviceProfileId, (attr as string)) || {} | |
| 34 | + const { latestValue } = useLatestMessageValue(message.data, (attr as string)) | |
| 33 | 35 | unref(chartInstance)?.setOption({ |
| 34 | 36 | title: { |
| 35 | - text: `${deviceName || ''}-${attrInfo.name || ''}`, | |
| 37 | + text: `${deviceName || ''} - ${functionName || ''}`, | |
| 36 | 38 | }, |
| 37 | 39 | series: [{ |
| 38 | 40 | data: [{ | ... | ... |
| 1 | +import { unref } from 'vue' | |
| 2 | +import { getDeviceAttributes, getListByConfigurationId, getListByDeviceProfileIds } from '@/api/device' | |
| 3 | +import type { DeviceItemType, ThingsModelItemType } from '@/api/device/model' | |
| 4 | +import type { FormSchema } from '@/components/Form' | |
| 5 | +import { ComponentEnum } from '@/components/Form/src/enum' | |
| 6 | +import { ContentDataFieldsEnum, ContentDataFieldsNameEnum } from '@/enums/datasource' | |
| 7 | +import { useContentDataStoreWithOut } from '@/store/modules/contentData' | |
| 8 | +import type { ProductAndDevice } from '@/api/content/model' | |
| 9 | +import { ControlComponentEnum } from '@/core/Library/packages/Control' | |
| 10 | +import { useParseParams } from '@/core/LoadData' | |
| 11 | +import { DataTypeEnum } from '@/enums/objectModelEnum' | |
| 12 | + | |
| 13 | +const contentDataStore = useContentDataStoreWithOut() | |
| 14 | +export const formSchemas = (componentKey?: string): FormSchema[] => { | |
| 15 | + const isTemplate = contentDataStore.isTemplate // 判断是否是模板组态 | |
| 16 | + const isTemplateLink = contentDataStore.getIsTemplateLink | |
| 17 | + const params = useParseParams() | |
| 18 | + const { configurationId } = params | |
| 19 | + return [ | |
| 20 | + { | |
| 21 | + field: ContentDataFieldsEnum.DEVICE_PROFILE_ID, | |
| 22 | + label: ContentDataFieldsNameEnum.DEVICE_PROFILE_ID, | |
| 23 | + component: ComponentEnum.INPUT, | |
| 24 | + ifShow: false, | |
| 25 | + }, | |
| 26 | + { | |
| 27 | + field: ContentDataFieldsEnum.DEVICE_PROFILE_TEMPLATE_ID, // 模板产品id | |
| 28 | + label: ContentDataFieldsNameEnum.DEVICE_PROFILE_ID, | |
| 29 | + component: ComponentEnum.API_SELECT, | |
| 30 | + ifShow: !!isTemplate, | |
| 31 | + required: !!isTemplate, | |
| 32 | + componentProps: ({ formModel }) => { | |
| 33 | + return { | |
| 34 | + options: (unref(contentDataStore.getProductAndDevice) || []).map((item: ProductAndDevice) => ({ label: item.profileName || item.name, value: item.profileId, transportType: item?.transportType, deviceType: item?.deviceType })), | |
| 35 | + placeholder: '请选择产品', | |
| 36 | + onSelect(value: string) { | |
| 37 | + formModel[ContentDataFieldsEnum.DEVICE_PROFILE_ID] = value | |
| 38 | + formModel[ContentDataFieldsEnum.ATTR] = [] | |
| 39 | + }, | |
| 40 | + getPopupContainer: () => document.body, | |
| 41 | + } | |
| 42 | + }, | |
| 43 | + }, | |
| 44 | + { | |
| 45 | + field: ContentDataFieldsEnum.DEVICE_ID, | |
| 46 | + label: ContentDataFieldsNameEnum.DEVICE_ID, | |
| 47 | + component: ComponentEnum.API_SELECT, | |
| 48 | + ifShow: !isTemplate, | |
| 49 | + required: !isTemplate, | |
| 50 | + componentProps: ({ formModel }) => { | |
| 51 | + const organizationId = window.useParseParams().organizationId | |
| 52 | + if (!organizationId) return | |
| 53 | + return { | |
| 54 | + showSearch: true, | |
| 55 | + api: async (params: Recordable) => { | |
| 56 | + if (isTemplateLink) return await getListByConfigurationId(configurationId!) | |
| 57 | + return await getListByDeviceProfileIds(params) | |
| 58 | + }, | |
| 59 | + params: { | |
| 60 | + deviceProfileIds: unref(contentDataStore.getProductIds), | |
| 61 | + organizationId, | |
| 62 | + }, | |
| 63 | + aliasField: 'alias', | |
| 64 | + fieldNames: { label: 'name', value: 'tbDeviceId' }, | |
| 65 | + onSelect(value: string, option: DeviceItemType) { | |
| 66 | + formModel[ContentDataFieldsEnum.DEVICE_PROFILE_ID] = value ? option.deviceProfileId : null | |
| 67 | + formModel[ContentDataFieldsEnum.ATTR] = [] | |
| 68 | + formModel[ContentDataFieldsEnum.DEVICE_NAME] = value ? option.alias || option.name : '' | |
| 69 | + }, | |
| 70 | + filterOption: (inputValue: string, option: DeviceItemType) => { | |
| 71 | + return option.alias?.includes?.(inputValue) || option.name?.includes?.(inputValue) | |
| 72 | + }, | |
| 73 | + } | |
| 74 | + }, | |
| 75 | + }, | |
| 76 | + { | |
| 77 | + field: ContentDataFieldsEnum.DEVICE_NAME, | |
| 78 | + label: ContentDataFieldsNameEnum.deviceName, | |
| 79 | + component: ComponentEnum.INPUT, | |
| 80 | + ifShow: false, | |
| 81 | + }, | |
| 82 | + { | |
| 83 | + field: ContentDataFieldsEnum.ATTR, | |
| 84 | + label: ContentDataFieldsNameEnum.ATTR, | |
| 85 | + component: ComponentEnum.API_SELECT, | |
| 86 | + required: true, | |
| 87 | + componentProps: ({ formModel }) => { | |
| 88 | + const deviceProfileId = isTemplate ? formModel[ContentDataFieldsEnum.DEVICE_PROFILE_TEMPLATE_ID] : formModel[ContentDataFieldsEnum.DEVICE_PROFILE_ID] | |
| 89 | + return { | |
| 90 | + showSearch: true, | |
| 91 | + mode: 'multiple', | |
| 92 | + api: async (params: string) => { | |
| 93 | + if (!deviceProfileId) return [] | |
| 94 | + const options = await getDeviceAttributes(params) | |
| 95 | + if (componentKey === ControlComponentEnum.SWITCH) { // 开关只返回bool | |
| 96 | + return options.filter((item) => { | |
| 97 | + return item.detail.dataType.type === DataTypeEnum.BOOL | |
| 98 | + }) | |
| 99 | + } | |
| 100 | + return options | |
| 101 | + }, | |
| 102 | + params: deviceProfileId, | |
| 103 | + fieldNames: { label: 'name', value: 'identifier' }, | |
| 104 | + filterOption: (inputValue: string, option: ThingsModelItemType) => { | |
| 105 | + return option.name.includes(inputValue) | |
| 106 | + }, | |
| 107 | + } | |
| 108 | + }, | |
| 109 | + }, | |
| 110 | + ] | |
| 111 | +} | ... | ... |
| 1 | +export { default as DataSourceForm } from './index.vue' | ... | ... |
| 1 | +<script setup lang="ts"> | |
| 2 | +import { computed, unref } from 'vue' | |
| 3 | +import type { RuleError } from 'ant-design-vue/lib/form/interface' | |
| 4 | +import { formSchemas } from './config' | |
| 5 | +import { FormLayoutEnum } from '@/components/Form/src/enum' | |
| 6 | +import { BasicForm, useForm } from '@/components/Form' | |
| 7 | + | |
| 8 | +export interface ComponentExposeType { | |
| 9 | + getFieldsValue: () => any | |
| 10 | + setFieldsValue: (value: any) => void | |
| 11 | + validate: () => Promise<RuleError | any> | |
| 12 | +} | |
| 13 | + | |
| 14 | +const props = defineProps<{ componentKey?: string }>() | |
| 15 | +defineEmits(['fieldValueChange']) | |
| 16 | + | |
| 17 | +const getComponentKey = computed(() => { | |
| 18 | + return props.componentKey | |
| 19 | +}) | |
| 20 | + | |
| 21 | +const [registerForm, formActionType] = useForm({ | |
| 22 | + schemas: formSchemas(unref(getComponentKey)), | |
| 23 | + showActionButtonGroup: false, | |
| 24 | + layout: FormLayoutEnum.HORIZONTAL, | |
| 25 | + labelWidth: 70, | |
| 26 | +}) | |
| 27 | + | |
| 28 | +const getFieldsValue = () => { | |
| 29 | + return formActionType.getFieldsValue() | |
| 30 | +} | |
| 31 | + | |
| 32 | +const validate = async () => { | |
| 33 | + return await formActionType.validate() | |
| 34 | +} | |
| 35 | + | |
| 36 | +const setFieldsValue = (value: Recordable) => { | |
| 37 | + formActionType.setFieldsValue(value) | |
| 38 | + formActionType.clearValidate() | |
| 39 | +} | |
| 40 | + | |
| 41 | +defineExpose<ComponentExposeType>({ | |
| 42 | + getFieldsValue, | |
| 43 | + setFieldsValue, | |
| 44 | + validate, | |
| 45 | +}) | |
| 46 | +</script> | |
| 47 | + | |
| 48 | +<template> | |
| 49 | + <BasicForm @register="registerForm" @field-value-change="$emit('fieldValueChange')" /> | |
| 50 | +</template> | ... | ... |
| 1 | 1 | <script lang="ts" setup> |
| 2 | 2 | import { computed, ref, unref } from 'vue' |
| 3 | +import { isNullOrUnDef } from '@wry-smile/utils-is' | |
| 3 | 4 | import type { RenderComponentExposeType, RenderComponentProps } from '@/core/Library/types' |
| 4 | 5 | import { useLatestMessageValue } from '@/core/Library/hook/useLatestMessageValue' |
| 5 | 6 | import { NodeUtils } from '@/hooks/business/useNodeUtils' |
| ... | ... | @@ -25,6 +26,10 @@ const onReceiveDataSourceMessage = (_commandSource: CommandSource, message: Subs |
| 25 | 26 | const cell = nodeUtils.getCellById(node) |
| 26 | 27 | if (!cell) return |
| 27 | 28 | const { latestValue } = useLatestMessageValue(message.data, attr!) |
| 29 | + | |
| 30 | + if (isNullOrUnDef(latestValue)) | |
| 31 | + return | |
| 32 | + | |
| 28 | 33 | const flag = rangeList.find(item => item.statusValue?.toString() === latestValue?.toString()) |
| 29 | 34 | |
| 30 | 35 | if (flag) { | ... | ... |
| ... | ... | @@ -8,6 +8,7 @@ import { useContentDataStoreWithOut } from '@/store/modules/contentData' |
| 8 | 8 | import { useUserStoreWithOut } from '@/store/modules/user' |
| 9 | 9 | import { useMessage } from '@/hooks/web/useMessage' |
| 10 | 10 | import { MessageEnum } from '@/enums/messageEnum' |
| 11 | +import { useProduct } from '@/core/Library/hook/useProduct' | |
| 11 | 12 | |
| 12 | 13 | export function useContentData() { |
| 13 | 14 | const contentDataStore = useContentDataStoreWithOut() |
| ... | ... | @@ -46,6 +47,9 @@ export function useContentData() { |
| 46 | 47 | const result = mode === PageModeEnum.SHARE ? await shareModeBootstrap() : await getContent() |
| 47 | 48 | |
| 48 | 49 | if (result) { |
| 50 | + const { getAllDeviceProfileIds } = useProduct() | |
| 51 | + await getAllDeviceProfileIds(result) | |
| 52 | + | |
| 49 | 53 | const { productAndDevice, nodelist, isTemplate, templateId } = result |
| 50 | 54 | if (nodelist) contentDataStore.saveContentData(nodelist) |
| 51 | 55 | if (isTemplate) contentDataStore.setIsTemplate(isTemplate) | ... | ... |
| ... | ... | @@ -84,11 +84,13 @@ export class DataDynamicEffectHandler { |
| 84 | 84 | if (flag) { |
| 85 | 85 | const nodeEl = this.nodeUtils.getNodesForCells([cell]) |
| 86 | 86 | const { type } = record! |
| 87 | + | |
| 87 | 88 | if (type === ActRangListItemTypeEnum.SHOW) { |
| 88 | 89 | nodeEl.forEach((node) => { |
| 89 | 90 | node.classList.add(ActAnimationName.VISIBLE) |
| 90 | 91 | node.classList.remove(ActAnimationName.HIDDEN) |
| 91 | 92 | }) |
| 93 | + this.nodeUtils.updateCellValue(cell, record?.title ? record.title : latestValue) | |
| 92 | 94 | } |
| 93 | 95 | else if (type === ActRangListItemTypeEnum.HIDDEN) { |
| 94 | 96 | nodeEl.forEach((node) => { | ... | ... |
| ... | ... | @@ -2,6 +2,7 @@ import { isNull } from 'lodash-es' |
| 2 | 2 | import type { App } from 'vue' |
| 3 | 3 | import type { SubscriptionUpdateMsg } from '../type/message' |
| 4 | 4 | import { DataDynamicEffectHandler } from './dataDynamicEffectHandler' |
| 5 | +import { useObjectModelValue } from './useObjectModelValue' | |
| 5 | 6 | import type { CommandSource, LightboxModeWebsocketService } from '.' |
| 6 | 7 | import type { ActTypeEnum } from '@/enums/datasource' |
| 7 | 8 | import { DataSourceTypeEnum } from '@/enums/datasource' |
| ... | ... | @@ -52,10 +53,13 @@ export class MessageHandler { |
| 52 | 53 | |
| 53 | 54 | defaultHandler(commandSource: CommandSource, message: SubscriptionUpdateMsg) { |
| 54 | 55 | const { data, node } = commandSource |
| 55 | - const { attr } = data as NodeDataDataSourceJsonType | |
| 56 | - const { latestValue } = useLatestMessageValue(message.data, attr) | |
| 56 | + const { attr, deviceProfileId } = data as NodeDataDataSourceJsonType | |
| 57 | + | |
| 58 | + let { latestValue } = useLatestMessageValue(message.data, attr as string) | |
| 57 | 59 | if (isNull(latestValue)) return |
| 60 | + latestValue = useObjectModelValue(deviceProfileId, attr as string, latestValue) | |
| 58 | 61 | const cell = this.nodeUtils.getCellById(node) |
| 62 | + | |
| 59 | 63 | const cellValue = cell.getValue() as Element |
| 60 | 64 | cellValue.setAttribute('label', latestValue) |
| 61 | 65 | this.nodeUtils.updateCellValue(cell, cellValue) | ... | ... |
| 1 | +import type { DataType, Specs, StructJSON } from '@/api/device/model' | |
| 2 | +import { TransportTypeEnum } from '@/enums/deviceEnum' | |
| 3 | +import { DataTypeEnum } from '@/enums/objectModelEnum' | |
| 4 | +import { useJsonParse } from '@/hooks/business/useJSONParse' | |
| 5 | +import { useProductsStoreWithOut } from '@/store/modules/products' | |
| 6 | + | |
| 7 | +function getBoolTypeValue(value: number, Specs: Specs) { | |
| 8 | + const { boolOpen, boolClose } = Specs | |
| 9 | + | |
| 10 | + return Number(value) ? boolOpen : boolClose | |
| 11 | +} | |
| 12 | + | |
| 13 | +function getEnumTypeValue(value: number, specsList: Specs[]) { | |
| 14 | + const res = specsList.find(item => item.value === Number(value)) | |
| 15 | + | |
| 16 | + return res?.name | |
| 17 | +} | |
| 18 | + | |
| 19 | +function getStructTypeValue(value: string, specs: StructJSON[]): string { | |
| 20 | + const res = useJsonParse(value).value | |
| 21 | + | |
| 22 | + function generateStruct(specs: StructJSON[], value: Recordable) { | |
| 23 | + if (!value) return {} | |
| 24 | + | |
| 25 | + return specs.reduce((prev, next) => { | |
| 26 | + return { | |
| 27 | + ...prev, | |
| 28 | + [next.functionName]: getValueByType(next.dataType!.type, value[next.identifier], next.dataType!), | |
| 29 | + } | |
| 30 | + }, {}) | |
| 31 | + } | |
| 32 | + | |
| 33 | + return JSON.stringify(generateStruct(specs, res)) | |
| 34 | +} | |
| 35 | + | |
| 36 | +function getValueByType(type: string, value: any, dataType: DataType) { | |
| 37 | + switch (type) { | |
| 38 | + case DataTypeEnum.BOOL: | |
| 39 | + return getBoolTypeValue(value, dataType.specs as Specs) | |
| 40 | + case DataTypeEnum.STRUCT: | |
| 41 | + return getStructTypeValue(value, dataType.specs as StructJSON[]) | |
| 42 | + case DataTypeEnum.ENUM: | |
| 43 | + return getEnumTypeValue(value, dataType.specsList as Specs[]) | |
| 44 | + default: | |
| 45 | + return value | |
| 46 | + } | |
| 47 | +} | |
| 48 | + | |
| 49 | +function getModbusDeviceValueByType(value: any, dataType: DataType) { | |
| 50 | + if (dataType.type === DataTypeEnum.BOOL && dataType.specsList && dataType.specsList.length) | |
| 51 | + return getEnumTypeValue(value, dataType.specsList as Specs[]) | |
| 52 | + | |
| 53 | + return value | |
| 54 | +} | |
| 55 | + | |
| 56 | +export function useObjectModelValue(deviceProfileId: string, attr: string, value: any) { | |
| 57 | + const productsStore = useProductsStoreWithOut() | |
| 58 | + | |
| 59 | + const result = productsStore.getObjectModelByIdWithIdentifier(deviceProfileId, attr) | |
| 60 | + if (!result) | |
| 61 | + return value | |
| 62 | + | |
| 63 | + const products = productsStore.getProductDetailById(deviceProfileId) | |
| 64 | + const isTCPModbus = products.transportType === TransportTypeEnum.TCP && result.extensionDesc?.originalDataType | |
| 65 | + | |
| 66 | + return isTCPModbus ? getModbusDeviceValueByType(value, result.specs.dataType) : getValueByType(result.specs.dataType.type as DataTypeEnum, value, result.specs.dataType) | |
| 67 | +} | ... | ... |
src/enums/alarmEnum.ts
0 → 100644
| 1 | +export enum AlarmStatusEnum { | |
| 2 | + CLEARED_UN_ACK = 'CLEARED_UNACK', | |
| 3 | + ACTIVE_UN_ACK = 'ACTIVE_UNACK', | |
| 4 | + CLEARED_ACK = 'CLEARED_ACK', | |
| 5 | + ACTIVE_ACK = 'ACTIVE_ACK', | |
| 6 | +} | |
| 7 | + | |
| 8 | +export enum AlarmStatusColorEnum { | |
| 9 | + CLEARED_UNACK = 'red', | |
| 10 | + ACTIVE_UNACK = 'orange', | |
| 11 | + CLEARED_ACK = 'cyan', | |
| 12 | + ACTIVE_ACK = 'green', | |
| 13 | +} | |
| 14 | + | |
| 15 | +export enum AlarmStatusNameEnum { | |
| 16 | + CLEARED_UNACK = '清除未确认', | |
| 17 | + ACTIVE_UNACK = '激活未确认', | |
| 18 | + CLEARED_ACK = '清除已确认', | |
| 19 | + ACTIVE_ACK = '激活已确认', | |
| 20 | +} | ... | ... |
src/enums/alarmListEnum.ts
deleted
100644 → 0
| ... | ... | @@ -7,3 +7,41 @@ export enum CommandWayNameEnum { |
| 7 | 7 | ONE_WAY = '单向', |
| 8 | 8 | TWO_WAY = '双向', |
| 9 | 9 | } |
| 10 | + | |
| 11 | +export enum CommandCallWayEnum { | |
| 12 | + SYNC = 'SYNC', | |
| 13 | + ASYNC = 'ASYNC', | |
| 14 | +} | |
| 15 | + | |
| 16 | +export enum CommandTypeEnum { | |
| 17 | + CUSTOM = 0, | |
| 18 | + SERVICE = 1, | |
| 19 | + ATTRIBUTE = 2, | |
| 20 | + API = 'api', | |
| 21 | +} | |
| 22 | + | |
| 23 | +export enum CommandTypeNameEnum { | |
| 24 | + CUSTOM = '自定义', | |
| 25 | + SERVICE = '服务', | |
| 26 | + ATTRIBUTE = '属性', | |
| 27 | +} | |
| 28 | + | |
| 29 | +export enum CommandDeliveryWayEnum { | |
| 30 | + CUSTOM = 'CUSTOM', | |
| 31 | + SERVICE = 'SERVICE', | |
| 32 | + MODBUS = 'MODBUS', | |
| 33 | +} | |
| 34 | + | |
| 35 | +export enum CommandDeliveryWayNameEnum { | |
| 36 | + CUSTOM = '自定义命令', | |
| 37 | + SERVICE = '服务调用', | |
| 38 | + MODBUS = 'MODBUS', | |
| 39 | +} | |
| 40 | + | |
| 41 | +export enum CommandMethodEnum { | |
| 42 | + THINGSKIT = 'methodThingskit', | |
| 43 | +} | |
| 44 | + | |
| 45 | +export enum ModbusCRCEnum { | |
| 46 | + CRC_16_LOWER = 'CRC_16_LOWER', | |
| 47 | +} | ... | ... |
| ... | ... | @@ -64,50 +64,6 @@ export enum ActTypeNameEnum { |
| 64 | 64 | VARIABLE_IMAGE = '变量图片', |
| 65 | 65 | } |
| 66 | 66 | |
| 67 | -export enum DeviceTypeEnum { | |
| 68 | - /** | |
| 69 | - * @description 网关设备 | |
| 70 | - */ | |
| 71 | - GATEWAY = 'GATEWAY', | |
| 72 | - | |
| 73 | - /** | |
| 74 | - * @description 直连设备 | |
| 75 | - */ | |
| 76 | - DIRECT_CONNECTION = 'DIRECT_CONNECTION', | |
| 77 | - | |
| 78 | - /** | |
| 79 | - * @description 网关子设备 | |
| 80 | - */ | |
| 81 | - SENSOR = 'SENSOR', | |
| 82 | -} | |
| 83 | - | |
| 84 | -export enum DeviceTypeNameEnum { | |
| 85 | - /** | |
| 86 | - * @description 网关设备 | |
| 87 | - */ | |
| 88 | - GATEWAY = '网关设备', | |
| 89 | - | |
| 90 | - /** | |
| 91 | - * @description 直连设备 | |
| 92 | - */ | |
| 93 | - DIRECT_CONNECTION = '直连设备', | |
| 94 | - | |
| 95 | - /** | |
| 96 | - * @description 网关子设备 | |
| 97 | - */ | |
| 98 | - SENSOR = '网关子设备', | |
| 99 | -} | |
| 100 | - | |
| 101 | -export enum TransportTypeEnum { | |
| 102 | - MQTT = 'MQTT', | |
| 103 | - TCP = 'TCP', | |
| 104 | -} | |
| 105 | - | |
| 106 | -export enum CodeTypeEnum { | |
| 107 | - CUSTOM = 'CUSTOM', | |
| 108 | - MODBUS_RTU = 'MODBUS_RTU', | |
| 109 | -} | |
| 110 | - | |
| 111 | 67 | export enum EventTypeEnum { |
| 112 | 68 | /** |
| 113 | 69 | * @description 鼠标抬起 |
| ... | ... | @@ -238,17 +194,9 @@ export enum ContentDataFieldsEnum { |
| 238 | 194 | DEVICE_ID = 'deviceId', |
| 239 | 195 | ORG_ID = 'orgId', |
| 240 | 196 | ATTR = 'attr', |
| 241 | - VIDEO_FILTER = 'videoFilter', | |
| 242 | - VIDEO_ID = 'id', | |
| 243 | - VIDEO_URL = 'videoUrl', | |
| 244 | - ACCESS_MODE = 'accessMode', | |
| 245 | - VIDEO_FLAG = 'videoComponentFlag', | |
| 246 | - | |
| 247 | - ATTR_INFO = 'attrInfo', | |
| 248 | - DEVICE_INFO = 'deviceInfo', | |
| 249 | - TRANSPORT_TYPE = 'transportType', | |
| 197 | + DEVICE_NAME = 'deviceName', | |
| 198 | + | |
| 250 | 199 | CODE_TYPE = 'codeType', |
| 251 | - DEVICE_ADDITIONAL_INFO = 'deviceAdditionalInfo', | |
| 252 | 200 | } |
| 253 | 201 | |
| 254 | 202 | export enum ContentDataFieldsNameEnum { |
| ... | ... | @@ -257,14 +205,7 @@ export enum ContentDataFieldsNameEnum { |
| 257 | 205 | ORG_ID = '组织', |
| 258 | 206 | DEVICE_ID = '设备', |
| 259 | 207 | ATTR = '属性', |
| 260 | - VIDEO_ID = '视频流', | |
| 261 | - ACCESS_MODE = 'ACCESS_MODE', | |
| 262 | - VIDEO_URL = '视频地址', | |
| 263 | - | |
| 264 | - ATTR_INFO = '物模型属性详情', | |
| 265 | - DEVICE_INFO = '设备详情', | |
| 266 | - TRANSPORT_TYPE = '传输协议', | |
| 267 | - DEVICE_ADDITIONAL_INFO = '设备额外信息', | |
| 208 | + deviceName = '设备名称', | |
| 268 | 209 | } |
| 269 | 210 | |
| 270 | 211 | export enum VariableImageSourceEnum { |
| ... | ... | @@ -277,46 +218,6 @@ export enum VariableImageSourceNameEnum { |
| 277 | 218 | GALLERY = '图库图形', |
| 278 | 219 | } |
| 279 | 220 | |
| 280 | -export enum CommandDeliveryWayEnum { | |
| 281 | - CUSTOM = 'CUSTOM', | |
| 282 | - SERVICE = 'SERVICE', | |
| 283 | - MODBUS = 'MODBUS', | |
| 284 | -} | |
| 285 | - | |
| 286 | -export enum CommandDeliveryWayNameEnum { | |
| 287 | - CUSTOM = '自定义命令', | |
| 288 | - SERVICE = '服务调用', | |
| 289 | - MODBUS = 'MODBUS', | |
| 290 | -} | |
| 291 | - | |
| 292 | -export enum FunctionType { | |
| 293 | - PROPERTIES = 'properties', | |
| 294 | - EVENTS = 'events', | |
| 295 | - SERVICE = 'services', | |
| 296 | -} | |
| 297 | - | |
| 298 | -export enum AssessMode { | |
| 299 | - READ = 'r', | |
| 300 | - WRITE = 'w', | |
| 301 | -} | |
| 302 | - | |
| 303 | -export enum CommandTypeEnum { | |
| 304 | - SERVICE = 1, | |
| 305 | - API = 'API', | |
| 306 | -} | |
| 307 | - | |
| 308 | -/** | |
| 309 | - * 新增参数 动态显示表单 | |
| 310 | - */ | |
| 311 | -export enum DataTypeEnum { | |
| 312 | - NUMBER_INT = 'INT', | |
| 313 | - NUMBER_DOUBLE = 'DOUBLE', | |
| 314 | - STRING = 'TEXT', | |
| 315 | - STRUCT = 'STRUCT', | |
| 316 | - BOOL = 'BOOL', | |
| 317 | - ENUM = 'ENUM', | |
| 318 | -} | |
| 319 | - | |
| 320 | 221 | export enum AggregateTypeEnum { |
| 321 | 222 | MIN = 'MIN', |
| 322 | 223 | MAX = 'MAX', |
| ... | ... | @@ -339,24 +240,3 @@ export enum SocketSubscriberEnum { |
| 339 | 240 | HISTORY_CMDS = 'historyCmds', |
| 340 | 241 | } |
| 341 | 242 | |
| 342 | -export enum AlarmStatusEnum { | |
| 343 | - CLEARED_UN_ACK = 'CLEARED_UNACK', | |
| 344 | - ACTIVE_UN_ACK = 'ACTIVE_UNACK', | |
| 345 | - CLEARED_ACK = 'CLEARED_ACK', | |
| 346 | - ACTIVE_ACK = 'ACTIVE_ACK', | |
| 347 | -} | |
| 348 | - | |
| 349 | -export enum AlarmStatusColorEnum { | |
| 350 | - CLEARED_UNACK = 'red', | |
| 351 | - ACTIVE_UNACK = 'orange', | |
| 352 | - CLEARED_ACK = 'cyan', | |
| 353 | - ACTIVE_ACK = 'green', | |
| 354 | -} | |
| 355 | - | |
| 356 | -export enum AlarmStatusNameEnum { | |
| 357 | - CLEARED_UNACK = '清除未确认', | |
| 358 | - ACTIVE_UNACK = '激活未确认', | |
| 359 | - CLEARED_ACK = '清除已确认', | |
| 360 | - ACTIVE_ACK = '激活已确认', | |
| 361 | -} | |
| 362 | - | ... | ... |
src/enums/deviceEnum.ts
0 → 100644
| 1 | +export enum DeviceTypeEnum { | |
| 2 | + /** | |
| 3 | + * @description 网关设备 | |
| 4 | + */ | |
| 5 | + GATEWAY = 'GATEWAY', | |
| 6 | + | |
| 7 | + /** | |
| 8 | + * @description 直连设备 | |
| 9 | + */ | |
| 10 | + DIRECT_CONNECTION = 'DIRECT_CONNECTION', | |
| 11 | + | |
| 12 | + /** | |
| 13 | + * @description 网关子设备 | |
| 14 | + */ | |
| 15 | + SENSOR = 'SENSOR', | |
| 16 | +} | |
| 17 | + | |
| 18 | +export enum DeviceTypeNameEnum { | |
| 19 | + /** | |
| 20 | + * @description 网关设备 | |
| 21 | + */ | |
| 22 | + GATEWAY = '网关设备', | |
| 23 | + | |
| 24 | + /** | |
| 25 | + * @description 直连设备 | |
| 26 | + */ | |
| 27 | + DIRECT_CONNECTION = '直连设备', | |
| 28 | + | |
| 29 | + /** | |
| 30 | + * @description 网关子设备 | |
| 31 | + */ | |
| 32 | + SENSOR = '网关子设备', | |
| 33 | +} | |
| 34 | + | |
| 35 | +export enum TransportTypeEnum { | |
| 36 | + DEFAULT = 'DEFAULT', | |
| 37 | + MQTT = 'MQTT', | |
| 38 | + COAP = 'COAP', | |
| 39 | + LWM2M = 'LWM2M', | |
| 40 | + SNMP = 'SNMP', | |
| 41 | + TCP = 'TCP', | |
| 42 | +} | |
| 43 | + | |
| 44 | +export enum TCPProtocolTypeEnum { | |
| 45 | + CUSTOM = 'CUSTOM', | |
| 46 | + MODBUS_RTU = 'MODBUS_RTU', | |
| 47 | +} | |
| 48 | + | |
| 49 | +export enum TCPProtocolTypeNameEnum { | |
| 50 | + CUSTOM = '自定义', | |
| 51 | + MODBUS_RTU = 'MODBUS_RTU', | |
| 52 | +} | ... | ... |
| 1 | 1 | /** |
| 2 | - * @description TCP物模型拓展描述符数据格式 | |
| 2 | + * 新增参数 动态显示表单 | |
| 3 | 3 | */ |
| 4 | -export enum TCPObjectModelActionTypeEnum { | |
| 5 | - BOOL = '05', | |
| 6 | - INT = '06', | |
| 7 | - DOUBLE = '16', | |
| 4 | +export enum DataTypeEnum { | |
| 5 | + NUMBER_INT = 'INT', | |
| 6 | + NUMBER_DOUBLE = 'DOUBLE', | |
| 7 | + STRING = 'TEXT', | |
| 8 | + STRUCT = 'STRUCT', | |
| 9 | + BOOL = 'BOOL', | |
| 10 | + ENUM = 'ENUM', | |
| 11 | +} | |
| 12 | + | |
| 13 | +export enum FunctionTypeEnum { | |
| 14 | + PROPERTIES = 'properties', | |
| 15 | + EVENTS = 'events', | |
| 16 | + SERVICE = 'services', | |
| 17 | +} | |
| 18 | + | |
| 19 | +export enum ObjectModelAccessModeEnum { | |
| 20 | + READ = 'r', | |
| 21 | + READ_AND_WRITE = 'rw', | |
| 22 | +} | |
| 23 | + | |
| 24 | +export enum OriginalDataTypeEnum { | |
| 25 | + INT16_AB = 'INT16_AB', | |
| 26 | + INT16_BA = 'INT16_BA', | |
| 27 | + UINT16_AB = 'UINT16_AB', | |
| 28 | + UINT16_BA = 'UINT16_BA', | |
| 29 | + INT32_AB_CD = 'INT32_AB_CD', | |
| 30 | + INT32_CD_AB = 'INT32_CD_AB', | |
| 31 | + INT32_BA_DC = 'INT32_BA_DC', | |
| 32 | + INT32_DC_BA = 'INT32_DC_BA', | |
| 33 | + UINT32_AB_CD = 'UINT32_AB_CD', | |
| 34 | + UINT32_CD_AB = 'UINT32_CD_AB', | |
| 35 | + UINT32_BA_DC = 'UINT32_BA_DC', | |
| 36 | + UINT32_DC_BA = 'UINT32_DC_BA', | |
| 37 | + FLOAT_AB_CD = 'FLOAT_AB_CD', | |
| 38 | + FLOAT_CD_AB = 'FLOAT_CD_AB', | |
| 39 | + FLOAT_BA_DC = 'FLOAT_BA_DC', | |
| 40 | + FLOAT_DC_BA = 'FLOAT_DC_BA', | |
| 41 | + DOUBLE = 'DOUBLE', | |
| 42 | + STRING = 'STRING', | |
| 43 | + BOOLEAN = 'BOOLEAN', | |
| 44 | + BITS = 'BITS', | |
| 45 | +} | |
| 46 | + | |
| 47 | +export enum OriginalDataTypeNameEnum { | |
| 48 | + INT16_AB = '16位有符号整数AB', | |
| 49 | + INT16_BA = '16位有符号整数BA', | |
| 50 | + UINT16_AB = '16位无符号整数AB', | |
| 51 | + UINT16_BA = '16位无符号整数BA', | |
| 52 | + INT32_AB_CD = '32位有符号整数AB_CD', | |
| 53 | + INT32_CD_AB = '32位有符号整数CD_AB', | |
| 54 | + INT32_BA_DC = '32位有符号整数BA_DC', | |
| 55 | + INT32_DC_BA = '32位有符号整数DC_BA', | |
| 56 | + UINT32_AB_CD = '32位无符号整数AB_CD', | |
| 57 | + UINT32_CD_AB = '32位无符号整数CD_AB', | |
| 58 | + UINT32_BA_DC = '32位无符号整数BA_DC', | |
| 59 | + UINT32_DC_BA = '32位无符号整数DC_BA', | |
| 60 | + FLOAT_AB_CD = '单精度浮点型AB_CD', | |
| 61 | + FLOAT_CD_AB = '单精度浮点型CD_AB', | |
| 62 | + FLOAT_BA_DC = '单精度浮点型BA_DC', | |
| 63 | + FLOAT_DC_BA = '单精度浮点型DC_BA', | |
| 64 | + DOUBLE = '双精度浮点型', | |
| 65 | + STRING = '字符串', | |
| 66 | + BOOLEAN = '布尔型', | |
| 67 | + BITS = '位', | |
| 68 | +} | |
| 69 | + | |
| 70 | +export enum ExtendDescOperationTypeEnum { | |
| 71 | + INPUT_STATUS_R_02 = 'inputStatus_r_02', | |
| 72 | + COIL_STATUS_R_01 = 'coilStatus_r_01', | |
| 73 | + COIL_STATUS_RW_01_05 = 'coilStatus_rw_01_05', | |
| 74 | + COIL_STATUS_RW_01_0F = 'coilStatus_rw_01_0F', | |
| 75 | + COIL_STATUS_W_05 = 'coilStatus_w_05', | |
| 76 | + COIL_STATUS_W_0F = 'coilStatus_w_0F', | |
| 77 | + HOLDING_REGISTER_R_03 = 'holdingRegister_r_03', | |
| 78 | + HOLDING_REGISTER_RW_03_06 = 'holdingRegister_rw_03_06', | |
| 79 | + HOLDING_REGISTER_RW_03_10 = 'holdingRegister_rw_03_10', | |
| 80 | + HOLDING_REGISTER_W_06 = 'holdingRegister_w_06', | |
| 81 | + HOLDING_REGISTER_W_10 = 'holdingRegister_w_10', | |
| 82 | + INPUT_REGISTER_R_04 = 'inputRegister_r_04', | |
| 83 | +} | |
| 84 | + | |
| 85 | +export enum ExtendDescOperationTypeNameEnum { | |
| 86 | + INPUT_STATUS_R_02 = '离散量输入(只读,0x02)', | |
| 87 | + COIL_STATUS_R_01 = '线圈状态(只读,0x01)', | |
| 88 | + COIL_STATUS_RW_01_05 = '线圈状态(读写,读取使用0x01,写入使用0x05)', | |
| 89 | + COIL_STATUS_RW_01_0F = '线圈状态(读写,读取使用0x01,写入使用0x0F)', | |
| 90 | + COIL_STATUS_W_05 = '线圈状态(只写,0x05)', | |
| 91 | + COIL_STATUS_W_0F = '线圈状态(只写,0x0F)', | |
| 92 | + HOLDING_REGISTER_R_03 = '保持寄存器(只读,0x03)', | |
| 93 | + HOLDING_REGISTER_RW_03_06 = '保持寄存器(读写,读取使用0x03,写入使用0x06)', | |
| 94 | + HOLDING_REGISTER_RW_03_10 = '保持寄存器(读写,读取使用0x03,写入使用0x10)', | |
| 95 | + HOLDING_REGISTER_W_06 = '保持寄存器(只写,0x06)', | |
| 96 | + HOLDING_REGISTER_W_10 = '保持寄存器(只写,0x10)', | |
| 97 | + INPUT_REGISTER_R_04 = '输入寄存器(只读,0x04)', | |
| 8 | 98 | } | ... | ... |
src/enums/videoEnum.ts
0 → 100644
src/hooks/business/useBaseConversion.ts
0 → 100644
| 1 | +import { OriginalDataTypeEnum } from '@/enums/objectModelEnum' | |
| 2 | +import { useParseOriginalDataType } from '@/hooks/business/useParseOriginalDataType' | |
| 3 | + | |
| 4 | +export function getBoolValueByStatus(status: boolean) { | |
| 5 | + return status ? parseInt('FF00', 16) : parseInt('0000', 16) | |
| 6 | +} | |
| 7 | + | |
| 8 | +export function useBaseConversion() { | |
| 9 | + function DecTo32Float(number: number) { | |
| 10 | + const arr = new Uint8Array(4) | |
| 11 | + const view = new DataView(arr.buffer) | |
| 12 | + view.setFloat32(0, +number) | |
| 13 | + return arr | |
| 14 | + } | |
| 15 | + | |
| 16 | + function DecTo64Double(number: number) { | |
| 17 | + const arr = new Uint8Array(8) | |
| 18 | + const view = new DataView(arr.buffer) | |
| 19 | + view.setFloat64(0, +number) | |
| 20 | + return arr | |
| 21 | + } | |
| 22 | + | |
| 23 | + function arrToBase(toBase: number, arr: Uint8Array) { | |
| 24 | + let result = '' | |
| 25 | + for (let i = 0; i < arr.length; i++) | |
| 26 | + result += (256 + arr[i]).toString(toBase).substring(1).toUpperCase() | |
| 27 | + | |
| 28 | + return result | |
| 29 | + } | |
| 30 | + | |
| 31 | + function DecTo16Uint(number: number) { | |
| 32 | + const arr = new Uint8Array(2) | |
| 33 | + const view = new DataView(arr.buffer) | |
| 34 | + view.setUint16(0, +number) | |
| 35 | + return arr | |
| 36 | + } | |
| 37 | + | |
| 38 | + function DecTo16Int(number: number) { | |
| 39 | + const arr = new Uint8Array(2) | |
| 40 | + const view = new DataView(arr.buffer) | |
| 41 | + view.setInt16(0, +number) | |
| 42 | + return arr | |
| 43 | + } | |
| 44 | + | |
| 45 | + function DecTo32Uint(number: number) { | |
| 46 | + const arr = new Uint8Array(4) | |
| 47 | + const view = new DataView(arr.buffer) | |
| 48 | + view.setUint32(0, +number) | |
| 49 | + return arr | |
| 50 | + } | |
| 51 | + | |
| 52 | + function DecTo32Int(number: number) { | |
| 53 | + const arr = new Uint8Array(4) | |
| 54 | + const view = new DataView(arr.buffer) | |
| 55 | + view.setInt32(0, +number) | |
| 56 | + return arr | |
| 57 | + } | |
| 58 | + | |
| 59 | + function DecToBinaryByType(type: OriginalDataTypeEnum, number: number) { | |
| 60 | + switch (type) { | |
| 61 | + case OriginalDataTypeEnum.INT16_AB: | |
| 62 | + case OriginalDataTypeEnum.INT16_BA: | |
| 63 | + return arrToBase(2, DecTo16Int(number)) | |
| 64 | + | |
| 65 | + case OriginalDataTypeEnum.UINT16_AB: | |
| 66 | + case OriginalDataTypeEnum.UINT16_BA: | |
| 67 | + return arrToBase(2, DecTo16Uint(number)) | |
| 68 | + | |
| 69 | + case OriginalDataTypeEnum.INT32_AB_CD: | |
| 70 | + case OriginalDataTypeEnum.INT32_CD_AB: | |
| 71 | + case OriginalDataTypeEnum.INT32_BA_DC: | |
| 72 | + case OriginalDataTypeEnum.INT32_DC_BA: | |
| 73 | + return arrToBase(2, DecTo32Int(number)) | |
| 74 | + | |
| 75 | + case OriginalDataTypeEnum.UINT32_AB_CD: | |
| 76 | + case OriginalDataTypeEnum.UINT32_CD_AB: | |
| 77 | + case OriginalDataTypeEnum.UINT32_BA_DC: | |
| 78 | + case OriginalDataTypeEnum.UINT32_DC_BA: | |
| 79 | + return arrToBase(2, DecTo32Uint(number)) | |
| 80 | + | |
| 81 | + case OriginalDataTypeEnum.FLOAT_AB_CD: | |
| 82 | + case OriginalDataTypeEnum.FLOAT_CD_AB: | |
| 83 | + case OriginalDataTypeEnum.FLOAT_BA_DC: | |
| 84 | + case OriginalDataTypeEnum.FLOAT_DC_BA: | |
| 85 | + return arrToBase(2, DecTo32Float(number)) | |
| 86 | + | |
| 87 | + case OriginalDataTypeEnum.DOUBLE: | |
| 88 | + return arrToBase(2, DecTo64Double(number)) | |
| 89 | + } | |
| 90 | + } | |
| 91 | + | |
| 92 | + function SplitStringToGroupByItemLength( | |
| 93 | + value: string, | |
| 94 | + itemLength = 8, | |
| 95 | + ignoreLessThan = false, | |
| 96 | + ): string[] { | |
| 97 | + const reg = new RegExp(`.{${ignoreLessThan ? '' : '1,'}${itemLength}}`, 'g') | |
| 98 | + return value.match(reg) || [] | |
| 99 | + } | |
| 100 | + | |
| 101 | + function ExchangeByteOrder(binary: string, order: string) { | |
| 102 | + const group = SplitStringToGroupByItemLength(binary) | |
| 103 | + | |
| 104 | + const BASE_ORDER = { | |
| 105 | + A: 0, | |
| 106 | + B: 1, | |
| 107 | + C: 2, | |
| 108 | + D: 3, | |
| 109 | + } | |
| 110 | + | |
| 111 | + const array: string[] = Array.from({ length: binary.length / 8 }) | |
| 112 | + | |
| 113 | + order | |
| 114 | + .split('') | |
| 115 | + .forEach((bytePosition, index) => (array[index] = group[BASE_ORDER[bytePosition as keyof typeof BASE_ORDER] as number])) | |
| 116 | + | |
| 117 | + return array.join('') | |
| 118 | + } | |
| 119 | + | |
| 120 | + function ByteToHex(binary: string) { | |
| 121 | + const group = SplitStringToGroupByItemLength(binary, 16) | |
| 122 | + return group.map(byte => parseInt(byte, 2).toString(16)) | |
| 123 | + } | |
| 124 | + | |
| 125 | + function ByteToDec(binary: string) { | |
| 126 | + const group = SplitStringToGroupByItemLength(binary, 16) | |
| 127 | + return group.map(byte => parseInt(byte, 2)) | |
| 128 | + } | |
| 129 | + | |
| 130 | + function StringToHEXBuffer(string: string | number) { | |
| 131 | + return string | |
| 132 | + .toString() | |
| 133 | + .split('') | |
| 134 | + .map(string => string.charCodeAt(0).toString(16)) | |
| 135 | + .reverse() | |
| 136 | + } | |
| 137 | + | |
| 138 | + function getRegisterValueByOriginalDataType( | |
| 139 | + value: number, | |
| 140 | + type: OriginalDataTypeEnum, | |
| 141 | + additional?: { bitMask?: number; registerNumber?: number }, | |
| 142 | + ) { | |
| 143 | + const { exchangeSortFlag } = useParseOriginalDataType(type) | |
| 144 | + // eslint-disable-next-line no-console | |
| 145 | + console.groupCollapsed('Modbus Debug') | |
| 146 | + | |
| 147 | + let result: number[] | |
| 148 | + | |
| 149 | + if (type === OriginalDataTypeEnum.BOOLEAN) { | |
| 150 | + result = [getBoolValueByStatus(!!value)] | |
| 151 | + } | |
| 152 | + else if (type === OriginalDataTypeEnum.BITS) { | |
| 153 | + const { bitMask = 0 } = additional || {} | |
| 154 | + const binaryArray = Array.from({ length: 16 }, () => 0) | |
| 155 | + binaryArray[15 - bitMask] = value | |
| 156 | + result = [parseInt(binaryArray.join(''), 2)] | |
| 157 | + } | |
| 158 | + else if (type === OriginalDataTypeEnum.STRING) { | |
| 159 | + let buffer = StringToHEXBuffer(value) | |
| 160 | + const { registerNumber = 0 } = additional || {} | |
| 161 | + | |
| 162 | + if (buffer.length < registerNumber * 2) { | |
| 163 | + buffer = [ | |
| 164 | + ...Array.from({ length: registerNumber * 2 - buffer.length }, () => '00'), | |
| 165 | + ...buffer, | |
| 166 | + ] | |
| 167 | + } | |
| 168 | + | |
| 169 | + result = SplitStringToGroupByItemLength(buffer.join(''), 4).map(hex => parseInt(hex, 16)) | |
| 170 | + } | |
| 171 | + else { | |
| 172 | + // eslint-disable-next-line no-console | |
| 173 | + console.table({ input: value, sort: exchangeSortFlag, type }) | |
| 174 | + | |
| 175 | + let binary = DecToBinaryByType(type, value)! | |
| 176 | + | |
| 177 | + // eslint-disable-next-line no-console | |
| 178 | + console.table({ beforeExchange: binary }) | |
| 179 | + | |
| 180 | + if (exchangeSortFlag) binary = ExchangeByteOrder(binary, exchangeSortFlag) | |
| 181 | + result = ByteToDec(binary) | |
| 182 | + | |
| 183 | + // eslint-disable-next-line no-console | |
| 184 | + console.table({ | |
| 185 | + afterEchange: binary, | |
| 186 | + dec: result.toString(), | |
| 187 | + hex: ByteToHex(binary).toString(), | |
| 188 | + }) | |
| 189 | + } | |
| 190 | + | |
| 191 | + // eslint-disable-next-line no-console | |
| 192 | + console.groupEnd() | |
| 193 | + | |
| 194 | + return result | |
| 195 | + } | |
| 196 | + | |
| 197 | + return { | |
| 198 | + DecToBinaryByType, | |
| 199 | + ByteToDec, | |
| 200 | + ByteToHex, | |
| 201 | + ExchangeByteOrder, | |
| 202 | + SplitStringToGroupByItemLength, | |
| 203 | + getRegisterValueByOriginalDataType, | |
| 204 | + StringToHEXBuffer, | |
| 205 | + } | |
| 206 | +} | ... | ... |
src/hooks/business/useCommandDelivery.ts
0 → 100644
| 1 | +import { ref } from 'vue' | |
| 2 | +import { isFunction } from '@wry-smile/utils-is' | |
| 3 | +import { useMessage } from '../web/useMessage' | |
| 4 | +import { useCoverModbusCommand } from './useCoverModbusCommand' | |
| 5 | +import { | |
| 6 | + TCPProtocolTypeEnum, | |
| 7 | + TransportTypeEnum, | |
| 8 | +} from '@/enums/deviceEnum' | |
| 9 | +import { FunctionTypeEnum } from '@/enums/objectModelEnum' | |
| 10 | +import { CommandCallWayEnum, CommandMethodEnum, CommandTypeEnum, CommandWayEnum } from '@/enums/commandEnum' | |
| 11 | +import type { DeviceItemType, DeviceProfileItemType, RpcCommandType, Tsl } from '@/api/device/model' | |
| 12 | +import { getDeviceActive, getDeviceInfo, doCommandDelivery as rpcCommandApi } from '@/api/device' | |
| 13 | +import { useProductsStoreWithOut } from '@/store/modules/products' | |
| 14 | + | |
| 15 | +interface SetupType { | |
| 16 | + entityId: string | |
| 17 | + transportType: TransportTypeEnum | |
| 18 | + isTCPModbus: boolean | |
| 19 | + deviceCode: string | undefined | |
| 20 | + deviceDetail: DeviceItemType | |
| 21 | + objectModel: Tsl | undefined | |
| 22 | + identifier: string | |
| 23 | +} | |
| 24 | + | |
| 25 | +export interface DoCommandDeliverParamsType { | |
| 26 | + value: any | |
| 27 | + deviceDetail?: DeviceItemType | |
| 28 | + deviceId?: string | |
| 29 | + identifier?: string | |
| 30 | + objectModel?: Tsl | |
| 31 | + deviceProfileId?: string | |
| 32 | + deviceProfileDetail?: DeviceProfileItemType | |
| 33 | + way?: CommandWayEnum | |
| 34 | + cmdType?: CommandTypeEnum | |
| 35 | + transportType?: TransportTypeEnum | |
| 36 | + penetration?: boolean | |
| 37 | + | |
| 38 | + beforeFetch?: ( | |
| 39 | + rpcCommand: RpcCommandType, | |
| 40 | + setup: SetupType | |
| 41 | + ) => RpcCommandType | Promise<RpcCommandType> | |
| 42 | +} | |
| 43 | + | |
| 44 | +export function useCommandDelivery() { | |
| 45 | + const loading = ref(false) | |
| 46 | + async function doSetup(params: DoCommandDeliverParamsType) { | |
| 47 | + let { deviceDetail, identifier, deviceProfileId, objectModel, transportType } = params | |
| 48 | + const { deviceId, deviceProfileDetail } = params | |
| 49 | + | |
| 50 | + const entityId = deviceId || deviceDetail?.tbDeviceId | |
| 51 | + if (!entityId) | |
| 52 | + throw new Error('not found entityId') | |
| 53 | + | |
| 54 | + identifier = identifier || objectModel?.identifier | |
| 55 | + | |
| 56 | + if (!identifier) | |
| 57 | + throw new Error('not found identifier') | |
| 58 | + | |
| 59 | + transportType = transportType || (deviceDetail?.transportType as TransportTypeEnum) | |
| 60 | + | |
| 61 | + if ( | |
| 62 | + !transportType | |
| 63 | + || (transportType === TransportTypeEnum.TCP && !deviceDetail) | |
| 64 | + || !deviceDetail?.deviceProfile | |
| 65 | + ) { | |
| 66 | + deviceDetail = await getDeviceInfo(entityId) | |
| 67 | + transportType = deviceDetail.transportType as TransportTypeEnum | |
| 68 | + } | |
| 69 | + | |
| 70 | + const isTCPModbus | |
| 71 | + = transportType === TransportTypeEnum.TCP | |
| 72 | + && deviceDetail?.deviceProfile?.profileData?.transportConfiguration?.protocol | |
| 73 | + === TCPProtocolTypeEnum.MODBUS_RTU | |
| 74 | + | |
| 75 | + if (isTCPModbus && !objectModel?.extensionDesc) { | |
| 76 | + deviceProfileId = deviceDetail.deviceProfileId || deviceProfileId || deviceProfileDetail?.id | |
| 77 | + if (!deviceProfileId) | |
| 78 | + throw new Error('not found deviceProfile') | |
| 79 | + | |
| 80 | + const productStore = useProductsStoreWithOut() | |
| 81 | + objectModel = productStore.getObjectModelByIdWithIdentifier(deviceProfileId!, identifier)! | |
| 82 | + } | |
| 83 | + | |
| 84 | + const deviceCode = deviceDetail.code | |
| 85 | + return { | |
| 86 | + entityId, | |
| 87 | + transportType, | |
| 88 | + isTCPModbus, | |
| 89 | + deviceCode, | |
| 90 | + deviceDetail, | |
| 91 | + objectModel, | |
| 92 | + identifier, | |
| 93 | + } | |
| 94 | + } | |
| 95 | + | |
| 96 | + async function doCommandDelivery(params: DoCommandDeliverParamsType) { | |
| 97 | + try { | |
| 98 | + loading.value = true | |
| 99 | + | |
| 100 | + const setupResult = await doSetup(params) | |
| 101 | + | |
| 102 | + const { entityId, transportType, isTCPModbus, deviceCode, objectModel, identifier } | |
| 103 | + = setupResult | |
| 104 | + | |
| 105 | + let command = params.value | |
| 106 | + | |
| 107 | + let rpcCommand: RpcCommandType = { | |
| 108 | + persistent: true, | |
| 109 | + method: CommandMethodEnum.THINGSKIT, | |
| 110 | + additionalInfo: { | |
| 111 | + cmdType: params.cmdType ?? CommandTypeEnum.API, | |
| 112 | + }, | |
| 113 | + params: command, | |
| 114 | + } | |
| 115 | + | |
| 116 | + let way = params.way ?? CommandWayEnum.ONE_WAY | |
| 117 | + | |
| 118 | + if (!params.penetration) { | |
| 119 | + if (transportType === TransportTypeEnum.TCP) { | |
| 120 | + command = params.value | |
| 121 | + if (isTCPModbus) { | |
| 122 | + const { doCoverCommand } = useCoverModbusCommand() | |
| 123 | + command = await doCoverCommand(params.value, objectModel!, deviceCode, entityId) | |
| 124 | + } | |
| 125 | + } | |
| 126 | + else { | |
| 127 | + command = { | |
| 128 | + [identifier]: command, | |
| 129 | + } | |
| 130 | + } | |
| 131 | + | |
| 132 | + if (objectModel?.functionType === FunctionTypeEnum.SERVICE) { | |
| 133 | + rpcCommand.additionalInfo.cmdType = CommandTypeEnum.SERVICE | |
| 134 | + way | |
| 135 | + = objectModel.callType === CommandCallWayEnum.ASYNC | |
| 136 | + ? CommandWayEnum.ONE_WAY | |
| 137 | + : CommandWayEnum.TWO_WAY | |
| 138 | + } | |
| 139 | + | |
| 140 | + rpcCommand.params = command | |
| 141 | + } | |
| 142 | + | |
| 143 | + const sendApi = rpcCommandApi | |
| 144 | + | |
| 145 | + if (params.beforeFetch && isFunction(params.beforeFetch)) | |
| 146 | + rpcCommand = await params.beforeFetch(rpcCommand, setupResult) | |
| 147 | + | |
| 148 | + if (way === CommandWayEnum.TWO_WAY) { | |
| 149 | + const result = await getDeviceActive(entityId) | |
| 150 | + const [firsetItem] = result || [] | |
| 151 | + | |
| 152 | + if (!firsetItem.value) { | |
| 153 | + const { createMessage } = useMessage() | |
| 154 | + const message = '当前设备不在线' | |
| 155 | + createMessage.warning(message) | |
| 156 | + throw Error(message) | |
| 157 | + } | |
| 158 | + } | |
| 159 | + await sendApi({ way, deviceId: entityId, command: rpcCommand }) | |
| 160 | + } | |
| 161 | + finally { | |
| 162 | + loading.value = false | |
| 163 | + } | |
| 164 | + } | |
| 165 | + | |
| 166 | + return { | |
| 167 | + loading, | |
| 168 | + doCommandDelivery, | |
| 169 | + } | |
| 170 | +} | ... | ... |
src/hooks/business/useCoverModbusCommand.ts
0 → 100644
| 1 | +import { isNullOrUnDef } from '@wry-smile/utils-is' | |
| 2 | +import { useParseOriginalDataType } from './useParseOriginalDataType' | |
| 3 | + | |
| 4 | +import { isFloatType, isNumberType, useParseOperationType } from './useParseOperationType' | |
| 5 | +import { useBaseConversion } from '@/hooks/business/useBaseConversion' | |
| 6 | +import { useMessage } from '@/hooks/web/useMessage' | |
| 7 | +import type { ExtensionDesc, Specs, Tsl } from '@/api/device/model' | |
| 8 | +import { type GenModbusCommandType, genModbusCommand, getDeviceInfo, getDeviceTelemetryValue } from '@/api/device' | |
| 9 | +import { ModbusCRCEnum } from '@/enums/commandEnum' | |
| 10 | +import { OriginalDataTypeEnum } from '@/enums/objectModelEnum' | |
| 11 | + | |
| 12 | +const getFloatPart = (number: string | number) => { | |
| 13 | + const isLessZero = Number(number) < 0 | |
| 14 | + number = number.toString() | |
| 15 | + const floatPartStartIndex = number.indexOf('.') | |
| 16 | + const value = ~floatPartStartIndex | |
| 17 | + ? `${isLessZero ? '-' : ''}0.${number.substring(floatPartStartIndex + 1)}` | |
| 18 | + : '0' | |
| 19 | + return Number(value) | |
| 20 | +} | |
| 21 | + | |
| 22 | +function getValueFromValueRange(value: number, valueRange: Record<'min' | 'max', number>) { | |
| 23 | + const { min, max } = valueRange || {} | |
| 24 | + if (!isNullOrUnDef(min) && value < min) return min | |
| 25 | + if (!isNullOrUnDef(max) && value > max) return max | |
| 26 | + return value | |
| 27 | +} | |
| 28 | + | |
| 29 | +async function getCurrentBitCommand(entityId: string, objectModel: Tsl, value: number) { | |
| 30 | + const deviceDetail = await getDeviceInfo(entityId) | |
| 31 | + const thingsModels = deviceDetail.deviceProfile.profileData.thingsModel | |
| 32 | + | |
| 33 | + const { registerAddress } = objectModel.extensionDesc || {} | |
| 34 | + | |
| 35 | + const bitsModel = thingsModels?.filter( | |
| 36 | + item => | |
| 37 | + item.extensionDesc?.originalDataType === OriginalDataTypeEnum.BITS | |
| 38 | + && item.extensionDesc.registerAddress === registerAddress, | |
| 39 | + ) | |
| 40 | + | |
| 41 | + const valuePositionMap | |
| 42 | + = bitsModel?.reduce((prev, next) => { | |
| 43 | + return { ...prev, [next.identifier]: next.extensionDesc?.bitMask } | |
| 44 | + }, {} as Record<string, number>) || {} | |
| 45 | + | |
| 46 | + const attrKeys = Object.keys(valuePositionMap) | |
| 47 | + | |
| 48 | + const latestBitsValues = await getDeviceTelemetryValue({ entityId, keys: attrKeys.join(',') }) | |
| 49 | + | |
| 50 | + const binaryArr = Array.from({ length: 16 }, () => 0) | |
| 51 | + | |
| 52 | + for (const key of attrKeys) { | |
| 53 | + const index = valuePositionMap[key] | |
| 54 | + | |
| 55 | + if (!isNullOrUnDef(index)) { | |
| 56 | + const [latest] = latestBitsValues[key] | |
| 57 | + const { value } = latest | |
| 58 | + binaryArr[index] = Number(value) | |
| 59 | + } | |
| 60 | + } | |
| 61 | + | |
| 62 | + if (!isNullOrUnDef(objectModel.extensionDesc?.bitMask)) | |
| 63 | + binaryArr[objectModel.extensionDesc.bitMask] = value | |
| 64 | + | |
| 65 | + return [parseInt(binaryArr.reverse().join(''), 2)] | |
| 66 | +} | |
| 67 | + | |
| 68 | +export function useCoverModbusCommand() { | |
| 69 | + const { createMessage } = useMessage() | |
| 70 | + | |
| 71 | + const doCoverCommand = async (value: number, objectModel: Tsl, deviceAddressCode?: string, entityId?: string) => { | |
| 72 | + if (!deviceAddressCode) { | |
| 73 | + const message = '当前设备未绑定设备地址码' | |
| 74 | + createMessage.warning(message) | |
| 75 | + throw new Error(message) | |
| 76 | + } | |
| 77 | + | |
| 78 | + const { | |
| 79 | + registerAddress, | |
| 80 | + operationType, | |
| 81 | + scaling, | |
| 82 | + originalDataType, | |
| 83 | + bitMask, | |
| 84 | + registerCount: registerNumber, | |
| 85 | + } = objectModel.extensionDesc as Required<ExtensionDesc> | |
| 86 | + | |
| 87 | + const { writeRegisterAddress } = useParseOperationType(operationType) | |
| 88 | + | |
| 89 | + const { unsigned, exchangeSortFlag, registerCount } | |
| 90 | + = useParseOriginalDataType(originalDataType) | |
| 91 | + | |
| 92 | + const params: GenModbusCommandType = { | |
| 93 | + crc: ModbusCRCEnum.CRC_16_LOWER, | |
| 94 | + registerNumber: registerCount || registerNumber, | |
| 95 | + deviceCode: deviceAddressCode, | |
| 96 | + registerAddress: parseInt(registerAddress, 16), | |
| 97 | + method: writeRegisterAddress!, | |
| 98 | + registerValues: [value], | |
| 99 | + } | |
| 100 | + | |
| 101 | + if (exchangeSortFlag) params.hexByteOrderEnum = exchangeSortFlag | |
| 102 | + | |
| 103 | + const { getRegisterValueByOriginalDataType } = useBaseConversion() | |
| 104 | + | |
| 105 | + if (isNumberType(originalDataType)) { | |
| 106 | + let newValue = Math.trunc(value) * scaling + getFloatPart(value) * scaling | |
| 107 | + | |
| 108 | + newValue = unsigned ? newValue : Math.abs(newValue) | |
| 109 | + | |
| 110 | + newValue = getValueFromValueRange( | |
| 111 | + newValue, | |
| 112 | + (objectModel.specs?.dataType.specs as Specs).valueRange!, | |
| 113 | + ) | |
| 114 | + | |
| 115 | + if (!isFloatType(originalDataType) && newValue % 1 !== 0) { | |
| 116 | + const message = `属性下发类型必须是整数,缩放因子为${scaling}` | |
| 117 | + createMessage.warning(message) | |
| 118 | + throw Error(message) | |
| 119 | + } | |
| 120 | + | |
| 121 | + value = newValue | |
| 122 | + } | |
| 123 | + | |
| 124 | + params.registerValues = originalDataType === OriginalDataTypeEnum.BITS | |
| 125 | + ? await getCurrentBitCommand(entityId!, objectModel, value) | |
| 126 | + : getRegisterValueByOriginalDataType(value, originalDataType, { | |
| 127 | + bitMask, | |
| 128 | + registerNumber, | |
| 129 | + }) | |
| 130 | + | |
| 131 | + if (!params.method) { | |
| 132 | + const message = '物模型操作类型无法进行写入' | |
| 133 | + createMessage.warning(message) | |
| 134 | + throw Error(message) | |
| 135 | + } | |
| 136 | + | |
| 137 | + return await genModbusCommand(params) | |
| 138 | + } | |
| 139 | + | |
| 140 | + return { | |
| 141 | + doCoverCommand, | |
| 142 | + } | |
| 143 | +} | ... | ... |
src/hooks/business/useParseOperationType.ts
0 → 100644
| 1 | +import type { ExtendDescOperationTypeEnum } from '@/enums/objectModelEnum' | |
| 2 | +import { | |
| 3 | + DataTypeEnum, | |
| 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 | +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 | +} | ... | ... |
| 1 | 1 | import { defineStore } from 'pinia' |
| 2 | -import { toRaw, unref } from 'vue' | |
| 2 | +import { toRaw } from 'vue' | |
| 3 | 3 | import { store } from '..' |
| 4 | 4 | import type { NodeDataType } from '@/api/node/model' |
| 5 | 5 | import type { ProductAndDevice } from '@/api/content/model' |
| ... | ... | @@ -7,12 +7,6 @@ import type { CreateComponentType } from '@/core/Library/types' |
| 7 | 7 | import { isShareMode } from '@/utils/env' |
| 8 | 8 | import { ConfigurationAuthEnum, ConfigurationTemplateAuthEnum } from '@/enums/authEnum' |
| 9 | 9 | |
| 10 | -interface DeviceList { | |
| 11 | - deviceId: string | |
| 12 | - name: string | |
| 13 | - codeType: string | |
| 14 | -} | |
| 15 | - | |
| 16 | 10 | interface ContentDataStoreType { |
| 17 | 11 | contentData: NodeDataType[] |
| 18 | 12 | configurationId: Nullable<string> |
| ... | ... | @@ -20,7 +14,6 @@ interface ContentDataStoreType { |
| 20 | 14 | isTemplate?: number | null | string |
| 21 | 15 | configurationContentList?: any |
| 22 | 16 | configurationContentId: Nullable<string> |
| 23 | - diveceDetailMap: Record<string, DeviceList> | |
| 24 | 17 | hasDesignAuth?: boolean |
| 25 | 18 | hasPerviewAuth?: boolean |
| 26 | 19 | isTemplateLink?: boolean |
| ... | ... | @@ -35,7 +28,6 @@ export const useContentDataStore = defineStore('app-content-data', { |
| 35 | 28 | productAndDevice: [], |
| 36 | 29 | configurationContentList: [], |
| 37 | 30 | configurationContentId: null, |
| 38 | - diveceDetailMap: {}, | |
| 39 | 31 | hasDesignAuth: true, |
| 40 | 32 | hasPerviewAuth: true, |
| 41 | 33 | isTemplateLink: false, |
| ... | ... | @@ -70,17 +62,9 @@ export const useContentDataStore = defineStore('app-content-data', { |
| 70 | 62 | |
| 71 | 63 | setProductAndDevice(list: ProductAndDevice[]) { |
| 72 | 64 | this.productAndDevice = list |
| 73 | - this.doBuildDeviceMap() | |
| 74 | 65 | return list |
| 75 | 66 | }, |
| 76 | 67 | |
| 77 | - doBuildDeviceMap() { | |
| 78 | - const list = this.productAndDevice.map(item => toRaw(unref(item.deviceList))).flat(1) | |
| 79 | - this.diveceDetailMap = list.reduce((prev, next) => { | |
| 80 | - return { ...prev, [next?.deviceId]: next } | |
| 81 | - }, {} as Record<'string', DeviceList>) | |
| 82 | - }, | |
| 83 | - | |
| 84 | 68 | getCurrentNodeDataById(config: CreateComponentType): Nullable<NodeDataType> { |
| 85 | 69 | const { cellInfo } = config |
| 86 | 70 | const { id } = cellInfo || {} | ... | ... |
src/store/modules/products.ts
0 → 100644
| 1 | +import { defineStore } from 'pinia' | |
| 2 | +import { store } from '..' | |
| 3 | +import type { ProductsDetailWithThingsModelType, Tsl } from '@/api/device/model' | |
| 4 | + | |
| 5 | +interface ProductsStoreType { | |
| 6 | + products: Record<string, ProductsDetailWithThingsModelType> | |
| 7 | +} | |
| 8 | + | |
| 9 | +export const useProductsStore = defineStore('app-products', { | |
| 10 | + state: (): ProductsStoreType => { | |
| 11 | + return { | |
| 12 | + products: {}, | |
| 13 | + } | |
| 14 | + }, | |
| 15 | + actions: { | |
| 16 | + setProducts(products: Record<string, ProductsDetailWithThingsModelType>) { | |
| 17 | + this.products = products | |
| 18 | + }, | |
| 19 | + | |
| 20 | + getProductDetailById(id: string) { | |
| 21 | + return this.products[id] | |
| 22 | + }, | |
| 23 | + | |
| 24 | + getObjectModelsById(id: string) { | |
| 25 | + return this.products[id] | |
| 26 | + }, | |
| 27 | + | |
| 28 | + getObjectModelByIdWithIdentifier(id: string, identifier: string): Nullable<Tsl> { | |
| 29 | + const product = this.getObjectModelsById(id) | |
| 30 | + if (!product) return null | |
| 31 | + return product.tsl.filter(item => item.identifier === identifier)[0] | |
| 32 | + }, | |
| 33 | + }, | |
| 34 | +}) | |
| 35 | + | |
| 36 | +export function useProductsStoreWithOut() { | |
| 37 | + return useProductsStore(store) | |
| 38 | +} | ... | ... |