Lashtw commited on
Commit
a65b09b
·
verified ·
1 Parent(s): 990a72a

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +211 -35
index.html CHANGED
@@ -224,7 +224,7 @@
224
  <li v-if="viewMode === 'overview'"><strong>點擊頁面:</strong>進入該頁編輯模式</li>
225
  <li v-else><strong>目前編輯:</strong>第 {{ activePageId }} 頁</li>
226
  <li><strong>點擊格子:</strong>選取編輯</li>
227
- <li><strong>Ctrl + 點擊:</strong>可多選格子一起編輯</li>
228
  <li><strong>再次點擊:</strong>單選時旋轉 90°</li>
229
  </ul>
230
  </div>
@@ -232,9 +232,39 @@
232
  <!-- Editing Controls -->
233
  <div v-if="viewMode === 'edit'" class="space-y-8 animate-fade-in pb-10">
234
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
235
  <!-- Selection Summary -->
236
- <div v-if="selectedCellIndices.size > 1" class="bg-indigo-100 px-4 py-2 rounded-lg text-indigo-700 font-bold text-sm text-center">
237
- 已選取 {{ selectedCellIndices.size }} 個格子
 
 
 
 
 
 
 
 
 
 
 
 
238
  </div>
239
 
240
  <!-- Colors -->
@@ -278,8 +308,24 @@
278
 
279
  <!-- Text Input -->
280
  <div>
281
- <label class="block text-sm font-bold text-slate-700 mb-2">3. 文字輸入 (最多3字)</label>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
282
  <input
 
283
  ref="textInputRef"
284
  type="text"
285
  v-model="inputBuffer"
@@ -291,7 +337,8 @@
291
  :class="selectedCellIndices.size === 0 ? 'bg-slate-100 border-slate-200 cursor-not-allowed' : 'bg-white border-indigo-300 focus:border-indigo-500 focus:ring-indigo-200'"
292
  :style="{ color: selectedCellIndices.size > 0 ? selectedColor : '' }"
293
  >
294
- <p class="text-xs text-slate-400 mt-1">提示:輸入 2~3 個字或數字時,字體會自動縮小。</p>
 
295
  </div>
296
 
297
  <!-- Icon Picker -->
@@ -345,16 +392,6 @@
345
  </div>
346
  </div>
347
 
348
- <!-- Cell Info -->
349
- <div v-if="selectedCellIndices.size > 0" class="bg-slate-100 p-4 rounded-lg flex justify-between items-center">
350
- <div class="text-sm font-bold text-slate-600">
351
- 已選 {{ selectedCellIndices.size }} 格
352
- </div>
353
- <button @click="rotateCurrentCell" class="px-3 py-1 bg-white border border-slate-300 rounded shadow-sm text-sm">
354
- 旋轉選取項目 (90°)
355
- </button>
356
- </div>
357
-
358
  <div>
359
  <button
360
  @click="clearCurrentCell"
@@ -375,7 +412,7 @@
375
  <!-- Footer Action -->
376
  <div class="p-6 border-t border-slate-200 bg-slate-50">
377
  <button
378
- @click="exportPDF"
379
  :disabled="isGenerating"
380
  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"
381
  >
@@ -435,6 +472,35 @@
435
  </div>
436
  </div>
437
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
438
  </div>
439
 
440
  <!-- =======================
@@ -540,6 +606,14 @@
540
  <a href="https://www.facebook.com/groups/1554372228718393" target="_blank" class="text-indigo-500 hover:text-indigo-700 font-bold hover:underline">萬物皆數</a>、
541
  <a href="https://www.facebook.com/groups/108923286120994" target="_blank" class="text-indigo-500 hover:text-indigo-700 font-bold hover:underline">藝數摺學</a>
542
  </p>
 
 
 
 
 
 
 
 
543
  </div>
544
  </div>
545
 
@@ -609,15 +683,19 @@
609
  const activePageId = ref(1);
610
 
611
  // Replaced single index with Set for multi-select
 
612
  const selectedCellIndices = ref(new Set());
 
613
 
614
  const inputBuffer = ref('');
 
615
  const selectedColor = ref('#1e293b');
616
  const selectedBgColor = ref('#ffffff');
617
  const isGenerating = ref(false);
618
 
619
  // Refs
620
  const textInputRef = ref(null);
 
621
  const fileInputRef = ref(null);
622
  const imageUploadInput = ref(null);
