一、引言:项目背景与完善目标

  1. 项目定位:轻量型 GitCode 辅助工具(鸿蒙端),基于 Flutter 跨端开发
  2. 阅读收益:掌握 Flutter 开发鸿蒙应用的模块化思路 + 实战细节
  3. 项目目标:做个能查 GitCode 仓库的鸿蒙手机小工具(就是打开 App 能搜代码仓库、存常用仓库)

二、第一步:准备工具

1.前期准备:

  • 环境:JDK 17 + DevEco Studio 4.0 + Flutter 3.10

  • 工具:PowerShell终端、VS Code(写Flutter代码)、DevEco Studio(管理鸿蒙工程)

2.拿“钥匙

去 GitCode 官网注册账号,申请一个 “API 密钥”(后续搜仓库要用)

  1. GitCode 的定位

    • GitCode 是一个面向开发者的代码托管平台,类似 GitHub、Gitee,主要提供代码仓库管理、协作开发等服务。
  2. API 密钥的作用

    • 申请的 API 密钥主要用于后续通过程序(而非手动操作)访问 GitCode 的服务(如搜索仓库、操作代码仓库),是接口调用的身份验证凭证。
  3. 操作的大致流程

    • 首先复制以下网址到浏览器搜索,自动跳转如图:(地址:https://gitcode.com/byyixuan/gitcode_pocket_tool)
    • 我们在下面的地址中下载好压缩包,点击下载ZIP即可https://gitcode.com/byyixuan/gitcode_pocket_tool
    • 完成账号注册(通常需提供邮箱、设置密码等);
    • 配置访问令牌(访问地址:https://gitcode.com/setting/token-classic/create)同样复制该网址到浏览器打开,跳转页面如图:
    • 然后点击右上角的新建访问令牌按键

      配置好名称后(如小博令牌名称是:Huang_Qi26),其他参数和设置都可以选择默认的,然后划到下面点击新建访问令牌

      注意注意:一定要记住你的个人访问令牌且要保密(这样“钥匙”就成功拿到了)

三、项目结构规划(文件夹 + 简单功能)

首先,我们将代码拆分为模块化结构,便于维护和扩展:

1.文件夹(不用记,跟着建就行),直接在文件管理器找到你的前期鸿蒙项目的文件(如小博的:D:\HarmonyOS\oh_code\demo_gitcode\lib),在你的项目lib文件夹下分别建constants,  themes,  services,  models,  screens文件夹如下图

lib/
├── main.dart                 # 应用入口
├── constants/                # 常量定义
│   └── app_constants.dart    # API地址、主题色等
├── themes/                   # 主题配置
│   └── app_theme.dart        # 毛玻璃二次元主题
├── services/                 # 服务层
│   └── gitcode_service.dart  # GitCode API请求
├── models/                   # 数据模型(可选,当前使用Map)
├── screens/                  # 页面组件
│   ├── login_screen.dart     # 登录页
│   ├── main_screen.dart      # 主页面(底部导航)
│   ├── home_screen.dart      # 首页动态
│   ├── repo_screen.dart      # 仓库页
│   └── profile_screen.dart   # 个人中心
└── widgets/                  # 自定义组件
    ├── glass_card.dart       # 毛玻璃卡片组件
    └── activity_card.dart    # 动态卡片组件

此时刚创建的各文件夹都是空的

2.然后是创建各文件夹下的Dart源文件(这里就实例一个文件夹screens的创建,其他文件的创建

同理)

在screens文件夹上方的框内直接输入cmd回车打开此目录的终端

然后用命令“type nul >”在此目录下创建Dart源文件,如下图创建了5个需分别输入5次命令

此时再打开screens文件下就有五个Dart源文件了,就已经为后续做好准备了(其他同理)

四.逐步实现各模块

1. 常量定义(constants/app_constants.dart

作用:集中管理 API 地址、主题色等常量,便于统一修改

  1. 把项目里反复用的固定信息(比如接口地址、APP 的主色调)都放这一个文件里,不用到处写重复的内容
  2. 以后要改这些信息(比如接口换地址、换个主题色),只改这一个文件就行,不用翻遍所有代码找
  3. 看代码的时候,一眼就能从这找到这些常用信息,不用猜 “这个数字 / 地址是啥意思”

VScode(即Visual Studio Code软件)中打开刚刚创建D:\HarmonyOS\oh_code\demo_gitcode\lib\constants\app_constants.dart文件去写代码(如图所示)

打开后直接在此页面写入如下代码:然后保存(save)

// lib/constants/app_constants.dart
class AppConstants {
  static const String apiBaseUrl = 'https://api.gitcode.com/api/v5';
  // 单个令牌存储键
  static const String tokenStorageKey = 'gitcode_token';
  // 🔴 新增:历史令牌存储键
  static const String tokenHistoryKey = 'gitcode_token_history';
  // 🔴 新增:历史记录最大数量
  static const int maxHistoryCount = 5;
}

2. 毛玻璃二次元主题(themes/app_theme.dart

作用:定义应用的整体风格,包括颜色、字体、组件样式,重点实现毛玻璃效果

  1. 把 APP 的 “颜值风格” 都统一装在这个文件里 —— 比如整体色调、用什么字体、按钮 / 卡片长啥样
  2. 专门在这里实现 “毛玻璃模糊感” 的效果,让 APP 界面看起来像二次元里那种通透的风格
  3. 以后想换风格(比如调个颜色、改个字体),直接改这个文件就行

VScode(即Visual Studio Code软件)中打开刚刚创建"D:\HarmonyOS\oh_code\demo_gitcode\lib\themes\app_theme.dart"文件,直接写入以下代码:记得保存(save)

// SPDX-License-Identifier: Apache-2.0
import 'package:flutter/material.dart';
 
/// 毛玻璃二次元主题配置
class AppTheme {
    /// 字体大小:超大
  static const double fontSizeXXLarge = 24;
  
  /// 字体大小:大
  static const double fontSizeXLarge = 18;
  
  /// 字体大小:中
  static const double fontSizeLarge = 16;
  
  /// 字体大小:中
  static const double fontSizeMedium = 14;
  
  /// 字体大小:小
  static const double fontSizeSmall = 12;
  /// 主色调(二次元粉紫色)
  static const Color primaryColor = Color(0xFF8B5CF6);
  
  /// 次要色调(天空蓝)
  static const Color secondaryColor = Color(0xFF38BDF8);
  
  /// 强调色(樱花粉)
  static const Color accentColor = Color(0xFFEC4899);
  
  /// 背景色(浅紫灰)
  static const Color backgroundColor = Color(0xFFF5F3FF);
  
  /// 卡片背景色(半透明白)
  static const Color cardBackgroundColor = Color.fromARGB(200, 255, 255, 255);
  
  /// 文字主色
  static const Color primaryTextColor = Color(0xFF1E1B4B);
  
  /// 文字次要色
  static const Color secondaryTextColor = Color(0xFF64748B);
  
  /// 边框颜色
  static const Color borderColor = Color.fromARGB(50, 139, 92, 246);
  
  /// 阴影效果
  static const BoxShadow cardShadow = BoxShadow(
    color: Color.fromRGBO(139, 92, 246, 0.1),  // 主题色阴影
    blurRadius: 12,                             // 模糊半径
    offset: Offset(0, 4),                       // 偏移量
  );
  
  /// 圆角大小
  static const BorderRadius cardBorderRadius = BorderRadius.all(Radius.circular(16));
  
  /// 构建Material主题
  static ThemeData get themeData => ThemeData(
    // 主色调
    primaryColor: primaryColor,
    // 画布背景色
    scaffoldBackgroundColor: backgroundColor,
    // 卡片主题
    cardTheme: CardTheme(
      color: cardBackgroundColor,
      elevation: 0,
      shape: RoundedRectangleBorder(borderRadius: cardBorderRadius),
      shadowColor: cardShadow.color,
    ),
    // AppBar主题
    appBarTheme: const AppBarTheme(
      backgroundColor: Colors.transparent,  // 透明背景(配合毛玻璃)
      foregroundColor: primaryTextColor,    // 文字颜色
      elevation: 0,                         // 无阴影
      centerTitle: true,                    // 标题居中
    ),
    // 底部导航栏主题
    bottomNavigationBarTheme: BottomNavigationBarThemeData(
      backgroundColor: cardBackgroundColor,  // 半透明背景
      selectedItemColor: primaryColor,       // 选中颜色
      unselectedItemColor: secondaryTextColor, // 未选中颜色
      elevation: 8,                          // 阴影
      type: BottomNavigationBarType.fixed,   // 固定类型
    ),
    // 文字主题
    textTheme: const TextTheme(
      bodyLarge: TextStyle(color: primaryTextColor),
      bodyMedium: TextStyle(color: secondaryTextColor),
      titleLarge: TextStyle(
        fontWeight: FontWeight.bold,
        color: primaryTextColor,
      ),
    ),
    // 按钮主题
    elevatedButtonTheme: ElevatedButtonThemeData(
      style: ElevatedButton.styleFrom(
        backgroundColor: primaryColor,       // 按钮背景色
        foregroundColor: Colors.white,       // 按钮文字色
        shape: RoundedRectangleBorder(borderRadius: cardBorderRadius), // 圆角
        padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12), // 内边距
      ),
    ),
    // 输入框主题
    inputDecorationTheme: InputDecorationTheme(
      filled: true,                          // 填充背景
      fillColor: cardBackgroundColor,        // 填充色
      border: OutlineInputBorder(
        borderRadius: cardBorderRadius,      // 圆角
        borderSide: BorderSide(color: borderColor), // 边框色
      ),
      enabledBorder: OutlineInputBorder(
        borderRadius: cardBorderRadius,
        borderSide: BorderSide(color: borderColor),
      ),
      focusedBorder: OutlineInputBorder(
        borderRadius: cardBorderRadius,
        borderSide: BorderSide(color: primaryColor, width: 2), // 聚焦边框色
      ),
    ),
  );
}
3. 毛玻璃卡片组件(widgets/glass_card.dart

作用:封装毛玻璃效果,供各页面复用。

  1. 把 “毛玻璃卡片” 做成一个可以重复用的 “模板”,不用每个页面都重新写一遍毛玻璃效果的代码
  2. 不管哪个页面需要这种通透模糊的卡片,直接 “拿” 这个组件来用就行,省得重复干活
  3. 要是想改毛玻璃的样式(比如模糊程度),只改这一个文件,所有页面用的这个卡片都会一起变

同上在VScode(即Visual Studio Code软件)中打开刚刚创建"D:\HarmonyOS\oh_code\demo_gitcode\lib\widgets\glass_card.dart""文件,直接写入以下代码:记得保存(save)

// SPDX-License-Identifier: Apache-2.0
import 'package:flutter/material.dart';
import 'dart:ui'; // 用于ImageFilter(毛玻璃)
import '../themes/app_theme.dart';
 
/// 毛玻璃卡片组件
class GlassCard extends StatelessWidget {
  /// 卡片子组件
  final Widget child;
  
  /// 内边距
  final EdgeInsets padding;
  
  /// 外边距
  final EdgeInsets margin;
  
  const GlassCard({
    Key? key,
    required this.child,
    this.padding = const EdgeInsets.all(16),
    this.margin = const EdgeInsets.only(bottom: 16),
  }) : super(key: key);
 
  @override
  Widget build(BuildContext context) {
    return Container(
      margin: margin,
      // 毛玻璃效果核心:BackdropFilter + ImageFilter.blur
      child: BackdropFilter(
        filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10), // 模糊程度
        child: Container(
          padding: padding,
          decoration: BoxDecoration(
            color: AppTheme.cardBackgroundColor, // 半透明背景
            borderRadius: AppTheme.cardBorderRadius, // 圆角
            boxShadow: [AppTheme.cardShadow], // 阴影
            border: Border.all(
              color: Colors.white.withOpacity(0.2), // 白色描边(增强玻璃感)
              width: 1,
            ),
          ),
          child: child,
        ),
      ),
    );
  }
}
4. 服务层(services/gitcode_service.dart

作用:处理 API 请求,封装网络逻辑。

  1. 专门管 “APP 和服务器通信” 的事儿 —— 比如给服务器发请求、收数据这些操作都放在这
  2. 把发请求的代码打包成 “现成的工具”,其他页面要用的时候直接调用,不用重复写发请求的步骤
  3. 统一处理网络问题(比如没网、请求失败),不用每个页面都单独写错误处理

同上在VScode(即Visual Studio Code软件)中打开刚刚创建"services\gitcode_service.dart"文件,直接写入以下代码:记得保存(save)

// SPDX-License-Identifier: Apache-2.0
import 'package:dio/dio.dart';
import '../constants/app_constants.dart';
 
/// GitCode API服务类
class GitCodeService {
  // 单例模式:确保全局只有一个实例
  static final GitCodeService _instance = GitCodeService._internal();
  factory GitCodeService() => _instance;
 
  late Dio dio; // Dio实例,用于网络请求
 
  // 内部构造函数
  GitCodeService._internal() {
    // 初始化Dio
    dio = Dio(
      BaseOptions(
        baseUrl: AppConstants.apiBaseUrl, // API基础地址
        connectTimeout: const Duration(seconds: 15), // 连接超时
        receiveTimeout: const Duration(seconds: 15), // 接收超时
        responseType: ResponseType.json, // 响应类型
        contentType: 'application/json; charset=utf-8', // 请求类型
        headers: {
          'Accept': 'application/json', // 接受JSON格式
        },
      ),
    );
 
    // 添加拦截器(日志、错误处理)
    dio.interceptors.add(
      InterceptorsWrapper(
        // 请求拦截
        onRequest: (options, handler) {
          // 如果有令牌,添加到请求头
          String? token = _accessToken;
          if (token != null && token.isNotEmpty) {
            options.headers['Authorization'] = 'Bearer $token';
          }
          print('🚀 请求: ${options.method} ${options.uri}');
          handler.next(options); // 继续请求
        },
        // 响应拦截
        onResponse: (response, handler) {
          print('✅ 响应: ${response.statusCode} ${response.requestOptions.uri}');
          handler.next(response); // 继续处理响应
        },
        // 错误拦截
        onError: (DioException e, handler) {
          print('❌ 错误: ${e.message}');
          print('❌ 响应状态: ${e.response?.statusCode}');
          print('❌ 响应体: ${e.response?.data}');
          
          // 统一错误信息
          String errorMsg = '网络请求失败';
          if (e.response?.statusCode == 404) {
            errorMsg = 'API端点不存在';
          } else if (e.response?.statusCode == 401) {
            errorMsg = '无效的访问令牌';
          } else if (e.type == DioExceptionType.connectionTimeout) {
            errorMsg = '网络超时,请检查网络连接';
          }
          
          // 包装错误
          e = DioException(
            requestOptions: e.requestOptions,
            type: e.type,
            error: errorMsg,
            response: e.response,
          );
          
          handler.next(e); // 继续处理错误
        },
      ),
    );
  }
 
  // 令牌存储(全局变量,实际项目建议使用本地存储)
  static String? _accessToken;
 
  /// 设置访问令牌
  void setToken(String token) {
    _accessToken = token;
  }
 
  /// 清除访问令牌
  void clearToken() {
    _accessToken = null;
  }
 
  /// 获取当前用户信息
  Future<Map<String, dynamic>> fetchUserInfo() async {
    try {
      final response = await dio.get('/user'); // 请求用户信息
      return response.data as Map<String, dynamic>; // 返回数据
    } on DioException catch (e) {
      throw Exception(e.error ?? '获取用户信息失败'); // 抛出异常
    }
  }
 
  /// 获取用户仓库列表
  Future<List<dynamic>> fetchMyRepositories() async {
    try {
      final response = await dio.get(
        '/user/repos',
        queryParameters: {
          'visibility': 'all', // 所有可见性
          'affiliation': 'owner', // 仅自己的仓库
          'page': 1, // 页码
          'per_page': 20, // 每页数量
        },
      );
      return response.data as List<dynamic>; // 返回仓库列表
    } on DioException catch (e) {
      throw Exception(e.error ?? '获取我的仓库失败'); // 抛出异常
    }
  }
 
  /// 获取动态列表
  Future<List<dynamic>> fetchActivities() async {
    try {
      final response = await dio.get(
        '/events',
        queryParameters: {'page': 1, 'per_page': 20}, // 分页参数
      );
      return response.data as List<dynamic>; // 返回动态列表
    } catch (e) {
      print('⚠️ 动态API调用失败,返回模拟数据: $e');
      // 返回模拟数据(防止API不可用导致崩溃)
      return [
        {
          "id": 1,
          "type": "PushEvent",
          "actor": {"login": "user1", "avatar_url": "https://placehold.co/40x40/8B5CF6/ffffff?text=U1"},
          "repo": {"name": "user1/repo1"},
          "created_at": DateTime.now().subtract(const Duration(hours: 1)).toIso8601String()
        },
        {
          "id": 2,
          "type": "WatchEvent",
          "actor": {"login": "user2", "avatar_url": "https://placehold.co/40x40/38BDF8/ffffff?text=U2"},
          "repo": {"name": "user2/repo2"},
          "created_at": DateTime.now().subtract(const Duration(hours: 2)).toIso8601String()
        },
      ];
    }
  }
 
  /// 获取热门仓库
  Future<List<dynamic>> fetchHotRepositories() async {
    try {
      final response = await dio.get(
        '/search/repositories',
        queryParameters: {
          'q': 'stars:>100', // 搜索条件:星数>100
          'sort': 'stars', // 按星数排序
          'order': 'desc', // 降序
          'page': 1, // 页码
          'per_page': 10, // 每页数量
        },
      );
      final data = response.data as Map<String, dynamic>;
      return data['items'] as List<dynamic>; // 返回热门仓库列表
    } catch (e) {
      print('⚠️ 热门仓库API调用失败,返回模拟数据: $e');
      // 返回模拟数据
      return [
        {
          "id": 101,
          "name": "flutter-kit",
          "full_name": "google/flutter-kit",
          "description": "Flutter 开发工具包",
          "language": "Dart",
          "stargazers_count": 5000,
          "forks_count": 1200,
          "html_url": "https://gitcode.com/google/flutter-kit"
        },
      ];
    }
  }
}
5. 登录页面(screens/login_screen.dart

作用:处理用户登录逻辑,获取访问令牌。

同上在VScode(即Visual Studio Code软件)中打开刚刚创建"\screens\login_screen.dart"文件,直接写入以下代码:然后保存(save)

// SPDX-License-Identifier: Apache-2.0
import 'package:flutter/material.dart';
import '../services/gitcode_service.dart';
import '../themes/app_theme.dart';
import '../widgets/glass_card.dart';
import './main_screen.dart';
 
/// 登录页面
class LoginScreen extends StatefulWidget {
  const LoginScreen({Key? key}) : super(key: key);
 
  @override
  State<LoginScreen> createState() => _LoginScreenState();
}
 
class _LoginScreenState extends State<LoginScreen> {
  /// 令牌输入控制器
  final TextEditingController _tokenController = TextEditingController();
  
  /// 加载状态
  bool _isLoading = false;
  
  /// 错误信息
  String? _errorMessage;
 
  /// 登录处理函数
  void _handleLogin() async {
    setState(() {
      _isLoading = true; // 显示加载中
      _errorMessage = null; // 清空错误信息
    });
 
    final token = _tokenController.text.trim(); // 获取输入的令牌
    if (token.isEmpty) {
      setState(() {
        _isLoading = false;
        _errorMessage = '请输入访问令牌'; // 空令牌提示
      });
      return;
    }
 
    try {
      // 初始化服务并设置令牌
      final service = GitCodeService();
      service.setToken(token);
      
      // 验证令牌是否有效(调用用户信息API)
      await service.fetchUserInfo();
      
      // 登录成功,跳转到主页面
      if (mounted) {
        Navigator.of(context).pushReplacement(
          MaterialPageRoute(builder: (_) => const MainScreen()),
        );
      }
    } catch (e) {
      setState(() {
        _isLoading = false;
        _errorMessage = e.toString(); // 显示错误信息
      });
    }
  }
 
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // 背景渐变(增强二次元风格)
      body: Container(
        decoration: const BoxDecoration(
          gradient: LinearGradient(
            begin: Alignment.topLeft,
            end: Alignment.bottomRight,
            colors: [
              Color(0xFFF5F3FF), // 浅紫灰
              Color(0xFFE0E7FF), // 浅蓝紫
            ],
          ),
        ),
        child: SafeArea(
          child: Center(
            child: SingleChildScrollView(
              padding: const EdgeInsets.all(32.0),
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                crossAxisAlignment: CrossAxisAlignment.center,
                children: <Widget>[
                  // Logo区域
                  GlassCard(
                    margin: const EdgeInsets.only(bottom: 32),
                    padding: const EdgeInsets.all(24),
                    child: Column(
                      children: [
                        // Logo图标
                        Container(
                          width: 100,
                          height: 100,
                          decoration: BoxDecoration(
                            color: AppTheme.primaryColor,
                            borderRadius: BorderRadius.circular(20),
                            boxShadow: [AppTheme.cardShadow],
                          ),
                          child: const Icon(
                            Icons.code,
                            size: 60,
                            color: Colors.white,
                          ),
                        ),
                        const SizedBox(height: 16),
                        // 应用标题
                        const Text(
                          'GitCode 客户端',
                          style: TextStyle(
                            fontSize: 28,
                            fontWeight: FontWeight.bold,
                            color: AppTheme.primaryTextColor,
                          ),
                        ),
                        const SizedBox(height: 8),
                        // 副标题
                        const Text(
                          '使用访问令牌登录',
                          style: TextStyle(
                            fontSize: 16,
                            color: AppTheme.secondaryTextColor,
                          ),
                        ),
                      ],
                    ),
                  ),
                  
                  // 登录表单
                  GlassCard(
                    child: Column(
                      children: [
                        // 令牌输入框
                        TextField(
                          controller: _tokenController,
                          obscureText: true, // 密码模式(隐藏令牌)
                          decoration: InputDecoration(
                            labelText: '访问令牌',
                            hintText: 'ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
                            hintStyle: const TextStyle(color: AppTheme.secondaryTextColor),
                            errorText: _errorMessage, // 错误提示
                            prefixIcon: const Icon(Icons.security, color: AppTheme.primaryColor),
                            suffixIcon: IconButton(
                              icon: const Icon(Icons.clear, color: AppTheme.secondaryTextColor),
                              onPressed: () => _tokenController.clear(), // 清空输入
                            ),
                          ),
                          maxLines: 1,
                          textInputAction: TextInputAction.done,
                          onSubmitted: (_) => _handleLogin(), // 回车登录
                        ),
                        
                        const SizedBox(height: 16),
                        // 帮助链接
                        Align(
                          alignment: Alignment.centerRight,
                          child: TextButton(
                            onPressed: () {
                              // 打开获取令牌的帮助页面(预留)
                              print('打开获取令牌帮助');
                            },
                            child: const Text(
                              '如何获取令牌?',
                              style: TextStyle(color: AppTheme.primaryColor),
                            ),
                          ),
                        ),
                        
                        const SizedBox(height: 24),
                        // 登录按钮
                        SizedBox(
                          width: double.infinity,
                          height: 50,
                          child: ElevatedButton.icon(
                            onPressed: _isLoading ? null : _handleLogin, // 加载中禁用
                            icon: _isLoading 
                                ? const SizedBox(
                                    width: 20,
                                    height: 20,
                                    child: CircularProgressIndicator(color: Colors.white, strokeWidth: 2),
                                  )
                                : const Icon(Icons.login),
                            label: const Text(
                              '登录',
                              style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
                            ),
                          ),
                        ),
                      ],
                    ),
                  ),
                ],
              ),
            ),
          ),
        ),
      ),
    );
  }
}
6. 主页面(screens/main_screen.dart

作用:底部导航栏,切换不同页面。

  1. 作为 APP 打开后的 “主界面”,展示底部导航栏(比如首页、我的等按钮)
  2. 通过底部导航栏,让用户能快速切换到不同功能页面(比如点 “我的” 就跳转到个人中心)
  3. 承载各功能页面的内容展示,是用户在 APP 里操作的核心入口
  4. 可以统一管理页面切换时的状态(比如记住用户上次停留的页面

同上在VScode(即Visual Studio Code软件)中打开刚刚创建"\screens\main_screen.dart"文件,直接写入以下代码:然后保存(save)

// SPDX-License-Identifier: Apache-2.0
import 'dart:ui'; // 用于 ImageFilter(毛玻璃效果)
import 'package:flutter/material.dart';
import './home_screen.dart';
import './repo_screen.dart';
import './profile_screen.dart';
import '../themes/app_theme.dart';
 
/// 主页面(底部导航)
class MainScreen extends StatefulWidget {
  const MainScreen({Key? key}) : super(key: key);
 
  @override
  State<MainScreen> createState() => _MainScreenState();
}
 
class _MainScreenState extends State<MainScreen> {
  /// 当前选中的页面索引
  int _selectedIndex = 0;
 
  /// 页面列表
  static const List<Widget> _widgetOptions = <Widget>[
    HomeScreen(),      // 首页动态
    RepoScreen(),      // 仓库页
    ProfileScreen(),   // 个人中心
  ];
 
  /// 导航栏点击事件
  void _onItemTapped(int index) {
    setState(() {
      _selectedIndex = index; // 更新选中索引
    });
  }
 
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // 背景渐变
      body: Container(
        decoration: const BoxDecoration(
          gradient: LinearGradient(
            begin: Alignment.topLeft,
            end: Alignment.bottomRight,
            colors: [
              Color(0xFFF5F3FF),
              Color(0xFFE0E7FF),
            ],
          ),
        ),
        child: _widgetOptions.elementAt(_selectedIndex), // 显示当前选中页面
      ),
      // 底部导航栏
      bottomNavigationBar: Container(
        // 毛玻璃效果
        child: BackdropFilter(
          filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
          child: BottomNavigationBar(
            items: const <BottomNavigationBarItem>[
              BottomNavigationBarItem(
                icon: Icon(Icons.home_outlined),
                activeIcon: Icon(Icons.home),
                label: '首页',
              ),
              BottomNavigationBarItem(
                icon: Icon(Icons.code_outlined),
                activeIcon: Icon(Icons.code),
                label: '仓库',
              ),
              BottomNavigationBarItem(
                icon: Icon(Icons.person_outlined),
                activeIcon: Icon(Icons.person),
                label: '我的',
              ),
            ],
            currentIndex: _selectedIndex,
            onTap: _onItemTapped,
          ),
        ),
      ),
    );
  }
}
7. 首页动态(screens/home_screen.dart

作用:显示 GitCode 动态列表。

  1. 作为主页面里的 “首页内容区”,专门展示 GitCode 平台的动态信息(比如别人的提交、项目更新这些内容)
  2. 从服务层获取 GitCode 的动态数据,把这些数据整理成列表样式展示给用户看
  3. 处理列表的交互(比如下拉刷新最新动态、上滑加载更多内容)
  4. 点击列表里的动态项,能跳转到对应的详情页面(比如项目详情、用户主页)

同上在VScode(即Visual Studio Code软件)中打开刚刚创建"D:\HarmonyOS\oh_code\demo_gitcode\lib\screens\home_screen.dart"文件,直接写入以下代码:记得保存(save)

// SPDX-License-Identifier: Apache-2.0
import 'package:flutter/material.dart';
import '../services/gitcode_service.dart';
import '../themes/app_theme.dart';
import '../widgets/glass_card.dart';
 
/// 首页动态页面
class HomeScreen extends StatefulWidget {
  const HomeScreen({Key? key}) : super(key: key);
 
  @override
  State<HomeScreen> createState() => _HomeScreenState();
}
 
class _HomeScreenState extends State<HomeScreen> {
  /// GitCode服务实例
  final GitCodeService _service = GitCodeService();
  
  /// 动态列表数据
  List<dynamic> _activities = [];
  
  /// 加载状态
  bool _isLoading = true;
  
  /// 错误信息
  String? _errorMessage;
 
  @override
  void initState() {
    super.initState();
    _fetchActivities(); // 初始化时加载动态
  }
 
  /// 获取动态列表
  Future<void> _fetchActivities() async {
    setState(() {
      _isLoading = true;
      _errorMessage = null;
    });
    try {
      final activities = await _service.fetchActivities();
      setState(() {
        _activities = activities;
      });
    } catch (e) {
      setState(() {
        _errorMessage = e.toString();
      });
    } finally {
      setState(() {
        _isLoading = false;
      });
    }
  }
 
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // 透明AppBar(配合背景渐变)
      appBar: AppBar(
        title: const Text('首页动态'),
        backgroundColor: Colors.transparent,
      ),
      body: RefreshIndicator(
        onRefresh: _fetchActivities, // 下拉刷新
        color: AppTheme.primaryColor,
        backgroundColor: AppTheme.cardBackgroundColor,
        child: _isLoading
            ? const Center(
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: [
                    CircularProgressIndicator(color: AppTheme.primaryColor),
                    SizedBox(height: 16),
                    Text('加载中...'),
                  ],
                ),
              )
            : _errorMessage != null
                ? Center(
                    child: GlassCard(
                      child: Column(
                        mainAxisAlignment: MainAxisAlignment.center,
                        children: [
                          const Icon(
                            Icons.error_outline,
                            size: 64,
                            color: Colors.red,
                          ),
                          const SizedBox(height: 16),
                          Text(
                            _errorMessage!,
                            textAlign: TextAlign.center,
                            style: const TextStyle(color: AppTheme.secondaryTextColor),
                          ),
                          const SizedBox(height: 16),
                          ElevatedButton(
                            onPressed: _fetchActivities,
                            child: const Text('重试'),
                          ),
                        ],
                      ),
                    ),
                  )
                : _activities.isEmpty
                    ? const Center(
                        child: Column(
                          mainAxisAlignment: MainAxisAlignment.center,
                          children: [
                            Icon(
                              Icons.inbox_outlined,
                              size: 64,
                              color: AppTheme.secondaryTextColor,
                            ),
                            SizedBox(height: 16),
                            Text('暂无动态'),
                          ],
                        ),
                      )
                    : ListView.builder(
                        padding: const EdgeInsets.all(16.0),
                        itemCount: _activities.length,
                        itemBuilder: (context, index) {
                          final activity = _activities[index];
                          return _buildActivityCard(activity); // 构建动态卡片
                        },
                      ),
      ),
    );
  }
 
  /// 构建动态卡片
  Widget _buildActivityCard(Map<String, dynamic> activity) {
    return GlassCard(
      child: Row(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          // 头像
          CircleAvatar(
            radius: 28,
            backgroundImage: NetworkImage(activity['actor']?['avatar_url'] ?? ''),
            backgroundColor: AppTheme.secondaryColor.withOpacity(0.1),
          ),
          const SizedBox(width: 16),
          
          // 内容区域
          Expanded(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                // 用户名和操作
                RichText(
                  text: TextSpan(
                    children: [
                      TextSpan(
                        text: '${activity['actor']?['login'] ?? '未知用户'} ',
                        style: const TextStyle(
                          fontWeight: FontWeight.bold,
                          color: AppTheme.primaryTextColor,
                          fontSize: AppTheme.fontSizeMedium,
                        ),
                      ),
                      TextSpan(
                        text: _getActivityMessage(activity), // 获取操作描述
                        style: TextStyle(
                          color: AppTheme.secondaryTextColor,
                          fontSize: AppTheme.fontSizeMedium,
                        ),
                      ),
                    ],
                  ),
                ),
                
                // 仓库名
                Padding(
                  padding: const EdgeInsets.symmetric(vertical: 4),
                  child: Text(
                    '仓库: ${activity['repo']?['name'] ?? '未知仓库'}',
                    style: TextStyle(
                      color: AppTheme.secondaryTextColor,
                      fontSize: AppTheme.fontSizeSmall,
                    ),
                  ),
                ),
                
                // 时间
                Text(
                  _formatTime(activity['created_at']), // 格式化时间
                  style: TextStyle(
                    color: AppTheme.secondaryTextColor,
                    fontSize: AppTheme.fontSizeSmall,
                  ),
                ),
              ],
            ),
          ),
          
          // 操作图标
          Icon(
            _getActivityIcon(activity), // 获取操作图标
            color: AppTheme.secondaryTextColor,
            size: 20,
          ),
        ],
      ),
    );
  }
 
  /// 获取操作描述
  String _getActivityMessage(Map<String, dynamic> activity) {
    final type = activity['type'] ?? 'unknown';
    switch (type) {
      case 'PushEvent':
        return '提交了代码';
      case 'WatchEvent':
        return '点赞了仓库';
      case 'FollowEvent':
        return '关注了用户';
      case 'IssuesEvent':
        return '创建了Issue';
      default:
        return '进行了操作';
    }
  }
 
  /// 获取操作图标
  IconData _getActivityIcon(Map<String, dynamic> activity) {
    final type = activity['type'] ?? 'unknown';
    switch (type) {
      case 'PushEvent':
        return Icons.code;
      case 'WatchEvent':
        return Icons.star_outline;
      case 'FollowEvent':
        return Icons.person_add_outlined;
      case 'IssuesEvent':
        return Icons.assignment_outlined;
      default:
        return Icons.notification_add_outlined;
    }
  }
 
  /// 格式化时间
  String _formatTime(String? timeStr) {
    if (timeStr == null) return '';
    final date = DateTime.parse(timeStr);
    final now = DateTime.now();
    final difference = now.difference(date);
    
    if (difference.inMinutes < 1) {
      return '刚刚';
    } else if (difference.inMinutes < 60) {
      return '${difference.inMinutes}分钟前';
    } else if (difference.inHours < 24) {
      return '${difference.inHours}小时前';
    } else if (difference.inDays < 30) {
      return '${difference.inDays}天前';
    } else {
      return '${date.month}/${date.day}';
    }
  }
}
8. 仓库页面(screens/repo_screen.dart

作用:显示热门仓库和我的仓库。

同上在VScode(即Visual Studio Code软件)中打开刚刚创建"screens\repo_screen.dart"文件,直接写入如以下代码:记得保存(save)

// SPDX-License-Identifier: Apache-2.0
import 'package:flutter/material.dart';
import '../services/gitcode_service.dart';
import '../themes/app_theme.dart';
import '../widgets/glass_card.dart';
 
/// 仓库页面
class RepoScreen extends StatefulWidget {
  const RepoScreen({Key? key}) : super(key: key);
 
  @override
  State<RepoScreen> createState() => _RepoScreenState();
}
 
class _RepoScreenState extends State<RepoScreen> {
  /// GitCode服务实例
  final GitCodeService _service = GitCodeService();
  
  /// 是否显示我的仓库(false: 热门仓库,true: 我的仓库)
  bool _showMyRepos = false;
  
  /// 仓库列表数据
  List<dynamic> _repos = [];
  
  /// 加载状态
  bool _isLoading = true;
  
  /// 错误信息
  String? _errorMessage;
 
  @override
  void initState() {
    super.initState();
    _fetchRepos(); // 初始化时加载仓库
  }
 
  /// 获取仓库列表
  Future<void> _fetchRepos() async {
    setState(() {
      _isLoading = true;
      _errorMessage = null;
    });
    try {
      final repos = _showMyRepos 
          ? await _service.fetchMyRepositories() // 我的仓库
          : await _service.fetchHotRepositories(); // 热门仓库
      setState(() {
        _repos = repos;
      });
    } catch (e) {
      setState(() {
        _errorMessage = e.toString();
      });
    } finally {
      setState(() {
        _isLoading = false;
      });
    }
  }
 
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('仓库'),
        backgroundColor: Colors.transparent,
      ),
      body: RefreshIndicator(
        onRefresh: _fetchRepos, // 下拉刷新
        color: AppTheme.primaryColor,
        backgroundColor: AppTheme.cardBackgroundColor,
        child: Column(
          children: [
            // 切换标签(热门/我的)
            GlassCard(
              margin: const EdgeInsets.all(16),
              padding: EdgeInsets.zero,
              child: ToggleButtons(
                isSelected: [!_showMyRepos, _showMyRepos],
                onPressed: (index) {
                  setState(() {
                    _showMyRepos = index == 1; // 切换标签
                    _fetchRepos(); // 重新加载数据
                  });
                },
                borderRadius: AppTheme.cardBorderRadius,
                selectedBorderColor: AppTheme.primaryColor,
                selectedColor: Colors.white,
                fillColor: AppTheme.primaryColor,
                color: AppTheme.secondaryTextColor,
                borderColor: AppTheme.borderColor,
                constraints: const BoxConstraints(
                  minHeight: 44.0,
                  minWidth: 140.0,
                ),
                children: const [
                  Padding(
                    padding: EdgeInsets.symmetric(horizontal: 20),
                    child: Text(
                      '热门仓库',
                      style: TextStyle(fontWeight: FontWeight.bold),
                    ),
                  ),
                  Padding(
                    padding: EdgeInsets.symmetric(horizontal: 20),
                    child: Text(
                      '我的仓库',
                      style: TextStyle(fontWeight: FontWeight.bold),
                    ),
                  ),
                ],
              ),
            ),
            
            // 仓库列表
            Expanded(
              child: _isLoading
                  ? const Center(
                      child: Column(
                        mainAxisAlignment: MainAxisAlignment.center,
                        children: [
                          CircularProgressIndicator(color: AppTheme.primaryColor),
                          SizedBox(height: 16),
                          Text('加载中...'),
                        ],
                      ),
                    )
                  : _errorMessage != null
                      ? Center(
                          child: GlassCard(
                            child: Column(
                              mainAxisAlignment: MainAxisAlignment.center,
                              children: [
                                const Icon(
                                  Icons.error_outline,
                                  size: 64,
                                  color: Colors.red,
                                ),
                                const SizedBox(height: 16),
                                Text(
                                  _errorMessage!,
                                  textAlign: TextAlign.center,
                                  style: const TextStyle(color: AppTheme.secondaryTextColor),
                                ),
                                const SizedBox(height: 16),
                                ElevatedButton(
                                  onPressed: _fetchRepos,
                                  child: const Text('重试'),
                                ),
                              ],
                            ),
                          ),
                        )
                      : _repos.isEmpty
                          ? const Center(
                              child: Column(
                                mainAxisAlignment: MainAxisAlignment.center,
                                children: [
                                  Icon(
                                    Icons.inbox_outlined,
                                    size: 64,
                                    color: AppTheme.secondaryTextColor,
                                  ),
                                  SizedBox(height: 16),
                                  Text('暂无仓库'),
                                ],
                              ),
                            )
                          : ListView.builder(
                              padding: const EdgeInsets.symmetric(horizontal: 16),
                              itemCount: _repos.length,
                              itemBuilder: (context, index) {
                                final repo = _repos[index];
                                return _buildRepoCard(repo); // 构建仓库卡片
                              },
                            ),
            ),
          ],
        ),
      ),
    );
  }
 
  /// 构建仓库卡片
  Widget _buildRepoCard(Map<String, dynamic> repo) {
    return GlassCard(
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          // 仓库名称
          Text(
            repo['full_name'] ?? 'N/A',
            style: const TextStyle(
              fontSize: AppTheme.fontSizeLarge,
              fontWeight: FontWeight.bold,
              color: AppTheme.primaryTextColor,
            ),
          ),
          const SizedBox(height: 8),
          
          // 仓库描述
          Text(
            repo['description'] ?? '无描述',
            style: TextStyle(
              fontSize: AppTheme.fontSizeMedium,
              color: AppTheme.secondaryTextColor,
            ),
            maxLines: 2,
            overflow: TextOverflow.ellipsis,
          ),
          
          const SizedBox(height: 16),
          
          // 仓库信息(语言、星数、fork数)
          Row(
            children: [
              // 语言
              if (repo['language'] != null)
                Row(
                  children: [
                    Container(
                      width: 12,
                      height: 12,
                      decoration: BoxDecoration(
                        color: _getLanguageColor(repo['language']), // 语言颜色
                        borderRadius: BorderRadius.circular(6),
                      ),
                    ),
                    const SizedBox(width: 6),
                    Text(
                      repo['language'] ?? '',
                      style: TextStyle(
                        fontSize: AppTheme.fontSizeSmall,
                        color: AppTheme.secondaryTextColor,
                      ),
                    ),
                  ],
                ),
              
              const SizedBox(width: 16),
              
              // 星数
              Row(
                children: [
                  const Icon(
                    Icons.star_outline,
                    size: 16,
                    color: AppTheme.secondaryTextColor,
                  ),
                  const SizedBox(width: 4),
                  Text(
                    (repo['stargazers_count'] ?? 0).toString(),
                    style: TextStyle(
                      fontSize: AppTheme.fontSizeSmall,
                      color: AppTheme.secondaryTextColor,
                    ),
                  ),
                ],
              ),
              
              const SizedBox(width: 16),
              
              // Fork数
              Row(
                children: [
                  const Icon(
                    Icons.fork_right_outlined,
                    size: 16,
                    color: AppTheme.secondaryTextColor,
                  ),
                  const SizedBox(width: 4),
                  Text(
                    (repo['forks_count'] ?? 0).toString(),
                    style: TextStyle(
                      fontSize: AppTheme.fontSizeSmall,
                      color: AppTheme.secondaryTextColor,
                    ),
                  ),
                ],
              ),
            ],
          ),
        ],
      ),
    );
  }
 
  /// 获取语言对应的颜色
  Color _getLanguageColor(String? language) {
    switch (language?.toLowerCase()) {
      case 'dart':
        return const Color(0xFF0175C2);
      case 'python':
        return const Color(0xFF3776AB);
      case 'java':
        return const Color(0xFFB07219);
      case 'go':
        return const Color(0xFF00ADD8);
      case 'javascript':
        return const Color(0xFFF7DF1E);
      case 'typescript':
        return const Color(0xFF3178C6);
      case 'c++':
        return const Color(0xFFF34B7D);
      case 'c#':
        return const Color(0xFF178600);
      default:
        return AppTheme.secondaryTextColor;
    }
  }
}
9. 个人中心(screens/profile_screen.dart

作用:显示用户个人信息。

  1. 展示当前登录用户的个人信息(比如头像、昵称、账号等)
  2. 提供个人相关的功能入口(比如修改个人资料、退出登录等)
  3. 从服务层获取用户的个人数据并展示,支持信息的编辑和更新

同上在VScode(即Visual Studio Code软件)中打开刚刚创建"D:\HarmonyOS\oh_code\demo_gitcode\lib\screens\profile_screen.dart"文件,直接写入如以下代码:然后记得保存(save)

// SPDX-License-Identifier: Apache-2.0
import './login_screen.dart';
import 'package:flutter/material.dart';
import '../services/gitcode_service.dart';
import '../themes/app_theme.dart';
import '../widgets/glass_card.dart';
 
/// 个人中心页面
class ProfileScreen extends StatefulWidget {
  const ProfileScreen({Key? key}) : super(key: key);
 
  @override
  State<ProfileScreen> createState() => _ProfileScreenState();
}
 
class _ProfileScreenState extends State<ProfileScreen> {
  /// GitCode服务实例
  final GitCodeService _service = GitCodeService();
  
  /// 用户信息
  Map<String, dynamic>? _userInfo;
  
  /// 加载状态
  bool _isLoading = true;
  
  /// 错误信息
  String? _errorMessage;
 
  @override
  void initState() {
    super.initState();
    _fetchUserInfo(); // 初始化时加载用户信息
  }
 
  /// 获取用户信息
  Future<void> _fetchUserInfo() async {
    setState(() {
      _isLoading = true;
      _errorMessage = null;
    });
    try {
      final userInfo = await _service.fetchUserInfo();
      setState(() {
        _userInfo = userInfo;
      });
    } catch (e) {
      setState(() {
        _errorMessage = e.toString();
      });
    } finally {
      setState(() {
        _isLoading = false;
      });
    }
  }
 
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('我的'),
        backgroundColor: Colors.transparent,
        actions: [
          IconButton(
            icon: const Icon(Icons.settings_outlined),
            onPressed: () {
              // 打开设置页面(预留)
              print('打开设置');
            },
          ),
        ],
      ),
      body: RefreshIndicator(
        onRefresh: _fetchUserInfo, // 下拉刷新
        color: AppTheme.primaryColor,
        backgroundColor: AppTheme.cardBackgroundColor,
        child: _isLoading
            ? const Center(
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: [
                    CircularProgressIndicator(color: AppTheme.primaryColor),
                    SizedBox(height: 16),
                    Text('加载中...'),
                  ],
                ),
              )
            : _errorMessage != null
                ? Center(
                    child: GlassCard(
                      child: Column(
                        mainAxisAlignment: MainAxisAlignment.center,
                        children: [
                          const Icon(
                            Icons.error_outline,
                            size: 64,
                            color: Colors.red,
                          ),
                          const SizedBox(height: 16),
                          Text(
                            _errorMessage!,
                            textAlign: TextAlign.center,
                            style: const TextStyle(color: AppTheme.secondaryTextColor),
                          ),
                          const SizedBox(height: 16),
                          ElevatedButton(
                            onPressed: () {
                              // 清除令牌并返回登录页
                              _service.clearToken();
                              Navigator.of(context).pushReplacement(
                                MaterialPageRoute(builder: (_) => const LoginScreen()),
                              );
                            },
                            child: const Text('重新登录'),
                          ),
                        ],
                      ),
                    ),
                  )
                : _userInfo == null
                    ? const Center(
                        child: Text('未能获取用户信息'),
                      )
                    : SingleChildScrollView(
                        padding: const EdgeInsets.all(20.0),
                        child: Column(
                          crossAxisAlignment: CrossAxisAlignment.center,
                          children: <Widget>[
                            // 用户信息卡片
                            GlassCard(
                              margin: const EdgeInsets.only(bottom: 24),
                              padding: const EdgeInsets.all(24.0),
                              child: Column(
                                crossAxisAlignment: CrossAxisAlignment.center,
                                children: [
                                  // 头像
                                  CircleAvatar(
                                    radius: 60,
                                    backgroundImage: NetworkImage(_userInfo!['avatar_url'] ?? ''),
                                    backgroundColor: AppTheme.secondaryColor.withOpacity(0.1),
                                  ),
                                  
                                  const SizedBox(height: 16),
                                  
                                  // 用户名
                                  Text(
                                    _userInfo!['name'] ?? _userInfo!['login'] ?? '匿名用户',
                                    style: const TextStyle(
                                      fontSize: AppTheme.fontSizeXXLarge,
                                      fontWeight: FontWeight.bold,
                                      color: AppTheme.primaryTextColor,
                                    ),
                                  ),
                                  
                                  // 登录名
                                  Text(
                                    '@${_userInfo!['login']}',
                                    style: TextStyle(
                                      fontSize: AppTheme.fontSizeLarge,
                                      color: AppTheme.secondaryTextColor,
                                    ),
                                  ),
                                  
                                  const SizedBox(height: 16),
                                  
                                  // 简介
                                  if (_userInfo!['bio'] != null)
                                    Text(
                                      _userInfo!['bio'] ?? '',
                                      textAlign: TextAlign.center,
                                      style: TextStyle(
                                        fontSize: AppTheme.fontSizeMedium,
                                        color: AppTheme.secondaryTextColor,
                                      ),
                                    ),
                                ],
                              ),
                            ),
                            
                            // 统计信息卡片
                            GlassCard(
                              margin: const EdgeInsets.only(bottom: 24),
                              padding: const EdgeInsets.all(24.0),
                              child: Row(
                                mainAxisAlignment: MainAxisAlignment.spaceAround,
                                children: [
                                  _buildStatItem(
                                    '仓库',
                                    _userInfo!['public_repos'] ?? 0,
                                    Icons.code_outlined,
                                  ),
                                  _buildStatItem(
                                    '关注者',
                                    _userInfo!['followers'] ?? 0,
                                    Icons.people_outline,
                                  ),
                                  _buildStatItem(
                                    '关注中',
                                    _userInfo!['following'] ?? 0,
                                    Icons.person_add_outlined,
                                  ),
                                ],
                              ),
                            ),
                            
                            // 详细信息卡片
                            GlassCard(
                              padding: const EdgeInsets.all(24.0),
                              child: Column(
                                crossAxisAlignment: CrossAxisAlignment.start,
                                children: [
                                  const Text(
                                    '个人信息',
                                    style: TextStyle(
                                      fontSize: AppTheme.fontSizeXLarge,
                                      fontWeight: FontWeight.bold,
                                      color: AppTheme.primaryTextColor,
                                    ),
                                  ),
                                  
                                  const SizedBox(height: 16),
                                  
                                  // 邮箱
                                  if (_userInfo!['email'] != null)
                                    _buildInfoItem(
                                      Icons.email_outlined,
                                      '邮箱',
                                      _userInfo!['email'] ?? '未公开',
                                    ),
                                  
                                  // 位置
                                  if (_userInfo!['location'] != null)
                                    _buildInfoItem(
                                      Icons.location_on_outlined,
                                      '位置',
                                      _userInfo!['location'] ?? '未公开',
                                    ),
                                  
                                  // 主页
                                  if (_userInfo!['blog'] != null)
                                    _buildInfoItem(
                                      Icons.link_outlined,
                                      '主页',
                                      _userInfo!['blog'] ?? '未公开',
                                    ),
                                  
                                  // 加入时间
                                  _buildInfoItem(
                                    Icons.calendar_today_outlined,
                                    '加入时间',
                                    _formatDate(_userInfo!['created_at']),
                                  ),
                                ],
                              ),
                            ),
                            
                            const SizedBox(height: 24),
                            
                            // 登出按钮
                            SizedBox(
                              width: double.infinity,
                              height: 50,
                              child: ElevatedButton.icon(
                                onPressed: () {
                                  // 清除令牌并返回登录页
                                  _service.clearToken();
                                  Navigator.of(context).pushReplacement(
                                    MaterialPageRoute(builder: (_) => const LoginScreen()),
                                  );
                                },
                                icon: const Icon(Icons.logout),
                                label: const Text('登出'),
                                style: ElevatedButton.styleFrom(
                                  backgroundColor: Colors.redAccent,
                                  foregroundColor: Colors.white,
                                ),
                              ),
                            ),
                          ],
                        ),
                      ),
      ),
    );
  }
 
  /// 构建统计项
  Widget _buildStatItem(String label, int count, IconData icon) {
    return Column(
      children: [
        Container(
          width: 56,
          height: 56,
          decoration: BoxDecoration(
            borderRadius: BorderRadius.circular(16),
            color: AppTheme.primaryColor.withOpacity(0.1),
          ),
          child: Icon(
            icon,
            size: 28,
            color: AppTheme.primaryColor,
          ),
        ),
        const SizedBox(height: 8),
        Text(
          count.toString(),
          style: const TextStyle(
            fontSize: 24,
            fontWeight: FontWeight.bold,
            color: AppTheme.primaryTextColor,
          ),
        ),
        const SizedBox(height: 4),
        Text(
          label,
          style: TextStyle(
            fontSize: AppTheme.fontSizeSmall,
            color: AppTheme.secondaryTextColor,
          ),
        ),
      ],
    );
  }
 
  /// 构建信息项
  Widget _buildInfoItem(IconData icon, String label, String value) {
    return Padding(
      padding: const EdgeInsets.only(bottom: 16),
      child: Row(
        children: [
          Container(
            width: 40,
            height: 40,
            decoration: BoxDecoration(
              borderRadius: BorderRadius.circular(12),
              color: AppTheme.secondaryColor.withOpacity(0.1),
            ),
            child: Icon(
              icon,
              size: 20,
              color: AppTheme.secondaryColor,
            ),
          ),
          const SizedBox(width: 16),
          Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Text(
                label,
                style: TextStyle(
                  fontSize: AppTheme.fontSizeSmall,
                  color: AppTheme.secondaryTextColor,
                ),
              ),
              const SizedBox(height: 4),
              Text(
                value,
                style: TextStyle(
                  fontSize: AppTheme.fontSizeMedium,
                  color: AppTheme.primaryTextColor,
                  fontWeight: FontWeight.w500,
                ),
              ),
            ],
          ),
        ],
      ),
    );
  }
 
  /// 格式化日期
  String _formatDate(String? dateStr) {
    if (dateStr == null) return '';
    final date = DateTime.parse(dateStr);
    return '${date.year}年${date.month}月${date.day}日';
  }
}
10. 入口文件(main.dart

作用:应用的入口点,初始化主题和根组件。

  1. 是整个 APP 的 “启动开关”—— 打开 APP 时,第一个运行的就是这个文件
  2. 初始化 APP 的基础配置:比如加载之前定义的毛玻璃主题、初始化核心组件
  3. 决定 APP 启动后先展示哪个页面(比如先跳登录页,还是直接进主页面)
  4. 统一配置 APP 的全局设置(比如屏幕适配、全局异常捕获)

VScode(即Visual Studio Code软件)中打开lib/main.dart文件

(如小博的目录路径"D:\HarmonyOS\oh_code\demo_gitcode\lib\main.dart")

然后再main.dart代码中头部加上如下两行代码:

import './screens/login_screen.dart';
import './themes/app_theme.dart';

整体代码如图:

五、运行与测试

(一)添加依赖:在 pubspec.yaml 中添加 dio 依赖:(因为上面添加了组件,需要我们需要添加他的依赖以供代码运行)

  1. VScode中打开项目根目录下(如小博的地址是"D:\HarmonyOS\oh_code\demo_gitcode\pubspec.yaml")然后找到对应位置添加这一行代码“flutter_easyloading: ^3.0.5   # 添加这个”

  2. dependencies:
      flutter:
        sdk: flutter
      dio: ^5.7.0
      flutter_easyloading: ^3.0.5   # 添加这个
      shared_preferences: ^2.3.0
  3. 如图所示,并且保存即可

(二)运行

打开软件DevEco Studio,然后在左上角File打开ohos文件(小博的文件地址是"D:\HarmonyOS\oh_code\demo_gitcode\ohos"),点击OK

然后点击运行符合运行,如图

(三)测试

打开手机,

点开你的桌面对应文件就会弹出如下界面

输入你的个人访问令牌然后登录就可以进入如下界面,实现个人账户登录

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