Files
suixinkan_uikit/suixinkan/Features/Orders/ViewModels/DepositOrderListViewModel.swift
汉秋 31d2b6094b Implement orders tab aligned with Android, including scan verify flows.
Add role-based order/deposit lists, AVFoundation QR scanning, verify dialogs, and ViewModel tests so photographers, scenic admins, and store admins match Android behavior.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-06 16:59:53 +08:00

244 lines
7.6 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.

//
// DepositOrderListViewModel.swift
// suixinkan
//
import Foundation
/// ViewModel Android `DepositOrderListViewModel`
final class DepositOrderListViewModel {
private(set) var searchText = ""
private(set) var filterLabel = "全部"
private(set) var tabIndex = 0
private(set) var startTime: Date?
private(set) var endTime: Date?
private(set) var orderList: [StoreOrderItem] = []
private(set) var isRefreshing = false
private(set) var canLoadMore = false
private(set) var isLoadingMore = false
private(set) var writeOffResponse: WriteOffOrderResponse?
var onStateChange: (() -> Void)?
var onShowMessage: ((String) -> Void)?
private var currentPage = 1
private var totalCount = 0
private var searchDebounceTask: Task<Void, Never>?
private let apiDateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.calendar = Calendar(identifier: .gregorian)
formatter.locale = Locale(identifier: "zh_CN")
formatter.dateFormat = "yyyy-MM-dd"
return formatter
}()
func refreshOrderList(api: OrderAPI) async {
currentPage = 1
isRefreshing = true
isLoadingMore = false
notifyStateChange()
await loadOrderList(refresh: true, api: api)
}
func loadMore(api: OrderAPI) async {
guard !isLoadingMore, !isRefreshing, orderList.count < totalCount else { return }
isLoadingMore = true
notifyStateChange()
await loadOrderList(refresh: false, api: api)
}
func shouldLoadMore(at index: Int) -> Bool {
guard canLoadMore, !isRefreshing, !isLoadingMore else { return false }
return index >= max(0, orderList.count - 3)
}
func onSearchTextChange(_ text: String, api: OrderAPI) {
searchText = text
notifyStateChange()
searchDebounceTask?.cancel()
let trimmed = text.trimmingCharacters(in: .whitespacesAndNewlines)
if trimmed.isEmpty {
Task { await refreshOrderList(api: api) }
return
}
if Self.isValidPhone(trimmed) {
searchDebounceTask = Task {
try? await Task.sleep(nanoseconds: 400_000_000)
guard !Task.isCancelled else { return }
await refreshOrderList(api: api)
}
}
}
func onFilterChange(tab: Int) {
tabIndex = tab
filterLabel = StoreOrderFilter.from(tabIndex: tab).label
notifyStateChange()
}
func onDateRangeSelected(start: Date?, end: Date?) {
startTime = start
endTime = end
notifyStateChange()
}
func dismissWriteOffDialog() {
writeOffResponse = nil
notifyStateChange()
}
func processScanResult(_ scanResult: String, api: OrderAPI) async {
guard let parsed = QRCodeScanResult.parse(scanResult) else {
onShowMessage?("请扫描正确的二维码")
return
}
guard parsed.method == "order_verify" else {
onShowMessage?("请扫描订单核销码")
return
}
guard parsed.userId > 0, !parsed.orderNumber.isEmpty, !parsed.time.isEmpty else {
onShowMessage?("核销码内容不完整")
return
}
let storeId = AppStore.shared.currentStoreId
guard storeId > 0 else {
onShowMessage?("店铺信息异常,请先选择店铺")
return
}
await executeStoreOrderVerify(
StoreOrderVerifyRequest(
userId: parsed.userId,
orderNumber: parsed.orderNumber,
time: parsed.time,
storeId: storeId
),
api: api
)
}
func requestRefund(
orderNumber: String,
refundType: Int,
refundAmount: String,
refundReason: String,
api: OrderAPI
) async {
let storeId = AppStore.shared.currentStoreId
guard storeId > 0 else {
onShowMessage?("店铺信息异常")
return
}
do {
try await api.storeOrderRefund(
StoreOrderRefundRequest(
storeId: storeId,
orderNumber: orderNumber,
refundType: refundType,
refundAmount: refundAmount,
refundReason: refundReason
)
)
onShowMessage?("退款成功")
await refreshOrderList(api: api)
} catch {
onShowMessage?(error.localizedDescription)
}
}
func canOpenDetail(for item: StoreOrderItem) -> Bool {
item.orderType == StoreOrderType.multiTravelShoot
}
static func isValidPhone(_ text: String) -> Bool {
let trimmed = text.trimmingCharacters(in: .whitespacesAndNewlines)
guard trimmed.count == 11, trimmed.first == "1" else { return false }
guard let second = trimmed.dropFirst().first, ("3" ... "9").contains(second) else { return false }
return trimmed.allSatisfy(\.isNumber)
}
static func apiOrderStatus(forTabIndex tabIndex: Int) -> Int? {
OrderListQueryBuilder.storeOrderStatusQuery(tabIndex: tabIndex)
}
private func loadOrderList(refresh: Bool, api: OrderAPI) async {
let phone = searchText.trimmingCharacters(in: .whitespacesAndNewlines)
let isPhoneSearch = !phone.isEmpty && Self.isValidPhone(phone)
let storeId = AppStore.shared.currentStoreId
if storeId <= 0 {
finishLoading()
orderList = []
canLoadMore = false
onShowMessage?("店铺信息异常,请先选择店铺")
return
}
let orderStatus: Int?
let start: String?
let end: String?
let userPhone: String?
if isPhoneSearch {
orderStatus = 0
start = nil
end = nil
userPhone = phone
} else {
orderStatus = Self.apiOrderStatus(forTabIndex: tabIndex)
start = startTime.map { apiDateFormatter.string(from: $0) }
end = endTime.map { apiDateFormatter.string(from: $0) }
userPhone = nil
}
do {
let response = try await api.storeOrderList(
storeId: storeId,
page: currentPage,
userPhone: userPhone,
orderStatus: orderStatus,
startTime: start,
endTime: end
)
totalCount = response.total
if refresh || currentPage == 1 {
orderList = response.list
currentPage = 2
} else {
orderList.append(contentsOf: response.list)
currentPage += 1
}
canLoadMore = orderList.count < totalCount
} catch is CancellationError {
return
} catch {
onShowMessage?(error.localizedDescription)
}
finishLoading()
}
private func executeStoreOrderVerify(_ request: StoreOrderVerifyRequest, api: OrderAPI) async {
do {
let response = try await api.storeOrderVerify(request)
writeOffResponse = response
if response.orderVerify {
await refreshOrderList(api: api)
}
notifyStateChange()
} catch {
onShowMessage?(error.localizedDescription)
}
}
private func finishLoading() {
isRefreshing = false
isLoadingMore = false
notifyStateChange()
}
private func notifyStateChange() {
onStateChange?()
}
}