Files
oneapp_docs/basic_uis/ui_business.md
2025-09-24 14:08:54 +08:00

14 KiB
Raw Permalink Blame History

UI Business - 业务UI组件模块文档

模块概述

ui_business 是 OneApp 基础UI模块群中的业务UI组件库提供与具体业务场景相关的UI组件和工具。该模块包含账户相关组件、订单相关组件、同意书组件等业务特定的界面元素和交互逻辑。

基本信息

  • 模块名称: ui_business
  • 模块路径: oneapp_basic_uis/ui_business
  • 类型: Flutter Package Module
  • 主要功能: 业务UI组件、账户组件、订单组件、同意书组件

核心特性

  • 账户UI组件: 提供登录、短信验证、证书拍照等账户相关UI
  • 订单UI组件: 订单相关的业务界面组件
  • 同意书组件: 用户协议和同意书相关UI
  • 国际化支持: 支持中英文多语言
  • 表单验证: 集成表单验证和数据处理
  • 路由守卫: 业务路由保护和权限控制

目录结构

ui_business/
├── lib/
│   ├── ui_account.dart            # 账户相关组件导出
│   ├── ui_order.dart             # 订单相关组件导出
│   ├── ui_consent.dart           # 同意书相关组件导出
│   ├── ui_common.dart            # 通用业务组件导出
│   ├── route_guard.dart          # 路由守卫
│   ├── customer_care.dart        # 客服相关
│   ├── poi_to_car.dart          # POI到车功能
│   ├── src/                      # 源代码目录
│   │   ├── ui_account/          # 账户相关组件
│   │   │   ├── widgets/         # 账户UI组件
│   │   │   │   ├── sms_auth/    # 短信验证组件
│   │   │   │   ├── check_spin/  # 验证转动组件
│   │   │   │   ├── certificates_photo/ # 证书拍照
│   │   │   │   └── time_picker/ # 时间选择器
│   │   │   ├── models/          # 账户数据模型
│   │   │   ├── mgmt/           # 账户管理
│   │   │   └── ui_config.dart  # UI配置
│   │   ├── ui_order/           # 订单相关组件
│   │   └── constants/          # 常量定义
│   ├── generated/              # 生成的国际化文件
│   └── l10n/                   # 国际化资源
├── assets/                     # 资源文件
└── pubspec.yaml               # 依赖配置

核心架构组件

1. 短信验证组件 (SmsAuthWidget)

提供短信验证码输入和验证功能的完整UI组件

/// 短信验证组件
class SmsAuthWidget extends StatelessWidget {
  /// 构造器
  ///
  /// [verificationType] 验证码类型1-登录注册 2-重置密码 3-设置/重置spin
  /// [callback] 事件回调
  /// [formKey] 表单键
  /// [onSubmitBtnLockChanged] 确认按钮锁定回调
  /// [smsCallbackOverride] 覆盖默认操作
  /// [lockPhoneEdit] 是否锁定手机号修改
  SmsAuthWidget({
    required this.verificationType,
    required this.formKey,
    required this.callback,
    required this.lockPhoneEdit,
    this.onSubmitBtnLockChanged,
    this.smsCallbackOverride,
    Key? key,
  }) : super(key: key);

  /// 默认超时时间
  static const defaultTimeout = 60;

  /// 类型1-登录注册 2-重置密码 3-设置/重置spin
  final String verificationType;

  /// 表单键
  final SmsAuthFormKey formKey;

  /// 结果回调
  final void Function(SmsAuthResult result) callback;

  /// 确认按钮锁定状态变化回调
  final OnSubmitBtnLockChanged? onSubmitBtnLockChanged;

  /// 短信校验覆盖回调
  final SmsCallbackOverride? smsCallbackOverride;

  /// 是否锁定手机号修改
  final bool lockPhoneEdit;

  @override
  Widget build(BuildContext context) => BlocProvider(
        create: (ctx) => _bloc = SmsAuthBloc(smsCallbackOverride)
          ..add(const SmsAuthEvent.getLocalProfile()),
        child: BlocConsumer<SmsAuthBloc, SmsAuthState>(
          listener: _pageListener,
          builder: _createLayout,
        ),
      );
}

2. 回调类型定义

/// 确认按钮锁定状态变化回调
typedef OnSubmitBtnLockChanged = void Function(bool locked);

/// 用于覆盖短信校验接口的回调
typedef SmsCallbackOverride = Future<bool> Function(
  PhoneNumber phone,
  VerificationCode smsCode,
);

