资讯动态

Flutter与Firebase构建用户系统全流程指南

发布时间:2026/10/30 5:15:36 来源:尧图企业网站定制
1. 项目概述与目标新建一个名为User的APP这个项目标题看似简单却蕴含着移动应用开发的核心流程。作为一个完整的APP开发项目我们需要从零开始构建一个以用户为中心的应用程序涵盖从概念设计到最终发布的完整生命周期。这个项目特别适合那些希望掌握全栈移动开发技能的开发者或者想要创建用户管理系统的创业团队。在当今移动优先的数字环境中用户类APP是最基础也最关键的应用程序类型之一。无论是社交平台、电商应用还是工具类软件用户系统都是不可或缺的核心模块。通过这个项目我们不仅能够学习基础的APP开发技术还能深入理解现代用户系统的设计理念和实现方法。2. 开发环境准备与技术选型2.1 开发工具与平台选择在开始开发User APP之前我们需要搭建合适的开发环境。对于移动应用开发主要有以下几种选择原生开发iOSXcode Swift/Objective-CAndroidAndroid Studio Java/Kotlin优势最佳性能完整API访问劣势需要维护两套代码跨平台框架FlutterDart语言React NativeJavaScriptXamarinC#优势一套代码多平台运行劣势性能略低部分原生功能受限对于这个User APP项目我推荐使用Flutter框架因为它提供了出色的开发体验和接近原生的性能同时能够覆盖iOS和Android两大平台。安装Flutter开发环境需要以下步骤# 下载Flutter SDK git clone https://github.com/flutter/flutter.git -b stable # 添加Flutter到环境变量 export PATH$PATH:pwd/flutter/bin # 运行医生检查工具 flutter doctor2.2 后端服务选择User APP通常需要后端支持来处理用户数据。我们有几种选择自建后端使用Node.js Express或Python Django/Flask需要自行管理服务器和数据库BaaS后端即服务Firebase提供完整的用户认证和数据存储AWS Amplify集成AWS的各种服务Supabase开源的Firebase替代方案考虑到开发效率和成本我建议初学者使用Firebase它提供了免费的额度和完善的文档。特别是它的Authentication服务可以轻松实现邮箱/密码、手机号、社交媒体账号等多种登录方式。3. 用户系统核心功能设计3.1 用户模型定义一个完整的用户系统需要定义清晰的用户模型。以下是User APP的基础用户模型字段class User { final String uid; // 用户唯一标识 final String? email; final String? displayName; final String? photoURL; final DateTime createdAt; // 创建时间 final DateTime? lastLoginAt; // 最后登录时间 final bool emailVerified; // 邮箱是否验证 final MapString, dynamic? customClaims; // 自定义声明 // 其他业务相关字段... }3.2 核心功能模块User APP应该包含以下基本功能模块认证流程注册邮箱/密码、手机号、第三方登录登录/登出密码重置邮箱验证用户资料管理查看/编辑个人资料头像上传个人设置安全功能会话管理敏感操作二次验证登录设备管理社交功能可选用户搜索关注/粉丝系统私信功能4. 使用Flutter实现User APP4.1 项目初始化与配置首先创建Flutter项目flutter create user_app cd user_app然后添加Firebase依赖。在pubspec.yaml中添加dependencies: firebase_core: ^2.4.1 firebase_auth: ^4.2.5 cloud_firestore: ^4.3.1 provider: ^6.0.5运行flutter pub get安装依赖。4.2 实现用户认证流程4.2.1 邮箱/密码注册FutureUser? registerWithEmailAndPassword( String email, String password) async { try { UserCredential credential await FirebaseAuth.instance .createUserWithEmailAndPassword( email: email, password: password); return credential.user; } on FirebaseAuthException catch (e) { print(注册失败: ${e.message}); return null; } }4.2.2 邮箱/密码登录FutureUser? signInWithEmailAndPassword( String email, String password) async { try { UserCredential credential await FirebaseAuth.instance .signInWithEmailAndPassword( email: email, password: password); return credential.user; } on FirebaseAuthException catch (e) { print(登录失败: ${e.message}); return null; } }4.2.3 密码重置Futurevoid sendPasswordResetEmail(String email) async { try { await FirebaseAuth.instance.sendPasswordResetEmail(email: email); } on FirebaseAuthException catch (e) { print(发送密码重置邮件失败: ${e.message}); } }4.3 用户资料管理实现4.3.1 获取当前用户信息User? getCurrentUser() { return FirebaseAuth.instance.currentUser; }4.3.2 更新用户资料Futurevoid updateProfile(String displayName, String photoURL) async { try { await FirebaseAuth.instance.currentUser?.updateDisplayName(displayName); await FirebaseAuth.instance.currentUser?.updatePhotoURL(photoURL); } on FirebaseAuthException catch (e) { print(更新资料失败: ${e.message}); } }4.3.3 存储额外用户信息Firebase Authentication只存储基本用户信息如果需要存储更多信息可以使用FirestoreFuturevoid saveUserData(User user, MapString, dynamic data) async { await FirebaseFirestore.instance .collection(users) .doc(user.uid) .set(data, SetOptions(merge: true)); } FutureMapString, dynamic? getUserData(String uid) async { DocumentSnapshot snapshot await FirebaseFirestore.instance .collection(users) .doc(uid) .get(); return snapshot.data() as MapString, dynamic?; }5. 用户界面设计与实现5.1 认证流程UI5.1.1 登录页面class LoginPage extends StatefulWidget { override _LoginPageState createState() _LoginPageState(); } class _LoginPageState extends StateLoginPage { final _formKey GlobalKeyFormState(); final _emailController TextEditingController(); final _passwordController TextEditingController(); override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text(登录)), body: Padding( padding: EdgeInsets.all(16.0), child: Form( key: _formKey, child: Column( children: [ TextFormField( controller: _emailController, decoration: InputDecoration(labelText: 邮箱), validator: (value) { if (value null || value.isEmpty) { return 请输入邮箱; } return null; }, ), TextFormField( controller: _passwordController, decoration: InputDecoration(labelText: 密码), obscureText: true, validator: (value) { if (value null || value.isEmpty) { return 请输入密码; } return null; }, ), SizedBox(height: 20), ElevatedButton( onPressed: () async { if (_formKey.currentState!.validate()) { User? user await signInWithEmailAndPassword( _emailController.text, _passwordController.text, ); if (user ! null) { Navigator.pushReplacementNamed(context, /home); } } }, child: Text(登录), ), TextButton( onPressed: () { Navigator.pushNamed(context, /register); }, child: Text(没有账号立即注册), ), ], ), ), ), ); } }5.1.2 注册页面注册页面与登录页面类似主要区别在于需要添加密码确认字段和调用注册方法。5.2 用户主页设计class HomePage extends StatelessWidget { override Widget build(BuildContext context) { final user Provider.ofUser?(context); return Scaffold( appBar: AppBar( title: Text(用户主页), actions: [ IconButton( icon: Icon(Icons.logout), onPressed: () async { await FirebaseAuth.instance.signOut(); Navigator.pushReplacementNamed(context, /login); }, ), ], ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ if (user?.photoURL ! null) CircleAvatar( backgroundImage: NetworkImage(user!.photoURL!), radius: 50, ), SizedBox(height: 20), Text( user?.displayName ?? 未设置用户名, style: Theme.of(context).textTheme.headline5, ), SizedBox(height: 10), Text(user?.email ?? ), SizedBox(height: 20), ElevatedButton( onPressed: () { Navigator.pushNamed(context, /profile); }, child: Text(编辑资料), ), ], ), ), ); } }5.3 资料编辑页面class ProfileEditPage extends StatefulWidget { override _ProfileEditPageState createState() _ProfileEditPageState(); } class _ProfileEditPageState extends StateProfileEditPage { final _formKey GlobalKeyFormState(); final _nameController TextEditingController(); File? _imageFile; override void initState() { super.initState(); final user FirebaseAuth.instance.currentUser; _nameController.text user?.displayName ?? ; } Futurevoid _pickImage() async { final picker ImagePicker(); final pickedFile await picker.pickImage(source: ImageSource.gallery); if (pickedFile ! null) { setState(() { _imageFile File(pickedFile.path); }); } } Futurevoid _saveProfile() async { if (_formKey.currentState!.validate()) { String? photoUrl; if (_imageFile ! null) { // 实际上传图片到Firebase Storage并获取URL // 这里简化处理 photoUrl https://example.com/new_avatar.jpg; } await updateProfile(_nameController.text, photoUrl); Navigator.pop(context); } } override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text(编辑资料)), body: Padding( padding: EdgeInsets.all(16.0), child: Form( key: _formKey, child: Column( children: [ GestureDetector( onTap: _pickImage, child: CircleAvatar( radius: 50, backgroundImage: _imageFile ! null ? FileImage(_imageFile!) : (FirebaseAuth.instance.currentUser?.photoURL ! null ? NetworkImage( FirebaseAuth.instance.currentUser!.photoURL!) : null), child: _imageFile null FirebaseAuth.instance.currentUser?.photoURL null ? Icon(Icons.camera_alt, size: 40) : null, ), ), SizedBox(height: 20), TextFormField( controller: _nameController, decoration: InputDecoration(labelText: 用户名), validator: (value) { if (value null || value.isEmpty) { return 请输入用户名; } return null; }, ), SizedBox(height: 20), ElevatedButton( onPressed: _saveProfile, child: Text(保存), ), ], ), ), ), ); } }6. 状态管理与路由配置6.1 使用Provider管理用户状态class AuthService { StreamUser? get user { return FirebaseAuth.instance.authStateChanges(); } } void main() { WidgetsFlutterBinding.ensureInitialized(); Firebase.initializeApp(); runApp( MultiProvider( providers: [ ProviderAuthService(create: (_) AuthService()), ], child: MyApp(), ), ); } class MyApp extends StatelessWidget { override Widget build(BuildContext context) { return MaterialApp( title: User APP, theme: ThemeData( primarySwatch: Colors.blue, ), home: StreamBuilderUser?( stream: Provider.ofAuthService(context).user, builder: (context, snapshot) { if (snapshot.connectionState ConnectionState.active) { final user snapshot.data; return user null ? LoginPage() : HomePage(); } return Scaffold( body: Center(child: CircularProgressIndicator()), ); }, ), routes: { /login: (context) LoginPage(), /register: (context) RegisterPage(), /home: (context) HomePage(), /profile: (context) ProfileEditPage(), }, ); } }6.2 路由保护与认证检查对于需要认证的页面可以创建一个高阶组件来保护路由class AuthWrapper extends StatelessWidget { final Widget child; AuthWrapper({required this.child}); override Widget build(BuildContext context) { final user Provider.ofUser?(context); if (user null) { return LoginPage(); } return child; } } // 使用方式 Navigator.push( context, MaterialPageRoute( builder: (context) AuthWrapper(child: ProfileEditPage()), ), );7. 测试与调试7.1 单元测试示例void main() { group(AuthService, () { late AuthService authService; setUp(() { authService AuthService(); }); test(user stream emits null when not authenticated, () { expect( authService.user, emits(null), ); }); }); }7.2 Widget测试示例void main() { testWidgets(LoginPage has email and password fields, (tester) async { await tester.pumpWidget(MaterialApp(home: LoginPage())); expect(find.byType(TextFormField), findsNWidgets(2)); expect(find.text(邮箱), findsOneWidget); expect(find.text(密码), findsOneWidget); expect(find.text(登录), findsOneWidget); }); }7.3 集成测试void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); testWidgets(full auth flow, (tester) async { // 启动应用 await tester.pumpWidget(MyApp()); // 点击注册按钮 await tester.tap(find.text(没有账号立即注册)); await tester.pumpAndSettle(); // 填写注册表单 await tester.enterText(find.byType(TextFormField).at(0), testexample.com); await tester.enterText(find.byType(TextFormField).at(1), password123); await tester.enterText(find.byType(TextFormField).at(2), password123); // 提交表单 await tester.tap(find.text(注册)); await tester.pumpAndSettle(Duration(seconds: 3)); // 验证是否跳转到主页 expect(find.text(用户主页), findsOneWidget); }); }8. 性能优化与安全考虑8.1 性能优化技巧图片优化使用缓存策略如cached_network_image包压缩上传的图片使用适当的分辨率数据加载分页加载用户列表使用Firestore的离线持久化避免不必要的数据监听状态管理只在需要的地方监听用户状态变化使用selectors减少不必要的重建8.2 安全最佳实践认证安全在Firebase控制台启用电子邮件验证设置密码强度要求启用账户锁定机制防止暴力破解数据安全设置适当的Firestore安全规则service cloud.firestore { match /databases/{database}/documents { match /users/{userId} { allow read, write: if request.auth ! null request.auth.uid userId; } } }敏感操作如密码更改需要重新认证传输安全确保所有API调用都使用HTTPS避免在URL或本地存储中保存敏感信息9. 发布准备与持续集成9.1 构建发布版本Android构建flutter build apk --release # 或 flutter build appbundleiOS构建flutter build ios --release # 然后在Xcode中归档和导出9.2 持续集成设置可以使用GitHub Actions自动化构建和测试流程。示例.github/workflows/flutter.ymlname: Flutter CI on: [push, pull_request] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkoutv2 - uses: subosito/flutter-actionv1 with: flutter-version: stable - run: flutter pub get - run: flutter analyze - run: flutter test - run: flutter build apk --release9.3 应用商店发布检查清单Android (Google Play)准备512x512和1024x1024的应用图标制作应用截图至少2张编写详细的应用描述设置适当的内容分级准备隐私政策链接iOS (App Store)准备1024x1024的应用图标制作5.5英寸、6.5英寸和iPad的截图填写应用元数据设置年龄分级准备应用预览视频可选10. 项目扩展与进阶功能10.1 添加第三方登录Firebase Authentication支持多种第三方登录方式。以Google登录为例添加依赖google_sign_in: ^5.4.0实现登录逻辑FutureUser? signInWithGoogle() async { try { final GoogleSignInAccount? googleUser await GoogleSignIn().signIn(); if (googleUser null) return null; final GoogleSignInAuthentication googleAuth await googleUser.authentication; final credential GoogleAuthProvider.credential( accessToken: googleAuth.accessToken, idToken: googleAuth.idToken, ); UserCredential userCredential await FirebaseAuth.instance.signInWithCredential(credential); return userCredential.user; } catch (e) { print(Google登录失败: $e); return null; } }10.2 实现用户搜索功能class UserSearchPage extends StatefulWidget { override _UserSearchPageState createState() _UserSearchPageState(); } class _UserSearchPageState extends StateUserSearchPage { final _searchController TextEditingController(); ListQueryDocumentSnapshot _searchResults []; Futurevoid _searchUsers(String query) async { if (query.isEmpty) { setState(() { _searchResults []; }); return; } final result await FirebaseFirestore.instance .collection(users) .where(displayName, isGreaterThanOrEqualTo: query) .where(displayName, isLessThan: query z) .limit(10) .get(); setState(() { _searchResults result.docs; }); } override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: TextField( controller: _searchController, decoration: InputDecoration( hintText: 搜索用户..., border: InputBorder.none, ), onChanged: _searchUsers, ), ), body: ListView.builder( itemCount: _searchResults.length, itemBuilder: (context, index) { final user _searchResults[index].data() as MapString, dynamic; return ListTile( leading: CircleAvatar( backgroundImage: user[photoURL] ! null ? NetworkImage(user[photoURL]) : null, ), title: Text(user[displayName] ?? ), subtitle: Text(user[email] ?? ), onTap: () { // 跳转到用户详情页 }, ); }, ), ); } }10.3 添加实时聊天功能使用Firebase的Realtime Database或Firestore可以实现实时聊天功能数据结构设计{ conversations: { conversationId1: { participants: [userId1, userId2], lastMessage: Hello!, lastMessageTime: 1634567890 } }, messages: { conversationId1: { messageId1: { senderId: userId1, text: Hello!, timestamp: 1634567890 } } } }实现消息发送Futurevoid sendMessage( String conversationId, String text) async { final message { senderId: FirebaseAuth.instance.currentUser!.uid, text: text, timestamp: FieldValue.serverTimestamp(), }; await FirebaseFirestore.instance .collection(messages) .doc(conversationId) .collection(messages) .add(message); // 更新会话最后消息 await FirebaseFirestore.instance .collection(conversations) .doc(conversationId) .update({ lastMessage: text, lastMessageTime: FieldValue.serverTimestamp(), }); }实时监听消息StreamQuerySnapshot getMessages(String conversationId) { return FirebaseFirestore.instance .collection(messages) .doc(conversationId) .collection(messages) .orderBy(timestamp, descending: true) .limit(20) .snapshots(); }11. 错误处理与用户体验优化11.1 常见错误处理Futurevoid handleAuthError(FirebaseAuthException e, BuildContext context) { String message; switch (e.code) { case invalid-email: message 邮箱格式不正确; break; case user-disabled: message 该账号已被禁用; break; case user-not-found: message 没有找到该用户; break; case wrong-password: message 密码错误; break; case email-already-in-use: message 该邮箱已被注册; break; case operation-not-allowed: message 该操作不被允许; break; case weak-password: message 密码强度不足; break; default: message 发生未知错误: ${e.message}; } ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(message)), ); }11.2 加载状态与空状态处理class UserListPage extends StatelessWidget { final FutureListUser usersFuture; UserListPage({required this.usersFuture}); override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text(用户列表)), body: FutureBuilderListUser( future: usersFuture, builder: (context, snapshot) { if (snapshot.connectionState ConnectionState.waiting) { return Center(child: CircularProgressIndicator()); } if (snapshot.hasError) { return Center(child: Text(加载失败)); } final users snapshot.data!; if (users.isEmpty) { return Center(child: Text(没有用户)); } return ListView.builder( itemCount: users.length, itemBuilder: (context, index) { final user users[index]; return ListTile( leading: CircleAvatar( backgroundImage: user.photoURL ! null ? NetworkImage(user.photoURL!) : null, ), title: Text(user.displayName ?? 未命名用户), subtitle: Text(user.email ?? ), ); }, ); }, ), ); } }11.3 表单验证增强String? validateEmail(String? value) { if (value null || value.isEmpty) { return 请输入邮箱; } final emailRegex RegExp( r^[a-zA-Z0-9.!#$%*/?^_{|}~-][a-zA-Z0-9-](?:\.[a-zA-Z0-9-])*$ ); if (!emailRegex.hasMatch(value)) { return 请输入有效的邮箱地址; } return null; } String? validatePassword(String? value) { if (value null || value.isEmpty) { return 请输入密码; } if (value.length 8) { return 密码至少需要8个字符; } return null; }12. 国际化与本地化支持12.1 添加多语言支持添加依赖dependencies: flutter_localizations: sdk: flutter intl: ^0.17.0创建arb文件lib/l10n/app_en.arb{ locale: en, loginTitle: Login, emailLabel: Email, passwordLabel: Password, loginButton: Sign In }lib/l10n/app_zh.arb{ locale: zh, loginTitle: 登录, emailLabel: 邮箱, passwordLabel: 密码, loginButton: 登录 }生成本地化类flutter pub run intl_translation:generate_from_arb \ --output-dirlib/l10n \ --no-use-deferred-loading \ lib/localizations.dart lib/l10n/app_*.arb使用本地化文本Text(AppLocalizations.of(context)!.loginTitle),12.2 动态切换语言class LocaleProvider with ChangeNotifier { Locale? _locale; Locale? get locale _locale; void setLocale(Locale locale) { if (!AppLocalizations.supportedLocales.contains(locale)) return; _locale locale; notifyListeners(); } } // 在MaterialApp中配置 MaterialApp( locale: Provider.ofLocaleProvider(context).locale, supportedLocales: AppLocalizations.supportedLocales, localizationsDelegates: AppLocalizations.localizationsDelegates, );13. 主题与自定义样式13.1 定义应用主题final lightTheme ThemeData( primarySwatch: Colors.blue, brightness: Brightness.light, appBarTheme: AppBarTheme( elevation: 0, centerTitle: true, ), inputDecorationTheme: InputDecorationTheme( border: OutlineInputBorder(), contentPadding: EdgeInsets.symmetric( horizontal: 16, vertical: 12, ), ), ); final darkTheme ThemeData( primarySwatch: Colors.blue, brightness: Brightness.dark, appBarTheme: AppBarTheme( elevation: 0, centerTitle: true, ), inputDecorationTheme: InputDecorationTheme( border: OutlineInputBorder(), contentPadding: EdgeInsets.symmetric( horizontal: 16, vertical: 12, ), ), );13.2 实现主题切换class ThemeProvider with ChangeNotifier { ThemeMode _themeMode ThemeMode.system; ThemeMode get themeMode _themeMode; void setThemeMode(ThemeMode mode) { _themeMode mode; notifyListeners(); } } // 在MaterialApp中配置 MaterialApp( theme: lightTheme, darkTheme: darkTheme, themeMode: Provider.ofThemeProvider(context).themeMode, );14. 分析与监控14.1 添加Firebase Analytics添加依赖firebase_analytics: ^9.1.0初始化final analytics FirebaseAnalytics.instance; // 记录屏幕访问 analytics.logAppOpen(); analytics.logScreenView(screenName: HomePage); // 记录事件 analytics.logEvent( name: user_login, parameters: {method: email}, );14.2 错误监控使用Firebase Crashlytics监控应用崩溃添加依赖firebase_crashlytics: ^2.5.0初始化FlutterError.onError FirebaseCrashlytics.instance.recordFlutterError; // 手动记录错误 FirebaseCrashlytics.instance.recordError( error, stackTrace, reason: a non-fatal error, );15. 项目结构与代码组织15.1 推荐的项目结构lib/ ├── main.dart ├── models/ │ ├── user.dart ├── services/ │ ├── auth_service.dart │ ├── user_service.dart │ ├── chat_service.dart ├── pages/ │ ├── auth/ │ │ ├── login_page.dart │ │ ├── register_page.dart │ ├── home/ │ │ ├── home_page.dart │ │ ├── profile_page.dart │ ├── chat/ │ │ ├── chat_list_page.dart │ │ ├── chat_page.dart ├── widgets/ │ ├── avatar.dart │ ├── loading_indicator.dart ├── utils/ │ ├── validators.dart │ ├── theme.dart ├── l10n/ │ ├── app_en.arb │ ├── app_zh.arb │ ├── localizations.dart15.2 依赖注入设置使用get_it进行依赖注入final getIt GetIt.instance; void setupLocator() { getIt.registerSingletonAuthService(AuthService()); getIt.registerSingletonUserService(UserService()); getIt.registerSingletonChatService(ChatService()); } // 在main.dart中调用 setupLocator(); // 使用 final authService getItAuthService();16. 用户反馈与支持16.1 添加反馈功能class FeedbackPage extends StatefulWidget { override _FeedbackPageState createState() _FeedbackPageState(); } class _FeedbackPageState extends StateFeedbackPage { final _formKey GlobalKeyFormState(); final _feedbackController TextEditingController(); int _rating 3; Futurevoid _submitFeedback() async { if (_formKey.currentState!.validate()) { final feedback { userId: FirebaseAuth.instance.currentUser!.uid, text: _feedbackController.text, rating: _rating, timestamp: FieldValue.serverTimestamp(), }; await FirebaseFirestore.instance .collection(feedback) .add(feedback); Navigator.pop(context); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(感谢您的反馈)), ); } } override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text(意见反馈)), body: Padding( padding: EdgeInsets.all(16.0), child: Form( key: _formKey, child: Column( children: [ Text(您对我们的应用满意吗), SizedBox(height: 10), Row( mainAxisAlignment: MainAxisAlignment.center, children: List.generate(5, (index) { return IconButton( icon: Icon( index _rating ? Icons.star : Icons.star_border, color: Colors.amber, size: 30, ), onPressed: () { setState(() { _rating index 1; }); }, ); }), ), SizedBox(height: 20), TextFormField( controller: _feedbackController, decoration: InputDecoration( labelText: 您的建议, border: OutlineInputBorder(), ), maxLines: 5, validator: (value) { if (value null || value.isEmpty) { return 请输入您的反馈; } return null; }, ), SizedBox(height: 20), ElevatedButton( onPressed: _submitFeedback, child: Text(提交反馈), ), ], ), ), ), ); } }16.2 常见问题解答(FAQ)页面class FAQPage extends StatelessWidget { final ListFAQItem faqs [ FAQItem( question: 如何重置密码, answer: 在登录页面点击忘记密码链接输入您的邮箱地址我们将发送密码重置链接。, ), FAQItem( question: 如何更改个人资料, answer: 在主页点击您的头像或用户名进入个人资料页面进行编辑。, ), // 更多FAQ... ]; override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text(常见问题)), body: ListView.builder( itemCount: faqs.length, itemBuilder: (context, index) { final faq faqs[index]; return ExpansionTile(

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