From 2b1dbd41e1f97a7c38d5b58b5bc6c4b38d903c97 Mon Sep 17 00:00:00 2001 From: hypercross Date: Sun, 15 Mar 2026 14:55:50 +0800 Subject: [PATCH] refactor: preview contour correctly --- src/components/md-deck/CardPreview.tsx | 6 +- src/components/md-deck/PrintPreview.tsx | 2 +- src/components/md-deck/hooks/shape-styles.ts | 103 +++++++++++++------ 3 files changed, 77 insertions(+), 34 deletions(-) diff --git a/src/components/md-deck/CardPreview.tsx b/src/components/md-deck/CardPreview.tsx index d4459ea..53e0b02 100644 --- a/src/components/md-deck/CardPreview.tsx +++ b/src/components/md-deck/CardPreview.tsx @@ -19,7 +19,11 @@ export function CardPreview(props: CardPreviewProps) { const selectionStyle = createMemo(() => getSelectionBoxStyle(store.state.selectStart, store.state.selectEnd, store.state.dimensions) ); - const shapeClipPath = createMemo(() => getShapeClipPath(store.state.shape)); + const shapeClipPath = createMemo(() => { + const dims = store.state.dimensions; + if (!dims) return 'none'; + return getShapeClipPath(store.state.shape, dims.cardWidth, dims.cardHeight, store.state.cornerRadius); + }); const selection = useCardSelection(store); diff --git a/src/components/md-deck/PrintPreview.tsx b/src/components/md-deck/PrintPreview.tsx index 6b4f90c..3a36fcb 100644 --- a/src/components/md-deck/PrintPreview.tsx +++ b/src/components/md-deck/PrintPreview.tsx @@ -152,7 +152,7 @@ export function PrintPreview(props: PrintPreviewProps) { const cardWidth = store.state.dimensions?.cardWidth || 56; const cardHeight = store.state.dimensions?.cardHeight || 88; const clipPathId = `clip-${page.pageIndex}-${card.data.id || card.x}-${card.y}`; - const shapeClipPath = getShapeSvgClipPath(clipPathId, cardWidth, cardHeight, store.state.shape); + const shapeClipPath = getShapeSvgClipPath(clipPathId, cardWidth, cardHeight, store.state.shape, store.state.cornerRadius); return ( diff --git a/src/components/md-deck/hooks/shape-styles.ts b/src/components/md-deck/hooks/shape-styles.ts index cd5bb8f..ee0d106 100644 --- a/src/components/md-deck/hooks/shape-styles.ts +++ b/src/components/md-deck/hooks/shape-styles.ts @@ -1,20 +1,52 @@ import type { CardShape } from '../types'; +import { getCardShapePoints, contourToSvgPath } from '../../../plotcutter/contour'; + +/** + * 将轮廓点转换为 CSS polygon() 格式 + * @param points 轮廓点数组(相对于左上角,单位:mm) + * @returns CSS polygon() 字符串,使用百分比坐标 + */ +function pointsToCssPolygon(points: [number, number][], width: number, height: number): string { + if (points.length === 0) return 'none'; + + const coords = points.map(([x, y]) => { + const percentX = (x / width) * 100; + const percentY = (y / height) * 100; + return `${percentX.toFixed(4)}% ${percentY.toFixed(4)}%`; + }); + + return `polygon(${coords.join(', ')})`; +} /** * 获取 CSS clip-path 值用于形状裁剪 + * + * @param shape 卡片形状 + * @param width 卡片宽度(用于计算多边形坐标) + * @param height 卡片高度(用于计算多边形坐标) + * @param cornerRadius 圆角半径(可选,>0 时使用多边形近似) + * @param segmentsPerCorner 每个圆角的分段数(可选) */ -export function getShapeClipPath(shape: CardShape): string { - switch (shape) { - case 'circle': - return 'circle(50% at 50% 50%)'; - case 'triangle': - return 'polygon(50% 0%, 0% 100%, 100% 100%)'; - case 'hexagon': - return 'polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%)'; - case 'rectangle': - default: - return 'none'; +export function getShapeClipPath( + shape: CardShape, + width: number, + height: number, + cornerRadius: number = 0, + segmentsPerCorner: number = 4 +): string { + // 无圆角的基本形状使用简化的 CSS clip-path + if (cornerRadius <= 0) { + switch (shape) { + case 'circle': + return 'circle(50% at 50% 50%)'; + case 'rectangle': + return 'none'; + } } + + // 其他情况使用多边形近似(包括圆角形状、三角形、六边形) + const points = getCardShapePoints(shape, width, height, cornerRadius, segmentsPerCorner); + return pointsToCssPolygon(points, width, height); } /** @@ -23,34 +55,41 @@ export function getShapeClipPath(shape: CardShape): string { * @param width 卡片宽度 * @param height 卡片高度 * @param shape 卡片形状 + * @param cornerRadius 圆角半径(可选) + * @param segmentsPerCorner 每个圆角的分段数(可选) */ export function getShapeSvgClipPath( id: string, width: number, height: number, - shape: CardShape + shape: CardShape, + cornerRadius: number = 0, + segmentsPerCorner: number = 4 ): string { - const halfW = width / 2; - const halfH = height / 2; + const points = getCardShapePoints(shape, width, height, cornerRadius, segmentsPerCorner); + const pathData = contourToSvgPath(points, true); - switch (shape) { - case 'circle': - return ` + return ` - + `; - case 'triangle': - return ` - - - `; - case 'hexagon': - return ` - - - `; - case 'rectangle': - default: - return ''; - } +} + +/** + * 获取卡片形状的 SVG path 数据 + * @param width 卡片宽度 + * @param height 卡片高度 + * @param shape 卡片形状 + * @param cornerRadius 圆角半径(可选) + * @param segmentsPerCorner 每个圆角的分段数(可选) + */ +export function getCardShapePath( + width: number, + height: number, + shape: CardShape, + cornerRadius: number = 0, + segmentsPerCorner: number = 4 +): string { + const points = getCardShapePoints(shape, width, height, cornerRadius, segmentsPerCorner); + return contourToSvgPath(points, true); }