Showing
42 changed files
with
1388 additions
and
132 deletions
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 | 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 | 12 | enum EventEnum { |
| 11 | 13 | UPDATE_VALUE = 'update:value', |
| ... | ... | @@ -16,89 +18,133 @@ |
| 16 | 18 | |
| 17 | 19 | const props = withDefaults( |
| 18 | 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 | 27 | height: 150, |
| 31 | 28 | } |
| 32 | 29 | ); |
| 33 | 30 | |
| 34 | 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 | 38 | const jsonEditorElRef = ref<Nullable<any>>(); |
| 42 | 39 | |
| 43 | - const editoreRef = ref<JSONEditor>(); | |
| 40 | + const editoreRef = ref<Ace.Editor>(); | |
| 44 | 41 | |
| 45 | 42 | const isFocus = ref(false); |
| 46 | 43 | |
| 47 | - const handleChange = (value: any) => { | |
| 44 | + const handleOnChange = () => { | |
| 45 | + const value = get(); | |
| 48 | 46 | emit(EventEnum.UPDATE_VALUE, value, unref(editoreRef)); |
| 49 | 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 | 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 | 84 | watch( |
| 77 | 85 | () => props.value, |
| 78 | 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 | 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 | 104 | const get = (): string => { |
| 88 | - return unref(editoreRef)?.getText() || ''; | |
| 105 | + return unref(editoreRef)?.getValue() || ''; | |
| 89 | 106 | }; |
| 90 | 107 | |
| 91 | 108 | const set = (data: any) => { |
| 92 | - return unref(editoreRef)?.set(data); | |
| 109 | + return unref(editoreRef)?.setValue(getFormatValue(data)); | |
| 93 | 110 | }; |
| 94 | 111 | |
| 95 | 112 | onMounted(() => { |
| 96 | 113 | initialize(); |
| 97 | - unref(editoreRef)?.setText(props.value || ''); | |
| 114 | + unref(editoreRef)?.setValue(getFormatValue(props.value)); | |
| 98 | 115 | }); |
| 99 | 116 | |
| 100 | 117 | onUnmounted(() => { |
| 118 | + unref(editoreRef)?.off('change', handleOnChange); | |
| 119 | + unref(editoreRef)?.off('blur', handleOnBlur); | |
| 120 | + unref(editoreRef)?.off('focus', handleOnFocus); | |
| 101 | 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 | 150 | defineExpose({ |
| ... | ... | @@ -108,22 +154,41 @@ |
| 108 | 154 | </script> |
| 109 | 155 | |
| 110 | 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 | 193 | </div> |
| 114 | 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 | 13 | export { default as ApiUpload } from './src/components/ApiUpload.vue'; |
| 14 | 14 | |
| 15 | 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 | 19 | export { | ... | ... |
| ... | ... | @@ -6,7 +6,7 @@ |
| 6 | 6 | <script lang="ts" setup> |
| 7 | 7 | import { ref, watchEffect, computed, unref, watch, reactive } from 'vue'; |
| 8 | 8 | import { Select, Spin } from 'ant-design-vue'; |
| 9 | - import { isFunction } from '/@/utils/is'; | |
| 9 | + import { isFunction, isNullAndUnDef } from '/@/utils/is'; | |
| 10 | 10 | import { useRuleFormItem } from '/@/hooks/component/useFormItem'; |
| 11 | 11 | import { useAttrs } from '/@/hooks/core/useAttrs'; |
| 12 | 12 | import { get, omit } from 'lodash-es'; |
| ... | ... | @@ -30,8 +30,11 @@ |
| 30 | 30 | labelField?: string; |
| 31 | 31 | valueField?: string; |
| 32 | 32 | immediate?: boolean; |
| 33 | - pagenation?: Pagination; | |
| 33 | + searchField?: string; | |
| 34 | + pagination?: Pagination; | |
| 34 | 35 | queryEmptyDataAgin?: boolean; |
| 36 | + fetchSearch?: boolean; | |
| 37 | + filterOption?: (inputValue: string, options: Recordable) => boolean; | |
| 35 | 38 | }>(), |
| 36 | 39 | { |
| 37 | 40 | resultField: '', |
| ... | ... | @@ -39,14 +42,15 @@ |
| 39 | 42 | valueField: 'value', |
| 40 | 43 | immediate: true, |
| 41 | 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 | 50 | const OptionsItem = (_, { attrs }: { attrs: { vNode: any } }) => attrs.vNode; |
| 47 | 51 | |
| 48 | 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 | 54 | const scrollLoading = ref(false); |
| 51 | 55 | const lock = ref(false); |
| 52 | 56 | const loading = ref(false); |
| ... | ... | @@ -55,6 +59,13 @@ |
| 55 | 59 | const attrs = useAttrs(); |
| 56 | 60 | const { t } = useI18n(); |
| 57 | 61 | |
| 62 | + const getPagination = computed(() => { | |
| 63 | + return { | |
| 64 | + ...props.pagination, | |
| 65 | + ...unref(pagination), | |
| 66 | + }; | |
| 67 | + }); | |
| 68 | + | |
| 58 | 69 | // Embedded in the form, just use the hook binding to perform form verification |
| 59 | 70 | const [state] = useRuleFormItem(props, 'value', 'change', emitData); |
| 60 | 71 | |
| ... | ... | @@ -86,16 +97,18 @@ |
| 86 | 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 | 102 | if (!api || !isFunction(api)) return; |
| 103 | + const isFetchSearchFlag = fetchSearch && !isNullAndUnDef(searchText) && searchField; | |
| 92 | 104 | try { |
| 93 | 105 | !unref(getOptions).length ? (loading.value = true) : (scrollLoading.value = true); |
| 94 | 106 | lock.value = true; |
| 95 | 107 | const { total, items } = await api({ |
| 96 | 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 | 114 | pagination.total = total; |
| ... | ... | @@ -105,11 +118,13 @@ |
| 105 | 118 | return; |
| 106 | 119 | } |
| 107 | 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 | 125 | emitChange(); |
| 111 | 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 | 128 | console.warn(error); |
| 114 | 129 | } finally { |
| 115 | 130 | isFirstLoad.value = false; |
| ... | ... | @@ -134,17 +149,39 @@ |
| 134 | 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 | 160 | async function handlePopupScroll(event: MouseEvent) { |
| 138 | 161 | const { scrollHeight, scrollTop, clientHeight } = event.target as HTMLDivElement; |
| 139 | 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 | 167 | await fetch(); |
| 143 | 168 | } |
| 144 | 169 | } |
| 145 | 170 | } |
| 146 | 171 | |
| 147 | 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 | 185 | </script> |
| 149 | 186 | |
| 150 | 187 | <template> |
| ... | ... | @@ -153,6 +190,8 @@ |
| 153 | 190 | v-bind="attrs" |
| 154 | 191 | @change="handleChange" |
| 155 | 192 | :options="getOptions" |
| 193 | + :filterOption="handleFilterOption" | |
| 194 | + :showSearch="true" | |
| 156 | 195 | v-model:value="state" |
| 157 | 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> | ... | ... |
| ... | ... | @@ -19,4 +19,8 @@ export enum DictEnum { |
| 19 | 19 | DISABLE_CUSTOMER_AUTH = 'disabled_customer_auth', |
| 20 | 20 | // 寄存器数据格式 |
| 21 | 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 | 91 | valueField: 'fileList', |
| 92 | 92 | componentProps: () => { |
| 93 | 93 | return { |
| 94 | - listType: 'picture-card', | |
| 94 | + // listType: 'picture-card', | |
| 95 | 95 | maxFileLimit: 1, |
| 96 | 96 | api: async (file: File) => { |
| 97 | 97 | try { |
| ... | ... | @@ -110,6 +110,10 @@ export const formSchema: FormSchema[] = [ |
| 110 | 110 | onPreview: (fileList: FileItem) => { |
| 111 | 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 | 46 | valueField: 'fileList', |
| 47 | 47 | componentProps: () => { |
| 48 | 48 | return { |
| 49 | - listType: 'picture-card', | |
| 49 | + // listType: 'picture-card', | |
| 50 | 50 | maxFileLimit: 1, |
| 51 | 51 | api: async (file: File) => { |
| 52 | 52 | try { |
| ... | ... | @@ -62,6 +62,10 @@ export const formSchema: FormSchema[] = [ |
| 62 | 62 | return {}; |
| 63 | 63 | } |
| 64 | 64 | }, |
| 65 | + showUploadList: true, | |
| 66 | + onDownload(file) { | |
| 67 | + console.log(file); | |
| 68 | + }, | |
| 65 | 69 | onPreview: (fileList: FileItem) => { |
| 66 | 70 | createImgPreview({ imageList: [fileList.url!] }); |
| 67 | 71 | }, | ... | ... |
| ... | ... | @@ -23,7 +23,7 @@ |
| 23 | 23 | layout: 'vertical', |
| 24 | 24 | }); |
| 25 | 25 | |
| 26 | - const { genForm } = useGenDynamicForm(); | |
| 26 | + const { genForm, transformValue } = useGenDynamicForm(); | |
| 27 | 27 | |
| 28 | 28 | const keys = ref<string[]>([]); |
| 29 | 29 | |
| ... | ... | @@ -127,8 +127,8 @@ |
| 127 | 127 | |
| 128 | 128 | sendValue.value = await genModbusCommand(unref(modBUSForm)); |
| 129 | 129 | } else { |
| 130 | - const _value = getFieldsValue(); | |
| 131 | - | |
| 130 | + await validate(); | |
| 131 | + const _value = transformValue(getFieldsValue()); | |
| 132 | 132 | sendValue.value = unref(keys).reduce((prev, next) => { |
| 133 | 133 | return { ...prev, [next]: _value[next] }; |
| 134 | 134 | }, {}); | ... | ... |
| 1 | 1 | import { DataType, Specs, StructJSON } from '/@/api/device/model/modelOfMatterModel'; |
| 2 | 2 | import { JSONEditor } from '/@/components/CodeEditor'; |
| 3 | 3 | import { FormSchema, useComponentRegister } from '/@/components/Form'; |
| 4 | +import { useJsonParse } from '/@/hooks/business/useJsonParse'; | |
| 4 | 5 | import { DataTypeEnum } from '/@/views/device/profiles/step/cpns/physical/cpns/config'; |
| 5 | 6 | |
| 6 | 7 | export interface BasicCreateFormParams { |
| ... | ... | @@ -20,7 +21,6 @@ export const useGenDynamicForm = () => { |
| 20 | 21 | const { specs, type } = dataType; |
| 21 | 22 | const { valueRange, step } = specs! as Partial<Specs>; |
| 22 | 23 | const { max, min } = valueRange || {}; |
| 23 | - console.log({ max, min }); | |
| 24 | 24 | return { |
| 25 | 25 | field: identifier, |
| 26 | 26 | label: functionName, |
| ... | ... | @@ -107,6 +107,19 @@ export const useGenDynamicForm = () => { |
| 107 | 107 | field: identifier, |
| 108 | 108 | label: functionName, |
| 109 | 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 | 131 | [DataTypeEnum.IS_STRUCT]: createInputJson, |
| 119 | 132 | }; |
| 120 | 133 | |
| 134 | + const fieldTypeMap = new Map<string, DataTypeEnum>(); | |
| 121 | 135 | const genForm = (schemas: StructJSON[]) => { |
| 136 | + fieldTypeMap.clear(); | |
| 122 | 137 | const formSchema = schemas.map((item) => { |
| 123 | 138 | const { functionName, identifier, dataType } = item; |
| 124 | 139 | const { type } = dataType || {}; |
| 125 | 140 | |
| 141 | + fieldTypeMap.set(identifier!, dataType!.type); | |
| 126 | 142 | const method = schemaMethod[type!]; |
| 127 | 143 | |
| 128 | 144 | const formSchema = method?.({ |
| ... | ... | @@ -137,5 +153,21 @@ export const useGenDynamicForm = () => { |
| 137 | 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 | 5 | |
| 6 | 6 | export enum NodeBindDataFieldEnum { |
| 7 | 7 | NAME = 'name', |
| 8 | - ALARM_STATUS_LIST = 'alarmStatusList', | |
| 9 | 8 | DESCRIPTION = 'description', |
| 10 | 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 | 48 | export enum NodeBindDataFieldNameEnum { |
| 14 | 49 | NAME = '名称', |
| 15 | - ALARM_STATUS_LIST = 'Alarm status filter', | |
| 16 | 50 | DESCRIPTION = '说明', |
| 17 | 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 | 89 | export enum EdgeBindDataFieldEnum { | ... | ... |
| ... | ... | @@ -53,7 +53,7 @@ export function useBasicDataTransform() { |
| 53 | 53 | |
| 54 | 54 | nodes.forEach((item, index) => indexMap.set(index, item)); |
| 55 | 55 | |
| 56 | - connections.forEach((item) => { | |
| 56 | + (connections || []).forEach((item) => { | |
| 57 | 57 | const { fromIndex, toIndex, type } = item; |
| 58 | 58 | const key = `${fromIndex}${SEPARATOR}${toIndex}`; |
| 59 | 59 | if (!groupByConnections.has(key)) groupByConnections.set(key, []); | ... | ... |
| ... | ... | @@ -124,7 +124,7 @@ export function useSaveAndRedo() { |
| 124 | 124 | firstNodeIndex, |
| 125 | 125 | ruleChainId: { |
| 126 | 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 | 140 | try { |
| 141 | 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 | 145 | const data = await getRuleChainData(id); |
| 146 | 146 | ... | ... |
| ... | ... | @@ -3,8 +3,27 @@ import { FormSchema } from '/@/components/Form'; |
| 3 | 3 | |
| 4 | 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 | 8 | import { NodeBindDataFieldEnum, NodeBindDataFieldNameEnum } from '../../../enum/node'; |
| 9 | +import { deviceProfilePage } from '/@/api/device/deviceManager'; | |
| 2 | 10 | import { FormSchema } from '/@/components/Form'; |
| 3 | 11 | |
| 4 | 12 | export const formSchemas: FormSchema[] = [ |
| ... | ... | @@ -7,4 +15,64 @@ export const formSchemas: FormSchema[] = [ |
| 7 | 15 | component: 'Input', |
| 8 | 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 | 2 | import { NodeBindDataFieldEnum, NodeBindDataFieldNameEnum } from '../../../enum/node'; |
| 2 | 3 | import { FormSchema } from '/@/components/Form'; |
| 3 | 4 | |
| 4 | 5 | export const formSchemas: FormSchema[] = [ |
| 5 | 6 | { |
| 6 | - field: NodeBindDataFieldEnum.NAME, | |
| 7 | + field: NodeBindDataFieldEnum.LATITUDE_KEY_NAME, | |
| 7 | 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 | 1 | import { NodeBindDataFieldEnum, NodeBindDataFieldNameEnum } from '../../../enum/node'; |
| 2 | +import { findDictItemByCode } from '/@/api/system/dict'; | |
| 2 | 3 | import { FormSchema } from '/@/components/Form'; |
| 4 | +import { DictEnum } from '/@/enums/dictEnum'; | |
| 3 | 5 | |
| 4 | 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 | 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 | 1 | import { NodeBindDataFieldEnum, NodeBindDataFieldNameEnum } from '../../../enum/node'; |
| 2 | +import { findDictItemByCode } from '/@/api/system/dict'; | |
| 2 | 3 | import { FormSchema } from '/@/components/Form'; |
| 4 | +import { DictEnum } from '/@/enums/dictEnum'; | |
| 3 | 5 | |
| 4 | 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 | 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 | 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 | 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 | 1 | <script lang="ts" setup> |
| 2 | 2 | import type { CreateModalDefineExposeType } from '../../../types'; |
| 3 | 3 | import { BasicForm, useForm } from '/@/components/Form'; |
| 4 | - import { formSchemas } from './create.config'; | |
| 4 | + import { formSchemas, validateJSON } from './create.config'; | |
| 5 | 5 | import { NodeData } from '../../../types/node'; |
| 6 | 6 | |
| 7 | 7 | defineProps<{ |
| ... | ... | @@ -16,12 +16,13 @@ |
| 16 | 16 | const getValue: CreateModalDefineExposeType['getFieldsValue'] = async () => { |
| 17 | 17 | await validate(); |
| 18 | 18 | const value = getFieldsValue() || {}; |
| 19 | - return value; | |
| 19 | + const { value: data } = validateJSON(value?.value); | |
| 20 | + return data as Recordable; | |
| 20 | 21 | }; |
| 21 | 22 | |
| 22 | 23 | const setValue: CreateModalDefineExposeType['setFieldsValue'] = (value) => { |
| 23 | 24 | resetFields(); |
| 24 | - setFieldsValue(value); | |
| 25 | + setFieldsValue({ value: JSON.stringify(value, null, 2) }); | |
| 25 | 26 | }; |
| 26 | 27 | |
| 27 | 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 | 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 | 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 | 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 | 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 | 27 | GpsGeofencingFilterConfig, |
| 28 | 28 | MessageTypeConfig, |
| 29 | 29 | MessageTypeSwitchConfig, |
| 30 | - OriginatorTypeSwitchConfig, | |
| 31 | 30 | OriginatorTypeConfig, |
| 31 | + OriginatorTypeSwitchConfig, | |
| 32 | 32 | SceneReactConfig, |
| 33 | 33 | ScriptConfig, |
| 34 | 34 | SwitchConfig, | ... | ... |
| ... | ... | @@ -75,6 +75,7 @@ |
| 75 | 75 | <section v-if="shadowComponent" class="w-full h-full"> |
| 76 | 76 | <BasicForm @register="topFormRegister" /> |
| 77 | 77 | <component |
| 78 | + class="rule-node-form" | |
| 78 | 79 | :is="shadowComponent" |
| 79 | 80 | ref="createComponentEl" |
| 80 | 81 | :key="getComponentKey" |
| ... | ... | @@ -87,3 +88,12 @@ |
| 87 | 88 | </Spin> |
| 88 | 89 | </BasicModal> |
| 89 | 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 | 10 | <p class="text-gray-600 italic"> |
| 11 | 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 | 14 | </section> |
| 15 | 15 | </template> | ... | ... |
| ... | ... | @@ -99,6 +99,7 @@ |
| 99 | 99 | <section v-if="shadowComponent" class="w-full h-full"> |
| 100 | 100 | <BasicForm @register="topFormRegister" @vue:mounted="handleTopFormMounted" /> |
| 101 | 101 | <component |
| 102 | + class="rule-node-form" | |
| 102 | 103 | :is="shadowComponent" |
| 103 | 104 | ref="createComponentEl" |
| 104 | 105 | :config="nodeData" |
| ... | ... | @@ -119,3 +120,12 @@ |
| 119 | 120 | </Tabs> |
| 120 | 121 | </BasicDrawer> |
| 121 | 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 | 136 | } |
| 137 | 137 | return { items, total }; |
| 138 | 138 | }, |
| 139 | - showSearch: true, | |
| 140 | 139 | labelField: 'name', |
| 141 | 140 | valueField: 'id.id', |
| 142 | 141 | filterOption: (inputValue: string, options: Record<'label' | 'value', string>) => { | ... | ... |