|
|
@@ -0,0 +1,344 @@
|
|
|
+# 弹窗引导页配置化管理设计文档
|
|
|
+
|
|
|
+> 日期:2026-09-22
|
|
|
+> 状态:✅ 已确认 - 可进入实现阶段
|
|
|
+> 需求来源:用户确认弹窗引导页需实现配置化管理,支持管理员配置弹出条件与内容
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 一、背景与目标
|
|
|
+
|
|
|
+### 现状问题
|
|
|
+- `OnboardingGuide.vue` 的 PARENT_STEPS / CHILD_STEPS 为硬编码
|
|
|
+- 触发条件仅依赖 `_onboardingDone` 本地标记(首次登录仅弹一次)
|
|
|
+- 内容无法动态调整,无法支持 A/B 测试或按场景定制
|
|
|
+
|
|
|
+### 设计目标
|
|
|
+1. **配置化管理**:管理员可通过 Web 后台配置弹窗内容、触发条件、样式
|
|
|
+2. **条件驱动**:支持按角色、页面路径、行为触发、时间窗口等多种条件
|
|
|
+3. **用户消费追踪**:记录每个用户的消费状态,避免重复弹出
|
|
|
+4. **数据看板**:统计曝光、点击、完成率,支持效果分析
|
|
|
+5. **向后兼容**:保留旧 `_onboardingDone` 逻辑,新配置逐步替代
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 二、数据库设计
|
|
|
+
|
|
|
+### 2.1 表结构
|
|
|
+
|
|
|
+#### `guide_popup`(引导弹窗配置表)
|
|
|
+
|
|
|
+| 字段 | 类型 | 说明 |
|
|
|
+|------|------|------|
|
|
|
+| `id` | BIGINT PK | 主键 |
|
|
|
+| `slug` | VARCHAR(64) UNIQUE | 唯一标识,如 `parent_first_login` |
|
|
|
+| `title` | VARCHAR(128) | 弹窗标题(可空,仅副标题时为空) |
|
|
|
+| `description` | TEXT | 主描述文案(支持换行) |
|
|
|
+| `icon` | VARCHAR(256) | 图标:emoji 或图片 URL |
|
|
|
+| `icon_type` | TINYINT | 图标类型:0=emoji 1=图片 |
|
|
|
+| `theme_color` | VARCHAR(20) | 主题色,如 `#FF8C42` |
|
|
|
+| `btn_text` | VARCHAR(64) | 按钮文案 |
|
|
|
+| `btn_action` | VARCHAR(256) | 点击跳转路径(空=关闭) |
|
|
|
+| `status` | TINYINT | 0=禁用 1=启用 |
|
|
|
+| `sort_order` | INT | 同类型排序优先级 |
|
|
|
+| `created_at` | DATETIME | 创建时间 |
|
|
|
+| `updated_at` | DATETIME | 更新时间 |
|
|
|
+
|
|
|
+#### `guide_popup_step`(步骤表)
|
|
|
+
|
|
|
+| 字段 | 类型 | 说明 |
|
|
|
+|------|------|------|
|
|
|
+| `id` | BIGINT PK | 主键 |
|
|
|
+| `popup_id` | BIGINT FK | 关联 guide_popup.id |
|
|
|
+| `step_order` | INT | 步骤顺序 |
|
|
|
+| `title` | VARCHAR(128) | 步骤标题 |
|
|
|
+| `desc` | TEXT | 步骤描述 |
|
|
|
+| `icon` | VARCHAR(256) | 步骤图标 |
|
|
|
+| `icon_type` | TINYINT | 图标类型 |
|
|
|
+| `is_intro` | TINYINT | 是否为开场身份定义页(特殊样式) |
|
|
|
+| `btn_text` | VARCHAR(64) | 本步骤按钮文案 |
|
|
|
+| `btn_action` | VARCHAR(256) | 最后一步跳转链接 |
|
|
|
+| `created_at` | DATETIME | 创建时间 |
|
|
|
+
|
|
|
+#### `guide_popup_trigger`(触发条件表)
|
|
|
+
|
|
|
+| 字段 | 类型 | 说明 |
|
|
|
+|------|------|------|
|
|
|
+| `id` | BIGINT PK | 主键 |
|
|
|
+| `popup_id` | BIGINT FK | 关联 guide_popup.id |
|
|
|
+| `trigger_type` | VARCHAR(32) | `page_enter`/`task_complete`/`assessment_done`/`first_login`/`daily_active`/`role_change` |
|
|
|
+| `target_page` | VARCHAR(128) | 目标页面路径(trigger_type=page_enter 时使用) |
|
|
|
+| `target_action` | VARCHAR(128) | 触发行为标识(trigger_type=task_complete 时使用) |
|
|
|
+| `min_role` | VARCHAR(32) | 最低角色要求:parent/child/teacher |
|
|
|
+| `extra_config` | JSON | 扩展条件:`{"days":30,"streak":7}` |
|
|
|
+| `created_at` | DATETIME | 创建时间 |
|
|
|
+
|
|
|
+#### `guide_popup_log`(用户消费记录表)
|
|
|
+
|
|
|
+| 字段 | 类型 | 说明 |
|
|
|
+|------|------|------|
|
|
|
+| `id` | BIGINT PK | 主键 |
|
|
|
+| `popup_id` | BIGINT FK | 关联 guide_popup.id |
|
|
|
+| `user_id` | BIGINT | 用户 ID |
|
|
|
+| `member_id` | BIGINT NULL | family_members.id(可空) |
|
|
|
+| `role` | VARCHAR(32) | 弹出时所在角色 |
|
|
|
+| `consumed_at` | DATETIME | 消费时间 |
|
|
|
+| `button_clicked` | TINYINT | 是否点击了按钮 |
|
|
|
+
|
|
|
+### 2.2 索引设计
|
|
|
+- `guide_popup`: `INDEX idx_status_sort (status, sort_order)`
|
|
|
+- `guide_popup_step`: `INDEX idx_popup_order (popup_id, step_order)`
|
|
|
+- `guide_popup_log`: `INDEX idx_user_popup (user_id, popup_id)`, `INDEX idx_consume_time (consumed_at)`
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 三、后端 API 设计
|
|
|
+
|
|
|
+### 3.1 用户端接口
|
|
|
+
|
|
|
+#### `POST /api/guide/popup/list`
|
|
|
+**功能**:查询当前用户应展示的激活弹窗列表
|
|
|
+
|
|
|
+**请求**:无参数(从 JWT 自动解析 userId/role)
|
|
|
+
|
|
|
+**响应示例**:
|
|
|
+```json
|
|
|
+{
|
|
|
+ "code": 200,
|
|
|
+ "data": [
|
|
|
+ {
|
|
|
+ "id": 1,
|
|
|
+ "slug": "parent_first_login",
|
|
|
+ "title": "欢迎使用浠艾福",
|
|
|
+ "description": "身·智·富·行·心,全家健康一站式管理",
|
|
|
+ "icon": "🌏",
|
|
|
+ "iconType": 0,
|
|
|
+ "themeColor": "#FF8C42",
|
|
|
+ "btnText": "开始体验",
|
|
|
+ "btnAction": "/pages/assessment/plan",
|
|
|
+ "steps": [
|
|
|
+ { "stepOrder": 0, "icon": "🌏", "title": "身·主动健康", "desc": "精准营养推荐,从根源调理体质", "isIntro": true },
|
|
|
+ { "stepOrder": 1, "icon": "⚡", "title": "智·赋能未来", "desc": "专业心智测评,看清能力与短板", "isIntro": false }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+ ]
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+**业务逻辑**:
|
|
|
+1. 从 JWT 获取 userId + role
|
|
|
+2. 查询所有 status=1 的 guide_popup
|
|
|
+3. 按 trigger_type 匹配当前用户状态
|
|
|
+4. 排除 guide_popup_log 中已消费且 button_clicked=false 的记录
|
|
|
+5. 按 sort_order 排序返回
|
|
|
+
|
|
|
+#### `POST /api/guide/popup/trigger`
|
|
|
+**功能**:上报触发行为
|
|
|
+
|
|
|
+**请求体**:
|
|
|
+```json
|
|
|
+{ "action": "task_complete", "taskId": 123 }
|
|
|
+```
|
|
|
+
|
|
|
+**响应**:
|
|
|
+```json
|
|
|
+{ "code": 200, "data": { "shouldShow": true, "popups": [...] } }
|
|
|
+```
|
|
|
+
|
|
|
+#### `POST /api/guide/popup/consume`
|
|
|
+**功能**:消费弹窗(点击/关闭)
|
|
|
+
|
|
|
+**请求体**:
|
|
|
+```json
|
|
|
+{ "popupId": 1, "buttonClicked": true }
|
|
|
+```
|
|
|
+
|
|
|
+**响应**:
|
|
|
+```json
|
|
|
+{ "code": 200, "data": true }
|
|
|
+```
|
|
|
+
|
|
|
+### 3.2 管理端接口
|
|
|
+
|
|
|
+| 方法 | 路径 | 说明 |
|
|
|
+|------|------|------|
|
|
|
+| `POST` | `/api/admin/guide/popup/list` | 列表(分页/筛选 status/slug) |
|
|
|
+| `POST` | `/api/admin/guide/popup/create` | 创建(含触发条件+步骤) |
|
|
|
+| `POST` | `/api/admin/guide/popup/update` | 更新(全量覆盖步骤和条件) |
|
|
|
+| `POST` | `/api/admin/guide/popup/status` | 启用/禁用(toggle) |
|
|
|
+| `POST` | `/api/admin/guide/popup/delete` | 软删除 |
|
|
|
+| `POST` | `/api/admin/guide/popup/stats` | 数据看板 |
|
|
|
+
|
|
|
+**stats 响应示例**:
|
|
|
+```json
|
|
|
+{
|
|
|
+ "code": 200,
|
|
|
+ "data": {
|
|
|
+ "totalViews": 1234,
|
|
|
+ "totalClicks": 567,
|
|
|
+ "completionRate": "45.9%",
|
|
|
+ "avgDuration": "3.2s",
|
|
|
+ "trendByHour": [
|
|
|
+ { "hour": "08:00", "views": 120, "clicks": 45 },
|
|
|
+ { "hour": "09:00", "views": 200, "clicks": 88 }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 四、前端组件设计
|
|
|
+
|
|
|
+### 4.1 新增组件:`ConfigurableGuide.vue`
|
|
|
+
|
|
|
+**位置**:`components/ConfigurableGuide.vue`
|
|
|
+
|
|
|
+**Props**:
|
|
|
+```js
|
|
|
+props: {
|
|
|
+ popup: {
|
|
|
+ type: Object,
|
|
|
+ required: true,
|
|
|
+ // { id, slug, title, description, icon, iconType, themeColor, btnText, btnAction, steps }
|
|
|
+ },
|
|
|
+ mode: {
|
|
|
+ type: String,
|
|
|
+ default: 'modal' // 'modal' | 'sheet' | 'banner'
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+**功能**:
|
|
|
+- 根据 `steps` 数组决定渲染模式(单步 vs 多步向导)
|
|
|
+- `icon_type=0` 渲染 emoji,`=1` 渲染 image 组件
|
|
|
+- `theme_color` 动态绑定样式
|
|
|
+- 按钮点击 → 调用 `/api/guide/popup/consume` + 跳转
|
|
|
+- 关闭 → 仅调用 consume(buttonClicked=false)
|
|
|
+- 步骤导航支持左右滑动(多步场景)
|
|
|
+
|
|
|
+**生命周期**:
|
|
|
+- `mounted`:调用 `/api/guide/popup/list` 拉取激活配置
|
|
|
+- 弹窗展示时上报曝光(可选,通过 `/trigger` 接口)
|
|
|
+
|
|
|
+### 4.2 页面接入
|
|
|
+
|
|
|
+#### `pages/index-home/index.vue`
|
|
|
+- 替换 `_maybeShowOnboardingGuide()` 中的硬编码逻辑
|
|
|
+- 改为调用 `GET /api/guide/popup/list`(实际为 POST 无参)
|
|
|
+- 根据返回的 `slug` 决定渲染哪个弹窗
|
|
|
+- 保留 `_onboardingDone` 兼容逻辑:若已消费则不展示新弹窗
|
|
|
+
|
|
|
+#### `pages/home-pages/child-index.vue`
|
|
|
+- 类似接入,支持子端专属配置
|
|
|
+
|
|
|
+### 4.3 Web 管理端页面
|
|
|
+
|
|
|
+#### `views/admin/GuidePopupConfig.vue`
|
|
|
+- 列表页:表格展示所有配置(启用/禁用/预览/编辑/删除)
|
|
|
+- 编辑抽屉:表单输入 title/description/icon 等字段
|
|
|
+- 步骤管理:可动态增删步骤
|
|
|
+- 触发条件:选择 trigger_type + 填写目标值
|
|
|
+
|
|
|
+#### `views/admin/GuidePopupStats.vue`
|
|
|
+- 统计卡片:总曝光/总点击/完成率/平均停留时长
|
|
|
+- 趋势图:按小时/天聚合的曝光+点击曲线
|
|
|
+- 分弹窗对比:各 slug 的独立统计
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 五、实现顺序
|
|
|
+
|
|
|
+### Phase 1: 后端基础(P0)
|
|
|
+```
|
|
|
+1. DatabaseInitializer 新增 4 张表迁移
|
|
|
+2. 实体类:GuidePopup, GuidePopupStep, GuidePopupTrigger, GuidePopupLog
|
|
|
+3. Mapper 接口 + XML(如需)
|
|
|
+4. Service: GuidePopupService(CRUD + 匹配逻辑)
|
|
|
+5. Controller: GuidePopupController(用户端 3 接口)
|
|
|
+6. Controller: AdminGuidePopupController(管理端 6 接口)
|
|
|
+```
|
|
|
+
|
|
|
+### Phase 2: 前端组件(P1)
|
|
|
+```
|
|
|
+1. components/ConfigurableGuide.vue
|
|
|
+2. pages/index-home/index.vue 接入
|
|
|
+3. pages/home-pages/child-index.vue 接入
|
|
|
+```
|
|
|
+
|
|
|
+### Phase 3: 管理端(P1)
|
|
|
+```
|
|
|
+1. views/admin/GuidePopupConfig.vue
|
|
|
+2. views/admin/GuidePopupStats.vue
|
|
|
+3. 路由注册(cfc-web/src/router/)
|
|
|
+4. API 封装(cfc-web/src/api/)
|
|
|
+```
|
|
|
+
|
|
|
+### Phase 4: 收尾(P2)
|
|
|
+```
|
|
|
+1. DatabaseInitializer 写入种子数据(现有 PARENT_STEPS 作为 seed)
|
|
|
+2. 兼容验证:旧 OnboardingGuide.vue 逻辑保留
|
|
|
+3. 文档更新(API_REFERENCE.md)
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 六、约束与约定
|
|
|
+
|
|
|
+### 后端
|
|
|
+- 遵循项目规范:统一 `@PostMapping`,禁止 `@GetMapping/@PutMapping/@DeleteMapping`
|
|
|
+- 实体:`@TableName` + `@TableId(type = IdType.AUTO)`
|
|
|
+- DI:`@Resource`,字段名匹配 Bean Name
|
|
|
+- 响应:`Result<T>` 统一包装
|
|
|
+- 迁移:通过 `DatabaseInitializer.runMigrations()` 幂等执行
|
|
|
+
|
|
|
+### 前端(小程序)
|
|
|
+- Vue 2 Options API,禁止 Composition API
|
|
|
+- 禁止可选链 `?.`,用 `&&` 替代
|
|
|
+- 禁止 `:key` 表达式,用方法调用
|
|
|
+- 时间格式统一 ISO 8601,前端格式化
|
|
|
+- API 调用去重:遵循 AGENTS.md 规则
|
|
|
+
|
|
|
+### 前端(Web 管理端)
|
|
|
+- Vue 2 + Element UI
|
|
|
+- 页面路由注册到 `src/router/index.js`
|
|
|
+- API 封装到 `src/api/` 目录
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 七、验收标准
|
|
|
+
|
|
|
+1. **功能验收**:
|
|
|
+ - 管理端可创建/编辑/启用/禁用弹窗配置
|
|
|
+ - 可配置多步骤向导
|
|
|
+ - 可设置触发条件(页面/行为/角色)
|
|
|
+ - 用户端按条件展示弹窗,消费后不再重复
|
|
|
+
|
|
|
+2. **数据验收**:
|
|
|
+ - 统计接口返回正确曝光/点击/完成率
|
|
|
+ - 分时段趋势数据准确
|
|
|
+
|
|
|
+3. **兼容性验收**:
|
|
|
+ - 旧 OnboardingGuide.vue 逻辑不受影响
|
|
|
+ - 旧 `_onboardingDone` 标记仍有效
|
|
|
+ - 种子数据写入后小程序首次加载正常
|
|
|
+
|
|
|
+4. **质量验收**:
|
|
|
+ - 后端 `mvn compile` 无错误
|
|
|
+ - 前端 `node scripts/audit-duplicate-api-calls.js` 无新增 ERROR
|
|
|
+ - 所有新增代码通过 Java 检查清单(无类型错误、无异常吞没)
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 八、风险与应对
|
|
|
+
|
|
|
+| 风险 | 影响 | 应对 |
|
|
|
+|------|------|------|
|
|
|
+| 触发条件逻辑复杂导致匹配效率低 | 性能问题 | 缓存激活配置到 Redis,本地匹配 |
|
|
|
+| 管理端页面交互复杂 | 开发周期长 | 简化 UI,先实现核心功能 |
|
|
|
+| 历史数据迁移 | 用户消费记录丢失 | 旧 `_onboardingDone` 逻辑保留,新旧并存过渡期 |
|
|
|
+| 多端一致性问题 | 用户体验不一致 | 先小程序后 Web,统一设计 Token |
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+**设计状态**:✅ 已确认,可进入实现阶段
|