// // WildPhotographerReportSubmitViewController.swift // suixinkan // import SnapKit import UIKit /// 提交举报页面,提供类型、证据、说明、定位和联系方式表单。 final class WildPhotographerReportSubmitViewController: BaseViewController { private let viewModel: WildPhotographerReportSubmitViewModel private let homeViewModel: WildPhotographerReportHomeViewModel private let scrollView = UIScrollView() private let contentStack = UIStackView() private let descriptionView = UITextView() private let contactField = UITextField() private let submitButton = UIButton(type: .system) init( homeViewModel: WildPhotographerReportHomeViewModel, locationProvider: any LocationProviding = LocationProvider.shared ) { self.homeViewModel = homeViewModel self.viewModel = WildPhotographerReportSubmitViewModel( homeViewModel: homeViewModel, locationProvider: locationProvider ) 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 scrollView.keyboardDismissMode = .interactive scrollView.showsVerticalScrollIndicator = false contentStack.axis = .vertical contentStack.spacing = AppSpacing.sm descriptionView.font = .systemFont(ofSize: 15) descriptionView.textColor = AppColor.textPrimary descriptionView.backgroundColor = .clear descriptionView.textContainerInset = UIEdgeInsets(top: 10, left: 8, bottom: 34, right: 8) descriptionView.delegate = self contactField.placeholder = "请输入摄影师微信号或手机号(选填)" contactField.font = .app(.body) contactField.textColor = AppColor.textPrimary contactField.backgroundColor = UIColor(hex: 0xFBFCFE) contactField.layer.cornerRadius = 10 contactField.layer.borderWidth = 1 contactField.layer.borderColor = UIColor(hex: 0xCDD5E1).cgColor contactField.leftView = UIView(frame: CGRect(x: 0, y: 0, width: 12, height: 1)) contactField.leftViewMode = .always contactField.addTarget(self, action: #selector(contactChanged), for: .editingChanged) submitButton.setTitle("提交举报", for: .normal) submitButton.titleLabel?.font = .systemFont(ofSize: 22, weight: .bold) submitButton.setTitleColor(.white, for: .normal) submitButton.backgroundColor = AppColor.primary submitButton.layer.cornerRadius = 14 submitButton.layer.shadowColor = AppColor.primary.cgColor submitButton.layer.shadowOpacity = 0.22 submitButton.layer.shadowRadius = 12 submitButton.layer.shadowOffset = CGSize(width: 0, height: 6) submitButton.addTarget(self, action: #selector(submitTapped), for: .touchUpInside) view.addSubview(scrollView) scrollView.addSubview(contentStack) rebuildContent() } override func setupConstraints() { scrollView.snp.makeConstraints { make in make.edges.equalTo(view.safeAreaLayoutGuide) } contentStack.snp.makeConstraints { make in make.edges.equalTo(scrollView.contentLayoutGuide).inset(UIEdgeInsets(top: 14, left: 18, bottom: 26, right: 18)) make.width.equalTo(scrollView.frameLayoutGuide).offset(-36) } } override func bindActions() { viewModel.onStateChange = { [weak self] in Task { @MainActor in self?.rebuildContent() } } viewModel.onShowMessage = { [weak self] message in Task { @MainActor in self?.showToast(message) } } viewModel.onSubmitSuccess = { [weak self] result in Task { @MainActor in guard let self else { return } self.navigationController?.pushViewController( WildPhotographerReportSuccessViewController( result: result, homeViewModel: self.homeViewModel ), animated: true ) } } } override func viewDidLoad() { super.viewDidLoad() viewModel.loadInitialLocationIfNeeded() } @MainActor private func rebuildContent() { contentStack.arrangedSubviews.forEach { view in contentStack.removeArrangedSubview(view) view.removeFromSuperview() } contentStack.addArrangedSubview(makeRealNameBanner()) contentStack.addArrangedSubview(makeTypeCard()) contentStack.addArrangedSubview(makeEvidenceCard()) contentStack.addArrangedSubview(makeDescriptionCard()) contentStack.addArrangedSubview(makeLocationCard()) contentStack.addArrangedSubview(makeContactCard()) let paymentCard = makePaymentCard() contentStack.addArrangedSubview(paymentCard) contentStack.setCustomSpacing(16, after: paymentCard) contentStack.addArrangedSubview(submitButton) submitButton.snp.remakeConstraints { make in make.height.equalTo(56) } } private func makeRealNameBanner() -> UIView { let banner = UIView() banner.backgroundColor = UIColor(hex: 0xF6FAFF) banner.layer.cornerRadius = 16 banner.layer.borderWidth = 1 banner.layer.borderColor = UIColor(hex: 0xBFD7FF).cgColor let iconWrap = UIView() iconWrap.backgroundColor = AppColor.primary.withAlphaComponent(0.10) iconWrap.layer.cornerRadius = 17 let icon = UIImageView(image: UIImage(systemName: "checkmark.shield.fill")) icon.tintColor = AppColor.primary iconWrap.addSubview(icon) icon.snp.makeConstraints { make in make.center.equalToSuperview() make.size.equalTo(22) } let message = UILabel() message.numberOfLines = 0 message.attributedText = realNameBannerText() banner.addSubview(iconWrap) banner.addSubview(message) iconWrap.snp.makeConstraints { make in make.top.leading.equalToSuperview().offset(16) make.size.equalTo(34) make.bottom.lessThanOrEqualToSuperview().inset(16) } message.snp.makeConstraints { make in make.top.trailing.bottom.equalToSuperview().inset(16) make.leading.equalTo(iconWrap.snp.trailing).offset(12) } return banner } private func makeTypeCard() -> UIView { let card = sectionCard(index: 1, title: "举报类型") let row = UIStackView() row.axis = .horizontal row.spacing = 8 row.distribution = .fillEqually WildReportType.allCases.forEach { type in let button = WildReportTypeOptionButton() button.apply(type: type, isSelected: type == viewModel.selectedReportType) button.addAction(UIAction { [weak self] _ in self?.viewModel.selectReportType(type) }, for: .touchUpInside) button.snp.makeConstraints { make in make.height.equalTo(72) } row.addArrangedSubview(button) } card.stack.addArrangedSubview(row) return card } private func makeEvidenceCard() -> UIView { let card = sectionCard(index: 2, title: "上传现场证据") card.stack.addArrangedSubview(makeEvidenceUploadBlock( icon: "photo.fill", title: "上传图片", subtitle: "支持 JPG、PNG 格式,最多 9 张", attachments: viewModel.images, maxCount: 9, kind: .image )) card.stack.addArrangedSubview(makeDivider()) card.stack.addArrangedSubview(makeEvidenceUploadBlock( icon: "video.fill", title: "上传视频", subtitle: "支持 MP4 格式,时长不超过 60 秒", attachments: viewModel.videos, maxCount: 3, kind: .video )) return card } private func makeDescriptionCard() -> UIView { let card = sectionCard(index: 3, title: "举报说明") descriptionView.text = viewModel.description let inputContainer = UIView() inputContainer.backgroundColor = UIColor(hex: 0xFBFCFE) inputContainer.layer.cornerRadius = 12 inputContainer.layer.borderWidth = 1 inputContainer.layer.borderColor = UIColor(hex: 0xCDD5E1).cgColor let placeholder = UILabel() placeholder.text = "请描述摄影师的位置、特征、行为..." placeholder.font = .systemFont(ofSize: 15) placeholder.textColor = UIColor(hex: 0x9CA3AF) placeholder.isHidden = !viewModel.description.isEmpty let count = WildReportUI.label("\(viewModel.description.count)/500", font: .systemFont(ofSize: 14), color: UIColor(hex: 0x6B7280)) count.textAlignment = .right inputContainer.addSubview(descriptionView) inputContainer.addSubview(placeholder) inputContainer.addSubview(count) descriptionView.snp.makeConstraints { make in make.edges.equalToSuperview() make.height.equalTo(132) } placeholder.snp.makeConstraints { make in make.top.equalToSuperview().offset(18) make.leading.equalToSuperview().offset(14) make.trailing.lessThanOrEqualToSuperview().inset(14) } count.snp.makeConstraints { make in make.trailing.bottom.equalToSuperview().inset(12) } card.stack.addArrangedSubview(inputContainer) return card } private func makeLocationCard() -> UIView { let card = sectionCard(index: 4, title: "举报位置") let container = UIView() container.backgroundColor = UIColor(hex: 0xF6FAFF) container.layer.cornerRadius = 12 container.layer.borderWidth = 1 container.layer.borderColor = UIColor(hex: 0xC8DDFF).cgColor let iconWrap = UIView() iconWrap.backgroundColor = AppColor.primary.withAlphaComponent(0.10) iconWrap.layer.cornerRadius = 21 let icon = UIImageView(image: UIImage(systemName: "mappin.circle.fill")) icon.tintColor = AppColor.primary iconWrap.addSubview(icon) icon.snp.makeConstraints { make in make.center.equalToSuperview() make.size.equalTo(30) } let textStack = UIStackView() textStack.axis = .vertical textStack.spacing = 5 let title = WildReportUI.label(viewModel.locationTitleText, font: .systemFont(ofSize: 15, weight: .semibold), color: AppColor.textPrimary, lines: 2) title.adjustsFontSizeToFitWidth = true title.minimumScaleFactor = 0.78 let subtitle = WildReportUI.label(viewModel.locationSubtitleText, font: .systemFont(ofSize: 12), color: AppColor.textSecondary, lines: 0) textStack.addArrangedSubview(title) textStack.addArrangedSubview(subtitle) let button = UIButton(type: .system) button.setTitle(viewModel.isUpdatingLocation ? "定位中" : "更新定位", for: .normal) button.titleLabel?.font = .systemFont(ofSize: 13, weight: .semibold) button.setTitleColor(AppColor.primary, for: .normal) button.backgroundColor = .white button.layer.cornerRadius = 17 button.layer.borderWidth = 1 button.layer.borderColor = AppColor.primary.cgColor button.isEnabled = !viewModel.isUpdatingLocation button.alpha = viewModel.isUpdatingLocation ? 0.6 : 1 button.addTarget(self, action: #selector(refreshLocationTapped), for: .touchUpInside) container.addSubview(iconWrap) container.addSubview(textStack) container.addSubview(button) iconWrap.snp.makeConstraints { make in make.leading.top.bottom.equalToSuperview().inset(12) make.size.equalTo(42) } button.snp.makeConstraints { make in make.trailing.equalToSuperview().inset(12) make.centerY.equalToSuperview() make.width.equalTo(82) make.height.equalTo(34) } textStack.snp.makeConstraints { make in make.leading.equalTo(iconWrap.snp.trailing).offset(12) make.trailing.equalTo(button.snp.leading).offset(-8) make.centerY.equalToSuperview() make.top.greaterThanOrEqualToSuperview().offset(12) make.bottom.lessThanOrEqualToSuperview().inset(12) } card.stack.addArrangedSubview(container) return card } private func makeContactCard() -> UIView { let card = sectionCard(index: 5, title: "摄影师微信号/手机号", optional: true) contactField.text = viewModel.contact card.stack.addArrangedSubview(contactField) contactField.snp.makeConstraints { make in make.height.equalTo(48) } return card } private func makePaymentCard() -> UIView { let card = sectionCard(index: 6, title: "线下微信/支付宝支付截图", optional: true) let row = UIView() row.backgroundColor = UIColor(hex: 0xFBFCFE) row.layer.cornerRadius = 12 row.layer.borderWidth = 1 row.layer.borderColor = UIColor(hex: 0xCDD5E1).cgColor let iconWrap = UIView() iconWrap.backgroundColor = AppColor.primaryLight iconWrap.layer.cornerRadius = 12 let icon = UIImageView(image: UIImage(systemName: "creditcard.fill")) icon.tintColor = AppColor.primary iconWrap.addSubview(icon) icon.snp.makeConstraints { make in make.center.equalToSuperview() make.size.equalTo(24) } let textStack = UIStackView() textStack.axis = .vertical textStack.spacing = 4 textStack.addArrangedSubview(WildReportUI.label("上传线下微信/支付宝支付截图(选填)", font: .systemFont(ofSize: 15, weight: .semibold), color: AppColor.textPrimary, lines: 0)) textStack.addArrangedSubview(WildReportUI.label("支持 JPG、PNG 格式,最多 3 张", font: .systemFont(ofSize: 12), color: AppColor.textSecondary)) let trailingView: UIView if let first = viewModel.paymentImages.first { trailingView = WildReportAttachmentTileView(attachment: first) { [weak self] in self?.viewModel.removeAttachment(first) } } else { trailingView = WildReportAddAttachmentTileView { [weak self] in self?.viewModel.addPaymentImage() } } row.addSubview(iconWrap) row.addSubview(textStack) row.addSubview(trailingView) iconWrap.snp.makeConstraints { make in make.leading.top.bottom.equalToSuperview().inset(12) make.size.equalTo(44) } trailingView.snp.makeConstraints { make in make.trailing.equalToSuperview().inset(12) make.centerY.equalToSuperview() make.size.equalTo(58) } textStack.snp.makeConstraints { make in make.leading.equalTo(iconWrap.snp.trailing).offset(12) make.trailing.lessThanOrEqualTo(trailingView.snp.leading).offset(-10) make.centerY.equalToSuperview() make.top.greaterThanOrEqualToSuperview().offset(12) make.bottom.lessThanOrEqualToSuperview().inset(12) } card.stack.addArrangedSubview(row) return card } private func sectionCard(index: Int, title: String, optional: Bool = false) -> WildReportCardView { let card = WildReportCardView(spacing: 18, inset: 16) card.layer.cornerRadius = 16 card.stack.addArrangedSubview(WildReportSectionTitleView(index: index, title: title, optional: optional)) return card } private func makeEvidenceUploadBlock( icon: String, title: String, subtitle: String, attachments: [WildReportAttachment], maxCount: Int, kind: WildReportAttachmentKind ) -> UIView { let block = UIStackView() block.axis = .vertical block.spacing = 12 let header = UIStackView() header.axis = .horizontal header.alignment = .center header.spacing = 12 let iconWrap = UIView() iconWrap.backgroundColor = AppColor.primaryLight iconWrap.layer.cornerRadius = 12 let image = UIImageView(image: UIImage(systemName: icon)) image.tintColor = AppColor.primary iconWrap.addSubview(image) image.snp.makeConstraints { make in make.center.equalToSuperview() make.size.equalTo(22) } iconWrap.snp.makeConstraints { make in make.size.equalTo(42) } let textStack = UIStackView() textStack.axis = .vertical textStack.spacing = 3 textStack.addArrangedSubview(WildReportUI.label(title, font: .systemFont(ofSize: 16, weight: .bold), color: AppColor.textPrimary)) textStack.addArrangedSubview(WildReportUI.label(subtitle, font: .systemFont(ofSize: 13), color: AppColor.textSecondary, lines: 0)) header.addArrangedSubview(iconWrap) header.addArrangedSubview(textStack) header.addArrangedSubview(UIView()) block.addArrangedSubview(header) let scroll = UIScrollView() scroll.showsHorizontalScrollIndicator = false let tiles = UIStackView() tiles.axis = .horizontal tiles.spacing = 10 scroll.addSubview(tiles) tiles.snp.makeConstraints { make in make.edges.equalTo(scroll.contentLayoutGuide).inset(UIEdgeInsets(top: 2, left: 0, bottom: 2, right: 0)) make.height.equalTo(scroll.frameLayoutGuide).offset(-4) } attachments.forEach { attachment in tiles.addArrangedSubview(WildReportAttachmentTileView(attachment: attachment) { [weak self] in self?.viewModel.removeAttachment(attachment) }) } let addCount = max(0, min(3, maxCount - attachments.count)) for _ in 0.. UIView { let divider = UIView() divider.backgroundColor = AppColor.border divider.snp.makeConstraints { make in make.height.equalTo(1) } return divider } private func realNameBannerText() -> NSAttributedString { let text = NSMutableAttributedString() [ ("实名登录", AppColor.primary, UIFont.systemFont(ofSize: 16, weight: .bold)), ("后提交,", AppColor.textPrimary, UIFont.systemFont(ofSize: 16, weight: .medium)), ("景区公安", AppColor.primary, UIFont.systemFont(ofSize: 16, weight: .bold)), ("将接收并处理", AppColor.textPrimary, UIFont.systemFont(ofSize: 16, weight: .medium)) ].forEach { value, color, font in text.append(NSAttributedString(string: value, attributes: [ .foregroundColor: color, .font: font ])) } let paragraph = NSMutableParagraphStyle() paragraph.lineSpacing = 4 text.addAttribute(.paragraphStyle, value: paragraph, range: NSRange(location: 0, length: text.length)) return text } @objc private func addImageTapped() { viewModel.addImage() } @objc private func addVideoTapped() { viewModel.addVideo() } @objc private func addPaymentTapped() { viewModel.addPaymentImage() } @objc private func refreshLocationTapped() { viewModel.refreshCurrentLocation() } @objc private func submitTapped() { viewModel.submitReport() } @objc private func contactChanged() { viewModel.updateContact(contactField.text ?? "") } } extension WildPhotographerReportSubmitViewController: UITextViewDelegate { func textViewDidChange(_ textView: UITextView) { viewModel.updateDescription(textView.text) } } /// 提交页步骤标题,复刻参考页左侧蓝色竖条与选填标识。 private final class WildReportSectionTitleView: UIView { init(index: Int, title: String, optional: Bool) { super.init(frame: .zero) let bar = UIView() bar.backgroundColor = AppColor.primary bar.layer.cornerRadius = 2 let label = UILabel() label.font = .systemFont(ofSize: 17, weight: .bold) label.textColor = AppColor.textPrimary label.text = "\(index). \(title)" let optionalLabel = UILabel() optionalLabel.font = .systemFont(ofSize: 14) optionalLabel.textColor = AppColor.textSecondary optionalLabel.text = "(选填)" optionalLabel.isHidden = !optional addSubview(bar) addSubview(label) addSubview(optionalLabel) bar.snp.makeConstraints { make in make.leading.equalToSuperview() make.centerY.equalToSuperview() make.width.equalTo(4) make.height.equalTo(20) } label.snp.makeConstraints { make in make.leading.equalTo(bar.snp.trailing).offset(8) make.top.bottom.equalToSuperview() } optionalLabel.snp.makeConstraints { make in make.leading.equalTo(label.snp.trailing) make.centerY.equalTo(label) make.trailing.lessThanOrEqualToSuperview() } } @available(*, unavailable) required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } } /// 提交页举报类型三列选择按钮。 private final class WildReportTypeOptionButton: UIControl { private let iconView = UIImageView() private let titleLabel = UILabel() override init(frame: CGRect) { super.init(frame: frame) layer.cornerRadius = 12 layer.borderWidth = 1 let stack = UIStackView() stack.axis = .vertical stack.alignment = .center stack.spacing = 6 stack.isUserInteractionEnabled = false iconView.contentMode = .scaleAspectFit titleLabel.font = .systemFont(ofSize: 13, weight: .bold) titleLabel.textAlignment = .center titleLabel.adjustsFontSizeToFitWidth = true titleLabel.minimumScaleFactor = 0.8 titleLabel.lineBreakMode = .byClipping addSubview(stack) stack.addArrangedSubview(iconView) stack.addArrangedSubview(titleLabel) iconView.snp.makeConstraints { make in make.size.equalTo(22) } stack.snp.makeConstraints { make in make.center.equalToSuperview() make.leading.trailing.equalToSuperview().inset(6) } } @available(*, unavailable) required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } func apply(type: WildReportType, isSelected: Bool) { iconView.image = UIImage(systemName: type.iconName) titleLabel.text = type.rawValue backgroundColor = isSelected ? AppColor.primary : UIColor(hex: 0xF6FAFF) layer.borderColor = (isSelected ? AppColor.primary : UIColor(hex: 0xC8DDFF)).cgColor iconView.tintColor = isSelected ? .white : AppColor.textPrimary titleLabel.textColor = isSelected ? .white : AppColor.textPrimary } } /// 提交页附件缩略 tile,使用渐变和删除角标模拟本地 Mock 附件。 private final class WildReportAttachmentTileView: UIControl { private let gradientLayer = CAGradientLayer() private let removeAction: () -> Void init(attachment: WildReportAttachment, onRemove: @escaping () -> Void) { self.removeAction = onRemove super.init(frame: .zero) layer.cornerRadius = 10 clipsToBounds = false gradientLayer.cornerRadius = 10 let colors: [UIColor] = attachment.kind == .video ? [UIColor(hex: 0xB7D8FF), UIColor(hex: 0x4C8FEF)] : [UIColor(hex: 0xCFE6FF), UIColor(hex: 0x7EB4FF)] gradientLayer.colors = colors.map(\.cgColor) gradientLayer.startPoint = CGPoint(x: 0, y: 0) gradientLayer.endPoint = CGPoint(x: 1, y: 1) layer.insertSublayer(gradientLayer, at: 0) let icon = UIImageView(image: UIImage(systemName: attachment.kind == .video ? "play.circle.fill" : "mountain.2.fill")) icon.tintColor = UIColor.white.withAlphaComponent(0.95) icon.contentMode = .scaleAspectFit addSubview(icon) icon.snp.makeConstraints { make in make.center.equalToSuperview() make.size.equalTo(attachment.kind == .video ? 30 : 26) } let removeButton = UIButton(type: .system) removeButton.setImage(UIImage(systemName: "xmark.circle.fill"), for: .normal) removeButton.tintColor = UIColor(hex: 0x8D93A1) removeButton.backgroundColor = .white removeButton.layer.cornerRadius = 9 removeButton.addTarget(self, action: #selector(removeTapped), for: .touchUpInside) addSubview(removeButton) removeButton.snp.makeConstraints { make in make.top.trailing.equalToSuperview().inset(-6) make.size.equalTo(18) } snp.makeConstraints { make in make.size.equalTo(58) } } @available(*, unavailable) required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func layoutSubviews() { super.layoutSubviews() gradientLayer.frame = bounds } @objc private func removeTapped() { removeAction() } } /// 提交页添加附件 tile,白底虚线加号。 private final class WildReportAddAttachmentTileView: UIControl { private let tapAction: () -> Void private let borderLayer = CAShapeLayer() init(action: @escaping () -> Void) { self.tapAction = action super.init(frame: .zero) backgroundColor = .white layer.cornerRadius = 10 borderLayer.strokeColor = UIColor(hex: 0xCDD5E1).cgColor borderLayer.fillColor = UIColor.clear.cgColor borderLayer.lineWidth = 1 borderLayer.lineDashPattern = [5, 4] layer.addSublayer(borderLayer) let icon = UIImageView(image: UIImage(systemName: "plus")) icon.tintColor = UIColor(hex: 0x8D93A1) icon.contentMode = .scaleAspectFit addSubview(icon) icon.snp.makeConstraints { make in make.center.equalToSuperview() make.size.equalTo(30) } addTarget(self, action: #selector(tapped), for: .touchUpInside) snp.makeConstraints { make in make.size.equalTo(58) } } @available(*, unavailable) required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func layoutSubviews() { super.layoutSubviews() borderLayer.path = UIBezierPath(roundedRect: bounds, cornerRadius: 10).cgPath } @objc private func tapped() { tapAction() } } /// 举报成功页面,展示编号、上传摘要和处理进度。 final class WildPhotographerReportSuccessViewController: BaseViewController { private let viewModel: WildPhotographerReportSuccessViewModel private let homeViewModel: WildPhotographerReportHomeViewModel private let stack = UIStackView() init(result: WildReportSubmitResult, homeViewModel: WildPhotographerReportHomeViewModel) { self.viewModel = WildPhotographerReportSuccessViewModel(result: result) 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 = "举报成功" } override func setupUI() { view.backgroundColor = AppColor.pageBackgroundSoft stack.axis = .vertical stack.spacing = AppSpacing.sm view.addSubview(stack) let hero = WildReportCardView(spacing: AppSpacing.md) hero.stack.alignment = .center let icon = UIImageView(image: UIImage(systemName: "checkmark.circle.fill")) icon.tintColor = AppColor.success icon.snp.makeConstraints { make in make.size.equalTo(58) } hero.stack.addArrangedSubview(icon) hero.stack.addArrangedSubview(WildReportUI.label("举报已提交", font: .app(.metric), color: AppColor.textPrimary)) let desc = WildReportUI.label("景区公安已收到线索,将尽快前往现场核实处理", font: .app(.body), color: AppColor.textSecondary, lines: 0) desc.textAlignment = .center hero.stack.addArrangedSubview(desc) stack.addArrangedSubview(hero) let info = WildReportCardView(spacing: AppSpacing.sm) info.stack.addArrangedSubview(WildReportInfoRowView(title: "举报编号", value: viewModel.result.record.id)) info.stack.addArrangedSubview(WildReportInfoRowView(title: "上传材料", value: viewModel.uploadSummary)) info.stack.addArrangedSubview(WildReportInfoRowView(title: "提交时间", value: viewModel.result.record.submitTime)) stack.addArrangedSubview(info) let progress = WildReportCardView(spacing: AppSpacing.sm) progress.stack.addArrangedSubview(WildReportUI.label("处理进度", font: .app(.title), color: AppColor.textPrimary)) viewModel.progressSteps.enumerated().forEach { index, title in progress.stack.addArrangedSubview(WildReportUI.label("\(index + 1). \(title)", font: .app(.body), color: AppColor.textSecondary)) } stack.addArrangedSubview(progress) let detailButton = AppButton(title: "查看处理进度") detailButton.addTarget(self, action: #selector(detailTapped), for: .touchUpInside) let homeButton = AppButton(title: "返回首页", style: .secondary) homeButton.addTarget(self, action: #selector(homeTapped), for: .touchUpInside) stack.addArrangedSubview(detailButton) stack.addArrangedSubview(homeButton) } override func setupConstraints() { stack.snp.makeConstraints { make in make.top.equalTo(view.safeAreaLayoutGuide).offset(AppSpacing.md) make.leading.trailing.equalToSuperview().inset(AppSpacing.md) } } @objc private func detailTapped() { navigationController?.pushViewController( WildPhotographerReportDetailViewController(record: viewModel.result.record, homeViewModel: homeViewModel), animated: true ) } @objc private func homeTapped() { navigationController?.popToViewController( navigationController?.viewControllers.first(where: { $0 is WildPhotographerReportHomeViewController }) ?? self, animated: true ) } }