feat: update app workflows and permissions
This commit is contained in:
@ -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 {
|
||||
/// 将「景区 · 详细地址」拆成两段展示文案。
|
||||
|
||||
Reference in New Issue
Block a user