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>
262 lines
9.2 KiB
Swift
262 lines
9.2 KiB
Swift
//
|
|
// ScenicPermissionViewControllers.swift
|
|
// suixinkan
|
|
//
|
|
// Created by Codex on 2026/6/26.
|
|
//
|
|
|
|
import CoreLocation
|
|
import UIKit
|
|
|
|
/// 景区选择页。
|
|
final class ScenicSelectionViewController: ModuleTableViewController {
|
|
private let viewModel = ScenicSelectionViewModel()
|
|
private let searchBar = UISearchBar()
|
|
private let locationProvider = ScenicSelectionLocationProvider()
|
|
|
|
/// 视图加载完成后的 UI 初始化与数据绑定。
|
|
override func viewDidLoad() {
|
|
title = "选择景区"
|
|
navigationItem.rightBarButtonItem = UIBarButtonItem(
|
|
title: "定位",
|
|
style: .plain,
|
|
target: self,
|
|
action: #selector(requestLocation)
|
|
)
|
|
super.viewDidLoad()
|
|
setupSearchBar()
|
|
wireViewModel(viewModel) { }
|
|
}
|
|
|
|
@objc private func requestLocation() {
|
|
viewModel.currentLocationText = "定位中..."
|
|
locationProvider.onLocation = { [weak self] location in
|
|
self?.viewModel.applyCurrentLocation(
|
|
latitude: location.coordinate.latitude,
|
|
longitude: location.coordinate.longitude
|
|
)
|
|
}
|
|
locationProvider.onFailure = { [weak self] message in
|
|
self?.services.toastCenter.show(message)
|
|
}
|
|
locationProvider.request()
|
|
}
|
|
|
|
/// 初始化SearchBar相关 UI 或状态。
|
|
private func setupSearchBar() {
|
|
searchBar.placeholder = "搜索景区"
|
|
searchBar.delegate = self
|
|
searchBar.frame = CGRect(x: 0, y: 0, width: view.bounds.width, height: 56)
|
|
tableView.tableHeaderView = searchBar
|
|
}
|
|
|
|
/// table行Count相关逻辑。
|
|
override func tableRowCount() -> Int {
|
|
viewModel.filteredItems.count
|
|
}
|
|
|
|
/// 配置Cell展示内容。
|
|
override func configureCell(_ cell: TitleSubtitleTableViewCell, at indexPath: IndexPath) {
|
|
let item = viewModel.filteredItems[indexPath.row]
|
|
cell.configure(title: item.name, subtitle: item.address, detail: item.distanceText)
|
|
}
|
|
|
|
/// didSelectTableRow 回调处理。
|
|
override func didSelectTableRow(at indexPath: IndexPath) {
|
|
let item = viewModel.filteredItems[indexPath.row]
|
|
viewModel.select(
|
|
scenicId: item.id,
|
|
accountContext: services.accountContext,
|
|
permissionContext: services.permissionContext,
|
|
snapshotStore: AccountSnapshotStore()
|
|
)
|
|
navigationController?.popViewController(animated: true)
|
|
}
|
|
|
|
/// 刷新Content。
|
|
override func reloadContent() async {
|
|
viewModel.reload(from: services.accountContext)
|
|
}
|
|
}
|
|
|
|
extension ScenicSelectionViewModel: ViewModelBindable {}
|
|
|
|
extension ScenicSelectionViewController: UISearchBarDelegate {
|
|
/// searchBar相关逻辑。
|
|
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
|
|
viewModel.searchQuery = searchText
|
|
}
|
|
}
|
|
|
|
/// 权限申请页。
|
|
final class PermissionApplyViewController: ModuleTableViewController {
|
|
private let viewModel = PermissionApplyViewModel()
|
|
|
|
/// 视图加载完成后的 UI 初始化与数据绑定。
|
|
override func viewDidLoad() {
|
|
title = "权限申请"
|
|
navigationItem.rightBarButtonItem = UIBarButtonItem(
|
|
title: "提交",
|
|
style: .done,
|
|
target: self,
|
|
action: #selector(submit)
|
|
)
|
|
super.viewDidLoad()
|
|
wireViewModel(viewModel) { }
|
|
}
|
|
|
|
/// 返回列表 section 数量。
|
|
override func numberOfTableSections() -> Int { 2 }
|
|
|
|
/// 返回指定 section 行数。
|
|
override func tableRowCount(in section: Int) -> Int {
|
|
section == 0 ? viewModel.roleOptions.count : viewModel.scenicOptions.count
|
|
}
|
|
|
|
/// section 标题。
|
|
override func tableSectionTitle(for section: Int) -> String? {
|
|
section == 0 ? "选择角色" : "选择景区"
|
|
}
|
|
|
|
/// 提供自定义 Cell。
|
|
override func tableCell(for indexPath: IndexPath, row: ModuleTableRow, in tableView: UITableView) -> UITableViewCell {
|
|
let cell = tableView.dequeueReusableCell(
|
|
withIdentifier: TitleSubtitleTableViewCell.reuseIdentifier,
|
|
for: indexPath
|
|
) as! TitleSubtitleTableViewCell
|
|
if indexPath.section == 0 {
|
|
let role = viewModel.roleOptions[indexPath.row]
|
|
cell.configure(title: role.name, subtitle: role.notes)
|
|
cell.accessoryType = viewModel.selectedRoleId == role.id ? .checkmark : .none
|
|
} else {
|
|
let scenic = viewModel.scenicOptions[indexPath.row]
|
|
cell.configure(title: scenic.name, subtitle: scenic.disabled ? "已有权限" : nil)
|
|
cell.accessoryType = scenic.selected ? .checkmark : .none
|
|
}
|
|
return cell
|
|
}
|
|
|
|
/// 处理行选中。
|
|
override func didSelectTableRow(at indexPath: IndexPath) {
|
|
if indexPath.section == 0 {
|
|
viewModel.selectRole(id: viewModel.roleOptions[indexPath.row].id)
|
|
Task { await viewModel.loadScenicListIfNeeded(api: services.scenicPermissionAPI, force: true) }
|
|
} else {
|
|
viewModel.toggleScenic(id: viewModel.scenicOptions[indexPath.row].id)
|
|
}
|
|
}
|
|
|
|
/// 刷新Content。
|
|
override func reloadContent() async {
|
|
viewModel.bootstrap(rolePermissions: services.permissionContext.rolePermissions)
|
|
if viewModel.selectedRoleId != nil {
|
|
await viewModel.loadScenicListIfNeeded(api: services.scenicPermissionAPI, force: true)
|
|
}
|
|
}
|
|
|
|
/// 提交。
|
|
@objc private func submit() {
|
|
Task {
|
|
await viewModel.submit(api: services.scenicPermissionAPI)
|
|
if viewModel.message == "提交成功,等待审核" {
|
|
services.toastCenter.show(viewModel.message ?? "提交成功")
|
|
navigationController?.popViewController(animated: true)
|
|
} else if let message = viewModel.message {
|
|
services.toastCenter.show(message)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
extension PermissionApplyViewModel: ViewModelBindable {}
|
|
|
|
/// 权限申请状态页。
|
|
final class PermissionApplyStatusViewController: ModuleTableViewController {
|
|
private let viewModel = PermissionApplyStatusViewModel()
|
|
|
|
/// 视图加载完成后的 UI 初始化与数据绑定。
|
|
override func viewDidLoad() {
|
|
title = "申请状态"
|
|
super.viewDidLoad()
|
|
viewModel.onChange = { [weak self] in self?.reloadTable() }
|
|
}
|
|
|
|
/// table行Count相关逻辑。
|
|
override func tableRowCount() -> Int {
|
|
viewModel.pending == nil ? 0 : 4
|
|
}
|
|
|
|
/// 配置Cell展示内容。
|
|
override func configureCell(_ cell: TitleSubtitleTableViewCell, at indexPath: IndexPath) {
|
|
guard let pending = viewModel.pending else { return }
|
|
switch indexPath.row {
|
|
case 0: cell.configure(title: "申请编号", subtitle: pending.code)
|
|
case 1: cell.configure(title: "角色", subtitle: pending.roleName)
|
|
case 2: cell.configure(title: "状态", subtitle: pending.statusLabel)
|
|
default: cell.configure(title: "申请时间", subtitle: pending.createdAt)
|
|
}
|
|
}
|
|
|
|
/// 刷新Content。
|
|
override func reloadContent() async {
|
|
await viewModel.load(api: services.scenicPermissionAPI, applyCode: nil)
|
|
}
|
|
}
|
|
|
|
/// 景区申请页。
|
|
final class ScenicApplicationViewController: ModuleTableViewController {
|
|
private let viewModel = ScenicApplicationViewModel()
|
|
private let nameField = UITextField()
|
|
|
|
/// 视图加载完成后的 UI 初始化与数据绑定。
|
|
override func viewDidLoad() {
|
|
title = "景区申请"
|
|
navigationItem.rightBarButtonItem = UIBarButtonItem(
|
|
title: "提交",
|
|
style: .done,
|
|
target: self,
|
|
action: #selector(submit)
|
|
)
|
|
super.viewDidLoad()
|
|
nameField.placeholder = "景区名称"
|
|
nameField.borderStyle = .roundedRect
|
|
nameField.frame = CGRect(x: 0, y: 0, width: view.bounds.width, height: 52)
|
|
tableView.tableHeaderView = nameField
|
|
wireViewModel(viewModel) { [weak self] in
|
|
if self?.nameField.text?.isEmpty != false {
|
|
self?.nameField.text = self?.viewModel.scenicName
|
|
}
|
|
}
|
|
}
|
|
|
|
/// table行Count相关逻辑。
|
|
override func tableRowCount() -> Int { 3 }
|
|
|
|
/// 配置Cell展示内容。
|
|
override func configureCell(_ cell: TitleSubtitleTableViewCell, at indexPath: IndexPath) {
|
|
switch indexPath.row {
|
|
case 0: cell.configure(title: "省份", subtitle: viewModel.selectedProvince)
|
|
case 1: cell.configure(title: "城市", subtitle: viewModel.selectedCity)
|
|
default: cell.configure(title: "合作类型", subtitle: viewModel.coopType == 1 ? "自营" : "合作")
|
|
}
|
|
}
|
|
|
|
/// 刷新Content。
|
|
override func reloadContent() async {
|
|
await viewModel.loadInitial(api: services.scenicPermissionAPI)
|
|
}
|
|
|
|
/// 提交。
|
|
@objc private func submit() {
|
|
viewModel.scenicName = nameField.text ?? ""
|
|
Task {
|
|
await viewModel.submit(api: services.scenicPermissionAPI, uploader: services.ossUploadService)
|
|
if let message = viewModel.message {
|
|
services.toastCenter.show(message)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
extension ScenicApplicationViewModel: ViewModelBindable {}
|