feat(profile): 桌面端账户管理「绑定小程序」—— 小程序码+扫码确认绑定
- 小程序行加「绑定」按钮 → Modal(bind-start小程序码, 轮询确认); auth.ts 加 bindMpStart
This commit is contained in:
@@ -190,6 +190,11 @@ export const authApi = {
|
||||
});
|
||||
},
|
||||
|
||||
/** 发起「绑定小程序」:返回小程序码(微信扫码自动打开小程序确认),绑定到当前账号 */
|
||||
bindMpStart: async (): Promise<{ scene: string; qr_image?: string; qr_url?: string; mp_enabled: boolean }> => {
|
||||
return request("/auth/mp-qr/bind-start");
|
||||
},
|
||||
|
||||
/** 小程序扫码登录:轮询登录态 */
|
||||
mpQrPoll: async (scene: string): Promise<{ status: string; token?: string; profile?: UserProfile }> => {
|
||||
return request(`/auth/mp-qr/poll?scene=${encodeURIComponent(scene)}`);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { Avatar, Button, Divider, Form, Input, Space, Tag } from "antd";
|
||||
import { useEffect, useState, useRef } from "react";
|
||||
import { Avatar, Button, Divider, Form, Input, Modal, Space, Tag, Spin } from "antd";
|
||||
|
||||
import { PageHeader } from "@/components/PageHeader";
|
||||
import { useAppMessage } from "../../../hooks/useAppMessage";
|
||||
@@ -81,6 +81,31 @@ function AccountPage() {
|
||||
finally { setBinding(false); }
|
||||
};
|
||||
|
||||
// 绑定小程序 modal
|
||||
const [bindMpOpen, setBindMpOpen] = useState(false);
|
||||
const [mpImg, setMpImg] = useState("");
|
||||
const timerRef = useRef<number | null>(null);
|
||||
useEffect(() => {
|
||||
if (!bindMpOpen) return;
|
||||
let scene = "";
|
||||
(async () => {
|
||||
try {
|
||||
const r = await authApi.bindMpStart();
|
||||
scene = r.scene;
|
||||
setMpImg(r.qr_image || "");
|
||||
timerRef.current = window.setInterval(async () => {
|
||||
try {
|
||||
const poll = await authApi.mpQrPoll(scene);
|
||||
if (poll.status === "done") { window.clearInterval(timerRef.current || 0); setBindMpOpen(false); message.success("小程序绑定成功"); await refreshBindings(); }
|
||||
else if (poll.status === "expired") { window.clearInterval(timerRef.current || 0); setBindMpOpen(false); }
|
||||
} catch { /* ignore */ }
|
||||
}, 2000);
|
||||
} catch (e) { message.error(e instanceof Error ? e.message : "发起失败"); setBindMpOpen(false); }
|
||||
})();
|
||||
return () => { if (timerRef.current) window.clearInterval(timerRef.current); };
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [bindMpOpen]);
|
||||
|
||||
const onSaveProfile = async (values: { nickname?: string }) => {
|
||||
setSaving(true);
|
||||
try {
|
||||
@@ -197,6 +222,7 @@ function AccountPage() {
|
||||
<span>小程序</span>
|
||||
<Space>
|
||||
<Tag color={bind.wxMiniBound ? "green" : "default"}>{bind.wxMiniBound ? "已绑定" : "未绑定"}</Tag>
|
||||
{!bind.wxMiniBound && <Button size="small" type="primary" onClick={() => setBindMpOpen(true)}>绑定</Button>}
|
||||
{bind.wxMiniBound && <Button size="small" loading={binding} onClick={() => doUnbind("wx_mini")}>解绑</Button>}
|
||||
</Space>
|
||||
</div>
|
||||
@@ -234,7 +260,17 @@ function AccountPage() {
|
||||
修改密码
|
||||
</Button>
|
||||
</Form>
|
||||
|
||||
<Divider />
|
||||
<div className={styles.bindTip}>③ 小程序绑定:用微信「扫一扫」打开小程序确认,即可把当前账号与该小程序微信身份绑定(若该微信已属其它账号会自动合并)。</div>
|
||||
</div>
|
||||
|
||||
<Modal open={bindMpOpen} footer={null} onCancel={() => setBindMpOpen(false)} title="绑定小程序">
|
||||
<div style={{ textAlign: "center", padding: "12px 0" }}>
|
||||
<p style={{ color: "#888", marginBottom: 12 }}>用微信扫一扫打开小程序并确认</p>
|
||||
{mpImg ? <img src={mpImg} alt="小程序码" style={{ width: 200, height: 200 }} /> : <Spin />}
|
||||
</div>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user