Lashtw commited on
Commit
bf43006
·
verified ·
1 Parent(s): 69e728a

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +151 -30
index.html CHANGED
@@ -15,6 +15,9 @@
15
  <script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
16
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
17
 
 
 
 
18
  <!-- Cropper.js for Image Cropping -->
19
  <link href="https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.5.13/cropper.min.css" rel="stylesheet">
20
  <script src="https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.5.13/cropper.min.js"></script>
@@ -435,9 +438,8 @@
435
  class="w-full py-4 bg-blue-600 hover:bg-blue-700 text-white font-bold rounded-xl shadow-lg hover:shadow-xl active:scale-95 transition-all flex items-center justify-center gap-2 disabled:opacity-70 disabled:cursor-wait"
436
  >
437
  <span v-if="!isGenerating">
438
- <!-- PDF Icon -->
439
- <svg viewBox="0 0 384 512" class="w-5 h-5 fill-current inline-block"><path d="M181.9 256.1c-5-16-4.9-46.9-2-46.9 8.4 0 7.6 36.9 2 46.9zm-1.7 47.2c-7.7 20.2-17.3 43.3-28.4 62.7 18.3-7 39-17.2 62.9-21.9-12.7-9.6-24.9-23.4-34.5-40.8zM86.1 428.1c0 .8 13.2-5.4 34.9-40.2-6.7 6.3-29.1 24.5-34.9 40.2zM248 160h136v328c0 13.3-10.7 24-24 24H24c-13.3 0-24-10.7-24-24V24C0 10.7 10.7 0 24 0h200v136c0 13.2 10.8 24 24 24zm-8 171.8c-20-12.2-33.3-29-42.7-53.8 4.5-18.5 11.6-46.6 6.2-64.2-4.7-29.4-42.4-26.5-47.8-6.8-5 18.3-.4 44.1 8.1 77-11.6 27.6-28.7 64.6-40.8 85.8-.1 0-.1.1-.2.1-27.1 13.9-73.6 44.5-54.5 68 5.6 6.9 16 10 21.5 10 17.9 0 35.7-18 61.1-61.8 25.8-8.5 54.1-19.1 79-23.2 21.7 11.8 47.1 19.5 64 19.5 29.2 0 31.2-32 19.7-43.4-13.9-13.6-54.3-9.7-73.6-7.2zM377 105L279 7c-4.5-4.5-10.6-7-17-7h-6v128h128v-6.1c0-6.3-2.5-12.4-7-16.9zm-74.1 255.3c4.1-2.7-2.5-11.9-4.2-17.9-2.9-4.1-32.9-14.9-49.6-19.7 1.4 4.9 3 9.8 4.2 14.6 3.1 17.9 6.9 51.1 12.8 62.1 9.5 1 34.1-33 36.8-39.1z"/></svg>
440
- 下載雙頁 PDF
441
  </span>
442
  <span v-else>
443
  <!-- Spinner -->
@@ -490,16 +492,16 @@
490
  </div>
491
  </div>
492
 
493
- <!-- Export PDF Modal -->
494
  <div v-if="showExportModal" class="absolute inset-0 z-50 bg-slate-900/50 flex items-center justify-center p-4 animate-fade-in">
495
  <div class="bg-white rounded-xl shadow-2xl w-full max-w-sm overflow-hidden">
496
  <div class="bg-indigo-600 px-6 py-4">
497
  <h3 class="text-lg font-bold text-white flex items-center gap-2">
498
- <i class="fa-solid fa-print"></i> 匯出 PDF 設定
499
  </h3>
500
  </div>
501
  <div class="p-6 space-y-4">
502
- <p class="text-sm text-slate-600 mb-2">請輸入資訊以產生專屬浮水印(可略過):</p>
503
  <div>
504
  <label class="block text-sm font-bold text-slate-700 mb-1">姓名 (選填)</label>
505
  <input type="text" v-model="exportName" placeholder="例如:王小明" class="w-full border-2 border-slate-200 rounded-lg p-2 focus:border-indigo-500 focus:outline-none">
@@ -508,13 +510,23 @@
508
  <label class="block text-sm font-bold text-slate-700 mb-1">作品名稱 (選填)</label>
