feat(profile): 网站个人中心「绑定小程序」—— 小程序码+扫码确认绑定
- 小程序行加「绑定」→ 显示bind-start小程序码, 轮询确认; services/auth 加 mpQrBindStart
This commit is contained in:
@@ -93,6 +93,10 @@ export async function mpQrStart() {
|
||||
export async function mpQrPoll(scene) {
|
||||
return req(`/auth/mp-qr/poll?scene=${encodeURIComponent(scene)}`, { method: 'GET' });
|
||||
}
|
||||
/** 发起「绑定小程序」:返回小程序码(微信扫码自动打开小程序确认),绑定到当前账号 */
|
||||
export async function mpQrBindStart() {
|
||||
return req('/auth/mp-qr/bind-start', { method: 'GET', auth: true });
|
||||
}
|
||||
/** 扫码/短信登录成功:落 session 后返回完整平台 profile */
|
||||
export function applyAuthResult(data) {
|
||||
if (data && data.token) saveSession(data);
|
||||
|
||||
@@ -137,3 +137,7 @@
|
||||
}
|
||||
.pf-tip { margin-top: 12px; font-size: 12.5px; color: #8e8e8e; }
|
||||
.btn-sm { font-size: 12.5px; padding: 4px 12px; line-height: 1.7; }
|
||||
|
||||
/* 绑定小程序 */
|
||||
.pf-mpbox { display: flex; flex-direction: column; align-items: center; gap: 10px; margin-top: 12px; padding: 10px; }
|
||||
.pf-mpimg { width: 180px; height: 180px; border-radius: 8px; }
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from 'react';
|
||||
import { ContentTemplate } from '@/components/templates';
|
||||
import { SectionHead, FieldText, FieldSelect, FieldCheck, Button, Icon } from '@/components/atoms';
|
||||
import AuthGate from '@/components/AuthGate';
|
||||
import { getUser, updateProfile, bindPhone, unbind, sendCode, me } from '@/services/auth';
|
||||
import { getUser, updateProfile, bindPhone, unbind, sendCode, me, mpQrBindStart, mpQrPoll } from '@/services/auth';
|
||||
import { BK_STATUS_OPTIONS, BK_TOPIC_OPTIONS, BK_SOURCE_OPTIONS } from '@/data/booking';
|
||||
import '@/styles/booking.css';
|
||||
|
||||
@@ -78,6 +78,30 @@ export default function Profile() {
|
||||
finally { setBinding(false); }
|
||||
};
|
||||
|
||||
/* 绑定小程序:发起 bind-start → 展示小程序码 → 轮询确认 */
|
||||
const [mpImg, setMpImg] = React.useState('');
|
||||
const [mpStarting, setMpStarting] = React.useState(false);
|
||||
const bindMini = async () => {
|
||||
setBindErr(''); setMpStarting(true); setMpImg('');
|
||||
try {
|
||||
const r = await mpQrBindStart();
|
||||
setMpImg(r.qr_image || '');
|
||||
setBindMsg('请用微信「扫一扫」打开小程序并确认绑定');
|
||||
if (r.scene) {
|
||||
const t = window.setInterval(async () => {
|
||||
try {
|
||||
const poll = await mpQrPoll(r.scene);
|
||||
if (poll.status === 'done') {
|
||||
window.clearInterval(t); setMpImg(''); setBindMsg('小程序绑定成功');
|
||||
await refreshBind();
|
||||
} else if (poll.status === 'expired') { window.clearInterval(t); setMpImg(''); setBindMsg('二维码已过期,请重试'); }
|
||||
} catch { /* ignore */ }
|
||||
}, 2000);
|
||||
}
|
||||
} catch (ex) { setBindErr((ex && ex.message) || '发起失败'); }
|
||||
finally { setMpStarting(false); }
|
||||
};
|
||||
|
||||
if (!authed) {
|
||||
return (
|
||||
<ContentTemplate active="profile" kicker="Profile" title="个人中心" desc="完善报名资料,报名活动时自动带入,无需重复填写。">
|
||||
@@ -124,9 +148,16 @@ export default function Profile() {
|
||||
</div>
|
||||
<div className="bk-line"><span>小程序</span>
|
||||
<span className="bk-bound">{bind.wxMiniBound ? '已绑定' : '未绑定'}
|
||||
{!bind.wxMiniBound && <button type="button" className="btn btn-cta btn-sm" disabled={binding || mpStarting} onClick={bindMini}>{mpStarting ? '发起中…' : '绑定'}</button>}
|
||||
{bind.wxMiniBound && <button type="button" className="btn btn-dark btn-sm" disabled={binding} onClick={() => doUnbind('wx_mini')}>解绑</button>}
|
||||
</span>
|
||||
</div>
|
||||
{mpImg && (
|
||||
<div className="pf-mpbox">
|
||||
<img src={mpImg} alt="小程序码" className="pf-mpimg" />
|
||||
<p className="pf-tip">用微信扫一扫打开小程序,在你手机上点「确认登录」完成绑定</p>
|
||||
</div>
|
||||
)}
|
||||
<div className="bk-line"><span>微信(开放平台)</span>
|
||||
<span className="bk-bound">{bind.wxBound ? '已绑定' : '未绑定'}
|
||||
{bind.wxBound && <button type="button" className="btn btn-dark btn-sm" disabled={binding} onClick={() => doUnbind('wx')}>解绑</button>}
|
||||
|
||||
Reference in New Issue
Block a user