完善风险地图定位与标记详情

This commit is contained in:
2026-07-09 12:47:47 +08:00
parent 42aca73588
commit 2970f1514b
5 changed files with 697 additions and 113 deletions

View File

@ -87,9 +87,9 @@ final class WildPhotographerReportAPI: WildPhotographerReportServing {
func markerDetail(_ request: WildReportMarkerDetailRequest) async throws -> WildReportMarkerDetailResponse {
try await client.send(
APIRequest(
method: .post,
method: .get,
path: "/api/yf-handset-app/photog/report/marker-detail",
body: request
queryItems: request.queryItems
)
)
}
@ -148,7 +148,7 @@ struct WildReportRiskMapRequest: Encodable, Equatable {
}
}
///
///
struct WildReportMarkerDetailRequest: Encodable, Equatable {
let scenicId: Int
let markerType: String
@ -156,6 +156,21 @@ struct WildReportMarkerDetailRequest: Encodable, Equatable {
let latitude: Double?
let longitude: Double?
var queryItems: [URLQueryItem] {
var items = [
URLQueryItem(name: "scenic_id", value: String(scenicId)),
URLQueryItem(name: "marker_type", value: markerType),
URLQueryItem(name: "id", value: String(id))
]
if let latitude {
items.append(URLQueryItem(name: "latitude", value: String(latitude)))
}
if let longitude {
items.append(URLQueryItem(name: "longitude", value: String(longitude)))
}
return items
}
enum CodingKeys: String, CodingKey {
case scenicId = "scenic_id"
case markerType = "marker_type"

View File

@ -1185,7 +1185,9 @@ struct WildReportRiskMapMarkerDTO: Decodable, Equatable, Hashable {
struct WildReportMarkerDetailResponse: Decodable, Equatable, Hashable {
let markerType: String
let id: Int
let clueId: String
let title: String
let statusTag: String
let tag: String
let name: String
let avatar: String
@ -1210,7 +1212,9 @@ struct WildReportMarkerDetailResponse: Decodable, Equatable, Hashable {
enum CodingKeys: String, CodingKey {
case markerType = "marker_type"
case id
case clueId = "clue_id"
case title
case statusTag = "status_tag"
case tag
case name
case avatar
@ -1237,7 +1241,9 @@ struct WildReportMarkerDetailResponse: Decodable, Equatable, Hashable {
init(
markerType: String,
id: Int,
clueId: String = "",
title: String = "",
statusTag: String = "",
tag: String = "",
name: String = "",
avatar: String = "",
@ -1261,7 +1267,9 @@ struct WildReportMarkerDetailResponse: Decodable, Equatable, Hashable {
) {
self.markerType = markerType
self.id = id
self.clueId = clueId
self.title = title
self.statusTag = statusTag
self.tag = tag
self.name = name
self.avatar = avatar
@ -1288,7 +1296,9 @@ struct WildReportMarkerDetailResponse: Decodable, Equatable, Hashable {
let container = try decoder.container(keyedBy: CodingKeys.self)
markerType = try container.decodeIfPresent(String.self, forKey: .markerType) ?? ""
id = try container.decodeIfPresent(Int.self, forKey: .id) ?? 0
clueId = try container.decodeIfPresent(String.self, forKey: .clueId) ?? ""
title = try container.decodeIfPresent(String.self, forKey: .title) ?? ""
statusTag = try container.decodeIfPresent(String.self, forKey: .statusTag) ?? ""
tag = try container.decodeIfPresent(String.self, forKey: .tag) ?? ""
name = try container.decodeIfPresent(String.self, forKey: .name) ?? ""
avatar = try container.decodeIfPresent(String.self, forKey: .avatar) ?? ""

View File

@ -1018,7 +1018,6 @@ final class WildReportRiskMapViewModel {
private(set) var markers: [WildReportMapMarker]
private(set) var scenicAreaName: String
private(set) var selectedMarkerID: String?
private(set) var toastMessage: String?
private(set) var isLoading = false
private(set) var isLoadingDetail = false
private(set) var errorMessage: String?
@ -1027,7 +1026,6 @@ final class WildReportRiskMapViewModel {
private(set) var currentCoordinate: CLLocationCoordinate2D?
var onStateChange: (() -> Void)?
var onShowMessage: ((String) -> Void)?
init() {
self.markers = []
@ -1090,7 +1088,7 @@ final class WildReportRiskMapViewModel {
mergeDetail(detail, intoMarkerID: marker.id)
loadedDetailMarkerIDs.insert(marker.id)
} catch {
showToast(error.localizedDescription)
errorMessage = error.localizedDescription
}
isLoadingDetail = false
notifyStateChange()
@ -1114,6 +1112,19 @@ final class WildReportRiskMapViewModel {
return marker
}
///
@discardableResult
func focusCurrentLocation() -> Bool {
guard let marker = markers.first(where: { $0.kind == .selfLocation }) else { return false }
selectedMarkerID = marker.id
region = MKCoordinateRegion(
center: marker.coordinate,
span: MKCoordinateSpan(latitudeDelta: 0.018, longitudeDelta: 0.018)
)
notifyStateChange()
return true
}
///
func refreshLocation(
api: any WildPhotographerReportServing,
@ -1132,21 +1143,12 @@ final class WildReportRiskMapViewModel {
/// 线
func shareSelectedClue() {
guard let clue = selectedClue else { return }
let title = clue.type == .red ? "线索 ID\(clue.id)" : clue.displayTitle
showToast("已生成\(title)的分享信息,可发送给他人查看。")
_ = selectedClue
}
///
func showImagePreview(_ url: String) {
_ = url
showToast("暂不支持预览图片")
}
private func showToast(_ message: String) {
toastMessage = message
onShowMessage?(message)
notifyStateChange()
}
private func loadRiskMap(
@ -1159,7 +1161,7 @@ final class WildReportRiskMapViewModel {
let resolvedScenicId = scenicId ?? AppStore.shared.currentScenicId
guard resolvedScenicId >= 1 else {
errorMessage = "请先选择景区后再查看风险地图"
showToast(errorMessage ?? "")
notifyStateChange()
return
}
@ -1170,8 +1172,7 @@ final class WildReportRiskMapViewModel {
scenicId: resolvedScenicId,
scenicName: scenicName,
coordinate: coordinate,
selectCurrentLocation: selectCurrentLocation,
showsLocationRefreshToast: coordinate != nil
selectCurrentLocation: selectCurrentLocation
)
}
@ -1184,8 +1185,7 @@ final class WildReportRiskMapViewModel {
scenicId: Int,
scenicName: String?,
coordinate: CLLocationCoordinate2D?,
selectCurrentLocation: Bool,
showsLocationRefreshToast: Bool
selectCurrentLocation: Bool
) async {
isLoading = true
errorMessage = nil
@ -1210,12 +1210,8 @@ final class WildReportRiskMapViewModel {
} else if selectedMarker == nil {
selectedMarkerID = markers.first?.id
}
if showsLocationRefreshToast {
showToast("已更新附近风险点位")
}
} catch {
errorMessage = error.localizedDescription
showToast(error.localizedDescription)
}
isLoading = false
@ -1354,7 +1350,8 @@ final class WildReportRiskMapViewModel {
else { return }
let markerType = WildReportRiskMarkerType(rawValue: detail.markerType.nonEmpty ?? oldClue.type.rawValue)
let statusText = detail.handleStatusText.nonEmpty
let statusText = detail.statusTag.nonEmpty
?? detail.handleStatusText.nonEmpty
?? detail.handleStatus?.displayText
?? oldClue.statusText
let reportContents = !detail.reportContents.isEmpty
@ -1362,7 +1359,7 @@ final class WildReportRiskMapViewModel {
: (detail.desc.nonEmpty.map { [WildReportRadarReportContent(time: detail.createdAt, content: $0)] } ?? oldClue.reportContents)
let imageURLs = detail.evidences.filter(\.isImage).map(\.fileURL)
let clue = WildReportRadarActiveClue(
id: String(detail.id > 0 ? detail.id : Int(oldClue.id) ?? 0),
id: detail.clueId.nonEmpty ?? String(detail.id > 0 ? detail.id : Int(oldClue.id) ?? 0),
type: markerType.radarType,
displayTitle: detail.title.nonEmpty ?? oldClue.displayTitle,
statusText: statusText,