Files
flow-center-front/src/views/flow/admin/def-editor/components/ConditionBox.vue
T
chenxing f0ec500348 B3-1 编辑器右信息面板 + 校验清单定位(先加面板,保存链路不动)
- index.vue:画布与面板包进 .ed-body(flex row),新增 aside.ed-info 五分区——
  流程信息(名称/编码/归属应用,与画布同源同一 ref,编码沿用 !!editId||!!baseGuid 禁用)/
  状态与版本(状态 Tag + v 版本号 + flowGuid 等宽 + 复制,新建态显示「保存后生成」)/
  校验问题清单(错误在前警告在后,项内 path+msg)/ 结构统计(walkItems 递归四类计数)/
  快捷入口(流程设置/源码/图示预览,:disabled=!editId 同现状)
- sourceFlowGuid ref:load() 里 defGet 后取 d.flowGuid,新建态空串
- locateIssue:清单项点击 scrollIntoView 居中 + .nd-locate 高亮 2.8s;三张卡片根元素加 :id="'nd-'+node.uid",
  uid 为空的清单项 class=fix 不可点;高亮样式走 :deep(卡片在子组件内)
- 画布左对齐:.ed-canvas align-items center→flex-start

验证:npm run build 通过;npx tsc --noEmit 48 条=基线无新增;convert 往返 ALL PASS
2026-10-12 16:19:51 +08:00

