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

@ -24,6 +24,8 @@ protocol WildPhotographerReportServing {
func reportList(_ request: WildReportListRequest) async throws -> WildReportListResponse
///
func reportDetail(id: Int) async throws -> WildReportDetailData
///
func reportShare(id: String) async throws -> WildReportMiniProgramShareData
}
/// API `photog/report`
@ -115,6 +117,17 @@ final class WildPhotographerReportAPI: WildPhotographerReportServing {
)
)
}
///
func reportShare(id: String) async throws -> WildReportMiniProgramShareData {
try await client.send(
APIRequest(
method: .get,
path: "/api/yf-handset-app/photog/report/share",
queryItems: [URLQueryItem(name: "id", value: id)]
)
)
}
}
///

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 {
/// ·

View File

@ -291,7 +291,6 @@ final class WildPhotographerReportSubmitViewModel {
guard !isSubmitting else { return }
isSubmitting = true
submitProgressText = "正在上传证据"
notifyStateChange()
defer {
isSubmitting = false
@ -303,19 +302,13 @@ final class WildPhotographerReportSubmitViewModel {
let evidenceRequests = try await uploadAttachments(
images + videos,
uploader: uploader,
scenicId: scenicId,
progressPrefix: "正在上传现场证据"
scenicId: scenicId
)
let paymentRequests = try await uploadAttachments(
paymentImages,
uploader: uploader,
scenicId: scenicId,
progressPrefix: "正在上传支付截图",
completedOffset: evidenceRequests.count,
totalOverride: evidenceRequests.count + paymentImages.count
scenicId: scenicId
)
submitProgressText = "正在提交举报"
notifyStateChange()
let submitResponse = try await api.submitReport(buildSubmitRequest(
scenicId: scenicId,
@ -447,27 +440,17 @@ final class WildPhotographerReportSubmitViewModel {
private func uploadAttachments(
_ attachments: [WildReportAttachment],
uploader: any WildReportAttachmentUploading,
scenicId: Int,
progressPrefix: String,
completedOffset: Int = 0,
totalOverride: Int? = nil
scenicId: Int
) async throws -> [WildReportEvidenceRequest] {
var requests: [WildReportEvidenceRequest] = []
let total = totalOverride ?? attachments.count
for (index, attachment) in attachments.enumerated() {
let current = completedOffset + index + 1
submitProgressText = "\(progressPrefix) \(current)/\(max(total, current))"
notifyStateChange()
for attachment in attachments {
let data = try attachment.uploadData()
let url = try await uploader.uploadWildReportAttachment(
data: data,
fileName: attachment.fileName,
fileType: attachment.fileType,
scenicId: scenicId
) { [weak self] progress in
self?.submitProgressText = "\(progressPrefix) \(current)/\(max(total, current)) · \(progress)%"
self?.notifyStateChange()
}
) { _ in }
requests.append(WildReportEvidenceRequest(
fileURL: url,
fileType: attachment.fileType,
@ -666,9 +649,21 @@ final class WildPhotographerReportDetailViewModel {
showActionMessage("举报位置:\(record.location)")
}
///
func shareReport() {
showActionMessage("已生成举报 \(record.id) 的分享信息,可发送给景区处理人员查看进度。")
///
func shareReport(api: any WildPhotographerReportServing) async -> WeChatMiniProgramSharePayload? {
guard let serverID = record.serverID, serverID > 0 else {
showActionMessage("当前举报暂未同步到服务器,无法分享")
return nil
}
do {
let shareData = try await api.reportShare(id: String(serverID))
return shareData.payload
} catch is CancellationError {
return nil
} catch {
showActionMessage(error.localizedDescription)
return nil
}
}
private func showActionMessage(_ message: String) {
@ -782,11 +777,27 @@ final class WildPhotographerReportListViewModel {
}
}
///
func shareReport(_ record: WildReportRecord) {
shareMessage = "已生成举报 \(record.id) 的分享信息,可发送给景区处理人员查看进度。"
onShowMessage?(shareMessage ?? "")
notifyStateChange()
///
func shareReport(_ record: WildReportRecord, api: any WildPhotographerReportServing) async -> WeChatMiniProgramSharePayload? {
guard let serverID = record.serverID, serverID > 0 else {
shareMessage = "当前举报暂未同步到服务器,无法分享"
onShowMessage?(shareMessage ?? "")
notifyStateChange()
return nil
}
do {
let shareData = try await api.reportShare(id: String(serverID))
shareMessage = nil
notifyStateChange()
return shareData.payload
} catch is CancellationError {
return nil
} catch {
shareMessage = error.localizedDescription
onShowMessage?(shareMessage ?? "")
notifyStateChange()
return nil
}
}
private func reload(api: any WildPhotographerReportServing, scenicId: Int?) async {
@ -907,7 +918,6 @@ final class WildReportSupplementEvidenceViewModel {
guard !isSubmitting else { return }
isSubmitting = true
submitProgressText = evidenceCount > 0 ? "正在上传补充证据" : "正在提交补充证据"
notifyStateChange()
defer {
isSubmitting = false
@ -921,8 +931,6 @@ final class WildReportSupplementEvidenceViewModel {
uploader: uploader,
scenicId: scenicId
)
submitProgressText = "正在提交补充证据"
notifyStateChange()
try await api.supplementReport(WildReportSupplementRequest(
id: serverID ?? 0,
@ -980,20 +988,14 @@ final class WildReportSupplementEvidenceViewModel {
scenicId: Int
) async throws -> [WildReportEvidenceRequest] {
var requests: [WildReportEvidenceRequest] = []
for (index, attachment) in attachments.enumerated() {
let current = index + 1
submitProgressText = "正在上传补充证据 \(current)/\(attachments.count)"
notifyStateChange()
for attachment in attachments {
let data = try attachment.uploadData()
let url = try await uploader.uploadWildReportAttachment(
data: data,
fileName: attachment.fileName,
fileType: attachment.fileType,
scenicId: scenicId
) { [weak self] progress in
self?.submitProgressText = "正在上传补充证据 \(current)/\(attachments.count) · \(progress)%"
self?.notifyStateChange()
}
) { _ in }
requests.append(WildReportEvidenceRequest(
fileURL: url,
fileType: attachment.fileType,

View File

@ -9,7 +9,7 @@
- 网络入口:`NetworkServices.shared.wildPhotographerReportAPI`
- 后端成功码:`100000`
- 响应包裹:工程统一 `APIEnvelope<T>`
- 当前状态:“首页文案”、“举报类型”、“提交举报”、“我的举报列表”、“举报详情”、“补充证据提交”和“附近风险地图”已接接口;模块内生产 Mock 数据已移除。
- 当前状态:“首页文案”、“举报类型”、“提交举报”、“我的举报列表”、“举报详情”、“微信小程序分享”、“补充证据提交”和“附近风险地图”已接接口;模块内生产 Mock 数据已移除。
## 首页文案
@ -334,7 +334,7 @@ struct WildReportListResponse: Decodable, Equatable {
### 用途
“举报详情”页面进入时调用。列表页传入 `WildReportRecord.serverID`,详情接口返回完整举报信息、现场证据、支付截图、补充内容和处理时间线。
“举报详情”页面进入时调用。列表页传入 `WildReportRecord.serverID`,详情接口返回完整举报信息、现场证据、支付截图、补充内容、处理信息和处理时间线。
### 请求
@ -358,7 +358,10 @@ GET /api/yf-handset-app/photog/report/detail?id=32
- `supplements` / `report_contents`:补充内容记录,优先使用 `supplements`,为空时从 `report_contents` 过滤 `type=supplement`
- `location_name` / `location_address`:详情页位置展示。
- `handle_status` / `handle_status_text`处理状态iOS 支持 0 待处理、1 处理中、2 已处理、3 驳回。
- `handle_remark`:处理备注,有值时在详情信息卡展示
- `handle_remark`:处理备注`handler.remark` 为空时作为“处理信息”卡的处理意见兜底
- `handler`:处理人结构,包含 `name``phone``started_at``finished_at``remark`,详情页“处理信息”卡优先使用。
- `handler_name` / `handler_phone` / `handled_at`:处理信息顶层兼容字段;`handler` 缺字段时作为兜底。
- `handle_evidences`:处理凭证附件数组,字段同证据附件,并额外支持 `evidence_kind` / `evidence_kind_text`
- `timeline`:处理进度时间线;为空时只展示列表记录中的当前处理状态,不生成本地模拟流程。
### iOS 模型
@ -366,6 +369,7 @@ GET /api/yf-handset-app/photog/report/detail?id=32
```swift
struct WildReportDetailData: Decodable, Equatable, Hashable
struct WildReportDetailEvidence: Decodable, Equatable, Hashable
struct WildReportDetailHandler: Decodable, Equatable, Hashable
struct WildReportDetailContent: Decodable, Equatable, Hashable
struct WildReportDetailTimelineItem: Decodable, Equatable, Hashable
```
@ -376,6 +380,9 @@ struct WildReportDetailTimelineItem: Decodable, Equatable, Hashable
- 如果传入记录没有 `serverID`,说明来自本地提交成功页等临时记录,不请求详情接口。
- 详情接口失败时保留现有基础信息,并 toast 错误。
- 图片证据使用 Kingfisher 加载网络图;视频证据展示视频样式缩略块和文件信息。
- 状态为已处理或驳回且存在处理信息时展示“处理信息”卡:处理人、处理人电话、处理时间、处理完成时间、处理意见。
- “处理人电话”展示脱敏文案,点击使用接口原始 `handler.phone``handler_phone` 发起 `tel://`
- `handle_evidences` 非空时在“处理信息”卡内展示“处理凭证”横向缩略图,`file_type=2` 按视频处理,其余按图片处理。
### 当前调用位置
@ -383,7 +390,62 @@ struct WildReportDetailTimelineItem: Decodable, Equatable, Hashable
- `WildPhotographerReportDetailViewModel.loadDetailIfNeeded(api:)`
- `WildPhotographerReportAPI.reportDetail(id:)`
## 5. 补充证据提交
## 5. 举报微信小程序分享
### 用途
“我的举报”列表卡片和“举报详情”头部点击分享时调用,后端按举报 ID 返回微信小程序分享配置iOS 映射为 `WeChatMiniProgramSharePayload` 后调用 `WeChatShareService.shareMiniProgram(...)`
### 请求
```http
GET /api/yf-handset-app/photog/report/share?id=32
```
### 请求参数
| 参数 | 类型 | 说明 |
| --- | --- | --- |
| `id` | string | 必填,举报记录服务端 IDiOS 使用 `WildReportRecord.serverID` |
### 响应示例
```json
{
"code": 100000,
"msg": "success",
"data": {
"userName": "wx8c85189bf3bdda29",
"path": "pages/scenic/radar/report-detail/index?id=32",
"title": "私下收款举报",
"imagePath": "/assets/share/report-share-cover.png",
"webpageUrl": "https://www.youfuntour.com",
"withShareTicket": true,
"miniprogramType": 0,
"scene": 0
},
"time": "2026-07-09 15:42:19"
}
```
### 字段说明
- `title`:微信分享卡片标题,不能为空。
- `webpageUrl`:小程序打不开时的兼容网页地址,微信要求不超过 1024 字节。
- `userName`:微信 SDK `WXMiniProgramObject.userName` 字段;请后端确认返回值是否应为小程序原始 ID `gh_...`
- `path`:小程序页面路径,可携带举报 ID 或分享 token。
- `miniprogramType`0 正式版、1 开发版、2 体验版;由后端按环境明确返回。
- `withShareTicket`:是否携带群分享票据。
- `scene`:当前仅支持 0 微信好友会话;非 0 会被通用微信分享模块拒绝。
- `imagePath`当前返回的是小程序内资源路径iOS 不下载;微信小程序预览图暂使用 App 图标兜底。若后续需要定制卡片图,请返回可下载的 `https` 图片地址。
### 当前调用位置
- `WildPhotographerReportListViewController` 列表卡片分享按钮
- `WildPhotographerReportDetailViewController` 详情头部分享按钮
- `WildPhotographerReportAPI.reportShare(id:)`
## 6. 补充证据提交
### 请求