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,6 +6,31 @@
import SnapKit
import UIKit
// MARK: - Diffable Int / String MainActor Hashable
/// section
private enum OrdersSectionID {
static let header = 0
static let toolbar = 1
static let content = 2
}
private typealias OrdersSection = Int
/// item
private enum OrdersItemID {
static let header = "orders:header"
static let filter = "orders:filter"
static let writeOffAction = "orders:writeOffAction"
static let missingContext = "orders:missingContext"
static let emptyStore = "orders:empty:store"
static let emptyWriteOff = "orders:empty:writeOff"
static func storeOrder(_ orderNumber: String) -> String { "orders:store:\(orderNumber)" }
static func writeOffOrder(_ orderNumber: String) -> String { "orders:writeoff:\(orderNumber)" }
}
private typealias OrdersItem = String
/// Tab
final class OrdersViewController: UIViewController {
@ -13,36 +38,43 @@ final class OrdersViewController: UIViewController {
private var manualOrderNumber = ""
private var scanHintMessage: String?
private lazy var tableView: UITableView = {
let table = UITableView(frame: .zero, style: .grouped)
table.backgroundColor = AppDesignUIKit.pageBackground
table.separatorStyle = .none
table.dataSource = self
table.delegate = self
table.register(OrderEntityCell.self, forCellReuseIdentifier: OrderEntityCell.reuseID)
table.register(WriteOffOrderCell.self, forCellReuseIdentifier: WriteOffOrderCell.reuseID)
table.register(OrdersHeaderCell.self, forCellReuseIdentifier: OrdersHeaderCell.reuseID)
table.register(OrdersFilterCell.self, forCellReuseIdentifier: OrdersFilterCell.reuseID)
table.register(OrdersWriteOffActionCell.self, forCellReuseIdentifier: OrdersWriteOffActionCell.reuseID)
return table
private lazy var collectionView: UICollectionView = {
let layout = makeCollectionLayout()
let collection = UICollectionView(frame: .zero, collectionViewLayout: layout)
collection.backgroundColor = AppDesignUIKit.pageBackground
collection.delegate = self
collection.register(OrdersHeaderCell.self, forCellWithReuseIdentifier: OrdersHeaderCell.reuseID)
collection.register(OrdersFilterCell.self, forCellWithReuseIdentifier: OrdersFilterCell.reuseID)
collection.register(OrdersWriteOffActionCell.self, forCellWithReuseIdentifier: OrdersWriteOffActionCell.reuseID)
collection.register(OrderEntityCell.self, forCellWithReuseIdentifier: OrderEntityCell.reuseID)
collection.register(WriteOffOrderCell.self, forCellWithReuseIdentifier: WriteOffOrderCell.reuseID)
collection.register(OrdersEmptyStateCell.self, forCellWithReuseIdentifier: OrdersEmptyStateCell.reuseID)
return collection
}()
private lazy var dataSource: UICollectionViewDiffableDataSource<OrdersSection, OrdersItem> = {
UICollectionViewDiffableDataSource<OrdersSection, OrdersItem>(collectionView: collectionView) { [weak self] collectionView, indexPath, item in
self?.cell(for: collectionView, at: indexPath, item: item) ?? UICollectionViewCell()
}
}()
private lazy var refreshControl = UIRefreshControl()
/// UI
override func viewDidLoad() {
super.viewDidLoad()
title = "订单"
view.backgroundColor = AppDesignUIKit.pageBackground
view.addSubview(tableView)
tableView.snp.makeConstraints { make in
view.addSubview(collectionView)
collectionView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
refreshControl.addTarget(self, action: #selector(refreshPulled), for: .valueChanged)
tableView.refreshControl = refreshControl
collectionView.refreshControl = refreshControl
viewModel.onChange = { [weak self] in
self?.tableView.reloadData()
self?.applySnapshot()
}
appServices.appRouter.onChange = { [weak self] in
@ -64,6 +96,202 @@ final class OrdersViewController: UIViewController {
}
}
/// Compositional Layout section
private func makeCollectionLayout() -> UICollectionViewCompositionalLayout {
UICollectionViewCompositionalLayout { [weak self] sectionIndex, _ in
guard let self else {
return CollectionDiffableLayout.fullWidthSection()
}
let section = self.dataSource.snapshot().sectionIdentifiers[sectionIndex]
switch section {
case OrdersSectionID.header:
return CollectionDiffableLayout.fullWidthSection(height: 120)
case OrdersSectionID.toolbar:
let height: CGFloat = self.viewModel.selectedEntry == .storeOrders ? 130 : 150
return CollectionDiffableLayout.fullWidthSection(height: height)
case OrdersSectionID.content:
return CollectionDiffableLayout.fullWidthSection(estimatedHeight: 88)
default:
return CollectionDiffableLayout.fullWidthSection()
}
}
}
/// ViewModel Diffable
private func applySnapshot(animatingDifferences: Bool = true) {
var snapshot = NSDiffableDataSourceSnapshot<OrdersSection, OrdersItem>()
snapshot.appendSections([OrdersSectionID.header])
snapshot.appendItems([OrdersItemID.header], toSection: OrdersSectionID.header)
guard currentScenicId != nil else {
dataSource.apply(snapshot, animatingDifferences: animatingDifferences)
return
}
snapshot.appendSections([OrdersSectionID.toolbar, OrdersSectionID.content])
if viewModel.selectedEntry == .storeOrders {
snapshot.appendItems([OrdersItemID.filter], toSection: OrdersSectionID.toolbar)
appendStoreOrderItems(to: &snapshot)
} else {
snapshot.appendItems([OrdersItemID.writeOffAction], toSection: OrdersSectionID.toolbar)
appendWriteOffOrderItems(to: &snapshot)
}
dataSource.apply(snapshot, animatingDifferences: animatingDifferences)
}
/// item
private func appendStoreOrderItems(to snapshot: inout NSDiffableDataSourceSnapshot<OrdersSection, OrdersItem>) {
if viewModel.storeOrders.isEmpty {
guard !(viewModel.loading && viewModel.storeOrders.isEmpty) else { return }
snapshot.appendItems([OrdersItemID.emptyStore], toSection: OrdersSectionID.content)
return
}
snapshot.appendItems(
viewModel.storeOrders.map { OrdersItemID.storeOrder($0.orderNumber) },
toSection: OrdersSectionID.content
)
}
/// item
private func appendWriteOffOrderItems(to snapshot: inout NSDiffableDataSourceSnapshot<OrdersSection, OrdersItem>) {
if viewModel.writeOffOrders.isEmpty {
guard !(viewModel.loading && viewModel.writeOffOrders.isEmpty) else { return }
snapshot.appendItems([OrdersItemID.emptyWriteOff], toSection: OrdersSectionID.content)
return
}
snapshot.appendItems(
viewModel.writeOffOrders.map { OrdersItemID.writeOffOrder($0.orderNumber) },
toSection: OrdersSectionID.content
)
}
/// item dequeue Cell
private func cell(
for collectionView: UICollectionView,
at indexPath: IndexPath,
item: OrdersItem
) -> UICollectionViewCell {
switch item {
case OrdersItemID.header:
let cell = collectionView.dequeueReusableCell(
withReuseIdentifier: OrdersHeaderCell.reuseID,
for: indexPath
) as! OrdersHeaderCell
cell.configure(
selectedEntry: viewModel.selectedEntry,
scenicName: appServices.accountContext.currentScenic?.name ?? "--",
storeTotal: viewModel.storeTotal,
writeOffTotal: viewModel.writeOffTotal,
onSelectEntry: { [weak self] entry in self?.switchEntry(entry) }
)
return cell
case OrdersItemID.filter:
let cell = collectionView.dequeueReusableCell(
withReuseIdentifier: OrdersFilterCell.reuseID,
for: indexPath
) as! OrdersFilterCell
let statusTitle = OrderFilters.statusFilters.first(where: { $0.id == viewModel.selectedStatus })?.title ?? "全部"
cell.configure(
statusTitle: statusTitle,
phone: viewModel.searchPhone,
onStatus: { [weak self] in self?.presentStatusFilter() },
onDate: { [weak self] in self?.presentDateFilter() },
onPhoneChange: { [weak self] text in self?.viewModel.searchPhone = text },
onSearch: { [weak self] in Task { await self?.reload(showLoading: true) } }
)
return cell
case OrdersItemID.writeOffAction:
let cell = collectionView.dequeueReusableCell(
withReuseIdentifier: OrdersWriteOffActionCell.reuseID,
for: indexPath
) as! OrdersWriteOffActionCell
cell.configure(
manualOrderNumber: manualOrderNumber,
isVerifying: viewModel.isVerifying,
hint: scanHintMessage,
onScan: { [weak self] in self?.presentScanner() },
onManualChange: { [weak self] text in self?.manualOrderNumber = text },
onVerify: { [weak self] in
guard let self, !self.manualOrderNumber.isEmpty else { return }
self.confirmVerify(orderNumber: self.manualOrderNumber)
}
)
return cell
case OrdersItemID.missingContext:
let cell = collectionView.dequeueReusableCell(
withReuseIdentifier: OrdersEmptyStateCell.reuseID,
for: indexPath
) as! OrdersEmptyStateCell
cell.embed(
makeEmptyStateView(
title: "缺少经营上下文",
message: "请先在首页选择景区后查看订单。",
systemImage: "mountain.2"
),
preferredHeight: 360
)
return cell
case OrdersItemID.emptyStore:
let cell = collectionView.dequeueReusableCell(
withReuseIdentifier: OrdersEmptyStateCell.reuseID,
for: indexPath
) as! OrdersEmptyStateCell
cell.embed(
makeEmptyStateView(title: "暂无订单", message: "可切换筛选条件或下拉刷新。", systemImage: "tray"),
preferredHeight: 260
)
return cell
case OrdersItemID.emptyWriteOff:
let cell = collectionView.dequeueReusableCell(
withReuseIdentifier: OrdersEmptyStateCell.reuseID,
for: indexPath
) as! OrdersEmptyStateCell
cell.embed(
makeEmptyStateView(title: "暂无核销订单", message: "可下拉刷新或切换景区查看。", systemImage: "tray"),
preferredHeight: 260
)
return cell
default:
if item.hasPrefix("orders:store:") {
let orderNumber = String(item.dropFirst("orders:store:".count))
guard let order = viewModel.storeOrders.first(where: { $0.orderNumber == orderNumber }) else {
return UICollectionViewCell()
}
let cell = collectionView.dequeueReusableCell(
withReuseIdentifier: OrderEntityCell.reuseID,
for: indexPath
) as! OrderEntityCell
cell.configure(item: order)
return cell
}
if item.hasPrefix("orders:writeoff:") {
let orderNumber = String(item.dropFirst("orders:writeoff:".count))
guard let order = viewModel.writeOffOrders.first(where: { $0.orderNumber == orderNumber }) else {
return UICollectionViewCell()
}
let cell = collectionView.dequeueReusableCell(
withReuseIdentifier: WriteOffOrderCell.reuseID,
for: indexPath
) as! WriteOffOrderCell
cell.configure(
item: order,
isVerifying: viewModel.currentVerifyingOrderNumber == order.orderNumber
)
return cell
}
return UICollectionViewCell()
}
}
///
@objc private func refreshPulled() {
Task {
await reload(showLoading: false)
@ -75,6 +303,7 @@ final class OrdersViewController: UIViewController {
private var currentStoreId: Int? { appServices.accountContext.currentStore?.id }
private var currentRoleId: Int? { appServices.permissionContext.currentRole?.id }
///
private func reload(showLoading: Bool) async {
do {
try await appServices.globalLoading.withOptionalLoading(showLoading, message: "加载订单...") {
@ -91,6 +320,7 @@ final class OrdersViewController: UIViewController {
}
}
///
private func loadMore() async {
do {
if viewModel.selectedEntry == .storeOrders {
@ -111,6 +341,7 @@ final class OrdersViewController: UIViewController {
}
}
/// /
private func switchEntry(_ entry: OrdersEntry) {
guard viewModel.selectedEntry != entry else { return }
viewModel.selectedEntry = entry
@ -118,6 +349,7 @@ final class OrdersViewController: UIViewController {
Task { await reload(showLoading: true) }
}
///
private func verify(orderNumber: String) async {
guard let scenicId = currentScenicId else { return }
do {
@ -135,12 +367,14 @@ final class OrdersViewController: UIViewController {
}
}
///
private func consumePendingScanCodeIfNeeded() async {
guard viewModel.selectedEntry == .verificationOrders,
let code = appServices.appRouter.consumePendingOrderScanCode() else { return }
handleScanResult(code)
}
///
private func handleScanResult(_ rawCode: String) {
guard let parsed = viewModel.matchedWriteOffOrder(for: rawCode) else {
showToast("未识别到有效订单号")
@ -150,10 +384,11 @@ final class OrdersViewController: UIViewController {
confirmVerify(orderNumber: parsed.orderNumber)
} else {
scanHintMessage = "扫码成功,当前列表未找到该订单"
tableView.reloadData()
applySnapshot()
}
}
///
private func confirmVerify(orderNumber: String) {
let alert = UIAlertController(title: "确认核销该订单?", message: orderNumber, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "取消", style: .cancel))
@ -163,6 +398,7 @@ final class OrdersViewController: UIViewController {
present(alert, animated: true)
}
///
private func presentScanner() {
let scanner = OrderCodeScannerViewController()
scanner.onScanResult = { [weak self, weak scanner] result in
@ -180,6 +416,7 @@ final class OrdersViewController: UIViewController {
present(nav, animated: true)
}
///
private func presentDateFilter() {
let alert = UIAlertController(title: "时间筛选", message: "选择开始和结束日期", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "清除筛选", style: .destructive) { [weak self] _ in
@ -198,6 +435,7 @@ final class OrdersViewController: UIViewController {
present(alert, animated: true)
}
/// ActionSheet
private func presentStatusFilter() {
let sheet = UIAlertController(title: "订单状态", message: nil, preferredStyle: .actionSheet)
for filter in OrderFilters.statusFilters {
@ -211,143 +449,51 @@ final class OrdersViewController: UIViewController {
}
}
extension OrdersViewController: UITableViewDataSource, UITableViewDelegate {
func numberOfSections(in tableView: UITableView) -> Int {
guard currentScenicId != nil else { return 1 }
return viewModel.selectedEntry == .storeOrders ? 3 : 3
}
// MARK: - UICollectionViewDelegate
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 { return 1 }
if section == 1 { return viewModel.selectedEntry == .storeOrders ? 1 : 1 }
if currentScenicId == nil { return 1 }
if viewModel.selectedEntry == .storeOrders {
return max(viewModel.storeOrders.count, viewModel.loading && viewModel.storeOrders.isEmpty ? 0 : 1)
}
return max(viewModel.writeOffOrders.count, viewModel.loading && viewModel.writeOffOrders.isEmpty ? 0 : 1)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.section == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: OrdersHeaderCell.reuseID, for: indexPath) as! OrdersHeaderCell
let services = appServices
cell.configure(
selectedEntry: viewModel.selectedEntry,
scenicName: services.accountContext.currentScenic?.name ?? "--",
storeTotal: viewModel.storeTotal,
writeOffTotal: viewModel.writeOffTotal,
onSelectEntry: { [weak self] entry in self?.switchEntry(entry) }
)
return cell
}
if currentScenicId == nil {
let cell = UITableViewCell()
cell.selectionStyle = .none
cell.backgroundColor = .clear
cell.contentView.subviews.forEach { $0.removeFromSuperview() }
let empty = makeEmptyStateView(title: "缺少经营上下文", message: "请先在首页选择景区后查看订单。", systemImage: "mountain.2")
cell.contentView.addSubview(empty)
empty.snp.makeConstraints { make in
make.edges.equalToSuperview()
make.height.equalTo(360)
}
return cell
}
if indexPath.section == 1 {
if viewModel.selectedEntry == .storeOrders {
let cell = tableView.dequeueReusableCell(withIdentifier: OrdersFilterCell.reuseID, for: indexPath) as! OrdersFilterCell
let statusTitle = OrderFilters.statusFilters.first(where: { $0.id == viewModel.selectedStatus })?.title ?? "全部"
cell.configure(
statusTitle: statusTitle,
phone: viewModel.searchPhone,
onStatus: { [weak self] in self?.presentStatusFilter() },
onDate: { [weak self] in self?.presentDateFilter() },
onPhoneChange: { [weak self] text in self?.viewModel.searchPhone = text },
onSearch: { [weak self] in Task { await self?.reload(showLoading: true) } }
)
return cell
}
let cell = tableView.dequeueReusableCell(withIdentifier: OrdersWriteOffActionCell.reuseID, for: indexPath) as! OrdersWriteOffActionCell
cell.configure(
manualOrderNumber: manualOrderNumber,
isVerifying: viewModel.isVerifying,
hint: scanHintMessage,
onScan: { [weak self] in self?.presentScanner() },
onManualChange: { [weak self] text in self?.manualOrderNumber = text },
onVerify: { [weak self] in
guard let self, !self.manualOrderNumber.isEmpty else { return }
self.confirmVerify(orderNumber: self.manualOrderNumber)
}
)
return cell
}
if viewModel.selectedEntry == .storeOrders {
if viewModel.storeOrders.isEmpty {
let cell = UITableViewCell()
cell.selectionStyle = .none
cell.backgroundColor = .clear
cell.contentView.subviews.forEach { $0.removeFromSuperview() }
let empty = makeEmptyStateView(title: "暂无订单", message: "可切换筛选条件或下拉刷新。", systemImage: "tray")
cell.contentView.addSubview(empty)
empty.snp.makeConstraints { make in make.edges.equalToSuperview(); make.height.equalTo(260) }
return cell
}
let item = viewModel.storeOrders[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: OrderEntityCell.reuseID, for: indexPath) as! OrderEntityCell
cell.configure(item: item)
return cell
}
if viewModel.writeOffOrders.isEmpty {
let cell = UITableViewCell()
cell.selectionStyle = .none
cell.backgroundColor = .clear
cell.contentView.subviews.forEach { $0.removeFromSuperview() }
let empty = makeEmptyStateView(title: "暂无核销订单", message: "可下拉刷新或切换景区查看。", systemImage: "tray")
cell.contentView.addSubview(empty)
empty.snp.makeConstraints { make in make.edges.equalToSuperview(); make.height.equalTo(260) }
return cell
}
let item = viewModel.writeOffOrders[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: WriteOffOrderCell.reuseID, for: indexPath) as! WriteOffOrderCell
cell.configure(item: item, isVerifying: viewModel.currentVerifyingOrderNumber == item.orderNumber)
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
guard indexPath.section == 2 else { return }
if viewModel.selectedEntry == .storeOrders, indexPath.row < viewModel.storeOrders.count {
HomeMenuRouting.pushOrders(.storeDetail(viewModel.storeOrders[indexPath.row]), from: self)
} else if viewModel.selectedEntry == .verificationOrders, indexPath.row < viewModel.writeOffOrders.count {
HomeMenuRouting.pushOrders(.writeOffDetail(viewModel.writeOffOrders[indexPath.row]), from: self)
extension OrdersViewController: UICollectionViewDelegate {
///
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
collectionView.deselectItem(at: indexPath, animated: true)
guard let item = dataSource.itemIdentifier(for: indexPath) else { return }
if item.hasPrefix("orders:store:") {
let orderNumber = String(item.dropFirst("orders:store:".count))
guard let order = viewModel.storeOrders.first(where: { $0.orderNumber == orderNumber }) else { return }
HomeMenuRouting.pushOrders(.storeDetail(order), from: self)
} else if item.hasPrefix("orders:writeoff:") {
let orderNumber = String(item.dropFirst("orders:writeoff:".count))
guard let order = viewModel.writeOffOrders.first(where: { $0.orderNumber == orderNumber }) else { return }
HomeMenuRouting.pushOrders(.writeOffDetail(order), from: self)
}
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
guard indexPath.section == 2 else { return }
///
func collectionView(
_ collectionView: UICollectionView,
willDisplay cell: UICollectionViewCell,
forItemAt indexPath: IndexPath
) {
guard let item = dataSource.itemIdentifier(for: indexPath) else { return }
let isLast: Bool
if viewModel.selectedEntry == .storeOrders {
isLast = indexPath.row == viewModel.storeOrders.count - 1
if item.hasPrefix("orders:store:") {
let orderNumber = String(item.dropFirst("orders:store:".count))
isLast = orderNumber == viewModel.storeOrders.last?.orderNumber
} else if item.hasPrefix("orders:writeoff:") {
let orderNumber = String(item.dropFirst("orders:writeoff:".count))
isLast = orderNumber == viewModel.writeOffOrders.last?.orderNumber
} else {
isLast = indexPath.row == viewModel.writeOffOrders.count - 1
isLast = false
}
if isLast {
Task { await loadMore() }
}
if isLast { Task { await loadMore() } }
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath.section == 0 { return 120 }
if indexPath.section == 1 { return viewModel.selectedEntry == .storeOrders ? 130 : 150 }
return UITableView.automaticDimension
}
}
// MARK: - Cells
private final class OrdersHeaderCell: UITableViewCell {
/// Cell
private final class OrdersHeaderCell: UICollectionViewCell {
static let reuseID = "OrdersHeaderCell"
private var onSelectEntry: ((OrdersEntry) -> Void)?
@ -356,16 +502,16 @@ private final class OrdersHeaderCell: UITableViewCell {
private let leftPill = UILabel()
private let rightPill = UILabel()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
selectionStyle = .none
/// Cell
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .clear
let card = UIView()
card.backgroundColor = .white
card.layer.cornerRadius = 8
contentView.addSubview(card)
card.snp.makeConstraints { make in
make.edges.equalToSuperview().inset(UIEdgeInsets(top: 4, left: 16, bottom: 4, right: 16))
make.edges.equalToSuperview().inset(UIEdgeInsets(top: 4, left: 0, bottom: 4, right: 0))
}
let segmentBackground = UIView()
@ -410,7 +556,14 @@ private final class OrdersHeaderCell: UITableViewCell {
@available(*, unavailable)
required init?(coder: NSCoder) { fatalError() }
func configure(selectedEntry: OrdersEntry, scenicName: String, storeTotal: Int, writeOffTotal: Int, onSelectEntry: @escaping (OrdersEntry) -> Void) {
///
func configure(
selectedEntry: OrdersEntry,
scenicName: String,
storeTotal: Int,
writeOffTotal: Int,
onSelectEntry: @escaping (OrdersEntry) -> Void
) {
self.onSelectEntry = onSelectEntry
updateSegment(storeButton, title: "订单管理", selected: selectedEntry == .storeOrders)
updateSegment(verifyButton, title: "核销订单", selected: selectedEntry == .verificationOrders)
@ -423,6 +576,7 @@ private final class OrdersHeaderCell: UITableViewCell {
}
}
///
private func updateSegment(_ button: UIButton, title: String, selected: Bool) {
button.setTitle(title, for: .normal)
button.titleLabel?.font = .systemFont(ofSize: AppMetrics.FontSize.subheadline, weight: selected ? .semibold : .medium)
@ -431,26 +585,29 @@ private final class OrdersHeaderCell: UITableViewCell {
button.layer.cornerRadius = 6
}
///
@objc private func storeTapped() { onSelectEntry?(.storeOrders) }
///
@objc private func verifyTapped() { onSelectEntry?(.verificationOrders) }
}
private final class OrdersFilterCell: UITableViewCell, UITextFieldDelegate {
/// Cell /
private final class OrdersFilterCell: UICollectionViewCell, UITextFieldDelegate {
static let reuseID = "OrdersFilterCell"
private let phoneField = UITextField()
private var onPhoneChange: ((String) -> Void)?
private var onSearch: (() -> Void)?
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
selectionStyle = .none
/// Cell
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .clear
let card = UIView()
card.backgroundColor = .white
card.layer.cornerRadius = 8
contentView.addSubview(card)
card.snp.makeConstraints { make in
make.edges.equalToSuperview().inset(UIEdgeInsets(top: 4, left: 16, bottom: 4, right: 16))
make.edges.equalToSuperview().inset(UIEdgeInsets(top: 4, left: 0, bottom: 4, right: 0))
}
phoneField.delegate = self
phoneField.keyboardType = .phonePad
@ -470,32 +627,44 @@ private final class OrdersFilterCell: UITableViewCell, UITextFieldDelegate {
@available(*, unavailable)
required init?(coder: NSCoder) { fatalError() }
func configure(statusTitle: String, phone: String, onStatus: @escaping () -> Void, onDate: @escaping () -> Void, onPhoneChange: @escaping (String) -> Void, onSearch: @escaping () -> Void) {
///
func configure(
statusTitle: String,
phone: String,
onStatus: @escaping () -> Void,
onDate: @escaping () -> Void,
onPhoneChange: @escaping (String) -> Void,
onSearch: @escaping () -> Void
) {
phoneField.text = phone
self.onPhoneChange = onPhoneChange
self.onSearch = onSearch
}
/// ViewModel
func textFieldDidChangeSelection(_ textField: UITextField) {
onPhoneChange?(textField.text ?? "")
}
}
private final class OrdersWriteOffActionCell: UITableViewCell, UITextFieldDelegate {
/// Cell
private final class OrdersWriteOffActionCell: UICollectionViewCell, UITextFieldDelegate {
static let reuseID = "OrdersWriteOffActionCell"
private let manualField = UITextField()
private var onManualChange: ((String) -> Void)?
private var onVerify: (() -> Void)?
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
selectionStyle = .none
/// Cell
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .clear
let card = UIView()
card.backgroundColor = .white
card.layer.cornerRadius = 8
contentView.addSubview(card)
card.snp.makeConstraints { make in make.edges.equalToSuperview().inset(UIEdgeInsets(top: 4, left: 16, bottom: 4, right: 16)) }
card.snp.makeConstraints { make in
make.edges.equalToSuperview().inset(UIEdgeInsets(top: 4, left: 0, bottom: 4, right: 0))
}
manualField.delegate = self
manualField.placeholder = "手动输入订单号"
@ -516,33 +685,45 @@ private final class OrdersWriteOffActionCell: UITableViewCell, UITextFieldDelega
@available(*, unavailable)
required init?(coder: NSCoder) { fatalError() }
func configure(manualOrderNumber: String, isVerifying: Bool, hint: String?, onScan: @escaping () -> Void, onManualChange: @escaping (String) -> Void, onVerify: @escaping () -> Void) {
///
func configure(
manualOrderNumber: String,
isVerifying: Bool,
hint: String?,
onScan: @escaping () -> Void,
onManualChange: @escaping (String) -> Void,
onVerify: @escaping () -> Void
) {
manualField.text = manualOrderNumber
self.onManualChange = onManualChange
self.onVerify = onVerify
}
///
func textFieldDidChangeSelection(_ textField: UITextField) {
onManualChange?(textField.text ?? "")
}
}
private final class OrderEntityCell: UITableViewCell {
/// Cell
private final class OrderEntityCell: UICollectionViewCell {
static let reuseID = "OrderEntityCell"
private let titleLabel = UILabel()
private let statusLabel = UILabel()
private let amountLabel = UILabel()
private let phoneLabel = UILabel()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
selectionStyle = .default
/// Cell
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .clear
let card = UIView()
card.backgroundColor = .white
card.layer.cornerRadius = 8
contentView.addSubview(card)
card.snp.makeConstraints { make in make.edges.equalToSuperview().inset(UIEdgeInsets(top: 4, left: 16, bottom: 4, right: 16)) }
card.snp.makeConstraints { make in
make.edges.equalToSuperview().inset(UIEdgeInsets(top: 4, left: 0, bottom: 4, right: 0))
}
titleLabel.font = .systemFont(ofSize: AppMetrics.FontSize.subheadline, weight: .semibold)
statusLabel.font = .systemFont(ofSize: AppMetrics.FontSize.caption, weight: .medium)
@ -563,6 +744,7 @@ private final class OrderEntityCell: UITableViewCell {
@available(*, unavailable)
required init?(coder: NSCoder) { fatalError() }
///
func configure(item: OrderEntity) {
titleLabel.text = item.orderNumber
statusLabel.text = item.orderStatusName
@ -571,19 +753,23 @@ private final class OrderEntityCell: UITableViewCell {
}
}
private final class WriteOffOrderCell: UITableViewCell {
/// Cell
private final class WriteOffOrderCell: UICollectionViewCell {
static let reuseID = "WriteOffOrderCell"
private let titleLabel = UILabel()
private let subtitleLabel = UILabel()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
/// Cell
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .clear
let card = UIView()
card.backgroundColor = .white
card.layer.cornerRadius = 8
contentView.addSubview(card)
card.snp.makeConstraints { make in make.edges.equalToSuperview().inset(UIEdgeInsets(top: 4, left: 16, bottom: 4, right: 16)) }
card.snp.makeConstraints { make in
make.edges.equalToSuperview().inset(UIEdgeInsets(top: 4, left: 0, bottom: 4, right: 0))
}
titleLabel.font = .systemFont(ofSize: AppMetrics.FontSize.subheadline, weight: .semibold)
subtitleLabel.font = .systemFont(ofSize: AppMetrics.FontSize.caption)
subtitleLabel.textColor = AppDesignUIKit.textSecondary
@ -597,8 +783,34 @@ private final class WriteOffOrderCell: UITableViewCell {
@available(*, unavailable)
required init?(coder: NSCoder) { fatalError() }
///
func configure(item: WriteOffOrderItem, isVerifying: Bool) {
titleLabel.text = item.orderNumber
subtitleLabel.text = isVerifying ? "核销中..." : (item.projectName.isEmpty ? item.userPhone : item.projectName)
}
}
/// Cell
private final class OrdersEmptyStateCell: UICollectionViewCell {
static let reuseID = "OrdersEmptyStateCell"
private var heightConstraint: Constraint?
/// Cell
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .clear
}
@available(*, unavailable)
required init?(coder: NSCoder) { fatalError() }
///
func embed(_ emptyView: UIView, preferredHeight: CGFloat) {
contentView.subviews.forEach { $0.removeFromSuperview() }
contentView.addSubview(emptyView)
emptyView.snp.makeConstraints { make in
make.edges.equalToSuperview()
heightConstraint = make.height.equalTo(preferredHeight).constraint
}
}
}