234 lines
7.0 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
// 条件分支容器(2026-09-24):紫系区分并行橙;与 ParallelBox 的本质差异——无 JOIN 语义,
// 分支按出口名互斥选路(运行时调判断服务/表达式求值),分支尾隐式汇合到容器后继。
// 分支头部的出口名 Input 即 contentJson 里 CONDITION 命名出边的 name(判断接口 outlets 契约);
// EXPRESSION 模式(2026-10-10)下分支头部再就地编辑出边 condition,序号即求值优先级。
import { computed, inject } from "vue";
import { BRANCH_LABELS, EDITOR_CTX, MAX_BRANCHES, MIN_BRANCHES, ConditionNode } from "../model";
import NodeList from "./NodeList.vue";
const props = defineProps<{ node: ConditionNode }>();
const ctx = inject(EDITOR_CTX)!;
const issues = computed(() => ctx.issueOf(props.node.uid));
const hasError = computed(() => issues.value.some((i) => i.level === "error"));
const canRemoveBranch = computed(() => props.node.branches.length > MIN_BRANCHES);
const canAddBranch = computed(() => props.node.branches.length < MAX_BRANCHES);
const expression = computed(() => props.node.judgeMode === "EXPRESSION");
const routerLabel = computed(() => {
if (expression.value) return "表达式判断(按分支顺序取首个为真)";
if (!props.node.routerKey) return "判断服务:未选择";
const r = ctx.routers.find((x) => x.routerKey === props.node.routerKey);
return r ? `判断服务:${r.name}(${props.node.routerKey})` : `判断服务:${props.node.routerKey}`;
});
/** 出口名就地编辑(重复/空由校验提示;与 branches 下标一一对应) */
function renameOutlet(i: number, v: string): void {
props.node.outlets[i] = v;
}
/** 出口条件就地编辑(EXPRESSION 模式;与 outlets 下标一一对应) */
function editCondition(i: number, v: string): void {
props.node.conditions = props.node.conditions || [];
while (props.node.conditions.length <= i) props.node.conditions.push("");
props.node.conditions[i] = v;
}
</script>
<template>
<div :id="'nd-' + node.uid" class="condition-box" :class="{ error: hasError }">
<div class="cb-title" @click="ctx.openNode(node.uid)">
条件分支(按出口互斥选路)
<span class="cb-hint">(点击标题配置{{ expression ? "表达式" : "判断服务" }})</span>
</div>
<div class="cb-router">{{ routerLabel }}</div>
<div class="cb-cols">
<div v-for="(branch, i) in node.branches" :key="i" class="cb-col">
<div class="cb-col-head">
<span class="cb-col-no" :title="expression ? '表达式求值优先级' : undefined">
{{ BRANCH_LABELS[i] || i + 1 }}
</span>
<input
class="cb-outlet"
:value="node.outlets[i]"
placeholder="出口名(必填唯一)"
maxlength="32"
@input="renameOutlet(i, ($event.target as HTMLInputElement).value)"
/>
<Poptip
v-if="canRemoveBranch"
confirm
title="删除该分支及其节点?"
ok-text="删除"
cancel-text="取消"
transfer
@on-ok="ctx.removeBranch(node.uid, i)"
>
<span class="cb-col-del" title="删除分支">×</span>
</Poptip>
</div>
<input
v-if="expression"
class="cb-cond"
:value="(node.conditions || [])[i] || ''"
placeholder="条件表达式,如 amount > 1000"
maxlength="200"
@input="editCondition(i, ($event.target as HTMLInputElement).value)"
/>
<NodeList :items="branch" :container="`${node.uid}:${i}`" />
<div v-if="!branch.length" class="cb-empty">该出口直达汇合点(直接通过)</div>
</div>
</div>
<Button v-if="canAddBranch" class="cb-add" size="small" long @click="ctx.addBranch(node.uid)">
+ 添加分支
</Button>
<div v-for="(issue, k) in issues" :key="k" class="cb-issue" :class="issue.level">⚠ {{ issue.text }}</div>
</div>
</template>
<style scoped>
.condition-box {
border: 1.5px dashed #b37feb;
border-radius: 10px;
padding: 14px 14px 12px;
background: rgba(249, 240, 255, 0.45);
}
.condition-box.error {
border-color: #ed4014;
}
.cb-title {
text-align: center;
color: #722ed1;
font-weight: 600;
font-size: 13px;
cursor: pointer;
}
.cb-title:hover {
text-decoration: underline;
}
.cb-hint {
font-weight: 400;
font-size: 11px;
color: #b37feb;
}
.cb-router {
text-align: center;
font-size: 12px;
color: #9254de;
margin: 6px 0 10px;
}
.cb-empty {
margin: 4px 0 8px;
padding: 8px 6px;
border: 1px dashed #d3c6e8;
border-radius: 6px;
text-align: center;
font-size: 11px;
color: #9254de;
background: rgba(255, 255, 255, 0.6);
}
.cb-cols {
display: flex;
gap: 14px;
align-items: flex-start;
justify-content: center;
}
.cb-col {
background: #fff;
border-radius: 8px;
padding: 12px 10px 14px;
min-width: 230px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
display: flex;
flex-direction: column;
align-items: center;
}
.cb-col-head {
display: flex;
align-items: center;
gap: 6px;
width: 100%;
margin-bottom: 8px;
padding: 0 2px;
}
.cb-col-no {
flex: none;
color: #9254de;
font-weight: 600;
font-size: 13px;
}
.cb-outlet {
flex: 1;
min-width: 0;
border: 1px solid #e3e5ee;
border-radius: 6px;
padding: 2px 8px;
font-size: 12px;
color: #722ed1;
font-weight: 600;
}
.cb-outlet:focus {
outline: none;
border-color: #9254de;
}
.cb-cond {
width: 100%;
box-sizing: border-box;
border: 1px dashed #d3b8f9;
border-radius: 6px;
padding: 2px 8px;
font-size: 12px;
color: #5b3fa8;
font-family: Consolas, Monaco, monospace;
margin-bottom: 8px;
}
.cb-cond:focus {
outline: none;
border-color: #9254de;
border-style: solid;
}
.cb-cond::placeholder {
color: #c5b3e8;
}
.cb-col-del {
flex: none;
cursor: pointer;
color: #c5c9d1;
font-size: 16px;
line-height: 1;
}
.cb-col-del:hover {
color: #ed4014;
}
.cb-add {
margin-top: 12px;
border-style: dashed;
color: #9254de;
border-color: #9254de;
background: transparent;
width: auto;
padding: 0 16px;
display: block;
margin-left: auto;
margin-right: auto;
}
.cb-add:hover {
background: #f9f0ff;
color: #722ed1;
border-color: #9254de;
}
.cb-issue {
text-align: center;
font-size: 12px;
padding-top: 8px;
}
.cb-issue.error {
color: #ed4014;
}
.cb-issue.warn {
color: #ff9900;
}
</style>