Initial Programm

This commit is contained in:
Joey Pillunat-Klebb | OH5 2025-11-03 15:36:43 +01:00
commit 748128d4d9
17 changed files with 7059 additions and 0 deletions

7
src-tauri/.gitignore vendored Normal file
View file

@ -0,0 +1,7 @@
# Generated by Cargo
# will have compiled files and executables
/target/
# Generated by Tauri
# will have schema files for capabilities auto-completion
/gen/schemas

5875
src-tauri/Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

22
src-tauri/Cargo.toml Normal file
View file

@ -0,0 +1,22 @@
[package]
name = "OH5_QR_Code_Generator"
version = "0.1.0"
description = "OH5 QR Code Generator"
authors = ["jpk aka Shiro"]
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[build-dependencies]
tauri-build = { version = "2", features = [] }
[dependencies]
tauri = { version = "2", features = [] }
tauri-plugin-opener = "2"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
anyhow = "1"
qrcode = { version = "0.14", features = ["image"] }
image = "0.25"
urlencoding = "2.1"
base64 = "0.22"

3
src-tauri/build.rs Normal file
View file

@ -0,0 +1,3 @@
fn main() {
tauri_build::build()
}

View file

@ -0,0 +1,10 @@
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "Capability for the main window",
"windows": ["main"],
"permissions": [
"core:default",
"opener:default"
]
}

BIN
src-tauri/icons/icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

BIN
src-tauri/icons/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

151
src-tauri/src/main.rs Normal file
View file

@ -0,0 +1,151 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
use serde::{Deserialize, Serialize};
use qrcode::{QrCode, EcLevel};
use image::{DynamicImage, ImageFormat, imageops, Luma};
use base64::Engine;
use urlencoding::encode;
#[derive(Debug, Deserialize)]
struct GeneratePayload {
name: String,
customer_no: String,
machine_from: u32,
machine_to: Option<u32>, // None => Single
text_body: String,
include_icon: bool,
icon_bytes: Option<Vec<u8>>, // PNG/JPG bytes (optional)
to_email: Option<String>,
}
#[derive(Debug, Serialize)]
struct OutputItem {
label: String,
filename: String,
png_base64: String,
}
#[derive(Debug, Serialize)]
struct Output {
items: Vec<OutputItem>,
}
fn build_mailto(
to: Option<&str>,
name: &str,
customer_no: &str,
machine_id: u32,
body: &str,
) -> String {
// Betreff nur Kundennummer + Fixtext
let subject = format!("Kunde: {} hat einen Fehler. QR Code Scan!", customer_no);
// Body enthält Firmenname, Kundennummer, Maschine + freien Text
let full_body = format!(
"Kunde: {}\n\nKundennummer: {}\n\nMaschine: {}\n\n{}",
name,
customer_no,
machine_id,
body
);
let subject_enc = encode(&subject);
let body_enc = encode(&full_body);
if let Some(addr) = to.filter(|s| !s.is_empty()) {
format!("mailto:{}?subject={}&body={}", addr, subject_enc, body_enc)
} else {
format!("mailto:?subject={}&body={}", subject_enc, body_enc)
}
}
fn render_qr_png(data: &str, icon_bytes: Option<&[u8]>) -> anyhow::Result<Vec<u8>> {
// Hohe Fehlerkorrektur, damit Icon robust ist
let code = QrCode::with_error_correction_level(data.as_bytes(), EcLevel::Q)?;
let target = 1024u32;
let qr_luma = code
.render::<Luma<u8>>()
.min_dimensions(target, target)
.quiet_zone(true)
.build();
let mut qr_rgba = DynamicImage::ImageLuma8(qr_luma).to_rgba8();
if let Some(bytes) = icon_bytes {
if let Ok(mut icon) = image::load_from_memory(bytes).map(|i| i.into_rgba8()) {
// Icon auf ~20% der QR-Breite skalieren
let target_w = (qr_rgba.width() as f32 * 0.20) as u32;
let scale = target_w as f32 / icon.width().max(1) as f32;
let target_h = (icon.height() as f32 * scale).round().max(1.0) as u32;
icon = imageops::resize(&icon, target_w, target_h, imageops::FilterType::Lanczos3);
let x = (qr_rgba.width() - icon.width()) / 2;
let y = (qr_rgba.height() - icon.height()) / 2;
imageops::overlay(&mut qr_rgba, &icon, x.into(), y.into());
}
}
let dyn_img = DynamicImage::ImageRgba8(qr_rgba);
let mut buf = Vec::new();
dyn_img.write_to(&mut std::io::Cursor::new(&mut buf), ImageFormat::Png)?;
Ok(buf)
}
#[tauri::command]
fn generate_qr(payload: GeneratePayload) -> Result<Output, String> {
let mut items = Vec::new();
let from = payload.machine_from;
let to = payload.machine_to.unwrap_or(from);
if to < from {
return Err("Ungültiger Bereich: Bis < Von".into());
}
let icon: Option<&[u8]> = if payload.include_icon {
payload.icon_bytes.as_deref()
} else {
None
};
for id in from..=to {
let to_addr = payload.to_email.as_deref().filter(|s| !s.is_empty()).unwrap_or("service@oh5.de");
let data = build_mailto(Some(to_addr), &payload.name, &payload.customer_no, id, &payload.text_body);
let png = render_qr_png(&data, icon).map_err(|e| format!("QR-Fehler: {e}"))?;
let b64 = base64::engine::general_purpose::STANDARD.encode(png);
let label = if from == to {
format!("Kunde: {}\nKundennummer: {}\nID: {}", payload.name, payload.customer_no, id)
} else {
//format!("ID: {}", id)
format!("Kunde: {}\nKundennummer: {}\nID: {}", payload.name, payload.customer_no, id)
};
let filename = format!("qr_{}_{}_{}.png", sanitize(&payload.name), payload.customer_no, id);
items.push(OutputItem { label, filename, png_base64: b64 });
}
Ok(Output { items })
}
// kleine Dateinamen-Helferfunktion
fn sanitize(s: &str) -> String {
let mut out = String::with_capacity(s.len());
for ch in s.chars() {
if ch.is_ascii_alphanumeric() || ch == '-' || ch == '_' {
out.push(ch);
} else if ch.is_whitespace() {
out.push('_');
}
}
if out.is_empty() { "kunde".to_string() } else { out }
}
fn main() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![generate_qr])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}

30
src-tauri/tauri.conf.json Normal file
View file

@ -0,0 +1,30 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "OH5 QR Code Generator",
"version": "0.1.0",
"identifier": "com.jpk.oh5-qr-generator",
"build": {
"frontendDist": "../src"
},
"app": {
"withGlobalTauri": true,
"windows": [
{
"title": "OH5 QR Code Generator",
"width": 1280,
"height": 700
}
],
"security": {
"csp": null
}
},
"bundle": {
"active": true,
"targets": "all",
"icon": [
"icons/icon.png",
"icons/icon.ico"
]
}
}