From c7d9a74063a877b08bd209f93f0e856414deadfe Mon Sep 17 00:00:00 2001 From: Pine Date: Wed, 26 Aug 2026 12:48:17 +0800 Subject: [PATCH] =?UTF-8?q?feat(profile):=20=E7=BD=91=E7=AB=99=E4=B8=AA?= =?UTF-8?q?=E4=BA=BA=E4=B8=AD=E5=BF=83=E3=80=8C=E7=BB=91=E5=AE=9A=E5=B0=8F?= =?UTF-8?q?=E7=A8=8B=E5=BA=8F=E3=80=8D=E2=80=94=E2=80=94=20=E5=B0=8F?= =?UTF-8?q?=E7=A8=8B=E5=BA=8F=E7=A0=81+=E6=89=AB=E7=A0=81=E7=A1=AE?= =?UTF-8?q?=E8=AE=A4=E7=BB=91=E5=AE=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 小程序行加「绑定」→ 显示bind-start小程序码, 轮询确认; services/auth 加 mpQrBindStart --- website/src/services/auth.js | 4 ++++ website/src/styles/booking.css | 4 ++++ website/src/user/Profile.jsx | 33 ++++++++++++++++++++++++++++++++- 3 files changed, 40 insertions(+), 1 deletion(-) 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 && }