import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:get/get.dart'; import 'package:openim_common/openim_common.dart'; import '../auth/company_login_logic.dart'; import '../brand/app_tokens.dart'; import '../feature_flags.dart'; /// 登录页第一批:v2 布局 + Token,保留 OpenIM 标识,不做畅联换标。 class CompanyLoginPage extends StatelessWidget { const CompanyLoginPage({super.key}); @override Widget build(BuildContext context) { final logic = Get.find(); return Scaffold( backgroundColor: AppColors.pageBg, body: Stack( children: [ SafeArea( child: LayoutBuilder( builder: (context, constraints) { return SingleChildScrollView( padding: const EdgeInsets.symmetric(horizontal: AppGap.x8), child: ConstrainedBox( constraints: BoxConstraints(minHeight: constraints.maxHeight), child: IntrinsicHeight( child: Column( children: [ const Spacer(flex: 2), _logo(), const SizedBox(height: AppGap.x4), Text( FeatureFlags.brandMigration ? '畅联' : 'OpenIM', style: const TextStyle( fontSize: 22, fontWeight: FontWeight.w700, color: AppColors.textPrimary, ), ), const SizedBox(height: AppGap.x1), const Text( '企业内部通讯', style: TextStyle(fontSize: AppFont.sub, color: AppColors.textSecondary), ), const SizedBox(height: AppGap.x8), Obx(() => _field( controller: logic.staffCtrl, hint: '请输入工号', icon: Icons.person_outline, error: logic.staffError.value, enabled: !logic.submitting.value, )), const SizedBox(height: AppGap.x3), Obx(() => _field( controller: logic.pwdCtrl, hint: '请输入密码', icon: Icons.lock_outline, obscure: logic.obscure.value, onToggleObscure: logic.toggleObscure, error: logic.pwdError.value, enabled: !logic.submitting.value, onSubmitted: (_) => logic.submit(), )), const SizedBox(height: AppGap.x4), Obx(() => _loginButton(logic)), const SizedBox(height: AppGap.x3), const Text( '登录即代表同意内部使用规范', style: TextStyle(fontSize: AppFont.small, color: AppColors.textSecondary), ), const Spacer(flex: 3), ], ), ), ), ); }, ), ), Obx(() { final msg = logic.toast.value; if (msg == null) return const SizedBox.shrink(); return SafeArea( child: Align( alignment: Alignment.topCenter, child: Padding( padding: const EdgeInsets.fromLTRB(AppGap.x4, AppGap.x2, AppGap.x4, 0), child: Material( color: AppColors.bannerBg, borderRadius: BorderRadius.circular(AppRadius.control), child: Padding( padding: const EdgeInsets.symmetric(horizontal: AppGap.x4, vertical: AppGap.x3), child: Text( msg, textAlign: TextAlign.center, style: const TextStyle(fontSize: AppFont.sub, color: Colors.white), ), ), ), ), ), ); }), ], ), ); } Widget _logo() { return ClipRRect( borderRadius: BorderRadius.circular(AppRadius.logo), child: Container( width: 72, height: 72, color: AppColors.primary, alignment: Alignment.center, child: ImageRes.loginLogo.toImage ..width = 48 ..height = 48, ), ); } Widget _field({ required TextEditingController controller, required String hint, required IconData icon, String? error, bool obscure = false, VoidCallback? onToggleObscure, bool enabled = true, ValueChanged? onSubmitted, }) { final borderColor = error != null ? AppColors.danger : Colors.transparent; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( height: 48, decoration: BoxDecoration( color: AppColors.searchBg, borderRadius: BorderRadius.circular(AppRadius.control), border: Border.all(color: borderColor, width: error != null ? 1 : 0), ), padding: const EdgeInsets.symmetric(horizontal: AppGap.x3), child: Row( children: [ Icon(icon, size: AppIconSize.sm, color: AppColors.textSecondary), const SizedBox(width: AppGap.x2), Expanded( child: TextField( controller: controller, enabled: enabled, obscureText: obscure, onSubmitted: onSubmitted, inputFormatters: [LengthLimitingTextInputFormatter(32)], style: const TextStyle(fontSize: AppFont.body, color: AppColors.textPrimary), decoration: InputDecoration( isDense: true, border: InputBorder.none, hintText: hint, hintStyle: const TextStyle(fontSize: AppFont.body, color: AppColors.textSecondary), ), ), ), if (onToggleObscure != null) IconButton( onPressed: onToggleObscure, icon: Icon( obscure ? Icons.visibility_off_outlined : Icons.visibility_outlined, size: AppIconSize.sm, color: AppColors.textSecondary, ), ), ], ), ), if (error != null) Padding( padding: const EdgeInsets.only(top: AppGap.x1, left: AppGap.x1), child: Text(error, style: const TextStyle(fontSize: AppFont.small, color: AppColors.danger)), ), ], ); } Widget _loginButton(CompanyLoginLogic logic) { final busy = logic.submitting.value; return SizedBox( width: double.infinity, height: 48, child: FilledButton( onPressed: busy ? null : logic.submit, style: FilledButton.styleFrom( backgroundColor: AppColors.primary, disabledBackgroundColor: AppColors.primary.withOpacity(0.4), foregroundColor: Colors.white, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(AppRadius.control)), ), child: busy ? const Row( mainAxisAlignment: MainAxisAlignment.center, children: [ SizedBox( width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white), ), SizedBox(width: AppGap.x2), Text('登录中', style: TextStyle(fontSize: AppFont.body, color: Colors.white)), ], ) : const Text('登 录', style: TextStyle(fontSize: AppFont.body, color: Colors.white)), ), ); } }