# 举报摄影师接口文档

本文档记录 iOS `WildPhotographerReport` 模块当前使用的后端接口。

## 基础约定

- 业务域：举报摄影师
- API 封装：`suixinkan/Features/WildPhotographerReport/API/WildPhotographerReportAPI.swift`
- 网络入口：`NetworkServices.shared.wildPhotographerReportAPI`
- 后端成功码：`100000`
- 响应包裹：工程统一 `APIEnvelope<T>`
- 当前状态：“首页文案”、“举报类型”、“提交举报”、“我的举报列表”、“举报详情”、“微信小程序分享”、“补充证据提交”和“附近风险地图”已接接口；模块内生产 Mock 数据已移除。

## 首页文案

### 用途

“举报摄影师”首页进入时拉取举报须知和举报规则文案。

### 请求

```http
GET /api/yf-handset-app/photog/report/copy
```

### 响应示例

```json
{
  "code": 100000,
  "msg": "success",
  "data": {
    "notice": [
      "需实名登录",
      "请尽量上传清晰证据",
      "恶意举报将被追责"
    ],
    "rule_groups": [
      {
        "title": "可举报行为",
        "items": [
          "疑似打野摄影师主动揽客",
          "未佩戴工牌开展摄影服务",
          "引导游客线下付款或私下交易"
        ]
      }
    ]
  },
  "time": "2026-07-09 10:15:17"
}
```

### UI 行为

- `notice` 用于首页“举报须知”条目展示。
- `rule_groups` 非空时显示“举报规则”按钮，点击后在举报规则页按分组展示 `title` 和 `items`。
- `notice` 与 `rule_groups` 都为空时，隐藏整个“举报须知”模块。
- 接口失败时不展示本地写死兜底文案，并 toast 错误。

### 当前调用位置

- `WildPhotographerReportHomeViewController.viewDidLoad()`
- `WildPhotographerReportHomeViewModel.loadReportCopy(api:)`
- `WildPhotographerReportAPI.reportCopy()`

## 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` 的类型；如果接口返回中不存在该类型，则选中列表第一项。
- 接口失败时不展示本地默认类型，并 toast 错误。

### 当前调用位置

- `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`：当前定位快照坐标；定位失败时不提交举报，提示用户重新获取位置。
- `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`：处理备注；`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 模型

```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
```

### UI 行为

- 详情页先展示列表带入的基础记录，再异步请求详情接口刷新完整数据。
- 如果传入记录没有 `serverID`，说明来自本地提交成功页等临时记录，不请求详情接口。
- 详情接口失败时保留现有基础信息，并 toast 错误。
- 图片证据使用 Kingfisher 加载网络图；视频证据展示视频样式缩略块和文件信息。
- 状态为已处理或驳回且存在处理信息时展示“处理信息”卡：处理人、处理人电话、处理时间、处理完成时间、处理意见。
- “处理人电话”展示脱敏文案，点击使用接口原始 `handler.phone` 或 `handler_phone` 发起 `tel://`。
- `handle_evidences` 非空时在“处理信息”卡内展示“处理凭证”横向缩略图，`file_type=2` 按视频处理，其余按图片处理。

### 当前调用位置

- `WildPhotographerReportDetailViewController.viewDidLoad()`
- `WildPhotographerReportDetailViewModel.loadDetailIfNeeded(api:)`
- `WildPhotographerReportAPI.reportDetail(id:)`

## 5. 举报微信小程序分享

### 用途

“我的举报”列表卡片和“举报详情”头部点击分享时调用，后端按举报 ID 返回微信小程序分享配置，iOS 映射为 `WeChatMiniProgramSharePayload` 后调用 `WeChatShareService.shareMiniProgram(...)`。

### 请求

```http
GET /api/yf-handset-app/photog/report/share?id=32
```

### 请求参数

| 参数 | 类型 | 说明 |
| --- | --- | --- |
| `id` | string | 必填，举报记录服务端 ID；iOS 使用 `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. 补充证据提交

### 请求

```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 清理

- 已删除本地种子举报、种子风险点、种子补充证据及对应生产代码文件。
- 举报类型、首页文案、列表、详情、补充证据和风险地图均不再使用本地写死数据兜底。
- 提交接口当前无业务 `data` 返回；提交成功页只展示本次用户提交快照，不伪造服务端举报编号，也不写入“我的举报”本地列表。
