Files
suixinkan_ios_uikit/suixinkan_ios/Features/Projects/ViewControllers/ProjectViewControllers.swift
汉秋 d99a5b1bf8 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>
2026-06-26 15:16:12 +08:00

315 lines
11 KiB
Swift

//
// ProjectViewControllers.swift
// suixinkan
//
// Created by Codex on 2026/6/26.
//
import UIKit
///
final class ProjectManagementViewController: ModuleTableViewController {
private let viewModel = ProjectManagementViewModel()
/// UI
override func viewDidLoad() {
title = "项目管理"
navigationItem.rightBarButtonItem = UIBarButtonItem(
title: "新建",
style: .plain,
target: self,
action: #selector(createProject)
)
super.viewDidLoad()
wireViewModel(viewModel) { }
}
/// tableCount
override func tableRowCount() -> Int { viewModel.items.count }
/// Cell
override func configureCell(_ cell: TitleSubtitleTableViewCell, at indexPath: IndexPath) {
let item = viewModel.items[indexPath.row]
cell.configure(title: item.name, subtitle: item.statusName, detail: "¥\(item.price)")
}
/// didSelectTableRow
override func didSelectTableRow(at indexPath: IndexPath) {
let item = viewModel.items[indexPath.row]
navigationController?.pushViewController(
ProjectDetailViewController(projectId: item.id, storeMode: false),
animated: true
)
}
/// Content
override func reloadContent() async {
await viewModel.reload(api: services.projectAPI, scenicId: services.currentScenicId)
}
///
@objc private func createProject() {
navigationController?.pushViewController(
ProjectEditorViewController(projectId: nil),
animated: true
)
}
}
extension ProjectManagementViewModel: ViewModelBindable {}
///
final class StoreProjectManagementViewController: ModuleTableViewController {
private let viewModel = StoreProjectManagementViewModel()
/// UI
override func viewDidLoad() {
title = "店铺项目"
super.viewDidLoad()
wireViewModel(viewModel) { }
}
/// tableCount
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.typeName, detail: "¥\(item.price)")
}
/// didSelectTableRow
override func didSelectTableRow(at indexPath: IndexPath) {
let item = viewModel.filteredItems[indexPath.row]
navigationController?.pushViewController(
ProjectDetailViewController(projectId: item.id, storeMode: true),
animated: true
)
}
/// Content
override func reloadContent() async {
await viewModel.reload(api: services.projectAPI, userId: services.userId)
}
}
extension StoreProjectManagementViewModel: ViewModelBindable {}
///
final class ProjectDetailViewController: ModuleTableViewController {
private let projectId: Int
private let storeMode: Bool
private let photographerViewModel = ProjectManagementViewModel()
private let storeViewModel = StoreProjectManagementViewModel()
///
init(projectId: Int, storeMode: Bool) {
self.projectId = projectId
self.storeMode = storeMode
super.init(nibName: nil, bundle: nil)
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
/// UI
override func viewDidLoad() {
title = "项目详情"
super.viewDidLoad()
if storeMode {
storeViewModel.onChange = { [weak self] in self?.reloadTable() }
} else {
photographerViewModel.onChange = { [weak self] in self?.reloadTable() }
}
}
private var detail: PhotographerProjectDetailResponse? {
storeMode ? storeViewModel.selectedDetail : photographerViewModel.selectedDetail
}
/// tableCount
override func tableRowCount() -> Int {
detail == nil ? 0 : 4
}
/// Cell
override func configureCell(_ cell: TitleSubtitleTableViewCell, at indexPath: IndexPath) {
guard let detail else { return }
switch indexPath.row {
case 0: cell.configure(title: "项目名称", subtitle: detail.name)
case 1: cell.configure(title: "简介", subtitle: detail.description)
case 2: cell.configure(title: "价格", subtitle: detail.price)
default: cell.configure(title: "打卡点", subtitle: "\(detail.scenicList.count)")
}
}
/// Content
override func reloadContent() async {
if storeMode {
await storeViewModel.loadDetail(id: projectId, api: services.projectAPI)
} else {
await photographerViewModel.loadDetail(id: projectId, api: services.projectAPI)
}
}
}
///
final class ProjectEditorViewController: ModuleTableViewController {
private let viewModel = ProjectEditorViewModel(mode: .create)
private let nameField = UITextField()
private let descriptionField = UITextField()
private let priceField = UITextField()
private let projectId: Int?
///
init(projectId: Int?) {
self.projectId = projectId
super.init(nibName: nil, bundle: nil)
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
/// UI
override func viewDidLoad() {
title = projectId == nil ? "新建项目" : "编辑项目"
navigationItem.rightBarButtonItem = UIBarButtonItem(
title: "保存",
style: .done,
target: self,
action: #selector(save)
)
super.viewDidLoad()
setupHeader()
wireViewModel(viewModel) { [weak self] in self?.fillFormIfNeeded() }
}
/// Header UI
private func setupHeader() {
nameField.placeholder = "项目名称"
nameField.borderStyle = .roundedRect
descriptionField.placeholder = "项目简介"
descriptionField.borderStyle = .roundedRect
priceField.placeholder = "价格"
priceField.borderStyle = .roundedRect
priceField.keyboardType = .decimalPad
let stack = UIStackView(arrangedSubviews: [nameField, descriptionField, priceField])
stack.axis = .vertical
stack.spacing = 8
stack.frame = CGRect(x: 0, y: 0, width: view.bounds.width, height: 132)
stack.layoutMargins = UIEdgeInsets(top: 12, left: 16, bottom: 12, right: 16)
stack.isLayoutMarginsRelativeArrangement = true
tableView.tableHeaderView = stack
}
/// tableCount
override func tableRowCount() -> Int { 0 }
/// Content
override func reloadContent() async {
guard let projectId else { return }
if let detail = try? await services.projectAPI.projectDetail(id: projectId) {
viewModel.apply(detail)
fillFormIfNeeded()
}
}
/// fill
private func fillFormIfNeeded() {
if nameField.text?.isEmpty != false { nameField.text = viewModel.name }
if descriptionField.text?.isEmpty != false { descriptionField.text = viewModel.descriptionText }
if priceField.text?.isEmpty != false { priceField.text = viewModel.price }
}
/// save
@objc private func save() {
viewModel.name = nameField.text ?? ""
viewModel.descriptionText = descriptionField.text ?? ""
viewModel.price = priceField.text ?? ""
Task {
let userId = Int(services.userId ?? "")
let success = await viewModel.submit(
scenicId: services.currentScenicId,
userId: userId,
api: services.projectAPI,
uploadService: services.ossUploadService
)
if success {
navigationController?.popViewController(animated: true)
} else if let message = viewModel.errorMessage {
services.toastCenter.show(message)
}
}
}
}
extension ProjectEditorViewModel: ViewModelBindable {}
///
final class StoreProjectEditorViewController: ModuleTableViewController {
private let viewModel = StoreProjectEditorViewModel(mode: .create)
private let nameField = UITextField()
///
init(projectId: Int?) {
super.init(nibName: nil, bundle: nil)
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
/// UI
override func viewDidLoad() {
title = "店铺项目"
navigationItem.rightBarButtonItem = UIBarButtonItem(
title: "保存",
style: .done,
target: self,
action: #selector(save)
)
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) { }
}
/// tableCount
override func tableRowCount() -> Int { viewModel.scenicList.count }
/// Cell
override func configureCell(_ cell: TitleSubtitleTableViewCell, at indexPath: IndexPath) {
let scenic = viewModel.scenicList[indexPath.row]
cell.configure(title: scenic.name, subtitle: viewModel.selectedScenicIds.contains(scenic.id) ? "已选择" : nil)
}
/// Content
override func reloadContent() async {
guard let userId = Int(services.userId ?? "") else { return }
await viewModel.loadScenicList(api: services.projectAPI, userId: userId)
}
/// save
@objc private func save() {
viewModel.name = nameField.text ?? ""
Task {
guard let userId = Int(services.userId ?? "") else { return }
let success = await viewModel.submit(
userId: userId,
api: services.projectAPI,
uploadService: services.ossUploadService
)
if success { navigationController?.popViewController(animated: true) }
}
}
}
extension StoreProjectEditorViewModel: ViewModelBindable {}