Compare commits
2 Commits
07da35c349
...
79ccac3d8e
| Author | SHA1 | Date | |
|---|---|---|---|
| 79ccac3d8e | |||
| 9f76bb31da |
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 刘航宇
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -1,10 +1,12 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useQrState } from '../store/qrContext';
|
||||
|
||||
/** 将 SVG 字符串转为安全的 data URL(<img> 标签中浏览器会阻止 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 (
|
||||
<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 ? (
|
||||
<span className="text-sm animate-pulse">生成中...</span>
|
||||
) : (
|
||||
@@ -31,16 +36,18 @@ export default function QrPreview() {
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center gap-3">
|
||||
{/* SVG 转为 data URL 通过 <img> 渲染,浏览器在 img 上下文中阻止脚本执行 */}
|
||||
<img
|
||||
src={svgDataUrl}
|
||||
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"
|
||||
/>
|
||||
<div className="flex gap-3 text-xs text-gray-500 dark:text-gray-400">
|
||||
<span>版本: {state.preview!.version}</span>
|
||||
{/* 纯白背景 + 微阴影,无边框/圆角干扰扫描 */}
|
||||
<div className={containerCls}>
|
||||
<img
|
||||
src={svgDataUrl}
|
||||
alt="QR 码"
|
||||
className="w-60 h-60"
|
||||
/>
|
||||
</div>
|
||||
<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!.mask}</span>
|
||||
<span>掩码 {state.preview!.mask}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user