Commit 239c2b3dab312449724b1743149be9e8761ccdcf

Authored by ww
1 parent ba789ed2

feat: 实现过滤器所有节点

Showing 42 changed files with 1388 additions and 132 deletions
... ... @@ -16,6 +16,7 @@
16 16 "inited",
17 17 "liveui",
18 18 "MQTT",
  19 + "noconflict",
19 20 "notif",
20 21 "PROTOBUF",
21 22 "rtsp",
... ...
... ... @@ -58,6 +58,7 @@
58 58 "flv.js": "^1.6.2",
59 59 "hls.js": "^1.0.10",
60 60 "intro.js": "^4.1.0",
  61 + "js-beautify": "^1.14.9",
61 62 "jsoneditor": "^9.7.2",
62 63 "jwt-decode": "^3.1.2",
63 64 "lodash-es": "^4.17.21",
... ...
  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 +};
... ...
  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>
... ...
... ... @@ -130,4 +130,6 @@ export type ComponentType =
130 130 | 'ControlGroup'
131 131 | 'JSONEditor'
132 132 | 'OrgTreeSelect'
133   - | 'ExtendDesc';
  133 + | 'ExtendDesc'
  134 + | 'JavaScriptFunctionEditor'
  135 + | 'JavaScriptFilterFunctionModal';
... ...
... ... @@ -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 }
... ...
  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 +}
... ...
  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 +}
... ...
  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 };
... ...
  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>) => {
... ...
... ... @@ -49,3 +49,10 @@ export interface PaginationResult<T = Recordable> {
49 49 items: T[];
50 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 +}
... ...