// // WildPhotographerReportListViewController.swift // suixinkan // import SnapKit import UIKit /// 我的举报列表页,支持按状态筛选并进入举报详情。 final class WildPhotographerReportListViewController: BaseViewController { private let viewModel: WildPhotographerReportListViewModel private let segmentedControl = UISegmentedControl(items: WildReportListFilter.allCases.map(\.rawValue)) private let tableView = UITableView(frame: .zero, style: .plain) init(homeViewModel: WildPhotographerReportHomeViewModel) { self.viewModel = WildPhotographerReportListViewModel(homeViewModel: homeViewModel) 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() { view.backgroundColor = AppColor.pageBackgroundSoft segmentedControl.selectedSegmentIndex = 0 segmentedControl.addTarget(self, action: #selector(filterChanged), for: .valueChanged) tableView.backgroundColor = .clear tableView.separatorStyle = .none tableView.delegate = self tableView.dataSource = self tableView.register(WildReportListCell.self, forCellReuseIdentifier: WildReportListCell.reuseIdentifier) tableView.contentInset = UIEdgeInsets(top: AppSpacing.sm, left: 0, bottom: AppSpacing.lg, right: 0) view.addSubview(segmentedControl) view.addSubview(tableView) } override func setupConstraints() { segmentedControl.snp.makeConstraints { make in make.top.equalTo(view.safeAreaLayoutGuide).offset(AppSpacing.sm) make.leading.trailing.equalToSuperview().inset(AppSpacing.md) make.height.equalTo(36) } tableView.snp.makeConstraints { make in make.top.equalTo(segmentedControl.snp.bottom).offset(AppSpacing.xs) make.leading.trailing.bottom.equalTo(view.safeAreaLayoutGuide) } } override func bindActions() { viewModel.onStateChange = { [weak self] in Task { @MainActor in self?.tableView.reloadData() } } viewModel.onShowMessage = { [weak self] message in Task { @MainActor in self?.showToast(message) } } } @objc private func filterChanged() { let filter = WildReportListFilter.allCases[segmentedControl.selectedSegmentIndex] viewModel.selectFilter(filter) } } extension WildPhotographerReportListViewController: UITableViewDataSource, UITableViewDelegate { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { let count = viewModel.filteredReports.count tableView.backgroundView = count == 0 ? WildReportEmptyView(text: "暂无相关举报记录") : nil return count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: WildReportListCell.reuseIdentifier, for: indexPath) as! WildReportListCell let record = viewModel.filteredReports[indexPath.row] cell.apply(record: record) cell.onShare = { [weak self] in self?.viewModel.shareReport(record) } return cell } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) let record = viewModel.filteredReports[indexPath.row] navigationController?.pushViewController( WildPhotographerReportDetailViewController(record: record, homeViewModel: viewModel.homeViewModel), animated: true ) } func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { UITableView.automaticDimension } } /// 我的举报列表卡片 Cell。 final class WildReportListCell: UITableViewCell { static let reuseIdentifier = "WildReportListCell" private let card = WildReportCardView(spacing: AppSpacing.sm) private let titleLabel = UILabel() private let statusView = WildReportStatusView() private let typeLabel = UILabel() private let locationLabel = UILabel() private let timeLabel = UILabel() private let descLabel = UILabel() private let materialLabel = UILabel() var onShare: (() -> Void)? override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) selectionStyle = .none backgroundColor = .clear contentView.addSubview(card) card.snp.makeConstraints { make in make.edges.equalToSuperview().inset(UIEdgeInsets(top: AppSpacing.xs, left: AppSpacing.md, bottom: AppSpacing.xs, right: AppSpacing.md)) } let header = UIStackView() header.axis = .horizontal header.alignment = .center header.spacing = AppSpacing.sm titleLabel.font = .app(.title) titleLabel.textColor = AppColor.textPrimary header.addArrangedSubview(titleLabel) header.addArrangedSubview(UIView()) statusView.snp.makeConstraints { make in make.width.equalTo(64) make.height.equalTo(24) } header.addArrangedSubview(statusView) [typeLabel, locationLabel, timeLabel, descLabel, materialLabel].forEach { label in label.font = .app(.body) label.textColor = AppColor.textSecondary label.numberOfLines = 0 } let shareButton = UIButton(type: .system) shareButton.setTitle("分享", for: .normal) shareButton.setImage(UIImage(systemName: "square.and.arrow.up"), for: .normal) shareButton.titleLabel?.font = .app(.captionMedium) shareButton.addTarget(self, action: #selector(shareTapped), for: .touchUpInside) card.stack.addArrangedSubview(header) card.stack.addArrangedSubview(typeLabel) card.stack.addArrangedSubview(locationLabel) card.stack.addArrangedSubview(timeLabel) card.stack.addArrangedSubview(descLabel) card.stack.addArrangedSubview(materialLabel) card.stack.addArrangedSubview(shareButton) } @available(*, unavailable) required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } func apply(record: WildReportRecord) { titleLabel.text = record.title statusView.apply(status: record.status) typeLabel.text = "举报类型:\(record.reportType.rawValue)" locationLabel.text = "位置:\(record.location)" timeLabel.text = "提交时间:\(record.submitTime)" descLabel.text = record.description materialLabel.text = "材料:\(record.imageCount)张图片 \(record.videoCount)段视频" } @objc private func shareTapped() { onShare?() } } /// 举报详情页。 final class WildPhotographerReportDetailViewController: BaseViewController { private let viewModel: WildPhotographerReportDetailViewModel private let homeViewModel: WildPhotographerReportHomeViewModel private let scrollView = UIScrollView() private let stack = UIStackView() init(record: WildReportRecord, homeViewModel: WildPhotographerReportHomeViewModel) { self.viewModel = WildPhotographerReportDetailViewModel(record: record, homeViewModel: homeViewModel) self.homeViewModel = homeViewModel super.init(nibName: nil, bundle: nil) } @available(*, unavailable) required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func setupNavigationBar() { title = "举报详情" navigationItem.rightBarButtonItem = UIBarButtonItem( image: UIImage(systemName: "square.and.arrow.up"), style: .plain, target: self, action: #selector(shareTapped) ) } override func setupUI() { view.backgroundColor = AppColor.pageBackgroundSoft stack.axis = .vertical stack.spacing = AppSpacing.sm view.addSubview(scrollView) scrollView.addSubview(stack) rebuildContent() } override func setupConstraints() { scrollView.snp.makeConstraints { make in make.edges.equalTo(view.safeAreaLayoutGuide) } stack.snp.makeConstraints { make in make.edges.equalToSuperview().inset(UIEdgeInsets(top: AppSpacing.sm, left: AppSpacing.md, bottom: AppSpacing.lg, right: AppSpacing.md)) make.width.equalTo(scrollView.snp.width).offset(-AppSpacing.md * 2) } } override func bindActions() { viewModel.onShowMessage = { [weak self] message in Task { @MainActor in self?.showToast(message) } } homeViewModel.onStateChange = { [weak self] in Task { @MainActor in self?.rebuildContent() } } } @MainActor private func rebuildContent() { stack.arrangedSubviews.forEach { view in stack.removeArrangedSubview(view) view.removeFromSuperview() } stack.addArrangedSubview(makeHeaderCard()) stack.addArrangedSubview(makeProgressCard()) stack.addArrangedSubview(makeEvidenceCard()) if viewModel.showHandlerInfo { stack.addArrangedSubview(makeHandlerCard()) } stack.addArrangedSubview(makeSupplementCard()) stack.addArrangedSubview(makeActionButtons()) } private func makeHeaderCard() -> UIView { let card = WildReportCardView(spacing: AppSpacing.sm) let header = UIStackView() header.axis = .horizontal header.alignment = .center header.addArrangedSubview(WildReportUI.label(viewModel.heroTitle, font: .app(.metric), color: AppColor.textPrimary)) header.addArrangedSubview(UIView()) let status = WildReportStatusView() status.apply(status: viewModel.record.status) status.snp.makeConstraints { make in make.width.equalTo(64) make.height.equalTo(24) } header.addArrangedSubview(status) card.stack.addArrangedSubview(header) card.stack.addArrangedSubview(WildReportInfoRowView(title: "举报编号", value: viewModel.record.id)) card.stack.addArrangedSubview(WildReportInfoRowView(title: "景区", value: viewModel.scenicAreaName)) card.stack.addArrangedSubview(WildReportInfoRowView(title: "位置", value: viewModel.detailAddress)) card.stack.addArrangedSubview(WildReportInfoRowView(title: "联系方式", value: viewModel.contactText)) return card } private func makeProgressCard() -> UIView { let card = WildReportCardView(spacing: AppSpacing.sm) card.stack.addArrangedSubview(WildReportUI.label("处理进度", font: .app(.title), color: AppColor.textPrimary)) viewModel.progressSteps.forEach { step in let prefix: String switch step.state { case .completed: prefix = "✓" case .current: prefix = "●" case .pending: prefix = "○" } card.stack.addArrangedSubview(WildReportUI.label("\(prefix) \(step.title) \(step.timeText)", font: .app(.body), color: AppColor.textSecondary, lines: 0)) } return card } private func makeEvidenceCard() -> UIView { let card = WildReportCardView(spacing: AppSpacing.sm) card.stack.addArrangedSubview(WildReportUI.label("举报证据", font: .app(.title), color: AppColor.textPrimary)) card.stack.addArrangedSubview(WildReportUI.label(viewModel.descriptionText, font: .app(.body), color: AppColor.textSecondary, lines: 0)) card.stack.addArrangedSubview(WildReportInfoRowView(title: "举报类型", value: viewModel.record.reportType.rawValue)) card.stack.addArrangedSubview(WildReportInfoRowView(title: "举报人材料", value: viewModel.reporterMediaSummary)) let previewButton = WildReportUI.iconButton(title: "预览材料", imageName: "photo.on.rectangle", style: .secondary) previewButton.addTarget(self, action: #selector(previewTapped), for: .touchUpInside) card.stack.addArrangedSubview(previewButton) return card } private func makeHandlerCard() -> UIView { let card = WildReportCardView(spacing: AppSpacing.sm) guard let info = viewModel.handlerInfo else { return card } card.stack.addArrangedSubview(WildReportUI.label("处理信息", font: .app(.title), color: AppColor.textPrimary)) card.stack.addArrangedSubview(WildReportInfoRowView(title: "处理人", value: info.handlerName)) card.stack.addArrangedSubview(WildReportInfoRowView(title: "处理电话", value: info.maskedHandlerPhone)) card.stack.addArrangedSubview(WildReportInfoRowView(title: "处理时间", value: info.processedAt)) card.stack.addArrangedSubview(WildReportUI.label(info.processOpinion, font: .app(.body), color: AppColor.textSecondary, lines: 0)) card.stack.addArrangedSubview(WildReportInfoRowView(title: "处理凭证", value: info.completionMediaSummary)) return card } private func makeSupplementCard() -> UIView { let card = WildReportCardView(spacing: AppSpacing.sm) card.stack.addArrangedSubview(WildReportUI.label("补充证据记录", font: .app(.title), color: AppColor.textPrimary)) let items = viewModel.supplementaryEvidences if items.isEmpty { card.stack.addArrangedSubview(WildReportUI.label("暂无补充证据", font: .app(.body), color: AppColor.textSecondary)) } else { items.enumerated().forEach { index, item in card.stack.addArrangedSubview(WildReportUI.label("第\(items.count - index)次补充 · \(item.submitTime)", font: .app(.bodyMedium), color: AppColor.textPrimary, lines: 0)) card.stack.addArrangedSubview(WildReportUI.label(item.summaryText, font: .app(.body), color: AppColor.textSecondary, lines: 0)) } } return card } private func makeActionButtons() -> UIView { let row = UIStackView() row.axis = .vertical row.spacing = AppSpacing.sm let supplement = AppButton(title: "补充证据") supplement.addTarget(self, action: #selector(supplementTapped), for: .touchUpInside) let map = AppButton(title: "查看附近风险地图", style: .secondary) map.addTarget(self, action: #selector(mapTapped), for: .touchUpInside) row.addArrangedSubview(supplement) row.addArrangedSubview(map) return row } @objc private func shareTapped() { viewModel.shareReport() } @objc private func previewTapped() { viewModel.showMediaPreview(kind: "图片/视频") } @objc private func supplementTapped() { navigationController?.pushViewController( WildReportSupplementEvidenceViewController(reportID: viewModel.record.id, homeViewModel: homeViewModel), animated: true ) } @objc private func mapTapped() { navigationController?.pushViewController( WildReportRiskMapViewController(record: viewModel.record, riskPoints: homeViewModel.riskPoints), animated: true ) } }