Browse Source

feat(desktop): 微信登录改为展示小程序码,不再打开系统浏览器

liaoxg 1 day ago
parent
commit
1fdb91c3ad

+ 20 - 4
cfc-desktop-pet/src-tauri/src/api/models.rs

@@ -110,12 +110,14 @@ pub struct MediaRecord {
     pub url: Option<String>,
 }
 
-/// POST /api/auth/wechat-web/qr → {state, url}
+/// POST /api/auth/wechat-web/qr → {state, qrImage}
+/// qrImage 为小程序码的 data:image/png;base64,... 数据 URI(测试模式为空串)
 #[derive(Debug, Clone, Deserialize)]
 #[serde(rename_all = "camelCase")]
 pub struct WechatQrResp {
     pub state: String,
-    pub url: String,
+    #[serde(default)]
+    pub qr_image: Option<String>,
 }
 
 /// POST /api/auth/wechat-web/poll → {status, token?, userId?, role?, nickname?, message?}
@@ -208,12 +210,26 @@ mod tests {
 
     #[test]
     fn parses_wechat_qr_response() {
-        let json = r#"{"code":200,"message":"success","data":{"state":"abc123","url":"https://open.weixin.qq.com/connect/qrconnect?appid=x"}}"#;
+        let json = r#"{"code":200,"message":"success","data":{"state":"abc123","qrImage":"data:image/png;base64,AAAA"}}"#;
         let r: ApiResult<WechatQrResp> = serde_json::from_str(json).unwrap();
         assert!(r.is_success());
         let q = r.data.unwrap();
         assert_eq!(q.state, "abc123");
-        assert!(q.url.contains("qrconnect"));
+        assert!(q
+            .qr_image
+            .as_deref()
+            .unwrap_or("")
+            .starts_with("data:image/png;base64,"));
+    }
+
+    #[test]
+    fn parses_wechat_qr_response_empty_image() {
+        // 测试模式后端返回 qrImage="":字段存在但为空
+        let json = r#"{"code":200,"data":{"state":"abc123","qrImage":""}}"#;
+        let r: ApiResult<WechatQrResp> = serde_json::from_str(json).unwrap();
+        let q = r.data.unwrap();
+        assert_eq!(q.state, "abc123");
+        assert_eq!(q.qr_image.as_deref(), Some(""));
     }
 
     #[test]

+ 10 - 30
cfc-desktop-pet/src-tauri/src/commands.rs

@@ -143,8 +143,9 @@ pub async fn data_url_from_url(url: String) -> Result<String, String> {
     Ok(format!("data:{};base64,{}", mime, b64))
 }
 
-/// 微信扫码登录:取二维码链接 → 用系统浏览器打开 → 后台轮询换取 JWT → 写入 keyring。
-/// 结果通过 `pet://wechat-login` 事件回传
+/// 微信小程序码扫码登录:取小程序码图片 → 前端展示 → 后台轮询换取 JWT → 写入 keyring。
+/// 二维码图片通过 `pet://wechat-qr` 事件回传({state, qrImage});
+/// 登录结果通过 `pet://wechat-login` 事件回传
 /// (status: pending / success / notfound / expired / error / timeout)。
 #[tauri::command]
 pub async fn start_wechat_login(app: AppHandle) -> Result<serde_json::Value, String> {
@@ -156,7 +157,12 @@ pub async fn start_wechat_login(app: AppHandle) -> Result<serde_json::Value, Str
             .await
             .map_err(|e| format!("获取二维码失败: {e}"))?
     };
-    open_url(&qr.url)?;
+
+    // 将小程序码图片推给设置窗口展示(用户用微信扫码后在小程序内确认登录)
+    let _ = app.emit(
+        "pet://wechat-qr",
+        serde_json::json!({ "state": qr.state.clone(), "qrImage": qr.qr_image.clone() }),
+    );
 
     let api = app.state::<AppState>().api.clone();
     let state_token = qr.state.clone();
@@ -212,33 +218,7 @@ pub async fn start_wechat_login(app: AppHandle) -> Result<serde_json::Value, Str
         let _ = handle.emit("pet://wechat-login", serde_json::json!({"status": "timeout"}));
     });
 
