import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import '../../core/responsive/responsive.dart'; import '../../core/theme/context_tokens.dart'; import '../../core/theme/app_dims.g.dart'; import '../../repositories/auth_repository.dart'; /// 应用内自助注册新门店(公开页)。提交后创建门店 + 管理员账号, /// 返回门店编号引导去登录。对应后端 POST /api/v1/register。 class RegisterScreen extends ConsumerStatefulWidget { const RegisterScreen({super.key}); @override ConsumerState createState() => _RegisterScreenState(); } class _RegisterScreenState extends ConsumerState { final _formKey = GlobalKey(); final _shopName = TextEditingController(); final _phone = TextEditingController(); final _address = TextEditingController(); final _manager = TextEditingController(); final _username = TextEditingController(); final _password = TextEditingController(); final _confirm = TextEditingController(); bool _agree = false; bool _obscure = true; bool _submitting = false; @override void dispose() { for (final c in [ _shopName, _phone, _address, _manager, _username, _password, _confirm ]) { c.dispose(); } super.dispose(); } Future _submit() async { if (!(_formKey.currentState?.validate() ?? false)) return; if (!_agree) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('请先阅读并同意服务条款与隐私政策')), ); return; } setState(() => _submitting = true); try { final r = await AuthRepository.register( shopName: _shopName.text.trim(), phone: _phone.text.trim(), address: _address.text.trim(), managerName: _manager.text.trim(), username: _username.text.trim(), password: _password.text, ); if (!mounted) return; await showDialog( context: context, barrierDismissible: false, builder: (ctx) => AlertDialog( title: const Text('注册成功'), content: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('门店「${r.shopName}」已创建。'), const SizedBox(height: 12), Text('门店编号(登录时需用):', style: TextStyle(color: context.tokens.muted, fontSize: 13)), const SizedBox(height: 4), SelectableText(r.shopCode, style: const TextStyle( fontFamily: 'monospace', fontSize: 18, fontWeight: FontWeight.w700)), ], ), actions: [ ElevatedButton( onPressed: () { Navigator.of(ctx).pop(); context.go('/login'); }, child: const Text('去登录'), ), ], ), ); } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(e.toString().replaceFirst('AuthException: ', '')), backgroundColor: context.tokens.danger), ); } } finally { if (mounted) setState(() => _submitting = false); } } @override Widget build(BuildContext context) { final t = context.tokens; final wide = !context.isMobile; return Scaffold( backgroundColor: t.page, body: Center( child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 920, maxHeight: 720), child: Card( clipBehavior: Clip.antiAlias, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(AppDims.rXl)), child: Row( children: [ if (wide) SizedBox(width: 320, child: _brandPanel(t)), Expanded(child: _formPanel(t)), ], ), ), ), ), ); } Widget _brandPanel(t) { Widget step(int n, String title, String sub) => Padding( padding: const EdgeInsets.only(bottom: 18), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( width: 24, height: 24, alignment: Alignment.center, decoration: BoxDecoration( color: t.onPrimary.withValues(alpha: 0.18), shape: BoxShape.circle), child: Text('$n', style: TextStyle( color: t.onPrimary, fontSize: 12, fontWeight: FontWeight.w700)), ), const SizedBox(width: 10), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(title, style: TextStyle( color: t.onPrimary, fontSize: AppDims.fsBody, fontWeight: FontWeight.w600)), Text(sub, style: TextStyle( color: t.onPrimary.withValues(alpha: 0.72), fontSize: AppDims.fsSm)), ], ), ), ], ), ); return Container( color: t.primary, padding: const EdgeInsets.all(28), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Icon(Icons.local_bar, color: t.onPrimary, size: 32), const SizedBox(height: 16), Text('开通门店', style: TextStyle( color: t.onPrimary, fontSize: AppDims.fsH1, fontWeight: FontWeight.w700)), const SizedBox(height: 8), Text('几分钟创建你的酒水进销存门店,立即开始入库出库', style: TextStyle( color: t.onPrimary.withValues(alpha: 0.78), fontSize: AppDims.fsSm, height: 1.6)), const SizedBox(height: 28), step(1, '填写门店信息', '店名、编号与联系人'), step(2, '创建管理员账号', '用于登录与管理成员'), step(3, '开始使用', '入库录单即自动建档'), ], ), ); } Widget _formPanel(t) { return SingleChildScrollView( padding: const EdgeInsets.all(28), child: Form( key: _formKey, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Text('注册新门店', style: TextStyle( fontSize: AppDims.fsH2, fontWeight: FontWeight.w700)), const Spacer(), TextButton( onPressed: () => context.go('/login'), child: const Text('已有门店?直接登录 →'), ), ], ), const SizedBox(height: 16), _field(_shopName, '门店名称 *', '例如 鼎昌酒行', required: true), _row2( _field(_phone, '联系电话 *', '11 位手机号', required: true, keyboard: TextInputType.phone), _field(_address, '门店地址 *', '省 / 市 / 区 详细地址', required: true), ), _field(_manager, '管理员姓名 *', '例如 王经理', required: true), _row2( _field(_username, '登录账号 *', '字母 / 数字', required: true), const SizedBox.shrink(), ), _row2( _field(_password, '登录密码 *', '至少 6 位', required: true, obscure: _obscure, minLen: 6), _field(_confirm, '确认密码 *', '再次输入', required: true, obscure: _obscure, validator: (v) => v != _password.text ? '两次密码不一致' : null), ), CheckboxListTile( value: _agree, onChanged: (v) => setState(() => _agree = v ?? false), controlAffinity: ListTileControlAffinity.leading, contentPadding: EdgeInsets.zero, dense: true, title: Text('我已阅读并同意 服务条款 与 隐私政策', style: TextStyle(fontSize: AppDims.fsSm, color: t.muted)), ), const SizedBox(height: 12), SizedBox( width: double.infinity, child: ElevatedButton( onPressed: _submitting ? null : _submit, style: ElevatedButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: 14)), child: _submitting ? const SizedBox( width: 18, height: 18, child: CircularProgressIndicator( strokeWidth: 2, color: Colors.white)) : const Text('创建门店并进入'), ), ), ], ), ), ); } Widget _row2(Widget a, Widget b) => Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded(child: a), const SizedBox(width: 12), Expanded(child: b), ], ); Widget _field( TextEditingController ctrl, String label, String hint, { bool required = false, bool obscure = false, int? minLen, TextInputType? keyboard, String? Function(String?)? validator, }) { return Padding( padding: const EdgeInsets.only(bottom: 14), child: TextFormField( controller: ctrl, obscureText: obscure, keyboardType: keyboard, decoration: InputDecoration( labelText: label, hintText: hint, isDense: true, border: const OutlineInputBorder(), ), validator: validator ?? (v) { if (required && (v == null || v.trim().isEmpty)) return '必填'; if (minLen != null && (v?.length ?? 0) < minLen) { return '至少 $minLen 位'; } return null; }, ), ); } }