feat: error handling

This commit is contained in:
hyper 2026-02-27 21:02:33 +08:00
parent 24c797abc6
commit 47c15e933c
2 changed files with 165 additions and 97 deletions

View File

@ -195,7 +195,13 @@ export function PrintPreview(props: PrintPreviewProps) {
const handleExportPDF = async () => { const handleExportPDF = async () => {
const pagesData = pages(); const pagesData = pages();
const a4Size = getA4Size(); const a4Size = getA4Size();
const totalPages = pagesData.length;
// 重置状态
store.actions.setExportProgress(0);
store.actions.setExportError(null);
try {
// 创建 jsPDF 实例 // 创建 jsPDF 实例
const pdf = new jsPDF({ const pdf = new jsPDF({
orientation: orientation() === 'landscape' ? 'landscape' : 'portrait', orientation: orientation() === 'landscape' ? 'landscape' : 'portrait',
@ -212,7 +218,7 @@ export function PrintPreview(props: PrintPreviewProps) {
const fontSize = store.state.dimensions?.fontSize || 3; const fontSize = store.state.dimensions?.fontSize || 3;
// 为每页生成内容 // 为每页生成内容
for (let i = 0; i < pagesData.length; i++) { for (let i = 0; i < totalPages; i++) {
if (i > 0) { if (i > 0) {
pdf.addPage(); pdf.addPage();
} }
@ -247,7 +253,10 @@ export function PrintPreview(props: PrintPreviewProps) {
} }
// 渲染卡牌内容 // 渲染卡牌内容
for (const card of page.cards) { const totalCards = page.cards.length;
for (let j = 0; j < totalCards; j++) {
const card = page.cards[j];
// 创建临时容器渲染卡牌内容 // 创建临时容器渲染卡牌内容
const container = document.createElement('div'); const container = document.createElement('div');
container.style.position = 'absolute'; container.style.position = 'absolute';
@ -295,14 +304,25 @@ export function PrintPreview(props: PrintPreviewProps) {
} }
document.body.removeChild(container); 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 文件
pdf.save('deck.pdf'); pdf.save('deck.pdf');
// 关闭预览 // 完成,关闭预览
props.onClose(); props.onClose();
} catch (err) {
const errorMsg = err instanceof Error ? err.message : '导出失败,未知错误';
store.actions.setExportError(errorMsg);
console.error('PDF 导出失败:', err);
}
}; };
// 渲染单个卡片的 SVG 内容(使用 foreignObject // 渲染单个卡片的 SVG 内容(使用 foreignObject
@ -435,6 +455,38 @@ export function PrintPreview(props: PrintPreviewProps) {
</div> </div>
</div> </div>
{/* 进度条和错误信息 */}
<Show when={store.state.exportProgress > 0 || store.state.exportError}>
<div class="fixed bottom-0 left-0 right-0 z-50 bg-white shadow-lg rounded-lg mx-4 mb-4 px-4 py-3">
<Show when={!store.state.exportError}>
<div class="flex items-center gap-4">
<span class="text-sm text-gray-600"></span>
<div class="flex-1 h-2 bg-gray-200 rounded-full overflow-hidden">
<div
class="h-full bg-blue-600 transition-all duration-200"
style={{ width: `${store.state.exportProgress}%` }}
/>
</div>
<span class="text-sm font-medium text-gray-700">{store.state.exportProgress}%</span>
</div>
</Show>
<Show when={store.state.exportError}>
<div class="flex items-center justify-between gap-4">
<div class="flex items-center gap-2 text-red-600">
<span></span>
<span class="text-sm font-medium">{store.state.exportError}</span>
</div>
<button
onClick={() => store.actions.clearExportError()}
class="text-gray-500 hover:text-gray-700 cursor-pointer"
>
</button>
</div>
</Show>
</div>
</Show>
{/* A4 纸张预览:每页都是一个完整的 SVG */} {/* A4 纸张预览:每页都是一个完整的 SVG */}
<div class="flex flex-col items-center gap-8"> <div class="flex flex-col items-center gap-8">
<For each={pages()}> <For each={pages()}>

View File

@ -56,6 +56,8 @@ export interface DeckState {
// 导出状态 // 导出状态
isExporting: boolean; isExporting: boolean;
exportProgress: number; // 0-100
exportError: string | null;
// 打印设置 // 打印设置
printOrientation: 'portrait' | 'landscape'; printOrientation: 'portrait' | 'landscape';
@ -106,6 +108,9 @@ export interface DeckActions {
// 导出操作 // 导出操作
setExporting: (exporting: boolean) => void; setExporting: (exporting: boolean) => void;
exportDeck: () => void; exportDeck: () => void;
setExportProgress: (progress: number) => void;
setExportError: (error: string | null) => void;
clearExportError: () => void;
// 打印设置 // 打印设置
setPrintOrientation: (orientation: 'portrait' | 'landscape') => void; setPrintOrientation: (orientation: 'portrait' | 'landscape') => void;
@ -147,6 +152,8 @@ export function createDeckStore(
isLoading: false, isLoading: false,
error: null, error: null,
isExporting: false, isExporting: false,
exportProgress: 0,
exportError: null,
printOrientation: 'portrait', printOrientation: 'portrait',
printOddPageOffsetX: 0, printOddPageOffsetX: 0,
printOddPageOffsetY: 0 printOddPageOffsetY: 0
@ -289,9 +296,15 @@ export function createDeckStore(
const setExporting = (exporting: boolean) => setState({ isExporting: exporting }); const setExporting = (exporting: boolean) => setState({ isExporting: exporting });
const exportDeck = () => { const exportDeck = () => {
setState({ isExporting: true }); setState({ isExporting: true, exportProgress: 0, exportError: null });
}; };
const setExportProgress = (progress: number) => setState({ exportProgress: progress });
const setExportError = (error: string | null) => setState({ exportError: error });
const clearExportError = () => setState({ exportError: null });
const setPrintOrientation = (orientation: 'portrait' | 'landscape') => { const setPrintOrientation = (orientation: 'portrait' | 'landscape') => {
setState({ printOrientation: orientation }); setState({ printOrientation: orientation });
}; };
@ -332,6 +345,9 @@ export function createDeckStore(
copyCode, copyCode,
setExporting, setExporting,
exportDeck, exportDeck,
setExportProgress,
setExportError,
clearExportError,
setPrintOrientation, setPrintOrientation,
setPrintOddPageOffsetX, setPrintOddPageOffsetX,
setPrintOddPageOffsetY setPrintOddPageOffsetY