623
  const cropperImgRef = ref(null);
@@ -626,6 +704,11 @@
626
  const showGridModal = ref(false);
627
  const customGridConfig = ref({ rows: 10, cols: 10 });
628
 
 
 
 
 
 
629
  // Image Upload & Crop State
630
  const showCropper = ref(false);
631
  const tempImageSrc = ref('');
@@ -681,7 +764,7 @@
681
  { id: 2, cells: createPageCells(totalCells, conf.rows, conf.cols) }
682
  ];
683
 
684
- selectedCellIndices.value.clear();
685
  activePageId.value = 1;
686
  viewMode.value = 'overview';
687
  };
@@ -716,7 +799,7 @@
716
  { id: 2, cells: createPageCells(totalCells, r, c) }
717
  ];
718
 
719
- selectedCellIndices.value.clear();
720
  activePageId.value = 1;
721
  viewMode.value = 'overview';
722
  showGridModal.value = false;
@@ -739,8 +822,7 @@
739
  { id: 2, cells: createPageCells(totalCells, rows, cols) }
740
  ];
741
 
742
- selectedCellIndices.value.clear();
743
- inputBuffer.value = '';
744
  alert("內容已清空!");
745
  }
746
  };
@@ -832,6 +914,7 @@
832
  cell.content = imgSrc;
833
  });
834
  inputBuffer.value = '';
 
835
  };
836
 
837
  const removeCustomImage = (idx) => {
@@ -884,7 +967,7 @@
884
  customImages.value = importedData.customImages;
885
  }
886
 
887
- selectedCellIndices.value.clear();
888
  activePageId.value = 1;
889
  viewMode.value = 'overview';
890
 
@@ -944,7 +1027,7 @@
944
  setCell(1, 8, 6, 'text', '海', 180);
945
  }
946
 
947
- selectedCellIndices.value.clear();
948
  activePageId.value = 1;
949
  viewMode.value = 'overview';
950
 
@@ -959,21 +1042,31 @@
959
  const switchToPage = (pageId) => {
960
  activePageId.value = pageId;
961
  viewMode.value = 'edit';
962
- selectedCellIndices.value.clear();
963
  inputBuffer.value = '';
964
  };
965
 
966
- // Enhanced Click Handler for Multi-select
 
 
 
 
 
 
 
 
967
  const handleCellClick = (index, event) => {
968
- // If Ctrl/Meta key is pressed, toggle selection
969
- if (event && (event.ctrlKey || event.metaKey)) {
 
 
970
  if (selectedCellIndices.value.has(index)) {
971
  selectedCellIndices.value.delete(index);
972
  } else {
973
  selectedCellIndices.value.add(index);
974
  }
975
 
976
- // If we have single selection after toggle, setup buffer
977
  if (selectedCellIndices.value.size === 1) {
978
  const idx = [...selectedCellIndices.value][0];
979
  const cell = activePageCells.value[idx];
@@ -982,18 +1075,19 @@
982
  selectedBgColor.value = cell.bgColor;
983
  nextTick(() => { if (textInputRef.value) textInputRef.value.focus(); });
984
  } else {
985
- inputBuffer.value = ''; // Multi-select clears buffer to avoid confusion
 
986
  }
987
  }
988
  else {
989
- // Normal click without modifiers
990
 
991
- // If clicking an already selected cell when it is the ONLY selected cell -> Rotate
992
  if (selectedCellIndices.value.has(index) && selectedCellIndices.value.size === 1) {
993
  rotateCurrentCell();
994
  }
995
  else {
996
- // Otherwise, reset selection to just this one
997
  selectedCellIndices.value.clear();
998
  selectedCellIndices.value.add(index);
999
 
@@ -1006,6 +1100,14 @@
1006
  }
1007
  };
1008
 
 
 
 
 
 
 
 
 
1009
  const rotateCurrentCell = () => {
1010
  if (selectedCellIndices.value.size === 0) return;
1011
  selectedCellIndices.value.forEach(index => {
@@ -1014,6 +1116,7 @@
1014
  });
1015
  };
1016
 
 
1017
  const updateSelectedCellText = () => {
1018
  if (selectedCellIndices.value.size === 0) return;
1019
  selectedCellIndices.value.forEach(index => {
@@ -1024,6 +1127,34 @@
1024
  });
1025
  };
1026
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1027
  const applyIconToCell = (iconName) => {
1028
  if (selectedCellIndices.value.size === 0) return;
1029
  selectedCellIndices.value.forEach(index => {
@@ -1033,6 +1164,7 @@
1033
  cell.color = selectedColor.value;
1034
  });
1035
  inputBuffer.value = '';
 
1036
  };
1037
 
1038
  const clearCurrentCell = () => {
@@ -1046,6 +1178,7 @@
1046
  cell.bgColor = '#ffffff'; // Reset BG
1047
  });
