// // WithdrawalSettingsViewModel.swift // suixinkan // import Foundation /// 提现设置 ViewModel,管理银行卡绑定两步表单与提交状态。 final class WithdrawalSettingsViewModel { private(set) var currentStep = 1 private(set) var holderName = "" private(set) var bankNo = "" private(set) var bankCardFrontURL = "" private(set) var bankCardBackURL = "" private(set) var bankName = "" private(set) var provinceCode = "" private(set) var provinceName = "" private(set) var cityCode = "" private(set) var cityName = "" private(set) var branchName = "" private(set) var verificationCode = "" private(set) var isAgree = false private(set) var banks: [String] = [] private(set) var areas: [AreasResponse] = [] private(set) var loading = false private(set) var sendingCode = false private(set) var submitting = false private(set) var pendingFrontImageData: Data? private(set) var pendingBackImageData: Data? private var pendingFrontFileName: String? private var pendingBackFileName: String? var onStateChange: (() -> Void)? var canGoNextStep: Bool { currentStep == 1 && !holderName.trimmed.isEmpty && (12 ... 25).contains(bankNo.filter(\.isNumber).count) && !bankCardFrontURL.isEmpty && !bankCardBackURL.isEmpty && isAgree } var canSubmit: Bool { currentStep == 2 && !holderName.trimmed.isEmpty && (12 ... 25).contains(bankNo.filter(\.isNumber).count) && !bankName.trimmed.isEmpty && !provinceCode.isEmpty && !cityCode.isEmpty && !branchName.trimmed.isEmpty && !verificationCode.trimmed.isEmpty && isAgree && !submitting } func updateHolderName(_ value: String) { holderName = value notifyStateChange() } func updateBankNo(_ value: String) { bankNo = value notifyStateChange() } func updateBankName(_ value: String) { bankName = value notifyStateChange() } func updateBranchName(_ value: String) { branchName = value notifyStateChange() } func updateVerificationCode(_ value: String) { verificationCode = value notifyStateChange() } func updateAgree(_ value: Bool) { isAgree = value notifyStateChange() } func selectProvince(_ area: AreasResponse) { provinceCode = area.code provinceName = area.name cityCode = "" cityName = "" notifyStateChange() } func selectCity(_ area: AreasResponse) { cityCode = area.code cityName = area.name notifyStateChange() } func goNextStep() { guard canGoNextStep else { return } currentStep = 2 notifyStateChange() } func prepareBankCardImage(data: Data, side: BankCardImageSide) throws { let processed = try BankCardImageProcessor.process(data: data, side: side) switch side { case .front: pendingFrontImageData = processed.data pendingFrontFileName = processed.fileName bankCardFrontURL = "pending" case .back: pendingBackImageData = processed.data pendingBackFileName = processed.fileName bankCardBackURL = "pending" } notifyStateChange() } func load(api: ProfileAPI) async throws { guard !loading else { return } loading = true notifyStateChange() defer { loading = false notifyStateChange() } async let bankList = api.bankList() async let areaList = api.areas() let result = try await (bankList, areaList) banks = result.0.banks areas = result.1 notifyStateChange() } func sendCode(api: ProfileAPI) async throws { guard !sendingCode else { return } sendingCode = true notifyStateChange() defer { sendingCode = false notifyStateChange() } try await api.bankCardSmsVerifyCode() } func submit(api: ProfileAPI, uploader: any OSSUploadServing, scenicId: Int) async throws { guard canSubmit else { throw WithdrawalValidationError.message("请完善银行卡信息") } submitting = true notifyStateChange() defer { submitting = false notifyStateChange() } try await uploadPendingImages(uploader: uploader, scenicId: scenicId) let request = UpdateBankInfoRequest( realName: holderName.trimmed, cardNumber: bankNo.filter(\.isNumber), bankName: bankName.trimmed, branchName: branchName.trimmed, frontUrl: bankCardFrontURL, backUrl: bankCardBackURL, provinceCode: provinceCode, cityCode: cityCode, smsVerifyCode: verificationCode.trimmed ) try await api.updateBankCard(request) } var provinceOptions: [AreasResponse] { areas } var cityOptions: [AreasResponse] { areas.first { $0.code == provinceCode || $0.name == provinceName }?.children ?? [] } private func uploadPendingImages(uploader: any OSSUploadServing, scenicId: Int) async throws { if let pendingFrontImageData { bankCardFrontURL = try await uploader.uploadBankCardImage( data: pendingFrontImageData, fileName: pendingFrontFileName ?? "bank_card_front.jpg", scenicId: scenicId ) { _ in } self.pendingFrontImageData = nil } if let pendingBackImageData { bankCardBackURL = try await uploader.uploadBankCardImage( data: pendingBackImageData, fileName: pendingBackFileName ?? "bank_card_back.jpg", scenicId: scenicId ) { _ in } self.pendingBackImageData = nil } } private func notifyStateChange() { onStateChange?() } } enum WithdrawalValidationError: LocalizedError, Equatable { case message(String) var errorDescription: String? { switch self { case .message(let message): message } } } private extension String { var trimmed: String { trimmingCharacters(in: .whitespacesAndNewlines) } }