Refactor AppServices into grouped bundles and document DI conventions.

Centralize dependency wiring with Session/Context/Network/UI/Runtime bundles, unify leaf ViewControllers on appServices, and add a test initializer with AppServicesTests.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-26 15:24:25 +08:00
parent d99a5b1bf8
commit 24a7339b68
10 changed files with 385 additions and 114 deletions

View File

@ -10,7 +10,6 @@ import UIKit
///
final class PhotographerInviteViewController: UIViewController {
private let services = AppServices.shared
private let viewModel = PhotographerInviteViewModel()
private let scrollView = UIScrollView()
private let contentStack = UIStackView()
@ -27,7 +26,7 @@ final class PhotographerInviteViewController: UIViewController {
view.backgroundColor = UIColor(hex: 0xF5F7FA)
setupUI()
viewModel.onChange = { [weak self] in self?.render() }
Task { await viewModel.reload(api: services.inviteAPI) }
Task { await viewModel.reload(api: appServices.inviteAPI) }
}
/// UI UI

View File

@ -15,7 +15,6 @@ final class OperatingAreaViewController: UIViewController {
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() {
@ -112,12 +111,12 @@ final class OperatingAreaViewController: UIViewController {
private func scopeText() -> String {
switch viewModel.mode {
case .storeAdmin:
return services.accountContext.currentStore?.name ?? "当前店铺"
return appServices.accountContext.currentStore?.name ?? "当前店铺"
case .scenicAdmin:
return services.accountContext.currentScenic?.name ?? "当前景区"
return appServices.accountContext.currentScenic?.name ?? "当前景区"
case nil:
return services.accountContext.currentScenic?.name
?? services.accountContext.currentStore?.name
return appServices.accountContext.currentScenic?.name
?? appServices.accountContext.currentStore?.name
?? "未选择业务范围"
}
}
@ -130,16 +129,16 @@ final class OperatingAreaViewController: UIViewController {
}
private func reload(showLoading: Bool) async {
if showLoading { services.globalLoading.show() }
defer { if showLoading { services.globalLoading.hide() } }
if showLoading { appServices.globalLoading.show() }
defer { if showLoading { appServices.globalLoading.hide() } }
await viewModel.reload(
api: services.operatingAreaAPI,
accountContext: services.accountContext,
permissionContext: services.permissionContext
api: appServices.operatingAreaAPI,
accountContext: appServices.accountContext,
permissionContext: appServices.permissionContext
)
if let message = viewModel.errorMessage {
services.toastCenter.show(message)
appServices.toastCenter.show(message)
}
}
}

View File

@ -10,7 +10,6 @@ import UIKit
///
final class PaymentCollectionViewController: UIViewController {
private let services = AppServices.shared
private let viewModel = PaymentCollectionViewModel()
private let qrImageView = UIImageView()
private let statusLabel = UILabel()
@ -25,7 +24,7 @@ final class PaymentCollectionViewController: UIViewController {
view.backgroundColor = UIColor(hex: 0xF5F7FA)
setupUI()
viewModel.onChange = { [weak self] in self?.render() }
Task { await viewModel.loadPayCode(api: services.paymentAPI, scenicId: services.currentScenicId) }
Task { await viewModel.loadPayCode(api: appServices.paymentAPI, scenicId: appServices.currentScenicId) }
}
/// UI UI
@ -76,8 +75,8 @@ final class PaymentCollectionViewController: UIViewController {
@objc private func togglePolling() {
Task {
await viewModel.pollUntilPaymentDetected(
api: services.paymentAPI,
scenicId: services.currentScenicId
api: appServices.paymentAPI,
scenicId: appServices.currentScenicId
)
}
}

View File

@ -141,8 +141,6 @@ final class PunchPointEditorViewController: UIViewController {
private let mapPicker = PunchPointMapPickerView()
private let locationProvider = ForegroundLocationProvider()
private var services: AppServices { AppServices.shared }
init(punchPointId: Int?) {
self.punchPointId = punchPointId
super.init(nibName: nil, bundle: nil)
@ -175,7 +173,7 @@ final class PunchPointEditorViewController: UIViewController {
Task {
guard let punchPointId else { return }
if let detail = try? await services.punchPointAPI.punchPointInfo(id: punchPointId) {
if let detail = try? await appServices.punchPointAPI.punchPointInfo(id: punchPointId) {
viewModel.apply(detail)
syncFieldsFromViewModel()
}
@ -252,7 +250,7 @@ final class PunchPointEditorViewController: UIViewController {
)
syncFieldsFromViewModel()
} catch {
services.toastCenter.show("定位失败:\(error.localizedDescription)")
appServices.toastCenter.show("定位失败:\(error.localizedDescription)")
}
}
}
@ -264,15 +262,15 @@ final class PunchPointEditorViewController: UIViewController {
viewModel.longitudeText = longitudeField.text ?? ""
Task {
let success = await viewModel.submit(
scenicId: services.currentScenicId,
api: services.punchPointAPI,
uploadService: services.ossUploadService
scenicId: appServices.currentScenicId,
api: appServices.punchPointAPI,
uploadService: appServices.ossUploadService
)
if success {
services.toastCenter.show("保存成功")
appServices.toastCenter.show("保存成功")
navigationController?.popViewController(animated: true)
} else if let message = viewModel.errorMessage {
services.toastCenter.show(message)
appServices.toastCenter.show(message)
}
}
}