3. 手机号掩码扩展

extension _PhoneStringExtension on String {
  /// 手机号掩码显示中间4位用*替换
  String get maskPhone {
    final buffer = StringBuffer();
    int i = 0;
    for (final c in characters) {
      if ([3, 4, 5, 6].contains(i)) {
        buffer.write('*');
      } else {
        buffer.write(c);
      }
      i++;
    }
    return buffer.toString();
  }
}

4. 通用项目组件 (ItemAComponent)

来自general_ui_component模块的通用组件

/// 通用项目组件A
class ItemAComponent extends StatelessWidget {
  ItemAComponent({
    required this.title,
    required this.logoImg,
    required this.onItemAClick,
    required this.index,
    this.size,
  });

  /// Logo图片URL
  String logoImg = '';
  
  /// 标题文本
  String title = '';
  
  /// 点击回调
  OnItemAClick onItemAClick;
  
  /// 索引
  int index;
  
  /// 自定义尺寸
  double? size;

  @override
  Widget build(BuildContext context) {
    return InkWell(
      highlightColor: Colors.transparent,
      splashColor: Colors.transparent,
      child: Column(
        children: [
          Stack(
            alignment: Alignment.center,
            children: [
              Container(
                width: width(designWidgetWidth: 48),
                height: width(designWidgetWidth: 48),
                decoration: BoxDecoration(
                  shape: BoxShape.circle,
                  color: OneColors.bgcSub2,
                ),
              ),
              SizedBox(
                height: size ?? width(designWidgetWidth: 40),
                width: size ?? width(designWidgetWidth: 40),
                child: CacheImageComponent(
                  imageUrl: logoImg,
                ),
              ),
            ],
          ),
          Padding(
            padding: EdgeInsets.only(top: 5.0),
            child: Text(
              title,
              style: OneTextStyle.content(
                color: const Color(0xFF7C7F81),
              ),
            ),
          )
        ],
      ),
      onTap: () => onItemAClick(index),
    );
  }
}

typedef OnItemAClick = Function(int index);

使用指南

1. 短信验证组件使用

import 'package:ui_business/ui_account.dart';

class LoginPage extends StatefulWidget {
  @override
  _LoginPageState createState() => _LoginPageState();
}

class _LoginPageState extends State<LoginPage> {
  late SmsAuthFormKey _formKey;
  bool _submitBtnLocked = true;

  @override
  void initState() {
    super.initState();
    _formKey = SmsAuthFormKey();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('短信验证')),
      body: Padding(
        padding: EdgeInsets.all(16.0),
        child: Column(
          children: [
            SmsAuthWidget(
              verificationType: '1', // 登录注册类型
              formKey: _formKey,
              lockPhoneEdit: false,
              callback: _handleSmsResult,
              onSubmitBtnLockChanged: (locked) {
                setState(() {
                  _submitBtnLocked = locked;
                });
              },
            ),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: _submitBtnLocked ? null : _handleSubmit,
              child: Text('确认'),
            ),
          ],
        ),
      ),
    );
  }

  void _handleSmsResult(SmsAuthResult result) {
    switch (result.state) {
      case SmsAuthResultEnum.success:
        // 验证成功,处理登录逻辑
        print('验证成功UUID: ${result.uuid}');
        Navigator.pushReplacementNamed(context, '/home');
        break;
      case SmsAuthResultEnum.failed:
        // 验证失败
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text('验证失败,请重试')),
        );
        break;
    }
  }

  void _handleSubmit() {
    // 触发表单验证和提交
    _formKey.controllerList.forEach((controller) => controller());
  }
}

2. 自定义短信验证回调

// 自定义短信验证逻辑
Future<bool> customSmsVerification(
  PhoneNumber phone, 
  VerificationCode smsCode
) async {
  try {
    // 调用自定义API进行验证
    final response = await MyApiService.verifySms(
      phone: phone.value,
      code: smsCode.value,
    );
    return response.success;
  } catch (e) {
    print('短信验证异常: $e');
    return false;
  }
}

// 使用自定义验证回调
SmsAuthWidget(
  verificationType: '1',
  formKey: _formKey,
  lockPhoneEdit: false,
  callback: _handleSmsResult,
  smsCallbackOverride: customSmsVerification, // 使用自定义验证
)

3. 通用组件列表使用

import 'package:general_ui_component/ItemAComponent.dart';

