import 'package:flutter/material.dart'; import 'package:mobile_scanner/mobile_scanner.dart'; import '../api/oa_client.dart'; import '../theme.dart'; class ScanLoginPage extends StatefulWidget { const ScanLoginPage({super.key, required this.api, this.ticket = ''}); final OaClient api; final String ticket; @override State createState() => _ScanLoginPageState(); } class _ScanLoginPageState extends State { late final TextEditingController _ticket; final MobileScannerController _scanner = MobileScannerController(); bool _loading = false; bool _scanned = false; String _err = ''; String _code = ''; @override void initState() { super.initState(); _ticket = TextEditingController(text: widget.ticket); } @override void dispose() { _ticket.dispose(); _scanner.dispose(); super.dispose(); } String _ticketFrom(String raw) { final value = raw.trim(); final uri = Uri.tryParse(value); if (uri?.scheme == 'fengyingoa' && uri?.host == 'qr') { return uri?.queryParameters['ticket']?.trim() ?? ''; } return value; } Future _ok([String? scanned]) async { final t = _ticketFrom(scanned ?? _ticket.text); if (t.isEmpty) { setState(() => _err = '请扫描电脑端二维码'); return; } setState(() { _loading = true; _err = ''; }); try { final raw = await widget.api.post('/auth/qr/confirm', {'ticket': t}); final map = raw is Map ? Map.from(raw) : {}; if (!mounted) return; setState(() { _ticket.text = t; _code = '${map['code'] ?? ''}'; _scanned = true; }); await _scanner.stop(); } catch (e) { if (mounted) setState(() => _err = '$e'); } finally { if (mounted) setState(() => _loading = false); } } Future _onDetect(BarcodeCapture capture) async { if (_loading || _scanned) return; final raw = capture.barcodes .map((b) => b.rawValue?.trim() ?? '') .firstWhere((v) => v.isNotEmpty, orElse: () => ''); if (raw.isEmpty) return; await _ok(raw); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.white, appBar: AppBar(title: const Text('扫一扫登录电脑')), body: Padding( padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ if (_code.isEmpty) ...[ const Text('扫描电脑端二维码后,手机会生成一次性六位登录码。请把该六码填写到电脑端,电脑才会完成登录。', style: TextStyle(color: kMute, height: 1.5)), const SizedBox(height: 16), ClipRRect( borderRadius: BorderRadius.circular(12), child: AspectRatio( aspectRatio: 1, child: MobileScanner(controller: _scanner, onDetect: _onDetect), ), ), ] else ...[ const Text('请在电脑端输入以下一次性六位登录码:', style: TextStyle(color: kMute, height: 1.5)), const SizedBox(height: 24), Center( child: SelectableText( _code, style: const TextStyle( fontSize: 42, fontWeight: FontWeight.w700, letterSpacing: 12, color: kInk), ), ), const SizedBox(height: 12), const Center( child: Text('两分钟内有效;不要把此码发给他人。', style: TextStyle(color: kMute))), const SizedBox(height: 24), FilledButton( onPressed: () => Navigator.pop(context, true), child: const Text('我已在电脑端输入'), ), ], const SizedBox(height: 16), if (_code.isEmpty) TextField( controller: _ticket, decoration: const InputDecoration(hintText: '扫码异常时,可粘贴二维码内容'), textCapitalization: TextCapitalization.characters, ), if (_err.isNotEmpty) ...[ const SizedBox(height: 12), Text(_err, style: const TextStyle(color: kDanger)), ], if (_code.isEmpty) ...[ const SizedBox(height: 20), FilledButton( onPressed: _loading ? null : _ok, child: Text(_loading ? '确认中…' : '确认扫码'), ), ], ], ), ), ); } }