From 7d5a221cdce11ff64d7ac9ead77ee3f71d412b08 Mon Sep 17 00:00:00 2001 From: API Changelog Bot Date: Sat, 18 Apr 2026 13:25:57 +0800 Subject: [PATCH] =?UTF-8?q?=E5=B0=8F=E7=A8=8B=E5=BA=8F=E9=A6=96=E9=A1=B5?= =?UTF-8?q?=E7=AC=AC=E4=BA=8C=E5=B1=8F=E9=85=8D=E7=BD=AE=20-=20=E5=93=81?= =?UTF-8?q?=E7=89=8C=E6=95=85=E4=BA=8B+=E4=B8=BB=E9=A2=98=E5=8D=A1?= =?UTF-8?q?=E7=89=87=E6=8E=A5=E5=8F=A3(PR=20#811)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2026-04/2026-04-18_home-screen2-config.md | 233 ++++++++++++++++++ 1 file changed, 233 insertions(+) create mode 100644 changelogs/2026-04/2026-04-18_home-screen2-config.md diff --git a/changelogs/2026-04/2026-04-18_home-screen2-config.md b/changelogs/2026-04/2026-04-18_home-screen2-config.md new file mode 100644 index 0000000..f5d20c4 --- /dev/null +++ b/changelogs/2026-04/2026-04-18_home-screen2-config.md @@ -0,0 +1,233 @@ +# 小程序首页第二屏配置 - 品牌故事 + 主题卡片入口 + +- **日期**: 2026-04-18 +- **PR**: [#811](https://git.1814.love:8443/wx/HL/pulls/811) (Closes #810) +- **服务**: hl-user-service (8081) + hl-mp-service (8085) +- **状态**: 已合并到 dev,待测试环境部署 + +--- + +## 背景 + +小程序首页分两屏: +- 第一屏:Hero 大图/轮播(现有 `sys_banner`,不变) +- **第二屏(本次新增)**:品牌故事文案 + 大主图 + 2 张主题卡片入口 + +原型节点:`用户端页面(5).pen / M57M7 首页 / Screen2 plwfj` + +--- + +## 1. 管理端接口(Admin) + +### 1.1 查询品牌故事(含草稿) + +``` +GET /admin/home-config/brand-story +Authorization: Bearer +``` + +**响应** `Result`: +```json +{ + "code": 200, + "data": { + "id": "2045369223068311553", + "title": "每一条线路,我们都亲自走过。", + "subtitle": null, + "description": "整个呼伦贝尔...", + "coverUrl": "https://cdn.example.com/home/team-photo.jpg", + "coverMaskType": "WHITE_TOP", + "version": 1, + "status": "ACTIVE", + "createTime": "2026-04-18 13:09:59", + "updateTime": "2026-04-18 13:09:59", + "createdBy": "1002", + "updatedBy": "1002", + "createdByName": null, + "updatedByName": null + }, + "success": true +} +``` + +**字段说明**: + +| 字段 | 类型 | 说明 | +|-----|------|-----| +| id | String | 主键(雪花ID,JS 精度防护用 String) | +| title | String | 主标题,支持 \n 换行 | +| subtitle | String? | 副标题(预留,当前可为 null) | +| description | String | 描述文案 | +| coverUrl | String? | 主图 URL(草稿可空;发布时必填) | +| coverMaskType | String | 蒙层类型:`WHITE_TOP`=上白下透 / `NONE`=无 | +| version | Integer | 版本号,publish 每次 +1 | +| status | String | `DRAFT`=草稿 / `ACTIVE`=已发布 / `ARCHIVED`=已归档 | +| createdBy/updatedBy | String | 操作人 adminId(String 防 JS 精度) | + +**语义**: +- 返回当前**最新**一条非删除记录(按 id 降序,包含 DRAFT) +- 管理端打开页面即可编辑当前状态,不管是草稿还是已发布 +- 无数据时返回 `data: null`(首次部署时可能出现) + +--- + +### 1.2 保存或发布品牌故事 + +``` +PUT /admin/home-config/brand-story +Authorization: Bearer +Content-Type: application/json +``` + +**请求体** `AdminBrandStorySaveReqVO`: +```json +{ + "id": null, + "title": "每一条线路,我们都亲自走过。", + "subtitle": null, + "description": "整个呼伦贝尔...", + "coverUrl": "https://cdn.example.com/home/team-photo.jpg", + "coverMaskType": "WHITE_TOP", + "publish": true +} +``` + +**字段校验**: + +| 字段 | 校验 | 说明 | +|-----|-----|-----| +| id | 选填 | 有值=更新指定记录,null=新建 | +| title | @NotBlank, @Length(max=100) | 必填 | +| subtitle | @Length(max=200) | 选填 | +| description | @NotBlank, @Length(max=1000) | 必填 | +| coverUrl | @NotBlank, @URL(protocol="https") | 必填(只接受 https 链接) | +| coverMaskType | @NotBlank | 必填,值同上 | +| publish | Boolean | `true`=直接发布(归档旧 ACTIVE,新记录设为 ACTIVE);`false`=保存草稿 | + +**行为**: +- `publish=false`:`saveDraft` 插入/更新 `status=DRAFT` 记录 +- `publish=true`:`publish` 先 `archiveAllActive()` 把当前 ACTIVE 改 ARCHIVED,再插入 ACTIVE 新记录;`@Lock4j` 防并发;自动 evict Redis 缓存 + +**响应** `Result`:`{"code":200, "message":"成功", "data":null, "success":true}` + +**幂等保护**:`@Idempotent(timeout=5, keyPrefix="admin-brand-story")`,5 秒内重复请求返回缓存结果。 + +**审计**:`@OperationLog("发布小程序首页第二屏品牌故事")`,操作日志自动写入。 + +--- + +## 2. 小程序端接口(MP) + +### 2.1 获取首页第二屏数据(聚合) + +``` +GET /mp/home-config/screen2 +``` + +**不需要 token**(Gateway 设为 `X-Auth-Level=SKIP`,匿名可访问)。 + +**响应** `Result`: +```json +{ + "code": 200, + "data": { + "brandStory": { + "title": "每一条线路,我们都亲自走过。", + "subtitle": null, + "description": "整个呼伦贝尔...", + "coverUrl": "https://cdn.example.com/home/team-photo.jpg", + "coverMaskType": "WHITE_TOP" + }, + "topics": [ + { + "id": "XXXX", + "title": "季节之旅", + "subtitle": "夏/秋/冬三季体验", + "coverUrl": "https://cdn.example.com/topics/season.jpg", + "linkType": "PRODUCT_LIST", + "linkTarget": "theme=SEASON" + } + ] + }, + "success": true +} +``` + +**字段说明**: + +| 字段 | 类型 | 说明 | +|-----|------|-----| +| brandStory | Object? | 品牌故事块,无数据时为 null | +| brandStory.title | String | 主标题,支持 \n 换行 | +| brandStory.subtitle | String? | 副标题 | +| brandStory.description | String | 描述 | +| brandStory.coverUrl | String? | 主图 URL | +| brandStory.coverMaskType | String | 蒙层类型 `WHITE_TOP`/`NONE` | +| topics | Array | 主题卡片列表(按 sort_order 升序) | +| topics[].id | String | 主题 ID | +| topics[].title | String | 卡片标题 | +| topics[].subtitle | String? | 卡片副标题 | +| topics[].coverUrl | String | 卡片封面图 | +| topics[].linkType | String | 跳转类型,如 `PRODUCT_LIST` / `TOPIC_DETAIL` | +| topics[].linkTarget | String | 跳转目标(路径/ID/参数) | + +**关键**:响应**不包含** `fromFallback` 字段(内部 Feign 协议字段,VO 层已过滤)。 + +--- + +## 3. 缓存策略 + +| 项 | 值 | +|---|---| +| Key | `home:screen2:mp:{env}`(env=dev/test/prod) | +| TTL | 30 分钟(1800s) | +| 空值 TTL | 60 秒(防穿透) | +| Evict 触发 | brand PUT / topic CUD 任一发生 | + +**前端渲染建议**:小程序可本地缓存 5 分钟减少重复请求;发现数据变化再 pull。 + +--- + +## 4. 错误处理 + +所有错误走项目统一错误码,HTTP **始终 200**,业务码在 `code` 字段: + +| code | 含义 | 常见场景 | +|------|------|---------| +| 200 | 成功 | — | +| 400 | 参数校验失败 | title 缺失 / coverUrl 非 https | +| 401 | 未登录 | admin token 过期 | +| 403 | 无权限 | 非 admin 访问 `/admin/**` | +| 409 | 状态冲突 | 禁止 ACTIVE → DRAFT 回退 | +| 500 | 服务器错误 | DB/Redis 异常 | + +--- + +## 5. 管理端前端需要做的 + +1. **页面**:首页配置 / 第二屏 +2. **表单字段**:title / subtitle / description / coverUrl(走素材库 system 分类上传) / coverMaskType(下拉 WHITE_TOP/NONE) +3. **操作按钮**:`保存草稿` (publish=false) / `立即发布` (publish=true,需二次确认弹窗) +4. **首次打开**:调 GET 接口,有数据则回填表单,无数据则空表单 +5. **发布后提示**:调 GET 验证 status=ACTIVE,提示"发布成功,小程序缓存最多 30 分钟后生效" + +--- + +## 6. 小程序前端需要做的 + +1. **首页渲染**:调 `GET /mp/home-config/screen2` +2. **数据位置**: + - `brandStory.coverUrl` 作为第二屏大图背景(375×497 区) + - `brandStory.title` + `brandStory.description` 浮于大图之上(位置 x=20, y=50 参考原型) + - `topics` 渲染为 2 个横向卡片(位置 x=20, y=520, 335×220 参考原型) +3. **空态**:`brandStory=null` 时第二屏隐藏;`topics=[]` 时卡片区隐藏 +4. **shimmer/skeleton**:请求中显示骨架屏 + +--- + +## 7. 变更影响 + +- **现有 `/admin/banner/*` 接口**:**不变**(第一屏轮播独立) +- **现有 `/mp/banner/active` 接口**:**不变** +- **现有 `/admin/topic/*` 接口**:**不变**(本期复用 sys_topic,不动表结构) +- **TopicService 新增方法**:仅内部调用,不暴露接口