196 lines
6.3 KiB
Swift
196 lines
6.3 KiB
Swift
//
|
|
// 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) ?? AppStore.shared.session.userName.nonEmpty ?? "未设置昵称"
|
|
}
|
|
|
|
var displayRealName: String {
|
|
nonEmpty(userInfo?.realName) ?? AppStore.shared.session.realName.nonEmpty ?? "--"
|
|
}
|
|
|
|
var displayPhone: String {
|
|
nonEmpty(userInfo?.phone) ?? AppStore.shared.session.phone.nonEmpty ?? "--"
|
|
}
|
|
|
|
var displayAvatarURL: String {
|
|
nonEmpty(userInfo?.avatar) ?? AppStore.shared.session.avatar.nonEmpty ?? ""
|
|
}
|
|
|
|
var displayUID: String {
|
|
let uid = AppStore.shared.session.userId.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
return uid.isEmpty ? "--" : uid
|
|
}
|
|
|
|
/// 本地是否已有可供冷启动首屏展示的基本资料。
|
|
var hasCachedBasicInfo: Bool {
|
|
[
|
|
AppStore.shared.session.userName,
|
|
AppStore.shared.session.realName,
|
|
AppStore.shared.session.phone,
|
|
AppStore.shared.session.avatar,
|
|
AppStore.shared.session.userId,
|
|
].contains { nonEmpty($0) != nil }
|
|
}
|
|
|
|
var accountDisplayName: String {
|
|
let name = AppStore.shared.session.accountDisplayName.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
return name.isEmpty ? "--" : name
|
|
}
|
|
|
|
/// 当前账号类型文案,如「门店账号」「景区账号」。
|
|
var accountTypeLabel: String {
|
|
switch AppStore.shared.session.accountType {
|
|
case .storeUser:
|
|
"门店账号"
|
|
case .scenicUser:
|
|
"景区账号"
|
|
case .unknown:
|
|
"--"
|
|
}
|
|
}
|
|
|
|
/// 当前是否为门店账号。
|
|
var isStoreAccount: Bool {
|
|
AppStore.shared.session.accountType == .storeUser
|
|
}
|
|
|
|
var currentScenicName: String {
|
|
let name = AppStore.shared.session.currentScenicName.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
return name.isEmpty ? "--" : name
|
|
}
|
|
|
|
var accountStatusText: String {
|
|
nonEmpty(userInfo?.statusName) ?? "--"
|
|
}
|
|
|
|
var showPhotographerFields: Bool {
|
|
if AppStore.shared.session.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.session.applyUserInfo(info)
|
|
if !info.roleName.isEmpty {
|
|
AppStore.shared.session.roleName = info.roleName
|
|
}
|
|
notifyStateChange()
|
|
|
|
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.session.userName.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
let nextAvatar = store.session.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
|
|
}
|
|
}
|