feat: code代码节点

This commit is contained in:
zhanghao 2026-04-13 15:17:11 +08:00
parent 1918528812
commit 090183a287
4 changed files with 566 additions and 976 deletions

View File

@ -1,26 +1,25 @@
<template> <template>
<el-drawer <el-drawer
v-model="props.drawer" v-model="props.drawer"
custom-class="custom-drawer-header"
class="no-header-margin"
:title="props.data.properties?.name || '参数配置'" :title="props.data.properties?.name || '参数配置'"
:before-close="handleClose" :before-close="handleClose"
> >
<div class="input__container" v-if="props.data.type === 'start'"> <div class="input__container" v-if="props.data.type === 'start'">
<div class="title"> <el-collapse v-model="activeNames">
<div class="left"> <el-collapse-item name="1" icon-position="left">
<div class="tag"></div> <template #title>
<div class="text">输入</div> 输入参数
</div>
<div class="right">
<el-button <el-button
@click="addFormItem('inputParams')"
:circle="true" :circle="true"
size="small"
@click="(e) => addFormItem(e, 'nodeParams')"
class="addFormItem" class="addFormItem"
> >
<el-icon :size="20"><Plus /></el-icon> <el-icon :size="14"><Plus /></el-icon>
</el-button> </el-button>
</div> </template>
</div>
<el-form <el-form
:inline="true" :inline="true"
:model="formData" :model="formData"
@ -40,25 +39,24 @@
@delete-item="(path) => deleteTopLevelItem(path, 'inputParams')" @delete-item="(path) => deleteTopLevelItem(path, 'inputParams')"
/> />
</el-form> </el-form>
</el-collapse-item>
</el-collapse>
</div> </div>
<div v-if="['currentLoop', 'serviceNode'].includes(props.data.type)"> <div v-if="['currentLoop', 'serviceNode'].includes(props.data.type)">
<div class="input__container"> <el-collapse v-model="activeNames">
<div class="title"> <el-collapse-item name="1" icon-position="left">
<div class="left"> <template #title>
<div class="tag"></div> 输入参数
<div class="text">输入</div>
</div>
<div class="right">
<el-button <el-button
:circle="true" :circle="true"
@click="addFormItem('nodeParams')" size="small"
@click="(e) => addFormItem(e, 'nodeParams')"
class="addFormItem" class="addFormItem"
> >
<el-icon :size="20"><Plus /></el-icon> <el-icon :size="14"><Plus /></el-icon>
</el-button> </el-button>
</div> </template>
</div>
<div class="form__container"> <div class="form__container">
<el-form <el-form
:inline="true" :inline="true"
@ -167,28 +165,30 @@
/> />
</el-form-item> </el-form-item>
</el-form-item> </el-form-item>
<el-button
:icon="Minus"
circle
size="small"
@click="handleDelete(index)"
class="deleteBtn"
/>
</el-row> </el-row>
</div> </div>
</el-form> </el-form>
</div> </div>
</div> </el-collapse-item>
<el-collapse-item name="2" icon-position="left">
<div class="output__container"> <template #title>
<div class="title"> 输出参数
<div class="left">
<div class="tag"></div>
<div class="text">输出</div>
</div>
<div class="right">
<el-button <el-button
@click="addFormItem('outputParams')" :circle="true"
size="small"
@click="(e) => addFormItem(e, 'outputParams')"
class="addFormItem" class="addFormItem"
> >
<el-icon :size="20"><Plus /></el-icon> <el-icon :size="14"><Plus /></el-icon>
</el-button> </el-button>
</div> </template>
</div>
<div class="form__container"> <div class="form__container">
<el-form <el-form
:inline="true" :inline="true"
@ -210,9 +210,190 @@
/> />
</el-form> </el-form>
</div> </div>
</div> </el-collapse-item>
</el-collapse>
</div> </div>
<div v-if="props.data.type === 'code'">
<el-collapse v-model="activeNames">
<el-collapse-item name="1" icon-position="left">
<template #title>
输入参数
<el-tooltip
class="box-item"
effect="dark"
content="这里输入的变量可以被下方的代码引用。"
placement="top"
>
<el-icon class="header-icon" style="margin-left: 6px">
<info-filled />
</el-icon>
</el-tooltip>
</template>
<div class="form__container">
<el-form
:inline="true"
:model="formData"
:rules="rules"
ref="dynamicForm"
label-position="top"
label-width="auto"
>
<div v-for="(property, index) in formData.nodeParams" :key="index">
<el-row v-if="property.name !== 'code'">
<el-form-item
:label="index === 0 ? '参数名' : ''"
:prop="`nodeParams.${index}.name`"
:rules="[
{ required: true, message: '请输入参数名', trigger: 'blur' },
]"
>
<el-input
:disabled="property?.disabled || false"
class="param-name"
v-model="property.name"
placeholder="请输入"
clearable
/>
</el-form-item>
<el-form-item
:label="index === 0 ? '参数值' : ''"
:prop="`nodeParams.${index}.type`"
>
<el-select
v-model="property.type"
class="param-type"
@change="handleTypeChange(index)"
>
<el-option label="引用" value="quote" />
<el-option label="输入" value="input" />
</el-select>
<el-form-item
v-if="property.type === 'input'"
:rules="[
{
required: property?.required ?? true,
message: '请输入参数值',
trigger: 'blur',
},
]"
:prop="`nodeParams.${index}.input`"
>
<el-input-number
class="param-value"
v-if="property.componentType === 'number'"
v-model="property.input"
:min="0"
:max="property.max || Infinity"
:controls="false"
:step-strictly="true"
placeholder="请输入"
clearable
/>
<el-select
v-model="property.input"
class="param-value"
v-else-if="property.componentType === 'select'"
>
<el-option
v-for="item in property.selectOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<el-input
class="param-value"
v-else
v-model="property.input"
placeholder="请输入"
clearable
/>
</el-form-item>
<el-form-item
v-if="property.type === 'quote'"
:rules="[
{
required: true,
message: '请选择参数值',
trigger: 'blur',
},
]"
:prop="`nodeParams.${index}.quote`"
>
<el-cascader
:ref="
(el) => {
if (el) cascaderRefs[index] = el;
}
"
v-model="property.quote"
:checkStrictly="true"
:options="quoteOptions"
placeholder="请选择"
@visible-change="
(visible) => visibleChange(visible, index, property.quote)
"
@change="(value) => cascaderChange(value, index)"
/>
</el-form-item>
</el-form-item>
<el-button
:icon="Minus"
circle
size="small"
@click="handleDelete(index)"
class="deleteBtn"
/>
</el-row>
</div>
</el-form>
</div>
</el-collapse-item>
<el-collapse-item title="代码" name="2" icon-position="left">
<div class="code__container">
<div ref="codeRef" style="width: 100%; height: 100%"></div>
</div>
</el-collapse-item>
<el-collapse-item name="3" icon-position="left">
<template #title>
输出参数
<el-tooltip
class="box-item"
effect="dark"
content="这里定义的输出变量可以被后续节点引用。"
placement="top"
>
<el-icon class="header-icon" style="margin-left: 6px">
<info-filled />
</el-icon>
</el-tooltip>
</template>
<div class="form__container">
<el-form
:inline="true"
:model="formData"
label-position="top"
label-width="auto"
:rules="outputRules"
ref="outputFormRef"
>
<FormItemRecursive
formType="output"
:current-list="formData.outputParams"
prop-path="outputParams"
:depth="0"
:is-first-level="true"
:endDepth="2"
:parent-path="[]"
@delete-item="(path) => deleteTopLevelItem(path, 'outputParams')"
/>
</el-form>
</div>
</el-collapse-item>
</el-collapse>
</div>
<template #footer> <template #footer>
<el-button @click="handleClose">关闭</el-button> <el-button @click="handleClose">关闭</el-button>
@ -223,9 +404,12 @@
<script setup> <script setup>
import FormItemRecursive from "./FormItemRecursive.vue"; import FormItemRecursive from "./FormItemRecursive.vue";
import { Plus } from "@element-plus/icons-vue"; import { Plus, Minus } from "@element-plus/icons-vue";
import { getInput, filterEmptyName } from "@/utils/flow"; import { getInput, filterEmptyName } from "@/utils/flow";
import { emitter } from "@/utils/eventBus"; import { emitter } from "@/utils/eventBus";
import "vue3-json-viewer/dist/index.css";
import * as monaco from 'monaco-editor';
import { nextTick, onUnmounted, watch } from "vue";
const props = defineProps({ const props = defineProps({
drawer: Boolean, drawer: Boolean,
@ -242,6 +426,9 @@ const formData = reactive({
const rules = reactive({}); const rules = reactive({});
const codeRef = ref()
let editorInstance
const handleClose = (done) => { const handleClose = (done) => {
const filteredObj = { const filteredObj = {
inputParams: filterEmptyName(formData.inputParams), inputParams: filterEmptyName(formData.inputParams),
@ -276,7 +463,8 @@ const deleteTopLevelItem = (fullPath, propPath) => {
currentLevel.splice(lastIndex, 1); currentLevel.splice(lastIndex, 1);
} }
const addFormItem = (type) => { const addFormItem = (e, type) => {
e.stopPropagation();
const obj ={ const obj ={
name: "", name: "",
type: type === 'nodeParams' ? 'input' : 'string', type: type === 'nodeParams' ? 'input' : 'string',
@ -337,6 +525,95 @@ const visibleChange = (value, index, quote) => {
} }
}; };
const activeNames = ref(['1', '2'])
function hasErrors() {
const model = editorInstance.getModel();
if (!model) return false;
//
const markers = monaco.editor.getModelMarkers({ resource: model.uri });
//
return markers.some(marker => marker.severity === monaco.MarkerSeverity.Error);
}
const initEditor = () => {
monaco.languages.typescript.javascriptDefaults.setCompilerOptions({
target: monaco.languages.typescript.ScriptTarget.ES2020,
allowNonTsExtensions: true, // ts .js
checkJs: true, // .js
strict: true, //
noImplicitAny: false, // any
noUnusedLocals: true, // 使
noUnusedParameters: true, // 使
});
//
monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions({
noSemanticValidation: false, //
noSyntaxValidation: false, //
});
editorInstance = monaco.editor.create(codeRef.value, {
value: [`
//
function handler(params) {
// json dict object
const result ={
type: 2,
message: params.input
}
return result
}`].join('\n'),
language: 'javascript',
fontSize: 14,
lineNumbers: 'on',
roundedSelection: true,
scrollBeyondLastLine: false,
formatOnPaste: true,
formatOnType: true,
quickSuggestions: true,
suggestOnTriggerCharacters: true,
//
suggest: {
showWords: true,
showFunctions: true,
showVariables: true,
showClasses: true,
showModules: true,
},
});
}
//
const setValue = (value) => {
if (editorInstance) {
editorInstance.setValue(value)
}
}
//
const disposeEditor = () => {
if (editorInstance) {
editorInstance.dispose()
editorInstance = null
}
}
watch(
() => props.drawer,
(newVal) => {
if (newVal && props.data.type === 'code') {
nextTick(() => {
initEditor();
});
} else {
disposeEditor();
}
}
);
watch( watch(
() => props.data, () => props.data,
() => { () => {
@ -345,7 +622,17 @@ watch(
} else if (['currentLoop', 'serviceNode'].includes(props.data.type)) { } else if (['currentLoop', 'serviceNode'].includes(props.data.type)) {
formData.nodeParams = props.data.properties.nodeParams; formData.nodeParams = props.data.properties.nodeParams;
formData.outputParams = props.data.properties.outputParams; formData.outputParams = props.data.properties.outputParams;
console.log('formData', formData) } else if (props.data.type === "code") {
formData.nodeParams = props.data.properties.nodeParams;
formData.outputParams = props.data.properties.outputParams;
const codeParams = props.data.properties.nodeParams.find(item => item.name === 'code')
if (codeParams) {
nextTick(() => {
if (editorInstance) {
setValue(codeParams.input)
}
})
}
} }
}, },
{ {
@ -354,50 +641,17 @@ watch(
} }
); );
onUnmounted(() => {
disposeEditor()
})
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.input__container { .addFormItem {
width: 100%; margin-left: 12px;
background-color: #fafbfc; }
padding: 0 16px;
border-radius: 8px;
box-sizing: border-box;
.title { .form__container {
height: 32px;
display: flex;
align-items: center;
justify-content: space-between;
.left {
display: flex;
align-items: center;
.tag {
width: 3px;
height: 16px;
background: #1664ff;
border-radius: 0 4px 4px 0;
margin-right: 12px;
}
.text {
font-size: 16px;
font-weight: 700;
color: #0c0d0e;
}
}
.right {
.addFormItem {
border: none;
background: transparent;
cursor: pointer;
}
}
}
.form__container {
margin: 12px 0; margin: 12px 0;
:deep(.el-row) { :deep(.el-row) {
@ -421,6 +675,8 @@ watch(
} }
.sub-properties { .sub-properties {
margin-left: 10px; margin-left: 10px;
.zw { .zw {
@ -458,73 +714,26 @@ watch(
} }
} }
} }
.deleteBtn {
margin-bottom: 22px;
cursor: pointer;
} }
} }
.output__container { .code__container {
display: flex;
position: relative;
text-align: initial;
width: 100%; width: 100%;
background-color: #fafbfc; height: 300px;
padding: 0 16px;
border-radius: 8px;
box-sizing: border-box;
.title {
height: 32px;
display: flex;
align-items: center;
justify-content: space-between;
.left {
display: flex;
align-items: center;
.tag {
width: 3px;
height: 16px;
background: #1664ff;
border-radius: 0 4px 4px 0;
margin-right: 12px;
}
.text {
font-size: 16px;
font-weight: 700;
color: #0c0d0e;
}
}
.right {
.addFormItem {
border: none;
background: transparent;
cursor: pointer;
}
}
}
.form__container {
margin: 12px 0; margin: 12px 0;
}
:deep(.el-row) { </style>
align-items: end;
<style lang="scss">
.el-form-item { /* 不加 scoped全局生效 */
margin-right: 12px; .no-header-margin .el-drawer__header {
} margin-bottom: 0 !important;
}
.param-name {
width: 160px;
}
.param-type {
width: 80px;
}
.param-desc {
width: 160px;
}
}
}
}
</style> </style>

View File

@ -306,7 +306,10 @@ export const collapseList = [
{ name: "input", type: "input", input: "", required: true } { name: "input", type: "input", input: "", required: true }
], ],
outputType: 'json', outputType: 'json',
outputParams: [{ name: 'result', type: 'object', children: [], desc: 'js节点返回'}] outputParams: [{ name: 'result', type: 'object', children: [
{name: 'type', type: 'number', desc: '占位属性'},
{name: 'message', type: 'string', desc: '占位属性'}
], desc: 'js节点返回'}]
}, },
{ {
icon: microphoneSvg, icon: microphoneSvg,

View File

@ -144,16 +144,6 @@ class CodeNodeHtmlModel extends HtmlNodeModel {
this.componentInstance = instance; this.componentInstance = instance;
} }
// 验证表单
async validateForm() {
const result = this.componentInstance.validateForm()
return result
}
setCustomProperties() {
this.componentInstance.setNodeProperties()
}
/** /**
* 设置节点属性 * 设置节点属性
* 包括宽度高度文本编辑属性等 * 包括宽度高度文本编辑属性等

View File

@ -40,196 +40,17 @@
:nodeType="props.properties.nodeType || 'NONE'" :nodeType="props.properties.nodeType || 'NONE'"
:nodeName="props.properties.name" :nodeName="props.properties.name"
:nodeDesc="props.properties.desc" :nodeDesc="props.properties.desc"
:zoom-state="nodeZoom"
@zoom="zoom"
@setNodeName="setNodeName" @setNodeName="setNodeName"
/> />
<div class="input__container" v-show="nodeZoom" @mousedown="(e) => e.stopPropagation()" @keydown="handleInputKeydown">
<div class="title">
<div class="left">
<div class="tag"></div>
<div class="text">输入</div>
</div>
<div class="right" v-if="properties?.canAddFormItem">
<el-button
:disabled="flowStore.disableForm"
@click="addFormItem"
class="addFormItem"
>
<el-icon :size="20"><Plus /></el-icon>
</el-button>
</div>
</div>
<div class="form__container">
<el-form
:inline="true"
:model="formData"
:rules="rules"
ref="dynamicForm"
label-position="top"
label-width="auto"
:disabled="flowStore.disableForm"
>
<div v-for="(property, index) in formData.nodeParams" :key="index">
<el-row v-if="property.name !== 'code'">
<el-form-item
:label="index === 0 ? '参数名' : ''"
:prop="`nodeParams.${index}.name`"
:rules="[
{ required: true, message: '请输入参数名', trigger: 'blur' },
]"
>
<el-input
:disabled="property?.disabled || false"
v-model="property.name"
@keydown="handleInputKeydown"
placeholder="请输入"
clearable
/>
</el-form-item>
<el-form-item
:label="index === 0 ? '参数值' : ''"
:prop="`nodeParams.${index}.type`"
>
<el-select
v-model="property.type"
@change="handleTypeChange(index)"
>
<el-option label="引用" value="quote" />
<el-option label="输入" value="input" />
</el-select>
<el-form-item
v-if="property.type === 'input'"
:rules="[
{
required: property?.required ?? true,
message: '请输入参数值',
trigger: 'blur',
},
]"
:prop="`nodeParams.${index}.input`"
>
<el-input-number
v-if="property.componentType === 'number'"
v-model="property.input"
:min="0"
:controls="false"
:step-strictly="true"
placeholder="请输入"
clearable
@keydown="handleInputKeydown"
/>
<el-select
v-model="property.input"
v-else-if="property.componentType === 'select'"
>
<el-option
v-for="item in property.selectOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<el-input
@keydown="handleInputKeydown"
v-else
v-model="property.input"
placeholder="请输入"
clearable
/>
</el-form-item>
<el-form-item
v-if="property.type === 'quote'"
:rules="[
{
required: true,
message: '请选择参数值',
trigger: 'blur',
},
]"
:prop="`nodeParams.${index}.quote`"
>
<el-cascader
:ref="
(el) => {
if (el) cascaderRefs[index] = el;
}
"
v-model="property.quote"
:checkStrictly="true"
:options="quoteOptions"
placeholder="请选择"
@visible-change="
(visible) => visibleChange(visible, index, property.quote)
"
@change="(value) => cascaderChange(value, index)"
/>
</el-form-item>
</el-form-item>
</el-row>
</div>
</el-form>
</div>
<div class="code__container">
<div ref="codeRef" style="width: 100%; height: 100%"></div>
</div>
</div>
<div class="output__container" v-show="nodeZoom">
<div v-if="props.properties?.outputParams?.length > 0">
<div class="title">
<div class="left">
<div class="tag"></div>
<div class="text">输出</div>
</div>
<div class="right">
<el-button
:disabled="flowStore.disableForm"
@click="addOutputFormItem"
class="addFormItem"
>
<el-icon :size="20"><Plus /></el-icon>
</el-button>
</div>
</div>
<div class="form__container">
<el-form
:inline="true"
:model="formData"
label-position="top"
label-width="auto"
:rules="outputRules"
ref="outputFormRef"
>
<FormItemRecursive
formType="output"
:current-list="formData.outputParams"
prop-path="outputParams"
:depth="0"
:is-first-level="true"
:endDepth="2"
:parent-path="[]"
@delete-item="deleteTopLevelItem"
/>
</el-form>
</div>
</div>
</div>
</div> </div>
</template> </template>
<script setup> <script setup>
import { ref, reactive, onMounted, onUnmounted, nextTick } from "vue"; import { ref, onMounted, onUnmounted } from "vue";
import { getInput, initNodeZoom } from "@/utils/flow";
import { useFlowStore } from "@/store/modules/flow";
import { Plus } from "@element-plus/icons-vue";
import NodeTitle from "../../components/NodeTitle.vue"; import NodeTitle from "../../components/NodeTitle.vue";
import NodeState from "../../components/NodeState.vue"; import NodeState from "../../components/NodeState.vue";
import "vue3-json-viewer/dist/index.css";
import { emitter } from "@/utils/eventBus"; import { emitter } from "@/utils/eventBus";
import IPlayer from "@/views/device/register/components/LogicFlow/IPlayer/index.vue"; import IPlayer from "@/views/device/register/components/LogicFlow/IPlayer/index.vue";
import FormItemRecursive from "../common/FormItemRecursive.vue";
import * as monaco from 'monaco-editor';
const props = defineProps({ const props = defineProps({
model: Object, model: Object,
@ -238,87 +59,6 @@ const props = defineProps({
const emits = defineEmits(["contentChange"]); const emits = defineEmits(["contentChange"]);
const codeRef = ref()
let editorInstance
const flowStore = useFlowStore();
const formData = reactive({
nodeParams: [],
outputParams: [],
});
const rules = reactive({});
const quoteOptions = ref([]);
const handleTypeChange = (index) => {
if (formData.nodeParams[index].type === "input") {
formData.nodeParams[index].quote = "";
} else {
formData.nodeParams[index].input = "";
const option = getInput(props.model.id);
if (option) {
quoteOptions.value = option;
}
}
};
const cascaderRefs = ref([]);
const outputRules = reactive({});
const cascaderChange = (value, index) => {
const selectedOptions = cascaderRefs.value[index].getCheckedNodes(true);
formData.nodeParams[index].quote = value;
formData.nodeParams[index].quoteType = selectedOptions[0].data.type;
};
const addOutputFormItem = () => {
formData.outputParams.push({
name: "",
type: "",
desc: "",
children: [],
});
};
const dynamicForm = ref();
const outputFormRef = ref();
const setNodeProperties = async () => {
try {
const result = await dynamicForm.value.validate();
let flag = true;
if (outputFormRef.value) {
flag = await outputFormRef.value.validate();
}
if (hasErrors()) {
console.log('编辑器中有错误')
return
}
if (result && flag) {
const data = toRaw(formData);
const targetObj = data.nodeParams.find(item => item.name === 'code');
if (targetObj) {
// input
targetObj.input = editorInstance.getValue();
} else {
data.nodeParams.push(
{ name: "code", type: "input", input: editorInstance.getValue() }
)
}
const properties = lf.getProperties(props.model.id);
lf.setProperties(props.model.id, {
...properties,
...data,
zoom: nodeZoom.value
});
emits("contentChange");
}
} catch {
console.log(12323)
// dynamicForm.value.clearValidate();
}
};
const setNodeName = (name) => { const setNodeName = (name) => {
const properties = lf.getProperties(props.model.id); const properties = lf.getProperties(props.model.id);
lf.setProperties(props.model.id, { lf.setProperties(props.model.id, {
@ -333,203 +73,6 @@ const inputJsonData = ref({});
const outputJsonData = ref({}); const outputJsonData = ref({});
const errorInfoData = ref(''); const errorInfoData = ref('');
const visibleChange = (value, index, quote) => {
if (value) {
const option = getInput(props.model.id);
if (option) {
quoteOptions.value = option;
const currentValue = [...quote];
//
if (cascaderRefs.value[index]) {
// DOMoptions
setTimeout(() => {
//
if (currentValue.length) {
formData.nodeParams[index].quote = [];
//
setTimeout(() => {
formData.nodeParams[index].quote = currentValue;
}, 0);
}
//
// cascaderRefs.value[index].updatePopper();
}, 0);
}
}
}
};
const validateForm = async () => {
try {
const result = await dynamicForm.value.validate();
if (result) {
return { valid: true, message: "验证通过" };
} else {
return { valid: false, message: "验证失败" };
}
} catch {
return { valid: false, message: "验证失败" };
}
};
const addFormItem = () => {
formData.nodeParams.push({
name: "",
type: "input",
desc: "",
required: false,
children: [],
});
emits("contentChange");
};
//
const deleteTopLevelItem = (fullPath) => {
//
let currentLevel = formData.outputParams;
//
for (let i = 0; i < fullPath.length - 1; i++) {
const index = fullPath[i];
//
currentLevel = currentLevel[index].children;
}
//
const lastIndex = fullPath[fullPath.length - 1];
currentLevel.splice(lastIndex, 1);
emits("contentChange");
};
const nodeZoom = ref(props?.properties?.zoom ?? true)
const zoom = (flag) => {
nodeZoom.value = flag
initNodeZoom(props.model.id, nodeZoom.value, '.node__box')
}
watch(
() => props.properties,
() => {
if (props.properties.nodeParams && props.properties.nodeParams.length > 0) {
// .filter(item => item.name !== 'code')
formData.nodeParams = props.properties.nodeParams;
const codeParams = props.properties.nodeParams.find(item => item.name === 'code')
if (codeParams) {
nextTick(() => {
if (editorInstance) {
setValue(codeParams.input)
}
})
}
const option = getInput(props.model.id);
if (option) {
quoteOptions.value = option;
}
nextTick(() => {
initNodeZoom(props.model.id, props?.properties?.zoom ?? true, '.node__box', true)
})
}
if (
props.properties.outputParams &&
props.properties.outputParams.length > 0
) {
formData.outputParams = props.properties.outputParams;
}
},
{
immediate: true,
deep: true,
}
);
const handleInputKeydown = (e) => {
// Logic Flow
e.stopPropagation();
// Ctrl+V
if ((e.ctrlKey || e.metaKey) && e.key === "v") {
e.returnValue = true; //
}
};
function hasErrors() {
const model = editorInstance.getModel();
if (!model) return false;
//
const markers = monaco.editor.getModelMarkers({ resource: model.uri });
//
return markers.some(marker => marker.severity === monaco.MarkerSeverity.Error);
}
const initEditor = () => {
monaco.languages.typescript.javascriptDefaults.setCompilerOptions({
target: monaco.languages.typescript.ScriptTarget.ES2020,
allowNonTsExtensions: true, // ts .js
checkJs: true, // .js
strict: true, //
noImplicitAny: false, // any
noUnusedLocals: true, // 使
noUnusedParameters: true, // 使
});
//
monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions({
noSemanticValidation: false, //
noSyntaxValidation: false, //
});
editorInstance = monaco.editor.create(codeRef.value, {
value: [`
//
function handler(params) {
// json dict object
const ret={
result: {
type: 2,
message: params.input
}
}
return ret
}`].join('\n'),
language: 'javascript',
fontSize: 14,
lineNumbers: 'on',
roundedSelection: true,
scrollBeyondLastLine: false,
formatOnPaste: true,
formatOnType: true,
quickSuggestions: true,
suggestOnTriggerCharacters: true,
//
suggest: {
showWords: true,
showFunctions: true,
showVariables: true,
showClasses: true,
showModules: true,
},
});
}
//
const setValue = (value) => {
if (editorInstance) {
editorInstance.setValue(value)
}
}
//
const disposeEditor = () => {
if (editorInstance) {
editorInstance.dispose()
editorInstance = null
}
}
onMounted(() => { onMounted(() => {
emitter.on("changeNodeState", (data) => { emitter.on("changeNodeState", (data) => {
if (data.nodeId === props.model.id) { if (data.nodeId === props.model.id) {
@ -559,179 +102,24 @@ onMounted(() => {
} }
}); });
initEditor() emitter.on("setProperties", (data) => {
if (data.id === props.model.id) {
emits("contentChange");
}
});
}) })
onUnmounted(() => { onUnmounted(() => {
emitter.off("changeNodeState"); emitter.off("changeNodeState");
emitter.off("contentChange"); emitter.off("contentChange");
disposeEditor() emitter.off("setProperties");
}); });
defineExpose({
validateForm,
setNodeProperties,
});
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.node__container { .node__container {
width: 100%; width: 100%;
height: auto; height: auto;
.input__container {
width: 100%;
background-color: #fafbfc;
padding: 0 16px;
border-radius: 8px;
box-sizing: border-box;
.title {
height: 32px;
display: flex;
align-items: center;
justify-content: space-between;
.left {
display: flex;
align-items: center;
.tag {
width: 3px;
height: 16px;
background: #1664ff;
border-radius: 0 4px 4px 0;
margin-right: 12px;
}
.text {
font-size: 16px;
font-weight: 700;
color: #0c0d0e;
}
}
.right {
.addFormItem {
border: none;
background: transparent;
cursor: pointer;
}
}
}
.code__container {
display: flex;
position: relative;
text-align: initial;
width: 100%;
height: 300px;
margin: 12px 0;
}
.form__container {
margin: 12px 0;
.sub-properties {
margin-left: 10px;
.zw {
margin-left: 15px;
&::before {
content: "";
position: absolute;
left: 0;
top: -4px;
width: 10px;
border-left: 1px solid gray;
border-bottom: 1px solid gray;
border-bottom-left-radius: 4px;
background-color: transparent;
height: 24px;
}
}
}
.gSon-properties {
margin-left: 10px;
.zw {
margin-left: 15px;
&::before {
content: "";
position: absolute;
left: 0;
top: -4px;
width: 10px;
border-left: 1px solid gray;
border-bottom: 1px solid gray;
border-bottom-left-radius: 4px;
background-color: transparent;
height: 24px;
}
}
}
}
}
.output__container {
width: 100%;
background-color: #fafbfc;
padding: 0 16px;
border-radius: 8px;
box-sizing: border-box;
.title {
height: 32px;
display: flex;
align-items: center;
justify-content: space-between;
.left {
display: flex;
align-items: center;
.tag {
width: 3px;
height: 16px;
background: #1664ff;
border-radius: 0 4px 4px 0;
margin-right: 12px;
}
.text {
font-size: 16px;
font-weight: 700;
color: #0c0d0e;
}
}
.right {
.addFormItem {
border: none;
background: transparent;
cursor: pointer;
}
}
}
.form__container {
margin: 12px 0;
}
}
:deep(.el-row) {
align-items: end;
.el-input {
--el-input-width: 148px;
}
.el-select {
--el-select-width: 148px;
}
.el-cascader {
--el-form-inline-content-width: 148px;
}
}
} }
</style> </style>