From 4ae51ccc93fee3ee142af0594a3589d2160d68df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=94=90=E4=BA=9A=E5=B3=B0=20=7C=20battcn?= <1837307557@qq.com> Date: Mon, 20 Jul 2026 11:12:36 +0800 Subject: [PATCH 1/3] =?UTF-8?q?fix:=20=E6=9A=97=E9=BB=91=E4=B8=BB=E9=A2=98?= =?UTF-8?q?=E5=8F=B3=E9=94=AE=E8=8F=9C=E5=8D=95=E7=99=BD=E5=BA=95=E7=99=BD?= =?UTF-8?q?=E5=AD=97=E4=B8=8D=E5=8F=AF=E8=A7=81=E4=B8=8E=20setAttribute('0?= =?UTF-8?q?')=20=E6=B8=B2=E6=9F=93=E5=B4=A9=E6=BA=83=EF=BC=88gitee=20#IJVB?= =?UTF-8?q?TJ=20#IJV7GC=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - .lf-menu 亮/暗两态显式着色:warm-flow-ui index.scss 补 html.dark 适配,设计器走 --wf-* token 自动翻转 - baseInfo 声明 defineEmits 并对 formPath/category 做数组转字符串防护(tree-select 旧内核回传数组的根因侧修复) - warm-flow-ui main.js 注入 setAttribute 非法属性名兜底,防旧 Chromium 白屏(对齐 PR #393,不采纳其依赖升级) - antd 适配器 TreeSelect 仅多选场景映射 treeCheckStrictly,消除 value should be an array 告警 Co-authored-by: Cursor --- warm-flow-ui/src/assets/styles/index.scss | 20 ++++++++++++++++ .../components/design/common/vue/baseInfo.vue | 23 +++++++++++++++++-- warm-flow-ui/src/main.js | 21 +++++++++++++++++ .../src/components/design/FlowDesigner.vue | 22 ++++++++++++++++++ .../components/design/common/vue/baseInfo.vue | 15 ++++++++++++ warm-flow-vue-designer/src/ui/antdvAdapter.ts | 8 ++++++- 6 files changed, 106 insertions(+), 3 deletions(-) diff --git a/warm-flow-ui/src/assets/styles/index.scss b/warm-flow-ui/src/assets/styles/index.scss index edd06472..7d3150d6 100644 --- a/warm-flow-ui/src/assets/styles/index.scss +++ b/warm-flow-ui/src/assets/styles/index.scss @@ -571,6 +571,26 @@ html.dark { } } + /* --- LogicFlow Menu 节点/边右键菜单暗黑适配(LF 默认白底不设文字色,暗黑下继承浅色文字导致白底白字不可见) --- */ + .lf-menu { + background: var(--wf-bg-white, #1f1f1f); + border: 1px solid var(--wf-border-color, #333333); + color: var(--wf-text-primary, #e0e0e0); + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.45); + + .lf-menu-item:hover { + background: var(--wf-bg-color, #141414); + } + + .lf-menu-item__disabled { + color: var(--wf-text-secondary, #808080); + + &:hover { + background: transparent; + } + } + } + /* --- 全局滚动条暗黑适配 --- */ ::-webkit-scrollbar-thumb { background: #4a4a4a; diff --git a/warm-flow-ui/src/components/design/common/vue/baseInfo.vue b/warm-flow-ui/src/components/design/common/vue/baseInfo.vue index 849780d6..e94c74b1 100644 --- a/warm-flow-ui/src/components/design/common/vue/baseInfo.vue +++ b/warm-flow-ui/src/components/design/common/vue/baseInfo.vue @@ -131,6 +131,10 @@ import { ref, onMounted, onUnmounted, watch } from "vue"; import {listenerList} from "@/api/flow/definition.js"; +// 显式声明 emits:未声明时 Vue3 会把事件当作 attrs 透传,部分浏览器下引发 +// setAttribute 无效属性名异常(gitee #IJV7GC / PR #393 根因之一) +const emit = defineEmits(['update:model-value', 'update:flow-name']); + const { proxy } = getCurrentInstance(); // 响应式屏幕检测 @@ -199,6 +203,7 @@ const form = ref({ watch(() => props.logicJson, newValue => { if (newValue && Object.keys(newValue).length > 0) { Object.assign(form.value, newValue); + normalizeStringFields(); setListenerData(); // 移动端新增时强制默认仿钉钉(覆盖logicJson中可能为空的值) if (isMobile.value && !props.definitionId) { @@ -235,6 +240,19 @@ const rules = { // 表单引用(用于校验) const formRef = ref(); +/** + * formPath / category 必须是字符串:el-tree-select 在部分(旧内核)浏览器下 v-model 可能回传数组, + * 数组渗入后会被下游组件展开成 DOM 属性,触发 setAttribute('0') 异常白屏(gitee #IJV7GC / PR #393) + */ +function normalizeStringFields() { + if (Array.isArray(form.value.formPath)) { + form.value.formPath = form.value.formPath[0] || ''; + } + if (Array.isArray(form.value.category)) { + form.value.category = form.value.category[0] || ''; + } +} + function setListenerData() { // 处理监听器数据 if (form.value.listenerType) { @@ -277,16 +295,17 @@ function validate() { function nameChange(flowName) { // 可以在这里添加额外的逻辑,比如验证或格式化 - proxy.$emit('update:flow-name', flowName); // 如果需要通知父组件 + emit('update:flow-name', flowName); // 如果需要通知父组件 } function modelValueChange() { - proxy.$emit('update:model-value'); // 如果需要通知父组件 + emit('update:model-value'); // 如果需要通知父组件 } function getFormData() { form.value.listenerType = form.value.listenerRows.map(row => row.listenerType).join(",") form.value.listenerPath = form.value.listenerRows.map(row => row.listenerPath).join("@@") + normalizeStringFields(); return form.value; } diff --git a/warm-flow-ui/src/main.js b/warm-flow-ui/src/main.js index da3fd477..e077580e 100644 --- a/warm-flow-ui/src/main.js +++ b/warm-flow-ui/src/main.js @@ -1,3 +1,24 @@ +/** + * 浏览器兼容兜底:拦截无效属性名的 setAttribute 调用,防止 DOMException 白屏(gitee #IJV7GC / PR #393)。 + * 当第三方组件把数组展开为 DOM 属性时(如 v-bind 收到数组),Vue 会尝试 setAttribute('0', value), + * 属性名以数字开头不符合 XML/HTML 规范,钉钉内嵌等旧 Chromium 内核浏览器会直接抛异常导致设计器崩溃。 + * 这里跳过该类调用并告警,作为最后一道防线(根因侧修复见 baseInfo.vue 的 emits 声明与数组防护)。 + */ +(function patchSetAttribute() { + const nativeSetAttr = Element.prototype.setAttribute; + Element.prototype.setAttribute = function (name, value) { + if (typeof name === 'string' && /^\d/.test(name)) { + console.warn( + '[Warm-Flow compat] Blocked invalid setAttribute("' + name + '", ...) on <' + + (this.tagName || 'unknown').toLowerCase() + + '>. Likely an array spread into DOM attrs by a third-party component.' + ); + return; + } + return nativeSetAttr.call(this, name, value); + }; +})(); + import { createApp } from 'vue' import FcDesigner from '@form-create/designer'; diff --git a/warm-flow-vue-designer/src/components/design/FlowDesigner.vue b/warm-flow-vue-designer/src/components/design/FlowDesigner.vue index 770a8709..f05d15ce 100644 --- a/warm-flow-vue-designer/src/components/design/FlowDesigner.vue +++ b/warm-flow-vue-designer/src/components/design/FlowDesigner.vue @@ -776,6 +776,28 @@ html.dark .container { border: 1px solid var(--wf-border-color) !important; } +/* ========== LogicFlow 节点/边右键菜单(Menu 扩展) ========== + LF 默认样式白底且不设文字色:暗黑下页面继承浅色文字 → 白底白字不可见(gitee #IJVBTJ)。 + 这里统一走 --wf-* token(html.dark 自动翻转),亮暗两态均显式着色。 */ +.lf-menu { + background: var(--wf-bg-white, #fff); + border: 1px solid var(--wf-border-light, #efefee); + color: var(--wf-text-primary, #303133); + box-shadow: var(--wf-shadow-lg, 0 4px 16px rgba(0, 0, 0, 0.1)); +} + +.lf-menu .lf-menu-item:hover { + background: var(--wf-bg-color, #f3f3f3); +} + +.lf-menu .lf-menu-item__disabled { + color: var(--wf-text-placeholder, #aaa); +} + +.lf-menu .lf-menu-item__disabled:hover { + background: transparent; +} + /* ========== 加载态 / 空态覆盖层 ========== */ .wf-state-overlay { position: absolute; diff --git a/warm-flow-vue-designer/src/components/design/common/vue/baseInfo.vue b/warm-flow-vue-designer/src/components/design/common/vue/baseInfo.vue index 28d14d4f..ba7b2288 100644 --- a/warm-flow-vue-designer/src/components/design/common/vue/baseInfo.vue +++ b/warm-flow-vue-designer/src/components/design/common/vue/baseInfo.vue @@ -202,6 +202,7 @@ watch(() => props.logicJson, newValue => { if (!form.value.formCustom) { form.value.formCustom = "N"; } + normalizeStringFields(); setListenerData(); // 移动端新增时强制默认仿钉钉(覆盖logicJson中可能为空的值) if (isMobile.value && !props.definitionId) { @@ -238,6 +239,19 @@ const rules = computed(() => ({ // 表单引用(用于校验) const formRef = ref(); +/** + * formPath / category 必须是字符串:tree-select 在部分(旧内核)浏览器下 v-model 可能回传数组, + * 数组渗入后会被下游组件展开成 DOM 属性,触发 setAttribute('0') 异常白屏(gitee #IJV7GC / PR #393) + */ +function normalizeStringFields() { + if (Array.isArray(form.value.formPath)) { + form.value.formPath = (form.value.formPath[0] as string) || ''; + } + if (Array.isArray(form.value.category)) { + form.value.category = (form.value.category[0] as string) || ''; + } +} + function setListenerData() { // 处理监听器数据 if (form.value.listenerType) { @@ -293,6 +307,7 @@ function modelValueChange() { function getFormData() { form.value.listenerType = form.value.listenerRows.map(row => row.listenerType).join(",") form.value.listenerPath = form.value.listenerRows.map(row => row.listenerPath).join("@@") + normalizeStringFields(); return form.value; } diff --git a/warm-flow-vue-designer/src/ui/antdvAdapter.ts b/warm-flow-vue-designer/src/ui/antdvAdapter.ts index 4c7ee9f6..e9162255 100644 --- a/warm-flow-vue-designer/src/ui/antdvAdapter.ts +++ b/warm-flow-vue-designer/src/ui/antdvAdapter.ts @@ -288,7 +288,13 @@ const AntCheckbox = vModelComp('WfAntCheckbox', ACheckbox, 'checked', (props, ra const AntTreeSelect = vModelComp('WfAntTreeSelect', ATreeSelect, 'value', (props, raw) => { if (raw.data != null) { props.treeData = raw.data; delete props.data } if (raw.props != null) { props.fieldNames = raw.props; delete props.props } - if (raw['check-strictly'] != null) { props.treeCheckStrictly = raw['check-strictly']; delete props['check-strictly'] } + if (raw['check-strictly'] != null) { + // EP 的 check-strictly(父子不联动、任意节点可选)只在勾选(多选)模式下才对应 antd 的 + // treeCheckStrictly;单选 TreeSelect 本就任意节点可选,若误传 treeCheckStrictly 会被 antd + // 判定为 checkable 模式,进而告警「value should be an array」且值形态错乱。 + if (raw.multiple != null || raw.treeCheckable != null) props.treeCheckStrictly = raw['check-strictly'] + delete props['check-strictly'] + } delete props['value-key'] // antd TreeSelect 不接受空串作为 value,空值统一为 undefined if (props.value === '' || props.value == null) props.value = undefined -- Gitee From 603a1b00a30b5c930e5365551454535a65503021 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=94=90=E4=BA=9A=E5=B3=B0=20=7C=20battcn?= <1837307557@qq.com> Date: Mon, 20 Jul 2026 11:13:00 +0800 Subject: [PATCH 2/3] =?UTF-8?q?update(designer):=20=E4=BB=BF=E9=92=89?= =?UTF-8?q?=E9=92=89=E7=94=BB=E5=B8=83=E8=A7=86=E8=A7=89=E4=BC=98=E5=8C=96?= =?UTF-8?q?=20=E2=80=94=20=E5=8A=A0=E8=8A=82=E7=82=B9=E5=BC=B9=E5=B1=82/?= =?UTF-8?q?=E8=BE=B9+=E6=8C=89=E9=92=AE/=E7=BD=91=E5=85=B3=E8=83=B6?= =?UTF-8?q?=E5=9B=8A/=E6=9D=A1=E4=BB=B6=E5=BE=BD=E6=A0=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - EdgeTooltip 重设计:加「添加节点」小标题、四选项按类型语义色着色(审批蓝/互斥橙/并行青/包含紫)+ 一句描述 + hover 染色动效;样式全量下沉 --wf-* token,暗黑自动翻转、不再依赖 JS themeColors - 新增 addBetween/addSerial/addParallel/addInclusive 线性图标(currentColor 可着色)与 edgeTooltip 中英文案 - 边「+」按钮:底色描边环 + 圆角十字 + 投影 + hover 放大(.wf-edge-plus) - 网关「加节点」重绘为全圆角胶囊:图标 24px 垂直居中、文字随语义色半粗、汇聚图标居中、投影 + hover 微放大(.wf-gateway-chip) - 条件徽标:32px 实心方块改 31px 圆角 chip(渐变底 + 描边环 + 投影),viewBox 裁至图形边界消除素材留白导致的图形过小;直连/拐角分支统一下移避免贴住胶囊;运行态保留状态色不受影响 - 条件文字改 token 化圆角胶囊并与徽标保持间距 Co-authored-by: Cursor --- .../src/components/design/FlowDesigner.vue | 43 +++- .../design/mimic/js/gatewayModel.ts | 5 +- .../components/design/mimic/js/gatewayView.ts | 16 +- .../components/design/mimic/js/inclusive.ts | 23 +-- .../components/design/mimic/js/parallel.ts | 23 +-- .../src/components/design/mimic/js/serial.ts | 23 +-- .../src/components/design/mimic/js/skip.ts | 70 ++++--- .../design/mimic/vue/EdgeTooltip.vue | 183 +++++++++++++----- warm-flow-vue-designer/src/i18n/index.ts | 14 +- warm-flow-vue-designer/src/icons/wfIcons.ts | 21 ++ 10 files changed, 302 insertions(+), 119 deletions(-) diff --git a/warm-flow-vue-designer/src/components/design/FlowDesigner.vue b/warm-flow-vue-designer/src/components/design/FlowDesigner.vue index f05d15ce..6d078cf6 100644 --- a/warm-flow-vue-designer/src/components/design/FlowDesigner.vue +++ b/warm-flow-vue-designer/src/components/design/FlowDesigner.vue @@ -314,10 +314,12 @@ const tooltipPosition = ref({ x: 0, y: 0 }); const tooltipEdge = ref({}); const handleOptionClick = (item: any) => { - if (item.icon === "between") { + // item.type 为节点类型(between/serial/parallel/inclusive);item.icon 仅为弹层展示图标名 + const nodeType = item.type || item.icon; + if (nodeType === "between") { addBetweenNode(lf.value, item.tooltipEdge); } else { - addGatewayNode(lf.value, item.tooltipEdge, item.icon); + addGatewayNode(lf.value, item.tooltipEdge, nodeType); } tooltipVisible.value = false; // 移动端/平板端:新增节点后自适应画布,确保所有节点可见 @@ -798,6 +800,43 @@ html.dark .container { background: transparent; } +/* ========== 仿钉钉边「+」加节点按钮 / 条件徽标(SVG 组,类名由 mimic/js/skip.ts 注入) ========== + 悬浮放大 + 主色投影,让「可点击加节点」的示能更明显;transform-box 保证以按钮自身为中心缩放。 */ +.wf-edge-plus { + transform-box: fill-box; + transform-origin: center; + transition: transform 0.18s cubic-bezier(0.4, 0, 0.2, 1), filter 0.18s ease; + filter: drop-shadow(0 2px 6px rgba(64, 158, 255, 0.35)); +} + +.wf-edge-plus:hover { + transform: scale(1.18); + filter: drop-shadow(0 4px 10px rgba(64, 158, 255, 0.5)); +} + +.wf-edge-cond { + transform-box: fill-box; + transform-origin: center; + transition: transform 0.18s cubic-bezier(0.4, 0, 0.2, 1); +} + +.wf-edge-cond:hover { + transform: scale(1.08); +} + +/* 仿钉钉网关胶囊 chip(类名由 mimic/js/gatewayView.ts 注入):柔和投影 + hover 微放大 */ +.wf-gateway-chip { + transform-box: fill-box; + transform-origin: center; + transition: transform 0.18s cubic-bezier(0.4, 0, 0.2, 1), filter 0.18s ease; + filter: drop-shadow(0 2px 6px rgba(15, 23, 42, 0.10)); +} + +.wf-gateway-chip:hover { + transform: scale(1.06); + filter: drop-shadow(0 4px 10px rgba(15, 23, 42, 0.16)); +} + /* ========== 加载态 / 空态覆盖层 ========== */ .wf-state-overlay { position: absolute; diff --git a/warm-flow-vue-designer/src/components/design/mimic/js/gatewayModel.ts b/warm-flow-vue-designer/src/components/design/mimic/js/gatewayModel.ts index 2bf34124..0efc75e6 100644 --- a/warm-flow-vue-designer/src/components/design/mimic/js/gatewayModel.ts +++ b/warm-flow-vue-designer/src/components/design/mimic/js/gatewayModel.ts @@ -4,9 +4,10 @@ export class GatewayModel extends RectNodeModel { initNodeData(data) { super.initNodeData(data); - this.width = 70; + this.width = 76; this.height = 30; - this.radius = 5; + // 全圆角胶囊(height/2),替代原 radius=5 的方角矩形,贴近现代 chip 质感 + this.radius = 15; } getNodeStyle() { return setCommonStyle(super.getNodeStyle(), this.properties, "node", "mimic"); diff --git a/warm-flow-vue-designer/src/components/design/mimic/js/gatewayView.ts b/warm-flow-vue-designer/src/components/design/mimic/js/gatewayView.ts index e31cbacf..8d05329e 100644 --- a/warm-flow-vue-designer/src/components/design/mimic/js/gatewayView.ts +++ b/warm-flow-vue-designer/src/components/design/mimic/js/gatewayView.ts @@ -12,13 +12,14 @@ export abstract class GatewayView extends RectNode { // 创建一个包含图标和文本的容器 if (incomingEdges.length > 1) { + // 汇聚(结束)网关只有图标:水平垂直居中于胶囊(24px 保证小尺寸下依然清晰) return h( 'svg', { - x: x - width / 2 + 23, - y: y - height / 2 + 3, - width: 25, - height: 25, + x: x - 12, + y: y - 12, + width: 24, + height: 24, viewBox: '0 0 1024 1024', }, [ @@ -41,12 +42,14 @@ export abstract class GatewayView extends RectNode { return this.getSvg(x, y, width, height, text.value, style) } - // 自定义节点外观 + // 自定义节点外观:语义色胶囊 chip(阴影 / hover 放大见 FlowDesigner 全局样式 .wf-gateway-chip) getShape(): h.JSX.Element { const { model } = this.props; const { x, y, width, height, radius } = model; const style = model.getNodeStyle(); - return h('g', {style: { + return h('g', { + className: 'wf-gateway-chip', + style: { cursor: 'pointer' }}, [ h('rect', { @@ -57,6 +60,7 @@ export abstract class GatewayView extends RectNode { ry: radius, width, height, + 'stroke-width': 1.2, }), this.getLabelShape(), ]); diff --git a/warm-flow-vue-designer/src/components/design/mimic/js/inclusive.ts b/warm-flow-vue-designer/src/components/design/mimic/js/inclusive.ts index a0b4cfdd..c758fad3 100644 --- a/warm-flow-vue-designer/src/components/design/mimic/js/inclusive.ts +++ b/warm-flow-vue-designer/src/components/design/mimic/js/inclusive.ts @@ -9,7 +9,7 @@ class InclusiveModel extends GatewayModel { applyClassicDesignColor(style, this.properties, '146,84,222'); const inDesigner = typeof window !== 'undefined' && (window as any).__WF_FLOW_DESIGN_MODE__; if (inDesigner && typeof style._statusRgba === 'function') { - style.fill = style._statusRgba(0.12); + style.fill = style._statusRgba(0.15); } return style; } @@ -19,12 +19,12 @@ class InclusiveView extends GatewayView { getSvg(x: number, y: number, width: number, height: number, textValue: string, style: { stroke: string, fill: string }): h.JSX.Element { return h('g', {}, [ - // 图标 SVG + // 图标 SVG:垂直居中于胶囊,左内边距 9 h('svg', { - x: x - width / 2, - y: y - height / 2 + 3, - width: 25, - height: 25, + x: x - width / 2 + 8, + y: y - 12, + width: 24, + height: 24, viewBox: '0 0 1024 1024', }, [ // 单个输入边时的图标 @@ -45,14 +45,15 @@ class InclusiveView extends GatewayView { strokeWidth: 60, }), ]), - // 文本元素,相对于g元素定位 + // 文本元素:随类型语义色着色 + 半粗,与图标同基线垂直居中 textValue ? h('text', { - x: x - width / 2 + 27, - y: y - height / 2 + 20, - fontSize: 13, + x: x - width / 2 + 35, + y: y + 4.5, + fontSize: 12.5, style: { userSelect: 'none', - fill: 'var(--wf-text-primary, #303133)', + fill: style.stroke || '#409eff', + fontWeight: 600, } }, textValue) : null ]); diff --git a/warm-flow-vue-designer/src/components/design/mimic/js/parallel.ts b/warm-flow-vue-designer/src/components/design/mimic/js/parallel.ts index 37706500..67dc3518 100644 --- a/warm-flow-vue-designer/src/components/design/mimic/js/parallel.ts +++ b/warm-flow-vue-designer/src/components/design/mimic/js/parallel.ts @@ -10,7 +10,7 @@ class ParallelModel extends GatewayModel { applyClassicDesignColor(style, this.properties, '19,194,194'); const inDesigner = typeof window !== 'undefined' && (window as any).__WF_FLOW_DESIGN_MODE__; if (inDesigner && typeof style._statusRgba === 'function') { - style.fill = style._statusRgba(0.12); + style.fill = style._statusRgba(0.15); } return style; } @@ -21,12 +21,12 @@ class ParallelView extends GatewayView { getSvg(x: number, y: number, width: number, height: number, textValue: string, style: { stroke: string, fill: string }): h.JSX.Element { // 创建一个包含图标和文本的容器 return h('g', {}, [ - // 图标 SVG + // 图标 SVG:垂直居中于胶囊,左内边距 9 h('svg', { - x: x - width / 2, - y: y - height / 2 + 3, - width: 25, - height: 25, + x: x - width / 2 + 8, + y: y - 12, + width: 24, + height: 24, viewBox: '0 0 1024 1024', }, [ h('path', { @@ -42,14 +42,15 @@ class ParallelView extends GatewayView { d: 'M512 970.453333a66.56 66.56 0 0 1-47.146667-19.626666L72.96 558.933333a66.346667 66.346667 0 0 1 0-94.293333L464.853333 72.533333a68.266667 68.266667 0 0 1 94.293334 0l391.893333 392.106667a66.346667 66.346667 0 0 1 0 94.293333L559.146667 950.826667a66.56 66.56 0 0 1-47.146667 19.626666z m0-853.333333a2.986667 2.986667 0 0 0-1.92 0L118.186667 509.866667a2.56 2.56 0 0 0 0 3.626666l391.893333 392.106667a3.2 3.2 0 0 0 3.84 0l391.893333-392.106667a2.986667 2.986667 0 0 0 0-3.626666L513.92 117.76a2.986667 2.986667 0 0 0-1.92-0.64z', }), ]), - // 文本元素,相对于g元素定位 + // 文本元素:随类型语义色着色 + 半粗,与图标同基线垂直居中 textValue ? h('text', { - x: x - width / 2 + 27, - y: y - height / 2 + 20, - fontSize: 13, + x: x - width / 2 + 35, + y: y + 4.5, + fontSize: 12.5, style: { userSelect: 'none', - fill: 'var(--wf-text-primary, #303133)', + fill: style.stroke || '#409eff', + fontWeight: 600, } }, textValue) : null ]); diff --git a/warm-flow-vue-designer/src/components/design/mimic/js/serial.ts b/warm-flow-vue-designer/src/components/design/mimic/js/serial.ts index e9f6dcf4..0b0ab79a 100644 --- a/warm-flow-vue-designer/src/components/design/mimic/js/serial.ts +++ b/warm-flow-vue-designer/src/components/design/mimic/js/serial.ts @@ -9,7 +9,7 @@ class SerialModel extends GatewayModel { applyClassicDesignColor(style, this.properties, '245,158,11'); const inDesigner = typeof window !== 'undefined' && (window as any).__WF_FLOW_DESIGN_MODE__; if (inDesigner && typeof style._statusRgba === 'function') { - style.fill = style._statusRgba(0.12); + style.fill = style._statusRgba(0.15); } return style; } @@ -19,12 +19,12 @@ class SerialView extends GatewayView { getSvg(x: number, y: number, width: number, height: number, textValue: string, style: { stroke: string, fill: string }): h.JSX.Element { return h('g', {}, [ - // 图标 SVG + // 图标 SVG:垂直居中于胶囊,左内边距 9 h('svg', { - x: x - width / 2, - y: y - height / 2 + 3, - width: 25, - height: 25, + x: x - width / 2 + 8, + y: y - 12, + width: 24, + height: 24, viewBox: '0 0 1024 1024', }, [ // 单个输入边时的图标 @@ -41,14 +41,15 @@ class SerialView extends GatewayView { d: 'M554.666667 516.266667l107.946666-107.946667a30.506667 30.506667 0 0 0-42.666666-42.666667L512 473.173333l-107.946667-107.946666a30.506667 30.506667 0 0 0-42.666666 42.666666L469.333333 516.266667l-108.8 107.946666a30.506667 30.506667 0 1 0 42.666667 42.666667l108.8-107.306667 106.666667 107.733334a30.506667 30.506667 0 0 0 42.666666-42.666667z', }), ]), - // 文本元素,相对于g元素定位 + // 文本元素:随类型语义色着色 + 半粗,与图标同基线垂直居中 textValue ? h('text', { - x: x - width / 2 + 27, - y: y - height / 2 + 20, - fontSize: 13, + x: x - width / 2 + 35, + y: y + 4.5, + fontSize: 12.5, style: { userSelect: 'none', - fill: 'var(--wf-text-primary, #303133)', + fill: style.stroke || '#409eff', + fontWeight: 600, } }, textValue) : null ]); diff --git a/warm-flow-vue-designer/src/components/design/mimic/js/skip.ts b/warm-flow-vue-designer/src/components/design/mimic/js/skip.ts index 119abbb5..dd5fbb52 100644 --- a/warm-flow-vue-designer/src/components/design/mimic/js/skip.ts +++ b/warm-flow-vue-designer/src/components/design/mimic/js/skip.ts @@ -70,7 +70,8 @@ class SkipView extends CurvedEdge { let nextEdge = model.graphModel.edges.filter(edge => edge.sourceNodeId === model.sourceNode.id); // 如果上一个节点是互斥网关,并且网关后节点大于1个,也就是说是互斥网关结束节点时 if (['serial', 'inclusive'].includes(model.sourceNode.type as string) && nextEdge.length > 1) { - const midPoint = [points[0][0], points[0][1] + offsetY - 10]; + // +4 让徽标与网关胶囊留出呼吸间距,避免描边环贴住胶囊底边 + const midPoint = [points[0][0], points[0][1] + offsetY + 4]; plusElements = this.getForeignObject(midPoint, condColor, model.text.value); } else if (!model.properties.chartStatusColor) { const midPoint = [points[0][0], points[0][1] + offsetY]; @@ -89,7 +90,8 @@ class SkipView extends CurvedEdge { // 判断是否由横线变为竖线,并且是互斥网关 if (model.sourceNode && ['serial', 'inclusive'].includes(model.sourceNode.type as string) && p0[1] === p1[1] && p0[0] !== p1[0]) { - const midPoint = [p2[0], p1[1] + offsetY]; + // +4 与直连分支保持同一垂直落点,且徽标不压横向连线 + const midPoint = [p2[0], p1[1] + offsetY + 4]; plusElements = this.getForeignObject(midPoint, condColor, model.text.value); } } @@ -109,14 +111,18 @@ class SkipView extends CurvedEdge { } private getForeignObject(midPoint: number[], stroke: string, text: string) { + // 设计态用主蓝渐变(chroma 规范),运行态(chartStatusColor)保留纯状态色 + const isRuntime = !!this.props.model.properties.chartStatusColor; + const bg = isRuntime ? stroke : 'linear-gradient(135deg, #409eff 0%, #2b7de9 100%)'; + const shadow = isRuntime ? '0 2px 6px rgba(0, 0, 0, 0.18)' : '0 2px 6px rgba(64, 158, 255, 0.35)'; let elements: h.JSX.Element[] = []; elements = [ - // 使用 SVG 图标代替原来的图形 + // 条件设置徽标:圆角 chip + 底色描边环 + 投影(替代原 32px 实心大方块) h('foreignObject', { - x: midPoint[0] - 16, + x: midPoint[0] - 18, y: midPoint[1] - 20, - width: 32, - height: 32, + width: 36, + height: 36, }, [ h('div', { style: { @@ -127,11 +133,12 @@ class SkipView extends CurvedEdge { height: '100%' }, innerHTML: ` -
- - +
+ + + fill="#FFFFFF">
`, @@ -144,20 +151,25 @@ class SkipView extends CurvedEdge { // 由于我们无法直接在当前环境中测量文本,我们使用一个估算方法 // 通常每个字符大约占用 8-10 像素宽度(取决于字体) const charWidth = 8; // 每个字符的估计宽度 - const padding = 10; // 左右内边距 + const padding = 14; // 左右内边距 const minWidth = 40; // 最小宽度 const textWidth = Math.max(minWidth, text.length * charWidth + padding); - // 添加背景矩形 + // 条件文本 pill:走 --wf-* token(暗黑自动翻转);CSS 变量须放 style 而非 presentation 属性。 + // y 偏移与放大后的徽标(下沿 +16)保持间距,避免文字胶囊压住徽标描边环 elements.push( h('rect', { x: midPoint[0] - textWidth / 2, - y: midPoint[1] + 10, + y: midPoint[1] + 20, width: textWidth, height: 20, - fill: "#fff", // 背景颜色 - rx: 3, // 圆角 - ry: 3 + 'stroke-width': 1, + rx: 10, // 圆角胶囊 + ry: 10, + style: { + fill: 'var(--wf-bg-white, #fff)', + stroke: 'var(--wf-border-lighter, #ebeef5)', + } }) ); @@ -165,10 +177,10 @@ class SkipView extends CurvedEdge { elements.push( h('text', { x: midPoint[0], - y: midPoint[1] + 25, - fontSize: 13, - fill: "#000", + y: midPoint[1] + 34, + fontSize: 12, style: { + fill: 'var(--wf-text-regular, #606266)', userSelect: 'none', textAnchor: 'middle' // 文本居中对齐 } @@ -188,31 +200,38 @@ class SkipView extends CurvedEdge { } private getAddElements(midPoint: number[], obj, isCondition: boolean = false) { + const isPlus = !obj; if (!obj) { const x = midPoint[0] const y = midPoint[1] obj = [ + // 描边把按钮从连线上「抬起来」,配合 CSS 投影与 hover 放大(见 FlowDesigner 全局样式 .wf-edge-plus) + // 注意:CSS 变量在 SVG presentation 属性里无效,必须走 style(暗黑模式自动切换描边底色) h('circle', { cx: x, cy: y, r: 13, fill: '#409eff', + 'stroke-width': '2', + style: { stroke: 'var(--wf-bg-page, #fff)' }, }), h('line', { - x1: x - 8, + x1: x - 6.5, y1: y, - x2: x + 8, + x2: x + 6.5, y2: y, stroke: 'white', - 'stroke-width': '2', + 'stroke-width': '2.2', + 'stroke-linecap': 'round', }), h('line', { x1: x, - y1: y - 8, + y1: y - 6.5, x2: x, - y2: y + 8, + y2: y + 6.5, stroke: 'white', - 'stroke-width': '2', + 'stroke-width': '2.2', + 'stroke-linecap': 'round', }) ] } @@ -220,6 +239,7 @@ class SkipView extends CurvedEdge { plusElements.push( h('g', { ...this.getEventHandlers(isCondition), // 根据 isCondition 动态选择事件处理器 + className: isPlus ? 'wf-edge-plus' : 'wf-edge-cond', style: { pointerEvents: 'auto', cursor: 'pointer' diff --git a/warm-flow-vue-designer/src/components/design/mimic/vue/EdgeTooltip.vue b/warm-flow-vue-designer/src/components/design/mimic/vue/EdgeTooltip.vue index 0a4d8ed6..4d58ee82 100644 --- a/warm-flow-vue-designer/src/components/design/mimic/vue/EdgeTooltip.vue +++ b/warm-flow-vue-designer/src/components/design/mimic/vue/EdgeTooltip.vue @@ -1,9 +1,19 @@