fix: print sizing
This commit is contained in:
parent
8cf27b3aa7
commit
1845576b18
|
|
@ -24,22 +24,38 @@ export function PrintPreview(props: PrintPreviewProps) {
|
|||
const { store } = props;
|
||||
|
||||
// A4 纸张尺寸(mm):210 x 297
|
||||
const A4_WIDTH = 210;
|
||||
const A4_HEIGHT = 297;
|
||||
const A4_WIDTH_PORTRAIT = 210;
|
||||
const A4_HEIGHT_PORTRAIT = 297;
|
||||
const A4_WIDTH_LANDSCAPE = 297;
|
||||
const A4_HEIGHT_LANDSCAPE = 210;
|
||||
const PRINT_MARGIN = 5; // 打印边距
|
||||
|
||||
// 获取打印设置
|
||||
const orientation = () => store.state.printOrientation;
|
||||
const oddPageOffsetX = () => store.state.printOddPageOffsetX;
|
||||
const oddPageOffsetY = () => store.state.printOddPageOffsetY;
|
||||
|
||||
// 根据方向获取 A4 尺寸
|
||||
const getA4Size = () => {
|
||||
if (orientation() === 'landscape') {
|
||||
return { width: A4_WIDTH_LANDSCAPE, height: A4_HEIGHT_LANDSCAPE };
|
||||
}
|
||||
return { width: A4_WIDTH_PORTRAIT, height: A4_HEIGHT_PORTRAIT };
|
||||
};
|
||||
|
||||
// 计算每张卡牌在 A4 纸上的位置(居中布局)
|
||||
const pages = createMemo(() => {
|
||||
const cards = store.state.cards;
|
||||
const cardWidth = store.state.dimensions?.cardWidth || 56;
|
||||
const cardHeight = store.state.dimensions?.cardHeight || 88;
|
||||
const { width: a4Width, height: a4Height } = getA4Size();
|
||||
|
||||
// 每行可容纳的卡牌数量
|
||||
const usableWidth = A4_WIDTH - PRINT_MARGIN * 2;
|
||||
const usableWidth = a4Width - PRINT_MARGIN * 2;
|
||||
const cardsPerRow = Math.floor(usableWidth / cardWidth);
|
||||
|
||||
// 每页可容纳的行数
|
||||
const usableHeight = A4_HEIGHT - PRINT_MARGIN * 2;
|
||||
const usableHeight = a4Height - PRINT_MARGIN * 2;
|
||||
const rowsPerPage = Math.floor(usableHeight / cardHeight);
|
||||
|
||||
// 每页的卡牌数量
|
||||
|
|
@ -50,8 +66,8 @@ export function PrintPreview(props: PrintPreviewProps) {
|
|||
const maxGridHeight = rowsPerPage * cardHeight;
|
||||
|
||||
// 居中偏移量(使卡牌区域在 A4 纸上居中)
|
||||
const offsetX = (A4_WIDTH - maxGridWidth) / 2;
|
||||
const offsetY = (A4_HEIGHT - maxGridHeight) / 2;
|
||||
const baseOffsetX = (a4Width - maxGridWidth) / 2;
|
||||
const baseOffsetY = (a4Height - maxGridHeight) / 2;
|
||||
|
||||
// 分页
|
||||
const result: {
|
||||
|
|
@ -72,9 +88,14 @@ export function PrintPreview(props: PrintPreviewProps) {
|
|||
currentPage = { pageIndex, cards: [], bounds: { minX: Infinity, minY: Infinity, maxX: -Infinity, maxY: -Infinity } };
|
||||
}
|
||||
|
||||
// 使用居中偏移量计算卡牌位置
|
||||
const cardX = offsetX + col * cardWidth;
|
||||
const cardY = offsetY + row * cardHeight;
|
||||
// 奇数页应用偏移(pageIndex 从 0 开始,所以偶数索引是奇数页)
|
||||
const isOddPage = pageIndex % 2 === 0;
|
||||
const pageOffsetX = isOddPage ? oddPageOffsetX() : 0;
|
||||
const pageOffsetY = isOddPage ? oddPageOffsetY() : 0;
|
||||
|
||||
// 使用居中偏移量 + 奇数页偏移计算卡牌位置
|
||||
const cardX = baseOffsetX + col * cardWidth + pageOffsetX;
|
||||
const cardY = baseOffsetY + row * cardHeight + pageOffsetY;
|
||||
|
||||
currentPage.cards.push({
|
||||
data: cards[i],
|
||||
|
|
@ -97,10 +118,10 @@ export function PrintPreview(props: PrintPreviewProps) {
|
|||
return result.map(page => ({
|
||||
...page,
|
||||
frameBounds: {
|
||||
minX: offsetX,
|
||||
minY: offsetY,
|
||||
maxX: offsetX + maxGridWidth,
|
||||
maxY: offsetY + maxGridHeight
|
||||
minX: baseOffsetX + (page.pageIndex % 2 === 0 ? oddPageOffsetX() : 0),
|
||||
minY: baseOffsetY + (page.pageIndex % 2 === 0 ? oddPageOffsetY() : 0),
|
||||
maxX: baseOffsetX + maxGridWidth + (page.pageIndex % 2 === 0 ? oddPageOffsetX() : 0),
|
||||
maxY: baseOffsetY + maxGridHeight + (page.pageIndex % 2 === 0 ? oddPageOffsetY() : 0)
|
||||
}
|
||||
}));
|
||||
});
|
||||
|
|
@ -160,13 +181,76 @@ export function PrintPreview(props: PrintPreviewProps) {
|
|||
|
||||
return (
|
||||
<div class="fixed inset-0 bg-black/50 z-50 overflow-auto print:overflow-visible print:absolute">
|
||||
{/* 打印样式:根据方向设置 @page 规则 */}
|
||||
<style>{`
|
||||
@media print {
|
||||
@page {
|
||||
size: A4 ${orientation() === 'landscape' ? 'landscape' : 'portrait'};
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
`}</style>
|
||||
<div class="min-h-screen py-20 px-4 print:p-0">
|
||||
{/* 打印预览控制栏 */}
|
||||
<div class="fixed top-0 left-0 right-0 z-50 bg-white shadow-lg rounded-lg mx-4 mt-4 px-4 py-1 flex items-center justify-between gap-4">
|
||||
<div class="fixed top-0 left-0 right-0 z-50 bg-white shadow-lg rounded-lg mx-4 mt-4 px-4 py-3 flex items-center justify-between gap-4">
|
||||
<div class="flex items-center gap-4">
|
||||
<h2 class="text-base font-bold mt-0 mb-0">打印预览</h2>
|
||||
<p class="text-xs text-gray-500 mb-0">共 {pages().length} 页,{store.state.cards.length} 张卡牌</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-4">
|
||||
{/* 方向选择 */}
|
||||
<div class="flex items-center gap-2">
|
||||
<label class="text-sm text-gray-600">方向:</label>
|
||||
<div class="flex gap-1">
|
||||
<button
|
||||
onClick={() => store.actions.setPrintOrientation('portrait')}
|
||||
class={`px-3 py-1 rounded text-sm font-medium cursor-pointer border ${
|
||||
orientation() === 'portrait'
|
||||
? 'bg-blue-600 text-white border-blue-600'
|
||||
: 'bg-white text-gray-700 border-gray-300 hover:bg-gray-50'
|
||||
}`}
|
||||
>
|
||||
竖向
|
||||
</button>
|
||||
<button
|
||||
onClick={() => store.actions.setPrintOrientation('landscape')}
|
||||
class={`px-3 py-1 rounded text-sm font-medium cursor-pointer border ${
|
||||
orientation() === 'landscape'
|
||||
? 'bg-blue-600 text-white border-blue-600'
|
||||
: 'bg-white text-gray-700 border-gray-300 hover:bg-gray-50'
|
||||
}`}
|
||||
>
|
||||
横向
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{/* 奇数页偏移 */}
|
||||
<div class="flex items-center gap-2">
|
||||
<label class="text-sm text-gray-600">奇数页偏移:</label>
|
||||
<div class="flex items-center gap-1">
|
||||
<span class="text-xs text-gray-500">X:</span>
|
||||
<input
|
||||
type="number"
|
||||
value={oddPageOffsetX()}
|
||||
onChange={(e) => store.actions.setPrintOddPageOffsetX(Number(e.target.value))}
|
||||
class="w-16 px-2 py-1 border border-gray-300 rounded text-sm"
|
||||
step="0.1"
|
||||
/>
|
||||
<span class="text-xs text-gray-500 ml-1">mm</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-1">
|
||||
<span class="text-xs text-gray-500">Y:</span>
|
||||
<input
|
||||
type="number"
|
||||
value={oddPageOffsetY()}
|
||||
onChange={(e) => store.actions.setPrintOddPageOffsetY(Number(e.target.value))}
|
||||
class="w-16 px-2 py-1 border border-gray-300 rounded text-sm"
|
||||
step="0.1"
|
||||
/>
|
||||
<span class="text-xs text-gray-500 ml-1">mm</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<button
|
||||
onClick={props.onPrint}
|
||||
|
|
@ -191,8 +275,8 @@ export function PrintPreview(props: PrintPreviewProps) {
|
|||
<div
|
||||
class="bg-white shadow-xl print:shadow-none print:w-full"
|
||||
style={{
|
||||
width: `${A4_WIDTH}mm`,
|
||||
height: `${A4_HEIGHT}mm`
|
||||
width: `${getA4Size().width}mm`,
|
||||
height: `${getA4Size().height}mm`
|
||||
}}
|
||||
data-page={page.pageIndex + 1}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -56,6 +56,11 @@ export interface DeckState {
|
|||
|
||||
// 打印状态
|
||||
isPrinting: boolean;
|
||||
|
||||
// 打印设置
|
||||
printOrientation: 'portrait' | 'landscape';
|
||||
printOddPageOffsetX: number;
|
||||
printOddPageOffsetY: number;
|
||||
}
|
||||
|
||||
export interface DeckActions {
|
||||
|
|
@ -101,6 +106,11 @@ export interface DeckActions {
|
|||
// 打印操作
|
||||
setPrinting: (printing: boolean) => void;
|
||||
printDeck: () => void;
|
||||
|
||||
// 打印设置
|
||||
setPrintOrientation: (orientation: 'portrait' | 'landscape') => void;
|
||||
setPrintOddPageOffsetX: (offset: number) => void;
|
||||
setPrintOddPageOffsetY: (offset: number) => void;
|
||||
}
|
||||
|
||||
export interface DeckStore {
|
||||
|
|
@ -136,7 +146,10 @@ export function createDeckStore(
|
|||
selectEnd: null,
|
||||
isLoading: false,
|
||||
error: null,
|
||||
isPrinting: false
|
||||
isPrinting: false,
|
||||
printOrientation: 'portrait',
|
||||
printOddPageOffsetX: 0,
|
||||
printOddPageOffsetY: 0
|
||||
});
|
||||
|
||||
// 更新尺寸并重新计算 dimensions
|
||||
|
|
@ -279,6 +292,18 @@ export function createDeckStore(
|
|||
setState({ isPrinting: true });
|
||||
};
|
||||
|
||||
const setPrintOrientation = (orientation: 'portrait' | 'landscape') => {
|
||||
setState({ printOrientation: orientation });
|
||||
};
|
||||
|
||||
const setPrintOddPageOffsetX = (offset: number) => {
|
||||
setState({ printOddPageOffsetX: offset });
|
||||
};
|
||||
|
||||
const setPrintOddPageOffsetY = (offset: number) => {
|
||||
setState({ printOddPageOffsetY: offset });
|
||||
};
|
||||
|
||||
const actions: DeckActions = {
|
||||
setSizeW,
|
||||
setSizeH,
|
||||
|
|
@ -306,7 +331,10 @@ export function createDeckStore(
|
|||
generateCode,
|
||||
copyCode,
|
||||
setPrinting,
|
||||
printDeck
|
||||
printDeck,
|
||||
setPrintOrientation,
|
||||
setPrintOddPageOffsetX,
|
||||
setPrintOddPageOffsetY
|
||||
};
|
||||
|
||||
return { state, actions };
|
||||
|
|
|
|||
|
|
@ -18,9 +18,4 @@
|
|||
width: 100vw;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
@page {
|
||||
size: A4;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue