Files
dudu/design/ui_kits/desktop/LoginPurchase.jsx
T
wangjia 40760aa884
ci / server (push) Failing after 14s
ci / design-tokens (push) Failing after 11s
dudu MVP:五端语音输入法初始提交
- server:Go 网关(WS 流式识别中继/计费配额/微信登录支付 mock/反馈/埋点),gummy provider 已真实联调
- desktop:Tauri 2(全局快捷键 push-to-talk/浮层/托盘/设置/登录购买/反馈/首启引导)
- android:Compose 主 App + IME(键盘内录音直传)
- ios:App + 键盘扩展(1A spike 实证键盘内不可录音,走 deep link 听写)
- design/design-pipeline:设计系统 + token 导出 iOS/Android 主题
- doc:前后端设计文档(HTML);web:官网宣传页;todo:任务看板

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 00:38:37 +08:00

109 lines
6.1 KiB
React
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// dudu 桌面端 · 登录 / 购买窗口(点击走完整流程:扫码登录 → 选时长包 → 支付 → 完成)
function LoginPurchaseWindow() {
const { Button, Badge } = window.DuduDesignSystem_2e0172;
const [step, setStep] = React.useState('login'); // login | plans | pay | done
const [plan, setPlan] = React.useState('m');
const PACKS = {
s: { label: '100 分钟', price: 9, desc: '约 ¥0.09 / 分钟', tag: '' },
m: { label: '500 分钟', price: 39, desc: '约 ¥0.078 / 分钟', tag: '省 13%' },
l: { label: '2000 分钟', price: 129, desc: '约 ¥0.065 / 分钟', tag: '省 28%' },
};
const Center = ({ children }) => (
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 14, padding: '26px 24px 30px', fontFamily: 'var(--font-sans)' }}>
{children}
</div>
);
return (
<MacWindow title={step === 'login' ? 'dudu — 登录' : 'dudu — 购买时长'} width={400}>
{step === 'login' && (
<Center>
<span style={{ color: 'var(--accent)' }}><DuduLogo size={40} /></span>
<div style={{ fontSize: 'var(--text-lg)', fontWeight: 'var(--weight-semibold)', color: 'var(--text-1)' }}>微信扫码登录</div>
<FakeQr seed={7} />
<div style={{ fontSize: 'var(--text-sm)', color: 'var(--text-2)', textAlign: 'center', lineHeight: 'var(--leading-normal)' }}>
打开微信扫一扫确认后自动登录
</div>
<Button variant="ghost" size="sm" onClick={() => setStep('plans')}>模拟手机已确认 </Button>
</Center>
)}
{step === 'plans' && (
<Center>
<div style={{ alignSelf: 'stretch', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<span style={{ fontSize: 'var(--text-lg)', fontWeight: 'var(--weight-semibold)', color: 'var(--text-1)' }}>购买时长</span>
<span style={{ display: 'inline-flex', alignItems: 'center', gap: 8, fontSize: 'var(--text-sm)', color: 'var(--text-2)' }}>
wang*** <Badge tone="blue">试用中</Badge>
</span>
</div>
<div style={{ alignSelf: 'stretch', display: 'flex', flexDirection: 'column', gap: 10 }}>
{Object.entries(PACKS).map(([key, p]) => {
const active = plan === key;
return (
<button key={key} type="button" onClick={() => setPlan(key)} style={{
display: 'flex', justifyContent: 'space-between', alignItems: 'center', textAlign: 'left',
padding: '13px 16px', borderRadius: 'var(--radius-md)', cursor: 'pointer',
border: active ? '1.5px solid var(--accent)' : '1px solid var(--border-1)',
background: active ? 'var(--accent-soft)' : 'var(--surface-card)',
fontFamily: 'var(--font-sans)', transition: 'border-color var(--dur-fast) var(--ease-out), background var(--dur-fast) var(--ease-out)',
}}>
<span>
<span style={{ display: 'inline-flex', alignItems: 'center', gap: 8 }}>
<span style={{ fontSize: 'var(--text-base)', fontWeight: 'var(--weight-medium)', color: 'var(--text-1)' }}>{p.label}</span>
{p.tag && <span style={{ fontSize: 10, padding: '1px 7px', borderRadius: 'var(--radius-full)', background: 'var(--warning-soft)', color: 'var(--warning)', fontWeight: 'var(--weight-medium)' }}>{p.tag}</span>}
</span>
<span style={{ display: 'block', fontSize: 'var(--text-xs)', color: 'var(--text-2)', marginTop: 3 }}>{p.desc}</span>
</span>
<span style={{ color: active ? 'var(--accent-text)' : 'var(--text-1)', whiteSpace: 'nowrap' }}>
<span style={{ fontSize: 'var(--text-2xl)', fontWeight: 'var(--weight-bold)' }}>¥{p.price}</span>
</span>
</button>
);
})}
</div>
<div style={{ alignSelf: 'stretch', fontSize: 'var(--text-xs)', color: 'var(--text-3)', lineHeight: 'var(--leading-normal)' }}>
时长不过期 · 每天另有 3 分钟免费试用
</div>
<Button variant="primary" block onClick={() => setStep('pay')}>微信支付 ¥{PACKS[plan].price}</Button>
</Center>
)}
{step === 'pay' && (
<Center>
<div style={{ fontSize: 'var(--text-lg)', fontWeight: 'var(--weight-semibold)', color: 'var(--text-1)' }}>微信扫码支付</div>
<div style={{ fontSize: 'var(--text-3xl)', fontWeight: 'var(--weight-bold)', color: 'var(--text-1)', fontFamily: 'var(--font-sans)' }}>
¥{PACKS[plan].price}<span style={{ fontSize: 'var(--text-sm)', fontWeight: 'var(--weight-regular)', color: 'var(--text-3)' }}> · {PACKS[plan].label}时长包</span>
</div>
<FakeQr seed={23} />
<div style={{ fontSize: 'var(--text-sm)', color: 'var(--text-2)' }}>支付完成后自动开通</div>
<div style={{ display: 'flex', gap: 10 }}>
<Button variant="secondary" size="sm" onClick={() => setStep('plans')}>返回</Button>
<Button variant="ghost" size="sm" onClick={() => setStep('done')}>模拟支付成功 </Button>
</div>
</Center>
)}
{step === 'done' && (
<Center>
<span style={{
width: 56, height: 56, borderRadius: '50%', background: 'var(--positive-soft)',
color: 'var(--positive)', display: 'flex', alignItems: 'center', justifyContent: 'center',
}}><DuIcons.Check size={28} sw={2.5} /></span>
<div style={{ fontSize: 'var(--text-lg)', fontWeight: 'var(--weight-semibold)', color: 'var(--text-1)' }}>已到账 {PACKS[plan].label}</div>
<div style={{ fontSize: 'var(--text-sm)', color: 'var(--text-2)', textAlign: 'center' }}>
时长余额 472 分钟 · 不过期 · 现在就按住 Space 试试
</div>
<Button variant="primary" onClick={() => setStep('login')}>开始使用</Button>
</Center>
)}
</MacWindow>
);
}
Object.assign(window, { LoginPurchaseWindow });