1048
  inputBuffer.value = '';
 
1049
  selectedColor.value = '#1e293b';
1050
  selectedBgColor.value = '#ffffff';
1051
  };
@@ -1115,6 +1248,15 @@
1115
  };
1116
 
1117
  // ... PDF Export Logic ...
 
 
 
 
 
 
 
 
 
1118
  const renderPageToCanvas = async (pageId) => {
1119
  const pageData = pages.value[pageId - 1];
1120
  const rows = currentGrid.value.rows;
@@ -1157,7 +1299,13 @@
1157
  });
1158
  gridHtml += `</div>`;
1159
 
1160
- gridHtml += `<div style="position: absolute; bottom: 5px; right: 10px; color: #e2e8f0; font-size: 10px; font-family: sans-serif;">Page ${pageId} - Magic Origami</div>`;
 
 
 
 
 
 
1161
 
1162
  wrapper.innerHTML = gridHtml;
1163
  container.appendChild(wrapper);
@@ -1183,9 +1331,24 @@
1183
  };
1184
 
1185
  const exportPDF = async () => {
1186
- if (selectedCellIndices.value.size > 0) selectedCellIndices.value.clear();
1187
  isGenerating.value = true;
1188
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1189
  try {
1190
  const pdf = new jsPDF('p', 'mm', 'a4');
1191
  const pdfWidth = 210;
@@ -1200,7 +1363,7 @@
1200
  const imgData2 = canvas2.toDataURL('image/jpeg', 0.95);
1201
  pdf.addImage(imgData2, 'JPEG', 0, 0, pdfWidth, pdfHeight);
1202
 
1203
- pdf.save('magic-origami-booklet.pdf');
1204
 
1205
  } catch (err) {
1206
  console.error(err);
@@ -1222,6 +1385,7 @@
1222
  activePageCells,
1223
  selectedCellIndices,
1224
  inputBuffer,
 
1225
  icons,
1226
  colors,
1227
  bgColors,
@@ -1230,11 +1394,13 @@
1230
  applyColor,
1231
  applyBgColor,
1232
  textInputRef,
 
1233
  isGenerating,
1234
  switchToPage,
1235
  handleCellClick,
1236
  rotateCurrentCell,
1237
  updateSelectedCellText,
 
1238
  applyIconToCell,
1239
  clearCurrentCell,
1240
  clearAllContent,
@@ -1262,7 +1428,17 @@
1262
  confirmCustomGrid,
1263
  isCustomGridActive,
1264
  getFontSizeClass,
1265
- getOverviewFontSizeClass
 
 
 
 
 
 
 
 
 
 
1266
  };
1267
  }
1268
  }).mount('#app');
 
224
  <li v-if="viewMode === 'overview'"><strong>點擊頁面:</strong>進入該頁編輯模式</li>
225
  <li v-else><strong>目前編輯:</strong>第 {{ activePageId }} 頁</li>
226
  <li><strong>點擊格子:</strong>選取編輯</li>
227
+ <li><strong>複選模式:</strong>開啟下方開關可連續點擊選取</li>
228
  <li><strong>再次點擊:</strong>單選時旋轉 90°</li>
229
  </ul>
230
  </div>
 
232
  <!-- Editing Controls -->
233
  <div v-if="viewMode === 'edit'" class="space-y-8 animate-fade-in pb-10">
234
 
235
+ <!-- Multi-Select Mode Toggle (NEW) -->
236
+ <div class="flex items-center justify-between bg-white p-3 border border-slate-200 rounded-lg shadow-sm">
237
+ <span class="text-sm font-bold text-slate-700 flex items-center gap-2">
238
+ <i class="fa-solid fa-check-double text-indigo-500"></i>
239
+ 複選模式
240
+ </span>
241
+ <button
242
+ @click="toggleMultiSelectMode"
243
+ class="relative inline-flex h-6 w-11 items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
244
+ :class="isMultiSelectMode ? 'bg-indigo-600' : 'bg-slate-200'"
245
+ >
246
+ <span
247
+ class="inline-block h-4 w-4 transform rounded-full bg-white transition-transform"
248
+ :class="isMultiSelectMode ? 'translate-x-6' : 'translate-x-1'"
249
+ />
250
+ </button>
251
+ </div>
252
+
253
  <!-- Selection Summary -->
