# 邀请卡自动弹出开关 + 移动端悬浮按钮配置 **日期**: 2026-06-23 **项目**: AI 客服系统 - VOLT/HESU **目标**: 为嵌入式组件(widget.js)新增两个 `data-*` 配置开关——控制「邀请卡是否自动弹出」、以及「是否使用移动端悬浮按钮样式」,并让移动端按钮采用「图标在上、文字在下」的竖排样式(D2)。 --- ## 一、设计概述 ### 1.1 项目背景 当前嵌入式组件 [static/widget.js](../../../static/widget.js) 有两处行为是写死、无法按部署点配置的: 1. **邀请卡固定 1.8s 后自动弹出**:[widget.js:306-316](../../../static/widget.js#L306-L316) 的 `startInviteTimer()` 无条件启动,所有嵌入站点都会弹。部分站点不希望主动打扰用户,但目前无法关闭。 2. **悬浮按钮只有桌面款一种样式**:[widget.js:233-244](../../../static/widget.js#L233-L244) 的 `toggle-btn` 是一个「图标在左、文字在右」的宽药丸。在移动端窄屏上偏宽、略挡内容。 本次复用上一轮「联系方式动态配置」已建立的 `data-*` 配置范式(见 [2026-06-18-dynamic-contact-config-design.md](./2026-06-18-dynamic-contact-config-design.md)),把上述两处行为也变成可配置。 ### 1.2 设计目标 - 接入方可通过 `data-auto-invite` 控制邀请卡是否自动弹出:默认弹(保持现状),设 `false` 则不弹。 - 接入方可通过 `data-mobile` 切换悬浮按钮到移动端竖排样式(D2:圆钮在上、文字外置在下)。 - **完全向后兼容**:不传任何新属性时,行为与现在线上完全一致。 - 复用已有 `data-*` 机制,不引入新的配置范式。 ### 1.3 非目标(Out of Scope) - `data-mobile` **只影响悬浮按钮样式**,不改聊天面板、不改邀请卡。聊天面板已有自己的 `@media(max-width:450px)` 响应式([widget.js:152](../../../static/widget.js#L152)),各管各的,本次不统一。 - **不做自动检测**:移动端判定采用纯手动标识(方案 A),不引入 UA 嗅探、不读 `window.innerWidth`。接入方按部署点自行打标。 - **独立聊天页(chat.html / chat.js)不在本次范围**:独立页没有邀请卡逻辑,悬浮按钮也是 widget 独有的概念。本次只改 widget.js。 - 后端、admin、配置文件均不动。 --- ## 二、配置约定(契约) ### 2.1 属性定义 | 属性 | 含义 | 取值 | 默认值(未提供时) | |------|------|------|--------------------| | `data-auto-invite` | 邀请卡是否在 1.8s 后自动弹出 | `"false"` 关闭;其它/缺省为开启 | 开启(保持现状) | | `data-mobile` | 是否使用移动端竖排悬浮按钮(D2) | `"true"` 启用;其它/缺省为桌面款 | `false`(桌面款) | ### 2.2 读取语义 沿用 widget.js 现有的字符串判定写法(与 [widget.js:11](../../../static/widget.js#L11) 的 `data-auto-open` 一致): ```js var autoInvite = script.getAttribute("data-auto-invite") !== "false"; // 默认 true var isMobile = script.getAttribute("data-mobile") === "true"; // 默认 false ``` - `data-auto-invite` 采用**负向逻辑**(设 `false` 关闭),是为了**保持现有默认行为不变**——现在线上就是会弹,缺省必须仍是「弹」。这与 `data-auto-open`(设 `true` 才开)方向相反,但两者默认状态不同,不冲突。 - 属性缺失、空字符串、或非 `"false"`/`"true"` 的任意值:`autoInvite` 视为 `true`、`isMobile` 视为 `false`,即回退默认。 ### 2.3 接入示例 ```html ``` 两个开关互相独立,可任意组合。 --- ## 三、改动详情 全部改动集中在 [static/widget.js](../../../static/widget.js) 与测试页 [static/widget-test.html](../../../static/widget-test.html)。 ### 3.1 `static/widget.js` — 读取两个新属性 在 [widget.js:8-14](../../../static/widget.js#L8-L14) 现有 `getAttribute` 调用旁,新增两行: ```js var autoInvite = script.getAttribute("data-auto-invite") !== "false"; var isMobile = script.getAttribute("data-mobile") === "true"; ``` ### 3.2 `static/widget.js` — 邀请卡自动弹出开关 在 [widget.js:306](../../../static/widget.js#L306) 的 `startInviteTimer()` 函数**最开头**加一句短路: ```js function startInviteTimer() { if (!autoInvite) return; // ← 新增:配置关闭则不启动计时器 if (checkInviteStorage()) { return; // 已被永久关闭 } inviteTimer = window.setTimeout(function () { ... }, 1800); } ``` 效果:`data-auto-invite="false"` 时计时器根本不启动,邀请卡永不自动弹出。点悬浮按钮仍照常打开聊天面板;邀请卡内部的 "Start Chat" / "No Thanks" 等逻辑因卡片不出现而自然不触发,无需额外改动。 ### 3.3 `static/widget.js` — 移动端悬浮按钮(D2) 悬浮按钮当前在 [widget.js:233-244](../../../static/widget.js#L233-L244) 一处生成,`toggleBtn.innerHTML` 为桌面款模板。改动为**按 `isMobile` 分两套模板**,点击事件不变。 **实现路径选择(见第四节):采用「独立移动端 DOM 模板」**,桌面款模板逐字不动搬入 `else` 分支。 **DOM 结构(D2,竖排:圆钮在上、文字在外在下)**: ```js var toggleBtn = document.createElement("button"); toggleBtn.className = "toggle-btn" + (isMobile ? " mobile" : ""); if (isMobile) { toggleBtn.innerHTML = `