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 | 4351 | if (success != null) |
4352 | 4352 | success() |
4353 | 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 | 4361 | handleLoadContent() |
... | ... | @@ -5137,7 +5139,7 @@ App.prototype.addPreviewButton = function () { |
5137 | 5139 | this.previewButton.style.border = '1px solid transparent' |
5138 | 5140 | this.previewButton.style.color = '#fff' |
5139 | 5141 | |
5140 | - const icon = document.createElement('img') | |
5142 | + const icon = document.createElement('img') | |
5141 | 5143 | icon.setAttribute('src', this.previewImage) |
5142 | 5144 | icon.setAttribute('align', 'absmiddle') |
5143 | 5145 | icon.style.marginRight = '4px' | ... | ... |
1 | -import type { FunctionType, TransportTypeEnum } from '@/enums/datasource' | |
1 | +import type { DataTypeEnum, FunctionType, TransportTypeEnum } from '@/enums/datasource' | |
2 | 2 | |
3 | 3 | export interface DeviceProfileItemType { |
4 | 4 | id: string |
... | ... | @@ -147,6 +147,7 @@ export interface Detail { |
147 | 147 | export interface DataType { |
148 | 148 | type: string |
149 | 149 | specs: Specs | StructJSON[] |
150 | + specsList: Specs[] | |
150 | 151 | } |
151 | 152 | |
152 | 153 | export interface Specs { |
... | ... | @@ -158,6 +159,9 @@ export interface Specs { |
158 | 159 | length?: string |
159 | 160 | min: string |
160 | 161 | max: string |
162 | + name?: string | |
163 | + value?: string | |
164 | + dataType?: DataTypeEnum | |
161 | 165 | } |
162 | 166 | |
163 | 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 | 1 | <script setup lang="ts"> |
2 | 2 | import { Modal } from 'ant-design-vue' |
3 | 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 | 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 | 12 | const resolveFn = ref<Fn>() |
9 | 13 | |
... | ... | @@ -11,61 +15,50 @@ const visible = ref(false) |
11 | 15 | |
12 | 16 | const password = ref() |
13 | 17 | |
18 | +const currentDataSourceJson = ref<NodeDataDataSourceJsonType>() | |
19 | + | |
14 | 20 | const [register, { getFieldsValue, resetFields, validate, setProps, clearValidate }] = useForm({ |
15 | 21 | layout: FormLayoutEnum.VERTICAL, |
16 | 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 | 26 | visible.value = true |
56 | 27 | password.value = operationPassword |
28 | + currentDataSourceJson.value = dataSourceJson | |
57 | 29 | return new Promise((resolve) => { |
58 | 30 | resolveFn.value = resolve |
59 | 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 | 57 | const handleOk = async () => { |
66 | 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 | 62 | visible.value = false |
70 | 63 | resetFields() |
71 | 64 | } |
... | ... | @@ -80,17 +73,9 @@ defineExpose({ open }) |
80 | 73 | |
81 | 74 | <template> |
82 | 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 | 79 | <BasicForm @register="register" /> |
95 | 80 | </Modal> |
96 | 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 | functionName, |
10 | 10 | dataType, |
11 | 11 | }: StructJSON): FormSchema => { |
12 | - const { specs } = dataType || {} | |
12 | + const { specs, type } = dataType || {} | |
13 | 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 | 15 | return { |
16 | 16 | field: identifier, |
17 | 17 | label: functionName, |
... | ... | @@ -21,22 +21,11 @@ export const getFormSchemas = ({ structJSON: structJson, required, transportType |
21 | 21 | required, |
22 | 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 | 25 | componentProps: { |
38 | 26 | max, |
39 | 27 | min, |
28 | + precision: type === DataTypeEnum.NUMBER_INT ? 0 : 2, | |
40 | 29 | }, |
41 | 30 | } as FormSchema |
42 | 31 | } |
... | ... | @@ -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 | 106 | const createStructJson = ({ identifier, functionName, dataType }: StructJSON): FormSchema => { |
97 | 107 | return { |
98 | 108 | field: identifier, |
... | ... | @@ -121,7 +131,6 @@ export const getFormSchemas = ({ structJSON: structJson, required, transportType |
121 | 131 | } |
122 | 132 | |
123 | 133 | const schemas: FormSchema[] = [] |
124 | - | |
125 | 134 | for (const item of structJson) { |
126 | 135 | const { dataType } = item |
127 | 136 | const { type } = dataType || {} |
... | ... | @@ -129,6 +138,8 @@ export const getFormSchemas = ({ structJSON: structJson, required, transportType |
129 | 138 | schemas.push(createTCPServiceCommandInput(item)) |
130 | 139 | if (type === DataTypeEnum.BOOL) |
131 | 140 | schemas.push(createSelect(item)) |
141 | + else if (type === DataTypeEnum.ENUM) | |
142 | + schemas.push(createEnumSelect(item)) | |
132 | 143 | else if (type === DataTypeEnum.NUMBER_INT) |
133 | 144 | schemas.push(createInputNumber(item)) |
134 | 145 | else if (type === DataTypeEnum.NUMBER_DOUBLE) | ... | ... |
1 | +import type { ValidatorRule } from 'ant-design-vue/lib/form/interface' | |
2 | + | |
1 | 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 | 22 | |
23 | 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 | 25 | const [register, formActionType] = useForm({ |
30 | 26 | schemas: getFormSchemas({ structJSON: props.inputData || [], required: props.required, transportType: props.transportType }), |
31 | 27 | showActionButtonGroup: false, |
32 | - // labelWidth: getLabelWidth() || 80, | |
28 | + labelWidth: 100, | |
33 | 29 | labelAlign: FormLabelAlignEnum.RIGHT, |
34 | 30 | layout: FormLayoutEnum.HORIZONTAL, |
35 | 31 | }) |
... | ... | @@ -105,19 +101,34 @@ defineExpose<ComponentExposeType>({ |
105 | 101 | <Card class="!border-2 !border-dashed" :title="title"> |
106 | 102 | <BasicForm class="things-model-form" @register="register"> |
107 | 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 | 109 | </template> |
110 | 110 | </BasicForm> |
111 | 111 | </Card> |
112 | 112 | </template> |
113 | 113 | |
114 | 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 | 134 | </style> | ... | ... |
1 | -import { h, render } from 'vue' | |
1 | +import { h, render, unref } from 'vue' | |
2 | 2 | import { ControlComponentEnum } from '../packages/Control' |
3 | +import { useGetModbusCommand } from '../components/PublicForm/components/DataEvents/CommandDeliveryModal/useGetModbusCommand' | |
3 | 4 | import { doCommandDelivery, getDeviceActive, getDeviceInfo } from '@/api/device' |
4 | 5 | import type { MouseUpEventDataType, NodeDataDataSourceJsonType, NodeDataEventJsonType, SingleClickEventDataType } from '@/api/node/model' |
5 | 6 | import { CommandWayEnum } from '@/enums/commandEnum' |
6 | -import { ActRangListItemTypeEnum, EventActionTypeEnum, TransportTypeEnum } from '@/enums/datasource' | |
7 | +import { ActRangListItemTypeEnum, CodeTypeEnum, EventActionTypeEnum, TransportTypeEnum } from '@/enums/datasource' | |
7 | 8 | import { useMessage } from '@/hooks/web/useMessage' |
8 | 9 | import { AttributeDeliverModal, CommandDeliveryConfirmModal, CommandDeliveryModal, ConfirmModal } from '@/core/Library/components/PublicForm/components/DataEvents/CommandDeliveryModal' |
9 | 10 | import type { MxCell } from '@/fitCore/types' |
... | ... | @@ -76,7 +77,7 @@ export function useNodeEvent(eventJson: NodeDataEventJsonType, dataSourceJson: N |
76 | 77 | const nodeUtils = new NodeUtils() |
77 | 78 | const { way = CommandWayEnum.ONE_WAY } = data |
78 | 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 | 81 | const contentDataStore = useContentDataStoreWithOut() |
81 | 82 | const currentData = contentDataStore.contentData.find(item => item.id === cell.getId()) |
82 | 83 | if (!currentData) return |
... | ... | @@ -95,6 +96,7 @@ export function useNodeEvent(eventJson: NodeDataEventJsonType, dataSourceJson: N |
95 | 96 | const res = rangeList.find(item => item.type === (status === ActRangListItemTypeEnum.OPEN ? ActRangListItemTypeEnum.CLOSE : ActRangListItemTypeEnum.OPEN)) |
96 | 97 | if (!res) return |
97 | 98 | const { statusValue } = res |
99 | + | |
98 | 100 | command.params = transportType === TransportTypeEnum.TCP |
99 | 101 | ? statusValue! |
100 | 102 | : { |
... | ... | @@ -104,7 +106,7 @@ export function useNodeEvent(eventJson: NodeDataEventJsonType, dataSourceJson: N |
104 | 106 | if (operationPasswordEnable) { |
105 | 107 | const instance = h(CommandDeliveryConfirmModal) |
106 | 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 | 111 | else { |
110 | 112 | const instance = h(ConfirmModal) |
... | ... | @@ -112,15 +114,23 @@ export function useNodeEvent(eventJson: NodeDataEventJsonType, dataSourceJson: N |
112 | 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 | 125 | await doCommandDelivery({ way, command, deviceId }) |
116 | 126 | createMessage.success('命令下发成功') |
117 | 127 | } |
118 | 128 | else { |
119 | 129 | const instance = h(AttributeDeliverModal) |
120 | 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 | 132 | command.params = transportType === TransportTypeEnum.TCP ? value : { [attr]: value } |
133 | + if (!command.params) return | |
124 | 134 | await doCommandDelivery({ way, command, deviceId }) |
125 | 135 | createMessage.success('命令下发成功') |
126 | 136 | } | ... | ... |
... | ... | @@ -53,7 +53,7 @@ const initFetchAlarmList = async () => { |
53 | 53 | })) |
54 | 54 | |
55 | 55 | initOptions.alarmList = resp.items || [] |
56 | - initOptions.interval = interval || 0 | |
56 | + initOptions.interval = interval * 1000 || 0 | |
57 | 57 | initOptions.scroll = autoPlay || false |
58 | 58 | initOptions.polling = polling || 30 |
59 | 59 | } |
... | ... | @@ -87,10 +87,10 @@ onMounted(async () => { |
87 | 87 | <div class="seamless-scroll w-full h-full flex justify-center items-center overflow-y-scroll"> |
88 | 88 | <ScrollList |
89 | 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 | 94 | <div class="text-xs mb-2"> |
95 | 95 | <span>设备:</span> |
96 | 96 | <span class="ml-1">{{ item.deviceAlias || item.deviceName }}</span> | ... | ... |
... | ... | @@ -73,7 +73,6 @@ const handleGetVideoPlay = async () => { |
73 | 73 | } |
74 | 74 | |
75 | 75 | const instance = unref(basicVideoPlayEl)?.customInit((options) => { |
76 | - withToken.value = true | |
77 | 76 | if (unref(withToken)) { |
78 | 77 | (options as any).flvjs.config.headers = { |
79 | 78 | 'X-Authorization': `Bearer ${isShareMode() ? getShareJwtToken() : getJwtToken()}`, |
... | ... | @@ -109,7 +108,7 @@ const handleSelectPreview = () => { |
109 | 108 | |
110 | 109 | onMounted(async () => { |
111 | 110 | await nextTick() |
112 | - isLightboxMode() | |
111 | + isLightboxMode() || isShareMode() | |
113 | 112 | ? handleGetVideoPlay() |
114 | 113 | : handleSelectPreview() |
115 | 114 | }) | ... | ... |
... | ... | @@ -78,6 +78,7 @@ export class EventHandler { |
78 | 78 | } |
79 | 79 | |
80 | 80 | const { handlerMouseDown, handlerMouseClick, handlerMouseDoubleClick, handlerMouseUp } = useNodeEvent(node.eventJson, node.dataSourceJson, cell) |
81 | + | |
81 | 82 | switch (eventName) { |
82 | 83 | case EventTypeEnum.DOWN: |
83 | 84 | handlerMouseDown() | ... | ... |