diff --git a/frontend/src/components/RecipeDetailOverlay.vue b/frontend/src/components/RecipeDetailOverlay.vue index 63c1801..8a0b589 100644 --- a/frontend/src/components/RecipeDetailOverlay.vue +++ b/frontend/src/components/RecipeDetailOverlay.vue @@ -1170,8 +1170,7 @@ async function saveRecipe() { .card-logo { position: absolute; bottom: 60px; - left: 50%; - transform: translateX(-50%); + left: 24px; height: 60px; object-fit: contain; z-index: 1; @@ -1300,7 +1299,7 @@ async function saveRecipe() { .card-footer { margin-top: 16px; - text-align: center; + text-align: right; font-size: 11px; color: var(--text-light, #9a8570); letter-spacing: 1px; diff --git a/frontend/src/views/MyDiary.vue b/frontend/src/views/MyDiary.vue index cf82f0c..6805a5d 100644 --- a/frontend/src/views/MyDiary.vue +++ b/frontend/src/views/MyDiary.vue @@ -462,11 +462,56 @@ function compressImage(base64, maxSize = 500000) { }) } +// Crop image to square from center +function cropToSquare(base64) { + return new Promise((resolve) => { + const img = new Image() + img.onload = () => { + const size = Math.min(img.width, img.height) + const x = (img.width - size) / 2 + const y = (img.height - size) / 2 + const canvas = document.createElement('canvas') + canvas.width = size + canvas.height = size + canvas.getContext('2d').drawImage(img, x, y, size, size, 0, 0, size, size) + resolve(canvas.toDataURL('image/png')) + } + img.onerror = () => resolve(base64) + img.src = base64 + }) +} + +// Check if image is roughly square +function checkSquare(base64) { + return new Promise((resolve) => { + const img = new Image() + img.onload = () => { + const ratio = img.width / img.height + resolve(ratio > 0.85 && ratio < 1.15) // within 15% of square + } + img.onerror = () => resolve(true) + img.src = base64 + }) +} + async function handleUpload(type, event) { const file = event.target.files[0] if (!file) return try { let base64 = await readFileAsBase64(file) + + // QR: check if square, offer to crop + if (type === 'qr') { + const isSquare = await checkSquare(base64) + if (!isSquare) { + const { showConfirm: confirm } = await import('../composables/useDialog') + const ok = await confirm('二维码图片不是正方形,是否自动裁剪为正方形?\n(取中心区域)') + if (ok) { + base64 = await cropToSquare(base64) + } + } + } + const maxSize = type === 'bg' ? 1000000 : 500000 base64 = await compressImage(base64, maxSize) const fieldMap = { logo: 'brand_logo', bg: 'brand_bg', qr: 'qr_code' } @@ -958,7 +1003,7 @@ async function applyBusiness() { transition: border-color 0.15s; } .upload-box:hover { border-color: var(--sage, #7a9e7e); } -.upload-box-img { width: 100%; height: 100%; object-fit: cover; } +.upload-box-img { width: 100%; height: 100%; object-fit: contain; } .upload-box-hint { font-size: 12px; color: var(--text-light, #9a8570); } .btn-clear { margin-top: 6px; @@ -1019,8 +1064,7 @@ async function applyBusiness() { .card-preview-logo { position: absolute; bottom: 40px; - left: 50%; - transform: translateX(-50%); + left: 16px; height: 20px; width: auto; object-fit: contain;