Files
e-party-iOS/issues/MomentListItem点赞功能实现.md
edwinQQQ 6b575dab27 feat: 实现MomentListItem点赞功能及状态管理
- 在MomentListItem中新增点赞功能,用户点击按钮可触发点赞请求。
- 使用MVVM+Combine架构管理点赞状态,确保UI与状态同步。
- 添加加载状态和错误处理,提升用户体验和交互反馈。
- 更新相关视图以支持新的点赞逻辑,优化代码可读性和维护性。
2025-08-07 11:50:30 +08:00

7.7 KiB
Raw Blame History

MomentListItem 点赞功能实现 (MVVM+Combine)

需求分析

  1. 用户可以点击 like 按钮
  2. 点击 like 按钮时,触发 LikeDynamicRequest 请求
  3. 当 moment.isLike 为 true 时,请求的 status 参数传 0取消点赞
  4. 当 moment.isLike 为 false 时,请求的 status 参数传 1点赞
  5. 请求成功后,更新 MomentListItem 的 like 状态

架构选择

使用 MVVM+Combine 架构,参考 MomentListHomeViewModel 的实现模式:

  • 不使用 TCA 框架
  • 使用 @State 管理本地状态
  • 使用 LiveAPIService 直接发起 API 请求
  • 使用 Task 和 async/await 处理异步操作

实施计划

文件结构

  • 修改:yana/MVVM/View/MomentListItem.swift

核心组件设计

  1. 状态管理

    • @State private var isLikeLoading = false - 点赞加载状态
    • @State private var localIsLike: Bool - 本地点赞状态
    • @State private var localLikeCount: Int - 本地点赞数量
  2. API 请求

    • 使用 LiveAPIService() 直接创建服务实例
    • 使用 UserInfoManager.getCurrentUserId() 获取当前用户ID
    • 使用 LikeDynamicRequest 创建请求
  3. 点赞处理逻辑

    • handleLikeTap() - 处理点赞按钮点击
    • performLikeRequest() - 执行点赞 API 请求

