// // 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? 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?() } }