feat: update app workflows and permissions
This commit is contained in:
@ -125,7 +125,7 @@ final class WildPhotographerReportListViewController: BaseViewController {
|
||||
reports.forEach { record in
|
||||
let card = WildReportListCardView(record: record)
|
||||
card.onOpen = { [weak self] in self?.openDetail(record) }
|
||||
card.onShare = { [weak self] in self?.viewModel.shareReport(record) }
|
||||
card.onShare = { [weak self] in self?.shareReport(record) }
|
||||
stack.addArrangedSubview(card)
|
||||
}
|
||||
}
|
||||
@ -148,6 +148,21 @@ final class WildPhotographerReportListViewController: BaseViewController {
|
||||
)
|
||||
}
|
||||
|
||||
private func shareReport(_ record: WildReportRecord) {
|
||||
showLoading()
|
||||
Task { [weak self] in
|
||||
guard let self else { return }
|
||||
let payload = await self.viewModel.shareReport(record, api: self.api)
|
||||
await MainActor.run {
|
||||
self.hideLoading()
|
||||
guard let payload else { return }
|
||||
WeChatShareService.shareMiniProgram(payload) { [weak self] result in
|
||||
self?.showWildReportShareResult(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var currentScenicId: Int? {
|
||||
let scenicId = AppStore.shared.currentScenicId
|
||||
return scenicId > 0 ? scenicId : nil
|
||||
@ -736,7 +751,7 @@ final class WildPhotographerReportDetailViewController: BaseViewController {
|
||||
card.stack.addArrangedSubview(WildReportDetailInfoRowView(title: "举报说明", value: viewModel.descriptionText, multiline: true))
|
||||
card.stack.addArrangedSubview(makeInsetDivider())
|
||||
card.stack.addArrangedSubview(WildReportDetailInfoRowView(title: "微信号/手机号", value: viewModel.contactText))
|
||||
if !viewModel.handleRemarkText.isEmpty {
|
||||
if !viewModel.handleRemarkText.isEmpty, !viewModel.showHandlerInfo {
|
||||
card.stack.addArrangedSubview(makeInsetDivider())
|
||||
card.stack.addArrangedSubview(WildReportDetailInfoRowView(title: "处理备注", value: viewModel.handleRemarkText, multiline: true))
|
||||
}
|
||||
@ -768,13 +783,16 @@ final class WildPhotographerReportDetailViewController: BaseViewController {
|
||||
make.edges.equalToSuperview().inset(UIEdgeInsets(top: 18, left: 18, bottom: 10, right: 18))
|
||||
}
|
||||
card.stack.addArrangedSubview(titleWrap)
|
||||
card.stack.addArrangedSubview(WildReportDetailInfoRowView(title: "处理人", value: info.handlerName))
|
||||
card.stack.addArrangedSubview(WildReportHandlerInfoRowView(title: "处理人", value: info.handlerName))
|
||||
card.stack.addArrangedSubview(makeInsetDivider())
|
||||
card.stack.addArrangedSubview(WildReportDetailInfoRowView(title: "处理人电话", value: info.maskedHandlerPhone, trailingIconName: "phone.fill"))
|
||||
let phoneRow = WildReportHandlerInfoRowView(title: "处理人电话", value: info.maskedHandlerPhone, trailingIconName: "phone.fill")
|
||||
phoneRow.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handlerPhoneTapped)))
|
||||
phoneRow.accessibilityTraits.insert(.button)
|
||||
card.stack.addArrangedSubview(phoneRow)
|
||||
card.stack.addArrangedSubview(makeInsetDivider())
|
||||
card.stack.addArrangedSubview(WildReportDetailInfoRowView(title: "处理时间", value: info.processingAt))
|
||||
card.stack.addArrangedSubview(WildReportHandlerInfoRowView(title: "处理时间", value: info.processingAt))
|
||||
card.stack.addArrangedSubview(makeInsetDivider())
|
||||
card.stack.addArrangedSubview(WildReportDetailInfoRowView(title: "处理完成时间", value: info.processedAt))
|
||||
card.stack.addArrangedSubview(WildReportHandlerInfoRowView(title: "处理完成时间", value: info.processedAt))
|
||||
card.stack.addArrangedSubview(makeInsetDivider())
|
||||
card.stack.addArrangedSubview(WildReportDetailInfoRowView(title: "处理意见", value: info.processOpinion, multiline: true))
|
||||
|
||||
@ -800,7 +818,11 @@ final class WildPhotographerReportDetailViewController: BaseViewController {
|
||||
header.alignment = .center
|
||||
header.addArrangedSubview(WildReportUI.label(item.submitTime, font: .app(.captionMedium), color: AppColor.textSecondary))
|
||||
header.addArrangedSubview(UIView())
|
||||
header.addArrangedSubview(WildReportUI.label("第\(items.count - index)次补充", font: .app(.captionMedium), color: AppColor.primary))
|
||||
header.addArrangedSubview(WildReportUI.label(
|
||||
WildReportSupplementDisplayFormatter.sequenceTitle(forDisplayIndex: index),
|
||||
font: .app(.captionMedium),
|
||||
color: AppColor.primary
|
||||
))
|
||||
itemStack.addArrangedSubview(header)
|
||||
itemStack.addArrangedSubview(WildReportUI.label(item.summaryText, font: .app(.bodyMedium), color: AppColor.textPrimary, lines: 0))
|
||||
card.stack.addArrangedSubview(itemStack)
|
||||
@ -948,12 +970,11 @@ final class WildPhotographerReportDetailViewController: BaseViewController {
|
||||
section.spacing = 12
|
||||
section.layoutMargins = UIEdgeInsets(top: 14, left: 18, bottom: 16, right: 18)
|
||||
section.isLayoutMarginsRelativeArrangement = true
|
||||
section.addArrangedSubview(makeSectionHeader(
|
||||
section.addArrangedSubview(makeCompactMediaSectionHeader(
|
||||
iconName: "photo.on.rectangle.angled",
|
||||
title: "处理凭证",
|
||||
subtitle: info.completionMediaSummary,
|
||||
chevron: false,
|
||||
action: nil
|
||||
summary: info.completionMediaSummary,
|
||||
action: #selector(completionPreviewTapped)
|
||||
))
|
||||
|
||||
let mediaScroll = UIScrollView()
|
||||
@ -1199,10 +1220,33 @@ final class WildPhotographerReportDetailViewController: BaseViewController {
|
||||
return wrapper
|
||||
}
|
||||
|
||||
@objc private func shareTapped() { viewModel.shareReport() }
|
||||
@objc private func shareTapped() {
|
||||
showLoading()
|
||||
Task { [weak self] in
|
||||
guard let self else { return }
|
||||
let payload = await self.viewModel.shareReport(api: self.api)
|
||||
await MainActor.run {
|
||||
self.hideLoading()
|
||||
guard let payload else { return }
|
||||
WeChatShareService.shareMiniProgram(payload) { [weak self] result in
|
||||
self?.showWildReportShareResult(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@objc private func previewTapped() { presentReporterMediaPreview(selectedKey: nil) }
|
||||
@objc private func paymentPreviewTapped() { presentPaymentMediaPreview(selectedKey: nil) }
|
||||
@objc private func completionPreviewTapped() { presentCompletionMediaPreview(selectedKey: nil) }
|
||||
@objc private func locationTapped() { viewModel.showLocationPreview() }
|
||||
@objc private func handlerPhoneTapped() {
|
||||
guard let phone = viewModel.handlerInfo?.handlerPhone.trimmingCharacters(in: .whitespacesAndNewlines),
|
||||
!phone.isEmpty,
|
||||
let url = URL(string: "tel://\(phone)") else {
|
||||
showToast("暂无处理人电话")
|
||||
return
|
||||
}
|
||||
UIApplication.shared.open(url)
|
||||
}
|
||||
|
||||
@objc private func reporterMediaTapped(_ recognizer: UITapGestureRecognizer) {
|
||||
guard let tile = recognizer.view as? WildReportDetailMediaThumbnailView else { return }
|
||||
@ -1367,6 +1411,60 @@ private final class WildReportDetailInfoRowView: UIView {
|
||||
}
|
||||
}
|
||||
|
||||
/// 举报详情页处理信息单行,左侧字段名、右侧内容。
|
||||
private final class WildReportHandlerInfoRowView: UIView {
|
||||
init(title: String, value: String, trailingIconName: String? = nil) {
|
||||
super.init(frame: .zero)
|
||||
|
||||
let titleLabel = WildReportUI.label(title, font: .app(.captionMedium), color: AppColor.textSecondary)
|
||||
titleLabel.setContentHuggingPriority(.required, for: .horizontal)
|
||||
titleLabel.setContentCompressionResistancePriority(.required, for: .horizontal)
|
||||
|
||||
let valueLabel = WildReportUI.label(value, font: .app(.subtitle), color: AppColor.textPrimary, lines: 1)
|
||||
valueLabel.textAlignment = .right
|
||||
valueLabel.lineBreakMode = .byTruncatingTail
|
||||
valueLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
|
||||
|
||||
addSubview(titleLabel)
|
||||
addSubview(valueLabel)
|
||||
|
||||
if let trailingIconName {
|
||||
let icon = UIImageView(image: UIImage(systemName: trailingIconName))
|
||||
icon.tintColor = AppColor.primary
|
||||
icon.contentMode = .scaleAspectFit
|
||||
addSubview(icon)
|
||||
icon.snp.makeConstraints { make in
|
||||
make.trailing.equalToSuperview().inset(18)
|
||||
make.centerY.equalToSuperview()
|
||||
make.size.equalTo(16)
|
||||
}
|
||||
valueLabel.snp.makeConstraints { make in
|
||||
make.trailing.equalTo(icon.snp.leading).offset(-6)
|
||||
}
|
||||
}
|
||||
|
||||
titleLabel.snp.makeConstraints { make in
|
||||
make.leading.equalToSuperview().offset(18)
|
||||
make.centerY.equalToSuperview()
|
||||
}
|
||||
valueLabel.snp.makeConstraints { make in
|
||||
make.leading.greaterThanOrEqualTo(titleLabel.snp.trailing).offset(14)
|
||||
make.centerY.equalToSuperview()
|
||||
if trailingIconName == nil {
|
||||
make.trailing.equalToSuperview().inset(18)
|
||||
}
|
||||
}
|
||||
snp.makeConstraints { make in
|
||||
make.height.equalTo(48)
|
||||
}
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
}
|
||||
|
||||
private extension MediaPreviewItem {
|
||||
init?(wildReportReporterItem item: WildReportReporterMediaItem) {
|
||||
guard let urlText = item.previewKey else { return nil }
|
||||
@ -1393,6 +1491,28 @@ private extension MediaPreviewItem {
|
||||
}
|
||||
}
|
||||
|
||||
private extension BaseViewController {
|
||||
@MainActor
|
||||
func showWildReportShareResult(_ result: WeChatShareResult) {
|
||||
switch result {
|
||||
case .success:
|
||||
showToast("分享成功")
|
||||
case .cancelled:
|
||||
showToast("已取消分享")
|
||||
case .notInstalled:
|
||||
showToast("请先安装微信")
|
||||
case .unsupported:
|
||||
showToast("当前微信版本不支持分享")
|
||||
case .invalidPayload(let message):
|
||||
showToast(message)
|
||||
case .sendFailed:
|
||||
showToast("微信分享发送失败")
|
||||
case .unknown:
|
||||
showToast("微信分享失败")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension WildReportReporterMediaItem {
|
||||
var previewKey: String? {
|
||||
guard let text = fileURL?.trimmingCharacters(in: .whitespacesAndNewlines), !text.isEmpty else { return nil }
|
||||
|
||||
@ -20,6 +20,7 @@ final class WildPhotographerReportSubmitViewController: BaseViewController {
|
||||
private let submitButton = UIButton(type: .system)
|
||||
private var pickerTarget: WildReportPickerTarget = .image
|
||||
private weak var activeInputView: UIView?
|
||||
private var isShowingSubmitLoading = false
|
||||
|
||||
init(
|
||||
homeViewModel: WildPhotographerReportHomeViewModel,
|
||||
@ -109,7 +110,11 @@ final class WildPhotographerReportSubmitViewController: BaseViewController {
|
||||
|
||||
override func bindActions() {
|
||||
viewModel.onStateChange = { [weak self] in
|
||||
Task { @MainActor in self?.rebuildContent() }
|
||||
Task { @MainActor in
|
||||
guard let self else { return }
|
||||
self.updateSubmitLoading()
|
||||
self.rebuildContent()
|
||||
}
|
||||
}
|
||||
viewModel.onShowMessage = { [weak self] message in
|
||||
Task { @MainActor in self?.showToast(message) }
|
||||
@ -152,7 +157,7 @@ final class WildPhotographerReportSubmitViewController: BaseViewController {
|
||||
contentStack.addArrangedSubview(paymentCard)
|
||||
contentStack.setCustomSpacing(16, after: paymentCard)
|
||||
contentStack.addArrangedSubview(submitButton)
|
||||
submitButton.setTitle(viewModel.isSubmitting ? viewModel.submitProgressText : "提交举报", for: .normal)
|
||||
submitButton.setTitle("提交举报", for: .normal)
|
||||
submitButton.isEnabled = !viewModel.isSubmitting
|
||||
submitButton.alpha = viewModel.isSubmitting ? 0.78 : 1
|
||||
submitButton.snp.remakeConstraints { make in
|
||||
@ -561,6 +566,18 @@ final class WildPhotographerReportSubmitViewController: BaseViewController {
|
||||
@objc private func contactChanged() { viewModel.updateContact(contactField.text ?? "") }
|
||||
@objc private func dismissKeyboard() { view.endEditing(true) }
|
||||
|
||||
@MainActor
|
||||
private func updateSubmitLoading() {
|
||||
if viewModel.isSubmitting {
|
||||
guard !isShowingSubmitLoading else { return }
|
||||
isShowingSubmitLoading = true
|
||||
showLoading()
|
||||
} else if isShowingSubmitLoading {
|
||||
isShowingSubmitLoading = false
|
||||
hideLoading()
|
||||
}
|
||||
}
|
||||
|
||||
private func registerKeyboardNotifications() {
|
||||
NotificationCenter.default.addObserver(
|
||||
self,
|
||||
|
||||
@ -23,6 +23,7 @@ final class WildReportSupplementEvidenceViewController: BaseViewController {
|
||||
|
||||
private var pickerTarget: WildReportPickerTarget = .image
|
||||
private weak var activeInputView: UIView?
|
||||
private var isShowingSubmitLoading = false
|
||||
|
||||
init(
|
||||
record: WildReportRecord,
|
||||
@ -111,6 +112,7 @@ final class WildReportSupplementEvidenceViewController: BaseViewController {
|
||||
viewModel.onStateChange = { [weak self] in
|
||||
Task { @MainActor in
|
||||
guard let self else { return }
|
||||
self.updateSubmitLoading()
|
||||
if self.textView.isFirstResponder {
|
||||
self.updateTextInputState()
|
||||
self.updateSubmitButton()
|
||||
@ -271,14 +273,23 @@ final class WildReportSupplementEvidenceViewController: BaseViewController {
|
||||
}
|
||||
|
||||
private func updateSubmitButton() {
|
||||
let title = viewModel.isSubmitting && !viewModel.submitProgressText.isEmpty
|
||||
? viewModel.submitProgressText
|
||||
: "提交补充证据"
|
||||
submitButton.setTitle(title, for: .normal)
|
||||
submitButton.setTitle("提交补充证据", for: .normal)
|
||||
submitButton.isEnabled = !viewModel.isSubmitting
|
||||
submitButton.alpha = viewModel.isSubmitting ? 0.78 : 1
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private func updateSubmitLoading() {
|
||||
if viewModel.isSubmitting {
|
||||
guard !isShowingSubmitLoading else { return }
|
||||
isShowingSubmitLoading = true
|
||||
showLoading()
|
||||
} else if isShowingSubmitLoading {
|
||||
isShowingSubmitLoading = false
|
||||
hideLoading()
|
||||
}
|
||||
}
|
||||
|
||||
private func canAddAttachment(kind: WildReportAttachmentKind, currentCount: Int, maxCount: Int) -> Bool {
|
||||
let ownRemaining = max(0, maxCount - currentCount)
|
||||
let evidenceRemaining = max(0, 9 - viewModel.images.count - viewModel.videos.count)
|
||||
|
||||
Reference in New Issue
Block a user