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:
@ -1,37 +1,146 @@
|
||||
//
|
||||
// OperatingAreaViewController.swift
|
||||
// suixinkan
|
||||
//
|
||||
// Created by Codex on 2026/6/26.
|
||||
// suixinkan_ios
|
||||
//
|
||||
|
||||
import SnapKit
|
||||
import UIKit
|
||||
|
||||
/// 运营区域列表页。
|
||||
final class OperatingAreaViewController: ModuleTableViewController {
|
||||
/// 运营区域页:汇总信息、围栏地图与区域列表。
|
||||
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>!
|
||||
private var services: AppServices { AppServices.shared }
|
||||
|
||||
/// 视图加载完成后的 UI 初始化与数据绑定。
|
||||
override func viewDidLoad() {
|
||||
title = "运营区域"
|
||||
super.viewDidLoad()
|
||||
wireViewModel(viewModel) { }
|
||||
title = "运营区域"
|
||||
view.backgroundColor = UIColor(hex: 0xF5F7FA)
|
||||
setupLayout()
|
||||
wireViewModel()
|
||||
Task { await reload(showLoading: true) }
|
||||
}
|
||||
|
||||
override func tableRowCount() -> Int {
|
||||
viewModel.items.count
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
override func configureCell(_ cell: TitleSubtitleTableViewCell, at indexPath: IndexPath) {
|
||||
let item = viewModel.items[indexPath.row]
|
||||
cell.configure(title: item.name, subtitle: "\(item.typeText) · \(item.statusText)", detail: "围栏 \(viewModel.fenceRings.count) 组")
|
||||
private func wireViewModel() {
|
||||
viewModel.onChange = { [weak self] in
|
||||
self?.render()
|
||||
}
|
||||
}
|
||||
|
||||
override func reloadContent() async {
|
||||
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 services.accountContext.currentStore?.name ?? "当前店铺"
|
||||
case .scenicAdmin:
|
||||
return services.accountContext.currentScenic?.name ?? "当前景区"
|
||||
case nil:
|
||||
return services.accountContext.currentScenic?.name
|
||||
?? services.accountContext.currentStore?.name
|
||||
?? "未选择业务范围"
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func onPullRefresh() {
|
||||
Task {
|
||||
await reload(showLoading: false)
|
||||
tableView.refreshControl?.endRefreshing()
|
||||
}
|
||||
}
|
||||
|
||||
private func reload(showLoading: Bool) async {
|
||||
if showLoading { services.globalLoading.show() }
|
||||
defer { if showLoading { services.globalLoading.hide() } }
|
||||
|
||||
await viewModel.reload(
|
||||
api: services.operatingAreaAPI,
|
||||
accountContext: services.accountContext,
|
||||
permissionContext: services.permissionContext
|
||||
)
|
||||
if let message = viewModel.errorMessage {
|
||||
services.toastCenter.show(message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user