feat: http、loop、sleep节点重构

This commit is contained in:
zhanghao 2026-04-14 11:35:42 +08:00
parent 090183a287
commit 75f64e8d9f
7 changed files with 665 additions and 1134 deletions

View File

@ -43,7 +43,7 @@
</el-collapse> </el-collapse>
</div> </div>
<div v-if="['currentLoop', 'serviceNode'].includes(props.data.type)"> <div v-if="['currentLoop', 'serviceNode', 'sleep', 'loop'].includes(props.data.type)">
<el-collapse v-model="activeNames"> <el-collapse v-model="activeNames">
<el-collapse-item name="1" icon-position="left"> <el-collapse-item name="1" icon-position="left">
<template #title> <template #title>
@ -395,6 +395,494 @@
</div> </div>
<div v-if="props.data.type === 'http'">
<el-form
:inline="true"
:model="httpNodeData"
:rules="rules"
class="http-form"
ref="dynamicForm"
label-position="top"
label-width="auto"
>
<el-collapse v-model="activeNames">
<el-collapse-item name="1" icon-position="left">
<template #title>
请求配置
<el-tooltip
class="box-item"
effect="dark"
content="支持HTTP/HTTPS协议"
placement="top"
>
<el-icon class="header-icon" style="margin-left: 6px">
<info-filled />
</el-icon>
</el-tooltip>
</template>
<div class="form__container">
<div v-for="(property, index) in httpNodeData.config" :key="index">
<el-row>
<el-form-item
:label="index === 0 ? '参数名' : ''"
:prop="`config.${index}.name`"
:rules="[
{ required: true, message: '请输入参数名', trigger: 'blur' },
]"
>
<el-input
:disabled="true"
class="param-name"
v-model="property.name"
placeholder="请输入"
clearable
/>
</el-form-item>
<el-form-item
:label="index === 0 ? '参数值' : ''"
:prop="`config.${index}.input`"
>
<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="`config.${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="`config.${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>
</div>
</el-collapse-item>
<el-collapse-item name="2" icon-position="left">
<template #title>
Header配置
<el-button
:circle="true"
size="small"
@click="(e) => addHttpNodeFormItem(e, 'headers')"
class="addFormItem"
>
<el-icon :size="14"><Plus /></el-icon>
</el-button>
</template>
<div class="form__container">
<div v-for="(property, index) in httpNodeData.headers" :key="index">
<el-row>
<el-form-item
:label="index === 0 ? '参数名' : ''"
:prop="`headers.${index}.name`"
:rules="[
{ required: true, message: '请输入参数名', trigger: 'blur' },
]"
>
<el-input
class="param-name"
v-model="property.name"
placeholder="请输入"
clearable
/>
</el-form-item>
<el-form-item
:label="index === 0 ? '参数值' : ''"
:prop="`headers.${index}.input`"
>
<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="`headers.${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="`headers.${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>
</div>
</el-collapse-item>
<el-collapse-item name="3" icon-position="left">
<template #title>
Param配置
<el-button
:circle="true"
size="small"
@click="(e) => addHttpNodeFormItem(e, 'params')"
class="addFormItem"
>
<el-icon :size="14"><Plus /></el-icon>
</el-button>
</template>
<div class="form__container">
<div v-for="(property, index) in httpNodeData.params" :key="index">
<el-row>
<el-form-item
:label="index === 0 ? '参数名' : ''"
:prop="`params.${index}.name`"
:rules="[
{ required: true, message: '请输入参数名', trigger: 'blur' },
]"
>
<el-input
class="param-name"
v-model="property.name"
placeholder="请输入"
clearable
/>
</el-form-item>
<el-form-item
:label="index === 0 ? '参数值' : ''"
:prop="`params.${index}.input`"
>
<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="`params.${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="`params.${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>
</div>
</el-collapse-item>
<el-collapse-item name="4" title="Body配置" icon-position="left">
<template #title>
Body配置
<el-button
:circle="true"
size="small"
@click="(e) => addHttpNodeFormItem(e, 'body')"
class="addFormItem"
>
<el-icon :size="14"><Plus /></el-icon>
</el-button>
</template>
<div class="form__container">
<el-select v-model="httpNodeData.body.type">
<el-option label="None" value="none" />
<el-option label="JSON" value="json" />
<el-option label="form-data" value="form-data" />
</el-select>
<div class="code__container" v-show="httpNodeData.body.type === 'json'">
<div ref="codeRef" style="width: 100%; height: 100%"></div>
</div>
<div class="test" v-show="httpNodeData.body.type === 'form-data'">
<div v-for="(property, index) in httpNodeData.body.formData" :key="index">
<el-row>
<el-form-item
:label="index === 0 ? '参数名' : ''"
:prop="`params.${index}.name`"
:rules="[
{ required: true, message: '请输入参数名', trigger: 'blur' },
]"
>
<el-input
class="param-name"
v-model="property.name"
placeholder="请输入"
clearable
/>
</el-form-item>
<el-form-item
:label="index === 0 ? '参数值' : ''"
:prop="`body.formData.${index}.input`"
>
<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="`body.formData.${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="`body.formData.${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>
</div>
</div>
</el-collapse-item>
</el-collapse>
</el-form>
</div>
<template #footer> <template #footer>
<el-button @click="handleClose">关闭</el-button> <el-button @click="handleClose">关闭</el-button>
<el-button type="primary" @click="confirm">确定</el-button> <el-button type="primary" @click="confirm">确定</el-button>
@ -430,6 +918,30 @@ const codeRef = ref()
let editorInstance let editorInstance
const handleClose = (done) => { const handleClose = (done) => {
if (props.data.type === 'http') {
const nodeParams = []
nodeParams.push({ name: "config", type: "input", input: "", children: httpNodeData.value.config, disabled: true, required: true })
if (httpNodeData.value.headers.length) {
nodeParams.push({ name: "headers", type: "input", input: "", children: httpNodeData.value.headers, disabled: true, required: true })
}
if (httpNodeData.value.params.length) {
nodeParams.push({ name: "params", type: "input", input: "", children: httpNodeData.value.params, disabled: true, required: true })
}
if (httpNodeData.value.body.type !== 'none') {
const bodyObj = {
name: "body", type: "input", input: "", children: [
{ name: 'bodyType', type: "input", input: "json", disabled: true },
{ name: 'json', type: "input", input: editorInstance.getValue() || '' },
{ name: 'formData', type: "input", input: "", children: httpNodeData.value.body.formData }
]
}
nodeParams.push(bodyObj)
}
lf.setProperties(props.data.id, {
...props.data.properties,
nodeParams
});
} else {
const filteredObj = { const filteredObj = {
inputParams: filterEmptyName(formData.inputParams), inputParams: filterEmptyName(formData.inputParams),
nodeParams: filterEmptyName(formData.nodeParams), nodeParams: filterEmptyName(formData.nodeParams),
@ -440,8 +952,9 @@ const handleClose = (done) => {
...props.data.properties, ...props.data.properties,
...filteredObj ...filteredObj
}); });
emitter.emit("setProperties", { id: props.data.id}); }
emitter.emit("setProperties", { id: props.data.id});
emits('close') emits('close')
} }
@ -525,7 +1038,7 @@ const visibleChange = (value, index, quote) => {
} }
}; };
const activeNames = ref(['1', '2']) const activeNames = ref(['1', '2', '3', '4'])
function hasErrors() { function hasErrors() {
const model = editorInstance.getModel(); const model = editorInstance.getModel();
@ -537,7 +1050,7 @@ function hasErrors() {
return markers.some(marker => marker.severity === monaco.MarkerSeverity.Error); return markers.some(marker => marker.severity === monaco.MarkerSeverity.Error);
} }
const initEditor = () => { const initEditor = (type) => {
monaco.languages.typescript.javascriptDefaults.setCompilerOptions({ monaco.languages.typescript.javascriptDefaults.setCompilerOptions({
target: monaco.languages.typescript.ScriptTarget.ES2020, target: monaco.languages.typescript.ScriptTarget.ES2020,
allowNonTsExtensions: true, // ts .js allowNonTsExtensions: true, // ts .js
@ -554,19 +1067,9 @@ const initEditor = () => {
noSyntaxValidation: false, // noSyntaxValidation: false, //
}); });
editorInstance = monaco.editor.create(codeRef.value, { editorInstance = monaco.editor.create(codeRef.value, {
value: [` value: ['{}'].join('\n'),
// language: type === 'code' ? 'javascript' : 'json',
function handler(params) {
// json dict object
const result ={
type: 2,
message: params.input
}
return result
}`].join('\n'),
language: 'javascript',
fontSize: 14, fontSize: 14,
lineNumbers: 'on', lineNumbers: 'on',
roundedSelection: true, roundedSelection: true,
@ -604,9 +1107,9 @@ const disposeEditor = () => {
watch( watch(
() => props.drawer, () => props.drawer,
(newVal) => { (newVal) => {
if (newVal && props.data.type === 'code') { if (newVal && ['code', 'http'].includes(props.data.type)) {
nextTick(() => { nextTick(() => {
initEditor(); initEditor(props.data.type);
}); });
} else { } else {
disposeEditor(); disposeEditor();
@ -614,12 +1117,62 @@ watch(
} }
); );
const httpMethodOptions = () => {
return [{
value: 'POST',
label: 'POST'
}, {
value: 'GET',
label: 'GET'
}, {
value: 'PUT',
label: 'PUT'
}, {
value: 'DELETE',
label: 'DELETE'
}, {
value: 'PATCH',
label: 'PATCH'
}]
}
const httpNodeData = ref({
config: [
{ name: "url", type: "input", input: "", disabled: true, required: true },
{ name: "method", type: "input", input: "POST", componentType: 'select', selectOptions: httpMethodOptions(), disabled: true },
{ name: "timeout", type: "input", input: "10000", componentType: 'number', disabled: true }
],
headers: [
{ name: "", type: "input", input: ""}
],
params: [
{ name: "", type: "input", input: ""}
],
body: {
type: 'json',
json: '{}',
formData: [
{ name: "", type: "input", input: ""}
]
},
timeout: 10000
});
const addHttpNodeFormItem = (e, type) => {
e.stopPropagation();
if (type === 'body') {
httpNodeData.value.body.formData.push({ name: "", type: "input", input: ""});
} else {
httpNodeData.value[type].push({ name: "", type: "input", input: ""});
}
};
watch( watch(
() => props.data, () => props.data,
() => { () => {
if (props.data.type === "start") { if (props.data.type === "start") {
formData.inputParams = props.data.properties?.inputParams || []; formData.inputParams = props.data.properties?.inputParams || [];
} else if (['currentLoop', 'serviceNode'].includes(props.data.type)) { } else if (['currentLoop', 'serviceNode', 'sleep', 'loop'].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;
} else if (props.data.type === "code") { } else if (props.data.type === "code") {
@ -627,12 +1180,14 @@ watch(
formData.outputParams = props.data.properties.outputParams; formData.outputParams = props.data.properties.outputParams;
const codeParams = props.data.properties.nodeParams.find(item => item.name === 'code') const codeParams = props.data.properties.nodeParams.find(item => item.name === 'code')
if (codeParams) { if (codeParams) {
nextTick(() => { setTimeout(() => {
if (editorInstance) { if (editorInstance) {
setValue(codeParams.input) setValue(codeParams.input)
} }
}) }, 100)
} }
} else if (props.data.type === 'http') {
} }
}, },
{ {
@ -641,7 +1196,6 @@ watch(
} }
); );
onUnmounted(() => { onUnmounted(() => {
disposeEditor() disposeEditor()
}) })
@ -651,6 +1205,14 @@ onUnmounted(() => {
margin-left: 12px; margin-left: 12px;
} }
.http-form {
width: 100%;
.el-collapse {
width: 100%;
}
}
.form__container { .form__container {
margin: 12px 0; margin: 12px 0;

View File

@ -171,6 +171,7 @@ export const collapseList = [
nodeParams: [ nodeParams: [
{ name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: deviceOptions(), disabled: true } { name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: deviceOptions(), disabled: true }
], ],
outputParams: [],
outputType: 'json' outputType: 'json'
}, },
{ {
@ -196,6 +197,7 @@ export const collapseList = [
nodeParams: [ nodeParams: [
{ name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: deviceOptions(), disabled: true } { name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: deviceOptions(), disabled: true }
], ],
outputParams: [],
outputType: 'json' outputType: 'json'
}, },
], ],
@ -213,6 +215,7 @@ export const collapseList = [
nodeParams: [ nodeParams: [
{ name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: deviceOptions(), disabled: true } { name: "deviceId", type: "input", input: "", componentType: 'select', selectOptions: deviceOptions(), disabled: true }
], ],
outputParams: [],
outputType: 'json' outputType: 'json'
}, },
{ {
@ -239,6 +242,9 @@ export const collapseList = [
type: "loop", type: "loop",
action: 'START_LOOP', action: 'START_LOOP',
desc: "实现对列表对象循环执行一系列任务", desc: "实现对列表对象循环执行一系列任务",
nodeParams: [{ name: "loopNum", type: "input", input: null, quote: "" }],
outputParams: [],
outputType: 'json'
}, },
{ {
icon: stopLoopSvg, icon: stopLoopSvg,
@ -275,8 +281,9 @@ export const collapseList = [
desc: "用于睡眠整个流程,表示延迟多少毫秒", desc: "用于睡眠整个流程,表示延迟多少毫秒",
action: 'sleep', action: 'sleep',
nodeParams: [ nodeParams: [
{ name: "delayMs", type: "input", componentType: 'number', input: "", disabled: true } { name: "delayMs", type: "input", componentType: 'number', input: 0, disabled: true }
], ],
outputParams: [],
outputType: 'json' outputType: 'json'
}, },
{ {
@ -303,7 +310,18 @@ export const collapseList = [
action: 'CODE', action: 'CODE',
canAddFormItem: true, canAddFormItem: true,
nodeParams: [ nodeParams: [
{ name: "input", type: "input", input: "", required: true } { name: "input", type: "input", input: "", required: true },
{ name: "code", type: "input", input: `
// 方法定义不能修改
function handler(params) {
// 返回值是一个可序列化成 json 的 dict 或 object
const result ={
type: 2,
message: params.input
}
return result
}
`, required: true }
], ],
outputType: 'json', outputType: 'json',
outputParams: [{ name: 'result', type: 'object', children: [ outputParams: [{ name: 'result', type: 'object', children: [

View File

@ -145,14 +145,14 @@ class HttpNodeHtmlModel extends HtmlNodeModel {
} }
// 验证表单 // 验证表单
async validateForm() { // async validateForm() {
const result = this.componentInstance.validateForm() // const result = this.componentInstance.validateForm()
return result // return result
} // }
setCustomProperties() { // setCustomProperties() {
this.componentInstance.setNodeProperties() // this.componentInstance.setNodeProperties()
} // }
/** /**
* 设置节点属性 * 设置节点属性

View File

@ -44,188 +44,16 @@
@zoom="zoom" @zoom="zoom"
@setNodeName="setNodeName" @setNodeName="setNodeName"
/> />
<div class="input__container" v-show="nodeZoom">
<div class="title">
<div class="left">
<div class="tag"></div>
<div class="text">输入</div>
</div>
<div class="right" v-if="properties.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>
<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>
<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, reactive, 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 "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";
const props = defineProps({ const props = defineProps({
model: Object, model: Object,
@ -234,70 +62,6 @@ const props = defineProps({
const emits = defineEmits(["contentChange"]); const emits = defineEmits(["contentChange"]);
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 (result && flag) {
const data = toRaw(formData);
const properties = lf.getProperties(props.model.id);
lf.setProperties(props.model.id, {
...properties,
...data,
zoom: nodeZoom.value
});
emits("contentChange");
}
} catch {
// 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, {
@ -312,115 +76,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: "",
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) {
formData.nodeParams = props.properties.nodeParams;
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; //
}
};
onMounted(() => { onMounted(() => {
emitter.on("changeNodeState", (data) => { emitter.on("changeNodeState", (data) => {
@ -450,16 +105,18 @@ onMounted(() => {
emits("contentChange"); emits("contentChange");
} }
}); });
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");
}); emitter.off("setProperties");
defineExpose({
validateForm,
setNodeProperties,
}); });
</script> </script>
@ -467,151 +124,5 @@ defineExpose({
.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;
}
}
}
.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>

View File

@ -21,21 +21,21 @@ class loopBodyModel extends GroupNodeModel {
} }
// 验证表单 // 验证表单
async validateForm() { // async validateForm() {
if (this.componentInstance && this.componentInstance.validate) { // if (this.componentInstance && this.componentInstance.validate) {
try { // try {
const result = await this.componentInstance.validate(); // const result = await this.componentInstance.validate();
if (result) { // if (result) {
return { valid: true, message: "验证通过" }; // return { valid: true, message: "验证通过" };
} else { // } else {
return { valid: false, message: "验证失败" }; // return { valid: false, message: "验证失败" };
} // }
} catch { // } catch {
return { valid: false, message: "验证失败" }; // return { valid: false, message: "验证失败" };
} // }
} // }
return { valid: true, message: "无验证方法" }; // return { valid: true, message: "无验证方法" };
} // }
// 计算子节点的包围盒(包含所有子节点的最小矩形区域) // 计算子节点的包围盒(包含所有子节点的最小矩形区域)
getChildrenBBox() { getChildrenBBox() {
@ -59,8 +59,8 @@ class loopBodyModel extends GroupNodeModel {
minY = Math.min(minY, node.y - height / 2); minY = Math.min(minY, node.y - height / 2);
maxY = Math.max(maxY, node.y + height / 2); maxY = Math.max(maxY, node.y + height / 2);
}); });
this.width = maxX - minX + 300; // 增加内边距 this.width = maxX - minX + 50; // 增加内边距
this.height = maxY - minY + 260; this.height = maxY - minY + 50;
this.x = (minX + maxX) / 2; this.x = (minX + maxX) / 2;
this.y = (minY + maxY) / 2 - 120; this.y = (minY + maxY) / 2 - 120;
const properties = lf.getProperties(this.id) const properties = lf.getProperties(this.id)
@ -101,14 +101,14 @@ class loopBodyModel extends GroupNodeModel {
return [ return [
{ {
x: x + width / 2 , x: x + width / 2 ,
y: y - 25, y: y + height / 2,
name: "right", name: "right",
id: "".concat(this.id, "_1"), id: "".concat(this.id, "_1"),
properties: { connectionType: "source" }, properties: { connectionType: "source" },
}, },
{ {
x: x - width / 2, x: x - width / 2,
y: y - 25, y: y + height / 2,
name: "left", name: "left",
id: "".concat(this.id, "_3"), id: "".concat(this.id, "_3"),
properties: { connectionType: "target" }, properties: { connectionType: "target" },

View File

@ -9,106 +9,6 @@
:showZoom="false" :showZoom="false"
@setNodeName="setNodeName" @setNodeName="setNodeName"
/> />
<div class="loop__container">
<div>
<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>
<el-form-item
:label="index === 0 ? '参数名' : ''"
:prop="`nodeParams.${index}.name`"
:rules="[
{
required: true,
message: '请输入参数名',
trigger: 'blur',
},
]"
>
<el-input
:disabled="index === 0"
class="param-name"
v-model="property.name"
placeholder="请输入"
clearable
/>
</el-form-item>
<el-form-item
:label="index === 0 ? '参数值' : ''"
:prop="`nodeParams.${index}.type`"
>
<el-select
class="param-type"
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: true,
message: '请输入参数值',
trigger: 'blur',
},
]"
:prop="`nodeParams.${index}.input`"
>
<el-input-number
class="param-value"
v-model="property.input"
:min="0"
:controls="false"
:step-strictly="true"
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
class="param-value"
: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>
<div>循环体</div> <div>循环体</div>
<div <div
class="child__container" class="child__container"
@ -120,11 +20,10 @@
</div> </div>
</template> </template>
<script setup> <script setup>
import { ref, onMounted } from "vue"; import { onMounted } from "vue";
import NodeTitle from "../../components/NodeTitle.vue"; import NodeTitle from "../../components/NodeTitle.vue";
import loop from "../../icon/loop.svg"; import loop from "../../icon/loop.svg";
import { useFlowStore } from "@/store/modules/flow"; import { addNewEdge } from "@/utils/flow";
import { addNewEdge, getInput } from "@/utils/flow";
const props = defineProps({ const props = defineProps({
model: Object, model: Object,
@ -133,25 +32,6 @@ const props = defineProps({
nowTime: Number, nowTime: Number,
}); });
const flowStore = useFlowStore();
const formData = reactive({
nodeParams: [{ name: "loopNum", type: "input", input: null, quote: "" }],
});
const quoteOptions = ref([]);
const handleTypeChange = (index) => {
if (formData.nodeParams[index].type === "input") {
formData.nodeParams[index].quote = "";
} else {
formData.nodeParams[index].input = null;
const option = getInput(props.model.id);
if (option) {
quoteOptions.value = option;
}
}
};
const rules = reactive({});
const emits = defineEmits(["bindRef", "addToGroup", "contentChange"]); const emits = defineEmits(["bindRef", "addToGroup", "contentChange"]);
@ -183,26 +63,6 @@ const handleDrop = (e) => {
}, 50); }, 50);
}; };
const dynamicForm = ref();
const setNodeProperties = async () => {
try {
const valid = await dynamicForm.value.validate();
if (valid) {
const data = toRaw(formData);
setTimeout(() => {
const properties = lf.getProperties(props.model.id);
window.lf.setProperties(props.model.id, {
...properties,
...data,
});
}, 50);
}
} catch (error) {
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, {
@ -212,60 +72,6 @@ const setNodeName = (name) => {
emits("contentChange"); emits("contentChange");
} }
const visibleChange = async (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(() => {
console.log(1247);
formData.nodeParams[index].quote = currentValue;
}, 0);
}
//
// cascaderRefs.value[index].updatePopper();
}, 0);
}
}
}
};
const cascaderRefs = ref([]);
const cascaderChange = (value, index) => {
const selectedOptions = cascaderRefs.value[index].getCheckedNodes(true);
formData.nodeParams[index].quote = value;
formData.nodeParams[index].quoteType = selectedOptions[0].data.type;
};
watch(
() => props.properties,
() => {
if (props.properties.nodeParams && props.properties.nodeParams.length > 0) {
formData.nodeParams = props.properties.nodeParams;
const option = getInput(props.model.id);
if (option) {
quoteOptions.value = option;
}
}
},
{
immediate: true,
deep: true,
}
);
onMounted(() => {
emits("bindRef", dynamicForm.value);
});
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.group__container { .group__container {
@ -286,30 +92,6 @@ onMounted(() => {
height: 40px; height: 40px;
} }
:deep(.loop__container) {
height: auto;
.el-row {
align-items: end;
.el-form-item {
margin-right: 12px;
}
.param-name {
width: 120px;
}
.param-type {
width: 80px;
}
.param-desc {
width: 120px;
}
}
}
.child__container { .child__container {
min-height: 100px; min-height: 100px;
flex: 1; flex: 1;

View File

@ -29,116 +29,6 @@
@zoom="zoom" @zoom="zoom"
@setNodeName="setNodeName" @setNodeName="setNodeName"
/> />
<div class="input__container" v-show="nodeZoom">
<div class="title">
<div class="left">
<div class="tag"></div>
<div class="text">输入</div>
</div>
<div class="right" v-if="properties.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" v-show="nodeZoom">
<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>
<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"
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: 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 />
<el-select v-model="property.input" v-else-if="property.componentType === 'select'" >
<el-option v-for="item in property.selectOptions" :key="item" :label="item" :value="item" />
</el-select>
<el-input 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"
:options="quoteOptions"
placeholder="请选择"
@visible-change="visibleChange"
@change="(value) => cascaderChange(value, index)"
/>
</el-form-item>
</el-form-item>
</el-row>
</div>
</el-form>
</div>
</div>
<div class="output__container" v-if="props.properties?.outputParams?.length > 0">
<div class="title">
<div class="tag"></div>
<div class="text">输出</div>
</div>
<div class="form__container">
<el-form
:inline="true"
label-position="top"
label-width="auto"
:disabled="true"
>
<el-row v-for="(item, index) in props.properties.outputParams">
<el-form-item label="参数名" >
<el-input :value="item.name" />
</el-form-item>
<el-form-item label="参数类型" >
<el-input :value="item.type" />
</el-form-item>
<el-form-item label="描述" >
<el-input :value="item.desc" />
</el-form-item>
</el-row>
</el-form>
</div>
</div>
</div> </div>
</template> </template>
@ -146,7 +36,6 @@
import { ref, reactive, onMounted, onUnmounted, nextTick } from "vue"; import { ref, reactive, onMounted, onUnmounted, nextTick } from "vue";
import { getInput, initNodeZoom } from "@/utils/flow"; import { getInput, initNodeZoom } from "@/utils/flow";
import { useFlowStore } from "@/store/modules/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 "vue3-json-viewer/dist/index.css";
@ -160,46 +49,10 @@ const props = defineProps({
const emits = defineEmits(["contentChange"]); const emits = defineEmits(["contentChange"]);
const flowStore = useFlowStore();
const formData = reactive({ const formData = reactive({
nodeParams: [], nodeParams: [],
}); });
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 dynamicForm = ref();
const setNodeProperties = async () => {
try {
const result = await dynamicForm.value.validate();
if (result) {
const data = toRaw(formData);
const properties = lf.getProperties(props.model.id);
lf.setProperties(props.model.id, {
...properties,
...data,
zoom: nodeZoom.value
});
emits("contentChange");
}
} catch {
dynamicForm.value.clearValidate();
}
};
const setNodeName = (name) => { const setNodeName = (name) => {
const properties = lf.getProperties(props.model.id); const properties = lf.getProperties(props.model.id);
@ -214,72 +67,6 @@ const nodeOperatingStatus = ref("NORMAL");
const inputJsonData = ref({}); const inputJsonData = ref({});
const outputJsonData = ref({}); const outputJsonData = ref({});
const visibleChange = (value) => {
if (value) {
const option = getInput(props.model.id);
if (option) {
quoteOptions.value = option;
}
}
};
const cascaderRefs = ref([]);
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 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: "",
desc: "",
required: false,
children: [],
});
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) {
formData.nodeParams = props.properties.nodeParams;
const option = getInput(props.model.id);
if (option) {
quoteOptions.value = option;
}
nextTick(() => {
initNodeZoom(props.model.id, props?.properties?.zoom ?? true, '.node__box', true)
})
}
},
{
immediate: true,
deep: true,
}
);
onMounted(() => { onMounted(() => {
emitter.on("changeNodeState", (data) => { emitter.on("changeNodeState", (data) => {
if (data.nodeId === props.model.id) { if (data.nodeId === props.model.id) {
@ -307,154 +94,25 @@ onMounted(() => {
emits("contentChange"); emits("contentChange");
} }
}); });
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");
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;
}
}
}
.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;
.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;
}
}
.form__container {
margin: 12px 0;
}
}
:deep(.el-row) {
align-items: end;
.el-input {
--el-input-width: 170px;
}
.el-select {
--el-select-width: 170px;
}
.el-cascader {
--el-form-inline-content-width: 170px;
}
}
} }
</style> </style>