Advance UIKit rewrite with AMap integration and core UI modules.

Integrate高德 SDK with simulator-safe build flags, add map views for operating area and punch points, refactor main tabs and key feature screens to UIKit with Diffable lists, and document Swift concurrency defaults in AGENTS.md.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-26 15:16:12 +08:00
parent 9edf993432
commit d99a5b1bf8
124 changed files with 5195 additions and 1536 deletions

View File

@ -6,32 +6,127 @@
import SnapKit
import UIKit
// MARK: - Deposit List Diffable
private typealias DepositListSection = Int
private typealias DepositListItem = String
/// /
private enum DepositListItemID {
static let placeholder = "depositList:placeholder"
}
// MARK: - Deposit Detail Diffable
private typealias DepositDetailSection = Int
private typealias DepositDetailRow = String
///
private enum DepositDetailRowID {
static let placeholder = "depositDetail:placeholder"
static func field(_ index: Int) -> String { "depositDetail:field:\(index)" }
}
// MARK: - Deposit Shooting Diffable
private typealias DepositShootingSection = Int
private typealias DepositShootingRow = String
/// section
private enum DepositShootingSectionID {
static let summary = 0
static let material = 1
static let complete = 2
}
///
private enum DepositShootingRowID {
static let placeholder = "depositShooting:placeholder"
static let scenicSpot = "depositShooting:scenicSpot"
static let rating = "depositShooting:rating"
static func material(_ index: Int) -> String { "depositShooting:material:\(index)" }
static func complete(_ index: Int) -> String { "depositShooting:complete:\(index)" }
}
/// 退
final class DepositOrderListViewController: UIViewController {
final class DepositOrderListViewController: UIViewController, UITableViewDelegate {
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
}()
/// Diffable
private var tableDataSource: UITableViewDiffableDataSource<DepositListSection, DepositListItem>!
/// 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?.tableView.reloadData() }
viewModel.onChange = { [weak self] in self?.applyTableSnapshot(reconfigure: true) }
setupHeader()
Task { await reload(showLoading: true) }
}
/// Diffable
private func configureTableDataSource() {
tableDataSource = UITableViewDiffableDataSource<DepositListSection, DepositListItem>(
tableView: tableView
) { [weak self] (tableView: UITableView, indexPath: IndexPath, item: DepositListItem) -> UITableViewCell? in
guard let self else { return UITableViewCell() }
if item == DepositListItemID.placeholder {
let cell = UITableViewCell()
cell.textLabel?.text = self.viewModel.loading ? "加载中..." : "暂无押金订单"
cell.selectionStyle = .none
return cell
}
guard let orderItem = self.viewModel.orders.first(where: { $0.orderNumber == item }) else {
return UITableViewCell()
}
let cell = tableView.dequeueReusableCell(withIdentifier: DepositOrderCell.reuseID, for: indexPath) as! DepositOrderCell
cell.configure(item: orderItem, isOperating: self.viewModel.operatingOrderNumber == orderItem.orderNumber)
cell.onDetail = { [weak self] in
self.flatMap { HomeMenuRouting.pushOrders(.depositDetail(orderNumber: orderItem.orderNumber), from: $0) }
}
cell.onWriteOff = { [weak self] in self?.writeOff(orderItem) }
cell.onRefund = { [weak self] in self?.refund(orderItem) }
return cell
}
applyTableSnapshot(animated: false)
}
/// Diffable snapshot
private func buildTableSnapshot() -> NSDiffableDataSourceSnapshot<DepositListSection, DepositListItem> {
var snapshot = NSDiffableDataSourceSnapshot<DepositListSection, DepositListItem>()
snapshot.appendSections([0])
if viewModel.orders.isEmpty {
snapshot.appendItems([DepositListItemID.placeholder], toSection: 0)
} else {
snapshot.appendItems(viewModel.orders.map(\.orderNumber), toSection: 0)
}
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)
}
/// Header UI
private func setupHeader() {
let header = UIView(frame: CGRect(x: 0, y: 0, width: view.bounds.width, height: 120))
orderNumberField.placeholder = "请输入押金订单号"
@ -55,12 +150,14 @@ final class DepositOrderListViewController: UIViewController {
tableView.tableHeaderView = header
}
/// open
@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 {
@ -72,6 +169,7 @@ final class DepositOrderListViewController: UIViewController {
if let message = viewModel.errorMessage { showToast(message) }
}
/// writeOff
private func writeOff(_ item: DepositOrderListItem) {
let alert = UIAlertController(title: "确认核销该押金订单?", message: item.orderNumber, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "取消", style: .cancel))
@ -86,6 +184,7 @@ final class DepositOrderListViewController: UIViewController {
present(alert, animated: true)
}
/// refund
private func refund(_ item: DepositOrderListItem) {
let alert = UIAlertController(title: "申请退款", message: "请填写退款原因", preferredStyle: .alert)
alert.addTextField { $0.placeholder = "退款原因" }
@ -101,43 +200,25 @@ final class DepositOrderListViewController: UIViewController {
})
present(alert, animated: true)
}
}
extension DepositOrderListViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
max(viewModel.orders.count, 1)
}
/// UITableView section
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
}
/// UITableView
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
guard indexPath.row == viewModel.orders.count - 1, viewModel.hasMore else { return }
guard let item = tableDataSource.itemIdentifier(for: indexPath),
item != DepositListItemID.placeholder,
item == viewModel.orders.last?.orderNumber,
viewModel.hasMore else { return }
Task {
await viewModel.reload(api: appServices.ordersAPI, scenicId: appServices.accountContext.currentScenic?.id, reset: false)
}
}
}
/// DepositOrder Cell
private final class DepositOrderCell: UITableViewCell {
static let reuseID = "DepositOrderCell"
var onDetail: (() -> Void)?
@ -148,6 +229,7 @@ private final class DepositOrderCell: UITableViewCell {
private let statusLabel = UILabel()
private let amountLabel = UILabel()
///
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
selectionStyle = .none
@ -182,14 +264,18 @@ private final class DepositOrderCell: UITableViewCell {
@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)"
}
/// detail
@objc private func detailTapped() { onDetail?() }
/// writeOff
@objc private func writeOffTapped() { onWriteOff?() }
/// refund
@objc private func refundTapped() { onRefund?() }
}
@ -201,11 +287,14 @@ final class DepositOrderDetailViewController: UIViewController {
private lazy var tableView: UITableView = {
let table = UITableView(frame: .zero, style: .insetGrouped)
table.dataSource = self
table.backgroundColor = AppDesignUIKit.pageBackground
return table
}()
/// Diffable
private var tableDataSource: UITableViewDiffableDataSource<DepositDetailSection, DepositDetailRow>!
///
init(orderNumber: String) {
self.orderNumber = orderNumber
super.init(nibName: nil, bundle: nil)
@ -214,14 +303,66 @@ final class DepositOrderDetailViewController: UIViewController {
@available(*, unavailable)
required init?(coder: NSCoder) { fatalError() }
/// UI
override func viewDidLoad() {
super.viewDidLoad()
title = "押金订单详情"
configureTableDataSource()
view.addSubview(tableView)
tableView.snp.makeConstraints { make in make.edges.equalToSuperview() }
Task { await loadDetail() }
}
/// Diffable
private func configureTableDataSource() {
tableDataSource = UITableViewDiffableDataSource<DepositDetailSection, DepositDetailRow>(
tableView: tableView
) { [weak self] (_: UITableView, _: IndexPath, row: DepositDetailRow) -> UITableViewCell? in
guard let self else { return UITableViewCell() }
let cell = UITableViewCell(style: .value1, reuseIdentifier: nil)
cell.selectionStyle = .none
guard let detail = self.viewModel.detail else {
cell.textLabel?.text = self.viewModel.errorMessage ?? (self.viewModel.loading ? "加载中..." : "暂无详情")
return cell
}
let rows: [(String, String)] = [
("订单号", detail.orderNumber),
("状态", detail.orderStatusName),
("类型", detail.orderTypeLabel),
("付款金额", detail.actualPayAmount),
("手机号", detail.phone),
("项目", detail.projectName),
("创建时间", detail.createdAt),
("备注", detail.remark)
]
let index = Int(row.split(separator: ":").last ?? "") ?? 0
if index < rows.count {
cell.textLabel?.text = rows[index].0
cell.detailTextLabel?.text = rows[index].1
}
return cell
}
applyTableSnapshot(animated: false)
}
/// Diffable snapshot
private func buildTableSnapshot() -> NSDiffableDataSourceSnapshot<DepositDetailSection, DepositDetailRow> {
var snapshot = NSDiffableDataSourceSnapshot<DepositDetailSection, DepositDetailRow>()
snapshot.appendSections([0])
if viewModel.detail == nil {
snapshot.appendItems([DepositDetailRowID.placeholder], toSection: 0)
} else {
snapshot.appendItems((0..<8).map { DepositDetailRowID.field($0) }, toSection: 0)
}
return snapshot
}
/// snapshot
private func applyTableSnapshot(animated: Bool = true) {
tableDataSource.apply(buildTableSnapshot(), animatingDifferences: animated)
}
/// Detail
private func loadDetail() async {
await appServices.globalLoading.withLoading {
await viewModel.load(
@ -230,41 +371,13 @@ final class DepositOrderDetailViewController: UIViewController {
orderNumber: orderNumber
)
}
tableView.reloadData()
applyTableSnapshot()
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 {
final class DepositOrderShootingInfoViewController: UIViewController, UITableViewDelegate {
private let orderNumber: String
private let scenicSpotId: Int
@ -273,10 +386,14 @@ final class DepositOrderShootingInfoViewController: UIViewController {
private lazy var tableView: UITableView = {
let table = UITableView(frame: .zero, style: .insetGrouped)
table.dataSource = self
table.delegate = self
return table
}()
/// Diffable
private var tableDataSource: UITableViewDiffableDataSource<DepositShootingSection, DepositShootingRow>!
///
init(orderNumber: String, scenicSpotId: Int, photogUid: Int) {
self.orderNumber = orderNumber
self.scenicSpotId = scenicSpotId
@ -287,14 +404,75 @@ final class DepositOrderShootingInfoViewController: UIViewController {
@available(*, unavailable)
required init?(coder: NSCoder) { fatalError() }
/// UI
override func viewDidLoad() {
super.viewDidLoad()
title = "押金拍摄信息"
configureTableDataSource()
view.addSubview(tableView)
tableView.snp.makeConstraints { make in make.edges.equalToSuperview() }
Task { await loadDetail() }
}
/// Diffable
private func configureTableDataSource() {
tableDataSource = UITableViewDiffableDataSource<DepositShootingSection, DepositShootingRow>(
tableView: tableView
) { [weak self] (_: UITableView, indexPath: IndexPath, row: DepositShootingRow) -> UITableViewCell? in
guard let self else { return UITableViewCell() }
guard let detail = self.viewModel.detail else {
let cell = UITableViewCell()
cell.textLabel?.text = self.viewModel.errorMessage ?? "加载中..."
return cell
}
if row == DepositShootingRowID.scenicSpot || row == DepositShootingRowID.rating {
let cell = UITableViewCell(style: .value1, reuseIdentifier: nil)
if row == DepositShootingRowID.scenicSpot {
cell.textLabel?.text = "打卡点"
cell.detailTextLabel?.text = detail.scenicSpotName
} else {
cell.textLabel?.text = "拍摄评分"
cell.detailTextLabel?.text = detail.orderComment.map { "\($0.starShooting)" } ?? "--"
}
return cell
}
let files: [OrderMediaFile]
if row.hasPrefix("depositShooting:material:") {
files = detail.materialList
} else {
files = detail.completeList
}
let index = Int(row.split(separator: ":").last ?? "") ?? 0
let media = files[index]
let cell = UITableViewCell(style: .subtitle, reuseIdentifier: nil)
cell.textLabel?.text = media.fileName
cell.detailTextLabel?.text = media.fileUrl
return cell
}
applyTableSnapshot(animated: false)
}
/// Diffable snapshot
private func buildTableSnapshot() -> NSDiffableDataSourceSnapshot<DepositShootingSection, DepositShootingRow> {
var snapshot = NSDiffableDataSourceSnapshot<DepositShootingSection, DepositShootingRow>()
guard let detail = viewModel.detail else {
snapshot.appendSections([0])
snapshot.appendItems([DepositShootingRowID.placeholder], toSection: 0)
return snapshot
}
snapshot.appendSections([DepositShootingSectionID.summary, DepositShootingSectionID.material, DepositShootingSectionID.complete])
snapshot.appendItems([DepositShootingRowID.scenicSpot, DepositShootingRowID.rating], toSection: DepositShootingSectionID.summary)
snapshot.appendItems(detail.materialList.indices.map { DepositShootingRowID.material($0) }, toSection: DepositShootingSectionID.material)
snapshot.appendItems(detail.completeList.indices.map { DepositShootingRowID.complete($0) }, toSection: DepositShootingSectionID.complete)
return snapshot
}
/// snapshot
private func applyTableSnapshot(animated: Bool = true) {
tableDataSource.apply(buildTableSnapshot(), animatingDifferences: animated)
}
/// Detail
private func loadDetail() async {
await appServices.globalLoading.withLoading {
await viewModel.load(
@ -305,56 +483,25 @@ final class DepositOrderShootingInfoViewController: UIViewController {
photogUid: photogUid
)
}
tableView.reloadData()
applyTableSnapshot()
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
}
}
/// UITableView section
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
guard viewModel.detail != nil else { return nil }
switch section {
case 1: return "素材"
case 2: return "成片"
guard viewModel.detail != nil,
let sectionID = tableDataSource.snapshot().sectionIdentifiers[safe: section] else { return nil }
switch sectionID {
case DepositShootingSectionID.material: return "素材"
case DepositShootingSectionID.complete: 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
/// section
private extension Array {
subscript(safe index: Int) -> Element? {
indices.contains(index) ? self[index] : nil
}
}