import type { ComponentType, JSXElement } from '../../jsx'; import { Defs, Ellipse, getElementBounds, Group, Path } from '../../jsx'; import { BtnAdd, BtnRemove, BtnsGroup, ItemIcon, ItemsGroup, ShapesGroup, } from '../components'; import { FlexLayout } from '../layouts'; import { getColorPrimary, getPaletteColor, getThemeColors } from '../utils'; import { registerStructure } from './registry'; import type { BaseStructureProps } from './types'; // W 型路径参数 const DX = 240; const DY = 130; const ITEM_TO_PATH_GAP = 105; // W 型路径宽度 const W_PATH_W = 12; // 光晕尺寸 (Ellipse W/H) const GLOW_W = 120; // 椭圆宽度 (水平对角线) const GLOW_H = 50; // 椭圆高度 (垂直对角线) const GLOW_OPACITY = 0.6; // 控制光晕的透明度,实现颜色调淡 // 原始装饰性 Path 的 D 属性 const DECO_PATH_1 = 'M132.821 50.6599L87.6795 76.8925C78.0629 82.4807 62.4238 82.4807 52.7445 76.8925L7.30824 50.6599C2.43719 47.8482 0.00459679 44.1562 0.0152907 40.472L3.17695e-05 45.6925C-0.0101404 49.378 2.42245 53.0686 7.29298 55.8803L52.7289 82.1129C62.4094 87.7011 78.0485 87.7011 87.6652 82.1129L132.806 55.8803C137.583 53.1038 139.975 49.4705 139.986 45.832L140 40.6116C139.989 44.2501 137.598 47.8834 132.821 50.6599Z'; const DECO_PATH_2 = 'M132.707 30.4224C142.386 36.0119 142.437 45.0704 132.821 50.6599L87.6795 76.8925C78.0629 82.4807 62.4238 82.4807 52.7445 76.8925L7.30825 50.6599C-2.37126 45.0717 -2.42225 36.0132 7.19414 30.4237L52.335 4.19115C61.9517 -1.39705 77.5908 -1.39705 87.2713 4.19115L132.707 30.4224Z'; // 装饰元素的偏移量 - 改为正值 const DECO_OFFSET_X = 70 + W_PATH_W / 2; const DECO_OFFSET_Y = 40 + W_PATH_W / 2; export interface SequenceZigzagStepsProps extends BaseStructureProps { dx?: number; dy?: number; iconSize?: number; } // 滤镜定义 const GlowFilter = ( ); export const SequenceZigzagSteps: ComponentType = ( props, ) => { const { Title, Item, data, options, dx = DX, dy = DY, iconSize = 30 } = props; const { title, desc, items = [] } = data; const titleContent = Title ? : null; const colorPrimary = getColorPrimary(options); if (items.length === 0) { const btnAddElement = <BtnAdd indexes={[0]} x={0} y={0} />; return ( <FlexLayout id="infographic-container" flexDirection="column" justifyContent="center" alignItems="center" > <Defs>{GlowFilter}</Defs> {titleContent} <Group> <BtnsGroup>{btnAddElement}</BtnsGroup> </Group> </FlexLayout> ); } const itemBounds = getElementBounds( <Item indexes={[0]} data={data} datum={items[0]} />, ); const btnBounds = getElementBounds(<BtnAdd indexes={[0]} />); const btnElements: JSXElement[] = []; const itemElements: JSXElement[] = []; const pathElements: JSXElement[] = []; const decoElements: JSXElement[] = []; const iconElements: JSXElement[] = []; let currentX = 0; let currentY = 0; let pathD = ''; let minPathY = Infinity; let maxItemY = -Infinity; let minDecoX = Infinity; let minDecoY = Infinity; const pathPoints: Array<{ x: number; y: number }> = []; // Pass 1: 计算路径点和装饰元素的边界 items.forEach((item, index) => { const cx = currentX; const cy = currentY; pathPoints.push({ x: cx, y: cy }); minPathY = Math.min(minPathY, cy); // 计算装饰元素的位置 const decoX = cx - DECO_OFFSET_X; const decoY = cy - DECO_OFFSET_Y; minDecoX = Math.min(minDecoX, decoX); minDecoY = Math.min(minDecoY, decoY); const isPeak = index % 2 === 0; if (index < items.length - 1) { currentX += dx; currentY = isPeak ? currentY + dy : currentY - dy; } }); // 计算偏移量以确保所有元素都在正坐标 const offsetX = Math.max(0, -minDecoX); const offsetY = Math.max(0, -minDecoY); currentX = 0; currentY = 0; // Pass 2: 放置元素,应用偏移量 items.forEach((item, index) => { const indexes = [index]; const pathPoint = pathPoints[index]; const cx = pathPoint.x + offsetX; const cy = pathPoint.y + offsetY; const currentColor = getPaletteColor(options, indexes); // 装饰元素 - 确保坐标为正值 const decoX = Math.max(0, cx - DECO_OFFSET_X); const decoY = Math.max(0, cy - DECO_OFFSET_Y); const glowX = 10; const glowY = GLOW_H + 10; const { colorPrimaryBg } = getThemeColors({ colorPrimary: currentColor, }); decoElements.push( <ShapesGroup x={decoX} y={decoY} width={140} height={110}> <Ellipse x={glowX} y={glowY} width={GLOW_W} height={GLOW_H} fill={currentColor || colorPrimary} filter="url(#sequence-zigzag-glow-filter)" opacity={GLOW_OPACITY} /> <Path d={DECO_PATH_1} fill={currentColor || colorPrimary} /> <Path d={DECO_PATH_2} fill={'#fff'} /> <Path d={DECO_PATH_2} fill={colorPrimaryBg || colorPrimaryBg} /> </ShapesGroup>, ); // Icon 元素 if (item.icon) { const iconX = Math.max(0, cx - iconSize / 2 - W_PATH_W / 2); const iconY = Math.max(0, cy - iconSize / 2 - W_PATH_W / 2); iconElements.push( <ItemIcon x={iconX} y={iconY} size={iconSize} indexes={indexes} fill={currentColor} />, ); } // Item 元素和按钮 const itemX = cx - itemBounds.width / 2 - W_PATH_W / 2; const itemY = Math.max(0, cy + ITEM_TO_PATH_GAP - W_PATH_W / 2); maxItemY = Math.max(maxItemY, itemY + itemBounds.height); if (index === 0) { pathD = `M ${cx} ${cy}`; } else { pathD += ` L ${cx} ${cy}`; } itemElements.push( <Item indexes={indexes} datum={item} data={data} x={itemX} y={itemY} />, ); btnElements.push( <BtnRemove indexes={indexes} x={Math.max(0, itemX + itemBounds.width - btnBounds.width / 2)} y={Math.max(0, itemY + itemBounds.height - btnBounds.height / 2)} />, ); if (index < items.length - 1) { const nextPoint = pathPoints[index + 1]; const nextCx = nextPoint.x + offsetX; const nextCy = nextPoint.y + offsetY; const btnAddX = Math.max(0, cx + (nextCx - cx) / 2 - btnBounds.width / 2); const btnAddY = Math.max( 0, cy + (nextCy - cy) / 2 - btnBounds.height / 2, ); btnElements.push( <BtnAdd indexes={[index + 1]} x={btnAddX} y={btnAddY} />, ); } else { btnElements.push( <BtnAdd indexes={[items.length]} x={Math.max(0, itemX + itemBounds.width + dx / 4)} y={Math.max(0, itemY + itemBounds.height / 2 - btnBounds.height / 2)} />, ); } }); if (pathD) { const finalX = (pathPoints[pathPoints.length - 1]?.x || 0) + offsetX; pathElements.push( <Path d={pathD} stroke="#f3f2f1" strokeWidth={W_PATH_W} fill="none" width={finalX} height={maxItemY - Math.min(minPathY + offsetY, 0)} />, ); } return ( <FlexLayout id="infographic-container" flexDirection="column" justifyContent="center" alignItems="center" gap={30} > <Defs>{GlowFilter}</Defs> {titleContent} <Group x={0} y={0}> <Group>{[...pathElements, ...decoElements, ...iconElements]}</Group> <ItemsGroup>{itemElements}</ItemsGroup> <BtnsGroup>{btnElements}</BtnsGroup> </Group> </FlexLayout> ); }; registerStructure('sequence-zigzag-steps', { component: SequenceZigzagSteps, composites: ['title', 'item'], });