fix(weixin): 扫码登录二维码真实可扫——前端生成QR(修坏图) + DecodeConfig容忍空config
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled

weixin qrcode_img_content 是微信liteapp HTML页(非图片),后端fetch转base64只得<!doctype>→前端img坏图。
- 后端 login-qr 改返回 qrUrl(qrcode_img_content URL)+qrCode(原始qrcode),去掉坏 qrImage
- 前端用 qrcode 库从 qrUrl 生成真实二维码图片(微信扫https链接即开连接页), 新增 qrcode+@types/qrcode(纯UI依赖)
- DecodeConfig: nil config map 当空map解码(不报错), 否则 pending账号(空config)构建被跳过→未注册→登录端点404
- 测试更新(DecodeConfigNil/admin QR端点断言 qrUrl+qrCode)
- 另有 weixinAccountFor 裸weixin兜底(此前提交 d3b0921) 保证 accountId 命中
- 实测: login-qr 200{waiting, qrUrl=89}, 前端能看到可扫二维码
build/vet/test 全绿, 已部署.
This commit is contained in:
Aska committed 2026-09-07 21:32:19 +08:00
1 parent d3b0921491
commit 36bd19e997
8 files changed
+394 -29

No files matched your search

@@ -1,14 +1,37 @@
import { useEffect, useRef, useState } from "react";
import QRCode from "qrcode";
import { adminWeixinLoginQR, adminWeixinLoginStatus } from "../../../lib/api";
import { useAuditStore } from "../../audit";
import { Button, Surface, Tag } from "../../../components/primitives";
import { Notice } from "../../components";
/** weixin-iLink scan-login panel (design/08 §4). On an account without a bot_token
* it shows a 扫码登录 button; clicking it mints a QR via GET /login-qr, renders the
* QR image, and polls GET /login-status until confirmation. On confirmation the
* backend persists the token and hot-connects the adapter, so the panel just
* flags "已登录". An account that already has a token never shows the button. */
* it shows a 扫码登录 button; clicking it mints a QR via GET /login-qr, renders a
* scannable QR from the WeChat QR URL (qrcode_img_content), and polls GET /login-status
* until confirmation. The liteapp URL is an HTML page that cannot be fetched as an
* image, so the frontend draws the QR itself from qrUrl. On confirmation the backend
* persists the token and hot-connects the adapter; the panel flags "已登录". */
function QrImage({ value }: { value: string }): React.ReactNode {
const [src, setSrc] = useState<string>("");
useEffect(() => {
let alive = true;
QRCode.toDataURL(value, { width: 200, margin: 1 })
.then((url) => alive && setSrc(url))
.catch(() => alive && setSrc(""));
return () => {
alive = false;
};
}, [value]);
if (!value) return <Notice tone="warn">未获得二维码,请点「刷新二维码」重试。</Notice>;
if (!src) return <Notice>生成二维码中…</Notice>;
return (
<img
src={src}
alt="微信 iLink 登录二维码"
className="h-48 w-48 rounded-[var(--mu-radius)] border border-[var(--mu-glass-border)] bg-[var(--mu-surface-2)] object-contain p-2"
/>
);
}
export function WeixinLogin({
platform,
accountId,
@@ -18,7 +41,6 @@ export function WeixinLogin({
}): React.ReactNode {
const log = useAuditStore((s) => s.log);
const [status, setStatus] = useState<string>("idle");
const [qrImage, setQrImage] = useState<string>("");
const [qrUrl, setQrUrl] = useState<string>("");
const [botId, setBotId] = useState<string>("");
const [busy, setBusy] = useState<string | null>(null);
@@ -64,12 +86,10 @@ export function WeixinLogin({
const start = () => {
setBusy("__qr__");
setNotice(null);
setQrImage("");
setQrUrl("");
adminWeixinLoginQR(platform, accountId)
.then((r) => {
log("channel.weixin.login.qr", "ok");
setQrImage(r.qrImage ?? "");
setQrUrl(r.qrUrl ?? "");
if (r.status === "confirmed") {
setStatus("confirmed");
@@ -97,7 +117,7 @@ export function WeixinLogin({
.finally(() => setBusy(null));
};
const showQR = status === "waiting" || (qrImage !== "" || qrUrl !== "");
const showQR = status === "waiting" || qrUrl !== "";
return (
<Surface gap="sm" className="space-y-3">
@@ -124,14 +144,10 @@ export function WeixinLogin({
{showQR ? (
<div className="space-y-2">
{qrImage || qrUrl ? (
<img
src={qrImage || qrUrl}
alt="微信 iLink 登录二维码"
className="h-48 w-48 rounded-[var(--mu-radius)] border border-[var(--mu-glass-border)] bg-[var(--mu-surface-2)] object-contain p-2"
/>
{qrUrl ? (
<QrImage value={qrUrl} />
) : (
<Notice tone="warn">二维码图片暂不可用,请点击「刷新二维码」重试。</Notice>
<Notice tone="warn">二维码暂不可用,请点击「刷新二维码」重试。</Notice>
)}
<p className="text-xs text-[var(--mu-text-lo)]">请用微信扫描上方二维码,确认后自动连接(最多等待约 8 分钟)。</p>
</div>