254
+ <div v-if="selectedCellIndices.size > 0" class="bg-indigo-50 px-4 py-3 rounded-lg border border-indigo-100">
255
+ <div class="flex justify-between items-center mb-2">
256
+ <div class="text-sm font-bold text-indigo-800">
257
+ 已選取 {{ selectedCellIndices.size }} 個格子
258
+ </div>
259
+ <button @click="clearSelection" class="text-xs text-slate-500 hover:text-red-500 underline">
260
+ 取消選取
261
+ </button>
262
+ </div>
263
+ <div class="flex gap-2">
264
+ <button @click="rotateCurrentCell" class="flex-1 px-3 py-1.5 bg-white border border-indigo-200 text-indigo-700 font-bold rounded shadow-sm text-xs hover:bg-indigo-50 transition-all">
265
+ <i class="fa-solid fa-rotate-right mr-1"></i> 旋轉 90°
266
+ </button>
267
+ </div>
268
  </div>
269
 
270
  <!-- Colors -->
 
308
 
309
  <!-- Text Input -->
310
  <div>
311
+ <label class="block text-sm font-bold text-slate-700 mb-2">3. 文字輸入 (單格最多3字)</label>
312
+ <div class="flex gap-2 mb-2" v-if="selectedCellIndices.size > 1">
313
+ <input
314
+ ref="batchInputRef"
315
+ type="text"
316
+ v-model="batchInputBuffer"
317
+ placeholder="輸入句子依序填入..."
318
+ class="flex-1 min-w-0 border-2 border-indigo-200 rounded-lg p-2 text-sm focus:border-indigo-500 focus:outline-none"
319
+ >
320
+ <button
321
+ @click="applyBatchText"
322
+ class="px-3 py-2 bg-indigo-600 text-white font-bold rounded-lg text-sm hover:bg-indigo-700 transition-all whitespace-nowrap"
323
+ >
324
+ 依序填入
325
+ </button>
326
+ </div>
327
  <input
328
+ v-else
329
  ref="textInputRef"
330
  type="text"
331
  v-model="inputBuffer"
 
337
  :class="selectedCellIndices.size === 0 ? 'bg-slate-100 border-slate-200 cursor-not-allowed' : 'bg-white border-indigo-300 focus:border-indigo-500 focus:ring-indigo-200'"
338
  :style="{ color: selectedCellIndices.size > 0 ? selectedColor : '' }"
339
  >
340
+ <p class="text-xs text-slate-400 mt-1" v-if="selectedCellIndices.size <= 1">提示:輸入 2~3 個字或數字時,字體會自動縮小。</p>
341
+ <p class="text-xs text-indigo-500 mt-1 font-bold" v-else>批次模式:按照點選順序(1→{{selectedCellIndices.size}})依序填入文字。</p>
342
  </div>
343
 
344
  <!-- Icon Picker -->
 
392
  </div>
393
  </div>
394
 
 
 
 
 
 
 
 
 
 
 
395
  <div>
396
  <button
397
  @click="clearCurrentCell"
 
412
  <!-- Footer Action -->
413
  <div class="p-6 border-t border-slate-200 bg-slate-50">
414
  <button
415
+ @click="openExportModal"
416
  :disabled="isGenerating"
417
  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"
418
  >
 
472
  </div>
473
  </div>
474
 
