Commit b2bff1c5a02bdcd83240baf8c86094679b46587b

Authored by xp.Huang
2 parents e7195427 43a27418

Merge branch 'main_dev' into 'main'

Main dev

See merge request yunteng/thingskit-scada!224
Showing 64 changed files with 2252 additions and 1334 deletions

Too many changes to show.

To preserve performance only 64 of 71 files are displayed.

1   -import type { AlarmStatusEnum } from '@/enums/datasource'
  1 +import type { AlarmStatusEnum } from '@/enums/alarmEnum'
2 2
3 3 /**
4 4 * 告警列表
... ...
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" />
... ...
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>
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 +}
... ...
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 }
... ...
... ... @@ -12,5 +12,4 @@ export interface ComponentExposeType {
12 12 getFieldsValue: () => any
13 13 setFieldsValue: (value: any) => void
14 14 validate: () => Promise<RuleError | any>
15   - updateSchema?: any
16 15 }
... ...
... ... @@ -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) {
... ...
  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 +}
... ...
  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 +}
... ...
... ... @@ -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   -
... ...
  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 }
... ...
  1 +export enum VideoAccessModeEnum {
  2 + ManuallyEnter = 0,
  3 + Streaming = 1,
  4 + GBT28181 = 2,
  5 +}
... ...
  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 +}
... ...
  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 +}
... ...