// // OrdersViewController.swift // suixinkan // import CoreLocation import SnapKit import UIKit /// 订单 Tab 根页面,按角色分流摄影师/景区管理员/店铺管理员。 final class OrdersViewController: BaseViewController, UITableViewDataSource, UITableViewDelegate { private let orderAPI = NetworkServices.shared.orderAPI private let orderListViewModel = OrderListViewModel() private let depositViewModel = DepositOrderListViewModel() private let filterBar = OrderFilterBarView() private let listView = OrderListView() private var hasInitialized = false private var isStoreMode: Bool { AppStore.shared.currentAppRole == .storeAdmin } private var pendingOrderSourceNumber: String? private var pendingOrderSourcePerson: OrderSourcePerson? private var pendingGiftOrderNumber: String? private var isFetchingMultiVerifySpots = false private let locationProvider = OrderLocationProvider() override func setupNavigationBar() { title = "订单" } override func setupUI() { view.addSubview(filterBar) view.addSubview(listView) listView.tableView.dataSource = self listView.tableView.delegate = self listView.tableView.register(OrderCardCell.self, forCellReuseIdentifier: OrderCardCell.reuseIdentifier) listView.tableView.register(DepositOrderCardCell.self, forCellReuseIdentifier: DepositOrderCardCell.reuseIdentifier) } override func setupConstraints() { filterBar.snp.makeConstraints { make in make.top.equalTo(view.safeAreaLayoutGuide) make.leading.trailing.equalToSuperview() } listView.snp.makeConstraints { make in make.top.equalTo(filterBar.snp.bottom) make.leading.trailing.bottom.equalToSuperview() } } override func bindActions() { orderListViewModel.onStateChange = { [weak self] in Task { @MainActor in self?.applyViewModel() } } orderListViewModel.onShowMessage = { [weak self] message in Task { @MainActor in self?.showToast(message) } } depositViewModel.onStateChange = { [weak self] in Task { @MainActor in self?.applyViewModel() } } depositViewModel.onShowMessage = { [weak self] message in Task { @MainActor in self?.showToast(message) } } filterBar.onSearchChanged = { [weak self] text in guard let self else { return } if self.isStoreMode { self.depositViewModel.onSearchTextChange(text, api: self.orderAPI) } else { self.orderListViewModel.onSearchTextChange(text) } } filterBar.onSearchSubmitted = { [weak self] in guard let self else { return } Task { if self.isStoreMode { await self.depositViewModel.refreshOrderList(api: self.orderAPI) } else { await self.orderListViewModel.refreshOrderList(api: self.orderAPI) } } } filterBar.onFilterSelected = { [weak self] tab, rTab in guard let self else { return } Task { if self.isStoreMode { self.depositViewModel.onFilterChange(tab: tab) await self.depositViewModel.refreshOrderList(api: self.orderAPI) } else { self.orderListViewModel.onFilterChange(orderStatus: tab, isRefined: rTab) await self.orderListViewModel.refreshOrderList(api: self.orderAPI) } } } filterBar.onDateRangeTap = { [weak self] in self?.presentDateRangeSheet() } listView.onRefresh = { [weak self] in guard let self else { return } Task { if self.isStoreMode { await self.depositViewModel.refreshOrderList(api: self.orderAPI) } else { await self.orderListViewModel.refreshOrderList(api: self.orderAPI) } } } listView.onLoadMore = { [weak self] index in guard let self else { return } Task { if self.isStoreMode { guard self.depositViewModel.shouldLoadMore(at: index) else { return } await self.depositViewModel.loadMore(api: self.orderAPI) } else { guard self.orderListViewModel.shouldLoadMore(at: index) else { return } await self.orderListViewModel.loadMore(api: self.orderAPI) } } } } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) guard !hasInitialized else { return } hasInitialized = true Task { await initializeList() } } private func initializeList() async { if isStoreMode { await depositViewModel.refreshOrderList(api: orderAPI) } else { await orderListViewModel.refreshOrderList(api: orderAPI) } } private func applyViewModel() { if isStoreMode { filterBar.configure( isStoreMode: true, filterLabel: depositViewModel.filterLabel, dateRangeText: dateRangeText(start: depositViewModel.startTime, end: depositViewModel.endTime), searchText: depositViewModel.searchText ) listView.apply( isRefreshing: depositViewModel.isRefreshing, canLoadMore: depositViewModel.canLoadMore, isEmpty: depositViewModel.orderList.isEmpty ) if let response = depositViewModel.writeOffResponse { depositViewModel.dismissWriteOffDialog() WriteOffResultAlertController.present(from: self, response: response) } } else { filterBar.configure( isStoreMode: false, filterLabel: orderListViewModel.filterLabel, dateRangeText: dateRangeText(start: orderListViewModel.startTime, end: orderListViewModel.endTime), searchText: orderListViewModel.searchText ) listView.apply( isRefreshing: orderListViewModel.isRefreshing, canLoadMore: orderListViewModel.canLoadMore, isEmpty: orderListViewModel.orderList.isEmpty ) if let response = orderListViewModel.signInResponse { orderListViewModel.clearSignInResponse() OrderSignInAlertController.present(from: self, response: response) } if let response = orderListViewModel.writeOffResponse { orderListViewModel.clearWriteOffResponse() WriteOffResultAlertController.present(from: self, response: response) } if orderListViewModel.multiVerifyPendingData != nil, orderListViewModel.multiVerifyScenicSpotList.isEmpty, !isFetchingMultiVerifySpots { Task { await self.fetchMultiVerifySpotsIfNeeded() } } if !orderListViewModel.multiVerifyScenicSpotList.isEmpty { presentMultiVerifySheet() } } listView.tableView.reloadData() } private func dateRangeText(start: Date?, end: Date?) -> String { let formatter = DateFormatter() formatter.dateFormat = "yyyy/MM/dd" if let start, let end { return "\(formatter.string(from: start))-\(formatter.string(from: end))" } return "全部" } private func presentDateRangeSheet() { let start = isStoreMode ? depositViewModel.startTime : orderListViewModel.startTime let end = isStoreMode ? depositViewModel.endTime : orderListViewModel.endTime let controller = DateRangePickerViewController(rangeStart: start, rangeEnd: end) controller.onShortcutSelected = { [weak self, weak controller] shortcut in guard let self else { return } let range = self.resolveShortcutRange(shortcut) Task { if self.isStoreMode { self.depositViewModel.onDateRangeSelected(start: range.start, end: range.end) await self.depositViewModel.refreshOrderList(api: self.orderAPI) } else { self.orderListViewModel.onDateRangeSelected(start: range.start, end: range.end) await self.orderListViewModel.refreshOrderList(api: self.orderAPI) } controller?.dismiss(animated: true) } } controller.onRangeSelected = { [weak self, weak controller] start, end in guard let self else { return } Task { if self.isStoreMode { self.depositViewModel.onDateRangeSelected(start: start, end: end) await self.depositViewModel.refreshOrderList(api: self.orderAPI) } else { self.orderListViewModel.onDateRangeSelected(start: start, end: end) await self.orderListViewModel.refreshOrderList(api: self.orderAPI) } controller?.dismiss(animated: true) } } if let sheet = controller.sheetPresentationController { sheet.detents = [.medium(), .large()] } present(controller, animated: true) } private func resolveShortcutRange(_ shortcut: String) -> (start: Date?, end: Date?) { let calendar = Calendar.current let today = calendar.startOfDay(for: Date()) switch shortcut { case StatisticsRangeShortcut.today.rawValue: return (today, today) case StatisticsRangeShortcut.yesterday.rawValue: let yesterday = calendar.date(byAdding: .day, value: -1, to: today)! return (yesterday, yesterday) case StatisticsRangeShortcut.last7Days.rawValue: let start = calendar.date(byAdding: .day, value: -6, to: today)! return (start, today) default: return (nil, nil) } } private func presentScanner(onResult: @escaping (String) -> Void) { let scanner = QRCodeScannerViewController() scanner.onScanResult = onResult let nav = UINavigationController(rootViewController: scanner) nav.modalPresentationStyle = .fullScreen present(nav, animated: true) } private func fetchMultiVerifySpotsIfNeeded() async { isFetchingMultiVerifySpots = true defer { isFetchingMultiVerifySpots = false } do { let coordinate = try await locationProvider.requestCoordinate() await orderListViewModel.fetchScenicSpotsAndShowSheet( lat: coordinate.latitude, lng: coordinate.longitude, api: orderAPI ) applyViewModel() } catch { showToast(error.localizedDescription) orderListViewModel.clearMultiVerifyPending() } } private func presentMultiVerifySheet() { let spots = orderListViewModel.multiVerifyScenicSpotList guard !spots.isEmpty else { return } let controller = MultiVerifyScenicSpotViewController( spots: spots, selected: orderListViewModel.selectedMultiVerifySpot ) controller.onConfirm = { [weak self] spot in guard let self else { return } self.orderListViewModel.onMultiVerifySpotSelected(spot) Task { await self.orderListViewModel.onMultiVerifyConfirm(api: self.orderAPI) } } controller.onCancel = { [weak self] in self?.orderListViewModel.clearMultiVerifySheet() } let nav = UINavigationController(rootViewController: controller) if let sheet = nav.sheetPresentationController { sheet.detents = [.medium(), .large()] } present(nav, animated: true) orderListViewModel.clearMultiVerifySheet() } // MARK: - UITableView func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { isStoreMode ? depositViewModel.orderList.count : orderListViewModel.orderList.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { if isStoreMode { let cell = tableView.dequeueReusableCell(withIdentifier: DepositOrderCardCell.reuseIdentifier, for: indexPath) as! DepositOrderCardCell let item = depositViewModel.orderList[indexPath.row] cell.configure(item: item, actions: makeDepositActions(for: item)) listView.notifyLoadMoreIfNeeded(at: indexPath.row, shouldLoad: depositViewModel.shouldLoadMore(at: indexPath.row)) return cell } let cell = tableView.dequeueReusableCell(withIdentifier: OrderCardCell.reuseIdentifier, for: indexPath) as! OrderCardCell let order = orderListViewModel.orderList[indexPath.row] let source = orderListViewModel.orderSourceSelection(for: order) cell.configure( order: order, hideWriteActions: orderListViewModel.isScenicAdminReadOnly, actions: makeOrderActions(for: order), showOrderSource: orderListViewModel.shouldShowOrderSource(for: order), sourceText: source.map { "\($0.person.name) · \($0.lead.displayText)" } ) listView.notifyLoadMoreIfNeeded(at: indexPath.row, shouldLoad: orderListViewModel.shouldLoadMore(at: indexPath.row)) return cell } private func makeOrderActions(for order: OrderEntity) -> OrderCardActions { var actions = OrderCardActions() actions.onRefund = { [weak self] in guard let self else { return } OrderRefundAlertController.present(from: self, order: order) { request in Task { await self.orderListViewModel.refundOrder(request, api: self.orderAPI) } } } actions.onVerify = { [weak self] in self?.presentScanner { result in Task { await self?.orderListViewModel.processScanResult(result, api: self?.orderAPI ?? NetworkServices.shared.orderAPI) } } } actions.onUploadTask = { [weak self] in self?.showToast("上传任务待接入") } actions.onCancel = { [weak self] in guard let self else { return } Task { await self.orderListViewModel.cancelOrder(orderNumber: order.orderNumber, api: self.orderAPI) } } actions.onCallPhone = { guard let url = URL(string: "tel://\(order.phone)") else { return } UIApplication.shared.open(url) } actions.onRefinedChanged = { [weak self] value in guard let self else { return } Task { await self.orderListViewModel.updateRefined(orderNumber: order.orderNumber, isRefined: value, api: self.orderAPI) } } actions.onHistoricalPhoto = { [weak self] in let controller = HistoricalShootingInfoViewController(orderNumber: order.orderNumber) self?.navigationController?.pushViewController(controller, animated: true) } actions.onOrderSourceTap = { [weak self] in self?.presentOrderSourcePicker(for: order) } if let travel = order.photoTravel, travel.canGiftRetouch { actions.onSendGift = { [weak self] in guard let self else { return } self.pendingGiftOrderNumber = order.orderNumber SendGiftAlertController.present( from: self, photoNum: travel.retouchGiftPhotoNum, videoNum: travel.retouchGiftVideoNum ) { photo, video in Task { await self.orderListViewModel.sendGift( orderNumber: order.orderNumber, photoNum: photo, videoNum: video, api: self.orderAPI ) } } } } return actions } private func makeDepositActions(for item: StoreOrderItem) -> DepositOrderCardActions { var actions = DepositOrderCardActions() actions.onRefund = { [weak self] in guard let self else { return } DepositOrderRefundAlertController.present( from: self, orderNumber: item.orderNumber, amount: item.actualPayAmount, refundType: 1 ) { amount, reason in Task { await self.depositViewModel.requestRefund( orderNumber: item.orderNumber, refundType: 1, refundAmount: amount, refundReason: reason, api: self.orderAPI ) } } } actions.onPartialRefund = { [weak self] in guard let self else { return } DepositOrderRefundAlertController.present( from: self, orderNumber: item.orderNumber, amount: item.actualPayAmount, refundType: 3 ) { amount, reason in Task { await self.depositViewModel.requestRefund( orderNumber: item.orderNumber, refundType: 3, refundAmount: amount, refundReason: reason, api: self.orderAPI ) } } } actions.onVerify = { [weak self] in self?.presentScanner { result in Task { await self?.depositViewModel.processScanResult(result, api: self?.orderAPI ?? NetworkServices.shared.orderAPI) } } } actions.onTap = { [weak self] in guard let self else { return } guard self.depositViewModel.canOpenDetail(for: item) else { self.showToast("该订单类型暂无详情") return } let controller = DepositOrderDetailViewController(orderNumber: item.orderNumber) self.navigationController?.pushViewController(controller, animated: true) } return actions } private func presentOrderSourcePicker(for order: OrderEntity) { pendingOrderSourceNumber = order.orderNumber Task { await orderListViewModel.onOrderSourceSheetOpen(api: orderAPI) let controller = OrderSourcePickerViewController( people: orderListViewModel.orderSourcePeople, leads: [], selectedPerson: nil ) controller.onPersonSelected = { [weak self] person in guard let self else { return } self.pendingOrderSourcePerson = person Task { await self.orderListViewModel.loadOrderSourceLeads(person: person, api: self.orderAPI) let leadsController = OrderSourcePickerViewController( people: self.orderListViewModel.orderSourcePeople, leads: self.orderListViewModel.orderSourceLeads[person.key] ?? [], selectedPerson: person ) leadsController.onLeadSelected = { [weak self] selection in guard let self, let orderNumber = self.pendingOrderSourceNumber else { return } Task { await self.orderListViewModel.bindOrderSource( orderNumber: orderNumber, selection: selection, api: self.orderAPI ) } } self.present(leadsController, animated: true) } } present(controller, animated: true) } } }