博客 / 詳情

返回

鴻蒙應用開發進階:Pocket Tool 分支協作模塊的設計與落地

鴻蒙應用開發進階:Pocket Tool 分支協作模塊的設計與落地

一、階段目標與實現總結

1.1 項目迭代背景

此前已完成倉庫詳情查看、基礎搜索及個人倉庫列表等核心功能,實現了數據的流暢加載與展示。隨着用户使用場景的深入,單一的倉庫瀏覽已無法滿足協作開發需求,本次迭代重點實現分支管理核心功能,支撐多人協作場景。

最終實現:

"倉庫詳情"頁面:新增分支列表入口及管理面板
"協作中心"頁面:新增分支創建、切換、刪除及合併請求發起功能

二、核心實現詳解

2.1 Branch模型的狀態與關聯處理

位置: lib/models/branch.dart

factory Branch.fromJson(Map<String, dynamic> json, String repoFullName) {
  // 解析分支基礎信息
  final name = json['name']?.toString() ?? '';
  final commit = json['commit'] as Map<String, dynamic>;
  final commitSha = commit['sha']?.toString() ?? '';
  final commitMessage = commit['commit']['message']?.toString() ?? '無提交信息';

  // 處理分支保護狀態(核心協作屬性)
  bool isProtected = false;
  if (json.containsKey('protection')) {
    isProtected = json['protection']['enabled'] as bool? ?? false;
  }

  // 關聯倉庫信息(用於後續API請求)
  final List<String> repoParts = repoFullName.split('/');
  String owner = '';
  String repoName = '';
  if (repoParts.length >= 2) {
    owner = repoParts.sublist(0, repoParts.length - 1).join('/');
    repoName = repoParts.last;
  }

  // 解析提交時間
  final updatedAt = _parseDateTime(commit['commit']['committer']['date']);

  return Branch(
    name: name,
    repoOwner: owner,
    repoName: repoName,
    commitSha: commitSha,
    commitMessage: commitMessage,
    isProtected: isProtected,
    updatedAt: updatedAt,
    isDefault: json['default'] as bool? ?? false
  );
}

// 輔助解析方法
static DateTime? _parseDateTime(String? dateStr) {
  if (dateStr == null) return null;
  try {
    return DateTime.parse(dateStr);
  } catch (_) {
    return null;
  }
}

代碼説明:

關聯關係設計:

通過倉庫全名拆分獲取所有者和倉庫名,建立分支與倉庫的強關聯,為後續分支操作API提供必要參數。支持嵌套命名空間場景(如gh_mirrors/op/OpenManus)的拆分處理。

協作屬性強化:

重點解析分支保護狀態(isProtected),為後續刪除/合併操作提供權限判斷依據;默認分支標識(isDefault)用於界面突出顯示,提升用户識別效率。

2.2 分支列表與操作面板實現

位置: lib/pages/repository_detail_page.dart - _BranchListState

// 分支列表構建(支持下拉刷新)
Widget _buildBranchList() {
  return RefreshIndicator(
    onRefresh: _fetchBranches,
    child: ListView.separated(
      padding: const EdgeInsets.all(16),
      itemCount: _branches.length,
      separatorBuilder: (context, index) => const Divider(height: 8),
      itemBuilder: (context, index) {
        final branch = _branches[index];
        return ListTile(
          leading: branch.isDefault 
              ? const Icon(Icons.star, color: Colors.amber)
              : const Icon(Icons.code_branch),
          title: Text(branch.name),
          subtitle: Text(
            '${branch.commitSha.substring(0, 7)} · ${branch.commitMessage}',
            maxLines: 1,
            overflow: TextOverflow.ellipsis,
          ),
          trailing: _buildBranchActionButtons(branch),
          onTap: () => _switchBranch(branch),
        );
      },
    ),
  );
}

// 分支操作按鈕(根據保護狀態動態顯示)
Widget _buildBranchActionButtons(Branch branch) {
  return Row(
    mainAxisSize: MainAxisSize.min,
    children: [
      IconButton(
        icon: const Icon(Icons.merge),
        onPressed: () => _showMergeRequestDialog(branch),
        tooltip: '發起合併請求',
      ),
      IconButton(
        icon: const Icon(Icons.delete),
        onPressed: branch.isProtected || branch.isDefault
            ? null // 保護分支/默認分支禁用刪除
            : () => _confirmDeleteBranch(branch),
        tooltip: branch.isProtected ? '分支已保護' : '刪除分支',
        disabledColor: Colors.grey[300],
      ),
    ],
  );
}

2.3 分支管理API服務層

位置: lib/services/api_service.dart

