Initial commit: suixinkan_ios UIKit rewrite project.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-26 14:33:31 +08:00
commit 9edf993432
297 changed files with 47151 additions and 0 deletions

View File

@ -0,0 +1,360 @@
//
// 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
}
}