509
  <input type="text" v-model="exportProjectName" placeholder="例如:我的幸運草" class="w-full border-2 border-slate-200 rounded-lg p-2 focus:border-indigo-500 focus:outline-none">
510
  </div>
 
 
 
 
 
 
 
 
511
  </div>
512
- <div class="bg-slate-50 px-6 py-4 flex justify-end gap-2 border-t border-slate-100">
513
- <button @click="showExportModal = false" class="px-4 py-2 text-slate-600 font-bold hover:bg-slate-200 rounded-lg transition-all">取消</button>
514
- <button @click="processExport" class="px-4 py-2 bg-indigo-600 text-white font-bold rounded-lg hover:bg-indigo-700 transition-all shadow-md flex items-center gap-2">
515
- <span v-if="isGenerating"><i class="fa-solid fa-circle-notch fa-spin"></i> 處理中</span>
516
- <span v-else>確認下載</span>
 
517
  </button>
 
518
  </div>
519
  </div>
520
  </div>
@@ -739,6 +751,8 @@
739
  const showExportModal = ref(false);
740
  const exportName = ref('');
741
  const exportProjectName = ref('');
 
 
742
 
743
  // Image Upload & Crop State
744
  const showCropper = ref(false);
@@ -1373,12 +1387,16 @@
1373
  showExportModal.value = true;
1374
  };
1375
 
1376
- const processExport = () => {
1377
  showExportModal.value = false;
1378
- exportPDF();
 
 
 
 
1379
  };
1380
 
