# 保险模块前端对接完整指南

**日期**: 2026-03-22
**模块**: 保险管理（hl-insurance-service）
**类型**: 前端对接指南

---

## 一、核心概念（必须理解）

保险模块有 **两层** 概念，搞混会导致字段传错：

### 第一层：第三方保险目录（只读，不可编辑）

来自保游网 API 同步，管理员只能查看，不能修改。

```
InsuranceProduct（保险产品）  ← 比如"保游未来星研学旅行保险"
  └── InsurancePlan（保险计划）  ← 比如"20万计划""30万计划""50万计划"
        └── InsuranceRate（费率）  ← 比如"1天=5元""4-7天=15元"
```

**前端使用场景**：
- "保险产品"页面展示产品列表
- 编辑方案时，选择"保险产品"下拉 → 联动加载该产品的"保险计划"下拉

### 第二层：自定义方案模板（可编辑）

管理员创建的投保模板，关联到订单上使用。

```
InsuranceScheme（保险方案）  ← 比如"6日行程保险方案"
  └── InsuranceSchemeSegment（保险段）  ← 比如"第1-3天低风险段""第4-6天高风险段"
        ├── productId → 引用哪个保险产品
        └── planId → 引用哪个保险计划
```

**关键区别**：
- **Product/Plan** = 保险公司提供的产品目录（同步来的，不能改）
- **Scheme/Segment** = 我们自己组装的方案模板（可以自由创建/编辑）
- 一个方案可以有多个段，每段可以用不同的产品和计划

---

## 二、方案编辑页面字段说明

### 方案基本信息

| 字段 | API字段名 | 类型 | 必填 | 说明 |
|------|----------|------|------|------|
| 方案名称 | `name` | String | ✅ | 最长100字符 |
| 产品类型 | `isOverseas` | Boolean | ✅ | `false`=境内，`true`=境外 |
| 方案描述 | `description` | String | ❌ | 最长500字符 |
| 排序 | `sortOrder` | Integer | ❌ | `>= 0` |

### 保险段配置（segments 数组）

| 字段 | API字段名 | 类型 | 必填 | 说明 |
|------|----------|------|------|------|
| 段名称 | `segmentName` | String | ❌ | 如"低风险段""高风险段" |
| **保险产品** | `productId` | **Long** | ✅ | 选择哪个保险产品（下拉选） |
| **保险计划** | `planId` | **Long** | ✅ | 选择该产品下的哪个计划（联动下拉） |
| 起始天 | `dayOffsetStart` | Integer | ✅ | **从1开始**，1=出发当天 |
| 结束天 | `dayOffsetEnd` | Integer | ✅ | **必须 >= 起始天**，如6日行程最后一段填6 |
| 排序 | `sortOrder` | Integer | ❌ | `>= 0`，段内排序 |

### ⚠️ 重点：dayOffsetEnd 不要传 -1

之前代码中 `-1` 表示"到行程最后一天"，但前端编辑时请直接填实际天数。例如：

```
6日行程保险方案：
  第1段：起始天=1，结束天=3（覆盖第1-3天）
  第2段：起始天=4，结束天=6（覆盖第4-6天）← 填6，不要填-1
```

### ⚠️ 重点：productId 和 planId 的联动

1. 用户选择"保险产品"下拉时 → 调用 `GET /admin/insurance/products/{productId}/plans` 获取该产品的计划列表
2. 用户再从计划列表中选择"保险计划"
3. 保存时传 `productId` + `planId`（都是 Long 类型的 ID，不是名称字符串）

**常见错误**：把 productName 当 productId 传了，或者没有联动清空 planId

---

## 三、接口清单

### 3.1 保险产品（只读）

#### 产品列表
```
GET /admin/insurance/products?page=1&pageSize=20&isOverseas=false
```
响应：
```json
{
  "code": 200,
  "data": {
    "list": [
      {
        "productId": 2026279733947334657,
        "productName": "保游未来星研学旅行保险",
        "companyName": "中国人寿",
        "isOverseas": false,
        "minDays": 1,
        "maxDays": 365,
        "status": "ON_SALE"
      }
    ],
    "total": 10, "page": 1, "pageSize": 20
  }
}
```

