Update index.html
Browse files- index.html +97 -6
index.html
CHANGED
|
@@ -19,6 +19,9 @@
|
|
| 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>
|
| 21 |
|
|
|
|
|
|
|
|
|
|
| 22 |
<!-- Google Fonts -->
|
| 23 |
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+TC:wght@400;700&display=swap" rel="stylesheet">
|
| 24 |
|
|
@@ -147,7 +150,7 @@
|
|
| 147 |
<!-- Grid Settings -->
|
| 148 |
<div>
|
| 149 |
<label class="block text-sm font-bold text-slate-700 mb-2">📐 網格設定</label>
|
| 150 |
-
<div class="grid grid-cols-
|
| 151 |
<button
|
| 152 |
v-for="conf in gridOptions"
|
| 153 |
:key="conf.label"
|
|
@@ -157,6 +160,14 @@
|
|
| 157 |
>
|
| 158 |
{{ conf.label }}
|
| 159 |
</button>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 160 |
</div>
|
| 161 |
<!-- Clear All Button -->
|
| 162 |
<button
|
|
@@ -201,7 +212,7 @@
|
|
| 201 |
class="py-2 bg-slate-600 hover:bg-slate-700 text-white font-bold rounded-lg shadow-sm active:scale-95 transition-all flex items-center justify-center gap-1 text-sm"
|
| 202 |
>
|
| 203 |
<!-- Upload Icon -->
|
| 204 |
-
<svg viewBox="0 0 512 512" class="w-4 h-4 fill-current mr-1"><path d="M288 109.3V352c0 17.7-14.3 32-32 32s-32-14.3-32-32V109.3l-73.4 73.4c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3l128-128c12.5-12.5 32.8-12.5 45.3 0l128 128c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0L288 109.3zM64 352H192c0 35.3 28.7 64 64 64s64-28.7 64-64H448c35.3 0 64 28.7 64 64v32c0 35.3-28.7 64-64 64H64c-35.3 0-64-28.7-64-64V416c0-35.3 28.7-64 64-64zM432 456c13.3 0 24-10.7 24-24s-10.7-24-24-24s-24 10.7
|
| 205 |
匯入舊檔
|
| 206 |
</button>
|
| 207 |
<input
|
|
@@ -374,6 +385,35 @@
|
|
| 374 |
</div>
|
| 375 |
</div>
|
| 376 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 377 |
</div>
|
| 378 |
|
| 379 |
<!-- =======================
|
|
@@ -501,7 +541,6 @@
|
|
| 501 |
const currentGrid = ref(gridOptions[1]); // Default 6x8
|
| 502 |
|
| 503 |
// --- Colors ---
|
| 504 |
-
// Slate-800 is default
|
| 505 |
const colors = ['#1e293b', '#ef4444', '#f97316', '#f59e0b', '#22c55e', '#14b8a6', '#3b82f6', '#6366f1', '#a855f7', '#ec4899'];
|
| 506 |
|
| 507 |
// --- Icons (Unified to Unicode Emoji) ---
|
|
@@ -552,6 +591,10 @@
|
|
| 552 |
const imageUploadInput = ref(null);
|
| 553 |
const cropperImgRef = ref(null);
|
| 554 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 555 |
// Image Upload & Crop State
|
| 556 |
const showCropper = ref(false);
|
| 557 |
const tempImageSrc = ref('');
|
|
@@ -562,6 +605,12 @@
|
|
| 562 |
const AUTOSAVE_KEY = 'magic_origami_autosave_v1';
|
| 563 |
|
| 564 |
const activePageCells = computed(() => pages.value[activePageId.value - 1].cells);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 565 |
|
| 566 |
// --- Actions ---
|
| 567 |
|
|
@@ -570,8 +619,7 @@
|
|
| 570 |
|
| 571 |
const hasContent = pages.value.some(p => p.cells.some(c => c.content !== ''));
|
| 572 |
if (hasContent) {
|
| 573 |
-
|
| 574 |
-
return;
|
| 575 |
}
|
| 576 |
|
| 577 |
currentGrid.value = conf;
|
|
@@ -587,6 +635,43 @@
|
|
| 587 |
viewMode.value = 'overview';
|
| 588 |
};
|
| 589 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 590 |
const clearAllContent = () => {
|
| 591 |
const hasContent = pages.value.some(p => p.cells.some(c => c.content !== ''));
|
| 592 |
if (!hasContent) {
|
|
@@ -1065,7 +1150,13 @@
|
|
| 1065 |
cancelCrop,
|
| 1066 |
confirmCrop,
|
| 1067 |
applyCustomImageToCell,
|
| 1068 |
-
removeCustomImage
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1069 |
};
|
| 1070 |
}
|
| 1071 |
}).mount('#app');
|
|
|
|
| 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>
|
| 21 |
|
| 22 |
+
<!-- FontAwesome 6 (For Consistent Icons) -->
|
| 23 |
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
| 24 |
+
|
| 25 |
<!-- Google Fonts -->
|
| 26 |
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+TC:wght@400;700&display=swap" rel="stylesheet">
|
| 27 |
|
|
|
|
| 150 |
<!-- Grid Settings -->
|
| 151 |
<div>
|
| 152 |
<label class="block text-sm font-bold text-slate-700 mb-2">📐 網格設定</label>
|
| 153 |
+
<div class="grid grid-cols-2 gap-2 mb-2">
|
| 154 |
<button
|
| 155 |
v-for="conf in gridOptions"
|
| 156 |
:key="conf.label"
|
|
|
|
| 160 |
>
|
| 161 |
{{ conf.label }}
|
| 162 |
</button>
|
| 163 |
+
<!-- Custom Grid Button -->
|
| 164 |
+
<button
|
| 165 |
+
@click="openCustomGridModal"
|
| 166 |
+
class="py-2 text-sm border border-dashed border-indigo-300 text-indigo-600 rounded-lg hover:bg-indigo-50 active:scale-95 transition-all font-bold"
|
| 167 |
+
:class="{'bg-indigo-600 text-white border-indigo-600 border-solid': isCustomGridActive}"
|
| 168 |
+
>
|
| 169 |
+
<i class="fa-solid fa-gear mr-1"></i> 自訂網格
|
| 170 |
+
</button>
|
| 171 |
</div>
|
| 172 |
<!-- Clear All Button -->
|
| 173 |
<button
|
|
|
|
| 212 |
class="py-2 bg-slate-600 hover:bg-slate-700 text-white font-bold rounded-lg shadow-sm active:scale-95 transition-all flex items-center justify-center gap-1 text-sm"
|
| 213 |
>
|
| 214 |
<!-- Upload Icon -->
|
| 215 |
+
<svg viewBox="0 0 512 512" class="w-4 h-4 fill-current mr-1"><path d="M288 109.3V352c0 17.7-14.3 32-32 32s-32-14.3-32-32V109.3l-73.4 73.4c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3l128-128c12.5-12.5 32.8-12.5 45.3 0l128 128c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0L288 109.3zM64 352H192c0 35.3 28.7 64 64 64s64-28.7 64-64H448c35.3 0 64 28.7 64 64v32c0 35.3-28.7 64-64 64H64c-35.3 0-64-28.7-64-64V416c0-35.3 28.7-64 64-64zM432 456c13.3 0 24-10.7 24-24s-10.7-24-24-24s-24 10.7 24 24s10.7 24 24 24z"/></svg>
|
| 216 |
匯入舊檔
|
| 217 |
</button>
|
| 218 |
<input
|
|
|
|
| 385 |
</div>
|
| 386 |
</div>
|
| 387 |
|
| 388 |
+
<!-- Custom Grid Modal -->
|
| 389 |
+
<div v-if="showGridModal" class="absolute inset-0 z-50 bg-slate-900/50 flex items-center justify-center p-4 animate-fade-in">
|
| 390 |
+
<div class="bg-white rounded-xl shadow-2xl w-full max-w-sm overflow-hidden">
|
| 391 |
+
<div class="bg-indigo-600 px-6 py-4">
|
| 392 |
+
<h3 class="text-lg font-bold text-white flex items-center gap-2">
|
| 393 |
+
<i class="fa-solid fa-gear"></i> 自訂網格設定
|
| 394 |
+
</h3>
|
| 395 |
+
</div>
|
| 396 |
+
<div class="p-6 space-y-4">
|
| 397 |
+
<div>
|
| 398 |
+
<label class="block text-sm font-bold text-slate-700 mb-1">列數 (Rows)</label>
|
| 399 |
+
<input type="number" v-model.number="customGridConfig.rows" min="1" max="20" class="w-full border-2 border-slate-200 rounded-lg p-2 focus:border-indigo-500 focus:outline-none">
|
| 400 |
+
</div>
|
| 401 |
+
<div>
|
| 402 |
+
<label class="block text-sm font-bold text-slate-700 mb-1">欄數 (Columns)</label>
|
| 403 |
+
<input type="number" v-model.number="customGridConfig.cols" min="1" max="20" class="w-full border-2 border-slate-200 rounded-lg p-2 focus:border-indigo-500 focus:outline-none">
|
| 404 |
+
</div>
|
| 405 |
+
<p class="text-xs text-slate-500 bg-slate-50 p-2 rounded">
|
| 406 |
+
<i class="fa-solid fa-circle-info mr-1"></i>
|
| 407 |
+
建議範圍:1 ~ 12,過大可能會影響列印清晰度。
|
| 408 |
+
</p>
|
| 409 |
+
</div>
|
| 410 |
+
<div class="bg-slate-50 px-6 py-4 flex justify-end gap-2 border-t border-slate-100">
|
| 411 |
+
<button @click="showGridModal = false" class="px-4 py-2 text-slate-600 font-bold hover:bg-slate-200 rounded-lg transition-all">取消</button>
|
| 412 |
+
<button @click="confirmCustomGrid" class="px-4 py-2 bg-indigo-600 text-white font-bold rounded-lg hover:bg-indigo-700 transition-all shadow-md">確認套用</button>
|
| 413 |
+
</div>
|
| 414 |
+
</div>
|
| 415 |
+
</div>
|
| 416 |
+
|
| 417 |
</div>
|
| 418 |
|
| 419 |
<!-- =======================
|
|
|
|
| 541 |
const currentGrid = ref(gridOptions[1]); // Default 6x8
|
| 542 |
|
| 543 |
// --- Colors ---
|
|
|
|
| 544 |
const colors = ['#1e293b', '#ef4444', '#f97316', '#f59e0b', '#22c55e', '#14b8a6', '#3b82f6', '#6366f1', '#a855f7', '#ec4899'];
|
| 545 |
|
| 546 |
// --- Icons (Unified to Unicode Emoji) ---
|
|
|
|
| 591 |
const imageUploadInput = ref(null);
|
| 592 |
const cropperImgRef = ref(null);
|
| 593 |
|
| 594 |
+
// Custom Grid State
|
| 595 |
+
const showGridModal = ref(false);
|
| 596 |
+
const customGridConfig = ref({ rows: 10, cols: 10 });
|
| 597 |
+
|
| 598 |
// Image Upload & Crop State
|
| 599 |
const showCropper = ref(false);
|
| 600 |
const tempImageSrc = ref('');
|
|
|
|
| 605 |
const AUTOSAVE_KEY = 'magic_origami_autosave_v1';
|
| 606 |
|
| 607 |
const activePageCells = computed(() => pages.value[activePageId.value - 1].cells);
|
| 608 |
+
|
| 609 |
+
// Computed for UI style of custom button
|
| 610 |
+
const isCustomGridActive = computed(() => {
|
| 611 |
+
// If current config matches none of the preset labels, it's custom
|
| 612 |
+
return !gridOptions.some(opt => opt.label === currentGrid.value.label);
|
| 613 |
+
});
|
| 614 |
|
| 615 |
// --- Actions ---
|
| 616 |
|
|
|
|
| 619 |
|
| 620 |
const hasContent = pages.value.some(p => p.cells.some(c => c.content !== ''));
|
| 621 |
if (hasContent) {
|
| 622 |
+
if(!confirm("切換網格設定將會清空您目前的設計,確定要繼續嗎?")) return;
|
|
|
|
| 623 |
}
|
| 624 |
|
| 625 |
currentGrid.value = conf;
|
|
|
|
| 635 |
viewMode.value = 'overview';
|
| 636 |
};
|
| 637 |
|
| 638 |
+
// Custom Grid Logic
|
| 639 |
+
const openCustomGridModal = () => {
|
| 640 |
+
const hasContent = pages.value.some(p => p.cells.some(c => c.content !== ''));
|
| 641 |
+
if (hasContent) {
|
| 642 |
+
if(!confirm("設定自訂網格將會清空您目前的設計,確定要繼續嗎?")) return;
|
| 643 |
+
}
|
| 644 |
+
|
| 645 |
+
// Set initial values from current grid
|
| 646 |
+
customGridConfig.value.rows = currentGrid.value.rows;
|
| 647 |
+
customGridConfig.value.cols = currentGrid.value.cols;
|
| 648 |
+
showGridModal.value = true;
|
| 649 |
+
};
|
| 650 |
+
|
| 651 |
+
const confirmCustomGrid = () => {
|
| 652 |
+
const r = Math.max(1, Math.min(20, parseInt(customGridConfig.value.rows) || 4));
|
| 653 |
+
const c = Math.max(1, Math.min(20, parseInt(customGridConfig.value.cols) || 4));
|
| 654 |
+
|
| 655 |
+
const customConf = {
|
| 656 |
+
label: `${r}x${c}`,
|
| 657 |
+
rows: r,
|
| 658 |
+
cols: c
|
| 659 |
+
};
|
| 660 |
+
|
| 661 |
+
currentGrid.value = customConf;
|
| 662 |
+
const totalCells = r * c;
|
| 663 |
+
|
| 664 |
+
pages.value = [
|
| 665 |
+
{ id: 1, cells: createPageCells(0, r, c) },
|
| 666 |
+
{ id: 2, cells: createPageCells(totalCells, r, c) }
|
| 667 |
+
];
|
| 668 |
+
|
| 669 |
+
selectedCellIndex.value = null;
|
| 670 |
+
activePageId.value = 1;
|
| 671 |
+
viewMode.value = 'overview';
|
| 672 |
+
showGridModal.value = false;
|
| 673 |
+
};
|
| 674 |
+
|
| 675 |
const clearAllContent = () => {
|
| 676 |
const hasContent = pages.value.some(p => p.cells.some(c => c.content !== ''));
|
| 677 |
if (!hasContent) {
|
|
|
|
| 1150 |
cancelCrop,
|
| 1151 |
confirmCrop,
|
| 1152 |
applyCustomImageToCell,
|
| 1153 |
+
removeCustomImage,
|
| 1154 |
+
// Custom Grid
|
| 1155 |
+
showGridModal,
|
| 1156 |
+
customGridConfig,
|
| 1157 |
+
openCustomGridModal,
|
| 1158 |
+
confirmCustomGrid,
|
| 1159 |
+
isCustomGridActive
|
| 1160 |
};
|
| 1161 |
}
|
| 1162 |
}).mount('#app');
|