# 举报摄影师接口文档 本文档记录 iOS `WildPhotographerReport` 模块当前使用的后端接口,以及仍处于 Mock 阶段的业务点。 ## 基础约定 - 业务域:举报摄影师 - API 封装:`suixinkan/Features/WildPhotographerReport/API/WildPhotographerReportAPI.swift` - 网络入口:`NetworkServices.shared.wildPhotographerReportAPI` - 后端成功码:`100000` - 响应包裹:工程统一 `APIEnvelope` - 当前状态:“举报类型”、“提交举报”、“我的举报列表”、“举报详情”和“补充证据提交”已接接口;风险地图仍使用 Mock 数据闭环。 ## 1. 获取举报类型 ### 用途 提交举报页面进入时拉取可选举报类型。举报类型由后台动态维护,客户端不能用枚举写死,也不展示图标。 ### 请求 ```http GET /api/yf-handset-app/photog/report/types ``` ### 请求参数 无。 ### 响应示例 ```json { "code": 100000, "msg": "success", "data": { "list": [ { "value": 1, "label": "私下收款" }, { "value": 2, "label": "疑似打野" }, { "value": 3, "label": "未戴工牌" }, { "value": 4, "label": "其他" } ] }, "time": "2026-07-08 14:21:21" } ``` ### iOS 模型 ```swift struct WildReportType: Decodable, Hashable { let value: Int let label: String } struct WildReportTypeListResponse: Decodable, Hashable { let list: [WildReportType] } ``` ### UI 行为 - 页面加载时调用 `WildPhotographerReportSubmitViewModel.loadReportTypes(api:)`。 - 按接口返回顺序渲染举报类型。 - 每行最多展示 3 个类型,超过 3 个自动换行。 - 按 `label` 展示文字,不展示图标。 - 默认选中 `value == 2` 的类型;如果接口返回中不存在该类型,则选中列表第一项。 - 接口失败时回退本地兜底列表,并提示“举报类型获取失败,已使用默认类型”。 ### 当前调用位置 - `WildPhotographerReportSubmitViewController.viewDidLoad()` - `WildPhotographerReportSubmitViewModel.loadReportTypes(api:)` - `WildPhotographerReportAPI.reportTypes()` ## 2. 提交举报 ### 用途 提交页点击“提交举报”时调用。客户端会先把现场证据和支付截图上传到 OSS,全部上传成功后再提交表单数据。 ### 请求 ```http POST /api/yf-handset-app/photog/report/submit ``` ### 请求参数 ```json { "scenic_id": 1, "report_type": 2, "desc": "疑似人员在观景台附近主动揽客,并引导游客线下转账。", "contact_phone": "13800138000", "location_name": "黄山风景区", "location_address": "光明顶景区", "lat": 30.12345, "lng": 120.12345, "evidences": [ { "file_url": "https://example.com/photog_report/20260708/1/report_image.jpg", "file_type": 1, "file_name": "report_image.jpg" }, { "file_url": "https://example.com/photog_report/20260708/1/report_video.mp4", "file_type": 2, "file_name": "report_video.mp4" } ], "payment_screenshots": [ { "file_url": "https://example.com/photog_report/20260708/1/payment.jpg", "file_type": 1, "file_name": "payment.jpg" } ] } ``` ### 字段说明 - `scenic_id`:当前景区 ID,来自 `AppStore.shared.currentScenicId`,必须大于 0。 - `report_type`:举报类型接口返回的 `value`。 - `desc`:举报说明,必填,最多 500 字。 - `store_user_id`:被举报摄影师 ID;当前页面无来源,本轮暂不传。 - `contact_phone`:当前联系方式输入值,非空才传。 - `location_name` / `location_address`:从当前定位展示文案拆分;无法拆分时使用景区名和完整地址兜底。 - `lat` / `lng`:当前定位快照坐标;定位失败时使用 Mock 地址对应的兜底坐标。 - `evidences`:现场证据,必填,至少 1 项、最多 9 项。 - `payment_screenshots`:线下微信、支付宝截图,选填,最多 3 张。 - `file_type`:举报提交接口约定为 `1 = 图片`,`2 = 视频`。 ### iOS 模型 ```swift struct WildReportSubmitRequest: Encodable, Equatable { let scenicId: Int let reportType: Int let desc: String let storeUserId: Int? let contactPhone: String? let locationName: String? let locationAddress: String? let lat: Double let lng: Double let evidences: [WildReportEvidenceRequest] let paymentScreenshots: [WildReportEvidenceRequest] } struct WildReportEvidenceRequest: Encodable, Equatable { let fileURL: String let fileType: Int let fileName: String? } ``` ### UI 与上传行为 - 图片、视频和支付截图使用系统相册选择器。 - 图片/视频选择后先保存到本地临时目录,点击附件 tile 可预览本地文件。 - 提交时顺序上传附件到 OSS,按钮展示上传进度并禁用重复提交。 - 任一 OSS 上传失败时不调用提交接口,并保留用户已填写内容。 - 支付截图通过 `payment_screenshots` 独立提交,不替代现场证据必填校验。 ### 当前调用位置 - `WildPhotographerReportSubmitViewController.submitTapped()` - `WildPhotographerReportSubmitViewModel.submitReport(api:uploader:scenicId:scenicName:)` - `OSSUploadService.uploadWildReportAttachment(...)` - `WildPhotographerReportAPI.submitReport(_:)` ## 3. 我的举报列表 ### 用途 “我的举报”页面进入、切换状态筛选、滚动加载更多时调用。 ### 请求 ```http GET /api/yf-handset-app/photog/report/list ``` ### 请求参数 | 参数 | 类型 | 说明 | | --- | --- | --- | | `scenic_id` | integer | 可选,当前景区 ID,大于 0 时传 | | `handle_status` | integer | 可选,状态筛选;“全部”不传该参数,其他筛选 iOS 固定使用该参数 | | `status` | integer | 可选,与 `handle_status` 等价;iOS 不传 | | `page` | integer | 可选,默认 1 | | `page_size` | integer | 可选,默认 10,最大 50 | ### 状态码 - 全部:不传 `handle_status` - `0`:待处理 - `1`:处理中 - `2`:已处理 ### 响应示例 ```json { "code": 100000, "msg": "success", "data": { "list": [ { "id": 32, "complaint_no": "JB20260708917", "title": "私下收款举报", "report_type": 1, "report_type_text": "私下收款", "desc": "我在", "scenic_id": 128, "scenic_name": "伊犁那拉提景区-5A", "location_name": "伊犁那拉提景区-5A", "latitude": "32.4299428", "longitude": "119.4403263", "handle_status": 0, "handle_status_text": "待处理", "created_at": "2026-07-08 16:06:59" } ], "total": 1, "page": 1, "page_size": 10 }, "time": "2026-07-08 16:07:30" } ``` ### iOS 模型 ```swift struct WildReportListRequest: Equatable { let scenicId: Int? let handleStatus: Int? let page: Int let pageSize: Int } struct WildReportListResponse: Decodable, Equatable { let list: [WildReportListItem] let total: Int let page: Int let pageSize: Int } ``` ### UI 行为 - 页面首次进入请求 `page=1&page_size=10`,不传 `handle_status`。 - 切换筛选时重置为第一页,待处理传 `handle_status=0`,处理中传 `handle_status=1`,已处理传 `handle_status=2`。 - 滚动到底部时,如果仍有更多数据,则请求下一页并追加。 - 首屏失败显示 toast 和空态;加载更多失败只 toast,并保留已有列表。 - 列表接口暂不返回媒体数量,`WildReportRecord.imageCount/videoCount` 暂按 0 处理,进入详情页后由详情接口补齐。 ### 当前调用位置 - `WildPhotographerReportListViewController.viewDidLoad()` - `WildPhotographerReportListViewModel.loadInitial(api:scenicId:)` - `WildPhotographerReportListViewModel.loadMoreIfNeeded(api:scenicId:)` - `WildPhotographerReportAPI.reportList(_:)` ## 4. 举报详情 ### 用途 “举报详情”页面进入时调用。列表页传入 `WildReportRecord.serverID`,详情接口返回完整举报信息、现场证据、支付截图、补充内容和处理时间线。 ### 请求 ```http GET /api/yf-handset-app/photog/report/detail?id=32 ``` ### 请求参数 | 参数 | 类型 | 说明 | | --- | --- | --- | | `id` | integer | 必填,举报记录数值 ID,来自列表接口返回的 `id` | ### 关键响应字段 - `complaint_no`:页面展示的举报编号。 - `report_type` / `report_type_text`:举报类型。 - `desc`:初始举报说明。 - `evidences`:初始现场证据,详情页“举报人材料”区域展示。 - `payment_screenshots`:支付截图,详情页单独展示。 - `supplements` / `report_contents`:补充内容记录,优先使用 `supplements`,为空时从 `report_contents` 过滤 `type=supplement`。 - `location_name` / `location_address`:详情页位置展示。 - `handle_status` / `handle_status_text`:处理状态;iOS 支持 0 待处理、1 处理中、2 已处理、3 驳回。 - `handle_remark`:处理备注,有值时在详情信息卡展示。 - `timeline`:处理进度时间线;为空时使用本地状态推导兜底。 ### iOS 模型 ```swift struct WildReportDetailData: Decodable, Equatable, Hashable struct WildReportDetailEvidence: Decodable, Equatable, Hashable struct WildReportDetailContent: Decodable, Equatable, Hashable struct WildReportDetailTimelineItem: Decodable, Equatable, Hashable ``` ### UI 行为 - 详情页先展示列表带入的基础记录,再异步请求详情接口刷新完整数据。 - 如果传入记录没有 `serverID`,说明来自本地提交成功页等临时记录,不请求详情接口。 - 详情接口失败时保留现有基础信息,并 toast 错误。 - 图片证据使用 Kingfisher 加载网络图;视频证据展示视频样式缩略块和文件信息。 ### 当前调用位置 - `WildPhotographerReportDetailViewController.viewDidLoad()` - `WildPhotographerReportDetailViewModel.loadDetailIfNeeded(api:)` - `WildPhotographerReportAPI.reportDetail(id:)` ## 5. 补充证据提交 ### 请求 ```http POST /api/yf-handset-app/photog/report/supplement ``` ### 请求体 ```json { "id": 32, "desc": "补充说明", "evidences": [ { "file_url": "https://example.com/supplement.jpg", "file_type": 1, "file_name": "supplement.jpg" } ] } ``` ### 字段说明 - `id`:服务端举报 ID,必填,需大于等于 1;本地临时记录没有 `serverID` 时不允许补充提交。 - `desc`:补充说明,选填,最多 500 字;与 `evidences` 至少填写一项。 - `evidences`:补充附件,选填,最多 9 项;附件先通过 OSS `photog_report` 模块上传后再提交 URL。 - `file_type`:沿用举报提交接口约定,1 图片,2 视频。 ### 当前调用位置 - `WildReportSupplementEvidenceViewModel.submit(api:uploader:scenicId:)` - `WildPhotographerReportAPI.supplementReport(_:)` - `WildReportSupplementEvidenceViewController.submitTapped()` ## Mock 阶段业务 以下业务当前仍由 `WildPhotographerReportMockStore` 和 ViewModel 本地逻辑完成,暂未接真实接口: - 附近风险地图 - 分享、媒体预览、地图导航演示提示 后续接入真实接口时,应优先在 `WildPhotographerReportServing` 中补充语义化方法,并保持 ViewModel 不依赖 UIKit 视图类型。