475
+ <!-- Export PDF Modal -->
476
+ <div v-if="showExportModal" class="absolute inset-0 z-50 bg-slate-900/50 flex items-center justify-center p-4 animate-fade-in">
477
+ <div class="bg-white rounded-xl shadow-2xl w-full max-w-sm overflow-hidden">
478
+ <div class="bg-indigo-600 px-6 py-4">
479
+ <h3 class="text-lg font-bold text-white flex items-center gap-2">
480
+ <i class="fa-solid fa-print"></i> 匯出 PDF 設定
481
+ </h3>
482
+ </div>
483
+ <div class="p-6 space-y-4">
484
+ <p class="text-sm text-slate-600 mb-2">請輸入資訊以產生專屬浮水印(可略過):</p>
485
+ <div>
486
+ <label class="block text-sm font-bold text-slate-700 mb-1">姓名 (選填)</label>
487
+ <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">
488
+ </div>
489
+ <div>
490
+ <label class="block text-sm font-bold text-slate-700 mb-1">作品名稱 (選填)</label>
491
+ <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">
492
+ </div>
493
+ </div>
494
+ <div class="bg-slate-50 px-6 py-4 flex justify-end gap-2 border-t border-slate-100">
495
+ <button @click="showExportModal = false" class="px-4 py-2 text-slate-600 font-bold hover:bg-slate-200 rounded-lg transition-all">取消</button>
496
+ <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">
497
+ <span v-if="isGenerating"><i class="fa-solid fa-circle-notch fa-spin"></i> 處理中</span>
498
+ <span v-else>確認下載</span>
499
+ </button>
500
+ </div>
501
+ </div>
502
+ </div>
503
+
504
  </div>
505
 
506
  <!-- =======================
 
606
  <a href="https://www.facebook.com/groups/1554372228718393" target="_blank" class="text-indigo-500 hover:text-indigo-700 font-bold hover:underline">萬物皆數</a>、
607
  <a href="https://www.facebook.com/groups/108923286120994" target="_blank" class="text-indigo-500 hover:text-indigo-700 font-bold hover:underline">藝數摺學</a>
608
  </p>
609
+ <!-- Feedback Link -->
610
+ <p class="mt-2 pt-2 border-t border-slate-200">
611
+ 如果您有任何建議或回饋,歡迎至
612
+ <a href="https://padlet.com/hccedu/padlet-5rkzrebd75t7al13" target="_blank" class="text-indigo-500 hover:text-indigo-700 font-bold hover:underline flex items-center gap-1 inline-flex">
613
+ <i class="fa-regular fa-comments"></i> 意見回饋留言板
614
+ </a> 留言。
615
+ <br>若您留下姓名,我們將會把您加入致謝名單中!
616
+ </p>
617
  </div>
618
  </div>
619
 
 
683
  const activePageId = ref(1);
684
 
685
  // Replaced single index with Set for multi-select
686
+ // For ordered batch fill, we need to track ORDER
687
  const selectedCellIndices = ref(new Set());
688
+ const isMultiSelectMode = ref(false); // Toggle for tablet friendly mode
689
 
690
  const inputBuffer = ref('');
691
+ const batchInputBuffer = ref(''); // For batch string input
692
  const selectedColor = ref('#1e293b');
693
  const selectedBgColor = ref('#ffffff');
694
  const isGenerating = ref(false);
695
 
696
  // Refs
697
  const textInputRef = ref(null);
698
+ const batchInputRef = ref(null);
699
  const fileInputRef = ref(null);
700
  const imageUploadInput = ref(null);
701
  const cropperImgRef = ref(null);
 
704
  const showGridModal = ref(false);
705
  const customGridConfig = ref({ rows: 10, cols: 10 });
706
 
707
+ // Export Modal State
708
+ const showExportModal = ref(false);
709
+ const exportName = ref('');
710
+ const exportProjectName = ref('');
711
+
712
  // Image Upload & Crop State
713
  const showCropper = ref(false);
714
  const tempImageSrc = ref('');
 
764
  { id: 2, cells: createPageCells(totalCells, conf.rows, conf.cols) }
765
  ];
766
 
767
+ clearSelection();
768
  activePageId.value = 1;
769
  viewMode.value = 'overview';
770
  };
 
799
  { id: 2, cells: createPageCells(totalCells, r, c) }
800
  ];
801
 
802
+ clearSelection();
803
  activePageId.value = 1;
804
  viewMode.value = 'overview';
805
  showGridModal.value = false;
 
822
  { id: 2, cells: createPageCells(totalCells, rows, cols) }
823
  ];
824
 
825
+ clearSelection();
 
826
  alert("內容已清空!");
827
  }
828
  };
 
914
  cell.content = imgSrc;
915
  });
916
  inputBuffer.value = '';
917
+ batchInputBuffer.value = '';
918
  };
919
 
920
  const removeCustomImage = (idx) => {
 
967
  customImages.value = importedData.customImages;
968
  }
969
 
970
+ clearSelection();
971
  activePageId.value = 1;
972
  viewMode.value = 'overview';
973
 
 
1027
  setCell(1, 8, 6, 'text', '海', 180);
