361 lines
14 KiB
Swift
361 lines
14 KiB
Swift
//
|
||
// 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
|
||
}
|
||
}
|