
- 新增MainView Tab切换问题分析文档,详细描述问题原因及解决方案。 - 优化BottomTabView的绑定逻辑,简化状态管理,确保Tab切换时状态正确更新。 - 在MeView中实现用户信息加载逻辑调整,确保动态列表仅在首次进入时加载,并添加错误处理视图。 - 创建EmptyStateView组件,提供统一的空状态展示和重试功能。 - 增强调试信息输出,便于后续问题排查和用户体验提升。
266 lines
12 KiB
Swift
266 lines
12 KiB
Swift
import Foundation
|
||
import ComposableArchitecture
|
||
|
||
@Reducer
|
||
struct MeFeature {
|
||
@Dependency(\.apiService) var apiService
|
||
@ObservableState
|
||
struct State: Equatable {
|
||
var isFirstLoad: Bool = true
|
||
var isUserInfoFirstLoad: Bool = true
|
||
var userInfo: UserInfo?
|
||
var isLoadingUserInfo: Bool = false
|
||
var userInfoError: String?
|
||
var moments: [MomentsInfo] = []
|
||
var isLoadingMoments: Bool = false
|
||
var momentsError: String?
|
||
var hasMore: Bool = true
|
||
var isLoadingMore: Bool = false
|
||
var isRefreshing: Bool = false
|
||
var page: Int = 1
|
||
var pageSize: Int = 20
|
||
var uid: Int = 0
|
||
// 新增:显示指定用户ID,如果为nil则显示当前登录用户
|
||
var displayUID: Int?
|
||
// 新增:DetailView相关状态
|
||
var showDetail: Bool = false
|
||
var selectedMoment: MomentsInfo?
|
||
// 新增:错误视图相关状态
|
||
var showErrorView: Bool = false
|
||
var momentsFirstLoadFailed: Bool = false
|
||
|
||
init(displayUID: Int? = nil) {
|
||
self.displayUID = displayUID
|
||
// 如果displayUID不为nil,说明要显示指定用户,将其设置为uid
|
||
if let displayUID = displayUID {
|
||
self.uid = displayUID
|
||
}
|
||
}
|
||
|
||
// 获取实际要显示的用户ID
|
||
var effectiveUID: Int {
|
||
return displayUID ?? uid
|
||
}
|
||
|
||
// 判断是否显示其他用户
|
||
var isDisplayingOtherUser: Bool {
|
||
return displayUID != nil && displayUID != uid
|
||
}
|
||
}
|
||
|
||
enum Action: Equatable {
|
||
case onAppear
|
||
case refresh
|
||
case loadMore
|
||
case loadUserInfo
|
||
case retryMoments
|
||
case userInfoResponse(Result<UserInfo, APIError>)
|
||
case momentsResponse(Result<MyMomentsResponse, APIError>)
|
||
// 设置按钮点击
|
||
case settingButtonTapped
|
||
// 新增:DetailView相关Action
|
||
case showDetail(MomentsInfo)
|
||
case detailDismissed
|
||
}
|
||
|
||
func reduce(into state: inout State, action: Action) -> Effect<Action> {
|
||
switch action {
|
||
case .onAppear:
|
||
debugInfoSync("\n📱 MeFeature onAppear")
|
||
debugInfoSync(" isFirstLoad: \(state.isFirstLoad)")
|
||
debugInfoSync(" isUserInfoFirstLoad: \(state.isUserInfoFirstLoad)")
|
||
debugInfoSync(" effectiveUID: \(state.effectiveUID)")
|
||
|
||
// 每次显示都获取用户信息
|
||
let userInfoEffect = fetchUserInfo(uid: state.effectiveUID)
|
||
|
||
// 只在首次进入时获取动态列表
|
||
if state.isFirstLoad {
|
||
state.isFirstLoad = false
|
||
return .merge(
|
||
userInfoEffect,
|
||
fetchMoments(uid: state.effectiveUID, page: 1, pageSize: state.pageSize)
|
||
)
|
||
} else {
|
||
return userInfoEffect
|
||
}
|
||
case .refresh:
|
||
guard state.effectiveUID > 0 else { return .none }
|
||
debugInfoSync("\n🔄 MeFeature refresh")
|
||
debugInfoSync(" effectiveUID: \(state.effectiveUID)")
|
||
state.isRefreshing = true
|
||
state.page = 1
|
||
state.hasMore = true
|
||
state.userInfoError = nil // 重置错误状态
|
||
state.momentsError = nil // 重置错误状态
|
||
state.showErrorView = false // 隐藏错误视图
|
||
return .merge(
|
||
fetchUserInfo(uid: state.effectiveUID),
|
||
fetchMoments(uid: state.effectiveUID, page: 1, pageSize: state.pageSize)
|
||
)
|
||
case .loadUserInfo:
|
||
guard state.effectiveUID > 0 else { return .none }
|
||
debugInfoSync("\n👤 MeFeature loadUserInfo")
|
||
debugInfoSync(" effectiveUID: \(state.effectiveUID)")
|
||
return fetchUserInfo(uid: state.effectiveUID)
|
||
case .retryMoments:
|
||
guard state.effectiveUID > 0 else { return .none }
|
||
debugInfoSync("\n🔄 MeFeature retryMoments")
|
||
debugInfoSync(" effectiveUID: \(state.effectiveUID)")
|
||
state.showErrorView = false // 隐藏错误视图
|
||
state.momentsFirstLoadFailed = false
|
||
state.isLoadingMoments = true
|
||
state.page = 1
|
||
state.hasMore = true
|
||
state.momentsError = nil
|
||
return fetchMoments(uid: state.effectiveUID, page: 1, pageSize: state.pageSize)
|
||
case .loadMore:
|
||
guard state.effectiveUID > 0, state.hasMore, !state.isLoadingMore else { return .none }
|
||
state.isLoadingMore = true
|
||
return fetchMoments(uid: state.effectiveUID, page: state.page + 1, pageSize: state.pageSize)
|
||
case let .userInfoResponse(result):
|
||
state.isLoadingUserInfo = false
|
||
state.isRefreshing = false
|
||
switch result {
|
||
case let .success(userInfo):
|
||
state.userInfo = userInfo
|
||
state.userInfoError = nil
|
||
case let .failure(error):
|
||
state.userInfoError = error.localizedDescription
|
||
}
|
||
return .none
|
||
case let .momentsResponse(result):
|
||
state.isLoadingMoments = false
|
||
state.isLoadingMore = false
|
||
state.isRefreshing = false
|
||
switch result {
|
||
case let .success(resp):
|
||
let myMoments = resp.data ?? []
|
||
// 将 MyMomentInfo 转换为 MomentsInfo,并填充用户信息
|
||
let newMoments = myMoments.map { myMoment in
|
||
var momentsInfo = myMoment.toMomentsInfo()
|
||
// 填充用户信息
|
||
if let userInfo = state.userInfo {
|
||
// 使用默认的成员初始化器
|
||
momentsInfo = MomentsInfo(
|
||
dynamicId: momentsInfo.dynamicId,
|
||
uid: momentsInfo.uid,
|
||
nick: userInfo.nick ?? userInfo.nickname ?? "未知用户",
|
||
avatar: userInfo.avatar ?? "",
|
||
type: momentsInfo.type,
|
||
content: momentsInfo.content,
|
||
likeCount: momentsInfo.likeCount,
|
||
isLike: momentsInfo.isLike,
|
||
commentCount: momentsInfo.commentCount,
|
||
publishTime: momentsInfo.publishTime,
|
||
worldId: momentsInfo.worldId,
|
||
status: momentsInfo.status,
|
||
playCount: momentsInfo.playCount,
|
||
dynamicResList: momentsInfo.dynamicResList,
|
||
gender: userInfo.gender,
|
||
squareTop: momentsInfo.squareTop,
|
||
topicTop: momentsInfo.topicTop,
|
||
newUser: userInfo.newUser,
|
||
defUser: userInfo.defUser,
|
||
scene: momentsInfo.scene,
|
||
userVipInfoVO: nil, // UserVipInfoVO 和 UserVipInfo 类型不匹配,暂时设为 nil
|
||
headwearPic: userInfo.userHeadwear?.pic,
|
||
headwearEffect: userInfo.userHeadwear?.effect,
|
||
headwearType: userInfo.userHeadwear?.type,
|
||
headwearName: userInfo.userHeadwear?.headwearName,
|
||
headwearId: userInfo.userHeadwear?.headwearId,
|
||
experLevelPic: userInfo.userLevelVo?.experUrl,
|
||
charmLevelPic: userInfo.userLevelVo?.charmUrl,
|
||
isCustomWord: momentsInfo.isCustomWord,
|
||
labelList: momentsInfo.labelList
|
||
)
|
||
}
|
||
return momentsInfo
|
||
}
|
||
|
||
if state.page == 1 {
|
||
state.moments = newMoments
|
||
} else {
|
||
state.moments += newMoments
|
||
}
|
||
state.hasMore = newMoments.count == state.pageSize
|
||
if state.hasMore { state.page += 1 }
|
||
state.momentsError = nil
|
||
state.showErrorView = false // 隐藏错误视图
|
||
state.momentsFirstLoadFailed = false
|
||
|
||
debugInfoSync("✅ 我的动态加载成功")
|
||
debugInfoSync(" 加载数量: \(newMoments.count)")
|
||
debugInfoSync(" 总数量: \(state.moments.count)")
|
||
debugInfoSync(" 是否有更多: \(state.hasMore)")
|
||
case let .failure(error):
|
||
state.momentsError = error.localizedDescription
|
||
// 如果是第一页加载失败,显示错误视图
|
||
if state.page == 1 {
|
||
state.showErrorView = true
|
||
state.momentsFirstLoadFailed = true
|
||
}
|
||
debugErrorSync("❌ 我的动态加载失败: \(error.localizedDescription)")
|
||
}
|
||
return .none
|
||
case .settingButtonTapped:
|
||
// 交由 MainFeature 处理
|
||
return .none
|
||
case .showDetail(let moment):
|
||
state.selectedMoment = moment
|
||
state.showDetail = true
|
||
return .none
|
||
case .detailDismissed:
|
||
state.showDetail = false
|
||
state.selectedMoment = nil
|
||
return .none
|
||
}
|
||
}
|
||
|
||
private func fetchUserInfo(uid: Int) -> Effect<Action> {
|
||
.run { send in
|
||
debugInfoSync("👤 开始获取用户信息")
|
||
debugInfoSync(" UID: \(uid)")
|
||
|
||
if let userInfo = await UserInfoManager.fetchUserInfoFromServer(uid: String(uid), apiService: apiService) {
|
||
debugInfoSync("✅ 用户信息获取成功")
|
||
debugInfoSync(" 昵称: \(userInfo.nick ?? userInfo.nickname ?? "未知")")
|
||
debugInfoSync(" 头像: \(userInfo.avatar ?? "无")")
|
||
await send(.userInfoResponse(.success(userInfo)))
|
||
} else {
|
||
debugErrorSync("❌ 用户信息获取失败")
|
||
await send(.userInfoResponse(.failure(.noData)))
|
||
}
|
||
}
|
||
}
|
||
|
||
private func fetchMoments(uid: Int, page: Int, pageSize: Int) -> Effect<Action> {
|
||
.run { send in
|
||
debugInfoSync("🔄 开始获取我的动态")
|
||
debugInfoSync(" UID: \(uid)")
|
||
debugInfoSync(" 页码: \(page)")
|
||
debugInfoSync(" 页大小: \(pageSize)")
|
||
|
||
do {
|
||
let req = GetMyDynamicRequest(fromUid: uid, uid: uid, page: page, pageSize: pageSize)
|
||
debugInfoSync("📡 发送请求: \(req.endpoint)")
|
||
debugInfoSync(" 参数: fromUid=\(uid), uid=\(uid), page=\(page), pageSize=\(pageSize)")
|
||
|
||
let resp = try await apiService.request(req)
|
||
debugInfoSync("✅ API 请求成功")
|
||
debugInfoSync(" 响应码: \(resp.code)")
|
||
debugInfoSync(" 消息: \(resp.message)")
|
||
debugInfoSync(" 数据数量: \(resp.data?.count ?? 0)")
|
||
|
||
await send(.momentsResponse(.success(resp)))
|
||
} catch {
|
||
debugErrorSync("❌ API 请求失败: \(error.localizedDescription)")
|
||
if let apiError = error as? APIError {
|
||
debugErrorSync(" API错误类型: \(apiError)")
|
||
}
|
||
await send(.momentsResponse(.failure(error as? APIError ?? .unknown(error.localizedDescription))))
|
||
}
|
||
}
|
||
}
|
||
}
|