diff --git a/src/components/md-deck/CardLayer.tsx b/src/components/md-deck/CardLayer.tsx index c1837ac..979f379 100644 --- a/src/components/md-deck/CardLayer.tsx +++ b/src/components/md-deck/CardLayer.tsx @@ -29,29 +29,32 @@ function renderLayerContent(layer: { prop: string }, cardData: CardData): string export function CardLayer(props: CardLayerProps) { return ( - {(layer) => ( - <> -
- {props.showBounds && ( -
{ + const layerStyle = getLayerStyle(layer, props.dimensions); + return ( + <> +
- )} - - )} + {props.showBounds && ( +
+ )} + + ); + }} ); } diff --git a/src/components/md-deck/editor-panel/LayerEditorPanel.tsx b/src/components/md-deck/editor-panel/LayerEditorPanel.tsx index 8cc4fc0..c9bfbcc 100644 --- a/src/components/md-deck/editor-panel/LayerEditorPanel.tsx +++ b/src/components/md-deck/editor-panel/LayerEditorPanel.tsx @@ -5,12 +5,26 @@ export interface LayerEditorPanelProps { store: DeckStore; } +const ORIENTATION_OPTIONS = [ + { value: 'n', label: '↑ 北' }, + { value: 'e', label: '→ 东' }, + { value: 's', label: '↓ 南' }, + { value: 'w', label: '← 西' } +] as const; + /** * 图层编辑面板:图层可见性切换、位置编辑、复制代码 */ export function LayerEditorPanel(props: LayerEditorPanelProps) { const { store } = props; + const updateLayerOrientation = (layerProp: string, orientation: 'n' | 's' | 'e' | 'w') => { + const layer = store.state.layerConfigs.find(l => l.prop === layerProp); + if (layer) { + store.actions.updateLayerConfig(layerProp, { ...layer, orientation }); + } + }; + return (

图层

@@ -18,24 +32,39 @@ export function LayerEditorPanel(props: LayerEditorPanelProps) {
{(layer) => ( -
- store.actions.toggleLayerVisible(layer.prop)} - class="cursor-pointer" - /> - {layer.prop} - +
+
+ store.actions.toggleLayerVisible(layer.prop)} + class="cursor-pointer" + /> + {layer.prop} +
+
+ + +
)} diff --git a/src/components/md-deck/hooks/deckStore.ts b/src/components/md-deck/hooks/deckStore.ts index 753ce6f..9aa7d77 100644 --- a/src/components/md-deck/hooks/deckStore.ts +++ b/src/components/md-deck/hooks/deckStore.ts @@ -28,6 +28,7 @@ export interface DeckState { fontSize: number; fixed: boolean; src: string; + rawSrc: string; // 原始 CSV 路径(用于生成代码时保持相对路径) // 解析后的尺寸 dimensions: Dimensions | null; @@ -97,7 +98,7 @@ export interface DeckActions { cancelSelection: () => void; // 数据加载 - loadCardsFromPath: (path: string, layersStr?: string) => Promise; + loadCardsFromPath: (path: string, rawSrc: string, layersStr?: string) => Promise; setError: (error: string | null) => void; clearError: () => void; @@ -140,6 +141,7 @@ export function createDeckStore( fontSize: DECK_DEFAULTS.FONT_SIZE, fixed: false, src: initialSrc, + rawSrc: initialSrc, dimensions: null, cards: [], activeTab: 0, @@ -237,13 +239,13 @@ export function createDeckStore( }; // 加载卡牌数据(核心逻辑) - const loadCardsFromPath = async (path: string, layersStr: string = '') => { + const loadCardsFromPath = async (path: string, rawSrc: string, layersStr: string = '') => { if (!path) { setState({ error: '未指定 CSV 文件路径' }); return; } - setState({ isLoading: true, error: null, src: path }); + setState({ isLoading: true, error: null, src: path, rawSrc: rawSrc }); try { const data = await loadCSV(path); @@ -277,9 +279,9 @@ export function createDeckStore( const generateCode = () => { const layersStr = state.layerConfigs .filter(l => l.visible) - .map(l => `${l.prop}:${l.x1},${l.y1}-${l.x2},${l.y2}`) + .map(l => `${l.prop}:${l.x1},${l.y1}-${l.x2},${l.y2}${l.orientation && l.orientation !== 'n' ? `${l.orientation}` : ''}`) .join(' '); - return `:md-deck[${state.src}]{size="${state.sizeW}x${state.sizeH}" grid="${state.gridW}x${state.gridH}" bleed="${state.bleed}" padding="${state.padding}" font-size="${state.fontSize}" layers="${layersStr}"}`; + return `:md-deck[${state.rawSrc || state.src}]{size="${state.sizeW}x${state.sizeH}" grid="${state.gridW}x${state.gridH}" bleed="${state.bleed}" padding="${state.padding}" font-size="${state.fontSize}" layers="${layersStr}"}`; }; const copyCode = async () => { diff --git a/src/components/md-deck/hooks/dimensions.ts b/src/components/md-deck/hooks/dimensions.ts index ef7d770..687cd5b 100644 --- a/src/components/md-deck/hooks/dimensions.ts +++ b/src/components/md-deck/hooks/dimensions.ts @@ -47,20 +47,89 @@ export function calculateDimensions(options: DimensionOptions): Dimensions { /** * 计算 layer 位置样式(单位:mm) + * 根据 orientation 旋转元素,同时保持覆盖相同的区域 + * + * orientation 含义: + * - n (0°): 正常方向,内容从上到下阅读 + * - e (90°): 顺时针旋转 90°,内容从右到左阅读(右侧为顶部) + * - s (180°): 旋转 180°,内容从下到上阅读 + * - w (270°): 顺时针旋转 270°,内容从左到右阅读(左侧为顶部) */ export function getLayerStyle( - layer: { x1: number; y1: number; x2: number; y2: number }, + layer: { x1: number; y1: number; x2: number; y2: number; orientation?: 'n' | 's' | 'e' | 'w' }, dims: Dimensions -): { left: string; top: string; width: string; height: string } { - const left = (layer.x1 - 1) * dims.cellWidth; - const top = (layer.y1 - 1) * dims.cellHeight; - const width = (layer.x2 - layer.x1 + 1) * dims.cellWidth; - const height = (layer.y2 - layer.y1 + 1) * dims.cellHeight; +): { + left: string; + top: string; + width: string; + height: string; + transform?: string; + 'transform-origin'?: string; +} { + const cellWidth = dims.cellWidth; + const cellHeight = dims.cellHeight; + // 计算原始矩形的边界(相对于网格区域起点) + const baseLeft = (layer.x1 - 1) * cellWidth; + const baseTop = (layer.y1 - 1) * cellHeight; + const baseWidth = (layer.x2 - layer.x1 + 1) * cellWidth; + const baseHeight = (layer.y2 - layer.y1 + 1) * cellHeight; + + const orientation = layer.orientation ?? 'n'; + + // 根据方向决定旋转角度 + // n: 0°, e: 90°, s: 180°, w: 270° + const rotationMap: Record<'n' | 's' | 'e' | 'w', number> = { + n: 0, + e: 90, + s: 180, + w: 270 + }; + + const rotation = rotationMap[orientation]; + + // 对于 e/w 方向,需要交换宽高 + if (orientation === 'e' || orientation === 'w') { + // 计算旋转后的位置,使得元素覆盖相同的区域 + // 旋转 90°/270°后,原来的宽度变成高度,高度变成宽度 + // 需要调整 left/top 使得旋转后的元素中心与原始矩形中心重合 + + // 元素尺寸交换 + const width = baseHeight; + const height = baseWidth; + + // 计算新起点,使得旋转后覆盖相同区域 + // 旋转中心设为元素中心,旋转后元素覆盖的区域与原始区域相同 + const left = baseLeft + (baseWidth - width) / 2; + const top = baseTop + (baseHeight - height) / 2; + + return { + left: `${left}mm`, + top: `${top}mm`, + width: `${width}mm`, + height: `${height}mm`, + transform: `rotate(${rotation}deg)`, + 'transform-origin': 'center center' + }; + } + + // n 或 s 方向,宽高不变 + if (orientation === 's') { + return { + left: `${baseLeft}mm`, + top: `${baseTop}mm`, + width: `${baseWidth}mm`, + height: `${baseHeight}mm`, + transform: `rotate(180deg)`, + 'transform-origin': 'center center' + }; + } + + // n 方向(默认) return { - left: `${left}mm`, - top: `${top}mm`, - width: `${width}mm`, - height: `${height}mm` + left: `${baseLeft}mm`, + top: `${baseTop}mm`, + width: `${baseWidth}mm`, + height: `${baseHeight}mm` }; } diff --git a/src/components/md-deck/hooks/layer-parser.ts b/src/components/md-deck/hooks/layer-parser.ts index f2c0252..0b9e0de 100644 --- a/src/components/md-deck/hooks/layer-parser.ts +++ b/src/components/md-deck/hooks/layer-parser.ts @@ -1,13 +1,13 @@ import type { Layer, LayerConfig } from '../types'; /** - * 解析 layers 字符串 "body:1,7-5,8 title:1,1-5,1" + * 解析 layers 字符串 "body:1,7-5,8 title:1,1-5,1" 或 "body:1,7-5,8,s title:1,1-5,1,e" */ export function parseLayers(layersStr: string): Layer[] { if (!layersStr) return []; const layers: Layer[] = []; - const regex = /(\w+):(\d+),(\d+)-(\d+),(\d+)/g; + const regex = /(\w+):(\d+),(\d+)-(\d+),(\d+)([nsew])?/g; let match; while ((match = regex.exec(layersStr)) !== null) { @@ -16,7 +16,8 @@ export function parseLayers(layersStr: string): Layer[] { x1: parseInt(match[2]), y1: parseInt(match[3]), x2: parseInt(match[4]), - y2: parseInt(match[5]) + y2: parseInt(match[5]), + orientation: match[6] as 'n' | 's' | 'e' | 'w' | undefined }); } @@ -29,7 +30,7 @@ export function parseLayers(layersStr: string): Layer[] { export function formatLayers(layers: LayerConfig[]): string { return layers .filter(l => l.visible) - .map(l => `${l.prop}:${l.x1},${l.y1}-${l.x2},${l.y2}`) + .map(l => `${l.prop}:${l.x1},${l.y1}-${l.x2},${l.y2}${l.orientation && l.orientation !== 'n' ? `${l.orientation}` : ''}`) .join(' '); } @@ -42,7 +43,7 @@ export function initLayerConfigs( ): LayerConfig[] { const parsed = parseLayers(existingLayersStr); const allProps = Object.keys(data[0] || {}).filter(k => k !== 'label'); - + return allProps.map(prop => { const existing = parsed.find(l => l.prop === prop); return { @@ -51,7 +52,8 @@ export function initLayerConfigs( x1: existing?.x1 || 1, y1: existing?.y1 || 1, x2: existing?.x2 || 2, - y2: existing?.y2 || 2 + y2: existing?.y2 || 2, + orientation: existing?.orientation }; }); } diff --git a/src/components/md-deck/index.tsx b/src/components/md-deck/index.tsx index 4ff9351..cb786f2 100644 --- a/src/components/md-deck/index.tsx +++ b/src/components/md-deck/index.tsx @@ -94,7 +94,7 @@ customElement('md-deck', { } // 加载 CSV 数据 - store.actions.loadCardsFromPath(resolvedSrc, (props.layers as string) || ''); + store.actions.loadCardsFromPath(resolvedSrc, csvPath, (props.layers as string) || ''); // 清理函数 onCleanup(() => { diff --git a/src/components/md-deck/types.ts b/src/components/md-deck/types.ts index 09840e4..540789a 100644 --- a/src/components/md-deck/types.ts +++ b/src/components/md-deck/types.ts @@ -8,6 +8,7 @@ export interface Layer { y1: number; x2: number; y2: number; + orientation?: 'n' | 's' | 'e' | 'w'; } export interface LayerConfig { @@ -17,6 +18,7 @@ export interface LayerConfig { y1: number; x2: number; y2: number; + orientation?: 'n' | 's' | 'e' | 'w'; } export interface Dimensions {