-
{title || '手机号登录 / 注册'}
+
{title || '登录 / 注册'}
{subtitle &&
{subtitle}
}
-
-
- setPhone(e.target.value)}
- inputMode="numeric"
- maxLength={11}
- placeholder="11 位手机号"
- autoComplete="tel"
- />
-
-
-
-
-
setCode(e.target.value)}
- inputMode="numeric"
- maxLength={6}
- placeholder="6 位验证码"
- style={{ flex: 1 }}
- autoComplete="one-time-code"
- />
-
- {debug &&
{debug}
}
- {err &&
{err}
}
-
{busy ? '处理中…' : '登录 / 注册'}
-
未注册的手机号将自动注册为账号
-
+ {mode === 'phone' ? (
+
+ ) : (
+
+
{ if (onAuthed) onAuthed(user); }} />
+ {err && {err}
}
+ 已在微信小程序登录过的账号,扫码即可一键登录网页端
+
+ )}
+
);
}
diff --git a/website/src/components/MpQrLogin.jsx b/website/src/components/MpQrLogin.jsx
new file mode 100644
index 0000000..17ac44d
--- /dev/null
+++ b/website/src/components/MpQrLogin.jsx
@@ -0,0 +1,60 @@
+import React from 'react';
+import { QRCodeSVG } from '@rc-component/qrcode';
+import { mpQrStart, mpQrPoll, applyAuthResult, getUser } from '@/services/auth';
+
+/** 小程序扫码登录:微信扫小程序码 → 打开小程序确认 → web 轮询拿 token。
+ * props: tip, onAuthed(user) — 登录成功回调(已写入 token 与用户信息)。 */
+export default function MpQrLogin({ tip, onAuthed }) {
+ const [qrImage, setQrImage] = React.useState('');
+ const [qrUrl, setQrUrl] = React.useState('');
+ const [status, setStatus] = React.useState('loading');
+ const [error, setError] = React.useState('');
+
+ React.useEffect(() => {
+ let timer = null;
+ (async () => {
+ setStatus('loading');
+ try {
+ const r = await mpQrStart();
+ const { scene, qr_image, qr_url } = r || {};
+ setQrImage(qr_image || '');
+ setQrUrl(qr_url || '');
+ setStatus('pending');
+ timer = window.setInterval(async () => {
+ const res = await mpQrPoll(scene);
+ if (res.status === 'done' && res.profile) {
+ window.clearInterval(timer);
+ applyAuthResult(res.profile);
+ if (onAuthed) onAuthed(getUser());
+ } else if (res.status === 'expired') {
+ window.clearInterval(timer);
+ setStatus('error');
+ setError('二维码已过期,请刷新');
+ }
+ }, 2000);
+ } catch (e) {
+ setStatus('error');
+ setError(e.message || '获取小程序码失败');
+ }
+ })();
+ return () => { if (timer) window.clearInterval(timer); };
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+
+ if (status === 'loading') return
加载中…
;
+ if (status === 'error')
+ return (
+
+
{error}
+
window.location.reload()}>点击刷新
+
+ );
+ return (
+
+
+ {qrImage ?

:
}
+
+
{tip || '使用微信扫一扫打开小程序,在小程序内确认登录'}
+
+ );
+}
diff --git a/website/src/styles/auth.css b/website/src/styles/auth.css
index d3adf83..01661c5 100644
--- a/website/src/styles/auth.css
+++ b/website/src/styles/auth.css
@@ -56,3 +56,6 @@
background: var(--panel); border: 1px dashed var(--line); border-radius: 12px; padding: 13px 16px;
}
.pine-note .logout-link { margin-left: 8px; color: var(--primary); cursor: pointer; text-decoration: underline; }
+
+/* 登录调试信息(演示验证码提示) */
+.auth-debug { margin: 6px 0; font-size: 12px; color: var(--success); background: var(--success-soft); border-radius: 8px; padding: 6px 10px; }