/// 獲取倉庫分支列表
static Future<List<Map<String, dynamic>>> getRepoBranches(String owner, String repo) async {
  final url = Uri.parse('$baseUrl/repos/$owner/$repo/branches');
  final response = await _get(url);
  if (response.statusCode == 200) {
    final dynamic data = json.decode(response.body);
    if (data is List) {
      return data.cast<Map<String, dynamic>>();
    }
    throw Exception('Unexpected branches list format.');
  }
  throw Exception('Failed to get branches: ${response.statusCode} - ${response.body}');
}

/// 創建新分支
static Future<Map<String, dynamic>> createBranch(String owner, String repo, String branchName, String baseSha) async {
  final url = Uri.parse('$baseUrl/repos/$owner/$repo/branches');
  final body = json.encode({
    'branch': branchName,
    'ref': baseSha // 基於指定提交創建分支
  });
  final response = await _post(url, body: body);
  if (response.statusCode == 201) {
    return json.decode(response.body) as Map<String, dynamic>;
  }
  throw Exception('Failed to create branch: ${response.statusCode} - ${response.body}');
}

/// 刪除分支
static Future<void> deleteBranch(String owner, String repo, String branchName) async {
  final url = Uri.parse('$baseUrl/repos/$owner/$repo/branches/$branchName');
  final response = await _delete(url);
  if (response.statusCode != 204) {
    throw Exception('Failed to delete branch: ${response.statusCode} - ${response.body}');
  }
}

代碼説明:

完整生命週期覆蓋:

提供分支查詢、創建、刪除全流程API封裝,支持基於指定提交SHA創建分支,滿足精準分支管理需求。

權限兼容處理:

通過HTTP狀態碼精準判斷操作結果,針對保護分支刪除等非法操作返回明確錯誤信息,便於前端提示。

2.4 合併請求核心功能

位置: lib/pages/merge_request_page.dart

合併請求作為分支協作的核心環節,實現以下關鍵功能:

  1. 分支選擇器: 支持源分支與目標分支的聯動選擇,默認選中當前分支作為源分支,主分支作為目標分支
  2. 衝突檢測: 提交前調用API預檢測分支衝突,衝突時顯示衝突文件列表及解決方案提示
  3. 請求詳情: 支持填寫合併標題、描述、指定審核人,關聯相關Issue
  4. 狀態跟蹤: 實時展示合併請求狀態(待審核、已通過、已拒絕、合併中、已合併)

三、體驗優化

  1. 操作反饋強化:分支創建/刪除成功後顯示頂部Toast提示,3秒後自動消失
  2. 衝突檢測結果以高亮卡片展示,提供"查看衝突文件"快捷入口
  3. 長時間操作(如分支創建)顯示加載對話框,防止重複提交
  4. 列表優化:默認分支添加星標標識,保護分支添加盾牌圖標,提升視覺識別效率
  5. 分支列表按更新時間倒序排列,最新操作的分支優先展示
  6. 支持分支搜索過濾,輸入關鍵詞實時匹配分支名稱
  7. 異常處理:網絡錯誤時顯示重試按鈕,點擊可重新執行操作
  8. 權限不足操作時顯示明確提示,並提供"申請權限"跳轉入口
  9. 刪除分支時增加二次確認對話框,防止誤操作

在這裏插入圖片描述

四、後續優化方向

  1. 分支可視化:添加分支歷史時間線,直觀展示分支創建、合併、刪除記錄
  2. 實現分支網絡拓撲圖,展示多分支間的關聯關係
  3. 協作效率提升:支持分支權限精細化管理(如指定人員可合併到主分支)
  4. 添加合併請求模板,規範提交內容
  5. 實現合併請求審核通知(站內信+推送)
  6. 離線能力增強:支持分支列表離線緩存,無網絡時可查看歷史分支信息
  7. 離線操作記錄本地存儲,網絡恢復後自動同步

五、測試結果

測試場景 測試用例 測試結果
分支管理 創建/刪除普通分支、默認分支、保護分支 通過(保護分支/默認分支刪除已禁用)
合併請求 無衝突合併、有衝突檢測、指定審核人 通過(衝突檢測準確,狀態跟蹤正常)
異常場景 網絡中斷、權限不足、重複創建同名分支 通過(錯誤提示清晰,支持重試)
性能測試 100+分支列表加載、批量刪除分支 通過(加載耗時<1s,無卡頓)

在這裏插入圖片描述
在這裏插入圖片描述

六、總結

本次迭代完成了分支管理全流程功能開發,核心實現了分支的創建、查詢、刪除及合併請求發起與跟蹤,通過精細化的權限控制和直觀的操作反饋,顯著提升了協作開發體驗。從單一的倉庫瀏覽工具,向輕量級協作平台邁出了關鍵一步。

後續將重點優化分支可視化和審核流程自動化,進一步提升團隊協作效率。深夜的代碼終於有了成果,期待明天用户的反饋!

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.