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:
@@ -11,6 +11,10 @@
|
|||||||
{
|
{
|
||||||
"identifier": "fs:allow-write-file",
|
"identifier": "fs:allow-write-file",
|
||||||
"allow": [{ "path": "$HOME/**" }]
|
"allow": [{ "path": "$HOME/**" }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifier": "fs:allow-read-file",
|
||||||
|
"allow": [{ "path": "$HOME/**" }, { "path": "$TEMP/**" }]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 { useQrState } from '../store/qrContext';
|
||||||
import { invoke } from '@tauri-apps/api/core';
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
import { writeText } from '@tauri-apps/plugin-clipboard-manager';
|
import { writeText } from '@tauri-apps/plugin-clipboard-manager';
|
||||||
import { save } from '@tauri-apps/plugin-dialog';
|
import { open, save } from '@tauri-apps/plugin-dialog';
|
||||||
import { writeFile } from '@tauri-apps/plugin-fs';
|
import { readFile, writeFile } from '@tauri-apps/plugin-fs';
|
||||||
import type { QrConfig } from '../types';
|
import type { QrConfig } from '../types';
|
||||||
import { buildEncodedText } from '../utils/qrText';
|
import { buildEncodedText } from '../utils/qrText';
|
||||||
|
|
||||||
@@ -11,6 +11,30 @@ export default function ExportPanel() {
|
|||||||
const { state, dispatch } = useQrState();
|
const { state, dispatch } = useQrState();
|
||||||
const [exporting, setExporting] = useState(false);
|
const [exporting, setExporting] = useState(false);
|
||||||
const [errorMsg, setErrorMsg] = useState<string | null>(null);
|
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 () => {
|
const handleCopySvg = async () => {
|
||||||
if (!state.preview?.svg) return;
|
if (!state.preview?.svg) return;
|
||||||
@@ -139,6 +163,25 @@ export default function ExportPanel() {
|
|||||||
>
|
>
|
||||||
导出 SVG
|
导出 SVG
|
||||||
</button>
|
</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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -104,6 +104,12 @@ fn load_history(state: tauri::State<AppState>) -> Result<Vec<HistoryEntry>, Stri
|
|||||||
Ok(history.clone())
|
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]
|
#[tauri::command]
|
||||||
fn clear_history(state: tauri::State<AppState>) -> Result<(), String> {
|
fn clear_history(state: tauri::State<AppState>) -> Result<(), String> {
|
||||||
@@ -125,6 +131,7 @@ pub fn run() {
|
|||||||
.invoke_handler(tauri::generate_handler![
|
.invoke_handler(tauri::generate_handler![
|
||||||
encode_qr,
|
encode_qr,
|
||||||
export_png,
|
export_png,
|
||||||
|
decode_qr,
|
||||||
save_history,
|
save_history,
|
||||||
load_history,
|
load_history,
|
||||||
clear_history,
|
clear_history,
|
||||||
|
|||||||
Reference in New Issue
Block a user