From 9906eb04b4d3fc2c303440642093bd2abf13f1ce Mon Sep 17 00:00:00 2001 From: Qing Bi Liao <78472206+Pencil126@users.noreply.github.com> Date: Tue, 5 May 2026 06:05:24 +0000 Subject: [PATCH 1/4] =?UTF-8?q?Feature:=20=E6=96=B0=E5=A2=9EQR=20Code?= =?UTF-8?q?=E6=A8=99=E9=A1=8C=E7=94=9F=E6=88=90=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot --- src/pages/qrcode_generator.js | 139 +++++++++++++++++++++++++++++++--- 1 file changed, 128 insertions(+), 11 deletions(-) diff --git a/src/pages/qrcode_generator.js b/src/pages/qrcode_generator.js index c331bfd..f7d8b89 100644 --- a/src/pages/qrcode_generator.js +++ b/src/pages/qrcode_generator.js @@ -10,7 +10,9 @@ const logo = "https://i.meee.com.tw/0SiZRVA.jpg"; const QRCodeGeneratorPage = () => { const [urlInput, setUrlInput] = useState(""); + const [titleInput, setTitleInput] = useState(""); const [qrCodeUrl, setQrCodeUrl] = useState(null); + const [composedUrl, setComposedUrl] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); @@ -19,8 +21,57 @@ const QRCodeGeneratorPage = () => { if (qrCodeUrl && qrCodeUrl.startsWith("blob:")) { URL.revokeObjectURL(qrCodeUrl); } + if (composedUrl && composedUrl.startsWith("blob:")) { + URL.revokeObjectURL(composedUrl); + } }; - }, [qrCodeUrl]); + }, [qrCodeUrl, composedUrl]); + + const buildPoster = async (qrBlobUrl, titleText) => { + const img = new Image(); + img.src = qrBlobUrl; + + await new Promise((resolve, reject) => { + img.onload = resolve; + img.onerror = reject; + }); + + const width = 1080; + const height = 2340; + const qrSize = 1000; + + const canvas = document.createElement("canvas"); + canvas.width = width; + canvas.height = height; + + const ctx = canvas.getContext("2d"); + if (!ctx) throw new Error("Canvas 不支援"); + + ctx.fillStyle = "#FFFFFF"; + ctx.fillRect(0, 0, width, height); + + ctx.textAlign = "center"; + ctx.textBaseline = "top"; + + ctx.fillStyle = "#FFAF73"; + ctx.font = "bold 80px 'Noto Serif TC', serif"; + ctx.fillText(titleText, width / 2, 325); + + const qrX = (width - qrSize) / 2; + const qrY = (height - qrSize) / 2; + ctx.drawImage(img, qrX, qrY, qrSize, qrSize); + + ctx.fillStyle = "#6D9DF8"; + ctx.font = "40px 'Noto Serif TC', serif"; + ctx.fillText("FCU iOS Club", width / 2, 2000); + + return new Promise((resolve, reject) => { + canvas.toBlob((blob) => { + if (!blob) return reject(new Error("toBlob failed")); + resolve(URL.createObjectURL(blob)); + }, "image/png"); + }); + }; const generateQRCode = async () => { if (!urlInput.trim()) { @@ -31,6 +82,7 @@ const QRCodeGeneratorPage = () => { setLoading(true); setError(null); setQrCodeUrl(null); + setComposedUrl(null); const payload = { data: urlInput, @@ -43,7 +95,7 @@ const QRCodeGeneratorPage = () => { gradientColor1: "#FFAF73", gradientColor2: "#6D9DF8", gradientOnEyes: true, - bgColor: "#ffffff", + bgColor: "#FFFFFF", logo: logo, logoMode: "clean", }, @@ -80,6 +132,18 @@ const QRCodeGeneratorPage = () => { const blob = await response.blob(); const url = URL.createObjectURL(blob); setQrCodeUrl(url); + if (titleInput.trim()) { + try { + const posterUrl = await buildPoster( + url, + titleInput.trim(), + urlInput, + ); + setComposedUrl(posterUrl); + } catch (composeError) { + console.error("Poster generation error:", composeError); + } + } } else { const result = await response.json(); console.log("API Response:", result); @@ -103,6 +167,18 @@ const QRCodeGeneratorPage = () => { const blob = await imgResponse.blob(); const url = URL.createObjectURL(blob); setQrCodeUrl(url); + if (titleInput.trim()) { + try { + const posterUrl = await buildPoster( + url, + titleInput.trim(), + urlInput, + ); + setComposedUrl(posterUrl); + } catch (composeError) { + console.error("Poster generation error:", composeError); + } + } } else { throw new Error(`圖片下載失敗 (${imgResponse.status})`); } @@ -126,19 +202,28 @@ const QRCodeGeneratorPage = () => { }; const downloadQRCode = () => { - if (!qrCodeUrl) return; + const targetUrl = composedUrl || qrCodeUrl; + if (!targetUrl) return; const link = document.createElement("a"); - link.href = qrCodeUrl; + link.href = targetUrl; link.download = "iOSClub_QRCode.png"; document.body.appendChild(link); link.click(); document.body.removeChild(link); }; - const clearInput = () => { + const clearUrlInput = () => { setUrlInput(""); setQrCodeUrl(null); + setComposedUrl(null); + setError(null); + }; + + const clearTitleInput = () => { + setTitleInput(""); + setQrCodeUrl(null); + setComposedUrl(null); setError(null); }; @@ -193,6 +278,13 @@ const QRCodeGeneratorPage = () => { 社團 Logo:內嵌 iOS Club 官方標誌 +
+ + + 可自帶標題: + 輸入標題自動生成手機螢幕比例之圖片 + +
@@ -219,13 +311,38 @@ const QRCodeGeneratorPage = () => { onKeyDown={(e) => { if (e.key === "Enter") generateQRCode(); }} - placeholder="請輸入網址..." + placeholder="請輸入網址⋯⋯" className="flex-1 px-4 py-3 border-2 border-gray-300 rounded-full font-mono text-lg focus:border-btnbg focus:outline-none transition-colors" /> + + + +
+ +
+ setTitleInput(e.target.value)} + onKeyDown={(e) => { + if (e.key === "Enter") generateQRCode(); + }} + placeholder="如未填標題則生成單一 QR Code" + className="w-full px-4 py-3 border-2 border-gray-300 rounded-full font-mono text-lg focus:border-btnbg focus:outline-none transition-colors" + /> + @@ -235,7 +352,7 @@ const QRCodeGeneratorPage = () => { {/* 生成按鈕 */}
{
Generated QR Code
From b65ffcd14f7060b3111c0b55c54cc364fd22e342 Mon Sep 17 00:00:00 2001 From: Qing Bi Liao <78472206+Pencil126@users.noreply.github.com> Date: Tue, 5 May 2026 14:14:37 +0800 Subject: [PATCH 2/4] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/pages/qrcode_generator.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/qrcode_generator.js b/src/pages/qrcode_generator.js index f7d8b89..09a3ef9 100644 --- a/src/pages/qrcode_generator.js +++ b/src/pages/qrcode_generator.js @@ -337,7 +337,7 @@ const QRCodeGeneratorPage = () => { if (e.key === "Enter") generateQRCode(); }} placeholder="如未填標題則生成單一 QR Code" - className="w-full px-4 py-3 border-2 border-gray-300 rounded-full font-mono text-lg focus:border-btnbg focus:outline-none transition-colors" + className="flex-1 px-4 py-3 border-2 border-gray-300 rounded-full font-mono text-lg focus:border-btnbg focus:outline-none transition-colors" />