// // PaymentCollectionDetailsViewModel.swift // suixinkan // import Foundation import UIKit /// 收款详情 ViewModel,管理收款码拉取、设金额、保存二维码与语音开关。 final class PaymentCollectionDetailsViewModel { private(set) var loading = false private(set) var qrImage: UIImage? private(set) var scenicName = "" private(set) var staffId = "" private(set) var isVoiceBroadcastOpen = false private(set) var showAmountDialog = false private(set) var amount = "" private(set) var remark = "" let merchantInfo = PaymentMerchantInfo.defaultInfo var onStateChange: (() -> Void)? var onShowMessage: ((String) -> Void)? private var staticPayURL = "" private var dynamicPayURL = "" private let appStore: AppStore init(appStore: AppStore = .shared) { self.appStore = appStore } var hasPayCode: Bool { qrImage != nil && !staticPayURL.isEmpty } var displayScenicName: String { scenicName.isEmpty ? "暂无景区" : scenicName } func refreshLocalState() { scenicName = appStore.currentScenicName staffId = appStore.userId isVoiceBroadcastOpen = appStore.isOpenReceiveVoice notifyStateChange() } func loadPayCode(api: PaymentAPI) async { guard !loading else { return } refreshLocalState() loading = true notifyStateChange() defer { loading = false notifyStateChange() } let scenicId = appStore.currentScenicId guard scenicId > 0 else { clearPayCode() return } do { let response = try await api.payCode(scenicId: scenicId) staticPayURL = response.staticPayURL dynamicPayURL = response.dynamicPayURL qrImage = QRCodeGenerator.generateStyledQRCode(content: staticPayURL) } catch { clearPayCode() } } func refresh(api: PaymentAPI) async { await loadPayCode(api: api) } func showSetAmountDialog() { guard !staticPayURL.isEmpty else { onShowMessage?("请先获取收款码") return } amount = "" remark = "" showAmountDialog = true notifyStateChange() } func hideAmountDialog() { showAmountDialog = false amount = "" remark = "" notifyStateChange() } func updateAmount(_ value: String) { guard value.isEmpty || value.matches(PaymentAmountValidator.inputPattern) else { return } amount = value notifyStateChange() } func updateRemark(_ value: String) { guard value.count <= 200 else { return } remark = value notifyStateChange() } func confirmAmount() { if let message = PaymentAmountValidator.validationMessage(for: amount) { onShowMessage?(message) return } guard !dynamicPayURL.isEmpty else { onShowMessage?("请先获取收款码") return } let dynamicURL = dynamicPayURL + "&amount=\(amount)&remark=\(remark)" qrImage = QRCodeGenerator.generateStyledQRCode(content: dynamicURL) showAmountDialog = false onShowMessage?("金额设置成功,二维码已更新") notifyStateChange() } func saveQRCode() async { guard qrImage != nil else { onShowMessage?("请先获取收款码") return } let success = await PhotoLibrarySaver.saveImage(qrImage!) onShowMessage?(success ? "保存成功" : "保存失败") } func toggleReceiveVoice() { isVoiceBroadcastOpen.toggle() appStore.isOpenReceiveVoice = isVoiceBroadcastOpen notifyStateChange() } private func clearPayCode() { staticPayURL = "" dynamicPayURL = "" qrImage = nil } private func notifyStateChange() { onStateChange?() } } /// 收款金额校验,对齐 Android `PaymentCollectionDetailsViewModel.confirmAmount`。 enum PaymentAmountValidator { static let inputPattern = #"^\d*(\.\d{0,2})?$"# private static let confirmPattern = #"^\d+(\.\d{1,2})?$"# static func validationMessage(for amount: String) -> String? { if amount.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { return "请输入收款金额" } guard let value = Double(amount), value > 0 else { return "请输入有效的收款金额" } if !amount.matches(confirmPattern) { return "金额最多支持两位小数" } return nil } } private extension String { func matches(_ pattern: String) -> Bool { range(of: pattern, options: .regularExpression) != nil } }