class ServiceListPage extends StatelessWidget {
  final List<ServiceItem> services = [
    ServiceItem('充电服务', 'assets/images/charging.png'),
    ServiceItem('维修保养', 'assets/images/maintenance.png'),
    ServiceItem('道路救援', 'assets/images/rescue.png'),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('服务列表')),
      body: GridView.builder(
        padding: EdgeInsets.all(16.0),
        gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 3,
          childAspectRatio: 1.0,
          crossAxisSpacing: 16.0,
          mainAxisSpacing: 16.0,
        ),
        itemCount: services.length,
        itemBuilder: (context, index) {
          final service = services[index];
          return ItemAComponent(
            title: service.name,
            logoImg: service.iconUrl,
            index: index,
            onItemAClick: _handleServiceClick,
          );
        },
      ),
    );
  }

  void _handleServiceClick(int index) {
    final service = services[index];
    print('点击了服务: ${service.name}');
    // 处理服务点击逻辑
  }
}

4. 国际化支持

import 'package:ui_business/generated/l10n.dart';

class LocalizedWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text(S.of(context).smsVerificationPage_hint_code),
        Text(S.of(context).smsVerificationPage_btn_get),
      ],
    );
  }
}

依赖配置

pubspec.yaml 关键依赖

dependencies:
  flutter:
    sdk: flutter

  # 基础UI组件
  ui_basic:
    path: ../ui_basic
    
  # 基础工具
  basic_utils:
    path: ../../oneapp_basic_utils/basic_utils
    
  # 模块化路由
  basic_modular:
    path: ../../oneapp_basic_utils/basic_modular
    
  # 账户服务
  clr_account:
    path: ../../oneapp_account/clr_account
    
  # 状态管理
  flutter_bloc: ^8.0.0
  
  # 国际化
  flutter_localizations:
    sdk: flutter
  intl: ^0.17.0

dev_dependencies:
  # 国际化代码生成
  intl_utils: ^2.8.0

国际化配置

l10n/intl_zh.arb (中文)

{
  "smsVerificationPage_hint_code": "请输入验证码",
  "smsVerificationPage_btn_get": "获取验证码",
  "smsVerificationsPage_error_invalidCode": "验证码格式不正确"
}

l10n/intl_en.arb (英文)

{
  "smsVerificationPage_hint_code": "Please enter verification code",
  "smsVerificationPage_btn_get": "Get Code",
  "smsVerificationsPage_error_invalidCode": "Invalid verification code format"
}

表单验证

SmsAuthFormKey 使用

class CustomFormPage extends StatefulWidget {
  @override
  _CustomFormPageState createState() => _CustomFormPageState();
}

class _CustomFormPageState extends State<CustomFormPage> {
  late SmsAuthFormKey _formKey;

  @override
  void initState() {
    super.initState();
    _formKey = SmsAuthFormKey();
  }

  @override
  Widget build(BuildContext context) {
    return Form(
      key: _formKey,
      child: Column(
        children: [
          // 表单字段
          ElevatedButton(
            onPressed: () {
              if (_formKey.validate()) {
                _formKey.save();
                // 使用表单数据
                print('手机号: ${_formKey.phone}');
                print('短信码: ${_formKey.sms}');
              }
            },
            child: Text('提交'),
          ),
        ],
      ),
    );
  }
}

最佳实践

1. 错误处理

// 推荐:完整的错误处理
void _handleSmsResult(SmsAuthResult result) {
  switch (result.state) {
    case SmsAuthResultEnum.success:
      // 成功处理
      _navigateToNextPage(result);
      break;
    case SmsAuthResultEnum.failed:
      // 失败处理,显示用户友好的错误信息
      _showErrorMessage('验证失败,请检查验证码是否正确');
      break;
    case SmsAuthResultEnum.timeout:
      // 超时处理
      _showErrorMessage('验证超时,请重新获取验证码');
      break;
  }
}

2. 资源管理

// 推荐:及时释放资源
@override
void dispose() {
  _formKey.dispose();
  super.dispose();
}

3. 无障碍支持

// 推荐:添加语义标签
Semantics(
  label: '验证码输入框',
  hint: '请输入6位数字验证码',
  child: SmsAuthWidget(
    // 配置参数
  ),
)

问题排查

常见问题

  1. 验证码收不到: 检查手机号格式和网络连接
  2. 倒计时不工作: 确认CountDownButton状态管理正确
  3. 国际化文本不显示: 检查l10n配置和S.of(context)使用

调试技巧

  • 使用Flutter Inspector查看Widget树
  • 检查BLoC状态变化日志
  • 验证表单验证逻辑