# 小程序首页第二屏 v2 - 去蒙层 + 内嵌卡片 + 类型校验

- **日期**: 2026-04-18
- **PR**: [#823](https://git.1814.love:8443/wx/HL/pulls/823) (Closes #822)
- **状态**: 已合并 + 测试环境 DDL/服务全就绪 + E2E 通过

---

## 变更概览

1. 删 `coverMaskType` 字段（全链路）
2. 品牌故事内嵌 2 张主题卡片（`cards` 数组，最多 2 张）
3. 修 PR #811 缓存失效 bug
4. 链接目标（产品/产品线）只允许 **CORE/GROUP** 类型，响应加 `linkTargetType`

---

## 1. Admin `PUT /admin/home-config/brand-story` 请求体变更

### 删除字段
- ~~`coverMaskType`~~

### 新增字段
- `cards`: `BrandCard[]`（最多 2 张）

```json
{
  "title": "每一条线路，我们都亲自走过。",
  "subtitle": null,
  "description": "整个呼伦贝尔...",
  "coverUrl": "https://cdn.1814.love/home/team-photo.jpg",
  "publish": true,
  "cards": [
    {
      "title": "季节之旅",
      "subtitle": "夏·秋·冬三季体验",
      "coverUrl": "https://cdn.1814.love/cards/season.jpg",
      "linkType": "PRODUCT_LINE",
      "linkTarget": "2045004175602868226"
    },
    {
      "title": "亲子研学",
      "subtitle": "边玩边学·深度体验",
      "coverUrl": "https://cdn.1814.love/cards/study.jpg",
      "linkType": "NONE",
      "linkTarget": ""
    }
  ]
}
```

**BrandCard 字段**:

| 字段 | 类型 | 必填 | 说明 |
|-----|------|------|-----|
| title | String | ✓ | 标题（如"季节之旅"） |
| subtitle | String | - | 副标题 |
| coverUrl | String | ✓ | 背景图 URL（必须 https） |
| linkType | String | ✓ | NONE/PRODUCT/PRODUCT_LINE/ARTICLE/URL/MINI_PAGE |
| linkTarget | String | - | 跳转目标（id 或 url） |

### 业务校验

- `cards` 最多 2 张（`@Size(max=2)`）
- **PRODUCT/PRODUCT_LINE 时 linkTarget 对应的产品/产品线类型必须是 `CORE` 或 `GROUP`**，其他类型返回：

```json
{"code":500, "message":"卡片只能关联核心或小蒙马类型的产品/产品线", "data":null, "success":false}
```

---

## 2. Admin/MP GET 响应新增

### 响应结构
- Admin `GET /admin/home-config/brand-story` 返回的 `data` 里，`coverMaskType` 字段**已移除**，新增 `cards`
- MP `GET /mp/home-config/screen2` 返回 `data.cards`（原 `data.topics` **移除**）

```json
{
  "code": 200,
  "data": {
    "brandStory": {
      "title": "每一条线路，我们都亲自走过。",
      "subtitle": null,
      "description": "...",
      "coverUrl": "..."
    },
    "cards": [
      {
        "title": "季节之旅",
        "subtitle": "夏·秋·冬三季体验",
        "coverUrl": "...",
        "linkType": "PRODUCT_LINE",
        "linkTarget": "2045004175602868226",
        "linkTargetType": "CORE"
      },
      ...
    ]
  },
  "success": true
}
```

**`linkTargetType`** 字段说明：
- `CORE` / `GROUP` / `null`
- 仅 PRODUCT/PRODUCT_LINE 类型时填充，其他返 null
- 批量回填（Feign 调 product-v2 一次性批查），避免 N+1

---

## 3. 轮播图 `/admin/banner` 变更

### 请求（保存）
- linkType=PRODUCT/PRODUCT_LINE 时后端校验 linkId 的 productType 必须 CORE/GROUP，否则返：
```
{"code":500, "message":"轮播图只能关联核心或小蒙马类型的产品/产品线", ...}
```

### 响应
- BannerVO 新增 `linkTargetType` 字段（CORE/GROUP/null）
- 列表查询也批量回填，前端展示"核心"/"小蒙马"标签不用二次查询

---

## 4. MP 缓存

- Key: `home:screen2:mp:{env}`
- TTL 30 分钟
- **修复**: PR #811 发布后不失效的 bug 已修（`HomeBrandStoryService.publish()` 现在真正调用 `evictAfterCommit()`）

---

## 5. 前端改造清单

### 管理端 (hl-ui)

1. **品牌故事表单**：
   - 删除"蒙层类型"下拉
   - 新增"主题卡片"section，固定 2 张子表单（每张：标题/副标题/背景图/跳转类型/跳转目标）
   - 跳转类型选择器选中 PRODUCT/PRODUCT_LINE 时，目标选择器**只显示 `productType=CORE` 或 `GROUP`** 的产品/产品线
   - 展示页可读取 `linkTargetType` 显示标签（"核心"/"小蒙马"）

2. **轮播图表单 (`/mp-config/home/banner`)**：
   - 选择器同上：只显示 CORE/GROUP
   - 列表页展示 `linkTargetType` 标签（当 linkType=PRODUCT/PRODUCT_LINE）

### 小程序端

- 首页第二屏渲染：
  - `data.brandStory` 是主文案块
  - `data.cards` 是 2 张卡片（如原来的 `data.topics` 结构，但字段略不同）
  - 卡片点击按 `linkType` + `linkTarget` 跳转

---

## 6. 产品/产品线选择器接口

前端选择器可用现有接口过滤 `productType`，或调新内部接口（如果暴露到管理端）：

- 管理端已有 `GET /admin/product-line/enabled`（列表接口）— 按 `productType` filter
- 如需求进一步精确，可联系后端加 `GET /admin/product-line/enabled?productTypes=CORE,GROUP`

---

## 7. 测试环境验证

- [x] DDL 已跑（sys_home_brand_story 无 cover_mask_type，有 cards JSON）
- [x] E2E: PUT cards → MP GET 立即可见（缓存 evict 生效）
- [x] E2E: cards linkType=PRODUCT_LINE linkTarget=假 id → 抛业务异常 "卡片只能关联核心或小蒙马类型的产品/产品线"
- [x] Admin GET 响应无 coverMaskType，MP GET 响应 `cards` 替代 `topics`

---

## 8. 关联 PR

- #811 首页第二屏 v1
- #813 Gateway 路由补丁
- #815 菜单 + 轮播图 PRODUCT_LINE 字典
- **#823 本 PR (v2)**
