// 登录 / 购买窗口四步流:扫码登录 → 时长包 → 支付二维码 → 完成 // 规格见 doc/frontend-design.html 4.2;二维码内容来自后端,轮询自动推进。 import { useEffect, useState } from 'react'; import { createRoot } from 'react-dom/client'; import '@dudu/design/styles.css'; import { Button } from '@dudu/design/components/core/Button'; import { Badge } from '@dudu/design/components/core/Badge'; import { invoke } from '../shared/tauri'; import { setThemePref } from '../shared/theme'; import { usePoll } from '../shared/usePoll'; function Center({ children }) { return (
{children}
); } // 简易二维码占位:真实渲染由 qr 内容字符串绘制(接入 qrcode 库或后端返图)。 function QrBox({ value }) { return (
{value || '加载中'}
); } function LoginApp() { const [step, setStep] = useState('login'); // login | plans | pay | done const [qr, setQr] = useState(null); const [qrExpired, setQrExpired] = useState(false); const [packs, setPacks] = useState([]); const [sel, setSel] = useState('pack_500'); const [order, setOrder] = useState(null); const [me, setMe] = useState(null); const [qrNonce, setQrNonce] = useState(0); // 递增以触发二维码重建(刷新) // 启动应用主题:随系统/已存偏好(深色模式下不再停在浅色)。 useEffect(() => { invoke('get_settings').then((s) => s && setThemePref(s.theme)); }, []); // 扫码登录:创建二维码(进入 login 步或点击刷新时重建)。 useEffect(() => { if (step !== 'login') return undefined; let live = true; setQrExpired(false); setQr(null); invoke('login_qr_create').then((r) => { if (live && r) setQr(r); }); return () => { live = false; }; }, [step, qrNonce]); // 扫码登录轮询:拿到 confirmed/expired 即停(不再永久 1Hz 打后端)。 usePoll(async (alive) => { const st = await invoke('login_qr_poll', { state: qr.state }); if (!alive()) return 'stop'; if (st && st.status === 'confirmed') { const m = await invoke('fetch_me'); const ps = await invoke('fetch_packs'); if (!alive()) return 'stop'; setMe(m); setPacks(ps || []); setStep('plans'); return 'stop'; } // 后端 2 分钟 qrTTL 过期后返回 expired:停轮询并提示点击刷新。 if (st && st.status === 'expired') { setQrExpired(true); return 'stop'; } return undefined; }, 1000, [qr && qr.state], step === 'login' && !!qr && !qrExpired); // 支付轮询:付款成功即停。 usePoll(async (alive) => { const st = await invoke('order_status', { orderId: order.order_id }); if (!alive()) return 'stop'; if (st && st.status === 'paid') { const m = await invoke('fetch_me'); if (!alive()) return 'stop'; setMe(m); setStep('done'); return 'stop'; } return undefined; }, 1000, [order && order.order_id], step === 'pay' && !!order); const refreshQr = () => setQrNonce((n) => n + 1); const pack = packs.find((p) => p.id === sel); if (step === 'login') { return (
微信扫码登录
{qrExpired ? ( <>
二维码已过期,点击刷新重新扫码
) : (
打开微信扫一扫,确认后自动登录
)}
); } if (step === 'plans') { return (
购买时长 {me && {me.nickname_masked} {me.account_state === 'ok' ? '余额充足' : '试用中'} }
{packs.map((p) => { const active = sel === p.id; return ( ); })}
时长不过期 · 每天另有 3 分钟免费试用
); } if (step === 'pay') { return (
微信扫码支付
¥{pack.price_cents / 100} · {pack.minutes} 分钟时长包
支付完成后自动开通
); } return (
已到账 {pack.minutes} 分钟
时长余额 {me ? Math.floor(me.balance_seconds / 60) : '—'} 分钟 · 不过期 · 现在就按住 ⌘⇧Space 试试
); } createRoot(document.getElementById('root')).render();