550 lines
14 KiB
Markdown
550 lines
14 KiB
Markdown
|
|
# 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组件:
|
|||
|
|
|
|||
|
|
```dart
|
|||
|
|
/// 短信验证组件
|
|||
|
|
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. 回调类型定义
|
|||
|
|
|
|||
|
|
```dart
|
|||
|
|
/// 确认按钮锁定状态变化回调
|
|||
|
|
typedef OnSubmitBtnLockChanged = void Function(bool locked);
|
|||
|
|
|
|||
|
|
/// 用于覆盖短信校验接口的回调
|
|||
|
|
typedef SmsCallbackOverride = Future<bool> Function(
|
|||
|
|
PhoneNumber phone,
|
|||
|
|
VerificationCode smsCode,
|
|||
|
|
);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3. 手机号掩码扩展
|
|||
|
|
|
|||
|
|
```dart
|
|||
|
|
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模块的通用组件:
|
|||
|
|
|
|||
|
|
```dart
|
|||
|
|
/// 通用项目组件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. 短信验证组件使用
|
|||
|
|
|
|||
|
|
```dart
|
|||
|
|
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. 自定义短信验证回调
|
|||
|
|
|
|||
|
|
```dart
|
|||
|
|
// 自定义短信验证逻辑
|
|||
|
|
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. 通用组件列表使用
|
|||
|
|
|
|||
|
|
```dart
|
|||
|
|
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. 国际化支持
|
|||
|
|
|
|||
|
|
```dart
|
|||
|
|
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 关键依赖
|
|||
|
|
|
|||
|
|
```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 (中文)
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"smsVerificationPage_hint_code": "请输入验证码",
|
|||
|
|
"smsVerificationPage_btn_get": "获取验证码",
|
|||
|
|
"smsVerificationsPage_error_invalidCode": "验证码格式不正确"
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### l10n/intl_en.arb (英文)
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"smsVerificationPage_hint_code": "Please enter verification code",
|
|||
|
|
"smsVerificationPage_btn_get": "Get Code",
|
|||
|
|
"smsVerificationsPage_error_invalidCode": "Invalid verification code format"
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 表单验证
|
|||
|
|
|
|||
|
|
### SmsAuthFormKey 使用
|
|||
|
|
|
|||
|
|
```dart
|
|||
|
|
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. 错误处理
|
|||
|
|
```dart
|
|||
|
|
// 推荐:完整的错误处理
|
|||
|
|
void _handleSmsResult(SmsAuthResult result) {
|
|||
|
|
switch (result.state) {
|
|||
|
|
case SmsAuthResultEnum.success:
|
|||
|
|
// 成功处理
|
|||
|
|
_navigateToNextPage(result);
|
|||
|
|
break;
|
|||
|
|
case SmsAuthResultEnum.failed:
|
|||
|
|
// 失败处理,显示用户友好的错误信息
|
|||
|
|
_showErrorMessage('验证失败,请检查验证码是否正确');
|
|||
|
|
break;
|
|||
|
|
case SmsAuthResultEnum.timeout:
|
|||
|
|
// 超时处理
|
|||
|
|
_showErrorMessage('验证超时,请重新获取验证码');
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2. 资源管理
|
|||
|
|
```dart
|
|||
|
|
// 推荐:及时释放资源
|
|||
|
|
@override
|
|||
|
|
void dispose() {
|
|||
|
|
_formKey.dispose();
|
|||
|
|
super.dispose();
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3. 无障碍支持
|
|||
|
|
```dart
|
|||
|
|
// 推荐:添加语义标签
|
|||
|
|
Semantics(
|
|||
|
|
label: '验证码输入框',
|
|||
|
|
hint: '请输入6位数字验证码',
|
|||
|
|
child: SmsAuthWidget(
|
|||
|
|
// 配置参数
|
|||
|
|
),
|
|||
|
|
)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 问题排查
|
|||
|
|
|
|||
|
|
### 常见问题
|
|||
|
|
1. **验证码收不到**: 检查手机号格式和网络连接
|
|||
|
|
2. **倒计时不工作**: 确认CountDownButton状态管理正确
|
|||
|
|
3. **国际化文本不显示**: 检查l10n配置和S.of(context)使用
|
|||
|
|
|
|||
|
|
### 调试技巧
|
|||
|
|
- 使用Flutter Inspector查看Widget树
|
|||
|
|
- 检查BLoC状态变化日志
|
|||
|
|
- 验证表单验证逻辑
|
|||
|
|
|