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 { 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