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 }> => {
|
mpQrPoll: async (scene: string): Promise<{ status: string; token?: string; profile?: UserProfile }> => {
|
||||||
return request(`/auth/mp-qr/poll?scene=${encodeURIComponent(scene)}`);
|
return request(`/auth/mp-qr/poll?scene=${encodeURIComponent(scene)}`);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState, useRef } from "react";
|
||||||
import { Avatar, Button, Divider, Form, Input, Space, Tag } from "antd";
|
import { Avatar, Button, Divider, Form, Input, Modal, Space, Tag, Spin } from "antd";
|
||||||
|
|
||||||
import { PageHeader } from "@/components/PageHeader";
|
import { PageHeader } from "@/components/PageHeader";
|
||||||
import { useAppMessage } from "../../../hooks/useAppMessage";
|
import { useAppMessage } from "../../../hooks/useAppMessage";
|
||||||
@@ -81,6 +81,31 @@ function AccountPage() {
|
|||||||
finally { setBinding(false); }
|
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 }) => {
|
const onSaveProfile = async (values: { nickname?: string }) => {
|
||||||
setSaving(true);
|
setSaving(true);
|
||||||
try {
|
try {
|
||||||
@@ -197,6 +222,7 @@ function AccountPage() {
|
|||||||
<span>小程序</span>
|
<span>小程序</span>
|
||||||
<Space>
|
<Space>
|
||||||
<Tag color={bind.wxMiniBound ? "green" : "default"}>{bind.wxMiniBound ? "已绑定" : "未绑定"}</Tag>
|
<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>}
|
{bind.wxMiniBound && <Button size="small" loading={binding} onClick={() => doUnbind("wx_mini")}>解绑</Button>}
|
||||||
</Space>
|
</Space>
|
||||||
</div>
|
</div>
|
||||||
@@ -234,7 +260,17 @@ function AccountPage() {
|
|||||||
修改密码
|
修改密码
|
||||||
</Button>
|
</Button>
|
||||||
</Form>
|
</Form>
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
<div className={styles.bindTip}>③ 小程序绑定:用微信「扫一扫」打开小程序确认,即可把当前账号与该小程序微信身份绑定(若该微信已属其它账号会自动合并)。</div>
|
||||||
</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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user