feat: update app workflows and permissions

This commit is contained in:
2026-07-09 17:34:00 +08:00
parent 43e6133c21
commit 8e356973bd
44 changed files with 2944 additions and 307 deletions

View File

@ -273,6 +273,138 @@ struct WildReportListResponse: Decodable, Equatable {
}
}
///
struct WildReportMiniProgramShareData: Decodable, Equatable, Hashable {
let userName: String
let path: String
let title: String
let description: String
let imagePath: String
let webpageURL: String
let withShareTicket: Bool
let miniProgramTypeValue: Int
let sceneValue: Int
let disableForward: Bool
enum CodingKeys: String, CodingKey {
case userName
case userNameSnake = "user_name"
case path
case title
case description
case imagePath
case imagePathSnake = "image_path"
case webpageURL = "webpageUrl"
case webpageURLSnake = "webpage_url"
case withShareTicket
case withShareTicketSnake = "with_share_ticket"
case miniProgramTypeValue = "miniprogramType"
case miniProgramTypeValueSnake = "mini_program_type"
case sceneValue = "scene"
case disableForward
case disableForwardSnake = "disable_forward"
}
init(
userName: String = "",
path: String = "",
title: String = "",
description: String = "",
imagePath: String = "",
webpageURL: String = "",
withShareTicket: Bool = false,
miniProgramTypeValue: Int = WeChatMiniProgramType.release.rawValue,
sceneValue: Int = WeChatShareScene.session.rawValue,
disableForward: Bool = false
) {
self.userName = userName
self.path = path
self.title = title
self.description = description
self.imagePath = imagePath
self.webpageURL = webpageURL
self.withShareTicket = withShareTicket
self.miniProgramTypeValue = miniProgramTypeValue
self.sceneValue = sceneValue
self.disableForward = disableForward
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
userName = try container.decodeString(primary: .userName, fallback: .userNameSnake)
path = try container.decodeIfPresent(String.self, forKey: .path) ?? ""
title = try container.decodeIfPresent(String.self, forKey: .title) ?? ""
description = try container.decodeIfPresent(String.self, forKey: .description) ?? ""
imagePath = try container.decodeString(primary: .imagePath, fallback: .imagePathSnake)
webpageURL = try container.decodeString(primary: .webpageURL, fallback: .webpageURLSnake)
withShareTicket = try container.decodeBool(primary: .withShareTicket, fallback: .withShareTicketSnake)
miniProgramTypeValue = try container.decodeInt(
primary: .miniProgramTypeValue,
fallback: .miniProgramTypeValueSnake,
defaultValue: WeChatMiniProgramType.release.rawValue
)
sceneValue = try container.decodeInt(
primary: .sceneValue,
fallback: nil,
defaultValue: WeChatShareScene.session.rawValue
)
disableForward = try container.decodeBool(primary: .disableForward, fallback: .disableForwardSnake)
}
var miniProgramType: WeChatMiniProgramType {
WeChatMiniProgramType(rawValue: miniProgramTypeValue) ?? .release
}
var scene: WeChatShareScene {
WeChatShareScene(rawValue: sceneValue) ?? .session
}
var payload: WeChatMiniProgramSharePayload {
WeChatMiniProgramSharePayload(
title: title,
description: description,
webpageURL: webpageURL,
userName: userName,
path: path.nonEmpty,
miniProgramType: miniProgramType,
withShareTicket: withShareTicket,
disableForward: disableForward
)
}
}
private extension KeyedDecodingContainer where Key == WildReportMiniProgramShareData.CodingKeys {
func decodeString(primary: Key, fallback: Key?) throws -> String {
if let value = try decodeIfPresent(String.self, forKey: primary) {
return value
}
if let fallback, let value = try decodeIfPresent(String.self, forKey: fallback) {
return value
}
return ""
}
func decodeBool(primary: Key, fallback: Key?) throws -> Bool {
if let value = try decodeIfPresent(Bool.self, forKey: primary) {
return value
}
if let fallback, let value = try decodeIfPresent(Bool.self, forKey: fallback) {
return value
}
return false
}
func decodeInt(primary: Key, fallback: Key?, defaultValue: Int) throws -> Int {
if let value = try decodeIfPresent(Int.self, forKey: primary) {
return value
}
if let fallback, let value = try decodeIfPresent(Int.self, forKey: fallback) {
return value
}
return defaultValue
}
}
///
struct WildReportListItem: Decodable, Equatable, Hashable {
let id: Int
@ -416,19 +548,32 @@ struct WildReportDetailEvidence: Decodable, Equatable, Hashable {
let fileTypeText: String
let fileURL: String
let fileName: String
let evidenceKind: Int
let evidenceKindText: String
enum CodingKeys: String, CodingKey {
case fileType = "file_type"
case fileTypeText = "file_type_text"
case fileURL = "file_url"
case fileName = "file_name"
case evidenceKind = "evidence_kind"
case evidenceKindText = "evidence_kind_text"
}
init(fileType: Int = 0, fileTypeText: String = "", fileURL: String = "", fileName: String = "") {
init(
fileType: Int = 0,
fileTypeText: String = "",
fileURL: String = "",
fileName: String = "",
evidenceKind: Int = 0,
evidenceKindText: String = ""
) {
self.fileType = fileType
self.fileTypeText = fileTypeText
self.fileURL = fileURL
self.fileName = fileName
self.evidenceKind = evidenceKind
self.evidenceKindText = evidenceKindText
}
init(from decoder: Decoder) throws {
@ -437,12 +582,52 @@ struct WildReportDetailEvidence: Decodable, Equatable, Hashable {
fileTypeText = try container.decodeIfPresent(String.self, forKey: .fileTypeText) ?? ""
fileURL = try container.decodeIfPresent(String.self, forKey: .fileURL) ?? ""
fileName = try container.decodeIfPresent(String.self, forKey: .fileName) ?? ""
evidenceKind = try container.decodeIfPresent(Int.self, forKey: .evidenceKind) ?? 0
evidenceKindText = try container.decodeIfPresent(String.self, forKey: .evidenceKindText) ?? ""
}
var isVideo: Bool { fileType == 2 }
var isImage: Bool { fileType == 1 || fileType == 5 }
}
///
struct WildReportDetailHandler: Decodable, Equatable, Hashable {
let id: Int
let name: String
let phone: String
let startedAt: String
let finishedAt: String
let remark: String
enum CodingKeys: String, CodingKey {
case id
case name
case phone
case startedAt = "started_at"
case finishedAt = "finished_at"
case remark
}
init(id: Int = 0, name: String = "", phone: String = "", startedAt: String = "", finishedAt: String = "", remark: String = "") {
self.id = id
self.name = name
self.phone = phone
self.startedAt = startedAt
self.finishedAt = finishedAt
self.remark = remark
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decodeIfPresent(Int.self, forKey: .id) ?? 0
name = try container.decodeIfPresent(String.self, forKey: .name) ?? ""
phone = try container.decodeIfPresent(String.self, forKey: .phone) ?? ""
startedAt = try container.decodeIfPresent(String.self, forKey: .startedAt) ?? ""
finishedAt = try container.decodeIfPresent(String.self, forKey: .finishedAt) ?? ""
remark = try container.decodeIfPresent(String.self, forKey: .remark) ?? ""
}
}
///
struct WildReportDetailContent: Decodable, Equatable, Hashable {
let type: String
@ -534,8 +719,13 @@ struct WildReportDetailData: Decodable, Equatable, Hashable {
let handleStatus: Int
let handleStatusText: String
let handleRemark: String
let handledAt: String
let handler: WildReportDetailHandler?
let handlerName: String
let handlerPhone: String
let evidenceSummary: WildReportEvidenceSummary?
let evidences: [WildReportDetailEvidence]
let handleEvidences: [WildReportDetailEvidence]
let paymentScreenshots: [WildReportDetailEvidence]
let timeline: [WildReportDetailTimelineItem]
let createdAt: String
@ -560,8 +750,13 @@ struct WildReportDetailData: Decodable, Equatable, Hashable {
case handleStatus = "handle_status"
case handleStatusText = "handle_status_text"
case handleRemark = "handle_remark"
case handledAt = "handled_at"
case handler
case handlerName = "handler_name"
case handlerPhone = "handler_phone"
case evidenceSummary = "evidence_summary"
case evidences
case handleEvidences = "handle_evidences"
case paymentScreenshots = "payment_screenshots"
case timeline
case createdAt = "created_at"
@ -587,8 +782,13 @@ struct WildReportDetailData: Decodable, Equatable, Hashable {
handleStatus: Int = 0,
handleStatusText: String = "",
handleRemark: String = "",
handledAt: String = "",
handler: WildReportDetailHandler? = nil,
handlerName: String = "",
handlerPhone: String = "",
evidenceSummary: WildReportEvidenceSummary? = nil,
evidences: [WildReportDetailEvidence] = [],
handleEvidences: [WildReportDetailEvidence] = [],
paymentScreenshots: [WildReportDetailEvidence] = [],
timeline: [WildReportDetailTimelineItem] = [],
createdAt: String = ""
@ -612,8 +812,13 @@ struct WildReportDetailData: Decodable, Equatable, Hashable {
self.handleStatus = handleStatus
self.handleStatusText = handleStatusText
self.handleRemark = handleRemark
self.handledAt = handledAt
self.handler = handler
self.handlerName = handlerName
self.handlerPhone = handlerPhone
self.evidenceSummary = evidenceSummary
self.evidences = evidences
self.handleEvidences = handleEvidences
self.paymentScreenshots = paymentScreenshots
self.timeline = timeline
self.createdAt = createdAt
@ -640,8 +845,13 @@ struct WildReportDetailData: Decodable, Equatable, Hashable {
handleStatus = try container.decodeIfPresent(Int.self, forKey: .handleStatus) ?? 0
handleStatusText = try container.decodeIfPresent(String.self, forKey: .handleStatusText) ?? ""
handleRemark = try container.decodeIfPresent(String.self, forKey: .handleRemark) ?? ""
handledAt = try container.decodeIfPresent(String.self, forKey: .handledAt) ?? ""
handler = try container.decodeIfPresent(WildReportDetailHandler.self, forKey: .handler)
handlerName = try container.decodeIfPresent(String.self, forKey: .handlerName) ?? ""
handlerPhone = try container.decodeIfPresent(String.self, forKey: .handlerPhone) ?? ""
evidenceSummary = try container.decodeIfPresent(WildReportEvidenceSummary.self, forKey: .evidenceSummary)
evidences = try container.decodeIfPresent([WildReportDetailEvidence].self, forKey: .evidences) ?? []
handleEvidences = try container.decodeIfPresent([WildReportDetailEvidence].self, forKey: .handleEvidences) ?? []
paymentScreenshots = try container.decodeIfPresent([WildReportDetailEvidence].self, forKey: .paymentScreenshots) ?? []
timeline = try container.decodeIfPresent([WildReportDetailTimelineItem].self, forKey: .timeline) ?? []
createdAt = try container.decodeIfPresent(String.self, forKey: .createdAt) ?? ""
@ -684,6 +894,40 @@ struct WildReportDetailData: Decodable, Equatable, Hashable {
}
}
var detailHandlerInfo: WildReportHandlerInfo? {
let resolvedName = handler?.name.nonEmpty ?? handlerName.nonEmpty ?? ""
let resolvedPhone = handler?.phone.nonEmpty ?? handlerPhone.nonEmpty ?? ""
let processingAt = handler?.startedAt.nonEmpty ?? ""
let processedAt = handler?.finishedAt.nonEmpty ?? handledAt.nonEmpty ?? ""
let processOpinion = handler?.remark.nonEmpty ?? handleRemark.nonEmpty ?? ""
let media = handleEvidences.enumerated().compactMap { index, evidence -> WildReportCompletionMediaItem? in
guard let url = evidence.fileURL.nonEmpty else { return nil }
return WildReportCompletionMediaItem(
id: "\(index)-\(url)",
kind: evidence.isVideo ? .video : .image,
coverURL: url,
duration: evidence.isVideo ? evidence.fileTypeText.nonEmpty : nil
)
}
guard !resolvedName.isEmpty
|| !resolvedPhone.isEmpty
|| !processingAt.isEmpty
|| !processedAt.isEmpty
|| !processOpinion.isEmpty
|| !media.isEmpty
else {
return nil
}
return WildReportHandlerInfo(
handlerName: resolvedName,
handlerPhone: resolvedPhone,
processingAt: processingAt,
processedAt: processedAt,
processOpinion: processOpinion,
completionMedia: media
)
}
/// UIKit
func toRecord(fallback: WildReportRecord? = nil) -> WildReportRecord {
WildReportRecord(
@ -702,7 +946,7 @@ struct WildReportDetailData: Decodable, Equatable, Hashable {
phoneNumber: contactPhone.nonEmpty ?? fallback?.phoneNumber ?? "",
imageCount: imageCount,
videoCount: videoCount,
handlerInfo: fallback?.handlerInfo
handlerInfo: detailHandlerInfo ?? fallback?.handlerInfo
)
}
}
@ -724,6 +968,14 @@ struct WildReportSupplementaryEvidence: Hashable {
}
}
///
enum WildReportSupplementDisplayFormatter {
/// 1
static func sequenceTitle(forDisplayIndex index: Int) -> String {
"\(max(0, index) + 1)次补充"
}
}
///
enum WildReportLocationParser {
/// ·