Files
suixinkan_ios_new/suixinkan/Features/Orders/ViewModels/OrdersViewModel.swift
汉秋 703078352c 从 iOS 17 Observation 迁移至 iOS 16 兼容的 Combine 架构
将最低部署版本降至 iOS 16,以 ObservableObject 替换 @Observable,新增导航与 UI 兼容层,并补充登录冒烟 UI 测试。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-26 10:16:35 +08:00

214 lines
7.7 KiB
Swift
Raw 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.

//
// OrdersViewModel.swift
// suixinkan
//
// Created by Codex on 2026/6/22.
//
import Foundation
import Combine
@MainActor
/// ViewModel
final class OrdersViewModel: ObservableObject {
@Published var selectedEntry: OrdersEntry = .storeOrders
@Published var selectedStatus = 0
@Published var searchPhone = ""
@Published var filterStartDate: Date?
@Published var filterEndDate: Date?
@Published private(set) var loading = false
@Published private(set) var loadingMore = false
@Published private(set) var isVerifying = false
@Published private(set) var currentVerifyingOrderNumber: String?
@Published private(set) var storeOrders: [OrderEntity] = []
@Published private(set) var storeTotal = 0
@Published private(set) var storePage = 1
@Published private(set) var storeHasMore = false
@Published private(set) var writeOffOrders: [WriteOffOrderItem] = []
@Published private(set) var writeOffTotal = 0
@Published private(set) var writeOffPage = 1
@Published private(set) var writeOffHasMore = false
///
var normalizedPhone: String? {
let text = searchPhone.trimmingCharacters(in: .whitespacesAndNewlines)
return text.isEmpty ? nil : text
}
///
func reload(api: OrderServing, scenicId: Int?, storeId: Int? = nil, roleId: Int?, showLoading: Bool = true) async throws {
switch selectedEntry {
case .storeOrders:
try await reloadStoreOrders(api: api, scenicId: scenicId, roleId: roleId, showLoading: showLoading)
case .verificationOrders:
try await reloadWriteOffOrders(api: api, scenicId: scenicId, storeId: storeId, showLoading: showLoading)
}
}
///
func reloadStoreOrders(api: OrderServing, scenicId: Int?, roleId: Int?, showLoading: Bool = true) async throws {
guard let scenicId else {
storeOrders = []
storeTotal = 0
storePage = 1
storeHasMore = false
return
}
if showLoading { loading = true }
defer { if showLoading { loading = false } }
let result = try await api.orderList(
scenicId: scenicId,
page: 1,
pageSize: pageSize,
orderStatus: orderStatusQuery,
userPhone: normalizedPhone,
startTime: startTimeQuery,
endTime: endTimeQuery,
isRefined: refinedQuery,
isScenicAdmin: roleId == 53
)
storeOrders = result.list
storeTotal = result.total
storePage = 1
storeHasMore = storeOrders.count < result.total && !result.list.isEmpty
}
///
func loadMoreStoreOrders(api: OrderServing, scenicId: Int?, roleId: Int?) async throws {
guard !loadingMore, storeHasMore, let scenicId else { return }
loadingMore = true
defer { loadingMore = false }
let nextPage = storePage + 1
let result = try await api.orderList(
scenicId: scenicId,
page: nextPage,
pageSize: pageSize,
orderStatus: orderStatusQuery,
userPhone: normalizedPhone,
startTime: startTimeQuery,
endTime: endTimeQuery,
isRefined: refinedQuery,
isScenicAdmin: roleId == 53
)
storeOrders.append(contentsOf: result.list)
storeTotal = result.total
storePage = nextPage
storeHasMore = storeOrders.count < result.total && !result.list.isEmpty
}
///
func reloadWriteOffOrders(api: OrderServing, scenicId: Int?, storeId: Int? = nil, showLoading: Bool = true) async throws {
guard let scenicId else {
writeOffOrders = []
writeOffTotal = 0
writeOffPage = 1
writeOffHasMore = false
return
}
if showLoading { loading = true }
defer { if showLoading { loading = false } }
let result = try await api.writeOffList(scenicId: scenicId, storeId: storeId, page: 1, pageSize: pageSize)
writeOffOrders = result.list
writeOffTotal = result.total
writeOffPage = 1
writeOffHasMore = writeOffOrders.count < result.total && !result.list.isEmpty
}
///
func loadMoreWriteOffOrders(api: OrderServing, scenicId: Int?, storeId: Int? = nil) async throws {
guard !loadingMore, writeOffHasMore, let scenicId else { return }
loadingMore = true
defer { loadingMore = false }
let nextPage = writeOffPage + 1
let result = try await api.writeOffList(scenicId: scenicId, storeId: storeId, page: nextPage, pageSize: pageSize)
writeOffOrders.append(contentsOf: result.list)
writeOffTotal = result.total
writeOffPage = nextPage
writeOffHasMore = writeOffOrders.count < result.total && !result.list.isEmpty
}
///
func verify(api: OrderServing, scenicId: Int, storeId: Int? = nil, orderNumber: String) async throws {
guard !isVerifying else { return }
isVerifying = true
currentVerifyingOrderNumber = orderNumber
defer {
isVerifying = false
currentVerifyingOrderNumber = nil
}
try await api.writeOff(orderNumber: orderNumber)
let refreshed = try await api.writeOffList(scenicId: scenicId, storeId: storeId, page: 1, pageSize: pageSize)
writeOffOrders = refreshed.list
writeOffTotal = refreshed.total
writeOffPage = 1
writeOffHasMore = writeOffOrders.count < refreshed.total && !refreshed.list.isEmpty
}
///
func matchedWriteOffOrder(for rawCode: String) -> (orderNumber: String, matched: WriteOffOrderItem?)? {
guard let orderNumber = OrderNumberParser.parse(rawCode) else {
return nil
}
let matched = writeOffOrders.first {
$0.orderNumber.caseInsensitiveCompare(orderNumber) == .orderedSame
}
return (matched?.orderNumber ?? orderNumber, matched)
}
///
func writeOffOrder(matching orderNumber: String) -> WriteOffOrderItem? {
writeOffOrders.first {
$0.orderNumber.caseInsensitiveCompare(orderNumber) == .orderedSame
}
}
///
func markVerifyingForTests(orderNumber: String) {
isVerifying = true
currentVerifyingOrderNumber = orderNumber
}
private let pageSize = 20
private var orderStatusQuery: Int? {
selectedStatus > 0 ? selectedStatus : nil
}
private var startTimeQuery: String? {
filterStartDate.map { Self.dayFormatter.string(from: $0) }
}
private var endTimeQuery: String? {
filterEndDate.map { Self.dayFormatter.string(from: $0) }
}
private var refinedQuery: Int? {
switch selectedStatus {
case -1:
return 1
case -2:
return 2
default:
return nil
}
}
private static let dayFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.calendar = Calendar(identifier: .gregorian)
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.dateFormat = "yyyy-MM-dd"
return formatter
}()
}