#### 产品计划及费率（联动下拉用）
```
GET /admin/insurance/products/{productId}/plans
```
响应：
```json
{
  "code": 200,
  "data": [
    {
      "planId": 2026295680158437378,
      "planName": "20万计划",
      "securityList": "<table>...</table>",
      "rates": [
        { "rateId": 1, "dayRange": "1", "premium": 5.00 },
        { "rateId": 2, "dayRange": "2-3", "premium": 8.00 },
        { "rateId": 3, "dayRange": "4-7", "premium": 15.00 }
      ]
    },
    {
      "planId": 2026295681706135562,
      "planName": "30万计划",
      "rates": [...]
    }
  ]
}
```

#### 同步产品数据（手动触发）
```
POST /admin/insurance/sync-products
```

### 3.2 保险方案 CRUD

#### 方案列表（管理页面用）
```
GET /admin/insurance/scheme/list-all
```
响应：
```json
{
  "code": 200,
  "data": [
    {
      "schemeId": 100001,
      "name": "6日行程保险方案",
      "description": "6天行程全覆盖：第1-3天低风险+第4-6天高风险",
      "isOverseas": false,
      "sortOrder": 15,
      "totalDays": 6,
      "status": "ACTIVE",
      "segmentCount": 2,
      "segments": [
        {
          "segmentId": 200011,
          "segmentName": "低风险段",
          "dayOffsetStart": 1,
          "dayOffsetEnd": 3,
          "productId": 2026279733947334657,
          "productName": "保游未来星研学旅行保险",
          "planId": 2026295680158437378,
          "planName": "20万计划",
          "sortOrder": 0
        },
        {
          "segmentId": 200012,
          "segmentName": "高风险段",
          "dayOffsetStart": 4,
          "dayOffsetEnd": 6,
          "productId": 2026279733947334657,
          "productName": "保游未来星研学旅行保险",
          "planId": 2026295681706135562,
          "planName": "30万计划",
          "sortOrder": 1
        }
      ]
    }
  ]
}
```

#### 活跃方案列表（下拉选择用，如订单页选方案）
```
GET /admin/insurance/scheme/list
```
仅返回 `status=ACTIVE` 的方案。

#### 方案详情
```
GET /admin/insurance/scheme/{schemeId}
```

#### 创建方案
```
POST /admin/insurance/scheme
Content-Type: application/json

{
  "name": "6日行程保险方案",
  "description": "6天行程全覆盖：第1-3天低风险+第4-6天高风险",
  "isOverseas": false,
  "sortOrder": 15,
  "segments": [
    {
      "segmentName": "低风险段",
      "dayOffsetStart": 1,
      "dayOffsetEnd": 3,
      "productId": 2026279733947334657,
      "planId": 2026295680158437378,
      "sortOrder": 0
    },
    {
      "segmentName": "高风险段",
      "dayOffsetStart": 4,
      "dayOffsetEnd": 6,
      "productId": 2026279733947334657,
      "planId": 2026295681706135562,
      "sortOrder": 1
    }
  ]
}
```

#### 更新方案（全量替换 segments）
```
PUT /admin/insurance/scheme/{schemeId}
```
请求体同创建。**注意：segments 是全量替换，不是增量。前端保存时要把所有段都传回去。**

#### 切换启用/停用
```
PUT /admin/insurance/scheme/{schemeId}/toggle-status
```

#### 删除方案
```
DELETE /admin/insurance/scheme/{schemeId}
```

### 3.3 保险订单

#### 订单列表
```
GET /admin/insurance/orders?page=1&pageSize=20&orderId=xxx&status=INSURED
```
status 可选值：`PENDING`(待出单) / `INSURED`(已承保) / `CANCELLED`(已退保) / `FAILED`(投保失败)

#### 订单详情（含被保人列表）
```
GET /admin/insurance/orders/{insuranceOrderId}
```

#### 按旅行订单查询保险单
```
GET /admin/insurance/orders/by-order/{orderId}
```

