// // OrderDetailViewControllers.swift // suixinkan // import SnapKit import UIKit // MARK: - Store Order Diffable 标识 private typealias StoreOrderDetailSection = Int private typealias StoreOrderDetailRow = String /// 门店订单详情 section 索引。 private enum StoreOrderDetailSectionID { static let context = 0 static let orderInfo = 1 static let payment = 2 static let customer = 3 static let shooting = 4 static let actions = 5 } /// 门店订单详情行标识。 private enum StoreOrderDetailRowID { static let context = "storeDetail:context" static func orderInfo(_ index: Int) -> String { "storeDetail:orderInfo:\(index)" } static func payment(_ index: Int) -> String { "storeDetail:payment:\(index)" } static func customer(_ index: Int) -> String { "storeDetail:customer:\(index)" } static func shooting(_ id: String) -> String { "storeDetail:shooting:\(id)" } static func action(_ index: Int) -> String { "storeDetail:action:\(index)" } } // MARK: - Write-Off Diffable 标识 /// 核销详情行标识。 private enum WriteOffDetailRowID { static let orderNumber = "writeOff:orderNumber" static let project = "writeOff:project" static let phone = "writeOff:phone" static let amount = "writeOff:amount" static let status = "writeOff:status" static let verificationTime = "writeOff:verificationTime" } /// 门店订单详情页,展示订单接口补全后的支付、客户、项目和拍摄点信息。 final class StoreOrderDetailViewController: UIViewController, UITableViewDelegate { private let item: OrderEntity private let viewModel: OrderDetailViewModel private let refundViewModel = OrderRefundViewModel() private lazy var tableView: UITableView = { let table = UITableView(frame: .zero, style: .insetGrouped) table.delegate = self table.backgroundColor = AppDesignUIKit.pageBackground return table }() /// Diffable 数据源,驱动多 section 订单详情刷新。 private var tableDataSource: UITableViewDiffableDataSource! /// 初始化实例。 init(item: OrderEntity) { self.item = item self.viewModel = OrderDetailViewModel(item: item) super.init(nibName: nil, bundle: nil) } @available(*, unavailable) required init?(coder: NSCoder) { fatalError() } /// 视图加载完成后的 UI 初始化与数据绑定。 override func viewDidLoad() { super.viewDidLoad() title = "订单详情" view.backgroundColor = AppDesignUIKit.pageBackground configureTableDataSource() view.addSubview(tableView) tableView.snp.makeConstraints { make in make.edges.equalToSuperview() } viewModel.onChange = { [weak self] in self?.applyTableSnapshot(reconfigure: true) } Task { await loadDetail() } } /// 配置 Diffable 数据源。 private func configureTableDataSource() { tableDataSource = UITableViewDiffableDataSource( tableView: tableView ) { [weak self] (_: UITableView, indexPath: IndexPath, row: StoreOrderDetailRow) -> UITableViewCell? in guard let self else { return UITableViewCell() } return self.configureDetailCell(row: row) } applyTableSnapshot(animated: false) } /// 构建 Diffable snapshot。 private func buildTableSnapshot() -> NSDiffableDataSourceSnapshot { var snapshot = NSDiffableDataSourceSnapshot() if viewModel.contextMessage != nil { snapshot.appendSections([StoreOrderDetailSectionID.context]) snapshot.appendItems([StoreOrderDetailRowID.context], toSection: StoreOrderDetailSectionID.context) } snapshot.appendSections([StoreOrderDetailSectionID.orderInfo]) snapshot.appendItems((0..<7).map { StoreOrderDetailRowID.orderInfo($0) }, toSection: StoreOrderDetailSectionID.orderInfo) snapshot.appendSections([StoreOrderDetailSectionID.payment]) snapshot.appendItems((0..<4).map { StoreOrderDetailRowID.payment($0) }, toSection: StoreOrderDetailSectionID.payment) snapshot.appendSections([StoreOrderDetailSectionID.customer]) snapshot.appendItems((0..<4).map { StoreOrderDetailRowID.customer($0) }, toSection: StoreOrderDetailSectionID.customer) if !viewModel.shootingList.isEmpty { snapshot.appendSections([StoreOrderDetailSectionID.shooting]) let shootingRows = viewModel.shootingList.enumerated().map { index, shooting in StoreOrderDetailRowID.shooting("\(shooting.id)-\(index)") } snapshot.appendItems(shootingRows, toSection: StoreOrderDetailSectionID.shooting) } snapshot.appendSections([StoreOrderDetailSectionID.actions]) snapshot.appendItems((0..<5).map { StoreOrderDetailRowID.action($0) }, toSection: StoreOrderDetailSectionID.actions) return snapshot } /// 应用 snapshot 刷新列表。 private func applyTableSnapshot(animated: Bool = true, reconfigure: Bool = false) { var snapshot = buildTableSnapshot() if reconfigure, !snapshot.itemIdentifiers.isEmpty { snapshot.reconfigureItems(snapshot.itemIdentifiers) } tableDataSource.apply(snapshot, animatingDifferences: animated) } /// 配置详情 Cell 内容。 private func configureDetailCell(row: StoreOrderDetailRow) -> UITableViewCell { let cell = UITableViewCell(style: .value1, reuseIdentifier: nil) cell.selectionStyle = .none cell.textLabel?.numberOfLines = 1 cell.textLabel?.textColor = nil cell.accessoryType = .none cell.isUserInteractionEnabled = true let display = viewModel.display if row == StoreOrderDetailRowID.context { cell.textLabel?.text = viewModel.contextMessage cell.textLabel?.numberOfLines = 0 return cell } if row.hasPrefix("storeDetail:orderInfo:") { let index = Int(row.split(separator: ":").last ?? "") ?? 0 let rows = ["订单号", "状态", "类型", "创建时间", "付款时间", "完成时间", "复制订单号"] cell.textLabel?.text = rows[index] switch index { case 0: cell.detailTextLabel?.text = display.orderNumber case 1: cell.detailTextLabel?.text = display.orderStatusName case 2: cell.detailTextLabel?.text = display.orderTypeLabel case 3: cell.detailTextLabel?.text = display.createdAt case 4: cell.detailTextLabel?.text = display.payTime case 5: cell.detailTextLabel?.text = display.completeTime default: cell.textLabel?.textColor = AppDesignUIKit.primary cell.selectionStyle = .default } return cell } if row.hasPrefix("storeDetail:payment:") { let index = Int(row.split(separator: ":").last ?? "") ?? 0 let rows = ["付款金额", "退款金额", "付款方式", "用户 UID"] cell.textLabel?.text = rows[index] switch index { case 0: cell.detailTextLabel?.text = "¥\(emptyToZero(display.actualPayAmount))" case 1: cell.detailTextLabel?.text = "¥\(emptyToZero(display.actualRefundAmount))" case 2: cell.detailTextLabel?.text = display.payTypeName default: cell.detailTextLabel?.text = "\(display.userId)" } return cell } if row.hasPrefix("storeDetail:customer:") { let index = Int(row.split(separator: ":").last ?? "") ?? 0 let rows = ["手机号", "关联项目", "项目 ID", "备注"] cell.textLabel?.text = rows[index] switch index { case 0: cell.detailTextLabel?.text = display.phone case 1: cell.detailTextLabel?.text = display.projectName case 2: cell.detailTextLabel?.text = "\(display.projectId)" default: cell.detailTextLabel?.text = display.remark } return cell } if row.hasPrefix("storeDetail:shooting:") { let suffix = row.replacingOccurrences(of: "storeDetail:shooting:", with: "") let indexPart = suffix.split(separator: "-").last.flatMap { Int($0) } ?? 0 if indexPart < viewModel.shootingList.count { let shooting = viewModel.shootingList[indexPart] cell.textLabel?.text = shooting.scenicSpotName cell.detailTextLabel?.text = shooting.staffName.isEmpty ? "状态 \(shooting.status)" : shooting.staffName } return cell } if row.hasPrefix("storeDetail:action:") { let index = Int(row.split(separator: ":").last ?? "") ?? 0 let actions = ["历史拍摄", "任务上传", "视频预告", "尾片上传", "退款"] cell.textLabel?.text = actions[index] cell.accessoryType = .disclosureIndicator cell.selectionStyle = .default if index == 4, !refundViewModel.canRefund(item) { cell.isUserInteractionEnabled = false cell.textLabel?.textColor = AppDesignUIKit.textSecondary } return cell } return cell } /// 加载Detail数据。 private func loadDetail() async { await appServices.globalLoading.withLoading(message: "加载详情中...") { await viewModel.load(api: appServices.ordersAPI, fallbackStoreId: appServices.accountContext.currentStore?.id) } applyTableSnapshot(reconfigure: true) if let error = viewModel.errorMessage { showToast(error) } } /// 复制订单Number。 private func copyOrderNumber() { UIPasteboard.general.string = viewModel.display.orderNumber showToast("订单号已复制") } /// 弹出Refund页面。 private func presentRefund() { refundViewModel.begin(item: item) let alert = UIAlertController(title: "订单退款", message: "可退 ¥\(refundViewModel.availableAmountText(for: item))", preferredStyle: .alert) alert.addTextField { $0.placeholder = "退款原因" } alert.addAction(UIAlertAction(title: "取消", style: .cancel)) alert.addAction(UIAlertAction(title: "提交", style: .default) { [weak self] _ in guard let self else { return } self.refundViewModel.reason = alert.textFields?.first?.text ?? "" Task { let success = await self.refundViewModel.submit(api: self.appServices.ordersAPI, item: self.item) if success { self.showToast("退款申请已提交") await self.viewModel.load(api: self.appServices.ordersAPI, fallbackStoreId: self.appServices.accountContext.currentStore?.id, forceReload: true) } else if let message = self.refundViewModel.errorMessage { self.showToast(message) } } }) present(alert, animated: true) } /// UITableView 代理:section 标题。 func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { guard let sectionID = tableDataSource.snapshot().sectionIdentifiers[safe: section] else { return nil } switch sectionID { case StoreOrderDetailSectionID.orderInfo: return "订单信息" case StoreOrderDetailSectionID.payment: return "支付信息" case StoreOrderDetailSectionID.customer: return "客户与项目" case StoreOrderDetailSectionID.shooting: return "拍摄点" case StoreOrderDetailSectionID.actions: return "后续功能" default: return nil } } /// UITableView 代理:处理行选中。 func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) guard let row = tableDataSource.itemIdentifier(for: indexPath) else { return } if row == StoreOrderDetailRowID.orderInfo(6) { copyOrderNumber() return } guard row.hasPrefix("storeDetail:action:") else { return } let index = Int(row.split(separator: ":").last ?? "") ?? -1 let orderNumber = viewModel.display.orderNumber switch index { case 0: appServices.appNavigator.push(.orders(.historicalShooting(orderNumber: orderNumber)), from: self) case 1 where item.orderType == 19: appServices.appNavigator.push(.orders(.multiTravelTaskUpload(orderNumber: orderNumber)), from: self) case 2: appServices.appNavigator.push(.orders(.orderTrailer(orderNumber: orderNumber, title: "视频预告")), from: self) case 3: appServices.appNavigator.push(.orders(.orderTrailer(orderNumber: orderNumber, title: "尾片上传")), from: self) case 4: presentRefund() default: break } } private var displayPayTime: String { let payTime = viewModel.display.payTime if payTime.isEmpty || payTime == "0" || payTime.hasPrefix("1970") { return item.depositPayTime } return payTime } /// empty至Zero相关逻辑。 private func emptyToZero(_ value: String) -> String { value.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty ? "0" : value } } /// 安全下标,避免 section 越界。 private extension Array { subscript(safe index: Int) -> Element? { indices.contains(index) ? self[index] : nil } } /// 核销订单详情页,展示核销列表项摘要信息。 final class WriteOffOrderDetailViewController: SimpleTableDiffableViewController { private let item: WriteOffOrderItem /// 初始化实例。 init(item: WriteOffOrderItem) { self.item = item super.init(style: .insetGrouped) } @available(*, unavailable) required init?(coder: NSCoder) { fatalError() } /// 视图加载完成后的 UI 初始化与数据绑定。 override func viewDidLoad() { super.viewDidLoad() title = "核销详情" tableView.backgroundColor = AppDesignUIKit.pageBackground } /// 构建 Diffable snapshot。 override func buildSnapshot() -> NSDiffableDataSourceSnapshot { var snapshot = NSDiffableDataSourceSnapshot() snapshot.appendSections([0]) snapshot.appendItems([ WriteOffDetailRowID.orderNumber, WriteOffDetailRowID.project, WriteOffDetailRowID.phone, WriteOffDetailRowID.amount, WriteOffDetailRowID.status, WriteOffDetailRowID.verificationTime ], toSection: 0) return snapshot } /// 配置 Cell 展示内容。 override func configureCell(_ cell: UITableViewCell, row: SimpleTableRow, at indexPath: IndexPath) { cell.selectionStyle = .none switch row { case WriteOffDetailRowID.orderNumber: cell.textLabel?.text = "订单号" cell.detailTextLabel?.text = item.orderNumber case WriteOffDetailRowID.project: cell.textLabel?.text = "项目" cell.detailTextLabel?.text = item.projectName case WriteOffDetailRowID.phone: cell.textLabel?.text = "手机号" cell.detailTextLabel?.text = item.userPhone case WriteOffDetailRowID.amount: cell.textLabel?.text = "金额" cell.detailTextLabel?.text = "¥\(item.orderAmount)" case WriteOffDetailRowID.status: cell.textLabel?.text = "状态" cell.detailTextLabel?.text = item.orderStatusName case WriteOffDetailRowID.verificationTime: cell.textLabel?.text = "核销时间" cell.detailTextLabel?.text = item.orderVerificationTime default: break } } }