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>
This commit is contained in:
@ -0,0 +1,243 @@
|
||||
//
|
||||
// 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?()
|
||||
}
|
||||
}
|
||||
473
suixinkan/Features/Orders/ViewModels/OrderListViewModel.swift
Normal file
473
suixinkan/Features/Orders/ViewModels/OrderListViewModel.swift
Normal file
@ -0,0 +1,473 @@
|
||||
//
|
||||
// OrderListViewModel.swift
|
||||
// suixinkan
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct MultiVerifyParseData: Equatable {
|
||||
let orderNumber: String
|
||||
let userId: Int
|
||||
let time: Int64
|
||||
let checkSum: String
|
||||
}
|
||||
|
||||
struct OrderSourcePerson: Equatable {
|
||||
let key: String
|
||||
let name: String
|
||||
let phone: String
|
||||
}
|
||||
|
||||
struct OrderSourceLead: Equatable {
|
||||
let key: String
|
||||
let displayText: String
|
||||
let userMobile: String
|
||||
let createdAt: String
|
||||
}
|
||||
|
||||
struct OrderSourceSelection: Equatable {
|
||||
let person: OrderSourcePerson
|
||||
let lead: OrderSourceLead
|
||||
}
|
||||
|
||||
/// 摄影师/景区管理员订单列表 ViewModel,对齐 Android `OrderListViewModel`。
|
||||
final class OrderListViewModel {
|
||||
|
||||
private(set) var orderList: [OrderEntity] = []
|
||||
private(set) var searchText = ""
|
||||
private(set) var filterLabel = "全部"
|
||||
private(set) var tabIndex = 0
|
||||
private(set) var rTabIndex = 0
|
||||
private(set) var startTime: Date?
|
||||
private(set) var endTime: Date?
|
||||
private(set) var isRefreshing = false
|
||||
private(set) var canLoadMore = false
|
||||
private(set) var isLoadingMore = false
|
||||
|
||||
private(set) var multiVerifyPendingData: MultiVerifyParseData?
|
||||
private(set) var multiVerifyScenicSpotList: [MultiTravelScenicSpotEntity] = []
|
||||
private(set) var selectedMultiVerifySpot: MultiTravelScenicSpotEntity?
|
||||
private(set) var signInResponse: OrderSignInResponse?
|
||||
private(set) var writeOffResponse: WriteOffOrderResponse?
|
||||
|
||||
private(set) var orderSourcePeople: [OrderSourcePerson] = []
|
||||
private(set) var orderSourceLeads: [String: [OrderSourceLead]] = [:]
|
||||
private(set) var orderSourceSelections: [String: OrderSourceSelection] = [:]
|
||||
private(set) var loadingOrderSourceLeadsFor: String?
|
||||
|
||||
var onStateChange: (() -> Void)?
|
||||
var onShowMessage: ((String) -> Void)?
|
||||
|
||||
private var currentPage = 1
|
||||
private var totalCount = 0
|
||||
private var loadingOrderSourcePeople = false
|
||||
|
||||
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
|
||||
}()
|
||||
|
||||
var isScenicAdminReadOnly: Bool {
|
||||
AppStore.shared.currentAppRole == .scenicAdmin
|
||||
}
|
||||
|
||||
var listAPIPath: String {
|
||||
isScenicAdminReadOnly
|
||||
? "/api/app/scenic-admin/order/list"
|
||||
: "/api/yf-handset-app/photog/order/listv2"
|
||||
}
|
||||
|
||||
func refreshOrderList(api: OrderAPI) async {
|
||||
currentPage = 1
|
||||
isRefreshing = true
|
||||
isLoadingMore = false
|
||||
orderList = []
|
||||
notifyStateChange()
|
||||
await loadOrderList(api: api, append: false)
|
||||
}
|
||||
|
||||
func loadMore(api: OrderAPI) async {
|
||||
guard !isLoadingMore, !isRefreshing else { return }
|
||||
guard orderList.count < totalCount else { return }
|
||||
isLoadingMore = true
|
||||
currentPage += 1
|
||||
notifyStateChange()
|
||||
await loadOrderList(api: api, append: true)
|
||||
}
|
||||
|
||||
func shouldLoadMore(at index: Int) -> Bool {
|
||||
guard canLoadMore, !isRefreshing, !isLoadingMore else { return false }
|
||||
return index >= max(0, orderList.count - 3)
|
||||
}
|
||||
|
||||
func onSearchTextChange(_ text: String) {
|
||||
searchText = text
|
||||
notifyStateChange()
|
||||
}
|
||||
|
||||
func onFilterChange(orderStatus: Int, isRefined: Int) {
|
||||
tabIndex = orderStatus
|
||||
rTabIndex = isRefined
|
||||
filterLabel = PhotographerOrderFilter.from(tabIndex: orderStatus, rTabIndex: isRefined).label
|
||||
notifyStateChange()
|
||||
}
|
||||
|
||||
func onDateRangeSelected(start: Date?, end: Date?) {
|
||||
startTime = start
|
||||
endTime = end
|
||||
notifyStateChange()
|
||||
}
|
||||
|
||||
func cancelOrder(orderNumber: String, api: OrderAPI) async {
|
||||
guard !abortIfScenicAdminReadOnlyMutation() else { return }
|
||||
do {
|
||||
try await api.cancelOrder(CancelRequest(orderNumber: orderNumber))
|
||||
onShowMessage?("取消成功")
|
||||
await refreshOrderList(api: api)
|
||||
} catch {
|
||||
onShowMessage?(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
|
||||
func refundOrder(_ request: OrderRefundRequest, api: OrderAPI) async {
|
||||
guard !abortIfScenicAdminReadOnlyMutation() else { return }
|
||||
do {
|
||||
try await api.refundOrder(request)
|
||||
onShowMessage?("退款成功")
|
||||
await refreshOrderList(api: api)
|
||||
} catch {
|
||||
onShowMessage?(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
|
||||
func sendGift(orderNumber: String, photoNum: Int, videoNum: Int, api: OrderAPI) async {
|
||||
guard !abortIfScenicAdminReadOnlyMutation() else { return }
|
||||
do {
|
||||
try await api.giftRetouch(SendGiftRequest(orderNumber: orderNumber, photoNum: photoNum, videoNum: videoNum))
|
||||
onShowMessage?("赠送成功")
|
||||
await refreshOrderList(api: api)
|
||||
} catch {
|
||||
onShowMessage?(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
|
||||
func updateRefined(orderNumber: String, isRefined: Int, api: OrderAPI) async {
|
||||
guard !abortIfScenicAdminReadOnlyMutation() else { return }
|
||||
do {
|
||||
try await api.updateRefined(orderNumber: orderNumber, isRefined: isRefined)
|
||||
await refreshOrderList(api: api)
|
||||
} catch {
|
||||
onShowMessage?(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
|
||||
func processScanResult(_ scanResult: String, api: OrderAPI) async {
|
||||
if scanResult.isEmpty {
|
||||
onShowMessage?("请扫描正确的二维码")
|
||||
return
|
||||
}
|
||||
guard !abortIfScenicAdminReadOnlyMutation() else { return }
|
||||
|
||||
if let parsed = QRCodeScanResult.parse(scanResult) {
|
||||
switch parsed.method {
|
||||
case "multi_verify":
|
||||
guard !parsed.orderNumber.isEmpty, parsed.userId > 0, parsed.timeLong > 0, !parsed.checkSum.isEmpty else {
|
||||
onShowMessage?("请扫描正确的多点位核销码")
|
||||
return
|
||||
}
|
||||
multiVerifyPendingData = MultiVerifyParseData(
|
||||
orderNumber: parsed.orderNumber,
|
||||
userId: parsed.userId,
|
||||
time: parsed.timeLong,
|
||||
checkSum: parsed.checkSum
|
||||
)
|
||||
notifyStateChange()
|
||||
return
|
||||
case SaleUserQrParser.methodSaleUserBind:
|
||||
if let saleUserId = SaleUserQrParser.parseSaleUserId(scanResult), saleUserId > 0 {
|
||||
onShowMessage?("获客员绑定功能待接入")
|
||||
} else {
|
||||
onShowMessage?("请扫描正确的获客员二维码")
|
||||
}
|
||||
return
|
||||
case "order_verify":
|
||||
await performOrderVerify(jsonBody: parsed.rawJSON, api: api)
|
||||
return
|
||||
case "check_in", "":
|
||||
await performCheckIn(scanResult: scanResult, api: api)
|
||||
return
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
await performCheckIn(scanResult: scanResult, api: api)
|
||||
}
|
||||
|
||||
func fetchScenicSpotsAndShowSheet(lat: Double, lng: Double, api: OrderAPI) async {
|
||||
guard let data = multiVerifyPendingData else { return }
|
||||
do {
|
||||
let response = try await api.multiTravelScenicSpotList(
|
||||
orderNumber: data.orderNumber,
|
||||
lat: lat,
|
||||
lng: lng
|
||||
)
|
||||
multiVerifyScenicSpotList = response.entityList
|
||||
selectedMultiVerifySpot = multiVerifyScenicSpotList.first
|
||||
notifyStateChange()
|
||||
if multiVerifyScenicSpotList.isEmpty {
|
||||
onShowMessage?("暂无可核销的打卡点")
|
||||
clearMultiVerifyPending()
|
||||
}
|
||||
} catch {
|
||||
onShowMessage?(error.localizedDescription)
|
||||
clearMultiVerifyPending()
|
||||
}
|
||||
}
|
||||
|
||||
func onMultiVerifySpotSelected(_ spot: MultiTravelScenicSpotEntity) {
|
||||
selectedMultiVerifySpot = spot
|
||||
notifyStateChange()
|
||||
}
|
||||
|
||||
func onMultiVerifyConfirm(api: OrderAPI) async {
|
||||
guard !abortIfScenicAdminReadOnlyMutation() else { return }
|
||||
guard let data = multiVerifyPendingData, let spot = selectedMultiVerifySpot else {
|
||||
onShowMessage?("请选择一个打卡点")
|
||||
return
|
||||
}
|
||||
do {
|
||||
let response = try await api.multiTravelOrderVerify(
|
||||
MultiTravelOrderVerifyRequest(
|
||||
orderNumber: data.orderNumber,
|
||||
userId: data.userId,
|
||||
time: data.time,
|
||||
checkSum: data.checkSum,
|
||||
scenicSpotId: spot.scenicSpotId
|
||||
)
|
||||
)
|
||||
signInResponse = response
|
||||
clearMultiVerifySheet()
|
||||
notifyStateChange()
|
||||
} catch {
|
||||
onShowMessage?(error.localizedDescription)
|
||||
clearMultiVerifyPending()
|
||||
}
|
||||
}
|
||||
|
||||
func clearMultiVerifySheet() {
|
||||
multiVerifyScenicSpotList = []
|
||||
selectedMultiVerifySpot = nil
|
||||
multiVerifyPendingData = nil
|
||||
notifyStateChange()
|
||||
}
|
||||
|
||||
func clearMultiVerifyPending() {
|
||||
multiVerifyPendingData = nil
|
||||
notifyStateChange()
|
||||
}
|
||||
|
||||
func clearSignInResponse() {
|
||||
signInResponse = nil
|
||||
notifyStateChange()
|
||||
}
|
||||
|
||||
func clearWriteOffResponse() {
|
||||
writeOffResponse = nil
|
||||
notifyStateChange()
|
||||
}
|
||||
|
||||
func shouldShowOrderSource(for order: OrderEntity) -> Bool {
|
||||
guard !isScenicAdminReadOnly else { return false }
|
||||
return order.orderType == OrderTypeCode.photographerFollow
|
||||
|| order.orderType == OrderTypeCode.photographerFollowOfflineScan
|
||||
}
|
||||
|
||||
func orderSourceSelection(for order: OrderEntity) -> OrderSourceSelection? {
|
||||
if let selection = orderSourceSelections[order.orderNumber] { return selection }
|
||||
guard let referral = order.referralOrder else { return nil }
|
||||
let person = OrderSourcePerson(
|
||||
key: String(referral.saleUserId),
|
||||
name: referral.displaySalerName,
|
||||
phone: referral.salerPhone
|
||||
)
|
||||
let lead = OrderSourceLead(
|
||||
key: referral.orderNo,
|
||||
displayText: referral.displayText.isEmpty ? referral.orderNo : referral.displayText,
|
||||
userMobile: referral.userMobile,
|
||||
createdAt: referral.createdAt
|
||||
)
|
||||
return OrderSourceSelection(person: person, lead: lead)
|
||||
}
|
||||
|
||||
func onOrderSourceSheetOpen(api: OrderAPI) async {
|
||||
orderSourceLeads = [:]
|
||||
guard !loadingOrderSourcePeople else { return }
|
||||
loadingOrderSourcePeople = true
|
||||
defer { loadingOrderSourcePeople = false }
|
||||
let storeId = AppStore.shared.currentStoreId
|
||||
let storeIdParam = storeId > 0 ? String(storeId) : nil
|
||||
do {
|
||||
let response = try await api.cooperativeSalers(storeId: storeIdParam)
|
||||
orderSourcePeople = response.list.map {
|
||||
OrderSourcePerson(key: String($0.saleUserId), name: $0.displayName, phone: $0.displayPhone)
|
||||
}
|
||||
notifyStateChange()
|
||||
} catch {
|
||||
onShowMessage?(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
|
||||
func loadOrderSourceLeads(person: OrderSourcePerson, api: OrderAPI) async {
|
||||
guard loadingOrderSourceLeadsFor == nil else { return }
|
||||
loadingOrderSourceLeadsFor = person.key
|
||||
notifyStateChange()
|
||||
defer {
|
||||
loadingOrderSourceLeadsFor = nil
|
||||
notifyStateChange()
|
||||
}
|
||||
do {
|
||||
let response = try await api.referralOrders(saleUserId: person.key)
|
||||
let leads = response.list.map {
|
||||
OrderSourceLead(
|
||||
key: $0.displayReferralOrderNo,
|
||||
displayText: $0.displayReferralOrderNo,
|
||||
userMobile: $0.userMobile,
|
||||
createdAt: $0.createdAt
|
||||
)
|
||||
}
|
||||
orderSourceLeads[person.key] = leads
|
||||
notifyStateChange()
|
||||
} catch {
|
||||
onShowMessage?(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
|
||||
func bindOrderSource(orderNumber: String, selection: OrderSourceSelection, api: OrderAPI) async {
|
||||
guard !abortIfScenicAdminReadOnlyMutation() else { return }
|
||||
do {
|
||||
try await api.bindReferralOrder(orderNumber: orderNumber, referralOrderNo: selection.lead.key)
|
||||
orderSourceSelections[orderNumber] = selection
|
||||
onShowMessage?("绑定成功")
|
||||
await refreshOrderList(api: api)
|
||||
} catch {
|
||||
onShowMessage?(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
|
||||
private func loadOrderList(api: OrderAPI, append: Bool) async {
|
||||
let scenicId = AppStore.shared.currentScenicId
|
||||
guard scenicId > 0 else {
|
||||
finishLoading()
|
||||
onShowMessage?("当前景区信息缺失")
|
||||
return
|
||||
}
|
||||
|
||||
let start = startTime.map { apiDateFormatter.string(from: $0) }
|
||||
let end = endTime.map { apiDateFormatter.string(from: $0) }
|
||||
let phone = searchText.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
|
||||
do {
|
||||
let response: OrderListResponse<OrderEntity>
|
||||
if isScenicAdminReadOnly {
|
||||
response = try await api.scenicAdminOrderList(
|
||||
scenicId: scenicId,
|
||||
page: currentPage,
|
||||
orderStatus: tabIndex,
|
||||
startTime: start,
|
||||
endTime: end,
|
||||
userPhone: phone.isEmpty ? nil : phone,
|
||||
isRefined: rTabIndex
|
||||
)
|
||||
} else {
|
||||
response = try await api.photographerOrderList(
|
||||
scenicId: scenicId,
|
||||
page: currentPage,
|
||||
orderStatus: tabIndex,
|
||||
startTime: start,
|
||||
endTime: end,
|
||||
userPhone: phone.isEmpty ? nil : phone,
|
||||
isRefined: rTabIndex
|
||||
)
|
||||
}
|
||||
totalCount = response.total
|
||||
if append {
|
||||
orderList.append(contentsOf: response.list)
|
||||
} else {
|
||||
orderList = response.list
|
||||
}
|
||||
syncOrderSourceSelections()
|
||||
canLoadMore = orderList.count < totalCount
|
||||
} catch is CancellationError {
|
||||
return
|
||||
} catch {
|
||||
onShowMessage?(error.localizedDescription)
|
||||
}
|
||||
finishLoading()
|
||||
}
|
||||
|
||||
private func performCheckIn(scanResult: String, api: OrderAPI) async {
|
||||
guard let jsonBody = appendScenicId(to: scanResult) else {
|
||||
onShowMessage?("请扫描正确的二维码")
|
||||
return
|
||||
}
|
||||
do {
|
||||
signInResponse = try await api.orderCheckIn(jsonBody: jsonBody)
|
||||
notifyStateChange()
|
||||
} catch {
|
||||
onShowMessage?(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
|
||||
private func performOrderVerify(jsonBody: String, api: OrderAPI) async {
|
||||
do {
|
||||
writeOffResponse = try await api.orderVerify(jsonBody: jsonBody)
|
||||
notifyStateChange()
|
||||
} catch {
|
||||
onShowMessage?(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
|
||||
private func appendScenicId(to scanResult: String) -> String? {
|
||||
guard let data = scanResult.data(using: .utf8),
|
||||
var json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {
|
||||
return nil
|
||||
}
|
||||
json["scenic_id"] = String(AppStore.shared.currentScenicId)
|
||||
guard let updated = try? JSONSerialization.data(withJSONObject: json),
|
||||
let string = String(data: updated, encoding: .utf8) else {
|
||||
return nil
|
||||
}
|
||||
return string
|
||||
}
|
||||
|
||||
private func syncOrderSourceSelections() {
|
||||
var updated = orderSourceSelections
|
||||
for order in orderList {
|
||||
if let selection = orderSourceSelection(for: order) {
|
||||
updated[order.orderNumber] = selection
|
||||
}
|
||||
}
|
||||
orderSourceSelections = updated
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
private func abortIfScenicAdminReadOnlyMutation() -> Bool {
|
||||
guard isScenicAdminReadOnly else { return false }
|
||||
onShowMessage?("景区管理员仅可查看")
|
||||
return true
|
||||
}
|
||||
|
||||
private func finishLoading() {
|
||||
isRefreshing = false
|
||||
isLoadingMore = false
|
||||
notifyStateChange()
|
||||
}
|
||||
|
||||
private func notifyStateChange() {
|
||||
onStateChange?()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user