重构消息通知弹窗
This commit is contained in:
parent
5e23c7b17f
commit
2dd4e11191
@ -52,6 +52,7 @@
|
|||||||
"@react-form-builder/designer-bundle": "^7.4.0",
|
"@react-form-builder/designer-bundle": "^7.4.0",
|
||||||
"@reduxjs/toolkit": "^2.0.1",
|
"@reduxjs/toolkit": "^2.0.1",
|
||||||
"@tanstack/react-virtual": "^3.13.12",
|
"@tanstack/react-virtual": "^3.13.12",
|
||||||
|
"@tisoap/react-flow-smart-edge": "^4.0.1",
|
||||||
"@types/recharts": "^1.8.29",
|
"@types/recharts": "^1.8.29",
|
||||||
"@types/uuid": "^10.0.0",
|
"@types/uuid": "^10.0.0",
|
||||||
"@xyflow/react": "^12.8.6",
|
"@xyflow/react": "^12.8.6",
|
||||||
|
|||||||
@ -131,6 +131,9 @@ importers:
|
|||||||
'@tanstack/react-virtual':
|
'@tanstack/react-virtual':
|
||||||
specifier: ^3.13.12
|
specifier: ^3.13.12
|
||||||
version: 3.13.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
version: 3.13.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
|
'@tisoap/react-flow-smart-edge':
|
||||||
|
specifier: ^4.0.1
|
||||||
|
version: 4.0.1(@xyflow/react@12.9.0(@types/react@18.3.18)(immer@10.1.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)
|
||||||
'@types/recharts':
|
'@types/recharts':
|
||||||
specifier: ^1.8.29
|
specifier: ^1.8.29
|
||||||
version: 1.8.29
|
version: 1.8.29
|
||||||
@ -2045,6 +2048,14 @@ packages:
|
|||||||
'@tanstack/virtual-core@3.13.12':
|
'@tanstack/virtual-core@3.13.12':
|
||||||
resolution: {integrity: sha512-1YBOJfRHV4sXUmWsFSf5rQor4Ss82G8dQWLRbnk3GA4jeP8hQt1hxXh0tmflpC0dz3VgEv/1+qwPyLeWkQuPFA==}
|
resolution: {integrity: sha512-1YBOJfRHV4sXUmWsFSf5rQor4Ss82G8dQWLRbnk3GA4jeP8hQt1hxXh0tmflpC0dz3VgEv/1+qwPyLeWkQuPFA==}
|
||||||
|
|
||||||
|
'@tisoap/react-flow-smart-edge@4.0.1':
|
||||||
|
resolution: {integrity: sha512-tqyQyaQFDc4QIL3Kw9UL9QVWId4cSuVNqKsDRzdPH1Mf8YMrwB8/dq/BMFEbuGaln9B3wlgeyhQx3vn6E9lpJA==}
|
||||||
|
peerDependencies:
|
||||||
|
'@xyflow/react': '>=12'
|
||||||
|
react: '>=18'
|
||||||
|
react-dom: '>=18'
|
||||||
|
typescript: '>=5'
|
||||||
|
|
||||||
'@types/babel__core@7.20.5':
|
'@types/babel__core@7.20.5':
|
||||||
resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
|
resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
|
||||||
|
|
||||||
@ -6409,6 +6420,13 @@ snapshots:
|
|||||||
|
|
||||||
'@tanstack/virtual-core@3.13.12': {}
|
'@tanstack/virtual-core@3.13.12': {}
|
||||||
|
|
||||||
|
'@tisoap/react-flow-smart-edge@4.0.1(@xyflow/react@12.9.0(@types/react@18.3.18)(immer@10.1.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)':
|
||||||
|
dependencies:
|
||||||
|
'@xyflow/react': 12.9.0(@types/react@18.3.18)(immer@10.1.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
|
react: 18.3.1
|
||||||
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
|
typescript: 5.7.2
|
||||||
|
|
||||||
'@types/babel__core@7.20.5':
|
'@types/babel__core@7.20.5':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/parser': 7.26.3
|
'@babel/parser': 7.26.3
|
||||||
|
|||||||
@ -17,7 +17,7 @@ import '@xyflow/react/dist/style.css';
|
|||||||
|
|
||||||
import type { FlowNode, FlowEdge } from '../types';
|
import type { FlowNode, FlowEdge } from '../types';
|
||||||
import { nodeTypes } from '../nodes';
|
import { nodeTypes } from '../nodes';
|
||||||
import CustomEdge from './CustomEdge';
|
import SmartEdge from './SmartEdge';
|
||||||
import { generateEdgeId } from '../utils/idGenerator';
|
import { generateEdgeId } from '../utils/idGenerator';
|
||||||
|
|
||||||
interface FlowCanvasProps {
|
interface FlowCanvasProps {
|
||||||
@ -48,109 +48,6 @@ const FlowCanvas: React.FC<FlowCanvasProps> = ({
|
|||||||
const [nodes, , onNodesStateChange] = useNodesState(initialNodes);
|
const [nodes, , onNodesStateChange] = useNodesState(initialNodes);
|
||||||
const [edges, setEdges, onEdgesStateChange] = useEdgesState(initialEdges);
|
const [edges, setEdges, onEdgesStateChange] = useEdgesState(initialEdges);
|
||||||
|
|
||||||
// --- Auto adjust edge vertices when nodes move (preserve shape, avoid excessive length) ---
|
|
||||||
useEffect(() => {
|
|
||||||
// helper: get node position by id
|
|
||||||
const getNodePos = (id: string) => {
|
|
||||||
const n = nodes.find((nn) => nn.id === id);
|
|
||||||
return n?.position;
|
|
||||||
};
|
|
||||||
|
|
||||||
// geometry helpers
|
|
||||||
const recomputeVertices = (
|
|
||||||
oldVerts: Array<{ x: number; y: number }> | undefined,
|
|
||||||
oldA: { x: number; y: number } | undefined,
|
|
||||||
oldB: { x: number; y: number } | undefined,
|
|
||||||
newA: { x: number; y: number },
|
|
||||||
newB: { x: number; y: number }
|
|
||||||
) => {
|
|
||||||
// default three vertices placed along the baseline
|
|
||||||
const defaultThree = () => [
|
|
||||||
{ x: newA.x + (newB.x - newA.x) * 0.25, y: newA.y + (newB.y - newA.y) * 0.25 },
|
|
||||||
{ x: (newA.x + newB.x) / 2, y: (newA.y + newB.y) / 2 },
|
|
||||||
{ x: newA.x + (newB.x - newA.x) * 0.75, y: newA.y + (newB.y - newA.y) * 0.75 },
|
|
||||||
];
|
|
||||||
|
|
||||||
// if no old endpoints or old vertices, return default
|
|
||||||
if (!oldA || !oldB || !oldVerts || oldVerts.length !== 3) {
|
|
||||||
return defaultThree();
|
|
||||||
}
|
|
||||||
|
|
||||||
const projOnLine = (p: { x: number; y: number }, a: { x: number; y: number }, b: { x: number; y: number }) => {
|
|
||||||
const dx = b.x - a.x, dy = b.y - a.y;
|
|
||||||
const len2 = dx * dx + dy * dy || 1;
|
|
||||||
const t = Math.max(0, Math.min(1, ((p.x - a.x) * dx + (p.y - a.y) * dy) / len2));
|
|
||||||
const px = a.x + t * dx, py = a.y + t * dy;
|
|
||||||
const offx = p.x - px, offy = p.y - py;
|
|
||||||
// sign: which side of baseline (using cross product sign)
|
|
||||||
const cross = dx * (p.y - a.y) - dy * (p.x - a.x);
|
|
||||||
const sign = cross >= 0 ? 1 : -1;
|
|
||||||
const offMag = Math.hypot(offx, offy);
|
|
||||||
return { t, offMag, sign };
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapToNew = (t: number, offMag: number, sign: number, a: { x: number; y: number }, b: { x: number; y: number }) => {
|
|
||||||
const dx = b.x - a.x, dy = b.y - a.y;
|
|
||||||
const len = Math.hypot(dx, dy) || 1;
|
|
||||||
// unit normal for new baseline
|
|
||||||
const nx = -dy / len, ny = dx / len;
|
|
||||||
// clamp offset to avoid huge bulges (auto shrink if nodes far apart)
|
|
||||||
const maxOffset = Math.max(20, 0.35 * len);
|
|
||||||
const useOff = Math.min(offMag, maxOffset);
|
|
||||||
const baseX = a.x + t * dx, baseY = a.y + t * dy;
|
|
||||||
return { x: baseX + sign * useOff * nx, y: baseY + sign * useOff * ny };
|
|
||||||
};
|
|
||||||
|
|
||||||
// Compute mapping (t, offset) from old baseline, then apply to new baseline
|
|
||||||
const tOff = oldVerts.map((v) => projOnLine(v, oldA, oldB));
|
|
||||||
// enforce monotonic t order to keep vertex order stable
|
|
||||||
tOff.sort((a, b) => a.t - b.t);
|
|
||||||
const mapped = tOff.map((m) => mapToNew(m.t, m.offMag, m.sign, newA, newB));
|
|
||||||
// ensure exactly 3 vertices
|
|
||||||
if (mapped.length === 3) return mapped as Array<{ x: number; y: number }>;
|
|
||||||
// fallback
|
|
||||||
return defaultThree();
|
|
||||||
};
|
|
||||||
|
|
||||||
setEdges((eds) => {
|
|
||||||
return eds.map((ed) => {
|
|
||||||
const s = getNodePos(ed.source);
|
|
||||||
const t = getNodePos(ed.target);
|
|
||||||
if (!s || !t) return ed;
|
|
||||||
|
|
||||||
const dataAny = (ed as any).data || {};
|
|
||||||
const last = dataAny._lastEndpoints as { sx: number; sy: number; tx: number; ty: number } | undefined;
|
|
||||||
const current = { sx: s.x, sy: s.y, tx: t.x, ty: t.y };
|
|
||||||
|
|
||||||
// Initialize vertices if missing
|
|
||||||
const curVerts = (dataAny.vertices as any) as Array<{ x: number; y: number }> | undefined;
|
|
||||||
let vertices = curVerts;
|
|
||||||
let changed = false;
|
|
||||||
|
|
||||||
if (!Array.isArray(vertices) || vertices.length !== 3) {
|
|
||||||
vertices = recomputeVertices(undefined, undefined, undefined, { x: current.sx, y: current.sy }, { x: current.tx, y: current.ty });
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Recompute when endpoints moved
|
|
||||||
if (!last || last.sx !== current.sx || last.sy !== current.sy || last.tx !== current.tx || last.ty !== current.ty) {
|
|
||||||
vertices = recomputeVertices(vertices, last ? { x: last.sx, y: last.sy } : undefined, last ? { x: last.tx, y: last.ty } : undefined, { x: current.sx, y: current.sy }, { x: current.tx, y: current.ty });
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!changed) return ed;
|
|
||||||
return {
|
|
||||||
...ed,
|
|
||||||
data: {
|
|
||||||
...ed.data,
|
|
||||||
vertices,
|
|
||||||
_lastEndpoints: current,
|
|
||||||
},
|
|
||||||
} as any;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}, [nodes, setEdges]);
|
|
||||||
|
|
||||||
// 处理边重连
|
// 处理边重连
|
||||||
const onReconnect = useCallback((oldEdge: Edge, newConnection: Connection) => {
|
const onReconnect = useCallback((oldEdge: Edge, newConnection: Connection) => {
|
||||||
setEdges((els) => reconnectEdge(oldEdge, newConnection, els));
|
setEdges((els) => reconnectEdge(oldEdge, newConnection, els));
|
||||||
@ -164,7 +61,7 @@ const FlowCanvas: React.FC<FlowCanvasProps> = ({
|
|||||||
source: params.source!,
|
source: params.source!,
|
||||||
target: params.target!,
|
target: params.target!,
|
||||||
type: 'smoothstep',
|
type: 'smoothstep',
|
||||||
animated: true,
|
animated: false,
|
||||||
style: {
|
style: {
|
||||||
stroke: '#94a3b8',
|
stroke: '#94a3b8',
|
||||||
strokeWidth: 2,
|
strokeWidth: 2,
|
||||||
@ -282,7 +179,7 @@ const FlowCanvas: React.FC<FlowCanvasProps> = ({
|
|||||||
onDragOver={handleDragOver}
|
onDragOver={handleDragOver}
|
||||||
onMove={onViewportChange}
|
onMove={onViewportChange}
|
||||||
nodeTypes={nodeTypes}
|
nodeTypes={nodeTypes}
|
||||||
edgeTypes={{ smoothstep: CustomEdge }}
|
edgeTypes={{ smoothstep: SmartEdge }}
|
||||||
isValidConnection={isValidConnection}
|
isValidConnection={isValidConnection}
|
||||||
defaultEdgeOptions={{
|
defaultEdgeOptions={{
|
||||||
type: 'smoothstep',
|
type: 'smoothstep',
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user