-    Ok(serde_json::json!({ "status": "pending", "url": qr.url }))
-}
-
-/// 用系统默认浏览器打开 URL(Windows: cmd start / macOS: open / 其它: xdg-open)。
-fn open_url(url: &str) -> Result<(), String> {
-    #[cfg(target_os = "windows")]
-    {
-        std::process::Command::new("cmd")
-            .args(["/C", "start", "", url])
-            .spawn()
-            .map_err(|e| e.to_string())?;
-    }
-    #[cfg(target_os = "macos")]
-    {
-        std::process::Command::new("open")
-            .arg(url)
-            .spawn()
-            .map_err(|e| e.to_string())?;
-    }
-    #[cfg(not(any(target_os = "windows", target_os = "macos")))]
-    {
-        std::process::Command::new("xdg-open")
-            .arg(url)
-            .spawn()
-            .map_err(|e| e.to_string())?;
-    }
-    Ok(())
+    Ok(serde_json::json!({ "status": "pending", "qrImage": qr.qr_image }))
 }
 
 #[tauri::command]

+ 1 - 1
cfc-desktop-pet/src-tauri/tauri.conf.json

@@ -27,7 +27,7 @@
         "title": "设置",
         "url": "settings.html",
         "width": 360,
-        "height": 470,
+        "height": 640,
         "resizable": false,
         "visible": false
       }

+ 24 - 3
cfc-desktop-pet/src/settings.html

@@ -17,6 +17,7 @@
                     border: 1px dashed #ccc; border-radius: 8px; display: none; margin-bottom: 8px; }
   hr { border: none; border-top: 1px solid #eee; margin: 14px 0; }
   #status { margin-top: 10px; font-size: 12px; color: #999; }
+  #wechat-qr { display: none; width: 180px; height: 180px; margin: 8px auto 0; }
 </style>
 </head>
 <body>
@@ -39,7 +40,8 @@
 
   <label>微信登录</label>
   <button id="wechat-login">微信扫码登录</button>
-  <div class="hint">用微信扫码,自动与小程序账号同步登录(无需手动填令牌)</div>
+  <div class="hint">点击后出现小程序码,用微信扫码并在小程序内确认,即可自动与小程序账号同步登录(无需手动填令牌)</div>
+  <img id="wechat-qr" alt="小程序码" />
 
   <div id="status"></div>
   <script>
@@ -130,12 +132,28 @@
       });
 
       document.getElementById('wechat-login').addEventListener('click', function () {
-        setStatus('正在打开微信登录…');
+        setStatus('正在获取小程序码…');
         invoke('start_wechat_login').then(function () {
-          setStatus('请用微信扫码,完成后自动登录');
+          setStatus('请用微信扫码并在小程序内确认');
         }).catch(function (e) { setStatus('发起登录失败:' + e); });
       });
 
+      // Rust 获取到小程序码后由 emit 推送
+      try {
+        window.__TAURI__.event.listen('pet://wechat-qr', function (ev) {
+          var p = (ev && ev.payload) || {};
+          var img = document.getElementById('wechat-qr');
+          if (p.qrImage) {
+            img.src = p.qrImage;
+            img.style.display = 'block';
+            setStatus('请用微信扫码并在小程序内确认');
+          } else {
+            img.style.display = 'none';
+            setStatus('(测试模式)已生成登录状态:请在已登录的小程序内访问扫码登录接口确认');
+          }
+        });
+      } catch (e) { /* event API 不可用时忽略 */ }
+
       // 后台轮询任务完成后由 Rust 侧 emit
       try {
         window.__TAURI__.event.listen('pet://wechat-login', function (ev) {
@@ -143,14 +161,17 @@
           if (p.status === 'success') {
             setStatus('登录成功' + (p.nickname ? ':' + p.nickname : ''));
             tokenEl.value = '';
+            document.getElementById('wechat-qr').style.display = 'none';
           } else if (p.status === 'pending') {
             setStatus('等待扫码…');
           } else if (p.status === 'timeout') {
             setStatus('登录超时,请重试');
+            document.getElementById('wechat-qr').style.display = 'none';
           } else if (p.status === 'notfound') {
             setStatus('该微信未绑定小程序账号');
           } else if (p.status === 'expired') {
             setStatus('二维码已过期,请重试');
+            document.getElementById('wechat-qr').style.display = 'none';
           } else {
             setStatus('登录失败:' + (p.message || p.status));
           }