1381
- const renderPageToCanvas = async (pageId) => {
1382
  const pageData = pages.value[pageId - 1];
1383
  const rows = currentGrid.value.rows;
1384
  const cols = currentGrid.value.cols;
@@ -1392,6 +1410,9 @@
1392
  wrapper.style.backgroundColor = 'white';
1393
  wrapper.style.position = 'relative';
1394
 
 
 
 
1395
  let gridHtml = `<div style="display: grid; grid-template-columns: repeat(${cols}, 1fr); grid-template-rows: repeat(${rows}, 1fr); width: 100%; height: 100%; border: 1px solid #e2e8f0;">`;
1396
 
1397
  pageData.cells.forEach((cell, idx) => {
@@ -1411,7 +1432,7 @@
1411
  }
1412
 
1413
  gridHtml += `
1414
- <div class="grid-cell" style="position: relative; border: 1px solid #cbd5e1; background-color: ${cell.bgColor}; display: flex; align-items: center; justify-content: center; overflow: hidden;">
1415
  <div style="width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; transform: rotate(${cell.rotation}deg);">
1416
  ${contentHtml}
1417
  </div>
@@ -1457,30 +1478,23 @@
1457
 
1458
  // Determine filename
1459
  let fileName = 'magic-origami-booklet.pdf';
1460
- if (exportName.value || exportProjectName.value) {
1461
- const namePart = exportName.value ? exportName.value : '';
1462
- const projPart = exportProjectName.value ? exportProjectName.value : '';
1463
-
1464
- if (namePart && projPart) {
1465
- fileName = `${namePart}的${projPart}.pdf`;
1466
- } else if (namePart) {
1467
- fileName = `${namePart}的作品.pdf`;
1468
- } else if (projPart) {
1469
- fileName = `${projPart}.pdf`;
1470
- }
1471
- }
1472
 
1473
  try {
1474
  const pdf = new jsPDF('p', 'mm', 'a4');
1475
  const pdfWidth = 210;
1476
  const pdfHeight = 297;
1477
 
1478
- const canvas1 = await renderPageToCanvas(1);
 
1479
  const imgData1 = canvas1.toDataURL('image/jpeg', 0.95);
1480
  pdf.addImage(imgData1, 'JPEG', 0, 0, pdfWidth, pdfHeight);
1481
 
1482
  pdf.addPage();
1483
- const canvas2 = await renderPageToCanvas(2);
 
1484
  const imgData2 = canvas2.toDataURL('image/jpeg', 0.95);
1485
  pdf.addImage(imgData2, 'JPEG', 0, 0, pdfWidth, pdfHeight);
1486
 
@@ -1495,6 +1509,113 @@
1495
  }
1496
  };
1497
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1498
  return {
1499
  gridOptions,
1500
  currentGrid,
@@ -1525,7 +1646,6 @@
1525
  applyIconToCell,
1526
  clearCurrentCell,
1527
  clearAllContent,
1528
- exportPDF,
1529
  exportProject,
1530
  triggerImport,
1531
  importProject,
@@ -1556,6 +1676,7 @@
1556
  processExport,
1557
  exportName,
1558
  exportProjectName,
 
1559
  // Multi-select
1560
  isMultiSelectMode,
1561
  toggleMultiSelectMode,
 
15
  <script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
16
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
17
 
18
+ <!-- PPTX Generation Library -->
19
+ <script src="https://cdn.jsdelivr.net/npm/pptxgenjs@3.12.0/dist/pptxgen.bundle.js"></script>
20
+
21
  <!-- Cropper.js for Image Cropping -->
22
  <link href="https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.5.13/cropper.min.css" rel="stylesheet">
23
  <script src="https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.5.13/cropper.min.js"></script>
 
438
  class="w-full py-4 bg-blue-600 hover:bg-blue-700 text-white font-bold rounded-xl shadow-lg hover:shadow-xl active:scale-95 transition-all flex items-center justify-center gap-2 disabled:opacity-70 disabled:cursor-wait"
439
  >
440
  <span v-if="!isGenerating">
441
+ <i class="fa-solid fa-file-export"></i>
442
+ 匯出設計 (PDF / PPTX)
 
443
  </span>
444
  <span v-else>
445
  <!-- Spinner -->
 
492
  </div>
493
  </div>
494
 
495
+ <!-- Export PDF/PPTX Modal -->
496
  <div v-if="showExportModal" class="absolute inset-0 z-50 bg-slate-900/50 flex items-center justify-center p-4 animate-fade-in">
497
  <div class="bg-white rounded-xl shadow-2xl w-full max-w-sm overflow-hidden">
498
  <div class="bg-indigo-600 px-6 py-4">
499
  <h3 class="text-lg font-bold text-white flex items-center gap-2">
500
+ <i class="fa-solid fa-file-export"></i> 匯出設定
501
  </h3>
502
  </div>
503
  <div class="p-6 space-y-4">
504
+ <p class="text-sm text-slate-600 mb-2">產生專屬浮水印(可略過):</p>
505
  <div>
506
  <label class="block text-sm font-bold text-slate-700 mb-1">姓名 (選填)</label>
507
  <input type="text" v-model="exportName" placeholder="例如:王小明" class="w-full border-2 border-slate-200 rounded-lg p-2 focus:border-indigo-500 focus:outline-none">
 
510
  <label class="block text-sm font-bold text-slate-700 mb-1">作品名稱 (選填)</label>
511
  <input type="text" v-model="exportProjectName" placeholder="例如:我的幸運草" class="w-full border-2 border-slate-200 rounded-lg p-2 focus:border-indigo-500 focus:outline-none">
512
  </div>
513
+
514
+ <!-- NEW: Checkbox for removing grid on page 2 -->
515
+ <div class="flex items-center gap-2 border-t border-slate-100 pt-3">
516
+ <input type="checkbox" id="removeGridPage2" v-model="removeGridOnPage2" class="w-4 h-4 text-indigo-600 rounded border-slate-300 focus:ring-indigo-500">
517
+ <label for="removeGridPage2" class="text-sm font-bold text-slate-700 cursor-pointer">
518
+ 消除第二頁格線 <span class="text-xs font-normal text-slate-500">(解決雙面列印偏移問題)</span>
519
+ </label>
520
+ </div>
521
  </div>
522
+ <div class="bg-slate-50 px-6 py-4 flex flex-col gap-2 border-t border-slate-100">
523
+ <button @click="processExport('pdf')" class="w-full py-3 bg-indigo-600 hover:bg-indigo-700 text-white font-bold rounded-lg transition-all shadow-md flex items-center justify-center gap-2">
524
+ <i class="fa-solid fa-file-pdf"></i> 下載雙頁 PDF
525
+ </button>
526
+ <button @click="processExport('pptx')" class="w-full py-3 bg-orange-500 hover:bg-orange-600 text-white font-bold rounded-lg transition-all shadow-md flex items-center justify-center gap-2">
527
+ <i class="fa-solid fa-file-powerpoint"></i> 下載可編輯 PPTX
528
  </button>
529
+ <button @click="showExportModal = false" class="w-full py-2 text-slate-500 hover:text-slate-700 font-bold text-sm mt-1">取消</button>
530
  </div>
531
  </div>
532
  </div>
 
751
  const showExportModal = ref(false);
752
  const exportName = ref('');
753
  const exportProjectName = ref('');
754
+ // NEW: Grid removal for PDF
755
+ const removeGridOnPage2 = ref(false);
756
 
757
  // Image Upload & Crop State
758
  const showCropper = ref(false);
 
1387
  showExportModal.value = true;
1388
  };
1389
 
1390
+ const processExport = (type) => {
1391
  showExportModal.value = false;
1392
+ if (type === 'pdf') {
1393
+ exportPDF();
1394
+ } else if (type === 'pptx') {
1395
+ exportPPTX();
1396
+ }
1397
  };
1398
 
1399
+ const renderPageToCanvas = async (pageId, hideGrid = false) => {
1400
  const pageData = pages.value[pageId - 1];
1401
  const rows = currentGrid.value.rows;
1402
  const cols = currentGrid.value.cols;
 
1410
  wrapper.style.backgroundColor = 'white';
1411
  wrapper.style.position = 'relative';
1412
 
1413
+ // Logic for grid borders
1414
+ const borderStyle = hideGrid ? 'border: none;' : 'border: 1px solid #cbd5e1;';
1415
+
1416
  let gridHtml = `<div style="display: grid; grid-template-columns: repeat(${cols}, 1fr); grid-template-rows: repeat(${rows}, 1fr); width: 100%; height: 100%; border: 1px solid #e2e8f0;">`;
1417
 
1418
  pageData.cells.forEach((cell, idx) => {
 
1432
  }
1433
 
1434
  gridHtml += `
1435
+ <div class="grid-cell" style="position: relative; ${borderStyle} background-color: ${cell.bgColor}; display: flex; align-items: center; justify-content: center; overflow: hidden;">
1436
  <div style="width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; transform: rotate(${cell.rotation}deg);">
1437
  ${contentHtml}
1438
  </div>
 
1478
 
1479
  // Determine filename
1480
  let fileName = 'magic-origami-booklet.pdf';
1481
+ const namePart = exportName.value ? exportName.value : '';
1482
+ const projPart = exportProjectName.value ? exportProjectName.value : '';
1483
+ if (namePart || projPart) fileName = `${namePart}${namePart && projPart ? '-' : ''}${projPart || '作品'}.pdf`;
 
 
 
 
 
 
 
 
 
1484
 
1485
  try {
1486
  const pdf = new jsPDF('p', 'mm', 'a4');
1487
  const pdfWidth = 210;
1488
  const pdfHeight = 297;
1489
 
1490
+ // Page 1 (Never hide grid)
1491
+ const canvas1 = await renderPageToCanvas(1, false);
1492
  const imgData1 = canvas1.toDataURL('image/jpeg', 0.95);
1493
  pdf.addImage(imgData1, 'JPEG', 0, 0, pdfWidth, pdfHeight);
1494
 
1495
  pdf.addPage();
1496
+ // Page 2 (Hide grid if requested)
1497
+ const canvas2 = await renderPageToCanvas(2, removeGridOnPage2.value);
1498
  const imgData2 = canvas2.toDataURL('image/jpeg', 0.95);
1499
  pdf.addImage(imgData2, 'JPEG', 0, 0, pdfWidth, pdfHeight);
1500
 
 
1509
  }
1510
  };
1511
 
1512
+ const exportPPTX = async () => {
1513
+ if (selectedCellIndices.value.size > 0) clearSelection();
1514
+ isGenerating.value = true;
1515
+
1516
+ try {
1517
+ const pres = new PptxGenJS();
1518
+ pres.layout = 'A4'; // 8.27 x 11.69 inches
1519
+
1520
+ const pageWidth = 8.27;
1521
+ const pageHeight = 11.69;
1522
+ const rows = currentGrid.value.rows;
1523
+ const cols = currentGrid.value.cols;
1524
+ const cellW = pageWidth / cols;
1525
+ const cellH = pageHeight / rows;
1526
+
1527
+ // Determine filename
1528
+ let fileName = 'magic-origami-project';
1529
+ const namePart = exportName.value ? exportName.value : '';
1530
+ const projPart = exportProjectName.value ? exportProjectName.value : '';
1531
+ if (namePart || projPart) fileName = `${namePart}${namePart && projPart ? '-' : ''}${projPart || '作品'}`;
1532
+
1533
+ pages.value.forEach((pageData, pageIndex) => {
1534
+ const slide = pres.addSlide();
1535
+
1536
+ // Add cells
1537
+ pageData.cells.forEach((cell, idx) => {
1538
+ const r = Math.floor(idx / cols);
1539
+ const c = idx % cols;
1540
+ const x = c * cellW;
1541
+ const y = r * cellH;
1542
+
1543
+ // 1. Background Rect
1544
+ if (cell.bgColor !== '#ffffff') {
1545
+ slide.addShape(pres.ShapeType.rect, {
1546
+ x: x, y: y, w: cellW, h: cellH,
1547
+ fill: { color: cell.bgColor.replace('#', '') },
1548
+ line: { color: 'CCCCCC', width: 0.5 } // Keep grid lines
1549
+ });
1550
+ } else {
1551
+ // Transparent/White bg but need grid lines
1552
+ slide.addShape(pres.ShapeType.rect, {
1553
+ x: x, y: y, w: cellW, h: cellH,
1554
+ fill: { color: 'FFFFFF' },
1555
+ line: { color: 'CCCCCC', width: 0.5 }
1556
+ });
1557
+ }
1558
+
1559
+ // 2. Content
1560
+ if (cell.content) {
1561
+ if (cell.type === 'text' || cell.type === 'icon') {
1562
+ let content = cell.content;
1563
+ if (cell.type === 'icon') content = icons[cell.content] || cell.content;
1564
+
1565
+ // Determine font size roughly
1566
+ let fontSize = 24;
1567
+ const len = content.length;
1568
+ if (len >= 3) fontSize = 20;
1569
+ if (len === 2) fontSize = 28;
1570
+ if (len === 1) fontSize = 36;
1571
+ if (cell.type === 'icon') fontSize = 32;
1572
+
1573
+ slide.addText(content, {
1574
+ x: x, y: y, w: cellW, h: cellH,
1575
+ align: 'center',
1576
+ valign: 'middle',
1577
+ fontSize: fontSize,
1578
+ fontFace: 'Arial', // Fallback font
1579
+ color: cell.color.replace('#', ''),
1580
+ rotate: cell.rotation
1581
+ });
1582
+ } else if (cell.type === 'image') {
1583
+ // Image
1584
+ // Calculate image size to fit within cell (80% padding like HTML)
1585
+ const padW = cellW * 0.1;
1586
+ const padH = cellH * 0.1;
1587
+ slide.addImage({
1588
+ data: cell.content,
1589
+ x: x + padW,
1590
+ y: y + padH,
1591
+ w: cellW * 0.8,
1592
+ h: cellH * 0.8,
1593
+ rotate: cell.rotation
1594
+ });
1595
+ }
1596
+ }
1597
+ });
1598
+
1599
+ // Watermark
1600
+ if (exportName.value || exportProjectName.value) {
1601
+ const watermarkText = `${exportName.value ? exportName.value + '的' : ''}${exportProjectName.value || ''}`;
1602
+ slide.addText(watermarkText, {
1603
+ x: pageWidth - 3, y: pageHeight - 0.8, w: 2.5, h: 0.5,
1604
+ align: 'right', fontSize: 10, color: '999999'
1605
+ });
1606
+ }
1607
+ });
1608
+
1609
+ await pres.writeFile({ fileName: `${fileName}.pptx` });
1610
+
1611
+ } catch (err) {
1612
+ console.error("PPTX Error", err);
1613
+ alert("PPTX 生成發生錯誤");
1614
+ } finally {
1615
+ isGenerating.value = false;
1616
+ }
1617
+ };
1618
+
1619
  return {
1620
  gridOptions,
1621
  currentGrid,
 
1646
  applyIconToCell,
1647
  clearCurrentCell,
1648
  clearAllContent,
 
1649
  exportProject,
1650
  triggerImport,
1651
  importProject,
 
1676
  processExport,
1677
  exportName,
1678
  exportProjectName,
1679
+ removeGridOnPage2, // Export ref
1680
  // Multi-select
1681
  isMultiSelectMode,
1682
  toggleMultiSelectMode,