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
+4
View File
@@ -11,6 +11,10 @@
{
"identifier": "fs:allow-write-file",
"allow": [{ "path": "$HOME/**" }]
},
{
"identifier": "fs:allow-read-file",
"allow": [{ "path": "$HOME/**" }, { "path": "$TEMP/**" }]
}
]
}
+1 -1
View File
@@ -1 +1 @@
{"default":{"identifier":"default","description":"QRGen 默认权限","local":true,"windows":["main"],"permissions":["core:default","store:default","dialog:default","clipboard-manager:default",{"identifier":"fs:allow-write-file","allow":[{"path":"$HOME/**"}]}]}}
{"default":{"identifier":"default","description":"QRGen 默认权限","local":true,"windows":["main"],"permissions":["core:default","store:default","dialog:default","clipboard-manager:default",{"identifier":"fs:allow-write-file","allow":[{"path":"$HOME/**"}]},{"identifier":"fs:allow-read-file","allow":[{"path":"$HOME/**"},{"path":"$TEMP/**"}]}]}}
@@ -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>
);
}
+7
View File
@@ -104,6 +104,12 @@ fn load_history(state: tauri::State<AppState>) -> Result<Vec<HistoryEntry>, Stri
Ok(history.clone())
}
/// 解码 QR 码图片,返回文本内容
#[tauri::command]
fn decode_qr(image_bytes: Vec<u8>) -> Result<String, String> {
qr_core::decoder::decode_image(&image_bytes).map(|r| r.text)
}
/// 清空历史记录
#[tauri::command]
fn clear_history(state: tauri::State<AppState>) -> Result<(), String> {
@@ -125,6 +131,7 @@ pub fn run() {
.invoke_handler(tauri::generate_handler![
encode_qr,
export_png,
decode_qr,
save_history,
load_history,
clear_history,