Commit 64b137e4a5483e3134ce926338e92b4ebad7ab35
Merge branch 'main_dev' into 'main'
Main dev See merge request yunteng/thingskit-scada!208
Showing
15 changed files
with
345 additions
and
96 deletions
@@ -4351,9 +4351,11 @@ App.prototype.loadFile = function (id, sameWindow, file, success, force) { | @@ -4351,9 +4351,11 @@ App.prototype.loadFile = function (id, sameWindow, file, success, force) { | ||
4351 | if (success != null) | 4351 | if (success != null) |
4352 | success() | 4352 | success() |
4353 | // TODO Thingskkit socket连接 | 4353 | // TODO Thingskkit socket连接 |
4354 | - const lightboxWebsocketService = useLightboxModeService(this) | ||
4355 | - this.lightboxWebsocketService = lightboxWebsocketService | ||
4356 | - this.lightboxWebsocketService?.init?.() | 4354 | + if (!result?.isTemplate) { |
4355 | + const lightboxWebsocketService = useLightboxModeService(this) | ||
4356 | + this.lightboxWebsocketService = lightboxWebsocketService | ||
4357 | + this.lightboxWebsocketService?.init?.() | ||
4358 | + } | ||
4357 | } | 4359 | } |
4358 | 4360 | ||
4359 | handleLoadContent() | 4361 | handleLoadContent() |
@@ -5137,7 +5139,7 @@ App.prototype.addPreviewButton = function () { | @@ -5137,7 +5139,7 @@ App.prototype.addPreviewButton = function () { | ||
5137 | this.previewButton.style.border = '1px solid transparent' | 5139 | this.previewButton.style.border = '1px solid transparent' |
5138 | this.previewButton.style.color = '#fff' | 5140 | this.previewButton.style.color = '#fff' |
5139 | 5141 | ||
5140 | - const icon = document.createElement('img') | 5142 | + const icon = document.createElement('img') |
5141 | icon.setAttribute('src', this.previewImage) | 5143 | icon.setAttribute('src', this.previewImage) |
5142 | icon.setAttribute('align', 'absmiddle') | 5144 | icon.setAttribute('align', 'absmiddle') |
5143 | icon.style.marginRight = '4px' | 5145 | icon.style.marginRight = '4px' |
@@ -27,7 +27,7 @@ export interface GenModbusCommandType { | @@ -27,7 +27,7 @@ export interface GenModbusCommandType { | ||
27 | crc: string | 27 | crc: string |
28 | deviceCode: string | 28 | deviceCode: string |
29 | method: string | 29 | method: string |
30 | - registerAddress: string | 30 | + registerAddress: number |
31 | registerNumber?: number | 31 | registerNumber?: number |
32 | registerValues?: number[] | 32 | registerValues?: number[] |
33 | } | 33 | } |
1 | -import type { FunctionType, TransportTypeEnum } from '@/enums/datasource' | 1 | +import type { DataTypeEnum, FunctionType, TransportTypeEnum } from '@/enums/datasource' |
2 | 2 | ||
3 | export interface DeviceProfileItemType { | 3 | export interface DeviceProfileItemType { |
4 | id: string | 4 | id: string |
@@ -147,6 +147,7 @@ export interface Detail { | @@ -147,6 +147,7 @@ export interface Detail { | ||
147 | export interface DataType { | 147 | export interface DataType { |
148 | type: string | 148 | type: string |
149 | specs: Specs | StructJSON[] | 149 | specs: Specs | StructJSON[] |
150 | + specsList: Specs[] | ||
150 | } | 151 | } |
151 | 152 | ||
152 | export interface Specs { | 153 | export interface Specs { |
@@ -158,6 +159,9 @@ export interface Specs { | @@ -158,6 +159,9 @@ export interface Specs { | ||
158 | length?: string | 159 | length?: string |
159 | min: string | 160 | min: string |
160 | max: string | 161 | max: string |
162 | + name?: string | ||
163 | + value?: string | ||
164 | + dataType?: DataTypeEnum | ||
161 | } | 165 | } |
162 | 166 | ||
163 | export interface ValueRange { | 167 | export interface ValueRange { |
1 | +import type { Specs, StructJSON } from '@/api/device/model' | ||
2 | +import type { NodeDataDataSourceJsonType } from '@/api/node/model' | ||
3 | +import { type FormSchema, useComponentRegister } from '@/components/Form' | ||
4 | +import { ComponentEnum } from '@/components/Form/src/enum' | ||
5 | +import { ThingsModelForm, validateTCPCustomCommand } from '@/core/Library/components/ThingsModelForm' | ||
6 | +import { getFormSchemas } from '@/core/Library/components/ThingsModelForm/config' | ||
7 | +import { CodeTypeEnum, DataTypeEnum, TransportTypeEnum } from '@/enums/datasource' | ||
8 | +import { TCPObjectModelActionTypeEnum } from '@/enums/objectModelEnum' | ||
9 | + | ||
10 | +useComponentRegister(ComponentEnum.THINGS_MODEL_FORM, ThingsModelForm) | ||
11 | + | ||
12 | +export enum FormFieldsEnum { | ||
13 | + ATTR_VALUE = 'attrValue', | ||
14 | + PASSWORD = 'password', | ||
15 | +} | ||
16 | + | ||
17 | +export interface AttributeDeliverModalOpenParamsType { | ||
18 | + title?: string | ||
19 | + operationPassword?: string | ||
20 | + operationPasswordEnable?: boolean | ||
21 | + dataSourceJson: NodeDataDataSourceJsonType | ||
22 | +} | ||
23 | + | ||
24 | +function getStructJsonFromDataSourceJson(dataSourceJson: NodeDataDataSourceJsonType): StructJSON { | ||
25 | + const { attrInfo } = dataSourceJson | ||
26 | + const { identifier, name, detail } = attrInfo || {} | ||
27 | + return { | ||
28 | + functionName: name, | ||
29 | + identifier, | ||
30 | + dataType: detail.dataType, | ||
31 | + } | ||
32 | +} | ||
33 | + | ||
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 || {} | ||
39 | + | ||
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 | + } | ||
55 | + } | ||
56 | + | ||
57 | + const createInput = ({ identifier, functionName }: StructJSON): FormSchema => { | ||
58 | + return { | ||
59 | + field: identifier, | ||
60 | + 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, | ||
73 | + }, | ||
74 | + } as FormSchema | ||
75 | + } | ||
76 | + | ||
77 | + return type === DataTypeEnum.STRING ? createInput(structJson) : createInputNumber(structJson) | ||
78 | +} | ||
79 | + | ||
80 | +export const createFormSchemas = ({ operationPassword, operationPasswordEnable, dataSourceJson }: AttributeDeliverModalOpenParamsType): FormSchema[] => { | ||
81 | + const schemas: FormSchema[] = [] | ||
82 | + | ||
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 })) | ||
86 | + } | ||
87 | + else { | ||
88 | + const isStructType = dataSourceJson.attrInfo?.detail?.dataType?.type === DataTypeEnum.STRUCT | ||
89 | + schemas.push( | ||
90 | + ...getFormSchemas({ structJSON: isStructType ? dataSourceJson?.attrInfo?.detail?.dataType?.specs as StructJSON[] || [] : [getStructJsonFromDataSourceJson(dataSourceJson)], required: !isStructType }), | ||
91 | + ) | ||
92 | + } | ||
93 | + | ||
94 | + if (operationPassword && operationPasswordEnable) { | ||
95 | + schemas.unshift({ | ||
96 | + field: FormFieldsEnum.PASSWORD, | ||
97 | + label: '操作密码', | ||
98 | + component: ComponentEnum.INPUT_PAWSSWORD, | ||
99 | + required: true, | ||
100 | + rules: [ | ||
101 | + { | ||
102 | + validator(_rule, value) { | ||
103 | + if (value && value !== operationPassword) return Promise.reject(new Error('操作密码不正确')) | ||
104 | + return Promise.resolve() | ||
105 | + }, | ||
106 | + }, | ||
107 | + ], | ||
108 | + }) | ||
109 | + } | ||
110 | + | ||
111 | + return schemas | ||
112 | +} |
1 | <script setup lang="ts"> | 1 | <script setup lang="ts"> |
2 | import { Modal } from 'ant-design-vue' | 2 | import { Modal } from 'ant-design-vue' |
3 | import { nextTick, ref, unref } from 'vue' | 3 | import { nextTick, ref, unref } from 'vue' |
4 | -import type { FormSchema } from '@/components/Form' | 4 | +import type { AttributeDeliverModalOpenParamsType } from './AttributeDeliverModal.config' |
5 | +import { createFormSchemas } from './AttributeDeliverModal.config' | ||
6 | +import { useGetModbusCommand } from './useGetModbusCommand' | ||
5 | import { BasicForm, useForm } from '@/components/Form' | 7 | import { BasicForm, useForm } from '@/components/Form' |
6 | -import { ComponentEnum, FormLayoutEnum } from '@/components/Form/src/enum' | 8 | +import { FormLayoutEnum } from '@/components/Form/src/enum' |
9 | +import type { NodeDataDataSourceJsonType } from '@/api/node/model' | ||
10 | +import { CodeTypeEnum, DataTypeEnum, TransportTypeEnum } from '@/enums/datasource' | ||
7 | 11 | ||
8 | const resolveFn = ref<Fn>() | 12 | const resolveFn = ref<Fn>() |
9 | 13 | ||
@@ -11,61 +15,50 @@ const visible = ref(false) | @@ -11,61 +15,50 @@ const visible = ref(false) | ||
11 | 15 | ||
12 | const password = ref() | 16 | const password = ref() |
13 | 17 | ||
18 | +const currentDataSourceJson = ref<NodeDataDataSourceJsonType>() | ||
19 | + | ||
14 | const [register, { getFieldsValue, resetFields, validate, setProps, clearValidate }] = useForm({ | 20 | const [register, { getFieldsValue, resetFields, validate, setProps, clearValidate }] = useForm({ |
15 | layout: FormLayoutEnum.VERTICAL, | 21 | layout: FormLayoutEnum.VERTICAL, |
16 | showActionButtonGroup: false, | 22 | showActionButtonGroup: false, |
17 | }) | 23 | }) |
18 | 24 | ||
19 | -enum FormFieldsEnum { | ||
20 | - ATTR_VALUE = 'attrValue', | ||
21 | - PASSWORD = 'password', | ||
22 | -} | ||
23 | - | ||
24 | -const createFormSchemas = (title?: string, password?: string, operationPasswordEnable?: boolean): FormSchema[] => { | ||
25 | - const schemas: FormSchema[] = [ | ||
26 | - { | ||
27 | - field: FormFieldsEnum.ATTR_VALUE, | ||
28 | - label: title || '属性值', | ||
29 | - component: ComponentEnum.INPUT, | ||
30 | - required: true, | ||
31 | - }, | ||
32 | - ] | ||
33 | - | ||
34 | - if (password && operationPasswordEnable) { | ||
35 | - schemas.unshift({ | ||
36 | - field: FormFieldsEnum.PASSWORD, | ||
37 | - label: '操作密码', | ||
38 | - component: ComponentEnum.INPUT_PAWSSWORD, | ||
39 | - required: true, | ||
40 | - rules: [ | ||
41 | - { | ||
42 | - validator(_rule, value) { | ||
43 | - if (value && value !== password) return Promise.reject(new Error('操作密码不正确')) | ||
44 | - return Promise.resolve() | ||
45 | - }, | ||
46 | - }, | ||
47 | - ], | ||
48 | - }) | ||
49 | - } | ||
50 | - | ||
51 | - return schemas | ||
52 | -} | ||
53 | - | ||
54 | -const open = async ({ title, operationPassword, operationPasswordEnable }: Partial<Record<'operationPassword' | 'title', string>> & { operationPasswordEnable: boolean }) => { | 25 | +const open = async ({ title, operationPassword, operationPasswordEnable, dataSourceJson }: AttributeDeliverModalOpenParamsType) => { |
55 | visible.value = true | 26 | visible.value = true |
56 | password.value = operationPassword | 27 | password.value = operationPassword |
28 | + currentDataSourceJson.value = dataSourceJson | ||
57 | return new Promise((resolve) => { | 29 | return new Promise((resolve) => { |
58 | resolveFn.value = resolve | 30 | resolveFn.value = resolve |
59 | nextTick(() => { | 31 | nextTick(() => { |
60 | - setProps({ schemas: createFormSchemas(title, operationPassword, operationPasswordEnable) }) | 32 | + setProps({ schemas: createFormSchemas({ title, operationPassword, operationPasswordEnable, dataSourceJson }) }) |
61 | }) | 33 | }) |
62 | }) | 34 | }) |
63 | } | 35 | } |
64 | 36 | ||
37 | +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 | ||
46 | + | ||
47 | + if (isString) return value | ||
48 | + | ||
49 | + const { getModbusCommand, validateCanGetCommand } = useGetModbusCommand() | ||
50 | + if (!validateCanGetCommand(unref(currentDataSourceJson)!.attrInfo.extensionDesc, unref(currentDataSourceJson)!.deviceInfo?.code).flag) return | ||
51 | + | ||
52 | + const res = await getModbusCommand(value as unknown as number, unref(currentDataSourceJson)!.attrInfo.extensionDesc!, unref(currentDataSourceJson)!.deviceInfo!.code!) | ||
53 | + return res | ||
54 | + } | ||
55 | +} | ||
56 | + | ||
65 | const handleOk = async () => { | 57 | const handleOk = async () => { |
66 | await validate() | 58 | await validate() |
67 | - const value = getFieldsValue() | ||
68 | - unref(resolveFn)?.(value[FormFieldsEnum.ATTR_VALUE]) | 59 | + const result = await getResult() |
60 | + if (!result) return | ||
61 | + unref(resolveFn)?.(result) | ||
69 | visible.value = false | 62 | visible.value = false |
70 | resetFields() | 63 | resetFields() |
71 | } | 64 | } |
@@ -80,17 +73,9 @@ defineExpose({ open }) | @@ -80,17 +73,9 @@ defineExpose({ open }) | ||
80 | 73 | ||
81 | <template> | 74 | <template> |
82 | <Modal | 75 | <Modal |
83 | - v-model:open="visible" title="属性下发" ok-text="确认" :width="400" cancel-text="取消" @cancel="handleCancel" @ok="handleOk" | 76 | + v-model:open="visible" title="属性下发" ok-text="确认" :width="400" cancel-text="取消" @cancel="handleCancel" |
77 | + @ok="handleOk" | ||
84 | > | 78 | > |
85 | - <!-- <section> | ||
86 | - <FormItem label="操作密码" :label-col="{ span: 24 }"> | ||
87 | - <Input v-model:value="value" placeholder="请输入下发值" /> | ||
88 | - </FormItem> | ||
89 | - <FormItem :label="fieldTitle" :label-col="{ span: 24 }"> | ||
90 | - <Input v-model:value="value" placeholder="请输入下发值" /> | ||
91 | - </FormItem> | ||
92 | - </section> --> | ||
93 | - | ||
94 | <BasicForm @register="register" /> | 79 | <BasicForm @register="register" /> |
95 | </Modal> | 80 | </Modal> |
96 | </template> | 81 | </template> |
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 | + } | ||
66 | + | ||
67 | + return await genModbusCommand(params) | ||
68 | + } | ||
69 | + | ||
70 | + /** | ||
71 | + * | ||
72 | + * @param extensionDesc 物模型拓展描述符 | ||
73 | + * @param deviceAddressCode 设备地址码 | ||
74 | + */ | ||
75 | + const validateCanGetCommand = (extensionDesc?: ExtensionDesc, deviceAddressCode?: string, createValidateMessage = true) => { | ||
76 | + const result = { flag: true, message: '' } | ||
77 | + if (!extensionDesc) { | ||
78 | + result.flag = false | ||
79 | + result.message = '当前物模型扩展描述没有填写' | ||
80 | + } | ||
81 | + | ||
82 | + if (!deviceAddressCode) { | ||
83 | + result.flag = false | ||
84 | + result.message = '当前设备未绑定设备地址码' | ||
85 | + } | ||
86 | + | ||
87 | + if (result.message && createValidateMessage) | ||
88 | + createMessage.warning(result.message) | ||
89 | + | ||
90 | + return result | ||
91 | + } | ||
92 | + | ||
93 | + return { | ||
94 | + getModbusCommand, | ||
95 | + validateCanGetCommand, | ||
96 | + } | ||
97 | +} |
@@ -9,9 +9,9 @@ export const getFormSchemas = ({ structJSON: structJson, required, transportType | @@ -9,9 +9,9 @@ export const getFormSchemas = ({ structJSON: structJson, required, transportType | ||
9 | functionName, | 9 | functionName, |
10 | dataType, | 10 | dataType, |
11 | }: StructJSON): FormSchema => { | 11 | }: StructJSON): FormSchema => { |
12 | - const { specs } = dataType || {} | 12 | + const { specs, type } = dataType || {} |
13 | const { valueRange } = specs! as Specs | 13 | const { valueRange } = specs! as Specs |
14 | - const { max = 2147483647, min = -2147483648 } = valueRange || {} | 14 | + const { max = Number.MAX_SAFE_INTEGER, min = Number.MIN_SAFE_INTEGER } = valueRange || {} |
15 | return { | 15 | return { |
16 | field: identifier, | 16 | field: identifier, |
17 | label: functionName, | 17 | label: functionName, |
@@ -21,22 +21,11 @@ export const getFormSchemas = ({ structJSON: structJson, required, transportType | @@ -21,22 +21,11 @@ export const getFormSchemas = ({ structJSON: structJson, required, transportType | ||
21 | required, | 21 | required, |
22 | message: `${functionName}是必填项`, | 22 | message: `${functionName}是必填项`, |
23 | }, | 23 | }, |
24 | - { | ||
25 | - type: 'number', | ||
26 | - trigger: 'change', | ||
27 | - validator: (_rule, value) => { | ||
28 | - const reg = /^[0-9]*$/ | ||
29 | - if (!reg.test(value)) return Promise.reject(new Error(`${functionName}不是一个有效的数字`)) | ||
30 | - if (value < min || value > max) | ||
31 | - return Promise.reject(new Error(`${functionName}取值范围在${min}~${max}之间`)) | ||
32 | - | ||
33 | - return Promise.resolve(value) | ||
34 | - }, | ||
35 | - }, | ||
36 | ], | 24 | ], |
37 | componentProps: { | 25 | componentProps: { |
38 | max, | 26 | max, |
39 | min, | 27 | min, |
28 | + precision: type === DataTypeEnum.NUMBER_INT ? 0 : 2, | ||
40 | }, | 29 | }, |
41 | } as FormSchema | 30 | } as FormSchema |
42 | } | 31 | } |
@@ -93,6 +82,27 @@ export const getFormSchemas = ({ structJSON: structJson, required, transportType | @@ -93,6 +82,27 @@ export const getFormSchemas = ({ structJSON: structJson, required, transportType | ||
93 | } | 82 | } |
94 | } | 83 | } |
95 | 84 | ||
85 | + const createEnumSelect = ({ identifier, functionName, dataType }: StructJSON): FormSchema => { | ||
86 | + const { specsList } = dataType || {} | ||
87 | + | ||
88 | + return { | ||
89 | + field: identifier, | ||
90 | + label: functionName, | ||
91 | + component: ComponentEnum.SELECT, | ||
92 | + rules: [ | ||
93 | + { | ||
94 | + required, | ||
95 | + message: `${functionName}是必填项`, | ||
96 | + type: 'number', | ||
97 | + }, | ||
98 | + ], | ||
99 | + componentProps: { | ||
100 | + options: specsList, | ||
101 | + fieldNames: { label: 'name', value: 'value' }, | ||
102 | + }, | ||
103 | + } | ||
104 | + } | ||
105 | + | ||
96 | const createStructJson = ({ identifier, functionName, dataType }: StructJSON): FormSchema => { | 106 | const createStructJson = ({ identifier, functionName, dataType }: StructJSON): FormSchema => { |
97 | return { | 107 | return { |
98 | field: identifier, | 108 | field: identifier, |
@@ -121,7 +131,6 @@ export const getFormSchemas = ({ structJSON: structJson, required, transportType | @@ -121,7 +131,6 @@ export const getFormSchemas = ({ structJSON: structJson, required, transportType | ||
121 | } | 131 | } |
122 | 132 | ||
123 | const schemas: FormSchema[] = [] | 133 | const schemas: FormSchema[] = [] |
124 | - | ||
125 | for (const item of structJson) { | 134 | for (const item of structJson) { |
126 | const { dataType } = item | 135 | const { dataType } = item |
127 | const { type } = dataType || {} | 136 | const { type } = dataType || {} |
@@ -129,6 +138,8 @@ export const getFormSchemas = ({ structJSON: structJson, required, transportType | @@ -129,6 +138,8 @@ export const getFormSchemas = ({ structJSON: structJson, required, transportType | ||
129 | schemas.push(createTCPServiceCommandInput(item)) | 138 | schemas.push(createTCPServiceCommandInput(item)) |
130 | if (type === DataTypeEnum.BOOL) | 139 | if (type === DataTypeEnum.BOOL) |
131 | schemas.push(createSelect(item)) | 140 | schemas.push(createSelect(item)) |
141 | + else if (type === DataTypeEnum.ENUM) | ||
142 | + schemas.push(createEnumSelect(item)) | ||
132 | else if (type === DataTypeEnum.NUMBER_INT) | 143 | else if (type === DataTypeEnum.NUMBER_INT) |
133 | schemas.push(createInputNumber(item)) | 144 | schemas.push(createInputNumber(item)) |
134 | else if (type === DataTypeEnum.NUMBER_DOUBLE) | 145 | else if (type === DataTypeEnum.NUMBER_DOUBLE) |
1 | +import type { ValidatorRule } from 'ant-design-vue/lib/form/interface' | ||
2 | + | ||
1 | export { default as ThingsModelForm } from './index.vue' | 3 | export { default as ThingsModelForm } from './index.vue' |
4 | + | ||
5 | +export const validateTCPCustomCommand: ValidatorRule['validator'] = (_rule, value) => { | ||
6 | + const reg = /^[\s0-9a-fA-F]+$/ | ||
7 | + if (reg.test(value)) return Promise.resolve() | ||
8 | + return Promise.reject(new Error('请输入ASCII或HEX服务命令(0~9/A~F)')) | ||
9 | +} |
@@ -22,14 +22,10 @@ const props = withDefaults(defineProps<{ | @@ -22,14 +22,10 @@ const props = withDefaults(defineProps<{ | ||
22 | 22 | ||
23 | const thingsModelFormListElMap = reactive<Record<string, { el: InstanceType<typeof ThingsModelForm>; structJSON: StructJSON }>>({}) | 23 | const thingsModelFormListElMap = reactive<Record<string, { el: InstanceType<typeof ThingsModelForm>; structJSON: StructJSON }>>({}) |
24 | 24 | ||
25 | -// const getLabelWidth = () => { | ||
26 | -// return Math.max(...((props.inputData || [])?.map(item => item?.functionName?.length))) * 12 | ||
27 | -// } | ||
28 | - | ||
29 | const [register, formActionType] = useForm({ | 25 | const [register, formActionType] = useForm({ |
30 | schemas: getFormSchemas({ structJSON: props.inputData || [], required: props.required, transportType: props.transportType }), | 26 | schemas: getFormSchemas({ structJSON: props.inputData || [], required: props.required, transportType: props.transportType }), |
31 | showActionButtonGroup: false, | 27 | showActionButtonGroup: false, |
32 | - // labelWidth: getLabelWidth() || 80, | 28 | + labelWidth: 100, |
33 | labelAlign: FormLabelAlignEnum.RIGHT, | 29 | labelAlign: FormLabelAlignEnum.RIGHT, |
34 | layout: FormLayoutEnum.HORIZONTAL, | 30 | layout: FormLayoutEnum.HORIZONTAL, |
35 | }) | 31 | }) |
@@ -105,19 +101,34 @@ defineExpose<ComponentExposeType>({ | @@ -105,19 +101,34 @@ defineExpose<ComponentExposeType>({ | ||
105 | <Card class="!border-2 !border-dashed" :title="title"> | 101 | <Card class="!border-2 !border-dashed" :title="title"> |
106 | <BasicForm class="things-model-form" @register="register"> | 102 | <BasicForm class="things-model-form" @register="register"> |
107 | <template v-for="item in getStructFormItem" #[item.identifier]="{ model, field }" :key="item.identifier"> | 103 | <template v-for="item in getStructFormItem" #[item.identifier]="{ model, field }" :key="item.identifier"> |
108 | - <ThingsModelForm :ref="(el) => setFormElRef(el as InstanceType<typeof ThingsModelForm>, item) " v-model:value="model[field]" :input-data="(item.dataType?.specs as StructJSON[]) || []" :title="item.functionName" /> | 104 | + <ThingsModelForm |
105 | + :ref="(el) => setFormElRef(el as InstanceType<typeof ThingsModelForm>, item)" | ||
106 | + v-model:value="model[field]" :input-data="(item.dataType?.specs as StructJSON[]) || []" | ||
107 | + :title="item.functionName" | ||
108 | + /> | ||
109 | </template> | 109 | </template> |
110 | </BasicForm> | 110 | </BasicForm> |
111 | </Card> | 111 | </Card> |
112 | </template> | 112 | </template> |
113 | 113 | ||
114 | <style lang="less" scoped> | 114 | <style lang="less" scoped> |
115 | - .things-model-form { | ||
116 | - :deep(.ant-input-number) { | ||
117 | - width: 100%; | ||
118 | - } | 115 | +.things-model-form { |
116 | + :deep(.ant-input-number) { | ||
117 | + width: 100%; | ||
119 | } | 118 | } |
120 | - :deep(.ant-form-item-control){ | ||
121 | - margin-left: 6px; | 119 | + |
120 | + :deep(.ant-form-item-label) { | ||
121 | + >label { | ||
122 | + display: block; | ||
123 | + text-align: right; | ||
124 | + white-space: nowrap; | ||
125 | + text-overflow: ellipsis; | ||
126 | + overflow: hidden; | ||
127 | + } | ||
122 | } | 128 | } |
129 | +} | ||
130 | + | ||
131 | +:deep(.ant-form-item-control) { | ||
132 | + margin-left: 6px; | ||
133 | +} | ||
123 | </style> | 134 | </style> |
1 | -import { h, render } from 'vue' | 1 | +import { h, render, unref } from 'vue' |
2 | import { ControlComponentEnum } from '../packages/Control' | 2 | import { ControlComponentEnum } from '../packages/Control' |
3 | +import { useGetModbusCommand } from '../components/PublicForm/components/DataEvents/CommandDeliveryModal/useGetModbusCommand' | ||
3 | import { doCommandDelivery, getDeviceActive, getDeviceInfo } from '@/api/device' | 4 | import { doCommandDelivery, getDeviceActive, getDeviceInfo } from '@/api/device' |
4 | import type { MouseUpEventDataType, NodeDataDataSourceJsonType, NodeDataEventJsonType, SingleClickEventDataType } from '@/api/node/model' | 5 | import type { MouseUpEventDataType, NodeDataDataSourceJsonType, NodeDataEventJsonType, SingleClickEventDataType } from '@/api/node/model' |
5 | import { CommandWayEnum } from '@/enums/commandEnum' | 6 | import { CommandWayEnum } from '@/enums/commandEnum' |
6 | -import { ActRangListItemTypeEnum, EventActionTypeEnum, TransportTypeEnum } from '@/enums/datasource' | 7 | +import { ActRangListItemTypeEnum, CodeTypeEnum, EventActionTypeEnum, TransportTypeEnum } from '@/enums/datasource' |
7 | import { useMessage } from '@/hooks/web/useMessage' | 8 | import { useMessage } from '@/hooks/web/useMessage' |
8 | import { AttributeDeliverModal, CommandDeliveryConfirmModal, CommandDeliveryModal, ConfirmModal } from '@/core/Library/components/PublicForm/components/DataEvents/CommandDeliveryModal' | 9 | import { AttributeDeliverModal, CommandDeliveryConfirmModal, CommandDeliveryModal, ConfirmModal } from '@/core/Library/components/PublicForm/components/DataEvents/CommandDeliveryModal' |
9 | import type { MxCell } from '@/fitCore/types' | 10 | import type { MxCell } from '@/fitCore/types' |
@@ -76,7 +77,7 @@ export function useNodeEvent(eventJson: NodeDataEventJsonType, dataSourceJson: N | @@ -76,7 +77,7 @@ export function useNodeEvent(eventJson: NodeDataEventJsonType, dataSourceJson: N | ||
76 | const nodeUtils = new NodeUtils() | 77 | const nodeUtils = new NodeUtils() |
77 | const { way = CommandWayEnum.ONE_WAY } = data | 78 | const { way = CommandWayEnum.ONE_WAY } = data |
78 | const { attr, deviceId, attrInfo } = dataSourceJson | 79 | const { attr, deviceId, attrInfo } = dataSourceJson |
79 | - const { transportType, alias, name: deviceName } = await getDeviceInfo(dataSourceJson.deviceId) || {}// 设备信息 | 80 | + const { transportType, alias, name: deviceName, code, codeType } = await getDeviceInfo(dataSourceJson.deviceId) || {}// 设备信息 |
80 | const contentDataStore = useContentDataStoreWithOut() | 81 | const contentDataStore = useContentDataStoreWithOut() |
81 | const currentData = contentDataStore.contentData.find(item => item.id === cell.getId()) | 82 | const currentData = contentDataStore.contentData.find(item => item.id === cell.getId()) |
82 | if (!currentData) return | 83 | if (!currentData) return |
@@ -95,6 +96,7 @@ export function useNodeEvent(eventJson: NodeDataEventJsonType, dataSourceJson: N | @@ -95,6 +96,7 @@ export function useNodeEvent(eventJson: NodeDataEventJsonType, dataSourceJson: N | ||
95 | const res = rangeList.find(item => item.type === (status === ActRangListItemTypeEnum.OPEN ? ActRangListItemTypeEnum.CLOSE : ActRangListItemTypeEnum.OPEN)) | 96 | const res = rangeList.find(item => item.type === (status === ActRangListItemTypeEnum.OPEN ? ActRangListItemTypeEnum.CLOSE : ActRangListItemTypeEnum.OPEN)) |
96 | if (!res) return | 97 | if (!res) return |
97 | const { statusValue } = res | 98 | const { statusValue } = res |
99 | + | ||
98 | command.params = transportType === TransportTypeEnum.TCP | 100 | command.params = transportType === TransportTypeEnum.TCP |
99 | ? statusValue! | 101 | ? statusValue! |
100 | : { | 102 | : { |
@@ -104,7 +106,7 @@ export function useNodeEvent(eventJson: NodeDataEventJsonType, dataSourceJson: N | @@ -104,7 +106,7 @@ export function useNodeEvent(eventJson: NodeDataEventJsonType, dataSourceJson: N | ||
104 | if (operationPasswordEnable) { | 106 | if (operationPasswordEnable) { |
105 | const instance = h(CommandDeliveryConfirmModal) | 107 | const instance = h(CommandDeliveryConfirmModal) |
106 | render(instance, document.body) | 108 | render(instance, document.body) |
107 | - await (instance.component?.exposed as InstanceType<typeof CommandDeliveryConfirmModal>)?.open(operationPassword) | 109 | + await (instance.component?.exposed as InstanceType<typeof CommandDeliveryConfirmModal>)?.open(operationPassword!) |
108 | } | 110 | } |
109 | else { | 111 | else { |
110 | const instance = h(ConfirmModal) | 112 | const instance = h(ConfirmModal) |
@@ -112,15 +114,23 @@ export function useNodeEvent(eventJson: NodeDataEventJsonType, dataSourceJson: N | @@ -112,15 +114,23 @@ export function useNodeEvent(eventJson: NodeDataEventJsonType, dataSourceJson: N | ||
112 | await (instance.component?.exposed as InstanceType<typeof ConfirmModal>)?.open() | 114 | await (instance.component?.exposed as InstanceType<typeof ConfirmModal>)?.open() |
113 | } | 115 | } |
114 | 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 | + | ||
115 | await doCommandDelivery({ way, command, deviceId }) | 125 | await doCommandDelivery({ way, command, deviceId }) |
116 | createMessage.success('命令下发成功') | 126 | createMessage.success('命令下发成功') |
117 | } | 127 | } |
118 | else { | 128 | else { |
119 | const instance = h(AttributeDeliverModal) | 129 | const instance = h(AttributeDeliverModal) |
120 | render(instance, document.body) | 130 | render(instance, document.body) |
121 | - const value = await (instance.component?.exposed as InstanceType<typeof AttributeDeliverModal>)?.open({ title: `${alias || deviceName}-${attrInfo.name}`, operationPassword, operationPasswordEnable }) as string | ||
122 | - | 131 | + const value = await (instance.component?.exposed as InstanceType<typeof AttributeDeliverModal>)?.open({ title: `${alias || deviceName}-${attrInfo.name}`, operationPassword, operationPasswordEnable, dataSourceJson }) as string |
123 | command.params = transportType === TransportTypeEnum.TCP ? value : { [attr]: value } | 132 | command.params = transportType === TransportTypeEnum.TCP ? value : { [attr]: value } |
133 | + if (!command.params) return | ||
124 | await doCommandDelivery({ way, command, deviceId }) | 134 | await doCommandDelivery({ way, command, deviceId }) |
125 | createMessage.success('命令下发成功') | 135 | createMessage.success('命令下发成功') |
126 | } | 136 | } |
@@ -53,7 +53,7 @@ const initFetchAlarmList = async () => { | @@ -53,7 +53,7 @@ const initFetchAlarmList = async () => { | ||
53 | })) | 53 | })) |
54 | 54 | ||
55 | initOptions.alarmList = resp.items || [] | 55 | initOptions.alarmList = resp.items || [] |
56 | - initOptions.interval = interval || 0 | 56 | + initOptions.interval = interval * 1000 || 0 |
57 | initOptions.scroll = autoPlay || false | 57 | initOptions.scroll = autoPlay || false |
58 | initOptions.polling = polling || 30 | 58 | initOptions.polling = polling || 30 |
59 | } | 59 | } |
@@ -87,10 +87,10 @@ onMounted(async () => { | @@ -87,10 +87,10 @@ onMounted(async () => { | ||
87 | <div class="seamless-scroll w-full h-full flex justify-center items-center overflow-y-scroll"> | 87 | <div class="seamless-scroll w-full h-full flex justify-center items-center overflow-y-scroll"> |
88 | <ScrollList | 88 | <ScrollList |
89 | v-if="initOptions.alarmList.length" | 89 | v-if="initOptions.alarmList.length" |
90 | - v-model="initOptions.scroll" :single-wait-time="initOptions.interval" :list="initOptions.alarmList" | ||
91 | - :is-rem-unit="true" :delay="10" :wheel="true" hover | 90 | + v-model="initOptions.scroll" :single-wait-time="initOptions.interval" :single-height="58" :list="initOptions.alarmList" |
91 | + :delay="10" hover | ||
92 | > | 92 | > |
93 | - <div v-for="(item, index) in initOptions.alarmList" :key="index" class="flex flex-col items-start p-2 border-gray-600 border-b border-solid border-t-transparent border-l-transparent border-r-transparent"> | 93 | + <div v-for="(item, index) in initOptions.alarmList" :key="index" class="flex flex-col items-start p-2 border-gray-600 border-t border-solid border-b-transparent border-l-transparent border-r-transparent"> |
94 | <div class="text-xs mb-2"> | 94 | <div class="text-xs mb-2"> |
95 | <span>设备:</span> | 95 | <span>设备:</span> |
96 | <span class="ml-1">{{ item.deviceAlias || item.deviceName }}</span> | 96 | <span class="ml-1">{{ item.deviceAlias || item.deviceName }}</span> |
@@ -73,7 +73,6 @@ const handleGetVideoPlay = async () => { | @@ -73,7 +73,6 @@ const handleGetVideoPlay = async () => { | ||
73 | } | 73 | } |
74 | 74 | ||
75 | const instance = unref(basicVideoPlayEl)?.customInit((options) => { | 75 | const instance = unref(basicVideoPlayEl)?.customInit((options) => { |
76 | - withToken.value = true | ||
77 | if (unref(withToken)) { | 76 | if (unref(withToken)) { |
78 | (options as any).flvjs.config.headers = { | 77 | (options as any).flvjs.config.headers = { |
79 | 'X-Authorization': `Bearer ${isShareMode() ? getShareJwtToken() : getJwtToken()}`, | 78 | 'X-Authorization': `Bearer ${isShareMode() ? getShareJwtToken() : getJwtToken()}`, |
@@ -109,7 +108,7 @@ const handleSelectPreview = () => { | @@ -109,7 +108,7 @@ const handleSelectPreview = () => { | ||
109 | 108 | ||
110 | onMounted(async () => { | 109 | onMounted(async () => { |
111 | await nextTick() | 110 | await nextTick() |
112 | - isLightboxMode() | 111 | + isLightboxMode() || isShareMode() |
113 | ? handleGetVideoPlay() | 112 | ? handleGetVideoPlay() |
114 | : handleSelectPreview() | 113 | : handleSelectPreview() |
115 | }) | 114 | }) |
@@ -78,6 +78,7 @@ export class EventHandler { | @@ -78,6 +78,7 @@ export class EventHandler { | ||
78 | } | 78 | } |
79 | 79 | ||
80 | const { handlerMouseDown, handlerMouseClick, handlerMouseDoubleClick, handlerMouseUp } = useNodeEvent(node.eventJson, node.dataSourceJson, cell) | 80 | const { handlerMouseDown, handlerMouseClick, handlerMouseDoubleClick, handlerMouseUp } = useNodeEvent(node.eventJson, node.dataSourceJson, cell) |
81 | + | ||
81 | switch (eventName) { | 82 | switch (eventName) { |
82 | case EventTypeEnum.DOWN: | 83 | case EventTypeEnum.DOWN: |
83 | handlerMouseDown() | 84 | handlerMouseDown() |
@@ -314,6 +314,7 @@ export enum DataTypeEnum { | @@ -314,6 +314,7 @@ export enum DataTypeEnum { | ||
314 | STRING = 'TEXT', | 314 | STRING = 'TEXT', |
315 | STRUCT = 'STRUCT', | 315 | STRUCT = 'STRUCT', |
316 | BOOL = 'BOOL', | 316 | BOOL = 'BOOL', |
317 | + ENUM = 'ENUM', | ||
317 | } | 318 | } |
318 | 319 | ||
319 | export enum AggregateTypeEnum { | 320 | export enum AggregateTypeEnum { |