【开源鸿蒙跨平台开发先锋训练营】DAY15~DAY19为开源鸿蒙跨平台应用全面集成添加核心场景-注册页面

【开源鸿蒙跨平台开发先锋训练营】DAY15~DAY19为开源鸿蒙跨平台应用全面集成添加核心场景-注册页面

有了登录页面 怎么可能没有注册页面呢 所以它来了

1.注册页面的开发先看截图效果

该页面包含手机号、密码、验证码输入,短信验证码发送与倒计时,表单验证,隐私政策同意与否。

2.功能部分

注册页面主要包含以下几个部分:

标题区域:用户注册标题

手机号输入框:带图标的手机号输入

密码输入框:支持显示/隐藏密码

验证码输入框:验证码输入 + 获取验证码按钮(带倒计时)

短信帮助链接:解决验证码问题的帮助信息

操作按钮:返回登录 + 立即注册

隐私政策同意:复选框 + 隐私政策页面

3.状态

class RegisterPage extends StatefulWidget { const RegisterPage({super.key}); @override State<RegisterPage> createState() => _RegisterPageState(); } class _RegisterPageState extends State<RegisterPage> { // 输入控制器 final TextEditingController _phoneController = TextEditingController(); final TextEditingController _passwordController = TextEditingController(); final TextEditingController _codeController = TextEditingController(); // 状态变量 bool _obscurePassword = true; // 密码是否隐藏 bool _agreedToPrivacy = false; // 是否同意隐私政策 int _countdown = 0; // 验证码倒计时 @override void dispose() { _phoneController.dispose(); _passwordController.dispose(); _codeController.dispose(); super.dispose(); } }

代码解释

三个TextEditingController分别为手机号、密码、验证码输入

_countdown是验证码按钮的倒计时状态

在 dispose中释放资源

4.页面整体代码结构

@override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.white, body: SafeArea( child: SingleChildScrollView( padding: const EdgeInsets.symmetric(horizontal: 32), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ const SizedBox(height: 60), // 标题 const Text( '用户注册', style: TextStyle( fontSize: 28, fontWeight: FontWeight.bold, color: Color(0xFF1F2937), ), textAlign: TextAlign.center, ), const SizedBox(height: 60), _buildPhoneInput(), const SizedBox(height: 24), _buildPasswordInput(), const SizedBox(height: 24), _buildCodeInput(), const SizedBox(height: 8), _buildSmsHelpLink(), const SizedBox(height: 24), _buildActionButtons(), const SizedBox(height: 40), _buildPrivacyAgreement(), ], ), ), ), ); }

5.手机号输入框

Widget _buildPhoneInput() { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon(Icons.phone_outlined, color: Colors.grey[600], size: 20), const SizedBox(width: 8), Text( '手机号', style: TextStyle( fontSize: 16, color: Colors.grey[700], fontWeight: FontWeight.w500, ), ), ], ), const SizedBox(height: 12), TextField( controller: _phoneController, keyboardType: TextInputType.phone, decoration: InputDecoration( hintText: '请输入手机号', hintStyle: TextStyle(color: Colors.grey[400]), filled: true, fillColor: const Color(0xFFF9FAFB), border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: BorderSide.none, ), contentPadding: const EdgeInsets.symmetric( horizontal: 16, vertical: 14, ), ), ), ], ); }

代码解释

1.与登录页面保持一致的设计风格

2.使用数字键盘类型TextInputType.phone

3.浅灰色背景

6.密码输入框

Widget _buildPasswordInput() { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon(Icons.lock_outline, color: Colors.grey[600], size: 20), const SizedBox(width: 8), Text( '密码', style: TextStyle( fontSize: 16, color: Colors.grey[700], fontWeight: FontWeight.w500, ), ), ], ), const SizedBox(height: 12), TextField( controller: _passwordController, obscureText: _obscurePassword, decoration: InputDecoration( hintText: '请输入密码', hintStyle: TextStyle(color: Colors.grey[400]), filled: true, fillColor: const Color(0xFFF9FAFB), border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: BorderSide.none, ), contentPadding: const EdgeInsets.symmetric( horizontal: 16, vertical: 14, ), suffixIcon: IconButton( icon: Icon( _obscurePassword ? Icons.visibility_off : Icons.visibility, color: Colors.grey[400], size: 20, ), onPressed: () { setState(() { _obscurePassword = !_obscurePassword; }); }, ), ), ), ], ); }

7.验证码输入框

