Initial commit: suixinkan_ios UIKit rewrite project.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-26 14:33:31 +08:00
commit 9edf993432
297 changed files with 47151 additions and 0 deletions

View File

@ -0,0 +1,285 @@
//
// ProjectViewControllers.swift
// suixinkan
//
// Created by Codex on 2026/6/26.
//
import UIKit
///
final class ProjectManagementViewController: ModuleTableViewController {
private let viewModel = ProjectManagementViewModel()
override func viewDidLoad() {
title = "项目管理"
navigationItem.rightBarButtonItem = UIBarButtonItem(
title: "新建",
style: .plain,
target: self,
action: #selector(createProject)
)
super.viewDidLoad()
wireViewModel(viewModel) { }
}
override func tableRowCount() -> Int { viewModel.items.count }
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)")
}
override func didSelectTableRow(at indexPath: IndexPath) {
let item = viewModel.items[indexPath.row]
navigationController?.pushViewController(
ProjectDetailViewController(projectId: item.id, storeMode: false),
animated: true
)
}
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()
override func viewDidLoad() {
title = "店铺项目"
super.viewDidLoad()
wireViewModel(viewModel) { }
}
override func tableRowCount() -> Int { viewModel.filteredItems.count }
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)")
}
override func didSelectTableRow(at indexPath: IndexPath) {
let item = viewModel.filteredItems[indexPath.row]
navigationController?.pushViewController(
ProjectDetailViewController(projectId: item.id, storeMode: true),
animated: true
)
}
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")
}
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
}
override func tableRowCount() -> Int {
detail == nil ? 0 : 4
}
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)")
}
}
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")
}
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() }
}
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
}
override func tableRowCount() -> Int { 0 }
override func reloadContent() async {
guard let projectId else { return }
if let detail = try? await services.projectAPI.projectDetail(id: projectId) {
viewModel.apply(detail)
fillFormIfNeeded()
}
}
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 }
}
@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")
}
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) { }
}
override func tableRowCount() -> Int { viewModel.scenicList.count }
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)
}
override func reloadContent() async {
guard let userId = Int(services.userId ?? "") else { return }
await viewModel.loadScenicList(api: services.projectAPI, userId: userId)
}
@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 {}