diff --git a/gui/src-frontend/src/components/QrPreview.tsx b/gui/src-frontend/src/components/QrPreview.tsx index 36360f2..7ecc85a 100644 --- a/gui/src-frontend/src/components/QrPreview.tsx +++ b/gui/src-frontend/src/components/QrPreview.tsx @@ -1,10 +1,12 @@ import { useMemo } from 'react'; import { useQrState } from '../store/qrContext'; -/** 将 SVG 字符串转为安全的 data URL( 标签中浏览器会阻止 SVG 内的脚本执行) */ +/** SVG 字符串 → data URL(安全渲染,img 上下文阻止脚本执行) */ function svgToDataUrl(svg: string): string { - const encoded = btoa(unescape(encodeURIComponent(svg))); - return `data:image/svg+xml;base64,${encoded}`; + // TextEncoder 比 btoa(unescape(...)) 更可靠,正确支持 UTF-8 + const bytes = new TextEncoder().encode(svg); + const binStr = Array.from(bytes, (b) => String.fromCharCode(b)).join(''); + return `data:image/svg+xml;base64,${btoa(binStr)}`; } export default function QrPreview() { @@ -15,10 +17,13 @@ export default function QrPreview() { [state.preview?.svg], ); + const containerCls = + 'w-64 h-64 flex items-center justify-center bg-white rounded-xl shadow-sm'; + if (!svgDataUrl) { return (
-
+
{state.loading ? ( 生成中... ) : ( @@ -31,16 +36,18 @@ export default function QrPreview() { return (
- {/* SVG 转为 data URL 通过 渲染,浏览器在 img 上下文中阻止脚本执行 */} - QR 码 -
- 版本: {state.preview!.version} + {/* 纯白背景 + 微阴影,无边框/圆角干扰扫描 */} +
+ QR 码 +
+
+ 版本 {state.preview!.version} {state.preview!.size}×{state.preview!.size} - 掩码: {state.preview!.mask} + 掩码 {state.preview!.mask}
);