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>
315 lines
11 KiB
Swift
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) { }
|
|
}
|
|
|
|
/// table行Count相关逻辑。
|
|
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) { }
|
|
}
|
|
|
|
/// 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.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
|
|
}
|
|
|
|
/// table行Count相关逻辑。
|
|
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
|
|
}
|
|
|
|
/// table行Count相关逻辑。
|
|
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) { }
|
|
}
|
|
|
|
/// table行Count相关逻辑。
|
|
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 {}
|