Widget _buildCodeInput() { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon(Icons.shield_outlined, color: Colors.grey[600], size: 20), const SizedBox(width: 8), Text( '验证码', style: TextStyle( fontSize: 16, color: Colors.grey[700], fontWeight: FontWeight.w500, ), ), ], ), const SizedBox(height: 12), Row( children: [ // 验证码输入框 Expanded( child: TextField( controller: _codeController, keyboardType: TextInputType.number, decoration: InputDecoration( hintText: '请输入验证码', hintStyle: TextStyle(color: Colors.grey[400]), filled: true, fillColor: const Color(0xFFF9FAFB), border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: BorderSide.none, ), contentPadding: const EdgeInsets.symmetric( horizontal: 16, vertical: 14, ), ), ), ), const SizedBox(width: 12), // 获取验证码按钮 ElevatedButton( onPressed: _countdown > 0 ? null : _sendVerificationCode, style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF4CAF50), foregroundColor: Colors.white, disabledBackgroundColor: Colors.grey[300], disabledForegroundColor: Colors.grey[600], padding: const EdgeInsets.symmetric( horizontal: 20, vertical: 14, ), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), elevation: 0, ), child: Text( _countdown > 0 ? '${_countdown}s' : '获取验证码', style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w500, ), ), ), ], ), ], ); } 

代码解释一下

输入框和按钮并排布局

按钮根据倒计时状态动态显示文字

倒计时期间按钮禁用(onPressed: null)

禁用状态使用灰色

8.短信帮助功能

这个我要额外说一下 由于本人是个人开发者 所以懂得都懂 只能借鉴第3方 这个时候 这个功能我觉得是非常重要的

Align( alignment: Alignment.centerRight, child: TextButton( onPressed: _showSmsHelpDialog, child: const Text( '短信遇到问题?', style: TextStyle( color: Color(0xFF9CA3AF), fontSize: 13, decoration: TextDecoration.underline, decorationColor: Color(0xFF9CA3AF), ), ), ), )

9.返回登录和立即注册功能

Row( children: [ // 返回登录按钮 Expanded( child: OutlinedButton( onPressed: () { Navigator.pop(context); }, style: OutlinedButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: 16), side: const BorderSide(color: Color(0xFFE5E7EB)), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), ), child: const Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.arrow_back, size: 18, color: Color(0xFF6B7280)), SizedBox(width: 8), Text( '返回登录', style: TextStyle( fontSize: 15, color: Color(0xFF6B7280), fontWeight: FontWeight.w500, ), ), ], ), ), ), const SizedBox(width: 12), // 立即注册按钮 Expanded( flex: 2, child: ElevatedButton( onPressed: _handleRegister, style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF4CAF50), foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(vertical: 16), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), elevation: 0, ), child: const Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.person_add, size: 18), SizedBox(width: 8), Text( '立即注册', style: TextStyle( fontSize: 15, fontWeight: FontWeight.w600, ), ), ], ), ), ), ], ) 

10.校验是否为空

void _handleRegister() async {

// 验证手机号

if (_phoneController.text.isEmpty) {

_showMessage('请输入手机号');

return;

}

// 验证密码

if (_passwordController.text.isEmpty) {

_showMessage('请输入密码');

return;

}

// 验证验证码

if (_codeController.text.isEmpty) {

_showMessage('请输入验证码');

return;

}

// 验证隐私政策

if (!_agreedToPrivacy) {

_showMessage('请先阅读并同意隐私政策');

return;

}

}

10.调用注册的API

final result = await UserApi.register(

phone: _phoneController.text,

password: _passwordController.text,

code: _codeController.text,

);

11.注册成功之后的处理逻辑

if (result != null && result['success'] == true) {

// 注册成功,跳转到主页面,其实我觉得调回登录页蛮好的 ,后续改改吧

if (mounted) {

Navigator.pushAndRemoveUntil(

context,

MaterialPageRoute(builder: (context) => const MainPage()),

(route) => false, 

);

}

} else {

// 注册失败,显示错误信息

final message = result?['message'] ?? '注册失败,请重试';

_showMessage(message);

}

12.颜色推荐

颜色用途颜色值说明
主题色0xFF4CAF50按钮,复选框
主文本0xFF1F2937标题文字
次要文本Colors.grey[700]
提示文本Colors.grey[400]输入框占位符
帮助文本0xFF9CA3AF
内容文本0xFF4B5563对话框内容
输入框背景0xFFF9FAFB浅灰色背景
边框颜色0xFFE5E7EB按钮边框
禁用背景Colors.grey[300]禁用按钮背景

13.最后

欢迎加入开源鸿蒙跨平台社区:
https://openharmonycrossplatform.ZEEKLOG.net

Read more

工程必学!红黑树从概念到手撕实现,讲透平衡树的 “折中智慧”----《Hello C++ Wrold!》(22)--(C/C++)

工程必学!红黑树从概念到手撕实现,讲透平衡树的 “折中智慧”----《Hello C++ Wrold!》(22)--(C/C++)

文章目录 * 前言 * 红黑树的概念 * 红黑树的性质 * AVL树跟红黑树的比较 * 红黑树的模拟实现 * 插入新节点的处理 * 红黑树的验证 * 作业部分 前言 学完 AVL 树后,你是不是也有过这样的疑惑:明明 AVL 树是 “严格平衡” 的二叉搜索树,查询效率还更高,可为啥 C++ STL 的map/set、Linux 内核里的关键结构,偏偏选红黑树而不用它?难道 “更平衡” 反而成了缺点? 其实答案藏在 “工程取舍” 里 —— 红黑树的精髓,从来不是 “比 AVL 树更平衡”,而是 “在‘查询效率’和‘写入开销’之间找最优解”。它不像 AVL 树那样追求 “极致的矮”,而是用

By Ne0inhk
【C++----红黑树封装set / map底层大致封装】在C++的世界里,每一次编译都是对智慧的考验,每一次调试都是对耐心的磨砺。开发者们在这里不断学习、成长,用代码编织出一个个精彩纷呈的故事。

【C++----红黑树封装set / map底层大致封装】在C++的世界里,每一次编译都是对智慧的考验,每一次调试都是对耐心的磨砺。开发者们在这里不断学习、成长,用代码编织出一个个精彩纷呈的故事。

红黑树 set / map封装 * 1 封装红⿊树实现set和map * 1.1对底层源码及框架分析 * 2. 模拟实现map和set * 2.1 实现出复⽤红⿊树的框架,并⽀持insert * 2.2 ⽀持iterator的实现 * 2.2.1红黑树迭代器结构 * 2.2.2 迭代器++ * 2.2.4 iterator-- * 3 注意须知 [实现map/set] * 3.1 map[]实现 * 3.2代码实现 1 封装红⿊树实现set和map 1.1对底层源码及框架分析 SGI-STL30版本源代码,map和set的源代码在map/set/stl_

By Ne0inhk
【C++】现代C++的新特性constexpr,及其在C++14、C++17、C++20中的进化

【C++】现代C++的新特性constexpr,及其在C++14、C++17、C++20中的进化

各位读者大佬好,我是落羽!一个坚持不断学习进步的学生。 如果您觉得我的文章还不错,欢迎多多互三分享交流,一起学习进步! 也欢迎关注我的blog主页:落羽的落羽 文章目录 * 一、从C++11引入 * 1. 常量表达式和constexpr关键字的概念 * 2. constexpr修饰函数 * 二、constexpr在C++14中的进化 * 三、constexpr在C++17中的进化 * 四、constexpr在C++20中的进化 一、从C++11引入 1. 常量表达式和constexpr关键字的概念 现代C++,从C++11开始,引入了常量表达式和constexpr关键字的概念,并且在之后的C++标准中不断更新 常量表达式是指,值不会改变并且在编译过程中就能得到计算结果的表达式。用字面量、常量表达式初始化的const对象都是常量表达式。但是用变量初始化的const对象不是常量表达式。 constint a =1;//a是常量表达式constint b = a +1;//b是常量表达式int c

By Ne0inhk
自动驾驶中间件iceoryx - (附录)C++ 内存模型与原子操作详解

自动驾驶中间件iceoryx - (附录)C++ 内存模型与原子操作详解

附录A: C++ 内存模型与原子操作详解 📚 本附录内容 本附录深入讲解 C++ 11引入的内存模型(Memory Model)和原子操作(Atomic Operations), 这是理解 iceoryx 等高性能进程间通信系统无锁机制的核心基础。 适合读者:想深入理解 acquire/release 内存序语义需要实现或优化无锁数据结构想理解 iceoryx 内部同步机制的原理对并发编程和性能优化感兴趣的开发者 与主文档的关系:本附录是 第5章 同步与通知机制 的扩展阅读主文档 5.2.3 节提供了简化版本,适合快速学习本附录提供完整的技术细节和深入分析 目录 * A.1 为什么需要内存序 * A.2 C++ 内存序类型 * A.3 实例:生产者-消费者 * A.4 iceoryx 中的内存序使用 * A.5

By Ne0inhk