// // LocationReportHistoryViewController.swift // suixinkan // import SnapKit import UIKit /// 位置上报历史页,对齐 Android `LocationReportHistoryScreen`。 final class LocationReportHistoryViewController: BaseViewController, UITableViewDelegate, UITableViewDataSource { private let viewModel: LocationReportHistoryViewModel private let homeAPI: HomeAPI private let filterBar = LocationReportHistoryFilterBar() private let tableView = UITableView(frame: .zero, style: .plain) private let emptyLabel = UILabel() private let footerLabel = UILabel() private let refreshControl = UIRefreshControl() init( viewModel: LocationReportHistoryViewModel = LocationReportHistoryViewModel(), homeAPI: HomeAPI? = nil ) { self.viewModel = viewModel self.homeAPI = homeAPI ?? NetworkServices.shared.homeAPI super.init(nibName: nil, bundle: nil) } @available(*, unavailable) required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func setupNavigationBar() { title = "位置上报历史" } override func setupUI() { tableView.delegate = self tableView.dataSource = self tableView.separatorStyle = .none tableView.backgroundColor = AppColor.pageBackground tableView.register(LocationReportHistoryCell.self, forCellReuseIdentifier: LocationReportHistoryCell.reuseIdentifier) tableView.refreshControl = refreshControl emptyLabel.text = "暂无位置上报记录" emptyLabel.textColor = AppColor.textTertiary emptyLabel.textAlignment = .center emptyLabel.isHidden = true footerLabel.text = "没有更多" footerLabel.font = .systemFont(ofSize: 13) footerLabel.textColor = AppColor.textTertiary footerLabel.textAlignment = .center footerLabel.isHidden = true view.addSubview(filterBar) view.addSubview(tableView) view.addSubview(emptyLabel) view.addSubview(footerLabel) filterBar.snp.makeConstraints { make in make.top.leading.trailing.equalTo(view.safeAreaLayoutGuide) } tableView.snp.makeConstraints { make in make.top.equalTo(filterBar.snp.bottom) make.leading.trailing.bottom.equalTo(view.safeAreaLayoutGuide) } emptyLabel.snp.makeConstraints { make in make.center.equalTo(tableView) } footerLabel.snp.makeConstraints { make in make.centerX.equalToSuperview() make.bottom.equalTo(view.safeAreaLayoutGuide).offset(-12) } } 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) } } refreshControl.addTarget(self, action: #selector(refreshTapped), for: .valueChanged) filterBar.onFilterTap = { [weak self] in self?.presentFilterPicker() } filterBar.onDateFilterTap = { [weak self] in self?.presentDateFilter() } filterBar.onClearDates = { [weak self] in guard let self else { return } Task { await self.viewModel.clearDateRange(api: self.homeAPI) } } Task { await viewModel.loadReportList(api: homeAPI, refresh: true) } } private func applyViewModel() { filterBar.apply( filterTitle: viewModel.selectedFilter.displayName, startText: viewModel.startDateText, endText: viewModel.endDateText, showsDateRange: viewModel.hasDateFilter ) tableView.reloadData() emptyLabel.isHidden = !viewModel.reportList.isEmpty footerLabel.isHidden = viewModel.reportList.isEmpty || viewModel.canLoadMore if !viewModel.isRefreshing { refreshControl.endRefreshing() } } @objc private func refreshTapped() { Task { await viewModel.loadReportList(api: homeAPI, refresh: true) } } private func presentFilterPicker() { let alert = UIAlertController(title: "筛选类型", message: nil, preferredStyle: .actionSheet) LocationReportFilterType.allCases.forEach { filter in alert.addAction(UIAlertAction(title: filter.displayName, style: .default) { [weak self] _ in guard let self else { return } Task { await self.viewModel.selectFilter(filter, api: self.homeAPI) } }) } alert.addAction(UIAlertAction(title: "取消", style: .cancel)) present(alert, animated: true) } private func presentDateFilter() { let alert = UIAlertController(title: "时间筛选", message: nil, preferredStyle: .actionSheet) alert.addAction(UIAlertAction(title: "选择开始日期", style: .default) { [weak self] _ in self?.presentDatePicker(isStart: true) }) alert.addAction(UIAlertAction(title: "选择结束日期", style: .default) { [weak self] _ in self?.presentDatePicker(isStart: false) }) alert.addAction(UIAlertAction(title: "取消", style: .cancel)) present(alert, animated: true) } private func presentDatePicker(isStart: Bool) { let alert = UIAlertController(title: isStart ? "开始日期" : "结束日期", message: "\n\n\n\n\n\n\n\n", preferredStyle: .actionSheet) let picker = UIDatePicker() picker.datePickerMode = .date if #available(iOS 13.4, *) { picker.preferredDatePickerStyle = .wheels } picker.date = isStart ? (viewModel.startDate ?? Date()) : (viewModel.endDate ?? Date()) alert.view.addSubview(picker) picker.snp.makeConstraints { make in make.centerX.equalToSuperview() make.top.equalToSuperview().offset(24) } alert.addAction(UIAlertAction(title: "确定", style: .default) { [weak self] _ in guard let self else { return } Task { if isStart { await self.viewModel.setDateRange(start: picker.date, end: self.viewModel.endDate, api: self.homeAPI) } else { await self.viewModel.setDateRange(start: self.viewModel.startDate, end: picker.date, api: self.homeAPI) } } }) alert.addAction(UIAlertAction(title: "取消", style: .cancel)) present(alert, animated: true) } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { viewModel.reportList.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell( withIdentifier: LocationReportHistoryCell.reuseIdentifier, for: indexPath ) as! LocationReportHistoryCell cell.apply(item: viewModel.reportList[indexPath.row]) return cell } func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { Task { await viewModel.loadMoreIfNeeded(currentIndex: indexPath.row, api: homeAPI) } } }