// // DepositOrderViewControllers.swift // suixinkan // import SnapKit import UIKit /// 押金订单列表页,支持查询、分页、核销和退款。 final class DepositOrderListViewController: UIViewController { private let viewModel = DepositOrderListViewModel() private var orderNumberField = UITextField() private lazy var tableView: UITableView = { let table = UITableView(frame: .zero, style: .insetGrouped) table.dataSource = self table.delegate = self table.register(DepositOrderCell.self, forCellReuseIdentifier: DepositOrderCell.reuseID) return table }() override func viewDidLoad() { super.viewDidLoad() title = "押金订单" view.backgroundColor = AppDesignUIKit.pageBackground view.addSubview(tableView) tableView.snp.makeConstraints { make in make.edges.equalToSuperview() } viewModel.onChange = { [weak self] in self?.tableView.reloadData() } setupHeader() Task { await reload(showLoading: true) } } private func setupHeader() { let header = UIView(frame: CGRect(x: 0, y: 0, width: view.bounds.width, height: 120)) orderNumberField.placeholder = "请输入押金订单号" orderNumberField.borderStyle = .roundedRect orderNumberField.autocorrectionType = .no orderNumberField.autocapitalizationType = .none let detailButton = makePrimaryButton(title: "查看订单详情") detailButton.addTarget(self, action: #selector(openDetail), for: .touchUpInside) header.addSubview(orderNumberField) header.addSubview(detailButton) orderNumberField.snp.makeConstraints { make in make.top.leading.trailing.equalToSuperview().inset(16) make.height.equalTo(44) } detailButton.snp.makeConstraints { make in make.top.equalTo(orderNumberField.snp.bottom).offset(12) make.leading.trailing.equalToSuperview().inset(16) } tableView.tableHeaderView = header } @objc private func openDetail() { let text = orderNumberField.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" guard !text.isEmpty else { return } HomeMenuRouting.pushOrders(.depositDetail(orderNumber: text), from: self) } private func reload(showLoading: Bool) async { if showLoading { await appServices.globalLoading.withLoading { await viewModel.reload(api: appServices.ordersAPI, scenicId: appServices.accountContext.currentScenic?.id, reset: true) } } else { await viewModel.reload(api: appServices.ordersAPI, scenicId: appServices.accountContext.currentScenic?.id, reset: true) } if let message = viewModel.errorMessage { showToast(message) } } private func writeOff(_ item: DepositOrderListItem) { let alert = UIAlertController(title: "确认核销该押金订单?", message: item.orderNumber, preferredStyle: .alert) alert.addAction(UIAlertAction(title: "取消", style: .cancel)) alert.addAction(UIAlertAction(title: "确认核销", style: .default) { [weak self] _ in Task { guard let self else { return } let success = await self.viewModel.writeOff(api: self.appServices.ordersAPI, scenicId: self.appServices.accountContext.currentScenic?.id, orderNumber: item.orderNumber) if success { self.showToast("核销成功") } else if let message = self.viewModel.errorMessage { self.showToast(message) } } }) present(alert, animated: true) } private func refund(_ item: DepositOrderListItem) { let alert = UIAlertController(title: "申请退款", message: "请填写退款原因", preferredStyle: .alert) alert.addTextField { $0.placeholder = "退款原因" } alert.addAction(UIAlertAction(title: "取消", style: .cancel)) alert.addAction(UIAlertAction(title: "提交", style: .default) { [weak self] _ in Task { guard let self else { return } let reason = alert.textFields?.first?.text ?? "" let success = await self.viewModel.refund(api: self.appServices.ordersAPI, scenicId: self.appServices.accountContext.currentScenic?.id, orderNumber: item.orderNumber, reason: reason) if success { self.showToast("退款申请已提交") } else if let message = self.viewModel.errorMessage { self.showToast(message) } } }) present(alert, animated: true) } } extension DepositOrderListViewController: UITableViewDataSource, UITableViewDelegate { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { max(viewModel.orders.count, 1) } func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { "押金订单列表 \(viewModel.orders.count)/\(viewModel.total)" } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { guard !viewModel.orders.isEmpty else { let cell = UITableViewCell() cell.textLabel?.text = viewModel.loading ? "加载中..." : "暂无押金订单" cell.selectionStyle = .none return cell } let cell = tableView.dequeueReusableCell(withIdentifier: DepositOrderCell.reuseID, for: indexPath) as! DepositOrderCell let item = viewModel.orders[indexPath.row] cell.configure(item: item, isOperating: viewModel.operatingOrderNumber == item.orderNumber) cell.onDetail = { [weak self] in self.flatMap { HomeMenuRouting.pushOrders(.depositDetail(orderNumber: item.orderNumber), from: $0) } } cell.onWriteOff = { [weak self] in self?.writeOff(item) } cell.onRefund = { [weak self] in self?.refund(item) } return cell } func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { guard indexPath.row == viewModel.orders.count - 1, viewModel.hasMore else { return } Task { await viewModel.reload(api: appServices.ordersAPI, scenicId: appServices.accountContext.currentScenic?.id, reset: false) } } } private final class DepositOrderCell: UITableViewCell { static let reuseID = "DepositOrderCell" var onDetail: (() -> Void)? var onWriteOff: (() -> Void)? var onRefund: (() -> Void)? private let titleLabel = UILabel() private let statusLabel = UILabel() private let amountLabel = UILabel() override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) selectionStyle = .none titleLabel.font = .systemFont(ofSize: AppMetrics.FontSize.subheadline, weight: .semibold) statusLabel.font = .systemFont(ofSize: AppMetrics.FontSize.caption) amountLabel.font = .systemFont(ofSize: AppMetrics.FontSize.body, weight: .semibold) amountLabel.textColor = AppDesignUIKit.primary let detailButton = UIButton(type: .system) detailButton.setTitle("详情", for: .normal) detailButton.addTarget(self, action: #selector(detailTapped), for: .touchUpInside) let writeOffButton = UIButton(type: .system) writeOffButton.setTitle("核销", for: .normal) writeOffButton.addTarget(self, action: #selector(writeOffTapped), for: .touchUpInside) let refundButton = UIButton(type: .system) refundButton.setTitle("退款", for: .normal) refundButton.addTarget(self, action: #selector(refundTapped), for: .touchUpInside) let buttonRow = UIStackView(arrangedSubviews: [detailButton, writeOffButton, refundButton]) buttonRow.axis = .horizontal buttonRow.distribution = .fillEqually let stack = UIStackView(arrangedSubviews: [titleLabel, amountLabel, buttonRow]) stack.axis = .vertical stack.spacing = 8 contentView.addSubview(stack) contentView.addSubview(statusLabel) stack.snp.makeConstraints { make in make.edges.equalToSuperview().inset(12) } statusLabel.snp.makeConstraints { make in make.trailing.top.equalToSuperview().inset(12) } } @available(*, unavailable) required init?(coder: NSCoder) { fatalError() } func configure(item: DepositOrderListItem, isOperating: Bool) { titleLabel.text = item.orderNumber statusLabel.text = isOperating ? "处理中" : item.statusName amountLabel.text = "¥\(item.amount.isEmpty ? "0.00" : item.amount)" } @objc private func detailTapped() { onDetail?() } @objc private func writeOffTapped() { onWriteOff?() } @objc private func refundTapped() { onRefund?() } } /// 押金订单详情页。 final class DepositOrderDetailViewController: UIViewController { private let orderNumber: String private let viewModel = DepositOrderDetailViewModel() private lazy var tableView: UITableView = { let table = UITableView(frame: .zero, style: .insetGrouped) table.dataSource = self table.backgroundColor = AppDesignUIKit.pageBackground return table }() init(orderNumber: String) { self.orderNumber = orderNumber super.init(nibName: nil, bundle: nil) } @available(*, unavailable) required init?(coder: NSCoder) { fatalError() } override func viewDidLoad() { super.viewDidLoad() title = "押金订单详情" view.addSubview(tableView) tableView.snp.makeConstraints { make in make.edges.equalToSuperview() } Task { await loadDetail() } } private func loadDetail() async { await appServices.globalLoading.withLoading { await viewModel.load( api: appServices.ordersAPI, storeId: appServices.accountContext.currentStore?.id, orderNumber: orderNumber ) } tableView.reloadData() if let message = viewModel.errorMessage { showToast(message) } } } extension DepositOrderDetailViewController: UITableViewDataSource { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { viewModel.detail == nil ? 1 : 8 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = UITableViewCell(style: .value1, reuseIdentifier: nil) cell.selectionStyle = .none guard let detail = viewModel.detail else { cell.textLabel?.text = viewModel.errorMessage ?? (viewModel.loading ? "加载中..." : "暂无详情") return cell } let rows: [(String, String)] = [ ("订单号", detail.orderNumber), ("状态", detail.orderStatusName), ("类型", detail.orderTypeLabel), ("付款金额", detail.actualPayAmount), ("手机号", detail.phone), ("项目", detail.projectName), ("创建时间", detail.createdAt), ("备注", detail.remark) ] cell.textLabel?.text = rows[indexPath.row].0 cell.detailTextLabel?.text = rows[indexPath.row].1 return cell } } /// 押金拍摄信息页。 final class DepositOrderShootingInfoViewController: UIViewController { private let orderNumber: String private let scenicSpotId: Int private let photogUid: Int private let viewModel = DepositOrderShootingInfoViewModel() private lazy var tableView: UITableView = { let table = UITableView(frame: .zero, style: .insetGrouped) table.dataSource = self return table }() init(orderNumber: String, scenicSpotId: Int, photogUid: Int) { self.orderNumber = orderNumber self.scenicSpotId = scenicSpotId self.photogUid = photogUid super.init(nibName: nil, bundle: nil) } @available(*, unavailable) required init?(coder: NSCoder) { fatalError() } override func viewDidLoad() { super.viewDidLoad() title = "押金拍摄信息" view.addSubview(tableView) tableView.snp.makeConstraints { make in make.edges.equalToSuperview() } Task { await loadDetail() } } private func loadDetail() async { await appServices.globalLoading.withLoading { await viewModel.load( api: appServices.ordersAPI, storeId: appServices.accountContext.currentStore?.id, orderNumber: orderNumber, scenicSpotId: scenicSpotId, photogUid: photogUid ) } tableView.reloadData() if let message = viewModel.errorMessage { showToast(message) } } } extension DepositOrderShootingInfoViewController: UITableViewDataSource { func numberOfSections(in tableView: UITableView) -> Int { viewModel.detail == nil ? 1 : 3 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { guard let detail = viewModel.detail else { return 1 } switch section { case 0: return 2 case 1: return detail.materialList.count default: return detail.completeList.count } } func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { guard viewModel.detail != nil else { return nil } switch section { case 1: return "素材" case 2: return "成片" default: return nil } } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { guard let detail = viewModel.detail else { let cell = UITableViewCell() cell.textLabel?.text = viewModel.errorMessage ?? "加载中..." return cell } if indexPath.section == 0 { let cell = UITableViewCell(style: .value1, reuseIdentifier: nil) if indexPath.row == 0 { cell.textLabel?.text = "打卡点" cell.detailTextLabel?.text = detail.scenicSpotName } else { cell.textLabel?.text = "拍摄评分" cell.detailTextLabel?.text = detail.orderComment.map { "\($0.starShooting) 星" } ?? "--" } return cell } let files = indexPath.section == 1 ? detail.materialList : detail.completeList let media = files[indexPath.row] let cell = UITableViewCell(style: .subtitle, reuseIdentifier: nil) cell.textLabel?.text = media.fileName cell.detailTextLabel?.text = media.fileUrl return cell } }