fix: QR 扫描失败 + GUI 导出失败 — PNG margin + separator + fs 插件
🔴 QR 扫描失败根因 (2项): - render/png: saturating_sub 导致 margin 区域映射到 finder 黑角, quiet zone 全黑,扫描器无法定位 QR - matrix/patterns: 缺少右上 finder 左侧 + 左下 finder 顶部 隔离带预留,数据模块破坏 finder 检测比率(1:1:3:1:1) 🔴 GUI 导出失败 (2项): - gui/Cargo.toml + gui/lib.rs: 注册 tauri-plugin-fs 后端插件 (前端 writeFile 调用缺少 Rust handler) - capabilities: fs:allow-write-file + $HOME/** 路径 scope (ACL 默认不给 fs 写权限,需显式声明) 🔧 其他: - ExportPanel: 导出失败显示红色错误信息(替代静默吞错) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -20,3 +20,4 @@ tauri = { version = "2", features = [] }
|
||||
tauri-plugin-store = "2"
|
||||
tauri-plugin-dialog = "2"
|
||||
tauri-plugin-clipboard-manager = "2"
|
||||
tauri-plugin-fs = "2"
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
{
|
||||
"$schema": "../gen/schemas/desktop-schema.json",
|
||||
"identifier": "default",
|
||||
"description": "QRGen 默认权限 — 限制前端 IPC 和平台 API 访问",
|
||||
"description": "QRGen 默认权限",
|
||||
"windows": ["main"],
|
||||
"permissions": [
|
||||
"core:default",
|
||||
"store:default",
|
||||
"dialog:default",
|
||||
"clipboard-manager:default"
|
||||
"clipboard-manager:default",
|
||||
{
|
||||
"identifier": "fs:allow-write-file",
|
||||
"allow": [{ "path": "$HOME/**" }]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -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/**"}]}]}}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,2 @@
|
||||
allowBuilds:
|
||||
esbuild: false
|
||||
@@ -10,17 +10,21 @@ import { buildEncodedText } from '../utils/qrText';
|
||||
export default function ExportPanel() {
|
||||
const { state, dispatch } = useQrState();
|
||||
const [exporting, setExporting] = useState(false);
|
||||
const [errorMsg, setErrorMsg] = useState<string | null>(null);
|
||||
|
||||
const handleCopySvg = async () => {
|
||||
if (!state.preview?.svg) return;
|
||||
try {
|
||||
await writeText(state.preview.svg);
|
||||
} catch { /* 剪贴板不可用时静默忽略 */ }
|
||||
} catch (e) {
|
||||
setErrorMsg(`复制失败: ${e}`);
|
||||
}
|
||||
};
|
||||
|
||||
const handleExportPng = async () => {
|
||||
if (!state.preview?.svg) return;
|
||||
setExporting(true);
|
||||
setErrorMsg(null);
|
||||
try {
|
||||
const filePath = await save({
|
||||
filters: [{ name: 'PNG 图片', extensions: ['png'] }],
|
||||
@@ -35,12 +39,15 @@ export default function ExportPanel() {
|
||||
moduleSize: state.config.moduleSize,
|
||||
});
|
||||
await writeFile(filePath, new Uint8Array(bytes));
|
||||
} catch { /* 导出失败时静默处理,UI 回到就绪状态 */ }
|
||||
} catch (e) {
|
||||
setErrorMsg(`导出 PNG 失败: ${e}`);
|
||||
}
|
||||
setExporting(false);
|
||||
};
|
||||
|
||||
const handleExportSvg = async () => {
|
||||
if (!state.preview?.svg) return;
|
||||
setErrorMsg(null);
|
||||
try {
|
||||
const filePath = await save({
|
||||
filters: [{ name: 'SVG 图片', extensions: ['svg'] }],
|
||||
@@ -48,13 +55,21 @@ export default function ExportPanel() {
|
||||
});
|
||||
if (!filePath) return;
|
||||
await writeFile(filePath, new TextEncoder().encode(state.preview.svg));
|
||||
} catch { /* 导出失败时静默处理 */ }
|
||||
} catch (e) {
|
||||
setErrorMsg(`导出 SVG 失败: ${e}`);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="text-xs font-semibold text-gray-400 uppercase tracking-wider">导出选项</div>
|
||||
|
||||
{errorMsg && (
|
||||
<div className="text-xs text-red-500 bg-red-50 dark:bg-red-900/20 rounded-lg px-2 py-1.5 break-all">
|
||||
{errorMsg}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<label className="text-xs text-gray-600 dark:text-gray-400">
|
||||
纠错级别
|
||||
<select
|
||||
|
||||
@@ -118,6 +118,7 @@ pub fn run() {
|
||||
.plugin(tauri_plugin_store::Builder::new().build())
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
.plugin(tauri_plugin_clipboard_manager::init())
|
||||
.plugin(tauri_plugin_fs::init())
|
||||
.manage(AppState {
|
||||
history: Mutex::new(Vec::new()),
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user