197 lines
6.6 KiB
Swift
197 lines
6.6 KiB
Swift
//
|
||
// OrdersViewModel.swift
|
||
// suixinkan
|
||
//
|
||
// Created by Codex on 2026/6/22.
|
||
//
|
||
|
||
import Foundation
|
||
import Observation
|
||
|
||
@MainActor
|
||
@Observable
|
||
/// 订单页面 ViewModel,管理订单列表、核销列表、筛选条件和手动核销流程。
|
||
final class OrdersViewModel {
|
||
var selectedEntry: OrdersEntry = .storeOrders
|
||
var selectedStatus = 0
|
||
var searchPhone = ""
|
||
var filterStartDate: Date?
|
||
var filterEndDate: Date?
|
||
private(set) var loading = false
|
||
private(set) var loadingMore = false
|
||
private(set) var isVerifying = false
|
||
private(set) var currentVerifyingOrderNumber: String?
|
||
|
||
private(set) var storeOrders: [OrderEntity] = []
|
||
private(set) var storeTotal = 0
|
||
private(set) var storePage = 1
|
||
private(set) var storeHasMore = false
|
||
|
||
private(set) var writeOffOrders: [WriteOffOrderItem] = []
|
||
private(set) var writeOffTotal = 0
|
||
private(set) var writeOffPage = 1
|
||
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?, 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, 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?, 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, 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?) 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, 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, 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, page: 1, pageSize: pageSize)
|
||
writeOffOrders = refreshed.list
|
||
writeOffTotal = refreshed.total
|
||
writeOffPage = 1
|
||
writeOffHasMore = writeOffOrders.count < refreshed.total && !refreshed.list.isEmpty
|
||
}
|
||
|
||
/// 用于测试重复提交场景的内部状态设置。
|
||
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
|
||
}()
|
||
}
|