diff --git a/website/src/services/auth.js b/website/src/services/auth.js index 87bf47f..70bacaa 100644 --- a/website/src/services/auth.js +++ b/website/src/services/auth.js @@ -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); diff --git a/website/src/styles/booking.css b/website/src/styles/booking.css index 9051551..1ba43e5 100644 --- a/website/src/styles/booking.css +++ b/website/src/styles/booking.css @@ -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; } diff --git a/website/src/user/Profile.jsx b/website/src/user/Profile.jsx index 9d91ed8..e54bda3 100644 --- a/website/src/user/Profile.jsx +++ b/website/src/user/Profile.jsx @@ -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 ( @@ -124,9 +148,16 @@ export default function Profile() {
小程序 {bind.wxMiniBound ? '已绑定' : '未绑定'} + {!bind.wxMiniBound && } {bind.wxMiniBound && }
+ {mpImg && ( +
+ 小程序码 +

用微信扫一扫打开小程序,在你手机上点「确认登录」完成绑定

+
+ )}
微信(开放平台) {bind.wxBound ? '已绑定' : '未绑定'} {bind.wxBound && }