# 弹窗引导页配置化管理设计文档 > 日期: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` 统一包装 - 迁移:通过 `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 | --- **设计状态**:✅ 已确认,可进入实现阶段