1028
  }
1029
 
1030
+ clearSelection();
1031
  activePageId.value = 1;
1032
  viewMode.value = 'overview';
1033
 
 
1042
  const switchToPage = (pageId) => {
1043
  activePageId.value = pageId;
1044
  viewMode.value = 'edit';
1045
+ clearSelection();
1046
  inputBuffer.value = '';
1047
  };
1048
 
1049
+ // Enhanced Click Handler for Multi-select with Toggle Switch
1050
+ const toggleMultiSelectMode = () => {
1051
+ isMultiSelectMode.value = !isMultiSelectMode.value;
1052
+ if (!isMultiSelectMode.value) {
1053
+ // When turning off, if multiple selected, keep them.
1054
+ // Or we could clear. Keeping seems safer.
1055
+ }
1056
+ };
1057
+
1058
  const handleCellClick = (index, event) => {
1059
+ // Check if Ctrl key is pressed (override toggle) OR Toggle is ON
1060
+ const isMultiSelect = (event && (event.ctrlKey || event.metaKey)) || isMultiSelectMode.value;
1061
+
1062
+ if (isMultiSelect) {
1063
  if (selectedCellIndices.value.has(index)) {
1064
  selectedCellIndices.value.delete(index);
1065
  } else {
1066
  selectedCellIndices.value.add(index);
1067
  }
1068
 
1069
+ // Setup buffer logic similar to before
1070
  if (selectedCellIndices.value.size === 1) {
1071
  const idx = [...selectedCellIndices.value][0];
1072
  const cell = activePageCells.value[idx];
 
1075
  selectedBgColor.value = cell.bgColor;
1076
  nextTick(() => { if (textInputRef.value) textInputRef.value.focus(); });
1077
  } else {
1078
+ inputBuffer.value = '';
1079
+ batchInputBuffer.value = '';
1080
  }
1081
  }
1082
  else {
1083
+ // Normal Single Click Mode
1084
 
1085
+ // If clicking an already selected cell (single), Rotate it
1086
  if (selectedCellIndices.value.has(index) && selectedCellIndices.value.size === 1) {
1087
  rotateCurrentCell();
1088
  }
1089
  else {
1090
+ // Reset to single selection
1091
  selectedCellIndices.value.clear();
1092
  selectedCellIndices.value.add(index);
1093
 
 
1100
  }
1101
  };
1102
 
1103
+ const clearSelection = () => {
1104
+ selectedCellIndices.value.clear();
1105
+ inputBuffer.value = '';
1106
+ batchInputBuffer.value = '';
1107
+ selectedColor.value = '#1e293b';
1108
+ selectedBgColor.value = '#ffffff';
1109
+ };
1110
+
1111
  const rotateCurrentCell = () => {
1112
  if (selectedCellIndices.value.size === 0) return;
1113
  selectedCellIndices.value.forEach(index => {
 
1116
  });
1117
  };
1118
 
1119
+ // Single Cell Text Update
1120
  const updateSelectedCellText = () => {
1121
  if (selectedCellIndices.value.size === 0) return;
1122
  selectedCellIndices.value.forEach(index => {
 
1127
  });
1128
  };
1129
 
1130
+ // Batch Text Update (Sequential)
1131
+ const applyBatchText = () => {
1132
+ const text = batchInputBuffer.value;
1133
+ if (!text || selectedCellIndices.value.size === 0) return;
1134
+
1135
+ // We need to respect the ORDER of selection.
1136
+ // However, Set iteration order is insertion order in JS.
1137
+ // So [...Set] preserves click order.
1138
+ const indices = [...selectedCellIndices.value];
1139
+ const chars = text.split(''); // Split string into chars.
1140
+ // Note: Complex emojis or surrogate pairs might need better splitting if advanced usage,
1141
+ // but for general text split('') is okay-ish, or use [...text] for better unicode support.
1142
+ const charArray = [...text];
1143
+
1144
+ indices.forEach((cellIndex, i) => {
1145
+ if (i < charArray.length) {
1146
+ const cell = activePageCells.value[cellIndex];
1147
+ cell.type = 'text';
1148
+ cell.content = charArray[i];
1149
+ cell.color = selectedColor.value;
1150
+ }
1151
+ });
1152
+
1153
+ // Clear buffers after apply
1154
+ batchInputBuffer.value = '';
1155
+ inputBuffer.value = '';
1156
+ };
1157
+
1158
  const applyIconToCell = (iconName) => {
1159
  if (selectedCellIndices.value.size === 0) return;
1160
  selectedCellIndices.value.forEach(index => {
 
1164
  cell.color = selectedColor.value;
1165
  });
1166
  inputBuffer.value = '';
1167
+ batchInputBuffer.value = '';
1168
  };
1169
 
1170
  const clearCurrentCell = () => {
 
1178
  cell.bgColor = '#ffffff'; // Reset BG
1179
  });
