fix: QR 预览优化 — 移除虚线边框/圆角/padding,专业白底+微阴影
- 去掉 border-dashed / rounded-2xl / p-4(干扰扫描) - 纯白容器 + shadow-sm,干净专业 - svgToDataUrl 改用 TextEncoder(替代废弃的 unescape) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,10 +1,12 @@
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { useQrState } from '../store/qrContext';
|
import { useQrState } from '../store/qrContext';
|
||||||
|
|
||||||
/** 将 SVG 字符串转为安全的 data URL(<img> 标签中浏览器会阻止 SVG 内的脚本执行) */
|
/** SVG 字符串 → data URL(安全渲染,img 上下文阻止脚本执行) */
|
||||||
function svgToDataUrl(svg: string): string {
|
function svgToDataUrl(svg: string): string {
|
||||||
const encoded = btoa(unescape(encodeURIComponent(svg)));
|
// TextEncoder 比 btoa(unescape(...)) 更可靠,正确支持 UTF-8
|
||||||
return `data:image/svg+xml;base64,${encoded}`;
|
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() {
|
export default function QrPreview() {
|
||||||
@@ -15,10 +17,13 @@ export default function QrPreview() {
|
|||||||
[state.preview?.svg],
|
[state.preview?.svg],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const containerCls =
|
||||||
|
'w-64 h-64 flex items-center justify-center bg-white rounded-xl shadow-sm';
|
||||||
|
|
||||||
if (!svgDataUrl) {
|
if (!svgDataUrl) {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center justify-center gap-3 text-gray-400">
|
<div className="flex flex-col items-center justify-center gap-3 text-gray-400">
|
||||||
<div className="w-64 h-64 border-2 border-dashed border-gray-300 dark:border-gray-700 rounded-2xl flex items-center justify-center bg-white/50 dark:bg-gray-800/50">
|
<div className={`${containerCls} border border-gray-200`}>
|
||||||
{state.loading ? (
|
{state.loading ? (
|
||||||
<span className="text-sm animate-pulse">生成中...</span>
|
<span className="text-sm animate-pulse">生成中...</span>
|
||||||
) : (
|
) : (
|
||||||
@@ -31,16 +36,18 @@ export default function QrPreview() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center gap-3">
|
<div className="flex flex-col items-center gap-3">
|
||||||
{/* SVG 转为 data URL 通过 <img> 渲染,浏览器在 img 上下文中阻止脚本执行 */}
|
{/* 纯白背景 + 微阴影,无边框/圆角干扰扫描 */}
|
||||||
|
<div className={containerCls}>
|
||||||
<img
|
<img
|
||||||
src={svgDataUrl}
|
src={svgDataUrl}
|
||||||
alt="QR 码"
|
alt="QR 码"
|
||||||
className="w-64 h-64 border-2 border-dashed border-gray-300 dark:border-gray-700 rounded-2xl p-4 bg-white dark:bg-white qr-preview"
|
className="w-60 h-60"
|
||||||
/>
|
/>
|
||||||
<div className="flex gap-3 text-xs text-gray-500 dark:text-gray-400">
|
</div>
|
||||||
<span>版本: {state.preview!.version}</span>
|
<div className="flex gap-3 text-xs text-gray-400">
|
||||||
|
<span>版本 {state.preview!.version}</span>
|
||||||
<span>{state.preview!.size}×{state.preview!.size}</span>
|
<span>{state.preview!.size}×{state.preview!.size}</span>
|
||||||
<span>掩码: {state.preview!.mask}</span>
|
<span>掩码 {state.preview!.mask}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user