feat: add wild photographer report mock flow

This commit is contained in:
2026-07-08 12:06:13 +08:00
parent 87fba3d5d6
commit 8a65d3c104
14 changed files with 3542 additions and 0 deletions

View File

@ -0,0 +1,349 @@
//
// WildPhotographerReportModels.swift
// suixinkan
//
import CoreLocation
import Foundation
import MapKit
import UIKit
///
enum WildReportPageState: String, CaseIterable, Hashable {
case normal
case loading
case empty
case error
var title: String {
switch self {
case .normal: return "正常"
case .loading: return "加载"
case .empty: return "空状态"
case .error: return "异常"
}
}
}
///
enum WildReportAttachmentKind: Hashable {
case image
case video
case payment
}
/// Mock
enum WildReportType: String, CaseIterable, Hashable {
case suspectedWild = "疑似打野"
case noWorkBadge = "未戴工牌"
case other = "其他"
var iconName: String {
switch self {
case .suspectedWild: return "person.crop.circle.badge.exclamationmark"
case .noWorkBadge: return "lanyardcard"
case .other: return "ellipsis.circle"
}
}
}
///
struct WildReportAttachment: Hashable {
let id: UUID
let kind: WildReportAttachmentKind
let title: String
init(id: UUID = UUID(), kind: WildReportAttachmentKind, title: String) {
self.id = id
self.kind = kind
self.title = title
}
}
///
enum WildReportStatus: String, Hashable, CaseIterable {
case pending = "待处理"
case processing = "处理中"
case processed = "已处理"
var displayColor: UIColor {
switch self {
case .pending: return AppColor.warning
case .processing: return AppColor.primary
case .processed: return AppColor.success
}
}
var backgroundColor: UIColor {
switch self {
case .pending: return AppColor.warningBackground
case .processing: return AppColor.infoBackground
case .processed: return AppColor.successBackground
}
}
}
///
enum WildReportListFilter: String, CaseIterable, Hashable {
case all = "全部"
case pending = "待处理"
case processing = "处理中"
case processed = "已处理"
}
///
struct WildReportSupplementaryEvidence: Hashable {
let id: String
let submitTime: String
let text: String
let imageCount: Int
let videoCount: Int
var summaryText: String {
var parts: [String] = []
if !text.isEmpty { parts.append(text) }
if imageCount > 0 { parts.append("\(imageCount)张图片") }
if videoCount > 0 { parts.append("\(videoCount)段视频") }
return parts.isEmpty ? "补充证据" : parts.joined(separator: " · ")
}
}
///
enum WildReportLocationParser {
/// ·
static func split(_ location: String) -> (scenic: String, detail: String) {
let parts = location
.components(separatedBy: " · ")
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
.filter { !$0.isEmpty }
if parts.count >= 2 {
return (parts[0], parts[1])
}
return (location, location)
}
}
///
struct WildReportRiskPoint: Hashable {
let id: Int
let title: String
let location: String
let distance: String
let riskLevel: String
let evidenceCount: Int
let latestTime: String
let description: String
}
///
struct WildReportRecord: Hashable {
let id: String
let title: String
let reportType: WildReportType
let status: WildReportStatus
let location: String
let submitTime: String
let description: String
let wechatID: String
let phoneNumber: String
let imageCount: Int
let videoCount: Int
let handlerInfo: WildReportHandlerInfo?
var contactDisplayText: String {
switch (wechatID.isEmpty, phoneNumber.isEmpty) {
case (true, true): return "未填写"
case (false, false): return "\(wechatID) / \(phoneNumber)"
case (false, true): return wechatID
case (true, false): return phoneNumber
}
}
}
///
struct WildReportCompletionMediaItem: Hashable {
enum Kind: Hashable {
case image
case video
}
let id: String
let kind: Kind
let coverURL: String
let duration: String?
}
///
struct WildReportHandlerInfo: Hashable {
let handlerName: String
let handlerPhone: String
let processingAt: String
let processedAt: String
let processOpinion: String
let completionMedia: [WildReportCompletionMediaItem]
var maskedHandlerPhone: String {
guard handlerPhone.count == 11, handlerPhone.allSatisfy(\.isNumber) else { return handlerPhone }
return "\(handlerPhone.prefix(3))****\(handlerPhone.suffix(4))"
}
var completionMediaSummary: String {
let imageCount = completionMedia.filter { $0.kind == .image }.count
let videoCount = completionMedia.filter { $0.kind == .video }.count
switch (imageCount, videoCount) {
case (0, 0): return "0项"
case (_, 0): return "\(imageCount)"
case (0, _): return "\(videoCount)"
default: return "\(imageCount)\(videoCount)"
}
}
}
///
enum WildReportProgressStepState: Hashable {
case completed
case current
case pending
}
///
struct WildReportProgressStep: Hashable {
let id: String
let title: String
let timeText: String
let state: WildReportProgressStepState
}
///
enum WildReportReporterMediaItem: Hashable {
case image(index: Int)
case video(index: Int)
var id: String {
switch self {
case .image(let index): return "reporter-image-\(index)"
case .video(let index): return "reporter-video-\(index)"
}
}
}
///
struct WildReportSubmitResult: Hashable {
let record: WildReportRecord
let imageCount: Int
let videoCount: Int
}
///
enum WildReportMapMarkerKind: Hashable {
case selfLocation
case wildPhotographer
case processingClue
case peerPhotographer
var color: UIColor {
switch self {
case .selfLocation: return AppColor.primary
case .wildPhotographer: return AppColor.danger
case .processingClue: return AppColor.warning
case .peerPhotographer: return AppColor.success
}
}
var title: String {
switch self {
case .selfLocation: return "我的位置"
case .wildPhotographer: return "疑似打野"
case .processingClue: return "处理中线索"
case .peerPhotographer: return "内部摄影师"
}
}
}
///
enum WildReportRadarMarkerType: String, Hashable {
case blue
case green
case red
}
/// 线
struct WildReportRadarReportContent: Hashable {
let time: String
let content: String
}
/// 线
struct WildReportRadarProcessRecord: Hashable {
let handlerName: String
let phone: String
let maskedPhone: String
let statusText: String
let processResult: String
let processedAt: String?
let processingAt: String?
}
/// 线
struct WildReportRadarProcessingTimelineItem: Hashable {
let title: String
let time: String
let status: String
}
/// 线
struct WildReportRadarActiveClue: Hashable {
let id: String
let type: WildReportRadarMarkerType
let displayTitle: String
let statusText: String
let actionText: String
let nearestAbnormalText: String?
let distance: String?
let direction: String?
let updatedAt: String?
let name: String?
let storeName: String?
let avatar: String?
let summary: String?
let detail: String?
let distanceText: String?
let reporterName: String?
let reporterPhone: String?
let maskedReporterPhone: String?
let reportContents: [WildReportRadarReportContent]
let images: [String]
let processingStarted: Bool
let completed: Bool
let record: WildReportRadarProcessRecord?
let processingTimeline: [WildReportRadarProcessingTimelineItem]
let completionPhoto: String?
}
///
enum WildReportMapMarkerDetail: Hashable {
case activeClue(WildReportRadarActiveClue)
}
///
struct WildReportMapMarker: Hashable {
let id: String
let title: String
let coordinate: CLLocationCoordinate2D
let kind: WildReportMapMarkerKind
let detail: WildReportMapMarkerDetail
static func == (lhs: WildReportMapMarker, rhs: WildReportMapMarker) -> Bool {
lhs.id == rhs.id
}
func hash(into hasher: inout Hasher) {
hasher.combine(id)
}
}
extension CLLocationCoordinate2D: @retroactive Equatable {
public static func == (lhs: CLLocationCoordinate2D, rhs: CLLocationCoordinate2D) -> Bool {
lhs.latitude == rhs.latitude && lhs.longitude == rhs.longitude
}
}