新增举报摄影师模块并接入首页路由,桌面应用名改为随心瞰商家版。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-01 16:51:22 +08:00
parent 19d681192c
commit fcb86d43f5
25 changed files with 5515 additions and 48 deletions

View File

@ -0,0 +1,349 @@
//
// WildPhotographerReportModels.swift
// suixinkan
//
// Created by Codex on 2026/7/1.
//
import CoreLocation
import MapKit
import SwiftUI
///
enum WildReportPageState: String, CaseIterable, Identifiable {
case normal
case loading
case empty
case error
var id: String { rawValue }
var title: String {
switch self {
case .normal: "正常"
case .loading: "加载"
case .empty: "空状态"
case .error: "异常"
}
}
}
///
enum WildReportAttachmentKind {
case image
case video
case payment
}
/// Mock
enum WildReportType: String, CaseIterable, Identifiable, Hashable {
case suspectedWild = "疑似打野"
case noWorkBadge = "未戴工牌"
case other = "其他"
var id: String { rawValue }
var iconName: String {
switch self {
case .suspectedWild:
"person.crop.circle.badge.exclamationmark"
case .noWorkBadge:
"lanyardcard"
case .other:
"ellipsis.circle"
}
}
}
///
struct WildReportAttachment: Identifiable {
let id = UUID()
let kind: WildReportAttachmentKind
let title: String
}
///
enum WildReportStatus: String, Hashable, CaseIterable {
case pending = "待处理"
case processing = "处理中"
case processed = "已处理"
var displayColor: Color {
switch self {
case .pending: AppDesign.warning
case .processing: AppDesign.primary
case .processed: AppDesign.success
}
}
}
///
enum WildReportListFilter: String, CaseIterable, Identifiable {
case all = "全部"
case pending = "待处理"
case processing = "处理中"
case processed = "已处理"
var id: String { rawValue }
}
extension WildReportStatus {
var listDisplayText: String { rawValue }
var listDisplayColor: Color { displayColor }
}
///
struct WildReportSupplementaryEvidence: Identifiable, 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: Identifiable {
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: Identifiable, 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): "未填写"
case (false, false): "\(wechatID) / \(phoneNumber)"
case (false, true): wechatID
case (true, false): phoneNumber
}
}
}
///
struct WildReportCompletionMediaItem: Identifiable, 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 {
case completed
case current
case pending
}
///
struct WildReportProgressStep: Identifiable {
let id: String
let title: String
let timeText: String
let state: WildReportProgressStepState
}
///
enum WildReportReporterMediaItem: Identifiable {
case image(index: Int)
case video(index: Int)
var id: String {
switch self {
case .image(let index): "reporter-image-\(index)"
case .video(let index): "reporter-video-\(index)"
}
}
}
///
struct WildReportSubmitResult: Identifiable, Hashable {
let record: WildReportRecord
let imageCount: Int
let videoCount: Int
var id: String { record.id }
}
///
enum WildReportMapMarkerKind {
case selfLocation
case wildPhotographer
case processingClue
case peerPhotographer
}
///
struct WildReportMapMediaItem: Identifiable, Hashable {
let id: String
let title: String
let systemImage: String
let tint: Color
let isVideo: Bool
}
///
struct WildReportPreviewImageURL: Identifiable, Hashable {
let url: String
var id: String { url }
}
///
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: Identifiable, 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
}
}