feat: GUI 端新增解码功能 — 选择图片解码 QR 码

- 新增 decode_qr Tauri command(接收图片字节,返回解码文本)
- ExportPanel 新增「选择图片解码」按钮 + 解码结果展示
- fs:allow-read-file 权限,支持 /c/Users/33644 和 C:\Users\33644\AppData\Local\Temp 路径
This commit is contained in:
2026-06-19 20:39:07 +08:00
parent effc88c6d7
commit 11fbe20102
4 changed files with 57 additions and 3 deletions
@@ -2,8 +2,8 @@ import { useState } from 'react';
import { useQrState } from '../store/qrContext';
import { invoke } from '@tauri-apps/api/core';
import { writeText } from '@tauri-apps/plugin-clipboard-manager';
import { save } from '@tauri-apps/plugin-dialog';
import { writeFile } from '@tauri-apps/plugin-fs';
import { open, save } from '@tauri-apps/plugin-dialog';
import { readFile, writeFile } from '@tauri-apps/plugin-fs';
import type { QrConfig } from '../types';
import { buildEncodedText } from '../utils/qrText';
@@ -11,6 +11,30 @@ export default function ExportPanel() {
const { state, dispatch } = useQrState();
const [exporting, setExporting] = useState(false);
const [errorMsg, setErrorMsg] = useState<string | null>(null);
const [decodedText, setDecodedText] = useState<string | null>(null);
const [decoding, setDecoding] = useState(false);
const handleDecode = async () => {
setDecoding(true);
setErrorMsg(null);
setDecodedText(null);
try {
const filePath = await open({
filters: [{ name: '图片文件', extensions: ['png', 'jpg', 'jpeg', 'webp', 'bmp'] }],
multiple: false,
});
if (!filePath) {
setDecoding(false);
return;
}
const bytes = await readFile(filePath);
const text: string = await invoke('decode_qr', { imageBytes: Array.from(bytes) });
setDecodedText(text);
} catch (e) {
setErrorMsg(`解码失败: ${e}`);
}
setDecoding(false);
};
const handleCopySvg = async () => {
if (!state.preview?.svg) return;
@@ -139,6 +163,25 @@ export default function ExportPanel() {
>
SVG
</button>
{/* 解码区 */}
<div className="mt-3 pt-3 border-t border-gray-200 dark:border-gray-700">
<div className="text-xs font-semibold text-gray-400 uppercase tracking-wider mb-2">
</div>
<button
onClick={handleDecode}
disabled={decoding}
className="w-full py-2 rounded-lg bg-amber-500 text-white text-sm font-medium hover:bg-amber-600 disabled:opacity-40 transition-all"
>
{decoding ? '解码中...' : '选择图片解码'}
</button>
{decodedText && (
<div className="mt-2 p-2 bg-green-50 dark:bg-green-900/20 rounded-lg text-xs text-green-700 dark:text-green-300 break-all max-h-24 overflow-auto">
{decodedText}
</div>
)}
</div>
</div>
);
}