实施步骤

  1. 移除 TCA 相关导入和依赖
  2. 添加 @State 状态变量
  3. 实现点赞按钮的点击处理
  4. 实现 API 请求逻辑(参考 MomentListHomeViewModel
  5. 更新 UI 显示状态
  6. 添加错误处理和加载状态

技术要点

  • 使用 LiveAPIService() 直接创建服务实例
  • 使用 UserInfoManager.getCurrentUserId() 获取当前用户ID
  • 使用 APILoadingManager 显示错误信息
  • 使用 debugInfoSyncdebugErrorSync 记录日志
  • 使用 MainActor.run 确保 UI 更新在主线程

实现细节

状态初始化

init(moment: MomentsInfo, onImageTap: @escaping (([String], Int)) -> Void = { (arg) in let (_, _) = arg;  }) {
    self.moment = moment
    self.onImageTap = onImageTap
    // 初始化本地状态
    self._localIsLike = State(initialValue: moment.isLike)
    self._localLikeCount = State(initialValue: moment.likeCount)
}

点赞按钮 UI

Button(action: {
    if !isLikeLoading {
        handleLikeTap()
    }
}) {
    HStack(spacing: 4) {
        if isLikeLoading {
            ProgressView()
                .progressViewStyle(CircularProgressViewStyle(tint: localIsLike ? .red : .white.opacity(0.8)))
                .scaleEffect(0.8)
        } else {
            Image(systemName: localIsLike ? "heart.fill" : "heart")
                .font(.system(size: 16))
        }
        Text("\(localLikeCount)")
            .font(.system(size: 14))
    }
    .foregroundColor(localIsLike ? .red : .white.opacity(0.8))
}
.disabled(isLikeLoading)

API 请求逻辑

private func performLikeRequest() async {
    // 设置加载状态
    await MainActor.run {
        isLikeLoading = true
    }
    
    do {
        // 获取当前用户ID
        guard let currentUserId = await UserInfoManager.getCurrentUserId(),
              let currentUserIdInt = Int(currentUserId) else {
            await MainActor.run {
                isLikeLoading = false
            }
            setAPILoadingErrorSync(UUID(), errorMessage: "无法获取用户信息,请重新登录")
            return
        }
        
        // 确定请求参数
        let status = localIsLike ? 0 : 1 // 0: 取消点赞, 1: 点赞
        
        // 创建 API 服务实例
        let apiService = LiveAPIService()
        
        // 创建请求
        let request = LikeDynamicRequest(
            dynamicId: moment.dynamicId,
            uid: moment.uid,
            status: status,
            likedUid: currentUserIdInt,
            worldId: moment.worldId
        )
        
        debugInfoSync("📡 MomentListItem: 发送点赞请求")
        debugInfoSync("   动态ID: \(moment.dynamicId)")
        debugInfoSync("   当前状态: \(localIsLike)")
        debugInfoSync("   请求状态: \(status)")
        
        // 发起请求
        let response: LikeDynamicResponse = try await apiService.request(request)
        
        await MainActor.run {
            isLikeLoading = false
            
            // 处理响应
            if let data = response.data, let success = data.success, success {
                // 更新本地状态
                localIsLike = !localIsLike
                localLikeCount = data.likeCount ?? localLikeCount
                debugInfoSync("✅ MomentListItem: 点赞操作成功")
                debugInfoSync("   动态ID: \(moment.dynamicId)")
                debugInfoSync("   新状态: \(localIsLike)")
                debugInfoSync("   新数量: \(localLikeCount)")
            } else {
                // 显示错误信息
                let errorMessage = response.message.isEmpty ? "点赞失败,请重试" : response.message
                setAPILoadingErrorSync(UUID(), errorMessage: errorMessage)
                debugErrorSync("❌ MomentListItem: 点赞操作失败")
                debugErrorSync("   动态ID: \(moment.dynamicId)")
                debugErrorSync("   错误: \(errorMessage)")
            }
        }
        
    } catch {
        await MainActor.run {
            isLikeLoading = false
        }
        setAPILoadingErrorSync(UUID(), errorMessage: error.localizedDescription)
        debugErrorSync("❌ MomentListItem: 点赞请求异常")
        debugErrorSync("   动态ID: \(moment.dynamicId)")
        debugErrorSync("   错误: \(error.localizedDescription)")
    }
}

架构对比

与 TCA 架构的区别

方面 TCA 架构 MVVM+Combine 架构
依赖注入 @Dependency(.apiService) LiveAPIService()
状态管理 @ObservableState @State
异步处理 Effect.task Task + async/await
错误处理 通过 Effect 处理 直接 try-catch
复杂度 较高 较低

与 MomentListHomeViewModel 的一致性

  • 使用相同的 API 服务创建方式
  • 使用相同的错误处理模式
  • 使用相同的日志记录方式
  • 使用相同的用户验证逻辑

功能特性

交互体验

  • 即时反馈:点击后立即显示加载状态
  • 状态切换:成功后在点赞/取消点赞状态间切换
  • 数量更新:实时更新点赞数量显示
  • 错误处理:网络错误或服务器错误时显示友好提示

状态管理

  • 本地状态:使用 @State 管理本地点赞状态,避免影响其他组件
  • 加载状态:防止重复点击,提供视觉反馈
  • 错误恢复:请求失败时保持原有状态

安全性

  • 用户验证:确保用户已登录才能点赞
  • 参数验证:正确传递点赞状态参数
  • 错误边界:完善的错误处理机制

测试要点

  1. 点赞状态切换正确true → false, false → true
  2. 点赞数量实时更新
  3. 加载状态显示正常
  4. 网络错误处理正确
  5. 用户未登录时的错误提示
  6. 重复点击防护
  7. 与其他组件的状态同步

完成状态

  • 移除 TCA 相关代码
  • 实现 MVVM+Combine 架构
  • 实现状态管理
  • 实现点赞按钮 UI
  • 实现 API 请求逻辑
  • 实现错误处理
  • 实现加载状态
  • 添加日志记录
  • 代码审查和优化

注意事项

  1. 本实现使用本地状态管理,不会影响其他使用相同动态数据的组件
  2. 如果需要全局状态同步,建议在父组件中实现状态管理
  3. 点赞操作是幂等的,重复请求不会产生副作用
  4. 错误处理使用全局的 APILoadingManager确保用户体验一致
  5. 架构选择符合项目要求,不使用 TCA 框架