diff --git a/src/components/md-deck/PrintPreview.tsx b/src/components/md-deck/PrintPreview.tsx
index bbb7898..ea6ffd1 100644
--- a/src/components/md-deck/PrintPreview.tsx
+++ b/src/components/md-deck/PrintPreview.tsx
@@ -195,114 +195,134 @@ export function PrintPreview(props: PrintPreviewProps) {
const handleExportPDF = async () => {
const pagesData = pages();
const a4Size = getA4Size();
+ const totalPages = pagesData.length;
- // 创建 jsPDF 实例
- const pdf = new jsPDF({
- orientation: orientation() === 'landscape' ? 'landscape' : 'portrait',
- unit: 'mm',
- format: 'a4'
- });
+ // 重置状态
+ store.actions.setExportProgress(0);
+ store.actions.setExportError(null);
- const cardWidth = store.state.dimensions?.cardWidth || 56;
- const cardHeight = store.state.dimensions?.cardHeight || 88;
- const gridOriginX = store.state.dimensions?.gridOriginX || 0;
- const gridOriginY = store.state.dimensions?.gridOriginY || 0;
- const gridAreaWidth = store.state.dimensions?.gridAreaWidth || cardWidth;
- const gridAreaHeight = store.state.dimensions?.gridAreaHeight || cardHeight;
- const fontSize = store.state.dimensions?.fontSize || 3;
+ try {
+ // 创建 jsPDF 实例
+ const pdf = new jsPDF({
+ orientation: orientation() === 'landscape' ? 'landscape' : 'portrait',
+ unit: 'mm',
+ format: 'a4'
+ });
- // 为每页生成内容
- for (let i = 0; i < pagesData.length; i++) {
- if (i > 0) {
- pdf.addPage();
- }
+ const cardWidth = store.state.dimensions?.cardWidth || 56;
+ const cardHeight = store.state.dimensions?.cardHeight || 88;
+ const gridOriginX = store.state.dimensions?.gridOriginX || 0;
+ const gridOriginY = store.state.dimensions?.gridOriginY || 0;
+ const gridAreaWidth = store.state.dimensions?.gridAreaWidth || cardWidth;
+ const gridAreaHeight = store.state.dimensions?.gridAreaHeight || cardHeight;
+ const fontSize = store.state.dimensions?.fontSize || 3;
- const page = pagesData[i];
- const cropData = cropMarks()[i];
-
- // 绘制外围边框
- const frameMargin = cropData.frameBoundsWithMargin;
- pdf.setDrawColor(0);
- pdf.setLineWidth(0.2);
- pdf.rect(frameMargin.x, frameMargin.y, frameMargin.width, frameMargin.height);
-
- // 绘制水平裁切线
- for (const line of cropData.horizontalLines) {
- pdf.setDrawColor(136);
- pdf.setLineWidth(0.1);
- // 左侧裁切线
- pdf.line(line.xStart, line.y, page.frameBounds.minX, line.y);
- // 右侧裁切线
- pdf.line(page.frameBounds.maxX, line.y, line.xEnd, line.y);
- }
-
- // 绘制垂直裁切线
- for (const line of cropData.verticalLines) {
- pdf.setDrawColor(136);
- pdf.setLineWidth(0.1);
- // 上方裁切线
- pdf.line(line.x, line.yStart, line.x, page.frameBounds.minY);
- // 下方裁切线
- pdf.line(line.x, page.frameBounds.maxY, line.x, line.yEnd);
- }
-
- // 渲染卡牌内容
- for (const card of page.cards) {
- // 创建临时容器渲染卡牌内容
- const container = document.createElement('div');
- container.style.position = 'absolute';
- container.style.left = '-9999px';
- container.style.top = '-9999px';
- container.style.width = `${cardWidth}mm`;
- container.style.height = `${cardHeight}mm`;
- container.style.background = 'white';
-
- // 网格区域容器
- const gridContainer = document.createElement('div');
- gridContainer.style.position = 'absolute';
- gridContainer.style.left = `${gridOriginX}mm`;
- gridContainer.style.top = `${gridOriginY}mm`;
- gridContainer.style.width = `${gridAreaWidth}mm`;
- gridContainer.style.height = `${gridAreaHeight}mm`;
-
- // 渲染每个 layer
- for (const layer of visibleLayers()) {
- const layerEl = document.createElement('div');
- layerEl.className = 'absolute flex items-center justify-center text-center prose prose-sm';
- Object.assign(layerEl.style, getLayerStyle(layer, store.state.dimensions!));
- layerEl.style.fontSize = `${fontSize}mm`;
- layerEl.innerHTML = renderLayerContent(layer, card.data);
- gridContainer.appendChild(layerEl);
+ // 为每页生成内容
+ for (let i = 0; i < totalPages; i++) {
+ if (i > 0) {
+ pdf.addPage();
}
- container.appendChild(gridContainer);
- document.body.appendChild(container);
+ const page = pagesData[i];
+ const cropData = cropMarks()[i];
- // 使用 html2canvas 渲染
- try {
- const html2canvas = (await import('html2canvas')).default;
- const canvas = await html2canvas(container, {
- scale: 2,
- backgroundColor: null,
- logging: false,
- useCORS: true
- });
+ // 绘制外围边框
+ const frameMargin = cropData.frameBoundsWithMargin;
+ pdf.setDrawColor(0);
+ pdf.setLineWidth(0.2);
+ pdf.rect(frameMargin.x, frameMargin.y, frameMargin.width, frameMargin.height);
- const imgData = canvas.toDataURL('image/png');
- pdf.addImage(imgData, 'PNG', card.x, card.y, cardWidth, cardHeight);
- } catch (e) {
- console.error('渲染卡牌内容失败:', e);
+ // 绘制水平裁切线
+ for (const line of cropData.horizontalLines) {
+ pdf.setDrawColor(136);
+ pdf.setLineWidth(0.1);
+ // 左侧裁切线
+ pdf.line(line.xStart, line.y, page.frameBounds.minX, line.y);
+ // 右侧裁切线
+ pdf.line(page.frameBounds.maxX, line.y, line.xEnd, line.y);
}
- document.body.removeChild(container);
+ // 绘制垂直裁切线
+ for (const line of cropData.verticalLines) {
+ pdf.setDrawColor(136);
+ pdf.setLineWidth(0.1);
+ // 上方裁切线
+ pdf.line(line.x, line.yStart, line.x, page.frameBounds.minY);
+ // 下方裁切线
+ pdf.line(line.x, page.frameBounds.maxY, line.x, line.yEnd);
+ }
+
+ // 渲染卡牌内容
+ const totalCards = page.cards.length;
+ for (let j = 0; j < totalCards; j++) {
+ const card = page.cards[j];
+
+ // 创建临时容器渲染卡牌内容
+ const container = document.createElement('div');
+ container.style.position = 'absolute';
+ container.style.left = '-9999px';
+ container.style.top = '-9999px';
+ container.style.width = `${cardWidth}mm`;
+ container.style.height = `${cardHeight}mm`;
+ container.style.background = 'white';
+
+ // 网格区域容器
+ const gridContainer = document.createElement('div');
+ gridContainer.style.position = 'absolute';
+ gridContainer.style.left = `${gridOriginX}mm`;
+ gridContainer.style.top = `${gridOriginY}mm`;
+ gridContainer.style.width = `${gridAreaWidth}mm`;
+ gridContainer.style.height = `${gridAreaHeight}mm`;
+
+ // 渲染每个 layer
+ for (const layer of visibleLayers()) {
+ const layerEl = document.createElement('div');
+ layerEl.className = 'absolute flex items-center justify-center text-center prose prose-sm';
+ Object.assign(layerEl.style, getLayerStyle(layer, store.state.dimensions!));
+ layerEl.style.fontSize = `${fontSize}mm`;
+ layerEl.innerHTML = renderLayerContent(layer, card.data);
+ gridContainer.appendChild(layerEl);
+ }
+
+ container.appendChild(gridContainer);
+ document.body.appendChild(container);
+
+ // 使用 html2canvas 渲染
+ try {
+ const html2canvas = (await import('html2canvas')).default;
+ const canvas = await html2canvas(container, {
+ scale: 2,
+ backgroundColor: null,
+ logging: false,
+ useCORS: true
+ });
+
+ const imgData = canvas.toDataURL('image/png');
+ pdf.addImage(imgData, 'PNG', card.x, card.y, cardWidth, cardHeight);
+ } catch (e) {
+ console.error('渲染卡牌内容失败:', e);
+ }
+
+ document.body.removeChild(container);
+
+ // 更新进度(按卡牌数量计算)
+ const currentCardIndex = i * totalCards + j + 1;
+ const totalCardCount = totalPages * totalCards;
+ const progress = Math.round((currentCardIndex / totalCardCount) * 100);
+ store.actions.setExportProgress(progress);
+ }
}
+
+ // 保存 PDF 文件
+ pdf.save('deck.pdf');
+
+ // 完成,关闭预览
+ props.onClose();
+ } catch (err) {
+ const errorMsg = err instanceof Error ? err.message : '导出失败,未知错误';
+ store.actions.setExportError(errorMsg);
+ console.error('PDF 导出失败:', err);
}
-
- // 保存 PDF 文件
- pdf.save('deck.pdf');
-
- // 关闭预览
- props.onClose();
};
// 渲染单个卡片的 SVG 内容(使用 foreignObject)
@@ -435,6 +455,38 @@ export function PrintPreview(props: PrintPreviewProps) {
+ {/* 进度条和错误信息 */}
+