feat: add wild photographer report mock flow
This commit is contained in:
@ -0,0 +1,685 @@
|
||||
//
|
||||
// WildPhotographerReportViewModels.swift
|
||||
// suixinkan
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import MapKit
|
||||
import UIKit
|
||||
|
||||
/// 举报摄影师首页 ViewModel,持有 Mock 举报数据并供子页面共享。
|
||||
final class WildPhotographerReportHomeViewModel {
|
||||
private(set) var pageState: WildReportPageState = .normal
|
||||
private(set) var reports: [WildReportRecord] = []
|
||||
private(set) var riskPoints: [WildReportRiskPoint] = []
|
||||
private(set) var supplementaryEvidenceMap: [String: [WildReportSupplementaryEvidence]] = [:]
|
||||
|
||||
var onStateChange: (() -> Void)?
|
||||
|
||||
private var nextSequence = 0
|
||||
private var nextSupplementSequence = 0
|
||||
|
||||
init() {
|
||||
resetDemoData(notify: false)
|
||||
}
|
||||
|
||||
var emptyTitle: String { "暂无举报服务" }
|
||||
var emptyMessage: String { "当前景区暂未开放举报摄影师演示入口。" }
|
||||
|
||||
/// 恢复演示数据并回到正常状态。
|
||||
func restoreDemoData() {
|
||||
resetDemoData(notify: false)
|
||||
pageState = .normal
|
||||
notifyStateChange()
|
||||
}
|
||||
|
||||
/// 模拟错误后重新加载。
|
||||
func retryLoadAfterError() {
|
||||
pageState = .loading
|
||||
notifyStateChange()
|
||||
Task {
|
||||
try? await Task.sleep(nanoseconds: 450_000_000)
|
||||
pageState = .normal
|
||||
notifyStateChange()
|
||||
}
|
||||
}
|
||||
|
||||
/// 切换页面演示状态。
|
||||
func setPageState(_ state: WildReportPageState) {
|
||||
pageState = state
|
||||
notifyStateChange()
|
||||
}
|
||||
|
||||
/// 提交举报并写入 Mock 列表,返回新记录。
|
||||
@discardableResult
|
||||
func submitReport(
|
||||
reportType: WildReportType,
|
||||
description: String,
|
||||
location: String,
|
||||
contact: String?,
|
||||
imageCount: Int,
|
||||
videoCount: Int
|
||||
) -> WildReportRecord {
|
||||
let trimmedDescription = description.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let parsedContact = WildPhotographerReportMockStore.parseContact(contact)
|
||||
let record = WildReportRecord(
|
||||
id: nextReportID(),
|
||||
title: "摄影师举报",
|
||||
reportType: reportType,
|
||||
status: .pending,
|
||||
location: location,
|
||||
submitTime: WildPhotographerReportMockStore.submitTimeFormatter.string(from: Date()),
|
||||
description: trimmedDescription,
|
||||
wechatID: parsedContact.wechat,
|
||||
phoneNumber: parsedContact.phone,
|
||||
imageCount: imageCount,
|
||||
videoCount: videoCount,
|
||||
handlerInfo: nil
|
||||
)
|
||||
reports.insert(record, at: 0)
|
||||
notifyStateChange()
|
||||
return record
|
||||
}
|
||||
|
||||
/// 读取指定举报的补充证据列表。
|
||||
func supplementaryEvidences(for reportID: String) -> [WildReportSupplementaryEvidence] {
|
||||
supplementaryEvidenceMap[reportID] ?? []
|
||||
}
|
||||
|
||||
/// 追加一条补充证据记录。
|
||||
func appendSupplementaryEvidence(
|
||||
reportID: String,
|
||||
text: String,
|
||||
imageCount: Int,
|
||||
videoCount: Int
|
||||
) {
|
||||
let item = WildReportSupplementaryEvidence(
|
||||
id: nextSupplementID(),
|
||||
submitTime: WildPhotographerReportMockStore.submitTimeFormatter.string(from: Date()),
|
||||
text: text.trimmingCharacters(in: .whitespacesAndNewlines),
|
||||
imageCount: imageCount,
|
||||
videoCount: videoCount
|
||||
)
|
||||
var items = supplementaryEvidenceMap[reportID] ?? []
|
||||
items.insert(item, at: 0)
|
||||
supplementaryEvidenceMap[reportID] = items
|
||||
notifyStateChange()
|
||||
}
|
||||
|
||||
/// 重置为种子 Mock 数据。
|
||||
func resetDemoData(notify: Bool = true) {
|
||||
reports = WildPhotographerReportMockStore.seedReports
|
||||
riskPoints = WildPhotographerReportMockStore.seedRiskPoints
|
||||
supplementaryEvidenceMap = WildPhotographerReportMockStore.seedSupplementaryEvidences
|
||||
nextSequence = WildPhotographerReportMockStore.maxSequence(from: WildPhotographerReportMockStore.seedReports) + 1
|
||||
nextSupplementSequence = 100
|
||||
if notify { notifyStateChange() }
|
||||
}
|
||||
|
||||
private func nextSupplementID() -> String {
|
||||
defer { nextSupplementSequence += 1 }
|
||||
return String(format: "SE%03d", nextSupplementSequence)
|
||||
}
|
||||
|
||||
private func nextReportID() -> String {
|
||||
let datePart = WildPhotographerReportMockStore.reportDateFormatter.string(from: Date())
|
||||
defer { nextSequence += 1 }
|
||||
return String(format: "JB\(datePart)%03d", nextSequence)
|
||||
}
|
||||
|
||||
private func notifyStateChange() {
|
||||
onStateChange?()
|
||||
}
|
||||
}
|
||||
|
||||
/// 提交举报页 ViewModel,负责表单校验和前台定位。
|
||||
final class WildPhotographerReportSubmitViewModel {
|
||||
private(set) var pageState: WildReportPageState = .normal
|
||||
private(set) var images: [WildReportAttachment]
|
||||
private(set) var videos: [WildReportAttachment]
|
||||
private(set) var paymentImages: [WildReportAttachment] = []
|
||||
private(set) var selectedReportType: WildReportType = .suspectedWild
|
||||
private(set) var description = ""
|
||||
private(set) var contact = ""
|
||||
private(set) var reportLocation = ""
|
||||
private(set) var isUpdatingLocation = false
|
||||
private(set) var locationConfirmed = false
|
||||
private(set) var submitResult: WildReportSubmitResult?
|
||||
private(set) var validationMessage: String?
|
||||
|
||||
var onStateChange: (() -> Void)?
|
||||
var onShowMessage: ((String) -> Void)?
|
||||
var onSubmitSuccess: ((WildReportSubmitResult) -> Void)?
|
||||
|
||||
private let homeViewModel: WildPhotographerReportHomeViewModel
|
||||
private let locationProvider: any LocationProviding
|
||||
private var hasRequestedInitialLocation = false
|
||||
|
||||
var locationTitleText: String {
|
||||
if reportLocation.isEmpty || isUpdatingLocation {
|
||||
return "正在获取当前位置..."
|
||||
}
|
||||
return "当前定位:\(reportLocation)"
|
||||
}
|
||||
|
||||
var locationSubtitleText: String {
|
||||
if isUpdatingLocation {
|
||||
return "正在更新定位,请稍候"
|
||||
}
|
||||
if locationConfirmed {
|
||||
return "已获取最新定位,如不准确请点击更新定位"
|
||||
}
|
||||
return "如位置不准确,请点击更新定位"
|
||||
}
|
||||
|
||||
init(
|
||||
homeViewModel: WildPhotographerReportHomeViewModel,
|
||||
locationProvider: any LocationProviding = LocationProvider.shared
|
||||
) {
|
||||
self.homeViewModel = homeViewModel
|
||||
self.locationProvider = locationProvider
|
||||
self.images = WildPhotographerReportMockStore.initialImages
|
||||
self.videos = WildPhotographerReportMockStore.initialVideos
|
||||
}
|
||||
|
||||
/// 首次进入页面时自动请求定位。
|
||||
func loadInitialLocationIfNeeded() {
|
||||
guard !hasRequestedInitialLocation else { return }
|
||||
hasRequestedInitialLocation = true
|
||||
refreshCurrentLocation()
|
||||
}
|
||||
|
||||
/// 选择举报类型。
|
||||
func selectReportType(_ type: WildReportType) {
|
||||
selectedReportType = type
|
||||
notifyStateChange()
|
||||
}
|
||||
|
||||
/// 限制举报说明最多 500 字。
|
||||
func updateDescription(_ newValue: String) {
|
||||
description = String(newValue.prefix(500))
|
||||
notifyStateChange()
|
||||
}
|
||||
|
||||
/// 更新联系方式。
|
||||
func updateContact(_ value: String) {
|
||||
contact = String(value.prefix(80))
|
||||
notifyStateChange()
|
||||
}
|
||||
|
||||
/// 添加演示用现场图片。
|
||||
func addImage() {
|
||||
images.append(WildReportAttachment(kind: .image, title: "现场图片\(images.count + 1)"))
|
||||
notifyStateChange()
|
||||
}
|
||||
|
||||
/// 添加演示用现场视频。
|
||||
func addVideo() {
|
||||
videos.append(WildReportAttachment(kind: .video, title: "现场视频\(videos.count + 1)"))
|
||||
notifyStateChange()
|
||||
}
|
||||
|
||||
/// 添加演示用支付截图。
|
||||
func addPaymentImage() {
|
||||
guard paymentImages.count < 3 else {
|
||||
onShowMessage?("支付截图最多上传3张")
|
||||
return
|
||||
}
|
||||
paymentImages.append(WildReportAttachment(kind: .payment, title: "支付截图\(paymentImages.count + 1)"))
|
||||
notifyStateChange()
|
||||
}
|
||||
|
||||
/// 删除指定附件。
|
||||
func removeAttachment(_ attachment: WildReportAttachment) {
|
||||
images.removeAll { $0.id == attachment.id }
|
||||
videos.removeAll { $0.id == attachment.id }
|
||||
paymentImages.removeAll { $0.id == attachment.id }
|
||||
notifyStateChange()
|
||||
}
|
||||
|
||||
/// 使用前台定位 Provider 刷新当前位置。
|
||||
func refreshCurrentLocation() {
|
||||
guard !isUpdatingLocation else { return }
|
||||
isUpdatingLocation = true
|
||||
locationConfirmed = false
|
||||
reportLocation = "正在获取当前位置..."
|
||||
notifyStateChange()
|
||||
|
||||
Task {
|
||||
do {
|
||||
let snapshot = try await locationProvider.requestSnapshot()
|
||||
let address = snapshot.address.isEmpty
|
||||
? String(format: "%.5f, %.5f", snapshot.latitude, snapshot.longitude)
|
||||
: snapshot.address
|
||||
applyResolvedLocation(address)
|
||||
} catch {
|
||||
applyResolvedLocation(WildPhotographerReportMockStore.fallbackLocation)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 清除提交结果,用于导航返回。
|
||||
func clearSubmitResult() {
|
||||
submitResult = nil
|
||||
notifyStateChange()
|
||||
}
|
||||
|
||||
/// 校验表单并提交举报。
|
||||
func submitReport() {
|
||||
validationMessage = nil
|
||||
guard !images.isEmpty || !videos.isEmpty else {
|
||||
setValidationMessage("请至少上传一项现场证据。")
|
||||
return
|
||||
}
|
||||
guard !description.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {
|
||||
setValidationMessage("请填写举报说明。")
|
||||
return
|
||||
}
|
||||
guard !isUpdatingLocation else {
|
||||
setValidationMessage("正在获取定位,请稍后再提交。")
|
||||
return
|
||||
}
|
||||
guard locationConfirmed, !reportLocation.isEmpty, reportLocation != "正在获取当前位置..." else {
|
||||
setValidationMessage("请先更新举报位置后再提交举报。")
|
||||
return
|
||||
}
|
||||
|
||||
let record = homeViewModel.submitReport(
|
||||
reportType: selectedReportType,
|
||||
description: description,
|
||||
location: reportLocation,
|
||||
contact: contact.isEmpty ? nil : contact,
|
||||
imageCount: images.count,
|
||||
videoCount: videos.count
|
||||
)
|
||||
let result = WildReportSubmitResult(
|
||||
record: record,
|
||||
imageCount: images.count,
|
||||
videoCount: videos.count
|
||||
)
|
||||
submitResult = result
|
||||
notifyStateChange()
|
||||
onSubmitSuccess?(result)
|
||||
}
|
||||
|
||||
/// 重置表单到初始状态。
|
||||
func resetForm() {
|
||||
images = WildPhotographerReportMockStore.initialImages
|
||||
videos = WildPhotographerReportMockStore.initialVideos
|
||||
paymentImages = []
|
||||
selectedReportType = .suspectedWild
|
||||
description = ""
|
||||
contact = ""
|
||||
reportLocation = ""
|
||||
locationConfirmed = false
|
||||
isUpdatingLocation = false
|
||||
hasRequestedInitialLocation = false
|
||||
submitResult = nil
|
||||
validationMessage = nil
|
||||
notifyStateChange()
|
||||
}
|
||||
|
||||
func applyResolvedLocation(_ location: String) {
|
||||
reportLocation = location
|
||||
locationConfirmed = true
|
||||
isUpdatingLocation = false
|
||||
notifyStateChange()
|
||||
}
|
||||
|
||||
private func setValidationMessage(_ message: String) {
|
||||
validationMessage = message
|
||||
notifyStateChange()
|
||||
onShowMessage?(message)
|
||||
}
|
||||
|
||||
private func notifyStateChange() {
|
||||
onStateChange?()
|
||||
}
|
||||
}
|
||||
|
||||
/// 举报成功页 ViewModel。
|
||||
final class WildPhotographerReportSuccessViewModel {
|
||||
let result: WildReportSubmitResult
|
||||
let progressSteps = ["已提交", "现场核查中", "处理结果反馈"]
|
||||
|
||||
init(result: WildReportSubmitResult) {
|
||||
self.result = result
|
||||
}
|
||||
|
||||
var uploadSummary: String {
|
||||
var parts: [String] = []
|
||||
if result.imageCount > 0 { parts.append("\(result.imageCount)张图片") }
|
||||
if result.videoCount > 0 { parts.append("\(result.videoCount)段视频") }
|
||||
return parts.isEmpty ? "暂无" : parts.joined(separator: " ")
|
||||
}
|
||||
|
||||
/// 复制举报编号到剪贴板。
|
||||
@MainActor
|
||||
func copyReportID() -> String {
|
||||
UIPasteboard.general.string = result.record.id
|
||||
return result.record.id
|
||||
}
|
||||
}
|
||||
|
||||
/// 举报详情页 ViewModel。
|
||||
final class WildPhotographerReportDetailViewModel {
|
||||
let record: WildReportRecord
|
||||
private let homeViewModel: WildPhotographerReportHomeViewModel?
|
||||
|
||||
private(set) var actionMessage: String?
|
||||
var onStateChange: (() -> Void)?
|
||||
var onShowMessage: ((String) -> Void)?
|
||||
|
||||
init(record: WildReportRecord, homeViewModel: WildPhotographerReportHomeViewModel? = nil) {
|
||||
self.record = record
|
||||
self.homeViewModel = homeViewModel
|
||||
}
|
||||
|
||||
var heroTitle: String { record.title.isEmpty ? "摄影师举报" : record.title }
|
||||
var scenicAreaName: String { WildReportLocationParser.split(record.location).scenic }
|
||||
var detailAddress: String {
|
||||
let detail = WildReportLocationParser.split(record.location).detail
|
||||
return detail.contains("附近") ? detail : "\(detail)附近"
|
||||
}
|
||||
|
||||
var displayStatusText: String { record.status.rawValue }
|
||||
var descriptionText: String { record.description }
|
||||
var contactText: String { record.contactDisplayText }
|
||||
var supplementaryEvidences: [WildReportSupplementaryEvidence] {
|
||||
homeViewModel?.supplementaryEvidences(for: record.id) ?? []
|
||||
}
|
||||
var reporterMediaSummary: String {
|
||||
switch (record.imageCount, record.videoCount) {
|
||||
case (0, 0): return "0项"
|
||||
case (_, 0): return "\(record.imageCount)张"
|
||||
case (0, _): return "\(record.videoCount)段"
|
||||
default: return "\(record.imageCount)张 \(record.videoCount)段"
|
||||
}
|
||||
}
|
||||
var progressSteps: [WildReportProgressStep] { Self.buildProgressSteps(for: record) }
|
||||
var showHandlerInfo: Bool { record.status == .processed && record.handlerInfo != nil }
|
||||
var handlerInfo: WildReportHandlerInfo? { record.handlerInfo }
|
||||
|
||||
/// 展示媒体预览提示。
|
||||
func showMediaPreview(kind: String) {
|
||||
showActionMessage("演示环境暂不支持预览\(kind),可在提交页查看上传内容。")
|
||||
}
|
||||
|
||||
/// 展示位置预览提示。
|
||||
func showLocationPreview() {
|
||||
showActionMessage("举报位置:\(record.location)")
|
||||
}
|
||||
|
||||
/// 生成分享提示文案。
|
||||
func shareReport() {
|
||||
showActionMessage("已生成举报 \(record.id) 的分享信息,可发送给景区处理人员查看进度。")
|
||||
}
|
||||
|
||||
private func showActionMessage(_ message: String) {
|
||||
actionMessage = message
|
||||
onShowMessage?(message)
|
||||
notifyStateChange()
|
||||
}
|
||||
|
||||
private static func buildProgressSteps(for record: WildReportRecord) -> [WildReportProgressStep] {
|
||||
let baseDate = submitDate(from: record.submitTime) ?? Date()
|
||||
let expectedFeedback = formatDate(baseDate.addingTimeInterval(8 * 3_600))
|
||||
|
||||
switch record.status {
|
||||
case .pending, .processing:
|
||||
return [
|
||||
makeStep(id: "1", title: "已提交", date: baseDate, state: .completed),
|
||||
makeStep(id: "2", title: "已派工作人员前往", date: baseDate.addingTimeInterval(22 * 60), state: .completed),
|
||||
makeStep(id: "3", title: "现场核查中", date: baseDate.addingTimeInterval(57 * 60), state: .current),
|
||||
makeStep(id: "4", title: "待结果反馈", date: nil, state: .pending, pendingText: "预计 \(expectedFeedback) 前")
|
||||
]
|
||||
case .processed:
|
||||
return [
|
||||
makeStep(id: "1", title: "已提交", date: baseDate, state: .completed),
|
||||
makeStep(id: "2", title: "已派工作人员前往", date: baseDate.addingTimeInterval(22 * 60), state: .completed),
|
||||
makeStep(id: "3", title: "现场核查中", date: baseDate.addingTimeInterval(57 * 60), state: .completed),
|
||||
makeStep(id: "4", title: "已反馈核查结果", date: baseDate.addingTimeInterval(3 * 3_600), state: .completed)
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
private static func makeStep(
|
||||
id: String,
|
||||
title: String,
|
||||
date: Date?,
|
||||
state: WildReportProgressStepState,
|
||||
pendingText: String? = nil
|
||||
) -> WildReportProgressStep {
|
||||
WildReportProgressStep(
|
||||
id: id,
|
||||
title: title,
|
||||
timeText: pendingText ?? (date.map(formatDate) ?? ""),
|
||||
state: state
|
||||
)
|
||||
}
|
||||
|
||||
private static func submitDate(from text: String) -> Date? {
|
||||
WildPhotographerReportMockStore.submitTimeFormatter.date(from: text)
|
||||
}
|
||||
|
||||
private static func formatDate(_ date: Date) -> String {
|
||||
WildPhotographerReportMockStore.submitTimeFormatter.string(from: date)
|
||||
}
|
||||
|
||||
private func notifyStateChange() {
|
||||
onStateChange?()
|
||||
}
|
||||
}
|
||||
|
||||
/// 我的举报列表 ViewModel。
|
||||
final class WildPhotographerReportListViewModel {
|
||||
private(set) var selectedFilter: WildReportListFilter = .all
|
||||
private(set) var shareMessage: String?
|
||||
|
||||
let homeViewModel: WildPhotographerReportHomeViewModel
|
||||
var onStateChange: (() -> Void)?
|
||||
var onShowMessage: ((String) -> Void)?
|
||||
|
||||
init(homeViewModel: WildPhotographerReportHomeViewModel) {
|
||||
self.homeViewModel = homeViewModel
|
||||
}
|
||||
|
||||
var filteredReports: [WildReportRecord] {
|
||||
let reports = homeViewModel.reports
|
||||
switch selectedFilter {
|
||||
case .all: return reports
|
||||
case .pending: return reports.filter { $0.status == .pending }
|
||||
case .processing: return reports.filter { $0.status == .processing }
|
||||
case .processed: return reports.filter { $0.status == .processed }
|
||||
}
|
||||
}
|
||||
|
||||
var footerTip: String { "温馨提示:为保障处理效率,请如实举报并提供有效证据" }
|
||||
|
||||
/// 切换列表筛选标签。
|
||||
func selectFilter(_ filter: WildReportListFilter) {
|
||||
selectedFilter = filter
|
||||
notifyStateChange()
|
||||
}
|
||||
|
||||
/// 生成分享提示文案。
|
||||
func shareReport(_ record: WildReportRecord) {
|
||||
shareMessage = "已生成举报 \(record.id) 的分享信息,可发送给景区处理人员查看进度。"
|
||||
onShowMessage?(shareMessage ?? "")
|
||||
notifyStateChange()
|
||||
}
|
||||
|
||||
private func notifyStateChange() {
|
||||
onStateChange?()
|
||||
}
|
||||
}
|
||||
|
||||
/// 补充证据页 ViewModel。
|
||||
final class WildReportSupplementEvidenceViewModel {
|
||||
private(set) var text = ""
|
||||
private(set) var images: [WildReportAttachment] = []
|
||||
private(set) var videos: [WildReportAttachment] = []
|
||||
private(set) var validationMessage: String?
|
||||
private(set) var didSubmit = false
|
||||
|
||||
let reportID: String
|
||||
var onStateChange: (() -> Void)?
|
||||
var onShowMessage: ((String) -> Void)?
|
||||
var onSubmitSuccess: (() -> Void)?
|
||||
|
||||
private let homeViewModel: WildPhotographerReportHomeViewModel
|
||||
|
||||
init(reportID: String, homeViewModel: WildPhotographerReportHomeViewModel) {
|
||||
self.reportID = reportID
|
||||
self.homeViewModel = homeViewModel
|
||||
}
|
||||
|
||||
/// 限制补充说明最多 500 字。
|
||||
func updateText(_ newValue: String) {
|
||||
text = String(newValue.prefix(500))
|
||||
notifyStateChange()
|
||||
}
|
||||
|
||||
/// 添加演示用补充图片。
|
||||
func addImage() {
|
||||
guard images.count < 9 else {
|
||||
onShowMessage?("补充图片最多上传9张")
|
||||
return
|
||||
}
|
||||
images.append(WildReportAttachment(kind: .image, title: "补充图片\(images.count + 1)"))
|
||||
notifyStateChange()
|
||||
}
|
||||
|
||||
/// 添加演示用补充视频。
|
||||
func addVideo() {
|
||||
guard videos.count < 3 else {
|
||||
onShowMessage?("补充视频最多上传3段")
|
||||
return
|
||||
}
|
||||
videos.append(WildReportAttachment(kind: .video, title: "补充视频\(videos.count + 1)"))
|
||||
notifyStateChange()
|
||||
}
|
||||
|
||||
/// 删除指定附件。
|
||||
func removeAttachment(_ attachment: WildReportAttachment) {
|
||||
images.removeAll { $0.id == attachment.id }
|
||||
videos.removeAll { $0.id == attachment.id }
|
||||
notifyStateChange()
|
||||
}
|
||||
|
||||
/// 校验并提交补充证据。
|
||||
func submit() {
|
||||
validationMessage = nil
|
||||
let trimmedText = text.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
guard !trimmedText.isEmpty || !images.isEmpty || !videos.isEmpty else {
|
||||
validationMessage = "请至少填写补充说明或上传一项证据。"
|
||||
onShowMessage?(validationMessage ?? "")
|
||||
notifyStateChange()
|
||||
return
|
||||
}
|
||||
|
||||
homeViewModel.appendSupplementaryEvidence(
|
||||
reportID: reportID,
|
||||
text: trimmedText,
|
||||
imageCount: images.count,
|
||||
videoCount: videos.count
|
||||
)
|
||||
didSubmit = true
|
||||
notifyStateChange()
|
||||
onSubmitSuccess?()
|
||||
}
|
||||
|
||||
private func notifyStateChange() {
|
||||
onStateChange?()
|
||||
}
|
||||
}
|
||||
|
||||
/// 附近风险地图 ViewModel。
|
||||
final class WildReportRiskMapViewModel {
|
||||
private(set) var region: MKCoordinateRegion
|
||||
private(set) var markers: [WildReportMapMarker]
|
||||
let scenicAreaName: String
|
||||
private(set) var selectedMarkerID: String?
|
||||
private(set) var toastMessage: String?
|
||||
|
||||
var onStateChange: (() -> Void)?
|
||||
var onShowMessage: ((String) -> Void)?
|
||||
|
||||
init(record: WildReportRecord, riskPoints: [WildReportRiskPoint]) {
|
||||
let markers = WildPhotographerReportMockStore.buildMarkers(record: record, riskPoints: riskPoints)
|
||||
self.markers = markers
|
||||
self.region = WildPhotographerReportMockStore.region(for: markers)
|
||||
self.scenicAreaName = "那拉提景区"
|
||||
self.selectedMarkerID = markers.first(where: { $0.kind == .selfLocation })?.id
|
||||
}
|
||||
|
||||
var selectedMarker: WildReportMapMarker? {
|
||||
guard let selectedMarkerID else { return nil }
|
||||
return markers.first { $0.id == selectedMarkerID }
|
||||
}
|
||||
|
||||
var selectedClue: WildReportRadarActiveClue? {
|
||||
guard let marker = selectedMarker,
|
||||
case .activeClue(let clue) = marker.detail else { return nil }
|
||||
return clue
|
||||
}
|
||||
|
||||
/// 选中地图标记。
|
||||
func selectMarker(_ marker: WildReportMapMarker) {
|
||||
selectedMarkerID = marker.id
|
||||
notifyStateChange()
|
||||
}
|
||||
|
||||
/// 导航到选中标记或刷新蓝点定位。
|
||||
func navigateToSelectedMarker() {
|
||||
guard let marker = selectedMarker,
|
||||
case .activeClue(let clue) = marker.detail else { return }
|
||||
|
||||
if clue.type == .blue {
|
||||
refreshLocation()
|
||||
return
|
||||
}
|
||||
|
||||
showToast("已切换到该点位")
|
||||
region = MKCoordinateRegion(
|
||||
center: marker.coordinate,
|
||||
span: MKCoordinateSpan(latitudeDelta: 0.018, longitudeDelta: 0.018)
|
||||
)
|
||||
notifyStateChange()
|
||||
}
|
||||
|
||||
/// 将地图居中到当前位置标记。
|
||||
func refreshLocation() {
|
||||
guard let blueMarker = markers.first(where: { $0.kind == .selfLocation }) else { return }
|
||||
selectedMarkerID = blueMarker.id
|
||||
region = MKCoordinateRegion(
|
||||
center: blueMarker.coordinate,
|
||||
span: MKCoordinateSpan(latitudeDelta: 0.018, longitudeDelta: 0.018)
|
||||
)
|
||||
showToast("已定位到当前位置")
|
||||
notifyStateChange()
|
||||
}
|
||||
|
||||
/// 生成线索分享提示。
|
||||
func shareSelectedClue() {
|
||||
guard let clue = selectedClue else { return }
|
||||
let title = clue.type == .red ? "线索 ID:\(clue.id)" : clue.displayTitle
|
||||
showToast("已生成\(title)的分享信息,可发送给他人查看。")
|
||||
}
|
||||
|
||||
/// 生成图片预览提示。
|
||||
func showImagePreview(_ url: String) {
|
||||
_ = url
|
||||
showToast("演示环境暂不支持预览图片")
|
||||
}
|
||||
|
||||
private func showToast(_ message: String) {
|
||||
toastMessage = message
|
||||
onShowMessage?(message)
|
||||
notifyStateChange()
|
||||
}
|
||||
|
||||
private func notifyStateChange() {
|
||||
onStateChange?()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user