Skip to content

Commit 0f43706

Browse files
committed
refactor: separate qr preview from export
1 parent de634dc commit 0f43706

File tree

1 file changed

+72
-40
lines changed

1 file changed

+72
-40
lines changed

src/App.tsx

Lines changed: 72 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -97,14 +97,24 @@ const createLabeledQr = async (text: string, displayName: string) => {
9797
)
9898
})
9999

100-
const padding = 28
101-
const headerLines = 2
102-
const lineHeight = 22
103-
const headerHeight = padding + headerLines * lineHeight
104-
const footerPadding = 30
100+
const horizontalPadding = 32
101+
const topPadding = 42
102+
const titleFontSize = 22
103+
const titleSpacing = 10
104+
const nameFontSize = 18
105+
const nameSpacing = 20
106+
const footerPadding = 42
107+
105108
const canvas = document.createElement('canvas')
106-
canvas.width = qrSize + padding * 2
107-
canvas.height = headerHeight + qrSize + footerPadding
109+
canvas.width = qrSize + horizontalPadding * 2
110+
canvas.height =
111+
topPadding +
112+
titleFontSize +
113+
titleSpacing +
114+
nameFontSize +
115+
nameSpacing +
116+
qrSize +
117+
footerPadding
108118

109119
const ctx = canvas.getContext('2d')
110120
if (!ctx) {
@@ -118,22 +128,29 @@ const createLabeledQr = async (text: string, displayName: string) => {
118128
ctx.textAlign = 'center'
119129
ctx.textBaseline = 'top'
120130

121-
ctx.font = '600 20px "Inter", "Noto Sans", "Helvetica Neue", Arial, sans-serif'
122-
ctx.fillText('SHIOAJI API TOKEN', canvas.width / 2, padding - 6)
131+
const centerX = canvas.width / 2
132+
const titleY = topPadding
133+
ctx.font = `700 ${titleFontSize}px "Inter", "Noto Sans", "Helvetica Neue", Arial, sans-serif`
134+
ctx.fillText('SHIOAJI API QRCODE', centerX, titleY)
123135

124-
ctx.font = '400 16px "Inter", "Noto Sans", "Helvetica Neue", Arial, sans-serif'
125-
ctx.fillText(`NAME: ${displayName || '未命名'}`, canvas.width / 2, padding - 6 + lineHeight)
136+
const nameY = titleY + titleFontSize + titleSpacing
137+
ctx.font = `500 ${nameFontSize}px "Inter", "Noto Sans", "Helvetica Neue", Arial, sans-serif`
138+
ctx.fillText(`NAME: ${displayName || '未命名'}`, centerX, nameY)
126139

127-
const qrY = headerHeight - 12
128-
ctx.drawImage(qrCanvas, padding, qrY, qrSize, qrSize)
140+
const qrY = nameY + nameFontSize + nameSpacing
141+
ctx.drawImage(qrCanvas, horizontalPadding, qrY, qrSize, qrSize)
129142

130143
ctx.strokeStyle = '#e5e7eb'
131144
ctx.lineWidth = 1
132-
ctx.strokeRect(padding - 1, qrY - 1, qrSize + 2, qrSize + 2)
145+
ctx.strokeRect(horizontalPadding - 1, qrY - 1, qrSize + 2, qrSize + 2)
133146

134147
ctx.font = '400 12px "Inter", "Noto Sans", "Helvetica Neue", Arial, sans-serif'
135148
ctx.fillStyle = '#6b7280'
136-
ctx.fillText('https://sinotrade.github.io/sj-qrcode/', canvas.width / 2, canvas.height - footerPadding + 6)
149+
ctx.fillText(
150+
'https://sinotrade.github.io/sj-qrcode/',
151+
centerX,
152+
canvas.height - footerPadding + 12,
153+
)
137154

138155
return canvas.toDataURL('image/png')
139156
}
@@ -163,13 +180,18 @@ function App() {
163180
const formatted = JSON.stringify(payload, null, 2)
164181

165182
try {
166-
const labeledQr = await createLabeledQr(
167-
formatted,
168-
values.name.trim() || '未命名',
169-
)
183+
const dataUrl = await QRCode.toDataURL(formatted, {
184+
errorCorrectionLevel: 'M',
185+
width: 320,
186+
margin: 1,
187+
color: {
188+
dark: '#030712',
189+
light: '#ffffff',
190+
},
191+
})
170192

171193
setJsonPayload(formatted)
172-
setQrCodeDataUrl(labeledQr)
194+
setQrCodeDataUrl(dataUrl)
173195
toast.success('QR code 已完成')
174196
} catch (error) {
175197
console.error('Failed to generate QR code', error)
@@ -192,20 +214,28 @@ function App() {
192214
}
193215
}
194216

195-
const handleDownloadQr = () => {
196-
if (!qrCodeDataUrl) {
217+
const handleDownloadQr = async () => {
218+
if (!jsonPayload) {
197219
toast.error('尚未生成 QR code')
198220
return
199221
}
200222

201-
const link = document.createElement('a')
202-
const timestamp = new Date().toISOString().replaceAll(/[:.]/g, '-').slice(0, 19)
203-
const normalizedName = formatFileNameSegment(form.getValues('name') ?? '')
204-
link.href = qrCodeDataUrl
205-
link.download = `sj-token-${normalizedName}-${timestamp}.png`
206-
link.click()
223+
try {
224+
const displayName = form.getValues('name')?.trim() || '未命名'
225+
const labeledQr = await createLabeledQr(jsonPayload, displayName)
226+
227+
const link = document.createElement('a')
228+
const timestamp = new Date().toISOString().replaceAll(/[:.]/g, '-').slice(0, 19)
229+
const normalizedName = formatFileNameSegment(displayName)
230+
link.href = labeledQr
231+
link.download = `sj-token-${normalizedName}-${timestamp}.png`
232+
link.click()
207233

208-
toast.success('QR code 已開始下載')
234+
toast.success('QR code 已開始下載')
235+
} catch (error) {
236+
console.error('Failed to export labeled QR code', error)
237+
toast.error('下載 QR code 時發生錯誤')
238+
}
209239
}
210240

211241
const handleDownloadJson = () => {
@@ -369,6 +399,15 @@ function App() {
369399
alt="SJ QR code"
370400
className="h-auto w-60 rounded border bg-white p-2 shadow"
371401
/>
402+
<Button
403+
type="button"
404+
variant="secondary"
405+
className="w-full max-w-xs"
406+
disabled={!qrCodeDataUrl}
407+
onClick={handleDownloadQr}
408+
>
409+
<Download className="mr-2 h-4 w-4" />下載 QR code
410+
</Button>
372411
<p className="text-center text-xs text-muted-foreground">
373412
如果顯示模糊,請再試一次或提高螢幕亮度後掃描。
374413
</p>
@@ -384,19 +423,20 @@ function App() {
384423

385424
<Separator />
386425

387-
<div className="flex flex-col gap-3 lg:flex-[1.3]">
426+
<div className="flex flex-col gap-3 lg:flex-[1.05]">
388427
<p className="text-sm font-medium text-muted-foreground">JSON 資料</p>
389428
<Textarea
390429
value={jsonPayload}
391430
readOnly
392431
placeholder="完成表單後會自動生成 JSON 資料"
393432
wrap="off"
394433
spellCheck={false}
395-
className="min-h-[220px] resize-y overflow-auto font-mono text-sm leading-relaxed lg:h-full lg:min-h-0"
434+
rows={5}
435+
className="resize-y overflow-auto font-mono text-sm leading-relaxed lg:h-full lg:min-h-0"
396436
/>
397437
</div>
398438
</CardContent>
399-
<CardFooter className="flex gap-3 overflow-x-auto pb-4 pt-0 flex-nowrap">
439+
<CardFooter className="flex flex-wrap items-center justify-center gap-3 pb-4 pt-0">
400440
<Button
401441
type="button"
402442
variant="secondary"
@@ -413,14 +453,6 @@ function App() {
413453
>
414454
<FileDown className="mr-2 h-4 w-4" />下載 JSON
415455
</Button>
416-
<Button
417-
type="button"
418-
variant="secondary"
419-
disabled={!qrCodeDataUrl}
420-
onClick={handleDownloadQr}
421-
>
422-
<Download className="mr-2 h-4 w-4" />下載 QR code
423-
</Button>
424456
</CardFooter>
425457
</Card>
426458
</section>

0 commit comments

Comments
 (0)