新增举报摄影师模块并接入首页路由,桌面应用名改为随心瞰商家版。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@ -0,0 +1,717 @@
|
||||
//
|
||||
// WildPhotographerReportMockStore.swift
|
||||
// suixinkan
|
||||
//
|
||||
// Created by Codex on 2026/7/1.
|
||||
//
|
||||
|
||||
import CoreLocation
|
||||
import Foundation
|
||||
import MapKit
|
||||
|
||||
/// 举报摄影师 Mock 数据仓库,统一管理演示种子数据和地图标记构建。
|
||||
enum WildPhotographerReportMockStore {
|
||||
static let fallbackLocation = "黄山风景区 · 光明顶景区"
|
||||
|
||||
static let initialImages = [
|
||||
WildReportAttachment(kind: .image, title: "现场图片1")
|
||||
]
|
||||
|
||||
static let initialVideos = [
|
||||
WildReportAttachment(kind: .video, title: "现场视频1")
|
||||
]
|
||||
|
||||
static let seedRiskPoints = [
|
||||
WildReportRiskPoint(
|
||||
id: 1,
|
||||
title: "光明顶观景台附近",
|
||||
location: "黄山风景区 · 光明顶景区",
|
||||
distance: "120m",
|
||||
riskLevel: "高",
|
||||
evidenceCount: 6,
|
||||
latestTime: "10分钟前",
|
||||
description: "多名游客反馈有疑似未备案摄影师主动揽客,集中在观景台东侧步道附近。"
|
||||
),
|
||||
WildReportRiskPoint(
|
||||
id: 2,
|
||||
title: "迎客松游客出口",
|
||||
location: "黄山风景区 · 玉屏景区",
|
||||
distance: "860m",
|
||||
riskLevel: "中",
|
||||
evidenceCount: 3,
|
||||
latestTime: "32分钟前",
|
||||
description: "疑似人员以低价加急出片为话术引导游客线下付款,建议现场核查。"
|
||||
),
|
||||
WildReportRiskPoint(
|
||||
id: 3,
|
||||
title: "云谷索道排队区",
|
||||
location: "黄山风景区 · 云谷景区",
|
||||
distance: "1.4km",
|
||||
riskLevel: "低",
|
||||
evidenceCount: 2,
|
||||
latestTime: "1小时前",
|
||||
description: "排队区附近存在疑似私下拉客行为,已收到图片和位置线索。"
|
||||
)
|
||||
]
|
||||
|
||||
static let seedReports = [
|
||||
WildReportRecord(
|
||||
id: "JB20260618001",
|
||||
title: "摄影师举报",
|
||||
reportType: .suspectedWild,
|
||||
status: .processing,
|
||||
location: "那拉提景区 · 空中草原入口附近",
|
||||
submitTime: "2026-06-18 10:24",
|
||||
description: "疑似人员在观景台附近主动向游客售卖拍摄服务,并引导线下转账。",
|
||||
wechatID: "wx_photographer_01",
|
||||
phoneNumber: "13812345678",
|
||||
imageCount: 3,
|
||||
videoCount: 1,
|
||||
handlerInfo: nil
|
||||
),
|
||||
WildReportRecord(
|
||||
id: "JB20260617008",
|
||||
title: "摄影师举报",
|
||||
reportType: .suspectedWild,
|
||||
status: .processed,
|
||||
location: "那拉提景区 · 河谷草原游客中心",
|
||||
submitTime: "2026-06-17 16:42",
|
||||
description: "现场发现疑似未备案摄影师持续向游客推销旅拍套餐。",
|
||||
wechatID: "nalati_photo",
|
||||
phoneNumber: "",
|
||||
imageCount: 2,
|
||||
videoCount: 1,
|
||||
handlerInfo: processedHandlerInfo(
|
||||
handlerName: "赵强",
|
||||
handlerPhone: "15938267120",
|
||||
processingAt: "2026-06-17 17:10",
|
||||
processedAt: "2026-06-17 18:26",
|
||||
processOpinion: "已到场核查并完成劝离,处理结果已同步景区公安。",
|
||||
imageSeedPrefix: "handler-jb008"
|
||||
)
|
||||
),
|
||||
WildReportRecord(
|
||||
id: "JB20260616003",
|
||||
title: "摄影师举报",
|
||||
reportType: .noWorkBadge,
|
||||
status: .pending,
|
||||
location: "那拉提景区 · 盘龙古道停车区",
|
||||
submitTime: "2026-06-16 09:15",
|
||||
description: "停车区附近有摄影师未佩戴工牌开展拍摄服务,已收到图片和位置线索。",
|
||||
wechatID: "",
|
||||
phoneNumber: "13987654321",
|
||||
imageCount: 1,
|
||||
videoCount: 0,
|
||||
handlerInfo: nil
|
||||
),
|
||||
WildReportRecord(
|
||||
id: "JB20260615002",
|
||||
title: "摄影师举报",
|
||||
reportType: .other,
|
||||
status: .processed,
|
||||
location: "那拉提景区 · 雪莲谷步道",
|
||||
submitTime: "2026-06-15 14:30",
|
||||
description: "举报线索经核查未发现异常摄影师服务行为,已归档。",
|
||||
wechatID: "grassland_cam",
|
||||
phoneNumber: "13600001111",
|
||||
imageCount: 1,
|
||||
videoCount: 0,
|
||||
handlerInfo: processedHandlerInfo(
|
||||
handlerName: "李娜",
|
||||
handlerPhone: "18674598210",
|
||||
processingAt: "2026-06-15 15:05",
|
||||
processedAt: "2026-06-15 16:55",
|
||||
processOpinion: "现场核查未发现未备案摄影师,已归档处理。",
|
||||
imageSeedPrefix: "handler-jb002",
|
||||
includeVideo: false
|
||||
)
|
||||
)
|
||||
]
|
||||
|
||||
static let seedSupplementaryEvidences: [String: [WildReportSupplementaryEvidence]] = [
|
||||
"JB20260618001": [
|
||||
WildReportSupplementaryEvidence(
|
||||
id: "SE001",
|
||||
submitTime: "2026-06-18 14:20",
|
||||
text: "补充现场视频,对方仍在揽客。",
|
||||
imageCount: 1,
|
||||
videoCount: 1
|
||||
)
|
||||
]
|
||||
]
|
||||
|
||||
/// 构建已处理举报的处理人信息 Mock 数据。
|
||||
static func processedHandlerInfo(
|
||||
handlerName: String,
|
||||
handlerPhone: String,
|
||||
processingAt: String,
|
||||
processedAt: String,
|
||||
processOpinion: String,
|
||||
imageSeedPrefix: String,
|
||||
includeVideo: Bool = true
|
||||
) -> WildReportHandlerInfo {
|
||||
var media: [WildReportCompletionMediaItem] = [
|
||||
WildReportCompletionMediaItem(
|
||||
id: "\(imageSeedPrefix)-img-1",
|
||||
kind: .image,
|
||||
coverURL: "https://picsum.photos/seed/\(imageSeedPrefix)-1/640/480",
|
||||
duration: nil
|
||||
),
|
||||
WildReportCompletionMediaItem(
|
||||
id: "\(imageSeedPrefix)-img-2",
|
||||
kind: .image,
|
||||
coverURL: "https://picsum.photos/seed/\(imageSeedPrefix)-2/640/480",
|
||||
duration: nil
|
||||
)
|
||||
]
|
||||
if includeVideo {
|
||||
media.append(
|
||||
WildReportCompletionMediaItem(
|
||||
id: "\(imageSeedPrefix)-vid-1",
|
||||
kind: .video,
|
||||
coverURL: "https://picsum.photos/seed/\(imageSeedPrefix)-video/640/480",
|
||||
duration: "00:18"
|
||||
)
|
||||
)
|
||||
}
|
||||
return WildReportHandlerInfo(
|
||||
handlerName: handlerName,
|
||||
handlerPhone: handlerPhone,
|
||||
processingAt: processingAt,
|
||||
processedAt: processedAt,
|
||||
processOpinion: processOpinion,
|
||||
completionMedia: media
|
||||
)
|
||||
}
|
||||
|
||||
/// 从已有举报编号中提取最大序号,供新编号递增。
|
||||
static func maxSequence(from reports: [WildReportRecord]) -> Int {
|
||||
reports.compactMap { record in
|
||||
guard record.id.count >= 3 else { return nil }
|
||||
return Int(record.id.suffix(3))
|
||||
}.max() ?? 0
|
||||
}
|
||||
|
||||
/// 解析联系方式,区分微信号和手机号。
|
||||
static func parseContact(_ contact: String?) -> (wechat: String, phone: String) {
|
||||
guard let contact else { return ("", "") }
|
||||
let trimmed = contact.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
guard !trimmed.isEmpty else { return ("", "") }
|
||||
|
||||
let digits = trimmed.filter(\.isNumber)
|
||||
if digits.count >= 7, trimmed.filter({ $0.isNumber || "+-() ".contains($0) }).count == trimmed.count {
|
||||
return ("", trimmed)
|
||||
}
|
||||
return (trimmed, "")
|
||||
}
|
||||
|
||||
static let reportDateFormatter: DateFormatter = {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateFormat = "yyyyMMdd"
|
||||
formatter.locale = Locale(identifier: "en_US_POSIX")
|
||||
return formatter
|
||||
}()
|
||||
|
||||
static let submitTimeFormatter: DateFormatter = {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateFormat = "yyyy-MM-dd HH:mm"
|
||||
formatter.locale = Locale(identifier: "en_US_POSIX")
|
||||
return formatter
|
||||
}()
|
||||
|
||||
// MARK: - 风险地图 Mock
|
||||
|
||||
static let nalatiCenter = CLLocationCoordinate2D(latitude: 43.382, longitude: 84.032)
|
||||
|
||||
private static let clueID1 = "2026年6月22日-0001"
|
||||
private static let clueID2 = "2026年6月22日-0002"
|
||||
private static let clueID3 = "2026年6月22日-0003"
|
||||
private static let initialProcessingClueIDs: Set<String> = [clueID2]
|
||||
|
||||
/// 根据线索状态返回地图标记类型。
|
||||
static func markerKind(for clue: WildReportRadarActiveClue) -> WildReportMapMarkerKind {
|
||||
switch clue.type {
|
||||
case .blue:
|
||||
return .selfLocation
|
||||
case .green:
|
||||
return .peerPhotographer
|
||||
case .red:
|
||||
if clue.processingStarted && !clue.completed {
|
||||
return .processingClue
|
||||
}
|
||||
return .wildPhotographer
|
||||
}
|
||||
}
|
||||
|
||||
/// 构建风险地图全部标记点。
|
||||
static func buildMarkers(record: WildReportRecord, riskPoints: [WildReportRiskPoint]) -> [WildReportMapMarker] {
|
||||
_ = record
|
||||
_ = riskPoints
|
||||
|
||||
let blueCoordinate = CLLocationCoordinate2D(latitude: 43.3779, longitude: 84.0217)
|
||||
let redSeeds: [(id: String, name: String, coordinate: CLLocationCoordinate2D, clue: WildReportRadarActiveClue)] = [
|
||||
(
|
||||
clueID1,
|
||||
"空中草原观景道",
|
||||
CLLocationCoordinate2D(latitude: 43.4068, longitude: 84.0582),
|
||||
WildReportRadarActiveClue(
|
||||
id: clueID1,
|
||||
type: .red,
|
||||
displayTitle: "线索 ID:\(clueID1)",
|
||||
statusText: "疑似打野",
|
||||
actionText: "导航到此",
|
||||
nearestAbnormalText: nil,
|
||||
distance: nil,
|
||||
direction: nil,
|
||||
updatedAt: "5分钟前",
|
||||
name: "空中草原观景道",
|
||||
storeName: nil,
|
||||
avatar: nil,
|
||||
summary: nil,
|
||||
detail: nil,
|
||||
distanceText: "320米",
|
||||
reporterName: "张三",
|
||||
reporterPhone: "13333333333",
|
||||
maskedReporterPhone: maskPhone("13333333333"),
|
||||
reportContents: [
|
||||
WildReportRadarReportContent(
|
||||
time: "14:32:18",
|
||||
content: "多人结伴揽客,疑似无资质摄影团队,携带专业器材在观景道附近活动。"
|
||||
),
|
||||
WildReportRadarReportContent(
|
||||
time: "14:45:06",
|
||||
content: "补充证据:对方仍在向游客推销拍照套餐,并拒绝出示相关资质。"
|
||||
)
|
||||
],
|
||||
images: [
|
||||
"https://picsum.photos/seed/nalati-radar-1/640/480",
|
||||
"https://picsum.photos/seed/nalati-radar-2/640/480",
|
||||
"https://picsum.photos/seed/nalati-radar-3/640/480"
|
||||
],
|
||||
processingStarted: false,
|
||||
completed: false,
|
||||
record: nil,
|
||||
processingTimeline: [],
|
||||
completionPhoto: nil
|
||||
)
|
||||
),
|
||||
(
|
||||
clueID2,
|
||||
"塔吾萨尼步道",
|
||||
CLLocationCoordinate2D(latitude: 43.3652, longitude: 84.0385),
|
||||
WildReportRadarActiveClue(
|
||||
id: clueID2,
|
||||
type: .red,
|
||||
displayTitle: "线索 ID:\(clueID2)",
|
||||
statusText: "疑似打野",
|
||||
actionText: "导航到此",
|
||||
nearestAbnormalText: nil,
|
||||
distance: nil,
|
||||
direction: nil,
|
||||
updatedAt: "18分钟前",
|
||||
name: "塔吾萨尼步道",
|
||||
storeName: nil,
|
||||
avatar: nil,
|
||||
summary: nil,
|
||||
detail: nil,
|
||||
distanceText: "780米",
|
||||
reporterName: "李四",
|
||||
reporterPhone: "18674598210",
|
||||
maskedReporterPhone: maskPhone("18674598210"),
|
||||
reportContents: [
|
||||
WildReportRadarReportContent(
|
||||
time: "10:18:42",
|
||||
content: "疑似二次转移,仍在塔吾萨尼步道附近揽客拍照。"
|
||||
),
|
||||
WildReportRadarReportContent(
|
||||
time: "10:26:15",
|
||||
content: "补充:对方穿黑色外套,携带三脚架,在步道入口反复搭讪游客。"
|
||||
)
|
||||
],
|
||||
images: [
|
||||
"https://picsum.photos/seed/nalati-radar-4/640/480",
|
||||
"https://picsum.photos/seed/nalati-radar-5/640/480"
|
||||
],
|
||||
processingStarted: false,
|
||||
completed: false,
|
||||
record: nil,
|
||||
processingTimeline: [],
|
||||
completionPhoto: nil
|
||||
)
|
||||
),
|
||||
(
|
||||
clueID3,
|
||||
"河谷草原西南侧",
|
||||
CLLocationCoordinate2D(latitude: 43.3546, longitude: 84.0136),
|
||||
WildReportRadarActiveClue(
|
||||
id: clueID3,
|
||||
type: .red,
|
||||
displayTitle: "线索 ID:\(clueID3)",
|
||||
statusText: "疑似打野",
|
||||
actionText: "导航到此",
|
||||
nearestAbnormalText: nil,
|
||||
distance: nil,
|
||||
direction: nil,
|
||||
updatedAt: "昨天17:45",
|
||||
name: "河谷草原西南侧",
|
||||
storeName: nil,
|
||||
avatar: nil,
|
||||
summary: nil,
|
||||
detail: nil,
|
||||
distanceText: "910米",
|
||||
reporterName: "王五",
|
||||
reporterPhone: "15938267120",
|
||||
maskedReporterPhone: maskPhone("15938267120"),
|
||||
reportContents: [
|
||||
WildReportRadarReportContent(
|
||||
time: "17:45:03",
|
||||
content: "游客反馈有人在步道旁揽客拍照,行为较为可疑。"
|
||||
)
|
||||
],
|
||||
images: [
|
||||
"https://picsum.photos/seed/nalati-radar-8/640/480"
|
||||
],
|
||||
processingStarted: false,
|
||||
completed: false,
|
||||
record: nil,
|
||||
processingTimeline: [],
|
||||
completionPhoto: nil
|
||||
)
|
||||
)
|
||||
]
|
||||
|
||||
let nearestText = nearestAbnormalText(
|
||||
blueCoordinate: blueCoordinate,
|
||||
redCoordinates: redSeeds.map(\.coordinate)
|
||||
)
|
||||
|
||||
var markers: [WildReportMapMarker] = [
|
||||
WildReportMapMarker(
|
||||
id: "ME",
|
||||
title: "河谷草原入口",
|
||||
coordinate: blueCoordinate,
|
||||
kind: .selfLocation,
|
||||
detail: .activeClue(
|
||||
WildReportRadarActiveClue(
|
||||
id: "ME",
|
||||
type: .blue,
|
||||
displayTitle: "当前位置:河谷草原入口",
|
||||
statusText: "当前位置",
|
||||
actionText: "刷新定位",
|
||||
nearestAbnormalText: nearestText,
|
||||
distance: "0 米",
|
||||
direction: "蓝色基准点",
|
||||
updatedAt: "实时定位",
|
||||
name: "河谷草原入口",
|
||||
storeName: nil,
|
||||
avatar: nil,
|
||||
summary: "蓝色标记为当前位置,可对比周边摄影师风险点位距离。",
|
||||
detail: "当前定位在河谷草原入口附近,建议优先关注 800 米内的异常点位。",
|
||||
distanceText: nil,
|
||||
reporterName: nil,
|
||||
reporterPhone: nil,
|
||||
maskedReporterPhone: nil,
|
||||
reportContents: [],
|
||||
images: [],
|
||||
processingStarted: false,
|
||||
completed: false,
|
||||
record: nil,
|
||||
processingTimeline: [],
|
||||
completionPhoto: nil
|
||||
)
|
||||
)
|
||||
)
|
||||
]
|
||||
|
||||
for seed in redSeeds {
|
||||
let processingStarted = initialProcessingClueIDs.contains(seed.id)
|
||||
let clue = enrichClue(seed.clue, processingStarted: processingStarted)
|
||||
markers.append(
|
||||
WildReportMapMarker(
|
||||
id: seed.id,
|
||||
title: seed.name,
|
||||
coordinate: seed.coordinate,
|
||||
kind: markerKind(for: clue),
|
||||
detail: .activeClue(clue)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
let greenSeeds: [(id: String, coordinate: CLLocationCoordinate2D, clue: WildReportRadarActiveClue)] = [
|
||||
(
|
||||
"PG20240618001",
|
||||
CLLocationCoordinate2D(latitude: 43.4042, longitude: 84.0186),
|
||||
WildReportRadarActiveClue(
|
||||
id: "PG20240618001",
|
||||
type: .green,
|
||||
displayTitle: "认证摄影师",
|
||||
statusText: "认证摄影师",
|
||||
actionText: "导航到此",
|
||||
nearestAbnormalText: nil,
|
||||
distance: "260 米",
|
||||
direction: "北侧点位",
|
||||
updatedAt: "在线",
|
||||
name: "阿依达娜",
|
||||
storeName: "空中草原旅拍店",
|
||||
avatar: "person.crop.circle.fill",
|
||||
summary: "绿色标记为已认证摄影师,可与红色摄影师风险点位区分。",
|
||||
detail: "当前在空中草原区域服务游客拍摄。",
|
||||
distanceText: nil,
|
||||
reporterName: nil,
|
||||
reporterPhone: nil,
|
||||
maskedReporterPhone: nil,
|
||||
reportContents: [],
|
||||
images: [],
|
||||
processingStarted: false,
|
||||
completed: false,
|
||||
record: nil,
|
||||
processingTimeline: [],
|
||||
completionPhoto: nil
|
||||
)
|
||||
),
|
||||
(
|
||||
"PG20240618002",
|
||||
CLLocationCoordinate2D(latitude: 43.3985, longitude: 84.0408),
|
||||
WildReportRadarActiveClue(
|
||||
id: "PG20240618002",
|
||||
type: .green,
|
||||
displayTitle: "认证摄影师",
|
||||
statusText: "认证摄影师",
|
||||
actionText: "导航到此",
|
||||
nearestAbnormalText: nil,
|
||||
distance: "430 米",
|
||||
direction: "东北方向",
|
||||
updatedAt: "在线",
|
||||
name: "波拉提",
|
||||
storeName: "天界台摄影服务点",
|
||||
avatar: "person.crop.circle.fill",
|
||||
summary: "绿色标记为已认证摄影师,属于正常服务点位。",
|
||||
detail: "当前在天界台附近接待预约游客。",
|
||||
distanceText: nil,
|
||||
reporterName: nil,
|
||||
reporterPhone: nil,
|
||||
maskedReporterPhone: nil,
|
||||
reportContents: [],
|
||||
images: [],
|
||||
processingStarted: false,
|
||||
completed: false,
|
||||
record: nil,
|
||||
processingTimeline: [],
|
||||
completionPhoto: nil
|
||||
)
|
||||
),
|
||||
(
|
||||
"PG20240618003",
|
||||
CLLocationCoordinate2D(latitude: 43.3828, longitude: 84.0794),
|
||||
WildReportRadarActiveClue(
|
||||
id: "PG20240618003",
|
||||
type: .green,
|
||||
displayTitle: "认证摄影师",
|
||||
statusText: "认证摄影师",
|
||||
actionText: "导航到此",
|
||||
nearestAbnormalText: nil,
|
||||
distance: "620 米",
|
||||
direction: "东侧点位",
|
||||
updatedAt: "在线",
|
||||
name: "米热丽",
|
||||
storeName: "塔吾萨尼影像馆",
|
||||
avatar: "person.crop.circle.fill",
|
||||
summary: "绿色标记为已认证摄影师,和红色异常点位分开显示。",
|
||||
detail: "当前在塔吾萨尼步道附近提供旅拍服务。",
|
||||
distanceText: nil,
|
||||
reporterName: nil,
|
||||
reporterPhone: nil,
|
||||
maskedReporterPhone: nil,
|
||||
reportContents: [],
|
||||
images: [],
|
||||
processingStarted: false,
|
||||
completed: false,
|
||||
record: nil,
|
||||
processingTimeline: [],
|
||||
completionPhoto: nil
|
||||
)
|
||||
)
|
||||
]
|
||||
|
||||
for seed in greenSeeds {
|
||||
markers.append(
|
||||
WildReportMapMarker(
|
||||
id: seed.id,
|
||||
title: seed.clue.name ?? seed.id,
|
||||
coordinate: seed.coordinate,
|
||||
kind: .peerPhotographer,
|
||||
detail: .activeClue(seed.clue)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
return markers
|
||||
}
|
||||
|
||||
/// 根据全部标记计算地图可视区域。
|
||||
static func region(for markers: [WildReportMapMarker]) -> MKCoordinateRegion {
|
||||
guard let first = markers.first else {
|
||||
return MKCoordinateRegion(
|
||||
center: nalatiCenter,
|
||||
span: MKCoordinateSpan(latitudeDelta: 0.025, longitudeDelta: 0.025)
|
||||
)
|
||||
}
|
||||
|
||||
var minLat = first.coordinate.latitude
|
||||
var maxLat = first.coordinate.latitude
|
||||
var minLon = first.coordinate.longitude
|
||||
var maxLon = first.coordinate.longitude
|
||||
|
||||
for marker in markers {
|
||||
minLat = min(minLat, marker.coordinate.latitude)
|
||||
maxLat = max(maxLat, marker.coordinate.latitude)
|
||||
minLon = min(minLon, marker.coordinate.longitude)
|
||||
maxLon = max(maxLon, marker.coordinate.longitude)
|
||||
}
|
||||
|
||||
let center = CLLocationCoordinate2D(
|
||||
latitude: (minLat + maxLat) / 2,
|
||||
longitude: (minLon + maxLon) / 2
|
||||
)
|
||||
let span = MKCoordinateSpan(
|
||||
latitudeDelta: max(0.022, (maxLat - minLat) * 1.8),
|
||||
longitudeDelta: max(0.022, (maxLon - minLon) * 1.8)
|
||||
)
|
||||
return MKCoordinateRegion(center: center, span: span)
|
||||
}
|
||||
|
||||
private static func processRecord(for clueID: String) -> WildReportRadarProcessRecord? {
|
||||
switch clueID {
|
||||
case clueID1:
|
||||
return WildReportRadarProcessRecord(
|
||||
handlerName: "王磊",
|
||||
phone: "13812346721",
|
||||
maskedPhone: maskPhone("13812346721"),
|
||||
statusText: "待处理",
|
||||
processResult: "待现场核查,请前往空中草原观景道确认摄影师线索。",
|
||||
processedAt: nil,
|
||||
processingAt: nil
|
||||
)
|
||||
case clueID2:
|
||||
return WildReportRadarProcessRecord(
|
||||
handlerName: "李娜",
|
||||
phone: "18674598210",
|
||||
maskedPhone: maskPhone("18674598210"),
|
||||
statusText: "处理中",
|
||||
processResult: "待现场核查,请前往塔吾萨尼步道确认摄影师线索。",
|
||||
processedAt: nil,
|
||||
processingAt: "2024-05-18 10:35"
|
||||
)
|
||||
case clueID3:
|
||||
return WildReportRadarProcessRecord(
|
||||
handlerName: "赵强",
|
||||
phone: "15938267120",
|
||||
maskedPhone: maskPhone("15938267120"),
|
||||
statusText: "已处理",
|
||||
processResult: "已到场核查并完成劝离,处理结果已同步景区公安。",
|
||||
processedAt: "2024-05-17 18:26",
|
||||
processingAt: "2024-05-17 17:45"
|
||||
)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
private static func buildProcessingTimeline(
|
||||
record: WildReportRadarProcessRecord,
|
||||
completed: Bool
|
||||
) -> [WildReportRadarProcessingTimelineItem] {
|
||||
let startedAt = record.processingAt ?? "2024-05-18 10:35"
|
||||
return [
|
||||
WildReportRadarProcessingTimelineItem(
|
||||
title: "处理时间",
|
||||
time: startedAt,
|
||||
status: "done"
|
||||
),
|
||||
WildReportRadarProcessingTimelineItem(
|
||||
title: "处理完成时间",
|
||||
time: completed ? (record.processedAt ?? "刚刚") : "待完成",
|
||||
status: completed ? "done" : "current"
|
||||
)
|
||||
]
|
||||
}
|
||||
|
||||
private static func enrichClue(
|
||||
_ base: WildReportRadarActiveClue,
|
||||
processingStarted: Bool,
|
||||
completed: Bool = false,
|
||||
completionPhoto: String? = nil
|
||||
) -> WildReportRadarActiveClue {
|
||||
let record = processRecord(for: base.id)
|
||||
let isProcessing = processingStarted && !completed
|
||||
let timeline = (isProcessing || completed) && record != nil
|
||||
? buildProcessingTimeline(record: record!, completed: completed)
|
||||
: []
|
||||
|
||||
return WildReportRadarActiveClue(
|
||||
id: base.id,
|
||||
type: base.type,
|
||||
displayTitle: base.displayTitle,
|
||||
statusText: completed
|
||||
? "已处理"
|
||||
: (isProcessing ? "正在处理中" : base.statusText),
|
||||
actionText: base.actionText,
|
||||
nearestAbnormalText: base.nearestAbnormalText,
|
||||
distance: base.distance,
|
||||
direction: base.direction,
|
||||
updatedAt: completed ? (record?.processedAt ?? "刚刚") : base.updatedAt,
|
||||
name: base.name,
|
||||
storeName: base.storeName,
|
||||
avatar: base.avatar,
|
||||
summary: base.summary,
|
||||
detail: base.detail,
|
||||
distanceText: base.distanceText,
|
||||
reporterName: base.reporterName,
|
||||
reporterPhone: base.reporterPhone,
|
||||
maskedReporterPhone: base.maskedReporterPhone,
|
||||
reportContents: base.reportContents,
|
||||
images: base.images,
|
||||
processingStarted: processingStarted || completed,
|
||||
completed: completed,
|
||||
record: (isProcessing || completed) ? record : nil,
|
||||
processingTimeline: timeline,
|
||||
completionPhoto: completionPhoto
|
||||
)
|
||||
}
|
||||
|
||||
private static func maskPhone(_ phone: String) -> String {
|
||||
guard phone.count == 11, phone.allSatisfy(\.isNumber) else { return phone }
|
||||
return "\(phone.prefix(3))****\(phone.suffix(4))"
|
||||
}
|
||||
|
||||
private static func nearestAbnormalText(
|
||||
blueCoordinate: CLLocationCoordinate2D,
|
||||
redCoordinates: [CLLocationCoordinate2D]
|
||||
) -> String {
|
||||
guard !redCoordinates.isEmpty else { return "距离最近的异常点位0.00KM" }
|
||||
let minKm = redCoordinates.map {
|
||||
calcDistanceKm(
|
||||
lat1: blueCoordinate.latitude,
|
||||
lon1: blueCoordinate.longitude,
|
||||
lat2: $0.latitude,
|
||||
lon2: $0.longitude
|
||||
)
|
||||
}.min() ?? 0
|
||||
return String(format: "距离最近的异常点位%.2fKM", minKm)
|
||||
}
|
||||
|
||||
private static func calcDistanceKm(
|
||||
lat1: Double,
|
||||
lon1: Double,
|
||||
lat2: Double,
|
||||
lon2: Double
|
||||
) -> Double {
|
||||
let earthRadiusKm = 6371.0
|
||||
let toRad = { (deg: Double) in deg * .pi / 180 }
|
||||
let dLat = toRad(lat2 - lat1)
|
||||
let dLon = toRad(lon2 - lon1)
|
||||
let a = sin(dLat / 2) * sin(dLat / 2)
|
||||
+ cos(toRad(lat1)) * cos(toRad(lat2)) * sin(dLon / 2) * sin(dLon / 2)
|
||||
return earthRadiusKm * 2 * atan2(sqrt(a), sqrt(1 - a))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user