Commit 15b017fc19db8192b2d05e5623eb4ca55b9720c8
Merge branch 'fix/DEFECT-1381' into 'main_dev'
fix: 命令下发新增服务下发功能 See merge request yunteng/thingskit-front!676
Showing
5 changed files
with
243 additions
and
40 deletions
... | ... | @@ -13,6 +13,7 @@ import { ChildDeviceParams } from './model/deviceModel'; |
13 | 13 | import { PaginationResult } from '/#/axios'; |
14 | 14 | import { AlarmLogItem } from './model/deviceConfigModel'; |
15 | 15 | import { omit } from 'lodash-es'; |
16 | +import { CommandDeliveryWayEnum } from '/@/enums/toolEnum'; | |
16 | 17 | enum DeviceManagerApi { |
17 | 18 | /** |
18 | 19 | * 设备URL |
... | ... | @@ -260,11 +261,14 @@ export const generateSNCode = () => { |
260 | 261 | }; |
261 | 262 | |
262 | 263 | //命令下发 |
263 | -export const commandIssuanceApi = (type, tbDeviceId, data) => { | |
264 | - const T = type === 'OneWay' ? 'oneway' : 'twoway'; | |
264 | +export const commandIssuanceApi = ( | |
265 | + type: CommandDeliveryWayEnum, | |
266 | + tbDeviceId: string, | |
267 | + data: Recordable | |
268 | +) => { | |
265 | 269 | return defHttp.post( |
266 | 270 | { |
267 | - url: DeviceManagerApi.COMMAND_ISSUANCE + '/' + T + '/' + tbDeviceId, | |
271 | + url: `${DeviceManagerApi.COMMAND_ISSUANCE}/${type}/${tbDeviceId}`, | |
268 | 272 | data, |
269 | 273 | }, |
270 | 274 | { | ... | ... |
1 | 1 | <script lang="ts" setup> |
2 | 2 | import { Card } from 'ant-design-vue'; |
3 | - import { nextTick, onBeforeUpdate, onUpdated } from 'vue'; | |
3 | + import { computed, nextTick, onMounted, onUpdated, unref, watch } from 'vue'; | |
4 | 4 | import { DataTypeEnum } from '../StructForm/config'; |
5 | 5 | import { BasicCreateFormParams } from './type'; |
6 | 6 | import { DynamicProps } from '/#/utils'; |
... | ... | @@ -22,6 +22,11 @@ |
22 | 22 | |
23 | 23 | const emit = defineEmits(['update:value']); |
24 | 24 | |
25 | + const getCurrentKeys = computed(() => { | |
26 | + const { inputData } = props; | |
27 | + return (inputData || []).map((item) => item.identifier); | |
28 | + }); | |
29 | + | |
25 | 30 | const [register, { setProps, getFieldsValue, setFieldsValue }] = useForm({ |
26 | 31 | schemas: [], |
27 | 32 | showActionButtonGroup: false, |
... | ... | @@ -32,8 +37,16 @@ |
32 | 37 | }); |
33 | 38 | |
34 | 39 | const syncValue = (key: string, value: any) => { |
35 | - const record = getFieldsValue(); | |
36 | - emit('update:value', { ...record, [key]: value }); | |
40 | + let record = getFieldsValue(); | |
41 | + record = { ...record, [key]: value }; | |
42 | + const setValues = {}; | |
43 | + Object.keys(record).forEach((key) => { | |
44 | + const hasKey = unref(getCurrentKeys).includes(key); | |
45 | + if (hasKey) { | |
46 | + setValues[key] = record[key]; | |
47 | + } | |
48 | + }); | |
49 | + emit('update:value', setValues); | |
37 | 50 | }; |
38 | 51 | |
39 | 52 | const createInputNumber = ({ |
... | ... | @@ -52,6 +65,8 @@ |
52 | 65 | type: 'number', |
53 | 66 | trigger: 'change', |
54 | 67 | validator: (_rule, value) => { |
68 | + const reg = /^[0-9]*$/; | |
69 | + if (!reg.test(value)) return Promise.reject(`${functionName}不是一个有效的数字`); | |
55 | 70 | if (value < min || value > max) { |
56 | 71 | return Promise.reject(`${functionName}取值范围在${min}~${max}之间`); |
57 | 72 | } |
... | ... | @@ -167,19 +182,37 @@ |
167 | 182 | return schemas; |
168 | 183 | }; |
169 | 184 | |
170 | - onBeforeUpdate(() => { | |
171 | - if (props.inputData && props.inputData.length) { | |
172 | - const schemas = transformToFormSchema(props.inputData); | |
185 | + const generateSchemas = (value: StructJSON[]) => { | |
186 | + if (value && value.length) { | |
187 | + const schemas = transformToFormSchema(value); | |
173 | 188 | setProps({ schemas }); |
174 | 189 | } |
190 | + }; | |
191 | + | |
192 | + onMounted(() => { | |
193 | + generateSchemas(props.inputData); | |
175 | 194 | }); |
176 | 195 | |
177 | 196 | onUpdated(async () => { |
178 | 197 | if (props.inputData && props.inputData.length) { |
179 | 198 | await nextTick(); |
180 | - setFieldsValue(props.value); | |
199 | + setFieldsValue(props.value || {}); | |
181 | 200 | } |
182 | 201 | }); |
202 | + | |
203 | + watch( | |
204 | + () => props.inputData, | |
205 | + (value) => { | |
206 | + generateSchemas(value); | |
207 | + } | |
208 | + ); | |
209 | + | |
210 | + watch( | |
211 | + () => props.formProps, | |
212 | + (value) => { | |
213 | + setProps(value); | |
214 | + } | |
215 | + ); | |
183 | 216 | </script> |
184 | 217 | |
185 | 218 | <template> | ... | ... |
... | ... | @@ -34,3 +34,13 @@ export enum ReadAndWriteEnum { |
34 | 34 | READ = 'r', |
35 | 35 | READ_AND_WRITE = 'rw', |
36 | 36 | } |
37 | + | |
38 | +export enum ServiceCallTypeEnum { | |
39 | + ASYNC = 'ASYNC', | |
40 | + SYNC = 'SYNC', | |
41 | +} | |
42 | + | |
43 | +export enum CommandDeliveryWayEnum { | |
44 | + ONE_WAY = 'oneway', | |
45 | + TWO_WAY = 'twoway', | |
46 | +} | ... | ... |
1 | -import { FormSchema, useComponentRegister } from '/@/components/Form'; | |
1 | +import { FormProps, FormSchema, useComponentRegister } from '/@/components/Form'; | |
2 | 2 | import { findDictItemByCode } from '/@/api/system/dict'; |
3 | 3 | import { deviceProfile, getGatewayDevice } from '/@/api/device/deviceManager'; |
4 | 4 | import { TransportTypeEnum } from '../../profiles/components/TransportDescript/const'; |
5 | 5 | import { JSONEditorValidator } from '/@/components/CodeEditor/src/JSONEditor'; |
6 | 6 | import { JSONEditor } from '/@/components/CodeEditor'; |
7 | 7 | import { DeviceTypeEnum } from '/@/api/device/model/deviceModel'; |
8 | +import { getModelServices } from '/@/api/device/modelOfMatter'; | |
9 | +import { ModelOfMatterParams } from '/@/api/device/model/modelOfMatterModel'; | |
10 | +import { toRaw, unref } from 'vue'; | |
11 | +import ObjectModelValidateForm from '/@/components/Form/src/externalCompns/components/ObjectModelValidateForm/ObjectModelValidateForm.vue'; | |
12 | +import { CommandDeliveryWayEnum, ServiceCallTypeEnum } from '/@/enums/toolEnum'; | |
8 | 13 | useComponentRegister('JSONEditor', JSONEditor); |
14 | +useComponentRegister('ObjectModelValidateForm', ObjectModelValidateForm); | |
9 | 15 | |
10 | 16 | export enum TypeEnum { |
11 | 17 | IS_GATEWAY = 'GATEWAY', |
... | ... | @@ -711,37 +717,87 @@ export const TokenSchemas: FormSchema[] = [ |
711 | 717 | }, |
712 | 718 | ]; |
713 | 719 | |
714 | -export const CommandSchemas = (transportType: TransportTypeEnum): FormSchema[] => { | |
720 | +export enum ValueType { | |
721 | + JSON = 'json', | |
722 | + STRING = 'string', | |
723 | +} | |
724 | + | |
725 | +export enum CommandFieldsEnum { | |
726 | + COMMAND_TYPE = 'commandType', | |
727 | + VALUE_TYPE = 'valueType', | |
728 | + COMMAND_TEXT = 'commandText', | |
729 | + COMAND_VALUE = 'commandValue', | |
730 | + SERVICE = 'service', | |
731 | + SERVICE_TYPE = 'service_type', | |
732 | + TCP_SERVICE = 'tcpService', | |
733 | + MODEL_INPUT = 'modelInput', | |
734 | + CUSTOM_TYPE = 'customType', | |
735 | +} | |
736 | + | |
737 | +export enum CommandType { | |
738 | + CUSTOM = 'custom', | |
739 | + SERVICE = 'service', | |
740 | +} | |
741 | + | |
742 | +export const CommandSchemas = ( | |
743 | + transportType: TransportTypeEnum, | |
744 | + deviceProfileId: string | |
745 | +): FormSchema[] => { | |
715 | 746 | return [ |
716 | 747 | { |
717 | - field: 'commandType', | |
748 | + field: CommandFieldsEnum.COMMAND_TYPE, | |
718 | 749 | component: 'RadioGroup', |
719 | 750 | label: '下发类型', |
720 | - defaultValue: 'OneWay', | |
751 | + defaultValue: CommandType.CUSTOM, | |
752 | + componentProps: ({ formActionType }) => { | |
753 | + const { setFieldsValue } = formActionType; | |
754 | + return { | |
755 | + options: [ | |
756 | + { label: '自定义', value: CommandType.CUSTOM }, | |
757 | + { label: '服务', value: CommandType.SERVICE }, | |
758 | + ], | |
759 | + onChange() { | |
760 | + setFieldsValue({ | |
761 | + [CommandFieldsEnum.SERVICE]: null, | |
762 | + [CommandFieldsEnum.MODEL_INPUT]: null, | |
763 | + [CommandFieldsEnum.COMAND_VALUE]: null, | |
764 | + [CommandFieldsEnum.COMMAND_TEXT]: null, | |
765 | + }); | |
766 | + }, | |
767 | + }; | |
768 | + }, | |
769 | + }, | |
770 | + { | |
771 | + field: CommandFieldsEnum.CUSTOM_TYPE, | |
772 | + component: 'RadioGroup', | |
773 | + label: '单向/双向', | |
774 | + defaultValue: CommandDeliveryWayEnum.ONE_WAY, | |
775 | + ifShow: ({ model }) => model[CommandFieldsEnum.COMMAND_TYPE] === CommandType.CUSTOM, | |
721 | 776 | componentProps: { |
722 | 777 | options: [ |
723 | 778 | { |
724 | 779 | label: '单向', |
725 | - value: 'OneWay', | |
780 | + value: CommandDeliveryWayEnum.ONE_WAY, | |
726 | 781 | }, |
727 | 782 | { |
728 | 783 | label: '双向', |
729 | - value: 'TwoWay', | |
784 | + value: CommandDeliveryWayEnum.TWO_WAY, | |
730 | 785 | }, |
731 | 786 | ], |
732 | 787 | }, |
733 | 788 | }, |
734 | 789 | { |
735 | - field: 'valueType', | |
790 | + field: CommandFieldsEnum.VALUE_TYPE, | |
736 | 791 | label: '命令类型', |
737 | 792 | component: 'RadioGroup', |
738 | - defaultValue: transportType === TransportTypeEnum.TCP ? 'string' : 'json', | |
793 | + ifShow: ({ model }) => model[CommandFieldsEnum.COMMAND_TYPE] === CommandType.CUSTOM, | |
794 | + defaultValue: transportType === TransportTypeEnum.TCP ? ValueType.STRING : ValueType.JSON, | |
739 | 795 | componentProps: () => { |
740 | 796 | const options: Record<'label' | 'value', string>[] = []; |
741 | 797 | if (transportType === TransportTypeEnum.TCP) { |
742 | - options.push({ label: '字符串', value: 'string' }); | |
798 | + options.push({ label: '字符串', value: ValueType.STRING }); | |
743 | 799 | } else { |
744 | - options.push({ label: 'JSON', value: 'json' }); | |
800 | + options.push({ label: 'JSON', value: ValueType.JSON }); | |
745 | 801 | } |
746 | 802 | return { |
747 | 803 | options, |
... | ... | @@ -749,18 +805,18 @@ export const CommandSchemas = (transportType: TransportTypeEnum): FormSchema[] = |
749 | 805 | }, |
750 | 806 | }, |
751 | 807 | { |
752 | - field: 'commandText', | |
753 | - label: '请输入命令内容', | |
754 | - ifShow: ({ model }) => { | |
755 | - return model['valueType'] === 'string'; | |
756 | - }, | |
808 | + field: CommandFieldsEnum.COMMAND_TEXT, | |
809 | + label: '命令', | |
810 | + ifShow: ({ model }) => | |
811 | + model[CommandFieldsEnum.VALUE_TYPE] === ValueType.STRING && | |
812 | + model[CommandFieldsEnum.COMMAND_TYPE] === CommandType.CUSTOM, | |
757 | 813 | component: 'InputTextArea', |
758 | 814 | componentProps: { |
759 | 815 | autoSize: { |
760 | - minRows: 6, | |
816 | + minRows: 3, | |
761 | 817 | }, |
818 | + placeholder: '请输入命令内容', | |
762 | 819 | }, |
763 | - colProps: { span: 20 }, | |
764 | 820 | dynamicRules: () => { |
765 | 821 | return [ |
766 | 822 | { |
... | ... | @@ -778,19 +834,97 @@ export const CommandSchemas = (transportType: TransportTypeEnum): FormSchema[] = |
778 | 834 | }, |
779 | 835 | }, |
780 | 836 | { |
781 | - field: 'commandValue', | |
782 | - label: '请输入命令内容', | |
837 | + field: CommandFieldsEnum.COMAND_VALUE, | |
838 | + label: '命令', | |
783 | 839 | component: 'JSONEditor', |
784 | 840 | colProps: { span: 20 }, |
785 | 841 | changeEvent: 'update:value', |
786 | 842 | valueField: 'value', |
787 | 843 | rules: [...JSONEditorValidator()], |
788 | - ifShow: ({ model }) => { | |
789 | - return model['valueType'] === 'json'; | |
790 | - }, | |
844 | + ifShow: ({ model }) => | |
845 | + model[CommandFieldsEnum.VALUE_TYPE] === ValueType.JSON && | |
846 | + model[CommandFieldsEnum.COMMAND_TYPE] === CommandType.CUSTOM, | |
847 | + | |
791 | 848 | componentProps: { |
792 | 849 | height: 250, |
793 | 850 | }, |
794 | 851 | }, |
852 | + { | |
853 | + field: CommandFieldsEnum.SERVICE, | |
854 | + label: '服务', | |
855 | + component: 'ApiSelect', | |
856 | + ifShow: ({ model }) => model[CommandFieldsEnum.COMMAND_TYPE] !== CommandType.CUSTOM, | |
857 | + rules: [{ required: true, message: '请选择服务' }], | |
858 | + componentProps: ({ formActionType }) => { | |
859 | + const { setFieldsValue, updateSchema } = formActionType; | |
860 | + return { | |
861 | + api: async () => { | |
862 | + try { | |
863 | + const result = await getModelServices({ deviceProfileId }); | |
864 | + return result || []; | |
865 | + } catch (error) { | |
866 | + console.error(error); | |
867 | + return []; | |
868 | + } | |
869 | + }, | |
870 | + valueField: 'id', | |
871 | + labelField: 'functionName', | |
872 | + getPopupContainer: () => document.body, | |
873 | + onChange(value: string, options: ModelOfMatterParams) { | |
874 | + if (!value) return; | |
875 | + | |
876 | + const setValues = { | |
877 | + [CommandFieldsEnum.CUSTOM_TYPE]: | |
878 | + options.callType === ServiceCallTypeEnum.ASYNC | |
879 | + ? CommandDeliveryWayEnum.TWO_WAY | |
880 | + : CommandDeliveryWayEnum.ONE_WAY, | |
881 | + [CommandFieldsEnum.MODEL_INPUT]: null, | |
882 | + }; | |
883 | + | |
884 | + if (transportType !== TransportTypeEnum.TCP) { | |
885 | + updateSchema({ | |
886 | + field: CommandFieldsEnum.MODEL_INPUT, | |
887 | + componentProps: { | |
888 | + inputData: toRaw(unref(options.functionJson.inputData)), | |
889 | + }, | |
890 | + }); | |
891 | + } else { | |
892 | + Object.assign(setValues, { | |
893 | + [CommandFieldsEnum.TCP_SERVICE]: | |
894 | + options.functionJson?.inputData?.[0]?.serviceCommand, | |
895 | + }); | |
896 | + } | |
897 | + | |
898 | + setFieldsValue(setValues); | |
899 | + }, | |
900 | + }; | |
901 | + }, | |
902 | + }, | |
903 | + { | |
904 | + field: CommandFieldsEnum.TCP_SERVICE, | |
905 | + component: 'Input', | |
906 | + label: '命令', | |
907 | + dynamicDisabled: true, | |
908 | + ifShow: ({ model }) => | |
909 | + model[CommandFieldsEnum.SERVICE] && | |
910 | + transportType === TransportTypeEnum.TCP && | |
911 | + model[CommandFieldsEnum.COMMAND_TYPE] !== CommandType.CUSTOM, | |
912 | + }, | |
913 | + { | |
914 | + field: CommandFieldsEnum.MODEL_INPUT, | |
915 | + component: 'ObjectModelValidateForm', | |
916 | + label: '输入参数', | |
917 | + changeEvent: 'update:value', | |
918 | + valueField: 'value', | |
919 | + ifShow: ({ model }) => | |
920 | + model[CommandFieldsEnum.SERVICE] && | |
921 | + transportType !== TransportTypeEnum.TCP && | |
922 | + model[CommandFieldsEnum.COMMAND_TYPE] !== CommandType.CUSTOM, | |
923 | + componentProps: { | |
924 | + formProps: { | |
925 | + wrapperCol: { span: 24 }, | |
926 | + } as FormProps, | |
927 | + }, | |
928 | + }, | |
795 | 929 | ]; |
796 | 930 | }; | ... | ... |
... | ... | @@ -12,7 +12,7 @@ |
12 | 12 | <script lang="ts"> |
13 | 13 | import { defineComponent, ref } from 'vue'; |
14 | 14 | import { BasicForm, useForm } from '/@/components/Form'; |
15 | - import { CommandSchemas } from '../../config/data'; | |
15 | + import { CommandFieldsEnum, CommandSchemas, CommandType, ValueType } from '../../config/data'; | |
16 | 16 | import { commandIssuanceApi } from '/@/api/device/deviceManager'; |
17 | 17 | import { useMessage } from '/@/hooks/web/useMessage'; |
18 | 18 | import { Button } from '/@/components/Button'; |
... | ... | @@ -20,6 +20,7 @@ |
20 | 20 | import { DeviceRecord } from '/@/api/device/model/deviceModel'; |
21 | 21 | import { TransportTypeEnum } from '../../../profiles/components/TransportDescript/const'; |
22 | 22 | import { parseStringToJSON } from '/@/components/CodeEditor/src/JSONEditor'; |
23 | + import { CommandDeliveryWayEnum } from '/@/enums/toolEnum'; | |
23 | 24 | |
24 | 25 | export default defineComponent({ |
25 | 26 | components: { BasicForm, Button, Space }, |
... | ... | @@ -37,8 +38,10 @@ |
37 | 38 | const [registerForm, { getFieldsValue, validate, resetFields }] = useForm({ |
38 | 39 | labelWidth: 120, |
39 | 40 | schemas: CommandSchemas( |
40 | - props.deviceDetail.deviceProfile.transportType as TransportTypeEnum | |
41 | + props.deviceDetail.deviceProfile.transportType as TransportTypeEnum, | |
42 | + props.deviceDetail.deviceProfileId | |
41 | 43 | ), |
44 | + baseColProps: { span: 20 }, | |
42 | 45 | labelAlign: 'right', |
43 | 46 | showSubmitButton: false, |
44 | 47 | showResetButton: false, |
... | ... | @@ -59,14 +62,27 @@ |
59 | 62 | let command = { |
60 | 63 | persistent: true, |
61 | 64 | method: 'methodThingskit', |
62 | - params: field.commandText, | |
65 | + params: field[CommandFieldsEnum.COMMAND_TEXT], | |
63 | 66 | }; |
64 | - if (field.valueType === 'json') { | |
65 | - const { json } = parseStringToJSON(field.commandValue); | |
66 | - command.params = json; | |
67 | + | |
68 | + if (field[CommandFieldsEnum.COMMAND_TYPE] === CommandType.CUSTOM) { | |
69 | + if (field[CommandFieldsEnum.VALUE_TYPE] === ValueType.JSON) { | |
70 | + const { json } = parseStringToJSON(field.commandValue); | |
71 | + command.params = json; | |
72 | + } | |
73 | + } else { | |
74 | + const { transportType } = props.deviceDetail.deviceProfile; | |
75 | + command.params = | |
76 | + transportType === TransportTypeEnum.TCP | |
77 | + ? field[CommandFieldsEnum.TCP_SERVICE] | |
78 | + : field[CommandFieldsEnum.MODEL_INPUT]; | |
67 | 79 | } |
68 | 80 | |
69 | - commandIssuanceApi(field.commandType, props.deviceDetail.tbDeviceId, command) | |
81 | + commandIssuanceApi( | |
82 | + field[CommandFieldsEnum.CUSTOM_TYPE] as CommandDeliveryWayEnum, | |
83 | + props.deviceDetail.tbDeviceId, | |
84 | + command | |
85 | + ) | |
70 | 86 | .then((res) => { |
71 | 87 | if (!res) return; |
72 | 88 | createMessage.success('命令下发成功'); |
... | ... | @@ -100,9 +116,15 @@ |
100 | 116 | }, |
101 | 117 | }); |
102 | 118 | </script> |
103 | -<style scoped> | |
119 | +<style scoped lang="less"> | |
104 | 120 | .jsoneditor-transform { |
105 | 121 | background-position: -144px -96px; |
106 | 122 | display: none !important; |
107 | 123 | } |
124 | + | |
125 | + .tabs-detail:deep(.object-model-validate-form) { | |
126 | + > .ant-row { | |
127 | + @apply w-full; | |
128 | + } | |
129 | + } | |
108 | 130 | </style> | ... | ... |