Showing
42 changed files
with
1388 additions
and
132 deletions
| @@ -58,6 +58,7 @@ | @@ -58,6 +58,7 @@ | ||
| 58 | "flv.js": "^1.6.2", | 58 | "flv.js": "^1.6.2", |
| 59 | "hls.js": "^1.0.10", | 59 | "hls.js": "^1.0.10", |
| 60 | "intro.js": "^4.1.0", | 60 | "intro.js": "^4.1.0", |
| 61 | + "js-beautify": "^1.14.9", | ||
| 61 | "jsoneditor": "^9.7.2", | 62 | "jsoneditor": "^9.7.2", |
| 62 | "jwt-decode": "^3.1.2", | 63 | "jwt-decode": "^3.1.2", |
| 63 | "lodash-es": "^4.17.21", | 64 | "lodash-es": "^4.17.21", |
src/api/ruleChainDesigner/index.ts
0 → 100644
| 1 | +import { DeviceInfoItemType } from './model'; | ||
| 2 | +import { TBPaginationResult } from '/#/axios'; | ||
| 3 | +import { defHttp } from '/@/utils/http/axios'; | ||
| 4 | + | ||
| 5 | +enum Api { | ||
| 6 | + GET_DEVICE_INFOS = '/tenant/deviceInfos', | ||
| 7 | +} | ||
| 8 | + | ||
| 9 | +export const getDeviceInfos = () => { | ||
| 10 | + return defHttp.get<TBPaginationResult<DeviceInfoItemType>>( | ||
| 11 | + { | ||
| 12 | + url: Api.GET_DEVICE_INFOS, | ||
| 13 | + }, | ||
| 14 | + { joinPrefix: false } | ||
| 15 | + ); | ||
| 16 | +}; |
src/api/ruleChainDesigner/model/index.ts
0 → 100644
| 1 | +export interface DeviceInfoItemType { | ||
| 2 | + id: Id; | ||
| 3 | + createdTime: number; | ||
| 4 | + additionalInfo: AdditionalInfo; | ||
| 5 | + tenantId: Id; | ||
| 6 | + customerId: Id; | ||
| 7 | + name: string; | ||
| 8 | + type: string; | ||
| 9 | + label: string; | ||
| 10 | + deviceProfileId: Id; | ||
| 11 | + deviceData: DeviceData; | ||
| 12 | + firmwareId: any; | ||
| 13 | + softwareId: any; | ||
| 14 | + customerTitle: any; | ||
| 15 | + customerIsPublic: boolean; | ||
| 16 | + deviceProfileName: string; | ||
| 17 | +} | ||
| 18 | + | ||
| 19 | +export interface Id { | ||
| 20 | + entityType: string; | ||
| 21 | + id: string; | ||
| 22 | +} | ||
| 23 | + | ||
| 24 | +export interface DeviceData { | ||
| 25 | + configuration: Configuration; | ||
| 26 | + transportConfiguration: TransportConfiguration; | ||
| 27 | +} | ||
| 28 | + | ||
| 29 | +export interface AdditionalInfo { | ||
| 30 | + gateway: boolean; | ||
| 31 | + description: string; | ||
| 32 | + overwriteActivityTime: boolean; | ||
| 33 | +} | ||
| 34 | +export interface Configuration { | ||
| 35 | + type: string; | ||
| 36 | +} | ||
| 37 | + | ||
| 38 | +export interface TransportConfiguration { | ||
| 39 | + type: string; | ||
| 40 | +} |
| 1 | <script lang="ts" setup> | 1 | <script lang="ts" setup> |
| 2 | - import { ref, watch } from 'vue'; | ||
| 3 | - import JSONEditor, { JSONEditorOptions } from 'jsoneditor'; | ||
| 4 | - import 'jsoneditor/dist/jsoneditor.min.css'; | ||
| 5 | - import { unref } from 'vue'; | ||
| 6 | - import { onMounted } from 'vue'; | ||
| 7 | - import { computed } from '@vue/reactivity'; | ||
| 8 | - import { onUnmounted } from 'vue'; | 2 | + import { onMounted, computed, onUnmounted, unref, ref, watch } from 'vue'; |
| 3 | + import { Tooltip } from 'ant-design-vue'; | ||
| 4 | + import { Icon } from '/@/components/Icon'; | ||
| 5 | + import { useFullscreen } from '@vueuse/core'; | ||
| 6 | + import { isNumber, isObject, isString } from '/@/utils/is'; | ||
| 7 | + import AceEditor, { Ace } from 'ace-builds'; | ||
| 8 | + import workerJsonUrl from 'ace-builds/src-noconflict/worker-json?url'; | ||
| 9 | + import githubTheme from 'ace-builds/src-noconflict/theme-github?url'; | ||
| 10 | + import 'ace-builds/src-noconflict/mode-json'; | ||
| 9 | 11 | ||
| 10 | enum EventEnum { | 12 | enum EventEnum { |
| 11 | UPDATE_VALUE = 'update:value', | 13 | UPDATE_VALUE = 'update:value', |
| @@ -16,89 +18,133 @@ | @@ -16,89 +18,133 @@ | ||
| 16 | 18 | ||
| 17 | const props = withDefaults( | 19 | const props = withDefaults( |
| 18 | defineProps<{ | 20 | defineProps<{ |
| 19 | - value?: string; | ||
| 20 | - options?: JSONEditorOptions; | ||
| 21 | - height?: number; | 21 | + value?: string | Recordable; |
| 22 | + height?: number | string; | ||
| 23 | + title?: string; | ||
| 24 | + disabled?: boolean; | ||
| 22 | }>(), | 25 | }>(), |
| 23 | { | 26 | { |
| 24 | - options: () => | ||
| 25 | - ({ | ||
| 26 | - mode: 'code', | ||
| 27 | - mainMenuBar: false, | ||
| 28 | - statusBar: false, | ||
| 29 | - } as JSONEditorOptions), | ||
| 30 | height: 150, | 27 | height: 150, |
| 31 | } | 28 | } |
| 32 | ); | 29 | ); |
| 33 | 30 | ||
| 34 | const emit = defineEmits<{ | 31 | const emit = defineEmits<{ |
| 35 | - (e: EventEnum.UPDATE_VALUE, value: any, instance?: JSONEditor): void; | ||
| 36 | - (e: EventEnum.CHANGE, value: any, instance?: JSONEditor): void; | ||
| 37 | - (e: EventEnum.BLUR, event: Event, instance?: JSONEditor): void; | ||
| 38 | - (e: EventEnum.FOCUS, event: Event, instance?: JSONEditor): void; | 32 | + (e: EventEnum.UPDATE_VALUE, value: any, instance?: Ace.Editor): void; |
| 33 | + (e: EventEnum.CHANGE, value: any, instance?: Ace.Editor): void; | ||
| 34 | + (e: EventEnum.BLUR, event: Event, instance?: Ace.Editor): void; | ||
| 35 | + (e: EventEnum.FOCUS, event: Event, instance?: Ace.Editor): void; | ||
| 39 | }>(); | 36 | }>(); |
| 40 | 37 | ||
| 41 | const jsonEditorElRef = ref<Nullable<any>>(); | 38 | const jsonEditorElRef = ref<Nullable<any>>(); |
| 42 | 39 | ||
| 43 | - const editoreRef = ref<JSONEditor>(); | 40 | + const editoreRef = ref<Ace.Editor>(); |
| 44 | 41 | ||
| 45 | const isFocus = ref(false); | 42 | const isFocus = ref(false); |
| 46 | 43 | ||
| 47 | - const handleChange = (value: any) => { | 44 | + const handleOnChange = () => { |
| 45 | + const value = get(); | ||
| 48 | emit(EventEnum.UPDATE_VALUE, value, unref(editoreRef)); | 46 | emit(EventEnum.UPDATE_VALUE, value, unref(editoreRef)); |
| 49 | emit(EventEnum.CHANGE, value, unref(editoreRef)); | 47 | emit(EventEnum.CHANGE, value, unref(editoreRef)); |
| 50 | }; | 48 | }; |
| 51 | 49 | ||
| 52 | - const handleEmit = (event: Event, key: EventEnum) => { | ||
| 53 | - emit(key as EventEnum[keyof EventEnum], event, unref(editoreRef)); | 50 | + const handleOnBlur = (event: Event) => { |
| 51 | + isFocus.value = false; | ||
| 52 | + emit(EventEnum.BLUR, event, unref(editoreRef)); | ||
| 54 | }; | 53 | }; |
| 55 | 54 | ||
| 56 | - const getOptions = computed(() => { | ||
| 57 | - const { options } = props; | ||
| 58 | - return { | ||
| 59 | - ...options, | ||
| 60 | - onChangeText: handleChange, | ||
| 61 | - onBlur: (event: Event) => { | ||
| 62 | - isFocus.value = false; | ||
| 63 | - handleEmit(event, EventEnum.BLUR); | ||
| 64 | - }, | ||
| 65 | - onFocus: (event: Event) => { | ||
| 66 | - isFocus.value = true; | ||
| 67 | - handleEmit(event, EventEnum.FOCUS); | ||
| 68 | - }, | ||
| 69 | - } as JSONEditorOptions; | ||
| 70 | - }); | 55 | + const handleOnFocus = (event: Event) => { |
| 56 | + isFocus.value = true; | ||
| 57 | + emit(EventEnum.FOCUS, event, unref(editoreRef)); | ||
| 58 | + }; | ||
| 59 | + | ||
| 60 | + const getFormatValue = (value: Recordable | string = '') => { | ||
| 61 | + return isObject(value) ? JSON.stringify(value, null, 2) : value; | ||
| 62 | + }; | ||
| 71 | 63 | ||
| 72 | const initialize = () => { | 64 | const initialize = () => { |
| 73 | - editoreRef.value = new JSONEditor(unref(jsonEditorElRef), unref(getOptions)); | 65 | + AceEditor.config.setModuleUrl('ace/mode/json_worker', workerJsonUrl); |
| 66 | + AceEditor.config.setModuleUrl('ace/theme/github', githubTheme); | ||
| 67 | + const editor = AceEditor.edit(unref(jsonEditorElRef)!, { | ||
| 68 | + mode: 'ace/mode/json', | ||
| 69 | + }); | ||
| 70 | + editor.setTheme('ace/theme/github'); | ||
| 71 | + editor.setOptions({ | ||
| 72 | + fontSize: 14, | ||
| 73 | + }); | ||
| 74 | + | ||
| 75 | + editor.on('change', handleOnChange); | ||
| 76 | + editor.on('blur', handleOnBlur); | ||
| 77 | + editor.on('focus', handleOnFocus); | ||
| 78 | + | ||
| 79 | + editoreRef.value = editor; | ||
| 80 | + unref(editoreRef)?.setValue(getFormatValue(props.value), 1); | ||
| 81 | + unref(editoreRef)?.setReadOnly(props.disabled); | ||
| 74 | }; | 82 | }; |
| 75 | 83 | ||
| 76 | watch( | 84 | watch( |
| 77 | () => props.value, | 85 | () => props.value, |
| 78 | (target) => { | 86 | (target) => { |
| 79 | - if (unref(isFocus)) return; | ||
| 80 | - unref(editoreRef)?.setText(target || ''); | 87 | + // const position = unref(editoreRef)?.getCursorPosition(); |
| 88 | + unref(editoreRef)?.setValue(getFormatValue(target)); | ||
| 89 | + unref(editoreRef)?.clearSelection(); | ||
| 90 | + // position && unref(editoreRef)?.moveCursorToPosition(position!); | ||
| 81 | }, | 91 | }, |
| 82 | { | 92 | { |
| 83 | immediate: true, | 93 | immediate: true, |
| 84 | } | 94 | } |
| 85 | ); | 95 | ); |
| 86 | 96 | ||
| 97 | + watch( | ||
| 98 | + () => props.disabled, | ||
| 99 | + (value) => { | ||
| 100 | + unref(editoreRef)?.setReadOnly(value); | ||
| 101 | + } | ||
| 102 | + ); | ||
| 103 | + | ||
| 87 | const get = (): string => { | 104 | const get = (): string => { |
| 88 | - return unref(editoreRef)?.getText() || ''; | 105 | + return unref(editoreRef)?.getValue() || ''; |
| 89 | }; | 106 | }; |
| 90 | 107 | ||
| 91 | const set = (data: any) => { | 108 | const set = (data: any) => { |
| 92 | - return unref(editoreRef)?.set(data); | 109 | + return unref(editoreRef)?.setValue(getFormatValue(data)); |
| 93 | }; | 110 | }; |
| 94 | 111 | ||
| 95 | onMounted(() => { | 112 | onMounted(() => { |
| 96 | initialize(); | 113 | initialize(); |
| 97 | - unref(editoreRef)?.setText(props.value || ''); | 114 | + unref(editoreRef)?.setValue(getFormatValue(props.value)); |
| 98 | }); | 115 | }); |
| 99 | 116 | ||
| 100 | onUnmounted(() => { | 117 | onUnmounted(() => { |
| 118 | + unref(editoreRef)?.off('change', handleOnChange); | ||
| 119 | + unref(editoreRef)?.off('blur', handleOnBlur); | ||
| 120 | + unref(editoreRef)?.off('focus', handleOnFocus); | ||
| 101 | unref(editoreRef)?.destroy(); | 121 | unref(editoreRef)?.destroy(); |
| 122 | + unref(editoreRef)?.container.remove(); | ||
| 123 | + }); | ||
| 124 | + | ||
| 125 | + const handleFormat = () => { | ||
| 126 | + const value = get(); | ||
| 127 | + if (isString(value) && !value) return; | ||
| 128 | + unref(editoreRef)?.setValue(JSON.stringify(JSON.parse(value), null, 2)); | ||
| 129 | + unref(editoreRef)?.clearSelection(); | ||
| 130 | + }; | ||
| 131 | + | ||
| 132 | + const handleCompress = () => { | ||
| 133 | + const value = get(); | ||
| 134 | + if (isString(value) && !value) return; | ||
| 135 | + unref(editoreRef)?.setValue(JSON.stringify(JSON.parse(value))); | ||
| 136 | + unref(editoreRef)?.clearSelection(); | ||
| 137 | + }; | ||
| 138 | + | ||
| 139 | + const jsonEditorContainerElRef = ref<Nullable<HTMLDivElement>>(); | ||
| 140 | + const { isFullscreen, isSupported, toggle } = useFullscreen(jsonEditorContainerElRef); | ||
| 141 | + const handleFullScreen = () => { | ||
| 142 | + toggle(); | ||
| 143 | + }; | ||
| 144 | + | ||
| 145 | + const getHeight = computed(() => { | ||
| 146 | + const { height } = props; | ||
| 147 | + return isNumber(height) ? `${height}px` : height; | ||
| 102 | }); | 148 | }); |
| 103 | 149 | ||
| 104 | defineExpose({ | 150 | defineExpose({ |
| @@ -108,22 +154,41 @@ | @@ -108,22 +154,41 @@ | ||
| 108 | </script> | 154 | </script> |
| 109 | 155 | ||
| 110 | <template> | 156 | <template> |
| 111 | - <div class="p-2 bg-gray-200" :style="{ height: `${height}px` }"> | ||
| 112 | - <div ref="jsonEditorElRef" class="jsoneditor"></div> | 157 | + <div |
| 158 | + ref="jsonEditorContainerElRef" | ||
| 159 | + class="p-2 bg-gray-200 flex flex-col" | ||
| 160 | + :style="{ height: getHeight }" | ||
| 161 | + > | ||
| 162 | + <div class="w-full h-8 flex justify-between items-center"> | ||
| 163 | + <div> | ||
| 164 | + {{ title }} | ||
| 165 | + </div> | ||
| 166 | + <slot name="header"></slot> | ||
| 167 | + <div class="flex h-8 gap-3 justify-end items-center text-dark-500 svg:text-2xl"> | ||
| 168 | + <slot name="beforeFormat"></slot> | ||
| 169 | + <Tooltip title="整洁"> | ||
| 170 | + <Icon @click="handleFormat" class="cursor-pointer" icon="gg:format-left" /> | ||
| 171 | + </Tooltip> | ||
| 172 | + <slot name="beforeCompress"></slot> | ||
| 173 | + | ||
| 174 | + <Tooltip title="迷你"> | ||
| 175 | + <Icon @click="handleCompress" class="cursor-pointer" icon="material-symbols:compress" /> | ||
| 176 | + </Tooltip> | ||
| 177 | + <slot name="beforeFullScreen"></slot> | ||
| 178 | + | ||
| 179 | + <Tooltip title="全屏"> | ||
| 180 | + <Icon | ||
| 181 | + v-if="isSupported" | ||
| 182 | + class="cursor-pointer" | ||
| 183 | + :icon=" | ||
| 184 | + isFullscreen ? 'material-symbols:fullscreen-exit' : 'material-symbols:fullscreen' | ||
| 185 | + " | ||
| 186 | + @click="handleFullScreen" | ||
| 187 | + /> | ||
| 188 | + </Tooltip> | ||
| 189 | + <slot name="afterFullScreen"></slot> | ||
| 190 | + </div> | ||
| 191 | + </div> | ||
| 192 | + <div ref="jsonEditorElRef" class="flex-auto"></div> | ||
| 113 | </div> | 193 | </div> |
| 114 | </template> | 194 | </template> |
| 115 | - | ||
| 116 | -<style lang="less" scoped> | ||
| 117 | - .jsoneditor { | ||
| 118 | - border: none !important; | ||
| 119 | - | ||
| 120 | - :deep(.jsoneditor) { | ||
| 121 | - border: none !important; | ||
| 122 | - | ||
| 123 | - .ace-jsoneditor, | ||
| 124 | - textarea.jsoneditor-text { | ||
| 125 | - min-height: auto; | ||
| 126 | - } | ||
| 127 | - } | ||
| 128 | - } | ||
| 129 | -</style> |
| @@ -13,6 +13,7 @@ export { default as ApiRadioGroup } from './src/components/ApiRadioGroup.vue'; | @@ -13,6 +13,7 @@ export { default as ApiRadioGroup } from './src/components/ApiRadioGroup.vue'; | ||
| 13 | export { default as ApiUpload } from './src/components/ApiUpload.vue'; | 13 | export { default as ApiUpload } from './src/components/ApiUpload.vue'; |
| 14 | 14 | ||
| 15 | export { default as StructForm } from './src/externalCompns/components/StructForm/StructForm.vue'; | 15 | export { default as StructForm } from './src/externalCompns/components/StructForm/StructForm.vue'; |
| 16 | +export { default as JavaScriptFunctionEditor } from './src/components/JavaScriptFunctionEditor.vue'; | ||
| 16 | 17 | ||
| 17 | //注册自定义组件 | 18 | //注册自定义组件 |
| 18 | export { | 19 | export { |
| @@ -6,7 +6,7 @@ | @@ -6,7 +6,7 @@ | ||
| 6 | <script lang="ts" setup> | 6 | <script lang="ts" setup> |
| 7 | import { ref, watchEffect, computed, unref, watch, reactive } from 'vue'; | 7 | import { ref, watchEffect, computed, unref, watch, reactive } from 'vue'; |
| 8 | import { Select, Spin } from 'ant-design-vue'; | 8 | import { Select, Spin } from 'ant-design-vue'; |
| 9 | - import { isFunction } from '/@/utils/is'; | 9 | + import { isFunction, isNullAndUnDef } from '/@/utils/is'; |
| 10 | import { useRuleFormItem } from '/@/hooks/component/useFormItem'; | 10 | import { useRuleFormItem } from '/@/hooks/component/useFormItem'; |
| 11 | import { useAttrs } from '/@/hooks/core/useAttrs'; | 11 | import { useAttrs } from '/@/hooks/core/useAttrs'; |
| 12 | import { get, omit } from 'lodash-es'; | 12 | import { get, omit } from 'lodash-es'; |
| @@ -30,8 +30,11 @@ | @@ -30,8 +30,11 @@ | ||
| 30 | labelField?: string; | 30 | labelField?: string; |
| 31 | valueField?: string; | 31 | valueField?: string; |
| 32 | immediate?: boolean; | 32 | immediate?: boolean; |
| 33 | - pagenation?: Pagination; | 33 | + searchField?: string; |
| 34 | + pagination?: Pagination; | ||
| 34 | queryEmptyDataAgin?: boolean; | 35 | queryEmptyDataAgin?: boolean; |
| 36 | + fetchSearch?: boolean; | ||
| 37 | + filterOption?: (inputValue: string, options: Recordable) => boolean; | ||
| 35 | }>(), | 38 | }>(), |
| 36 | { | 39 | { |
| 37 | resultField: '', | 40 | resultField: '', |
| @@ -39,14 +42,15 @@ | @@ -39,14 +42,15 @@ | ||
| 39 | valueField: 'value', | 42 | valueField: 'value', |
| 40 | immediate: true, | 43 | immediate: true, |
| 41 | queryEmptyDataAgin: true, | 44 | queryEmptyDataAgin: true, |
| 42 | - pagenation: () => ({ page: 1, pageSize: 10 }), | 45 | + pagination: () => ({ page: 1, pageSize: 10 }), |
| 46 | + fetchSearch: false, | ||
| 43 | } | 47 | } |
| 44 | ); | 48 | ); |
| 45 | 49 | ||
| 46 | const OptionsItem = (_, { attrs }: { attrs: { vNode: any } }) => attrs.vNode; | 50 | const OptionsItem = (_, { attrs }: { attrs: { vNode: any } }) => attrs.vNode; |
| 47 | 51 | ||
| 48 | const options = ref<OptionsItem[]>([]); | 52 | const options = ref<OptionsItem[]>([]); |
| 49 | - const pagination = reactive(Object.assign({ total: 0, page: 1, pageSize: 10 }, props.pagenation)); | 53 | + const pagination = reactive<Record<'total' | 'page' | 'pageSize', number>>({} as any); |
| 50 | const scrollLoading = ref(false); | 54 | const scrollLoading = ref(false); |
| 51 | const lock = ref(false); | 55 | const lock = ref(false); |
| 52 | const loading = ref(false); | 56 | const loading = ref(false); |
| @@ -55,6 +59,13 @@ | @@ -55,6 +59,13 @@ | ||
| 55 | const attrs = useAttrs(); | 59 | const attrs = useAttrs(); |
| 56 | const { t } = useI18n(); | 60 | const { t } = useI18n(); |
| 57 | 61 | ||
| 62 | + const getPagination = computed(() => { | ||
| 63 | + return { | ||
| 64 | + ...props.pagination, | ||
| 65 | + ...unref(pagination), | ||
| 66 | + }; | ||
| 67 | + }); | ||
| 68 | + | ||
| 58 | // Embedded in the form, just use the hook binding to perform form verification | 69 | // Embedded in the form, just use the hook binding to perform form verification |
| 59 | const [state] = useRuleFormItem(props, 'value', 'change', emitData); | 70 | const [state] = useRuleFormItem(props, 'value', 'change', emitData); |
| 60 | 71 | ||
| @@ -86,16 +97,18 @@ | @@ -86,16 +97,18 @@ | ||
| 86 | { deep: true } | 97 | { deep: true } |
| 87 | ); | 98 | ); |
| 88 | 99 | ||
| 89 | - async function fetch() { | ||
| 90 | - const api = props.api; | 100 | + async function fetch(searchText?: string) { |
| 101 | + const { api, searchField, fetchSearch } = props; | ||
| 91 | if (!api || !isFunction(api)) return; | 102 | if (!api || !isFunction(api)) return; |
| 103 | + const isFetchSearchFlag = fetchSearch && !isNullAndUnDef(searchText) && searchField; | ||
| 92 | try { | 104 | try { |
| 93 | !unref(getOptions).length ? (loading.value = true) : (scrollLoading.value = true); | 105 | !unref(getOptions).length ? (loading.value = true) : (scrollLoading.value = true); |
| 94 | lock.value = true; | 106 | lock.value = true; |
| 95 | const { total, items } = await api({ | 107 | const { total, items } = await api({ |
| 96 | ...props.params, | 108 | ...props.params, |
| 97 | - page: pagination.page, | ||
| 98 | - pageSize: pagination.pageSize, | 109 | + page: unref(getPagination).page, |
| 110 | + pageSize: unref(getPagination).pageSize, | ||
| 111 | + ...(isFetchSearchFlag ? { [searchField!]: searchText } : {}), | ||
| 99 | }); | 112 | }); |
| 100 | 113 | ||
| 101 | pagination.total = total; | 114 | pagination.total = total; |
| @@ -105,11 +118,13 @@ | @@ -105,11 +118,13 @@ | ||
| 105 | return; | 118 | return; |
| 106 | } | 119 | } |
| 107 | if (props.resultField) { | 120 | if (props.resultField) { |
| 108 | - options.value = [...options.value, ...(get(items, props.resultField) || [])]; | 121 | + options.value = isFetchSearchFlag |
| 122 | + ? get(items, props.resultField) || [] | ||
| 123 | + : [...options.value, ...(get(items, props.resultField) || [])]; | ||
| 109 | } | 124 | } |
| 110 | emitChange(); | 125 | emitChange(); |
| 111 | } catch (error) { | 126 | } catch (error) { |
| 112 | - pagination.page = Math.ceil(unref(getOptions).length / pagination.pageSize); | 127 | + pagination.page = Math.ceil(unref(getOptions).length / unref(getPagination).pageSize); |
| 113 | console.warn(error); | 128 | console.warn(error); |
| 114 | } finally { | 129 | } finally { |
| 115 | isFirstLoad.value = false; | 130 | isFirstLoad.value = false; |
| @@ -134,17 +149,39 @@ | @@ -134,17 +149,39 @@ | ||
| 134 | emitData.value = args; | 149 | emitData.value = args; |
| 135 | } | 150 | } |
| 136 | 151 | ||
| 152 | + const sleep = async (number: number) => { | ||
| 153 | + return new Promise((resolve) => { | ||
| 154 | + setTimeout(() => { | ||
| 155 | + resolve(number); | ||
| 156 | + }, number); | ||
| 157 | + }); | ||
| 158 | + }; | ||
| 159 | + | ||
| 137 | async function handlePopupScroll(event: MouseEvent) { | 160 | async function handlePopupScroll(event: MouseEvent) { |
| 138 | const { scrollHeight, scrollTop, clientHeight } = event.target as HTMLDivElement; | 161 | const { scrollHeight, scrollTop, clientHeight } = event.target as HTMLDivElement; |
| 139 | if (scrollTop + clientHeight >= scrollHeight) { | 162 | if (scrollTop + clientHeight >= scrollHeight) { |
| 140 | - if (unref(getOptions).length < pagination.total && !unref(lock)) { | ||
| 141 | - pagination.page = pagination.page + 1; | 163 | + if (unref(getOptions).length < unref(getPagination).total && !unref(lock)) { |
| 164 | + pagination.page = unref(getPagination).page + 1; | ||
| 165 | + scrollLoading.value = true; | ||
| 166 | + await sleep(500); | ||
| 142 | await fetch(); | 167 | await fetch(); |
| 143 | } | 168 | } |
| 144 | } | 169 | } |
| 145 | } | 170 | } |
| 146 | 171 | ||
| 147 | const debounceHandlePopupScroll = useDebounceFn(handlePopupScroll, 100); | 172 | const debounceHandlePopupScroll = useDebounceFn(handlePopupScroll, 100); |
| 173 | + | ||
| 174 | + const handleFilterOption = async (inputValue: string, option: Recordable) => { | ||
| 175 | + const { filterOption, fetchSearch } = props; | ||
| 176 | + if (filterOption && isFunction(filterOption)) { | ||
| 177 | + filterOption?.(inputValue, option); | ||
| 178 | + return; | ||
| 179 | + } | ||
| 180 | + | ||
| 181 | + if (fetchSearch) { | ||
| 182 | + await fetch(inputValue); | ||
| 183 | + } | ||
| 184 | + }; | ||
| 148 | </script> | 185 | </script> |
| 149 | 186 | ||
| 150 | <template> | 187 | <template> |
| @@ -153,6 +190,8 @@ | @@ -153,6 +190,8 @@ | ||
| 153 | v-bind="attrs" | 190 | v-bind="attrs" |
| 154 | @change="handleChange" | 191 | @change="handleChange" |
| 155 | :options="getOptions" | 192 | :options="getOptions" |
| 193 | + :filterOption="handleFilterOption" | ||
| 194 | + :showSearch="true" | ||
| 156 | v-model:value="state" | 195 | v-model:value="state" |
| 157 | @popup-scroll="debounceHandlePopupScroll" | 196 | @popup-scroll="debounceHandlePopupScroll" |
| 158 | > | 197 | > |
| 1 | +<script lang="ts" setup> | ||
| 2 | + import { Icon } from '/@/components/Icon'; | ||
| 3 | + import { Tooltip } from 'ant-design-vue'; | ||
| 4 | + import { computed, onMounted, onUnmounted, ref, shallowRef, unref, watch } from 'vue'; | ||
| 5 | + import AceEditor, { Ace } from 'ace-builds'; | ||
| 6 | + import workerJavascriptUrl from 'ace-builds/src-noconflict/worker-javascript?url'; | ||
| 7 | + import githubTheme from 'ace-builds/src-noconflict/theme-github?url'; | ||
| 8 | + import 'ace-builds/src-noconflict/mode-javascript'; | ||
| 9 | + import 'ace-builds/src-noconflict/ext-searchbox'; | ||
| 10 | + import 'ace-builds/src-noconflict/ext-language_tools'; | ||
| 11 | + import 'ace-builds/src-noconflict/snippets/javascript'; | ||
| 12 | + import { useBeautify } from '/@/hooks/business/useBeautify'; | ||
| 13 | + import { useFullscreen } from '@vueuse/core'; | ||
| 14 | + import { isNumber } from '/@/utils/is'; | ||
| 15 | + | ||
| 16 | + const emit = defineEmits(['update:value', 'focus', 'blur']); | ||
| 17 | + const props = withDefaults( | ||
| 18 | + defineProps<{ | ||
| 19 | + functionName?: string; | ||
| 20 | + paramsName?: string[]; | ||
| 21 | + height?: number | string; | ||
| 22 | + value?: string; | ||
| 23 | + disabled?: boolean; | ||
| 24 | + validateStatus?: boolean; | ||
| 25 | + }>(), | ||
| 26 | + { | ||
| 27 | + functionName: 'method', | ||
| 28 | + paramsName: () => [], | ||
| 29 | + height: 200, | ||
| 30 | + value: '', | ||
| 31 | + } | ||
| 32 | + ); | ||
| 33 | + | ||
| 34 | + const getHeight = computed(() => { | ||
| 35 | + const { height } = props; | ||
| 36 | + return isNumber(height) ? `${height}px` : height; | ||
| 37 | + }); | ||
| 38 | + | ||
| 39 | + const javaScriptEditorElRef = ref<Nullable<HTMLDivElement>>(); | ||
| 40 | + const editorInstance = shallowRef<Ace.Editor>(); | ||
| 41 | + const isFocus = ref(false); | ||
| 42 | + | ||
| 43 | + const handleFocus = () => { | ||
| 44 | + isFocus.value = true; | ||
| 45 | + emit('focus', unref(editorInstance)); | ||
| 46 | + }; | ||
| 47 | + | ||
| 48 | + const handleBlur = () => { | ||
| 49 | + isFocus.value = false; | ||
| 50 | + emit('update:value', get()); | ||
| 51 | + emit('blur', unref(editorInstance)); | ||
| 52 | + }; | ||
| 53 | + | ||
| 54 | + const handleChange = () => { | ||
| 55 | + emit('update:value', get()); | ||
| 56 | + }; | ||
| 57 | + | ||
| 58 | + const initEditor = () => { | ||
| 59 | + AceEditor.config.setModuleUrl('ace/mode/javascript_worker', workerJavascriptUrl); | ||
| 60 | + AceEditor.config.setModuleUrl('ace/theme/github', githubTheme); | ||
| 61 | + const editor = AceEditor.edit(unref(javaScriptEditorElRef)!, { | ||
| 62 | + mode: 'ace/mode/javascript', | ||
| 63 | + }); | ||
| 64 | + editor.setTheme('ace/theme/github'); | ||
| 65 | + editor.setOptions({ | ||
| 66 | + fontSize: 14, | ||
| 67 | + enableBasicAutocompletion: true, | ||
| 68 | + enableSnippets: true, | ||
| 69 | + enableLiveAutocompletion: true, | ||
| 70 | + }); | ||
| 71 | + | ||
| 72 | + editor.on('focus', handleFocus); | ||
| 73 | + editor.on('blur', handleBlur); | ||
| 74 | + editor.on('change', handleChange); | ||
| 75 | + editorInstance.value = editor; | ||
| 76 | + editor.setValue(props.value); | ||
| 77 | + editor?.clearSelection(); | ||
| 78 | + unref(editorInstance)?.setReadOnly(props.disabled); | ||
| 79 | + }; | ||
| 80 | + | ||
| 81 | + const get = () => { | ||
| 82 | + return unref(editorInstance)?.getValue(); | ||
| 83 | + }; | ||
| 84 | + | ||
| 85 | + const set = (val: string, cursorPos?: number) => { | ||
| 86 | + return unref(editorInstance)?.setValue(val, cursorPos); | ||
| 87 | + }; | ||
| 88 | + | ||
| 89 | + onMounted(() => { | ||
| 90 | + initEditor(); | ||
| 91 | + }); | ||
| 92 | + | ||
| 93 | + onUnmounted(() => { | ||
| 94 | + unref(editorInstance)?.off('change', handleChange); | ||
| 95 | + unref(editorInstance)?.off('focus', handleFocus); | ||
| 96 | + unref(editorInstance)?.off('blur', handleBlur); | ||
| 97 | + unref(editorInstance)?.destroy(); | ||
| 98 | + unref(editorInstance)?.container.remove(); | ||
| 99 | + }); | ||
| 100 | + | ||
| 101 | + const { beautifyJs } = useBeautify(); | ||
| 102 | + const handleFormatCode = async () => { | ||
| 103 | + const res = await beautifyJs(get() || '', { indent_size: 4, wrap_line_length: 60 }); | ||
| 104 | + set(res || '', -1); | ||
| 105 | + }; | ||
| 106 | + | ||
| 107 | + const jsFunctionContainerElRef = ref<Nullable<HTMLDivElement>>(); | ||
| 108 | + const { isFullscreen, isSupported, toggle } = useFullscreen(jsFunctionContainerElRef); | ||
| 109 | + const handleFullScreen = () => { | ||
| 110 | + toggle(); | ||
| 111 | + }; | ||
| 112 | + | ||
| 113 | + watch( | ||
| 114 | + () => props.value, | ||
| 115 | + (value) => { | ||
| 116 | + // const position = unref(editorInstance)?.getCursorPosition(); | ||
| 117 | + if (unref(isFocus)) return; | ||
| 118 | + set(value); | ||
| 119 | + unref(editorInstance)?.clearSelection(); | ||
| 120 | + // position && unref(editorInstance)?.moveCursorToPosition(position!); | ||
| 121 | + }, | ||
| 122 | + { | ||
| 123 | + immediate: true, | ||
| 124 | + } | ||
| 125 | + ); | ||
| 126 | + | ||
| 127 | + watch( | ||
| 128 | + () => props.disabled, | ||
| 129 | + (value) => { | ||
| 130 | + unref(editorInstance)?.setReadOnly(value); | ||
| 131 | + } | ||
| 132 | + ); | ||
| 133 | + | ||
| 134 | + defineExpose({ | ||
| 135 | + get, | ||
| 136 | + set, | ||
| 137 | + }); | ||
| 138 | +</script> | ||
| 139 | + | ||
| 140 | +<template> | ||
| 141 | + <section | ||
| 142 | + ref="jsFunctionContainerElRef" | ||
| 143 | + class="p-2 bg-gray-200 flex flex-col" | ||
| 144 | + :style="{ height: getHeight }" | ||
| 145 | + > | ||
| 146 | + <head class="flex justify-between h-8 items-center"> | ||
| 147 | + <div class="font-bold"> | ||
| 148 | + <span>function</span> | ||
| 149 | + <span class="ml-1">{{ functionName }}</span> | ||
| 150 | + <span>({{ paramsName.join(',') }})</span> | ||
| 151 | + <span class="ml-1">{</span> | ||
| 152 | + </div> | ||
| 153 | + <div class="flex gap-3 items-center svg:text-2xl"> | ||
| 154 | + <slot name="beforeFormat"></slot> | ||
| 155 | + | ||
| 156 | + <Tooltip title="整洁"> | ||
| 157 | + <Icon class="cursor-pointer" icon="gg:format-left" @click="handleFormatCode" /> | ||
| 158 | + </Tooltip> | ||
| 159 | + <slot name="beforeFullScreen"></slot> | ||
| 160 | + <Tooltip title="全屏"> | ||
| 161 | + <Icon | ||
| 162 | + v-if="isSupported" | ||
| 163 | + class="cursor-pointer" | ||
| 164 | + :icon=" | ||
| 165 | + isFullscreen ? 'material-symbols:fullscreen-exit' : 'material-symbols:fullscreen' | ||
| 166 | + " | ||
| 167 | + @click="handleFullScreen" | ||
| 168 | + /> | ||
| 169 | + </Tooltip> | ||
| 170 | + <slot name="afterFullScreen"></slot> | ||
| 171 | + </div> | ||
| 172 | + </head> | ||
| 173 | + <main ref="javaScriptEditorElRef" class="flex-auto"> </main> | ||
| 174 | + <footer class="font-bold">}</footer> | ||
| 175 | + </section> | ||
| 176 | +</template> |
| @@ -130,4 +130,6 @@ export type ComponentType = | @@ -130,4 +130,6 @@ export type ComponentType = | ||
| 130 | | 'ControlGroup' | 130 | | 'ControlGroup' |
| 131 | | 'JSONEditor' | 131 | | 'JSONEditor' |
| 132 | | 'OrgTreeSelect' | 132 | | 'OrgTreeSelect' |
| 133 | - | 'ExtendDesc'; | 133 | + | 'ExtendDesc' |
| 134 | + | 'JavaScriptFunctionEditor' | ||
| 135 | + | 'JavaScriptFilterFunctionModal'; |
| @@ -19,4 +19,8 @@ export enum DictEnum { | @@ -19,4 +19,8 @@ export enum DictEnum { | ||
| 19 | DISABLE_CUSTOMER_AUTH = 'disabled_customer_auth', | 19 | DISABLE_CUSTOMER_AUTH = 'disabled_customer_auth', |
| 20 | // 寄存器数据格式 | 20 | // 寄存器数据格式 |
| 21 | REGISTER_DATA_FORMAT = 'register_data_format', | 21 | REGISTER_DATA_FORMAT = 'register_data_format', |
| 22 | + // 消息类型 规则节点 Filter message type switch | ||
| 23 | + MESSAGE_TYPES_FILTER = 'message_types_filter', | ||
| 24 | + // 实体类型 规则节点 Filter originator types switch | ||
| 25 | + ORIGINATOR_TYPES = 'originator_types', | ||
| 22 | } | 26 | } |
src/hooks/business/useBatchSettingDict.ts
0 → 100644
| 1 | +import { saveOrEditDictItem } from '/@/api/system/dict'; | ||
| 2 | +import { SysDictItem } from '/@/api/system/model/dictModel'; | ||
| 3 | + | ||
| 4 | +interface BatchSettingParamsType { | ||
| 5 | + dictId: string; | ||
| 6 | + list: any[]; | ||
| 7 | + transformText: (item: any) => any; | ||
| 8 | + transformValue: (item: any) => any; | ||
| 9 | + update?: boolean; | ||
| 10 | +} | ||
| 11 | + | ||
| 12 | +export function useBatchSettingDict() { | ||
| 13 | + const batchSetting = async (options: BatchSettingParamsType) => { | ||
| 14 | + const { dictId, list, transformText, transformValue, update = false } = options; | ||
| 15 | + if (!transformText || !transformValue) return; | ||
| 16 | + let index = 1; | ||
| 17 | + for (const item of list) { | ||
| 18 | + const params: Partial<SysDictItem> & { dictId: string } = { | ||
| 19 | + dictId, | ||
| 20 | + sort: index++, | ||
| 21 | + status: 1, | ||
| 22 | + itemText: transformText(item), | ||
| 23 | + itemValue: transformValue(item), | ||
| 24 | + }; | ||
| 25 | + | ||
| 26 | + await saveOrEditDictItem(params as SysDictItem, update); | ||
| 27 | + } | ||
| 28 | + }; | ||
| 29 | + | ||
| 30 | + const batchDelete = () => {}; | ||
| 31 | + | ||
| 32 | + return { | ||
| 33 | + batchSetting, | ||
| 34 | + batchDelete, | ||
| 35 | + }; | ||
| 36 | +} |
src/hooks/business/useBeautify.ts
0 → 100644
| 1 | +export function useBeautify() { | ||
| 2 | + let jsBeautifyModule: any; | ||
| 3 | + | ||
| 4 | + async function loadJsBeautify() { | ||
| 5 | + if (jsBeautifyModule) return jsBeautifyModule; | ||
| 6 | + jsBeautifyModule = await import('js-beautify/js/lib/beautifier'); | ||
| 7 | + return jsBeautifyModule; | ||
| 8 | + } | ||
| 9 | + | ||
| 10 | + async function beautifyJs(source: string, options?: any) { | ||
| 11 | + const module = await loadJsBeautify(); | ||
| 12 | + return module.js(source, options); | ||
| 13 | + } | ||
| 14 | + return { beautifyJs }; | ||
| 15 | +} |
src/hooks/business/useJsonParse.ts
0 → 100644
| 1 | +import { isObject, isString } from '/@/utils/is'; | ||
| 2 | + | ||
| 3 | +export function useJsonParse(value: any, defaultValue = {}) { | ||
| 4 | + let flag = false; | ||
| 5 | + try { | ||
| 6 | + value = JSON.parse(value); | ||
| 7 | + if (isObject(value) || (isString(value) && value.trim() === '')) flag = true; | ||
| 8 | + } catch (error) { | ||
| 9 | + value = defaultValue; | ||
| 10 | + } | ||
| 11 | + return { flag, value }; | ||
| 12 | +} |
| @@ -91,7 +91,7 @@ export const formSchema: FormSchema[] = [ | @@ -91,7 +91,7 @@ export const formSchema: FormSchema[] = [ | ||
| 91 | valueField: 'fileList', | 91 | valueField: 'fileList', |
| 92 | componentProps: () => { | 92 | componentProps: () => { |
| 93 | return { | 93 | return { |
| 94 | - listType: 'picture-card', | 94 | + // listType: 'picture-card', |
| 95 | maxFileLimit: 1, | 95 | maxFileLimit: 1, |
| 96 | api: async (file: File) => { | 96 | api: async (file: File) => { |
| 97 | try { | 97 | try { |
| @@ -110,6 +110,10 @@ export const formSchema: FormSchema[] = [ | @@ -110,6 +110,10 @@ export const formSchema: FormSchema[] = [ | ||
| 110 | onPreview: (fileList: FileItem) => { | 110 | onPreview: (fileList: FileItem) => { |
| 111 | createImgPreview({ imageList: [fileList.url!] }); | 111 | createImgPreview({ imageList: [fileList.url!] }); |
| 112 | }, | 112 | }, |
| 113 | + showUploadList: { | ||
| 114 | + showDownloadIcon: true, | ||
| 115 | + showRemoveIcon: true, | ||
| 116 | + }, | ||
| 113 | }; | 117 | }; |
| 114 | }, | 118 | }, |
| 115 | }, | 119 | }, |
| @@ -46,7 +46,7 @@ export const formSchema: FormSchema[] = [ | @@ -46,7 +46,7 @@ export const formSchema: FormSchema[] = [ | ||
| 46 | valueField: 'fileList', | 46 | valueField: 'fileList', |
| 47 | componentProps: () => { | 47 | componentProps: () => { |
| 48 | return { | 48 | return { |
| 49 | - listType: 'picture-card', | 49 | + // listType: 'picture-card', |
| 50 | maxFileLimit: 1, | 50 | maxFileLimit: 1, |
| 51 | api: async (file: File) => { | 51 | api: async (file: File) => { |
| 52 | try { | 52 | try { |
| @@ -62,6 +62,10 @@ export const formSchema: FormSchema[] = [ | @@ -62,6 +62,10 @@ export const formSchema: FormSchema[] = [ | ||
| 62 | return {}; | 62 | return {}; |
| 63 | } | 63 | } |
| 64 | }, | 64 | }, |
| 65 | + showUploadList: true, | ||
| 66 | + onDownload(file) { | ||
| 67 | + console.log(file); | ||
| 68 | + }, | ||
| 65 | onPreview: (fileList: FileItem) => { | 69 | onPreview: (fileList: FileItem) => { |
| 66 | createImgPreview({ imageList: [fileList.url!] }); | 70 | createImgPreview({ imageList: [fileList.url!] }); |
| 67 | }, | 71 | }, |
| @@ -23,7 +23,7 @@ | @@ -23,7 +23,7 @@ | ||
| 23 | layout: 'vertical', | 23 | layout: 'vertical', |
| 24 | }); | 24 | }); |
| 25 | 25 | ||
| 26 | - const { genForm } = useGenDynamicForm(); | 26 | + const { genForm, transformValue } = useGenDynamicForm(); |
| 27 | 27 | ||
| 28 | const keys = ref<string[]>([]); | 28 | const keys = ref<string[]>([]); |
| 29 | 29 | ||
| @@ -127,8 +127,8 @@ | @@ -127,8 +127,8 @@ | ||
| 127 | 127 | ||
| 128 | sendValue.value = await genModbusCommand(unref(modBUSForm)); | 128 | sendValue.value = await genModbusCommand(unref(modBUSForm)); |
| 129 | } else { | 129 | } else { |
| 130 | - const _value = getFieldsValue(); | ||
| 131 | - | 130 | + await validate(); |
| 131 | + const _value = transformValue(getFieldsValue()); | ||
| 132 | sendValue.value = unref(keys).reduce((prev, next) => { | 132 | sendValue.value = unref(keys).reduce((prev, next) => { |
| 133 | return { ...prev, [next]: _value[next] }; | 133 | return { ...prev, [next]: _value[next] }; |
| 134 | }, {}); | 134 | }, {}); |
| 1 | import { DataType, Specs, StructJSON } from '/@/api/device/model/modelOfMatterModel'; | 1 | import { DataType, Specs, StructJSON } from '/@/api/device/model/modelOfMatterModel'; |
| 2 | import { JSONEditor } from '/@/components/CodeEditor'; | 2 | import { JSONEditor } from '/@/components/CodeEditor'; |
| 3 | import { FormSchema, useComponentRegister } from '/@/components/Form'; | 3 | import { FormSchema, useComponentRegister } from '/@/components/Form'; |
| 4 | +import { useJsonParse } from '/@/hooks/business/useJsonParse'; | ||
| 4 | import { DataTypeEnum } from '/@/views/device/profiles/step/cpns/physical/cpns/config'; | 5 | import { DataTypeEnum } from '/@/views/device/profiles/step/cpns/physical/cpns/config'; |
| 5 | 6 | ||
| 6 | export interface BasicCreateFormParams { | 7 | export interface BasicCreateFormParams { |
| @@ -20,7 +21,6 @@ export const useGenDynamicForm = () => { | @@ -20,7 +21,6 @@ export const useGenDynamicForm = () => { | ||
| 20 | const { specs, type } = dataType; | 21 | const { specs, type } = dataType; |
| 21 | const { valueRange, step } = specs! as Partial<Specs>; | 22 | const { valueRange, step } = specs! as Partial<Specs>; |
| 22 | const { max, min } = valueRange || {}; | 23 | const { max, min } = valueRange || {}; |
| 23 | - console.log({ max, min }); | ||
| 24 | return { | 24 | return { |
| 25 | field: identifier, | 25 | field: identifier, |
| 26 | label: functionName, | 26 | label: functionName, |
| @@ -107,6 +107,19 @@ export const useGenDynamicForm = () => { | @@ -107,6 +107,19 @@ export const useGenDynamicForm = () => { | ||
| 107 | field: identifier, | 107 | field: identifier, |
| 108 | label: functionName, | 108 | label: functionName, |
| 109 | component: 'JSONEditor', | 109 | component: 'JSONEditor', |
| 110 | + valueField: 'value', | ||
| 111 | + changeEvent: 'update:value', | ||
| 112 | + rules: [ | ||
| 113 | + { | ||
| 114 | + validator: (_rule, value: any) => { | ||
| 115 | + if (value) { | ||
| 116 | + const { flag } = useJsonParse(value); | ||
| 117 | + if (!flag) return Promise.reject(`${functionName} 不是一个有效的JSON对象`); | ||
| 118 | + } | ||
| 119 | + return Promise.resolve(); | ||
| 120 | + }, | ||
| 121 | + }, | ||
| 122 | + ], | ||
| 110 | }; | 123 | }; |
| 111 | }; | 124 | }; |
| 112 | 125 | ||
| @@ -118,11 +131,14 @@ export const useGenDynamicForm = () => { | @@ -118,11 +131,14 @@ export const useGenDynamicForm = () => { | ||
| 118 | [DataTypeEnum.IS_STRUCT]: createInputJson, | 131 | [DataTypeEnum.IS_STRUCT]: createInputJson, |
| 119 | }; | 132 | }; |
| 120 | 133 | ||
| 134 | + const fieldTypeMap = new Map<string, DataTypeEnum>(); | ||
| 121 | const genForm = (schemas: StructJSON[]) => { | 135 | const genForm = (schemas: StructJSON[]) => { |
| 136 | + fieldTypeMap.clear(); | ||
| 122 | const formSchema = schemas.map((item) => { | 137 | const formSchema = schemas.map((item) => { |
| 123 | const { functionName, identifier, dataType } = item; | 138 | const { functionName, identifier, dataType } = item; |
| 124 | const { type } = dataType || {}; | 139 | const { type } = dataType || {}; |
| 125 | 140 | ||
| 141 | + fieldTypeMap.set(identifier!, dataType!.type); | ||
| 126 | const method = schemaMethod[type!]; | 142 | const method = schemaMethod[type!]; |
| 127 | 143 | ||
| 128 | const formSchema = method?.({ | 144 | const formSchema = method?.({ |
| @@ -137,5 +153,21 @@ export const useGenDynamicForm = () => { | @@ -137,5 +153,21 @@ export const useGenDynamicForm = () => { | ||
| 137 | return formSchema.filter(Boolean); | 153 | return formSchema.filter(Boolean); |
| 138 | }; | 154 | }; |
| 139 | 155 | ||
| 140 | - return { genForm }; | 156 | + const transformValue = (value: Recordable) => { |
| 157 | + return Object.keys(value || {}).reduce((prev, next) => { | ||
| 158 | + const dataType = fieldTypeMap.get(next)!; | ||
| 159 | + | ||
| 160 | + let itemValue = value[next]; | ||
| 161 | + if (dataType === DataTypeEnum.IS_STRUCT) { | ||
| 162 | + const { value } = useJsonParse(itemValue); | ||
| 163 | + itemValue = value; | ||
| 164 | + } | ||
| 165 | + return { | ||
| 166 | + ...prev, | ||
| 167 | + [next]: itemValue, | ||
| 168 | + }; | ||
| 169 | + }, {} as Recordable); | ||
| 170 | + }; | ||
| 171 | + | ||
| 172 | + return { genForm, transformValue }; | ||
| 141 | }; | 173 | }; |
src/views/rule/designer/enum/form.ts
0 → 100644
| 1 | +/** | ||
| 2 | + * @description 方向 | ||
| 3 | + */ | ||
| 4 | +export enum DirectionEnum { | ||
| 5 | + FROM = 'FROM', | ||
| 6 | + TO = 'TO', | ||
| 7 | +} | ||
| 8 | + | ||
| 9 | +export enum DirectionNameEnum { | ||
| 10 | + FROM = '从', | ||
| 11 | + TO = '到', | ||
| 12 | +} | ||
| 13 | +/** | ||
| 14 | + * @description 关联类型 | ||
| 15 | + */ | ||
| 16 | +export enum RelationTypeEnum { | ||
| 17 | + CONTAINS = 'contains', | ||
| 18 | + MANAGES = 'manages', | ||
| 19 | +} | ||
| 20 | + | ||
| 21 | +/** | ||
| 22 | + * @description 类型 | ||
| 23 | + */ | ||
| 24 | +export enum EntityTypeEnum { | ||
| 25 | + DEVICE = 'DEVICE', | ||
| 26 | + ASSET = 'ASSET', | ||
| 27 | + ENTITY_VIEW = 'ENTITY_VIEW', | ||
| 28 | + TENANT = 'TENANT', | ||
| 29 | + CUSTOMER = 'CUSTOMER', | ||
| 30 | + USER = 'USER', | ||
| 31 | + DASHBOARD = 'DASHBOARD', | ||
| 32 | + EDGE = 'EDGE', | ||
| 33 | +} | ||
| 34 | + | ||
| 35 | +export enum EntityTypeNameEnum { | ||
| 36 | + DEVICE = '设备', | ||
| 37 | + ASSET = '资产', | ||
| 38 | + ENTITY_VIEW = '实体视图', | ||
| 39 | + TENANT = '租户', | ||
| 40 | + CUSTOMER = '客户', | ||
| 41 | + USER = '用户', | ||
| 42 | + DASHBOARD = '仪表板', | ||
| 43 | + EDGE = 'Edge', | ||
| 44 | +} | ||
| 45 | + | ||
| 46 | +/** | ||
| 47 | + * @description Filter Perimeter type | ||
| 48 | + */ | ||
| 49 | +export enum PerimeterTypeEnum { | ||
| 50 | + POLYGON = 'POLYGON', | ||
| 51 | + CIRCLE = 'CIRCLE', | ||
| 52 | +} | ||
| 53 | + | ||
| 54 | +/** | ||
| 55 | + * @description Filter Range util | ||
| 56 | + */ | ||
| 57 | +export enum RangeUtilEnum { | ||
| 58 | + METER = 'METER', | ||
| 59 | + KILOMETER = 'KILOMETER', | ||
| 60 | + FOOT = 'FOOT', | ||
| 61 | + MILE = 'MILE', | ||
| 62 | + NAUTICAL_MILE = 'NAUTICAL_MILE', | ||
| 63 | +} | ||
| 64 | + | ||
| 65 | +export enum RangeUtilNameEnum { | ||
| 66 | + METER = 'Meter', | ||
| 67 | + KILOMETER = 'Kilometer', | ||
| 68 | + FOOT = 'Foot', | ||
| 69 | + MILE = 'Mile', | ||
| 70 | + NAUTICAL_MILE = 'Nautical mile', | ||
| 71 | +} | ||
| 72 | + | ||
| 73 | +export enum MessageTypesFilterEnum { | ||
| 74 | + POST_ATTRIBUTES = 'POST_ATTRIBUTES', | ||
| 75 | + POST_TELEMETRY = 'POST_TELEMETRY', | ||
| 76 | + RPC_REQUEST_FROM_DEVICE = 'RPC_REQUEST_FROM_DEVICE', | ||
| 77 | + RPC_REQUEST_TO_DEVICE = 'RPC_REQUEST_TO_DEVICE', | ||
| 78 | + ACTIVITY_EVENT = 'ACTIVITY_EVENT', | ||
| 79 | + INACTIVITY_EVENT = 'INACTIVITY_EVENT', | ||
| 80 | + CONNECT_EVENT = 'CONNECT_EVENT', | ||
| 81 | + DISCONNECT_EVENT = 'DISCONNECT_EVENT', | ||
| 82 | + ENTITY_CREATED = 'ENTITY_CREATED', | ||
| 83 | + ENTITY_UPDATED = 'ENTITY_UPDATED', | ||
| 84 | + ENTITY_DELETED = 'ENTITY_DELETED', | ||
| 85 | + ENTITY_ASSIGNED = 'ENTITY_ASSIGNED', | ||
| 86 | + ENTITY_UNASSIGNED = 'ENTITY_UNASSIGNED', | ||
| 87 | + ATTRIBUTES_UPDATED = 'ATTRIBUTES_UPDATED', | ||
| 88 | + ATTRIBUTES_DELETED = 'ATTRIBUTES_DELETED', | ||
| 89 | + TIMESERIES_UPDATED = 'TIMESERIES_UPDATED', | ||
| 90 | + TIMESERIES_DELETED = 'TIMESERIES_DELETED', | ||
| 91 | + RPC_QUEUED = 'RPC_QUEUED', | ||
| 92 | + RPC_DELIVERED = 'RPC_DELIVERED', | ||
| 93 | + RPC_SUCCESSFUL = 'RPC_SUCCESSFUL', | ||
| 94 | + RPC_TIMEOUT = 'RPC_TIMEOUT', | ||
| 95 | + RPC_FAILED = 'RPC_FAILED', | ||
| 96 | +} | ||
| 97 | + | ||
| 98 | +export enum MessageTypesFilterNameEnum { | ||
| 99 | + POST_ATTRIBUTES = 'Post attributes', | ||
| 100 | + POST_TELEMETRY = 'Post telemetry', | ||
| 101 | + RPC_REQUEST_FROM_DEVICE = 'RPC Request from Device', | ||
| 102 | + RPC_REQUEST_TO_DEVICE = 'RPC Request to Device', | ||
| 103 | + ACTIVITY_EVENT = 'Activity Event', | ||
| 104 | + INACTIVITY_EVENT = 'Inactivity Event', | ||
| 105 | + CONNECT_EVENT = 'Connect Event', | ||
| 106 | + DISCONNECT_EVENT = 'Disconnect Event', | ||
| 107 | + ENTITY_CREATED = 'Entity Created', | ||
| 108 | + ENTITY_UPDATED = 'Entity Updated', | ||
| 109 | + ENTITY_DELETED = 'Entity Deleted', | ||
| 110 | + ENTITY_ASSIGNED = 'Entity Assigned', | ||
| 111 | + ENTITY_UNASSIGNED = 'Entity Unassigned', | ||
| 112 | + ATTRIBUTES_UPDATED = 'Attributes Updated', | ||
| 113 | + ATTRIBUTES_DELETED = 'Attributes Deleted', | ||
| 114 | + TIMESERIES_UPDATED = 'Timeseries Updated', | ||
| 115 | + TIMESERIES_DELETED = 'Timeseries Deleted', | ||
| 116 | + RPC_QUEUED = 'RPC Queued', | ||
| 117 | + RPC_DELIVERED = 'RPC Delivered', | ||
| 118 | + RPC_SUCCESSFUL = 'RPC Successful', | ||
| 119 | + RPC_TIMEOUT = 'RPC Timeout', | ||
| 120 | + RPC_FAILED = 'RPC Failed', | ||
| 121 | +} |
| @@ -5,16 +5,85 @@ export enum FetchNodeComFlagTypeENum { | @@ -5,16 +5,85 @@ export enum FetchNodeComFlagTypeENum { | ||
| 5 | 5 | ||
| 6 | export enum NodeBindDataFieldEnum { | 6 | export enum NodeBindDataFieldEnum { |
| 7 | NAME = 'name', | 7 | NAME = 'name', |
| 8 | - ALARM_STATUS_LIST = 'alarmStatusList', | ||
| 9 | DESCRIPTION = 'description', | 8 | DESCRIPTION = 'description', |
| 10 | DEBUG_MODE = 'debugMode', | 9 | DEBUG_MODE = 'debugMode', |
| 10 | + | ||
| 11 | + // Filter Check Alarm status | ||
| 12 | + ALARM_STATUS_LIST = 'alarmStatusList', | ||
| 13 | + | ||
| 14 | + // Filter Check Existence Fields | ||
| 15 | + MESSAGE_NAMES = 'messageNames', | ||
| 16 | + METADATA_NAMES = 'metadataNames', | ||
| 17 | + CHECK_ALL_KEYS = 'checkAllKeys', | ||
| 18 | + | ||
| 19 | + // Filter Check Relation | ||
| 20 | + DIRECTION = 'direction', | ||
| 21 | + CHECK_FOR_SINGLE_ENTITY = 'checkForSingleEntity', | ||
| 22 | + ENTITY_TYPE = 'entityType', | ||
| 23 | + RELEATION_TYPE = 'relationType', | ||
| 24 | + ENTITY_ID = 'entityId', | ||
| 25 | + | ||
| 26 | + // Filter Gps geofencing filter | ||
| 27 | + LATITUDE_KEY_NAME = 'latitudeKeyName', | ||
| 28 | + LONGITUDE_KEY_NAME = 'longitudeKeyName', | ||
| 29 | + PERIMETER_TYPE = 'perimeterType', | ||
| 30 | + FETCH_PERIMETER_INFO_FROM_MESSAGE_METADATA = 'fetchPerimeterInfoFromMessageMetadata', | ||
| 31 | + PERIMETER_KEY_NAME = 'perimeterKeyName', | ||
| 32 | + CENTER_LATITUDE = 'centerLatitude', | ||
| 33 | + CENTER_LONGITUDE = 'centerLongitude', | ||
| 34 | + RANGE = 'range', | ||
| 35 | + RANGE_UNIT = 'rangeUnit', | ||
| 36 | + POLYGONS_DEFINITION = 'polygonsDefinition', | ||
| 37 | + | ||
| 38 | + // Filter Message Type | ||
| 39 | + MESSAGE_TYPES = 'messageTypes', | ||
| 40 | + | ||
| 41 | + // Filter Originator Type | ||
| 42 | + ORIGINATOR_TYPES = 'originatorTypes', | ||
| 43 | + | ||
| 44 | + // Filter Script | ||
| 45 | + JS_SCRIPT = 'jsScript', | ||
| 11 | } | 46 | } |
| 12 | 47 | ||
| 13 | export enum NodeBindDataFieldNameEnum { | 48 | export enum NodeBindDataFieldNameEnum { |
| 14 | NAME = '名称', | 49 | NAME = '名称', |
| 15 | - ALARM_STATUS_LIST = 'Alarm status filter', | ||
| 16 | DESCRIPTION = '说明', | 50 | DESCRIPTION = '说明', |
| 17 | DEBUG_MODE = '调试模式', | 51 | DEBUG_MODE = '调试模式', |
| 52 | + | ||
| 53 | + // Filter Check Alarm status | ||
| 54 | + ALARM_STATUS_LIST = 'Alarm status filter', | ||
| 55 | + | ||
| 56 | + // Filter Check Existence Fields | ||
| 57 | + MESSAGE_NAMES = '消息数据', | ||
| 58 | + METADATA_NAMES = '消息元数据', | ||
| 59 | + CHECK_ALL_KEYS = '检查所有选择的键是否都存在', | ||
| 60 | + | ||
| 61 | + // Filter Check Relation | ||
| 62 | + DIRECTION = '方向', | ||
| 63 | + CHECK_FOR_SINGLE_ENTITY = 'Check relation to specific entity', | ||
| 64 | + ENTITY_TYPE = '类型', | ||
| 65 | + RELEATION_TYPE = '关联类型', | ||
| 66 | + | ||
| 67 | + // Filter Gps geofencing filter | ||
| 68 | + LATITUDE_KEY_NAME = 'Latitude Key Name', | ||
| 69 | + LONGITUDE_KEY_NAME = 'Longitude Key Name', | ||
| 70 | + PERIMETER_TYPE = 'Perimeter Type', | ||
| 71 | + FETCH_PERIMETER_INFO_FROM_MESSAGE_METADATA = 'Fetch perimeter information from message metadata', | ||
| 72 | + CENTER_LATITUDE = 'Center latitude', | ||
| 73 | + CENTER_LONGITUDE = 'Center longitude', | ||
| 74 | + RANGE = 'Range', | ||
| 75 | + RANGE_UNIT = 'Range unit', | ||
| 76 | + PERIMETER_KEY_NAME = 'Perimeter key name', | ||
| 77 | + POLYGONS_DEFINITION = 'Polygons definition', | ||
| 78 | + | ||
| 79 | + // Filter Message Type | ||
| 80 | + MESSAGE_TYPES = 'Message Types Filter', | ||
| 81 | + | ||
| 82 | + // Filter Originator Type | ||
| 83 | + ORIGINATOR_TYPES = 'Originator types filter', | ||
| 84 | + | ||
| 85 | + // Filter Script | ||
| 86 | + JS_SCRIPT = 'Filter', | ||
| 18 | } | 87 | } |
| 19 | 88 | ||
| 20 | export enum EdgeBindDataFieldEnum { | 89 | export enum EdgeBindDataFieldEnum { |
| @@ -53,7 +53,7 @@ export function useBasicDataTransform() { | @@ -53,7 +53,7 @@ export function useBasicDataTransform() { | ||
| 53 | 53 | ||
| 54 | nodes.forEach((item, index) => indexMap.set(index, item)); | 54 | nodes.forEach((item, index) => indexMap.set(index, item)); |
| 55 | 55 | ||
| 56 | - connections.forEach((item) => { | 56 | + (connections || []).forEach((item) => { |
| 57 | const { fromIndex, toIndex, type } = item; | 57 | const { fromIndex, toIndex, type } = item; |
| 58 | const key = `${fromIndex}${SEPARATOR}${toIndex}`; | 58 | const key = `${fromIndex}${SEPARATOR}${toIndex}`; |
| 59 | if (!groupByConnections.has(key)) groupByConnections.set(key, []); | 59 | if (!groupByConnections.has(key)) groupByConnections.set(key, []); |
| @@ -124,7 +124,7 @@ export function useSaveAndRedo() { | @@ -124,7 +124,7 @@ export function useSaveAndRedo() { | ||
| 124 | firstNodeIndex, | 124 | firstNodeIndex, |
| 125 | ruleChainId: { | 125 | ruleChainId: { |
| 126 | entityType: 'RULE_CHAIN', | 126 | entityType: 'RULE_CHAIN', |
| 127 | - id: 'd6ddea70-25da-11ee-bc6b-47e715464e68', | 127 | + id: '992ddda0-3d97-11ee-8b46-0dfb0900ab17', |
| 128 | }, | 128 | }, |
| 129 | }); | 129 | }); |
| 130 | 130 | ||
| @@ -140,7 +140,7 @@ export function useSaveAndRedo() { | @@ -140,7 +140,7 @@ export function useSaveAndRedo() { | ||
| 140 | try { | 140 | try { |
| 141 | loading.value = true; | 141 | loading.value = true; |
| 142 | 142 | ||
| 143 | - const id = 'd6ddea70-25da-11ee-bc6b-47e715464e68'; | 143 | + const id = '992ddda0-3d97-11ee-8b46-0dfb0900ab17'; |
| 144 | 144 | ||
| 145 | const data = await getRuleChainData(id); | 145 | const data = await getRuleChainData(id); |
| 146 | 146 |
| @@ -3,8 +3,27 @@ import { FormSchema } from '/@/components/Form'; | @@ -3,8 +3,27 @@ import { FormSchema } from '/@/components/Form'; | ||
| 3 | 3 | ||
| 4 | export const formSchemas: FormSchema[] = [ | 4 | export const formSchemas: FormSchema[] = [ |
| 5 | { | 5 | { |
| 6 | - field: NodeBindDataFieldEnum.NAME, | ||
| 7 | - component: 'Input', | ||
| 8 | - label: NodeBindDataFieldNameEnum.NAME, | 6 | + field: NodeBindDataFieldEnum.MESSAGE_NAMES, |
| 7 | + component: 'Select', | ||
| 8 | + label: NodeBindDataFieldNameEnum.MESSAGE_NAMES, | ||
| 9 | + componentProps: { | ||
| 10 | + mode: 'tags', | ||
| 11 | + getPopupContainer: () => document.body, | ||
| 12 | + }, | ||
| 13 | + }, | ||
| 14 | + { | ||
| 15 | + field: NodeBindDataFieldEnum.METADATA_NAMES, | ||
| 16 | + component: 'Select', | ||
| 17 | + label: NodeBindDataFieldNameEnum.METADATA_NAMES, | ||
| 18 | + componentProps: { | ||
| 19 | + mode: 'tags', | ||
| 20 | + getPopupContainer: () => document.body, | ||
| 21 | + }, | ||
| 22 | + }, | ||
| 23 | + { | ||
| 24 | + field: NodeBindDataFieldEnum.CHECK_ALL_KEYS, | ||
| 25 | + component: 'Checkbox', | ||
| 26 | + label: NodeBindDataFieldNameEnum.CHECK_ALL_KEYS, | ||
| 27 | + labelWidth: 200, | ||
| 9 | }, | 28 | }, |
| 10 | ]; | 29 | ]; |
| 1 | +import { | ||
| 2 | + DirectionEnum, | ||
| 3 | + DirectionNameEnum, | ||
| 4 | + EntityTypeEnum, | ||
| 5 | + EntityTypeNameEnum, | ||
| 6 | + RelationTypeEnum, | ||
| 7 | +} from '../../../enum/form'; | ||
| 1 | import { NodeBindDataFieldEnum, NodeBindDataFieldNameEnum } from '../../../enum/node'; | 8 | import { NodeBindDataFieldEnum, NodeBindDataFieldNameEnum } from '../../../enum/node'; |
| 9 | +import { deviceProfilePage } from '/@/api/device/deviceManager'; | ||
| 2 | import { FormSchema } from '/@/components/Form'; | 10 | import { FormSchema } from '/@/components/Form'; |
| 3 | 11 | ||
| 4 | export const formSchemas: FormSchema[] = [ | 12 | export const formSchemas: FormSchema[] = [ |
| @@ -7,4 +15,64 @@ export const formSchemas: FormSchema[] = [ | @@ -7,4 +15,64 @@ export const formSchemas: FormSchema[] = [ | ||
| 7 | component: 'Input', | 15 | component: 'Input', |
| 8 | label: NodeBindDataFieldNameEnum.NAME, | 16 | label: NodeBindDataFieldNameEnum.NAME, |
| 9 | }, | 17 | }, |
| 18 | + { | ||
| 19 | + field: NodeBindDataFieldEnum.CHECK_FOR_SINGLE_ENTITY, | ||
| 20 | + component: 'Checkbox', | ||
| 21 | + label: NodeBindDataFieldNameEnum.CHECK_FOR_SINGLE_ENTITY, | ||
| 22 | + labelWidth: 220, | ||
| 23 | + }, | ||
| 24 | + { | ||
| 25 | + field: NodeBindDataFieldEnum.DIRECTION, | ||
| 26 | + component: 'Select', | ||
| 27 | + label: NodeBindDataFieldEnum.DIRECTION, | ||
| 28 | + componentProps: { | ||
| 29 | + options: [ | ||
| 30 | + { label: DirectionNameEnum.FROM, value: DirectionEnum.FROM }, | ||
| 31 | + { label: DirectionNameEnum.TO, value: DirectionEnum.TO }, | ||
| 32 | + ], | ||
| 33 | + }, | ||
| 34 | + }, | ||
| 35 | + { | ||
| 36 | + field: NodeBindDataFieldEnum.ENTITY_TYPE, | ||
| 37 | + component: 'Select', | ||
| 38 | + label: NodeBindDataFieldNameEnum.ENTITY_TYPE, | ||
| 39 | + colProps: { span: 8 }, | ||
| 40 | + componentProps: { | ||
| 41 | + options: Object.keys(EntityTypeEnum).map((key) => ({ | ||
| 42 | + label: EntityTypeNameEnum[key], | ||
| 43 | + value: key, | ||
| 44 | + })), | ||
| 45 | + }, | ||
| 46 | + }, | ||
| 47 | + { | ||
| 48 | + field: NodeBindDataFieldEnum.ENTITY_ID, | ||
| 49 | + component: 'ApiSelectScrollLoad', | ||
| 50 | + label: ' ', | ||
| 51 | + colProps: { span: 16 }, | ||
| 52 | + show: ({ model }) => model[NodeBindDataFieldEnum.ENTITY_TYPE], | ||
| 53 | + componentProps: ({ formModel }) => { | ||
| 54 | + const entityType = formModel[NodeBindDataFieldEnum.ENTITY_TYPE]; | ||
| 55 | + | ||
| 56 | + return { | ||
| 57 | + showSearch: true, | ||
| 58 | + api: deviceProfilePage, | ||
| 59 | + resultField: 'items', | ||
| 60 | + labelField: 'name', | ||
| 61 | + valueField: 'id', | ||
| 62 | + placeholder: `请选择${EntityTypeNameEnum[entityType]}`, | ||
| 63 | + }; | ||
| 64 | + }, | ||
| 65 | + }, | ||
| 66 | + { | ||
| 67 | + field: NodeBindDataFieldEnum.RELEATION_TYPE, | ||
| 68 | + component: 'Select', | ||
| 69 | + label: NodeBindDataFieldNameEnum.RELEATION_TYPE, | ||
| 70 | + defaultValue: RelationTypeEnum.CONTAINS, | ||
| 71 | + componentProps: { | ||
| 72 | + options: [ | ||
| 73 | + { label: RelationTypeEnum.CONTAINS, value: RelationTypeEnum.CONTAINS }, | ||
| 74 | + { label: RelationTypeEnum.MANAGES, value: RelationTypeEnum.MANAGES }, | ||
| 75 | + ], | ||
| 76 | + }, | ||
| 77 | + }, | ||
| 10 | ]; | 78 | ]; |
| 1 | +import { PerimeterTypeEnum, RangeUtilEnum, RangeUtilNameEnum } from '../../../enum/form'; | ||
| 1 | import { NodeBindDataFieldEnum, NodeBindDataFieldNameEnum } from '../../../enum/node'; | 2 | import { NodeBindDataFieldEnum, NodeBindDataFieldNameEnum } from '../../../enum/node'; |
| 2 | import { FormSchema } from '/@/components/Form'; | 3 | import { FormSchema } from '/@/components/Form'; |
| 3 | 4 | ||
| 4 | export const formSchemas: FormSchema[] = [ | 5 | export const formSchemas: FormSchema[] = [ |
| 5 | { | 6 | { |
| 6 | - field: NodeBindDataFieldEnum.NAME, | 7 | + field: NodeBindDataFieldEnum.LATITUDE_KEY_NAME, |
| 7 | component: 'Input', | 8 | component: 'Input', |
| 8 | - label: NodeBindDataFieldNameEnum.NAME, | 9 | + label: NodeBindDataFieldNameEnum.LATITUDE_KEY_NAME, |
| 10 | + }, | ||
| 11 | + { | ||
| 12 | + field: NodeBindDataFieldEnum.LONGITUDE_KEY_NAME, | ||
| 13 | + component: 'Input', | ||
| 14 | + label: NodeBindDataFieldNameEnum.LONGITUDE_KEY_NAME, | ||
| 15 | + }, | ||
| 16 | + { | ||
| 17 | + field: NodeBindDataFieldEnum.PERIMETER_TYPE, | ||
| 18 | + component: 'Select', | ||
| 19 | + label: NodeBindDataFieldNameEnum.PERIMETER_TYPE, | ||
| 20 | + componentProps: { | ||
| 21 | + options: Object.keys(PerimeterTypeEnum).map((value) => ({ label: value, value })), | ||
| 22 | + }, | ||
| 23 | + }, | ||
| 24 | + { | ||
| 25 | + field: NodeBindDataFieldEnum.FETCH_PERIMETER_INFO_FROM_MESSAGE_METADATA, | ||
| 26 | + component: 'Checkbox', | ||
| 27 | + label: NodeBindDataFieldNameEnum.FETCH_PERIMETER_INFO_FROM_MESSAGE_METADATA, | ||
| 28 | + labelWidth: 350, | ||
| 29 | + }, | ||
| 30 | + { | ||
| 31 | + field: NodeBindDataFieldEnum.PERIMETER_KEY_NAME, | ||
| 32 | + component: 'Input', | ||
| 33 | + label: NodeBindDataFieldNameEnum.PERIMETER_KEY_NAME, | ||
| 34 | + show: ({ model }) => model[NodeBindDataFieldEnum.FETCH_PERIMETER_INFO_FROM_MESSAGE_METADATA], | ||
| 35 | + }, | ||
| 36 | + { | ||
| 37 | + field: NodeBindDataFieldEnum.POLYGONS_DEFINITION, | ||
| 38 | + component: 'Input', | ||
| 39 | + label: NodeBindDataFieldNameEnum.POLYGONS_DEFINITION, | ||
| 40 | + helpMessage: | ||
| 41 | + 'Please, use the following format for manual definition of polygon: [[lat1,lon1],[lat2,lon2], ... ,[latN,lonN]].', | ||
| 42 | + show: ({ model }) => | ||
| 43 | + !model[NodeBindDataFieldEnum.FETCH_PERIMETER_INFO_FROM_MESSAGE_METADATA] && | ||
| 44 | + model[NodeBindDataFieldEnum.PERIMETER_TYPE] === PerimeterTypeEnum.POLYGON, | ||
| 45 | + }, | ||
| 46 | + { | ||
| 47 | + field: NodeBindDataFieldEnum.CENTER_LATITUDE, | ||
| 48 | + component: 'InputNumber', | ||
| 49 | + label: NodeBindDataFieldNameEnum.CENTER_LATITUDE, | ||
| 50 | + colProps: { span: 12 }, | ||
| 51 | + show: ({ model }) => | ||
| 52 | + model[NodeBindDataFieldEnum.PERIMETER_TYPE] === PerimeterTypeEnum.CIRCLE && | ||
| 53 | + !model[NodeBindDataFieldEnum.FETCH_PERIMETER_INFO_FROM_MESSAGE_METADATA], | ||
| 54 | + }, | ||
| 55 | + { | ||
| 56 | + field: NodeBindDataFieldEnum.CENTER_LONGITUDE, | ||
| 57 | + component: 'InputNumber', | ||
| 58 | + label: NodeBindDataFieldNameEnum.CENTER_LONGITUDE, | ||
| 59 | + colProps: { span: 12 }, | ||
| 60 | + show: ({ model }) => | ||
| 61 | + model[NodeBindDataFieldEnum.PERIMETER_TYPE] === PerimeterTypeEnum.CIRCLE && | ||
| 62 | + !model[NodeBindDataFieldEnum.FETCH_PERIMETER_INFO_FROM_MESSAGE_METADATA], | ||
| 63 | + }, | ||
| 64 | + { | ||
| 65 | + field: NodeBindDataFieldEnum.RANGE, | ||
| 66 | + component: 'InputNumber', | ||
| 67 | + label: NodeBindDataFieldNameEnum.RANGE, | ||
| 68 | + colProps: { span: 12 }, | ||
| 69 | + show: ({ model }) => | ||
| 70 | + model[NodeBindDataFieldEnum.PERIMETER_TYPE] === PerimeterTypeEnum.CIRCLE && | ||
| 71 | + !model[NodeBindDataFieldEnum.FETCH_PERIMETER_INFO_FROM_MESSAGE_METADATA], | ||
| 72 | + }, | ||
| 73 | + { | ||
| 74 | + field: NodeBindDataFieldEnum.RANGE_UNIT, | ||
| 75 | + component: 'Select', | ||
| 76 | + label: NodeBindDataFieldNameEnum.RANGE_UNIT, | ||
| 77 | + colProps: { span: 12 }, | ||
| 78 | + show: ({ model }) => | ||
| 79 | + model[NodeBindDataFieldEnum.PERIMETER_TYPE] === PerimeterTypeEnum.CIRCLE && | ||
| 80 | + !model[NodeBindDataFieldEnum.FETCH_PERIMETER_INFO_FROM_MESSAGE_METADATA], | ||
| 81 | + componentProps: { | ||
| 82 | + options: Object.keys(RangeUtilEnum).map((value) => ({ | ||
| 83 | + label: RangeUtilNameEnum[value], | ||
| 84 | + value, | ||
| 85 | + })), | ||
| 86 | + }, | ||
| 9 | }, | 87 | }, |
| 10 | ]; | 88 | ]; |
| 1 | import { NodeBindDataFieldEnum, NodeBindDataFieldNameEnum } from '../../../enum/node'; | 1 | import { NodeBindDataFieldEnum, NodeBindDataFieldNameEnum } from '../../../enum/node'; |
| 2 | +import { findDictItemByCode } from '/@/api/system/dict'; | ||
| 2 | import { FormSchema } from '/@/components/Form'; | 3 | import { FormSchema } from '/@/components/Form'; |
| 4 | +import { DictEnum } from '/@/enums/dictEnum'; | ||
| 3 | 5 | ||
| 4 | export const formSchemas: FormSchema[] = [ | 6 | export const formSchemas: FormSchema[] = [ |
| 5 | { | 7 | { |
| 6 | - field: NodeBindDataFieldEnum.NAME, | ||
| 7 | - component: 'Input', | ||
| 8 | - label: NodeBindDataFieldNameEnum.NAME, | 8 | + field: NodeBindDataFieldEnum.MESSAGE_TYPES, |
| 9 | + component: 'ApiSelect', | ||
| 10 | + label: NodeBindDataFieldNameEnum.MESSAGE_TYPES, | ||
| 11 | + componentProps: { | ||
| 12 | + api: findDictItemByCode, | ||
| 13 | + params: { | ||
| 14 | + dictCode: DictEnum.MESSAGE_TYPES_FILTER, | ||
| 15 | + }, | ||
| 16 | + mode: 'multiple', | ||
| 17 | + labelField: 'itemText', | ||
| 18 | + valueField: 'itemValue', | ||
| 19 | + getPopupContainer: () => document.body, | ||
| 20 | + }, | ||
| 9 | }, | 21 | }, |
| 10 | ]; | 22 | ]; |
| 1 | -import { NodeBindDataFieldEnum, NodeBindDataFieldNameEnum } from '../../../enum/node'; | ||
| 2 | import { FormSchema } from '/@/components/Form'; | 1 | import { FormSchema } from '/@/components/Form'; |
| 3 | 2 | ||
| 4 | -export const formSchemas: FormSchema[] = [ | ||
| 5 | - { | ||
| 6 | - field: NodeBindDataFieldEnum.NAME, | ||
| 7 | - component: 'Input', | ||
| 8 | - label: NodeBindDataFieldNameEnum.NAME, | ||
| 9 | - }, | ||
| 10 | -]; | 3 | +export const formSchemas: FormSchema[] = []; |
| 1 | import { NodeBindDataFieldEnum, NodeBindDataFieldNameEnum } from '../../../enum/node'; | 1 | import { NodeBindDataFieldEnum, NodeBindDataFieldNameEnum } from '../../../enum/node'; |
| 2 | +import { findDictItemByCode } from '/@/api/system/dict'; | ||
| 2 | import { FormSchema } from '/@/components/Form'; | 3 | import { FormSchema } from '/@/components/Form'; |
| 4 | +import { DictEnum } from '/@/enums/dictEnum'; | ||
| 3 | 5 | ||
| 4 | export const formSchemas: FormSchema[] = [ | 6 | export const formSchemas: FormSchema[] = [ |
| 5 | { | 7 | { |
| 6 | - field: NodeBindDataFieldEnum.NAME, | ||
| 7 | - component: 'Input', | ||
| 8 | - label: NodeBindDataFieldNameEnum.NAME, | 8 | + field: NodeBindDataFieldEnum.ORIGINATOR_TYPES, |
| 9 | + component: 'ApiSelect', | ||
| 10 | + label: NodeBindDataFieldNameEnum.ORIGINATOR_TYPES, | ||
| 11 | + componentProps: { | ||
| 12 | + api: findDictItemByCode, | ||
| 13 | + params: { | ||
| 14 | + dictCode: DictEnum.ORIGINATOR_TYPES, | ||
| 15 | + }, | ||
| 16 | + mode: 'multiple', | ||
| 17 | + labelField: 'itemText', | ||
| 18 | + valueField: 'itemValue', | ||
| 19 | + getPopupContainer: () => document.body, | ||
| 20 | + }, | ||
| 9 | }, | 21 | }, |
| 10 | ]; | 22 | ]; |
| 1 | -import { NodeBindDataFieldEnum, NodeBindDataFieldNameEnum } from '../../../enum/node'; | ||
| 2 | import { FormSchema } from '/@/components/Form'; | 1 | import { FormSchema } from '/@/components/Form'; |
| 3 | 2 | ||
| 4 | -export const formSchemas: FormSchema[] = [ | ||
| 5 | - { | ||
| 6 | - field: NodeBindDataFieldEnum.NAME, | ||
| 7 | - component: 'Input', | ||
| 8 | - label: NodeBindDataFieldNameEnum.NAME, | ||
| 9 | - }, | ||
| 10 | -]; | 3 | +export const formSchemas: FormSchema[] = []; |
| 1 | -import { NodeBindDataFieldEnum, NodeBindDataFieldNameEnum } from '../../../enum/node'; | ||
| 2 | import { FormSchema } from '/@/components/Form'; | 1 | import { FormSchema } from '/@/components/Form'; |
| 2 | +import { isObject } from '/@/utils/is'; | ||
| 3 | + | ||
| 4 | +export const validateJSON = (value: string): { value: Recordable | string; flag: boolean } => { | ||
| 5 | + let flag = false; | ||
| 6 | + try { | ||
| 7 | + value = JSON.parse(value); | ||
| 8 | + if (isObject(value)) flag = true; | ||
| 9 | + } catch (error) {} | ||
| 10 | + return { value, flag }; | ||
| 11 | +}; | ||
| 3 | 12 | ||
| 4 | export const formSchemas: FormSchema[] = [ | 13 | export const formSchemas: FormSchema[] = [ |
| 5 | { | 14 | { |
| 6 | - field: NodeBindDataFieldEnum.NAME, | ||
| 7 | - component: 'Input', | ||
| 8 | - label: NodeBindDataFieldNameEnum.NAME, | 15 | + field: 'value', |
| 16 | + component: 'JSONEditor', | ||
| 17 | + label: '配置', | ||
| 18 | + valueField: 'value', | ||
| 19 | + changeEvent: 'update:value', | ||
| 20 | + rules: [ | ||
| 21 | + { | ||
| 22 | + required: true, | ||
| 23 | + validator: (_rule, value) => { | ||
| 24 | + const { flag } = validateJSON(value); | ||
| 25 | + return flag ? Promise.resolve() : Promise.reject('配置项不是一个JSON对象'); | ||
| 26 | + }, | ||
| 27 | + }, | ||
| 28 | + ], | ||
| 29 | + componentProps: { | ||
| 30 | + height: 300, | ||
| 31 | + }, | ||
| 9 | }, | 32 | }, |
| 10 | ]; | 33 | ]; |
| 1 | <script lang="ts" setup> | 1 | <script lang="ts" setup> |
| 2 | import type { CreateModalDefineExposeType } from '../../../types'; | 2 | import type { CreateModalDefineExposeType } from '../../../types'; |
| 3 | import { BasicForm, useForm } from '/@/components/Form'; | 3 | import { BasicForm, useForm } from '/@/components/Form'; |
| 4 | - import { formSchemas } from './create.config'; | 4 | + import { formSchemas, validateJSON } from './create.config'; |
| 5 | import { NodeData } from '../../../types/node'; | 5 | import { NodeData } from '../../../types/node'; |
| 6 | 6 | ||
| 7 | defineProps<{ | 7 | defineProps<{ |
| @@ -16,12 +16,13 @@ | @@ -16,12 +16,13 @@ | ||
| 16 | const getValue: CreateModalDefineExposeType['getFieldsValue'] = async () => { | 16 | const getValue: CreateModalDefineExposeType['getFieldsValue'] = async () => { |
| 17 | await validate(); | 17 | await validate(); |
| 18 | const value = getFieldsValue() || {}; | 18 | const value = getFieldsValue() || {}; |
| 19 | - return value; | 19 | + const { value: data } = validateJSON(value?.value); |
| 20 | + return data as Recordable; | ||
| 20 | }; | 21 | }; |
| 21 | 22 | ||
| 22 | const setValue: CreateModalDefineExposeType['setFieldsValue'] = (value) => { | 23 | const setValue: CreateModalDefineExposeType['setFieldsValue'] = (value) => { |
| 23 | resetFields(); | 24 | resetFields(); |
| 24 | - setFieldsValue(value); | 25 | + setFieldsValue({ value: JSON.stringify(value, null, 2) }); |
| 25 | }; | 26 | }; |
| 26 | 27 | ||
| 27 | defineExpose({ | 28 | defineExpose({ |
| 1 | +<script lang="ts" setup> | ||
| 2 | + import { BasicModal, useModalInner } from '/@/components/Modal'; | ||
| 3 | + import ScriptFilterTest from './ScriptFilterTest.vue'; | ||
| 4 | + import { MessageTypesFilterEnum } from '../../../enum/form'; | ||
| 5 | + import { ref } from 'vue'; | ||
| 6 | + import { JavaScriptFunctionEditor } from '/@/components/Form'; | ||
| 7 | + | ||
| 8 | + defineProps<{ | ||
| 9 | + visible?: boolean; | ||
| 10 | + javaScriptEditorProps?: InstanceType<typeof JavaScriptFunctionEditor>['$props']; | ||
| 11 | + }>(); | ||
| 12 | + | ||
| 13 | + const emit = defineEmits(['save', 'register']); | ||
| 14 | + | ||
| 15 | + interface SaveValueType { | ||
| 16 | + msg: Recordable; | ||
| 17 | + metadata: Recordable; | ||
| 18 | + msgType: MessageTypesFilterEnum; | ||
| 19 | + javascriptFunction: string; | ||
| 20 | + } | ||
| 21 | + | ||
| 22 | + const value = ref(''); | ||
| 23 | + | ||
| 24 | + const [register, { closeModal }] = useModalInner((data: ModalParamsType<string>) => { | ||
| 25 | + value.value = data.record; | ||
| 26 | + }); | ||
| 27 | + | ||
| 28 | + const handleSaveScript = (value: SaveValueType) => { | ||
| 29 | + emit('save', value); | ||
| 30 | + closeModal(); | ||
| 31 | + }; | ||
| 32 | +</script> | ||
| 33 | + | ||
| 34 | +<template> | ||
| 35 | + <BasicModal | ||
| 36 | + wrapClassName="scriptTestModal" | ||
| 37 | + @register="register" | ||
| 38 | + :visible="visible" | ||
| 39 | + title="测试脚本功能" | ||
| 40 | + defaultFullscreen | ||
| 41 | + :showCancelBtn="false" | ||
| 42 | + :showOkBtn="false" | ||
| 43 | + :footer="null" | ||
| 44 | + > | ||
| 45 | + <ScriptFilterTest | ||
| 46 | + :javaScriptEditorProps="javaScriptEditorProps" | ||
| 47 | + :value="value" | ||
| 48 | + @cancel="closeModal()" | ||
| 49 | + @save="handleSaveScript" | ||
| 50 | + /> | ||
| 51 | + </BasicModal> | ||
| 52 | +</template> |
| 1 | +<script lang="ts" setup> | ||
| 2 | + import { Icon } from '/@/components/Icon'; | ||
| 3 | + import { Tooltip } from 'ant-design-vue'; | ||
| 4 | + import { ref } from 'vue'; | ||
| 5 | + | ||
| 6 | + const visible = ref(false); | ||
| 7 | + const handleOpenHelpMessage = () => { | ||
| 8 | + visible.value = true; | ||
| 9 | + }; | ||
| 10 | +</script> | ||
| 11 | + | ||
| 12 | +<template> | ||
| 13 | + <Tooltip title="点击获取帮助"> | ||
| 14 | + <Icon | ||
| 15 | + icon="material-symbols:help-outline" | ||
| 16 | + class="cursor-pointer svg:text-xl" | ||
| 17 | + @click="handleOpenHelpMessage" | ||
| 18 | + /> | ||
| 19 | + </Tooltip> | ||
| 20 | +</template> |
| 1 | +<script setup lang="ts"> | ||
| 2 | + import { JavaScriptFunctionEditor } from '/@/components/Form'; | ||
| 3 | + import { Button } from 'ant-design-vue'; | ||
| 4 | + import FilterModal from './FilterModal.vue'; | ||
| 5 | + import { useModal } from '/@/components/Modal'; | ||
| 6 | + import { DataActionModeEnum } from '/@/enums/toolEnum'; | ||
| 7 | + import { computed } from 'vue'; | ||
| 8 | + | ||
| 9 | + const props = withDefaults( | ||
| 10 | + defineProps<{ | ||
| 11 | + value?: string; | ||
| 12 | + buttonName?: string; | ||
| 13 | + javaScriptEditorProps?: InstanceType<typeof JavaScriptFunctionEditor>['$props']; | ||
| 14 | + }>(), | ||
| 15 | + { | ||
| 16 | + buttonName: 'Test Filter Function', | ||
| 17 | + javaScriptEditorProps: () => ({ | ||
| 18 | + functionName: 'Filter', | ||
| 19 | + paramsName: ['msg', 'metadata', 'msgType'], | ||
| 20 | + }), | ||
| 21 | + } | ||
| 22 | + ); | ||
| 23 | + | ||
| 24 | + const emit = defineEmits(['update:value']); | ||
| 25 | + const [register, { openModal }] = useModal(); | ||
| 26 | + | ||
| 27 | + const handleOpen = () => { | ||
| 28 | + openModal(true, { | ||
| 29 | + record: props.value, | ||
| 30 | + mode: DataActionModeEnum.CREATE, | ||
| 31 | + } as ModalParamsType<string>); | ||
| 32 | + }; | ||
| 33 | + | ||
| 34 | + const getValue = computed<string>({ | ||
| 35 | + get() { | ||
| 36 | + return props.value || ''; | ||
| 37 | + }, | ||
| 38 | + set(value) { | ||
| 39 | + emit('update:value', value); | ||
| 40 | + }, | ||
| 41 | + }); | ||
| 42 | + | ||
| 43 | + const handleSave = (value: Record<'javascriptFunction', string>) => { | ||
| 44 | + emit('update:value', value.javascriptFunction); | ||
| 45 | + }; | ||
| 46 | +</script> | ||
| 47 | + | ||
| 48 | +<template> | ||
| 49 | + <section> | ||
| 50 | + <JavaScriptFunctionEditor v-model:value="getValue" v-bind="javaScriptEditorProps" /> | ||
| 51 | + <Button class="mt-4" type="primary" @click="handleOpen">{{ buttonName }}</Button> | ||
| 52 | + <FilterModal | ||
| 53 | + :javaScriptEditorProps="javaScriptEditorProps" | ||
| 54 | + @register="register" | ||
| 55 | + @save="handleSave" | ||
| 56 | + /> | ||
| 57 | + </section> | ||
| 58 | +</template> |
| 1 | +<script lang="ts" setup> | ||
| 2 | + import { Card, Select, Tag, Form, Button, Input } from 'ant-design-vue'; | ||
| 3 | + import { ref, unref, watch } from 'vue'; | ||
| 4 | + import { MessageTypesFilterEnum, MessageTypesFilterNameEnum } from '../../../enum/form'; | ||
| 5 | + import { JSONEditor } from '/@/components/CodeEditor'; | ||
| 6 | + import { buildShortUUID } from '/@/utils/uuid'; | ||
| 7 | + import { Icon } from '/@/components/Icon'; | ||
| 8 | + import { JavaScriptFunctionEditor } from '/@/components/Form'; | ||
| 9 | + import { useJsonParse } from '/@/hooks/business/useJsonParse'; | ||
| 10 | + import { useMessage } from '/@/hooks/web/useMessage'; | ||
| 11 | + import { isNullOrUnDef } from '/@/utils/is'; | ||
| 12 | + | ||
| 13 | + interface MetaDataItemType { | ||
| 14 | + label: string; | ||
| 15 | + value: string; | ||
| 16 | + uuid: string; | ||
| 17 | + } | ||
| 18 | + | ||
| 19 | + interface Value { | ||
| 20 | + msg: Recordable; | ||
| 21 | + metadata: Recordable; | ||
| 22 | + msgType: MessageTypesFilterEnum; | ||
| 23 | + javascriptFunction: string; | ||
| 24 | + } | ||
| 25 | + | ||
| 26 | + const props = withDefaults( | ||
| 27 | + defineProps<{ | ||
| 28 | + value?: string; | ||
| 29 | + javaScriptEditorProps?: InstanceType<typeof JavaScriptFunctionEditor>['$props']; | ||
| 30 | + }>(), | ||
| 31 | + { | ||
| 32 | + javaScriptEditorProps: () => ({ | ||
| 33 | + functionName: 'Filter', | ||
| 34 | + paramsName: ['msg', 'metadata', 'msgType'], | ||
| 35 | + }), | ||
| 36 | + } | ||
| 37 | + ); | ||
| 38 | + | ||
| 39 | + const emit = defineEmits<{ | ||
| 40 | + (eventName: 'cancel'): void; | ||
| 41 | + (eventName: 'test', value: Value): void; | ||
| 42 | + (eventName: 'save', value: Value): void; | ||
| 43 | + }>(); | ||
| 44 | + | ||
| 45 | + const messageType = ref(MessageTypesFilterEnum.POST_TELEMETRY); | ||
| 46 | + const messageTypeOptions = Object.keys(MessageTypesFilterEnum).map((value) => ({ | ||
| 47 | + label: MessageTypesFilterNameEnum[value], | ||
| 48 | + value, | ||
| 49 | + })); | ||
| 50 | + | ||
| 51 | + const messageContent = ref({ | ||
| 52 | + temperature: 22.4, | ||
| 53 | + humidity: 78, | ||
| 54 | + }); | ||
| 55 | + | ||
| 56 | + const scriptContent = ref('return msg.temperature > 20;'); | ||
| 57 | + | ||
| 58 | + const outputContent = ref(''); | ||
| 59 | + | ||
| 60 | + const metaData = ref<MetaDataItemType[]>([ | ||
| 61 | + { uuid: buildShortUUID(), label: 'deviceName', value: 'Test Device' }, | ||
| 62 | + { uuid: buildShortUUID(), label: 'deviceType', value: 'default' }, | ||
| 63 | + { uuid: buildShortUUID(), label: 'ts', value: Date.now().toString() }, | ||
| 64 | + ]); | ||
| 65 | + | ||
| 66 | + const handleAddMetadata = () => { | ||
| 67 | + metaData.value.push({ uuid: buildShortUUID(), label: '', value: '' }); | ||
| 68 | + }; | ||
| 69 | + | ||
| 70 | + const handleRemove = (data: MetaDataItemType) => { | ||
| 71 | + const index = unref(metaData).findIndex((item) => item.uuid === data.uuid); | ||
| 72 | + ~index && unref(metaData).splice(index, 1); | ||
| 73 | + }; | ||
| 74 | + | ||
| 75 | + const { createMessage } = useMessage(); | ||
| 76 | + const handleValidate = () => { | ||
| 77 | + const message = unref(messageContent); | ||
| 78 | + const metadata = unref(metaData); | ||
| 79 | + const { flag } = useJsonParse(message); | ||
| 80 | + | ||
| 81 | + if (!flag) { | ||
| 82 | + createMessage.warning('消息不是一个有效的JSON对象'); | ||
| 83 | + return false; | ||
| 84 | + } | ||
| 85 | + | ||
| 86 | + for (const item of metadata) { | ||
| 87 | + const { label, value } = item; | ||
| 88 | + if (isNullOrUnDef(label) || isNullOrUnDef(value)) { | ||
| 89 | + createMessage.warning('元数据的键名和键值为必填项'); | ||
| 90 | + return false; | ||
| 91 | + } | ||
| 92 | + } | ||
| 93 | + return true; | ||
| 94 | + }; | ||
| 95 | + | ||
| 96 | + const getValue = (): Value => { | ||
| 97 | + const msg = unref(messageContent); | ||
| 98 | + const msgType = unref(messageType); | ||
| 99 | + const metadata = unref(metaData).reduce( | ||
| 100 | + (prev, next) => ({ ...prev, [next.label]: next.value }), | ||
| 101 | + {} as Recordable | ||
| 102 | + ); | ||
| 103 | + const javascriptFunction = unref(scriptContent); | ||
| 104 | + return { msg, msgType, metadata, javascriptFunction }; | ||
| 105 | + }; | ||
| 106 | + | ||
| 107 | + const handleTestScript = () => { | ||
| 108 | + const flag = handleValidate(); | ||
| 109 | + | ||
| 110 | + flag && emit('test', getValue()); | ||
| 111 | + }; | ||
| 112 | + | ||
| 113 | + const handleSave = () => { | ||
| 114 | + const flag = handleValidate(); | ||
| 115 | + flag && emit('save', getValue()); | ||
| 116 | + }; | ||
| 117 | + | ||
| 118 | + watch( | ||
| 119 | + () => props.value, | ||
| 120 | + (value) => { | ||
| 121 | + scriptContent.value = value || ''; | ||
| 122 | + } | ||
| 123 | + ); | ||
| 124 | +</script> | ||
| 125 | + | ||
| 126 | +<template> | ||
| 127 | + <section class="script-test-container w-full h-full flex flex-col"> | ||
| 128 | + <Form class="flex-auto grid grid-cols-2 grid-rows-2 bg-gray-100 gap-2"> | ||
| 129 | + <main> | ||
| 130 | + <Card class="w-full h-full"> | ||
| 131 | + <div class="w-full h-full flex flex-col"> | ||
| 132 | + <div class="flex justify-between items-center"> | ||
| 133 | + <div class="flex flex-col"> | ||
| 134 | + <Form.Item label="消息类型" labelAlign="left" :colon="false"> | ||
| 135 | + <Select | ||
| 136 | + v-model:value="messageType" | ||
| 137 | + :options="messageTypeOptions" | ||
| 138 | + :bordered="false" | ||
| 139 | + class="min-w-60 border-b-gray-400 border-b" | ||
| 140 | + /> | ||
| 141 | + </Form.Item> | ||
| 142 | + </div> | ||
| 143 | + <Tag color="blue"> 消息 </Tag> | ||
| 144 | + </div> | ||
| 145 | + <JSONEditor v-model:value="messageContent" title="消息" class="mt-4 flex-auto" /> | ||
| 146 | + </div> | ||
| 147 | + </Card> | ||
| 148 | + </main> | ||
| 149 | + <main> | ||
| 150 | + <Card class="w-full h-full"> | ||
| 151 | + <div class="flex flex-col"> | ||
| 152 | + <div class="flex justify-between"> | ||
| 153 | + <span class="text-gray-400">元数据</span> | ||
| 154 | + <Tag color="blue">元数据</Tag> | ||
| 155 | + </div> | ||
| 156 | + <div class="mt-4 overflow-y-auto"> | ||
| 157 | + <div v-for="item in metaData" :key="item.uuid" class="flex mt-2 gap-2 items-end"> | ||
| 158 | + <Input v-model:value="item.label" placeholder="请输入键名" /> | ||
| 159 | + <Input v-model:value="item.value" placeholder="请输入键值" /> | ||
| 160 | + <Icon | ||
| 161 | + icon="material-symbols:close" | ||
| 162 | + class="svg:text-2xl cursor-pointer" | ||
| 163 | + @click="handleRemove(item)" | ||
| 164 | + /> | ||
| 165 | + </div> | ||
| 166 | + </div> | ||
| 167 | + <div class="mt-4"> | ||
| 168 | + <Button type="primary" @click="handleAddMetadata">添加</Button> | ||
| 169 | + </div> | ||
| 170 | + </div> | ||
| 171 | + </Card> | ||
| 172 | + </main> | ||
| 173 | + <main> | ||
| 174 | + <Card class="w-full h-full"> | ||
| 175 | + <JavaScriptFunctionEditor | ||
| 176 | + v-model:value="scriptContent" | ||
| 177 | + v-bind="javaScriptEditorProps" | ||
| 178 | + height="100%" | ||
| 179 | + > | ||
| 180 | + <template #beforeFormat> | ||
| 181 | + <Tag color="blue" class="!mr-0">{{ javaScriptEditorProps.functionName }}</Tag> | ||
| 182 | + </template> | ||
| 183 | + <template #beforeFullScreen> | ||
| 184 | + <Icon icon="material-symbols:help-outline" class="cursor-pointer svg:text-xl" /> | ||
| 185 | + </template> | ||
| 186 | + </JavaScriptFunctionEditor> | ||
| 187 | + </Card> | ||
| 188 | + </main> | ||
| 189 | + <main> | ||
| 190 | + <Card class="w-full h-full"> | ||
| 191 | + <JSONEditor | ||
| 192 | + v-model:value="outputContent" | ||
| 193 | + title="输出" | ||
| 194 | + class="flex-auto" | ||
| 195 | + height="100%" | ||
| 196 | + :disabled="true" | ||
| 197 | + > | ||
| 198 | + <template #beforeFormat> | ||
| 199 | + <Tag color="blue" class="!mr-0">输出</Tag> | ||
| 200 | + </template> | ||
| 201 | + </JSONEditor> | ||
| 202 | + </Card> | ||
| 203 | + </main> | ||
| 204 | + </Form> | ||
| 205 | + <div class="h-12 flex items-center justify-between px-4 bg-light-50"> | ||
| 206 | + <Button type="primary" @click="handleTestScript">测试</Button> | ||
| 207 | + <div class="flex gap-4"> | ||
| 208 | + <Button @click="emit('cancel')">取消</Button> | ||
| 209 | + <Button type="primary" @click="handleSave">保存</Button> | ||
| 210 | + </div> | ||
| 211 | + </div> | ||
| 212 | + </section> | ||
| 213 | +</template> | ||
| 214 | + | ||
| 215 | +<style lang="less" scoped> | ||
| 216 | + .script-test-container { | ||
| 217 | + :deep(.ant-card-body) { | ||
| 218 | + @apply w-full h-full p-2; | ||
| 219 | + | ||
| 220 | + .ant-form-item { | ||
| 221 | + label { | ||
| 222 | + @apply text-xs pl-3 text-gray-400; | ||
| 223 | + } | ||
| 224 | + } | ||
| 225 | + | ||
| 226 | + .ant-input { | ||
| 227 | + @apply !border-transparent !border-b !border-b-dark-300; | ||
| 228 | + | ||
| 229 | + &:focus { | ||
| 230 | + @apply !shadow-none; | ||
| 231 | + } | ||
| 232 | + } | ||
| 233 | + } | ||
| 234 | + } | ||
| 235 | +</style> |
| 1 | +import { h } from 'vue'; | ||
| 1 | import { NodeBindDataFieldEnum, NodeBindDataFieldNameEnum } from '../../../enum/node'; | 2 | import { NodeBindDataFieldEnum, NodeBindDataFieldNameEnum } from '../../../enum/node'; |
| 2 | -import { FormSchema } from '/@/components/Form'; | 3 | +import { FormSchema, useComponentRegister } from '/@/components/Form'; |
| 4 | +import HelpMessage from './HelpMessage.vue'; | ||
| 5 | +import JavaScriptFilterFunctionModal from './JavaScriptFilterFunctionModal.vue'; | ||
| 6 | + | ||
| 7 | +useComponentRegister('JavaScriptFilterFunctionModal', JavaScriptFilterFunctionModal); | ||
| 3 | 8 | ||
| 4 | export const formSchemas: FormSchema[] = [ | 9 | export const formSchemas: FormSchema[] = [ |
| 5 | { | 10 | { |
| 6 | - field: NodeBindDataFieldEnum.NAME, | ||
| 7 | - component: 'Input', | ||
| 8 | - label: NodeBindDataFieldNameEnum.NAME, | 11 | + field: NodeBindDataFieldEnum.JS_SCRIPT, |
| 12 | + component: 'JavaScriptFilterFunctionModal', | ||
| 13 | + label: NodeBindDataFieldNameEnum.JS_SCRIPT, | ||
| 14 | + changeEvent: 'update:value', | ||
| 15 | + valueField: 'value', | ||
| 16 | + renderComponentContent: () => { | ||
| 17 | + return { | ||
| 18 | + beforeFullScreen: () => h(HelpMessage), | ||
| 19 | + }; | ||
| 20 | + }, | ||
| 9 | }, | 21 | }, |
| 10 | ]; | 22 | ]; |
| 1 | import { NodeBindDataFieldEnum, NodeBindDataFieldNameEnum } from '../../../enum/node'; | 1 | import { NodeBindDataFieldEnum, NodeBindDataFieldNameEnum } from '../../../enum/node'; |
| 2 | -import { FormSchema } from '/@/components/Form'; | 2 | +import { FormSchema, useComponentRegister } from '/@/components/Form'; |
| 3 | +import JavaScriptFilterFunctionModal from '../Script/JavaScriptFilterFunctionModal.vue'; | ||
| 3 | 4 | ||
| 5 | +useComponentRegister('JavaScriptFilterFunctionModal', JavaScriptFilterFunctionModal); | ||
| 4 | export const formSchemas: FormSchema[] = [ | 6 | export const formSchemas: FormSchema[] = [ |
| 5 | { | 7 | { |
| 6 | - field: NodeBindDataFieldEnum.NAME, | ||
| 7 | - component: 'Input', | ||
| 8 | - label: NodeBindDataFieldNameEnum.NAME, | 8 | + field: NodeBindDataFieldEnum.JS_SCRIPT, |
| 9 | + component: 'JavaScriptFilterFunctionModal', | ||
| 10 | + label: NodeBindDataFieldNameEnum.JS_SCRIPT, | ||
| 11 | + changeEvent: 'update:value', | ||
| 12 | + valueField: 'value', | ||
| 13 | + componentProps: { | ||
| 14 | + buttonName: 'Test Switch Function', | ||
| 15 | + javaScriptEditorProps: { | ||
| 16 | + functionName: 'Switch', | ||
| 17 | + paramsName: ['msg', 'metadata', 'mstType'], | ||
| 18 | + }, | ||
| 19 | + }, | ||
| 20 | + renderComponentContent: () => { | ||
| 21 | + return { | ||
| 22 | + // beforeFullScreen: () => h(HelpMessage), | ||
| 23 | + }; | ||
| 24 | + }, | ||
| 9 | }, | 25 | }, |
| 10 | ]; | 26 | ]; |
| @@ -27,8 +27,8 @@ export const FilterComponents: NodeItemConfigType[] = [ | @@ -27,8 +27,8 @@ export const FilterComponents: NodeItemConfigType[] = [ | ||
| 27 | GpsGeofencingFilterConfig, | 27 | GpsGeofencingFilterConfig, |
| 28 | MessageTypeConfig, | 28 | MessageTypeConfig, |
| 29 | MessageTypeSwitchConfig, | 29 | MessageTypeSwitchConfig, |
| 30 | - OriginatorTypeSwitchConfig, | ||
| 31 | OriginatorTypeConfig, | 30 | OriginatorTypeConfig, |
| 31 | + OriginatorTypeSwitchConfig, | ||
| 32 | SceneReactConfig, | 32 | SceneReactConfig, |
| 33 | ScriptConfig, | 33 | ScriptConfig, |
| 34 | SwitchConfig, | 34 | SwitchConfig, |
| @@ -75,6 +75,7 @@ | @@ -75,6 +75,7 @@ | ||
| 75 | <section v-if="shadowComponent" class="w-full h-full"> | 75 | <section v-if="shadowComponent" class="w-full h-full"> |
| 76 | <BasicForm @register="topFormRegister" /> | 76 | <BasicForm @register="topFormRegister" /> |
| 77 | <component | 77 | <component |
| 78 | + class="rule-node-form" | ||
| 78 | :is="shadowComponent" | 79 | :is="shadowComponent" |
| 79 | ref="createComponentEl" | 80 | ref="createComponentEl" |
| 80 | :key="getComponentKey" | 81 | :key="getComponentKey" |
| @@ -87,3 +88,12 @@ | @@ -87,3 +88,12 @@ | ||
| 87 | </Spin> | 88 | </Spin> |
| 88 | </BasicModal> | 89 | </BasicModal> |
| 89 | </template> | 90 | </template> |
| 91 | + | ||
| 92 | +<style lang="less" scoped> | ||
| 93 | + .rule-node-form { | ||
| 94 | + :deep(.ant-input-number) { | ||
| 95 | + width: 100%; | ||
| 96 | + min-width: none; | ||
| 97 | + } | ||
| 98 | + } | ||
| 99 | +</style> |
| @@ -10,6 +10,6 @@ | @@ -10,6 +10,6 @@ | ||
| 10 | <p class="text-gray-600 italic"> | 10 | <p class="text-gray-600 italic"> |
| 11 | {{ nodeData?.config?.configurationDescriptor.nodeDefinition.description }}</p | 11 | {{ nodeData?.config?.configurationDescriptor.nodeDefinition.description }}</p |
| 12 | > | 12 | > |
| 13 | - <p>{{ nodeData?.config?.configurationDescriptor.nodeDefinition.details }}</p> | 13 | + <p v-html="nodeData?.config?.configurationDescriptor.nodeDefinition.details"> </p> |
| 14 | </section> | 14 | </section> |
| 15 | </template> | 15 | </template> |
| @@ -99,6 +99,7 @@ | @@ -99,6 +99,7 @@ | ||
| 99 | <section v-if="shadowComponent" class="w-full h-full"> | 99 | <section v-if="shadowComponent" class="w-full h-full"> |
| 100 | <BasicForm @register="topFormRegister" @vue:mounted="handleTopFormMounted" /> | 100 | <BasicForm @register="topFormRegister" @vue:mounted="handleTopFormMounted" /> |
| 101 | <component | 101 | <component |
| 102 | + class="rule-node-form" | ||
| 102 | :is="shadowComponent" | 103 | :is="shadowComponent" |
| 103 | ref="createComponentEl" | 104 | ref="createComponentEl" |
| 104 | :config="nodeData" | 105 | :config="nodeData" |
| @@ -119,3 +120,12 @@ | @@ -119,3 +120,12 @@ | ||
| 119 | </Tabs> | 120 | </Tabs> |
| 120 | </BasicDrawer> | 121 | </BasicDrawer> |
| 121 | </template> | 122 | </template> |
| 123 | + | ||
| 124 | +<style lang="less" scoped> | ||
| 125 | + .rule-node-form { | ||
| 126 | + :deep(.ant-input-number) { | ||
| 127 | + width: 100%; | ||
| 128 | + min-width: none; | ||
| 129 | + } | ||
| 130 | + } | ||
| 131 | +</style> |
| @@ -136,7 +136,6 @@ export const tenantFormSchema: FormSchema[] = [ | @@ -136,7 +136,6 @@ export const tenantFormSchema: FormSchema[] = [ | ||
| 136 | } | 136 | } |
| 137 | return { items, total }; | 137 | return { items, total }; |
| 138 | }, | 138 | }, |
| 139 | - showSearch: true, | ||
| 140 | labelField: 'name', | 139 | labelField: 'name', |
| 141 | valueField: 'id.id', | 140 | valueField: 'id.id', |
| 142 | filterOption: (inputValue: string, options: Record<'label' | 'value', string>) => { | 141 | filterOption: (inputValue: string, options: Record<'label' | 'value', string>) => { |
| @@ -49,3 +49,10 @@ export interface PaginationResult<T = Recordable> { | @@ -49,3 +49,10 @@ export interface PaginationResult<T = Recordable> { | ||
| 49 | items: T[]; | 49 | items: T[]; |
| 50 | total: number; | 50 | total: number; |
| 51 | } | 51 | } |
| 52 | + | ||
| 53 | +export interface TBPaginationResult<T = Recordable> { | ||
| 54 | + data: T[]; | ||
| 55 | + hasNext: boolean; | ||
| 56 | + totalElements: number; | ||
| 57 | + totalPages: number; | ||
| 58 | +} |