Files
suixinkan_ios_uikit/suixinkan_ios/Features/OperatingArea/ViewControllers/OperatingAreaViewController.swift
汉秋 24a7339b68 Refactor AppServices into grouped bundles and document DI conventions.
Centralize dependency wiring with Session/Context/Network/UI/Runtime bundles, unify leaf ViewControllers on appServices, and add a test initializer with AppServicesTests.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-26 15:24:25 +08:00

147 lines
5.6 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// OperatingAreaViewController.swift
// suixinkan_ios
//
import SnapKit
import UIKit
///
final class OperatingAreaViewController: UIViewController {
private let viewModel = OperatingAreaViewModel()
private let mapView = OperatingAreaMapView()
private let tableView = UITableView(frame: .zero, style: .insetGrouped)
private let summaryLabel = UILabel()
private lazy var blockReasonView = AppContentUnavailableView(title: "无法展示地图", systemImage: "mappin.slash")
private var dataSource: UITableViewDiffableDataSource<Int, String>!
/// UI
override func viewDidLoad() {
super.viewDidLoad()
title = "运营区域"
view.backgroundColor = UIColor(hex: 0xF5F7FA)
setupLayout()
wireViewModel()
Task { await reload(showLoading: true) }
}
private func setupLayout() {
summaryLabel.font = .systemFont(ofSize: AppMetrics.FontSize.subheadline)
summaryLabel.textColor = AppDesign.textSecondary
summaryLabel.numberOfLines = 0
mapView.snp.makeConstraints { make in
make.height.equalTo(320)
}
blockReasonView.isHidden = true
tableView.register(TitleSubtitleTableViewCell.self, forCellReuseIdentifier: TitleSubtitleTableViewCell.reuseIdentifier)
tableView.refreshControl = UIRefreshControl()
tableView.refreshControl?.addTarget(self, action: #selector(onPullRefresh), for: .valueChanged)
dataSource = UITableViewDiffableDataSource<Int, String>(tableView: tableView) { [weak self] tableView, indexPath, itemID in
guard let self else { return UITableViewCell() }
let cell = tableView.dequeueReusableCell(
withIdentifier: TitleSubtitleTableViewCell.reuseIdentifier,
for: indexPath
) as! TitleSubtitleTableViewCell
guard let item = self.viewModel.items.first(where: { String($0.id) == itemID }) else {
return cell
}
let ringCount = self.viewModel.fenceRings.filter { $0.itemId == item.id }.count
cell.configure(
title: item.name.isEmpty ? "未命名区域" : item.name,
subtitle: [item.typeText, item.statusText].filter { !$0.isEmpty }.joined(separator: " · "),
detail: "围栏 \(ringCount)"
)
return cell
}
let headerStack = UIStackView(arrangedSubviews: [summaryLabel, blockReasonView, mapView])
headerStack.axis = .vertical
headerStack.spacing = AppMetrics.Spacing.medium
let headerContainer = UIView()
headerContainer.addSubview(headerStack)
headerStack.snp.makeConstraints { make in
make.edges.equalToSuperview().inset(AppMetrics.Spacing.pageHorizontal)
}
headerContainer.layoutIfNeeded()
let height = headerStack.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height
headerContainer.frame = CGRect(x: 0, y: 0, width: view.bounds.width, height: height + 24)
tableView.tableHeaderView = headerContainer
view.addSubview(tableView)
tableView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
}
private func wireViewModel() {
viewModel.onChange = { [weak self] in
self?.render()
}
}
private func render() {
summaryLabel.text = "\(viewModel.title) · \(scopeText()) · 区域 \(viewModel.summary.areaCount) · 围栏 \(viewModel.summary.fenceCount)"
if let reason = viewModel.blockReason {
blockReasonView.isHidden = false
blockReasonView.update(title: reason.message, systemImage: "mappin.slash")
mapView.isHidden = true
} else {
blockReasonView.isHidden = true
mapView.isHidden = false
mapView.rings = viewModel.fenceRings
}
applyTableSnapshot()
}
/// Diffable snapshot
private func applyTableSnapshot(animated: Bool = true) {
var snapshot = NSDiffableDataSourceSnapshot<Int, String>()
snapshot.appendSections([0])
snapshot.appendItems(viewModel.items.map { String($0.id) }, toSection: 0)
dataSource.apply(snapshot, animatingDifferences: animated)
}
private func scopeText() -> String {
switch viewModel.mode {
case .storeAdmin:
return appServices.accountContext.currentStore?.name ?? "当前店铺"
case .scenicAdmin:
return appServices.accountContext.currentScenic?.name ?? "当前景区"
case nil:
return appServices.accountContext.currentScenic?.name
?? appServices.accountContext.currentStore?.name
?? "未选择业务范围"
}
}
@objc private func onPullRefresh() {
Task {
await reload(showLoading: false)
tableView.refreshControl?.endRefreshing()
}
}
private func reload(showLoading: Bool) async {
if showLoading { appServices.globalLoading.show() }
defer { if showLoading { appServices.globalLoading.hide() } }
await viewModel.reload(
api: appServices.operatingAreaAPI,
accountContext: appServices.accountContext,
permissionContext: appServices.permissionContext
)
if let message = viewModel.errorMessage {
appServices.toastCenter.show(message)
}
}
}
extension OperatingAreaViewModel: ViewModelBindable {}