Files
suixinkan_uikit/suixinkan/UI/Statistics/StatisticsViewController.swift
汉秋 87696a4774 Introduce unified design tokens and migrate Home, Orders, and Statistics UI.
Establish AppColor/AppFont/AppSpacing/AppRadius theming, shared components, and design-system docs so pilot screens align with Android while keeping iOS-native spacing and touch targets.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-06 17:58:18 +08:00

144 lines
4.5 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// StatisticsViewController.swift
// suixinkan
//
import SnapKit
import UIKit
/// Tab
final class StatisticsViewController: BaseViewController {
private let viewModel = StatisticsViewModel()
private let statisticsAPI = NetworkServices.shared.statisticsAPI
private let summaryCardView = StatisticsSummaryCardView()
private let dailyListView = StatisticsDailyListView()
private var hasInitialized = false
private var isLoadingMore = false
override func setupNavigationBar() {
title = "数据"
}
override func setupUI() {
view.backgroundColor = AppColor.pageBackgroundSoft
view.addSubview(summaryCardView)
view.addSubview(dailyListView)
}
override func setupConstraints() {
summaryCardView.snp.makeConstraints { make in
make.top.equalTo(view.safeAreaLayoutGuide).offset(AppSpacing.md)
make.leading.trailing.equalToSuperview().inset(AppSpacing.screenHorizontalInset)
}
dailyListView.snp.makeConstraints { make in
make.top.equalTo(summaryCardView.snp.bottom).offset(AppSpacing.md)
make.leading.trailing.equalToSuperview().inset(AppSpacing.screenHorizontalInset)
make.bottom.equalTo(view.safeAreaLayoutGuide).inset(AppSpacing.md)
}
}
override func bindActions() {
viewModel.onStateChange = { [weak self] in
Task { @MainActor in self?.applyViewModel() }
}
viewModel.onShowMessage = { [weak self] message in
Task { @MainActor in self?.showToast(message) }
}
summaryCardView.onPeriodSelected = { [weak self] period in
self?.selectPeriod(period)
}
dailyListView.onRangeTap = { [weak self] in
self?.presentDateRangeSheet()
}
dailyListView.onRefresh = { [weak self] in
self?.refreshList()
}
dailyListView.onLoadMore = { [weak self] in
self?.loadMoreIfNeeded()
}
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
guard !hasInitialized else { return }
hasInitialized = true
Task { await initializeStatistics() }
}
private func applyViewModel() {
summaryCardView.apply(
selectedPeriod: viewModel.selectedPeriod,
selectedPeriodTime: viewModel.selectedPeriodTime,
summary: viewModel.statistics
)
dailyListView.apply(
rangeText: viewModel.statisticsRangeText,
items: viewModel.statisticsList,
isRefreshing: viewModel.isRefreshing,
canLoadMore: viewModel.canLoadMore
)
if viewModel.isLoadingSummary {
showLoading()
} else {
hideLoading()
}
}
private func initializeStatistics() async {
await viewModel.initStatistics(api: statisticsAPI)
applyViewModel()
}
private func selectPeriod(_ period: String) {
Task {
await viewModel.selectPeriod(period, api: statisticsAPI)
}
}
private func refreshList() {
Task { await viewModel.refreshStatistics(api: statisticsAPI) }
}
private func loadMoreIfNeeded() {
guard viewModel.canLoadMore, !isLoadingMore else { return }
isLoadingMore = true
Task {
await viewModel.loadMore(api: statisticsAPI)
isLoadingMore = false
}
}
private func presentDateRangeSheet() {
let controller = StatisticsDateRangeViewController(
rangeStart: viewModel.rangeStartDate,
rangeEnd: viewModel.rangeEndDate
)
controller.onShortcutSelected = { [weak self, weak controller] shortcut in
guard let self else { return }
Task {
await self.viewModel.updateRangeByShortcut(shortcut, api: self.statisticsAPI)
controller?.dismiss(animated: true)
}
}
controller.onRangeSelected = { [weak self, weak controller] start, end in
guard let self else { return }
Task {
await self.viewModel.updateRangeByCalendar(start: start, end: end, api: self.statisticsAPI)
controller?.dismiss(animated: true)
}
}
if let sheet = controller.sheetPresentationController {
sheet.detents = [.medium(), .large()]
sheet.prefersGrabberVisible = true
}
present(controller, animated: true)
}
}