#### 查询订单保险保障全貌
```
GET /admin/insurance/coverage/{orderId}
```

### 3.4 投保操作

#### 保费试算
```
POST /admin/insurance/trial-price
{
  "planId": 2026295680158437378,
  "startDate": "2026-04-01",
  "endDate": "2026-04-06",
  "insuredCount": 5
}
```
响应：
```json
{
  "code": 200,
  "data": {
    "totalPremium": 75.00,
    "unitPremium": 15.00,
    "tripDays": 6,
    "insuredCount": 5
  }
}
```

#### 按方案自动投保（推荐）
```
POST /admin/insurance/purchase-by-scheme
{
  "orderId": 123456,
  "schemeId": 100001
}
```

#### 预览方案应用效果（投保前先看）
```
POST /admin/insurance/scheme/preview-apply
{
  "schemeId": 100001,
  "orderId": 123456
}
```
响应包含每段的实际日期、预估保费、是否跳过及原因。

#### 手动投保（指定计划）
```
POST /admin/insurance/purchase
{
  "orderId": 123456,
  "planId": 2026295680158437378,
  "entityCode": "hh_nmg"
}
```
传了 orderId 会自动从订单获取出行人和日期，不需要再传 insuredPersons。

#### 退保
```
POST /admin/insurance/cancel/{insuranceOrderId}
```

### 3.5 其他

#### 账户余额
```
GET /admin/insurance/balance
```

#### 在线充值
```
POST /admin/insurance/recharge
{ "money": 1000, "payType": 33 }
```
payType: 11=支付宝扫码, 33=微信扫码

#### 下载保单PDF
```
GET /admin/insurance/policy/{insuranceOrderId}/download
```
返回 Base64 编码的 PDF 字符串。

---

## 四、页面交互建议

### 方案编辑页

```
┌─────────────────────────────────────────────┐
│ 方案名称 *  [____________]                    │
│ 产品类型 *  ○ 境内  ○ 境外                     │
│ 方案描述    [________________________]        │
│ 排序        [0] ─ +                          │
│                                              │
│ 保险段配置                    [+ 添加保险段]    │
│ ┌──────────────────────────────────────┐     │
│ │ 第1段                        ↑ ↓ 移除 │     │
│ │ 段名称  [低风险段]                      │     │
│ │ 保险产品* [保游未来星研学旅行保险 ▼]      │ ← 下拉，数据来自 GET /products │
│ │ 保险计划* [20万计划 ▼]                  │ ← 联动下拉，数据来自 GET /products/{id}/plans │
│ │ 起始天*  [1] ─ +   结束天* [3] ─ +     │     │
│ │ 排序    [0] ─ +                        │     │
│ └──────────────────────────────────────┘     │
│ ┌──────────────────────────────────────┐     │
│ │ 第2段                        ↑ ↓ 移除 │     │
│ │ 段名称  [高风险段]                      │     │
│ │ 保险产品* [保游未来星研学旅行保险 ▼]      │     │
│ │ 保险计划* [30万计划 ▼]                  │     │
│ │ 起始天*  [4] ─ +   结束天* [6] ─ +     │     │
│ │ 排序    [1] ─ +                        │     │
│ └──────────────────────────────────────┘     │
│                          [取消]  [保存]       │
└─────────────────────────────────────────────┘
```

### 交互要点

1. **保险产品下拉**：页面加载时调 `GET /admin/insurance/products?pageSize=100` 一次性加载
2. **保险计划联动**：选择产品后调 `GET /admin/insurance/products/{productId}/plans`，清空已选的计划
3. **编辑回显**：从 `GET /admin/insurance/scheme/{id}` 拿到 segments，每段的 `productId` 和 `planId` 用于回显下拉选中状态，`productName` 和 `planName` 用于显示文字
4. **起始天/结束天**：`<el-input-number :min="1">`，结束天 `:min` 动态绑定为该段起始天
5. **保存提交**：segments 全量提交，每段必须包含 `productId`(Long) 和 `planId`(Long)
