Files
suixinkan_uikit/suixinkan/Features/Payment/ViewModels/PaymentCollectionDetailsViewModel.swift
汉秋 005349f8e6 模块化 AppStore 并完善素材管理与个人空间设置。
将会话、权限、位置、收款与排队存储拆分为独立模块,同步更新各 ViewModel 与单元测试,并补充素材管理 UI 与个人空间设置交互。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-13 11:01:08 +08:00

176 lines
4.8 KiB
Swift
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// 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.session.currentScenicName
staffId = appStore.session.userId
isVoiceBroadcastOpen = appStore.payment.isOpenReceiveVoice
notifyStateChange()
}
func loadPayCode(api: PaymentAPI) async {
guard !loading else { return }
refreshLocalState()
loading = true
notifyStateChange()
defer {
loading = false
notifyStateChange()
}
let scenicId = appStore.session.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.payment.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
}
}