//! QR 编码文本构造工具 //! //! 集中管理各模式的文本格式(与 gui 前端 `utils/qrText.ts` 功能一致) /// 构造 WiFi 连接字符串 pub fn build_wifi_text(ssid: &str, password: &str, encryption: &str, hidden: bool) -> String { let h = if hidden { "H:true;" } else { "" }; format!("WIFI:T:{encryption};S:{ssid};P:{password};{h};") } /// 构造 vCard 3.0 字符串(含扩展字段) #[allow(clippy::too_many_arguments)] pub fn build_vcard_text( name: &str, phone: &str, email: &str, company: &str, address: &str, title: &str, url: &str, birthday: &str, note: &str, photo: &str, ) -> String { let mut s = format!("BEGIN:VCARD\nVERSION:3.0\nFN:{name}\nTEL:{phone}\nEMAIL:{email}\nORG:{company}"); if !title.is_empty() { s.push_str(&format!("\nTITLE:{title}")); } if !address.is_empty() { s.push_str(&format!("\nADR:{address}")); } if !url.is_empty() { s.push_str(&format!("\nURL:{url}")); } if !birthday.is_empty() { s.push_str(&format!("\nBDAY:{birthday}")); } if !note.is_empty() { s.push_str(&format!("\nNOTE:{note}")); } if !photo.is_empty() { s.push_str(&format!("\nPHOTO:{photo}")); } s.push_str("\nEND:VCARD"); s } /// 构造 mailto 链接 pub fn build_email_text(to: &str, subject: &str, body: &str) -> String { let subject_enc = urlencoding(subject); let body_enc = urlencoding(body); format!("mailto:{to}?subject={subject_enc}&body={body_enc}") } /// 构造电话链接 pub fn build_phone_text(number: &str) -> String { format!("tel:{number}") } /// 构造短信链接 pub fn build_sms_text(number: &str, message: &str) -> String { format!("smsto:{number}:{message}") } /// 简易 URL 编码(仅编码特殊字符) fn urlencoding(s: &str) -> String { s.chars() .map(|c| match c { ' ' => "%20".into(), '&' => "%26".into(), '=' => "%3D".into(), '#' => "%23".into(), '%' => "%25".into(), '+' => "%2B".into(), '\n' => "%0A".into(), '\r' => "%0D".into(), _ => c.to_string(), }) .collect() } #[cfg(test)] mod tests { use super::*; #[test] fn test_build_wifi_text() { let text = build_wifi_text("MyWiFi", "pass123", "WPA", false); assert!(text.contains("WIFI:T:WPA;S:MyWiFi;P:pass123;")); } #[test] fn test_build_wifi_hidden() { let text = build_wifi_text("HiddenNet", "secret", "WPA2", true); assert!(text.contains("H:true;")); } #[test] fn test_build_vcard_text() { let text = build_vcard_text( "张三", "13800138000", "a@b.com", "公司", "北京", "", "", "", "", "", ); assert!(text.contains("BEGIN:VCARD")); assert!(text.contains("FN:张三")); assert!(text.contains("END:VCARD")); assert!(!text.contains("TITLE:")); // 空字段不输出 } #[test] fn test_build_vcard_full() { let text = build_vcard_text( "张三", "13800138000", "a@b.com", "公司", "北京", "工程师", "https://z.com", "1990-01-01", "备注", "https://z.com/p.jpg", ); assert!(text.contains("TITLE:工程师")); assert!(text.contains("URL:https://z.com")); assert!(text.contains("BDAY:1990-01-01")); assert!(text.contains("NOTE:备注")); assert!(text.contains("PHOTO:https://z.com/p.jpg")); } #[test] fn test_build_email_text() { let text = build_email_text("a@b.com", "Hello World", "Test body"); assert!(text.starts_with("mailto:a@b.com")); assert!(text.contains("Hello%20World")); } #[test] fn test_build_phone_text() { assert_eq!(build_phone_text("13800138000"), "tel:13800138000"); } #[test] fn test_build_sms_text() { let text = build_sms_text("13800138000", "Hi"); assert_eq!(text, "smsto:13800138000:Hi"); } }