// // ProfileViewModel.swift // suixinkan // import Foundation /// 个人信息页 ViewModel,负责资料加载与账号相关操作。 final class ProfileViewModel { private(set) var userInfo: UserInfoResponse? private(set) var realNameInfo: RealNameInfo? private(set) var bankCardInfo: BankCardInfo? private(set) var isLoading = false private(set) var isSaving = false var onStateChange: (() -> Void)? var displayNickname: String { nonEmpty(userInfo?.nickname) ?? "未设置昵称" } var displayRealName: String { nonEmpty(userInfo?.realName) ?? "--" } var displayPhone: String { nonEmpty(userInfo?.phone) ?? AppStore.shared.phone.nonEmpty ?? "--" } var displayAvatarURL: String { nonEmpty(userInfo?.avatar) ?? AppStore.shared.avatar.nonEmpty ?? "" } var displayUID: String { let uid = AppStore.shared.userId.trimmingCharacters(in: .whitespacesAndNewlines) return uid.isEmpty ? "--" : uid } var accountDisplayName: String { let name = AppStore.shared.accountDisplayName.trimmingCharacters(in: .whitespacesAndNewlines) return name.isEmpty ? "--" : name } var currentScenicName: String { let name = AppStore.shared.currentScenicName.trimmingCharacters(in: .whitespacesAndNewlines) return name.isEmpty ? "--" : name } var accountStatusText: String { nonEmpty(userInfo?.statusName) ?? "--" } var showPhotographerFields: Bool { if AppStore.shared.isPhotographerRole { return true } if let roleName = nonEmpty(userInfo?.roleName) { return AppRoleCode.fromRoleName(roleName)?.isPhotographer == true || roleName == "摄影师" } return false } var realNameStatusText: String { guard let realNameInfo else { return "点击去实名认证" } switch realNameInfo.auditStatus { case 2: return "已实名认证" case 3: return "审核不通过" default: return "审核中" } } var bankCardDisplayText: String { guard let bankCardInfo else { return "点击绑定银行卡" } if bankCardInfo.auditStatus == 2 { let masked = Self.maskCardNumber(bankCardInfo.cardNumber) return "\(bankCardInfo.bankName) \(masked)" } let label = bankCardInfo.auditStatusLabel.trimmingCharacters(in: .whitespacesAndNewlines) return label.isEmpty ? "审核中" : label } func reload(api: ProfileAPI) async throws { guard !isLoading else { return } isLoading = true notifyStateChange() defer { isLoading = false notifyStateChange() } let info = try await api.userInfo() userInfo = info AppStore.shared.applyUserInfo(info) if !info.roleName.isEmpty { AppStore.shared.roleName = info.roleName } if showPhotographerFields { async let realName = api.realNameInfo() async let bankCard = api.bankCardInfo() let extra = try await (realName, bankCard) realNameInfo = extra.0.realNameInfo bankCardInfo = extra.1.bankCard } else { realNameInfo = nil bankCardInfo = nil } } /// 从 `AppStore` 同步头像与昵称,用于资料保存后的本地即时刷新。 func applyLocalProfileUpdate(from store: AppStore = .shared) { let previous = userInfo ?? UserInfoResponse() let nextNickname = store.userName.trimmingCharacters(in: .whitespacesAndNewlines) let nextAvatar = store.avatar.trimmingCharacters(in: .whitespacesAndNewlines) userInfo = UserInfoResponse( avatar: nextAvatar.isEmpty ? previous.avatar : nextAvatar, realName: previous.realName, phone: previous.phone, nickname: nextNickname.isEmpty ? previous.nickname : nextNickname, roleName: previous.roleName, status: previous.status, statusName: previous.statusName ) notifyStateChange() } func updatePassword(_ password: String, api: ProfileAPI) async throws { let nextPassword = password.trimmingCharacters(in: .whitespacesAndNewlines) guard nextPassword.count >= 6 else { throw ProfileValidationError.shortPassword } guard !isSaving else { return } isSaving = true notifyStateChange() defer { isSaving = false notifyStateChange() } try await api.updateUserInfo(password: nextPassword) } static func maskCardNumber(_ cardNumber: String) -> String { let digits = cardNumber.filter(\.isNumber) guard digits.count >= 4 else { return cardNumber } return "**** **** **** \(digits.suffix(4))" } private func nonEmpty(_ value: String?) -> String? { let text = value?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" return text.isEmpty ? nil : text } private func notifyStateChange() { onStateChange?() } } private extension String { var nonEmpty: String? { let text = trimmingCharacters(in: .whitespacesAndNewlines) return text.isEmpty ? nil : text } }