1180
  inputBuffer.value = '';
1181
+ batchInputBuffer.value = '';
1182
  selectedColor.value = '#1e293b';
1183
  selectedBgColor.value = '#ffffff';
1184
  };
 
1248
  };
1249
 
1250
  // ... PDF Export Logic ...
1251
+ const openExportModal = () => {
1252
+ showExportModal.value = true;
1253
+ };
1254
+
1255
+ const processExport = () => {
1256
+ showExportModal.value = false;
1257
+ exportPDF();
1258
+ };
1259
+
1260
  const renderPageToCanvas = async (pageId) => {
1261
  const pageData = pages.value[pageId - 1];
1262
  const rows = currentGrid.value.rows;
 
1299
  });
1300
  gridHtml += `</div>`;
1301
 
1302
+ // --- Watermark Logic ---
1303
+ if (exportName.value || exportProjectName.value) {
1304
+ const watermarkText = `${exportName.value ? exportName.value + '的' : ''}${exportProjectName.value || ''}`;
1305
+ if (watermarkText) {
1306
+ gridHtml += `<div style="position: absolute; bottom: 15mm; right: 15mm; color: #94a3b8; font-size: 12px; font-family: 'Noto Sans TC', sans-serif;">${watermarkText}</div>`;
1307
+ }
1308
+ }
1309
 
1310
  wrapper.innerHTML = gridHtml;
1311
  container.appendChild(wrapper);
 
1331
  };
1332
 
1333
  const exportPDF = async () => {
1334
+ if (selectedCellIndices.value.size > 0) clearSelection();
1335
  isGenerating.value = true;
1336
 
1337
+ // Determine filename
1338
+ let fileName = 'magic-origami-booklet.pdf';
1339
+ if (exportName.value || exportProjectName.value) {
1340
+ const namePart = exportName.value ? exportName.value : '';
1341
+ const projPart = exportProjectName.value ? exportProjectName.value : '';
1342
+
1343
+ if (namePart && projPart) {
1344
+ fileName = `${namePart}的${projPart}.pdf`;
1345
+ } else if (namePart) {
1346
+ fileName = `${namePart}的作品.pdf`;
1347
+ } else if (projPart) {
1348
+ fileName = `${projPart}.pdf`;
1349
+ }
1350
+ }
1351
+
1352
  try {
1353
  const pdf = new jsPDF('p', 'mm', 'a4');
1354
  const pdfWidth = 210;
 
1363
  const imgData2 = canvas2.toDataURL('image/jpeg', 0.95);
1364
  pdf.addImage(imgData2, 'JPEG', 0, 0, pdfWidth, pdfHeight);
1365
 
1366
+ pdf.save(fileName);
1367
 
1368
  } catch (err) {
1369
  console.error(err);
 
1385
  activePageCells,
1386
  selectedCellIndices,
1387
  inputBuffer,
1388
+ batchInputBuffer,
1389
  icons,
1390
  colors,
1391
  bgColors,
 
1394
  applyColor,
1395
  applyBgColor,
1396
  textInputRef,
1397
+ batchInputRef,
1398
  isGenerating,
1399
  switchToPage,
1400
  handleCellClick,
1401
  rotateCurrentCell,
1402
  updateSelectedCellText,
1403
+ applyBatchText,
1404
  applyIconToCell,
1405
  clearCurrentCell,
1406
  clearAllContent,
 
1428
  confirmCustomGrid,
1429
  isCustomGridActive,
1430
  getFontSizeClass,
1431
+ getOverviewFontSizeClass,
1432
+ // Export Modal
1433
+ showExportModal,
1434
+ openExportModal,
1435
+ processExport,
1436
+ exportName,
1437
+ exportProjectName,
1438
+ // Multi-select
1439
+ isMultiSelectMode,
1440
+ toggleMultiSelectMode,
1441
+ clearSelection
1442
  };
1443
  }
1444
  }).mount('#app');