fix(weixin): 扫码登录二维码真实可扫——前端生成QR(修坏图) + DecodeConfig容忍空config
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:
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>
|
||||
|
||||
Reference in new issue
Block a user