refactor: font size and split fields

This commit is contained in:
hypercross 2026-02-27 15:32:04 +08:00
parent c6580b7c69
commit ce01044c41
6 changed files with 142 additions and 50 deletions

View File

@ -99,7 +99,10 @@ export function CardPreview(props: CardPreviewProps) {
class={`absolute flex items-center justify-center text-center prose prose-sm ${ class={`absolute flex items-center justify-center text-center prose prose-sm ${
store.state.isEditing ? 'bg-blue-500/20 ring-2 ring-blue-500' : '' store.state.isEditing ? 'bg-blue-500/20 ring-2 ring-blue-500' : ''
}`} }`}
style={style} style={{
...style,
'font-size': `${store.state.dimensions?.fontSize}mm`
}}
innerHTML={renderLayerContent(layer, currentCard())} innerHTML={renderLayerContent(layer, currentCard())}
/> />
); );

View File

@ -18,21 +18,51 @@ export function PropertiesEditorPanel(props: PropertiesEditorPanelProps) {
<div class="space-y-3"> <div class="space-y-3">
<div> <div>
<label class="block text-sm font-medium text-gray-700"> (mm)</label> <label class="block text-sm font-medium text-gray-700"> (mm)</label>
<div class="flex gap-2">
<input <input
type="text" type="number"
class="w-full border border-gray-300 rounded px-2 py-1 text-sm" class="w-full border border-gray-300 rounded px-2 py-1 text-sm"
value={store.state.size} value={store.state.sizeW}
onInput={(e) => store.actions.setSize(e.target.value)} onChange={(e) => store.actions.setSizeW(Number(e.target.value))}
placeholder="宽"
/> />
<input
type="number"
class="w-full border border-gray-300 rounded px-2 py-1 text-sm"
value={store.state.sizeH}
onChange={(e) => store.actions.setSizeH(Number(e.target.value))}
placeholder="高"
/>
</div>
</div> </div>
<div> <div>
<label class="block text-sm font-medium text-gray-700"></label> <label class="block text-sm font-medium text-gray-700"></label>
<div class="flex gap-2">
<input <input
type="text" type="number"
class="w-full border border-gray-300 rounded px-2 py-1 text-sm" class="w-full border border-gray-300 rounded px-2 py-1 text-sm"
value={store.state.grid} value={store.state.gridW}
onInput={(e) => store.actions.setGrid(e.target.value)} onChange={(e) => store.actions.setGridW(Number(e.target.value))}
placeholder="宽"
/>
<input
type="number"
class="w-full border border-gray-300 rounded px-2 py-1 text-sm"
value={store.state.gridH}
onChange={(e) => store.actions.setGridH(Number(e.target.value))}
placeholder="高"
/>
</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700"> (mm)</label>
<input
type="number"
class="w-full border border-gray-300 rounded px-2 py-1 text-sm"
value={store.state.fontSize}
onChange={(e) => store.actions.setFontSize(Number(e.target.value))}
/> />
</div> </div>
@ -70,7 +100,7 @@ export function PropertiesEditorPanel(props: PropertiesEditorPanelProps) {
/> />
<span class="text-sm flex-1">{layer.prop}</span> <span class="text-sm flex-1">{layer.prop}</span>
<button <button
onClick={() => store.actions.setEditingLayer(layer.prop)} onClick={() => store.actions.setEditingLayer(store.state.editingLayer === layer.prop ? null : layer.prop)}
class={`text-xs px-2 py-0.5 rounded cursor-pointer ${ class={`text-xs px-2 py-0.5 rounded cursor-pointer ${
store.state.editingLayer === layer.prop store.state.editingLayer === layer.prop
? 'bg-blue-500 text-white' ? 'bg-blue-500 text-white'

View File

@ -8,18 +8,24 @@ import type { CardData, LayerConfig, Dimensions } from '../types';
* *
*/ */
export const DECK_DEFAULTS = { export const DECK_DEFAULTS = {
SIZE: '54x86', SIZE_W: 54,
GRID: '5x8', SIZE_H: 86,
GRID_W: 5,
GRID_H: 8,
BLEED: '1', BLEED: '1',
PADDING: '2' PADDING: '2',
FONT_SIZE: 3
} as const; } as const;
export interface DeckState { export interface DeckState {
// 基本属性 // 基本属性
size: string; sizeW: number;
grid: string; sizeH: number;
gridW: number;
gridH: number;
bleed: string; bleed: string;
padding: string; padding: string;
fontSize: number;
fixed: boolean; fixed: boolean;
src: string; src: string;
@ -51,10 +57,13 @@ export interface DeckState {
export interface DeckActions { export interface DeckActions {
// 基本属性设置 // 基本属性设置
setSize: (size: string) => void; setSizeW: (size: number) => void;
setGrid: (grid: string) => void; setSizeH: (size: number) => void;
setGridW: (grid: number) => void;
setGridH: (grid: number) => void;
setBleed: (bleed: string) => void; setBleed: (bleed: string) => void;
setPadding: (padding: string) => void; setPadding: (padding: string) => void;
setFontSize: (size: number) => void;
// 数据设置 // 数据设置
setCards: (cards: CardData[]) => void; setCards: (cards: CardData[]) => void;
@ -100,10 +109,13 @@ export function createDeckStore(
initialLayers: string = '' initialLayers: string = ''
): DeckStore { ): DeckStore {
const [state, setState] = createStore<DeckState>({ const [state, setState] = createStore<DeckState>({
size: DECK_DEFAULTS.SIZE, sizeW: DECK_DEFAULTS.SIZE_W,
grid: DECK_DEFAULTS.GRID, sizeH: DECK_DEFAULTS.SIZE_H,
gridW: DECK_DEFAULTS.GRID_W,
gridH: DECK_DEFAULTS.GRID_H,
bleed: DECK_DEFAULTS.BLEED, bleed: DECK_DEFAULTS.BLEED,
padding: DECK_DEFAULTS.PADDING, padding: DECK_DEFAULTS.PADDING,
fontSize: DECK_DEFAULTS.FONT_SIZE,
fixed: false, fixed: false,
src: initialSrc, src: initialSrc,
dimensions: null, dimensions: null,
@ -122,20 +134,31 @@ export function createDeckStore(
// 更新尺寸并重新计算 dimensions // 更新尺寸并重新计算 dimensions
const updateDimensions = () => { const updateDimensions = () => {
const dims = calculateDimensions({ const dims = calculateDimensions({
size: state.size, sizeW: state.sizeW,
grid: state.grid, sizeH: state.sizeH,
gridW: state.gridW,
gridH: state.gridH,
bleed: state.bleed, bleed: state.bleed,
padding: state.padding padding: state.padding,
fontSize: state.fontSize
}); });
setState({ dimensions: dims }); setState({ dimensions: dims });
}; };
const setSize = (size: string) => { const setSizeW = (size: number) => {
setState({ size }); setState({ sizeW: size });
updateDimensions(); updateDimensions();
}; };
const setGrid = (grid: string) => { const setSizeH = (size: number) => {
setState({ grid }); setState({ sizeH: size });
updateDimensions();
};
const setGridW = (grid: number) => {
setState({ gridW: grid });
updateDimensions();
};
const setGridH = (grid: number) => {
setState({ gridH: grid });
updateDimensions(); updateDimensions();
}; };
const setBleed = (bleed: string) => { const setBleed = (bleed: string) => {
@ -146,6 +169,10 @@ export function createDeckStore(
setState({ padding }); setState({ padding });
updateDimensions(); updateDimensions();
}; };
const setFontSize = (size: number) => {
setState({ fontSize: size });
updateDimensions();
};
const setCards = (cards: CardData[]) => setState({ cards, activeTab: 0 }); const setCards = (cards: CardData[]) => setState({ cards, activeTab: 0 });
const setActiveTab = (index: number) => setState({ activeTab: index }); const setActiveTab = (index: number) => setState({ activeTab: index });
@ -224,7 +251,7 @@ export function createDeckStore(
.filter(l => l.visible) .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}`)
.join(' '); .join(' ');
return `:md-deck[${state.src}]{size="${state.size}" grid="${state.grid}" bleed="${state.bleed}" padding="${state.padding}" layers="${layersStr}"}`; return `:md-deck[${state.src}]{size="${state.sizeW}x${state.sizeH}" grid="${state.gridW}x${state.gridH}" bleed="${state.bleed}" padding="${state.padding}" fontSize="${state.fontSize}" layers="${layersStr}"}`;
}; };
const copyCode = async () => { const copyCode = async () => {
@ -239,10 +266,13 @@ export function createDeckStore(
}; };
const actions: DeckActions = { const actions: DeckActions = {
setSize, setSizeW,
setGrid, setSizeH,
setGridW,
setGridH,
setBleed, setBleed,
setPadding, setPadding,
setFontSize,
setCards, setCards,
setActiveTab, setActiveTab,
updateCardData, updateCardData,

View File

@ -1,17 +1,19 @@
import type { Dimensions } from '../types'; import type { Dimensions } from '../types';
export interface DimensionOptions { export interface DimensionOptions {
size: string; sizeW: number;
sizeH: number;
bleed: string; bleed: string;
padding: string; padding: string;
grid: string; gridW: number;
gridH: number;
fontSize?: number;
} }
/** /**
* *
*/ */
export function calculateDimensions(options: DimensionOptions): Dimensions { export function calculateDimensions(options: DimensionOptions): Dimensions {
const [width, height] = options.size.split('x').map(Number);
const [bleedW, bleedH] = options.bleed.includes('x') const [bleedW, bleedH] = options.bleed.includes('x')
? options.bleed.split('x').map(Number) ? options.bleed.split('x').map(Number)
: [Number(options.bleed), Number(options.bleed)]; : [Number(options.bleed), Number(options.bleed)];
@ -20,19 +22,16 @@ export function calculateDimensions(options: DimensionOptions): Dimensions {
: [Number(options.padding), Number(options.padding)]; : [Number(options.padding), Number(options.padding)];
// 实际卡牌尺寸(含出血) // 实际卡牌尺寸(含出血)
const cardWidth = width + bleedW * 2; const cardWidth = options.sizeW + bleedW * 2;
const cardHeight = height + bleedH * 2; const cardHeight = options.sizeH + bleedH * 2;
// 网格区域尺寸(减去 padding // 网格区域尺寸(减去 padding
const gridAreaWidth = width - padW * 2; const gridAreaWidth = options.sizeW - padW * 2;
const gridAreaHeight = height - padH * 2; const gridAreaHeight = options.sizeH - padH * 2;
// 解析网格
const [gridW, gridH] = options.grid.split('x').map(Number);
// 每个网格单元的尺寸mm // 每个网格单元的尺寸mm
const cellWidth = gridAreaWidth / gridW; const cellWidth = gridAreaWidth / options.gridW;
const cellHeight = gridAreaHeight / gridH; const cellHeight = gridAreaHeight / options.gridH;
// 网格区域起点(相对于卡牌左上角,含 bleed 和 padding // 网格区域起点(相对于卡牌左上角,含 bleed 和 padding
const gridOriginX = bleedW + padW; const gridOriginX = bleedW + padW;
@ -45,10 +44,11 @@ export function calculateDimensions(options: DimensionOptions): Dimensions {
gridAreaHeight, gridAreaHeight,
cellWidth, cellWidth,
cellHeight, cellHeight,
gridW, gridW: options.gridW,
gridH, gridH: options.gridH,
gridOriginX, gridOriginX,
gridOriginY gridOriginY,
fontSize: options.fontSize ?? 3
}; };
} }

View File

@ -8,18 +8,28 @@ import { DataEditorPanel, PropertiesEditorPanel } from './editor-panel';
interface DeckProps { interface DeckProps {
size?: string; size?: string;
sizeW?: number;
sizeH?: number;
grid?: string; grid?: string;
gridW?: number;
gridH?: number;
bleed?: string; bleed?: string;
padding?: string; padding?: string;
fontSize?: number;
layers?: string; layers?: string;
fixed?: boolean | string; fixed?: boolean | string;
} }
customElement<DeckProps>('md-deck', { customElement<DeckProps>('md-deck', {
size: '54x86', size: '',
grid: '5x8', sizeW: 54,
sizeH: 86,
grid: '',
gridW: 5,
gridH: 8,
bleed: '1', bleed: '1',
padding: '2', padding: '2',
fontSize: 3,
layers: '', layers: '',
fixed: false fixed: false
}, (props, { element }) => { }, (props, { element }) => {
@ -43,11 +53,29 @@ customElement<DeckProps>('md-deck', {
// 创建 store 并加载数据 // 创建 store 并加载数据
const store = createDeckStore(resolvedSrc, (props.layers as string) || ''); const store = createDeckStore(resolvedSrc, (props.layers as string) || '');
// 初始化 store 属性 // 解析 size 属性(支持旧格式 "54x86" 和新格式)
store.actions.setSize(props.size || '54x86'); if (props.size && props.size.includes('x')) {
store.actions.setGrid(props.grid || '5x8'); const [w, h] = props.size.split('x').map(Number);
store.actions.setSizeW(w);
store.actions.setSizeH(h);
} else {
store.actions.setSizeW(props.sizeW ?? 54);
store.actions.setSizeH(props.sizeH ?? 86);
}
// 解析 grid 属性(支持旧格式 "5x8" 和新格式)
if (props.grid && props.grid.includes('x')) {
const [w, h] = props.grid.split('x').map(Number);
store.actions.setGridW(w);
store.actions.setGridH(h);
} else {
store.actions.setGridW(props.gridW ?? 5);
store.actions.setGridH(props.gridH ?? 8);
}
store.actions.setBleed(props.bleed || '1'); store.actions.setBleed(props.bleed || '1');
store.actions.setPadding(props.padding || '2'); store.actions.setPadding(props.padding || '2');
store.actions.setFontSize(props.fontSize ?? 3);
// 加载 CSV 数据 // 加载 CSV 数据
store.actions.loadCardsFromPath(resolvedSrc, (props.layers as string) || ''); store.actions.loadCardsFromPath(resolvedSrc, (props.layers as string) || '');

View File

@ -30,6 +30,7 @@ export interface Dimensions {
gridH: number; gridH: number;
gridOriginX: number; gridOriginX: number;
gridOriginY: number; gridOriginY: number;
fontSize: number;
} }
export interface SelectionState { export interface SelectionState {